How to use DMA for SPI without wasting memory

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

How to use DMA for SPI without wasting memory

6,779 Views
christophredeck
Contributor II

Hi,

I want to use DMA to transfer a large number of bytes (n, 16 KB) to an SPI slave and I need to know when the last bit has been shifted out, so the last transfer must have the end of queue bit set so I can check for it in the status register. My source buffer is an array of bytes.

How can I configure a DMA transfer that sets the EOQ bit in the last transfer? It seems to me that I can only do this with two transfers, one with n-1 bytes and one with a single 32-bit word that has the EOQ bit set. That looks a bit clumsy and I would need a chain of DMA transfers for that. Or is there another way of telling if all bits have been shifted out? I can periodically check for transfer completion; an interrupt handler is not necessary. If the solution incorporates one, that's fine.

So far I have only used DMA for a simple memcpy experiment to see if I can configure it correctly. I have no experience with more complex DMA applications or those involving peripherals such as the SPI.

Regards,

Christoph

Labels (1)
Tags (3)
8 Replies

2,545 Views
prahsman
Contributor I

You may have found your answer by now, but I just discovered it. I didn't want to use the queue stuff because of the overhead. I needed to send a bunch of bytes and didn't want to convert them to 32-bit words to add in the queue management stuff. The trick I used is to use the TXCTR and TCF bit in the SPI_SR register. You'll want to generate an interrupt when the DMA has transfered all of the bytes. The problem is that the SPI is not actually finished sending the bytes when the interrupt happens. Therefore in the ISR you need to first check for the SPI TXCTR to be zero. However, when this happens, the last byte has not completely been sent. Now clear the TCF bit by writing a one to it (it's been set since the end of the first byte sent). Now wait until it goes high again indicating that the last byte has finished. Now you can drop CS. There may be better way to do this without all this happening in the isr (with interrupts disabled) however it works.

0 Kudos

2,545 Views
egoodii
Senior Contributor III

The 'upper bits' of SPIx_PUSHR have nothing to do with 'queue stuff' -- they are to manage CS selection and Timing-option selection, so that entire transfers can be 'self timed'.  I take it from your description you are working with 'manual' CS handling to avoid that requirement, but what CTAR selection do you end up with if you don't write these upper bits of SPIx_PUSHR?  On another note, an 'easier way' to insure that all the TX are 'done' is to wait for all the simultaneous RX to be 'done' (and interrupt on THAT).  You may not WANT that RX data, but just set a DMA up for it with the same transfer count, and a 'bit bucket' writeable destination, such as a single RAM location (or to be even trickier some 'nominally read-only' location like SPI0_TXFR1).  Your TX-checks surely work, but I just *detest* spinning in an ISR!

0 Kudos

2,545 Views
egoodii
Senior Contributor III

You might be wanting to set up a DMA for the RX channel to follow the TX operations and empty the receive queue.  When that has received the 'full count' of bytes, you know the TX shifts have completed.  On a side note, I personally found relying on EOQ to signal the 'end of a transaction set' to be unreliable, as it would not always get set.

0 Kudos

2,545 Views
christophredeck
Contributor II

Thank you, but this would only work while there's another part of the application that writes data to the TX FIFO. Counting is easier though, as I can use the Rx FIFO Drain DMA request to trigger a read DMA. This read DMA would have the major loop count set to the number of bytes I actually want to transfer and set an interrupt request when it is done. But where does the data come from? I need an ISR for that, it seems...

0 Kudos

2,545 Views
egoodii
Senior Contributor III

Your verbiage has me a little confused regarding what you want to 'send' and what you want to 'receive', but in any case ALL SPI transactions do a simultaneous send and receive.  To make 'anything' happen requires a MOSI initiation, and that naturally instigates a MISO result.  In the general case, you set up a DMA for both, and each DMA-complete will net an interrupt (if enabled).  If you don't need the info going 'one way or the other' for any particular block, then set the memory address to a 'bit bucket' with no pointer increment.

You might get some benefit from my code in:

Re: DMA with SPI to read SD Card?

but note that in that instance I am 'regularly' sending a fixed block of memory to a write-only device (and I don't care when it gets done), and as such I don't even 'bother' with a read, I just let 'er overrun.  And I didn't put in any DMA interrupt(s) either.

2,545 Views
christophredeck
Contributor II

Well your code was indeed helpful, but I found out the the SPI also accepts 8-bit writes to PUSHR via DMA. My code is very similar to yours, but doesn't use the ability to select a slave when writing to PUSHR:

// data for the SPI slave:

  uint8_t source_array[4096];

// DMA configuration:

  DMAMUX0_CHCFG2 = 0;

  DMA_ERQ = DMA_ERQ_ERQ2;

  DMAMUX0_CHCFG2 = DMAMUX_ENABLE | DMAMUX_SOURCE_SPI0_TX;

  DMA_TCD2_SADDR = source_array;

  DMA_TCD2_SOFF = 1; // increment source by 1 on each iteration

  DMA_TCD2_ATTR = DMA_TCD_ATTR_SSIZE(0) | DMA_TCD_ATTR_DSIZE(0); // 8 bit -> 8 bit

  DMA_TCD2_NBYTES_MLNO = 1; // 1 byte per transaction

  DMA_TCD2_SLAST = -4096;

  DMA_TCD2_DADDR = (volatile void*)(&SPI0_PUSHR);

  DMA_TCD2_DOFF = 0; // don't increment destination address

  DMA_TCD2_CITER_ELINKNO = 4096;

  DMA_TCD2_DLASTSGA = 0;

  DMA_TCD2_BITER_ELINKNO = 4096;

// start transfer:

  SPI0_RSER = SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;

  DMA_ERQ = DMA_ERQ_ERQ2;

  DMA_TCD2_CSR = DMA_TCD_CSR_DREQ | DMA_TCD_CSR_START;

Thank you for your help!

2,545 Views
tsvetanmudrov
Contributor III

Hello Christoph.

Can you confirm that your solution works like it's expected? I mean - do you able to transfer a data buffer from memory to SPI via DMA in 8 bit fashion (without enlarge it to 32 bits before)? The data sheet is not clear in this part. It's written that 8 or 16 bit transfers to PUSHR are possible and all 32 bits are transferred.  But it's not written from where it comes the upper half of the word - they are 0, last state or...

In this thread is written that manual 8/16 bit access to PUSHR register is NOT working like we expect (or like we want):

8-bit/16-bit writes to SPI PUSHR not working as expected

I'm wondering what happens if DMA makes such a "shorter" access to PUSHR register.

0 Kudos

2,545 Views
christophredeck
Contributor II

Well my application is also sending a block of data to a read-only device, so your code will probably help. I also know about the fact that with SPI, each read must also be a write - I don't need MISO data though. I'll try to re-phrase my problem:

My source data is an array: uint8_t source_array[4096];

This array is supposed to be sent to an SPI device. Writing to an SPI device using the hardware chip select pins is tricky, as the chip select information goes into PUSHR along with the actual data. Each byte in my source_array must consequently be expanded to a 32-bit word. If I understand your code correctly, this is exactly what you are doing. If I wanted to do the same thing, my array would occupy 16KB of RAM (which is everything my controller has).

I also need to know when a transfer is finished, because I could deselect the chip too early: it would miss the last bytes which were already copied into PUSHR, but haven't been sent yet. The DMA transfer will be flagged as finished when the last word is in the FIFO, not when it has been shifted out.

0 Kudos