; ALARMH.ASM: Contains the complete clock and alarm software.

; Register use: Only Bank 0 registers will be used in main code. It is
; generally wise to leave Bank 1 registers unused, for use by interrupt service
; routines.
;   R0: General purpose register, may be altered by routines.
;   R1: Clock tick counter, decrements from 50 or 60 to 0.
;   R2: Character to be written to the top display.
;   R3: Character to be written to the display 2nd. from top.
;   R4: Character to be written to the display 2nd. from bottom.
;   R5: Character to be written to the bottom display.
;   R6: Clock Recovery State Machine current state.
;   R7: This is used as a bit field:
;       R7.0: "previous !INT state" flag. Stores the previous state of !INT, so
;         it can be compared to the current state of !INT.
;       R7.1: Set to 1 if a rising edge was found on !INT in the current Clock
;         Recovery State Machine cycle.
;       R7.2: a copy of R7.1, made early in the Clock Recovery State Machine
;         cycle. This is logic 1 if a rising edge was found on !INT in the
;         previous Clock Recovery State Machine cycle.
;       R7.3: Seconds Increment Flag. Set to 1 if the Clock Tick Counter (R1)
;         has decremented to zero; set to 0 otherwise.
;         - It is set to 1 by the Clock Recovery State Machine.
;         - It is set to 0 in Main State Machine State 1 (lSt1), which uses it.
;       R7.4: Alarm On flag. When it is logic 1, the alarm is on.
;       R7.5: Alarm Silenced flag. It is 0, unless the alarm has been silenced
;         by pushing the button on T0 while the alarm is ringing.

; Flags used:
;   F0: Not used.
;   F1: Not used, useful for passing information to interrupt service routines.


; DIP switches on port P1:
;   SW1 on P1.0: Used as "Clock Set". To set the clock, set to On (logic 0).
;   SW2 on P1.1: -
;   SW3 on P1.2:  | Used by subroutine SetClock to set the clock.
;   SW4 on P1.3: -  On is logic 0, off is logic 1:
;                   SW4  | SW3  | SW2  |
;                   P1.3 | P1.2 | P1.1 | Function 
;                   --------------------------------------------------
;                   On   | On   | On   | Reset Seconds
;                   On   | On   | Off  | Set Minute
;                   On   | Off  | On   | Set Hour
;                   On   | Off  | Off  | Set Date
;                   Off  | On   | On   | Set Month
;                   Off  | On   | Off  | Set Year
;                   Off  | Off  | On   | Set Alarm Hour
;                   Off  | Off  | Off  | Set Alarm Minute
;    SW5 on P1.4: On (logic 0): 12h clock. Off (logic 1): 24h clock.

; DIP switch SW6 on T1:
;   SW6 switches between 50 and 60Hz: On (logic 0): 50Hz. Off (logic 1): 60Hz.

; External pushbutton on T0:
; - When in clock set mode (P1.0==0), push the pushbutton (making T0 logic 0),
;   to advance the counter being set.
; - When in normal mode (P1.0==1), holding the pushbutton pressed for 1 second
;   will toggle the alarm on and off. Also, pushing the pushbutton while the
;   alarm is sounding will silence the alarm.

; Alarm output:
;   P1.5 is the alarm output. When pulled logic low, the alarm sounds.


; --------------------------------- DEFINITIONS -------------------------------


; Birthday:
.equ eBrthD	#0x16	; 22nd. of
.equ eBrthM	#0x04	; April
.equ eBrthY	#0xFA	; 2's complement of last two digits of birth year (06).

; 7-segment display definitions:
;    --A--
;   |     |
;   F     B
;   |     |
;    --G--
;   |     |
;   E     C
;   |     |
;    --D--
;			  ABCDEFG0
.equ eBlank	#0x00	; 00000000
.equ eNum0	#0xFC	; 11111100
.equ eNum1	#0x60	; 01100000
.equ eNum2	#0xDA	; 11011010
.equ eNum3	#0xF2	; 11110010
.equ eNum4	#0x66	; 01100110
.equ eNum5	#0xB6	; 10110110
.equ eNum6	#0xBE	; 10111110
.equ eNum7	#0xE0	; 11100000
.equ eNum8	#0xFE	; 11111110
.equ eNum9	#0xF6	; 11110110
.equ eLetA	#0xEE	; 11101110
.equ eLetB	#0x3E	; 00111110
.equ eLetD	#0x7A	; 01111010
.equ eLetHC	#0x6E	; 01101110
.equ eLetHL	#0x2E	; 00101110
.equ eLetL	#0x1C	; 00011100
.equ eLetI	#0x08	; 00001000
.equ eLetP	#0xCE	; 11001110
.equ eLetR	#0x0A	; 00001010
.equ eLetT	#0x1E	; 00011110
.equ eLetY	#0x76	; 01110110

; 7-segment display definitions for Clock Recovery State Machine display:
;			  ABCDEFG0
.equ eCRStU	#0x7C	; 01111100: U, displayed while bSync has not been set.
.equ eCRStL	#0x1C	; 00011100: L, displayed when !INT input is stuck low.
.equ eCRStH	#0x6E	; 01101110: H, displayed when !INT input is stuck high.
.equ eCREgE	#0x80	; 10000000: Displayed for early rising edge on !INT.
.equ eCREgC	#0x02	; 00000010: Displayed for on-time rising edge on !INT.
.equ eCREgL	#0x10	; 00010000: Displayed for late rising edge on !INT.

; RAM variables
.equ bSec	#0x20	; Seconds counter.
.equ bMin	#0x21	; Minutes counter.
.equ bHour	#0x22	; Hour counter.
.equ bDate	#0x23	; Date counter.
.equ bMonth	#0x24	; Month counter.
.equ bYear	#0x25	; Year counter.
.equ bAlmHr	#0x26	; Alarm hour.
.equ bAlmMn	#0x27	; Alarm minute.

.equ bSync	#0x28	; Character to show in order to display the Clock
                        ; Recovery State Machine state where !INT input
			; rising edge was found.
.equ bStMSt	#0x29	; Main State Machine State Counter.
.equ bDbnce	#0x2A	; Stores the value of the Clock Tick Counter (R1) the
			; moment the button on T0 is pressed.
.equ bAlStM	#0x2B	; Alarm State Machine Counter.


; -------------------------- RESET / INTERRUPT VECTORS ------------------------


.org 0x0000			; RESET vector.
  JMP lMain

.org 0x0003			; External interrupt vector.
  RETR

.org 0x0007			; Timer interrupt vector.
  RETR


; --------------------------- MAIN PROGRAM ROUTINE ----------------------------


lMain:
; Main program routine.
  ; Initialization:
  ; Select Bank 0 registers:
    SEL RB0

  ; Initialize RAM variables:
    CLR A
    MOV R0, bSec
    MOV @R0, A			; Initialize bSec=0.
    MOV R0, bMin
    MOV @R0, A			; Initialize bMin=0.
    MOV R0, bAlmMn
    MOV @R0, A			; Initialize bAlmMn=0.
    MOV R0, bHour
    MOV @R0, A			; Initialize bHour=0.
    MOV R0, bAlmHr
    MOV @R0, A			; Initialize bAlmHr=0.
    INC A
    MOV R0, bDate
    MOV @R0, A			; Initialize bDate=1.
    MOV R0, bMonth
    MOV @R0, A			; Initialize bMonth=1.
    MOV R0, bYear
    MOV @R0, #0x0F		; Initialize bYear=15.
      
    MOV R0, bSync
    MOV @R0, eCRStU		; Initialize bSync to "Undefined".

    MOV R0, bStMSt		; Reset Main State Machine
    MOV @R0, #0x07		; (start at 7, to be later incremented to 0).
      
    CLR A
    MOV R0, bDbnce
    MOV @R0, A			; Initialize bDbnce=0 (arbitrary value).
    MOV R0, bAlStM
    MOV @R0, A			; Initialize Alarm on-off State Machine Counter
				; to 0.
  ; Initialize flags:
  ; The Clock Recovery State Machine flags R7.0 to R7.3 are set to 0.
  ; The Alarm flags R7.4 and R7.5 are set to 0.
    MOV R7, #0x00
      
  ; Clock Recovery State Machine initialization:
    ANL P1, #0xBF		; Make P1.6 low to disable SN75176's output.
    MOV R6, #0x00		; Reset the Clock Recovery State Machine.
  ; Initialise the Clock Tick Counter (R1):
    MOV R1, #0x3C		; Start with R1=60 (for 60Hz).
    JT1 l60HzC			; If T1 is high, leave R1 at 60.
    MOV R1, #0x32		; T1 was low, so start with R1=50 (for 50Hz).
  l60HzC:
  

  ; Start the State Machine Timer, which runs the State Machines.
  ; The crystal frequency is 4.608MHz, so the timer tick is
  ; 1/4.608MHz*3*5*32=104usec. If I set the timer to overflow every 16 ticks,
  ; then I will have an overflow every about 1,67msec. This is the period I
  ; found the original software used for display column multiplexing.
  ; Note that between each timer overflow, 32*16=512 instruction cycles can
  ; can be processed.
    MOV A, #0xF0		; (256-16, for a timer count of 16).
    MOV T, A
    STRT T

  lELoop:
  ; Event loop:
  ; Has the State Machine Timer timed out?
    JTF lStChg			; Yes, so enter Main State Machine.
    JMP lELoop			; No, so do noting.
      
  lStChg:
  ; The State Machine Timer has timed out.
  ; Restart the timer:
    MOV A, #0xF0
    MOV T, A

    
