6
TIMERS AND COUNTERS

General

If you have no knowledge about timers, you should read this chapter carefully before taking on the chapters that follow. Those chapters provide a much more advanced discussion of the devices based on their usage, as opposed to the introductory approach provided here. However, there is some repetition, so as to allow each part of this book to stand independently.

Most users will find that using timers and counters is the hardest part of learning how to use PIC microcontrollers. With this in mind, we will proceed in a step-by-step manner and build up the programs in pieces that are easier to digest. Once you get comfortable with their setup procedures, you will find that timers and counters are not so intimidating.

We will cover timers and counters separately. Counters are essentially timers that get their clock input from an outside source. There are two counters in the 16F877A, and they are associated with Timer0 and Timer1. Timer2 cannot be used as a counter because it has no way to read an external signal into this timer.

Note The clock frequency utilized by the timers is one-fourth of the oscillator frequency. This is the frequency of the instruction clock. This means that the counters are affected by every fourth count of the main oscillator. The frequency is referred to as Fosc/4 in the literature. When responding to an external clock signal, the response is to the actual frequency of the input.

Caution The PICBASIC PRO Compiler generates code that does not respond to interrupts while a compiled instruction is being executed. Therefore, long PAUSEs (meaning long enough to lose an interrupt signal, depending on how the timer is set up) can lead to lost interrupts if more than one interrupt occurs during the pause. Since interrupts are used for the express purpose of handling critical response/timing needs, this is most undesirable. PAUSE commands should be used with care under these conditions. The program samples provided next give examples of how they can be written to generate shorter pauses.

Timers

Timer0 will be covered in more detail as a prototypical timer, and discussion and examples for the use of Timer1 and Timer2 will be provided.

The use of timers internal to microprocessors is a bit more complicated than what we have been doing so far because there is a considerable amount of setup required before the timer can be used, and also because the options for setting up the timers are extensive. We will cover the timers one at a time in an introductory manner, but be warned that there is an entire manual (PICmicro Mid-Range MCU Family Reference Manual [DS33023]) available from Microchip Technology Inc. that covers nothing but timers, so our coverage here will, of necessity, be rudimentary.

Understanding timers has to do with understanding how to turn them ON and OFF and how to read and set the various bits and bytes that relate to them. Essentially, in the typical timer application, you turn a timer ON by turning on its enable bit. The timer then counts a certain number of clock cycles and sets an interrupt bit, thus causing an interrupt. Your program responds to the interrupt by executing a specific interrupt handling routine and then clearing the interrupt bit. The program then returns to wherever it was when the interrupt occurred. The pre/postscalars have to do with modifying the time it takes for an interrupt to take place. The hard part is finding out which bit does what and where it is located, so reading and understanding the datasheet chapter on the timer you are using is imperative. There is no escaping this horror!

Timers allow the microcontroller to create and react to chronological events. These include:

sqr Timing events

sqr The creation of clocks for various purposes

sqr Generating timed interrupts

sqr Controlling PWM generation

sqr Waking the PIC from its sleep mode at intervals to do work (and back to sleep)

sqr Special use of the watchdog timer

The PIC 16F877A has three internal timers. There is also the watchdog timer, which is discussed after the standard timers.

sqr Timer0 An 8-bit free-running timer/counter with an optional prescalar. It’s the simplest of the timers to use.

sqr Timer1 A 16-bit timer that can be used as a timer or as a counter. It is the only 16-bit timer that can be used as a counter. It is also the most complicated of the timers.

sqr Timer2 An 8-bit timer with a prescalar and postscalar and cannot be used as a counter. There is no input line for this timer.

Each timer has a timer control register that sets the options and properties that the timer will exhibit. All the timers are similar and each of them has special features that give it special properties. It is imperative you refer to your datasheet for the PIC 16F877A as you experiment with the timer functions. Once you start to understand what the PIC designers are up to with the timer functions, it will start to come together in your mind.

Timers can have prescalars and/or postscalars associated with them that can be used to multiply the timer setting by a limited number of integer counts. The scaling ability is not adequate to allow all exact time intervals to be created but is adequate for all practical purposes. To the inability to create perfectly timed interrupts, we have to add the uncertainty in the frequency of the oscillator crystal, which is usually not exactly what it is stated to be (and which is affected by the ambient temperature as the circuitry warms up). Though fairly accurate timings can be achieved with the hardware as received, additional software adjustments may have to be added if extremely accurate results are desired. The software can be designed to make a correction to the timing every so often to make it more accurate. We will also need an external source that is at least as accurate as we want our timer to be, so we can verify the accuracy of the device we create.

Timer0

Let’s write a simple program to see how Timer0 works. We will use the LED bargraph to show us what is going on inside the microcontroller.

As always, the bargraph is connected to the eight lines of PORTD of the LAB-X1.

First, let’s write a program that will light the two LEDs connected to D0 and D1 alternately. (See Program 6.1.) Having them light alternately lets you know that the program is running, or more accurately, it lets you know that the segment of the program that contains this part of the code is working. These two LEDs will be used to represent the foreground task in our program. There is no timer process in this program at this stage. There are no interrupts. The program just blinks the LEDs.

Program 6.1 Foreground program blinks two LEDs alternately (No timer is being used in this program at this time)

 CLEAR                    ; clears all memory locations
 DEFINE OSC 4             ; using a 4-MHz oscillator here
 TRISD = %11110000        ; make D0 to D3 outputs
 PORTD.0 = 0              ; turn off bit D0
 PORTD.1 = 1              ; turn on bit D1
 ALPHA VAR WORD           ; Set up a variable for counting
                          ;
 MAINLOOP:                ; main loop
   IF PORTD.1 = 0 THEN    ; the next lines of code turn the LEDs ON
     PORTD.1 = 1          ; if they are OFF
     PORTD.0 = 0          ;
   ELSE                   ;
     PORTD.1 = 0          ; and OFF if they 
     PORTD.0 = 1          ; are ON
   ENDIF                  ;
   FOR ALPHA = 1 TO 300   ; this loop replaces a long pause command
     PAUSEUS 100          ; with short pauses that are essentially
   NEXT ALPHA             ; independent of the clock frequency.
 GOTO MAINLOOP            ; do it all forever
 END                      ; all programs need to end with END

The use of the PAUSEUS loop in the Program 6.1 provides a latency of 100 microseconds (worst case) in the response to an interrupt and eliminates most of the effect of changing the OSC frequency if that should become necessary. It is better than using an empty counter, which would be completely dependent of the frequency of the system oscillator. (There is an assumption here that the 100 μsec latency is completely tolerable to the task at hand, and it is for this program. It may not be for your real-world program though, so it may need to be adjusted.)

We are turning one LED OFF and another LED ON to provide a more positive feedback. As long as we are executing the main loop, the LCDs will light alternately and provide a dynamic feedback of the operation of the program in the foreground loop.

We select a relatively fast ON-OFF cycle so we will better be able to see minor delays and glitches that may appear in the operation of the program as we proceed.

Run this program to get familiar with the operation of the two LEDs. Adjust the counter (the 300 value) to suit your taste.

Next, we want to add the code that will interrupt this program periodically and make a third LED go ON and then OFF using an approximately one-second cycle. This will serve as the interrupt-driven task we are interested in learning how to create. This is the IMPORTANT task in this particular exercise.

Here is what must be added to the program to get the interrupt-driven LED operational.

Enable Timer0 and its interrupts with appropriate register/bit settings.

Add the ON INTERRUPT command to tell the program where to go when an interrupt occurs.

