
$NOMOD51
$INCLUDE(REG51FX.INC)               ; register definitions compatible 
                                    ; with standard Intel architecture FX cpu

        USING   0                   ; use register block 0

SWTPORT  EQU        P2              ; switches connected to Port 2
LEDPORT  EQU        P3              ; LEDs connected to Port 3

;***************************************************
; INTERNAL DATA BIT DEFINITIONS, locations 20H-2FH
;---------------------------------------------------

BSEG    AT      00
Tick:
        DBIT    1                   ; 1 = 1.25 msec tick has come

;***************************************************
; INTERNAL DATA BYTE DEFINITIONS
;---------------------------------------------------

;internal data area variables.
;
DSEG    AT      30H                 ; word locations are HIGH:LOW

SwitchNo:
        DS      1                   ; current switch number to process (0->7)

SwitchStates:
        DS      1                   ; current state of Switch 0
        DS      1                   ; current state of Switch 1
        DS      1                   ; current state of Switch 2
        DS      1                   ; current state of Switch 3
        DS      1                   ; current state of Switch 4
        DS      1                   ; current state of Switch 5
        DS      1                   ; current state of Switch 6
        DS      1                   ; current state of Switch 7

SwitchCounters:
        DS      2                   ; counter for Switch 0
        DS      2                   ; counter for Switch 1
        DS      2                   ; counter for Switch 2
        DS      2                   ; counter for Switch 3
        DS      2                   ; counter for Switch 4
        DS      2                   ; counter for Switch 5
        DS      2                   ; counter for Switch 6
        DS      2                   ; counter for Switch 7

STACK:
        DS      20                  ; reserve 20 bytes of space
RAM_END:
        DS      1                   ; end of allocated RAM marker

;***************************************************
; INTERRUPT VECTOR DEFINITIONS
;---------------------------------------------------

CSEG    AT      0000H               ; reset vector
        JMP     PWR_UP              ; power-on entry point

CSEG    AT      0003H               ; intr 0 vector
        CLR     EX0                 ; external int 0 not used
        RETI

CSEG    AT      000BH               ; timer 0 vector
        RETI                        ; not used

CSEG    AT      0013H               ; intr 1 vector
        CLR     EX1                 ; external int 1 not used
        RETI

CSEG    AT      001BH               ; timer 1 vector
        JMP     T1_ISR              ; timer 1 int isr

CSEG    AT      0023H               ; UART vector
        CLR     ES                  ; serial port int not used
        RETI

;***************************************************
; BASE OF CODE AREA
;---------------------------------------------------

CSEG    AT      040H
        DB      'Copyright (C) Carousel Design 2003',0

;***************************************************
; NAME: INIT_T1
;   Initialize a 1.25 mSec period interrupt for a 
;   state machine dispatcher on timer 1. This is same 
;   as a frequency of 800 Hz.
;   Mode 1 is 16-bit
;   TMOD is not bit addressable.
;
;   Timer clocks are 11.0952 mHz / 12 = 924600 Hz 
;   Divisor for 800 Hz = 924600 / 800 = 1155.75 (approx 1156)
;---------------------------------------------------

T1_RELOAD EQU   (65536 - 1156)

INIT_T1:
        MOV     TMOD, #10H          ; timer 1 - mode 1
; 
        MOV     TH1, #HIGH(T1_RELOAD) ;to setup 800 Hz rate
        MOV     TL1, #LOW(T1_RELOAD)  ;timer 1 for 1.25 ms
        CLR     Tick                ; clear the tick indicator flag
        SETB    TR1                 ; start the timer 1
        RET


;***************************************************
;NAME: T1_ISR
;   Used to time an 800 Hz ticker bit
;---------------------------------------------------

T1_ISR:
        PUSH    PSW                 ; save entry state
        SETB    Tick                ; show tick time
;
        CLR     ET1                 ; stop it for a moment
        MOV     TH1, #HIGH(T1_RELOAD) ; to setup 800 Hz rate
        MOV     TL1, #LOW(T1_RELOAD)  ; timer 1 for 1.25 ms
        SETB    ET1                 ; restart the timer
;
        POP     PSW                 ; restore state
        RETI

;***************************************************
;NAME: SWT_MSK
;   Used to convert a switch number to a port mask
;   Entry A is the 0-7 switch mask
;---------------------------------------------------

SWT_MSK:
        INC  A
        MOVC    A, @A+PC
        RET
;
        DB      00000001B           ; Bit 0    <-  0
        DB      00000010B           ; Bit 1    <-  1
        DB      00000100B           ; Bit 2    <-  2
        DB      00001000B           ; Bit 3    <-  3
        DB      00010000B           ; Bit 4    <-  4
        DB      00100000B           ; Bit 5    <-  5
        DB      01000000B           ; Bit 6    <-  6
        DB      10000000B           ; Bit 7    <-  7

