$page
;*****************************************************************************
;*****************************************************************************
;*                                                                           *
;*                           DRIVER MODULE                                   *
;*                                                                           *
;*                               v2.b                                        *
;*                                                                           *
;*****************************************************************************
;*****************************************************************************
;
; This module contains drivers for the following components:
;      DDS
;         Set_DDS
;      Shaft Encoders (Special and Grey)
;         Do_Special_Encoder
;         Do_Grey_Encoder
;      LCD
;         Cmnd2LCD
;         Data2LCD
;         BusyCheck
;         LCD_Init
;         LCD_Clear
;         ClearLCDLine1
;         ClearLCDLine2
;         BlankLCD
;         UnBlankLCD
;         ShowCursor
;         HideCursor
;         Disp_Msg
;      EEPROM
;         Write_EEPROM_Byte
;         Erase_EEPROM_All
;         Erase_EEPROM_Byte
;         Init_EEPROM
;         EEPROM_On
;         EEPROM_Off
;         EEPROM_Security_On
;         EEPROM_Security_Off
;         EEPROM_Disable
;         EEPROM_Enable
;
;*****************************************************************************


;*****************************************************************************
; Set_DDS - This routine sends the 5-byte DDS control word (DDS_w4 through
;           DDS_w0, LSB to MSB in each) to the DDS chip by serial data transfer.

Set_dds:
        ldhx    #DDS_w4               ;point to the start of the DDS programming words
        mov     #$05,byte_count       ;we'll process 5 bytes

nxtbyt: mov     X+,byte2send          ;get the DDS word (and increment pointer H:X)
        mov     #$08,bit_count        ;we'll process 8 bits

nxtbit: ror     byte2send             ;test next LSB for 0 or 1
        bcc     send0                 ;send a 0 if carry clear

send1:  bset    DDS_data,portf        ;else send a one
        bset    DDS_clk,portf         ;Toggle write clock
        bclr    DDS_clk,portf
        bra     check

send0:  bclr    DDS_data,portf        ;Send zero
        bset    DDS_clk,portf         ;Toggle write clock
        bclr    DDS_clk,portf

check:  dbnz    bit_count,nxtbit      ;get next DDS programming bit if not done
        dbnz    byte_count,nxtbyt     ;get next DDS programming byte if not done

        bset    DDS_load,portf        ;else done - send DDS load signal
        bclr    DDS_load,portf
        rts


$page
;*****************************************************************************
;**************     SHAFT ENCODER DRIVER     *********************************
;*****************************************************************************

;*****************************************************************************
; Read Special Encoder - Wait here for shaft to turn.  Exit with
;                             Z=0 if CCW
;                             Z=1 if CW
;                             CY=1 if pushbutton depressed
;                             CY=0 if not
;*****************************************************************************
;             +     +
;       ----  +   --+-      ----
;  A        | +  |  + |    |    |
;           | +  |  + |    |    |
;            -+--   +  ----
;             +     +
;          ---+     +---      ----
;  B          +|   |+   |    |
;             +|   |+   |    |
;             + --- +    ----
;             +     +
;*****************************************************************************
             ;Await negative transition on either A or B shaft encoder line

Do_Special_Encoder:
        brclr   ENC_PB,portF,de5      ;if pushbutton depressed, exit with CY=1
        brset   ENC_A,portF,de1       ;A still high, go check B
        tst     A_flag                ;A is low ... previously low too?
        beq     de2                   ;yes, previously low so not neg-edge. Check B next.
        clr     A_flag                ;neg edge A found!  Set flag to indicate it's now low
        bra     de4                   ;and go process rotation

de1:    mov     #$FF,A_flag           ;set A flag to indicate that it's (still) high

de2:    brclr   ENC_B,portF,de3       ;B is low ... continue checking it
        mov     #$FF,B_flag           ;B is high, so set flag indicating this
        bra     do_special_encoder    ;and do all again
de3:    tst     B_flag                ;B previously low?
        beq     do_special_encoder    ;yes, nothing to process, so do all over again
        clr     B_flag                ;neg edge B found! Set flag indicate it's now low
                                      ;and continue onward to process direction

             ;negative edge found on A or B.  Determine which direction shaft was turned.
             ;         Direction = CW if B toggled
             ;         Direction = CCW if A toggled

de4:    mov     enc_new,enc_old
        lda     portF
        and     #$30
        sta     enc_new
        eor     enc_old
        and     #$10                    ;z=1 ==> A toggled = CCW rotation = down
                                        ;z=0 ==> B toggled = CW rotation = up
        clc                             ;CY=0 for pushbutton not depressed
        rts

             ;exit with CY=1 if pushbutton pressed
de5:    sec                             ;set CY to indicate pushbutton pressed
        rts

