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.

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.
    – Vlad
    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 I²S 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.

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.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.