; ***********************************************************************
; Scrolling LED Clock Display - 7x10 Matrix LEDS
;                             - 60Hz AC interrupts
;
; Revision: 5/08/1999
; Copyright 1999, LNS Technologies - All rights reserved
; EMAIL: lnstech@ncal.verio.com
; INTERNET: http://www.techkits.com
; ***********************************************************************

$mod2051                            ; load 2051 definitions file

ACFREQ      EQU    60               ; 60 interrupts per second
RAMSIZE     EQU    32               ; 32 columns of 8 bit row data
SCRATE      EQU    30               ; default scroll rate

PTR_R0      EQU    R0               ; data pointer
PTR_R1      EQU    R1               ; data pointer
INDEX       EQU    R3               ; index pointer
ONEPASS     EQU    R4               ; one scroll pass counter

SELRB0      EQU    000H             ; Register bank 0
SELRB1      EQU    008H             ; Register bank 1
SELRB2      EQU    010H             ; Register bank 2
SELRB3      EQU    018H             ; Register bank 3

        ; Microcontroller input pins
ACINT0      BIT    P3.2             ; AC-60Hz interrupt signal
ADVANCE     BIT    P3.3             ; time increment pushbutton
MINSET      BIT    P3.4             ; slide switch in minute set position
HRSET       BIT    P3.5             ; slide switch in hour set position
        ; Microcontroller output pins
RST         BIT    P3.7             ; CD4017 reset to column 0
CLK         BIT    P1.7             ; CD4017 clock to increment column

        DSEG AT 0020H               ; RAM space
            ORG    030H             ; 

            ; 8x22 RAM hidden buffer
bufend:     
            DS     4
bufdig4:    DS     1                ; 4th digit in hidden buffer
            DS     4
bufdig3:    DS     1                ; 3th digit in hidden buffer
            DS     1
bufcoln:    DS     1                ; colon in hidden buffer
            DS     4
bufdig2:    DS     1                ; 2th digit in hidden buffer
            DS     4
bufdig1:    DS     1                ; 1st digit in hidden buffer

            ; 8x10 RAM display buffer
dispend:
            DS     4
disdig2:    DS     1                ; 2nd digit in display buffer
            DS     4
disdig1:    DS     1                ; 1st digit in display buffer

dummy1:     DS     2

            ; RAM variables
ac60hz:     DS     1                ; counts # of 60 Hz interrupts
seconds:    DS     1                ; seconds
tensecs:    DS     1                ; ten seconds
minutes:    DS     1                ; minutes
tenmins:    DS     1                ; ten minutes
hours:      DS     1                ; hours
tenhours:   DS     1                ; ten hours
twelvehr:   DS     1                ; twelve hour rollover
rate:       DS     1                ; scroll rate in RAM

dummy2:     DS     2

            ; Stack in RAM for subroutine & interrupt processing
stack:      DS     020H             ; stack depth
stackend:   DS     1                ; end of stack

        CSEG                        ; PROGRAM space

        ORG     0000H               ; power on/reset vector
        ajmp    on_reset            ; jump to power-on reset routine
        ORG     0003H               ; external interrupt 0 vector
        clr     EA                  ; disable interrupts
        ajmp    ac_intr             ; jump to AC-60Hz interrupt routine
        ORG     000BH               ; timer 0 overflow vector
        reti                        ; not used
        ORG     0013H               ; external interrupt 1 vector
        reti                        ; not used
        ORG     001BH               ; timer 1 overflow vector
        reti                        ; not used
        ORG     0023H               ; serial I/O interrupt vector
        reti                        ; not used
        ORG     0040H               ; begin constant data space

; ***********************************************************************
; Lookup table to convert time digits to display dot patterns.
; Bit 7 is used to clock the 4017 so it is always a 0 in the data table
; Data is inverted: 0 => LED is ON & 1 => LED is OFF
; ***********************************************************************
X0:     DB      01000001B
        DB      00111110B
        DB      00111110B
        DB      01000001B
        DB      07FH                ; blank column between digits