Set up the interrupt routine to do what needs to be done.

The interrupt routine counts to 61 and turns the LED ON if it is OFF and OFF if it is ON.

Clear the interrupt flag that was set by Timer0.

Send the program back to where it was interrupted with the RESUME command.

WHY ARE WE USING 61?

The prescalar is set to 64 (bits 0 to 2 are set at 101 in the OPTION_REG).

The counter interrupts every 256 counts.

256 × 64 = 16,384

Clock is at 4,000,000 Hz

Fosc/4 is 1,000,000

1,000,000 / 16,384 = 61.0532. Its not exactly 61, but it is close enough for our purposes for now.

The lines of code now look like those in Program 6.2.

Program 6.2 Using TIMER0 (Program blinks two LEDs [D1 and D0] alternately and blinks a third LED [D2] for one second ON and one second OFF as controlled by the interrupt signal)

CLEAR                 ; clear memory
DEFINE OSC 4           ; using a 4-MHz oscillator
                        ;
OPTION_REG=%10000101    ; page 48 of datasheet
                        ; bit 7=1 disable pull ups on PORTB
                        ; bit 5=0 selects timer mode
                        ; bit 2=1 }
                        ; bit 1=0 } sets Timer0 prescalar to 64
                        ; bit 0=1 }
                        ;
INTCON=%10100000        ; bit 7=1 Enables all unmasked interrupts
                        ; bit 5=1 Enables Timer0 overflow interrupt
                        ; bit 2 flag will be set on interrupt and
                        ; has to be cleared in the interrupt
                        ; routine. It is set clear at start
ALPHA VAR WORD          ; this variable counts in the PauseUS loop
BETA VAR BYTE           ; this variable counts the 61 interrupt
                        ; ticks
TRISD = %11110100       ; sets the 3 output pins in the D port
PORTD = %00000000       ; sets all pins low in the D port
BETA=0                  ;
ON INTERRUPT GOTO INTROUTINE    ; this line needs to be early in
                                ; the program,
                        ; in any case, before the routine is called.
                        ;
MAINLOOP:           ; main loop blinks D0 and D1 alternately
  IF PORTD.1 = 0 THEN   ; ]
   PORTD.1 = 1          ; ]
   PORTD.0 = 0          ; ] this part of the program blinks two
                        ; LEDs in
  ELSE                  ; ] the foreground as described before
   PORTD.1 = 0          ; ]
   PORTD.0 = 1          ; ]
  ENDIF                 ; ]
                        ;
  FOR ALPHA = 1 TO 300  ; the long pause is eliminated with this
                        ; loop
    PAUSEUS 100         ; PAUSE command with short latency
  NEXT ALPHA           ;
GOTO MAINLOOP           ; end of loop
                        ;
DISABLE                 ; DISABLE//ENABLE must brkt the
                        ; interrupt routine

 INTROUTINE:            ; this information is used by the compiler
                        ; only.
   BETA = BETA + 1      ;
   IF BETA < 61 THEN ENDINTERRUPT  ; one second has not yet passed
   BETA = 0  ;          ;
   IF PORTD.3 = 1 THEN  ; interrupt loop turns D3 on and off every
     PORTD.3 = 0        ; 61 times through the interrupt routine.
   ELSE                 ; That is about one second per full cycle
     PORTD.3 = 1        ;
   ENDIF                ;
 ENDINTERRUPT:          ;
   INTCON.2 = 0         ; clears the interrupt flag.
 RESUME                 ; resume the main program
 ENABLE                 ; DISABLE and ENABLE must bracket
                        ; the interrupt routine
 END                    ; end program

Make your predictions and then…

Try changing the 3 low bits in OPTION_REG to see how they affect the operation of the interrupt.

In Program 6.2, Timer0 is running free and providing an interrupt every time its 8-bit counter overflows from FF to 00. The prescalar is set to 64 so we get the interrupt after 64 of these cycles. When this happens, we jump to the “IntRoutine” routine, where we make sure that 61 interrupts have taken place, and if they have, we change the state of an LED and return to the place where the interrupt took place. (It happens that it takes approximately 61 interrupts to equal one second in this routine with a processor running at 4 MHz. This could be refined by trial and error after the initial calculation.)

Note that the interrupt is disabled while we are in the “IntRoutine” routine, but the free running counter is still running toward its next overflow meaning that whatever we do has to get done in less than 1/61 seconds if we are not going to miss the next interrupt, unless we make some other arrangements to count all the interrupts (with an internal subroutine or some other scheme). It can become quite complicated if a lot needs to be done, so we will not worry about it here.

Before going any further, let’s take a closer look at the OPTION_REG and the INTCON (interrupt control) register. These are 8-bit registers with the 8 bits of each register assigned as follows:

OPTION_REG the option register:

Bit 7

RBPU. Not of interest to us at this time. (This bit enables the port B pull ups.)

Bit 6

INTEDG. Not of interest to us at this time. (The interrupt edge select bit determines which edge the interrupt will be selected on, rising [1] or falling [0]). Either one works for us.

Bit 5

T0CS, Timer0 clock select bit. Selects which clock will be used.

1 = Transition on TOCKI pin.

0 = Internal instruction cycle clock (CLKOUT). We will use this, the oscillator. See bit 4 description

Bit 4

T0SE, source edge select bit. Determines when the counter will increment.

1 = Increment on high-to-low transition of TOCKI pin.

0 = Increment on low-to-high transition of TOCKI pin.

Bit 3

PSA, prescalar assignment pin. Decides what the prescalar applies to.

1 = Select watchdog timer (WDT)

0 = Select Timer0. We will be using this.

Bits 2, 1, and 0 define the prescalar value for the timer. As mentioned earlier, the prescalar can be associated with Timer0 or with the watchdog timer (WDT) but not both. Note that the scaling for the WDT is half the value for Timer0 for the same three bits.

Bit value

TMR0 rate

WDTrate

000

1:2

1:1

001

1:4

1:2

010

1:8

1:4

011

1:16

1:8

100

1:32

1:16

101

1:64

1:32    We will use this.

110

1:128

1:64

111

1:256

1:128

Caution A very specific sequence must be followed (which does not apply here) when changing the prescalar assignment from Timer0 to the WDT to make sure an unintended reset does not take place. This is described in detail in the PICmicro Mid-Range MCU Family Reference Manual (DS33023)

As per the preceding, in our specific example, OPTION_REG is set to %10000101.

Refer to the datasheet for more specific information.

INTCON the interrupt control register values are as follows:

Bit 7=1

Enables global interrupts.

Bit 6=1

Enables all peripheral interrupts.

Bit 5 =1

Enables an interrupt to be set on Bit 2 below when Timer0 overflows.

Bit 4 =1

Enables an interrupt if RB0 changes.

Bit 3 =1

Enables an interrupt if any of the PORTB pins are programmed as inputs and change state.

Bit 2

Is the Interrupt flag for Timer0.

Bit 1

Is the Interrupt flag for all internal interrupts.

Bit 0

Is the Interrupt flag for pins B7 to B4 if they change state.

Note that Bit 2 is set clear when we start and will be set to 1 when the first interrupt takes place. It has to be re-cleared within the interrupt service routine thereafter. (This is usually at the end of the routine, but not necessarily so.)

A TIMER0 CLOCK: FROM A PROGRAM BY MICROENGINEERING LABS (ON THEIR WEB SITE)

The following program, written by microEngineering Labs and provided by them as a part of the information on their Web site, demonstrates the use of interrupts to create a reasonably accurate clock that uses the LCD display to show the time in hours, minutes, and seconds.

