[Solved]SPI transfer using DMAC controller

Hi,

I want to do a MEM to Periph(SPI) single buffer transfer (no LLI/descriptor usage).

I have read Sam3x datasheet in details (SPI, DMAC & BUS MATRIX sections) but I still don't get the whole thing to work. DMAC is poorly documented :confused:

My code SPI is using "hardware Handshaking" mode. What I understood is that SPI is doing that through SPI_SR_TRDE & probably SPI_SR_RDRF as well.

Is it possible to only SEND data over one single DMAC channel (& SPI Transmit HW Interface) or is it required to open a second channel to collect data returned by SPI in SPI_RDR ?

As SPI is doing WRITE/READ through in a single transfer, maybe RDR has to be read. I tried both with no success.

It seems that the transfer never starts: (dataLen bytes at dataBlock address in memory)

sysclk_enable_peripheral_clock(ID_DMAC);
DMAC->DMAC_EN = ~DMAC_EN_ENABLE;
DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_ROUND_ROBIN;
DMAC->DMAC_EN = DMAC_EN_ENABLE;
DMAC->DMAC_EBCIER = DMAC_EBCIER_BTC0|DMAC_EBCIER_ERR0;		// Enable IRQ on Buffer Transfer Completed or on Access Error
NVIC_SetPriority(DMAC_IRQn, 0x00);				// Set IRQ TOP Priority
NVIC_EnableIRQ(DMAC_IRQn);					// Enable IRQ for PIOA in NVIC => will trigger DMAC_Handler(void)


DMAC->DMAC_CHDR = DMAC_CHDR_DIS0;

// Set the channel DMAC_CTRL A/B, CFG, ADDR ... (SRC:dataBlock, DST:@SPI0->SPI_TDR, 8 bits data, dataLen)
DMAC->DMAC_CH_NUM[0].DMAC_SADDR = (uint32_t)dataBlock;										// Source Addr
DMAC->DMAC_CH_NUM[0].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR;									// Destination Addr (SPI0 TDR address = 0x4000800c)
DMAC->DMAC_CH_NUM[0].DMAC_DSCR = 0;												// No Multi-Buffers (No Descriptor required)
DMAC->DMAC_CH_NUM[0].DMAC_CTRLA = DMAC_CTRLA_SRC_WIDTH_BYTE|DMAC_CTRLA_DST_WIDTH_BYTE|dataLen;
DMAC->DMAC_CH_NUM[0].DMAC_CTRLB = DMAC_CTRLB_SRC_INCR_INCREMENTING|DMAC_CTRLB_SRC_DSCR_FETCH_DISABLE|DMAC_CTRLB_DST_INCR_FIXED|DMAC_CTRLB_DST_DSCR_FETCH_DISABLE|DMAC_CTRLB_FC_MEM2PER_DMA_FC;
DMAC->DMAC_CH_NUM[0].DMAC_CFG = DMAC_CFG_DST_H2SEL_HW|DMAC_CFG_DST_PER(SPI0_TX_HWC)|DMAC_CFG_SOD_ENABLE|DMAC_CFG_FIFOCFG_ALAP_CFG;	// DST uses SPI0 TX interface, use IRQ (HW handshaking) and disable Channel after transfer ended

// Enable Channel "0"
DMAC->DMAC_CHER = DMAC_CHER_ENA0;

// Transfer supposed to start here ...
while((DMAC->DMAC_CHSR & DMAC_CHSR_ENA0) != 0); <---- stuck here
// ... and be achieved here

Am I missing something to make it work ?

Thanks !

Well, I solved the issue.

Was due to my SPI configuration. The behavior depends on the SPI_MR_WDRBT flag of SPI_MR.
This bit is a RDR overrun protection and block any data in SPI_TDR to be loaded in the shift register as long as RDR is not read.

WDRBT = 0
No need to fetch the data returned by SPI when transfer starts sending a buffer

WDRBT = 1
SPI_RDR has to be read:

  • or by doing a SPI_RDR read in the active wait loop (but I don't recommend active wait)
  • or by initiating a second DMAC channel from Periph (SPI_RDR addr) to memory. Can write at the same memory address (= INCR_FIXED) instead of allocating a buffer which will be filled with dummy data (or maybe replace datas in the sending buffer)