3

I need to send data as fast as possible from an Arduino DUE to an extern DAC. To do so I use DMA & SPI and I want DMA to fetch data from the memory and send it to the SPI which will just relay it via its Master Output Slave input. So far I did a DMA transfer from a variable to another, woked perfectly. I'm using the same code but change the address as SPI_TDR (Transmit Data Register), unfortunatly it's not working. I guess the address is not good but if so what should I do ?

Here is my code :

#include <dmac.h>
#include <SPI.h>
#define DMA_CH 0 //N° Canal du DMA
#define DMA_BUF_SIZE 32 //Taille mémoire DMA
uint32_t g_dma_buf2[DMA_BUF_SIZE];
void setup() {
 Serial.begin(9600);
 SPI.begin();
 SPI0->SPI_WPMR = 0x53504900; //Protection key
 SPI0->SPI_IDR = 0x0000070F; //Desactivation interrupts
 SPI0->SPI_MR = SPI_MR_MSTR | SPI_MR_PS; //SPI master
}
void loop() {
 Serial.println("+++++");
 pmc_enable_periph_clk(ID_DMAC);
 uint32_t i;
 uint32_t cfg;
 dma_transfer_descriptor_t desc;
 for (i = 0; i < DMA_BUF_SIZE; i++) {
 g_dma_buf2[i] = i;
 Serial.print(g_dma_buf2[i]);
 }
 Serial.println();
 dmac_init(DMAC);
 dmac_set_priority_mode(DMAC, DMAC_PRIORITY_ROUND_ROBIN);
 dmac_enable(DMAC);
 cfg = DMAC_CFG_SOD_ENABLE | DMAC_CFG_AHB_PROT(1) | DMAC_CFG_FIFOCFG_ALAP_CFG; //Config registre CFG
 dmac_channel_set_configuration(DMAC, DMA_CH, cfg);
 desc.ul_source_addr = (uint32_t)g_dma_buf2;
 desc.ul_destination_addr = SPI0->SPI_TDR;
 desc.ul_ctrlA = DMAC_CTRLA_BTSIZE(DMA_BUF_SIZE) | DMAC_CTRLA_SRC_WIDTH_WORD | DMAC_CTRLA_DST_WIDTH_WORD;
 desc.ul_ctrlB = DMAC_CTRLB_SRC_DSCR_FETCH_DISABLE | DMAC_CTRLB_DST_DSCR_FETCH_DISABLE | DMAC_CTRLB_FC_MEM2MEM_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED;
 desc.ul_descriptor_addr = 0;
 SPI_Enable(SPI0);
 dmac_channel_multi_buf_transfer_init(DMAC, DMA_CH, &desc);
 dmac_channel_enable(DMAC, DMA_CH);
 Serial.println("*****");
 while (!dmac_channel_is_transfer_done(DMAC, DMA_CH)) { Serial.print('X'); }
Se
 Serial.print("SR : "); Serial.println(SPI0->SPI_SR, HEX);
 Serial.print("TDR : "); Serial.println(SPI0->SPI_TDR, HEX);
 Serial.print("PSR : "); Serial.println(PIOA->PIO_PSR, HEX); //PIO_SODR
 Serial.print("OSR : "); Serial.println(PIOA->PIO_OSR, HEX);
 Serial.println(DMAC->DMAC_CH_NUM[0].DMAC_SADDR , HEX);
 Serial.println(DMAC->DMAC_CH_NUM[0].DMAC_DADDR, HEX);
 Serial.println("-----");
}

I use mainly this example : https://ww1.microchip.com/downloads/en/Appnotes/Atmel-42291-SAM3A-3U-3X-4E-DMA-Controller-DMAC_ApplicationNote_AT07892.pdf#_OPENTOPIC_TOC_PROCESSING_d91e3076 And here is the datasheet of the μc : https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf

You can see at the bottom of my code sevral prints, from them I had many idea : does the PIO could block the data ? Does the address PIO_PA26A_SPI0_MOSI could work ? Could the SPI block the data because conditions are not met ?

Any idea is welcomed, I'm on this for some time now.

Edit : SPI is not an necessity, the idea is to send data without length limit (unlike UART). I'm considering using SSC.