LCD CLOCK PROGRAM USING ON INTERRUPT

This program uses TMR0 and prescalar. Watchdog timer should be set to OFF at program time, and Nap and Sleep should not be used.

Buttons may be used to set hours and minutes.

In Program 6.3, the CLEAR and OSC commands are not used, but we will always use them in our programs.

Program 6.3 Timer0 usage per microEngineering Labs program (Hours, seconds, and minutes digital clock)

 DEFINE LCD_DREG PORTD   ; define LCD connections
 DEFINE LCD_DBIT 4       ;
 DEFINE LCD_RSREG PORTE  ;
 DEFINE LCD_RSBIT 0      ;
 DEFINE LCD_EREG PORTE   ;
 DEFINE LCD_EBIT 1       ;
                         ;
 HOUR VAR BYTE           ; define hour variable
 DHOUR VAR BYTE          ; define display hour variable
 MINUTE VAR BYTE         ; define minute variable
 SECOND VAR BYTE         ; define second variable
 TICKS VAR BYTE          ; define pieces of seconds variable
 UPDATE VAR BYTE         ; define variable to indicate update of LCD
 I VAR BYTE              ; de bounce loop variable
 ADCON1 = %00000111      ; parts of PORTA and E made digital
 LOW PORTE.2             ; LCD R/W low = write
 PAUSE 100               ; wait for LCD to startup

 HOUR = 0                ; set initial time to 00:00:00
 MINUTE = 0              ;
 SECOND = 0              ;
 TICKS = 0               ;
 UPDATE = 1              ; force first display
                         ;set TMR0 to interrupt every 16.384
                         ; milliseconds
 OPTION_REG = %01010101  ; set TMR0 configuration and enable 
                         ; PORTB pullups
 INTCON = %10100000      ; enable TMR0 interrupts
 ON INTERRUPT GOTO TICKINT  ;
                         ; main program loop -
 MAINLOOP:               ; in this case, it only updates the LCD
                         ; with the it
 TRISB = %11110000       ; enable all buttons 
 PORTB =%00000000        ; PORTB lines low to read buttons
                         ; check any button pressed to set time
 IF PORTB.7 = 0 THEN DECMIN  ;
 IF PORTB.6 = 0 THEN INCMIN  ; last 2 buttons set minute
 IF PORTB.5 = 0 THEN DECHR   ;
 IF PORTB.4 = 0 THEN INCHR   ;
                             ; first 2 buttons set hour
 CHKUP:  IF UPDATE = 1 THEN  ; check for time to update screen
 LCDOUT $FE, 1               ; clear screen
                             ; display time as hh:mm:ss
 DHOUR = HOUR                ; change hour 0 to 12
 IF (HOUR // 12) = 0 THEN    ;
      DHOUR = DHOUR + 12     ;
 ENDIF                       ;
                             ;
 IF HOUR < 12 THEN           ; check for AM or PM
   LCDOUT DEC2 DHOUR, “:”, DEC2 MINUTE, “:”, DEC2 second, “ AM”_
   ELSE                      ;
     LCDOUT DEC2 (DHOUR - 12), “:”, DEC2 MINUTE, “:”, DEC2 SECOND,_
     “ PM”
   ENDIF                     ;
   UPDATE = 0                ; screen updated
 ENDIF                       ;
 GOTO MAINLOOP               ; do it all forever
                             ; increment minutes
 INCMIN: MINUTE = MINUTE + 1 ;
 IF MINUTE >= 60 THEN        ;
   MINUTE = 0                ;
 ENDIF                       ;
 GOTO DEBOUNCE               ;
                             ; increment hours
 
 INCHR:  HOUR = HOUR + 1         ;
 IF HOUR >= 24 THEN              ;
   HOUR = 0                      ;
 ENDIF                           ;
 GOTO DEBOUNCE                   ;
                                 ; decrement minutes
 DECMIN: MINUTE = MINUTE - 1     ;
 IF MINUTE >= 60 THEN            ;
   MINUTE = 59                   ;
 ENDIF                           ;
 GOTO DEBOUNCE                   ;
                                 ; decrement hours
 DECHR:  HOUR = HOUR - 1         ;
 IF HOUR >= 24 THEN              ;
   HOUR = 23                     ;
 ENDIF                           ;
                                 ; de-bounce and delay for 250 ms
 DEBOUNCE: FOR I = 1 TO 25       ;
   PAUSE 10                      ; 10 ms at a time so no interrupts 
                                 ; are lost
 NEXT I                          ;
 UPDATE = 1                      ; set to update screen
 GOTO CHKUP                      ;
                                 ; interrupt routine to handle each
                                 ; timer tick
 DISABLE                         ; disable interrupts during 
                                 ; interrupt handler
   TICKINT: TICKS = TICKS + 1    ; count pieces of seconds
     IF TICKS < 61 THEN TIEXIT   ; 61 ticks per second (16.384 ms 
                                 ; per tick)
                                 ; one second elapsed - update time
     TICKS = 0                   ;
     SECOND = SECOND + 1         ;
       IF SECOND >= 60 THEN      ;
        SECOND = 0               ;
        MINUTE = MINUTE + 1      ;
       IF MINUTE >= 60 THEN      ;
        MINUTE = 0               ;
        HOUR = HOUR + 1          ;
          IF HOUR >= 24 THEN     ;
          HOUR = 0               ;
        ENDIF                    ;
       ENDIF                     ;
     ENDIF                       ;
     UPDATE = 1                  ; set to update LCD
 TIEXIT: INTCON.2 = 0            ; reset timer interrupt flag
 RESUME                          ;
 END                             ;

In the preceding clock, the keyboard buttons are used as follows

SW1 and SW5 increment the hours.

SW2 and SW6 decrement the hours.

SW3 and SW7 increment the minutes.

SW4 and SW8 decrement the minutes.

The seconds cannot be affected other than with the reset switch.

Timer1: The Second Timer

The second timer, Timer1 is the 16-bit timer/counter. This is the most powerful timer in the MCU. As such, it is the hardest of the timers to understand and use but is also the most flexible of the three timers. It consists of two 8-bit registers and each register can be read and written to. The timer can be used either as a timer or as a counter depending on how the Timer1 clock select bit (TMR1CS), which is bit 1 in the Timer1 control register (T1CON), is set.

In Timer1, we can control the value that the timer starts its count with, and thus change the frequency of the interrupts. Here we are looking to see the effect of changing the value preload into Timer1 on the frequency of the interrupts as reflected in a very rudimentary pseudo-stopwatch. The higher the value of the preload, the sooner the counter will get to $FF and the faster the interrupts will come. We will display the value of the prescalar loaded into the timer on the LCD so we can see the correlation between the values and the actual operation of the interrupts. As the interrupts get closer and closer together, the time left to do the main task gets smaller and smaller and you can see this in the speed at which the stopwatch runs.

In the following program:

SW1 turns the stopwatch on.

SW2 stops the stopwatch.

SW3 resets the stopwatch.

POT0, the first potentiometer, is read and then written into TMR1H. (TMR1L is ignored in our case, but you may want to use it in a more critical application.)

The results of the experiment are shown in the LCD display.

Let’s creep up on the solution. We will develop the program segments and discuss them as we go, putting the segments together later for a program we can run, as shown in Program 6.4.

Program 6.4 Timer1 usage (Rudimentary timer operation that depends on value of POT-1)

 ; first let us set up the LCD display parameters.
 CLEAR                   ; clear memory
 DEFINE OSC 4            ; set osc speed
 DEFINE LCD_DREG PORTD   ; lcd is on PORTD
 DEFINE LCD_DBIT 4       ; we will use 4-bit protocol
 DEFINE LCD_RSREG PORTE  ; register select register
 DEFINE LCD_RSBIT 0      ; register select bit
 DEFINE LCD_EREG PORTE   ; enable Register
 DEFINE LCD_EBIT 1       ; enable bit
 PORTE.2 = 0             ; set for write mode
 PAUSE 500               ; wait .5 seconds
                         ;
 ; Next let us define the variables we will be using
 ADVAL VAR BYTE          ; create adval to store result
 TICKS VAR WORD          ;
 TENTHS VAR BYTE         ;
 SECS VAR WORD           ;
 MINS VAR BYTE           ;
                         ;
 ; Set the variable to specific values, not necessary in this program
 ; but a formality for clarity
 TICKS = 0               ;
 TENTHS = 0              ;
 SECS = 0                ;
 MINS = 0                ;
 ;
 ; Set the registers that will control the work. ;
 ; This is the nitty gritty of it so we will call out each bit.
 ; INTCON is the interrupt control register.
 ;
 INTCON      =%11000000
 ;  bit 7: GIE: Global Interrupt Enable bit, this has to be set
 ;  for any interrupt to work.
 ;         1 = Enables all un-masked interrupts
 ;         0 = Disables all interrupts
 ;  bit 6: PEIE: Peripheral Interrupt Enable bit
 ;         1 = Enables all un-masked peripheral interrupts
 ;         0 = Disables all peripheral interrupts
 ;  bit 5: T0IE: TMR0 Overflow Interrupt Enable bit
 ;         1 = Enables the TMR1 interrupt
 ;         0 = Disables the TMR1 interrupt
 ;  bit 4: INTE: RB0/INT External Interrupt Enable bit
 ;         1 = Enables the RB0/INT external interrupt
 ;         0 = Disables the RB0/INT external interrupt
 
 ;  bit 3: RBIE: RB Port Change Interrupt Enable bit
 ;         1 = Enables the RB port change interrupt
 ;         0 = Disables the RB port change interrupt
 ;  bit 2: T0IF: TMR0 Overflow Interrupt Flag bit
 ;         1 = TMR0 register has overflowed (must be cleared in
 ;             software)
 ;         0 = TMR0 register did not overflow
 ;  bit 1: INTF: RB0/INT External Interrupt Flag bit
 ;         1 = The RB0/INT external interrupt occurred (must be
 ;             cleared in software)
 ;         0 = The RB0/INT external interrupt did not occur
 ;  bit 0: RBIF: RB Port Change Interrupt Flag bit
 ;         1 = At least one of the RB7:RB4 pins changed state (must
 ;             be cleared in software)
 ;         0 = None of the RB7:RB4 pins have changed state
 ;
 ; T1CON is the timer 1 control register.
 T1CON =%00000001
 ;  bit 7-6: Unimplemented: Read as ‘0’
 ;  bit 5-4: T1CKPS1:T1CKPS0: Timer1 Input Clock Prescale Select bits
 ;           11 = 1:8 Prescale value
 ;           10 = 1:4 Prescale value
 ;           01 = 1:2 Prescale value
 ;           00 = 1:1 Prescale value
 ;  bit 3:   T1OSCEN: Timer1 Oscillator Enable Control bit
 ;           1 = Oscillator is enabled
 ;           0 = Oscillator is shut off (The oscillator inverter
 ;               is turned off to eliminate power drain)
 ;  bit 2:   T1SYNC: Timer1 External Clock Input Synchronization
 ;           Control bit
 ;           TMR1CS = 1
 ;           1 = Do not synchronize external clock input
 ;           0 = Synchronize external clock input
 ;           TMR1CS = 0
 ;           This bit is ignored. Timer1 uses the internal clock
 ;           when TMR1CS = 0.
 ;  bit 1:   TMR1CS: Timer1 Clock Source Select bit
 ;           1 = External clock from pin RC0/T1OSO/T1CKI (on the
 ;               rising edge)
 ;           0 = Internal clock (FOSC/4)
 ;  bit 0:   TMR1ON: Timer1 On bit
 ;           1 = Enables Timer1
 ;           0 = Stops Timer1
 ;
 ; The option register

 OPTION_REG = %00000000    ; Set Bit 7 to 0 and enable PORTB pullups
                           ; All other bits are for Timer 0 and
                           ; not applicable
                           ; here
 PIE1=%00000001            ; See datasheet, enables interrupt.
 ADCON0= %11000001         ; Configure and turn on A/D Module
 ;  bit 7-6: ADCS1: ADCS0: ; A/D Conversion Clock Select bits
 ;           00 = FOSC/2
 ;           01 = FOSC/8
 ;           10 = FOSC/32
 ;           11 = FRC (clock derived from an RC oscillation)
 ;  bit 5-3: CHS2:CHS0: Analog Channel Select bits
 ;           000 = channel 0, (RA0/AN0)
 ;           001 = channel 1, (RA1/AN1)
 ;           010 = channel 2, (RA2/AN2)
 ;           011 = channel 3, (RA3/AN3)
 ;           100 = channel 4, (RA5/AN4)
 ;           101 = channel 5, (RE0/AN5)(1)
 ;           110 = channel 6, (RE1/AN6)(1)
 ;           111 = channel 7, (RE2/AN7)(1)
 ;  bit 2: GO/DONE: A/D Conversion Status bit
 ;           If ADON = 1 See bit 0
 ;           1 = A/D conversion in progress (setting this bit starts
 ;               the A/D conversion)
 ;           0 = A/D conversion not in progress (This bit is
 ;               automatically cleared by hardware when the A/D
 ;               conversion is complete)
 ;  bit 1: Unimplemented: Read as ; 0;
 ;  bit 0: ADON: A/D On bit
 ;           1 = A/D converter module is operating
 ;           0 = A/D converter module is shutoff and consumes no
 ;               operating current
 ;
 ; The A to D control Register for Port A is ADCON1
 ADCON1 = %00000010  ; set part of PORTA analog
 ; The relevant table is on page 112 of the datasheet
 ; There are a number of choices which give us analog capabilities
 ; on PORTA.0
 ; and allow the voltage ; reference between Vdd and Vss. We have
 ; chosen 0010
 ; on the third line down in the table
 ;
 ; Next let us set up the port pin directions
 TRISA = %11111111   ; set PORTA to all input
 TRISB = %11110000   ; set up PORTB for keyboard reads
 PORTB.0 = 0         ; set so we can read row 1 only for now ;
 
 ON INTERRUPT GOTO TICKINT  ; tells the program where to go on
                            ; interrupt
                            ;
                            ; Initialize display and write to top
                            ; line
 LCDOUT $FE, 1, $FE, $80, “MM SS T”  ;
                                     ;
 MAINLOOP:     ;
 ADCON0.2 = 1  ; conversion to reads POT-1. Conversion start
 ;
               ; now and takes place during loop. If loop was
               ; short we would allow for that.
               ; then check the buttons to decide what to do
 IF PORTB.4  = 0 THEN STARTCLOCK  ;
 IF PORTB.5  = 0 THEN STOPCLOCK   ;
 IF PORTB.6  = 0 THEN CLEARCLOCK  ;
                                  ;
                                  ; and display what the clock
                                  ; status is
 LCDOUT $FE, $80, DEC2 MINS, “:“,DEC2 SECS, “:”, DEC TENTHS,_
 “POT1=”, DEC ADVAL, “ ”

 ; We are now ready to read what potentiometer setting is.
 ADVAL = ADRESH             ; we assumed that enough time has
                            ; passed
                            ; to have an updated value in the
                            ; registers. If not add wait
 GOTO MAINLOOP              ; do it again
                            ;
 DISABLE                    ; disable interrupts during
                            ; interrupt handler
 TICKINT:                   ;
   TICKS = TICKS + 1        ; ticks are influenced by the setting
                            ; of POT-1
   IF TICKS < 5 THEN TIEXIT ; arbitrary value to get one
                            ; second
   TICKS = 0                ;
                            ;
   TENTHS = TENTHS + 1      ;
   IF TENTHS <9 THEN TIEXIT ;
   TENTHS = 0               ;
                            ;
   SECS = SECS + 1          ; update seconds
   IF SECS < 59 THEN TIEXIT ;
   SECS = 0                 ;
   MINS = MINS + 1          ; update minutes
   TIEXIT:                  ;
 
   IF PORTB.5 = 0 THEN STOPCLOCK ;
   TMR1H=ADRESH             ;
   PIR1=0                   ;
 RESUME                     ; go back to the main routine
 ENABLE                     ;
                            ;
 DISABLE                    ;
 STARTCLOCK:                ;
   INTCON = %10100011       ; enable TMR1 interrupts
   TICKS = 0                ;
 GOTO MAINLOOP              ;
                            ;
 STOPCLOCK:                 ;
   INTCON = %10000011       ; disable TMR1 interrupts
   PAUSE 2                  ;
   TICKS = 0                ;
 GOTO MAINLOOP              ;
                            ;
 CLEARCLOCK:                ;
   INTCON = %10000011       ; disable TMR1 interrupts
   MINS = 0                 ;
   SECS = 0                 ;
   TENTHS = 0               ;
   TICKS = 0                ;
 GOTO MAINLOOP              ;
 ENABLE                     ;
 END                        ;

Run this program to see how the setting of the potentiometer affects the operation of the stopwatch. It becomes clear that choosing how the interrupt will serve our purposes is very important, and a bad choice can pretty much compromise the operation of the program.

We can read the timer and the interrupts at our discretion either before or after an interrupt has occurred, and the interrupt flag can be cleared whenever we wish, if it has been set. If it has not been set, there is no need to clear it.

Figure 6.1 provides a diagrammatic representation of how an interrupt routine is implemented in a typical program.

Even the 16-bit Timer1 on the 16F877A cannot time a long interval. Repeated intervals have to be put together to create long time periods. The longest possible time between interrupts for Timer1 (with a 4-MHz clock) is 0.524288 seconds. The maximum prescale value is 1:8. The postscalar is only available on Timer2 (which in any case is a shorter 8-bit timer). This results in a maximum time and is determined by multiplying the instruction clock cycle (1 μsec @ 4 MHz) by the prescale (8) by the number of counts from one overflow to the next (65536). 1 μsec * 8 * 65536 = 0.524288 seconds. On a 20-MHz machine, the interval would be one-fifth of this.

f0099-01

Figure 6.1 The simplified, basic structure of a typical interrupt routine. (Bits shown as being set are not the real bits.)

Timer1 uses two registers TMR1H and TMR1L.
The timer has the following general properties:

1. Increments from $0000 to $FFFF in two registers.

2. If the interrupt is enabled, an interrupt will occur when the two byte counter over-flows from $FFFF to $0000.

3. The device can be used as a timer.

4. The device can be used as a counter.

5. The timer registers can be read and written to at any time.

6. There is no postscalar for this timer.

Simply stated again, this timer is used by setting its registers to a specific value and using the interrupts this value creates in a useful way. A 16-bit timer will count up from where set to FFFF and then flip to the selected value and start over again. An interrupt occurs and the interrupt flag is set every time the register overflows from FFFF to 0. We respond to the interrupt by doing whatever needs to be done in response to the interrupt, resetting the interrupt flag and then going back to the main routine. On timers that permit the use of a prescalar and postscalars, the pre/postscalar allows us to increase the time between interrupts by multiplying the time between interrupts with a defined value in a 3- to 8-bit location. On writable timers, we have the ability to start the timers with values of our choice in the timer register(s). This gives us very useable but not absolute control over the interrupt intervals.

Consider the fact that a 0.01 second timer setting with a prescalar set to 16 would provide us with an interrupt every 0.16 seconds and we would have 0.16 seconds to do whatever we wanted to do between interrupts. Actually, less than 0.16 seconds, because there are still the other lines of code in that program that need to be executed.

So there are serious limits as to what can be put in the timer counter and what can be put in the prescalar. In addition, the interrupt frequency is (also) affected by the accuracy of the processor clock oscillator.

PRESCALARS

The value of the scaling that will be applied to the timer is determined by the contents of two bits in the interrupt control register. These bits multiply the time between interrupts by powers of 2 as explained in the following.

Prescalar

For Timer1

For the Watchdog Timer

00

Multiply by

1

00

Multiply by

2

01

Multiply by

2

01

Multiply by

4

10

Multiply by

4

10

Multiply by

8

11

Multiply by

8

11

Multiply by

16

The two bits are bit 4 and bit 5 of the Timer1 control register T1CON. The 8 bits in T1CON are assigned as follows

TMR1ON

Bit 0

1=Enables Timer1

0=Disables timer

TMR1CS

Bit 1

1=Use external clock

0=Use internal clock

TISYNC

Bit 2

1=Sync with internal clock input

0=Sync with external clock input

TIOSCEN

Bit 3

1=Enable oscillator

0=Shut off oscillator

T1CKPS1

Bit 4

Counter scalar is described above

T1CKPS0

Bit 5

Counter scalar is described above

a0100-01

a0100-02

USING TIMER1 TO RUN A CRITICAL INTERRUPT-DRIVEN TASK WHILE THE MAIN PROGRAM RUNS A FOREGROUND TASK

Let’s use this timer in the same way we used Timer0 earlier and see what the differences between the two timers are. To begin with, because Timer1 is 16 bits wide, it can take much longer for it to set its interrupt flag. The interrupt flag was set approximately 61 times a second by Timer0. Timer1 flag can take approximately 0.524 seconds, as calculated earlier, so it is set about two times a second. Let’s write a short Timer1 program that is similar to the original Timer0 blinker program to see how the differences shape up.

Program 6.5 blinks the LEDs at D0 and D1 ON and OFF alternately as the foreground part of the program. The interrupts generated by Timer1 are used to blink D3 ON and OFF at half-second intervals. Since the control of D3 is driven by the interrupt, the timing stays accurate. Any time used by the interrupt routine is lost by the foreground task and affects the overall frequency of the D0/D1 blink rate. It is important to understand this loss.

Program 6.5 Using Timer0 (Programs blinks two LEDs alternately and blinks a third LED approximately a half second ON and a half second OFF)

 CLEAR               ; clear
 DEFINE OSC 4        ; osc speed
 TRISD = %00000000   ; set all PORTD lines to output
 TRISE = %00000000   ; set all PORTE lines to output 
                     ; set the A to D control register for
                     ; digital ports D, E
 ADCON1=%00000111    ; needed, 16F877A because it 
                     ; has analog properties
 T1CON = %00000001   ; turn on Timer0, prescalar = 1
 INTCON = %11000000  ; enable global interrupts, peripheral 
                     ; interrupts
                     ;
 I VAR WORD          ; counter variable 
 J VAR WORD          ; counter variable
 PAUSE 500           ;
 I=0                 ; set counters to 0
 J=0                 ;
 PIE1.0 = 1          ; enable TMR1 overflow interrupt 
 ON INTERRUPT GOTO INTHANDLER  ;
 PORTD=0             ; turn off the entire port
 PORTD.3 = 0         ; light d3 on bargraph off, repeats above instr
 PORTD.2 = 0         ; light d3 on bargraph off, repeats above instr
                     ;
 MAINLOOP:           ;
 IF PORTD.1 = 0 THEN ; routine lights Do and D1 alternately to 
   PORTD.1 = 1       ; that the program is running the main routine
   PORTD.0 = 0       ;
 ELSE                ;
   PORTD.1 = 0       ;
   PORTD.0 = 1       ;
 ENDIF               ;
 FOR I = 1 TO 300    ; this is in lieu of a long pause instruction
   PAUSEUS 100       ; so that interrupt is not compromised
 NEXT I              ;
 GOTO MAINLOOP       ; do it all forever
                     ;
 DISABLE             ;

 INTHANDLER:         ; this is the interrupt service routine
 IF J < 6 THEN       ; this routine allows 6
   J = J+1           ; interrupts for each change of state
   GOTO COUNTNOTFULL ; of LED D3.
 ELSE                ;
   J = 0             ;
 ENDIF               ;
 IF PORTD.3 = 1 THEN ; the D3 blink routine
   PORTD.3 = 0       ;
 ELSE                ;
   PORTD.3 = 1       ;
 ENDIF               ;
 COUNTNOTFULL:       ;
 PIR1.0 = 0          ; must now clear the interrupt flag
 RESUME              ;
 ENABLE              ;
 END                 ; end program

Play with the value of the counter J to see how this affects the operation of the program. Study the differences between the programs to set and clear the timer flags. Though both of the preceding programs do the same thing, the setting of the potentiometer in the first program must be modified to match the needs of the timer being used.

Timer2: The Third Timer

Timer2 is an 8-bit “timer only,” meaning it cannot be used as a counter. It has a prescalar and a postscalar. The timer register for this counter is both writable and readable. If you can write to a counting register, you can set the value the count starts at and thus control the interval between interrupts (to some degree). That, and the ability to set the pre- and postscalars, gives you the control you need for effective control of the interrupt interval even though you still cannot time all events exactly because of the coarseness of the settings available. Timer2 has a period register PR2, which can be set by the user. The timer counts up from $00 to the value set in PR2, and when the two are the same, it resets to 0. Small values in PR2 can be used to create very rapid interrupts, so much so that there may be no time left to do anything else.

The Timer2 control register is T2CON and its 8 bits are assigned as follows:

T2CKPS0

Bit 0

Counter prescalar

0 = Disables timer

T2CKPS1

Bit 1

Counter prescalar

0 = Use internal clock

TMR2ON

Bit 2

1 = Timer2 is on

0 = Timer2 is off, shuts off oscillator

TOUTPS0

Bit 3

) Counter postscalar value