; ----------------- CLOCK RECOVERY STATE MACHINE ROUTINES ---------------------
 
    
; The Clock Recovery State Machine has 11 states (60Hz) or 13 states (50Hz).
; This is clocked by the State Machine Timer, thus separating each state by
; 1,67msec.
; Within the 11 or 13 states of this state machine, the following are active:
; With t=1/60Hz (if T1==0) or t=1/50Hz (if T1==1),
; - lCREgE would catch an early rising edge on !INT (i.e. at less than t).
; - lCREgC would catch a rising edge on !INT occurring at time==t.
; - lCREgL would catch a late rising edge on !INT (i.e. at more than t).
; At state lCRPrv, the State Machine starts looking for a logic 0 on the
; !INT input. When this is found (if not in lCRPrv then in lCREgE or
; lCREgC), it sets flag R7.0 to 0.
; The State Machine then starts looking for a logic 1 on the !INT input.
; When this is found, a rising edge on the !INT input has been detected,
; so a Clock Tick is provided and the State Machine is reset to state 0.
; If no rising edge on !INT is detected until the final state (lCREgL),
; the State Machine assumes we have lost mains power. It sets flag R7.1
; and resets the State Machine to State 0. In the new Clock Recovery State
; Machine cycle, state lCREgC checks whether R7.1 had been set. In this
; case, it will provide a "fill-in" Clock Tick to compensate for the 
; earlier missing rising edge on !INT.
; Variable bSync is set to the state in which the rising edge was found.
    MOV A, R6			; Increment the Clock Recovery State Counter
    INC A			; (R6) to the next state.
    ANL A, #0x0F		; If R6 exceeds 15, reset it to 0. Note that
				; this should never happen, it is just put
				; here as a precautionary measure.
    MOV R6, A

    JT1 l60HzA			; If T1 is high, we are running at 60Hz.
      
  ; T1 was low, so use state machine for 50Hz.
    ADD A, #lSM50Hz		; 50Hz Cl. Rec. St. Machine jump table origin.
    JMPP @A			; Select the appropriate routine.
  lSM50Hz:			; 50Hz Clock Recovery State Machine jump table:
    .db #lCREnd			;   State  0.  0,00msec. Unreachable.
    .db #lCREnd			;   State  1.  1,67msec. Do nothing.
    .db #lCREnd			;   State  2.  3,33msec. Do nothing.
    .db #lCREnd			;   State  3.  5,00msec. Do nothing.
    .db #lCREnd			;   State  4.  6,67msec. Do nothing.
    .db #lCREnd			;   State  5.  8,33msec. Do nothing.
    .db #lCREnd			;   State  6. 10,00msec. Do nothing.
    .db #lCREnd			;   State  7. 11,67msec. Do nothing.
    .db #lCREnd			;   State  8. 13,33msec. Do nothing.
    .db #lCREnd			;   State  9. 15,00msec. Do nothing.
    .db #lCRPrv			;   State 10. 16,67msec. Store previous state.
    .db #lCREgE			;   State 11. 18,33msec. Look for early edge.
    .db #lCREgC			;   State 12. 20,00msec. Look for correct edge.
    .db #lCREgL			;   State 13. 21,67msec. Look for late edge.
    .db #lCREnd			;   State 14. 23,33msec. Unreachable.
    .db #lCREnd			;   State 15. 25,00msec. Unreachable.
    
  l60HzA:
  ; T1 was high, so use state machine for 60Hz.
    ADD A, #lSM60Hz		; 60Hz Cl. Rec. St. Machine jump table origin.
    JMPP @A			; Select the appropriate routine.
  lSM60Hz:			; 60Hz Clock Recovery State Machine jump table:
    .db #lCREnd			;   State  0.  0,00msec. Unreachable.
    .db #lCREnd			;   State  1.  1,67msec. Do nothing.
    .db #lCREnd			;   State  2.  3,33msec. Do nothing.
    .db #lCREnd			;   State  3.  5,00msec. Do nothing.
    .db #lCREnd			;   State  4.  6,67msec. Do nothing.
    .db #lCREnd			;   State  5.  8,33msec. Do nothing.
    .db #lCREnd			;   State  6. 10,00msec. Do nothing.
    .db #lCREnd			;   State  7. 11,67msec. Do nothing.
    .db #lCRPrv			;   State  8. 13,33msec. Store previous state.
    .db #lCREgE			;   State  9. 15,00msec. Look for early edge.
    .db #lCREgC			;   State 10. 16,67msec. Look for correct edge.
    .db #lCREgL			;   State 11. 18,33msec. Look for late edge.
    .db #lCREnd			;   State 12. 20,00msec. Unreachable.
    .db #lCREnd			;   State 13. 21,67msec. Unreachable.
    .db #lCREnd			;   State 14. 23,33msec. Unreachable.
    .db #lCREnd			;   State 15. 25,00msec. Unreachable.

  lCRPrv:
  ; Set the "previous !INT state" flag (R7.0) to the current state of !INT,
  ; for use in later comparisons.
    MOV A, R7			; Get R7.
    ANL A, #0xFE		; Start by setting Acc. bit 0 to 0.
    JNI lINT0A			; If !INT is logic 0, leave Acc. bit 0 at 0.
    ORL A, #0x01		; !INT was logic 1, so set Acc. bit 0 to 1.
  lINT0A:
  ; Note: The result (which is to go in R7.0) has been left in the Accumulator.
  
  ; Store a copy of R7.1 in R7.2, because R7.1 is set to 0 below, while its
  ; value is needed in lCREgC.
    ORL A, #0x04		; Start by setting Acc. bit 2 to 1.
    JB1 l71E1A			; If R7.1==1, leave Acc. bit 2 at 1.
    ANL A, #0xFB		; R7.1 was 0, so set Acc. bit 2 to 0.
  l71E1A:
  ; Note: The result (which is to go in R7.2) has been left in the Accumulator.
      
  ; Set R7.1 to 0 (since no rising edge on !INT has been found yet)!
    ANL A, #0xFD		; Set Acc. bit 1 to 0.
    MOV R7, A			; Store R7.  
    JMP lCREnd			; Finished.

  lCREgE:
  ; Look for early rising edge on !INT0.
  ; - If the previous !INT state (R7.0) was 1, this means !INT had not fallen
  ;   to logic 0. Continue searching for a logic 0 on !INT (really, just
  ;   store the value of !INT in R7.0).
  ; - If the previous value of !INT (i.e. R7.0) is 0, then
  ;   - if the current value of !INT is 0, do nothing (since nothing has
  ;     changed).
  ;   - if the current value of !INT is 1, we have a rising edge on !INT.
  ;     In this case,
  ;     - A Clock Tick is provided,
  ;     - R7.1 is set to 1 to inform the next Clock Recovery State Machine
  ;       cycle that a Clock Tick has been provided,
  ;     - bSync is set to "Early"
  ;     - the Clock Recovery State Machine is reset to state 0.
    MOV A, R7			; Get R7.
    JB0 l70E1A			; If R7.0==1 go and set R7.0 to !INT. 

    JNI lCREnd			; Is !INT still logic 0? If so, do nothing.
      
				; The previous !INT state (R7.0) was logic 0
				; and the current !INT state is logic 1, so
				; we have a rising edge on !INT:
    CALL lClkTk			;   Provide a Clock Tick,
      
    MOV A, R7
    ORL A, #0x02		;   Set R7.1 to 1,
    MOV R7, A
      
    MOV R0, bSync		;   Set bSync to "Early",
    MOV @R0, eCREgE
      
    MOV R6, #0x00		;   Reset the Clock Recovery State Machine,
    JMP lCREnd			;   Finished.
      
  l70E1A:			; Set R7.0 to !INT. Note: Acc. already is =R7:
    ANL A, #0xFE		;   Start by setting Acc. bit 0 to 0.
    JNI lINT0C			;   If !INT is logic 0, leave Acc. bit 0 at 0.
    ORL A, #0x01		;   !INT was logic 1, so set Acc. bit 0 to 1.    
  lINT0C:
    MOV R7, A			;   Store R7.
    JMP lCREnd      		;   Finished.

  lCREgC:
  ; Look for "correct" rising edge on !INT0 (i.e. arriving at the expected
  ; time). The routine is the same as for lCREgE.
    MOV A, R7			; Get R7.
    JB0 l70E1B			; If R7.0==1 go and set R7.0 to !INT. 

    JNI lINT0D			; Is !INT still logic 0? If so, do nothing.
      
				; The previous !INT state (R7.0) was logic 0
				; and the current !INT state is logic 1, so
				; we have a rising edge on !INT:
    CALL lClkTk			;   Provide a Clock Tick,
      
    MOV A, R7
    ORL A, #0x02		;   Set R7.1 to 1,
    MOV R7, A
      
    MOV R0, bSync		;   Set bSync to "On-time",
    MOV @R0, eCREgC
      
    MOV R6, #0x00		;   Reset the Clock Recovery State Machine,
    JMP lINT0D			;   Finished.
      
  l70E1B:			; Set R7.0 to !INT. Note: Acc. already is =R7:
    ANL A, #0xFE		;   Start by setting Acc. bit 0 to 0.
    JNI lINT0E			;   If !INT is logic 0, leave Acc. bit 0 at 0.
    ORL A, #0x01		;   !INT was logic 1, so set Acc. bit 0 to 1.    
  lINT0E:
    MOV R7, A			;   Store R7.
				;   Finished.
  lINT0D:
  ; Additionally, if the previous Clock Recovery State Machine cycle had not
  ; provided a Clock Tick (this is signalled by R7.2==0), then provide a
  ; "fill-in" Clock tick to compensate, and forcibly reset the Clock Recovery
  ; State Machine:
    MOV A, R7			; Get R7.
    JB2 lCREnd			; If R7.2==1, the previous Clock Recovery State
				; Machine cycle had provided a Clock Tick, so
				; do nothing.
    CALL lClkTk			; Else, provide a "fill-in" Clock Tick,
    MOV R6, #0x00		; Reset the Clock Recovery State Machine,
    JMP lCREnd      		; Finished.
      
  lCREgL:
  ; Look for late rising edge on !INT0.
  ; - If the previous !INT state (R7.0) was 1, this means !INT had not fallen
  ;   to logic 0. It is too late to look for a logic 0 on !INT now; assume
  ;   that !INT is stuck high.
  ; - If the previous value of !INT (i.e. R7.0) is 0, then
  ;   - if the current value of !INT is 0, assume !INT is stuck low.
  ;   - if the current value of !INT is 1, we have a rising edge on !INT.
  ;     In this case,
  ;     - A Clock Tick is provided,
  ;     - R7.1 is set to 1 to inform the next Clock Recovery State Machine
  ;       cycle that a Clock Tick has been provided,
  ;     - bSync is set "Late",
  ;     - the Clock Recovery State Machine is reset to state 0.
    MOV A, R7			; Get R7.
    JB0 l70E1C			; If R7.0==1 go and set bSync to "Stuck high".

    JNI lINT0F			; Is !INT still logic 0? If so, go and set
				; bSync to "Stuck low".
      
				; The previous !INT state (R7.0) was logic 0
				; and the current !INT state is logic 1, so
				; we have a rising edge on !INT:
    CALL lClkTk			;   Provide a Clock Tick,
      
    MOV A, R7
    ORL A, #0x02		;   Set R7.1 to 1,
    MOV R7, A
      
    MOV R0, bSync		;   Set bSync to "Late".
    MOV @R0, eCREgL
    JMP lSkipA
      
  l70E1C:			; !INT stuck high: Set bSync to "Stuck high".
    MOV R0, bSync
    MOV @R0, eCRStH
    JMP lSkipA

  lINT0F:			; !INT stuck low: Set bSync to "Stuck low".
    MOV R0, bSync
    MOV @R0, eCRStL
      
  lSkipA:
    MOV R6, #0x00		; Reset the Clock Recovery State Machine.
    JMP lCREnd			; Finished.

  lCREnd:    			; Clock Recovery State Machine end.

  ; Jump to Main State Machine:
    JMP lMStM			; A JMP instruction is used because of the
				; change in memory page.


