Timers

In the Digital I/O section I provided some code to make an LED blink:

while(1) { PORTBbits.RB7 = 1; __delay_ms(500); PORTBbits.RB7 = 0; __delay_ms(500); }

In this example, we used the __delay_ms() function to halt the CPU for a specified amount of time. This served our purposes well, but the CPU is unable to do anything useful during the delay. What if we wanted to make an LED blink once per second, but also perform other tasks at the same time? One solution is to use a timer.

A timer is essentially a flexible counter. Each timer has a register that is incremented by a clock. When the register is incremented to the point that it rolls over (e.g. goes from 255 to 0 in an 8-bit timer), a status flag is set. If enabled, an interrupt will also occur.

Therefore the amount of time that passes in between timer interrupts is dependent on the speed of the clock feeding the timer and the starting value placed in the timer register.

The easiest way to understand how timers work is to look at some examples.

Example 1: Using a timer to wait a specific amount of time.

In this example we will mimic the functionality of the __delay_ms() function.

#include <xc.h> #define _XTAL_FREQ 4000000 //set the configuration bits: internal OSC, everything off except MCLR #pragma config FOSC=INTRCIO, WDTE=OFF, PWRTE=OFF, MCLRE=ON, CP=OFF, \ CPD=OFF, BOREN=OFF, IESO=OFF, FCMEN=OFF int main() { TRISA = 0xFF; //set all digital I/O to inputs TRISB = 0xFF; TRISC = 0xFF; ANSEL = 0x00; //disable all analog ports ANSELH = 0x00; TRISBbits.TRISB7 = 0; //set RB7 as an output /////////////////// // Timer 0 Setup // /////////////////// OPTION_REGbits.PSA = 0; //Prescaler assigned to Timer 0 (other option is to //the Watchdog timer (WDT)) OPTION_REGbits.PS = 0b111; //Set the prescaler to 1:256 OPTION_REGbits.T0CS = 0; //Use the instruction clock (Fcy/4) as the timer //clock. Other option is an external oscillator //or clock on the T0CKI pin. /////////////////////// // Main Program Loop // /////////////////////// while(1) { INTCONbits.T0IF = 0; //Clear the Timer 0 interrupt flag TMR0 = 0; //Load a value of 0 into the timer INTCONbits.T0IE = 1; //Enable the Timer 0 interrupt while(INTCONbits.T0IF == 0) //Wait for the interrupt to occur. This { //happens when the TMR0 register rolls over. NOP(); } PORTBbits.RB7 = ~PORTBbits.RB7; //Toggle the LED } return 0; }

In this example, we set up the basic settings for the timer. First we tell the prescaler that it will be used with Timer 0. A prescaler is a circuit that reduces the frequency of a clock using integer division. The prescaler can be set anywhere from 1:2 to 1:256 for Timer 0.

In our case, we set the prescaler to 1:256. This means the clock is being slowed down 256 times before entering the timer control circuitry. NOTE: The clock we are slowing down is NOT the 4MHz system clock (Fosc). It's the system's instruction clock (Fcy), which is always Fosc/4. In this case, our instruction clock is 1MHz.

In our case, we are slowing down a 1MHz system clock by 256 times. So Timer 0 will be incrementing at a rate of 1,000,000/256 = 3906.25Hz. How fast will our LED blink, then?

Well, the timer expires when the TMR0 register rolls over. The TMR0 register is an 8bit register, therefore it will roll over after 256 counts of the Timer 0 clock (which itself is 256 times slower than the system clock, which is 4 times slower than the oscillator!).

Putting this all together we get: Fosc / (4 * prescaler * 256) = rollover frequency = 15.2588Hz. Inverting this, we get a time of 0.0655 seconds (65.5ms) per rollover.

You probably noticed that we didn't accomplish anything we couldn't have done by just calling __delay_ms(65). However, there is one major difference. In the above example, your CPU can be put to work in the while(INTCONbits.T0IF == 0) loop. The CPU is free to do any task you decide while waiting for the Timer 0 interrupt to occur. If you used __delay_ms(65), you could not do this!

Example 2: Using a timer to interrupt the code at a set interval.

In the previous example we were able to make an LED blink with a 130ms period (65ms on, 65ms off). We were free to do other work as well while waiting for the interrupt to occur. However, we were simply polling (repeatedly checking) the interrupt flag status. If we had a lot of code in our while(INTCONbits.T0IF == 0) loop, it could be a long time between the timer rolling over (setting the interrupt flag), and the code checking that flag (since that only happens at the beginning of each loop).

We will remedy this by using the interrupt for Timer 0 to call an interrupt service routine.

