By Gennette Bruce [bruce.gennette at TAFE.NSW.EDU.AU]
thanks to viewit for the updated picture!
Source Code for PIC16F84 and CD4511 decoder
Also, there is an updated version with NPN drivers replacing the 4511 and many new features by Brendon Archibald [websales optusnet.com.au]
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 programming 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 DESCRIPTION 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 before countdown. 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 displayed. MPASM 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 the processor, 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 beyond. #define 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 and messages. 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 afterwards. THE CODE 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 flag at 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). TIMING Two methods of timing are used in the program, TMR0 for the interrupt routine and instruction length timing for delays in switch debouncing and alarm generation. 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 for 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 routine. 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 the 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. It is 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 enable bits. * 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 you exit. 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 with timing 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. SWITCH DEBOUNCING 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 of going 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. 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 when the 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 the routine 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 that I 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. SUGGESTED MODIFICATIONS 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 the displays). 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 digital clock, (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.
James thank you for the circuit you have provided.
I've built the circuit and flashed the PIC and all works perfectly!
I was just wondering if you have a modification to the code so that it can count hours and minutes rather than minutes and seconds?
The Capacitor inline with the Peizo speaker, is it 0.05uF or nF?
This is my first circuit using an Oscillator, can you recommend a Crystal I should use. When searching on rswww.com I see 4pin and 2pin crystals, so i'm slightly confused what to use.
With the crystal what are the two capacitor looking components between the crystal and Vss? They seem to have no values, so I assume their not capacitors. Could you please advise me.
Thank you, Rob Hurd
James Newton replies: Capacitor values are generally uF unless other specified. 2 pin crystals are what I'm used to. The components on either side of the crystal are very small capacitors. See PIC oscillators for more information.+
|file: /Techref/microchip/cntdn/index.htm, 22KB, , updated: 2013/7/22 11:43, local time: 2019/10/13 16:46,
|©2019 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?|
<A HREF="http://massmind.org/techref/microchip/cntdn/index.htm"> Gennette Bruce's countdown timer</A>
|Did you find what you needed?|
Welcome to massmind.org!
Welcome to massmind.org!