; ---------------------- MAIN STATE MACHINE ROUTINES --------------------------


.org 0x0100			; Helps avoid page break within routine.


lMStM:
; The Main State Machine has eight states, which it cycles through using
; memory variable bStMSt as the State Counter.
; The states are:
; - 0: Writes leftmost display column segments.
; - 1: Strobes P2.7 low to switch on leftmost display column,
;      then deals with seconds increment.
; - 2: Writes 2nd. from left display column segments.
; - 3: Strobes P2.6 low to switch on 2nd. from left display column,
;      then runs the T0 button Alarm on-off State Machine and the alarm
;      engine.
; - 4: Writes 2nd. from right display column segments.
; - 5: Strobes P2.5 low to switch on 2nd. from right display column.
; - 6: Writes rightmost display column.
; - 7: Strobes P2.4 low to switch on rightmost display column.
; The Main State Machine then returns to 0.
    MOV R0, bStMSt		; Increment the Main State Machine State
    MOV A, @R0			; Counter to the next State.
    INC A
    ANL A, #0x07		; If State==8, then make State=0.
    MOV @R0, A
  ; Jump to the routine for the State:
    ADD A, #lStJmp - 0x0100	; Main State Machine jump table origin.
    JMPP @A			; Select the appropriate routine.
  lStJmp:			; Main State Machine jump table:
    .db #lStLc0 - 0x0100
    .db #lStLc1 - 0x0100
    .db #lStLc2 - 0x0100
    .db #lStLc3 - 0x0100
    .db #lStLc4 - 0x0100
    .db #lStLc5 - 0x0100
    .db #lStLc6 - 0x0100
    .db #lStLc7 - 0x0100
  ; "Real" jumps are necessary due to the possibility of a change
  ; in memory page:
  lStLc0:
    JMP lSt0
  lStLc1:
    JMP lSt1
  lStLc2:
    JMP lSt2
  lStLc3:
    JMP lSt3
  lStLc4:
    JMP lSt4
  lStLc5:
    JMP lSt5
  lStLc6:
    JMP lSt6
  lStLc7:
    JMP lSt7
      

lSt0:
; State 0: Write segments for leftmost display column.
  ; P2.7->P2.4 all logic high:
    ORL P2, #0xF0		; Upper 4 bits.

  ; Write leftmost display column:
    CALL lDWhat			; Find out what is to be displayed.
    ADD A, #lS0Jmp - 0x0100	; State 0 jump table origin.
    JMPP @A			; Select the appropriate action.
  lS0Jmp:
    .db #lS0Tme	- 0x0100
    .db #lS0Alm - 0x0100
    .db #lS0HBr - 0x0100
  
  lS0Tme:
  ; Display the time (leftmost column).
    MOV R0, bHour		; Get hour.
    IN A, P1			; Get P1 state.
    JB4 l24hA			; Is P1.4 logic 1 (SW5 Off, 24h clock)?
    MOV A, @R0			; - No (12h clock). Get hour,
    CALL l24H12			;   Convert hours from 24h to 12h,
    JMP l24hB			;   Done.
  l24hA:
    MOV A, @R0			; - Yes (24h clock). Get hour.
  l24hB:    
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R2, A			; Leftmost top character.
    
    MOV R0, bSec		; Get second.
    MOV A, @R0
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R3, A			; Leftmost 2nd. from top character.
    
    MOV R0, bDate		; Get date.
    MOV A, @R0
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R4, A			; Leftmost 2nd. from bottom character.
    
    MOV R5, eNum2		; Leftmost bottom character ("2").
    JMP lS0Fin			; Finished.

  lS0Alm:
  ; Display the alarm time (leftmost column).
    MOV R0, bAlmHr		; Get alarm hour.
    IN A, P1			; Get P1 state.
    JB4 l24hI			; Is P1.4 logic 1 (SW5 Off, 24h clock)?
    MOV A, @R0			; - No (12h clock). Get alarm hour,
    CALL l24H12			;   Convert hours from 24h to 12h,
    JMP l24hF			;   Done.
  l24hI:
    MOV A, @R0			; - Yes (24h clock). Get alarm hour.
  l24hF:    
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R2, A			; Leftmost top character.
    
    MOV R3, eBlank		; Leftmost 2nd. from top character.
    MOV R4, eBlank		; Leftmost 2nd. from bottom character.
    MOV R5, eBlank		; Leftmost bottom character.
    JMP lS0Fin			; Finished.

  lS0HBr:
  ; Display Happy Birthday (leftmost column).
    MOV R2, eLetHC		; Leftmost top character.
    MOV R3, eLetY		; Leftmost 2nd. from top character.
    MOV R4, eLetB		; Leftmost 2nd. from bottom character.
    MOV R5, eLetHL		; Leftmost bottom character.
    JMP lS0Fin			; Finished.

  lS0Fin:
  ; Write leftmost column.
    CALL lsWCol			; Write column.
    JMP lELoop			; Return.


