i spent a fair bit of time looking around for good information on how to synchronize timers within the ATmega328, and didnt really find anything that fixed my problems, so ive decided to put one together myself. the full text is also up on my website:
1. why synchronize timers?
this can be really handy if you are summing up multiple pwm outputs, or want to use one interrupt to change a bunch of parameters on multiple timers, or for keeping capacitive sensing on timers lines in sync so they dont crosstalk. you can create some pretty complicated waveforms by summing up multiple pwm outputs.
2. the basic version - using GTCCR.
for simple applications, where you setup the timers once at the beginning of your program, synchronization is pretty straightforward. the code would look something like this:
// 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
the GTCCR controls the timer prescalers, and has 3 bits in it. 2 of these bits are for resetting the prescaler, one each for the synchronous prescaler (timer0 and timer1) and the asynchronous prescaler (timer2). these bits act as strobes, so if you write them to 1, they are cleard instantly by hardware. the third bit is the TSM bit, which holds the state of the strobes, and halts the timers. this is very handy, as you can halt all timers at once, regardless of prescaler value, and then start them all again at some later time. it is important to remember that the prescaler will have been reset, so your last clock pulse may end up shorter than expected if you are using a prescaler other than 1.
in the above example, this ability of the GTCCR to freeze the timers is used to keep all of the timers synchronized. it is very important that the timers be setup after TSM is set, so that all of the timers are in the exact same state when TSM is released. and be sure to set the TCNT values all to 0, or the same value. this is the basic mode, and works well if all of your timers are running in the same mode.
2. timers in different modes.
for the most part, all of the modes are compatible except for any of the modes that have OCRA as the TOP value of the count. for these modes, there is an offset of 1 for when compare matches are made (perhaps due to compare matches being disabled 1 clock cycle after TCNT changes, but im not really sure). to compensate for this, just add 1 to any counter that has OCRA as TOP. if all of your counters have OCRA as TOP, then this isnt neccessary. and, oddly enough, ICRA as TOP doesnt have this problem. a code example for this is shown below.
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
3. timers with different prescalers.
similarly, if you have timers with different prescalers, and want to synchronize them so you get rising edges happening at the same time, you will need to add an offset. this is because the first timer tick on a prescaled clock does not happen until N CPU clocks after restart, where N is the prescaler divisor. to remedy this, you set TCNT = (0 - (M - 1)) for the faster timer, and TCNT = 0 for the slower timer. in this case M is the ratio of the 2 prescaler divisors. i suspect the reason for (M - 1) above in comparison to just (M) is that the prescaler counter actually clocks on 0xFF and not 0 as the datasheet says. an example is gvien below:
Timer1 Prescaler = CK/1, Timer2 Prescaler = CK/8:
GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt all timers // setup timer registers here TCNT1H = 0xFF; // set timer1 high byte to (0 - (8 - 1)) TCNT1L = 0xF9; TCNT2 = 0; // set timer2 to 0 GTCCR = 0; // restart timers
4. synchronizing mid-application.
although the above schemes work really well for general initialization, if you want to change your timers half-way through a program, things get rather complicated. one of the reasons for this is that some of the timer states are not controllable via the IO registers. for example, the output compare pin state in toggle mode is stored internally. so, if you sync the timers, you have a 50/50 chance of actually having the toggle state synchronized. to remedy this, you can set the timer to a known state before doing the syncrhonization. again, an example is shown below.
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 other problems associated with mid-application changes to the timers, but i do not fully understand why they happen, so i dont have a good solution. for example, if one timer uses OCRA as top, and is active but not yet made its first compare match, it will not be able to be synchronized to any other timers using overflow mode. this happens when you set up timers outside of GTCCR at the beginning of a program. you get a few clock cycles, and then try to sync them, which is not possible for any value of TCNT you select. delaying until the first compare match is made is the only way to get this to work.