The DMA controller rewrittes the first position of the buffer which holds the values of the first transfer with the values of the second transfer. After that it starts to work correctly.
Also the variable that holds the position of the last transmission does not update correctly.
I have configured the DMA to save the incoming data from the SPI MOSI (I am the slave) into a circular buffer of 500 bytes. SPI transmissions come in 50-byte frames so that is the size I have configured the descriptor to handle and thus the size of a burst transmission.
On each transmission complete interrupt I update the descriptor so that the value of the destination address is increased by 50 bytes (taking into account that it is a circular buffer).
I get the expected behaviour except for the second transmission, which rewrittes the first 50 bytes of the buffer corresponding with the first transmission. After that there is no more rewriting and the system behaves as expected.
Also, with the system behaving correctly, the value of the variable I use to keep track of the last position written dmaBufferPosition
, which is used for data processsing gives a value that is 50-100 bytes higher than the actual value being written.
Please note that these DMA controllers use reverse access, so that if we want to write in position 0 we provide in the descriptor the size of the transfer (50) and the “last” destination adress (also 50)
#include "dma_transfer.h"
#include "sam.h"
#include "GlobalConfig.h"
#define PORTA 0
volatile uint8_t circular_buffer[DMA_BUFFER_SIZE];
volatile uint16_t dmaBufferPosition = DMA_BURST_SIZE;
volatile DmacDescriptor dmaDescriptorArray[1] __attribute__ ((aligned (16)));
volatile DmacDescriptor dmaDescriptorWritebackArray[1] __attribute__ ((aligned (16)));
void configure_spi_slave() {
// Configure SERCOM1 Pins for SPI Slave
// PA16 (MOSI), PA17 (SCK), PA18 (SS), PA19 (MISO)
// Set PA16, PA17, PA18, PA19 to peripheral function C (SERCOM1)
//PORT->Group[0].PINCFG[16].reg = PORT_PINCFG_PMUXEN; // PA16 MOSI
//PORT->Group[0].PMUX[8].bit.PMUXE = PORT_PMUX_PMUXE_C; // SERCOM1 PAD[0]
PORT->Group[PORTA].PINCFG[16].bit.PMUXEN = 0x1; // Enable Peripheral Multiplexing for SERCOM1 SPI PA16, Arduino PIN11
PORT->Group[PORTA].PMUX[8].bit.PMUXE = 0x2; // SERCOM1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
//PORT->Group[0].PINCFG[17].reg = PORT_PINCFG_PMUXEN; // PA17 SCK
//PORT->Group[0].PMUX[8].bit.PMUXO = PORT_PMUX_PMUXO_C; // SERCOM1 PAD[1]
// Set PA17 as input (SCK) 1 PA17 corresponds to: PORTA, PMUX[8], Odd
PORT->Group[PORTA].PINCFG[17].bit.PMUXEN = 0x1; // Enable Peripheral Multiplexing for SERCOM1 SPI PA17, Arduino PIN13
PORT->Group[PORTA].PMUX[8].bit.PMUXO = 0x2; // SERCOM1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
//PORT->Group[0].PINCFG[18].reg = PORT_PINCFG_PMUXEN; // PA18 SS
//PORT->Group[0].PMUX[9].bit.PMUXE = PORT_PMUX_PMUXE_C; // SERCOM1 PAD[2]
// Set PA18 as input (SS) 2 PA18 corresponds to: PORTA, PMUX[9], Even
PORT->Group[PORTA].PINCFG[18].bit.PMUXEN = 0x1; // Enable Peripheral Multiplexing for SERCOM1 SPI PA18, Arduino PIN10
PORT->Group[PORTA].PMUX[9].bit.PMUXE = 0x2; // SERCOM1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
//PORT->Group[0].PINCFG[19].reg = PORT_PINCFG_PMUXEN; // PA19 MISO
//PORT->Group[0].PMUX[9].bit.PMUXO = PORT_PMUX_PMUXO_C; // SERCOM1 PAD[3]
// Set PA19 as output (MISO) 3 PA19 corresponds to: PORTA, PMUX[9], Odd
PORT->Group[PORTA].PINCFG[19].bit.PMUXEN = 0x1; // Enable Peripheral Multiplexing for SERCOM1 SPI PA19, Arduino PIN12
PORT->Group[PORTA].PMUX[9].bit.PMUXO = 0x2; // SERCOM1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
// Disable SPI 1
SERCOM1->SPI.CTRLA.bit.ENABLE = 0; // page 481
while (SERCOM1->SPI.SYNCBUSY.bit.ENABLE); // Wait until bit is enabled.
// Enable SERCOM1 and Port Bus Clocks
// Reset SPI 1
SERCOM1->SPI.CTRLA.bit.SWRST = 1; // page 481
while (SERCOM1->SPI.CTRLA.bit.SWRST || SERCOM1->SPI.SYNCBUSY.bit.SWRST); // Wait until software reset is complete.
//PM->APBCMASK.reg |= PM_APBCMASK_SERCOM1;
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(SERCOM1_GCLK_ID_CORE) |
GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_CLKEN;
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); // Wait for synchronisation
// Set up SPI control A register
SERCOM1->SPI.CTRLA.bit.DORD = 0; // MSB is transferred first. // page 492
SERCOM1->SPI.CTRLA.bit.CPOL = 0; // SCK is low when idle. The leading edge of a clock cycle is a rising edge, while the trailing edge is a falling edge. // page 492
SERCOM1->SPI.CTRLA.bit.CPHA = 0; // Data is sampled on a leading SCK edge and changed on a trailing SCK edge. // page 492
SERCOM1->SPI.CTRLA.bit.FORM = 0x0; // SPI frame // page 493
SERCOM1->SPI.CTRLA.bit.DIPO = 0x0; // DATA PAD0 is used as slave input: MOSI // (slave mode) page 493
SERCOM1->SPI.CTRLA.bit.DOPO = 0x2; // DATA PAD2 is used as slave output: MISO // (slave mode) page 493
SERCOM1->SPI.CTRLA.bit.MODE = 0x2; // SPI slave operation. // page 494
SERCOM1->SPI.CTRLA.bit.IBON = 0x1; // Immediate Buffer Overflow Notification. STATUS.BUFOVF is asserted immediately upon buffer overflow. // page 494
SERCOM1->SPI.CTRLA.bit.RUNSTDBY = 1; // Wake on Receive Complete interrupt. // page 494
// Set up SPI control B register
//SERCOM1->SPI.CTRLB.bit.RXEN = 0x1; // Enable Receiver // page 496
SERCOM1->SPI.CTRLB.bit.SSDE = 0x1; // Enable Slave Select Low Detect // page 497
SERCOM1->SPI.CTRLB.bit.CHSIZE = 0; // Character Size 8 bits // page 497
//SERCOM1->SPI.CTRLB.bit.PLOADEN = 0x1; // Enable Slave Data Preload // page 497
//while (SERCOM1->SPI.SYNCBUSY.bit.CTRLB); // Wait until receiver is enabled
/*// Set up SPI interrupts
SERCOM1->SPI.INTENSET.bit.SSL = 0x1; // Enable Slave Select Low interrupt. // page 501
SERCOM1->SPI.INTENSET.bit.RXC = 0x1; // Enable Receive Complete interrupt. // page 501
SERCOM1->SPI.INTENSET.bit.TXC = 0x1; // Enable Transmit Complete interrupt. // page 501
SERCOM1->SPI.INTENSET.bit.ERROR = 0x1; // Enable Error interrupt. // page 501
SERCOM1->SPI.INTENSET.bit.DRE = 0x1; // Enable Data Register Empty interrupt. // page 501*/
// Enable SPI
SERCOM1->SPI.CTRLA.bit.ENABLE = 1; // page 481
while (SERCOM1->SPI.SYNCBUSY.bit.ENABLE); // Wait until bit is enabled.
SERCOM1->SPI.CTRLB.bit.RXEN = 0x1; // Enable Receiver // page 496. This is done here rather than in section "Set up SPI control B register" due to an errate issue.
while (SERCOM1->SPI.SYNCBUSY.bit.CTRLB); // Wait until receiver is enabled.
}
// Set up the DMA dmaDescriptorArray[]
void configure_dma_descriptor() {
dmaDescriptorArray[0].BTCTRL.bit.VALID = 1; // Descriptor is valid
dmaDescriptorArray[0].BTCTRL.bit.EVOSEL = 0; // No event output
dmaDescriptorArray[0].BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_INT_Val; // Interrupt after block transfer
dmaDescriptorArray[0].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_BYTE_Val; // 8-bit beats
dmaDescriptorArray[0].BTCTRL.bit.SRCINC = 0; // Source does not increment (fixed)
dmaDescriptorArray[0].BTCTRL.bit.DSTINC = 1; // Destination increments (circular buffer)
dmaDescriptorArray[0].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_DST_Val; // Step size applied to destination
dmaDescriptorArray[0].BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val; // Step size of 1
dmaDescriptorArray[0].BTCNT.reg = DMA_BURST_SIZE;
dmaDescriptorArray[0].SRCADDR.reg = (uint32_t)&SERCOM1->SPI.DATA.reg; // Source address (SPI DATA register)
dmaDescriptorArray[0].DSTADDR.reg = (uint32_t)circular_buffer + dmaBufferPosition; // Destination address + buffer size (for reverse access)
dmaDescriptorArray[0].DESCADDR.reg = (uint32_t)&dmaDescriptorArray[0]; // Next dmaDescriptorArray[] address (self-reload)
}
void configure_dma() {
// Enable the DMAC controller
PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
// Reset the DMA controller
DMAC->CTRL.reg = DMAC_CTRL_SWRST;
// Configure the DMA channel (e.g., channel 0)
DMAC->CHID.reg = DMAC_CHID_ID(0); // Select channel 0
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST; // Reset the channel
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_TCMPL; //Enable transfer complete interrupt
// Assign the dmaDescriptorArray[] to the DMA channel
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | // Priority level 0
DMAC_CHCTRLB_TRIGSRC(SERCOM1_DMAC_ID_RX) | // Trigger on SERCOM1 RX
DMAC_CHCTRLB_TRIGACT_BEAT; // Trigger action: beat
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_ENABLE; // Enable the DMA channel
DMAC->BASEADDR.reg = (uint32_t)&dmaDescriptorArray[0]; // Set the base dmaDescriptorArray[] address
DMAC->WRBADDR.reg = (uint32_t)&dmaDescriptorWritebackArray[0]; // Write-back memory section address
// Enable the DMA controller
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMA controller
}
void dma_init() {
configure_dma_descriptor();
configure_dma();
NVIC_EnableIRQ(DMAC_IRQn);
NVIC_SetPriority(DMAC_IRQn, 1);
}
// DMA interrupt handler for handling 50-byte chunk completion
void DMAC_Handler(void) {
// Check if Transfer Complete interrupt occurred for channel 0
if (DMAC->CHINTFLAG.bit.TCMPL) {
// Clear the interrupt flag
DMAC->CHID.reg = DMAC_CHID_ID(0); // Select Channel 0
DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; // Clear Transfer Complete interrupt flag
// Reset DMA descriptor for the next 50-byte block
dmaDescriptorArray[0].BTCNT.reg = DMA_BURST_SIZE; // Set the next block transfer size to 50 bytes
dmaBufferPosition = (dmaBufferPosition % DMA_BUFFER_SIZE) + DMA_BURST_SIZE; //Move position by 50 bytes
dmaDescriptorArray[0].DSTADDR.reg = (uint32_t)(circular_buffer) + dmaBufferPosition;// The destination address wraps within the 500-byte buffer
}
}```