Ideas for PWM with repeating pattern

Hi -- I'm working on a project that involves simulating a 60 Hz power main by using two PWM outputs, each generating 1/2 the cycle alternately from 200 pulses with changing duty cycle based on a lookup table.

Thanks to threads in this forum and other web resources, I have the basic pieces working, but there is one thing I don't know how to do. That is how to step through the lookup table ensuring that each duty cycle value is applied to exactly one pulse. In other words, something like this:

// first half cycle
// enable counter
TCC0->CTRLBSET.reg = TCC_CTRLBCLR_CMD_RETRIGGER;
while(TCC0->SYNCBUSY.bit.CTRLB);
for (int i = 0 ; i < 200 ; i++) {
// step through duty cycle table
TCC0->CCB[1].reg = lookup_table[i];
while (TCC0->SYNCBUSY.bit.CCB2) {};
}
// turn counter off
TCC0->CTRLBSET.reg = TCC_CTRLBCLR_CMD_STOP;
while(TCC0->SYNCBUSY.bit.CTRLB);

// for second half cycle, repeat using TCC1

My question is -- how do I ensure that each step of the lookup is applied to exactly one pulse? Do I need to wait on a register to tell me it's time to apply the next duty cycle change?

Thanks very much for any pointers on this!

Thanks!

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

MartinL, thank you so much for the quick and incredibly helpful response! I have been playing with your code and see how it works, though some of the incantations are pure magic. :slight_smile:

I do have a couple of questions to help me move on...

I need to put 1/2 the sine wave on each of two pins in sequence (ie, pin1 is the top half and pin 2 the bottom). My sin wave table will have values for half the cycle, so what I need to do is "play" the waveform on pin 1, then immediately play it on pin 2, so that the two together equal the full cycle time.

Can I use the one DMAC channel to drive both pins sequentially that way, or do I need to set up a second channel for the second pin. And how do I make the outputs sequential so pin 2 starts running right after pin 1 stops? Is that the normal timer enable register?

Second, I'm still learning to deal with the timer nomenclature on the SAMD21. I am using an Adafruit Trinket M0 that has only five pins, and some have limited timer capabilities:

Digital 0 PA08, SAMD pin 11, E=TCC0/W0[0], F=TCC1/WO[2]
Digital 1 PA02, SAMD pin 3, no timers
Digital 2 PA09, SAMD pin 12, E=TCC0/WO[1], F=TCC1/WO[3]
Digital 3 PA07, SAMD pin 8, E=TCC1/WO[1], F= N/A
Digital 4 PA06, SAMD pin 7, E=TCC1/WO[0], F= N/A
Digital 13 (LED) PA10, SAMD pin 13, E=TCC1/WO[0], F = TCC0/WO[2]

I am planning to use pins 2 and 3 (PA09 and PA07) to each output half my waveform.

I am confused about when to use function E vs. function F. Is it the case that you choose so that one pin is mapped to TCC0 and the other to TCC1? (ie, PA07 and PA09 both use function E because that maps each to a different TCC?)

I am also confused the W[] value and how it maps to the CC[] or CCB[] values. From some other code I stole, there was this comment:

/* n for CCB[n] is determined by n = x % 4 where x
* is from WO[x]. Use the CCB register rather than
* CC because it will buffer and cause the change
* to occur at the beginning of the next output
* cycle, avoiding glitches
*/

So I would expect to use CC[1] or CCB[1] for both pins since both are assigned WO[1] on their respective timer. But you are calling CC[3]. Where does the 3 come from? I am guessing that there is a pairing of 0 and 2, and 1 and 3 for different purposes, but I'm not sure what those purposes are.

Sorry if these are basic questions, but the SAMD21 is like drinking from a fire hose. Thanks again for being so helpful!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.