Here's the code:
// Use event system to use 62.5kHz PWM signal to trigger DMAC to send SPI ADC conversion
#include <SPI.h>
typedef struct // DMAC descriptor structure
{
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
// DMAC_CH_NUM = 12 channels on SAMD21
volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16))); // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16))); // Place holder descriptor
uint8_t data[2] = { 0x68, 0x77 }; // Dummy SPI command: 0x68 = Register address, 0x77 = Command
void setup()
{
SPI.begin();
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0)); // SPI at 10MHz
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
DMAC->BASEADDR.reg = (uint32_t)descriptor_section; // Set the descriptor section base address
DMAC->WRBADDR.reg = (uint32_t)wrb; // Set the write-back descriptor base adddress
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC and priority levels
DMAC->CHID.reg = DMAC_CHID_ID(0); // Select DMAC channel 0
// Set DMAC channel 0 to priority level 3 (highest), to trigger on TCC0 overflow and to trigger every beat
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(3) |
DMAC_CHCTRLB_TRIGACT_BEAT |
DMAC_CHCTRLB_EVIE |
DMAC_CHCTRLB_EVACT_TRIG;
DMAC->CHID.reg = DMAC_CHID_ID(1); // Select DMAC channel 1
// Set DMAC channel 1 to priority level 3 (highest), to trigger on TCC0 overflow and to trigger every beat
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(3) |
DMAC_CHCTRLB_TRIGACT_BEAT |
DMAC_CHCTRLB_EVIE |
DMAC_CHCTRLB_EVACT_TRIG;
descriptor.descaddr = (uint32_t)&descriptor_section[0]; // Circular descriptor
descriptor.srcaddr = (uint32_t)&data[0] + 1;
descriptor.dstaddr = (uint32_t)&SERCOM4->SPI.DATA.reg;
descriptor.btcnt = 1;
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_BYTE | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID;
memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));
descriptor.descaddr = (uint32_t)&descriptor_section[1]; // Circular descriptor
descriptor.srcaddr = (uint32_t)&data[1] + 1;
descriptor.dstaddr = (uint32_t)&SERCOM4->SPI.DATA.reg;
descriptor.btcnt = 1;
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_BYTE | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID;
memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Feed GCLK4 to TCC0 and TCC1
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
// Connect the TCC timers to the port outputs - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_DMAC_CH_0); // Set the event user (receiver) as DMAC channel 0
EVSYS->USER.reg = EVSYS_USER_CHANNEL(2) | // Attach the event user (receiver) to channel 1 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_DMAC_CH_1); // Set the event user (receiver) as DMAC channel 0
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TCC0_MCX_0) | // Set event generator (sender) as timer TCC0 match compare channel 0
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TCC0_MCX_1) | // Set event generator (sender) as timer TCC0 match compare channel 1
EVSYS_CHANNEL_CHANNEL(1); // Attach the generator (sender) to channel 0
TCC0->EVCTRL.reg |= TCC_EVCTRL_MCEO0 | // Enable the TCC0 match compare channel 0 output
TCC_EVCTRL_MCEO1; // Enable the TCC0 match compare channel 1 output
// Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
TCC0->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER register,
// this determines the frequency of the PWM operation:
TCC0->PER.reg = 767; // Set the frequency of the PWM on TCC0 to 62.5kHz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// The CCBx register value corresponds to the pulsewidth in microseconds (us)
TCC0->CC[2].reg = 384; // TCC0 CC2 - 50% duty cycle
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC0->CC[3].reg = 129; // TCC0 CC3 - set duty cycle
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
TCC0->DRVCTRL.reg |= TCC_DRVCTRL_INVEN7; // Invert CC3 = WO[7]
TCC0->CC[0].reg = 1; // TCC0 CC0 - set duty cycle
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CC[1].reg = 49; // TCC0 CC1 - set duty cycle
while (TCC0->SYNCBUSY.bit.CC1); // Wait for synchronization
// Divide the 16MHz signal by 1 giving 16MHz (62.5ns) TCC0 timer tick and enable the outputs
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1; // Divide GCLK4 by 1
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 timer
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
DMAC->CHID.reg = DMAC_CHID_ID(0); // Select DMAC channel 0
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable DMAC channel 0
DMAC->CHID.reg = DMAC_CHID_ID(1); // Select DMAC channel 1
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable DMAC channel 1
}
void loop() {}