;----------------------------------------------------------------------;
; WKTIM4M.ASM  Pushbutton toggles clock on and off, time on LCD  4 MHz ;                    
;----------------------------------------------------------------------;

;  Note: must add additional time digit to run over 99 hrs.

;                            .-----------.
;                           -|RA2     RA1|----[LCD E]       
;                           -|RA3     RA0|----[LCD RS]      
;                           -|RA4    OSC1|--|X|-||--- gnd    
;                      V+ ---|MCLR   OSC2|--|X|-||--- gnd  
;                     gnd ---|Vss     Vdd|--- V+   
;                [LCD D4]----|RB0     RB7|-   X = 4.096 MHz xtal 20 pfd
;                [LCD D5]----|RB1     RB6|-   V+ = 4.5 or 5 Volts
;                [LCD D6]----|RB2     RB5|-       
;                [LCD D7]----|RB3     RB4|---[PB]--- gnd        
;                            '-----------'         
;                               PIC16F84      -[PB]- pushbutton

;     LCD pin connections:
;      1 - gnd     ground    2 - Vcc V+    3 - contrast 10K pot V+ & gnd
;      4 - RS  RA0 PIC(17)   5 - RW  gnd   6 - E   RA1 PIC(18) 
;      7 - D0   gnd      8 - D1  gnd       9 - D2 gnd        10 - D3 gnd       
;      11 - D4  RB0 (6)  12 - D5 RB1 (7)   13 - D6 RB2 (8)   14 - D7 (9)

           LIST P=16F84           ;  16F84 Runs at 4.096 MHz
           INCLUDE "p16f84.inc"
           __CONFIG _PWRTE_ON & _XT_OSC & _WDT_OFF  
           ERRORLEVEL -224        ;  supress annoying message from tris



;  Define Information
     #DEFINE RS PORTA, 0
     #DEFINE E  PORTA, 1
     #DEFINE TOGGLESW PORTA, 4

;  Macro

EStrobe MACRO                   ;  Strobe the "E" Bit
  bsf    E
  bcf    E
 ENDM

            CBLOCK     0CH
                sec             ; seconds digit
                sec10           ; 10's of second digit
                mins            ; minutes digit
                min10           ; 10's of minutes digit
                hr              ; hours digit
                hr10            ; 10's of hours digit
                highlim         ; high limit + 1 of digit
                w_temp          ; holds W during interrupt
                status_temp     ; holds STATUS during interrupt
                fsr_temp        ; holds FSR during interrupt
                Dlay            ; 8 Bit Delay Variable
                working         ; working flag 0 not working, 1 working
                ptr             ; used in displaying message
                Temp            ; a temporary variable
                bin             ; a temporary variable
                oset            ; offset of time register
                oldtime         ; holds last value of sec
                cntmsec         ; used in counting milliseconds
                isrcnt          ; used in isr to stretch time to 1 sec
               ENDC
          
            ORG 0               ; start at location 0

            goto main           ; jump over to main routine       

            ORG 4
             
            goto isr            ; jump to interrupt routine

;----------------------------------------------------------------------;
;                      High limit + 1 of digits at position W          ;
;----------------------------------------------------------------------;
sethi:
            addwf PCL, f
         dt H'A',H'6',H'A',H'6',H'A',H'A'

;----------------------------------------------------------------------;
;                    Data for message to be output                     ;
;----------------------------------------------------------------------;
shomsg                          ;  Message to Output
  addwf  PCL, f                 ;  Output the Characters
  dt     "Working Time:", 0

;----------------------------------------------------------------------;
; ISR, increments time by one second, (BCD), every 125th time through  ;
;----------------------------------------------------------------------;
isr:
            movwf w_temp             ; save W
            swapf STATUS,W           ; save status
            movwf status_temp        ; without changing flags
            swapf FSR,W              ; save FSR
            movwf fsr_temp           ; without changing flags

            decfsz isrcnt, f         ; count down to zero before incr.
            goto restore
            movlw D'125'             ; reset isr count
            movwf isrcnt
            movf working, f          ; check working flag
            btfsc STATUS, Z          ; if not zero then increment time
            goto restore             ; else get out of interrupt routine            

            movlw sec                ; point at sec register
            movwf FSR