X1:     DB      01111111B
        DB      00111101B
        DB      00000000B
        DB      00111111B
        DB      07FH                ; blank column between digits
X2:     DB      00011101B
        DB      00101110B
        DB      00110110B
        DB      00111001B
        DB      07FH                ; blank column between digits
X3:     DB      01011101B
        DB      00111110B
        DB      00110110B
        DB      01001001B
        DB      07FH                ; blank column between digits
X4:     DB      01110000B
        DB      01110111B
        DB      00000001B
        DB      01110111B
        DB      07FH                ; blank column between digits
X5:     DB      01011000B
        DB      00111010B
        DB      00111010B
        DB      01000110B
        DB      07FH                ; blank column between digits
X6:     DB      01000001B
        DB      00110110B
        DB      00110110B
        DB      01001101B
        DB      07FH                ; blank column between digits
X7:     DB      01111100B
        DB      01111110B
        DB      00000110B
        DB      01111000B
        DB      07FH                ; blank column between digits
X8:     DB      01001001B
        DB      00110110B
        DB      00110110B
        DB      01001001B
        DB      07FH                ; blank column between digits
X9:     DB      01011001B
        DB      00110110B
        DB      00110110B
        DB      01000001B
        DB      07FH                ; blank column between digits
XBLANK: DB      01111111B
        DB      01111111B
        DB      01111111B
        DB      01111111B
        DB      07FH                ; blank column between digits

; Perform initializations on power-up.
; ***********************************************************************
on_reset:
        mov     IE, #0              ; deactivate all interrupts
        mov     TCON, #0            ; timers off
        mov     TMOD, #0            ; timers off
        mov     SCON, #0            ; no serial communications
        mov     SP, #(stack-1)      ; set stack pointer
        mov     PSW, #SELRB0        ; set active register bank

ramclear:                           ; clear all ram locations
        mov     A, #0               ; including stack space
        mov     PTR_R0, #07FH
        mov     PTR_R1, #07EH
  ramloop:
        mov     @PTR_R0, A
        dec     PTR_R0
        djnz    PTR_R1, ramloop

        acall   dispclr             ; clear display & hidden buffer

        mov     P1, #01111111B      ; make sure row LEDs are off
        mov     P3, #0              ; set port 3 pins low
        setb    MINSET              ; set pin high to use as input
        setb    HRSET               ; set pin high to use as input
        setb    ADVANCE             ; set pin high to use as input
        setb    ACINT0              ; set pin high to use as input

        setb    RST                 ; reset CD4017 to column 0
        mov     A, #1
        acall   delay_ms            ; delay 1 msec
        clr     RST                 ; allow CD4017 to count

default:                            ; reset to a default time on power-up
        mov     tenhours, #1        ; default to 12:00
        mov     hours, #2           ; 
        mov     tenmins, #0         ;
        mov     minutes, #0         ; 
        mov     seconds, #0         ; 
        mov     twelvehr, #12       ; set twelvehr for rollover
        acall   newdigits           ; build digit patterns in hidden buffer

        mov     A, hours            ; display hours digit
        mov     PTR_R1, #disdig2    ; point to ram buffer
        acall   decode              ; decode & write the LED patterns to RAM

        mov     A, tenhours         ; display ten hours digit
        mov     PTR_R1, #disdig1    ; point to ram buffer
        acall   decode              ; decode & write the LED patterns to RAM

pwr_fail:                           ; flash 12 to show power fail
        acall   refresh             ; refresh display
        mov     A, #250             ; blink display
        acall   delay_ms            ; delay 250 msec
        mov     A, #250             ; blink display
        acall   delay_ms            ; delay 250 msec
        jnb     minset, ac_init     ; is minute set switch on?
        jnb     hrset, ac_init      ; is hour set switch on?
        ajmp    pwr_fail            ; flash 12 until time gets set

