Ideas for PWM with repeating pattern

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() {}