asked Apr 18, 2023 at 6:35
1
  • I had a look at the block diagram (section 32.2) and I'm wondering if DMA really can address SPI or if I have to manage some layer in-between. Commented Apr 18, 2023 at 12:26

2 Answers 2

2

I managed to solve it but not with SPI, I use the SSC of the μcontroller (which does I2S and uses DMA automatically) :

uint32_t liste[] PROGMEM = { 0x8F66F1, 0x0, 0xAAAAAB }; //Data to send
#define DMA_BUF_SIZE (sizeof(liste) / sizeof(liste[0])) //Size DMA buffer
uint32_t i;
void setup() {
 uint clk_div = 0x90; //Clock divider wanted
 clk_div = (clk_div / 24) / DMA_BUF_SIZE;
 clk_div = floor(clk_div + 0.5);
 clk_div = clk_div * 24 * DMA_BUF_SIZE; //Clock divider adjusted -> MCK/2*clk_div
 PMC->PMC_WPMR = 0x504D4300; //Desactivation protection PMC
 PMC->PMC_PCER0 = (1 << ID_SSC); //Activation SSC clock
 PMC->PMC_SCER |= 0x100; //Activation clock
 PIOA->PIO_WPMR = 0x50494F00; //Desactivation protection Port I/O A
 PIOA->PIO_PDR = PIO_PDR_P14 | PIO_PDR_P15 | PIO_PDR_P16; //I/O controlled by the peripheral
 PIOA->PIO_ABSR |= PIO_PA14B_TK | PIO_PA15B_TF | PIO_PA16B_TD; //Assignation of I/O to SSC
 SSC->SSC_CR = SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST; //Desactivation and reset SSC
 SSC->SSC_WPMR = 0x53534300; //Desactivation protection SSC
 SSC->SSC_IDR = 0xFFFFFFFF; //Desactivation Interrupts
 SSC->SSC_IER = 0x00000000; //Desactivation Interrupts bis
 SSC->SSC_CMR = clk_div; //Clock management
 SSC->SSC_TFMR = SSC_TFMR_DATLEN(0x18) | SSC_TFMR_MSBF | SSC_TFMR_DATNB(0); //Data transfert management
 SSC->SSC_TCMR = SSC_TCMR_CKS_MCK | SSC_TCMR_CKO_CONTINUOUS | SSC_TCMR_START_CONTINUOUS | SSC_TCMR_STTDLY(0); //Clock during transfert management
 SSC->SSC_CR = SSC_CR_TXEN; //Activation transmission SSC
}
void loop() {
 for (i = 0; i < DMA_BUF_SIZE; i++) {
 ssc_write((Ssc*)SSC, (uint32_t)liste[i]); //Transmission SSC
 }
}

In case someone try the same thing.

answered Apr 26, 2023 at 7:47
0

