Smoothly changing PWM output frequency

I've got an application where I need to vary the output of a square wave on pin PA30 (using TCC1/WO[0]) from around 3kHz to around 32kHz.

Right now I'm using analogWrite() to initialize the pin at the SAMD21's base PWM frequency (about 192kHz on my core) and a 50% duty cycle. Once that's set up I am using the following function to allow the user to adjust the output frequency:

REG_TCC1_PER = myPer;      // Set the frequency of the PWM on TCC1 to 50kHz
while (TCC1->SYNCBUSY.bit.PER);

REG_TCC1_CCB0 = myPer / 2;      // Set the duty cycle of the PWM on TCC0 to 50%
while (TCC1->SYNCBUSY.bit.CCB0);

This works fine, but I get discontinuities in the output as I change the frequency.

I've been using a different setup from Nebs Petrovic to control TC3 on another project. His code pulls the current values from the counter and then re-maps them to proportional values at the new frequency to ensure that the timer output is the same at the new frequency as it was at the old one.

int compareValue = (CPU_HZ / (TIMER_PRESCALER_DIV * frequencyHz)) — 1;
TcCount16* TC = (TcCount16*) TC3;
TC->COUNT.reg = map(TC->COUNT.reg, 0, TC->CC[0].reg, 0, compareValue);
TC->CC[0].reg = compareValue;
while (TC->STATUS.bit.SYNCBUSY == 1);

I've made an attempt to get this code working on TCC1 but I'm having troubles. I'm still unsure about the setup for reading the COUNT.reg for TCC1 as compared to TC3, so if anyone has an idea about how to implement this smooth change on TCC1 I'd appreciate some pointers.

Thanks!

Hi pharaohamps,

To seamlessly change the PWM frequency during operation, it's necessary to use the buffered period or PERB register, (rather than the unbuffered PER register), for example:

TCC1->PERB.reg = 0xFF;
while(TCC1->SYNCBUSY.bit.PERB);

This causes changes to the PWM frequency to only occur at the beginning of the next timer cycle and therefore prevents glitches from appearing on your output.

On the SAMD21 buffered registers are only available on the TCCx timers and not the TCx ones. It's possible to implement this on the TC timers, but changes to the PER register need to happen during a timer overflow, inside the timer's interrupt service routine.

Reading the COUNT registers on the TCCx registers is rather convoluted. First it's necessary to generate COUNT read request in the timer's CTRLB register. Once this register has been write synchronized, it's then necessary to set up a read synchronization, before reading the COUNT register itself:

TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC;        // Trigger a read synchronization on the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB);                      // Wait for the CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT);                      // Wait for the COUNT register read sychronization
SerialUSB.println(TCC0->COUNT.reg);                    // Print the result

MartinL, you’re a lifesaver. Changing my original code from TCC1->PER to TCC1->PERB fixes the issues I was having with no other changes. The output changes smoothly with no artifacts, since the timer now waits until the counter reaches the next cycle there are no dropouts.

Thank you for the information regarding reading COUNT from TCC1, I’m sure I’ll need that sooner rather than later.

I’ve used the “map the current COUNT value to the new timer range” trick successfully with TC3 and TC5 for generating timer interrupts, advancing a wavetable counter in the ISR, but someday I’ll need to do the same thing with TCC1 or TCC0 so this page is now bookmarked.

Cheers!