;**************************************************************************
; Process Grey Code Encoder - Wait here for shaft to turn.  Exit with
;                             Z=0 if CCW
;                             Z=1 if CW
;**************************************************************************

Do_Grey_Encoder:
        jsr     read_encoder
        sta     temp2                 ;buffer the input
        lda     temp2
        and     #$30                  ;isolate the encoder bits
        sta     enc_new
        eor     enc_old
        beq     Do_Grey_Encoder            ;no change, wait in encoder loop

        bclr    3,enc_old             ;change detected ... which way?
        lda     enc_old
        rola
        eor     enc_new
        and     #$10                  ;b5 changed
        mov     enc_new,enc_old       ;update new/old bits
        rts

;-----------------------------------------------------------
; Read Grey Code Shaft Encoder -- returns contents of portF
;                         To deal with contact bounce on shaft encoder bits,
;                         3 successive readings must be identical before returning

Read_Encoder:
        mov     portF,enc_temp          ;make initial reading
        jsr     wait1ms
        lda     portF                   ;make first check
        sub     enc_temp
        bne     Read_Encoder            ;if not the same, try again

        jsr     wait1ms
        lda     portF                   ;make 2nd check
        sub     enc_temp
        bne     Read_Encoder            ;if not the same as 1st, try again

        lda     enc_temp
        rts

        jsr     wait1ms
        lda     portF                   ;make 3rd check
        sub     enc_temp
        bne     Read_Encoder            ;if not the same as 2nd, try again
        lda     enc_temp
        rts

$page
;**************************************************************************
;*******************      LCD Driver      *********************************
;**************************************************************************

;------------------------------------------------------------
;Display character in A to the LCD.  LCD_rw and LCD_rs must be
;set up prior to entry.

Cmnd2LCD:
        sta     LCD_char        ;temp save char to be written
        bclr    LCD_rs,porta    ;rs is low for cmnd
        jsr     busy_check      ;LCD ready for new data?
        clr     porta
        mov     #$FF,ddra       ;setup for all outputs
        bclr    LCD_rw,porta    ;change LCD back to Write mode
        bclr    LCD_rs,porta    ;ensure that rs is low for cmnd
        bra     write_LCD       ;continue
Data2LCD:
        sta     LCD_char        ;temp save char to be written
        bset    LCD_rs,porta    ;rs is hi for data
        jsr     busy_check      ;LCD ready for new data?
        clr     porta
        mov     #$FF,ddra       ;setup for all outputs
        bclr    LCD_rw,porta    ;change LCD back to Write mode
        bset    LCD_rs,porta    ;ensure that rs is hi for data
Write_LCD:
        lda     LCD_char        ;get the character
        and     #$F0            ;deal with hi nibble
        sta     temp1
        lda     porta           ;get porta image
        and     #$0F            ;clear the data nibble
        ora     temp1           ;combine with character hi nibble
        sta     porta
        bset    LCD_e,porta     ;strobe enable
        jsr     wait1us
        bclr    LCD_e,porta

        lda     LCD_char        ;get character back again
        nsa                     ;get low nibble hi
        and     #$F0
        sta     temp1
        lda     porta
        and     #$0F
        ora     temp1
        sta     porta
        jsr     wait1us
        bset    LCD_e,porta
        jsr     wait1us
        bclr    LCD_e,porta
        rts

;-----------------------------------------------------
;Busy_check Subroutine
;
;LCD read/write operations are slooooow, this subroutine
;polls the LCD busy flag to determine if previous operations are completed.

Busy_Check:
        clra
        mov     #$0F,ddra       ;tristate the data pins
        bclr    LCD_rs,porta    ;set up to read busy flag
        bset    LCD_rw,porta    ;set up for Read
        jsr     wait1us
        mov     #$FF,LCD_timer  ;init timeout counter
busy:   bset    LCD_e,porta     ;raise the Enable bit
        jsr     wait1us
        mov     porta,LCD_read  ;read the byte
        bclr    LCD_e,porta
        jsr     wait1us
        bset    LCD_e,porta     ;get next nibble to keep LCD happy
        jsr     wait1us
        bclr    LCD_e,porta
        jsr     wait1us
        dbnz    LCD_timer,busy2 ;timer not zero
        bra     not_busy        ;timer=0 so exit anyway
busy2:  brset   7,LCD_read,busy ;check for busy flag - wait if set

not_busy:
        rts

;-----------------------------------------------------------
; LCD_INIT
; Power on initialization of LCD.
; The LCD controller chip must be equivalent to an Hitachi 44780.

