Basically, a timer is a clock that controls an event sequence at a fixed amount of time. Timers are used for the precise delay generation and also used to trigger an activity before and after a predetermined time and to measure the time elapsed between two successive events. Here in this chapter how to use a timer and counter with PIC18F4550 is described in detail.
The timer inside a microcontroller is a free running binary counter. The counter increments for each pulse applied to it. The counter counts continuously from 0 to (2^n)-1 where n is the number of bits. In PIC18F4550, there are 8-bit and 16-bit timers. The timer takes the internal clock as a reference clock, while the counter counts external clocks or pulses applied through port pins. So basically timer is a counter with an internal clock.
The main advantage of timers and counters is that it works independent of microcontroller CPU and the timer values can be read whenever needs. Basically, a standard microcontroller consists of 1 or more hardware timer modules of different bit lengths.
The initial values of the timer register can be set by the user and can be used to generate required counts.
Below is a functional diagram of a timer module.
Timers are also called counters because they are used to count external events. Timers are mainly used for counting or measuring external events.
For example in the case of a visitor counter, the sensor placed for detecting the presence of a person goes high when someone crosses the door. The output of the sensor is connected to the Timer Clock Input Pin of the microcontroller. The timer register inside the microcontroller increments each time when a person crosses the door. The value can be later read by the CPU.
Prescalar is a configurable clock-divider circuit. It can be used to divide the clock frequency input to the timer module. For example, if the instruction clock is 5MHz and we use a prescaler of 2 to divide it which effectively make the clock 2.5MHz. So each counting time will increase from 0.2 µs to 0.4 µs.
In PIC microcontroller, timer module provides 256, 128, 64, 32, 16, 8, 4, 2 and 1. 1 is actually prescaler bypassed.
Overflow means the counter reached its maximum output and roll over to zero. The microcontroller has an overflow flag to indicate the overflow of the counter and generates overflow interrupts. This gives an additional option to count the number of times the counter overflowed and it extends the range of the counter. For example, if we need to count up to 512, we can use the 8-bit timer and overflow flag. After two overflows, the count must be 512 (256+256).
PIC18F4550 consists of four hardware timers namely Timer 0, Timer 1, Timer 2, Timer 3. Timer 2 is an 8-bit timer and all others are 16-bit timers.
The Timer0 module has the following features:
The Timer1 timer/counter module incorporates these features:
The timer2 module timer incorporates the following features:
The Timer3 module timer/counter incorporates these features:
The Functioning of the timer0 can be understood from this block diagram.
The Timer 0 stores the value TMR0 registers, TMR0H is the high byte and TMR0L is the lower byte. The higher byte will be useful when using 16-bit mode. Timer0 is worked as timer and counter. In timer mode, the module increments on every clock by default, unless a prescaler value is written into TMR0 register. Clear the T0CS bit(5th bit) of T0CON register to select the timer mode.
T0CONbits.T08BIT = 0; // 16-bit mode selected T0CONbits.T0CS = 0; // Internal clock selected (timer mode ON) T0CONbits.PSA = 1; // Prescalar NOT assigned T0CONbits.TMR0ON = 1; // Turn ON the timer
For using prescaler, clear the PSA bit and assign the corresponding value in T0PS2, T0PS1 and T0PS0 registers.
For example, to assign a prescaler of value 2
T0CONbits.T08BIT = 0; // 16-bit mode selected T0CONbits.T0CS = 0; // Internal clock selected (timer mode ON) T0CONbits.PSA = 0; // Prescalar assigned T0CONbits.T0PS0 = 0; // Prescalar values T0CONbits.T0PS1 = 0; // Prescalar values T0CONbits.T0PS2 = 0; // Prescalar values T0CONbits.TMR0ON = 1; // Turn ON the timer
Set the T0CS bit in T0CON register to select the Counter mode. Timer0 increments based on the pulse on pin RA4/T0CKI. The T0SE bit in T0CON register determines the rising edge or falling edge. Setting this bit will make it a rising edge and clearing it will select a falling edge. The procedure of delay calculation is described in the chapter Timer Delay Implementation
T0CONbits.T0SE = 0; // falling edge selected T0CONbits.T0CS = 0; // Internal clock selected (timer mode ON) T0CONbits.PSA = 1; // Prescalar NOT assigned T0CONbits.TMR0ON = 1; // Turn ON the timer
Every time an overflow occurs at (FF to 00 in 8-bit mode and FFFF to 0000 in 16-bit mode). The setting of corresponding bits in INTCON registers to generate an interrupt.
Peripheral interrupt enable (GIE), global interrupt enable (PEIE), Timer0 interrupt enable (TMR0IE) bits to activate it. Overflow will set TMR0IF bit.
In the above timer code, we didn’t use any built-in library for learning. For the actual usage, we don’t need to write register level definitions for each time, instead, we can use a built-in library in XC8 compiler.
In the below firmware, we use 16-bit mode with 256 prescaled with an interrupt. The PORT B will complement in every overflow.
#include <xc.h> #include <plib/timers.h> // include header files #include "config.h" // define configuration bits in this file unsigned char timerconfig; void main(void) { TRISB=0x00; // make PORTB as output LATB0=1; // make PORTB high /* set timer configuration * Timer interrupt on * Set timer 16 bit *setting prescalar 256 */ timerconfig= TIMER_INT_ON & T0_16BIT & T0_SOURCE_INT & T0_PS_1_256 ; OpenTimer0(timerconfig); // Load timer 0 configuration WriteTimer0(0xB3B4); // Load starting value to the timer 0 INTCONbits.TMR0IF = 1; // Enable timer interrupts ei(); // enabling interrupt while(1); // Runs forever (wait here) } void interrupt TimerOverflow() // Interrupt service routine { di(); // disable interrupt forawhile if(INTCONbits.TMR0IF == 1) // When timer overflows { LATB0 = ~LATB0; // make PORTB LOW INTCONbits.TMR0IF = 0; // clear the timer0 interrupt flag WriteTimer0(0xB3B4); // load the timer value } ei(); // Enable interrupt }