lSt1:
; State 1:
  ; Strobe P2.7 logic low to switch on leftmost display column:
    ANL P2, #0x7F

  ; Deal with the seconds increment flag:
    MOV A, R7			; Get R7.
    JB3 lSecA			; Is R7.3==1 (i.e. Has one second elapsed)?
    JMP lELoop			; - No, so do nothing (Return).

  lSecA:
  ; One second has elapsed.
    IN A, P1			; Get P1 state.
    JB0 lNCStA			; Is P1.0 (Not Clock Set)==1?
				; - No, so check T0 (the pushbutton).
    JT0 lEndA			;     If it is logic 1 (unpressed), do nothing.
    CALL lStClk			;     If it is logic 0 (pressed), set clock.
    JMP lEndA

  lNCStA:    
    CALL lIncTm			; - Yes, so increment time.

  lEndA:
    MOV A, R7			
    ANL A, #0xF7		; Clear the Seconds Increment Flag (R7.3).
    MOV R7, A
    JMP lELoop			; Return.


lSt2:
; State 2: Write segments for 2nd. from left display column.
  ; P2.7->P2.4 all logic high:
    ORL P2, #0xF0		; Upper 4 bits.
      
  ; Write 2nd. from left display column:
    CALL lDWhat			; Find out what is to be displayed.
    ADD A, #lS2Jmp - 0x0100	; State 2 jump table origin.
    JMPP @A			; Select the appropriate action.
  lS2Jmp:
    .db #lS2Tme	- 0x0100
    .db #lS2Alm - 0x0100
    .db #lS2HBr - 0x0100

  lS2Tme:
  ; Display the time (2nd. from left column).
    MOV R0, bHour		; Get hour.
    IN A, P1			; Get P1 state.
    JB4 l24hC			; Is P1.4 logic 1 (SW5 Off, 24h clock)?
    MOV A, @R0			; - No (12h clock). Get hour,
    CALL l24H12			;   Convert hours from 24h to 12h,
    JMP l24hD			;   Done.
  l24hC:
    MOV A, @R0			; - Yes (24h clock). Get hour.
  l24hD:
    CALL l7SegU			; Convert to seven segment (units).
    MOV R2, A			; 2nd. from left, top character.
    
    MOV R0, bSec		; Get second.
    MOV A, @R0
    CALL l7SegU			; Convert to seven segment (units).
    MOV R3, A			; 2nd. from left, 2nd. from top character.
  
    MOV R0, bDate		; Get date.
    MOV A, @R0
    CALL l7SegU			; Convert to seven segment (units).
    MOV R4, A			; 2nd. from left, 2nd. from bottom character.
    
    MOV R5, eNum0		; 2nd. from left, bottom character ("0").
    JMP lS2Fin			; Finished.

  lS2Alm:
  ; Display alarm time (2nd. from left column).
    MOV R0, bAlmHr		; Get alarm hour.
    IN A, P1			; Get P1 state.
    JB4 l24hJ			; Is P1.4 logic 1 (SW5 Off, 24h clock)?
    MOV A, @R0			; - No (12h clock). Get alarm hour,
    CALL l24H12			;   Convert hours from 24h to 12h,
    JMP l24hG			;   Done.
  l24hJ:
    MOV A, @R0			; - Yes (24h clock). Get alarm hour.
  l24hG:    
    CALL l7SegU			; Convert to seven segment (units).
    MOV R2, A			; 2nd. from left, top character.
   
    MOV R3, eBlank		; 2nd. from left, 2nd. from top character.
    MOV R4, eBlank		; 2nd. from left, 2nd. from bottom character.
    MOV R5, eBlank		; 2nd. from left, bottom character.
    JMP lS2Fin			; Finished.

  lS2HBr:
  ; Display Happy Birthday (2nd. from left column).
    MOV R2, eLetA		; 2nd. from left, top character.
    MOV R3, eBlank		; 2nd. from left, 2nd. from top character.
    MOV R4, eLetI		; 2nd. from left, 2nd. from bottom character.
    MOV R5, eLetD		; 2nd. from left, bottom character.
    JMP lS2Fin			; Finished.

  lS2Fin:
  ; Write 2nd. from left column.
    CALL lsWCol			; Write column.    
    JMP lELoop			; Return.


.org 0x0200			; Helps avoid page break within routine.


lSt3:
; State 3:
  ; Strobe P2.6 logic low to switch on 2nd. from left display column:
    ANL P2, #0xBF

  ; Alarm on-off State Machine:
  ; A three-state State Machine is used to determine whether the button on T0
  ; has been held pressed for 1 second, to toggle Alarm on-off.
  ; - In State 0, the State Machine checks whether the button has been pressed.
  ;   If it has, the Clock Tick Counter (R1) is stored in bDbnce and we move to
  ;   State 1.
  ; - In State 1, the State Machine waits until the Clock Tick Counter advances
  ;   beyond its current value (==bDbnce).
  ; - In State 2, the State Machine knows that the Clock Tick Counter has
  ;   advanced beyond bDbnce. The Clock Tick Counter cycles every 1 second, so
  ;   when the Clock Tick Counter is again ==bDbnce, we know that 1 second has
  ;   elapsed. If the button is still pressed, then Alarm on-off is toggled.
    MOV R0, bAlStM		; Get the Alarm on-off State Machine counter. 
    MOV A, @R0
    ADD A, #lAlStJ - 0x0200	; Alarm on-off State Machine jump table origin.
    JMPP @A			; Select the appropriate routine.
  lAlStJ:			; Alarm on-off State Machine jump table:
    .db #lAlSM0 - 0x0200
    .db #lAlSM1 - 0x0200
    .db #lAlSM2 - 0x0200
    
  lAlSM0:
    ; State 0: Check button on T0:
    JT0 lASEnd			; Is T0==1 (button open)? If so, do nothing.
    IN A, P1			; Get P1 state.
    JB0 lNCStB			; Is P1.0 (Not Clock Set)==1?
    JMP lASEnd			; - No, so the clock is being set; the button
				;   is used for setting the clock, not for
				;   alarm functions. So, do nothing.
  lNCStB:			; - Yes, so the clock is not being set.
    MOV A, R1			;   Store the Clock Tick Counter (R1)
    MOV R0, bDbnce		;   in bDbnce,
    MOV @R0, A
    MOV R0, bAlStM		;   Proceed to Alarm on-off State Machine
    MOV @R0, #0x01		;   State 1,
    JMP lASEnd			;   Finished.
    
  lAlSM1:
    ; State 1: Wait for Clock Tick:
    MOV R0, bDbnce		; Is R1==bDbnce? If so, we are still on the
    MOV A, R1			; Clock Tick as when the button was first
    XRL A, @R0			; pressed:
    JZ lASEnd			; - Yes, so do nothing.
    MOV R0, bAlStM		; - No, so proceed to Alarm on-off State
    MOV @R0, #0x02		;   Machine State 2.
    JMP lASEnd			;   Finished.

  lAlSM2:
    ; State 2: Wait for 1 second:
    JT0 lASMRs			; Is T0==1 (button open)? If so, go and reset
				; the Alarm on-off State Machine.
    IN A, P1			; Get P1 state.
    JB0 lNCStD			; Is P1.0 (Not Clock Set)==1?
    JMP lASMRs			; - No, so the clock is being set; go and reset
				;   the Alarm on-off State Machine.
  lNCStD:			; - Yes, so the clock is not being set.
    MOV R0, bDbnce		; Is R1==bDbnce?
    MOV A, R1
    XRL A, @R0
    JNZ lASEnd			; - No, so do nothing.
				; - Yes, so 1 second has elapsed, the
				;   button is still pressed and the clock is
				;   not being set.
    MOV A, R7			;   Toggle R7.4 (the Alarm On flag):
    JB4 lAlOnB			;   Is R7.4==1?
    ORL A, #0x10		;   - No, so set R7.4=1,
    MOV R7,A			;     and store R7.
    JMP lASMRs
  lAlOnB:			;   - Yes,
    ANL A, #0xEF		;     so set R7.4=0,
    MOV R7,A			;     and store R7.
    
  lASMRs:			; Reset Alarm on-off State Machine to State 0:
    MOV R0, bAlStM
    MOV @R0, #0x00
    
  lASEnd:			; Alarm on-off State Machine finished.
    
  ; Alarm engine:
    MOV A, R7			; Get R7.
    JB4 lAlOnA			; Is R7.4==1 (Alarm On)?
    JMP lNoAlm			; - No, so do not sound alarm.
  
  lAlOnA:			; - Yes, the alarm is on.
    MOV R0, bHour		;   Is bHour==bAlmHr?
    MOV A, @R0
    MOV R0, bAlmHr
    XRL A, @R0
    JNZ lNoAlm			;   - No, so do not sound alarm.
				;   - Yes.
    MOV R0, bMin		;     Is bMin==bAlmMn?
    MOV A, @R0
    MOV R0, bAlmMn
    XRL A, @R0
    JNZ lNoAlm			;     - No, so do not sound alarm.
				;     - Yes.
    JT0 lButOA			;       Is T0==1 (button open)?
				;       - No, so the button is pushed while the
				;         alarm is ringing. Silence the alarm:
    MOV A, R7			;         Get R7.
    ORL A, #0x20		;         Set R7.5 (Alarm Silence)=1.
    MOV R7, A			;         Store R7.
  lButOA:			;	- Yes, T0==1, so the button is open.
  
    MOV A, R7			;       Get R7.
    JB5 lNoSnd			;	Is R7.5==1 (Alarm Silence)?
				;       - No, so it is time to sound the alarm
				;         and the alarm is not silenced.
				;         Alternately start and stop the sound
				;         every second:
     MOV R0, bSec		;         Get the seconds counter bSec.
     MOV A, @R0
     JB0 lNoSnd			;         Is bSec an odd number?
				;         - If yes, stop the sound.
     ANL P1, #0xDF		;         - No. Set P1.5=0 to make a sound,
     JMP lELoop			;           Return.
  
  lNoAlm:			; Either the alarm is turned off, or today's
				; alarm is finished. Set R7.5 (Alarm Silence)=0
				; in preparation for tomorrow's alarm:
    MOV A, R7			; Get R7,
    ANL A, #0xDF		; Set R7.5 (Alarm Silence)=0,
    MOV R7, A			; Store R7.
  lNoSnd:
    ORL P1, #0x20		; Set P1.5=1 to stop the sound.
    JMP lELoop			; Return.

    
