Use PIC Timer2 not Timer0 for accurate Interval Timing on an Embedded system

Recently I was struggling to achieve very accurately synchronised camera and light triggers for a real-time computer vision project that I was working on, my original PIC embedded system for triggering everything using a PIC micro-controller had a fairly reliable accuracy of about 250us which was sufficient for a few years, but for various reasons this needed to be reduced by a factor of 10 to about 25us. The problem was that as I reduced the timer’s overflow period it started to become quite inaccurate and erratic.

It is possible to achieve quite accurate timing on your embedded software system using a PIC, however it can take a bit of work to tune things. Most articles that introduce the use of timers on the PIC use the Timer0 module as an example, maybe because it has a ‘0’ in it?

Now, when using Timer0 to produce a repeated time interval, its High & Low overflow registers must be manually reset in the interrupt handler to setup the next time period, all while the timer is still counting away.

This it makes it difficult to know what values to put into the registers to get an accurate overflow period – you end up trying to account for the interrupt latency and counting assembly instructions in the interrupt handler to estimate times etc.

The upshot is that it’s hard to get a reliable overflow period with a desired accuracy when using Timer0.

Example, initial Timer0 setup code:

void init_timers() {

    // See http://www.enmcu.com/software/timer0calculatorandcodegeneration
    // for values.

    INTCON.GIE=1;         // global interrupt enable
    INTCON.PEIE=1;        // peripheral interrupt enable
    INTCON.TMR0IF = 0x0;  // Clear timer0 overflow interrupt flag
    INTCON.TMR0IE = 1;    // enable the timer0 by setting TRM0IE flag

    T0CON.T08BIT = 0;     // 16 Bit timer
    T0CON.T0CS = 0;       // Internal clock
    T0CON.PSA = 1;        // Set scaler to 1:4

    // Set timer counts
    TMR0H = TIMER_HIGH;
    TMR0L = TIMER_LOW;

    T0CON.TMR0ON = 1;     // Turn Timer0 on.
}

// interrupt handler for the timer overflow
void interrupt(void) {

    // Is this a Timer 0 interrupt?
    if (INTCON.TMR0IF) {
        // yes!
 
        // Reset the timer values
        // These are actually now incorrect
        // as time has elapsed since the timer
        // overflow and now!
        TMR0H = TIMER_HIGH;
        TMR0L = TIMER_LOW;
        
        // Reset interrupt flag
        INTCON.TMR0IF = 0;

        // Increase some counter
        ++counter;
    }
}

The best thing to do is to ditch Timer0 and switch to Timer2 instead. Timer2 automatically handles this kind of pattern (and it’s not much harder to use even though it’s got a ‘2’ in its name!).

When using Timer2 you don’t need to reload the overflow registers and so it is much easier to get an accurate and reliable overflow period! Have a look here if you need help in figuring how to setup Timer2 for your desired timing parameters – it’s very handy!

void init_timers() {
  
    // Have a look at the URL below for setting up Timer2
    // http://eng-serve.com/pic/pic_timer.html
    //
    // Setup Timer2 for 40KHz, period = 25us
    
    // Timer2 Registers
    // Prescaler = 1 - TMR2
    // PostScaler = 1 - PR2 = 200 
    // Freq = 40000.00 Hz 
    // Period = 0.000025 seconds
    T2CON |= 0;         // bits 6-3 Post scaler 1:1 thru 1:16
    T2CON.TMR2ON = 1;   // bit 2 turn timer2 on;
    T2CON.T2CKPS1 = 0;  // bits 1-0  Prescaler Rate Select bits
    T2CON.T2CKPS0 = 0;
    PR2 = 200 - 1;      // PR2 (Timer2 Match value), 200 - 1 (1 = magic value, KG)

    // Interrupt Registers
    INTCON = 0;           // clear the interrupt control register
    INTCON.TMR0IE = 0;    // bit5 TMR0 Overflow Interrupt Enable bit...0 = Disables the TMR0 interrupt
    PIR1.TMR2IF = 0;      // clear timer1 interrupt flag TMR1IF
    PIE1.TMR2IE = 1;      // enable Timer2 interrupts
    INTCON.TMR0IF = 0;    // bit2 clear timer 0 interrupt flag
    INTCON.GIE = 1;       // bit7 global interrupt enable
    INTCON.PEIE = 1;      // bit6 Peripheral Interrupt Enable bit...1 = Enables all unmasked peripheral interrupts
}

void interrupt(void) {

    // Is this a timer 2 interrupt?
    if (PIR1.TMR2IF == 1) {
        // Yes, all we have to do is clear the
        // flag, we don't need to reload any registers!
        // Clear interrupt flag
        PIR1.TMR2IF = 0;        
        ++counter;
    }
}

Once Timer2 is initialised in this manner, it will cycle automatically without the need to reload any values yielding a stable (and hopefully more accurate) time period.