SPI using DMA with a Circular DMA buffer (K64f)

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

SPI using DMA with a Circular DMA buffer (K64f)

3,035 Views
unknowncoder
Contributor III

I am using the SPI in DMA mode. I have setup the DMA to use circular buffer by calling "EDMA_SetModulo()"

The circular buffer feature only need for the receiving data on the SPI.

The call process goes like this

  1. Setup DMA as usual 
  2. call this EDMA_SetModulo() with the destination set to kEDMA_Modulo512bytes. The destination of the DMA is the RAM, the source of the DMA is the SPI RX register.
  3. Finish setting up the SPI in DMA Mode 
  4. call DSPI_SlaveTransferEDMA() with the rxData pointer pointing to a 512byte buffer.

After the master has transfer a total of 512 byte the DMA callback runs as expected but after this point if the master start sending more data nothing happens. I think after every DMA transfer complete runs I need to call "DSPI_SlaveTransferEDMA()" method again. 

Which makes no sense if my DMA is in circular buffer mode.

Can someone let em know what I may be doing wrong.

Edit:

I looks like the SPI_EDMA driver doesn't support setting up the DMA in circular buffer mode. I had edited the SPI_EDMA driver to correctly setup the ATTR register but it still doesn't work. 

When EDMA_DspiSlaveCallback() it disable the DMA, but if I remove this call, it still does not work. Can anyone offer me some insight?

Labels (1)
7 Replies

2,174 Views
unknowncoder
Contributor III

Thanks alot Mark. This is greatly appreciated. 

0 Kudos

2,174 Views
unknowncoder
Contributor III

Do you happen to have any example code showing this?

0 Kudos

2,174 Views
mjbcswitzerland
Specialist V

Hi

There is code in the open source uTasker project.

This is the extract from the developer's version (more advanced and supported). Eg. for any UART (channel is 0..5)

fnConfigDMA_buffer(UART_DMA_RX_CHANNEL[channel], (DMAMUX_CHCFG_SOURCE_UART0_RX + (2 * channel)), buffer_length, uart_data_reg, (void *)buffer_address, (DMA_DIRECTION_INPUT | DMA_BYTES), 0, 0);

so for SPI slave uart_data_reg is replaced by the SPI rx reg address  and DMAMUX_CHCFG_SOURCE_UART0_RX by DMAMUX_CHCFG_SOURCE_SPI0_RX.If the SPI is not in 8 bit mode DMA_HALF_WORDS or DMA_LONG_WORDS is defined to suit (instead of DMA_BYTES).

fnConfigDMA_buffer() works for all DMA controller types (compatible on all K or KL parts with DMA) and free-running mode is set when no full-buffer interrupt or half-buffer-interupt is defined (last parameter 0).

Complete routine follows so you can pick out the bits of interest.

Note that the uTasker project also simulates the DMA operation in Visual Studio so that it can be completely verified (as well a Kinetis peripherals, interrupts etc.) so will typically allow developments (debugging, testing, validating) in a fraction of the time using traditional techniques.

Regards

Mark

