; > kbd
; *****************************************************************************
; Source for Perth 80C51-based keyboard
;
; Author:       Alex Bienek (modified from A3000 code by Tim Dobson)
; Version:      See below
; Status:       Development
; Started:      23-Aug-90
; Last updated:
; Notes:-
;     New commands AMAC, SBAT, and SFNM included.
;     Display version and debug code commented out
;     LCD control signal modulation added
;
;     3.20 - Set LTID and PCID to &82 
;            Give function keys F3 thru F10 FN-mode codes
;     
;     3.21 - 3/2/92 Remove flag ARKBenable and ID associated code.
;                   (Code was occasionally returning ARID instead of LTID)
;                   ARID also removed.
;                   Mouse buttons moved from FN- 1 2 3 to FN- Q W E, this
;                   removes ghosting of COPY by FN, 1, Cursor Left, which
;                   caused problems in draw when dragging selected objects.
;
;     3.22 - 19/2/92 Modulation of P0 for battery LCD (not used) sometimes will
;                    also modulate LEDs. ZeroRAMLoop at power on reset should
;                    prevent this happening but it doesn't always seem to do 
;                    so. Add initialisation to soft-reset and remove calls to
;                    modulation routine.
;                    BTW, flag ARKBenable has not been removed.
;
;     3.23 - 20/2/92 Translation table change, see file Kbdtran.
;     
;     3.24 - 11/5/92 Five NOPs added in main loop between setting PC_Clock high
;                    and testing it low for presence of Archie keyboard. This
;                    delay overcomes the RFI capacitor RC delay.
;    
;     3.26 - 13/5/92 Trying to cure ctrl-break problem
;                    When 80C51 receives a HRST command from host a flag is set
;                    which causes AltMainLoop to send the PC keyboard a reset
;                    command. Also PCDiagFail used to cause BombOutToError, now
;                    it is simply ignored.
;     3.27 - 19/5/92 Remove all debug calls so that it can be OTPed
;
;     3.28 - 20/5/92 Correct bug in PC reset command sender
;                    Had to simply put delay in to cover BAT time.
;
;     3.29 - 21/5/92 AltMainLoop must accept ack from reset command before
;                    continuing. Debug calls removed.
;                    
; *****************************************************************************
        GBLS    Title
        GBLS    Version
        GBLS    NewLine
        GBLS    Mess1
        GBLS    Mess2

NewLine SETS    :CHR:10 :CC: :CHR:13
Title   SETS    "80C51 Perth keyboard controller" :CC: NewLine
Title   SETS    Title :CC: "(C) Acorn Computers Ltd, 1990" :CC: NewLine
Version SETS    "Version 3.29 (21-May-92)"

        GET     Hdr.Macros8051
        GET     Hdr.SFRs
        GET     Hdr.Hardware
        GET     Hdr.Def

OV_bit  Bit     PSW, 2
MI_bit  Bit     ACC, 7
ACC_7   Bit     ACC, 7
ACC_6   Bit     ACC, 6
ACC_5   Bit     ACC, 5
ACC_4   Bit     ACC, 4
ACC_3   Bit     ACC, 3
ACC_2   Bit     ACC, 2
ACC_1   Bit     ACC, 1
ACC_0   Bit     ACC, 0

B_2     Bit     B, 2
B_1     Bit     B, 1
B_0     Bit     B, 0

        ^       &00             ; register bank 0
KBDBank #       0
MouseBank #     0

; The next two MUST be R0 and R1 in some order (used as ptrs)

StablePtr #     1               ; points to stable state for current row
XORPtr    #     1               ; points to last XOR for current row

KBDRow    #     1               ; current row number (0*8..14*8)
                                ; (bottom 3 bits hold column while outputting
                                ; key transitions)

OldData   #     1               ; old mouse quadrature data
XDir      #     1               ; mouse X velocity (used for hysteresis)
YDir      #     1               ; mouse Y velocity (used for hysteresis)
MouseX    #     1               ; mouse X position (since last sent)
MouseY    #     1               ; mouse Y position (since last sent)

        ASSERT  @ <= &08

        ^       &08             ; register bank 1
IrqBank    #    0

; The next two MUST be R0 and R1 in some order (used as ptrs)

HeadPtr    #    1               ; where key transitions get inserted
TailPtr    #    1               ; where key transitions get removed from

ResetState #    1               ; value to send or receive
Byte2      #    1               ; 2nd byte of 2 byte protocol
KBID       #    1               ; keyboard ID
SPDData    #    1               ; SPD conversion data
XAccel     #    1               ;counter used during X cursor-mouse acceleration
YAccel     #    1               ;counter used during Y cursor-mouse acceleration


        ASSERT  @ <= &10

        ^       &10             ; register bank 2
FNBank     #    0

; The next one MUST be R0 (used as ptrs)
                               
FNLogPtr   #    1               ;Pointer to bit map of keys down in FN mode 
MatrixPos  #    1               ;Temporary store for matrix offset
FNLogMask  #    1               ;Temporary store for Log byte mask
DownUse    #    1               ;Temporary store for KeyDown handler
                                ;also used by PollMouse

RWRxd      #    0               ;Byte received during Read/Write byte
XSpeed     #    1               ;Current X Speed

StillPres  #    0               ;also used to check if PC kbd still present
YSpeed     #    1               ;Current Y Speed

MouseStable #   0               ;also used for mouse buttons in PC kbd mode
XCntr      #    1               ;X counter

MouseXOR   #    0               ;also used for mouse buttons in PC kbd mode
YCntr      #    1               ;Y counter

        ASSERT  @ <= &18

        ^       &18             ;register bank 3
PCBank     #    0
PCHeadPtr  #    1               ;Where PC data gets inserted
PCTailPtr  #    1               ;Where PC data gets removed
AccelRate  #    1               ;Cursor mouse acceleration rate
PC_TOCntr  #    1               ;Counter while waiting for PC keyboard
Bit_Cntr   #    1               ;Count in the bits
PC_Byte    #    1               ;Temp store used by ReadByte & WriteByte
PC_B2      #    1               ;2nd byte of command or LookForPCKB timer
PC_Error   #    1               ;Store for error code

        ASSERT  @ <= &20

; Bit data


        ^       0               ; bit address

MouseMoved      #       1       ; set when mouse moved or RQMP received
                                ; cleared when mouse data transmitted

MouseOverflow   #       1       ; set when counter(s) have overflowed
                                ; cleared when mouse data transmitted

MouseEnabled    #       1       ; set when RQMP or MACK or SMAK received
                                ; cleared when mouse data transmitted

KeysEnabled     #       1       ; set when SACK or SMAK received
                                ; cleared when key info sent

InError         #       1       ; set when we are about to transmit the 1st FF

Resetting       #       1       ; when set, we are in reset protocol

ResetTXNotRX    #       1       ; set when TX OK during reset protocol
                                ; clear when RX OK during reset protocol

TXIdle          #       1       ; set when TI happens and there is nothing to
                                ; transmit; cleared (and TI set) when there is
                                ; something to transmit

InProtocol      #       1       ; set when in a 1-byte or 2-byte protocol

ExpectBACK      #       1       ; set after transmitting 1st of 2 byte packet
                                ; cleared after receiving BACK

SendByte2       #       1       ; set to indicate that Byte2 should be sent
                                ; cleared when byte 2 is sent

ExpectACK       #       1       ; set after last byte of protocol has been sent
                                ; also set when expect ACK after reset protocol
                                ; cleared after an ACK has been received

SendKBID        #       1       ; set to indicate that KBID should be sent

SendPDAT        #       1       ; set to indicate that PDAT should be sent

CapsLockStatus  #       1       ; status of CAPS lock (for saving over reset)
ScrollLockStatus #      1       ; status of SCROLL lock (--------""---------)
NumLockStatus   #       1       ; status of NUM lock  (----------""---------)
PCCapsLock      #       1       ; status of CAPS lock on PC kbd LEDs
PCScrollLock    #       1       ; ditto SCROLL lock
PCNumLock       #       1       ; ditto NUM lock

FNmode          #       1       ; set to indicate in FN mode
FNlock          #       1       ; set to indicate FN mode locked
FNEnabled       #       1       ; set to allow FN processing in keyboard
FNKeyDown       #       1       ; set indicates FN key down

UpState         #       1       ;clear indicates up arrow is pressed
DownState       #       1       ;ditto down arrow
RightState      #       1       ;and so on
LeftState       #       1       ;

IgnoreEsc       #       1       ;ignore escape due to ghosting by Cursor mouse
EscIgnored      #       1       ;Escape going down was ignored

PCKBEnable      #       1       ;PC keyboard enable
PC_Parity       #       1       ;Store for the PC keyboard parity bit
PCParErr        #       1       ;Indicates a parity error in the PC data
HadUpCode       #       1       ;set indicates we had a key up code
HadE0Seq        #       1       ;set indicates we had an E0 sequence code
HadE1Seq        #       1       ;set indicates we had an E1 sequence code
HadPCEcho       #       1       ;Used by LookForPCKB to indicate a response
PCErrorFlag     #       1       ;An error, other than Parity, occured
RxdPCAck        #       1       ;Received ACK ($FA) from PC keyboard
Wait1stAck      #       1       ;Waiting for 1st Ack in two byte command
Wait2ndAck      #       1       ;Waiting for 2nd Ack in two byte command
PCInProtocol    #       1       ;In a write command protocol sequence
LookPCInit      #       1       ;A look for a PC keyboard has been initiated
HadArByte       #       1       ;Had a byte from Archie during Read/Write byte
HadHRSTCom      #       1       ;Had reset command from host

ARKBEnable      #       1       ;Archimedes keybaord enable (plugged into PC socket)

DirectMode      #       1       ;Data transfered directly between Host & PC keyboard

ResKey1Dn        #       1      ; A three key sequence
ResKey2Dn        #       1      ; causes a reset of the host
ResKey3Dn        #       1      ;

LCDphase         #       1      ;Used to say if going high or low
                      
        ASSERT   @ <= &78

        ^        &78            ;Place these flags in the last eight bits
LCD20flag        #       1      ;Byte address is &2F
LCD40flag        #       1      ;When set segment should be on
LCD60flag        #       1
LCD80flag        #       1      ;LCD20flag should be LSB
LCD100flag       #       1       
LCDfill5         #       1      ;bits to fill rest of byte
LCDfill6         #       1
LCDfill7         #       1

        ASSERT  @ = &80

        ^       &2F
LCDflags        #        1      ;Byte access to above flags

; Indirectly accessed data

        ^       &30
StableStates    #       17      ; stable states of keys (0=down, 1=up)
                                ; indexed by row number
                                ; (0..15 normal, 16 mouse buttons)
                                ; (8 columns,    top 3 bits)

LastTimeXOR     #       17      ; indicates which keys changed last scan
                                ; (0=no change, 1=changed)
PCBufEnd        #       0       ; End of buffer when used for PC keyboard data

KeyTranBuffer   #       9       ; buffer for key transitions (8+1 unused slot)
KeyTranBufEnd   #       0

