Hi n8ur,
The easiest way to implement a sine look-up table, is to use the SAMD21's DMAC (Direct Memory Access Controller) to fetch the data from the table and place it in the timer's CC (Counter Compare) register each time an overflow occurs. Using the DMAC means that it doesn't take up CPU resources moving the table data.
Here's some example code:
// Direct Digital Synthesis - generate 100Hz sine wave from 25kHz PWM signal plus RC LPF on output
// Sine table
volatile const uint16_t sintable[256] = {
512,524,537,549,562,574,587,599,611,624,636,648,660,672,684,696,707,719,730,741,753,764,774,785,796,806,816,826,836,846,855,864,873,882,890,899,907,915,922,930,
937,944,950,957,963,968,974,979,984,989,993,997,1001,1004,1008,1011,1013,1015,1017,1019,1021,1022,1022,1023,1023,1023,1022,1022,1021,1019,1017,1015,1013,1011,1008,1004,1001,997,993,989,
984,979,974,968,963,957,950,944,937,930,922,915,907,899,890,882,873,864,855,846,836,826,816,806,796,785,774,764,753,741,730,719,707,696,684,672,660,648,636,624,
611,599,587,574,562,549,537,524,512,499,486,474,461,449,436,424,412,399,387,375,363,351,339,327,316,304,293,282,270,259,249,238,227,217,207,197,187,177,168,159,
150,141,133,124,116,108,101,93,86,79,73,66,60,55,49,44,39,34,30,26,22,19,15,12,10,8,6,4,2,1,1,0,0,0,1,1,2,4,6,8,
10,12,15,19,22,26,30,34,39,44,49,55,60,66,73,79,86,93,101,108,116,124,133,141,150,159,168,177,187,197,207,217,227,238,249,259,270,282,293,304,
316,327,339,351,363,375,387,399,412,424,436,449,461,474,486,499
};
typedef struct // DMAC descriptor structure
{
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16))); // Write-back DMAC descriptors
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16))); // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16))); // Place holder descriptor
void setup()
{
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 0 (lowest), to trigger on TCC0 overflow and to trigger every beat
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_OVF) | DMAC_CHCTRLB_TRIGACT_BEAT;
descriptor.descaddr = (uint32_t)&descriptor_section[0]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&sintable[0] + 256 * sizeof(uint16_t); // Read the current value in the sine table
descriptor.dstaddr = (uint32_t)&TCC0->CC[3].reg; // Copy it into the TCC0 counter comapare 0 register
descriptor.btcnt = 256; // This takes the number of sine table entries = 256 beats
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Copy 16-bits (HWORD), increment the source and flag discriptor as valid
memcpy(&descriptor_section[0], &descriptor, sizeof(dmacdescriptor)); // Copy to the channel 0 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
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 DFLL48M clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Select GCLK4 as a clock source for TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Select GCLK4 as a co TCC0 and TCC1
// Enable the port multiplexer for the digital pins D7
PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 timer to the port output D7
PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F; //| PORT_PMUX_PMUXE_F;
// Normal (single slope) PWM operation: timers countinuously 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
TCC0->PER.reg = 1919; // Set the frequency of the PWM on TCC0 to 25kHz
while(TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
TCC0->CTRLA.reg = //TCC_CTRLA_PRESCSYNC_PRESC | // Set timer to overflow on the prescaler rather than GCLK
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
}
void loop() {}