[Solved] Problem on SPI slave transfer using DMA on Arduino DUE

Hi everyone,

I try to establish SPI communication using DMA between an Arduino Uno (Master) and an Arduino Due (Slave). I need DMA to accelerate the processing time and respect the timings I need (6 frames of 4 bytes every 1.25ms). The request frames send by the master work well and respect timings. The Due, configured as a slave, receives well all the 6 frames but doesn't transmit answer frames well.

The SPI (slave) is configure as follows (set in setup) :

void SPI_configure()
{  SPI.begin(SS);
  
  REG_SPI0_CR = SPI_CR_SPIDIS;      //Disable SPI
  REG_SPI0_CR = SPI_CR_SWRST;     // reset SPI
// Setup interrupt
  REG_SPI0_IDR = SPI_IDR_TDRE | SPI_IDR_MODF | SPI_IDR_OVRES | SPI_IDR_NSSR | SPI_IDR_TXEMPTY | SPI_IDR_UNDES;
  REG_SPI0_IER = SPI_IER_RDRF;

  // Setup the SPI registers.
  REG_SPI0_CR = SPI_CR_SPIEN;     // enable SPI
  REG_SPI0_MR = SPI_MR_MODFDIS;     // slave and no modefault
  REG_SPI0_CSR = SPI_CSR_NCPHA | SPI_CSR_BITS_16_BIT;    // DLYBCT=0, DLYBS=0, SCBR=0, 16 bits transfer

  pmc_enable_periph_clk(ID_SPI0);
}

The DMA is first configure as follow (in Setup) :