TOUTPS1

Bit 4

) Counter postscalar value

TOUTPS2

Bit 5

) Counter postscalar value

TOUTPS3

Bit 6

) Counter postscalar value

a0103-01

As always, the input clock for this timer is divided by 4 before it is fed to the timer. On a processor running at 4 MHz, the feed to the timer is at 1 MHz.

Prescalar for Timer2

Postscalar for Timer2

00

Multiply by

1, no scaling

0000

Multiply by

1, no scaling

01

Multiply by

4

0001

Multiply by

2

Multiply by

16

0010

Multiply by

3

 

 

 

0011

Multiply by

4

 

 

 

0100

Multiply by

5

 

 

 

0101

Multiply by

6

 

 

 

0110

Multiply by

7

 

 

 

0111

Multiply by

8

 

 

 

1000

Multiply by

9

 

 

 

1001

Multiply by

10

 

 

 

1010

Multiply by

11

 

 

 

1011

Multiply by

12

 

 

 

1100

Multiply by

13

 

 

 

1101

Multiply by

14

 

 

 

1110

Multiply by

15

 

 

 

1111

Multiply by

16

The timer is turned ON by setting bit 2 in register T2CON (the Timer2 control register).

The interrupt for Timer2 is enabled by setting Bit 1 of PIE1 and the interrupt lets the program know that it has occurred by setting Bit 1 in PIR1. (Bit 0 in both these registers are for Timer1.)