newdigit:   incf INDF, f             ; current digit up one
            movlw sec                ; get difference, sec and FSR
            subwf FSR, W
            call sethi               ; use to get high limit + 1
            subwf INDF, W            ; reached that number yet?
            btfss STATUS, Z          ; skip over if yes
            goto restore             ; else exit isr
            clrf INDF                ; set current digit to 0
            incf FSR, f               ; point at next digit
            goto newdigit            ; no, increment the next digit
restore: 
            swapf status_temp,W      ; get original status back
            movwf STATUS             ; into status register
            swapf fsr_temp,W         ; get original fsr back
            movwf FSR                ; into status register
            swapf w_temp,f           ; old no flags trick again
            swapf w_temp,W           ; to restore W
            bcf INTCON,T0IF          ; clear the TMR0 interrupt flag
            retfie                   ; finished reset GIE

;----------------------------------------------------------------------;
;                       Initialize the ports                           ;
;----------------------------------------------------------------------;
init:
            clrf   PORTA
            clrf   PORTB

            movlw B'00010000'          ;  RA4 input, others outputs
            tris PORTA
            movlw B'00000000'          ;  all outputs on port B
            tris PORTB
            movlw B'00000011'          ;  pull-ups enabled                                    
                                       ;  prescaler assigned to TMR0
                                       ;  prescaler set to 1:16
                                       ;  rolls over each 125th second
            option                
 
            movlw 0                    ;  zero out all registers
            movwf hr10
            movwf hr
            movwf min10                
            movwf mins
            movwf sec10
            movwf sec
            clrf working               ;  working flag to zero

            movlw B'10100000'          ;  GIE set T0IE set, T0IF cleared
            movwf INTCON
   return

;----------------------------------------------------------------------;
;                 Initialize the LCD                                   ;
;----------------------------------------------------------------------;
initlcd:
  movlw D'40'
  call   nmsec                  ;  Wait 40 msecs before Reset
  bcf    RS                     ;  send an 8 bit instruction
  movlw  0x03                   ;  Reset Command
  call   NybbleOut              ;  Send the Nybble
  call   Dlay5                  ;  Wait 5 msecs before Sending Again
  EStrobe
  call Dlay160                  ;  Wait 160 usecs before Sending 2nd Time
  EStrobe
  call Dlay160                  ;  Wait 160 usecs before Sending 3rd Time
  bcf    RS                     ;  send an 8 bit instruction
  movlw  0x02                   ;  Set 4 Bit Mode
  call   NybbleOut              
  call Dlay160
  movlw  0x028                  ;  4 bit, 2 Line, 5x7 font
  call   SendINS
  movlw  0x010                  ;  display shift off
  call   SendINS
  movlw  0x001                  ;  Clear the Display RAM
  call   SendINS
  call   Dlay5                  ;  Note, Can take up to 4.1 msecs
  movlw  0x006                  ;  increment cursor
  call   SendINS
  movlw  0x00C                  ;  display on cursor off
  call   SendINS
  return

;----------------------------------------------------------------------;
;              Send the character in W out to the LCD                  ;
;----------------------------------------------------------------------;
SendASCII
  addlw '0'                     ;  Send nbr as ASCII character
SendCHAR                        ;  Send the Character to the LCD
  movwf  Temp                   ;  Save the Temporary Value
  swapf  Temp, w                ;  Send the High Nybble
  bsf    RS                     ;  RS = 1
  call   NybbleOut
  movf   Temp, w                ;  Send the Low Nybble
  bsf    RS
  call   NybbleOut
  return

;----------------------------------------------------------------------;
;              Send an instruction in W out to the LCD                 ;
;----------------------------------------------------------------------;
SendINS                         ;  Send the Instruction to the LCD
  movwf  Temp                   ;  Save the Temporary Value
  swapf  Temp, w                ;  Send the High Nybble
  bcf    RS                     ;  RS = 0
  call   NybbleOut
  movf   Temp, w                ;  Send the Low Nybble
  bcf    RS
  call   NybbleOut
  return

;----------------------------------------------------------------------;
;              Send the nibble in W out to the LCD                     ;
;----------------------------------------------------------------------;
NybbleOut                       ;  Send a Nybble to the LCD
  movwf  PORTB                            
  EStrobe                       ;  Strobe out the LCD Data
  call Dlay160                  ;  delay for 160 usec
  return

