Using PIC Timer with PIC16F84A

PIC16F877A

In this tutorial, I will discuss how to use the PIC16F84A microcontroller timer module for a variety of applications including turning a LED on and off without the use of a software delay subroutine.

Introduction

The PIC16F84A has one 8-bit free-running timer named TMR0. The term "free-running" means it continually runs in the background and has no start or stop control. You can read or write values from/to this timer. Since it's an 8-bit timer, the TMR0 can count from 0 to 255 (28 = 256).

Other microcontrollers have multiple timers with higher bit numbers: the PIC16F877A has two 8-bit and one 16-bit timer while the PIC18F4550 has three 16-bit and one 8-bit timer. Each count elapses after two instruction cycles by default. Recall that the instruction cycle speed is calculated (for a 4 MHz oscillator) using

You can extend the time it takes for each count using prescalers. When the timer reaches 255, the timer overflows and will go back to 0. An interrupt is associated with timer overflow.

Configuring the PIC Timer

All settings for the TMR0 is configured using the OPTION register. The PIC16F84A datasheet describes this register as follows:

The OPTION register is a readable and writable register which contains various control bits to configure the TMR0/WDT prescaler, the external INT interrupt, TMR0, and the weak pull-ups on PORTB.

Here are the bits for this register:

option register

Look at bit 5 of the OPTION register named TOCS (TMR0 Clock Source Select). To use the timer, this bit should be cleared. If this is set, the TMR0 module is used for counting (discussed in a separate tutorial).

Prescaler

The prescaler will determine how many source edges will increment the TMR0 register value by 1. In short, the prescaler allows us to extend the time before the timer overflows. There are eight prescale settings which are chosen via the PS <2:0> bits of the OPTION register:

PIC16F84A Timer0 Prescaler

Bit 3 is the prescale assignment bit. Clearing it will assign the prescale to TMR0. Setting it would assign the prescale to the watchdog timer (which is another timer with a different use). Note that any write instructions to TMR0 (clrf, bsf, movwf, etc) will clear the prescale value.

Example: If the prescaler is set to the TMR0 and PS <3:0> is binary 2 (0102), then it would take 8 counts before the timer increments by one and so overflows after 2.04 milliseconds (assuming a 4 MHz crystal is used) because

Setting up the PIC Timer via ASM

Here is a short code snippet that configures the timer using the maximum prescale.

bsf STATUS, RP0 ;switch to Bank 1; OPTION register is at Bank 1
movlw 27h ;binary 00100111 - maximum prescale, assigned to TMR0
movwf OPTION_REG ;this is the name of the register on the .INC of P16F84A
bcf STATUS, RP0 ;switch to Bank 0

Turning an LED On/Off using PIC Timer

list p=16f84a
include <P16F84A.INC>

org 0x00
goto start

start bsf STATUS, RP0 ;switch to Bank 1
bsf STATUS, RP0 ;switch to Bank 1; OPTION register is at Bank 1
movlw 0x00
movwf TRISB ;PORTB all output
movlw 27h ;binary 00100111 - maximum prescale, assigned to TMR0
movwf OPTION_REG ;this is the name of the register on the .INC of P16F84A
bcf STATUS, RP0 ;switch to Bank 0
clrf TMR0 ;initialize the timer to zero
;---------------main routine
main
movf TMR0,0 ;move contents of TMR0 to W
andlw 0x01 ;mask so that only bit 0 of W is moved
movwf PORTB ;to PORTB
goto main
end

What this code does is simply toggle the state of PORTB.0 synchronously with bit 0 of the TMR0 register. The toggling is a bit fast since the least significant bit changes state the fastest (around 254 microseconds for a 4 MHz crystal) for every count.

To make it slower, you can use the next bit (bit 1) by changing

andlw 0x01

to

andlw 0x02

Of course, PORTB.1 will now be blinking instead of PORTB.0. The slowest blink can be achieved if andlw 0x80 (masking the most significant bit) is used.

A One Second Delay

So how can we achieve a delay of one second? If you do the math, the timer overflows (max prescale, 4 MHz crystal) after 65.536 milliseconds.  That's around 1/15 of a full second. Knowing this, we can wait for the timer to overflow 15 times before toggling the state of a pin. This is achieved by the code below:

list p=16f84a
include <P16F84A.INC>

cblock 0x0c
COUNT
endc

org 0x00
goto start

start bsf STATUS, RP0 ;switch to Bank 1
movlw 0x10
movwf TRISA ;PORTA all output
movlw 0x00
movwf TRISB ;PORTB all output
movlw 07h ;00000111
movwf OPTION_REG ;Counter mode, falling edge, prescaler set to maximum
bcf STATUS, RP0 ;switch to Bank 0
movlw 0x0F
movwf COUNT

;---------------main routine
main bcf PORTB,0
movf TMR0,0
sublw .255
btfss STATUS, Z
goto main ; keep looping until timer reaches 255
decfsz COUNT,1 ;if set, decrement COUNT
goto main ;if count is not yet zero, repeat the loop
main2 bsf PORTB,0 ;if count reaches zero, set PORTB.0
movf TMR0,0 ;repeat process to delay turn off
sublw .255
btfss STATUS, Z
goto main2
decfsz COUNT,1
goto main2
goto main
end

What I did here is continually subtract 255 from TMR0.  If TMR0 reaches 255, the result of the sublw .255 instruction would be zero, setting the Z bit of the STATUS register. When this happens, the COUNT variable which is initialized to 15, will be decreased by 1. PORTB.0 will be set when the COUNT variable reaches zero. The process is then repeated to delay the clearing of PORTB.0.

Although the above code works, I still suggest you use the timer overflow interrupt to delay things which uses less program space.

Next>> Asynchronous Serial (USART) Communication with PIC16F877A