ResetID         #       3       ; 3-byte ID used to identify power-on resets

FNDownLog       #       17      ;bit map of keys that went down in FN mode

StackBase       #       0       ; use from here upwards for the stack

; Constants

ResetID0 * &41                  ; 3-byte 'random' pattern stored in memory -
ResetID1 * &6C                  ; if pattern not there after reset, then must
ResetID2 * &78                  ; be power-on reset

; Matrix/code table positions of special keys on Perth keyboard for FN mode processing
FNPos          * &27
NumLockPos     * &34
UpArrowPos     * &70
DownArrowPos   * &5F
RightArrowPos  * &67
LeftArrowPos   * &77
EscapePos      * &20

FNKey          * &7F
NumLockKey     * &34
UpArrowKey     * &59
DownArrowKey   * &63
RightArrowKey  * &64
LeftArrowKey   * &62

;The three keys that make up the Reset sequence
;They are trapped in Output_Key_Transtion so that it works
;on the Perth keyboard and PC-AT keyboards. Acorn key codes.
; CURRENTLY NOT IMPLEMENTED
ResSeq1Dn      *  &3B    ;LHS Ctrl
ResSeq2Dn      *  &5E    ;LHS Alt
ResSeq3Dn      *  &1F    ;Insert
ResSeq1Up      *  &BB    ;LHS Ctrl
ResSeq2Up      *  &DE    ;LHS Alt
ResSeq3Up      *  &9F    ;Insert

MaxYSpeed      * &10    ;Must be power of 2
MaxXSpeed      * &10    ;Must be power of 2
MinYSpeed      * &80    ;It gets rolled-right
MinXSpeed      * &80    ;ditto
DefaultAccel   * &02

; Constants for PC keyboard
PCBitTO        * &FF   ;Time out while receiving a byte from PC keyboard
PCPrepSTO      * &28   ;Delay while preparing to send data to keybaord
PCUpCode       * &F0   ;Always precedes key code for key-up
PCE0Seq        * &E0   ;Precedes keys which send two codes, three when going up
PCE1Seq        * &E1   ;For special sequence E1-14 given by the break key
PCBreakCode    * &14   ;the rest of that sequence
AcornBreak     * &0F   ;the Acorn BREAK key code

;PC keyboard error codes
PCStartEr      * &10   ;Start bit was not zero
PCStopEr       * &20   ;Stop bit was not one
PCNotAck       * &30   ;Byte received was not expected Ack
PCOverErr      * &40   ;PC keyboard had FIFO overrun
PCDiagFail     * &50   ;PC keyboard failed its internal diagnostics
PCPrtclErr     * &60   ;Timeout waiting for command acceptance and acknowledge

RdBitFETO      * &80   ;Timeout looking for falling edge in ReadBit
RdBitRETO      * &90   ;Timeout looking for rising edge in ReadBit
WrBitFETO      * &A0   ;Timeout looking for falling edge in WriteBit
WrBitRETO      * &B0   ;Timeout looking for rising edge in WriteBit
WrComAckTO     * &C0   ;Timeout looking for acknowledge
RdByteSt       * &D0   ;Timeout waiting for clock to go low in ReadByte
LineCtrlErr    * &E0   ;Time-out waiting for end of line control bit

; Commands host -> Keyboard
PC_Reset       * &FF   ;keybpard responds by issuing ACK which must be accepted
Resend         * &FE   ;keybaord will resend last transmission
PC_Echo        * &EE   ;Keyboard will echo EE. Used to see if keyboard present
Set_Typematic  * &F3   ;Set auto repeat delay and rate
MinTypematic   * &7F   ;1 sec delay, 2.0 Hz auto-repeat
Set_ModeInd    * &ED   ;set the LEDs. Bits 2 1 0 = Caps, Num, Scroll
; keyboard ->  host
;Resend is same as host to keybaord
Acknowledge    * &FA   ;acknowldge code from keyboard
Overrun        * &00   ;keyboard FIFO has over-run
Diag_Fail      * &FD   ;Sense amplifier failure
BAT_Complete   * &AA   ;Basic assurance test passed. Any other code - failed

; Keyboard IDs for the two types of keyboard
LTID           * &82   ;Lap-Top (or internal) keyboard ID
PCID           * &82   ;External PC Keyboard ID

TMODValue * TMOD_T1_NoGate :OR: TMOD_T1_Timer :OR: TMOD_T1_8BitReload :OR: TMOD_T0_NoGate :OR: TMOD_T0_Timer :OR: TMOD_T0_16Bit

SCONValue * SCON_9BitVariable :OR: SCON_Check9thBit :OR: SCON_TB8 :OR: SCON_REN

; Timer 0 is used to create a delay of 600s between each row scan.
; This makes a complete scan take 15 rows * 600s = 9ms + time for polling
; mouse and checking result of kbd scan, making it about 10ms ie reasonable
; debounce period. Also allows plenty of time for keyboard capacitance to
; die away.

Timer0Value *   &10000-600
Timer0Mouse *   &10000-&27D8    ;1/17th rate since only scan mouse buttons
Timer0Prot  *   &10000-&FFFF    ;Used for 65mS timeout during PC com. protocol
Timer0Reset *   &10000-2000     ;2ms used to ensure PC_Data low is Archie reset
Timer0HostRes * &10000-&FFFF    ;Lenght of Host Reset Pulse

        ORG     0

; Reset

        NoRP
        org     &00
ResetV
        ajmp    Reset

; External interrupt 0  - PC Keybaord Data line

        NoRP
        org     &03
        ljmp    ReadByte

; Timer 0 interrupt

        NoRP
        org     &0B
        clrb    ET0
        ajmp    SerialIRQ

; External interrupt 1 - PC keybaord Clock line

        NoRP
        org     &13
        ljmp    WriteByte

; Timer 1 interrupt

        NoRP
        org     &1B
        ajmp    BadReset

; Serial interrupt

        NoRP
        org     &23
        ajmp    SerialIRQ

; Timer 2 or External interrupt 2

        NoRP
        org     &2B
        ajmp    BadReset

        =       Title
Verlbl  =       Version, 0

; *****************************************************************************
;  Handle all UART receiver activity
; *****************************************************************************
        RPIs    IrqBank
RXReset
        jb      ResetTXNotRX, GoRXExit
        jb      ExpectACK, GoResetACK
        cjne    A, ResetState, TryHRST  ; if not correct char, check for HRST
        setb    ResetTXNotRX
RXExitWakeUpTX
        acall   WakeUpTX                ; restart transmission if necessary
GoRXExit
        ajmp    RXExit                  ; and exit

GoResetACK
        ajmp    ResetACK

RXReqOrACK
        jb      ACC_4, RXACK

; It's a request

        cjne    A, #K1rqid, NotRQID
        setb    SendKBID
        sjmp    RXExitWakeUpTX

NotRQID
        cjne    A, #K1prst, NotPRST
        ljmp    RXExit

TryHRST
        cjne    A, #K1hrst, StepOutToError
        setb    HadHRSTCom
        ljmp    BombOutToRestart

TryRQPD
        jb      ACC_5, TrySMDESDFN
        jb      ACC_4, TryAMACSBAT
        xrl     A, #(K1pdat :EOR: K1rqpd)
        mov     SPDData, A
        setb    SendPDAT
        sjmp    RXExitWakeUpTX

NotPRST
        cjne    A, #K1rqmp, StepOutToError
        setb    MouseEnabled
        setb    MouseMoved
        sjmp    RXExitWakeUpTX

RXACK
        cjne    A, #K1back, RXNormalACK
        jnb     ExpectBACK, StepOutToError
        clrb    ExpectBACK
        setb    SendByte2
        sjmp    RXExitWakeUpTX

StepOutToError                          ;Stepping stone for instructions
        ljmp    BombOutToError          ;that cannot reach.

RXNormalACK
        jb      ACC_3, StepOutToError
        jb      ACC_2, StepOutToError
        jnb     ExpectACK, StepOutToError
        clrb    ExpectACK
        clrb    InProtocol
        rrc     A
        mov     KeysEnabled, C
        rrc     A
        mov     MouseEnabled, C
        sjmp    RXExitWakeUpTX

RXIRQ  ; Enter here with the data to to be processed
        jb      DirectMode, RXPCDirect
        jb      Resetting, RXReset
        jb      ACC_7, TryHRST          ; if top bit set then must be reset
        jb      ACC_6, TryRQPD
        jb      ACC_5, RXReqOrACK

        jb      ACC_4, BombOutToError
        jb      ACC_3, BombOutToError

;Must be 'Set LEDs' command
        mov     C, ACC_0
        mov     CapsLockStatus, C
        mov     CapsLockLED, C
        mov     C, ACC_1
        mov     NumLockStatus, C
        mov     NumLockLED, C
        mov     C, ACC_2
        mov     ScrollLockStatus, C
        mov     ScrollLockLED, C
        ljmp    RXExit                 ;only long jump while debugging in 'SerialIRQ'

; New Commands for Perth
TrySMDESDFN
        jb      ACC_4, BombOutToError
        jb      ACC_3, ItIsSDFN
; Power-down command code was here but was removed because no longer valid
        sjmp    BombOutToError

TryAMACSBAT
        jb      ACC_3, ItIsSBAT         ;its set battery segement
;must be set mouse acceleration rate
        anl     A, #&07                 ;isolate new accel. rate
        mov     AccelRate, A            ;and use it
        sjmp    RXExit

ItIsSDFN ;it is 'Set Direct & FN mode enable flags'
        jb      ACC_0, EnableFNmode
        clrb    FNEnabled               ;Disable FN mode
        clrb    FNmode                  ;and clear it
        clrb    FNlock
        sjmp    Check_PC_Direct
EnableFNmode
        setb    FNEnabled               ;allow FN mode
      
Check_PC_Direct
        jb      ACC_1, EnablePCDirect
        clrb    DirectMode              ;disable direct mode for PC keyboard
        sjmp    RXExit
EnablePCDirect
        setb    DirectMode              ;enable direct mode for PC keyboard
        sjmp    RXExit

ItIsSBAT
        push    DPH                     ;save pointer
        push    DPL
        mov     DPTR, #FNMask           ;use these masks. Well why not!!
        anl     A, #&07                 ;isolate segment bits
        movc    A, @A+DPTR              ;get a byte with 1 bit set
        anl     A, #&1F                 ;don't allow LED bits to be set
        mov     LCDflags, A             ;Set all flags in one go
        pull    DPL                     ;Restore pointer
        pull    DPH
        sjmp    RXExit

RXPCDirect ;the byte received should go direct to PC keyboard
        SetRP   PCBank
        acall   RequestToSend           ;initiate transfer to PC keyboard
        SetRP   IrqBank
        sjmp    RXExit

; *****************************************************************************
;  Handle erroneous situations
; *****************************************************************************
BombOutToError
        setb    InError
BombOutToRestart
        clrb    EA                      ; disable IRQs for after the reti
        mov     StackBase+0, #(Restart :AND: &FF)
        mov     StackBase+1, #(Restart / &100)
        mov     SPTR, #StackBase+1      ; point stack at fake return address
        reti