;***************************************************
; MAIN PROGRAM INITIALIZATION
;---------------------------------------------------
PWR_UP:
        MOV     SP, #STACK          ; set stack pointer
;
        MOV     SWTPORT, 0FFH       ; set to make the switch ports inputs
        MOV     LEDPORT, 0FFH       ; fix so all LEDs are off

        CALL    INIT_T1             ; initialize timer 1 interrupt

        CLR     A                   ; initialize switch number variable
        MOV     SwitchNo, A

        MOV     R0, #SwitchStates   ; clear all switch states to 0
        MOV     R2, #8
SwStInit:
        MOV     @R0, #0
        INC     R0
        DJNZ    R2, SwStInit

        MOV     R0, #SwitchCounters ; clear all switch counters
        MOV     R2, #8
SwCtInit:
        MOV     @R0, #0
        INC     R0
        MOV     @R0, #0
        INC     R0
        DJNZ    R2, SwCtInit
;
        SETB    EA                  ; enable interrupts
        JMP     MAIN_LOOP
;
;
; here is the main loop state table for the processing 
; of the states for a specific switch.
;
; States are defined as:
;    State 0:  Waiting for a switch input to show it was
;              detected going low.
;    State 1:  Waiting for second verification of a switch
;              input in the low state as a debounce verification.
;    State 2:  Waiting for 5 second counter time to expire while LED
;              is kept on for 5 seconds
;    State 3:  Waiting for switch input to show high again
;
STATE_TABLE:
        DW      MAIN_STATE_0        ; pointer to State 0 routine
        DW      MAIN_STATE_1        ; pointer to State 1 routine
        DW      MAIN_STATE_2        ; pointer to State 2 routine
        DW      MAIN_STATE_3        ; pointer to State 3 routine
STATE_CNT   EQU     ($ - STATE_TABLE)/2 ;number of states
;
;               
;***************************************************
; main loop process
;
; This processes the current state each switch in a round robin manner.
; Each state is dispatched whenever the timer 1 interrupt indicates that
; 1.25 milliseconds has gone by. Then the state routine for one switch is called
; followed by an increment of the switch number variable. The main loop
; then goes back to the top to wait for another 1.25 msec period to expire.
; Since we process each of 8 switches in turn the effective processing rate
; for each switch input is 1,25 msec * 8 = 10 mSec (or 100 Hz).
;---------------------------------------------------
;
MAIN_LOOP:
        JNB     Tick, MAIN_LOOP     ; wait till a tick has gone by
;
        CLR     Tick                ; clear bit once seen
;
        MOV     A, SwitchNo         ; fetch the state for the current
        ADD     A, #SwitchStates    ; switch
        MOV     R0, A
        MOV     A, @R0
        CJNE    A, #STATE_CNT,ML_A  ; check for legal state number
ML_A:
        JC      ML_B                ; state number OK
        CLR     A                   ; reset to 0 if invalid
        MOV     @R0, A
ML_B:       
        MOV     DPTR, #STATE_TABLE  ; point to state branch table
        CALL    CALL_TABLE          ; call to state routine
;
        MOV     A, SwitchNo         ; increment to next switch number
        INC     A
        ANL     A, #7               ; limit to 3 bits of switch number
        MOV     SwitchNo, A
;
        JMP     MAIN_LOOP           ; go wait for next tick time

;***************************************************
;NAME: MAIN_STATE_0
;   This is state processing routine to wait for a
;   switch to be in the pressed state where the input
;   for the switch comes from the SWTPORT. If the input
;   is still high then just stay in State 0 otherwise
;   transition to State 1.
;
;   Variable SwitchNo has the current switch number.
;---------------------------------------------------

MAIN_STATE_0:
        MOV     A, SwitchNo         ; get the switch number
        CALL    SWT_MSK             ; get mask for this switch number
        ANL     A, SWTPORT          ; look at switch state
        JNZ     MAIN_STATE_0X       ; exit no change if switch still high
;
;switch is pressed so change to State 1.
;
        MOV     A, SwitchNo         ; set new state for the current
        ADD     A, #SwitchStates    ; switch 
        MOV     R0, A
        MOV     @R0, #1             ; force next state number to State 1
;
MAIN_STATE_0X:
        RET

;***************************************************
;NAME: MAIN_STATE_1
;   This is state processing routine to validate that
;   switch is still in the pressed state where the input
;   for the switch comes from the SWTPORT. If the input
;   is still low then transfer to State 2 with LED on 
;   for 5 seconds. Otherwise if input is high we have
;   bounce so return back to state 0.
;
;   Variable SwitchNo has the current switch number.
;---------------------------------------------------

FIVE_SEC_CNT    EQU     5 * 100     ; 5 seconds is 5 * 100 Hz state 2 rate (i,e, 10 msec)

MAIN_STATE_1:
        MOV     A, SwitchNo         ; get the switch number
        CALL    SWT_MSK             ; get mask for this switch number
        ANL     A, SWTPORT          ; look at switch state
        JZ      MAIN_STATE_1B       ; input low so switch still pressed
