In this post I’ll explain how to operate timers with the PIC microcontroller and give you some examples how they may be used. In general, timers come very useful for all kinds of applications where precise timing is important, such as digital clocks, stopwatches, alarm clocks or PWM. As an example, we’ll make an LED blink in exactly 1/2Hz intervals, (unlike when just using the imprecise system __delay() function).
How timers work
In this example we’ll be using the timer1 module, although PIC18F452 also contains timer2 and timer3 (which I’ll get to later). Before we can set it up, we first need to know how a timer operates. This is illustrated in the PIC18F452 datasheet (p. 108, 11-1), which I annotated below:
The diagram consists of the following steps: (I’ll define the terms in a moment)
- (Every 4 clock cycles an instruction cycle occurs.)
- Every instruction cycle the prescaler gets increased by 1.
- After every 1, 2, 4 or 8 increases of the prescaler (depends on the setting), the timer1 holding register gets increased by 1.
- After every increases of the timer1 holding register, the overflow interrupt flag is set.
Clock & instruction cycle
So, at the beginning there’s our oscillator generating the clock cycle of the frequency specified in its datasheet — mine is a 20MHz oscillator, which means that it has the oscillator frequency or that it’s period is . Therefore, 1 clock cycle occurs every 50ns. Since the PICs are designed to carry out 1 instruction every 4 clock cycles, the instruction cycle‘s period takes 4-times longer: . Since the timer is also operating at the instruction cycle frequency, it periodically increases the prescaler by 1 every 200ns.
Prescaler
The prescaler is essentially just a register for slowing down the timer since in many applications the timer’s frequency is just too high; if we wouldn’t use a prescaler, the timer1 holding register may fill up too quickly and fire interrupts at a rate which is too fast for our application. The prescaler setting may be set to 1, 2, 4 or 8 in the T1CKPS (timer 1 clock prescaler) register, each number meaning that it can be increased 1, 2, 4 or 8 times before it increases the timer1 holding register TMR1 by 1. This essentially divides the instruction cycle frequency (or multiplies the period) by 1, 2, 4 or 8. Since we have a high-speed 20MHz crystal and don’t need that high speed to blink an LED, we’re going to use the setting PRESC=4 for this example. This means that the prescaler needs to be increased 4 times before it increases the holding register. Using this, we can calculate the period of the prescaler, or the time at which the prescaler increases the timer1 holding register: (PRESC is the prescaler setting).
Holding register
The PIC18F452’s timer1 holding register TMR1 is a 16-bit register consisting of 2 8bit high and low registers: TMR1H and TMR1L respectively. The most significant bits are stored in TMR1H and the least in TMR1L. This means that the register can hold possible values, from 0 to . Since one increase of the TMR1 register takes , the total time to fill the entire register (including the overflow) takes , where RES is the timer’s resolution.
Overflow interrupt
After time has passed, the TMR1 register overflows from 65535 to 0 and the overflow interrupt flag TMR1IF is set. We can check for this flag during the execution of our program to see if the overflow has occured, which would mean that 52.4ms have passed from the last overflow. We may then set up the program to toggle the LED on/off after some multiple of this time has passed. If we want to blink the LED at the frequency of , it’s period must be . Therefore, the time it’s on (or off) is half of that, which is 1 second or 1000ms. We can approximate this time by calculating how many times the overflow flag needs to be set, since we know that the period of setting the flag is 52.4ms — therefore, the number of overflows must be .
Calculation of output frequency
After you know how to do the calculations, the following equation might come in handy if you want to quickly calculate one of the parameters mentioned above. It can also be useful if you’re calculating using frequencies instead of periods since you can just plug in all the frequencies:
This equation relates the output frequency to the oscillator frequency , prescaler setting PRESC and timer resolution RES. You can think of it in this way: at the beginning you have the oscillator frequency, in our case 20MHz. Since the timer operates at the instruction cycle which is 4 times longer than the clock cycle, we must divide the equation by 4 to get the 4-times slower instruction cycle frequency, in our case 5MHz. Then we have a prescaler which further slows down the instruction cycle frequency by the PRESC value 1-, 2-, 4- or 8-times, so we divide by PRESC as well. In our case where PRESC=4 this gives us the frequency 5MHz/4 = 1.25MHz, which is the prescaler frequency. Finally, we need to divide by the timer resolution to get the frequency at which the overflow interrupts will be fired. In our case of 16-bit register the resolution is , so the interrupt frequency, or the final output frequency, is . Thus the interrupt is fired approximately 19 times a second, which means its period, or the time between 2 interrupts is , which is exactly the same number what we’ve calculated before. Again, from this we can calculate the number of overflows which it takes for an LED to be on (or off) as 1000ms/52ms ≈ 19.
Also, it may often happen that you’ll know your oscillator frequency, the timer resolution and the final frequency you want to get. In that case the only variable is the prescaler setting which can be calculated by rearranging the formula from above to the following form:
Now that we know how the timer works, let’s look at an example where we let LED blink in precise amounts of time.
Blink an LED at exactly 1/2 Hz
What you’ll need
- Circuit containing an LED connected to PIC18F452 which we made before (make sure the LED is connected to PIC’s pin RD1)
Initialize timer
The first thing we need to do is to set up our timer called timer1. We’ll be using timer1 because it has higher (16bit) resolution (unlike timer2 having 8bits), which is better for our application where the delays are quite slow (on the order of seconds). We’ll put all the initialization settings into a function timer_init() so that we can just call it in our main function.
The following image shows all the registers with their bits which we’ll set in a moment (PIC 18F452 datasheet, p. 110, tbl. 11-2.), shown for reference.
The first thing we’re going to do is to clear the TMR1 holding register (both the high byte and the low byte) so that it starts counting from 0:
TMR1H = TMR1L = 0;
Now we’re going to set various bits of the T1CON (timer 1 configuration) register used for settings for timer1. For reference, the following image shows all the bits of the T1CON (timer 1 configuration) register with their values, which we’ll use in a moment. You can find it in the PIC18F452 datasheet, p. 107, register 11-1.
First we set the prescale value to 4. This is set in the T1CKPS bits, namely T1CKPS1:T1CKPS0. There are 4 possible values for these bits and they represent the following prescale values:
- 00– 1:1 prescale value
- 01 — 1:2 prescale value
- 10 — 1:4 prescale value
- 11 — 1:8 prescale value
Since we have a prescale value of 4, we’re going to use the setting 10:
T1CONbits.T1CKPS1 = 1;
T1CONbits.T1CKPS0 = 0;
Some optional information about T1CON bits (feel free to skip this section)
- Other T1CON bits (not needed for this setup) are RD16, TMR1CS, T1OSCEN and T1SYNC. These have their default values set automatically to 0, so we don’t need to change them.
- The RD16 bit sets the mode for reading and writing from the TMR1 holding register. If it’s 0, the reading/writing is performed in two 8-bit operations. If it’s 1, the R/W is performed in one 16-bit operation. For our purpose, we’ll be reading in 2 8-bit operations, therefore we leave it set to the default 0 value.
- The TMR1CS bit selects the clock for the PIC timer. If it’s 1, the signal is taken from the external oscillator connected to PIC. If it’s 0, the signal is provided by the system form the internal clock, which has the frequency of . In our case we’ll use the internal oscillator, so we’ll leave it set to 0.
- The T1OSCEN bit enables the external oscillator if you have chosen it in the TMR1CS bit. Since we’re using the internal oscillator, we won’t need to enable the external one, so we leave this bit to be 0.
- Finally, the T1SYNC bit enables (1) or disables (0) the synchronization of the external clock input. Since, again, we’re using the internal clock, we won’t need this feature and thus leave it set to 0.
Next, we tell the PIC that we want the timer1 to fire overflow interrupts (when TMR1 overflows from 65535 to 0). This is done by setting the TMR1IE (timer1 interrupt enable) bit of the PIE1 (peripheral interrupt enable) register:
PIE1bits.TMR1IE = 1;
We now turn timer1 on by setting the TMR1ON bit off the T1CON register:
T1CONbits.TMR1ON = 1;
Although we already have timer1 interrupts enabled, no interrupts will be fired at this point, because peripheral interrupts (which include timer1 interrupts) are disabled. We enable them by setting the PEIE (peripheral interrupt enable) bit of the INTCON (interrupt configuration) register:
INTCONbits.PEIE = 1;
Finally, we enable global interrupts (which include peripheral interrupts) by setting the GIE (global interrupt enable) bit of INTCON register:
INTCONbits.GIE = 1;
And here’s a visual way to see how the bits we’ve talked about play together (PIC18F452 datasheet, p. 108, fig. 11-1).
Now that the timer is counting and firing interrupts, we’re ready to use those interrupts in our program.
Toggle the LED
We’ll write the following into our main function. The first thing we’re going to do is set the pin where our LED is connected to output. If you have it connected to pin RD1, it’s done this way:
TRISDbits.RD1 = 0;
Then, we’ll initialize our timer, which will perform all the functions we’ve talked about before:
timer_init();
Then, we just wait. All the work will be done in an interrupt service routine which we’ll write in a moment.
while(1);
And that’s all for the main function.
Our timer1 has now been setup with the oscillator frequency , prescale value of and its resolution is $RES=2^{16}$ bits. Therefore, using the equation from the introduction, we get that the output frequency is , therefore the period of setting interrupt flag is . Since we want the LED to blink in 1/2Hz period, the time the LED is on or off is equal to 1s = 1000ms and so the number of overflows causing the interrupt flag to be set needs to be . So what we want to do is wait for those 19 interrupts to fire and then toggle the state of the LED (if it was on, now it will be off and vice versa). We’ll do this in a special function called interrupt service routine, whose definition will look like this:
void interrupt ISR()
Notice the interrupt
modifier in front of the name of the function. This makes sure that any time an interrupt is fired, the PIC will call this function. Inside it, we’re going to write the code for toggling the LED.
First, we ask if the interrupt has been fired by timer1 and not some other component; we do this by checking if the interrupt flag of timer1 has been set. The interrupt flag state can be checked in the TMR1IF (timer 1 interrupt flag) bit of the PIR1 (peripheral interrupt request) register.
if(PIR1bits.TMR1IF == 1) {
// interrupt was fired by timer1
}
We know that the code in this if
will be executed every 52ms, so we want it to execute 19 times and then toggle the LED. We do this by creating a global volatile integer in which we’ll store how many times has the if already been executed. Volatile just means that the value of the variable may be changed externally, without the program doing anything (in our case it will change with the firing of interrupt).
volatile int count = 0;
Back inside our if
, we’ll increase this variable by 1 every time an interrupt is fired:
count++;
Then, we check if the interrupt has already been fired 19 times, and if yes, we’ll toggle the LED and reset the counter. The toggling is done through the XOR operator ^
operator, which will make the value of the pin 1 if it was 0 and 0 if it was 1.
if(count == 19) {
LATDbits.LATD1 ^= 1;
count = 0;
}
Finally, we reset the interrupt flag back to 0:
PIR1bits.TMR1IF = 0;
And there you have it! The program should now blink the led in exactly 1/2 Hz intervals.
In the next post, I’ll show you how to use this PIC timer to make something a bit more interesting — we’ll make a digital clock showing current time!
Need help with a project or coursework? Check out my tutoring services here.
Documentation
Source code
For completeness, here’s the whole program:
/* (c) 2014 Marian Longa. All rights reserved. */
#include <xc.h>
#pragma config OSC = HS // High-Speed oscillator
#pragma config WDT = OFF // Watchdog timer OFF
#pragma config LVP = OFF // Low-voltage programming OFF
#define _XTAL_FREQ 20000000 // 20MHz crystal
volatile int count = 0;
void delay_ms(int ms) {
for(int i = ms; i > 0; i--) __delay_ms(1);
}
// timer1 initialization
void timer_init() {
// clear timer1 high and low holding registers
TMR1H = TMR1L = 0;
// reading in 2 8-bit operations (it's 0 by default)
//T1CONbits.RD16 = 0;
// Prescaler value of 1:4
T1CONbits.T1CKPS1 = 1;
T1CONbits.T1CKPS0 = 0;
// disable timer 1 oscillator (it's 0 by default)
//T1CONbits.T1OSCEN = 0;
// timer 1 uses internal clock (it's 0 by default)
//T1CONbits.TMR1CS = 0;
// enable interrupt flag for timer 1
PIE1bits.TMR1IE = 1;
// turn timer 1 on
T1CONbits.TMR1ON = 1;
// enable peripheral interrupts
INTCONbits.PEIE = 1;
// enable global interrupts
INTCONbits.GIE = 1;
}
// interrupt service routine
void interrupt ISR() {
// has the interrupt been fired by timer 1?
if(PIR1bits.TMR1IF == 1) {
// increase the number of times interrupt has been fired
count++;
// count till 19 interrupts have been fired (about 1 second has elapsed)
if(count == 19) {
// toggle RD1 pin
LATDbits.LATD1 ^= 1;
// reset counter
count = 0;
}
// reset the overflow interrupt flag
PIR1bits.TMR1IF = 0;
}
}
int main() {
// set RD1 pin to output
TRISDbits.RD1 = 0;
// initialize timer
timer_init();
// do nothing. All work is done by the ISR.
while(1);
}
References
- PIC18F452 datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/39564c.pdf
- Elia R’s timers tutorial: https://www.youtube.com/watch?v=EmN7CkN72EY
Leave a Reply