; *****************************************************************************
;  Come here if UART interrupts
;  Identify source (Rx or Tx) and service it 
; *****************************************************************************
SerialIRQ
        clrb    EA
        push    PSW
        push    ACC
        SetRP   IrqBank
        jnb     RI, RXExit
        clrb    RI
        mov     A, SBUF
        setb    EA
        ljmp    RXIRQ
RXExit
        setb    EA
        jnb     HadArByte, CheckTX   ;check if we rxd another byte during PC Coms
        clrb    HadArByte            ;if we did, clear the flag
        mov     A, RWRxd             ;get the byte
        ljmp    RXIRQ                ;and process it
CheckTX
        jbc     TI, TXIRQ
TXExit
        pull    ACC
        pull    PSW
AReti
        reti

; *****************************************************************************
;  Handle UART transmitter interrupts
; *****************************************************************************
        RPIs    IrqBank
TXIRQ
        jb      DirectMode, TXPCDirect
        jbc     InError, TXInError
        jb      Resetting, TXReset
        jb      InProtocol, TXInProtocol
        jbc     SendKBID, TXSendKBID
        jbc     SendPDAT, TXSendPDAT
        jnb     KeysEnabled, DontSendKeys

; Now check if any key transitions in buffer

        mov     A, TailPtr              ; if pointers different then
        cjne    A, HeadPtr, TXKeyTrans  ; there are transitions to transmit

DontSendKeys
        jnb     MouseEnabled, TXGoIdle
        jnb     MouseMoved, TXGoIdle

; Send mouse data

        mov     A, MouseY               ; shift zero into top bit of Y coord
        clrb    C
        rrc     A
        mov     Byte2, A                ; and send that after this

        clr     A
        mov     MouseY, A               ; zero MouseY
        xch     A, MouseX               ; read MouseX and zero it
        clrb    C                       ; shift zero into top bit of X coord
        rrc     A

        clrb    MouseMoved
        clrb    MouseEnabled
        clrb    MouseOverflow
TX2Byte
        setb    InProtocol
        setb    ExpectBACK
        sjmp    TXSendAcc

TXInError
        mov     A, #K1hrst
TXSendAccWaitForRX
        clrb    ResetTXNotRX
TXSendAcc
        mov     SBUF, A
        ajmp    TXExit

TXReset
        jnb     ResetTXNotRX, TXGoIdle
        mov     A, ResetState
        dec     ResetState
        cjne    ResetState, #K1rak2-1, TXSendAccWaitForRX
        setb    ExpectACK
        sjmp    TXSendAccWaitForRX

TXInProtocol
        jbc     SendByte2, TXSendByte2
TXGoIdle
        setb    TXIdle
        ajmp    TXExit

TXSendByte2
        mov     A, Byte2
        setb    ExpectACK
        sjmp    TXSendAcc

TXSendKBID
        mov     A, KBID
        setb    ExpectACK
        setb    InProtocol
        sjmp    TXSendAcc

TXSendPDAT
        mov     A, SPDData
        setb    ExpectACK
        setb    InProtocol
        sjmp    TXSendAcc

; transmit a key transition

TXKeyTrans
        mov     A, @TailPtr
        inc     TailPtr
        cjne    TailPtr, #KeyTranBufEnd, NoRemoveWrap
        mov     TailPtr, #KeyTranBuffer
NoRemoveWrap
        mov     C, ACC_7                ; 0 => down, 1 => up
        push    ACC
        anl     A, #&0F                 ; get lo-nibble (to be send 2nd)
        orl     A, #K1kdda              ; merge with protocol bits
        mov     ACC_4, C                ; specify up or down
        mov     Byte2, A

        pull    ACC
        swap    A                       ; send hi-nibble first
        anl     A, #&07                 ; hi nibble is only 3 bits
        orl     A, #K1kdda              ; merge with protocol bits
        mov     ACC_4, C
        sjmp    TX2Byte

TXPCDirect
        SetRP   PCBank
        mov     A, PCHeadPtr            ;see if data in buffer
        cjne    A, PCTailPtr, TXPCData  ;jump if there is
        sjmp    TXGoIdle                ;else save this interrupt for later
TXPCData
        mov     A, @PCTailPtr           ;get the data
        inc     PCTailPtr
        cjne    PCTailPtr, #PCBufEnd, NoPCRmvWrap1
        mov     PCTailPtr, #StableStates ;marks end of buffer
NoPCRmvWrap1
        sjmp    TXSendAcc               ;send to host

; *****************************************************************************
;  Processor Reset routines
; *****************************************************************************
        RPIs    IrqBank

GoTryHRST
        ajmp    TryHRST

ResetACK
        jb      ACC_7, GoTryHRST
        jb      ACC_6, GoBombOutToError
        jnb     ACC_5, GoBombOutToError
        jnb     ACC_4, GoBombOutToError
        jb      ACC_3, GoBombOutToError
        jb      ACC_2, GoBombOutToError

        push    ACC

        SetRP   MouseBank
        mov     A, MouseInput
        orl     A, #(&FF :EOR: MouseMask) ; just get mouse data
        mov     OldData, A
        clr     A
        mov     XDir, A
        mov     YDir, A
        mov     MouseX, A
        mov     MouseY, A
        clrb    MouseMoved
        clrb    MouseOverflow
        clrb    Resetting

        SetRP   IrqBank
        mov     HeadPtr, #KeyTranBuffer ; initialise buffer pointers
        mov     TailPtr, #KeyTranBuffer
        pull    ACC
        ajmp    RXNormalACK

GoBombOutToError
        ljmp    BombOutToError

; *****************************************************************************

        NoRP
WakeUpTX
        jbc     TXIdle, TXasleep
        ret
TXasleep
        setb    TI
        ret

