# Precise timing with a PIC18 microcontroller?

I'm trying to write a software serial implementation and am having trouble with timing. I'm using Timer0 on a PIC18F242 and for some reason it does not seem to be very accurate.

I've been trying to debug the problem by toggling a pin on the microcontroller and watching the frequency on my oscilloscope. The oscilloscope shows that the period is greater than it should be, and I don't know why.

With my full code the period was something like 10us too long, if I remember correctly, but even with a very simple example, such as the one below, the timing is off.

Here is an example:

#include <p18f242.h>

// fuse configurations
#pragma config OSC = HSPLL
#pragma config OSCS = OFF
#pragma config PWRT = OFF
#pragma config BOR = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF
#pragma config CP0 = OFF

// CALCULATIONS:
// 8Mhz crystal with PLL enabled
// FOSC = 4*8Mhz = 32MHz
// TOSC = 1/FOSC = 31.25ns
// TCY = 4*TOSC = 125 ns
// timer has 1:8 prescaler, therefore timer increments every 8*TOSC = 1us.

main(void)
{
// port configuration
TRISBbits.RB1 = 0; // make RB1 an output

// configure Timer0
T0CONbits.TMR0ON = 0; // disable (stop) the timer
T0CONbits.T08BIT = 0; // 16-bit timer
T0CONbits.T0CS   = 0; // use internal instruction clock
T0CONbits.PSA    = 0; // use prescaler (prescaler assigned below)
T0CONbits.T0PS0  = 0; // 010 = 1:8 prescaler
T0CONbits.T0PS1  = 1;
T0CONbits.T0PS2  = 0;
T0CONbits.TMR0ON = 1; // enable (start) the timer

while(1)
{
if(TMR0L >= 100)
{
PORTBbits.RB1 ^= 1;

// reset the timer
TMR0H = 0; // MUST SET TMR0H FIRST
TMR0L = 0;
}
}
}


My oscilloscope says the period of this signal is 204.0us, which means that the pin is toggling every 102.0us. If my oscilloscope is correct, then the pin is toggling 2us too late every time.

Interestingly, changing if(TMR0L >= 100) to if(TMR0L >= 102) results in the same period, but going below 100 or above 102 results in the period decreasing and increasing, respectively.

Why is this happening?

Also, as I mentioned, having additional code in the main loop seems to exacerbate the problem. Why would that be?

If you want accurate timing, use interrupts (i.e. reset the timer value in it's interrupt routine).
As you have it it only checks the timer value once per loop, so if you add code into the loop the checks become spread out.
The interrupt will switch context as soon as the timer overflows, so it doesn;t matter how much code is in your loop then.

What you see here is the delay between the moment the timer has reached 100 (meaning your if-condition is true) and when you reset it. In your case this is 2 timer ticks, meaning you get a longer period. As already said, if you want to have precise timing use interrupts. Alternatively, you can configure your timer to overflow exactly in the right moment. That way, you just need to check for an overflow to occur, and don't need to reset it.

This isn't the most effective way to use a timer. If you want precise timing, set an interrupt for when the timer rolls over. The interrupt routine should set a timer flag and do whatever else is quick that needs to be dealt with in a time critical way, and reset the timer count (reading the current timer value so you can account for any delays associated with interrupt servicing). Everything that's not quick, you put in an if loop that checks the timer flag. The first thing you do in that loop is reset the timer flag, and then do all the non-time-critical stuff associated with your loop.

The extra delay is due to the time it takes to execute the while, if compare, toggle and reset instructions, because when you zero out the timer you lose up to all those counts, depending on when exactly the timer reached 100 in that particular cycle. Tcy is 125 ns , so only 16 assembly instructions are needed to account for the 2us extra delay. Take a look at the assembly listing and you'll realize how much overhead you are adding.

Like others have said, use an interrupt for least uncertainty and jitter. Instead of resetting the timer, let it overflow and keep counting, that way you don't zero out counts, which are added to the total period.

If you don't want to use interrupts (but you should), also set the period so that it overflows, and check the overflow flag. This will allow you to not zero out the timer and will preserve the calculated period, but you'll have jitter.

If you use a prescalar with Timer 0, it is not possible to accurately generate interrupts at rates which are not power-of-two multiples of 256. If you want to generate a rate which is not a power-of-two multiple of 256, your best bet is probably to use Timer 2, which is configurable for any number of the form (A*B*C) where A is any number 1-256, B is any number 1-16, and C is an allowable power of two (I forget which ones are allowed). Otherwise, if you want to use timer 0 efficiently in 8-bit mode with no prescalar, saying TMR0L += N will advance the timer by N-3 cycles. Thus, if you wanted to have timer 0 wrap every 200 cycles, you could after each time it wraps, add (259-200) cycles to it (since advancing the timer by 56 cycles after it wraps would leave 256-56 cycles, i.e. 200). Note that using this technique, it doesn't matter precisely when the "add" takes place, provided it occurs soon enough that it doesn't push the timer "over the edge".

It can be a pain if you do not know assembly but inserting assembly routines into C code can help you achieve accurate timing. If I recall correctly to insert assembly into PIC C you use

_asm