;----------------------------------------------------------------------;
;                   Output the message on the LCD                      ;
;----------------------------------------------------------------------;
OutMessage:
  movwf  FSR                    ;  Point at first letter
OutLoop:
  movf   FSR, w                 ;  Get pointer into W
  incf   FSR, f                 ;  Set up for next letter
  call   shomsg                 ;  Get character to output
  iorlw  0                      ;  At the End of the Message?
  btfsc  STATUS, Z              ;  Skip if not at end
  return                        ;  Yes - Equal to Zero
  call   SendCHAR               ;  Output the ASCII Character
  goto   OutLoop                ;  Get the next character


;----------------------------------------------------------------------;
;                Wait until button is released                         ;
;----------------------------------------------------------------------;
waitup:    
            btfss TOGGLESW           ; test toggle switch
            goto $ -1                ; ck again if pressed
            movlw 20                 ; wait 20 msec for debounce
            call nmsec              
            btfss TOGGLESW           ; check again, still up?
            goto $ -4                ; no start over
            return                   ; yes, finished

;----------------------------------------------------------------------;
;                        time delay routines                           ;
;----------------------------------------------------------------------;

Dlay160:    movlw D'41'                ;  delay about 160 usec
micro4      addlw H'FF'                ;  subtract 1 from 'W'
            btfss STATUS,Z             ;  skip when you reach zero
            goto micro4                ;  more loops
            return                     

Dlay5:      movlw 5                    ;  delay for 5 milliseconds
            goto $ + 2
msec250:    movlw D'250'               ;  delay for 250 milliseconds
                ;*** N millisecond delay routine ***
nmsec:      movwf cntmsec              ; delay for N (in W) millisec
msecloop:   movlw D'254'               ; load takes .9765625 microsec
            call micro4                ; by itself CALL takes ...
                                       ; 2 + 253 X 4 + 3 + 2 = 1019 
                                       ; 1019 * .977 = 995 microsec
            nop                        ; .98 microsec 
            decfsz cntmsec, f          ; .98 skip not taken, else 1.95
            goto msecloop              ; 1.95 here: total ~1000 / loop
            return                     ; final time through ~999 to here
                                       ; overhead in and out ignored

;----------------------------------------------------------------------;
;                       Display the Time                               ;
;----------------------------------------------------------------------;
DispTime
              MOVLW H'C0'       ;  position at beginning of second line
              CALL SendINS
              MOVF hr10, W      ;  tens of hours
              CALL SendASCII
              MOVF hr, W        ;  hours
              CALL SendASCII
              MOVLW ":"         
              CALL SendCHAR
              MOVF min10, W     ;  tens of minutes
              CALL SendASCII
              MOVF mins, W      ;  minutes
              CALL SendASCII
              MOVLW ":"
              CALL SendCHAR
              MOVF sec10, W     ;  tens of seconds
              CALL SendASCII
              MOVF sec, W       ;  seconds
              CALL SendASCII
              RETURN
 
;----------------------------------------------------------------------;
;                            Toggle Work Flag                          ;
;----------------------------------------------------------------------;
togglewk:
            movf working, f   ; set zero flag if zero
            btfss STATUS, Z   ; skip if working is zero
            goto turnoff
            incf working, f   ; set working to 1
            call waitup       ; wait for button release
            return
turnoff:
            clrf working      ; working set to zero
            call waitup       ; wait for button to be released
            return

;----------------------------------------------------------------------;
;                           The Main routine                           ;
;----------------------------------------------------------------------;
main:
       call init               ; initialize ports, set up timer
       call initlcd            ; initialize the LCD
       movlw 0                 ; display 'Working Time:'
       call OutMessage
ckbutton:                      ; check for a press of the TOGGLE button
       btfss TOGGLESW          ; skip if not pressed
       call togglewk           ; else change the mode of timer
       movf oldtime, W         ; is oldtime the same as sec?
       subwf sec, W
       btfsc STATUS, Z         ; if not, skip over next instruction
       goto ckbutton           ; else continue checking
       call DispTime           ; sec has changed, display the time
       movf sec, W             ; make sec and oldsec the same
       movwf oldtime
       goto ckbutton           ; and continue checking

       end 

; Note:  You could use 4 mhz crystal and change the isr count to 
; 125 * 0.98 = 122 but timing would not be exact.  You could also use 
; a 4 mhz ceramic resonator, just to see if the program works