ac_init:                            ; initialization for external interrupts
        mov     ac60hz, #ACFREQ     ; load interrupt repeat value
        setb    IT0                 ; make interrupt 0 edge activated
        setb    EX0                 ; enable external 0 interrupts
        setb    EA                  ; enable interrupts
        ajmp    fullscroll          ; start scrolling the current time

; The main loop refreshes the display, scrolls & checks the switches.
; ***********************************************************************
scrolltime:
        mov     onepass, #RAMSIZE   ; scroll all hidden buffer thru display
  scrollcol:
        acall   refresh             ; refresh display for 1 scroll period
        acall   shiftram            ; scroll left 1 column - shift RAM data
        jnb     minset, setmins     ; is minute set switch on?
        jnb     hrset, sethrs       ; is hour set switch on?
        djnz    onepass, scrollcol  ; has entire hidden buffer scrolled by?

fullscroll:                         ; at end of full scroll - test switches
        acall   dispclr             ; clear display & hidden buffer
        acall   newdigits           ; update the time digits
        ajmp    scrolltime          ; do full time scroll again

setmins:
        acall   incmins             ; set the minutes
        ajmp    fullscroll          ; do full scroll again

sethrs: 
        acall   inchours            ; set the hours
        ajmp    fullscroll          ; do full scroll again

; This loops one scroll period to refresh the led display.
; ***********************************************************************
refresh:
        mov     rate, #SCRATE       ; reset scroll rate
  restart:           
        mov     B, #10              ; number of columns in display buffer
        mov     PTR_R1, #dispend    ; point to ram display buffer
    refloop:        
        mov     A, @PTR_R1          ; get row data from RAM buffer
        mov     P1, A               ; display row data in LEDs

        mov     A, #1               ;
        acall   delay_ms            ; display row data for 1 ms

        mov     P1, #07FH           ; turn the LEDs off
        nop                         ; delay for visual effect
        mov     P1, #0FFH           ; increment 4017 to next column
        nop                         ; delay to allow 4017 to clock

        inc     PTR_R1              ; advance to next row in RAM buffer
        djnz    B, refloop          ; loop to display all 10 columns

        djnz    rate, restart       ; if not time to scroll, continue refresh
        ret                         ; time to scroll - return

; Shift the data in the ram buffer to make the time digits scroll
; ***********************************************************************
shiftram:
        mov     B, #RAMSIZE-1       ; number of ram buffer locations - 1
        mov     PTR_R0, #disdig1    ; start with digit 1
        mov     PTR_R1, #disdig1-1  ; adjacent buffer location
    shiftloop:
        mov     A, @PTR_R1          ; read ram buffer value
        mov     @PTR_R0, A          ; write to ram buffer
        dec     PTR_R0              ; advance to next location
        dec     PTR_R1              ; advance to next location
        djnz    B, shiftloop        ; shift next byte
        ret

; Update the time digits in the hidden buffer area.
; ***********************************************************************
newdigits:
        mov     A, minutes          ; minutes digit
        mov     PTR_R1, #bufdig4    ; point to ram buffer
        acall   decode              ; Yes - do update

        mov     A, tenmins          ; ten minutes digit
        mov     PTR_R1, #bufdig3    ; point to ram buffer
        acall   decode              ; Yes - do update

        mov     bufcoln, #01101011B ; store colon in buffer

        mov     A, hours            ; hours digit
        mov     PTR_R1, #bufdig2    ; point to ram buffer
        acall   decode              ; Yes - do update

        mov     A, tenhours         ; ten hours digit
        mov     PTR_R1, #bufdig1    ; point to ram buffer
        acall   decode              ; Yes - do update

        ret