I believe I have gotten this to work the way you intended, without SSI. Bits of this code have been borrowed from an SD Card library (https://github.com/openbci-archive/OpenBCI_8bit/blob/master/OpenBCI_8bit_SDfat_Library/SdFat/SdSpiSAM3X.cpp, around line 110). I am trying to run a TFT using a "framebuffer," but in order to do so I need DMA working with SPI.

In your original source, it appears you are missing a few things in cfg. According to the aforementioned code listing, cfg should look something like DMAC_CFG_DST_PER(1) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD_ENABLE | DMAC_CFG_AHB_PROT(1) | DMAC_CFG_FIFOCFG_ALAP_CFG. Per the datasheet, DST_PER must be set to one of the DMA channels seen on page 339, table 22-2. In this case, Channel 1 is used, as it corresponds to SPI0_TX. Likewise, you appear to be missing DMAC_CFG_DST_H2SEL for hardware handshaking. There's likely a few other things I'm not pointing out, as I had a fair amount of trouble with this too.

TL;DR The following code should do the trick for a basic DMA transfer from a buffer:

#include "sam.h"
#define CLOCK_SPEED 84000000
void setup_spi()
{
 PMC->PMC_PCER0 |= (1 << ID_PIOA); // Enable PIOA
 PIOA->PIO_ODR |= PIO_PA25; // MISO Pin - Input
 PIOA->PIO_PDR |= PIO_PA25;
 PIOA->PIO_ABSR &= ~(PIO_PA25); // MISO Pin - (A) Peripheral
 
 PIOA->PIO_OER |= PIO_PA26; // MOSI Pin - Output
 PIOA->PIO_PDR |= PIO_PA26;
 PIOA->PIO_ABSR &= ~(PIO_PA26); // MOSI Pin - (A) Peripheral
 PIOA->PIO_OER |= PIO_PA27; // SCLK Pin - Output
 PIOA->PIO_PDR |= PIO_PA27;
 PIOA->PIO_ABSR &= ~(PIO_PA27); // SCLK Pin - (A) Peripheral
 PIOA->PIO_OER |= PIO_PA28; // CS Pin 0 - Output
 PIOA->PIO_PDR |= PIO_PA28;
 PIOA->PIO_ABSR &= ~(PIO_PA28); // CS Pin 0 - (A) Peripheral
 PIOA->PIO_PUER |= PIO_PA28;
 
 // Disable the SPI to configure it
 SPI0->SPI_CR |= SPI_CR_SPIDIS;
 // SPI0 Setup --------------
 PMC->PMC_PCER0 = (1 << ID_SPI0); // Enable SPI0 Clock
 // Master Mode, Fixed Peripheral Mode, Mode fault detection disabled, Fixed peripheral modes
 SPI0->SPI_MR = SPI_MR_MSTR | SPI_MR_MODFDIS;
 
 // Mode and Clock Clock Freq Calculation
 SPI0->SPI_CSR[0] |= SPI_CSR_NCPHA | (((int8_t)(CLOCK_SPEED / 1000000)) << 8); // Need to stick clock calculation in byte 2
 
 // 8 bits per transfer
 SPI0->SPI_CSR[0] |= SPI_CSR_BITS_8_BIT;
 
 SPI0->SPI_CR |= SPI_CR_SPIEN; // Enable SPI0
}
uint8_t pixbuf[] = "This is a test!"; // Buffer to be transmitted
void setup_dma()
{
 PMC->PMC_PCER1 |= (1 << (ID_DMAC - 32)); // Turn on DMA Controller Clock (?)
 
 DMAC->DMAC_EN = 0; // Disable DMA Controller
 
 DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_ROUND_ROBIN; // Set arbitration method
 
 DMAC->DMAC_EN = 1; // Enable DMA Controller
 
 DMAC->DMAC_CHDR = DMAC_CHDR_DIS0; // Disable DMA Channel 0
 
 DMAC->DMAC_CH_NUM[0].DMAC_SADDR = (uint32_t)pixbuf; // Set src buffer
 DMAC->DMAC_CH_NUM[0].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; // Set dst buffer
 DMAC->DMAC_CH_NUM[0].DMAC_DSCR = 0x0;
 
 DMAC->DMAC_CH_NUM[0].DMAC_CTRLA = DMAC_CTRLA_DST_WIDTH_BYTE | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_BTSIZE(0x10); // Set src/dest bit width (8 bits) and set buffer size to 16
 DMAC->DMAC_CH_NUM[0].DMAC_CTRLB = DMAC_CTRLB_DST_INCR_FIXED | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC;
 
 DMAC->DMAC_CH_NUM[0].DMAC_CFG = DMAC_CFG_DST_PER(1) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD_ENABLE | DMAC_CFG_AHB_PROT(1) | DMAC_CFG_FIFOCFG_ALAP_CFG; // Configure DMA Controller
 
 DMAC->DMAC_EBCISR; // Read interrupt register to clear interrupt flags
 
 DMAC->DMAC_CHER = DMAC_CHER_ENA0; // Enable DMA Channel 0
}
int main(void)
{
 /* Initialize the SAM system */
 SystemInit();
 
 setup_dma();
 setup_spi();
 while (!(DMAC->DMAC_CHSR & DMAC_CHSR_ENA0 << 0));
 uint32_t dma_status;
 dma_status = DMAC->DMAC_EBCISR;
 //SPI0->SPI_TDR = 'a'; // Write dummy out
 
 /* Replace with your application code */
 while (1) 
 {
 }
}

Hope this helps.

answered Jan 11, 2024 at 0:26

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.