I have custom hardware that consists of an MK22FN512VMP12 processor connected to a sensor through a GPIO for power and four lines for SPI.
I am trying to trigger DMA SPI transfers through a PIT interrupt. The problem I'm seeing is that the DMA transfer happens only on the first PIT interrupt. For subsequent interrupts, nothing happens. At this point, I don't care if the sensor is seeing it or not, I just want to get the DMA transfers working. After I get transmit working, I want to get receive working.
I have the DMA error interrupt handler declared, but it is not currently used. When I had it enabled, I never got any interrupts.
I've been following this example:
Here's my code. I've attached the file also.
/*
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* o Neither the name of Freescale Semiconductor, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdint.h>
#include <stdbool.h>
#include "MK22F51212.h"
/*
* Macros
*/
#define SPI_PUSHR_CTAS_CTAR0 (0x0)
/*
*
*/
#define EnableInterrupts __asm(" CPSIE i");
#define DisableInterrupts __asm(" CPSID i");
/*
* Data array - WHOAMI register from sensor
*/
#define SENSOR_FRAME_READ_WHOAMI (0x40000091)
#define SENSOR_TRANSMIT_READ_WHOAMI_COUNT (8)
static uint32_t SENSOR_TRANSMIT_READ_WHOAMI[SENSOR_TRANSMIT_READ_WHOAMI_COUNT] =
{
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 24) & 0xFF),
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 16) & 0xFF),
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> & 0xFF),
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI ) & 0xFF),
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 24) & 0xFF),
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 16) & 0xFF),
SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> & 0xFF),
SPI_PUSHR_EOQ_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI ) & 0xFF),
};
uint32_t receive_buffer[128]; // Not currently used
/*
* Transmit
*/
static void spi_transmit_receive(uint32_t* buffer, int count)
{
SPI0->MCR = SPI_MCR_PCSIS(0x01) | SPI_MCR_MSTR_MASK |
SPI_MCR_DIS_RXF_MASK | SPI_MCR_DIS_TXF_MASK |
SPI_MCR_CLR_RXF_MASK | SPI_MCR_CLR_TXF_MASK |
SPI_MCR_HALT_MASK;
DMA0->CR = 0x0;
// Transmit
DMAMUX->CHCFG[0] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(15);
/* Set the Source and destination addresses */
DMA0->TCD[0].SADDR = (uint32_t)buffer;
DMA0->TCD[0].DADDR = (uint32_t)&SPI0->PUSHR;
/* Source source and destination offsets */
DMA0->TCD[0].SOFF = sizeof(uint32_t);
DMA0->TCD[0].DOFF = 0;
/* Modulo off and port sizes */
DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2); //source and destination size 0 = 8 bits, 2 = 32 bits
/* Transfer size */
DMA0->TCD[0].NBYTES_MLNO = 4;
/* No link channel, transactions */
DMA0->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count);
DMA0->TCD[0].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(count);
/* Adjustment to source and destination addresses */
DMA0->TCD[0].SLAST = 0x0;
DMA0->TCD[0].DLAST_SGA = 0x0;
DMA0->TCD[0].CSR = DMA_CSR_DREQ_MASK;
#if 0
// Receive
DMAMUX->CHCFG[1] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(14);
/* Set the Source and destination addresses */
DMA0->TCD[1].SADDR = (uint32_t)&SPI0->POPR;
DMA0->TCD[1].DADDR = (uint32_t)receive_buffer;
/* Source source and destination offsets */
DMA0->TCD[1].SOFF = 0;
DMA0->TCD[1].DOFF = sizeof(uint32_t);
/* Modulo off and port sizes */
DMA0->TCD[1].ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2); //source and destination size 0 = 8 bits, 2 = 32 bits
/* Transfer size */
DMA0->TCD[1].NBYTES_MLNO = 4;
/* No link channel, transactions */
DMA0->TCD[1].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count);
DMA0->TCD[1].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(count);
/* Adjustment to source and destination addresses */
DMA0->TCD[1].SLAST = 0x0;
DMA0->TCD[1].DLAST_SGA = 0x0;
DMA0->TCD[1].CSR = DMA_CSR_DREQ_MASK | DMA_CSR_INTMAJOR_MASK;
DMA0->ERQ = DMA_ERQ_ERQ0_MASK | DMA_ERQ_ERQ1_MASK;
#else
DMA0->ERQ = DMA_ERQ_ERQ0_MASK | DMA_ERQ_ERQ1_MASK;
#endif
/*
* Start the SPI -- now configured for DMA
*/
#if 0
SPI0->RSER |= SPI_RSER_TFFF_RE_MASK | SPI_RSER_TFFF_DIRS_MASK | SPI_RSER_RFDF_RE_MASK | SPI_RSER_RFDF_DIRS_MASK;
#else
SPI0->RSER |= SPI_RSER_TFFF_RE_MASK | SPI_RSER_TFFF_DIRS_MASK;
#endif
SPI0->RSER |= SPI_RSER_EOQF_RE_MASK;
SPI0->MCR &= ~SPI_MCR_HALT_MASK;
}
/*
*
*/
void PIT1_IRQHandler(void)
{
/* We should get PIT1 interrupts more often -- check them first */
if (PIT->CHANNEL[1].TFLG & PIT_TFLG_TIF_MASK)
{
spi_transmit_receive(SENSOR_TRANSMIT_READ_WHOAMI, SENSOR_TRANSMIT_READ_WHOAMI_COUNT);
/* Clear interrupt flag */
PIT->CHANNEL[1].TFLG = PIT_TFLG_TIF_MASK;
}
}
void DMA0_IRQHandler(void)
{
SPI0_SR |= SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK;
SPI0_MCR |= SPI_MCR_HALT_MASK;
DMA_CINT |= DMA_CINT_CAIR_MASK;
}
void DMA_Error_IRQHandler(void)
{
__asm("nop");
}
/*
* None
*/
static void pit_init(void)
{
/* Start PIT module clock */
SIM->SCGC6 |= SIM_SCGC6_PIT_MASK;
// Initialize timer
PIT->MCR = PIT_MCR_FRZ_MASK;
PIT->CHANNEL[1].LDVAL = 48000000UL/(2000UL);
PIT->CHANNEL[1].TFLG = PIT_TFLG_TIF_MASK;
PIT->CHANNEL[1].TCTRL = PIT_TCTRL_TIE_MASK;
/* Enable IRQ */
NVIC_EnableIRQ(PIT1_IRQn);
PIT->CHANNEL[1].TCTRL |= PIT_TCTRL_TEN_MASK;
}
/*
*
*/
static void ports_init(void)
{
/* Clocking */
SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK | SIM_SCGC5_PORTD_MASK;
/* Power for sensor */
PORTC->PCR[5] = PORT_PCR_MUX(1);
PTC->PDDR = (1 << 5);
PTC->PSOR = (1 << 5);
/* Configure SPI ports */
PORTD->PCR[0] = PORT_PCR_MUX(2);
PORTD->PCR[1] = PORT_PCR_MUX(2);
PORTD->PCR[2] = PORT_PCR_MUX(2);
PORTD->PCR[3] = PORT_PCR_MUX(2);
}
/*
*
*/
static void spi_dma_init(void)
{
/* Clocking */
SIM->SCGC6 |= SIM_SCGC6_SPI0_MASK;
SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;
/* Configure SPI */
SPI0->MCR = SPI_MCR_HALT_MASK;
SPI0->CTAR[0] = SPI_CTAR_FMSZ(7);
SPI0->CTAR[1] = SPI_CTAR_FMSZ(7);
NVIC_EnableIRQ(DMA0_IRQn);
}
/*
*
*/
int main(void)
{
EnableInterrupts;
ports_init();
spi_dma_init();
pit_init();
while (true)
{
}
return 0;
}
Solved! Go to Solution.
Hi
Below is the code to do this in the uTasker project:
#define SPI_DMA_LENGTH 11
#define SPI_DMA_CHANNEL 7
// PIT 0 interrupt callback
//
static __callback_interrupt void _startTx(void)
{
static unsigned long ulSPI_TxBuffer[SPI_DMA_LENGTH] = {0}; // tx buffer
unsigned char ucByteValue = (unsigned char)ulSPI_TxBuffer[0];
WRITE_ONE_TO_CLEAR(SPI1_SR, (SPI_SR_EOQF)); // clear the end of queue flag
while (i < (SPI_DMA_LENGTH - 1)) { // prepare a tx data pattern
ulSPI_TxBuffer[i++] = (SPI_PUSHR_CONT | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue);
}
ulSPI_TxBuffer[i] = (SPI_PUSHR_EOQ | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue); // negate CS line after final bytes is sent
// Configure Tx DMA (buffer to SPI, single buffer with no interrupt on termination)
//
fnConfigDMA_buffer(SPI_DMA_CHANNEL, DMAMUX0_CHCFG_SOURCE_SPI1_TX, sizeof(ulSPI_TxBuffer), &ulSPI_TxBuffer[0], (void *)SPI1_PUSHR_ADDR, (DMA_LONG_WORDS | DMA_DIRECTION_OUTPUT | DMA_SINGLE_CYCLE), 0, PRIORITY_DMA10);
fnDMA_BufferReset(SPI_DMA_CHANNEL, DMA_BUFFER_START); // start the transfer
}
// PIT and SPI configuration
//
static void fnConfigurePIT_SPI(void)
{
PIT_SETUP pit_setup; // PIT interrupt configuration parameters
// Configure SPI
//
POWER_UP_ATOMIC(6, SPI1);
_CONFIG_PERIPHERAL(D, 4, (PD_4_SPI1_PCS0 | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 5, (PD_5_SPI1_SCK | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 7, (PD_7_SPI1_SIN | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 6, (PD_6_SPI1_SOUT));
SPI1_MCR = (SPI_MCR_MSTR | SPI_MCR_DCONF_SPI | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS_CS0 | SPI_MCR_PCSIS_CS1 | SPI_MCR_PCSIS_CS2 | SPI_MCR_PCSIS_CS3 | SPI_MCR_PCSIS_CS4 | SPI_MCR_PCSIS_CS5);
SPI1_CTAR0 = (SPI_CTAR_DBR | SPI_CTAR_FMSZ_8 | SPI_CTAR_PDT_7 | SPI_CTAR_BR_2);
SPI1_RSER = (SPI_SRER_TFFF_DIRS | SPI_SRER_TFFF_RE); // enable DMA request when tx fifo not full
pit_setup.int_type = PIT_INTERRUPT;
pit_setup.mode = PIT_PERIODIC; // PIT periodic interrupt
pit_setup.ucPIT = 0; // use PIT0
pit_setup.int_handler = _startTx; // PIT interrupt callback
pit_setup.int_priority = PIT0_INTERRUPT_PRIORITY;
pit_setup.count_delay = PIT_FREERUN_FREQ(64000); // 64kHz interrupt rate
fnConfigureInterrupt((void *)&pit_setup); // configure and start the PIT
}
This is the output showing 11 bytes sent 64000 times a second:
Closer look:
Some notes:
It is not absolutely necessary to use a DMA completion interrupt since the PIT interrupt can simply configure everything needed each time it starts the transfer.
It IS IMPORTANT to clear the EOQF flag in the status register otherwise it will not transmit a second time.
There is no need to enable and disable the SPI since if the DMA is not active it won't send anything anyway.
Although only a detail it is not recommended to clear flags with
SPI0_SR |= SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK
but correctly they should be cleared with (not ORing with what is read)
SPI0_SR = SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK
Although the DMA API you are using needs a lot of careful setup (and error prone due to this) I didn't actually spot anything that was incorrectly configured. Therefore check that the EOQF clear is not somehow being optimised away and try not disabling/enabling the SPI between transfers.
Regards
Mark
[uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training, solutions to problems or rapid product development requirements
For professionals searching for faster, problem-free Kinetis and i.MX RT 10xx developments the uTasker project holds the key: https://www.utasker.com/iMX/RT1064.html
It was the setting of the HALT bit in the SPI MCR register that was causing the problem. I don't know how they got away with it in the demo...
Thanks!
Hi
Below is the code to do this in the uTasker project:
#define SPI_DMA_LENGTH 11
#define SPI_DMA_CHANNEL 7
// PIT 0 interrupt callback
//
static __callback_interrupt void _startTx(void)
{
static unsigned long ulSPI_TxBuffer[SPI_DMA_LENGTH] = {0}; // tx buffer
unsigned char ucByteValue = (unsigned char)ulSPI_TxBuffer[0];
WRITE_ONE_TO_CLEAR(SPI1_SR, (SPI_SR_EOQF)); // clear the end of queue flag
while (i < (SPI_DMA_LENGTH - 1)) { // prepare a tx data pattern
ulSPI_TxBuffer[i++] = (SPI_PUSHR_CONT | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue);
}
ulSPI_TxBuffer[i] = (SPI_PUSHR_EOQ | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue); // negate CS line after final bytes is sent
// Configure Tx DMA (buffer to SPI, single buffer with no interrupt on termination)
//
fnConfigDMA_buffer(SPI_DMA_CHANNEL, DMAMUX0_CHCFG_SOURCE_SPI1_TX, sizeof(ulSPI_TxBuffer), &ulSPI_TxBuffer[0], (void *)SPI1_PUSHR_ADDR, (DMA_LONG_WORDS | DMA_DIRECTION_OUTPUT | DMA_SINGLE_CYCLE), 0, PRIORITY_DMA10);
fnDMA_BufferReset(SPI_DMA_CHANNEL, DMA_BUFFER_START); // start the transfer
}
// PIT and SPI configuration
//
static void fnConfigurePIT_SPI(void)
{
PIT_SETUP pit_setup; // PIT interrupt configuration parameters
// Configure SPI
//
POWER_UP_ATOMIC(6, SPI1);
_CONFIG_PERIPHERAL(D, 4, (PD_4_SPI1_PCS0 | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 5, (PD_5_SPI1_SCK | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 7, (PD_7_SPI1_SIN | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 6, (PD_6_SPI1_SOUT));
SPI1_MCR = (SPI_MCR_MSTR | SPI_MCR_DCONF_SPI | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS_CS0 | SPI_MCR_PCSIS_CS1 | SPI_MCR_PCSIS_CS2 | SPI_MCR_PCSIS_CS3 | SPI_MCR_PCSIS_CS4 | SPI_MCR_PCSIS_CS5);
SPI1_CTAR0 = (SPI_CTAR_DBR | SPI_CTAR_FMSZ_8 | SPI_CTAR_PDT_7 | SPI_CTAR_BR_2);
SPI1_RSER = (SPI_SRER_TFFF_DIRS | SPI_SRER_TFFF_RE); // enable DMA request when tx fifo not full
pit_setup.int_type = PIT_INTERRUPT;
pit_setup.mode = PIT_PERIODIC; // PIT periodic interrupt
pit_setup.ucPIT = 0; // use PIT0
pit_setup.int_handler = _startTx; // PIT interrupt callback
pit_setup.int_priority = PIT0_INTERRUPT_PRIORITY;
pit_setup.count_delay = PIT_FREERUN_FREQ(64000); // 64kHz interrupt rate
fnConfigureInterrupt((void *)&pit_setup); // configure and start the PIT
}
This is the output showing 11 bytes sent 64000 times a second:
Closer look:
Some notes:
It is not absolutely necessary to use a DMA completion interrupt since the PIT interrupt can simply configure everything needed each time it starts the transfer.
It IS IMPORTANT to clear the EOQF flag in the status register otherwise it will not transmit a second time.
There is no need to enable and disable the SPI since if the DMA is not active it won't send anything anyway.
Although only a detail it is not recommended to clear flags with
SPI0_SR |= SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK
but correctly they should be cleared with (not ORing with what is read)
SPI0_SR = SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK
Although the DMA API you are using needs a lot of careful setup (and error prone due to this) I didn't actually spot anything that was incorrectly configured. Therefore check that the EOQF clear is not somehow being optimised away and try not disabling/enabling the SPI between transfers.
Regards
Mark
[uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training, solutions to problems or rapid product development requirements
For professionals searching for faster, problem-free Kinetis and i.MX RT 10xx developments the uTasker project holds the key: https://www.utasker.com/iMX/RT1064.html