arduinospidmaarduino-c++arduino-due

Send data through SPI with DMA


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'); }

  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 a necessity, the idea is to send data without length limit (unlike UART). I'm considering using SSC.


Solution

  • 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.