Synchronizing Timers

Within the Arduino microcontroller (ATmega328), there are 3 timers which you can use to output PWM, make waveforms, or trigger external events. Sometimes it is handy to have 2, or all 3 of these timers running exactly in sequence with each other. In this way, they all roll-over to 0 at the same time, and the output waveforms that are generated stay locked together. A specific example of this would be the creation of a Triple PWM, where 3 outputs are summed together to make a higher resolution waveform. You can do 2 of these with a single timer (2 outputs per timer), but to get the third, a second timer will need to be kept synchronized.

Performing this synchronization task can be quite easy, except for a few cases, where it’s very tricky. To help make things easier for you, we have documented a number of these tricky cases, and show good work-arounds to get your project up and running.

1. ATmega328 timer overview.

Before we get into how to setup these timers, it will be useful to explain a bit about how they work. At their most basic level, they are numbers that increment with every tick of the clock. Once that number equals a certain value (which is settable by the user) the timer either resets to 0, or starts down-counting to 0. There are a bunch of different modes the timers can be put into, most of which are dedicated to making PWM outputs. But, at their lowest level they all count up, and then either reset or count down.

Exactly which clock they increment by is another parameter that is determined by the user. They can either be advanced by the CPU clock, a divided down version of the CPU clock, or an external clock source. Exactly which source is used is determined by the Prescaler. This is basically another timer that counts up, and then puts out a pulse to clock our timers when it rolls over to 0. The number it counts up to is settable to fixed values (1, 8, 64, etc.), which allows the timers to count really slowly if needed, to catch things that happen on a more human time scale. This Prescaler can be halted and reset, which is how we will synchronize our timers.

And, just to add one more variable to the mix, the clock can either be synchronous (sampled and indexed by the CPU clock), or asynchronous (free running with respect to the CPU), the latter being very useful for sleep modes where the CPU clock is halted. Timer0 and Timer1 are both synchronous, and share a Prescaler. Timer2 can be either synchronous or asynchronous, and as such has its own Prescaler. Since we are using the Prescaler to sync up the timers to each other, we can only do it in discrete steps – Timer0 and Timer1 can be sync’d, or Timer0, Timer1, and Timer2 can be sync’d.

2. GTCCR.

The GTCCR (General Timer/Counter Control Register) is the main way you synchronize timers. Within the ATmega328 it has 3 bits which you can set, the TSM (Timer/Counter Synchronization Mode), and the PSRSYNC and PSRASY (Prescaler Reset for synchronous and asynchronous timers, respectively). Writing a 1 to either PSRSYNC or PSRASY resets the associated timer Prescalers. These bits are cleared immediately by hardware, making them act as a strobe. Writing a a 1 to TSM holds the state of the PSRSYNC and PSRASY bits, so if you write them to 1 at the same time, you can reset and halt the Prescalers, freezing all associated timers. Writing TSM to 0 releases the Prescalers, and the PSRSYNC and PSRASY bits go back to 0. GTCCR works regardless of the Prescaler settings, and can be used at any time to freeze the counters.

So, synchronization is as easy as doing the following in your setup() routine:

// use only one of the following 3 lines
GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt all timers
GTCCR = (1<<TSM)|(1<<PSRASY)|(0<<PSRSYNC); // halt timer2 only
GTCCR = (1<<TSM)|(0<<PSRASY)|(1<<PSRSYNC); // halt timer0 and timer1 only

// place all timer setup code here
// do not do any timer setup before this section

// set all timers to the same value
TCNT0 = 0; // set timer0 to 0
TCNT1H = 0; // set timer1 high byte to 0
TCNT1L = 0; // set timer1 low byte to 0
TCNT2 = 0; // set timer2 to 0

GTCCR = 0; // release all timers

This is the basic setup routine. What works best, is placing all of your timer configuration settings within the two GTCCR statements. That way all of the timers are halted while they are being setup, so nothing can get out of alignment between one statement and the next. This will handle 99% of the cases, but there are a few times when it will not work, and these are when 2 timers are not setup completely identical. Examples are given below.

3. Different Timer modes.