#include <xc.h> //Use this to include the device header for your PIC. #define _XTAL_FREQ 4000000 //4MHz, which is default //set the configuration bits: internal OSC, everything off except MCLR #pragma config FOSC=INTRCIO, WDTE=OFF, PWRTE=OFF, MCLRE=ON, CP=OFF, \ CPD=OFF, BOREN=OFF, IESO=OFF, FCMEN=OFF void interrupt isr() { INTCONbits.T0IF = 0; //Clear the Timer 0 interrupt flag TMR0 = 0; //Load a value of 0 into the timer //This is actually not necessary since the //register will be at 0 anyway after rolling //over PORTBbits.RB7 = ~PORTBbits.RB7; //Toggle the LED } int main() { TRISA = 0xFF; //set all digital I/O to inputs TRISB = 0xFF; TRISC = 0xFF; ANSEL = 0x00; //disable all analog ports ANSELH = 0x00; TRISBbits.TRISB7 = 0; //set RB7 as an output /////////////////// // Timer 0 Setup // /////////////////// OPTION_REGbits.PSA = 0; //Prescaler assigned to Timer 0 (other option is to //the Watchdog timer (WDT)) OPTION_REGbits.PS = 0b111; //Set the prescaler to 1:256 OPTION_REGbits.T0CS = 0; //Use the instruction clock (Fcy/4) as the timer //clock. Other option is an external oscillator //or clock on the T0CKI pin. INTCONbits.T0IF = 0; //Clear the Timer 0 interrupt flag TMR0 = 0; //Load a value of 0 into the timer INTCONbits.T0IE = 1; //Enable the Timer 0 interrupt INTCONbits.GIE = 1; //Set the Global Interrupt Enable /////////////////////// // Main Program Loop // /////////////////////// while(1) { //We can put any application code we want here. It will simply //get interrupted when the timer expires. } return 0; }

So now as soon as the interrupt occurs, the event is handled.

Another question you may be asking is how to adjust how frequently the timer expires. The answer is in the interrupt service routine:

void interrupt isr() { INTCONbits.T0IF = 0; //Clear the Timer 0 interrupt flag TMR0 = 0; //Load a value of 0 into the timer //This is actually not necessary since the //register will be at 0 anyway after rolling //over PORTBbits.RB7 = ~PORTBbits.RB7; //Toggle the LED }

By loading a value other than 0 into the TMR0 register, we can make it take less time for the timer to roll over! For example, by setting TMR0 to 128, we would make the timer run twice as fast (since it will only take another 128 cycles to roll over).

So, by adjusting which clock you use for your timer, changing the prescaler division, and pre-loading a value into the timer's register, you can create a wide variety of timer periods.

One thing you may have noticed is that in our example, we created the slowest timer we could (prescaler of 256 and pre-load TMR0 with 0). This only resulted in a timer period of 65ms. If we want our timer to be slower, we will have to either use an external clock or oscillator on the T0CKI pin and select that as our clock source (OPTION_REGbits.T0CS = 1), or set up our timer to have a period that is an even division of the desired period (say 1/1000th) and count how many times the timer expires in the ISR. See the following example.

Example 3: Creating a long period timer.

In order to create a timer with a long period, we need to set up the timer for a shorter period and then count how many times it expires. In the example below, I want a timer that lasts 6.5 seconds. First we set up the timer for a period of 1ms. Then, the ISR checks to see if 6500 ms have elapsed (6.5 seconds). When this happens, the timer performs its task and resets the time to 0.

#include <xc.h> //Use this to include the device header for your PIC. #define _XTAL_FREQ 4000000 //4MHz, which is default //set the configuration bits: internal OSC, everything off except MCLR #pragma config FOSC=INTRCIO, WDTE=OFF, PWRTE=OFF, MCLRE=ON, CP=OFF, \ CPD=OFF, BOREN=OFF, IESO=OFF, FCMEN=OFF #define TIMER_RESET_VALUE 6 //Calculated by the formula //TMR0 = 256 - (timerPeriod*Fosc)/(4*prescaler) + x //In this case, timerPeriod = 0.001s // Fosc = 4,000,000 // prescaler = 4 // x = 0 because PS is > 2 //So, TMR0 = 256 - (0.001*4000000)/(4*4) // TMR0 = 256 - 4000/16 // TMR0 = 256 - 250 // TMR0 = 6 int delayTime = 0; //A global variable to store the time that has //elapsed. This must be global! void interrupt isr() { INTCONbits.T0IF = 0; //Clear the Timer 0 interrupt flag TMR0 = TIMER_RESET_VALUE; //Load the starting value back into the timer delayTime++; if(delayTime >= 6500) { delayTime = 0; //We reach here when 6.5 seconds has elapsed. //Here you can put in the code for whatever is supposed to happen //after 6.5 seconds. In this case, toggle the LED. PORTBbits.RB7 = ~PORTBbits.RB7; } } int main() { TRISA = 0xFF; //set all digital I/O to inputs TRISB = 0xFF; TRISC = 0xFF; ANSEL = 0x00; //disable all analog ports ANSELH = 0x00; TRISBbits.TRISB7 = 0; //set RB7 as an output /////////////////// // Timer 0 Setup // /////////////////// OPTION_REGbits.PSA = 0; //Prescaler assigned to Timer 0 (other option is to //the Watchdog timer (WDT)) OPTION_REGbits.PS = 0b001; //Set the prescaler to 1:4 OPTION_REGbits.T0CS = 0; //Use the instruction clock (Fcy/4) as the timer //clock. Other option is an external oscillator //or clock on the T0CKI pin. INTCONbits.T0IF = 0; //Clear the Timer 0 interrupt flag TMR0 = TIMER_RESET_VALUE; //Load the starting value into the timer INTCONbits.T0IE = 1; //Enable the Timer 0 interrupt INTCONbits.GIE = 1; //set the Global Interrupt Enable /////////////////////// // Main Program Loop // /////////////////////// while(1) { //We can put any application code we want here. It will simply //get interrupted when the timer expires. } return 0; }

Let's discuss the formula used to calculate the value to load into the TMR0 register to achieve a 1ms timer period.

The total amount of time it takes for the timer to expire is a combination of three factors; the timer clock speed, the number of clock ticks required to overflow the TMR register, and the timer clock synchronisation delay. The formula for the period of Timer 0 is thus:


The formula for 8-bit Timer 0's period.

1. This is the number of timer clock ticks required for the timer to expire. Since this is an 8-bit timer, the timer overflows at 256. Therefore, the number of ticks is the difference between the value loaded in the TMR0 register and 256. For a 16-bit timer (Timer 1), the TMR register is actually a pair of 8-bit registers (TMR1H:TMR1L). In that case, the formula would be (65536 - TMR1H:TMR1L) where TMR1H:TMR1L is the 16-bit value loaded into the register pair.

2. This is the length of time of a single timer clock tick. The frequency of the timer clock is Fosc/(4*PS) where Fosc is the oscillator clock speed (4 MHz default) and PS is the pre-scaler selected. If you are using an external clock on the T0CKI pin (T0CS = 1), the frequency is simply the value for that clock source.

3. This is the synchronisation delay for Timer 0. This is due to the fact that the TMR0 incrementation is disabled for 2 instruction cycles after being written. x is the number of timer cycles that will be missed during this time. If you are using the system clock with the timer and your pre-scaler is set for 1:1, x is 2. This is because the timer clock is the same speed as the instruction clock, and therefore the first two pulses will fail to increment the register. If your pre-scaler is set to 1:2, x is 1 because only the first clock pulse will arrive during the delay phase. If the pre-scaler is set to 1:4 or greater, x is 0 because by the time the first timer clock pulse comes in, the incrementation is already active for the TMR0 register.

A far more useful formula is to solve for the value that you must write into the TMR0 register in order to achieve a specific timer period. With a little bit of re-arranging, we come to this:


The formula to determine which value to load in the TMR0 register to achieve a specific timer period.

Once again, replace the 256 with 65536 for a 16-bit timer (Timer 1).

Looking at Example 3, you will see that this is the formula used to determine that a value of 6 should be loaded into the TMR0 register.

In Example 3, the math worked out well and we weren't left with any fractional component. However, that may not always be the case. In these situations, your timer period will be slightly different from the desired value. Let's work through an example.

Let's say we wish to have a timer that expires every 0.95ms. In this case, Tt is 0.00095s. If we use a pre-scaler of 4 once again and use the formula, we will end up with a result of TMR0 = 18.5. If we use the value 18, we can use the first formula to calculate the timer period to be Tt = 0.000952s or 0.952ms. If we use the value of 19 in TMR0, Tt = 0.000948s or 0.948ms.

Neither of these values are the value we wished for. We can calculate the percentage error using the following formula:


We can use this to calculate the percentage error in our timer period.

In the case of loading 18 into TMR0, the percentage error would be: (0.952 - 0.950)/0.950 * 100% = 0.21%. Depending on your application, this may be an acceptable level of error. It's up to you to decide.

In all these examples I've used a pre-scaler of 4. That was to keep things simple. For the best accuracy, you should use the smallest pre-scaler value you can. The easiest way to do this is to use the formula for calculating the value you load into TMR0 and start with the pre-scaler set to 1. If this is too fast, you will get a negative answer for TMR0. Increment the pre-scaler step by step until you get a positive answer. This will be the smallest pre-scaler value you can use with your system to achieve your desired timer period.

In the next section we will discuss how to configure the microcontroller and explain the use of the #pragma config line in our examples.