LCD_Init:
        jsr     Wait500ms
        mov     #$30,porta            ;func set
        bset    LCD_e,porta
        jsr     Wait64ms
        bclr    LCD_e,porta
        jsr     Wait1us

        bset    LCD_e,porta
        jsr     Wait1us
        mov     #$30,porta            ;func set
        bset    LCD_e,porta
        jsr     Wait32ms
        bclr    LCD_e,porta
        jsr     Wait1us

        bset    LCD_e,porta
        jsr     Wait1us
        mov     #$30,porta            ;func set
        bset    LCD_e,porta
        jsr     Wait32ms
        bclr    LCD_e,porta
        jsr     Wait1us

        mov     #$20,porta            ;set 4-bit mode
        bset    LCD_e,porta
        jsr     Wait16ms
        bclr    LCD_e,porta
        jsr     Wait1us

        lda     #$28                  ;1/16 duty cycle, 5x8 matrix
        jsr     cmnd2LCD

        lda     #$08                  ;disp off, curs & blank off
        jsr     cmnd2LCD

        lda     #$01                  ;clear & reset cursor
        jsr     cmnd2LCD

        lda     #$06                  ;cursor moves right, no shift
        jsr     cmnd2LCD

        lda     #$0C                  ;display on, cursor & blink off
        jsr     cmnd2LCD

        rts

;-----------------------------------------------------------
; LCD_clear -- clears the LCD display

LCD_Clear:
        lda     #$01
        jsr     cmnd2LCD
        rts

;-----------------------------------------------------------
; ClearLCDLine1 -- point to 1st char on Line 1, clear line, leave cursor at position 1

ClearLCDLine1:
        lda     #$80		      ;point to 1st char line 1
	jsr	Cmnd2LCD
	jsr	wait1ms
	ldhx    #_blank_line
	jsr	disp_msg
	lda     #$80		      ;leave cursor at home on line 1
	jsr	Cmnd2LCD
	rts

_blank_line     fcb     "                    ",0

;-----------------------------------------------------------
; ClearLCDLine2 -- point to 1st char on Line 2, clear line, leave cursor at position 1

ClearLCDLine2:
        lda     #$C0		      ;point to 1st char line 2
	jsr	Cmnd2LCD
	jsr	wait1ms
	ldhx    #_blank_line
	jsr	disp_msg
	lda     #$C0		      ;leave cursor at home on line 1
	jsr	Cmnd2LCD
	rts

;-----------------------------------------------------------
; BlankLCD

BlankLCD:
        lda     #8		      ;Blank the LCD
	jsr     Cmnd2LCD
	rts

;-----------------------------------------------------------
; UnBlankLCD

UnBlankLCD:
        lda     #12		      ;UnBlank the LCD
	jsr     Cmnd2LCD
	rts

;-----------------------------------------------------------
; ShowCursor -- produce a blinking cursor

ShowCursor:
        lda     #15		      ;Show Cursor (blinking)
        jsr     Cmnd2LCD
	jsr     wait1ms
	rts

;-----------------------------------------------------------
; HideCursor

HideCursor:
        lda     #12		       ;Hide Cursor
        lda     Cmnd2LCD
	jsr     wait1ms
	rts

;-----------------------------------------------------------
; DISP_MSG -- displays the message pointed to by H:X

Disp_Msg:
        lda     ,x
        beq     disp_msg_end           ;zero ends the string
        jsr     data2LCD
        aix     #1
        bra     disp_msg

disp_msg_end:
        rts

$page
;**************************************************************************
;*******************      EEPROM Driver      ******************************
;**************************************************************************
;
;--------------------------------------------------------------------------
; Write_EEPROM_Byte -- The target EEPROM address H:X (in the range 800-9FF)
;                      is first erased, then written with the byte supplied
;                      in reg A.

Write_EEPROM_Byte:
        psha                            ;save the data to be written
        jsr     Erase_EEPROM_Byte       ;erase the target location
        lda     #(EERAS1|EERAS0)
        coma                            ;clear EERAS1 & EERAS0 in EECR
        and     EECR
        sta     EECR
        ora     #EELAT                  ;set EELAT in EECR
        sta     EECR
        pula                            ;and retrieve the data
        sta     ,x                      ;Write data to the desired EEPROM address
        lda     #EEPGM                  ;Set the EEPGM bit in the EECR
        ora     EECR
        sta     EECR
        jsr     Wait100ms                ;Wait 10ms to program the byte.
        lda     #EEPGM                  ;clear EEPGM
        coma
        and     EECR
        sta     EECR
        jsr     Wait100us               ;Wait 100us for pgm voltage to fall.
        lda     #EELAT
        coma                            ;clear EELAT
        and     EECR
        sta     EECR
        rts

;--------------------------------------------------------------------------
; Erase_EEPROM_All -- Bulk erase all EEPROM locations (800-9FF)

Erase_EEPROM_all:
        ldhx    #$800                   ;any location in EEPROM area will do bulk erase
        lda     EECR
        ora     #(EERAS1|EERAS0)
        sta     EECR
        bra     ee_1

;--------------------------------------------------------------------------
; Erase_EEPROM_Byte -- Erase target EEPROM address H:X (in the range 800-9FF)

