Jump to content


View Other Content



Search Articles



Recent Comments


- - - - -

PIC18F4550 Timer0


As always, make sure you have a copy of the datasheet for your PIC. Here's a link to the PIC18F4550 datasheet.

In the LED blink code on the Downloads page, I use a for loop to create a delay in the program; after each delay, the logic level of the digital output driving the LED is toggled, turning on or off the LED. This is a bad way of doing things for a number of reasons! For one, it is very imprecise, and you might want to do something on an exact time interval as opposed to just around a certain time. Also, using a loop to delay the program is called a "blocking loop" because it doesn't allow anything else to execute in the program. This is really bad, because chances are you will want other things to be happening in your program (serial/USB communication, polling inputs, etc..) and a blocking loop doesn't allow for this. A much better way of making a delay in your program is using a timer, which is what you'll learn to do in this tutorial.

You can follow the configuration tutorial to learn how to configure your PIC18. I will be using a PIC18F4550 in this tutorial.

We will be using the Timer0 module, but the other timers are set up very similarly.

Some of the timers, including timer0, can be run in either 8-bit or 16-bit modes. We will use the 16-bit mode for convenience.

There are five registers we need to use to control the timer:

  • T0CON - The Timer0 Control Register
  • INTCON - Interrupt settings
  • INTCON2 - Interrupt settings
  • TMR0L - The low byte of the timers value
  • TMR0H - The high byte of the timers value

Here is the configuration code for the T0CON register:

T0CONbits.TMR0ON = 0; // Stop the timer
T0CONbits.T08BIT = 0; // Run in 16-bit mode
T0CONbits.T0CS = 0;   // Use system clock to increment timer
T0CONbits.PSA = 0;    // A prescaler is assigned for Timer0
T0CONbits.T0PS2 = 1;  // Use a 1:256 prescaler
T0CONbits.T0PS1 = 1;
T0CONbits.T0PS0 = 1;

First we stop the timer, so that we can change its settings. Then we set the timer to run in 16-bit mode, choose to increment the timer's value every instruction cycle (as opposed to an external trigger). Next, we turn on the prescaler and make the prescale value 1:256. This means that instead of the timer incrementing every instruction cycle, it increments every 256 instruction cycles. Why would we want to do this? Well, one reason is so that we can run the timer for a longer amount of time before it overflows, allowing us to easily have a longer delay.

Now we configure the INTCON register:

INTCONbits.GIEH = 1;   // Global Interrupt Enable - enables all unmasked interrupts
INTCONbits.TMR0IE = 1; // Enable Timer0 interrupt (occurs on overflow)
INTCONbits.TMR0IF = 0; // Clear Timer0 interrupt flag

If you do not want to use interrupts, then set GIEH and TMR0IE to zero, but you must still clear TMR0IF.

The last thing to do is start the timer:

T0CONbits.TMR0ON = 1; // Start the timer

Writing the timer value is a little tricky. A detail in the datasheet that could easily be overlooked is that you must first set the value of the high byte (TMR0H) and then set the value of the low byte (TMR0L). This is because the actual high byte of the timer (TMR0H is just a buffer) is updated on a write to the low byte.

Now that the timer is configured, you can either poll the timer value in your main loop (by reading TMR0L and then TMR0H), or by reading the value inside your ISR (Interrupt Service Routine).

Putting this all together, along with adding the configuration code for the device and a main function, we now have a program with a working timer, and I'll add in some LED-blinking code to boot. I'll post the non-interrupt version, since it's simpler:

#include <p18f4550.h> // Always include the header file
  
#pragma config FOSC = HSPLL_HS    // Using 20 MHz crystal with PLL
#pragma config PLLDIV = 5         // Divide by 5 to provide the 96 MHz PLL with 4 MHz input
#pragma config CPUDIV = OSC1_PLL2 // Divide 96 MHz PLL output by 2 to get 48 MHz system clock
#pragma config USBDIV = 2         // USB clock comes from 96 MHz PLL output / 2
#pragma config FCMEN = OFF // Disable Fail-Safe Clock Monitor
#pragma config IESO = OFF  // Disable Oscillator Switchover mode
#pragma config PWRT = OFF  // Disable Power-up timer
#pragma config BOR = OFF   // Disable Brown-out reset
#pragma config VREGEN = ON // Use internal USB 3.3V voltage regulator
#pragma config WDT = OFF   // Disable Watchdog timer
#pragma config MCLRE = ON  // Enable MCLR Enable
#pragma config LVP = OFF   // Disable low voltage ICSP
#pragma config ICPRT = OFF // Disable dedicated programming port (44-pin devices)
#pragma config CP0 = OFF   // Disable code protection
  
  
void main(void)
{
    unsigned int timerValue = 0;
    unsigned char* timerValuePtr = 0;
     
    // Timer0 configuration
    T0CONbits.TMR0ON = 0; // Stop the timer
    T0CONbits.T08BIT = 0; // Run in 16-bit mode
    T0CONbits.T0CS = 0;   // Use system clock to increment timer
    T0CONbits.PSA = 0;    // A prescaler is assigned for Timer0
    T0CONbits.T0PS2 = 1;  // Use a 1:256 prescaler
    T0CONbits.T0PS1 = 1;
    T0CONbits.T0PS0 = 1;
     
    INTCONbits.GIEH = 0;
    INTCONbits.TMR0IE = 0;
    INTCONbits.TMR0IF = 0;
     
    T0CONbits.TMR0ON = 1; // Start the timer
     
    // LED configuration
    TRISDbits.TRISD4 = 0; // Configure RD4, pin 2, as an output
     
    while(1) // Program loop
    {
        timerValuePtr = (unsigned char*)&timerValue;
        *timerValuePtr = TMR0L;
        timerValuePtr++;
        *timerValuePtr = TMR0H;
         
        if(timerValue >= 23441) // TCY = 4/FOSC = 4/48MHz ~= 83.33 ns. Timer increments every 83.33 ns * prescaler = 83.88 ns * 256 = 21.33 uS.
        {                       // So, for a half second delay, the timer value would be .5/(21.33 uS) ~= 23441.
            LATDbits.LATD4 ^= 1; // RD4, pin 2 
             
            timerValue = 0;
            TMR0H = 0;
            TMR0L = 0;
        }
    }
}



0 Comments


or Sign In