void DMAC_configure()
{
 
  
  pmc_enable_periph_clk(ID_DMAC);       // Enable clock
//  REG_PMC_PCER1 |= PMC_PCER1_PID39;
  REG_DMAC_WPMR = DMAC_WPMR_WPEN | DMAC_WPMR_WPKEY(0x50494F);   // Write protection
  
  
  DMAC_Disable();
  REG_DMAC_GCFG = 0x10; //Round-Robin
  DMAC_Enable();
  
  
  REG_DMAC_CFG1 = DMAC_CFG_SRC_PER(SPI_RX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; //Configuration de Rx
  REG_DMAC_CFG0 = DMAC_CFG_SRC_PER(SPI_TX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; //Configuration de Rx
  
  
  NVIC_SetPriority(DMAC_IRQn, 0x00);
  NVIC_EnableIRQ(DMAC_IRQn);
  DMAC_enableInterrupt(1 << SPI_DMAC_RX_CH);

}

Then, at the end of SPI_configure() function, a call to SPI_DMA_TransReceive() function initializes the first transfer :

void SPI_DMA_TransReceive(uint16_t * srcTX, uint16_t * dstRX, uint16_t count)
{
  // start of TX DMA configuration  
  REG_DMAC_EBCISR;
  static uint16_t ff = 0xFFFF;
  uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING;
  if(!srcTX){
    srcTX = &ff;
    src_incr = DMAC_CTRLB_SRC_INCR_FIXED;
  }
 
  DMAC_channelDisable(SPI_DMAC_TX_CH);
  
  REG_DMAC_EBCISR;
  REG_DMAC_SADDR0 = (uint32_t)srcTX;

  REG_DMAC_DADDR0 = (uint32_t)& REG_SPI0_TDR;
  REG_DMAC_DSCR0 = 0;
  REG_DMAC_CTRLA0 = 
    count |           // data amount to transmit
    DMAC_CTRLA_SRC_WIDTH_HALF_WORD |  // * the transfer size is set to 16-bit width
    DMAC_CTRLA_DST_WIDTH_HALF_WORD; // * the transfer size is set to 16-bit width
  
  REG_DMAC_CTRLB0 = 
    DMAC_CTRLB_SRC_DSCR |     // brief (DMAC_CTRLB) Source Address Descriptor
    DMAC_CTRLB_DST_DSCR |     // brief (DMAC_CTRLB) Destination Address Descriptor 
    DMAC_CTRLB_FC_MEM2PER_DMA_FC |          // Memory-to-Peripheral Transfer DMAC is flow controller
    DMAC_CTRLB_SRC_INCR_INCREMENTING  |     // * Incremented source address
    DMAC_CTRLB_DST_INCR_FIXED;              // The destination address remains unchanged
 

  
  REG_DMAC_CFG0 = 
    DMAC_CFG_DST_PER(SPI_TX_IDX) |  // Destination with Peripheral identifier (SPI_TX_IDX)
    DMAC_CFG_DST_H2SEL_HW |        // S/H.ware Handshaking Selection for the Destination
   // DMAC_CFG_SOD |        // Stop On Done
    DMAC_CFG_FIFOCFG_ALAP_CFG;  // The largest defined length AHB burst is performed on the destination AHB interface.
  // end of TX DMA configuration
  
  DMAC_channelEnable(SPI_DMAC_TX_CH);
  
  
  // start of RX DMA configuration
  DMAC_channelDisable(SPI_DMAC_RX_CH);
  REG_DMAC_EBCISR;
  REG_DMAC_SADDR1 = (uint32_t)& REG_SPI0_RDR; // source = Receive Data Reg
  REG_DMAC_DADDR1 = (uint32_t)dstRX;// * Destination = dst(memory)
  REG_DMAC_DSCR1 = 0;// buffer transfer descriptor address
  
  REG_DMAC_CTRLA1 = 
    count |           // data amounut to receive
    DMAC_CTRLA_SRC_WIDTH_HALF_WORD |  // the transfer size is set to 16-bit width
    DMAC_CTRLA_DST_WIDTH_HALF_WORD; // the transfer size is set to 16-bit width
    
  REG_DMAC_CTRLB1 = 
    DMAC_CTRLB_SRC_DSCR |     // brief (DMAC_CTRLB) Source Address Descriptor
    DMAC_CTRLB_DST_DSCR |     // brief (DMAC_CTRLB) Destination Address Descriptor 
    DMAC_CTRLB_FC_PER2MEM_DMA_FC |// Peripheral-to-Memory Transfer DMAC is flow controller
    DMAC_CTRLB_SRC_INCR_FIXED |   // The source address remains unchanged
    DMAC_CTRLB_DST_INCR_INCREMENTING;//The destination address is incremented
    
  REG_DMAC_CFG1 = 
    DMAC_CFG_SRC_PER(SPI_RX_IDX) |  // Source with Peripheral identifier (SPI_RX_IDX)
    DMAC_CFG_SRC_H2SEL |      // S/H.ware Handshaking Selection for the Source
    DMAC_CFG_SOD |        // Stop On Done
    DMAC_CFG_FIFOCFG_ASAP_CFG;  // When there is enough space/data available to perform-
                  // -a single AHB access, then the request is serviced.
  // end of RX DMA configuration
  
  DMAC_channelEnable(SPI_DMAC_RX_CH);
  
  
  NVIC_ClearPendingIRQ(DMAC_IRQn);
  NVIC_SetPriority(DMAC_IRQn, 1);
  NVIC_EnableIRQ(DMAC_IRQn);

  
}

My DMA_IRQ is :

#define NB_TRANSFERT 12;     //2 transfer 16 bits per frame of 32 bits : 2*6 frames
void DMAC_Handler(void)
{
  static uint32_t dmaStatus = DMAC_getStatus();
  if(dmaStatus & (1<< SPI_DMAC_RX_CH))
  {
      receipt = true;    //Flag for frame processing
      SPI_DMA_TransReceive(bufTx, bufRx,NB_TRANSFERT);
  }
}

The processing loop is very simple, it only prepares answer frames depending on what the slave receives. But the problem is not related to the treatment in the loop. Indeed, the transfer via SPI_TDR is done badly even if the buffer bufTx is not modified. The bufTx contains :
uint16_t bufTx[12]={0x1234,0x5678,0x9ABC,0xDEFF,0xEDCB,0xA987,0x6543,0x2111, 0x2222, 0x3333, 0x4444, 0x5555};

But I receive on the arduino Uno :

image

instead of :

12345678
9ABCDEFF
EDCBA987
65432111
22223333
44445555 

[...]

12345678
9ABCDEFF
EDCBA987
65432111
22223333
44445555 

I hope you have understand my problem and can help me to fix it. I really want to make it works well.
If you need more precision, don't hesitate to ask.

eloisedlme

Solution : I've finally found a solution to my problem. I've just replace the SPI_TX_IDX value used to specify the DST_PER (Destination with peripheral identifier) by the value of SPI_RX_IDX. Then, the two channels "look" at the same peripheral (not sure about this explanation).

This library might help you out:

Thank you for your fast answer. I have already seen this library and was inspired by it to set up my SPi communication using DMA. I will test using it directly.
If you think about another solution, don't hesitate :slight_smile:

IMO your code never goes into DMA_Handler() interrupt routine. DMAC_EBCIER should be set beforehand in setup().

I have provided an example sketch for an USART transfer with AHB DMA using a single Arduino DUE board. You will see how DMAC peripheral should be triggered, the same operation applies to SPI.

/****************************************************************************/
/*        Echo USART1 with AHB DMA for reception and transmission           */
/*  Hook a jumper between RX1 and TX2 and another one between RX2 and TX1   */
/****************************************************************************/

#define DMAC_CH 0
extern void USART1_Handler(void); 
/*
 ***********   in variant.h  *****************
  void USART1_Handler(void) __attribute__((weak));
  void USART1_Handler(void)
  {
  Serial2.IrqHandler();
  }
*/
char c = 0;
void setup() {
  Serial.begin(250000);
  Serial1.begin(250000);

  // Handy to initialize some USART1 registers
  Serial2.begin(250000); // USART1 init

  PMC->PMC_PCER1 |= PMC_PCER1_PID39; // DMAC power ON
  DMAC->DMAC_EN = DMAC_EN_ENABLE;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DSCR = 0;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_SADDR = (uint32_t)&USART1->US_RHR;
  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DADDR = (uint32_t)&USART1->US_THR;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLA = DMAC_CTRLA_BTSIZE(16) |
                                          DMAC_CTRLA_SRC_WIDTH_BYTE |
                                          DMAC_CTRLA_DST_WIDTH_BYTE;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLB = DMAC_CTRLB_FC_PER2PER_DMA_FC |
                                          DMAC_CTRLB_SRC_INCR_FIXED |
                                          DMAC_CTRLB_DST_INCR_FIXED;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CFG = DMAC_CFG_SRC_H2SEL_HW |
                                        DMAC_CFG_DST_H2SEL_HW |
                                        DMAC_CFG_SRC_PER(14) |
                                        DMAC_CFG_DST_PER(13) |
                                        DMAC_CFG_SOD_DISABLE |
                                        DMAC_CFG_FIFOCFG_ASAP_CFG;

  DMAC->DMAC_EBCIER = DMAC_EBCIER_CBTC0 << DMAC_CH;
  NVIC_EnableIRQ(DMAC_IRQn);

  NVIC_DisableIRQ(USART1_IRQn);
  DMAC->DMAC_CHER = DMAC_CHER_ENA0 << DMAC_CH;

  Serial1.print("**** Hello World ****");
}

void loop() {

  String s;
  s = "";

  while (Serial1.available() > 0) {
    c = Serial1.read();
    s += c;

  }
  if (s.length() > 0) {
    Serial.println(s);
    Serial1.print(s);
  }
  delay(1000);
}

void DMAC_Handler(void)
{
 DMAC->DMAC_EBCISR;  // Read and clear status register

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLA &= ~(uint32_t)DMAC_CTRLA_BTSIZE_Msk;
  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLA |= DMAC_CTRLA_BTSIZE(16);

  DMAC->DMAC_CHER = DMAC_CHER_ENA0 << DMAC_CH;
 
}

I checked if my code goes in the DMAC_Handler() routine and it is. I set DMAC_EBCIER in my function DMAC_configure() but the problem persist. It seems that my configuration is the same as yours (adapted to SPI and my application).

I try to remove the interrupt routine of DMA and check if the transfer is done directly in the loop (using a if(condition)). Also, I remove the receipt of data from master because it work well. Unfortunately, the transfer from Due to Uno is not working well at all.

Sometimes, some datas are transmit twice. It looks like sometimes the DMA doesn't write the data in SPI_TDR so the same data is sent again.

Have you already heard about this kind of problem ? I asked myself if it could be a SAM3x hardware problem

eloisedlme

AFAIK DMA works properly.

For debugging, I suggest firstly that you write a full SPI working code without the AHB DMA until your data transfer between DUE and UNO is correct.

Another DMA Transfer code with SPI:

The SPI only (without using DMA) works perfectly. The transfer doesn't work only when I add DMA.

Thank you for the other example but I've seen it already and didn't find how to fix my problem with it.

It really seems like the DMA sometimes doesn't transfer data to the SPI_TDR register as it should be.

On the master side, instead of receiving for example 0x1111 0x2222 0x3333 0x4444, I'm receiving 0x1111 0x2222 0x2222 0x3333.

So the SPI and the DMA are still "working" but not quite perfectly.

Here is now my code without using DMA interrupts and only focusing on DMA Tx Channel since Rx Channel works perfectly :

void setup(){
  
   PIO_Configure(
   g_APinDescription[PIN_SPI_MOSI].pPort,
    g_APinDescription[PIN_SPI_MOSI].ulPinType,
    g_APinDescription[PIN_SPI_MOSI].ulPin,
    g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration);
  PIO_Configure(
    g_APinDescription[PIN_SPI_MISO].pPort,
    g_APinDescription[PIN_SPI_MISO].ulPinType,
    g_APinDescription[PIN_SPI_MISO].ulPin,
    g_APinDescription[PIN_SPI_MISO].ulPinConfiguration);
  PIO_Configure(
    g_APinDescription[PIN_SPI_SCK].pPort,
    g_APinDescription[PIN_SPI_SCK].ulPinType,
    g_APinDescription[PIN_SPI_SCK].ulPin,
    g_APinDescription[PIN_SPI_SCK].ulPinConfiguration);

  pmc_enable_periph_clk(ID_SPI0);
  
  SPI0->SPI_CR = SPI_CR_SPIDIS;
  SPI0->SPI_CR = SPI_CR_SWRST;     // reset SPI

  // Setup the SPI registers.
  REG_SPI0_MR = SPI_MR_MODFDIS;     // slave and no modefault
  REG_SPI0_CSR = SPI_CSR_NCPHA | SPI_CSR_BITS_16_BIT;    // DLYBCT=0, DLYBS=0, SCBR=0, 8 bit transfer
 
  REG_SPI0_CR  |= SPI_CR_SPIEN;     // enable SPI

  
  pmc_enable_periph_clk(ID_DMAC);       // Enable clock
  REG_DMAC_WPMR = DMAC_WPMR_WPEN | DMAC_WPMR_WPKEY(0x50494F);   // Enlever la protection par l'écriture de certains registres
  
  DMAC_Disable();
  REG_DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED; //Arbitrary function : fixed
  DMAC_Enable();
  REG_DMAC_CFG1 = DMAC_CFG_SRC_PER(SPI_RX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; //Configuration de Rx
  REG_DMAC_CFG0 = DMAC_CFG_SRC_PER(SPI_TX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; //Configuration de Rx

  
  DMAC_channelDisable(SPI_DMAC_TX_CH);

  REG_DMAC_CTRLA0 = 8u | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE;

  REG_DMAC_SADDR0 = (uint32_t)&bufTx[0];
  REG_DMAC_DADDR0 = (uint32_t)&REG_SPI0_TDR;
  REG_DMAC_SADDR0 = (uint32_t)&bufTx[0];
  
  REG_DMAC_DSCR0 = 0;
  REG_DMAC_CTRLB0 = DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED;
  REG_DMAC_CFG0 = DMAC_CFG_DST_PER(SPI_TX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG;
  DMAC_channelEnable(SPI_DMAC_TX_CH);
}
void loop(){

  if (DMAC_channelTransferDone(SPI_DMAC_TX_CH)){
      
    DMAC_channelDisable(SPI_DMAC_TX_CH);

    REG_DMAC_CTRLA0 = 8u | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE;
  
    REG_DMAC_SADDR0 = (uint32_t)&bufTx[0];
    REG_DMAC_DADDR0 = (uint32_t)&REG_SPI0_TDR;
    REG_DMAC_SADDR0 = (uint32_t)&bufTx[0];
    
    REG_DMAC_DSCR0 = 0;
    REG_DMAC_CTRLB0 = DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED;
    REG_DMAC_CFG0 = DMAC_CFG_DST_PER(SPI_TX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG;
    DMAC_channelEnable(SPI_DMAC_TX_CH);
  }
  
}

The transfer size X (DMAC_CTRLA_BTSIZE(X)) relates to the number of transfers to be performed, that is, for writes it refers to the number of source width transfers (you selected DMAC_CTRLA_SRC_WIDTH_BYTE) to perform when DMAC is flow controller. Is 8u correct in your example sketch?

Yes it is for this simplified code. Originally, I wanted 12 transfers of 2 bytes width (as it was in my settings of the first post). Even with 2 transfers of 1 byte width, data are not send correctly.