Use of DMA with SPI in Kinetis K61

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Use of DMA with SPI in Kinetis K61

1,580 Views
larryhill
Contributor I

I need to connect K61 to an external  ADC chip.  This part acts as a SPI slave to output its readings.  It samples at about an 8 KHz rate on 8 channels, delivering a stream of 27 bytes comprising each reading. Every time sampling of the 8 channels is completed, the ADC provides a high-low transition on an output pin called DRDY.  DRDY is attached to a  K61 GPIO input, along with the standard SPI lines for SPI0.   Once it has signaled with DRDY, the ADC chip waits to receive clock from the spi master (K61), which when received cause it to stream out its 27 bytes of data, after which raises the DRDY line until the next conversion is complete.

I have set up the DRDY GPIO input to trigger a DMA request to start a minor loop of duration 27 bytes.  I use two DMA channels, one which sends a stream of 27 0xFF bytes (inactive data state) to the  SPI0 transmit FIFO,  which generates SPI clock to the slave ADC.  The second channel is sourced to the SPI0 receive FIFO to read in the data that results. There are 128  iterations in the major loop count.

My problem is that I cannot make flow control work on the transmit channel unless I specify SPI0 as the DMA request source. When I do that, I see 27 bytes of clock come out. But when I specify the GPIO as the source of request, apparently the SPI block stops listening to the SPI SR[TFFF} bit which tells it when to pause to avoid overflow (when enabled by the SPI RSER[TFFF_RE]. The result is I get an overflow and my clocks never get sent after a few bytes. Surely there is another bit I should be setting so that the SPI block will initiate a minor loop based on a GPIO transition, and then be flow controlled by the SPI block so it does not overflow the transmit FIFO?    Or did I make some other error?  I realize I could configure DRDY as an interrupt, and then start the transaction with the DMA start bit, but that would waste a lot of cpu time I can ill afford. 

Any suggestions would be welcome.

Here is my initialization code:

void DSA_dma_init(void)

{

#pragma diag_suppress=Pm140 /* cast between pointer and integer */

  /* Create data for SPI0_PUSHR register.  The word sent is for all but last is:

     SPI_PUSHR_TXDATA(DUMMY_DATA_FF) |SPI_PUSHR_CONT_MASK | SPI_PUSHR_PCS(1) |

       SPI_PUSHR_PCS(1) | SPI_PUSHR_CTAS(1);

     For last word,   SPI_PUSHR_EOQ_MASK is added. */

  static const uint32_t dmaTxData[27] =

  { 0x900100FFu,    /* or 27 loops of 1 byte if set to 0x980100FF */

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x900100FFu,

    0x980100FFu };

  /*

   *Set up the transmit channel

   */

    /*

     * Disable DMA while we modify it.

     * ---------------------------

     *   Note that SERQ and CERQ modify single bits in DMA_ERQ.

     */

    DMA_CERQ = DSA_DMA_TX_CHANNEL; /* Disable DSA_DMA TX_CHANNEL's DMA_ERQ bit*/

    /*

     * Clock Module:

     * ---------------------------

     * Enable the DMA MUX clock gate and the DMA clock.

     */

    SIM_SCGC6 |= SIM_SCGC6_DMAMUX0_MASK;

    SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;

    /*

     * PORT module:

     * Set up the trigger port BSP_DS_DRDY_MUX_GPIO to be a DMA request

     *   on the falling edge.  This is Port C Pin 4 - Processor pin E14.

     *   1u is DMA rising edge, 2u is DMA falling edge, 3u is DMA either edge.

     *   9u is interrupt rising edge, 10u is interupt falling edge.

     */

    PORTC_PCR4 = PORT_PCR_IRQC(2u)

                | (BSP_DS_DRDY_MUX_GPIO << PORT_PCR_MUX_SHIFT);

    /*

    * SPI DMA/Interrupt request Select and enable:

    * This register is set in DSA_spi_init(); DMA request is generated when SR[TFFF]

    * is set, indicating FIFO is full, or RSER[TFFF_RE], indicating it is not full.

    * Need to set DMA_ERQ register to recognize flow control from SPI block.

    */

    /*

     * DMAMUX module:

     * Set up the DMA request source.

     *   Using Channel 4 in Mux 0.

     *   The appropriate port is defined in the Reference Manual in the Chip

     *     Configuration section under "DMA request multiplexer configuration"

     *     Section 3.3.9.1

     *     Port C transmit is Source Number 51 on MUX 0.

     *   Note: if a PIT trigger is to be used, then bit DMAMUX_CHCFG_TRIG_MASK

     *     should be ORed into the command.  PIT triggers are only available

     *     in the first four channels.

     */

   DMAMUX0_CHCFG3 = 0u;        /* Clear before changing */

   DMAMUX0_CHCFG3 = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE( 51u );

     /* source = SPI0 TX uses 17; DRDY uses 51 */

    /*

     * DMA module:

     * ---------------------------

     *    Load the 32 byte Transfer Control Descriptor (TCD) for this channel.

     *    27 minor and one major loop occur for each transaction.

     */

    DMA_TCD3_SADDR = (uint32_t) dmaTxData;                    /* Source data */

    DMA_TCD3_SOFF = 4u;                               /*  addr inc = 4 bytes */

    DMA_TCD3_ATTR = DMA_ATTR_SSIZE(2u) | DMA_ATTR_DSIZE(2u);

            /* 32 bit source no burst, 32 bit destination no burst, no modulo */

    DMA_TCD3_NBYTES_MLOFFNO = (int32_t) DSA_DMA_TX_BYTES;   /* Minor loop bytes */

    DMA_TCD3_SLAST = - (int32_t) DSA_DMA_TX_BYTES;  /* address offset per minor loop */

    DMA_TCD3_DADDR = (uint32_t) &SPI0_PUSHR;/*Destination data-SPI0 TX register*/

    DMA_TCD3_DOFF = 0u;                            /* Dest increment  0 bytes */

    DMA_TCD3_CITER_ELINKNO = (uint32_t) DSA_DMA_LOOPS;    /* Major loop count */

    DMA_TCD3_DLASTSGA = 0u;               /* Byte adjustment after major loop */

/*    DMA_TCD3_CSR = 0u  This register can also be used to start a transfer */

    DMA_TCD3_BITER_ELINKNO = (uint32_t) DSA_DMA_LOOPS;    /* Major loop count *

#pragma diag_default=Pm140  /* cast between pointer and integer */

}

Here is the SPI init section which seems to work only when SPI is selected as the DMA request source.

  SPI0_RSER = SPI_RSER_TFFF_RE_MASK |     /* transmit FIFO fill request */

              SPI_RSER_TFFF_DIRS_MASK |   /* to DMA */

              SPI_RSER_RFDF_RE_MASK |     /* receive FIFO drain request */

              SPI_RSER_RFDF_DIRS_MASK;    /* to DMA */

Labels (1)
1 Reply

792 Views
jeremyzhou
NXP Employee
NXP Employee

Hi Larry,

In my opinion, I still like to recommend you to treat the DRDY as a interrupt trigger, then set the DMA start bit when the DRDY occurs.

However as you mentioned, you were afraid of waste a lot of CPU time that beyond afford range.

So I'd like to suggest that you can use the GPIO as the DMA trigger source of one channel, then can trigger a DMA request to start a minor loop of 4 bytes and there are 7 iterations in the major loop count which be used to generate the SPI clock to the slave ADC.

To implement it, you can use the dynamic scatter/gather feature, and please refer to the the illustration as below for details.

使用DMA降低SPI通信过程中内核负荷 Reduce core work load with DMA Module during SPI communication

In meanwhile, set another DMA channel to read receive SPI FIFO registers, as the FIFO level is 4, you should set the a minor loop of 28 bytes and there are 1 iterations in the major loop count, one more note, the priority should be higher than the first channel.

Hope it helps.
Have a great day,

Ping

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos
Reply