;----------------------------------------------------------------------;
; EGGTIM4M.ASM   A 3 minute countdown timer for boiling eggs, 4 MHz osc;
; Pushbutton on RB4 starts countdown.  Displays minutes and tens of    ;
; seconds in binary on LEDs.  Zero represented by all leds lit.        ;
; A second pushbutton can be added to cycle through starting times.    ;
;----------------------------------------------------------------------;

;                            .-----------.
;                           -|RA2     RA1|-       V+ = 4.5 or 5 Volts
;                           -|RA3     RA0|-       X = 4 MHz cer. res.
;                           -|RA4    OSC1|--|X|___ gnd
;   -{r}- = 470 ohm    V+ ---|MCLR   OSC2|--|X|  
;   -|<- = LED        gnd ---|Vss     Vdd|--- V+
;            gnd ---|<--{r}--|RB0     RB7|-         
;            gnd ---|<--{r}--|RB1     RB6|-         
;            gnd ---|<--{r}--|RB2     RB5|---[PB]--- gnd       
;            gnd ---|<--{r}--|RB3     RB4|---[PB]--- gnd        
;                            '-----------'         
;                               PIC16F84      -[PB]- pushbutton


        LIST P=16F84           ;  tells which processor is used
        INCLUDE "p16f84.inc"   ;  defines various registers etc. Look
        ERRORLEVEL -224        ;  suppress annoying message from tris
        __CONFIG _PWRTE_ON & _XT_OSC & _WDT_OFF   ;  config. switches

         #DEFINE button1 PORTB, 4   ; RB4 pushbutton to ground
         #DEFINE button2 PORTB, 5   ; RB5 pushbutton to ground

;----------------------------------------------------------------------;
;            Here we set up the user defined registers                 ;
;----------------------------------------------------------------------;
           CBLOCK H'0C'         ;  first free register address is 12
               sec              ;  keeps track of seconds
               sec10            ;  keeps track of tens of seconds
               mins             ;  keeps track of minutes
               cntmsec          ;  counter used in millisecond delays
               counter          ;  general counter
           ENDC

           ORG 0              ;  start a program memory location zero

           goto main          ;  jump over the interrupt routine

;----------------------------------------------------------------------;
;           initialize the ports, set up interrupts etc:               ;
;----------------------------------------------------------------------;
init:
         movlw B'00000000'    ;  all outputs
         tris PORTA           ;  on PORT A  
         movlw B'00110000'    ;  RB4 & RB5 inputs, all others outputs
         tris PORTB           ;  on PORT B
         movlw B'00000000'    ;  port B pull-ups active
         option
         clrf sec             ;  sec = 0
         clrf sec10           ;  sec10 = 0 
         movlw D'3'           ;  and minutes = 3
         movwf mins   
         movlw D'10'          ;  initialize counter to 10
         movwf counter        
         return

;----------------------------------------------------------------------;
;          Wait for pushbutton. Start countdown when pressed.          ;
;----------------------------------------------------------------------;
waitpushbutton:
         movf mins, W         ;  display minutes on LEDs at start 
         movwf PORTB

  ; to add a pushbutton on RB5 to select starting minutes uncomment the
  ; next two instructions
  ;       btfss button2        ;  button2 pressed? skip if not
  ;       call selectstart     ;  select starting number of mins 1-16

         btfsc button1        ;  switch closed, (gives 0)?
         goto waitpushbutton  ;  not yet
                              ;  switch has been detected closed
                              ;  ( no debounce necessary )
         clrf PORTB           ;  lights out
         return

;----------------------------------------------------------------------;
;              Select the starting number of minutes 1-15              ;
;----------------------------------------------------------------------;
selectstart:
         incf mins, W         ;  increment minutes
         andlw H'0F'          ;  limit to 15
         movwf mins           ;  store in mins
waitup:
         movlw 10             ;  delay for 10 msec (debounce press)
         call nmsec
         btfss button2        ;  button 2 up yet?
         goto waitup          ;  no, keep checking
         return

;----------------------------------------------------------------------;
;                    This is the main program                          ;
;----------------------------------------------------------------------;
main:
         call init            ;  set up ports, initialize registers
         call waitpushbutton  ;  wait for button to be pressed 

loop:  
         decfsz counter, f    ;  show time every tenth time
         goto $ +2            ;  skip over next instruction
         goto showtime        ;  display time, (lasts 1 sec)
         call onesecond       ;  delay for one second
nextsec:
         decf sec, f          ;  down one sec
         movlw H'FF'          ;  check for underflow
         subwf sec, W         ;  will give zero if underflow
         btfss STATUS, Z      ;  skip if underflow
         goto loop            ;  else continue
         movlw D'9'           ;  sec to 9
         movwf sec
         decf sec10, f        ;  same for sec10 register   
         movlw H'FF'          ;  check for underflow
         subwf sec10, W       ;  gives zero if underflow
         btfss STATUS, Z      ;  skip if underflow
         goto loop            ;  else continue
         movlw D'5'           ;  sec10 to 5
         movwf sec10
         decf mins, f         ;  now minutes
         movlw H'FF'          ;  check for underflow
         subwf mins, W        ;  gives zero if underflow
         btfss STATUS, Z      ;  skip if underflow
         goto loop            ;  else continue
                              ;  come here when the countdown is over    
finished:
         movlw H'0F'          ;  turn on all leds indicating finish
         movwf PORTB
         movlw D'40'          ;  wait for 10 seconds
         movwf counter        
         call msec250         ;  1/4 sec delay
         decfsz counter, f    ;  finished?
         goto $ -2            ;  not yet
         goto main            ;  start all over again

showtime:                     ;  this lasts approximately one second
     ; usec instructions are insignificant compared to 100's of msec         
         movf mins, W         ;  display minutes
         btfsc STATUS, Z      ;  skip if not zero
         movlw H'0F'          ;  else all LEDs on
         movwf PORTB          ;  turn on LEDs
         call msec250         ;  on for 400 msec
         movlw D'150'     
         call nmsec
         clrf PORTB           ;  blank briefly
         movlw D'200'
         call nmsec
         movf sec10, W        ;  now the same with sec10
         btfsc STATUS, Z      ;  skip if not zero
         movlw H'0F'          ;  else all LEDs on
         movwf PORTB          ;  show 10's of seconds
         call msec250         ;  on for 400 msec
         movlw D'150'  
         call nmsec   
         clrf PORTB           ;  blank
         movlw D'10'          ;  reset counter to 10
         movwf counter
         goto nextsec 

;----------------------------------------------------------------------;
;                        time delay routines                           ;
;----------------------------------------------------------------------;
micro4      addlw H'FF'                ;  subtract 1 from 'W'
            btfss STATUS,Z             ;  skip when you reach zero
            goto micro4                ;  more loops
            return                     

                ;*** N millisecond delay routine ***
msec250:    movlw D'250'               ;  250 millisecond delay
nmsec:      movwf cntmsec              ;  delay for N (in W) millisec
msecloop:   movlw D'248'               ;  1 usec for load
            call micro4                ;  this instruction is 995 usec
            nop                        ;  1 usec 
            decfsz cntmsec,f           ;  1 usec, (2 if skip taken)
            goto msecloop              ;  2 usec, loop = 995+5 = 1 msec
            return 
                    
onesecond:  call msec250               ; delay for one second
            call msec250
            call msec250
            call msec250
            return

         end                           ; end of program

;  Note:   This program differs from eggtimer.asm (used a 32 kHz xtal).
;  That program has errors due to subtitle interaction of the isr and main
;  program.  The easiest fix is do not use a interrupt subroutine.