// Buffer source to fixed destination address or fixed source address to buffer
//
extern void fnConfigDMA_buffer(unsigned char ucDMA_channel, unsigned short usDmaTriggerSource, unsigned long ulBufLength, void *ptrBufSource, void *ptrBufDest, unsigned long ulRules, void (*int_handler)(void), int int_priority)
{
    unsigned char ucSize = (unsigned char)(ulRules & 0x07);              // transfer size 1, 2 or 4 bytes

    #if defined KINETIS_KL && !defined DEVICE_WITH_eDMA
    KINETIS_DMA *ptrDMA = (KINETIS_DMA *)DMA_BLOCK;
        #if defined _WINDOWS
    if (ucDMA_channel >= DMA_CHANNEL_COUNT) {
        _EXCEPTION("Error - peripheral DMA is specifying a non-existent channel!!");
    }
        #endif
    ptrDMA += ucDMA_channel;                                             // move to the DMA channel to be used
    ptrDMA->DMA_DSR_BCR = DMA_DSR_BCR_DONE;                              // clear the DONE flag and clear errors etc.
    switch (ucSize) {
    default:
    case 1:                                                              // byte transfers
        ucSize = 1;
        ptrDMA->DMA_DCR = (DMA_DCR_DSIZE_8 | DMA_DCR_SSIZE_8 | DMA_DCR_DMOD_OFF | DMA_DCR_SMOD_OFF); // transfer size bytes
        break;
    case 2:                                                              // half-word transfers
        ptrDMA->DMA_DCR = (DMA_DCR_DSIZE_16 | DMA_DCR_SSIZE_16 | DMA_DCR_DMOD_OFF | DMA_DCR_SMOD_OFF); // transfer size half-words
        break;
    case 4:
        ptrDMA->DMA_DCR = (DMA_DCR_DSIZE_32 | DMA_DCR_SSIZE_32 | DMA_DCR_DMOD_OFF | DMA_DCR_SMOD_OFF); // transfer sizes long words
        break;
    }
    if ((ulRules & DMA_DIRECTION_OUTPUT) != 0) {                         // buffer to fixed output
        ptrDMA->DMA_DCR |= (DMA_DCR_SINC);                               // transfers with increment only on source
        ucDirectionOutput[ucDMA_channel] = DMA_TRANSFER_OUTPUT;          // remember the output direction
        ptrStart[ucDMA_channel] = ptrBufSource;                          // remember the start of the source buffer
    }
    else {                                                               // fixed input to buffer
        ptrDMA->DMA_DCR |= (DMA_DCR_DINC);                               // transfers with increment only on destination
        ucDirectionOutput[ucDMA_channel] = DMA_TRANSFER_INPUT;           // remember the input direction
        ptrStart[ucDMA_channel] = ptrBufDest;                            // remember the start of the destination buffer
    }
    if ((DMA_FIXED_ADDRESSES & ulRules) != 0) {                          // if both source and destination addresses are fixed
        ptrDMA->DMA_DCR &= ~(DMA_DCR_DINC | DMA_DCR_SINC);               // disable source and destination increments
    }
    ptrDMA->DMA_SAR = (unsigned long)ptrBufSource;                       // set source buffer
    ptrDMA->DMA_DAR = (unsigned long)ptrBufDest;                         // set destination buffer
    if ((ulRules & DMA_AUTOREPEAT) != 0) {                               // {1} the DMA is to be automatically repeated after each transfer has completed
        ulRepeatLength[ucDMA_channel] = ulBufLength;                     // the full-buffer length to be repeated
        if ((ulRules & DMA_HALF_BUFFER_INTERRUPT) != 0) {
        #if defined _WINDOWS
            if ((ulBufLength%(ucSize * 2)) != 0) {
                _EXCEPTION("Buffer length is expected to be divisible by twice the transfer size");
            }
        #endif
            ulBufLength /= 2;                                            // half buffer mode length
            ucDirectionOutput[ucDMA_channel] |= DMA_TRANSFER_HALF_BUFFER;// emulate half buffer interrupt operation
        }
    }
    else {
        ulRepeatLength[ucDMA_channel] = 0;                               // no automatic repetition based on end of buffer interrupt
    }
    _DMA_handler[ucDMA_channel] = int_handler;                           // user interrupt callback
    if ((int_handler != 0) || ((ulRules & DMA_AUTOREPEAT) != 0)) {       // if there is a buffer interrupt handler at the end of DMA buffer operation (only full buffer interrupt is supported by the KL DMA controller)
        ptrDMA->DMA_DSR_BCR = ulBufLength;                               // set transfer count (don't set DMA_DSR_BCR_DONE at the same time otherwise BCR is reset)
        fnEnterInterrupt((irq_DMA0_ID + ucDMA_channel), int_priority, (void (*)(void))_DMA_Interrupt[ucDMA_channel]); // enter DMA interrupt handler on buffer completion
        ptrDMA->DMA_DCR |= (DMA_DCR_EINT | DMA_DCR_D_REQ);               // interrupt when the transmit buffer is empty and stop operation after full buffer has been transferred
    }
    else {                                                               // else free-running in circular buffer without any interrupt (source buffer must also be aligned to the correct buffer boundary!!)
        ptrDMA->DMA_DSR_BCR = 0xffff0;                                   // set maximum transfer count (this needs to be regularly retriggered for infinite operation)
        if ((DMA_NO_MODULO & ulRules) == 0) {                            // if modulo operation hasn't been disabled
            unsigned long ulMod = DMA_DCR_SMOD_256K;                     // configure circular buffer operation - the length and buffer alignment must be suitable
    #if defined _WINDOWS
            if ((ulBufLength != 16) && (ulBufLength != 32) && (ulBufLength != 64) && (ulBufLength != 128) && (ulBufLength != 256) && (ulBufLength != 512) && (ulBufLength != 1024) && (ulBufLength != (2 * 1024)) && (ulBufLength != (4 * 1024)) && (ulBufLength != (8 * 1024)) && (ulBufLength != (16 * 1024)) && (ulBufLength != (32 * 1024)) && (ulBufLength != (64 * 1024)) && (ulBufLength != (128 * 1024)) && (ulBufLength != (256 * 1024))) {
                _EXCEPTION("Invalid circular buffer size!!");
            }
            if ((ulRules & DMA_FIXED_ADDRESSES) == 0) {
                if ((ulRules & DMA_DIRECTION_OUTPUT) != 0) {
                    if ((unsigned long)ptrBufSource & (ulBufLength - 1)) {
                        _EXCEPTION("Circular source buffer not-aligned!!");
                    }
                }
                else {
                    if ((unsigned long)ptrBufDest & (ulBufLength - 1)) {
                        _EXCEPTION("Circular destination buffer not-aligned!!");
                    }
                }
            }
    #endif
            while (ulBufLength < (256 * 1024)) {                         // calculate the modulo value required for the source
                ulBufLength *= 2;
                ulMod -= DMA_DCR_SMOD_16;
            }
            if ((ulRules & DMA_DIRECTION_OUTPUT) == 0) {                 // if the buffer is the destination
                ulMod >>= 4;                                             // move to destination MOD field
            }
            ptrDMA->DMA_DCR |= ulMod;                                    // the modulo setting
        }
    }
    POWER_UP_ATOMIC(6, DMAMUX0);                                         // enable DMA multiplexer 0
    *(unsigned char *)(DMAMUX0_BLOCK + ucDMA_channel) = (unsigned char)(usDmaTriggerSource | DMAMUX_CHCFG_ENBL); // connect trigger to DMA channel
    ptrDMA->DMA_DCR |= (DMA_DCR_CS | DMA_DCR_EADREQ);                    // enable peripheral request - single cycle for each request (asynchronous requests enabled in stop mode)
    #else                                                                // eDMA
    KINETIS_DMA_TDC *ptrDMA_TCD = (KINETIS_DMA_TDC *)eDMA_DESCRIPTORS;
    ptrDMA_TCD += ucDMA_channel;                                         // set to the channel registers to be used
    if ((DMA_FIXED_ADDRESSES & ulRules) != 0) {                          // if both source and destination addresses are fixed
        ptrDMA_TCD->DMA_TCD_SOFF = 0;                                    // source not incremented
        ptrDMA_TCD->DMA_TCD_DOFF = 0;                                    // destination not incremented
        ptrDMA_TCD->DMA_TCD_SLAST = 0;                                   // no source displacement on transmit buffer completion
        ptrDMA_TCD->DMA_TCD_DLASTSGA = 0;                                // no destination displacement on transmit buffer completion
    }
    else {
        if ((ulRules & DMA_DIRECTION_OUTPUT) != 0) {                     // buffer to fixed output
            ptrDMA_TCD->DMA_TCD_SOFF = ucSize;                           // source increment (buffer)
            ptrDMA_TCD->DMA_TCD_DOFF = 0;                                // destination not incremented
            ptrDMA_TCD->DMA_TCD_DLASTSGA = 0;                            // {3} no destination displacement on transmit buffer completion
            ptrDMA_TCD->DMA_TCD_SLAST = (-(signed long)(ulBufLength));   // {3} when the buffer has been transmitted set the destination back to the start of it
        }
        else {                                                           // fixed input to buffer
            ptrDMA_TCD->DMA_TCD_SOFF = 0;                                // source not incremented
            ptrDMA_TCD->DMA_TCD_DOFF = ucSize;                           // destination increment one word (buffer)
            ptrDMA_TCD->DMA_TCD_DLASTSGA = (-(signed long)(ulBufLength));// {3} when the buffer has been filled set the destination back to the start of it
            ptrDMA_TCD->DMA_TCD_SLAST = 0;                               // {3} no source displacement on receive buffer completion
        }
    }
    switch (ucSize) {                                                    // the individual transfer size
    default:
    case 1:                                                              // single byte
        ucSize = 1;
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_8 | DMA_TCD_ATTR_SSIZE_8); // transfer sizes bytes
        break;
    case 2:                                                              // half-word
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_16 | DMA_TCD_ATTR_SSIZE_16); // transfer sizes words
        break;
    case 4:                                                              // word
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_32 | DMA_TCD_ATTR_SSIZE_32); // transfer sizes long words
        break;
    }
    ptrDMA_TCD->DMA_TCD_SADDR = (unsigned long)ptrBufSource;             // source buffer
    ptrDMA_TCD->DMA_TCD_NBYTES_ML = ucSize;                              // each request starts a single transfer of this size
    _DMA_handler[ucDMA_channel] = int_handler;                           // user interrupt callback
    if (int_handler != 0) {                                              // if there is a buffer interrupt handler at the end of DMA buffer operation
        if ((ulRules & DMA_HALF_BUFFER_INTERRUPT) != 0) {
            ptrDMA_TCD->DMA_TCD_CSR = (DMA_TCD_CSR_INTMAJOR | DMA_TCD_CSR_INTHALF); // interrupt when the transmit/receive buffer is half full (and when full)
        }
        else {
            ptrDMA_TCD->DMA_TCD_CSR = (DMA_TCD_CSR_INTMAJOR);            // interrupt when the transmit/receive buffer is full
        }
        #if defined eDMA_SHARES_INTERRUPTS                               // interrupts are shared between channel 0 and _DMA_CHANNEL_COUNT, 1 and _DMA_CHANNEL_COUNT + 1, etc.
        fnEnterInterrupt((irq_DMA0_0_4_ID + (ucDMA_channel%_DMA_CHANNEL_COUNT)), int_priority, (void(*)(void))_DMA_Interrupt[ucDMA_channel%_DMA_CHANNEL_COUNT]); // enter DMA interrupt handler on full/half buffer completion
        #else
        fnEnterInterrupt((irq_DMA0_ID + ucDMA_channel), int_priority, (void(*)(void))_DMA_Interrupt[ucDMA_channel]); // enter DMA interrupt handler on full/half buffer completion
        #endif
    }
    else {
        ptrDMA_TCD->DMA_TCD_CSR = 0;                                     // free-running mode without any interrupt
    }
    if ((ulRules & DMA_SINGLE_CYCLE) != 0) {                             // {6}
        ptrDMA_TCD->DMA_TCD_CSR |= DMA_TCD_CSR_DREQ;                     // stop the DMA activity once the transfer completes
    }
    ptrDMA_TCD->DMA_TCD_DADDR = (unsigned long)ptrBufDest;               // destination
  //ptrDMA_TCD->DMA_TCD_DLASTSGA = 0;                                    // {3} no destination displacement on transmit buffer completion
  //ptrDMA_TCD->DMA_TCD_SLAST = (-(signed long)(ulBufLength));           // {3} when the buffer has been transmitted set the destination back to the start of it
    if ((ulRules & DMA_SW_TRIGGER) != 0) {                               // {8} no peripheral trigger used - use software start
        ptrDMA_TCD->DMA_TCD_CITER_ELINK = 1;                             // one main loop iteration
        ptrDMA_TCD->DMA_TCD_NBYTES_ML = ulBufLength;                     // total number of bytes
        if ((ulRules & DMA_INITIATE_TRANSFER) != 0) {                    // if the transfer is to be initiated immediately
            ptrDMA_TCD->DMA_TCD_CSR = DMA_TCD_CSR_START;                 // start DMA transfer
            if ((ulRules & DMA_WAIT_TERMINATION) != 0) {                 // if the call is a blocking call we wait until the transfer terminates
                while ((ptrDMA_TCD->DMA_TCD_CSR & DMA_TCD_CSR_DONE) == 0) { fnSimulateDMA(ucDMA_channel); } // wait until completed
            }
        }
        return;                                                          
    }
    ptrDMA_TCD->DMA_TCD_BITER_ELINK = ptrDMA_TCD->DMA_TCD_CITER_ELINK = (signed short)(ulBufLength/ucSize); // the number of service requests to be performed each cycle
    POWER_UP_ATOMIC(6, DMAMUX0);                                         // enable DMA multiplexer 0
        #if defined TRGMUX_AVAILABLE
    if ((usDmaTriggerSource & DMAMUX_CHCFG_TRIG) != 0) {                 // triggered source (LPIT)
    #if defined _WINDOWS
        if ((usDmaTriggerSource >> 8) >= 4) {
            _EXCEPTION("Invalid LPIT periodic trigger source!");
        }
    #endif
        TRGMUX_DMAMUX0 = (TRGMUX_SEL_LPIT0_CHANNEL_0 + (usDmaTriggerSource >> 8)); // LPIT is connected to the DMAMUX
        usDmaTriggerSource = DMAMUX0_DMA0_CHCFG_SOURCE_PIT0;
    }
        #endif
    *(unsigned char *)(DMAMUX0_BLOCK + ucDMA_channel) = (unsigned char)(usDmaTriggerSource | DMAMUX_CHCFG_ENBL); // connect trigger source to DMA channel
    #endif
    #if defined _WINDOWS                                                 // simulator checks to help detect incorrect usage
        #if defined DMA_MEMCPY_CHANNEL
    if (DMA_MEMCPY_CHANNEL == ucDMA_channel) {
        _EXCEPTION("Warning - peripheral DMA is using the channel reserved for DMA based uMemcpy()!!");
    }
        #endif
        #if defined DMA_MEMCPY_CHANNEL_ALT                               // {5}
    if (DMA_MEMCPY_CHANNEL_ALT == ucDMA_channel) {
        _EXCEPTION("Warning - peripheral DMA is using the alternative channel reserved for DMA based uMemcpy()!!");
    }
        #endif
    if (DMAMUX0_DMA0_CHCFG_SOURCE_PIT0 == usDmaTriggerSource) {
        if (ucDMA_channel != 0) {
        #if defined LPITS_AVAILABLE
            _EXCEPTION("LPIT triggers only operate on DMA channel 0!!");
        #else
            _EXCEPTION("PIT0 trigger only operates on DMA channel 0!!");
        #endif
        }
        #if defined ERRATA_ID_5746
        if ((ptrDMA->DMA_DCR & DMA_DCR_CS) != 0) {
            _EXCEPTION("PIT0 trigger generates two data transfers when in cycle-steal mode!!");
        }
        #endif
    }
        #if defined DMAMUX0_DMA0_CHCFG_SOURCE_PIT1
    else if (DMAMUX0_DMA0_CHCFG_SOURCE_PIT1 == usDmaTriggerSource) {
        if (ucDMA_channel != 1) {
            _EXCEPTION("PIT1 trigger only operates on DMA channel 1!!");
        }
            #if defined ERRATA_ID_5746
        if ((ptrDMA->DMA_DCR & DMA_DCR_CS) != 0) {
            _EXCEPTION("PIT1 trigger generates two data transfers when in cycle-steal mode!!");
        }
            #endif
    }
    else if (DMAMUX0_DMA0_CHCFG_SOURCE_PIT2 == usDmaTriggerSource) {
        if (ucDMA_channel != 2) {
            _EXCEPTION("PIT2 trigger only operates on DMA channel 2!!");
        }
    }
    else if (DMAMUX0_DMA0_CHCFG_SOURCE_PIT3 == usDmaTriggerSource) {
        if (ucDMA_channel != 3) {
            _EXCEPTION("PIT3 trigger only operates on DMA channel 3!!");
        }
    }
        #endif
    #endif
    // Note that the DMA channel has not been activated yet - to do this fnDMA_BufferReset(channel_number, DMA_BUFFER_START); is performed
    //
}

