please dont rip this site

Darkroom Timer

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]


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


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 

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 


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 ""
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


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


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 


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 

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.


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 

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 


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.


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.


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.   

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.





See also:

file: /Techref/microchip/cntdn/index.htm, 22KB, , updated: 2013/7/22 11:43, local time: 2017/4/28 22:58,

 ©2017 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?
Please DO link to this page! Digg it! / MAKE! / 

<A HREF=""> Gennette Bruce's countdown timer</A>

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.

Link? Put it here: 
if you want a response, please enter your email address: 
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed?

  PICList 2017 contributors:
o List host: MIT, Site host, Top posters @20170428 RussellMc, IVP, Van Horn, David, James Cameron, alan.b.pearce, Neil, Sean Breheny, Josh Koffman, Brent Brown, Bob Blick,
* Page Editors: James Newton, David Cary, and YOU!
* Roman Black of Black Robotics donates from sales of Linistep stepper controller kits.
* Ashley Roll of Digital Nemesis donates from sales of RCL-1 RS232 to TTL converters.
* Monthly Subscribers: Gregg Rew. on-going support is MOST appreciated!
* Contributors: Richard Seriani, Sr.

Welcome to!