TIMERS
Timer0 will be covered first, and 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 the options for setting the timers up are extensive. We will cover the timers one at a time in an introductory manner. However, you should be aware that there is an entire book available from Microchip Technologies that cov- ers nothing but timers, so the coverage here will be rudimentary.
Note The Microchip Technologies timer manual is called The PICmicro Mid- Range MCU Family Reference Book (DS33023), available from Microchip Technology Inc.
To understand timers, you must understand 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 on a timer by turning on its enable bit. The timer then counts a certain number of clock cycles, sets an interrupt bit that causes an interrupt, and continues running toward the next interrupt. Your program responds to the inter- rupt 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-/post-scalers have to do with modifying the time it takes for an interrupt to take place. The hard part is getting familiar with which bit does what and where it is located, which is why reading and understanding the data sheet chapters (5, 6, or 7) depending on the timer you are using is imperative. There is no escaping this horror! However, this chapter will ease your pain.
Timers allow the microcontroller to create and react to chronological events. These include:
N Timing events for communications
N Creation of clocks for various purposes
N Generating timed interrupts
N Controlling PWM generation
N Waking the PIC up from the sleep mode at intervals to do work and go back to sleep
N Special use of the Watchdog Timer
There are three internal timers in the PIC 16F877A (there is also a Watchdog Timer, which is discussed after the timers in this chapter):
N Timer0 is an 8-bit free running timer/counter with an optional pre-scaler. It is the simplest of the timers to use.
N Timer1 is a 16-bit timer that can be used as a timer or as a counter. It is the only 16-bit timer and can also be used as a counter. It is the most complicated of the timers.
N Timer2 is an 8-bit timer with a pre-scaler and a post-scaler and cannot be used as a counter. (There is no way to input a signal to 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, but each of them has special features that give it special properties. It is imperative that you refer to the data sheet for the PIC 16F877A (Chapters 5, 6, and 7) 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 pre-scalers and post-scalers associated with them that can be used to multiply the timer setting by a limited number of integer counts. The scaling abil- ity is not adequate to allow all exact time intervals to be created, but it 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 hard- ware 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 to have 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 that we create.
First we’ll write a simple program to see how this timer works. We will use the LED bar graph to show what is going on inside the microcontroller.
As always, the bar graph is connected to the eight lines of PORTD of the LAB-X1. First, we will write a program that will light the two LEDs connected to D0 and D1 alternately. Having them light alternately lets you know that the program is running or, more accurately, the segment of the program that contains this part of the code is running. These two LEDs will be used to represent the foreground task in the program.
There is no timer process in use in Program 6.1 at this stage.
The use of the PAUSEUS loop in Program 6.1 provides a latency of 100 Ms (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 oscil- lator. There is an assumption here that the 100 microsecond latency is completely tolerable to the task at hand and it is for this program. This may not be true for your real-world program and may have to be adjusted.
We are turning one LED off and another LED on to provide a more positive feed- back. 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. It is important that you learn to develop failsafe techniques for your programs, and this is a rudimentary one for making sure that a program is running.
We have selected a relatively fast on/off cycle so that 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, in Program 6.2, we will add the code that will interrupt this program periodi- cally and make a third LED go on and then off on an approximately one-second cycle. This will serve as the interrupt driven task that we are interested in learning how to create. In most programs this would be the critical, time-dependent task.
Here is what you have to do to make the interrupt driven LED operational:
N Enable Timer0 and its interrupts with appropriate register/bit settings.
N Add the ON INTERRUPT command to tell the program where to go to handle the interrupt when an interrupt occurs.
N 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).
N Clear the interrupt flag that was set by Timer0.
N Send the program back to where it was interrupted with the RESUME command.
WHY ARE WE USING 61?
N Set the pre-scaler to 64 (bits 0 to 2 are set at 101 in the OPTION_REG).
N Set the counter to interrupt every 256 counts (256 s 64 = 16,384).
N Set the clock to 4,000,000 Hz
N Set Fosc/4 to 1,000,000 (1,000,000 /16,384 = 61.0532—it’s not exact but close enough for our purposes for now).
The lines of code now look like Program 6.2.
Make your predictions and then try changing the three 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 pre-scaler is set to 64 so we get the interrupt
after 64 of these interrupt cycles. When this happens we jump to the “InterruptRoutine” routine, where we make sure that 61 interrupts have taken place. 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 initial calculation if necessary.)
Note that the interrupts are disabled while we are in the “InterruptRoutine” 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 and we will not worry about it for now.
Before going any further, let’s take a closer look at the OPTION_REG and the INTCON (INTerrupt CONtrol) register. These registers were used in Program 6.1, but the details of their operations were not explained. These are 8-bit registers with the eight 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 weak pull-ups.)
Bit 6 INTEDG. Not of interest to us at this time. Interrupt edge select bit deter mines 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 oscil- later. 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, Pre-scaler assignment pin. Decides what the pre-scaler applies to.
1= Select Watch Dog Timer (WDT).
0 = Select Timer0. We will be using this.
Bits 2, 1 and 0 define the pre-scaler value for the timer. As mentioned previously, the pre-scaler 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.
Caution There is a very specific sequence that must be followed (which does not apply here) when changing the pre-scaler assignment from Timer0 to the WDT (Watch Dog Timer) to make sure that an unintended reset does not take place. This is described in detail in The PICmicro Mid-Range MCU Family Reference Book (DS33023).
ote that Bit 2 is set clear when we start and will be set to 1 when the interrupt takes place. It has to be recleared within every interrupt service routine, usually at the end of the interrupt routine.
A Timer0 Clock
The following program (Program 6.3) 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. (I did not modify this program in any way so it does not include the CLEAR or OSC statements and so on that we have been using in our programs.)
; LCD clock program using On Interrupt
; Uses TMR0 and pre-scaler. 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 the clock implemented in Program 6.3, the keyboard buttons are used as follows:
N SW1 and SW5 increment the hours
N SW2 and SW6 decrement the hours
N SW3 and SW7 increment the minutes
N 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 three timers to understand and use, and it is also the most flexible. It consists of two 8-bit registers; each register can be read and be written to. The timer can be used either as a timer or as a counter depending on how the Timer1 Clock Select bit (TMR1CS) is set. This bit is Bit 1 in the Timer1 Control Register (T1CON).
In Timer1, we can set the value 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 preloaded into Timer1 on the frequency of the interrupts as reflected in a very rudi- mentary pseudo stopwatch. The higher the value of the preload, the sooner the counter will get to $FFFF, and the faster the interrupts will come. We will display the value of the pre-scaler loaded into the timer on the LCD so that 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 shorter and shorter and you can see this on the speed at which the stopwatch runs.
In Program 6.4 the switches perform the following actions:
N SW1 turns the stopwatch on
N SW2 stops the stopwatch
N SW3 resets the stopwatch
POT1 is the first potentiometer. It is read and then written to TMR1H to change the interrupt rate. (We are ignoring the low byte because it does not affect the interrupt rate much in this experiment.)
The results of the experiment are displayed on the LCD display.
Let us creep up on the solution. We will develop the program segments and discuss them as we go along and then put the segments together for a program we can run.
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 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 want to, if it has been set. If it has not been set there is no need to clear it. A simplified flow diagram is provided in Figure 6.1 to help you to understand what is going on in an interrupt servicing routine.
Even the 16-bit Timer1 on the 16F877A cannot time a period of any length. 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 sec- onds. The maximum pre-scale value is 1:8. The post-scaler is only available on Timer2, which in any case is an 8-bit timer. This results in a maximum time that is determined by multiplying the instruction clock cycle (1 MS @ 4 MHz) by the pre- scale (8) by the number of counts from one overflow to the next (65536):
1 μS * 8 * 65536 = 0.524288 seconds
On a 20 MHz machine the time would be one-fifth of this.
` Timer1, the 16-bit timer/counter, uses two registers: TMR1H and TMR1L. The timer has the following general properties:
N It increments from $0000 to $FFFF in two registers.
N If the interrupt is enabled, an overflow will occur when the 2-byte counter over- flows from $FFFF to $0000.
N The device can be used as a timer.
N The device can be used as a counter.
N The timer registers can be read and written to.
N There is no post-scaler for this timer.
Simply stated again, this timer is used by setting its register to a selected value and using the interrupts this value creates for our purpose. A 16-bit timer will count up from where set to 65,535 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 65,535 to 0. We do 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 pre-scaler and post-scaler, the pre-/post-scaler allows us to increase the time between interrupts by multiplying the time between interrupts with a definable value in a three to eight bit location. On writable timers we have the ability to start the tim- ers with values of our choice in the timer register(s). This gives us very usable but not absolute control over the interrupt intervals.
Consider the fact that a 0.01 second timer setting with a pre-scaler set to 16 would provide us with an interrupt every 0.16 seconds. We would have 0.16 seconds to do whatever we wanted to do between interrupts. There are limits to what can be put in the timer counter and what can be put in the pre-scaler, and the interrupt frequency is also affected by the accuracy of the processor clock oscillator.
The value of the scaling that will be applied to the timer is determined by the con- tents of two bits in the interrupt control register. These bits multiply the time between interrupts by powers of 2 as under:
Using Timer1 to Run a Critical Interrupt Driven Task While the Main Program Runs a Foreground Task
Let us use this timer in the same way we used Timer0 earlier and see what the differences between the two timers are. Since 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. The Timer1 flag can take approximately 0.524 seconds, as calculated earlier. Let us write a short Timer1 program that is similar to the original Timer0 blinker program to see what the differences are.
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 tim- ing stays accurate. Any time used by the interrupt routine is lost by the foreground task and affects the overall frequency of D0/D1 blink rate.
Play with the value of the counter J to see how this affects the operation of the program. Study the differences between the two programs to set and clear the timer flags. Though Programs 6.4 and 6.5 do essentially the same thing, the setting of the potentiometer in the first program has to be modified to match the needs of the timer being used.
TIMER2: THE THIRD TIMER
Timer2 is an 8-bit timer only. It cannot be used as a counter. It has a pre-scaler and a post-scaler. 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 post-scalers gives you the control you need for effective control of the interrupt interval, although 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 $00. Small values in PR2 can be used to create very rapid interrupts—so much so that there may be no time to do anything else.
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.
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 system lets the program know that an interrupt has occurred by setting Bit 1 in PIR1. (Bit 0 in both these registers is 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 is one of the ways to awaken a sleeping MCU.
Timer2 can also control the operation of the two PWM signals that can be pro- grammed to appear on lines C1 and C2 with the HPWM command in PICBASIC PRO. Since one timer must control both lines, they both have to have the same PWM frequency. 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 Chapter 7 of the data sheet.