2,174 Views
unknowncoder
Contributor III

Hello, 

Can anyone offer any assistance on this or better yet if you already have a driver that would be great.

  • I have modified the SPI DMA driver significantly mostly just scrap code. To try to get the dma to stay in circular buffer mode.
  • As far as I can tell the DMA is setup correctly in circular buffer mode because the ATTR register is correctly set.
  • What I think is happening is after the DMA complete callback runs the SPI peripheral doesn't send any more DMA request. But I can't see why DMA request would stop because data i still coming in.
0 Kudos

2,174 Views
mjbcswitzerland
Specialist V

Hi

To ensure DMA operates continuously in circular buffer mode (on K64F) make sure that DMAx_TCD_CSR is kept at 0 (don't allow the DMA_TCD_CSR_DREQ) to be used) and ensure that DMAx_TCD_DLAST is equal to the -(minus) the circular buffer length.

Regards

Mark

uTasker developer and supporter (+5'000 hours experience on +60 Kinetis derivatives in +80 product developments)
Kinetis: http://www.utasker.com/kinetis.html

0 Kudos

2,174 Views
Hui_Ma
NXP TechSupport
NXP TechSupport

Hi,

The DSPI_SlaveTransferEDMA() function with below description:

pastedImage_1.png

The K64 chip SPI modules, only SPI0 module provide split RX/TX DMA request.

pastedImage_2.png

Please check if you are using SPI0 module as SPI slave.

And how do you set the TRANSFER_SIZE value, if you want to get more than 512 bytes SPI data, the TRANSFER_SIZE should be more than 512.

Wish it helps.


Have a great day,
Mike

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

0 Kudos

2,174 Views
unknowncoder
Contributor III

Hi,

Thanks for the post. But this is not what I am asking.

  • I am using SPI0 so I can get separate DMA request. 
  • I am only receiving data from the master. So I am not doing any transmitting. 
  • The EDMA support circular buffer mode by setting up the ATTR register. 
  • I have setup the DMA in circular buffer mode then when I call DSPI_SlaveTransferEDMA() it erase the ATTR register, because the call to DSPI_SlaveTransferEDMA() is also calling EDMA_SetTransferConfig(). When EDMA_SetTransferConfig() calls it complete erase the ATTR register. 
  • Also when EDMA_DspiSlaveCallback() callback is called by the DMA it stops the DMA from running by calling DSPI_DisableDMA() this is another reason why the DMA can't be placed in circular buffer mode with the SPI driver. 
  • It looks like the SPI_DMA driver does not support using the DMA in circular buffer mode. From the looks of it it seems I will need to write a custom SPI_DMA driver to use the DMA in circular buffer modem, because at the throughput I am running I could miss data if I don't leave the DMA running.

If anyone else has use the SPI_DMA driver in circular mode sharing the code would be greatly apperciated.

0 Kudos