lSt4:
; State 4: Write segments for 2nd. from right display column.
  ; P2.7->P2.4 all logic high:
    ORL P2, #0xF0		; Upper 4 bits.

  ; Write 2nd. from right display column:
    CALL lDWhat			; Find out what is to be displayed.
    ADD A, #lS4Jmp - 0x0200	; State 4 jump table origin.
    JMPP @A			; Select the appropriate action.
  lS4Jmp:
    .db #lS4Tme	- 0x0200
    .db #lS4Alm - 0x0200
    .db #lS4HBr - 0x0200
  
  lS4Tme:
  ; Display the time (2nd. from right column).
    MOV R0, bMin		; Get minute.
    MOV A, @R0
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R2, A			; 2nd. from right, top character.

  ; 2nd. from right, 2nd. from top character (R3):
  ; If P1.4 is logic 1 (SW5 Off, 24h clock): R3=eBlank.
  ; If P1.4 is logic 0 (SW5 On,  12h clock): R3=eLetA (AM) or eLetP (PM).
    MOV R3, eBlank		; Make R3=eBlank to begin with.
    IN A, P1			; Get P1 state.
    JB4 l24hE			; Is P1.4 logic 1?
				; - No (12h clock), so write AM or PM:
    MOV R0, bHour		;   Get hour.
    MOV A, @R0
    ADD A, #0xF4		;   Set carry flag if bHour>=12.
    MOV R3, eLetA		;   Let's start with R3=eLetA.
    JNC lAMB			;   Is bHour>=12? (use carry flag set above).
    MOV R3, eLetP		;   - Yes it is, so make R3=eLetP.
  lAMB:				;   - No it isn't, so leave R3=eLetA.
  l24hE:			; - Yes (24h clock), so leave R3=eBlank.

    MOV R0, bMonth		; Get month.
    MOV A, @R0
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R4, A			; 2nd. from right, 2nd. from bottom character.
    
    MOV R0, bYear		; Get year.
    MOV A, @R0
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R5, A			; 2nd. from right, bottom character.
    JMP lS4Fin			; Finished.

  lS4Alm:
  ; Display alarm time (2nd. from right column).
    MOV R0, bAlmMn		; Get alarm minute.
    MOV A, @R0
    CALL l7SegT			; Convert to seven segment (tens).
    MOV R2, A			; 2nd. from right, top character.

  ; 2nd. from right, 2nd. from top character (R3):
  ; If P1.4 is logic 1 (SW5 Off, 24h clock): R3=eBlank.
  ; If P1.4 is logic 0 (SW5 On,  12h clock): R3=eLetA (AM) or eLetP (PM).
    MOV R3, eBlank		; Make R3=eBlank to begin with.
    IN A, P1			; Get P1 state.
    JB4 l24hH			; Is P1.4 logic 1?
				; - No (12h clock), so write AM or PM:
    MOV R0, bAlmHr		;   Get alarm hour.
    MOV A, @R0
    ADD A, #0xF4		;   Set carry flag if bAlmHr>=12.
    MOV R3, eLetA		;   Let's start with R3=eLetA.
    JNC lAMC			;   Is bHour>=12? (use carry flag set above).
    MOV R3, eLetP		;   - Yes it is, so make R3=eLetP.
  lAMC:				;   - No it isn't, so leave R3=eLetA.
  l24hH:			; - Yes (24h clock), so leave R3=eBlank.
    
    MOV R4, eBlank		; 2nd. from right, 2nd. from bottom character.
    MOV R5, eBlank		; 2nd. from right, bottom character.
    JMP lS4Fin			; Finished.

  lS4HBr:
  ; Display Happy Birthday (2nd. from right column).
    MOV R2, eLetP		; 2nd. from right, top character.

    MOV R0, bYear		; Get current year.
    MOV A, @R0
    ADD A, eBrthY		; Subtract birth year.
    CALL l7SegT			; Convert to seven segment (tens).      
    MOV R3, A			; 2nd. from right, 2nd. from top character.
      
    MOV R4, eLetR		; 2nd. from right, 2nd. from bottom character.
    MOV R5, eLetA		; 2nd. from right, bottom character.
    JMP lS4Fin			; Finished.    
    
  lS4Fin:
  ; Write 2nd. from right column.    
    CALL lsWCol			; Write column.
    JMP lELoop			; Return.


.org 0x0300			; Helps avoid page break within subroutine.


lSt5:
; State 5:
  ; Strobe P2.5 logic low to switch on 2nd. from right display column:
    ANL P2, #0xDF
    JMP lELoop			; Return.


lSt6:
; State 6: Write segments for rightmost display column.
  ; P2.7->P2.4 all logic high:
    ORL P2, #0xF0		; Upper 4 bits.

  ; Write rightmost display column:
    CALL lDWhat			; Find out what is to be displayed.
    ADD A, #lS6Jmp - 0x0300	; State 6 jump table origin.
    JMPP @A			; Select the appropriate action.
  lS6Jmp:
    .db #lS6Tme	- 0x0300
    .db #lS6Alm - 0x0300
    .db #lS6HBr - 0x0300  
  
  lS6Tme:
  ; Display the time (rightmost column).
    MOV R0, bMin		; Get minute.
    MOV A, @R0
    CALL l7SegU			; Convert to seven segment (units).
    MOV R2, A			; Rightmost top character.
  
				; Rightmost 2nd. from top character:
    MOV R3, eLetL		; Begin with the letter "L" ("Alarm On").
    MOV A, R7			; Get R7.
    JB4 lAlOnC			; Is R7.4==1?
    MOV R3, eBlank		; - No, so R3=blank ("Alarm Off").
  lAlOnC:			; - Yes, so leave R3=letter "L".
    
    MOV R0, bMonth		; Get month.
    MOV A, @R0
    CALL l7SegU			; Convert to seven segment (units).
    MOV R4, A			; Rightmost 2nd. from bottom character.
    
    MOV R0, bYear		; Get year.
    MOV A, @R0
    CALL l7SegU			; Convert to seven segment (units).
    MOV R5, A			; Rightmost bottom character.
    JMP lS6Fin			; Finished.
    
  lS6Alm:
  ; Display alarm time (rightmost column).
    MOV R0, bAlmMn		; Get alarm minute.
    MOV A, @R0
    CALL l7SegU			; Convert to seven segment (units).
    MOV R2, A			; Rightmost top character.

				; Rightmost 2nd. from top character:
    MOV R3, eLetL		; Begin with the letter "L" ("Alarm On").
    MOV A, R7			; Get R7.
    JB4 lAlOnD			; Is R7.4==1?
    MOV R3, eBlank		; - No, so R3=blank ("Alarm Off").
  lAlOnD:			; - Yes, so leave R3=letter "L".
    
    MOV R4, eBlank		; Rightmost 2nd. from bottom character.

    MOV R0, bSync		; Get Clock Recovery State Machine status
    MOV A, @R0			; display character.
    MOV R5, A			; Rightmost bottom character.
    JMP lS6Fin			; Finished.

  lS6HBr:
  ; Display Happy Birthday (rightmost column).
    MOV R2, eLetP		; Rightmost top character.

    MOV R0, bYear		; Get current year.
    MOV A, @R0
    ADD A, eBrthY		; Subtract birth year.
    CALL l7SegU			; Convert to seven segment (units).      
    MOV R3, A			; Rightmost 2nd. from top character.

    MOV R4, eLetT		; Rightmost 2nd. from bottom character.
    MOV R5, eLetY		; Rightmost bottom character.
    JMP lS6Fin			; Finished.    
    
  lS6Fin:
  ; Write rightmost column.    
    CALL lsWCol			; Write column.
    JMP lELoop			; Return.