; *****************************************************************************
; Come here if we get an unexpected IRQ (shouldn't happen)
; *****************************************************************************
BadReset
        mov     IE, #0                  ; disable all IRQs
        mov     SPTR, #StackBase-1      ; set up stack pointer
        lcall   AReti                   ; make sure the processor doesn't think
                                        ; we're in an IRQ routine ?

; Come here on a reset

Reset
        setb    HostReset               ;ensure host not in reset
        clrb    DirectMode              ;Mode survives re-start
        mov     IE, #0                  ; disable all interrupts
        mov     SPTR, #StackBase-1
        mov     IP, #0                  ; all interrupts low priority
        setb    PX0                     ; PC data line high priority interrupt
        setb    PX1                     ; PC clock line high priority interrupt
        clrb    IT0                     ; PC data line interrupt level triggered
        clrb    IT1                     ; PC clock line interrupt level triggered
        mov     PCON, #PCON_BaudNormal
        mov     TCON, #TCON_TR0 :OR: TCON_TR1 ; start timers
        mov     TMOD, #TMODValue
        mov     SCON, #SCONValue
        mov     Timer1Latch, #&FF       ; baud rate = 12E6/(384*(256-latch))
                                        ; = 31250
        mov     Port0, #&FF             ; all inputs
        mov     Port1, #&FF             ; all inputs
        mov     P2, #MouseMask          ; set mouse quad lines to inputs,
                                        ; matrix output to 0
        mov     P3, #&FF                ; set mouse buttons and serial in to
                                        ; inputs, LEDs to on

; Now check 3-byte ID - if intact, then it's a soft reset, else it's a power-on

        SetRP   0
        mov     R0, #ResetID
        cjne    @R0, #ResetID0, PowerOnReset
        inc     R0
        cjne    @R0, #ResetID1, PowerOnReset
        inc     R0
        cjne    @R0, #ResetID2, PowerOnReset
        sjmp    SoftReset

; It's power-on, so clear out RAM (including stack!)

PowerOnReset
        mov     R0, #&7F
        clr     A
ZeroRAMLoop
        mov     @R0, A
        djnz    R0, ZeroRAMLoop

; Initialise 3-byte ID

        mov     ResetID+0, #ResetID0
        mov     ResetID+1, #ResetID1
        mov     ResetID+2, #ResetID2
SoftReset

; now put back old LED status

        mov     C, CapsLockStatus
        mov     CapsLockLED, C
        mov     C, ScrollLockStatus
        mov     ScrollLockLED, C
        mov     C, NumLockStatus
        mov     NumLockLED, C

;        lcall   DispVer
        lcall   ReadKBID                ; corrupts register bank
        NoRP

        clrb    HadHRSTCom
        setb    TXIdle
        setb    InError                 ; indicate sending 1st &FF
Restart
        setb    HostReset
        mov     SPTR, #StackBase-1
        mov     ResetState, #K1hrst
        setb    Resetting
        setb    ResetTXNotRX
        clrb    ExpectACK
        clrb    ExpectBACK
        clrb    SendByte2
        clrb    InProtocol
        clrb    SendKBID
        clrb    SendPDAT
        clrb    ResKey1Dn
        clrb    ResKey2Dn
        clrb    ResKey3Dn
                     
        clrb    LCDphase
        mov     LCDflags, #&00         ;clear all flags
        setb    LCD20
        setb    LCD40
        setb    LCD60
        setb    LCD80
        setb    LCD100

        clrb    PC_Clock               ;inhibit PC keyboard if present
        setb    PC_Data
        clrb    PCKBEnable             ;disable PC keyboard communication
        clrb    ARKBEnable             ;and Archie keyboard
        lcall   InitPCFlags            ;initialise all the PC keyboard flags
        lcall   InitKeys               ;initialise all the normal keyboard flags

        acall   WakeUpTX

        setb    ES                      ; enable serial port interrupts
        clrb    EX0                     ; disable PC interrupts
        clrb    EX1
        setb    EA                      ; enable IRQs
                                     
WaitForResetToFinish
        jb      Resetting, WaitForResetToFinish

; *****************************************************************************
;  Main Loop if code - scanning keyboard and polling mouse
; *****************************************************************************
MainLoop
        SetRP   MouseBank
        acall   PollMouse
        jnb     KeysEnabled, MainLoop   ; if keys not wanted, just poll mouse
        jnb     TF0, MainLoop           ; timer not exhausted, carry on polling

; It's time to scan the next row of the keyboard

        ASSERT  KBDBank=MouseBank

; See if anything needs doing with the mouse
        acall   GoCursorMouse

; and then continue with scanning
        cjne    KBDRow, #16*8, MainKeyboard    

        nop
        nop

        jnb     LookPCInit, CheckArchie ;Jump if we havn't already initiated
        setb    PC_Data                 ;else stop driving PC Data line low
        clrb    LookPCInit              ;cancel the already initiated flag
        jnb     HadPCEcho, CheckMouse   ;If no echo there is no PC keyboard
        ljmp    AltMainLoop             ;else there is a PC keyboard so use it

CheckArchie
        setb    PC_Clock                ;if we set the clock line high
        nop
        nop
        nop
        nop
        nop
        jb      PC_Clock, TimeToTry     ;but it is low
        setb    ARKBEnable              ;we must have an Archie keyboard plugged in
        ljmp    ARKBTransfer            ;Begin bit copy loop

TimeToTry                              
        djnz    PC_B2, CheckMouse
        acall   LookForPCKB             ;this happens every 2.5 secs approx.

CheckMouse                                              
        mov     A, MouseButtonInput             ;Read mouse buttons
        orl     A, #&FF :EOR: MouseButtonMask   ;set other bits to 1
        sjmp    DoScan

MainKeyboard
        mov     A, Port1
DoScan
        xrl     A, @StablePtr           ; new difference from stable state
        xch     A, @XORPtr              ; swap new and old differences
        anl     A, @XORPtr              ; get stable differences
        jz      KeyNoChange             ; none, so we've done this row

; now XOR both stable state and last XOR with stable difference, preserving A
        xch     A, @XORPtr
        xrl     A, @XORPtr
        xch     A, @XORPtr

        xch     A, @StablePtr
        xrl     A, @StablePtr           ; A now holds new stable value
        mov     B, A                    ; save it away so we know up or down
        xch     A, @StablePtr

        dec     KBDRow                  ; we don't want first increment
        clrb    C                       ; to fill top bits with zero
IncLoop
        inc     KBDRow                  ; increment offset into table
        rrc     A                       ; shift next bit into carry
        jc      FoundBit                ; if set, it's a key transition
NextBit
        xch     A, B                    ; shift stable state byte
        rr      A                       ; preserves carry (should be clear)
        xch     A, B
        jnz     IncLoop                 ; if A still has bits in it then loop

KeyNoChange
        mov     A, KBDRow
        anl     A, #&F8                 ; knock out the column bits
        dec     StablePtr
        dec     XORPtr
        clrb    C
        subb    A, #&08                 ; go to previous row
        acall   SetRow
        mov     A, StablePtr
        sjmp    MainLoop

FoundBit
        push    ACC                     
        mov     A, KBDRow               ;KBDRow is offset into table
        cjne    A, #EscapePos, NotEscKey

; this ignoring Escape key code is required because the FN-mode cursor
; key control of the mouse ghosts the escape key
        jnb     IgnoreEsc, NotEscKey    ;if we are not to ignore escape
        jbc     EscIgnored, InvalidKey  ;if we ignored it going down this
                                        ;flag will be set, so clear it & branch
        setb    EscIgnored              ;Set it as key goes down
        sjmp    InvalidKey              ;and ignore the key

NotEscKey
        mov     C, B_0                  ;carry set indicates key up
        jc      ItsKeyUp
        acall   KeyDown
        jb      ACC_7, InvalidKey       ;top bit set indicates invalid key
        acall   OutputKeyTransition     ;otherwise send it
        sjmp    InvalidKey              ;and exit in the same manner

ItsKeyUp
        acall   KeyUp
        jb      ACC_7, InvalidKey       ;top bit set indicates invalid key
        acall   OutputKeyTransition     ;otherwise send it

InvalidKey
        pull    ACC
        clrb    C
        sjmp    NextBit

; *****************************************************************************
;  Handle key down
;  Matrix position in Acc.  Carry is clear to indicate key down
; *****************************************************************************
KeyDown
        mov     DownUse, A              ;Keep copy of matrix pos. Direct access
        jnb     FNmode, SkipLog         ;If not in FN mode don't log key down
        acall   GetAndAdjLog            ;Logs key down in FN mode and returns
        sjmp    DoKeyChecks             ;key code from FNtran table
SkipLog
        mov     DPTR, #KBDTran
        movc    A, @A+DPTR              ;Get normal key code
DoKeyChecks
        xch     A, DownUse              ;Swap code for matrix position
        cjne    A, #FNPos, NotFNKey     ;Branch if not FN key

; It is the FN key gone down so set FN mode if allowed
        setb    FNKeyDown               ;First set "FN Is Down' flag
        setb    IgnoreEsc               ;ignore the Escape key
        jnb     FNEnabled, ExitKeyDown  ;If FN mode not allowed then quit
        setb    FNmode                  ;else go into FN mode
        sjmp    ExitKeyDown             ;and quit

NotFNKey
        cjne    A, #NumLockPos, NotNumLock
; It is Num-Lock so if FN is down and FN mode allowed, lock it
        jnb     FNEnabled, ExitKeyDown  ;If not allowed then quit
        jnb     FNKeyDown, ExitKeyDown  ;If FN not down then quit
        cpl     FNlock                  ;else toggle lock
        jnb     FNlock, ExitKeyDown     ;If we just cleared it then leave
        setb    FNmode                  ;else set FNmode just to be sure
        sjmp    ExitKeyDown             ;and then leave.

NotNumLock
        jnb     FNmode, ExitKeyDown     ;if not in FN mode cursor key down
        acall   CurMouseHndlr           ;doesn't matter, else check it

ExitKeyDown
        xch     A, DownUse              ;Retrieve key code (Normal or FN mode)
        clrb    C                       ;Make sure key is indicated as down
        ret
        
; *****************************************************************************
;  Handle key up
;  Matrix position in Acc.  Carry is set to indicate key up
; *****************************************************************************
KeyUp
        cjne    A, #FNPos, ChkCursor    ;if its not FN key up then maybe cursor
        clrb    FNKeyDown               ;Clear 'FN Is Down' flag
        clrb    IgnoreEsc               ;Can no longer ghost Escape
        jb      FNlock, GetCode         ;if FN mode locked do nothing
        clrb    FNmode                  ;else clear transitory FN mode
        sjmp    GetCode                 ;then get code and clear log

ChkCursor
        acall   CurMouseHndlr           ;call Cursor mouse control handler
        setb    C                       ;return with matrix position still in A

GetCode
        acall   GetAndAdjLog            ;clear the log and get the key code
        setb    C
        ret

; *****************************************************************************
; Cursor mouse handler - check if key pressed was a cursor key
; Only called by KeyDown if in FN mode, hence only activate flags if mouse move
; Always called by KeyUp since always stop mouse move when key is released
; GetAndAdjLog will handle key code if key went down in FN mode
; *****************************************************************************
CurMouseHndlr
        cjne    A, #UpArrowPos, TryDown
        mov     C, B_0
        mov     UpState, C                      ;its up arrow so adjust flag
        ret

TryDown
        cjne    A, #DownArrowPos, TryRight
        mov     C, B_0
        mov     DownState, C                    ;or ditto down
        ret

TryRight
        cjne    A, #RightArrowPos, TryLeft
        mov     C, B_0
        mov     RightState, C                   ;or right
        ret

TryLeft
        cjne    A, #LeftArrowPos, ExitCurHndlr
        mov     C, B_0
        mov     LeftState, C                    ;or left
ExitCurHndlr
        ret

; *****************************************************************************
; On entry A contains a matrix position.
; When KeyUp calls this routine B_0 is set so this routine checks the FN log to
; see if the key was presssed during FN mode. If so it returns the FN code,
; otherwise it returns the normal code. It also clears the log.
; When KeyDown calls this routine B_0 is clear so this routine sets the FN log.
; Since KeyDown only calls it in FNmode it uses the FN translation table to
; provide the key code.
; *****************************************************************************
GetAndAdjLog
        SetRP   FNBank                  ;select FN register bank  * WIPE FLAGS *
        mov     MatrixPos, A            ;preserve matrix position
        mov     FNLogPtr, #FNDownLog    ;initialise pointer to start of log
        anl     A, #&F8                 ;clear bottom 3 bits
        rr      A                       ;shift down top five bits
        rr      A
        rr      A
        add     A, FNLogPtr             ;add to log pointer to select byte
        mov     FNLogPtr, A             ;Log pointer now points to correct byte
        mov     A, MatrixPos
        anl     A, #&07                 ;bottom three bits offset into mask table
        mov     DPTR, #FNMask           ;get pointer to mask table
        movc    A, @A+DPTR              ;get mask
        jb      B_0, ChkForKeyUp        ;B_0 set indicates KeyUp

; This section is executed if B_0 clear, ie key down
        orl     A, @FNLogPtr            ;else we are working for KeyDown so set bit
        mov     DPTR, #FNTran           ;only called in FN mode so use FN table
        jnb     B_0, PutBack            ;put back byte into bit map & get code
; End of KeyDown section

ChkForKeyUp
        mov     FNLogMask, A            ;keep a copy of the mask
        anl     A, @FNLogPtr            ;check if bit was set
        jz      NormalTable             ;if zero it was not so use normal table
        mov     DPTR, #FNTran           ;else use FN translation table
        jnz     DoTheClear
NormalTable
        mov     DPTR, #KBDTran
DoTheClear
        mov     A, FNLogMask            ;retrieve mask
        cpl     A                       ;invert it
        anl     A, @FNLogPtr            ;and clear the appropriate bit
PutBack
        mov     @FNLogPtr, A            ;and put back into bit map
        mov     A, MatrixPos            ;Retrieve matrix position
        movc    A, @A+DPTR              ;and get the key code
        SetRP   KBDBank                 ;Restore bank ** WIPE FLAGS **
        ret

; *****************************************************************************
;  Send key transition to host
;  On entry Carry set indicates key up, clear indicates down
;  If output buffer is full, wait polling mouse until space becomes available
; *****************************************************************************
OutputKeyTransition
        mov     ACC_7, C                ; bit 7 set => key up not down
        SetRP   IrqBank
        mov     @HeadPtr, A
        mov     A, HeadPtr
        inc     A
        cjne    A, #KeyTranBufEnd, NoInsertWrap
        mov     A, #KeyTranBuffer
NoInsertWrap
        cjne    A, TailPtr, BufferNotFull
; buffer is full so poll mouse while we're waiting
; Since there is data in the buffer the transmitter cannot be idle
        push    DPH
        push    DPL
        push    ACC
        push    PSW
        SetRP   MouseBank
        acall   PollMouse
        pull    PSW
        pull    ACC
        pull    DPL
        pull    DPH
        sjmp    NoInsertWrap

BufferNotFull
        mov     HeadPtr, A
        acall   WakeUpTX
        SetRP   KBDBank
        ret

; *****************************************************************************
;
;       InitKeys - Initialise StableStates to 1 and LastTimeXOR to 0,
;       set KBDRow to 16*8, StablePtr to StableStates+16,
;       XORPtr to LastTimeXOR+16, set hardware row select to 16,
;       then initialise timer 0
;
; *****************************************************************************
InitKeys
        SetRP   KBDBank
        mov     KBDRow, #17             ; temporary counter
        mov     StablePtr, #StableStates+16
        mov     XORPtr, #LastTimeXOR+16
        clr     A
InitKeyLoop
        mov     @XORPtr, A
        mov     @StablePtr, #&FF
        dec     XORPtr
        dec     StablePtr
        djnz    KBDRow, InitKeyLoop

        SetRP   FNBank
        mov     FNLogPtr, #FNDownLog+16
        mov     DownUse, #17            ;temporary counter
        clr     A
BlankFNMap
        mov     @FNLogPtr, A
        dec     FNLogPtr
        djnz    DownUse, BlankFNMap     ;loop to wipe FNLogMap
        SetRP   KBDBank

        clrb    FNmode                 ;initialise all the FN mode flags
        clrb    FNlock
        clrb    FNKeyDown
        clrb    FNLED
        setb    FNEnabled               ;default to FN mode allowed

        setb    UpState                 ;set all cursor keys to up
        setb    DownState
        setb    RightState
        setb    LeftState
        clr     A
        mov     XCntr, A
        mov     YCntr, A
        mov     PC_Error, A
        mov     XSpeed, #MinXSpeed
        mov     YSpeed, #MinYSpeed
        mov     AccelRate, #DefaultAccel
        mov     XAccel, AccelRate
        mov     YAccel, AccelRate

        clrb    IgnoreEsc
        clrb    EscIgnored

        setb    C
SetRow
        jnc     SetRowAcc
        mov     A, #16*8
        mov     StablePtr, #StableStates+16
        mov     XORPtr, #LastTimeXOR+16
SetRowAcc
        mov     KBDRow, A
        swap    A
        rl      A                       ; move row to bits 0..3
        orl     A, #MouseMask
        mov     MatrixOutput, A

; Now start timer again

        clrb    TR0
        mov     TL0, #(Timer0Value :AND: 255)
        mov     TH0, #(Timer0Value/256)
        clrb    TF0
        setb    TR0
        ret

; *****************************************************************************
;
;       ReadKBID - Read keyboard ID and store in KBID variable
;
; *****************************************************************************

ReadKBID
        push    PSW                     ;Preserve registers
        push    ACC
        SetRP   KBDBank
        jb      PCKBEnable, ReturnPCID  ; Return different IDs for PC keyboard
                                        ;
        mov     A, #LTID                ; or the default (Lap-Top) keyboard
        sjmp    SetKBID                 ;
                                        ; Laptop and PC IDs are the same.
ReturnPCID                              
        mov     A, #PCID                

SetKBID
        mov     KBID, A
        pull    ACC
        pull    PSW
        ret

; *****************************************************************************
; CursorMouse
;       Simulate mouse movement depending on what cursor keys are down
;       State flags are 0 if down, 1 if up
;       Mouse/Keyboard bank will be selected - assume no bank for this routine
; *****************************************************************************
        NoRP                            ;assume no particuler register bank
CMYDir
        jb      UpState, ChkDown        ;jump if up-arrow not pressed
        jnb     DownState, ExitYDir     ;if down is also pressed do nothing
        mov     YDir, #&02              ;else move up
        sjmp    DoYMove
ChkDown
        jb      DownState, ExitYDir     ;jump if down-arrow not pressed
        mov     YDir, #&FE              ;else set for move down
DoYMove
        mov     A, YSpeed               ;get Y speed
        cjne    A, #MaxYSpeed, AccelY   ;if not max then accelerate
        ret
AccelY
        djnz    YAccel, SkipYSpeedInc
        mov     A, YSpeed
        rr      A
        mov     YSpeed, A
        mov     YAccel, AccelRate
SkipYSpeedInc
        ret
ExitYDir
        mov     YSpeed, #MinYSpeed
        mov     YAccel, AccelRate
        ret

CMXDir
        jb     LeftState, ChkRight      ;jump if left-arrow not pressed
        jnb    RightState, ExitXDir     ;if right also pressed do nothing
        mov    XDir, #&FE               ;else move left
        sjmp   DoXMove
ChkRight
        jb      RightState, ExitXDir    ;if not pressed do nothing
        mov     XDir, #&02
DoXMove
        mov     A, XSpeed               ;get X speed
        cjne    A, #MaxXSpeed, AccelX   ;if not max then accelerate
        ret
AccelX
        djnz    XAccel, SkipXSpeedInc
        mov     A, XSpeed
        rr      A
        mov     XSpeed, A
        mov     XAccel, AccelRate
SkipXSpeedInc
        ret
ExitXDir
        mov     XSpeed, #MinXSpeed
        mov     XAccel, AccelRate
        ret

GoCursorMouse
        inc     XCntr
        mov     A, XCntr                ;get the counter
        cjne    A, XSpeed, NoXMove      ;if not counted-out jump
        mov     XCntr, #0
        acall   CMXDir                  ;else if moving accelerate
NoXMove
        inc     YCntr
        mov     A, YCntr
        cjne    A, YSpeed, NoYMove
        mov     YCntr, #0
        acall   CMYDir
NoYMove
        mov     A, YDir                 ;only call mouse code if some thing
        jnz     CMMove                  ;has changed
        mov     A, XDir
        jnz     CMMove
        ret
CMMove
        sjmp    CursorEntry             ;Don't return here, no point.

; *****************************************************************************
;
;       PollMouse - Routine to check mouse quadrature signals and
;       update mouse coordinates
;
; in:   RP = MouseBank
;
; *****************************************************************************
        RPIs    MouseBank
PollMouse
        mov     A, MouseInput           ; load mouse data
        orl     A, #(&FF :EOR: MouseMask) ; ignore other bits NNNN1111
        cjne    A, OldData, Changed     ; if same
        ret                             ; then exit

Changed
        mov     DPTR, #NewXYTable
        xch     A, OldData              ; store back new data and get old   OOOO1111
        swap    A                       ; move old data to other nibble     1111OOOO
        anl     A, OldData              ; get old and new data in 2 nibbles NNNNOOOO        
        push    ACC                     ; XDir bits are xxNNxxOO (O=old, N=New)
        anl     A, #&03                 ; isolate the new X bits
        mov     DownUse, A              ; keep a copy
        pull    ACC
        push    ACC
        anl     A, #&30                 ;isolate the old X bits
        rr      A                       ;shift into top two bits of lower nybble
        rr      A
        orl     A, DownUse              ;merge in A to give 0000NNOO

        xch     A, XDir
        jnz     XAdd10                  ;If XDir<>0 then must be +ve or -ve
        xch     A, XDir                 ;else retrieve data
        sjmp    GetXDir
XAdd10  jnb     MI_bit, XAdd20          ;if +ve Add &20
        xch     A, XDir
        add     A, #&10                 ; else add &10
        sjmp    GetXDir
XAdd20  xch     A, XDir
        add     A, #&20                ; then add &20

GetXDir
        movc    A, @A+DPTR              ; load value 
        mov     XDir, A

        pull    ACC                     ; YDir bits are NNxxOOxx (O=old, N=New)
        push    ACC
        anl     A, #&0C                 ; isolate the new Y bits
        mov     DownUse, A              ; keep a copy
        pull    ACC
        anl     A, #&C0                 ;isolate the old Y bits
        rr      A                       ;shift into lower two bits of upper nybble
        rr      A
        orl     A, DownUse              ;merge in A to give 00NNOO00
        rr      A                       ;shift whole thing into lower nybble
        rr      A

        xch     A, YDir
        jnz     YAdd10                  ;If YDir<>0 then must be +ve or -ve
        xch     A, YDir                 ;else retrieve data
        sjmp    GetYDir
YAdd10  jnb     MI_bit, YAdd20          ;if +ve Add &20
        xch     A, YDir
        add     A, #&10                 ; else add &10
        sjmp    GetYDir
YAdd20  xch     A, YDir
        add     A, #&20                ; then add &20

GetYDir
        movc    A, @A+DPTR              ; load value 
        mov     YDir, A

CursorEntry  ;Enter here from cursor mouse control routine
        jb      MouseOverflow, NoAdd
        push    IE                      ; save interrupt state
        clrb    ES                      ; disable serial IRQs while we modify
                                        ; mouse coordinates
        mov     A, MouseX
        add     A, XDir
        jb      OV_bit, XOverflow
XOverflowReturn
        mov     MouseX, A
        mov     A, MouseY
        add     A, YDir
        jb      OV_bit, YOverflow
YOverflowReturn
        mov     MouseY, A
        setb    MouseMoved              ; only set this after coords updated
        jnb     MouseEnabled, DontWakeTX
        acall   WakeUpTX
DontWakeTX
        pull    IE                     ; restore IRQs
NoAdd
        clr     A
        mov     XDir, A                 ;Zero the bytes for the CM
        mov     YDir, A
        ret

XOverflow
        setb    MouseOverflow
        jb      MI_bit, XMaxPos ; if -ve now then should be max +ve
        mov     A, #&80         ; else +ve now so should be max -ve
        sjmp    XOverflowReturn
XMaxPos
        mov     A, #&7E
        sjmp    XOverflowReturn
        
YOverflow
        setb    MouseOverflow
        jb      MI_bit, YMaxPos ; if -ve now then should be max +ve
        mov     A, #&80         ; else +ve now so should be max -ve
        sjmp    YOverflowReturn
YMaxPos
        mov     A, #&7E
        sjmp    YOverflowReturn
; *****************************************************************************
; LCD output modulation - come here 100 times per second
; *****************************************************************************
ModLCD
        jnb     LCDphase, SetLCDlow 
        clrb    LCDphase
        orl     P0, #&1F                   ;Set all LCD bits high, leave LEDs
        ret
; 
SetLCDlow 
        setb    LCDphase
        mov     A, LCDflags
        xrl     P0, A                      ;any LCD flags set will be set to zero
        ret

; *****************************************************************************
; Alternative main loop when using PC keybaord
; *****************************************************************************
AltMainLoop
        lcall   InitPCBuf                  ;initialise the buffers etc
        jb      DirectMode, DirectMainLoop ;if in Direct-Mode do nothing else
        RPIs    PCBank                     ;calls InitPCFlags, leaves PCBank sel.
        setb    PCKBEnable                 ;and use the PC keyboard
        jnb     HadHRSTCom, DontReset      ;if we had the reset command we must
        clrb    HadHRSTCom                 ;clear the flag and
        acall   ResetPCKbd                 ;reset the PC keyboard

; Must allow timeout for this loop
        clrb    TR0                        ;initialise the counter for
        mov     TL0, #(Timer0Prot :AND: 255) ;protocol timeout
        mov     TH0, #(Timer0Prot/256)
        clrb    TF0
        setb    TR0

; Must wait until reeset command acknowledge is accepted before sending next
; command
DontReset
        jnb     PCInProtocol, GoSetTypematic
        jnb     TF0, ChkRstProg
        mov     A, #WrComAckTO
        ljmp    PCKbdErr

ChkRstProg
        mov     A, PCHeadPtr               ;see if data in buffer
        cjne    A, PCTailPtr, RstAckAvail
        sjmp    DontReset
RstAckAvail
        lcall   ChkPCData                  ;process buffer contents
        lcall   Send2ByteCom               ;check on Reset command progress
        sjmp    DontReset

GoSetTypematic
        lcall   SendTypematic              ;Set typematic rate to min. Sets InProt
PCMainLoop
        SetRP   PCBank
        jnb     PCInProtocol, MouseButsLEDs ;jump if not in the middle of command
        jnb     TF0, ChkCommProg           ;if not timed-out check command progress
        mov     A, #WrComAckTO             ;else 'PCInProtocol' flag was set too 
        ljmp    PCKbdErr                   ;long so error
ChkCommProg
        lcall   Send2ByteCom               ;Check on command progress
        sjmp    NoMouseLEDs                ;and DONT initiate another command

MouseButsLEDs
        SetRP   MouseBank
        acall   PollMouse                  ;Poll the mouse for movement.
        SetRP   PCBank                     ;Reselect PCBank for buffer check.
        jnb     TF0, NoMouseLEDs           ;if not 10mS skip Mouse buttons & LEDs
        lcall   ScanMouseButs              ;and scan mouse buts. Leave PCBank sel.
        lcall   SendModeInd                ;See if need to send new LED status,
NoMouseLEDs
        mov     A, PCHeadPtr               ;see if data in buffer
        cjne    A, PCTailPtr, PCDataAvail
        sjmp    NothingToSend
PCDataAvail
        lcall   ChkPCData                  ;deal with data
NothingToSend
        sjmp    PCMainLoop

; *****************************************************************************
; Main Loop if directly transferring all data between the Host and the PC kbd
; and vice-versa.
; *****************************************************************************
DirectMainLoop
        mov     A, PCHeadPtr               ;see if data in buffer
        cjne    A, PCTailPtr, PCDirectAvail
        sjmp    DirectMainLoop
PCDirectAvail
        acall   WakeUpTX                   ;wake up the transmitter if needs be
        sjmp    DirectMainLoop             ;and continue loop

; *****************************************************************************
; Check to See if PC Keybaord is present
; Do this by initiating an Echo command and seeing if there is any response
; *****************************************************************************
LookForPCKB
        push    PSW                ;preserve bank
        push    ACC                ;and Acc
        SetRP   PCBank
        clrb    EX0                ;kill ReadByte interrupts
        clrb    EX1                ;make sure clock line ints. are off
        setb    LookPCInit         ;indicate to MainLoop that look was initiated
        mov     PC_Byte, #PC_Echo  ;Set up data for WriteByte
        setb    PC_Parity          ;Parity for Echo command is 1
        mov     PC_TOCntr, #PCPrepSTO ;set time-out for 60uS preparation delay
        clrb    PC_Clock            ;force PC keyboard clock line low
LPrepSend
        djnz    PC_TOCntr, LPrepSend ;Wait for minimum of 60uS
        setb    PC_Clock            ;release clock line
        clrb    PC_Data             ;force data line low
        setb    EX1                 ;allow clock line interrupts
        pull    ACC
        pull    PSW
        ret

; *****************************************************************************
; Issue a reset command to the PCKeybaord
; *****************************************************************************
ResetPCKbd
        mov     A, #PC_Reset        ;Get the Reset command
        setb    C                   ;This is a one byte command
        acall   RequestToSend       ;Initiate it

; Must wait 500uS with clock and data high for PC keyboard to accept reset
; command and then upto 900mS for reset and BAT to take place
; so wait approximately one second
RSWait1S1
        mov     A, #16
RSWait1S2
        clrb    TR0                        ;initialise the counter for
        mov     TL0, #(Timer0Prot :AND: 255) ;protocol timeout
        mov     TH0, #(Timer0Prot/256)
        clrb    TF0
        setb    TR0
RSWait1S3
        jnb     TF0, RSWait1S3 

        dec     A
        cjne    A, #0, RSWait1S2

ExitPCReset
        ret

; *****************************************************************************
; EXTERNAL INTERRUPT 0 SERVICE ROUTINE
; Read a byte including start and stop bits
; Bit order: START - LSB0 - 1 - 2 - 3 - 4 - 5 - 6 - MSB7 - PARITY(odd) - STOP
; Set the HadPCEcho flag if we did, don't put Echo in the buffer
; Don't put anything if the buffer if PC keyboard is not enabled
; *****************************************************************************
ReadByte
        push    PSW                ;save registers
        push    ACC
        jb      ARKBEnable, ARKBReset ;if it is an Archie keyboard check reset
        SetRP   PCBank
        mov     Bit_Cntr, #9       ;initialise the bit counter
        mov     PC_TOCntr, #PCBitTO ;miss initialisation in ReadBit, so do it here        
        acall   RFalling_Edge      ;Read data & look for rising edge only  
        jnc     RdStartOK          ;if zero OK, else error
        mov     A, #PCStartEr      ;get Start bit error code
        ljmp    PCKbdErr
RdStartOK
        dec     Bit_Cntr
Bit_Loop
        acall   ReadBit            ;get a bit (LSB first)
        rrc     A                  ;rotate into ACC
        djnz    Bit_Cntr, Bit_Loop ;count out bits
        mov     PC_Byte, A         ;store the data
        acall   ReadBit            ;get the Parity
        mov     PC_Parity, C       ;keep it
        acall   ReadBit            ;read the stop bit
        jc      RdStopOK           ;if stop bit 1 OK, else error
        mov     A, #PCStopEr
        ljmp    PCKbdErr
RdStopOK  ;Now check the parity, the data byte is in ACC
        clrb    C                     ;clear the carry
        mov     Bit_Cntr, #8          ;use this to count the bits
        mov     PC_TOCntr, #0         ;use this to count the ones
RParityLp
        rlc     A                     ;shift a bit into carry
        jnc     Rskipinc              ;if not one jump
        inc     PC_TOCntr             ;else increment the number of ones
Rskipinc
        djnz    Bit_Cntr, RParityLp   ;loop for all eight bits
        mov     A, PC_TOCntr          ;get the total number of ones
        rrc     A                     ;shift LSB into carry
        jc      RExpect0Par           ;if LSB 1 (ie odd number) expect 0 parity
        jb      PC_Parity, InsertPCBuf ;else expect parity to be 1
        setb    PCParErr
        sjmp    ExitRdByte            

RExpect0Par
        jnb     PC_Parity, InsertPCBuf
        setb    PCParErr              
        sjmp    ExitRdByte

InsertPCBuf
        cjne    PC_Byte, #PC_Echo, NotPCEcho
        setb    HadPCEcho                  ;this is used for looking for the keybaord
        sjmp    ExitRdByte                 ;no point putting the echo in the buffer

NotPCEcho
        jnb     PCKBEnable, ExitRdByte     ;don't put in buffer if not enabled
        mov     @PCHeadPtr, PC_Byte        ;insert into buffer, then check
        inc     PCHeadPtr                  ;if end reached. If so set pointer
        cjne    PCHeadPtr, #PCBufEnd, NoPCInsWrap
        mov     PCHeadPtr, #StableStates   ;to the beginning again
NoPCInsWrap
        mov     A, PCHeadPtr               ;now check if buffer full
        cjne    A, PCTailPtr, ExitRdByte   ;if it is full
        clrb    PC_Clock                   ;try to prevent further data

ExitRdByte                       
        pull    ACC
        pull    PSW
        reti                  

; *****************************************************************
; This bit only executed if Archie keyboard in use
; Check that line remains low for at least 2mS in case it is really
; a PC keyboard plugged in which is sending us some data.
ARKBReset
        clrb    TR0                        ;set up timer
        mov     TL0, #(Timer0Reset :AND: 255)
        mov     TH0, #(Timer0Reset/256)
        clrb    TF0
        setb    TR0

ARPressed
        jb      PC_Data, ExitRdByte        ;its gone high so quit
        jnb     TF0, ARPressed             ;keep checking for 2mS
        ljmp    ResetHost                  ;Convinced, so reset host

; *****************************************************************************
; Generate a Request-to-send to the PC keyboard
; Command/Data to be sent is in A, put into PC_Byte.
; On entry Carry set indicates this is the second of a two byte command,
;  therefore set the flag indicating waiting for second acknowledge
; Work out Parity and put in PC_Parity
; Disable data line interrupts and leave clock line interrupts enabled so that
; when the keyboard responds we can send it the command byte.
; Cannot receive data FROM the keyboard until command is fully sent
; Response to the command may take anything upto 20mS.
; *****************************************************************************
RequestToSend
        RPIs    PCBank
        clrb    EX0                ;kill ReadByte interrupts
        clrb    EX1                ;make sure clock line ints. are off
        clrb    RxdPCAck           ;clear the 'Received Ack' flag
        setb    PCInProtocol       ;Upto external routine to clear this flag

        clrb    TR0                        ;initialise the counter for
        mov     TL0, #(Timer0Prot :AND: 255) ;protocol timeout
        mov     TH0, #(Timer0Prot/256)
        clrb    TF0
        setb    TR0

        jc      Set2ndAck
        setb    Wait1stAck         ;indicate will be waiting for first Ack
        clrb    Wait2ndAck
        sjmp    WCalcParity

Set2ndAck
        setb    Wait2ndAck         ;indicate will be waiting for second Ack
        clrb    Wait1stAck

WCalcParity                        ;Now calculate the parity
        mov     PC_Byte, A         ;Keep a copy of the data
        clrb    PC_Parity          ;Start off with Parity 0
        clrb    C                  ;clear the carry
        mov     Bit_Cntr, #8       ;use this to count the bits
        mov     PC_TOCntr, #0      ;use this to count the ones
WParityLp
        rlc     A                  ;shift a bit into carry
        jnc     Wskipinc           ;if not one jump
        inc     PC_TOCntr          ;else increment the number of ones
Wskipinc
        djnz    Bit_Cntr, WParityLp ;loop for all eight bits
        mov     A, PC_TOCntr       ;get the total number of ones
        rrc     A                  ;shift LSB into carry
        mov     A, PC_Byte         ;(retrieve data)
        jc      WExpect0Par        ;if LSB 1 (ie odd number) already set 0 parity
        setb    PC_Parity          ;else set Parity bit to one
WExpect0Par
        mov     PC_TOCntr, #PCPrepSTO ;set time-out for 60uS preparation delay
        clrb    PC_Clock            ;force PC keyboard clock line low
PrepSend
        djnz    PC_TOCntr, PrepSend ;Wait for minimum of 60uS
        setb    PC_Clock            ;release clock line
        clrb    PC_Data             ;force data line low
        setb    EX1                 ;allow clock line interrupts
        ret

; *****************************************************************************
; EXTERNAL INTERRUPT 1 SERVICE ROUTINE
; Write a byte to the PC keybaord now that it had responded to the request
; *****************************************************************************
WriteByte
        push    PSW
        push    ACC
        SetRP   PCBank             
        mov     A, PC_Byte          ;get the data to be sent
        rrc     A                   ;LSB shifted into Carry
        mov     Bit_Cntr, #8        ;initialise the bit counter
        mov     PC_TOCntr, #PCBitTO ;miss initialisation in WriteBit, so do it here
        acall   WFallingEdge        ;falling edge already happened so don't look
        dec     Bit_Cntr
WBit_Loop
        rrc     A                   ;get the next bit into carry
        acall   WriteBit            ;write it to the keyboard
        djnz    Bit_Cntr, WBit_Loop ;loop for remaining 7 bits
        mov     C, PC_Parity
        acall   WriteBit            ;send the parity
        setb    C
        acall   WriteBit            ;send the stop bit
        acall   WriteBit            ;Line control
        mov     PC_TOCntr, #PCBitTO ;initialise timeout counter
WaitLineCtrl
        jb      PC_Data, EndLineCtrl ;wait for PC kbd to allow data high
        djnz    PC_TOCntr, WaitLineCtrl
        mov     A, #LineCtrlErr
        sjmp    PCKbdErr

EndLineCtrl
        clrb    EX1                 ;prevent further clock line interrupts
        setb    EX0                 ;allow data line interrupts
        pull    ACC
        pull    PSW
        reti

; *****************************************************************************
; Wait for falling edge, read data line, wait for rising edge.
; *****************************************************************************
ReadBit
        mov     PC_TOCntr, #PCBitTO     ;initialise timeout counter
RLookFalling
        jnb     PC_Clock, RFalling_Edge ;if clock goes low - falling edge
        djnz    PC_TOCntr, RLookFalling
        mov     A, #RdBitFETO
        sjmp    PCKbdErr                ;error has occurred

RFalling_Edge
        mov     C, PC_Data              ;get the data bit into carry 

        jb      HadArByte, RLookRising  ;already had a byte from archie
        jnb     RI, RLookRising         ;nothing received from archie
        mov     RWRxd, SBUF             ;keep the byte from archie
        setb    HadArByte               ;set flag saying we had it
        clrb    RI
        setb    ET0                     ;enable timer 0 interrupts
        setb    TF1                     ;and cause one

RLookRising
        jb      PC_Clock, RRising_Edge  ;if clock goes high - rising edge
        djnz    PC_TOCntr, RLookRising  ;timeout on loop
        mov     A, #RdBitRETO
        sjmp    PCKbdErr                ;error has occurred

RRising_Edge
        ret

; *****************************************************************************
; Wait for falling edge, read data line, wait for rising edge.
; *****************************************************************************
WriteBit
        mov     PC_TOCntr, #PCBitTO     ;initialise timeout counter
WLookFalling
        jnb     PC_Clock, WFallingEdge  ;if clock goes low - falling edge
        djnz    PC_TOCntr, WLookFalling ;timeout on loop
        mov     A, #WrBitFETO
        sjmp    PCKbdErr                ;error has occurred

WFallingEdge
        mov     PC_Data, C              ;put out data bit

        jb      HadArByte, WLookRising  ;already had a byte from archie
        jnb     RI, WLookRising         ;nothing received from archie
        mov     RWRxd, SBUF             ;keep the byte from archie
        setb    HadArByte               ;set flag saying we had it
        clrb    RI
        setb    ET0                     ;enable timer 0 interrupts
        setb    TF1                     ;and cause one

WLookRising
        jb      PC_Clock, WRising_Edge  ;if clock goes high - rising edge
        djnz    PC_TOCntr, WLookRising  ;timeout on loop
        mov     A, #WrBitRETO
        sjmp    PCKbdErr                ;error has occurred
WRising_Edge
        ret

; *****************************************************************************
; Error handler - will do more in future, eg reset the PC keyboard
; *****************************************************************************
PCKbdErr
        clrb    PCKBEnable              ;stop looking at PC keyboard
        clrb    LookPCInit
        setb    PCErrorFlag             ;indicate error occured
        orl     A, Bit_Cntr             ;merge with bit counter if relevant
        mov     PC_Error, A             ;store error code - CODE*BIT
 ;       lcall   dispACC10               ;DISPLAY FOR DEBUG ONLY
        mov     A, PC_Byte
 ;       lcall   dispACC32
        ljmp    BombOutToError

; *****************************************************************************
; Send mode indicators
; A copy of PC LED status is kept. If it differs from normal LEDs then update
; is sent to the PC keyboard by initiating a 2 byte command.
; Every 2.5 seconds LED update is sent even if there has been no change in 
; order to check the continued presence of the PC keyboard. If the command
; fails we assume the PC keyboard is no longer there.
; *****************************************************************************
SendModeInd
        RPIs    PCBank
        clr     A                           ;read current state of PC kbd
        mov     C, PCCapsLock               ;LEDs into ACC
        rlc     A
        mov     C, PCNumLock
        rlc     A
        mov     C, PCScrollLock
        rlc     A
        mov     B, A                        ;move them into B
        clr     A                           
        mov     C, CapsLockStatus           ;Read the real LED status flags
        mov     PCCapsLock, C               ;into A, updating the PC ones as we go.
        rlc     A                           ;Remember that the pre-updated PC flags
        mov     C, NumLockStatus            ;are in the B register
        mov     PCNumLock, C
        rlc     A
        mov     C, ScrollLockStatus
        mov     PCScrollLock, C
        rlc     A
        xch     A, B                        ;Now real in B, old PC in A
        xrl     A, B                        ;if result is zero they are all the same
        jz      ChkStillPres                ;so do nothing, unless time to check if
SendModeCom                                 ;PC keyboard is still there.
        mov     PC_B2, B                    ;Otherwise get the real ones into PC_B2
        mov     A, #Set_ModeInd             ;get the command code into A
        lcall   Send2ByteCom                ;and send the 2 byte command
ExitSendMode
        ret

ChkStillPres
        djnz    StillPres, ExitSendMode     ;Check every 256 mouse scans, ie
        sjmp    SendModeCom                 ;about every 2.5 seconds

; *****************************************************************************
; Set PC keybaord typematic rate to minimum to minimise unnecessary interrupts
; *****************************************************************************
SendTypematic
        mov     PC_B2, #MinTypematic        ;second byte will be min. rate
        mov     A, #Set_Typematic           ;first byte is the command
        acall   Send2ByteCom                ;initiate the command
        ret

; *****************************************************************************
; Send a two byte command. 1st byte in Acc, 2nd byte in PC_B2
; *****************************************************************************
Send2ByteCom
        RPIs    PCBank
        jb      Wait1stAck, SendPCByte2     ;Waiting for 1st acknowledge
        jb      Wait2ndAck, Cmplt2Comm      ;Waiting for 2nd acknowledge
        clrb    C                           ;Carry clear indicates its 1st byte
        lcall   RequestToSend               ;request sending of the command
        sjmp    ExitSend2Comm               ;something to happen and quit

SendPCByte2
        jnb     RxdPCAck, ExitSend2Comm     ;not seen the Ack so quit
        setb    C                           ;indicate this is the second ack
        mov     A, PC_B2                    ;get the second byte (the data)
        lcall   RequestToSend               ;request sending of the data byte
        sjmp    ExitSend2Comm

Cmplt2Comm                                  ;command complete
        jnb     RxdPCAck, ExitSendMode
        clrb    RxdPCAck
        clrb    Wait2ndAck
        clrb    PCInProtocol

ExitSend2Comm
        ret

; *****************************************************************************
; Check data from PC keyboard
; *****************************************************************************
ChkPCData      
        SetRP   PCBank
        mov     A, @PCTailPtr           ;get the data
        inc     PCTailPtr
        cjne    PCTailPtr, #PCBufEnd, NoPCRmvWrap
        mov     PCTailPtr, #StableStates
NoPCRmvWrap
        cjne    A, #Acknowledge, ChkOvrRun
        setb    RxdPCAck                ;if acknowledge set the flag
        ret

ChkOvrRun
        cjne    A, #Overrun, ChkDiagFail
        mov     A, #PCOverErr
        ljmp    PCKbdErr

ChkDiagFail
        cjne    A, #Diag_Fail, ChkBATCmplt
        mov     A, #PCDiagFail
        ret                             ;ignore the daig_fail report

ChkBATCmplt
        cjne    A, #BAT_Complete, PCKeyUp
        ret

PCKeyUp
        cjne    A, #PCUpCode, PCKeyDown ;jump if its not the up code
        setb    HadUpCode               ;else set the flag saying we had one
        ret

PCKeyDown
        cjne    A, #PCE0Seq, PCBreakKey ;jump if its not the E0 sequence code
        setb    HadE0Seq                ;else set flag saying we had an E0
        ret

; The break key on a PC-AT keyboard produces one of two possible code sequences:-
; On its own (or with Shift or Alt)  E1-14-77-E1-F0-14-F0-77
; and with Ctrl  E0-7E-E0-F0-7E. THE Latter can be handled by the table as code E0-7E.
; The former is handled by the following code. It will appear to the host that
; NumLock has also been pressed and released.
PCBreakKey
        jbc     HadE1Seq, BreakKey      ;if last code was E1 check if this is 14
        cjne    A, #PCE1Seq, SendPCKey  ;else check if this is E1
        setb    HadE1Seq                ;if it is set the flag
        ret
 
BreakKey
        cjne    A, #PCBreakCode, SendPCKey
        mov     A, #AcornBreak          ;if it is get the Acorn Break key code
        sjmp    BreakEntry              ;and skip the table look up


; Convert it to Acorn code and send it to the host. 
; Down keys are logged using the FN log and only sent once to the
; host which handles auto-repeat. PC keyboards auto repeat will be set to minimum
; and ignored.

SendPCKey
        mov     DPTR, #USATPS2       ;get pointer to translation table
        jnb     HadE0Seq, FirstHalf  ;jump if no E0 preceded the key code
        orl     A, #&80              ;else set top bit to use second half of table
        clrb    HadE0Seq             ;and clear the flag
FirstHalf
        movc    A, @A+DPTR           ;get the Acorn code
        jb      ACC_7, ExitLogSend   ;if top bit is set its invalid

BreakEntry
; Use FN mode log to log which PC keys are down in order to defeat the auto repeat
        SetRP   FNBank               ;use this bank for log manipulation
        mov     MatrixPos, A            ;preserve Acorn matrix position
        mov     FNLogPtr, #FNDownLog    ;initialise pointer to start of log
        anl     A, #&F8                 ;clear bottom 3 bits
        rr      A                       ;shift down top five bits
        rr      A
        rr      A
        add     A, FNLogPtr             ;add to log pointer to select byte
        mov     FNLogPtr, A             ;Log pointer now points to correct byte
        mov     A, MatrixPos
        anl     A, #&07                 ;bottom three bits offset into mask table
        mov     DPTR, #FNMask           ;get pointer to mask table
        movc    A, @A+DPTR              ;get mask
        mov     B, A                    ;keep a copy incase we need it
        jb      HadUpCode, ChkPCKeyUp   ;jump if we've had the up code

; This section is executed if key went down
        anl     A, @FNLogPtr            ;if bit is alreay set, nothing to do
        jnz     ExitLogSend

        mov     A, B                    ;if not set retrieve copy of mask
        orl     A, @FNLogPtr            ;set bit saying key is down
        mov     @FNLogPtr, A            ;put back into log
        clrb    C                       ;clear carry to indicate key down
        mov     A, MatrixPos            ;retrieve the Acorn matrix position
        lcall   OutputKeyTransition     ;send it to host
        sjmp    ExitLogSend             ;and exit. Register bank changed.
; End of KeyDown section

ChkPCKeyUp
        cpl     A                       ;invert the mask
        anl     A, @FNLogPtr            ;and clear the appropriate bit
        mov     @FNLogPtr, A            ;and put back into bit map
        mov     A, MatrixPos            ;Retrieve Acorn matrix position
        setb    C                       ;set carry to indicate key up
        lcall   OutputKeyTransition
ExitLogSend
        clrb    HadUpCode               ;down here so invalid key can clear it
        ret

; *****************************************************************************
; Re-initialise the timer for mouse scans and check the states of the buttons
; *****************************************************************************
ScanMouseButs
        SetRP   FNBank
        mov     A, MouseButtonInput         ;read mouse buttons
        orl     A, #&FF :EOR: MouseButtonMask ;set unused bits to one
        xrl     A, MouseStable              ;new differences from stable state
        xch     A, MouseXOR                 ;swap new and old differences
        anl     A, MouseXOR                 ;get stable differences
        jz      MouseNoChange

;now XOR both stable state and last XOR with stable difference, preserving A
        xch     A, MouseXOR
        xrl     A, MouseXOR
        xch     A, MouseXOR

        xch     A, MouseStable
        xrl     A, MouseStable              ;A now holds new stable value
        mov     B, A
        xch     A, MouseStable

        mov     KBDRow, #16*8-1             ;the -1 compensates for 1st inc
        clrb    C
MIncLoop
        inc     KBDRow                      ;increment offset into table
        rrc     A                           ;shift next bit into carry
        jc      MFoundBit                   ;if set we've found a bit
MNextBit
        xch     A, B                        ;shift stable state byte
        rr      A                           ;preserves carry (should be clear)
        xch     A, B
        jnz     MIncLoop                    ;if A still has bits in it then loop

MouseNoChange
        clrb    TR0                           ;initialise the counter for
        mov     TL0, #(Timer0Mouse :AND: 255) ;mouse button debounce
        mov     TH0, #(Timer0Mouse/256)
        clrb    TF0
        setb    TR0
        SetRP   PCBank                         ;must restore for buf. check
        ret

MFoundBit ;found a change in the mouse buttons
        push    ACC
        mov     A, KBDRow                   ;KBDRow is offset into table
        mov     C, B_0                      ;carry set indicates key up
        mov     DPTR, #KBDTran              ;get pointer to table
        movc    A, @A+DPTR                  ;get the keycode
        jb      ACC_7, InvalidBut           ;top bit set indicates invalid button
        lcall   OutputKeyTransition         ;otherwise send it
; after OutputKeyTransition KBDBank will be selected but this does not matter since
; we will not refer to the MouseStable & MouseXOR locations again
InvalidBut
        pull    ACC
        clrb    C
        sjmp    MNextBit

; *******************************************************************************
; Two routines to initialise the PC keyboard.
; The first only initialises the flags and does not affect operation with the
; normal key-switch matrix.
; The second sets up all the buffers, timer and interrupts and is only to be used
; when a PC keybaord has been found. Cannot return to normal keyboard without
; doing a Restart.
; Clear StableStates to use as buffer, clear FNDownLog to use as auto-repeat log.
; *******************************************************************************
InitPCFlags
        NoRP  
        clrb    PCCapsLock                 ;clear all LED flags
        clrb    PCScrollLock
        clrb    PCNumLock
        mov     PCHeadPtr, #StableStates   ;initialise pointers
        mov     PCTailPtr, #StableStates
        clr     A                          ;zero registers
        mov     PC_Byte, A
        mov     PC_Error, A
        mov     PC_B2, #1                  ;don't delay 1st check
        clrb    PCParErr                   ;initialise all the flags
        clrb    HadUpCode
        clrb    HadE0Seq
        clrb    HadPCEcho
        clrb    PCErrorFlag
        clrb    RxdPCAck
        clrb    Wait1stAck
        clrb    Wait2ndAck
        clrb    PCInProtocol
        clrb    HadE1Seq
        clrb    LookPCInit
        ret

InitPCBuf
        clrb    EA                         ;disable all interrupts
        SetRP   PCBank
        mov     PC_Byte, #17               ;use this as a temporary counter
        mov     PCTailPtr, #FNDownLog+16
        clr     A
InitPCLoop1
        mov     @PCTailPtr, A              ;this wipes the FNDownLog area
        dec     PCTailPtr
        djnz    PC_Byte, InitPCLoop1       ;loop till done

        mov     PC_Byte, #34               ;use this as a temporary counter
        mov     PCHeadPtr, #StableStates+33
        clr     A
InitPCLoop2
        mov     @PCHeadPtr, A              ;this wipes the StableStates
        dec     PCHeadPtr                  ;and LastTimeXOR area
        djnz    PC_Byte, InitPCLoop2       ;loop till done

        mov     RWRxd, A                   ;zero dual-purpose bytes
        mov     StillPres, A
        mov     MouseStable, A
        mov     MouseXOR, A

        acall   InitPCFlags

        clrb    TR0                        ;initialise the counter for
        mov     TL0, #(Timer0Mouse :AND: 255) ;mouse buttons
        mov     TH0, #(Timer0Mouse/256)
        clrb    TF0
        setb    TR0

        setb    PC_Data
        setb    PC_Clock
        clrb    EX1                        ;disbale PC clock line interrupts
        setb    EX0                        ;enable PC data line interrupts
        setb    ES                         ;enable serial line interrupts
        setb    EA                         ;global interrupt enable
        ret

; *****************************************************************************
; Allow an Archimedes keyboard to be plugged into the PC keyboard socket
; by simply doing a bit for bit copy between the Ports.
; Serial port is not used
; *****************************************************************************
ARKBTransfer
        clrb    EA                ;kill all interrupts

        setb    LCD20             ;Cannot modulate LCD in this mode
        setb    LCD40             ;so blank it
        setb    LCD60
        setb    LCD80
        setb    LCD100

        setb    PC_Clock          ; of Archie keyboard
        setb    FNLED
        setb    SerialDataIn
        setb    SerialDataOut
        SetRP   PCBank
        acall   SendARReset
        mov     A, #&FF
        mov     PC_TOCntr, A
ARKBTXLoop
        mov     C, SerialDataIn   ;read input from host                1uS
        mov     FNLED, C          ;pass onto Archie keyboard           2uS
        mov     C, PC_Clock       ;read input from Archie keyboard     1uS
        cpl     C                 ;invert because we have no inverter  1uS
        mov     SerialDataOut, C  ;and pass on to host                 2uS
        jnc     SkipZeroCntr      ;if we read a hi, do not zero Count  2uS
        mov     PC_TOCntr, A      ;if we read a low zero counter       1uS
SkipZeroCntr
        djnz    PC_TOCntr, ARKBTXLoop ;                                2uS
; If we have not seen a low for 255 loops of this code                 ---
; we can assume that the Archie keyboard is no longer there    Total  12uS
        clrb    ARKBEnable
        setb    PC_Clock        
        setb    FNLED
        setb    SerialDataIn
        setb    SerialDataOut
        ljmp    BombOutToError
       
; *****************************************************************************
; Send Reset command to Archie keyboard we think we have found
; *****************************************************************************
SendARReset
        clrb    FNLED
        mov     PC_TOCntr, #255         ;Ensure Archie keyboard does not think
ARClrComLoop                            ;it is in the middle of a byte transfer
        djnz    PC_TOCntr, ARClrComLoop ;Loop for 510uS

        mov     PC_TOCntr, #16
        setb    FNLED                   ;start bit
ARStartLoop
        djnz    PC_TOCntr, ARStartLoop  ;Loop for 32us

        clrb    FNLED
        mov     PC_TOCntr, #160
ARResComLoop
        djnz    PC_TOCntr, ARResComLoop ;Loop for 320uS

        ret

; *****************************************************************************
; Reset the host system
; *****************************************************************************
        NoRP                      ;assume no particular register bank selected
ResetHost
        clrb    TR0               ;set timer for length of reset pulse
        mov     TL0, #(Timer0HostRes :AND: 255)
        mov     TH0, #(Timer0HostRes/256)
        clrb    TF0
        setb    TR0
        clrb    HostReset         ;pull down reset line
ResetPulseDelay
        jnb     TF0, ResetPulseDelay ;wait specified time
        setb    HostReset         ;end reset pulse
        ljmp    BombOutToRestart

; *****************************************************************************
; Display driver/debug routines
; *****************************************************************************
        NoRP                      ;assume no particular register bank selected
dispACC10
        push    PSW
        push    ACC
        anl     A, #&F0           ;select hi nybble
        swap    A                 ;swap into lo nybble
        lcall   Convert           ;convert to ascii
        mov     CharSel, #&F1
        mov     Display, A
        lcall   wrch
        pull    ACC
        push    ACC
        anl     A, #&0F           ;select low nybble
        lcall   Convert
        mov     CharSel, #&F0
        mov     Display, A
        lcall   wrch
        pull    ACC
        pull    PSW
        ret

dispACC32
        push    PSW
        push    ACC
        anl     A, #&F0           ;select hi nybble
        swap    A                 ;swap into lo nybble
        lcall   Convert           ;convert to ascii
        mov     CharSel, #&F3
        mov     Display, A
        lcall   wrch
        pull    ACC
        push    ACC
        anl     A, #&0F           ;select low nybble
        lcall   Convert
        mov     CharSel, #&F2
        mov     Display, A
        lcall   wrch
        pull    ACC
        pull    PSW
        ret

Convert
        add     A, #&30            ;takes 0 to 9 to ASCII
        cjne    A, #&3A, continue  ;check if should be A to F
continue
        jc      dncvrt             ;if a borrow occured it means number is &30 to &39
        add     A, #7              ;else add 7 to change &3A to &41 etc
dncvrt  ret

DispVer 
        setb    WrStrobe
        mov     DPTR, #Verlbl+8
        mov     CharSel, #&F3
        clr     A
        movc    A, @A+DPTR
        mov     Display, A
        lcall   wrch
        inc     DPTR
        mov     CharSel, #&F2
        clr     A
        movc    A, @A+DPTR
        mov     Display, A
        lcall   wrch
        inc     DPTR
        mov     CharSel, #&F1
        clr     A
        movc    A, @A+DPTR
        mov     Display, A
        lcall   wrch
        inc     DPTR
        mov     CharSel, #&F0
        clr     A
        movc    A, @A+DPTR
        mov     Display, A
        lcall   wrch
        ret

DispPE 
        setb    WrStrobe
        mov     CharSel, #&F3
        mov     Display, #&50
        lcall   wrch
        mov     CharSel, #&F2
        mov     Display, #&45
        lcall   wrch
        ret

DispARKB 
        setb    WrStrobe
        mov     CharSel, #&F3
        mov     Display, #&41
        lcall   wrch
        mov     CharSel, #&F2
        mov     Display, #&52
        lcall   wrch
        mov     CharSel, #&F1
        mov     Display, #&4B
        lcall   wrch
        mov     CharSel, #&F0
        mov     Display, #&42
        lcall   wrch
        ret

DispPC 
        setb    WrStrobe
        mov     CharSel, #&F3
        mov     Display, #&50
        lcall   wrch
        mov     CharSel, #&F2
        mov     Display, #&43
        lcall   wrch
        ret
wrch
        clrb    WrStrobe
        setb    WrStrobe
        ret

; *****************************************************************************
; Now the keyboard translation table
        GET     KbdTran
; *****************************************************************************
        WHILE   . < &1000
        =       &46, &52, &45, &45, &20
        WEND

        END

; end of file
