In this example, I will show how to setup the timer/counter type A to generate PWM waveforms and output them to I/O pins. I used a tiny1616 here, but the process is similar for other chips of the AVR 0/1 series.
The first step is to setup the system clock, in this case I will use a 10x divider. Given the default oscillator frequency of 20MHz (set by a fuse), this will result in a system clock of 2MHz.
Note that the clock registers are protected, so to write them, the _PROTECTED_WRITE macro found in the <avr/xmega.h> header file should be used.
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_10X_gc | CLKCTRL_PEN_bm);
Now to setup Timer A itself. In split mode, timer A, which by default is a 16 bit timer, behaves as 2 independent 8 bit timers, H and L, each with 3 output channels.
Each timer can have a different period, but in this example I will set the same for both, and instead show how a different phase can be set for each timer.
// Configure timer A0 to generate waveforms // In split mode, the type A timer can be used as 2x 8 bit timers, "high" (H) and "low" (L) // Each timer has 3 channels, here we will use Ch0 from both timers TCA0.SINGLE.CTRLD = TCA_SINGLE_SPLITM_bm; // enable split mode TCA0.SPLIT.HPER = 99; // set 100 counts period on H TCA0.SPLIT.LPER = 99; // and L timers TCA0.SPLIT.HCMP0 = 50; // set 50% duty cycle on H timer Ch0 TCA0.SPLIT.LCMP0 = 25; // set 25% duty cycle on L timer Ch0 TCA0.SPLIT.HCNT = 75; // phase = 90 deg. on H timer TCA0.SPLIT.LCNT = 100; // phase = 0 deg. on L timer TCA0.SPLIT.CTRLB = TCA_SPLIT_HCMP0EN_bm | TCA_SPLIT_LCMP0EN_bm; // enable PWM outputs on Ch0 of H and L timers TCA0.SPLIT.CTRLA = TCA_SPLIT_ENABLE_bm; // start timer
The period is set by loading HPER/LPER with (period-1), so in this case the period will be 100 timer cycles. Since the timer is using the system clock, the resulting waveforms will have a frequency of 2MHz/100 = 20kHz.
In split mode, the timers always count from top to bottom then wrap around. The output of a timer channel is LOW while xCNT >= xCMPn (n is the channel), HIGH otherwise.
The duty cycle is chosen by setting xCMPn. I chose a period of 100 so that the values of xCMPn translate directly to percentage duty cycles.
So, HCMP0 = 50 means that channel 0 of timer H will have 50% duty cycle and TCA0.SPLIT.LCMP0 = 25 means channel 0 of timer L will have 25% duty cycle.
The phase can be chosen by setting initial values on the count registers, while the timer is stopped.
Setting LCNT = 100 means the phase for timer L will be zero degrees. A value of 200 or 0 would give the same result (i.e., a multiple of the period).
Setting HCNT = 75 means the phase for timer H will be leading timer L by 90º, because the timer is counting down. Timer H will reach zero and wraparound 25 timer cycles (i.e. 1/4 period or 90º) before timer L.
CTRLB is used to enable timer outputs on their respective ports. L timer channels are present in WO0 to WO2, while H channels are sent to WO3 to WO5. Here we enable only the channels zero of both timers (WO0 and WO3).
Finally, CTRLA enables the timer.
However, this procedure is not enough to actually get the PWM outputs. The last step is to set the ports corresponding to WO0 and WO3 as outputs.
Looking at the "Multiplexing and considerations" chapter of the tiny1616 datasheet, we can see that the ports are B0 for WO0 and A3 for WO3.
This is the code to enable them:
PORTB.DIRSET = 1 << 0; PORTA.DIRSET = 1 << 3;
Putting it all together, this is the complete code:
#include <avr/io.h> inline void clock_init(void); inline void timer_init(void); int main(void) { clock_init(); timer_init(); while (1); } /* Set 10x divider for main clock. This results in a 2 MHz clock. */ void clock_init(void) { _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_10X_gc | CLKCTRL_PEN_bm); } /* This function uses timer type A to generate 2 waves, out of phase by 90 deg. The resulting frequency for these settings is 20kHz (main clock /100) */ void timer_init(void) { // Configure timer A0 to generate waveforms // In split mode, the type A timer can be used as 2x 8 bit timers, "high" (H) and "low" (L) // Each timer has 3 channels, here we will use Ch0 from both timers TCA0.SINGLE.CTRLD = TCA_SINGLE_SPLITM_bm; // enable split mode TCA0.SPLIT.HPER = 99; // set 100 counts period on H TCA0.SPLIT.LPER = 99; // and L timers TCA0.SPLIT.HCMP0 = 50; // set 50% duty cycle on H timer Ch0 TCA0.SPLIT.LCMP0 = 25; // set 25% duty cycle on L timer Ch0 TCA0.SPLIT.HCNT = 75; // phase = 90 deg. on H timer TCA0.SPLIT.LCNT = 100; // phase = 0 deg. on L timer TCA0.SPLIT.CTRLB = TCA_SPLIT_HCMP0EN_bm | TCA_SPLIT_LCMP0EN_bm; // enable PWM outputs on Ch0 of H and L timers TCA0.SPLIT.CTRLA = TCA_SPLIT_ENABLE_bm; // start timer // Set PWM pins as outputs; PB0 = L timer Ch0 (WO0), PA3 = H timer Ch0 (WO3) PORTB.DIRSET = 1 << 0; PORTA.DIRSET = 1 << 3; }
These are the resulting PWM waves (top - timer H, bottom - timer L)
I recommend changing the period, duty and phase to see the effects on the generated waveforms.