; Increment the minutes digits while the button is pressed.
; ***********************************************************************
incmins:
        mov     A, minutes          ; minutes digit
        mov     PTR_R1, #disdig2    ; point to ram buffer
        acall   decode              ; decode & write the LED patterns to RAM

        mov     A, tenmins          ; ten minutes digit
        mov     PTR_R1, #disdig1    ; point to ram buffer
        acall   decode              ; decode & write the LED patterns to RAM

        acall   refresh             ; refresh display
        mov     A, #150             ; blink display
        acall   delay_ms            ; delay 150 msec

        jb      advance, minskip    ; is pushbutton pressed?
        inc     minutes             ; yes - increment the minutes
        mov     A, minutes          ;
        cjne    A, #10, minskip     ; 10 minutes?
        mov     minutes, #0         ; yes, zero minutes

        inc     tenmins             ; increment tenmins
        mov     A, tenmins          ;
        cjne    A, #6, minskip      ; 6 tenmins?
        mov     tenmins, #0         ; yes, zero tenmins

minskip:
        jnb     minset, incmins     ; test switch
        ret

; Increment the hours digits while the pushbutton is pressed.
; ***********************************************************************
inchours:
        mov     A, hours            ; hours digit
        mov     PTR_R1, #disdig2    ; point to ram buffer
        acall   decode              ; decode & write the LED patterns to RAM

        mov     A, tenhours         ; ten hours digit
        mov     PTR_R1, #disdig1    ; point to ram buffer
        acall   decode              ; decode & write the LED patterns to RAM

        acall   refresh             ; refresh display
        mov     A, #150             ; blink display
        acall   delay_ms            ; delay 150 msec

        jb      advance, hrskip     ; is pushbutton pressed?
        inc     hours               ; increment hours
        inc     twelvehr            ; increment twelvehr
        mov     A, hours            ;
        cjne    A, #10, hrsroll     ; 10 hours?
        mov     hours, #0           ; yes, zero hours
        mov     tenhours, #1        ; yes, set tenhours to 1

hrsroll:
        mov     A, twelvehr         ;
        cjne    A, #13, hrskip      ; 13 twelvehr?
        mov     twelvehr, #1        ; yes, reset twelvehr
        mov     hours, #1           ; yes, set 1 o'clock
        mov     tenhours, #99       ; yes, blank tenhours

hrskip:
        jnb     hrset, inchours     ; test switch
        ret

; External interrupt routine - happens every 16.67 msec (60 Hz).
; ***********************************************************************
ac_intr:
        push    PSW                 ; save current registers
        push    ACC                 ; save accumulator

        djnz    ac60hz, ac_retn     ; decrement interrupt counter
        mov     ac60hz, #ACFREQ     ; reset counter after 60 interrupts

        inc     seconds             ; increment seconds
        mov     A, seconds          ;
        cjne    A, #10, ac_retn     ; 10 seconds?
        mov     seconds, #0         ; yes, zero seconds

        inc     tensecs             ; increment tensecs
        mov     A, tensecs          ;
        cjne    A, #6, ac_retn      ; 6 tensecs?
        mov     tensecs, #0         ; yes, zero tensecs

        inc     minutes             ; increment minutes
        mov     A, minutes          ;
        cjne    A, #10, ac_retn     ; 10 minutes?
        mov     minutes, #0         ; yes, zero minutes

        inc     tenmins             ; increment tenmins
        mov     A, tenmins          ;
        cjne    A, #6, ac_retn      ; 6 tenmins?
        mov     tenmins, #0         ; yes, zero tenmins

        inc     hours               ; increment hours
        inc     twelvehr            ; increment twelvehr
        mov     A, hours            ;
        cjne    A, #10, rollovr     ; 10 hours?
        mov     hours, #0           ; yes, zero hours
        mov     tenhours, #1        ; yes, set tenhours to 1
        ajmp    ac_retn

rollovr:
        mov     A, twelvehr         ;
        cjne    A, #13, ac_retn     ; 13 twelvehr?
        mov     twelvehr, #1        ; yes, reset twelvehr
        mov     hours, #1           ; yes, set 1 o'clock
        mov     tenhours, #99       ; yes, blank tenhours

ac_retn:
        pop     ACC                 ; restore accumulator
        pop     PSW                 ; restore program status word
        clr     IE0                 ; clear interrupt flag
        setb    EA                  ; re-enable interrupts
        reti                        ; return from interrupt