Erase_EEPROM_byte:
        lda     EECR
        and     #$EF                    ;clear EERAS1 (b4)
        ora     #$08                    ;set EERAS0 (b3)
        sta     EECR
ee_1:   ora     #EELAT                  ;set EELAT in EECR
        sta     EECR
        sta     ,x                      ;write any data to the target location erases
        lda     #EEPGM                  ;Set the EEPGM bit in the EECR
        ora     EECR
        sta     EECR
        jsr     Wait100ms                ;Wait 10ms for byte erase
        lda     #EEPGM
        coma                            ;clear EEPGM
        and     EECR
        sta     EECR
        jsr     Wait100us               ;Wait 100us for pgm voltage to fall.
        lda     #EELAT
        coma                            ;clear EELAT
        and     EECR
        sta     EECR
        rts

;--------------------------------------------------------------------------
; Init_EEPROM -- Initialize the EEPROM configuration registers
;                This is a nonvolatile register setting

Init_EEPROM:
        jsr     EEPROM_On               ;Turn on array for operation

        lda     #$10                    ;lower 4 bits are positions for EEPROM bank enables
        ldhx    #EENVR
        jsr     Write_EEPROM_Byte       ;enable the EEPROM
        lda    ,x                       ;puts new settings into effect

        lda     #$80                    ;set upper bit EEDIVSECD to allow the values to be modified later
        ldhx    #EEDIVHNVR
        jsr     Write_EEPROM_Byte       ;write the high divider byte
        lda     #172                    ;divider value is 170 (based on CGMXCLK clock of 4.9152 MHz)
        ldhx    #EEDIVLNVR
        jsr     Write_EEPROM_Byte       ;write the low divider byte

        rts

;--------------------------------------------------------------------------
; EEPROM_On -- Turns on the EEPROM array for operation (default = ON)

EEPROM_On:
        lda     #EEOFF
        coma
        and     EECR                    ;clear EEOF in EECR
        sta     EECR
        rts

;--------------------------------------------------------------------------
; EEPROM_Off -- Turns off the EEPROM array.  Provides for lower power
;               operation (default = ON)

EEPROM_Off:
        lda     EECR
        ora     #EEOFF                  ;set EEOFF on EECR
        sta     EECR
        rts

;--------------------------------------------------------------------------
; EEPROM_Security_On -- Turns on the EEPROM array security bit
;                       This is a nonvolatile setting. (Initially = disabled)
;                       Takes effect after reset.

EEPROM_Sec_On:
        mov    EEPRTCT,temp             ;set up temp to hold inverse of EEPRTCT bit
        com    temp                     ; to enable clearing of bit in EENVR below
        ldhx   #EENVR
        lda    ,x
        and    temp                     ;0=secure
        jsr    Write_EEPROM_Byte
        rts

;--------------------------------------------------------------------------
; EEPROM_Security_Off -- Turns off the EEPROM array security bit
;                        This is a nonvolatile setting. (Initially = disabled)
;                        Changes take effect after reset.

EEPROM_Sec_Off:
        ldhx   #EENVR
        lda    ,x
        ora    #EEPRTCT                 ;1=security disabled
        jsr    Write_EEPROM_Byte
        lda    ,x                       ;puts new settings into effect
        rts

;--------------------------------------------------------------------------
; EEPROM_Disable -- Protects the selected EEPROM block (0,1,2,3)
;                   from being written or erased.  Block indicated
;                   in corresponding bits of reg A.
;                   All four blocks are initially unprotected. (3:0 =0)
;                   This is a nonvolile setting. (Initially = disabled)
;                   Changes take effect after a reset.

EEPROM_Disable:
        and    #$0F                     ;isolate the block bits (3:0)
        sta    temp                     ;temp save selected block(s)
        ldhx   #EENVR
        lda    ,x
        ora    temp                     ;disable (protect) the selected bits/banks
        jsr    Write_EEPROM_Byte
        lda    ,x                       ;puts new seetings into effect
        rts

;--------------------------------------------------------------------------
; EEPROM_Enable -- Enables the selected EEPROM block (0,1,2,3)
;                  to be being written or erased.  Block indicated
;                  in corresponding bits of reg A.
;                  All four blocks are initially unprotected. (3:0 =0)
;                  This is a nonvolatile setting. (Initially = disabled)
;                  Changes take effect after a reset.

EEPROM_Enable:
        and    #$0F                     ;isolate the block bits (3:0)
        coma                            ;invert (0 means enable)
        sta    temp                     ;temp save selected block(s)
        ldhx   #EENVR
        lda    ,x
        and    temp                     ;enable (unprotect) the selected bits/banks
        jsr    Write_EEPROM_Byte
        lda    ,x                       ;puts new seetings into effect
        rts