lSt7:
; State 7:
  ; Strobe P2.4 logic low to switch on rightmost display column.
    ANL P2, #0xEF
    JMP lELoop			; Return.



; ---------------------------- GENERAL SUBROUTINES ----------------------------


.org 0x0400			; Helps avoid page break within subroutine.


lClkTk:
; Subroutine ClockTick:
; Provides a "Clock Tick". It decrements the Clock Tick Counter R1.
; - If R1 has not reached zero, it just returns.
; - If R1 has reached zero, the Seconds Increment Flag (R7.3) is set to inform
;   the Main State Machine that one second has elapsed.
;   Then, R1 is reset to 60 (for 60Hz) or 50 (for 50Hz).
; Affects R1.
; Affects bit 3 of R7.
    DJNZ R1, lCTEnd		; Decrement the Clock Tick Counter. If it
				; has not reached zero, we are finished.

    MOV A, R7			; The Clock Tick Counter has reached zero, so:
    ORL A, #0x08		; Set the Seconds Increment Flag (R7.3),
    MOV R7, A
				; Re-Initialise the Clock Tick Counter:
    MOV R1, #0x3C		; Start with R1=60 (for 60Hz).
    JT1 lCTEnd			; If T1 is high, leave R1 at 60.
    MOV R1, #0x32		; T1 was low, so set R1=50 (for 50Hz).
  lCTEnd:
    RET


lDWhat:
; Subroutine DisplayWhat:
; Determines what is to be displayed and returns the result in the accumulator:
;   Accumulator=0 if the time is to be displayed.
;   Accumulator=1 if the alarm time is to be displayed.
;   Accumulator=2 if "Happy Birthday" is to be displayed.
; Affects R0.
    IN A, P1			; Get P1 state.
    ANL A, #0x0D		; 00001101 (keep only P1.0, P1.2, P1.3).
    XRL A, #0x0C		; 00001100: Are P1.0==0, P1.2==1, P1.3==1?
				; (i.e. is the alarm time being set)?
    JZ lDsAlm			; If so, go and display the alarm time.

    IN A, P1			; Get P1 state.
    JB0 lNCStC			; Is P1.0==0 (i.e. is the clock being set)?
    JMP lDsTme			; - Yes, so go and display the time.
  lNCStC:			; - No, the clock is not being set.

    JNT0 lDsAlm			; Is the button on T0 pushed (T0==0)? If so, go
				; and display the alarm time.

    MOV R0, bDate		; Get the date.
    MOV A, @R0
    XRL A, eBrthD		; Is the date equal to birth date?
    JNZ lDsTme			; If not, go and display the time.
    MOV R0, bMonth		; Get the month.
    MOV A, @R0
    XRL A, eBrthM		; Is the month equal to birth month?
    JNZ lDsTme			; If not, go and display the time.
  ; The clock or alarm are not being set, the button on T0 is not pushed and
  ; the date and month are == birthday. Display Happy Birthday:
    MOV A, #0x02
    RET

  lDsAlm:			; Display the alarm time.
    MOV A, #0x01
    RET

  lDsTme:			; Display the time.
    CLR A
    RET


lsWCol:
; Subroutine WriteColumn:
; Uses subroutine lsWSeg to write the segment information to
; the displays in the column which is to be addressed with P2.7->P2.4 after
; this function returns.
; The data to be written to the display column should be provided as follows:
;   R2: Character to be written to the top display.
;   R3: Character to be written to the display 2nd. from top.
;   R4: Character to be written to the display 2nd. from bottom.
;   R5: Character to be written to the bottom display.
; Affects R0, R2, R3, R4, R5.
; The Carry flag is affected.
    CALL lsWSeg			; Write dummy segment into 4015 shift register.
    CALL lsWSeg			; Write G segments.
    CALL lsWSeg			; Write F segments.
    CALL lsWSeg			; Write E segments.
    CALL lsWSeg			; Write D segments.
    CALL lsWSeg			; Write C segments.
    CALL lsWSeg			; Write B segments.
    CALL lsWSeg			; Write A segments.
    RET


lsWSeg:
; Subroutine WriteSegments:
; Writes the segment information in the LSB of R2, R3, R4, R5 to data bits
; D3, D2, D1 and D0 of a dummy external address.
; R0 is used as a temporary store.
; R2, R3, R4 and R5 are returned rotated right through carry by 1 bit.
; The Carry flag is affected.
    MOV R0, #0x00		; Clear R0.

  ; Write segment for the top display to R0:
    MOV A, R2
    RRC A			; Move segment data to carry flag.
    MOV R2, A
    JNC lR2OK			; If segment is zero, jump.
    INC R0			; Segment was one so make lowest bit of R0 one.
  lR2OK:
  
  ; Prepare R0 to receive next bit:
    MOV A, R0
    RL A
    MOV R0, A
  ; Write segment for the 2nd. from top display to R0:
    MOV A, R3
    RRC A			; Move segment data to carry flag.
    MOV R3, A
    JNC lR3OK			; If segment is zero, jump.
    INC R0			; Segment was one so make lowest bit of R0 one.
  lR3OK: 

  ; Prepare R0 to receive next bit:
    MOV A, R0
    RL A
    MOV R0, A
  ; Write segment for the 2nd. from bottom display to R0:
    MOV A, R4
    RRC A			; Move segment data to carry flag.
    MOV R4, A
    JNC lR4OK			; If segment is zero, jump.
    INC R0			; Segment was one so make lowest bit of R0 one.    
  lR4OK:

  ; Prepare R0 to receive next bit:
    MOV A, R0
    RL A
    MOV R0, A
  ; Write segment for the bottom display to R0:
    MOV A, R5
    RRC A			; Move segment data to carry flag.
    MOV R5, A
    JNC lR5OK			; If segment is zero, jump.
    INC R0			; Segment was one so make lowest bit of R0 one. 
  lR5OK:

  ; Send segment data, which is now in R0, to the displays:
    MOV A, R0
    MOVX @R0, A			; Dummy, irrelevant address in R0.
    RET


l24H12:
; Converts the 24 hour value in the accumulator (0 to 23) to the 12 hour value
; (0 to 12).
; The Carry flag is affected.
    JNZ #ln12AA			; Special case: Is it 12AM (hour===0)?  
    MOV A, #0x0C		; - Yes, so make A=12.
    RET
  ln12AA:			; - No, it is not 12AM.
    ADD A, #0xF3		;   Set carry bit if Hour > 13
    JNC lAMA			;   Is it 1PM (hour==13) or beyond?
  ; It is 1PM (hour==13) or beyond, so subtract 12 from hour value.
  ; To subtract 12, we would have to add 12's two's complement (0xF4) to A.
  ; Since we have already added 0xF3 above, all we need to do is INC A.
    INC A
    RET
  lAMA:
  ; It is from 1 to 12AM, so A has to be returned unaltered. However, we have
  ; already added 0xF3 to A, so we need to add 0xF3's two's complement to
  ; bring A back to where it was.
    ADD A, #0x0D
    RET

    
.org 0x0500			; Helps avoid page break within subroutine.
    