;
MAIN_STATE_1A:          ; here if switch input bounced back high
        MOV     A, SwitchNo         ; set new state for the current
        ADD     A, #SwitchStates    ; switch 
        MOV     R0, A
        MOV     @R0, #0             ; force next state number back to State 0
        JMP     MAIN_STATE_1X
;
MAIN_STATE_1B:          ;here if switch input valid low for 10 mSec
        MOV     A, SwitchNo         ; get bit to set on the LED for this switch
        CALL    SWT_MSK             ; get mask for this switch number
        XRL     A, 0FFH             ; invert mask
        ANL     LEDPORT, A          ; drive the LED bit low to turn on the LED
;
        MOV     A, SwitchNo         ; set the counter for this switch to 5 seconds
        CLR     C
        RLC     A                   ; make *2 word type index to counter table
        ADD     A, #SwitchCounters
        MOV     R0, A               ; make pointer to counter word
        MOV     @R0, #HIGH(FIVE_SEC_CNT)    ;set counter for 5 seconds
        INC     R0
        MOV     @R0, #LOW(FIVE_SEC_CNT)
;
        MOV     A, SwitchNo         ; set new state for the current
        ADD     A, #SwitchStates    ; switch 
        MOV     R0, A
        MOV     @R0, #2             ; force next state number to State 2
;
MAIN_STATE_1X:
        RET

;***************************************************
;NAME: MAIN_STATE_2
;   This is state processing routine to wait out the 
;   five second time that the LED is activated. The 
;   switch input status is just ignored. If the count 
;   is still active then the state stays as State 2. 
;   When the 5 seconds has expired then the LED for
;   this switch is shut off and the state is changed
;   to state 3.
;
;   Variable SwitchNo has the current switch number.
;---------------------------------------------------

MAIN_STATE_2:
        MOV     A, SwitchNo         ; decrement the counter for this switch
        CLR     C
        RLC     A                   ; make *2 word type index to counter table
        ADD     A, #SwitchCounters+1    ;offset to access the low byte first.
        MOV     R0, A
        MOV     A, @R0              ; fetch low byte and decrement it
        DEC     A
        CJNE    A, #0FFH, MAIN_STATE_2A ;low byte did not underflow
        DEC     R0
        DEC     @R0                 ; decrement the high byte
        INC     R0
MAIN_STATE_2A:
        MOV     @R0, A              ; save the low byte
;
        DEC     R0                  ; check for count having gone to zero
        ORL     A, @R0
        JNZ     MAIN_STATE_2X       ; exit staying in state 2 if counter not expired
;
MAIN_STATE_2B:              ;here when the 5 second timer has expired
        MOV     A, SwitchNo         ; get bit to set on the LED for this switch
        CALL    SWT_MSK             ; get mask for this switch number
        ORL     LEDPORT, A          ; drive the LED bit high to turn off the LED
;
        MOV     A, SwitchNo         ; set new state for the current
        ADD     A, #SwitchStates    ; switch 
        MOV     R0, A
        MOV     @R0, #3             ; force next state number to State 3
;
MAIN_STATE_2X:
        RET
        
;***************************************************
;NAME: MAIN_STATE_3
;   This is state processing routine to wait for the 
;   switch input to go back high, It may have already
;   gone high during the 5 second LED period in 
;   which case we catch it high here on the first check.
;   If the switch input is still low then stay in 
;   State 3. If the switch has gone high then transfer 
;   back to State 0. 
;
;   Variable SwitchNo has the current switch number.
;---------------------------------------------------

MAIN_STATE_3:
        MOV     A, SwitchNo         ; get the switch number
        CALL    SWT_MSK             ; get mask for this switch number
        ANL     A, SWTPORT          ; look at switch state
        JZ      MAIN_STATE_3X       ; exit no change if switch still low
;
;switch is released so change to State 0.
;
        MOV     A, SwitchNo         ; set new state for the current
        ADD     A, #SwitchStates    ; switch 
        MOV     R0, A
        MOV     @R0, #0             ; force next state number to State 0
;
MAIN_STATE_3X:
        RET
    
;***************************************************
;NAME: CALL_TABLE
;   Routine to call a routine through a table.
;   Come here with A as the 0-n index into the
;   table and DPTR pointing to the base of the
;   table. The return at end of called routine
;   takes execution back to where this routine
;   was called from. This routine also uses R0.
;---------------------------------------------------

CALL_TABLE:
        CLR     C                   ; 
        RLC     A                   ; multiply * 2 for word access
        MOV     R0, A               ; save a copy of index
        INC     A                   ; increment index to the hig byte
        MOVC    A, @A+DPTR          ; low byte
        PUSH    ACC                 ; onto stack
        MOV     A, R0
        MOVC    A, @A+DPTR          ; high byte
        PUSH    ACC                 ; onto stack
        RET                         ; direct branch to the subroutine

        END
