I have a project where I need to store data (~16 Mo) on a memory and be able to fetch the data fast (<36000 bits/s) with DMA because I have other signals to take care of.
Currently I use an SD card with the SD library, but it doesn't seems to use DMA because of the delay between sending.
Many people told me it shouldn't be that hard and I suppose so. My guess is that I can't configure the DMA and/or the SPI correctly.
I tried this a long time ago.
Can someone show me an example of SPI with DMA, or tips on how to configure both?
1 Answer 1
#include <dmac.h>
#include <SPI.h>
#include <Arduino.h>
#include <sam.h>
uint8_t sourceBuffer[256];
void setup() {
SPI.begin();
SPI.setClockDivider(21); // 4MHz clock (assuming 84MHz system clock)
setupDMA();
}
void loop() {
for(int i = 0; i < sizeof(sourceBuffer); i++) {
sourceBuffer[i] = i;
}
startDMATransfer();
delay(1000);
}
void setupDMA() {
// Enable the DMA controller
PMC->PMC_PCER1 |= PMC_PCER1_PID34; // DMA ID for the Due is 34
// Disable SPI DMA TX and RX
SPI0->SPI_PTCR = SPI_PTCR_RXTDIS | SPI_PTCR_TXTDIS;
// Setup a DMA channel for the transfer (channel 1 for this example)
DmacChannel* channel = &(DMAC->DMAC_CH_NUM[1]);
// Disable the DMA channel
channel->DMAC_CHDR = DMAC_CHDR_DIS;
// Clear any flags and reset the status
channel->DMAC_CHDR = DMAC_CHDR_RES;
// Set up source and destination pointers
channel->DMAC_SADDR = (uint32_t) sourceBuffer;
channel->DMAC_DADDR = (uint32_t) &(SPI0->SPI_TDR);
// Configure source and destination pointers: source increments, destination does not
channel->DMAC_CTRLA = DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE;
// Set source and destination handshake (related to the SPI0 Tx)
channel->DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR_FETCH_DISABLE | DMAC_CTRLB_DST_DSCR_FETCH_DISABLE |
DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING |
DMAC_CTRLB_DST_INCR_FIXED;
channel->DMAC_CFG = DMAC_CFG_SRC_PER(1) | DMAC_CFG_DST_PER(1) | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG;
}
void startDMATransfer() {
// Assuming channel 1 is used
DmacChannel* channel = &(DMAC->DMAC_CH_NUM[1]);
// Set up the buffer size
channel->DMAC_CUBC = sizeof(sourceBuffer);
// Enable SPI DMA for TX
SPI0->SPI_PTCR = SPI_PTCR_TXTEN;
// Enable the DMA channel to start the transfer
channel->DMAC_CHER = DMAC_CHER_ENA;
}
When you want to send data via SPI using DMA, you can fill the sourceBuffer
and call startDMATransfer()
.
Remember to always refer to the Atmel SAM3X8E datasheet and the Arduino Due's schematic when you are working at this level, as it provides in-depth details on how the hardware functions. I am unable to run the code or test it. But I believe that the code logic is proper. Error handling is up to you.