Bit 7 (the global interrupt enable bit) of INTCON, the interrupt control register, enables all interrupts, including those created by Timer2. Bit 6 of INTCON enables all unmasked peripheral interrupts, and using this feature is one of the ways of awakening a sleeping MCU.

Timer2 can also control the operation of the two PWM signals that can be programmed to appear on lines C1 and C2 with the HPWM command in PICBASIC PRO. Since this one timer controls both lines simultaneously, they both have to have the same PWM frequency. However, the relative width of the pulse within each of the PWM signals during each cycle does not have to be the same.

Timer2 is also used as a baud rate clock timer for communications. See page 54 of the datasheet.

MAKING SURE A TIMER IS WORKING

If you are uncomfortable about getting a timer working, or knowing for sure that it is working, write a very short program in which the main loop displays a variable that is incremented in the interrupt routine. If you see the value of the variable going up, you know the program is going to, and returning from, the interrupt routine.

WATCHDOG TIMER

A watchdog timer is a timer that sets an interrupt that tells us that for some reason the program has hung up or otherwise gone awry. As such, it is expected that in a properly written program, the watchdog timer will never set an interrupt. This is accomplished by resetting the watchdog timer every so often in the program. The compiler inserts these instructions automatically if the watchdog timer option is selected. However, setting the option does not guarantee that a program cannot or will not hang up. Software errors and infinite loops that reset the timer within themselves can still cause hangups.

