counts down from 0-99 min 0-59
The purpose of this project is to present a device that is useful
and at the same time demonstrate to the beginner many features involved in
the PIC. Some of the topics included are:
* Simple use of MPASM assembler
* Demonstration of use of timer 0 and the prescaler
* Use of length of instructions to set up timing delays
* Using interrupt routines
* Detection of switch closures including debouncing
* Saving and recovering data from the onboard EEPROM
When the unit is turned on the last used starting count, minutes
0-99, seconds 0-59, is showing on the display. The start count is held in
data EEPROM of the PIC16F84. Countdown starts when the start button is pressed.
An alarm is sounded when the count reaches zero. The alarm continues until start is pressed again. This press also returns the starting count to the
display. Pressing start before reaching zero also returns to starting conditions.
The start count can be changed if the set button is pressed
Each digit is lit in turn, incrementing from zero until the set button is pressed again. The new start count is saved in EEPROM after the final press
of the set button.
There are 15 settable start counts. You cycle through them using
the select pushbutton. The set button changes only the starting count presently
The source code for MPASM is in the file 'CNTDN.ASM'. It's about
as simple as you can get as far as assembler directives go. 'LIST' defines
while additional code brought in by '#INCLUDE' define all special function registers, bits etc. #DEFINEs are used to make the code clearer.'ORG 0' says
to start the code at location 0 and 'END' marks the end of the program.
Labels start in the first column. Both the equates and destination
lines have labels attached to them. Everything else starts in column 2 or
and #include could optionally start in column 1 also. Look over "p16F84.inc" to see all the definitions included. Individual bits of registers have names
which should be used rather than numbers, i.e. STATUS,Z rather than STATUS,2.
Defines replace the corresponding numbers involved and make things clearer, ( PORTA,START_PB rather than PORTA,7).
When you assemble 'CNTDN.ASM', you will get a number of warnings
The warnings are because of the instructions 'TRIS' and 'OPTION'. Ignore them, it's the easiest way to set up these registers. The messages are because MPASM
can't keep track of which page you are in. Just make sure that RB0 of STATUS has been set before the instructions mentioned are reached and cleared
There are two routine going on at the same time. The main routine
sets initial conditions and then loops, checking switches and for an alarm
termination of the count. An interrupt routine does the multiplexing of the display and decrements the count every second if a countdown is in progress.
It also sets an alarm flag when the count reaches zero. The interrupt is based on the overflow of timer 0, (TMR0).
Two methods of timing are used in the program, TMR0 for the
interrupt routine and instruction length timing for delays in switch debouncing
SETTING UP TIMER ZERO
TMR0 setup is complicated. Timer zero continually increments.
When it rolls over, a flag, T0IF in the INTCON register, is set. We are responsible
clearing the flag in software. If we wanted, we could just poll this flag.
This requires a loop, constantly checking the flag. A better way is to enable timer zero interrupt, (T0IE in INTCON = 1), and enable interrupts in
general, (GIE in INTCON = 1). With both bits set, an overflow of TMR0 will raise T0IF and cause a CALL to location 4 which is a jump to the interrupt
GIE is cleard when the routine is entered so other interrupts
won't interfere. GIE will be reset at the end of the routine by RETFIE, (return
and enable GIE). Don't forget to clear T0IF or we are right back in the interrupt situation again. Code is also necessary at the beginning and end
of the routine to save and restore the values of W and the STATUS register.
Remember, there is another routine going on, (MAIN), which may require these values. Saving these is a little tricky because we can't use any
instructions that change the value of STATUS to do it. SWAP seems to work.
When we start up the PIC, TMR0 is set to increment on pulses
from Port A bit 4 pin, (T0CS in OPTION = 1). Clear T0CS, (Timer 0 Clock Select),
to 0 to make
TMR0 increment with the instruction cycle. This is every microsecond for a 4Mhz crystal. TMR0 will overflow after 256 microseconds. This is too fast.
We use the prescaler to slow the rate down. The prescaler comes up assigned to the watchdog timer, (PSA of OPTION = 1). PSA = 0 will assign it to TMR0.
While we are talking about OPTION, bits 0-3 control the division ratio for the prescaler. We set bits 0 and 1 to get a 1:16 rate. This gives an overflow
every 256 X 16 = 4096 microseconds. All of this adds up to putting a 3 in the OPTION register.
I told you it was complicated. The good part is that once it
is set up it just goes on automatically in the background. Every 4 milliseconds
interrupt routine is entered. The digit to display is changed and the value from the appropriate register, (SEC, SEC10, MIN or MIN10), is sent to the CD4511
,(through Port A), where segments to be lit are decided. A pattern is selected to turn on the appropriate transistor and sent to Port B. Every second a call
is made to EVERYSEC which decrements the count and checks for 0000. If zero is reached the flag bit in ALARM is set.
One more additional complication is the exact timing for 1 second.
A counter INTCNT is decremented each time the interrupt routine is entered.
normally initially set to 244, (H'F4'). 244 X 4096 = 999424 microseconds, slightly less than 1 second. Every 7th time it is set to 245 instead, through
the use of the counter FUDGE. This is 1003520 microseconds. The average works out to 1000009 microseconds. Not perfect, but pretty close.
To review the interrupt procedure:
* There are 4 conditions in the PIC that cause interrupts. Each condition raises a flag in INTCON. This happens independent of the state of the
* Each condition has an enable bit which when set indicates that a interrupt should be considerd. If GIE is also set an interrupt will occur and a
call made to location 4.
* We are interested only in the interrupt that can occur when TMR0 rolls over from 255 to 0. By using the prescaler, we make this happen about
every 4 milliseconds.
* GIE is used to disable all interrupts by going to zero when any of the interrupt conditions occur. This prevents any further interruption while
the current interrupt is being serviced. GIE is reset by RETFIE.
* You have to remember to clear the flag set by the interrupt condition in the interrupt routine itself. Otherwise the condition applies as soon as
TIMING USING INSTRUCTION LENGTH
TMR0 is handy when something has to occur at regular intervals.
Sometimes we just want to delay for a set period of time. This can be done
based on the instruction length, one instruction cycle for most instructions, two if the program counter has to be changed. Timing routines appear at the
end of the program. Based on a 4Mhz crystal the routine at ONEMSEC takes one millisecond, if you include the two microseconds necessary for the call. In
similar fashion NMSEC take the number of milliseconds in W when the routine is entered.
The most elementary loop in the timing routines is at MICRO4.
Each time through this loop requires 4 microseconds, (two single cycle instructions
and one two cycle instruction). Notice that when W goes from 1 to 0, the last time through takes 3 microseconds. Call with 249 in W and the total time
looping adds up to 995 microseconds. Add 2 for the call, two for the return and 1 for the load of W and you end up with exactly 1000 microseconds.
For multiples of 1 millisecond, (NMSEC), we need to load an
external counter and keep track of this counter as we go through a number
of loops. Since we
have to allow for any number of loops 1-255, the best we can do is make each loop come out 1 msec and ignore the slight over head getting into the looping
situation. This would be 4 microseconds to load W, do the call and load CNTMSEC.
A couple of routines are used in switch debouncing. The problem
here is that when you press or release a pushbutton it is not a simple matter
from one state to another.
Normally open push button are attached to Port B pins RB7, (start),
and RB6,(set). The port pins are set high by activating internal pull-ups.
are not activated upon power on reset. To activate them you make sure bit 7 of OPTION is low. When you push one of these buttons, connection is made with
a contact that is grounded. This will pull the pin low. The problem is that the contact bounces and the connection is made and broken a number of times
before the contacts settle down into the closed position. Each time the contact bounces off, the pull-ups will try to pull the pin high again. The
contact may not go all the way back to the original position but if the level is high enough for even a microsecond the PIC can interpret it as an 'OPEN'.
A similar problem occurs when the pushbutton is released. The problem is not as bad in this case though because the contact has to bounce all the way back
to the orignal closed position in order to be interpreted as a 'LOW'. Some switches are a lot less 'bouncy' than others.
What can we do about the problem? One solution is to put a capacitor
across the switch. If it is about the right size, the time it takes to charge
prevents rapid changes of the state of the pin and sort of average out the bounces which usually last only a milliseconds or two. You would have to
play with the size to find what works, usually something between 0.01 and 0.1 mfd. Another electronic solution is a RS flip-flop for each switch.
The solution can be done in software. The idea is to look at
the situation every few milliseconds and find three or four times in succession
reading is the same. Another solution, if you have the time, is to simply check start at the first indication of say a closure and then wait long enough
for any bouncing to have stopped before checking again. If you get an opposite reading you ignore this as a closure.
If you can assume that the switches start high and any initial
low comes from pressing a switch you can ignore bounces on the press. Go to
required by the press and wait for a release at the end of the routine.
Notice that the wait for release routines are just that, they lock you in a loop until the key is definately released. Even if the switch were
still bouncing from the press, that would be ignored. This is the method used in the program. You see it used throughout the set digits routine as
well as in the main loop. Even before the main loop is entered, three waits in a row make sure no buttons are pressed.
SAVING STARTING COUNT IN EEPROM
The routines for saving and recovering data from data EEPROM
are straight out of the Microchip literature. There are magic sequences involved
don't understand. I just used the code provided. One thing that caused me some trouble was forgetting to disable interrupts before writing to the EEPROM.
This could have been done in the routine WRITEE but I chose to do it in the routine SETDISP at the end, either side of the call to PUTEE and in the routine
SETSELECT just before and after WRITEE.
Initial data is placed in EEPROM when the PIC is programmed
using the DE directive at the end of CNTDN.ASM. Location 0 of EEPROM holds
an offset which
decides the four locations holding digits to be placed in the display for the starting count. Location is initially set to zero and then incremented by
four each time the select pushbutton is pressed. The four selected locations are modified and replaced by using the set pushbutton.
I used three AA alkaline batteries for a power source. The unit
draws about 50 ma. so these should last a few hundred hours. You could use
a power line
operated 5 volt supply.
If you use high efficiency LEDs for the display you might increase
the size of the 150 ohm resistors and reduce the current/segment to a few
milliamperes. If so, you could do away with the transistors.
The unit could be built without the CD4511 decoder. This chip
provides at least two advantages:
1. It frees up 3 I/O lines and prevents having to multiplex the switches.
2. It simplifies the code by selecting the segments to be lit. It also blanks the display when an illegal #, like hex A, is entered.
You could do away with the chip, select the segments in software and multiplex in the switches, ( which will take a few more resistors to isolate them from
I actually didn't like the sound of the piezo speaker in the
schematic. I added a couple of transistors and a speaker I found from an old
(it was 50 ohms either side of center tap).
Pushbutton switches vary considerably in quality. The ones I used were pretty cheap and seem to have trouble on making contact sometimes.
OVERLOOKING SOMETHING IMPORTANT
I originally rushed getting this project out. I built the unit, wrote the code and it worked, (not the first time of course). I then read in Piclist
of another method of using TMR0 for timing. It involved writing a count to TMR0 so the remaining counts to the 255 to 0 rollover would give the desired
time. I never even thought of doing it this way, I always just used the whole 256 counts. Then it struck me. The timing of the first second could be way
off. TMR0 is running continually and could have any value 0-255 when the button is pressed. You of course have to set it to zero to get the full 256
counts. This made me realize that something else could be wrong. You have the option of cancelling a count down. This means that INTCNT doesn't
necessarily get to zero and get reset. Better reload INTCNT too just to make sure. The moral ... just because something looks like it is working
doesn't mean it actually is.
© 2001 - YO5OFH, Csaba Gajdos