Darkroom Timer using PIC16F84 microcontroller

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]


Darkroom Timer

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


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). 
Schematic Darkroom Timer

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.

For more detail: Darkroom Timer using PIC16F84 microcontroller

Current Project / Post can also be found using:

  • how to build a darkroom timer circuit
  • darkroom
  • darkroom timer pic
  • timer pcb layout

Leave a Comment

= 3 + 0

Read previous post:
PIC Frequency Counter
IK3OIL 16F84 PIC Frequency Counter Files

Caption (copied from the NorCal meeting listed in the links below): IK3OIL Frequency counter built by Wayne McFee.  This is...

Scroll to top