The watchdog timer is scalable. It shares its scalar with Timer0 on an exclusive basis. Either it uses the scalar or Timer0 uses it. They cannot both use it at the same time. See discussion under Timer0 in the datasheet for more information.

Since PICBASIC PRO assumes that the watchdog timer will be run with a 1:128 prescalar, unwanted watchdog resets could occur when you assign the prescalar to Timer0. If you change the prescalar setting in OPTION_REG, you should disable the watchdog timer when programming. The watchdog enable/disable can be found on the configuration screen of your (hardware) programmer’s software.

Counters

Of the three timers in the 16F877A, only Timer0 (the 8-bit timer) and Timer1 (the 16-bit timer) can be used as counters. Timer2 does not have a counter input line provided for it. Generally speaking, this makes Timer0 suitable for use with small counts and rapid interrupts, and Timer1 suitable for larger counts.

HOW DOES A COUNTER WORK?

The operation of a counter is similar to the operation of a timer except that instead of getting its count from an internal clock or oscillator, the counter gets its signals from an outside source. This means we have to do the following things to use a counter:

sqr Decide which counter (timer) to use.

sqr Tell the counter where the signal is coming from.

sqr Tell it whether to count on a rising or falling edge.

sqr Decide what target count we are looking for.

sqr Tell the counter where to start counting because the interrupt will occur when the counter gets full.

sqr Decide whether we will need to scale the count by setting the scalar(s).

Once we start a counter, the counting continues until we deactivate it. There is no other way to stop it, nor any reason for doing so. It will reset if the MCU is reset and it can be reset by writing to it. The rest has to do with knowing what bits to set in the control registers to get the counters to operate in the way we want them to.

