16
CREATING ACCURATE INTERVALS WITH TIMERS: THE METRONOMES

Project 2

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:

sqr Used with the HPWM instruction

sqr Used for communications

sqr Control of the slave port

sqr Baud rate generation

sqr 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.

f0210-01

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.)

f0210-02

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

sqr This is an 8-bit timer.

sqr Official name TMR0.

sqr Register used TMR0.

sqr This register can be written to and read from at will.

sqr Runs all the time, cannot be stopped or started.

sqr Uses a prescalar.

sqr Does not use a postscalar.

sqr Can run from an internal clock (at Fosc/4).

sqr Can be used with an external signal for counting.

sqr Generates a programmable interrupt on overflow from 255 to 0.

TIMER1

sqr This is a 16-bit timer.

sqr Official name: TMR1.

sqr Registers used: TMR1H and TMR1L for the high and low bytes of the 16-bit word.

sqr The two registers mentioned in the previous bullet point can be written to and read from.

sqr The timer can be turned on and off.

sqr Uses a prescalar.

sqr Does not use a postscalar.

sqr Can run from an internal clock (at Fosc/4).

sqr Can also run from an external clock for timing and counting.

sqr Generates a programmable interrupt.

TIMER2

sqr This is an 8-bit timer.

sqr Official name: TMR2.

sqr Register used: TMR2.

sqr This timer can be written to and read from.

sqr Has a register it can be compared to in order to generate an interrupt.

sqr 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.

sqr The timer can be turned on and off.

sqr Uses a prescalar.

sqr Uses a postscalar.

sqr Cannot use an external clock and so cannot be used as a counter.

sqr Generates a programmable interrupt.

sqr Shares certain functions with the watchdog timer. This is important.

sqr 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

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:

t0214-01

a0214-01

*TOCK1 is pin 6 on the PIC 16F877A, also known as PORTA.4.
PORTA Pin 4 is the fifth pin on PORTA.

4 Bit value

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

a0215-01

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.

sqr 4,000,000/4/256 = 3,906.25 (the theoretical value for a 4 MHz processor)

sqr 3906.25/256 = 15.26 (used 15 in the preceding program since it is the closest integer)

sqr 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

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.

f0225-01

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:

a0226-01

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

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

a0229-01

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 Timer2 Program

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

The Watchdog Timer

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.