If you have no knowledge about timers, you should read this chapter carefully before taking on the chapters that follow. Those chapters provide a much more advanced discussion of the devices based on their usage, as opposed to the introductory approach provided here. However, there is some repetition, so as to allow each part of this book to stand independently.
Most users will find that using timers and counters is the hardest part of learning how to use PIC microcontrollers. With this in mind, we will proceed in a step-by-step manner and build up the programs in pieces that are easier to digest. Once you get comfortable with their setup procedures, you will find that timers and counters are not so intimidating.
We will cover timers and counters separately. Counters are essentially timers that get their clock input from an outside source. There are two counters in the 16F877A, and they are associated with Timer0 and Timer1. Timer2 cannot be used as a counter because it has no way to read an external signal into this timer.
Note The clock frequency utilized by the timers is one-fourth of the oscillator frequency. This is the frequency of the instruction clock. This means that the counters are affected by every fourth count of the main oscillator. The frequency is referred to as Fosc/4 in the literature. When responding to an external clock signal, the response is to the actual frequency of the input.
Caution The PICBASIC PRO Compiler generates code that does not respond to interrupts while a compiled instruction is being executed. Therefore, long PAUSEs (meaning long enough to lose an interrupt signal, depending on how the timer is set up) can lead to lost interrupts if more than one interrupt occurs during the pause. Since interrupts are used for the express purpose of handling critical response/timing needs, this is most undesirable. PAUSE commands should be used with care under these conditions. The program samples provided next give examples of how they can be written to generate shorter pauses.
Timer0 will be covered in more detail as a prototypical timer, and discussion and examples for the use of Timer1 and Timer2 will be provided.
The use of timers internal to microprocessors is a bit more complicated than what we have been doing so far because there is a considerable amount of setup required before the timer can be used, and also because the options for setting up the timers are extensive. We will cover the timers one at a time in an introductory manner, but be warned that there is an entire manual (PICmicro Mid-Range MCU Family Reference Manual [DS33023]) available from Microchip Technology Inc. that covers nothing but timers, so our coverage here will, of necessity, be rudimentary.
Understanding timers has to do with understanding how to turn them ON and OFF and how to read and set the various bits and bytes that relate to them. Essentially, in the typical timer application, you turn a timer ON by turning on its enable bit. The timer then counts a certain number of clock cycles and sets an interrupt bit, thus causing an interrupt. Your program responds to the interrupt by executing a specific interrupt handling routine and then clearing the interrupt bit. The program then returns to wherever it was when the interrupt occurred. The pre/postscalars have to do with modifying the time it takes for an interrupt to take place. The hard part is finding out which bit does what and where it is located, so reading and understanding the datasheet chapter on the timer you are using is imperative. There is no escaping this horror!
Timers allow the microcontroller to create and react to chronological events. These include:
Timing events
The creation of clocks for various purposes
Generating timed interrupts
Controlling PWM generation
Waking the PIC from its sleep mode at intervals to do work (and back to sleep)
Special use of the watchdog timer
The PIC 16F877A has three internal timers. There is also the watchdog timer, which is discussed after the standard timers.
Timer0 An 8-bit free-running timer/counter with an optional prescalar. It’s the simplest of the timers to use.
Timer1 A 16-bit timer that can be used as a timer or as a counter. It is the only 16-bit timer that can be used as a counter. It is also the most complicated of the timers.
Timer2 An 8-bit timer with a prescalar and postscalar and cannot be used as a counter. There is no input line for this timer.
Each timer has a timer control register that sets the options and properties that the timer will exhibit. All the timers are similar and each of them has special features that give it special properties. It is imperative you refer to your datasheet for the PIC 16F877A as you experiment with the timer functions. Once you start to understand what the PIC designers are up to with the timer functions, it will start to come together in your mind.
Timers can have prescalars and/or postscalars associated with them that can be used to multiply the timer setting by a limited number of integer counts. The scaling ability is not adequate to allow all exact time intervals to be created but is adequate for all practical purposes. To the inability to create perfectly timed interrupts, we have to add the uncertainty in the frequency of the oscillator crystal, which is usually not exactly what it is stated to be (and which is affected by the ambient temperature as the circuitry warms up). Though fairly accurate timings can be achieved with the hardware as received, additional software adjustments may have to be added if extremely accurate results are desired. The software can be designed to make a correction to the timing every so often to make it more accurate. We will also need an external source that is at least as accurate as we want our timer to be, so we can verify the accuracy of the device we create.
Let’s write a simple program to see how Timer0 works. We will use the LED bargraph to show us what is going on inside the microcontroller.
As always, the bargraph is connected to the eight lines of PORTD of the LAB-X1.
First, let’s write a program that will light the two LEDs connected to D0 and D1 alternately. (See Program 6.1.) Having them light alternately lets you know that the program is running, or more accurately, it lets you know that the segment of the program that contains this part of the code is working. These two LEDs will be used to represent the foreground task in our program. There is no timer process in this program at this stage. There are no interrupts. The program just blinks the LEDs.
Program 6.1 Foreground program blinks two LEDs alternately (No timer is being used in this program at this time)
CLEAR ; clears all memory locations
DEFINE OSC 4 ; using a 4-MHz oscillator here
TRISD = %11110000 ; make D0 to D3 outputs
PORTD.0 = 0 ; turn off bit D0
PORTD.1 = 1 ; turn on bit D1
ALPHA VAR WORD ; Set up a variable for counting
;
MAINLOOP: ; main loop
IF PORTD.1 = 0 THEN ; the next lines of code turn the LEDs ON
PORTD.1 = 1 ; if they are OFF
PORTD.0 = 0 ;
ELSE ;
PORTD.1 = 0 ; and OFF if they
PORTD.0 = 1 ; are ON
ENDIF ;
FOR ALPHA = 1 TO 300 ; this loop replaces a long pause command
PAUSEUS 100 ; with short pauses that are essentially
NEXT ALPHA ; independent of the clock frequency.
GOTO MAINLOOP ; do it all forever
END ; all programs need to end with END
The use of the PAUSEUS loop in the Program 6.1 provides a latency of 100 microseconds (worst case) in the response to an interrupt and eliminates most of the effect of changing the OSC frequency if that should become necessary. It is better than using an empty counter, which would be completely dependent of the frequency of the system oscillator. (There is an assumption here that the 100 μsec latency is completely tolerable to the task at hand, and it is for this program. It may not be for your real-world program though, so it may need to be adjusted.)
We are turning one LED OFF and another LED ON to provide a more positive feedback. As long as we are executing the main loop, the LCDs will light alternately and provide a dynamic feedback of the operation of the program in the foreground loop.
We select a relatively fast ON-OFF cycle so we will better be able to see minor delays and glitches that may appear in the operation of the program as we proceed.
Run this program to get familiar with the operation of the two LEDs. Adjust the counter (the 300 value) to suit your taste.
Next, we want to add the code that will interrupt this program periodically and make a third LED go ON and then OFF using an approximately one-second cycle. This will serve as the interrupt-driven task we are interested in learning how to create. This is the IMPORTANT task in this particular exercise.
Here is what must be added to the program to get the interrupt-driven LED operational.
Enable Timer0 and its interrupts with appropriate register/bit settings.
Add the ON INTERRUPT command to tell the program where to go when an interrupt occurs.
Set up the interrupt routine to do what needs to be done.
The interrupt routine counts to 61 and turns the LED ON if it is OFF and OFF if it is ON.
Clear the interrupt flag that was set by Timer0.
Send the program back to where it was interrupted with the RESUME command.
WHY ARE WE USING 61?
The prescalar is set to 64 (bits 0 to 2 are set at 101 in the OPTION_REG).
The counter interrupts every 256 counts.
256 × 64 = 16,384
Clock is at 4,000,000 Hz
Fosc/4 is 1,000,000
1,000,000 / 16,384 = 61.0532. Its not exactly 61, but it is close enough for our purposes for now.
The lines of code now look like those in Program 6.2.
Program 6.2 Using TIMER0 (Program blinks two LEDs [D1 and D0] alternately and blinks a third LED [D2] for one second ON and one second OFF as controlled by the interrupt signal)
CLEAR ; clear memory
DEFINE OSC 4 ; using a 4-MHz oscillator
;
OPTION_REG=%10000101 ; page 48 of datasheet
; bit 7=1 disable pull ups on PORTB
; bit 5=0 selects timer mode
; bit 2=1 }
; bit 1=0 } sets Timer0 prescalar to 64
; bit 0=1 }
;
INTCON=%10100000 ; bit 7=1 Enables all unmasked interrupts
; bit 5=1 Enables Timer0 overflow interrupt
; bit 2 flag will be set on interrupt and
; has to be cleared in the interrupt
; routine. It is set clear at start
ALPHA VAR WORD ; this variable counts in the PauseUS loop
BETA VAR BYTE ; this variable counts the 61 interrupt
; ticks
TRISD = %11110100 ; sets the 3 output pins in the D port
PORTD = %00000000 ; sets all pins low in the D port
BETA=0 ;
ON INTERRUPT GOTO INTROUTINE ; this line needs to be early in
; the program,
; in any case, before the routine is called.
;
MAINLOOP: ; main loop blinks D0 and D1 alternately
IF PORTD.1 = 0 THEN ; ]
PORTD.1 = 1 ; ]
PORTD.0 = 0 ; ] this part of the program blinks two
; LEDs in
ELSE ; ] the foreground as described before
PORTD.1 = 0 ; ]
PORTD.0 = 1 ; ]
ENDIF ; ]
;
FOR ALPHA = 1 TO 300 ; the long pause is eliminated with this
; loop
PAUSEUS 100 ; PAUSE command with short latency
NEXT ALPHA ;
GOTO MAINLOOP ; end of loop
;
DISABLE ; DISABLE//ENABLE must brkt the
; interrupt routine
INTROUTINE: ; this information is used by the compiler
; only.
BETA = BETA + 1 ;
IF BETA < 61 THEN ENDINTERRUPT ; one second has not yet passed
BETA = 0 ; ;
IF PORTD.3 = 1 THEN ; interrupt loop turns D3 on and off every
PORTD.3 = 0 ; 61 times through the interrupt routine.
ELSE ; That is about one second per full cycle
PORTD.3 = 1 ;
ENDIF ;
ENDINTERRUPT: ;
INTCON.2 = 0 ; clears the interrupt flag.
RESUME ; resume the main program
ENABLE ; DISABLE and ENABLE must bracket
; the interrupt routine
END ; end program
Make your predictions and then…
Try changing the 3 low bits in OPTION_REG to see how they affect the operation of the interrupt.
In Program 6.2, Timer0 is running free and providing an interrupt every time its 8-bit counter overflows from FF to 00. The prescalar is set to 64 so we get the interrupt after 64 of these cycles. When this happens, we jump to the “IntRoutine” routine, where we make sure that 61 interrupts have taken place, and if they have, we change the state of an LED and return to the place where the interrupt took place. (It happens that it takes approximately 61 interrupts to equal one second in this routine with a processor running at 4 MHz. This could be refined by trial and error after the initial calculation.)
Note that the interrupt is disabled while we are in the “IntRoutine” routine, but the free running counter is still running toward its next overflow meaning that whatever we do has to get done in less than 1/61 seconds if we are not going to miss the next interrupt, unless we make some other arrangements to count all the interrupts (with an internal subroutine or some other scheme). It can become quite complicated if a lot needs to be done, so we will not worry about it here.
Before going any further, let’s take a closer look at the OPTION_REG and the INTCON (interrupt control) register. These are 8-bit registers with the 8 bits of each register assigned as follows:
OPTION_REG the option register:
Bits 2, 1, and 0 define the prescalar value for the timer. As mentioned earlier, the prescalar can be associated with Timer0 or with the watchdog timer (WDT) but not both. Note that the scaling for the WDT is half the value for Timer0 for the same three bits.
Bit value |
TMR0 rate |
WDTrate |
000 |
1:2 |
1:1 |
001 |
1:4 |
1:2 |
010 |
1:8 |
1:4 |
011 |
1:16 |
1:8 |
100 |
1:32 |
1:16 |
101 |
1:64 |
1:32 We will use this. |
110 |
1:128 |
1:64 |
111 |
1:256 |
1:128 |
Caution A very specific sequence must be followed (which does not apply here) when changing the prescalar assignment from Timer0 to the WDT to make sure an unintended reset does not take place. This is described in detail in the PICmicro Mid-Range MCU Family Reference Manual (DS33023)
As per the preceding, in our specific example, OPTION_REG is set to %10000101.
Refer to the datasheet for more specific information.
INTCON the interrupt control register values are as follows:
Note that Bit 2 is set clear when we start and will be set to 1 when the first interrupt takes place. It has to be re-cleared within the interrupt service routine thereafter. (This is usually at the end of the routine, but not necessarily so.)
A TIMER0 CLOCK: FROM A PROGRAM BY MICROENGINEERING LABS (ON THEIR WEB SITE)
The following program, written by microEngineering Labs and provided by them as a part of the information on their Web site, demonstrates the use of interrupts to create a reasonably accurate clock that uses the LCD display to show the time in hours, minutes, and seconds.
LCD CLOCK PROGRAM USING ON INTERRUPT
This program uses TMR0 and prescalar. Watchdog timer should be set to OFF at program time, and Nap and Sleep should not be used.
Buttons may be used to set hours and minutes.
In Program 6.3, the CLEAR and OSC commands are not used, but we will always use them in our programs.
Program 6.3 Timer0 usage per microEngineering Labs program (Hours, seconds, and minutes digital clock)
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ;
DEFINE LCD_RSREG PORTE ;
DEFINE LCD_RSBIT 0 ;
DEFINE LCD_EREG PORTE ;
DEFINE LCD_EBIT 1 ;
;
HOUR VAR BYTE ; define hour variable
DHOUR VAR BYTE ; define display hour variable
MINUTE VAR BYTE ; define minute variable
SECOND VAR BYTE ; define second variable
TICKS VAR BYTE ; define pieces of seconds variable
UPDATE VAR BYTE ; define variable to indicate update of LCD
I VAR BYTE ; de bounce loop variable
ADCON1 = %00000111 ; parts of PORTA and E made digital
LOW PORTE.2 ; LCD R/W low = write
PAUSE 100 ; wait for LCD to startup
HOUR = 0 ; set initial time to 00:00:00
MINUTE = 0 ;
SECOND = 0 ;
TICKS = 0 ;
UPDATE = 1 ; force first display
;set TMR0 to interrupt every 16.384
; milliseconds
OPTION_REG = %01010101 ; set TMR0 configuration and enable
; PORTB pullups
INTCON = %10100000 ; enable TMR0 interrupts
ON INTERRUPT GOTO TICKINT ;
; main program loop -
MAINLOOP: ; in this case, it only updates the LCD
; with the it
TRISB = %11110000 ; enable all buttons
PORTB =%00000000 ; PORTB lines low to read buttons
; check any button pressed to set time
IF PORTB.7 = 0 THEN DECMIN ;
IF PORTB.6 = 0 THEN INCMIN ; last 2 buttons set minute
IF PORTB.5 = 0 THEN DECHR ;
IF PORTB.4 = 0 THEN INCHR ;
; first 2 buttons set hour
CHKUP: IF UPDATE = 1 THEN ; check for time to update screen
LCDOUT $FE, 1 ; clear screen
; display time as hh:mm:ss
DHOUR = HOUR ; change hour 0 to 12
IF (HOUR // 12) = 0 THEN ;
DHOUR = DHOUR + 12 ;
ENDIF ;
;
IF HOUR < 12 THEN ; check for AM or PM
LCDOUT DEC2 DHOUR, “:”, DEC2 MINUTE, “:”, DEC2 second, “ AM”_
ELSE ;
LCDOUT DEC2 (DHOUR - 12), “:”, DEC2 MINUTE, “:”, DEC2 SECOND,_
“ PM”
ENDIF ;
UPDATE = 0 ; screen updated
ENDIF ;
GOTO MAINLOOP ; do it all forever
; increment minutes
INCMIN: MINUTE = MINUTE + 1 ;
IF MINUTE >= 60 THEN ;
MINUTE = 0 ;
ENDIF ;
GOTO DEBOUNCE ;
; increment hours
INCHR: HOUR = HOUR + 1 ;
IF HOUR >= 24 THEN ;
HOUR = 0 ;
ENDIF ;
GOTO DEBOUNCE ;
; decrement minutes
DECMIN: MINUTE = MINUTE - 1 ;
IF MINUTE >= 60 THEN ;
MINUTE = 59 ;
ENDIF ;
GOTO DEBOUNCE ;
; decrement hours
DECHR: HOUR = HOUR - 1 ;
IF HOUR >= 24 THEN ;
HOUR = 23 ;
ENDIF ;
; de-bounce and delay for 250 ms
DEBOUNCE: FOR I = 1 TO 25 ;
PAUSE 10 ; 10 ms at a time so no interrupts
; are lost
NEXT I ;
UPDATE = 1 ; set to update screen
GOTO CHKUP ;
; interrupt routine to handle each
; timer tick
DISABLE ; disable interrupts during
; interrupt handler
TICKINT: TICKS = TICKS + 1 ; count pieces of seconds
IF TICKS < 61 THEN TIEXIT ; 61 ticks per second (16.384 ms
; per tick)
; one second elapsed - update time
TICKS = 0 ;
SECOND = SECOND + 1 ;
IF SECOND >= 60 THEN ;
SECOND = 0 ;
MINUTE = MINUTE + 1 ;
IF MINUTE >= 60 THEN ;
MINUTE = 0 ;
HOUR = HOUR + 1 ;
IF HOUR >= 24 THEN ;
HOUR = 0 ;
ENDIF ;
ENDIF ;
ENDIF ;
UPDATE = 1 ; set to update LCD
TIEXIT: INTCON.2 = 0 ; reset timer interrupt flag
RESUME ;
END ;
In the preceding clock, the keyboard buttons are used as follows
SW1 and SW5 increment the hours.
SW2 and SW6 decrement the hours.
SW3 and SW7 increment the minutes.
SW4 and SW8 decrement the minutes.
The seconds cannot be affected other than with the reset switch.
The second timer, Timer1 is the 16-bit timer/counter. This is the most powerful timer in the MCU. As such, it is the hardest of the timers to understand and use but is also the most flexible of the three timers. It consists of two 8-bit registers and each register can be read and written to. The timer can be used either as a timer or as a counter depending on how the Timer1 clock select bit (TMR1CS), which is bit 1 in the Timer1 control register (T1CON), is set.
In Timer1, we can control the value that the timer starts its count with, and thus change the frequency of the interrupts. Here we are looking to see the effect of changing the value preload into Timer1 on the frequency of the interrupts as reflected in a very rudimentary pseudo-stopwatch. The higher the value of the preload, the sooner the counter will get to $FF and the faster the interrupts will come. We will display the value of the prescalar loaded into the timer on the LCD so we can see the correlation between the values and the actual operation of the interrupts. As the interrupts get closer and closer together, the time left to do the main task gets smaller and smaller and you can see this in the speed at which the stopwatch runs.
In the following program:
SW1 turns the stopwatch on.
SW2 stops the stopwatch.
SW3 resets the stopwatch.
POT0, the first potentiometer, is read and then written into TMR1H. (TMR1L is ignored in our case, but you may want to use it in a more critical application.)
The results of the experiment are shown in the LCD display.
Let’s creep up on the solution. We will develop the program segments and discuss them as we go, putting the segments together later for a program we can run, as shown in Program 6.4.
Program 6.4 Timer1 usage (Rudimentary timer operation that depends on value of POT-1)
; first let us set up the LCD display parameters.
CLEAR ; clear memory
DEFINE OSC 4 ; set osc speed
DEFINE LCD_DREG PORTD ; lcd is on PORTD
DEFINE LCD_DBIT 4 ; we will use 4-bit protocol
DEFINE LCD_RSREG PORTE ; register select register
DEFINE LCD_RSBIT 0 ; register select bit
DEFINE LCD_EREG PORTE ; enable Register
DEFINE LCD_EBIT 1 ; enable bit
PORTE.2 = 0 ; set for write mode
PAUSE 500 ; wait .5 seconds
;
; Next let us define the variables we will be using
ADVAL VAR BYTE ; create adval to store result
TICKS VAR WORD ;
TENTHS VAR BYTE ;
SECS VAR WORD ;
MINS VAR BYTE ;
;
; Set the variable to specific values, not necessary in this program
; but a formality for clarity
TICKS = 0 ;
TENTHS = 0 ;
SECS = 0 ;
MINS = 0 ;
;
; Set the registers that will control the work. ;
; This is the nitty gritty of it so we will call out each bit.
; INTCON is the interrupt control register.
;
INTCON =%11000000
; bit 7: GIE: Global Interrupt Enable bit, this has to be set
; for any interrupt to work.
; 1 = Enables all un-masked interrupts
; 0 = Disables all interrupts
; bit 6: PEIE: Peripheral Interrupt Enable bit
; 1 = Enables all un-masked peripheral interrupts
; 0 = Disables all peripheral interrupts
; bit 5: T0IE: TMR0 Overflow Interrupt Enable bit
; 1 = Enables the TMR1 interrupt
; 0 = Disables the TMR1 interrupt
; bit 4: INTE: RB0/INT External Interrupt Enable bit
; 1 = Enables the RB0/INT external interrupt
; 0 = Disables the RB0/INT external interrupt
; bit 3: RBIE: RB Port Change Interrupt Enable bit
; 1 = Enables the RB port change interrupt
; 0 = Disables the RB port change interrupt
; bit 2: T0IF: TMR0 Overflow Interrupt Flag bit
; 1 = TMR0 register has overflowed (must be cleared in
; software)
; 0 = TMR0 register did not overflow
; bit 1: INTF: RB0/INT External Interrupt Flag bit
; 1 = The RB0/INT external interrupt occurred (must be
; cleared in software)
; 0 = The RB0/INT external interrupt did not occur
; bit 0: RBIF: RB Port Change Interrupt Flag bit
; 1 = At least one of the RB7:RB4 pins changed state (must
; be cleared in software)
; 0 = None of the RB7:RB4 pins have changed state
;
; T1CON is the timer 1 control register.
T1CON =%00000001
; bit 7-6: Unimplemented: Read as ‘0’
; bit 5-4: T1CKPS1:T1CKPS0: Timer1 Input Clock Prescale Select bits
; 11 = 1:8 Prescale value
; 10 = 1:4 Prescale value
; 01 = 1:2 Prescale value
; 00 = 1:1 Prescale value
; bit 3: T1OSCEN: Timer1 Oscillator Enable Control bit
; 1 = Oscillator is enabled
; 0 = Oscillator is shut off (The oscillator inverter
; is turned off to eliminate power drain)
; bit 2: T1SYNC: Timer1 External Clock Input Synchronization
; Control bit
; TMR1CS = 1
; 1 = Do not synchronize external clock input
; 0 = Synchronize external clock input
; TMR1CS = 0
; This bit is ignored. Timer1 uses the internal clock
; when TMR1CS = 0.
; bit 1: TMR1CS: Timer1 Clock Source Select bit
; 1 = External clock from pin RC0/T1OSO/T1CKI (on the
; rising edge)
; 0 = Internal clock (FOSC/4)
; bit 0: TMR1ON: Timer1 On bit
; 1 = Enables Timer1
; 0 = Stops Timer1
;
; The option register
OPTION_REG = %00000000 ; Set Bit 7 to 0 and enable PORTB pullups
; All other bits are for Timer 0 and
; not applicable
; here
PIE1=%00000001 ; See datasheet, enables interrupt.
ADCON0= %11000001 ; Configure and turn on A/D Module
; bit 7-6: ADCS1: ADCS0: ; A/D Conversion Clock Select bits
; 00 = FOSC/2
; 01 = FOSC/8
; 10 = FOSC/32
; 11 = FRC (clock derived from an RC oscillation)
; bit 5-3: CHS2:CHS0: Analog Channel Select bits
; 000 = channel 0, (RA0/AN0)
; 001 = channel 1, (RA1/AN1)
; 010 = channel 2, (RA2/AN2)
; 011 = channel 3, (RA3/AN3)
; 100 = channel 4, (RA5/AN4)
; 101 = channel 5, (RE0/AN5)(1)
; 110 = channel 6, (RE1/AN6)(1)
; 111 = channel 7, (RE2/AN7)(1)
; bit 2: GO/DONE: A/D Conversion Status bit
; If ADON = 1 See bit 0
; 1 = A/D conversion in progress (setting this bit starts
; the A/D conversion)
; 0 = A/D conversion not in progress (This bit is
; automatically cleared by hardware when the A/D
; conversion is complete)
; bit 1: Unimplemented: Read as ; 0;
; bit 0: ADON: A/D On bit
; 1 = A/D converter module is operating
; 0 = A/D converter module is shutoff and consumes no
; operating current
;
; The A to D control Register for Port A is ADCON1
ADCON1 = %00000010 ; set part of PORTA analog
; The relevant table is on page 112 of the datasheet
; There are a number of choices which give us analog capabilities
; on PORTA.0
; and allow the voltage ; reference between Vdd and Vss. We have
; chosen 0010
; on the third line down in the table
;
; Next let us set up the port pin directions
TRISA = %11111111 ; set PORTA to all input
TRISB = %11110000 ; set up PORTB for keyboard reads
PORTB.0 = 0 ; set so we can read row 1 only for now ;
ON INTERRUPT GOTO TICKINT ; tells the program where to go on
; interrupt
;
; Initialize display and write to top
; line
LCDOUT $FE, 1, $FE, $80, “MM SS T” ;
;
MAINLOOP: ;
ADCON0.2 = 1 ; conversion to reads POT-1. Conversion start
;
; now and takes place during loop. If loop was
; short we would allow for that.
; then check the buttons to decide what to do
IF PORTB.4 = 0 THEN STARTCLOCK ;
IF PORTB.5 = 0 THEN STOPCLOCK ;
IF PORTB.6 = 0 THEN CLEARCLOCK ;
;
; and display what the clock
; status is
LCDOUT $FE, $80, DEC2 MINS, “:“,DEC2 SECS, “:”, DEC TENTHS,_
“POT1=”, DEC ADVAL, “ ”
; We are now ready to read what potentiometer setting is.
ADVAL = ADRESH ; we assumed that enough time has
; passed
; to have an updated value in the
; registers. If not add wait
GOTO MAINLOOP ; do it again
;
DISABLE ; disable interrupts during
; interrupt handler
TICKINT: ;
TICKS = TICKS + 1 ; ticks are influenced by the setting
; of POT-1
IF TICKS < 5 THEN TIEXIT ; arbitrary value to get one
; second
TICKS = 0 ;
;
TENTHS = TENTHS + 1 ;
IF TENTHS <9 THEN TIEXIT ;
TENTHS = 0 ;
;
SECS = SECS + 1 ; update seconds
IF SECS < 59 THEN TIEXIT ;
SECS = 0 ;
MINS = MINS + 1 ; update minutes
TIEXIT: ;
IF PORTB.5 = 0 THEN STOPCLOCK ;
TMR1H=ADRESH ;
PIR1=0 ;
RESUME ; go back to the main routine
ENABLE ;
;
DISABLE ;
STARTCLOCK: ;
INTCON = %10100011 ; enable TMR1 interrupts
TICKS = 0 ;
GOTO MAINLOOP ;
;
STOPCLOCK: ;
INTCON = %10000011 ; disable TMR1 interrupts
PAUSE 2 ;
TICKS = 0 ;
GOTO MAINLOOP ;
;
CLEARCLOCK: ;
INTCON = %10000011 ; disable TMR1 interrupts
MINS = 0 ;
SECS = 0 ;
TENTHS = 0 ;
TICKS = 0 ;
GOTO MAINLOOP ;
ENABLE ;
END ;
Run this program to see how the setting of the potentiometer affects the operation of the stopwatch. It becomes clear that choosing how the interrupt will serve our purposes is very important, and a bad choice can pretty much compromise the operation of the program.
We can read the timer and the interrupts at our discretion either before or after an interrupt has occurred, and the interrupt flag can be cleared whenever we wish, if it has been set. If it has not been set, there is no need to clear it.
Figure 6.1 provides a diagrammatic representation of how an interrupt routine is implemented in a typical program.
Even the 16-bit Timer1 on the 16F877A cannot time a long interval. Repeated intervals have to be put together to create long time periods. The longest possible time between interrupts for Timer1 (with a 4-MHz clock) is 0.524288 seconds. The maximum prescale value is 1:8. The postscalar is only available on Timer2 (which in any case is a shorter 8-bit timer). This results in a maximum time and is determined by multiplying the instruction clock cycle (1 μsec @ 4 MHz) by the prescale (8) by the number of counts from one overflow to the next (65536). 1 μsec * 8 * 65536 = 0.524288 seconds. On a 20-MHz machine, the interval would be one-fifth of this.
Figure 6.1 The simplified, basic structure of a typical interrupt routine. (Bits shown as being set are not the real bits.)
Timer1 uses two registers TMR1H and TMR1L.
The timer has the following general properties:
1. Increments from $0000 to $FFFF in two registers.
2. If the interrupt is enabled, an interrupt will occur when the two byte counter over-flows from $FFFF to $0000.
3. The device can be used as a timer.
4. The device can be used as a counter.
5. The timer registers can be read and written to at any time.
6. There is no postscalar for this timer.
Simply stated again, this timer is used by setting its registers to a specific value and using the interrupts this value creates in a useful way. A 16-bit timer will count up from where set to FFFF and then flip to the selected value and start over again. An interrupt occurs and the interrupt flag is set every time the register overflows from FFFF to 0. We respond to the interrupt by doing whatever needs to be done in response to the interrupt, resetting the interrupt flag and then going back to the main routine. On timers that permit the use of a prescalar and postscalars, the pre/postscalar allows us to increase the time between interrupts by multiplying the time between interrupts with a defined value in a 3- to 8-bit location. On writable timers, we have the ability to start the timers with values of our choice in the timer register(s). This gives us very useable but not absolute control over the interrupt intervals.
Consider the fact that a 0.01 second timer setting with a prescalar set to 16 would provide us with an interrupt every 0.16 seconds and we would have 0.16 seconds to do whatever we wanted to do between interrupts. Actually, less than 0.16 seconds, because there are still the other lines of code in that program that need to be executed.
So there are serious limits as to what can be put in the timer counter and what can be put in the prescalar. In addition, the interrupt frequency is (also) affected by the accuracy of the processor clock oscillator.
PRESCALARS
The value of the scaling that will be applied to the timer is determined by the contents of two bits in the interrupt control register. These bits multiply the time between interrupts by powers of 2 as explained in the following.
Prescalar |
For Timer1 |
For the Watchdog Timer |
|||
00 |
Multiply by |
1 |
00 |
Multiply by |
2 |
01 |
Multiply by |
2 |
01 |
Multiply by |
4 |
10 |
Multiply by |
4 |
10 |
Multiply by |
8 |
11 |
Multiply by |
8 |
11 |
Multiply by |
16 |
The two bits are bit 4 and bit 5 of the Timer1 control register T1CON. The 8 bits in T1CON are assigned as follows
TMR1ON |
Bit 0 |
1=Enables Timer1 |
0=Disables timer |
TMR1CS |
Bit 1 |
1=Use external clock |
0=Use internal clock |
TISYNC |
Bit 2 |
1=Sync with internal clock input |
0=Sync with external clock input |
TIOSCEN |
Bit 3 |
1=Enable oscillator |
0=Shut off oscillator |
T1CKPS1 |
Bit 4 |
Counter scalar is described above |
|
T1CKPS0 |
Bit 5 |
Counter scalar is described above |
|
USING TIMER1 TO RUN A CRITICAL INTERRUPT-DRIVEN TASK WHILE THE MAIN PROGRAM RUNS A FOREGROUND TASK
Let’s use this timer in the same way we used Timer0 earlier and see what the differences between the two timers are. To begin with, because Timer1 is 16 bits wide, it can take much longer for it to set its interrupt flag. The interrupt flag was set approximately 61 times a second by Timer0. Timer1 flag can take approximately 0.524 seconds, as calculated earlier, so it is set about two times a second. Let’s write a short Timer1 program that is similar to the original Timer0 blinker program to see how the differences shape up.
Program 6.5 blinks the LEDs at D0 and D1 ON and OFF alternately as the foreground part of the program. The interrupts generated by Timer1 are used to blink D3 ON and OFF at half-second intervals. Since the control of D3 is driven by the interrupt, the timing stays accurate. Any time used by the interrupt routine is lost by the foreground task and affects the overall frequency of the D0/D1 blink rate. It is important to understand this loss.
Program 6.5 Using Timer0 (Programs blinks two LEDs alternately and blinks a third LED approximately a half second ON and a half second OFF)
CLEAR ; clear
DEFINE OSC 4 ; osc speed
TRISD = %00000000 ; set all PORTD lines to output
TRISE = %00000000 ; set all PORTE lines to output
; set the A to D control register for
; digital ports D, E
ADCON1=%00000111 ; needed, 16F877A because it
; has analog properties
T1CON = %00000001 ; turn on Timer0, prescalar = 1
INTCON = %11000000 ; enable global interrupts, peripheral
; interrupts
;
I VAR WORD ; counter variable
J VAR WORD ; counter variable
PAUSE 500 ;
I=0 ; set counters to 0
J=0 ;
PIE1.0 = 1 ; enable TMR1 overflow interrupt
ON INTERRUPT GOTO INTHANDLER ;
PORTD=0 ; turn off the entire port
PORTD.3 = 0 ; light d3 on bargraph off, repeats above instr
PORTD.2 = 0 ; light d3 on bargraph off, repeats above instr
;
MAINLOOP: ;
IF PORTD.1 = 0 THEN ; routine lights Do and D1 alternately to
PORTD.1 = 1 ; that the program is running the main routine
PORTD.0 = 0 ;
ELSE ;
PORTD.1 = 0 ;
PORTD.0 = 1 ;
ENDIF ;
FOR I = 1 TO 300 ; this is in lieu of a long pause instruction
PAUSEUS 100 ; so that interrupt is not compromised
NEXT I ;
GOTO MAINLOOP ; do it all forever
;
DISABLE ;
INTHANDLER: ; this is the interrupt service routine
IF J < 6 THEN ; this routine allows 6
J = J+1 ; interrupts for each change of state
GOTO COUNTNOTFULL ; of LED D3.
ELSE ;
J = 0 ;
ENDIF ;
IF PORTD.3 = 1 THEN ; the D3 blink routine
PORTD.3 = 0 ;
ELSE ;
PORTD.3 = 1 ;
ENDIF ;
COUNTNOTFULL: ;
PIR1.0 = 0 ; must now clear the interrupt flag
RESUME ;
ENABLE ;
END ; end program
Play with the value of the counter J to see how this affects the operation of the program. Study the differences between the programs to set and clear the timer flags. Though both of the preceding programs do the same thing, the setting of the potentiometer in the first program must be modified to match the needs of the timer being used.
Timer2 is an 8-bit “timer only,” meaning it cannot be used as a counter. It has a prescalar and a postscalar. The timer register for this counter is both writable and readable. If you can write to a counting register, you can set the value the count starts at and thus control the interval between interrupts (to some degree). That, and the ability to set the pre- and postscalars, gives you the control you need for effective control of the interrupt interval even though you still cannot time all events exactly because of the coarseness of the settings available. Timer2 has a period register PR2, which can be set by the user. The timer counts up from $00 to the value set in PR2, and when the two are the same, it resets to 0. Small values in PR2 can be used to create very rapid interrupts, so much so that there may be no time left to do anything else.
The Timer2 control register is T2CON and its 8 bits are assigned as follows:
As always, the input clock for this timer is divided by 4 before it is fed to the timer. On a processor running at 4 MHz, the feed to the timer is at 1 MHz.
Prescalar for Timer2 |
Postscalar for Timer2 |
||||
00 |
Multiply by |
1, no scaling |
0000 |
Multiply by |
1, no scaling |
01 |
Multiply by |
4 |
0001 |
Multiply by |
2 |
1× |
Multiply by |
16 |
0010 |
Multiply by |
3 |
|
|
|
0011 |
Multiply by |
4 |
|
|
|
0100 |
Multiply by |
5 |
|
|
|
0101 |
Multiply by |
6 |
|
|
|
0110 |
Multiply by |
7 |
|
|
|
0111 |
Multiply by |
8 |
|
|
|
1000 |
Multiply by |
9 |
|
|
|
1001 |
Multiply by |
10 |
|
|
|
1010 |
Multiply by |
11 |
|
|
|
1011 |
Multiply by |
12 |
|
|
|
1100 |
Multiply by |
13 |
|
|
|
1101 |
Multiply by |
14 |
|
|
|
1110 |
Multiply by |
15 |
|
|
|
1111 |
Multiply by |
16 |
The timer is turned ON by setting bit 2 in register T2CON (the Timer2 control register).
The interrupt for Timer2 is enabled by setting Bit 1 of PIE1 and the interrupt lets the program know that it has occurred by setting Bit 1 in PIR1. (Bit 0 in both these registers are for Timer1.)
Bit 7 (the global interrupt enable bit) of INTCON, the interrupt control register, enables all interrupts, including those created by Timer2. Bit 6 of INTCON enables all unmasked peripheral interrupts, and using this feature is one of the ways of awakening a sleeping MCU.
Timer2 can also control the operation of the two PWM signals that can be programmed to appear on lines C1 and C2 with the HPWM command in PICBASIC PRO. Since this one timer controls both lines simultaneously, they both have to have the same PWM frequency. However, the relative width of the pulse within each of the PWM signals during each cycle does not have to be the same.
Timer2 is also used as a baud rate clock timer for communications. See page 54 of the datasheet.
MAKING SURE A TIMER IS WORKING
If you are uncomfortable about getting a timer working, or knowing for sure that it is working, write a very short program in which the main loop displays a variable that is incremented in the interrupt routine. If you see the value of the variable going up, you know the program is going to, and returning from, the interrupt routine.
WATCHDOG TIMER
A watchdog timer is a timer that sets an interrupt that tells us that for some reason the program has hung up or otherwise gone awry. As such, it is expected that in a properly written program, the watchdog timer will never set an interrupt. This is accomplished by resetting the watchdog timer every so often in the program. The compiler inserts these instructions automatically if the watchdog timer option is selected. However, setting the option does not guarantee that a program cannot or will not hang up. Software errors and infinite loops that reset the timer within themselves can still cause hangups.
The watchdog timer is scalable. It shares its scalar with Timer0 on an exclusive basis. Either it uses the scalar or Timer0 uses it. They cannot both use it at the same time. See discussion under Timer0 in the datasheet for more information.
Since PICBASIC PRO assumes that the watchdog timer will be run with a 1:128 prescalar, unwanted watchdog resets could occur when you assign the prescalar to Timer0. If you change the prescalar setting in OPTION_REG, you should disable the watchdog timer when programming. The watchdog enable/disable can be found on the configuration screen of your (hardware) programmer’s software.
Of the three timers in the 16F877A, only Timer0 (the 8-bit timer) and Timer1 (the 16-bit timer) can be used as counters. Timer2 does not have a counter input line provided for it. Generally speaking, this makes Timer0 suitable for use with small counts and rapid interrupts, and Timer1 suitable for larger counts.
HOW DOES A COUNTER WORK?
The operation of a counter is similar to the operation of a timer except that instead of getting its count from an internal clock or oscillator, the counter gets its signals from an outside source. This means we have to do the following things to use a counter:
Decide which counter (timer) to use.
Tell the counter where the signal is coming from.
Tell it whether to count on a rising or falling edge.
Decide what target count we are looking for.
Tell the counter where to start counting because the interrupt will occur when the counter gets full.
Decide whether we will need to scale the count by setting the scalar(s).
Once we start a counter, the counting continues until we deactivate it. There is no other way to stop it, nor any reason for doing so. It will reset if the MCU is reset and it can be reset by writing to it. The rest has to do with knowing what bits to set in the control registers to get the counters to operate in the way we want them to.
USING TIMER0 AS A COUNTER
Note Though often called TIMER0, and referred to as TIMER0 here and in the datasheet, the real designation of this timer address is TMR0.
The three registers related to the control of the TIMER0 module are TMR0, INTCON, and OPTION_REG. INTCON is the interrupt control register. See the datasheet for more information.
This counter has the following properties:
8-bit timer/counter
Readable and writable
8-bit software programmable prescalar
Internal or external clock select
Interrupt on overflow from FFh to 00h
Edge select for external clock
Counter mode is selected by setting OPTION_REG.5=1.
The external input for the timer will come in on PORTA.4, which is pin 6 on the PIC.
The edge direction is selected in OPTION_REG.4 (1=Rising edge).
The prescalar is assigned with bits 0 to 3 of the OPTION_REG register. (See page 49 of the datasheet.)
Note again that the watchdog timer cannot use the prescalar when the prescalar is being used by Timer0.
Since this is an 8-bit counter, it is suited to the counting of small number of counts, but longer counts can be accommodated by using a routine to keep track of the interrupts.
Let’s use the counter to count the pulses received from a 20-slot encoder mounted on a small DC motor. This same source will be used later for the Timer1 experiment for comparison between counters.
We will set up to use the LCD display so we can display certain registers during the operation of Program 6.6. We will also set up to read the potentiometers so we can use their values to modify the program as it runs. Only POT0 and POT1 are used in the program.
Program 6.6 Using Timer0: Program counts the pulses from a motor-driven encoder (You can change the speed of the motor and the time for counts with the 2 POTs)
CLEAR
DEFINE OSC 4 ; 4 MHz clock
DEFINE LCD_DREG PORTD ; data register
DEFINE LCD_RSREG PORTE ; register select
DEFINE LCD_RSBIT 0 ; pin number
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
DEFINE LCD_RWREG PORTE ; read/write register
DEFINE LCD_RWBIT 2 ; read/write bit
DEFINE LCD_BITS 8 ; width of data
DEFINE LCD_LINES 2 ; lines in display
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
;
DEFINE CCP1_REG PORTC ; define the hpwm settings
DEFINE CCP1_BIT 2 ;
; define the A2D values
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set internal clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in us
; set the analog to digital control
; register
ADCON1=%00000110 ; needed for the 16F877A LCD
TEST VAR WORD ;
ADVAL0 VAR BYTE ; create adval to store result
ADVAL1 VAR BYTE ; create adval to store result
X VAR WORD ;
Y VAR WORD ;
PAUSE 500 ; LCD start up
LCDOUT $FE, 1 ; clear display
OPTION_REG=%00110000 ;
TMR0=0 ;
; set up the register i/o
TRISC = %11110001 ; PORTC.0 is going to be the input to
; start the motor in that we are using
; a motor
; encoder for input
PORTC.3=0 ; enable the motor
PORTC.2=0 ; set the rotation direction
;
LOOP: ;
ADCIN 0, ADVAL0 ; read channel 0 to ADVAL0
ADCIN 1, ADVAL1 ; read channel 1 to ADVAL1
ADCIN 3, ADVAL2 ; read channel 3 to ADVAL2
;
TMR0=0 ;
PAUSE ADVAL1 ;
X=TMR0 ;
IF ADVAL0>20 THEN ;
HPWM 2, ADVAL0, 32000 ;
LCDOUT $FE, $80, DEC4 X,” “,DEC ADVAL1,” “
LCDOUT $FE, $C0, “PWM = “,DEC ADVAL0,” ”
ELSE ;
LCDOUT $FE, $C0, “PWM TOO LOW “,DEC ADVAL0,” “
ENDIF ;
;
GOTO LOOP ;
END ;
Play with the values of the two potentiometers to see what happens. Be careful about overflowing the counter past 255. Unexpected results can appear.
The Timer0 counter is affected by the OPTION REGISTER bits as follows:
OPTION_REG.6 = 1 |
; Interrupt on rising edge |
OPTION_REG.5 = 0 |
; External clock |
OPTION_REG.4 = 1 |
; Increment on falling edge not used |
OPTION_REG.3 = 0 |
; Assign prescalar to Timer0 |
OPTION_REG.2 = 1 |
; ] These 3 bits set the prescalar |
OPTION_REG.1 = 1 |
; ] You can experiment with changing these 3 |
OPTION_REG.0 = 1 |
; ] bits to see what happens. |
Put these and other values in the program and run the program. See what happens.
USING TIMER1 AS A COUNTER
The operation of Timer1 as a counter is similar to the operation of Timer0, but because Timer1 is a 16-bit timer, much longer counts can be handled, and counts coming in at faster rates can be counted. It also means that a lot more can be done in the TimerLoop and BlinkerLoop routines if the program is designed to do so. However, the setup for Timer1 is more complicated because of the more numerous options available.
The differences between the use of the two timers have to do with the setup of the controlling registers. Timer1 is controlled by/uses six registers as compared to three for Timer0. They are:
Again, the frequency of the oscillator is divided by 4 before being fed to the counter when you use the internal clock (Fosc/4).
Page 52 of the datasheet reads:
Counter mode is selected by setting bit TMR1CS. In this mode, the timer increments on every rising edge of clock input on pin RC1/T1OSI/CCP2, when bit T1OSCEN is set, or on pin RC0/T1OSO/T1CKI, when bit T1OSCEN is cleared.
So three of the pins on the 16F877A can be used as inputs to the Timer1 counter module. They are:
Pin PORTA.4 |
which is the external clock input. Pin 6 on the PIC. |
Pin PORTC.0 |
selected by setting TIOSCEN =1 |
Pin PORTC.1 |
selected by setting TIOSCEN=0 |
Timer1 is enabled by setting T1CON.0=1. It stops when this bit is turned off or disabled.
The clock that Timer1 will use is selected by T1CON.1. The external clock is selected by setting this to 1. The input for this external clock must be on PORTB.4.
In summary, 8 bits in the Timer1 control register, T1CON, provides the following functions:
Bit 5 |
Input prescalar |
Bit 4 |
Input prescalar |
Bit 3 |
Timer1 oscillator enable |
Bit 2 |
Timer1 external clock synchronization |
Bit 1 |
Timer1 clock select |
Bit 0 |
Timer1 enable |
If the interrupts are not going to be used, the other registers can be ignored. Set T1CON = %00110001.
The setting of these bits is described in detail on page 51 of the datasheet. Let’s look at Program 6.7, which reflects the preceding information.
; First let us define all the defines that we will need.
; Here all the defines are included as an example but
; not all are needed when using the LAB-X1.
Program 6.7 Timer1 as counter (Timer1 counts signals from a motor encoder)
CLEAR ; clear memory
DEFINE OSC 4 ; 4 MHz clock
DEFINE LCD_DREG PORTD ; data register
DEFINE LCD_RSREG PORTE ; register select
DEFINE LCD_RSBIT 0 ; pin number
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
DEFINE LCD_RWREG PORTE ; read/write register
DEFINE LCD_RWBIT 2 ; read/write bit
DEFINE LCD_BITS 8 ; width of data
DEFINE LCD_LINES 2 ; lines in display
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
; The next two lines define which pin is going to be used for the
; HPWM signal
; that will control the speed of the motor. The encoder that we
; are looking
; at is attached to the motor
DEFINE CCP1 REG PORTC ; define the HPWM settings
DEFINE CCP1_BIT 2 ; pin C1
; The next few lines define the reading of the three
; potentiometers on the board. Only the first
; potentiometer is being used in the program but the others are
; defined so that you can
; use them when you modify the program. The potentiometers give
; you values you can
; change in real time.
; define the A2D values
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set internal clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in uS
; Next we set ADCON1 to bring the MCU back into digital mode.
; Since this PIC has analog capability, it comes up in
; analog mode after a reset or on startup.
; set the Analog-to-Digital control register
ADCON1=%00000111 ; needed for the LCD operation
; we create the variables that we will need.
TMR1 VAR WORD ; set the variable for the timer
ADVAL0 VAR BYTE ; create adval to store result
ADVAL1 VAR BYTE ; create adval to store result
ADVAL2 VAR BYTE ; create adval to store result
X VAR WORD ; spare variable for experimentation
Y VAR WORD ; spare variable for experimentation
PAUSE 500 ; pause for LCD to start up
LCDOUT $FE, 1 ; clear Display and cursor home
;
; set up the register I/O
TRISC = %11110001 ; PORTC.0 is going to be the Input
CCP1CON = %00000101 ; capture every rising edge
T1CON = %00000011 ; no prescale/Osc off/Sync on
; external source/TMR1 on
; start the motor, using a motor encoder
; for input
PORTC.3=0 ; enable the motor
PORTC.2=1 ; set the rotation direction
; Next we go into the body of the program. The loop starts with
; reading all
; three potentiometers though we are using only the first one to
; set the power
; and thus the speed of the motor.
LOOP: ;
ADCIN 0, ADVAL0 ; read channel 0 to ADVAL0
ADCIN 1, ADVAL1 ; read channel 1 to ADVAL1
ADCIN 3, ADVAL2 ; read channel 3 to ADVAL2
; If the duty cycle of the motor is less than 20 out of 255 the
; motor will not come on
; so we make an allowance for that and display the condition on
; the LCD.
IF ADVAL0>20 THEN ;
HPWM 2, ADVAL0, 32000 ;
LCDOUT $FE, $C0, “PWM = “,DEC ADVAL0,” “
ELSE ;
LCDOUT $FE, $C0, “PWM TOO LOW “,DEC ADVAL0,” “
ENDIF ;
; Then we read the two timer registers to see how many counts
; went by.
; In our case the counts were too low to show up in the high
; bits so that
; it can be ignored but if you have a faster count input, you
; might want
; to add this information to the readout.
TMR1H = 0 ; clear Timer1 high 8-bits
TMR1L = 0 ; clear Timer1 low 8-bits
T1CON.0 = 1 ; start 16-bit timer
PAUSE 100 ; capture 100 ms of Input Clock Frequency
T1CON.0 = 0 ; stop 16-bit Timer
TMR1.BYTE0 = TMR1L ; read Low 8-bits
TMR1.BYTE1 = TMR1H ; read High 8-bits
TMR1 = TMR1 - 11 ; capture Correction
IF TMR1 = 65525 THEN NOSIGNAL ; see PICBASIC PRO manual for
; explanation.
LCDOUT $FE, $80, DEC5 TMR1,” COUNTS” ; frequency display
PAUSE 10 ; slow down
GOTO LOOP ; do it again
;
NOSIGNAL: ;
LCDOUT $FE, $80, “NO SIGNAL ” ;
GOTO LOOP ;
END ;
PRESCALARS AND POSTSCALARS
Prescalars and postscalars can be confusing for the beginner. Here is a simple explanation.
A prescalar is applied to the system clock and affects the timer by slowing down the system clock as it applies to the timer. Normally, the timer is fed by a fourth of the basic clock frequency, which is called Fosc/4. In a system running a 4 MHz clock, the timer sees a clock running at 1 MHz. If the prescalar is set for 1:8, the clock will be slowed down by another eight times, and the timer will see a clock at 125 KHz. Refer to the diagram on the bottom half of page 52 (for Timer1) in the datasheet to see how this applies to Timer1.
A postscalar is applied after the timer count exceeds its maximum value, generating an overflow condition. The postscalar setting determines how many overflows will go by before an interrupt is triggered. If the postscalar is set for 1:16, the timer will over-flow 16 times before an interrupt flag is set. The upper diagram on page 55 (for Timer2) of the datasheet shows this in its diagrammatic form and is worth studying.
All other things being equal, both pre- and postscalars are used to increase the time between interrupts.
When starting out, just leave the scalars at 1:1 values and nothing will be affected. We will not need to use them for any of the experiments we will be doing. Once you get more sophisticated in the use of timers, you can play with the values and learn more about how to use them. The primary use is in creating accurate timing intervals for communications and so on, because no external routines are necessary when this is done with scalars. When doing it this way, everything becomes internal to the PIC and is therefore not affected by external disturbances.
Additional information on timer modules is available in the PICmicro Mid-Range MCU Family Reference Manual (DS33023).
1. Write a program to generate a 1-minute timer/clock with a 0.1 second display. Check its accuracy with the time site on the Internet.
Make adjustments so it is accurate to within 1 second per hour, and then 1 second per day. Can this be done? Why? Which timer works best? Which timer is the easiest to use for such a task?
2. Write the preceding program for each of the other two timers.
1. Design and make a tachometer for small model aircraft engines. Have a range of from 5 rev per second to 50,000 rev per minute displayed on the LCD in real-time.
2. Design and build a thermometer based on the changes in frequency exhibited by a 555 timer circuit being controlled by a thermistor. Calibrate the thermometer with a lookup table. If you are not familiar with the use of lookup tables, you should undertake the research necessary to understand how to use them. They are very useful devices at the level we are working.