Go Down

Topic: SPI write with DMA (Read 8118 times) previous topic - next topic

mantoui

Aug 25, 2015, 10:49 pm Last Edit: Aug 26, 2015, 06:20 pm by mantoui
I have a proof of concept of using DMA to do a SPI block write on the ZERO.  I couldn't quite get the syntax right for the address of the SPI data register, so I did something ugly.  I haven't run it with a SPI device, but have hooked it up to logic analyzer to confirm SPI CLK and MOSI data.  Initial sketch is github.  It only uses one DMA channel to do the transmit.  You would need two channels for concurrent send/receive.  Data rate is close to SPI CLK speed. 

here are data rates (megabits/sec  mbs) for SPI.transfer() in a loop versus DMA transfer of 1024 bytes.
Code: [Select]
                SPI      DMA
SPI  clock    write
4MHz          1.7 mbs    3.99
8MHz          2.2 mbs    7.92
12MHz         2.5 mbs   11.8


For comparison, here are SPI+DMA results for other MCUs.


EDIT:
  Extended sketch to use two DMA channels, so you can do read, write, transfer.  github site updated.  Stable up to 8mhz, some strangeness at 12 mhz when mixing DMA SPI with standard SPI?  TODO.

david_prentice

Ah-ha.  I wondered what millibit-seconds would mean.   Megabits/sec are commonly called MegaBaud.

You should be able to get 7.11 Megabits/sec on a UNO's SPI peripheral.   And 8 Megabits/sec on the UNO's USART_MSPI peripheral.

You can see the effect of a software loop overhead. 

David.

dlabun

This is a great proof of concept for DMA. I found that the last few bits of each SPI transfer using DMA was getting cut off. I added a 25 micro Second delay before bringing the Slave Select high solved the issue. I take it up to a clock speed of 12MHz without any noticeable issues.

Code: [Select]

void spi_write(void *data,  size_t n) {
xtype = DoTX;
spi_xfr(data,rxsink,n);
  delayMicroseconds(25);
}

AloyseTech

@ dlabun : are you shure the problem doesn't come from your system? I use this code to transfer 256 bytes buffer in continuous and I miss no data. I've even checked with my scope to see what the signal is looking like.

david_prentice

The whole point of DMA is to handle the transfer without further assistance.
You can use the DMA_COMPLETE interrupt to disable your Chip-Select.
You can be using the CPU for unrelated processing.

Ok,   you know how long the transfer will be,    but simply delaying for this time is no better than polling the transfer in software without DMA.    You can not do anything else.

Incidentally,   the official SPI.transfer() method is cr*p.    The underlying SERCOM can achieve full speed SPI.   But there are a few "anomalies".

David.

dlabun

From what I can see on my scope the Slave Select is rising just slightly faster than the last bit ending. It's very close, but the Slave Select beats out the SPI transmission just about every time. It could merely be an electrical issue with my board.

Dave brings up valid points, I am going to see if I can implement using DMA_COMPLETE.

AloyseTech

Incidentally,   the official SPI.transfer() method is cr*p.    The underlying SERCOM can achieve full speed SPI.   But there are a few "anomalies".

David, do you mean that the Arduino SPI lib is the limiting element that constrain SPI to 12MHz, and that using sercom only, we can achieve higher speed?

david_prentice

Yes,  it is a problem with the SPI lib.    You would think that SPI.transfer(char *string) would work without gaps like Serial.write(char *string) does with the UART.

However,   the obvious solution does not seem to work properly.    There is something odd going on.     I will post a few examples later.

David.

AloyseTech

I have another question about this code : how to reset everything (DMA register) to initial state (state of register before use of this code), without resetting the board?
Maybe something like a DMA.end() function...

By the way, I'm using a library wrap up for this code, see attached document.

Sulimarco

Hi,

I think you can try this:


Code: [Select]
      DMAC->CTRL.bit.DMAENABLE = 0;                      // disable DMA controller
      DMAC->CTRL.bit.SWRST = 1;                          // reset all DMA registers
      while (DMAC->CTRL.bit.SWRST){};                    // wait for reset to complete
      DMAC->CTRL.bit.DMAENABLE = 1;                      // enable DMA controller



Marco

AloyseTech

Thanks you, I now have :

Code: [Select]
DMA::end(){
      DMAC->CTRL.bit.DMAENABLE = 0;                      // disable DMA controller
      DMAC->CTRL.bit.SWRST = 1;                          // reset all DMA registers
      while (DMAC->CTRL.bit.SWRST){};                    // wait for reset to complete
}


It works great :)

AvaGhafari

Hi,

I have a STM32F103, and I want to use it as an interfacing circuit to transfer the data of two SPI's to a computer via USB 2.
After my explorations, I found that it is not possible to transfer this amount of data( each SPI have a frequency about 2 MHz) without a DMA activation.
Anyone can help me? I don't know how to use DMA on STM at all:( I am even not familiar with the code of USB 2.

Looking forward for your answers,
Thanks in advance,
Ava

Go Up