lStClk:
; Subroutine SetClock: Increments the appropriate counter (minute, hour, date
; etc) relevant to the setting of P1.1, P1.2 and P1.3, or resets the seconds
; counter.
; Uses R0.
; The Carry flag is affected.
  ; Check the setting of P1.1, P1.2 and P1.3:
    IN A, P1			; Get P1 state.
    ANL A, #0x0E		; 00001110 (keep only P1.1, P1.2 and P1.3).
    RR A			; Move to bits 0, 1 and 2.
    ADD A, #lSCJmp - 0x0500	; SetClock jump table origin.
    JMPP @A			; Select the appropriate routine.
  lSCJmp:			; SetClock jump table:
    .db #lSCSe - 0x0500
    .db #lSCMi - 0x0500
    .db #lSCHr - 0x0500
    .db #lSCDt - 0x0500
    .db #lSCMo - 0x0500
    .db #lSCYr - 0x0500
    .db #lSCAH - 0x0500
    .db #lSCAM - 0x0500

  lSCSe:			; Reset the seconds counter:
    CLR A
    MOV R0, bSec
    MOV @R0, A
    RET

  lSCMi:
    CALL lIncMi			; Increment minute.
    RET

  lSCHr:
    CALL lIncHr			; Increment hour.
    RET

  lSCDt:
    CALL lIncDt			; Increment date.
    RET

  lSCMo:
    CALL lIncMo			; Increment month.
    RET
  
  lSCYr:
    CALL lIncYr			; Increment year.
    RET
  
  lSCAH:			; Increment Alarm Hour:
    MOV R0, bAlmHr		; Get current Alarm Hour value.
    MOV A, @R0
    ADD A, #0xE9		; Set carry flag if bAlmHr >= 23.
    MOV A, @R0			; Get value of bAlmHr again.
    INC A			; Increment alarm hour.
    JNC lnH23B			; Was bAlmHr >= 23 (carry flag from above)?
    CLR A			; - Yes, so set bAlmHr=0.
  lnH23B:
    MOV @R0, A			; Store bAlmHr.
    RET
  
  lSCAM:			; Increment Alarm Minute:
    MOV R0, bAlmMn		; Get current Alarm Minutes value.
    MOV A, @R0
    ADD A, #0xC5		; Set carry flag if bAlmMn >= 59.
    MOV A, @R0			; Get value of bAlmMn again.
    INC A			; Increment alarm minutes.
    JNC lnM59B			; Was bAlmMn >= 59 (carry flag from above)?
    CLR A			; - Yes, so set bAlmMn=0.
  lnM59B:
    MOV @R0, A			; Store bAlmMn.
    RET

    
lIncTm:
; Subroutine IncrementTime: Increments the seconds counter, and rolls over
; to all other time units.
; Uses R0.
; The Carry flag is affected.
  ; Increment the seconds counter bSec. If the seconds counter reaches 59,
  ; it is returned to 0 and the Carry flag is set.
    MOV R0, bSec
    MOV A, @R0
    ADD A, #0xC5		; Set carry flag if bSec >= 59.
    MOV A, @R0			; Get value of bSec again.
    INC A			; Increment seconds.
    JNC lnS59A			; Was bSec >= 59 (carry flag from above)?
    CLR A			; - Yes, so set bSec=0.
  lnS59A:
    MOV @R0, A			; Store bSec.
  
    JNC lEndB			; Did seconds roll over? If not, we are done.
  
  ; Seconds did roll over, so increment minutes:
    CALL lIncMi
    JNC lEndB			; Did minutes roll over? If not, we are done.
    
  ; Minutes did roll over, so increment hours:
    CALL lIncHr
    JNC lEndB			; Did hours roll over? If not, we are done.
    
  ; Hours did roll over, so increment date:
    CALL lIncDt
    JNC lEndB			; Did date roll over? If not, we are done.
    
  ; Date did roll over, so increment month:
    CALL lIncMo
    JNC lEndB			; Did month roll over? If not, we are done. 
    
  ; Month did roll over, so increment year:
    CALL lIncYr

  lEndB:			; Finished.
    RET
    
    
lIncMi:
; Subroutine lIncrementMinutes. Increments the minute counter bMin.
; If the minutes counter reaches 59, it is returned to 0 and the Carry flag
; is set.
; Uses R0.
; Affects the Carry flag.
    MOV R0, bMin
    MOV A, @R0
    ADD A, #0xC5		; Set carry flag if bMin >= 59.
    MOV A, @R0			; Get value of bMin again.
    INC A			; Increment minutes.
    JNC lnM59A			; Was bMin >= 59 (carry flag from above)?
    CLR A			; - Yes, so set bMin=0.
  lnM59A:
    MOV @R0, A			; Store bMin.
    RET

  
lIncHr:
; Subroutine lIncrementHours. Increments the hours counter bHour.
; If the hours counter reaches 23, it is returned to 0 and the Carry flag
; is set.
; Uses R0.
; Affects the Carry flag.
    MOV R0, bHour
    MOV A, @R0
    ADD A, #0xE9		; Set carry flag if bHour >= 23.
    MOV A, @R0			; Get value of bHour again.
    INC A			; Increment hours.
    JNC lnH23A			; Was bHour >= 23 (carry flag from above)?
    CLR A			; - Yes, so set bHour=0.
  lnH23A:
    MOV @R0, A			; Store bHour.
    RET


lIncDt:
; Subroutine lIncrementDate. Increments the date counter bDate.
; If the date counter reaches the number of days in the month, it is returned
; to 1 and the Carry flag is set.
; Note that this subroutine does cater for leap years.
; Uses R0.
; Affects the Carry flag.
; Also uses bMonth, bYear.
  ; Set A to 2's complement of number of days in month:
    MOV R0, bMonth
    MOV A, @R0			; Get current month.
    ADD A, #0xFE		; Is it February?
    JNZ lnFebA			; - No, so use lookup table.
  
  ; It is February, so use a special routine:
    MOV R0, bYear
    MOV A, @R0			; Get current year.
    ANL A, 0x03			; Keep only the lowest two bits.
    JZ lLeapY			; Are the lowest two bits 0?
    MOV A, #0xE4		; - No (not leap year). A=2's complement of 28.
    JMP lContA
  lLeapY:
    MOV A, #0xE3		; - Yes (leap year). A=2's complement of 29.
    JMP lContA
  
  lnFebA:
  ; It is not February, so use lookup table to find how many days in month.
    MOV R0, bMonth
    MOV A, @R0			; Get current month.
    ADD A, #lMDLkp - 0x0500	; Lookup table base address.
    MOVP A, @A
    JMP lContA
  lMDLkp:   			; Lookup table for days in each month:
    .db #0x00			; Dummy (month 0).
    .db #0xE1			; January	(2's complement of 31).
    .db #0xE4			; February	(2's complement of 28).
    .db #0xE1			; March		(2's complement of 31).
    .db #0xE2			; April		(2's complement of 30).
    .db #0xE1			; May		(2's complement of 31).
    .db #0xE2			; June		(2's complement of 30).
    .db #0xE1			; July		(2's complement of 31).
    .db #0xE1			; August	(2's complement of 31).
    .db #0xE2			; September	(2's complement of 30).
    .db #0xE1			; October	(2's complement of 31).
    .db #0xE2			; November	(2's complement of 30).
    .db #0xE1			; December	(2's complement of 31).
  lContA:			; We arrive here with A==2's complement of
				; number of days in month.
    MOV R0, bDate
    ADD A, @R0			; Set carry flag if bDate >= days in month.
    MOV A, @R0			; Get value of bDate again.
    INC A			; Increment date.
    JNC lnDMxA			; Was bDate >= days in month (carry above)?
    MOV A, #0x01		; - Yes, so set bDate=1.
  lnDMxA:
    MOV @R0, A			; Store bDate.
    RET    


lIncMo:
; Subroutine lIncrementMonths. Increments the months counter bMonth.
; If the months counter reaches 12, it is returned to 1 and the Carry flag
; is set.
; Uses R0.
; Affects the Carry flag.
    MOV R0, bMonth
    MOV A, @R0
    ADD A, #0xF4		; Set carry flag if bMonth >= 12.
    MOV A, @R0			; Get value of bMonth again.
    INC A			; Increment months.
    JNC lnM12A			; Was bMonth >= 12 (carry flag from above)?
    MOV A, #0x01		; - Yes, so set bMonth=1.
  lnM12A:
    MOV @R0, A			; Store bMonth.
    RET


lIncYr:
; Subroutine lIncrementYears. Increments the years counter bYear.
; If the years counter reaches 99, it is returned to 15 and the Carry flag
; is set.
; Uses R0.
; Affects the carry flag.
    MOV R0, bYear
    MOV A, @R0
    ADD A, #0x9D		; Set carry flag if bYear >= 99.
    MOV A, @R0			; Get value of bYear again.
    INC A			; Increment years.
    JNC lnY99A			; Was bYear >= 99 (carry flag from above)?
    MOV A, #0x0F		; - Yes, so set bYear=15.
  lnY99A:
    MOV @R0, A			; Store bYear.
    RET
  

; -------------------- 7-SEGMENT DISPLAY LOOKUP SUBROUTINES -------------------


.org 0x0600			; Helps avoid page break within subroutine.


