This chapter is about learning to use the timers in a PIC.
First a note about the timer functions and availability. Though there are four timers in the PIC 16F877A, not all the timers are available for our projects, nor are they available at all times. The other uses for which a timer may be employed are:
Used with the HPWM instruction
Used for communications
Control of the slave port
Baud rate generation
Resources are shared with a watchdog timer (Watchdog timer [timer #4] is not available for any programming purposes but does share scaling resources with Timer2.)
GETTING COMFORTABLE
Our goal is to be comfortable using the three timers after we have created the metronomes for this project. All the metronomes created in software and hardware behave identically but use different timers.
The metronome project is essentially a software project in that it is about learning how to use the timers. We should already have the two pieces of hardware needed to run the metronome software. They are, of course, the LAB-X1 and the board we made for the last project, the tachometer (to which we will have to add connection points for a potentiometer and a speaker [or LED]). The existing piezo beeper on the LAB-X1 may also be used.
Figure 16.2 show that back of the metronome board shown in Figure 16-1.
Figure 16.1 The electronic metronome: the card fits nicely on a box from All Electronics. (Uses the same card as that for the second tachometer.)
Figure 16.2 Electronic metronome back—Custom-made card. (Some of the wiring is on the back of the card to provide a flexible wiring system. Most I/O points are not attached to anything and can therefore be connected to any of the MCU pins with suitable jumpers.)
We will use each of the three timers (we do not have the ability to use the watchdog timer) in the PIC 16F877A to create instruments that provide us with accurate predictable timed intervals we can select with a potentiometer. Two of the three timers can also be used as counters, and that will be undertaken in the next chapter where we count marbles in some of the many different ways available to us. Counters are essentially timers that get their count pulse from an external source.
On the metronomes created, the counts per minute will be controlled by a potentiometer, they will be displayed on the display, and they will be annunciated on a speaker (or LED). Since both the tachometer hardware and the LAB-X1 hardware will have these accessories on them, either one can be programmed for the purpose we have in mind. Though the output needed from a metronome is rather limited (40 to 208 ticks per minute is the standard), our project will be able to provide any count desired, from a count every few seconds to a few thousand counts per second. All we will need to do is change a few lines of code in the software. That, in a nutshell, is the power of instruments that you yourself make.
The three timers in the PIC 16F877A have the following salient features, and the characteristics of the timers in most other PICs are similar. Once you get comfortable with the three timers in this PIC, you should have no difficulty with any timer in any PIC. The hard part will be getting familiar with the new datasheet for the PIC you select.
TIMERO
This is an 8-bit timer.
Official name TMR0.
Register used TMR0.
This register can be written to and read from at will.
Runs all the time, cannot be stopped or started.
Uses a prescalar.
Does not use a postscalar.
Can run from an internal clock (at Fosc/4).
Can be used with an external signal for counting.
Generates a programmable interrupt on overflow from 255 to 0.
TIMER1
This is a 16-bit timer.
Official name: TMR1.
Registers used: TMR1H and TMR1L for the high and low bytes of the 16-bit word.
The two registers mentioned in the previous bullet point can be written to and read from.
The timer can be turned on and off.
Uses a prescalar.
Does not use a postscalar.
Can run from an internal clock (at Fosc/4).
Can also run from an external clock for timing and counting.
Generates a programmable interrupt.
This is an 8-bit timer.
Official name: TMR2.
Register used: TMR2.
This timer can be written to and read from.
Has a register it can be compared to in order to generate an interrupt.
The special 8-bit register that is compared to the timer can be written to. An interrupt is generated when the comparison yields the specified match.
The timer can be turned on and off.
Uses a prescalar.
Uses a postscalar.
Cannot use an external clock and so cannot be used as a counter.
Generates a programmable interrupt.
Shares certain functions with the watchdog timer. This is important.
This timer has a special function that is used to drive a synchronous port for transferring data between the PIC and a computer.
DEFINING SOME TERMS
Before we start using the timers, let’s define a few timer-related words and concepts that will help us understand the functions of the timers more easily.
HPWM Commands
When using the HPWM commands, a timer must be assigned to help generate the frequencies used by these commands. The timer assigned to this function is then not available for any other use. In general, each timer can be used to support one function at one time and is busy to all other functions. (The default timer for HPWM use is Timer1, the 16-bit timer).
Prescalar
A prescalar is a divider function that slows down the rate at which the interrupts are generated by the timer. If a prescaler is set to 8, the timer clock feed will be divided by eight times before if is fed to the timer count. This slows down the rate at which the interrupts occur by eight. Prescalars are specified by setting a few bits in one of the registers that control the timer. The registers and prescalar values are different for each timer.
Postscalar
A postscalar has the same effect as a prescalar, it too slows down the rate at which the interrupts occur, but instead of slowing down the clock feed, it counts the overflows. If a postscalar is set for 4, the timer will overflow four times before an interrupt is generated.
The prescalar and postscalar values are multiplied by one another to get the total delay seen on the interrupts.
Interrupt
An interrupt is a signal that informs the system that something needs immediate attention. The interrupt manifests itself by setting a bit somewhere in the processor. The compiler software for the system is designed to be able to jump to a designated subroutine when this happens. We specify the target destination for this with the ON INTERRUPT GOTO command.
Interrupt Flag
An interrupt flag is a bit that is set in one of the registers associated with a timer when the timer overflows and after all the pre- and postscalar effects have been taken into account. It indicates that the program is ready to be given attention by the main part of the processor. After the interrupt has been taken care of, the interrupt bit must be set to 0 (cleared) by the interrupt service routine to allow it to be set to 1 again when the next interrupt takes place. The bit is usually cleared at the end of the interrupt-handling routine. The interrupt-handling routine must be short enough to complete before the next interrupt arrives. If this is not the case, an interrupt will be lost. (This is considered fatal and is unacceptable.)
Interrupt Enable Bit
An interrupt enable bit is the bit that has to be set to 1, within one of the registers that controls the timer, before an interrupt can be generated. This bit can usually be turned on and off by the program. Before a specific interrupt can take place, its specific enable bit must be set to 1.
Interrupt Latency
Beware that the interrupts are not handled as soon as they occur. The PBP compiler inhibits all interrupts while it executes an instruction. If an interrupt occurs while an instruction is being executed, the interrupt will be addressed after the instruction is complete. This can lead to a particularly long delay in the case of a long PAUSE instruction. PAUSE instructions should be broken up into smaller pauses in loops to reduce this latency so the largest latency encountered can be tolerated by the task at hand. Though interrupts may occur exactly and as frequently as programmed, they may not be handled immediately by the program. The programmer needs to be aware of this condition when programming for time-critical applications. (Assembly language programming does not have the problem to this extent. It depends on how the assembler is designed, but even so it takes some time to get the interrupt-handling task accomplished.)
Global Interrupt Enable (GIE) Bit
The GIE bit in the PIC allows you to shut off all interrupts when it is set to 0. It is located at INTCON.7. On two of the timers—Timer1 and Timer2—this global interrupt enable bit must be enabled before any interrupt can occur. Timer0 runs all the time and is not affected by the global interrupt enable bit. See datasheet page 22.
Whenever any interrupt flag is set, the GIE bit is turned off by the operating system. When the flag is cleared by you (with the software), the GIE bit will be reset automatically by the operating system. This means no other interrupt can occur until you take care of the interrupt that has occurred. If you are using more than one interrupt, this can cause problems, and addressing these problems is beyond the scope of this book. Beginners are advised to use only one interrupt in their programs till they get proficient enough with the PICs to create more complicated programs.
(For those who have an insatiable need to try these things out, the solution to the previously mentioned problem has to do with replacing the interrupts very quickly with internal flags, which are addressed later as a part of the main loop, and then proceeding with the program. So the interrupt routine for each interrupt just sets its own designated flag, resets its interrupt flag and ends. When the main loop sees this designated flag, it takes care of it as part of the main routine, not the interrupt routine. In the mean time, you can address the next interrupt flag. This gets complicated in a hurry and the program designer must guarantee that doing it this way will actually work. In other words, this is not trivial, so it is best avoided for now.)
Timers are not the only things that create interrupts. External events can be used to initiate an interrupt. PORTB has special significance in the generation of these interrupts. See the datasheet.
Now let’s look at each timer in greater detail.
Timer0 is discussed on page 51 in the datasheet.
Basic description: Timer0 has 8 bits, runs all the time, and sets its interrupt bit every 256 counts of Fosc/4 if the bit has been cleared. It can use an internal or external signal (for counter use). It can count on a falling or rising edge. The frequency of the interrupt can be modified with a prescalar, and the register itself can be read and written to.
The two registers that control Timer0 are OPTION_REG and INTCON. The bits in these registers affect Timer0 as follows:
*TOCK1 is pin 6 on the PIC 16F877A, also known as PORTA.4.
PORTA Pin 4 is the fifth pin on PORTA.
Prescalar value |
|
|
0000 |
1:2 |
|
0001 |
1:4 |
|
0010 |
1:8 |
|
0011 |
1:16 |
|
0100 |
1:32 |
|
0101 |
1:64 |
|
0110 |
1:128 |
|
0111 |
1:256 |
|
1xxx |
1:1 |
No prescalar applied if bit 3 is set to 1. Prescalar is therefore OFF. |
INTCON is the interrupt control register |
||
As mentioned in the preceding, there is no way to start and stop Timer0. It runs all the time. However, since it can be written to, it can be set to 0 or any value up to 255 whenever desired. Also in the counter mode, if there is no signal on TOCK1, the counter will not increment, and so no interrupt will occur—thus, in a sense the counter function (but not the internally clocked timer function) can be stopped. If running free, Timer0 generates an interrupt every 256 Fosc/4 cycles whether any attention is paid to the interrupt or not, so if its interrupt flag is never cleared, it stays set.
Watchdog timer interaction: If the prescalar is being used by the watchdog timer, it cannot be used by Timer2 and visa versa.
Now that we know where all the relevant bits are, we can write a program for the LAB-X1 to test things out. In this program, we will set the prescalar to its maximum value to slow things down as far as possible so we can see what is going on more easily.
Here is the plan for testing the operation of Timer0:
1. We will increment the value of the variable X in the interrupt routine. Therefore, we will be sure we have entered and returned from the interrupt routine if this value is being incremented.
2. We will display the value of X in the main loop. Therefore, if we see X incremented, the interrupts are being called and returned from while we are in the main loop.
3. If we see the preceding two things taking place, we will have successfully used Timer0. It’s that simple. We will be ready to use Timer0 in our programs.
These first programs for Timer0 are heavily commented, so you can see exactly what is being done. The programs that follow are less heavily commented because they are very similar to these first programs.
First, let’s set up the LCD the usual way, as shown in Program 16.1.
Program 16.1 Basic interrupt routine for Timer1
CLEAR ; always start with clear
DEFINE OSC 4 ; define oscillator speed
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; select reg
DEFINE LCD_RSBIT 0 ; select bit
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; make low for write only
;
; Set the port directions. We must set all of PORTD and all of
; PORTE as outputs
; even though PORTE has only 3 lines. The other 5 lines will be
; ignored by the system.
; PORTC is needed because the piezo speaker is on pin 2 of this
; port.
;
TRISC = %11111001 ; set PORTC.1 and PORTC.2 as output
; for the speaker and LED connections
TRISD = %00000000 ; set all PORTD lines to output
TRISE = %00000000 ; set all PORTE lines to output
X VAR WORD ; set up the variable
ADCON1=%00000111 ; set the Analog-to-Digital control
; register
; needed for the 16F877A see notes
PAUSE 500 ; pause for LCD to start up
LCDOUT $FE, 1 ; clear screen
;
ON INTERRUPT GOTO INT_ROUTINE ; tells program where to go on an
; interrupt
INTCON.5=1 ; sets up the interrupt enable
INTCON.2=0 ; clears the interrupt flag so it
; can be set
OPTION_REG=%00000111 ; sets the prescalar to 256
X=0 ; sets the initial value for X
LCDOUT $FE, $80, “Metronome” ; display first line
;
MAIN: ; the main loop of the program
LCDOUT $FE, $C0, DEC5 X ; write X to line 2
IF X>;=15 THEN ; check value of X
X=0 ; reset the value
TOGGLE PORTC.2 ; toggle the speaker so we can hear it
TOGGLE PORTC.1 ; for the ext speaker or an LED
ENDIF ; end of testing X
GOTO MAIN ; repeat loop
;
DISABLE ; reqd instruction, to the compiler
INT_ROUTINE: ; interrupt service routine
X=X+1 ; Increment the counter
INTCON.2=0 ; Clear the interrupt flag
RESUME ; Go back to where you were
ENABLE ; reqd instruction, to the compiler
;
END ; all programs must end with End
This program toggles line C.2 each time through its cycle and gives us a click about once a second, or once every 15 interrupts, using a prescalar of 256. This means that the fastest interrupts we could get with Timer0 would come 256 × 15 faster, or at about 3840 interrupts per second if we did not change the clock rate or try to load the timer any other way. The number 3480 is good to remember when using Timer0 because it defines the empirical maximum interrupt rate for us.
4,000,000/4/256 = 3,906.25 (the theoretical value for a 4 MHz processor)
3906.25/256 = 15.26 (used 15 in the preceding program since it is the closest integer)
15*256 = 3480 (as mentioned earlier, not an exact value)
The prescalar value we use has an important effect on the length of the interrupt service routine. If we are using a small value for the prescalar, the time available between interrupts becomes very small and the moment may come when all the time available is used up servicing the interrupt routine, leaving no time to do the foreground task. We will investigate this by setting the prescalar with the first potentiometer on the LAB-X1 board and loading its value into the prescalar in real time to see what happens. To do this, we need the following code added to the program. It consists of reading the potentiometer and then placing the read value into the four prescalar bits.
The code for reading POT 1 is shown in Program 16.2.
Program 16.2 Basic program to see the effect of the prescalar value on Timer0 operation
A2D_V VAR BYTE ; create A2D_Value to store result
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in uS
; The value is read with
ADCIN 0, A2D_V ; read channel 0 to A2D_V
; The value is displayed on the LCD to three places with
LCDOUT $FE, $C0, DEC3 A2D_V
; The value is then placed in the prescalar with
OPTION_REG= (A2D_V / 32)
When these lines of code are added to the program the program becomes:
CLEAR ; always start with clear
DEFINE OSC 4 ; define oscillator speed
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; select reg
DEFINE LCD_RSBIT 0 ; select bit
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in uS
LOW PORTE.2 ; make low for write only
;
; Set the port directions. We must set all of PORTD and all of
; PORTE as outputs
; even though PORTE has only 3 lines. The other 5 lines will
; be ignored by the system.
; PORTC is needed because the piezo speaker is on pin 2 of this port.
;
TRISC = %11111011 ; set port c.2 to speaker
; connection to output
TRISD = %00000000 ; set all PORTD lines to output
TRISE = %00000000 ; set all PORTE lines to output
X VAR WORD ; set up the variable
A2D_V VAR BYTE ; create A2D_Value to store result
ADCON1=%00000111 ; Set the Analog-to-Digital control
; register
; needed for the 16F877A see notes
PAUSE 500 ; pause for LCD to start up
LCDOUT $FE, 1 ; clear screen
ON INTERRUPT GOTO INT_ROUTINE ; tells program where to go on
; interrupt
INTCON.5=1 ; sets up the interrupt enable bit
INTCON.2=0 ; clears the interrupt flag so it can
; be set
X=0 ; sets the initial value for X
LCDOUT $FE, $80, “Metronome” ; display first line
;
MAIN: ; the main loop of the program
ADCIN 0, A2D_V ; read channel 0 to A2D_Value
OPTION_REG= (A2D_V / 32) ; set the option register low nibble
LCDOUT $FE, $C0, DEC3 A2D_V/32,” ; display value
GOTO MAIN ; do it again
;
DISABLE ; reqd Instruction to the compiler
INT_ROUTINE: ; interrupt service routine
TOGGLE PORTC.2 ; toggle the port
X=X+1 ; Increment the counter
INTCON.2=0 ; clear the interrupt flag
RESUME ; go back to where you were
ENABLE ; reqd Instruction to the compiler
;
END ; all programs must end with End
In Program 16.2, the LCD shows us the value of X and the bits that have been set in the OPTION_REG register. We have had to remove the comparison and resetting of the X variable because it takes too much time and the toggling has been moved into the interrupt service routine, so you can hear how often the interrupt is being called. Notice that as the interrupts become more frequent, the incrementing of the value of X in the main routine cannot keep up with the speed with which the interrupts are arriving. Even though a minimal amount of work is being done in the interrupt service routine, it is too much.
We can add to the time taken by the interrupt service routine by adding a PAUSE in the routine. Play with adding a PAUSE of between 1 and 25 μsec in the routine to see what happens. (Interrupts are missed and the toggling does not respond to the changes made to the option register bits.)
Now that we have a feel for the problems involved, let’s write the metronome program. A standard metronome provides between 40 and 208 counts per minute. We are going to use POT 0 to control the rate so the 256 values that can be read for the POT must be mapped to the 168 rates (208 – 40) needed by the metronome, and each rate must be accurate enough to serve everyday musical needs.
The formula for converting from 0 to 255 to from 40 to 208 is
Ticks = 40 + [(208 – 40) * POT.0]/255
Looking back at 3480 as the maximum number of interrupts we can handle with ease, and seeing that we need to generate 168 different frequencies, we see that we can have about 3480/168 = 20 interrupt counts separating each frequency, so we know that they can all be differentiated. If we set the option register to %00000011, we will get a prescalar of 16 and increase the number of interrupts received by a factor of 16 (256/16). This will be enough to do the job. Increasing the number of interrupts by a factor of 16 means we need to look at 16 times more counts on the X counter before we toggle the speaker.
The program for a metronome using Timer0 and the LAB-X1 is shown in Program 16.3.
Program 16.3 Working metronome based on Timer0 and the LAB-X1
CLEAR ; always start with clear
DEFINE OSC 4 ; define oscillator speed
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; select reg
DEFINE LCD_RSBIT 0 ; select bit
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in uS
LOW PORTE.2 ; make low for write only
;
; Set the port directions. We must set all of PORTD and all of
; PORTE as outputs
; even though PORTE has only 3 lines. The other 5 lines will
; be ignored by the system.
; PORTC is needed because the piezo speaker is on pin 2 of this port.
;
TRISC = %11111011 ; set PORTC.2 to speaker connection
; to output
TRISD = %00000000 ; set all PORTD lines to output
TRISE = %00000000 ; set all PORTE lines to output
X VAR WORD ; set up the variable
POTPOS VAR WORD ;
A2D_V VAR BYTE ; create A2D_Value to store result
ADCON1=%00000111 ; set the Analog-to-Digital
; control register
; needed for the 16F877A see notes
PAUSE 500 ; pause for LCD to start up
LCDOUT $FE, 1 ; clear screen
ON INTERRUPT GOTO INT_ROUTINE ; tells program where to go on
; interrupt
INTCON.5=1 ; sets us the interrupt enable
INTCON.2=0 ; clears the interrupt flag so it
; can be set
X=0 ; sets the initial value for X
;
MAIN: ; the main loop of the program
ADCIN 0, A2D_V ; read channel 0 to A2D_Value
OPTION_REG=%00000011 ; prescalar is 16
POTPOS= 208-(((208-40)*A2D_V)/255) ; the potentiometer
; position value
LCDOUT $FE, $80, DEC3 POTPOS,” ” ; Display the position
LCDOUT $FE, $C0, DEC X ,” ” ; display the X count
IF X>=16*15*60/POTPOS THEN ; this is where we add the multiply
; by 16
TOGGLE PORTC.2 ; toggle the speaker
X=0 ; reset x
ELSE ;
ENDIF ;
GOTO MAIN ; go back and do it again
;
DISABLE ; required by the compiler
INT_ROUTINE: ; interrupt service routing
X=X+1 ; increment value of X
INTCON.2=0 ; reset/clear the flag
RESUME ; go back to interrupt point
ENABLE ; required by the compiler
END ; end all programs with end
In the wiring diagram shown in Figure 16.3, a ten-pin connector is shown (for programming the PIC in place) on the circuit board. This is an important convenience that should not be omitted on any programmable device you create. You will use it many, many times as you experiment with the project, and not having to take the PIC out of its socket to reprogram it will save you a lot of headaches. Take the time to wire in the programmer connection (in all your projects).
Next, we need to convert the program we have developed to run on the tachometer hardware with its seven-segment displays. To allow us to turn the tachometer into a metronome, we also have to make two hardware additions. We need to add a potentiometer and a speaker to the card. The circuitry for adding these two items is shown circled in Figure 16.3.
In order to display on the 4 seven-segment LED displays on the tachometer, we must add the code for the displays in place of the instructions for the LCD. This code is taken from the code in the tachometer program (with minor modifications as needed) shown in Program 16.4.
Program 16.4 Program to use the tachometer board as a metronome
CLEAR ; clear memory
DEFINE OSC 4 ; osc speed
; The following are the images of the numbers
; stored in memory before we do anything. These will
; be used in PORTB to set the number in each digit.
; Your wiring will require numbers to suit it.
;
WRITE 1999, %11111111 ; blank, no LED is lit
WRITE 2000, %00000110 ; the number 0 )
WRITE 2001, %10111110 ; the number 1 )
WRITE 2002, %01001100 ; the number 2 )
WRITE 2003, %00011100 ; the number 3 ) will depend on
WRITE 2004, %10110100 ; the number 4 ) how your particular
WRITE 2005, %00010101 ; the number 5 ) engine is wired.
WRITE 2006, %00000101 ; the number 6 )
WRITE 2007, %10011110 ; the number 7 )
WRITE 2008, %00000100 ; the number 8 )
WRITE 2009, %00010100 ; the number 9 )
;
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in uS
;
TRISA=%00000000 ; set up PORTA
TRISB=%00000000 ; set up PORTB
TRISC=%11111001 ; set up PORTC
TRISE=%00000100 ; set up PORTE
P VAR WORD ; pause variable
X VAR BYTE ; counter variable
NUM VAR WORD ; number read from Pot
LEDPOS VAR WORD ; position in display
VALUE VAR WORD ; value of metronome counts
DIGIT VAR BYTE ; each digit in display
P=4 ; pause in microseconds
ADCON1=%00000000 ; this selects first line in the table
INTCON.5=1 ; sets us the interrupt enable
INTCON.2=0 ; clears the interrupt flag so it can be set
OPTION_REG=%00000011 ; sets the prescalar to 16
X=0 ; sets the initial value for X
HIGH PORTC.2 ; sets pin high to start with
ON INTERRUPT GOTO INT_ROUTINE ; tells program where to go on
; interrupt
;
MAIN: ; main loop of program
ADCIN 7, NUM ; read channel 0 pot value
LEDPOS=208-(((208-40)*NUM)/255) ; the potentiometer
; position value
VALUE=LEDPOS ; remember value we are
; working with
IF X>=16*15*30/LEDPOS THEN ; this is where we add the
; multiply by 16
TOGGLE PORTC.2 ; toggle the speaker
X=0 ; reset x
ELSE ;
ENDIF ;
GOSUB DISPLAY ; show value on the 4 seven seg displays
GOTO MAIN ; do it again forever
;
DISPLAY: ; the routine to display the 4 numbers
IF VALUE<1000 THEN ; check value for first digit
DIGIT=1999 ; set a blank
ELSE ;
DIGIT=LEDPOS/1000 +2000 ; set pattern position in table
ENDIF ;
READ DIGIT, PORTB ; read the pattern for the digit
PORTA=%00000001 ; display digit 1 from if 1000 to 9999
PAUSE P ; pause to let displays come ON
;
LEDPOS=LEDPOS//1000 ; get Remainder
IF VALUE<100 THEN ; check value for second digit
DIGIT=1999 ;
ELSE ;
DIGIT=LEDPOS/100 +2000 ;
ENDIF ;
READ DIGIT, PORTB ;
PORTA=%00000010 ; display digit 2 from 100 to 999
PAUSE P ;
;
LEDPOS=LEDPOS//100 ;
IF VALUE<10 THEN ; check value for third digit
DIGIT=1999 ;
ELSE ;
DIGIT=LEDPOS/10 +2000;
ENDIF ;
READ DIGIT, PORTB ;
PORTA=%00001000 ; display digit 3 from 10 to 99
PAUSE P ;
;
LEDPOS=LEDPOS//10 ;
DIGIT=LEDPOS +2000 ;
READ DIGIT, PORTB ;
PORTA=%00100000 ; display last digit form 0 to 9
PAUSE P ;
RETURN ; end of routine
;
DISABLE ; required by the compiler
INT_ROUTINE: ; interrupt service routing
X=X+1 ; increment value of X
INTCON.2=0 ; reset/clear the flag
RESUME ; go back to interrupt point
ENABLE ; required by the compiler
;
END ; end all programs with end
Note This program will be modified for each of the timers, but since it does not really change when the other timers are used, I will not list this program again. The changes to the timers will be the same as will be made for the LCD on the LAB-X1. These changes are listed under each timer section.
Timer1 is discussed on page 55 in the datasheet.
Basic description: Timer1 is a 16-bit timer with a prescalar. It can be turned on and off, can use an internal or external crystal, can be synchronized with an internal or external clock, and be used as a counter.
Let’s see what it takes to create the preceding metronome with Timer1, what must be changed, and what the problems and advantages of using this particular timer are.
Timer1 is the only 16-bit timer in the 16F877A. The longest interval that can be timed with a 4 MHz clock is a little over half a second. This is calculated as follows.
The maximum prescale value is 1:8
Instruction clock cycle (1 uS @ 4 MHz)
Counts from one overflow to the next (65536)
1 uS * 8 * 65536 = 0.524288 seconds
This timer uses two bytes, identified as TMR1H and TMR1L (the high and low byte). These 2 bytes cannot be read or written in one instruction, and the attendant problems having to do with the low byte overflow or underflow affecting the high byte in the middle of a read or write. It is a problem that must be taken care of when reading and writing to these 2 bytes. (The usual advice is to stop the timer before reading or writing to it, and then start it again immediately after that is done.)
The registers that control Timer1 are T1CON, INTCON, PIE1, and PIR1. The bits in these registers are used to control Timer1 as shown next:
Bits T1CON.5 and T1CON.4 set the value of the prescalar as follows. This timer does not have a postscalar capability.
Bit value |
Prescalar value selected |
T1CON.5, 4 =00 |
1 meaning that no prescalar is used |
T1CON.5, 4 =01 |
2 |
T1CON.5, 4 =10 |
4 |
T1CON.5, 4 =11 |
8 |
PIE1.0 and INTCON.6 control the interrupt enable bit; both must be set to 1
The interrupt flag is located at PIR1.0 and must be cleared in the interrupt routine.
Figure 16.3 Addition of speaker and potentiometer to the tachometer circuitry for the metronome. (Speaker and potentiometer have been added.)
Like all the timers, Timer1 is clocked by the crystal frequency divided by 4 (or Fosc/4). It can also be clocked by an external oscillator connected between PORTC.0 and PORTC.1. The selection between the internal and external oscillators is made by what is in T1CON.1 and T1CON.3, as follows:
T1CON.1=0 |
Selects the Fosc/4 signal |
|
T1CON.1=1 |
Selects the external input between PORTC.0 and PORTC.1 |
|
T1CON.3=1 |
Also must be set if T1CON.1 is set |
|
The use of T1CON to control Timer1 is summarized as follows: |
||
T1CON.5 |
) These 2 bits |
|
T1CON.4 |
) specify the prescalar |
|
T1CON.3 |
Set to 1 to enable oscillator |
|
T1CON.2 |
Set to 1 to selects external clock |
|
T1CON.1 |
Selects external clock when set to |
1, internal clock when 0 |
T1CON.0 |
Turns Timer1 on when set to 1, |
off when 0, becomes counter |
USING TIMER1
The listing in Program 16.5 shows how Timer1 can be used to create a metronome. Compare this to the listing for Timer0 mentioned previously.
Program 16.5 Metronome using Timer1
CLEAR ; clear
DEFINE OSC 4 ; define oscillator speed
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; select reg
DEFINE LCD_RSBIT 0 ; select bit
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; make low for write only
; Set the port directions. We must set all of PORTD and all of
; PORTE as outputs
; even though PORTE has only 3 lines. The other 5 lines will
; be ignored by the system.
; PORTC is needed because the piezo speaker is on pin 2 of this
; port.
TRISC = %11111001 ; set PORTC.2, the speaker
; connection
; to an output line
TRISD = %00000000 ; set all PORTD lines to output
TRISE = %00000000 ; set all PORTE lines to output
PORTE.2=0 ; set LCD to write mode
X VAR WORD ; set up the variable
ADCON1=%00000111 ; Set the Analog-to-Digital control
; register
; needed for the 16F877A see notes
PAUSE 500 ; pause for LCD to start up
LCDOUT $FE, 1 ; clear screen
;
ON INTERRUPT GOTO INT_ROUTINE ; tells program where to go on
; interrupt
T1CON=%01110001 ; sets us the interrupt enable
PIE1.0=1 ; clears the interrupt flag so it can
; be set
INTCON.6=1 ;
OPTION_REG=%00000111 ; sets the prescalar to 256
X=0 ; sets the initial value for X
;
MAIN: ; the main loop of the program
LCDOUT $FE, $80, “Metronome” ; display first line
LCDOUT $FE, $C0, DEC5 X ; write X to line 2
IF X=15 THEN ; check value of X
X=0 ;
TOGGLE PORTC.2 ; toggle the speaker so we can hear
; it
ENDIF ;
GOTO MAIN ; repeat loop
;
DISABLE ; req’d Instruction to the compiler
INT_ROUTINE: ; interrupt service routine
X=X+1 ; increment the counter
PIR1.0=0 ; clear the interrupt flag
RESUME ; go back to where you were
ENABLE ; reqd Instruction to the compiler
;
END ; all programs must end with End
Timer2 is discussed on page 59 in the datasheet.
Basic description: Timer2 has 8 bits, and both a pre- and postscalar. It uses the internal clock only and shares the prescalar with the watchdog timer. It can be turned on and off but cannot be used as a counter. It cannot be used as a counter because it does not have a pin on which it can receive the counter signals.
Timer2 is an 8-bit timer with both a prescalar and a postscalar. The prescalar can be as great as 16, and the postscalar can be as great as 16, for a total scaling of 16 × 16 = 256, which is the same as for Timer0. However, not all 256 scalings are available as they are for Timer0 (because the prescalar can only be set to 1, 4, or 16).
The Timer2 is identified as register TMR2.
The control registers for Timer2 are INTCON, T2CON, PIE1, and PIR1. The bits in these registers control Timer2 as follows:
T2CON.1 and T2CON.0 set the value of the prescalar as follows:
Bit value |
Prescalar value selected |
00 |
1, No prescalar is used |
01 |
4 |
1x |
16 |
The postscalar is specified by the 4 bits from T2CON.6 to T2CON.3 as follows:
Bit value |
Postscalar value selected |
0000 |
1, No prescalar is used |
0001 |
2 |
0010 |
3 |
0011 |
4 |
0100 |
5 |
0101 |
6 |
0110 |
7 |
0111 |
8 |
1000 |
9 |
1001 |
10 |
1010 |
11 |
1011 |
12 |
1100 |
13 |
1101 |
14 |
1110 |
15 |
1111 |
16 |
PIE1.1 and INTCON.6 control the interrupt enable bit
The interrupt flag is located at PIR1.1.
Like all the timers, Timer2 is clocked by the crystal frequency divided by 4 (or Fosc/4). It can not be used with an external oscillator.
The timer is controlled by T2CON.2 and is on when this bit is set to 1.
The listing in Program 16.6 shows how Timer2 can be used to create a metronome. Compare this to the listings for Timer0 and Timer1 mentioned previously.
Program 16.6 Metronome using Timer2
; Like we did for TIMER0, first let us set up the LCD
CLEAR ; always start with clear
DEFINE OSC 4 ; define oscillator speed
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; select reg
DEFINE LCD_RSBIT 0 ; select bit
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; make low for write only
; Set the port directions. We must set all of PORTD and all of
; PORTE as outputs
; even though PORTE has only 3 lines. The other 5 lines will
; be ignored by the system.
; PORTC is needed because the piezo speaker is on pin 2 of this
; port.
;
TRISC = %11111011 ; set PORTC.2 the speaker connection
; to an output line
TRISD = %00000000 ; set all PORTD lines to output
TRISE = %00000000 ; set all PORTE lines to output
PORTE.2=0 ; set LCD to write mode
X VAR WORD ; set up the variable
ADCON1=%00000111 ; set the Analog-to-Digital control
; register
; needed for the 16F877A see notes
PAUSE 500 ; pause for LCD to start up
LCDOUT $FE, 1 ; clear screen
;
ON INTERRUPT GOTO INT_ROUTINE ; tells program where to go on
; interrupt
T2CON.5=%01111011 ; sets us the interrupt enable
; bit 0=1
; bit 1=1
; bit 2=0
; bit 3=1
; bit 4=1 Prescaler
; bit 5=1 Prescaler
; bit 6=1
; bit 7=0
PIE1.1=0 ; interrupt enabled
INTCON.6=1 ; permits interrupts to occur
X=0 ; sets the initial value for X
;
MAIN: ; the main loop of the program
LCDOUT $FE, $80, “Metronome” ; display first line
LCDOUT $FE, $C0, DEC5 X ; write X to line 2 of the LCD
IF X=15 THEN ; check value of X
X=0 ;
TOGGLE PORTC.2 ; toggle the speaker so we can
; hear it
ENDIF ;
GOTO MAIN ; repeat loop
;
DISABLE ; reqd instruction to the compiler
INT_ROUTINE: ; interrupt service routine
X=X+1 ; increment the counter
PIR1.1=0 ; clear the interrupt flag
RESUME ; go back to where you were
ENABLE ; reqd instruction to the compiler
;
END ; all programs must end with end
A fourth timer in the 16F877A is used as a watchdog timer. The function of a watch-dog timer is to provide an interrupt if a program hangs up and thereby fails to periodically reset the watchdog timer. The watchdog timer interrupt flag is reset from time to time (always before it runs out) in the program to keep this from happening. The code for resetting the timer is provided by the compiler automatically. Check the appropriate option in the programming options.
The watchdog timer shares its prescalar with Timer0 on an exclusive basis, meaning that either the watchdog timer or Timer0 can use the prescalar (but not both). When you select the watchdog timer, the prescalar for Timer0 goes to 1:1. In fact, you select 1:1 for Timer0 by setting bit 3 in the option register, which at the same time assigns the prescalar to the watchdog timer.