USING TIMER0 AS A COUNTER

Note Though often called TIMER0, and referred to as TIMER0 here and in the datasheet, the real designation of this timer address is TMR0.

The three registers related to the control of the TIMER0 module are TMR0, INTCON, and OPTION_REG. INTCON is the interrupt control register. See the datasheet for more information.

This counter has the following properties:

sqr 8-bit timer/counter

sqr Readable and writable

sqr 8-bit software programmable prescalar

sqr Internal or external clock select

sqr Interrupt on overflow from FFh to 00h

sqr Edge select for external clock

Counter mode is selected by setting OPTION_REG.5=1.

sqr The external input for the timer will come in on PORTA.4, which is pin 6 on the PIC.

sqr The edge direction is selected in OPTION_REG.4 (1=Rising edge).

sqr The prescalar is assigned with bits 0 to 3 of the OPTION_REG register. (See page 49 of the datasheet.)

Note again that the watchdog timer cannot use the prescalar when the prescalar is being used by Timer0.

Since this is an 8-bit counter, it is suited to the counting of small number of counts, but longer counts can be accommodated by using a routine to keep track of the interrupts.

Let’s use the counter to count the pulses received from a 20-slot encoder mounted on a small DC motor. This same source will be used later for the Timer1 experiment for comparison between counters.

We will set up to use the LCD display so we can display certain registers during the operation of Program 6.6. We will also set up to read the potentiometers so we can use their values to modify the program as it runs. Only POT0 and POT1 are used in the program.

Program 6.6 Using Timer0: Program counts the pulses from a motor-driven encoder (You can change the speed of the motor and the time for counts with the 2 POTs)

 CLEAR  
 DEFINE OSC 4              ; 4 MHz clock 
 DEFINE LCD_DREG PORTD     ; data register
 DEFINE LCD_RSREG PORTE    ; register select
 DEFINE LCD_RSBIT 0        ; pin number
 DEFINE LCD_EREG PORTE     ; enable register
 DEFINE LCD_EBIT  1        ; enable bit
 DEFINE LCD_RWREG PORTE    ; read/write register
 DEFINE LCD_RWBIT  2       ; read/write bit
 DEFINE LCD_BITS  8        ; width of data 
 DEFINE LCD_LINES 2        ; lines in display
 DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
 DEFINE LCD_DATAUS  50     ; delay in micro seconds
                           ;
 DEFINE CCP1_REG PORTC     ; define the hpwm settings
 DEFINE CCP1_BIT 2         ;
                           ; define the A2D values
 DEFINE ADC_BITS 8         ; set number of bits in result
 DEFINE ADC_CLOCK 3        ; set internal clock source (3=rc)
 DEFINE ADC_SAMPLEUS 50    ; set sampling time in us
                           ; set the analog to digital control
                           ; register
 ADCON1=%00000110          ; needed for the 16F877A LCD
 TEST VAR WORD             ;
 ADVAL0 VAR BYTE           ; create adval to store result 
 ADVAL1 VAR BYTE           ; create adval to store result 
 X VAR WORD                ;
 Y VAR WORD                ;
 PAUSE 500                 ; LCD start up
 LCDOUT $FE, 1             ; clear display
 OPTION_REG=%00110000      ;
 TMR0=0                    ;
                           ; set up the register i/o
 TRISC = %11110001         ; PORTC.0 is going to be the input to
                           ; start the motor in that we are using
                           ; a motor 
                           ; encoder for input
 PORTC.3=0                 ; enable the motor
 PORTC.2=0                 ; set the rotation direction
                           ;
 LOOP:                     ;
 ADCIN 0, ADVAL0           ; read channel 0 to ADVAL0
 ADCIN 1, ADVAL1           ; read channel 1 to ADVAL1
 ADCIN 3, ADVAL2           ; read channel 3 to ADVAL2 
                           ;
 TMR0=0                    ;

 PAUSE ADVAL1              ;
 X=TMR0                    ;
 IF ADVAL0>20 THEN         ;
   HPWM 2, ADVAL0, 32000   ;
   LCDOUT $FE, $80, DEC4 X,”  “,DEC ADVAL1,”  “
   LCDOUT $FE, $C0, “PWM = “,DEC ADVAL0,”          ”

 ELSE                      ;
   LCDOUT $FE, $C0, “PWM TOO LOW “,DEC ADVAL0,”  “
 ENDIF
                     ;
                           ;
 GOTO LOOP                 ;
 END                       ;

Play with the values of the two potentiometers to see what happens. Be careful about overflowing the counter past 255. Unexpected results can appear.

The Timer0 counter is affected by the OPTION REGISTER bits as follows:

OPTION_REG.6 = 1

; Interrupt on rising edge

OPTION_REG.5 = 0

; External clock

OPTION_REG.4 = 1

; Increment on falling edge not used

OPTION_REG.3 = 0

; Assign prescalar to Timer0

OPTION_REG.2 = 1

; ] These 3 bits set the prescalar

OPTION_REG.1 = 1

; ] You can experiment with changing these 3

OPTION_REG.0 = 1

; ] bits to see what happens.

Put these and other values in the program and run the program. See what happens.

USING TIMER1 AS A COUNTER

The operation of Timer1 as a counter is similar to the operation of Timer0, but because Timer1 is a 16-bit timer, much longer counts can be handled, and counts coming in at faster rates can be counted. It also means that a lot more can be done in the TimerLoop and BlinkerLoop routines if the program is designed to do so. However, the setup for Timer1 is more complicated because of the more numerous options available.

The differences between the use of the two timers have to do with the setup of the controlling registers. Timer1 is controlled by/uses six registers as compared to three for Timer0. They are:

INTCON

Interrupt control register

PIR1

Peripheral interrupt register 1

PIE1

Peripheral interrupt enable register 1

TMR1L

Low byte of the timer register

TMR1H

High byte of the timer register

T1CON

Timer1 interrupt control

Again, the frequency of the oscillator is divided by 4 before being fed to the counter when you use the internal clock (Fosc/4).

Page 52 of the datasheet reads:

Counter mode is selected by setting bit TMR1CS. In this mode, the timer increments on every rising edge of clock input on pin RC1/T1OSI/CCP2, when bit T1OSCEN is set, or on pin RC0/T1OSO/T1CKI, when bit T1OSCEN is cleared.

So three of the pins on the 16F877A can be used as inputs to the Timer1 counter module. They are:

Pin PORTA.4

which is the external clock input. Pin 6 on the PIC.

Pin PORTC.0

selected by setting TIOSCEN =1

Pin PORTC.1

selected by setting TIOSCEN=0

Timer1 is enabled by setting T1CON.0=1. It stops when this bit is turned off or disabled.

The clock that Timer1 will use is selected by T1CON.1. The external clock is selected by setting this to 1. The input for this external clock must be on PORTB.4.

In summary, 8 bits in the Timer1 control register, T1CON, provides the following functions:

a0108-01

a0108-02

a0108-03

a0108-04

Bit 5

Input prescalar

Bit 4

Input prescalar

Bit 3

Timer1 oscillator enable

Bit 2

Timer1 external clock synchronization

Bit 1

Timer1 clock select

Bit 0

Timer1 enable

If the interrupts are not going to be used, the other registers can be ignored. Set T1CON = %00110001.

The setting of these bits is described in detail on page 51 of the datasheet. Let’s look at Program 6.7, which reflects the preceding information.

 ; First let us define all the defines that we will need.
 ; Here all the defines are included as an example but
 ; not all are needed when using the LAB-X1.