; Convert time digit to dot pattern & store in the ram buffer.
; A = TIMEDIGIT
; PTR_R1 = RAM BUFFER LOCATION
; ***********************************************************************
decode:
        cjne    A, #00, tst1        ; 0?
        mov     dptr, #X0           ; point to table for 0
        ajmp    wrchar              ; write the LED patterns to RAM
tst1:
        cjne    A, #01, tst2        ; 1?
        mov     dptr, #X1           ; point to table for 1
        ajmp    wrchar              ; write the LED patterns to RAM
tst2:
        cjne    A, #02, tst3        ; 2?
        mov     dptr, #X2           ; point to table for 2
        ajmp    wrchar              ; write the LED patterns to RAM
tst3:
        cjne    A, #03, tst4        ; 3?
        mov     dptr, #X3           ; point to table for 3
        ajmp    wrchar              ; write the LED patterns to RAM
tst4:
        cjne    A, #04, tst5        ; 4?
        mov     dptr, #X4           ; point to table for 4
        ajmp    wrchar              ; write the LED patterns to RAM
tst5:
        cjne    A, #05, tst6        ; 5?
        mov     dptr, #X5           ; point to table for 5
        ajmp    wrchar              ; write the LED patterns to RAM
tst6:
        cjne    A, #06, tst7        ; 6?
        mov     dptr, #X6           ; point to table for 6
        ajmp    wrchar              ; write the LED patterns to RAM
tst7:
        cjne    A, #07, tst8        ; 7?
        mov     dptr, #X7           ; point to table for 7
        ajmp    wrchar              ; write the LED patterns to RAM
tst8:
        cjne    A, #08, tst9        ; 8?
        mov     dptr, #X8           ; point to table for 8
        ajmp    wrchar              ; write the LED patterns to RAM
tst9:
        cjne    A, #09, blank       ; 9?
        mov     dptr, #X9           ; point to table for 9
        ajmp    wrchar              ; write the LED patterns to RAM
blank:
        cjne    A, #99, wrdone      ; use 99 to display blank digit
        mov     dptr, #XBLANK       ; point to table for blank
        ajmp    wrchar              ; write the LED patterns to RAM

wrchar:
        mov     B, #5               ; 5 dot patterns columns per digit
        mov     index, #0           ; index to first dot pattern column
    wrloop:
        mov     A, index            ; use column index into pattern table
        movc    A, @A+dptr          ; get LED pattern from table
        mov     @PTR_R1, A          ; write LED pattern to display buffer
        dec     PTR_R1              ; advance to next buffer location
        inc     index               ; advance to next dot pattern column
        djnz    B, wrloop           ; process next column
wrdone:                             ; done with this character
        ret

; ***********************************************************************
; UTILITY Routines
; ***********************************************************************

; Even though the RAM has been zeroed, the buffer data must be inverted
; to turn the LEDS off.
; ***********************************************************************
dispclr:                            ; clear display & hidden buffer
        mov     A, #07FH            ; inverted data to turn LEDs off
        mov     PTR_R0, #disdig1
        mov     PTR_R1, #ramsize
  clrloop:
        mov     @PTR_R0, A
        dec     PTR_R0
        djnz    PTR_R1, clrloop
        ret

; Delay for approximately one millisecond times the value in A.
; All registers preserved, including flags.
; ***********************************************************************
delay_ms:
        push    PSW                 ; save program status word
        push    ACC                 ; save accumulator contents
        push    B                   ; save register B contents

    dly_loop:
        mov     B, #204
        djnz    B, $                ; 445 uS @ 11.0592 MHz
        djnz    B, $                ; 555 uS @ 11.0592 MHz
        djnz    ACC, dly_loop       ; Repeat # of times in A

        pop     B                   ; restore register B contents
        pop     ACC                 ; restore accumulator contents
        pop     PSW                 ; restore program status word
        ret

; ***********************************************************************
        end

