PIC Timers with Blinking LED

Timer blinks an LED with text (960px)

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:

PIC18F452-p108-fig11-1

The diagram consists of the following steps: (I’ll define the terms in a moment)

  1. (Every 4 clock cycles an instruction cycle occurs.)
  2. Every instruction cycle the prescaler gets increased by 1.
  3. After every 1, 2, 4 or 8 increases of the prescaler (depends on the setting), the timer1 holding register gets increased by 1.
  4. After every 2^{16} 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 F_{OSC} = 20MHz or that it’s period is T_{OSC} = 1/F_{OSC} = 1/20000000 s = 50ns. 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: T_{INS} = 4 \cdot T_{OSC} = 4 \cdot 50ns = 200ns. 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: T_{PRESC}=PRESC\cdot T_{INS}=4\cdot 200ns=800ns  (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 2^{16} possible values, from 0 to 2^{16}-1 = 65535. Since one increase of the TMR1 register takes T_{PRESC} = 800ns, the total time to fill the entire register (including the overflow) takes T_{TMR}=RES\cdot T_{PRESC}=2^{16}\cdot 800ns=52.4288ms, where RES is the timer’s resolution.

Overflow interrupt

After T_{TMR} =52.4ms 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 T_{TMR} time has passed. If we want to blink the LED at the frequency of F_{LED} = 1/2 Hz , it’s period must be T_{LED} = 1/F_{LED} = 1/(1/2) = 2s . 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 N = t / T_{TMR} = 1000ms / 52.4288ms \approx 19 .

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:

F_{OUT}=\frac{F_{OSC}}{4\cdot PRESC\cdot RES}

This equation relates the output frequency F_{OUT} to the oscillator frequency F_{OSC} , 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 2^{16} , so the interrupt frequency, or the final output frequency, is 1.25MHz/2^{16} \approx 19Hz. Thus the interrupt is fired approximately 19 times a second, which means its period, or the time between 2 interrupts is T = 1/F = 1/19Hz \approx 52ms, 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:

PRESC=\frac{F_{OSC}}{4\cdot RES \cdot F_{OUT}}

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

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.

PIC18F452-p110-tbl11-2

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.

PIC18F452-p107-reg11-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;
Click to reveal more about T1CON bits (not required for the setup).

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).PIC18F452-p108-fig11-1-2

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 F_{OSC}=20MHz , prescale value of PRESC=4 and its resolution is $RES=2^{16}$ bits. Therefore, using the equation from the introduction, we get that the output frequency is F_{OUT}=\frac{F_{OSC}}{4\cdot PRESC\cdot RES}=\frac{20MHz}{4\cdot 4\cdot 2^{16}}=19.07Hz, therefore the period of setting interrupt flag is T_{OUT}=1/F_{OUT}=1/19.07Hz=52.4ms . 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 N=1000ms/52.4ms \approx 19. 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!

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

  • Pingback: Digital Clock using PIC Timers — Marian Longa's blog()

  • Navin Dias

    Impressive.can you make a tutorial on prioritybased interrupt functions.plz

  • jayesh

    hi sir, can u explain little bit about configuring bits while using timer?

  • jayesh

    what is the purpose of a delay function. and its never been called in the program, why?