Program 6.7 Timer1 as counter (Timer1 counts signals from a motor encoder)

 CLEAR                       ; clear memory
 DEFINE OSC 4                ; 4 MHz clock 
 DEFINE LCD_DREG PORTD       ; data register
 DEFINE LCD_RSREG PORTE      ; register select
 DEFINE LCD_RSBIT 0          ; pin number
 DEFINE LCD_EREG PORTE       ; enable register
 DEFINE LCD_EBIT 1           ; enable bit
 DEFINE LCD_RWREG PORTE      ; read/write register
 DEFINE LCD_RWBIT 2          ; read/write bit
 DEFINE LCD_BITS 8           ; width of data 
 DEFINE LCD_LINES 2          ; lines in display
 DEFINE LCD_COMMANDUS 2000   ; delay in micro seconds
 DEFINE LCD_DATAUS 50        ; delay in micro seconds
 ; The next two lines define which pin is going to be used for the
 ; HPWM signal
 ; that will control the speed of the motor. The encoder that we
 ; are looking
 ; at is attached to the motor
 DEFINE CCP1 REG PORTC      ; define the HPWM settings
 DEFINE CCP1_BIT 2          ; pin C1
 ; The next few lines define the reading of the three
 ; potentiometers on the board. Only the first 
 ; potentiometer is being used in the program but the others are
 ; defined so that you can 
 ; use them when you modify the program. The potentiometers give
 ; you values you can 
 ; change in real time. 
 ; define the A2D values
 DEFINE ADC_BITS  8     ; set number of bits in result
 DEFINE ADC_CLOCK 3     ; set internal clock source (3=rc)
 DEFINE ADC_SAMPLEUS 50 ; set sampling time in uS
 ; Next we set ADCON1 to bring the MCU back into digital mode. 
 ; Since this PIC has analog capability, it comes up in
 ; analog mode after a reset or on startup. 
                        ; set the Analog-to-Digital control register
 ADCON1=%00000111       ; needed for the LCD operation
                        ; we create the variables that we will need.
 TMR1 VAR WORD          ; set the variable for the timer
 ADVAL0 VAR BYTE        ; create adval to store result 
 ADVAL1 VAR BYTE        ; create adval to store result
 ADVAL2 VAR BYTE        ; create adval to store result 

 X VAR WORD             ; spare variable for experimentation
 Y VAR WORD             ; spare variable for experimentation
 PAUSE 500              ; pause for LCD to start up
 LCDOUT $FE, 1          ; clear Display and cursor home
                        ;
                        ; set up the register I/O
 TRISC = %11110001      ; PORTC.0 is going to be the Input
 CCP1CON = %00000101    ; capture every rising edge 
 T1CON = %00000011      ; no prescale/Osc off/Sync on
                        ; external source/TMR1 on 
                        ; start the motor, using a motor encoder
                        ; for input
 PORTC.3=0              ; enable the motor
 PORTC.2=1              ; set the rotation direction
 ; Next we go into the body of the program. The loop starts with
 ; reading all 
 ; three potentiometers though we are using only the first one to
 ; set the power
 ; and thus the speed of the motor.
 LOOP:                  ;
 ADCIN 0, ADVAL0        ; read channel 0 to ADVAL0
 ADCIN 1, ADVAL1        ; read channel 1 to ADVAL1
 ADCIN 3, ADVAL2        ; read channel 3 to ADVAL2
 ; If the duty cycle of the motor is less than 20 out of 255 the
 ; motor will not come on 
 ; so we make an allowance for that and display the condition on
 ; the LCD.
 IF ADVAL0>20 THEN      ;
   HPWM 2, ADVAL0, 32000 ;
   LCDOUT $FE, $C0, “PWM = “,DEC ADVAL0,”         “
 ELSE                  ;
   LCDOUT $FE, $C0, “PWM TOO LOW “,DEC ADVAL0,” “
 ENDIF                 ;

 ; Then we read the two timer registers to see how many counts
 ; went by.
 ; In our case the counts were too low to show up in the high
 ; bits so that 
 ; it can be ignored but if you have a faster count input, you
 ; might want 
 ; to add this information to the readout.
 TMR1H = 0              ; clear Timer1 high 8-bits 
 TMR1L = 0              ; clear Timer1 low 8-bits 
 T1CON.0 = 1            ; start 16-bit timer 
 PAUSE 100              ; capture 100 ms of Input Clock Frequency 

 T1CON.0 = 0            ; stop 16-bit Timer 
 TMR1.BYTE0 = TMR1L     ; read Low 8-bits 
 TMR1.BYTE1 = TMR1H     ; read High 8-bits 
 TMR1 = TMR1 - 11       ; capture Correction 
 IF TMR1 = 65525 THEN NOSIGNAL  ; see PICBASIC PRO manual for
                                ; explanation.
   LCDOUT $FE, $80, DEC5 TMR1,” COUNTS”  ; frequency display 
   PAUSE 10             ; slow down
 GOTO LOOP              ; do it again 
                        ;
 NOSIGNAL:              ;
   LCDOUT $FE, $80, “NO SIGNAL ”  ;
 GOTO LOOP              ;
 END                    ;

PRESCALARS AND POSTSCALARS

Prescalars and postscalars can be confusing for the beginner. Here is a simple explanation.

A prescalar is applied to the system clock and affects the timer by slowing down the system clock as it applies to the timer. Normally, the timer is fed by a fourth of the basic clock frequency, which is called Fosc/4. In a system running a 4 MHz clock, the timer sees a clock running at 1 MHz. If the prescalar is set for 1:8, the clock will be slowed down by another eight times, and the timer will see a clock at 125 KHz. Refer to the diagram on the bottom half of page 52 (for Timer1) in the datasheet to see how this applies to Timer1.

A postscalar is applied after the timer count exceeds its maximum value, generating an overflow condition. The postscalar setting determines how many overflows will go by before an interrupt is triggered. If the postscalar is set for 1:16, the timer will over-flow 16 times before an interrupt flag is set. The upper diagram on page 55 (for Timer2) of the datasheet shows this in its diagrammatic form and is worth studying.

All other things being equal, both pre- and postscalars are used to increase the time between interrupts.

When starting out, just leave the scalars at 1:1 values and nothing will be affected. We will not need to use them for any of the experiments we will be doing. Once you get more sophisticated in the use of timers, you can play with the values and learn more about how to use them. The primary use is in creating accurate timing intervals for communications and so on, because no external routines are necessary when this is done with scalars. When doing it this way, everything becomes internal to the PIC and is therefore not affected by external disturbances.

Additional information on timer modules is available in the PICmicro Mid-Range MCU Family Reference Manual (DS33023).

Exercises for Timers

1. Write a program to generate a 1-minute timer/clock with a 0.1 second display. Check its accuracy with the time site on the Internet.

Make adjustments so it is accurate to within 1 second per hour, and then 1 second per day. Can this be done? Why? Which timer works best? Which timer is the easiest to use for such a task?

2. Write the preceding program for each of the other two timers.

Exercises for Counters

1. Design and make a tachometer for small model aircraft engines. Have a range of from 5 rev per second to 50,000 rev per minute displayed on the LCD in real-time.

2. Design and build a thermometer based on the changes in frequency exhibited by a 555 timer circuit being controlled by a thermistor. Calibrate the thermometer with a lookup table. If you are not familiar with the use of lookup tables, you should undertake the research necessary to understand how to use them. They are very useful devices at the level we are working.