If your timers are running in different modes, you will need to pay special attention to their setup. Most of the modes are compatible with each other, except for any mode that uses OCRA for setting the TOP value. These will have an offset of 1, for reasons i don’t fully understand. It is simple enough to remedy this, though, by adjusting the counters accordingly, as shown below. Oddly enough, using ICRA as TOP works just fine for sync’ing with the “overflow” modes.

Synchronizing Timer0 and Timer2, with Timer2 using OCRA as TOP:

GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt timers
TCCR0A = 0x23; // set timer0 to 8b Fast PWM
TCCR0B = 0x01; // CK = CPU/1
TCCR2A = 0x33; // set timer2 to Fast PWM
TCCR2B = 0x09; // OCRA as TOP, CK = CPU/1
OCR2A = 0xff; // set timer2 TOP
OCR0B = 0x80; // set compare match registers
OCR2B = 0x80;
TCNT0 = 0; // sync timers
TCNT2 = 1; // offset of 1 for OCRA as TOP timer
GTCCR = 0; // restart timers

4. Different Prescaler settings.

Even if all of your timers are set to the same modes (e.g. 8 bit, fast PWM), if you use different Prescaler settings, they will not end up in alignment if you set the counters to 0. This is because the Prescalers need to count up to their TOP value before the first clock pulse is sent out. So, to deal with this, the other timers must be preloaded with that TOP value as an offset, so they hit 0 at the exact same time as the prescaled timer. For example:

Timer1 Prescaler = CK/1, Timer2 Prescaler = CK/8

GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt all timers
TCNT1H = 0xFF; // set timer1 high byte to (0 - (8 - 1))
TCNT1L = 0xF9;
TCNT2 = 0; // set timer2 to 0
GTCCR = 0; // restart timers

This can be done for many timers with all different Prescaler settings, as long as they each have the correct offset for largest Prescaler value. Note that the offset is negative – it needs to be subtracted from 0 to get the right placement, and is subtracted from the faster counting timer. This makes sense, as the faster counting timer start before the slower one (it doesn’t have to count to TOP to start). Also, there is an offset of 1, which is probably due to the Prescaler actually incrementing at 0xFF and not 0. So the equation is TCNT = (0 – (N – 1)) where N is the ratio of the Prescaler values.

5. Synchronizing mid-application.

In an ideal world, you would set everything up at the beginning of your program and be done with it. But, this is not always possible, as the timers may be serving different purposes throughout the application. This makes things very difficult for synchronization, as some aspects of the timers are not directly settable. To accommodate for this, you must set the timers to a known state before you synchronize them. One example of this is output pin state.

If you are using the “toggle on compare match” option, the output pin state is stored within the timer, and is not related to the PORT register state. So, if you have 2 timers with different toggle states, synchronizing the timers will not synchronize the outputs, and you will have to manually set them equal before synchronization. Unfortunately, the only way to do this is to increment the timer forward until the desired state is reached, and then sync them. Luckily you can do this rather quickly by halting the timers!

Synchronizing Timer0 and Timer2 mid-application, with pin toggle:

GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt timers
TCCR0A = 0x30; // set timer0 to normal mode
TCCR0B = 0x01; // CK = CPU/1
TCCR2A = 0x30; // same for timer2
TCCR2B = 0x01;
TCNT0 = 0; // set counters to 0
TCNT2 = 0;
OCR2B = 3; // set compare match to low value larger than 1
OCR0B = 3; // as compare fails 1 ck cycle after TCNT change
GTCCR = 0; // restart timers
asm volatile ("nop"); // delay for the length of your count
asm volatile ("nop");
asm volatile ("nop");
GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt timers
TCCR0A = 0x10; // set timers to desired state
TCCR2A = 0x10;
OCR2B = 0x80;
OCR0B = 0x80;
TCNT0 = 0; // sync timers
TCNT2 = 0;
GTCCR = 0; // restart timers

There are many more possible issues one might encounter when changing the timer states mid-application. For example, if one timer is running, and another has just started, but not made its first compare match, and is using OCRA as TOP, there will be no way to synchronize the timers. One work-around in this case is to delay until the timer makes a compare match, and then sync the timers. There may be other solutions to this problem, but it seems obscure enough to not worry about at the moment. And remember, a compare-match will always fail 1 clock cycle after TCNT is changed!