l7SegT:
; Translates the hexadecimal number in the accumulator to its 7-segment
; representation for the tens digit. The result is returned in the accumulator.
; Only works for numbers up to 99 decimal (0x63).
; Note that this is routine is frightfully wasteful of space; however, it
; returns in very few clock cycles (whereas a "proper" binary-to-BCD conversion
; would take about 80 clock cycles).
    ADD A, #l7SLkT - 0x0600	; Lookup table base address.
    MOVP A, @A
    RET
  l7SLkT:			; Lookup table:
    .db eNum0			; Decimal 0.
    .db eNum0			; Decimal 1.
    .db eNum0			; Decimal 2.
    .db eNum0			; Decimal 3.
    .db eNum0			; Decimal 4.
    .db eNum0			; Decimal 5.
    .db eNum0			; Decimal 6.
    .db eNum0			; Decimal 7.
    .db eNum0			; Decimal 8.
    .db eNum0			; Decimal 9.
    .db eNum1			; Decimal 10.
    .db eNum1			; Decimal 11.
    .db eNum1			; Decimal 12.
    .db eNum1			; Decimal 13.
    .db eNum1			; Decimal 14.
    .db eNum1			; Decimal 15.
    .db eNum1			; Decimal 16.
    .db eNum1			; Decimal 17.
    .db eNum1			; Decimal 18.
    .db eNum1			; Decimal 19.
    .db eNum2			; Decimal 20.
    .db eNum2			; Decimal 21.
    .db eNum2			; Decimal 22.
    .db eNum2			; Decimal 23.
    .db eNum2			; Decimal 24.
    .db eNum2			; Decimal 25.
    .db eNum2			; Decimal 26.
    .db eNum2			; Decimal 27.
    .db eNum2			; Decimal 28.
    .db eNum2			; Decimal 29.
    .db eNum3			; Decimal 30.
    .db eNum3			; Decimal 31.
    .db eNum3			; Decimal 32.
    .db eNum3			; Decimal 33.
    .db eNum3			; Decimal 34.
    .db eNum3			; Decimal 35.
    .db eNum3			; Decimal 36.
    .db eNum3			; Decimal 37.
    .db eNum3			; Decimal 38.
    .db eNum3			; Decimal 39.
    .db eNum4			; Decimal 40.
    .db eNum4			; Decimal 41.
    .db eNum4			; Decimal 42.
    .db eNum4			; Decimal 43.
    .db eNum4			; Decimal 44.
    .db eNum4			; Decimal 45.
    .db eNum4			; Decimal 46.
    .db eNum4			; Decimal 47.
    .db eNum4			; Decimal 48.
    .db eNum4			; Decimal 49.
    .db eNum5			; Decimal 50.
    .db eNum5			; Decimal 51.
    .db eNum5			; Decimal 52.
    .db eNum5			; Decimal 53.
    .db eNum5			; Decimal 54.
    .db eNum5			; Decimal 55.
    .db eNum5			; Decimal 56.
    .db eNum5			; Decimal 57.
    .db eNum5			; Decimal 58.
    .db eNum5			; Decimal 59.
    .db eNum6			; Decimal 60.
    .db eNum6			; Decimal 61.
    .db eNum6			; Decimal 62.
    .db eNum6			; Decimal 63.
    .db eNum6			; Decimal 64.
    .db eNum6			; Decimal 65.
    .db eNum6			; Decimal 66.
    .db eNum6			; Decimal 67.
    .db eNum6			; Decimal 68.
    .db eNum6			; Decimal 69.
    .db eNum7			; Decimal 70.
    .db eNum7			; Decimal 71.
    .db eNum7			; Decimal 72.
    .db eNum7			; Decimal 73.
    .db eNum7			; Decimal 74.
    .db eNum7			; Decimal 75.
    .db eNum7			; Decimal 76.
    .db eNum7			; Decimal 77.
    .db eNum7			; Decimal 78.
    .db eNum7			; Decimal 79.
    .db eNum8			; Decimal 80.
    .db eNum8			; Decimal 81.
    .db eNum8			; Decimal 82.
    .db eNum8			; Decimal 83.
    .db eNum8			; Decimal 84.
    .db eNum8			; Decimal 85.
    .db eNum8			; Decimal 86.
    .db eNum8			; Decimal 87.
    .db eNum8			; Decimal 88.
    .db eNum8			; Decimal 89.
    .db eNum9			; Decimal 90.
    .db eNum9			; Decimal 91.
    .db eNum9			; Decimal 92.
    .db eNum9			; Decimal 93.
    .db eNum9			; Decimal 94.
    .db eNum9			; Decimal 95.
    .db eNum9			; Decimal 96.
    .db eNum9			; Decimal 97.
    .db eNum9			; Decimal 98.
    .db eNum9			; Decimal 99.


l7SegU:
; Translates the hexadecimal number in the accumulator to its 7-segment
; representation for the units digit. The result is returned in the
; accumulator.
; Only works for numbers up to 99 decimal (0x63).
; Note that this is routine is frightfully wasteful of space; however, it
; returns in very few clock cycles (whereas a "proper" binary-to-BCD conversion
; would take about 80 clock cycles).
    ADD A, #l7SLkU - 0x0600	; Lookup table base address.
    MOVP A, @A
    RET
  l7SLkU:			; Lookup table:
    .db eNum0			; Decimal 0.
    .db eNum1			; Decimal 1.
    .db eNum2			; Decimal 2.
    .db eNum3			; Decimal 3.
    .db eNum4			; Decimal 4.
    .db eNum5			; Decimal 5.
    .db eNum6			; Decimal 6.
    .db eNum7			; Decimal 7.
    .db eNum8			; Decimal 8.
    .db eNum9			; Decimal 9.
    .db eNum0			; Decimal 10.
    .db eNum1			; Decimal 11.
    .db eNum2			; Decimal 12.
    .db eNum3			; Decimal 13.
    .db eNum4			; Decimal 14.
    .db eNum5			; Decimal 15.
    .db eNum6			; Decimal 16.
    .db eNum7			; Decimal 17.
    .db eNum8			; Decimal 18.
    .db eNum9			; Decimal 19.
    .db eNum0			; Decimal 20.
    .db eNum1			; Decimal 21.
    .db eNum2			; Decimal 22.
    .db eNum3			; Decimal 23.
    .db eNum4			; Decimal 24.
    .db eNum5			; Decimal 25.
    .db eNum6			; Decimal 26.
    .db eNum7			; Decimal 27.
    .db eNum8			; Decimal 28.
    .db eNum9			; Decimal 29.
    .db eNum0			; Decimal 30.
    .db eNum1			; Decimal 31.
    .db eNum2			; Decimal 32.
    .db eNum3			; Decimal 33.
    .db eNum4			; Decimal 34.
    .db eNum5			; Decimal 35.
    .db eNum6			; Decimal 36.
    .db eNum7			; Decimal 37.
    .db eNum8			; Decimal 38.
    .db eNum9			; Decimal 39.
    .db eNum0			; Decimal 40.
    .db eNum1			; Decimal 41.
    .db eNum2			; Decimal 42.
    .db eNum3			; Decimal 43.
    .db eNum4			; Decimal 44.
    .db eNum5			; Decimal 45.
    .db eNum6			; Decimal 46.
    .db eNum7			; Decimal 47.
    .db eNum8			; Decimal 48.
    .db eNum9			; Decimal 49.
    .db eNum0			; Decimal 50.
    .db eNum1			; Decimal 51.
    .db eNum2			; Decimal 52.
    .db eNum3			; Decimal 53.
    .db eNum4			; Decimal 54.
    .db eNum5			; Decimal 55.
    .db eNum6			; Decimal 56.
    .db eNum7			; Decimal 57.
    .db eNum8			; Decimal 58.
    .db eNum9			; Decimal 59.
    .db eNum0			; Decimal 60.
    .db eNum1			; Decimal 61.
    .db eNum2			; Decimal 62.
    .db eNum3			; Decimal 63.
    .db eNum4			; Decimal 64.
    .db eNum5			; Decimal 65.
    .db eNum6			; Decimal 66.
    .db eNum7			; Decimal 67.
    .db eNum8			; Decimal 68.
    .db eNum9			; Decimal 69.
    .db eNum0			; Decimal 70.
    .db eNum1			; Decimal 71.
    .db eNum2			; Decimal 72.
    .db eNum3			; Decimal 73.
    .db eNum4			; Decimal 74.
    .db eNum5			; Decimal 75.
    .db eNum6			; Decimal 76.
    .db eNum7			; Decimal 77.
    .db eNum8			; Decimal 78.
    .db eNum9			; Decimal 79.
    .db eNum0			; Decimal 80.
    .db eNum1			; Decimal 81.
    .db eNum2			; Decimal 82.
    .db eNum3			; Decimal 83.
    .db eNum4			; Decimal 84.
    .db eNum5			; Decimal 85.
    .db eNum6			; Decimal 86.
    .db eNum7			; Decimal 87.
    .db eNum8			; Decimal 88.
    .db eNum9			; Decimal 89.
    .db eNum0			; Decimal 90.
    .db eNum1			; Decimal 91.
    .db eNum2			; Decimal 92.
    .db eNum3			; Decimal 93.
    .db eNum4			; Decimal 94.
    .db eNum5			; Decimal 95.
    .db eNum6			; Decimal 96.
    .db eNum7			; Decimal 97.
    .db eNum8			; Decimal 98.
    .db eNum9			; Decimal 99.
