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
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?
Thanks alot Mark. This is greatly appreciated.
Do you happen to have any example code showing this?
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
//
}
Hello,
Can anyone offer any assistance on this or better yet if you already have a driver that would be great.
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
Hi,
The DSPI_SlaveTransferEDMA() function with below description:
The K64 chip SPI modules, only SPI0 module provide split RX/TX DMA request.
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!
-----------------------------------------------------------------------------------------------------------------------
Hi,
Thanks for the post. But this is not what I am asking.
If anyone else has use the SPI_DMA driver in circular mode sharing the code would be greatly apperciated.