diff --git a/spicode.s65 b/spicode.s65 index 0be8e7d..d40c549 100644 --- a/spicode.s65 +++ b/spicode.s65 @@ -3,7 +3,7 @@ .include "lcd.h65" .include "math.h65" .include "keypad.h65" -.include "keyboard.h65" +.include "keyboard_handler.h65" .include "chars.h65" .import homeloop:absolute .import home:absolute @@ -25,47 +25,49 @@ CODE_START: DEBUG_LED_OFF 1 DEBUG_LED_OFF 2 - lda #kb_irq1 - sta ARG1 - lda #<$3000 - sta ARG2 - lda #>$3000 - sta ARG3 + StoreDByte kb_irq1, ARG0 + StoreDByte $3000, ARG2 + ; lda #kb_irq1 + ; sta ARG1 + ; lda #<$3000 + ; sta ARG2 + ; lda #>$3000 + ; sta ARG3 ldy #10 jsr memcopy - lda #kb_irq2 - sta ARG1 - lda #<$3100 - sta ARG2 - lda #>$3100 - sta ARG3 + StoreDByte kb_irq2, ARG0 + StoreDByte $3100, ARG2 + ; lda #kb_irq2 + ; sta ARG1 + ; lda #<$3100 + ; sta ARG2 + ; lda #>$3100 + ; sta ARG3 ldy #10 jsr memcopy - ; PrintNC $3000 - - jsr kb::init - + jsr ps2kb::init stz kb::status + StoreDByte process_scancode,ps2kb::scancode_handler + jsr ps2kb::begin_receive lda #'%' jsr lcd::print_char stz kp::_DEBUG_VAL ldy #0 - @loop: wai - lda kb::scancode + lda ps2kb::scancode beq @noscancode jsr process_scancode - stz kb::scancode + stz ps2kb::scancode lda char beq @onlykeycode Strf fmt_codechar,out_str,keycode,kb::status,char @@ -131,7 +133,7 @@ CODE_START: iny jmp @loop @lA: - lda kb::KB_IO + IO::SR + lda ps2kb::VIA + IO::SR jsr lcd::print_char jmp @loop @lB: @@ -154,7 +156,7 @@ CODE_START: kb_irq1: ; lda #'!' ; jsr lcd::print_char - jsr kb::irq_shift_reg_handler + jsr ps2kb::_receive_irq_shift_reg_handler ; lda #':' ; jsr lcd::print_char rts @@ -163,7 +165,7 @@ kb_irq2: ; lda #'?' ; jsr lcd::print_char ; jsr kb::on_timer_irq - jsr kb::irq_timer_handler + jsr ps2kb::_receive_irq_timer_handler ; lda #';' ; jsr lcd::print_char rts @@ -172,7 +174,7 @@ kb_irq2: .proc process_scancode ; DEBUG_LED_OFF 1 ; check how this scancode needs to be interpreted - ldx kb::scancode + ldx ps2kb::scancode bit kb::status jmi release_key ; bit 7 set bvs @twobytecode ; bit 6 set diff --git a/system/io_W65C22.h65 b/system/io_W65C22.h65 index bb2a7b8..52d4ffc 100644 --- a/system/io_W65C22.h65 +++ b/system/io_W65C22.h65 @@ -109,5 +109,24 @@ INCLUDE_IOW65C22 = 1 IRQ = %10000000 .endenum +;;******************************************************************************** +;; @macro Enable an interrupt source +;; @modifies: A +;; @param flag: A flag of the interrupt flag register (IO:IRQ) +;;******************************************************************************** +.macro IO_EnableIRQ ioaddr, flag + lda #(IO::IRQ::IRQ | flag) + sta ioaddr + IO::IER +.endmacro +;;******************************************************************************** +;; @macro Disable an interrupt source +;; @modifies: A +;; @param flag: A flag of the interrupt flag register (IO:IRQ) +;;******************************************************************************** +.macro IO_DisableIRQ ioaddr, flag + lda #flag + sta ioaddr + IO::IER +.endmacro + .endscope ; IO .endif ; guard diff --git a/system/keyboard.s65 b/system/keyboard.s65 deleted file mode 100644 index 86d6e38..0000000 --- a/system/keyboard.s65 +++ /dev/null @@ -1,138 +0,0 @@ -.include "keyboard.h65" -.include "string.h65" -.include "lcd.h65" -Export kb,init,irq_shift_reg_handler,irq_timer_handler,scancode,key_read -Export kb, CHARS_NOMOD, CHARS_MODSHIFT -ExportZp kb,status - -.zeropage -status: -.bss -key_read: .res 2 -scancode: .res 1 - -.code -;;******************************************************************************** -;; @function Initialize the PS2 keyboard -;; @modifies: A -;;******************************************************************************** -.proc init - ; - use the shift register interrupts to read the first 8 bits - ; set shift register to shift in under external clock on CB1 - ; - configure timer for timing the read of the last 3 bits - ; timer 2 one shot mode is sufficient, leaves T1 available - lda #(IO::ACR::SR_SIN_PHIE | IO::ACR::T2_IRQ_LOAD) - tsb kb::KB_IO + IO::ACR - ; the 3 last bits take about 230us, at @1MHz => wait 230 cycles and then the shift register - ; (this could be shorter since the it takes a few cycles after the interrupt) - lda #230 - sta kb::KB_IO + IO::T2CL - stz key_read - stz key_read+1 - stz scancode - stz status - - ; enable SR interrupts - lda #(IO::IRQ::IRQ | IO::IRQ::SR) - sta kb::KB_IO + IO::IER - ; load SR to reset - lda kb::KB_IO + IO::SR - rts -.endproc - - -;;******************************************************************************** -;; @function Read the first 8 bits an -;; @modifies: A -;; @details -;; - read shift register -;; - disable shift register interrupts -;; - reset shift register -;; - enable timer 2 interrupts -;; - start timer 2 -;; IO::SR has to be read before the next bit is shifted in, which happens ~75us after the irq -;; at 1MHz, handling this interrupt takes about 50us (without any additional debug code), -;; so it should work -;;******************************************************************************** -.proc irq_shift_reg_handler - lda kb::KB_IO + IO::SR - sta key_read - stz kb::KB_IO + IO::SR - - ; disable SR interrupts - lda #IO::IRQ::SR - sta kb::KB_IO + IO::IER - ; enable timer interrupts - lda #(IO::IRQ::IRQ | IO::IRQ::T2) - sta kb::KB_IO + IO::IER - ; start timer, low order count already in latch after init - lda #0 - sta kb::KB_IO + IO::T2CH - rts -.endproc - - -;;******************************************************************************** -;; @function Read the last 3 bits after after timer 2 is up -;; @modifies: A -;; @details -;; - read shift register -;; - disable timer 2 interrupts -;; - enable shift register interrupts -;; - reset shift register -;;******************************************************************************** -.proc irq_timer_handler - lda kb::KB_IO + IO::SR - sta key_read + 1 - - lda kb::KB_IO + IO::T2CL ; clear interrupt flag - - ; disable timer interrupts - lda #(IO::IRQ::T2) - sta kb::KB_IO + IO::IER - - ; reset SR - ; disabling shifting in acr seems necessary to reset - otherwise - ; it continues counting and interrupts after the first 5 bits of the next keypress - lda #(IO::ACR::SR_SIN_PHIE | IO::ACR::T2_IRQ_LOAD) - trb kb::KB_IO + IO::ACR - tsb kb::KB_IO + IO::ACR - stz kb::KB_IO + IO::SR - ; enable shift register interrupts - lda #(IO::IRQ::IRQ | IO::IRQ::SR) - sta kb::KB_IO + IO::IER - - lda key_read+1 - ror - ror - ror - lda key_read ; not affecting carry - rol ; rotate carry into byte, rotate startbit into carry - Reverse A - sta scancode - - rts -.endproc - -.rodata -.align 256 -CHARS_NOMOD: - .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00^\x00" - .byte "\x00\x00\x00\x00\x00q1\x00\x00\x00ysaw2\x00" - .byte "\x00cxde43\x00\x00 vftr5\x00" - .byte "\x00nbhgz6\x00\x00\x00mju78\x00" - .byte "\x00,kio09\x00\x00.-l\xEFp\xE2\x00" - .byte "\x00\x00\xE1\x00\xF5`\x00\x00\x00\x00\x00\x00\x00#\x00\x00" - .byte "\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - -.align 256 -CHARS_MODSHIFT: - .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xDF\x00" - .byte "\x00\x00\x00\x00\x00Q!\x00\x00\x00YSAW\"\x00" - .byte "\x00CXDE$\xED\x00\x00 VFTR%\x00" - .byte "\x00NBHGZ&\x00\x00\x00MJU/(\x00" - .byte "\x00;KIO=)\x00\x00:_L\xEFP\xE2\x00" - .byte "\x00\x00\xE1\x00\xF5`\x00\x00\x00\x00\x00\x00\x00'\x00\x00" - .byte "\x00>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/system/keyboard.h65 b/system/keyboard_handler.h65 similarity index 79% rename from system/keyboard.h65 rename to system/keyboard_handler.h65 index f8d7de0..117238e 100644 --- a/system/keyboard.h65 +++ b/system/keyboard_handler.h65 @@ -1,25 +1,17 @@ ;;******************************************************************************** -;; @module keyboard -;; @type drive +;; @module keyboard_handler +;; @type system ;; @details: -;; Support for a PS2 Keyboard using the shift register of a 6522 VIA -;; Pressing a key causes 11 bits to be sent: 1 start - 8 scancode - 1 parity - 1 stop -;; The VIA is set up to interrupt after 8 bits have been shifted into the shift register -;; from the external clock pulses of the keyboard (additional hardware required to -;; address the hardware bug of the VIA, where bit get lost when the external clock -;; transition happens during falling edge of PHI2). After reading the shift register, -;; the VIAs T2 timer is set to interrupt after the last 3 bits have been shifted in, -;; which takes about ~230ms. +;; This module processes keyboard scancodes from the ps2_keyboard module. ;;******************************************************************************** .ifndef INCLUDE_KEYBOARD INCLUDE_KEYBOARD = 1 -.include "system.h65" + +.include "ps2_keyboard.h65" .scope kb -Import kb,init,irq_shift_reg_handler,irq_timer_handler,scancode,key_read Import kb, CHARS_NOMOD, CHARS_MODSHIFT ImportZp kb,status -KB_IO = IO1 .enum STATUS RELEASE = %10000000 @@ -145,7 +137,5 @@ K_RELEASE = $F0 PAGEUP = $7D .endenum - .endscope - .endif ; guard diff --git a/system/keyboard_handler.s65 b/system/keyboard_handler.s65 new file mode 100644 index 0000000..9ce816b --- /dev/null +++ b/system/keyboard_handler.s65 @@ -0,0 +1,28 @@ +.include "keyboard_handler.h65" +Export kb, CHARS_NOMOD, CHARS_MODSHIFT +ExportZp kb,status +.zeropage +status: .res 1 + +.rodata +.align 256 +CHARS_NOMOD: + .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00^\x00" + .byte "\x00\x00\x00\x00\x00q1\x00\x00\x00ysaw2\x00" + .byte "\x00cxde43\x00\x00 vftr5\x00" + .byte "\x00nbhgz6\x00\x00\x00mju78\x00" + .byte "\x00,kio09\x00\x00.-l\xEFp\xE2\x00" + .byte "\x00\x00\xE1\x00\xF5`\x00\x00\x00\x00\x00\x00\x00#\x00\x00" + .byte "\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +.align 256 +CHARS_MODSHIFT: + .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xDF\x00" + .byte "\x00\x00\x00\x00\x00Q!\x00\x00\x00YSAW\"\x00" + .byte "\x00CXDE$\xED\x00\x00 VFTR%\x00" + .byte "\x00NBHGZ&\x00\x00\x00MJU/(\x00" + .byte "\x00;KIO=)\x00\x00:_L\xEFP\xE2\x00" + .byte "\x00\x00\xE1\x00\xF5`\x00\x00\x00\x00\x00\x00\x00'\x00\x00" + .byte "\x00>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + .byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/system/ps2_keyboard.h65 b/system/ps2_keyboard.h65 new file mode 100644 index 0000000..f8f5a52 --- /dev/null +++ b/system/ps2_keyboard.h65 @@ -0,0 +1,51 @@ +;;******************************************************************************** +;; @module ps2_keyboard +;; @type driver +;; @details: +;; Support for a PS2 Keyboard using the shift register of a 6522 VIA +;; @section Reading a scancode/command answer +;; Pressing a key causes 11 bits to be sent: 1 start - 8 scancode - 1 parity - 1 stop +;; The VIA is set up to interrupt after 8 bits have been shifted into the shift register +;; from the external clock pulses of the keyboard (additional hardware required to +;; address the hardware bug of the VIA, where bit get lost when the external clock +;; transition happens during falling edge of PHI2). After reading the shift register, +;; the VIAs T2 timer is set to interrupt after the last 3 bits have been shifted in, +;; which takes about ~230ms. +;; T2 is used because it leaves to more versatile T1 available for something else + +;; @section Processing a scancode +;; The scancode may be processed by storing the address of a handler subroutine in +;; ps2kb::scancode_handler. This handler can load the scancode byte from ps2kb::scancode. +;; +;; +;; To use a different layout, change the enum K and the CHARS_NOMOD and CHARS_MODSHIFT +;; @warning +;; The value with which the timer is loaded must depends on the clock frequency +;; of the keyboard and the computer +;; +;;******************************************************************************** +.ifndef INCLUDE_PS2_KEYBOARD +INCLUDE_PS2_KEYBOARD = 1 +.include "system.h65" + +.scope ps2kb +Import ps2kb,init,begin_receive,send_command,scancode,status,scancode_handler +Import ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler + +VIA = IO1 +TIMER = 230 ; 230 ms (@1MHz) +; use RA4 to pull the clock low +CLK_PULL_MASK= %00001000 +CLK_PULL_R = IO::RANH +CLK_PULL_DDR = IO::DDRA + + +.enum STATUS + RECEIVE_KEYS = %10000000 + RECEIVE_ANSWER = %01000000 + SEND_CMD = %00100000 + NONE = %00000000 +.endenum +.endscope + +.endif ; guard diff --git a/system/ps2_keyboard.s65 b/system/ps2_keyboard.s65 new file mode 100644 index 0000000..664f7d7 --- /dev/null +++ b/system/ps2_keyboard.s65 @@ -0,0 +1,313 @@ +.include "ps2_keyboard.h65" +.include "string.h65" +.include "lcd.h65" +.include "parity.h65" +Export ps2kb,init,begin_receive,send_command,scancode,status,scancode_handler +Export ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler + +.bss +status: .res 1 +send_last_bits: .res 1 +send_data: .res 1 +send_cmd: .res 1 +key_read: .res 2 +scancode: .res 1 +scancode_handler: .res 2 + +.code + +;;******************************************************************************** +;; @macro Enable the clock signal from the keyboard +;; @modifies: A +;; @details +;; Stop pulling the keyboards clock low +;;******************************************************************************** +.macro _EnableClock + ; set pin to input + lda ps2kb::VIA + ps2kb::CLK_PULL_DDR + and #<~ps2kb::CLK_PULL_MASK + sta ps2kb::VIA + ps2kb::CLK_PULL_DDR +.endmacro + +.macro _DisableTimerIRQ + ; set pin to input + lda ps2kb::VIA + ps2kb::CLK_PULL_DDR + and #<~ps2kb::CLK_PULL_MASK + sta ps2kb::VIA + ps2kb::CLK_PULL_DDR +.endmacro + +;;******************************************************************************** +;; @macro Disable the clock signal from the keyboard +;; @modifies: A +;; @details +;; Pulls the keyboards clock low +;;******************************************************************************** +.macro _DisableClock + ; set pin to output + lda ps2kb::VIA + ps2kb::CLK_PULL_DDR + ora #ps2kb::CLK_PULL_MASK + sta ps2kb::VIA + ps2kb::CLK_PULL_DDR + ; set pin low + lda ps2kb::VIA + ps2kb::CLK_PULL_R + and #<~ps2kb::CLK_PULL_MASK + sta ps2kb::VIA + ps2kb::CLK_PULL_R +.endmacro + + +.proc init + stz key_read + stz key_read+1 + stz scancode + stz status + ; T2 oneshot mode, clear shift reg + lda ps2kb::VIA + IO::ACR + and #<~IO::ACR_MASK::SR + ora #IO::ACR::T2_IRQ_LOAD + sta ps2kb::VIA + IO::ACR + ; the 3 last bits take about 230us, at @1MHz => wait 230 cycles and then the shift register + ; (this could be shorter since the it takes a few cycles after the interrupt) + lda #ps2kb::TIMER + sta ps2kb::VIA + IO::T2CH + rts +.endproc + + +;;******************************************************************************** +;; @function Read the last 3 bits after after timer 2 is up +;; @modifies: A +;; @details +;; - read shift register +;; - disable timer 2 interrupts +;; - enable shift register interrupts +;; - reset shift register +;; - jump to the subroutine pointed to by scancode_handler +;;******************************************************************************** +.proc _receive_irq_timer_handler + lda ps2kb::VIA + IO::SR + sta key_read + 1 + + lda ps2kb::VIA + IO::T2CL ; clear interrupt flag + + IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2 + ; reset SR + ; disabling shifting in acr seems necessary to reset - otherwise + ; it continues counting and interrupts after the first 5 bits of the next keypress + lda #(IO::ACR::SR_SIN_PHIE | IO::ACR::T2_IRQ_LOAD) + trb ps2kb::VIA + IO::ACR + tsb ps2kb::VIA + IO::ACR + stz ps2kb::VIA + IO::SR + IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR + + lda key_read+1 + ror ; stop bit -> C + sta key_read+1 ; parity in bit 0, D7 in bit 1 + ror ; parity -> C + ror ; D7 -> C + lda key_read ; not affecting carry + rol ; C -> bit 0, startbit -> C + Reverse A + sta scancode + CalculateOddParity + eor key_read+1 ; if bit 0 is 1 - parity error + and #1 ; bit 1 is still D7 + bne @parity_error + ; check what to do with the scancode + bit status + bcc @status_not_receive_keys + jmp (scancode_handler) +@status_not_receive_keys: + bvc @status_ignore + jmp _process_cmd_answer +@status_ignore: + rts +@parity_error: ; TODO handle somehow + lda #$ff + sta scancode + DEBUG_LED_ON 2 + rts +.endproc + + +;;******************************************************************************** +;; @function Send a command to the keyboard +;; @modifies: A,X,Y +;; @param A: The command byte +;; @param X: The data byte or 0 if only the command byte should be sent +;;******************************************************************************** +.proc send_command + sta send_cmd ; store if it needs to be resent + cpx #0 + beq @jmp_send_byte +@store_data: + stx send_data +@jmp_send_byte: + jmp _send_byte +.endproc + + +;;******************************************************************************** +;; @function Send a byte to the keyboard +;; @modifies: A,X,Y +;; @param A: The byte to send +;; @details +;; - pull clock low to stop keyboard transmissions +;; - setup shift register to shift out +;; - put startbit + D0-6 in shift register +;; - store D7 + parity + stopbit in memory +;; - TODO setup SR and T2 interrupt handlers +;;******************************************************************************** +.proc _send_byte + pha + IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2 + _DisableClock + ; (re)set shift register to shift out under external clock on CB1 + lda ps2kb::VIA + IO::ACR + and #<~IO::ACR_MASK::SR + ora #IO::ACR::SR_SOUT_PHIE + sta ps2kb::VIA + IO::ACR + IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR + pla + pha + ; split into the 11 bits + CalculateOddParity + tax + pla + clc ; C = 0 (startbi) + rol ; C -> bit 0, D7 -> C + sta ps2kb::VIA + IO::SR + txa + ror ; C -> bit 0 (D7) + ora #%00000100 ; set stopbit + ; A = 000001P7 where 7 = D7, P = Parity + sta send_last_bits ; loaded into SR by irq handler + ; stop pulling clk low, which causes the startbit to be shifted out + _EnableClock + rts +.endproc + + +;;******************************************************************************** +;; @function Send the lasts 3 bits +;; @modifies: A +;; @details +;; - load the remaining 3 bits of the transmission into the shift register +;; - disable shift register interrupts +;; - enable timer 2 interrupts +;; - start timer 2 +;;******************************************************************************** +.proc _send_irq_shift_reg_handler + lda send_last_bits + sta ps2kb::VIA + IO::SR + + ; disable SR interrupts + lda #IO::IRQ::SR + sta ps2kb::VIA + IO::IER + ; enable timer interrupts + lda #(IO::IRQ::IRQ | IO::IRQ::T2) + sta ps2kb::VIA + IO::IER + ; start timer, low order count already in latch after init + lda #>ps2kb::TIMER + sta ps2kb::VIA + IO::T2CH + rts +.endproc + + +;;******************************************************************************** +;; @function Send databyte or reveice the keyboards answer +;; @modifies: A, X, Y +;; @details +;; - disable timer 2 interrupts +;; - if send_data is zero: +;; - begin_receive +;; - set status RECEIVE_ANSWER +;; - else +;; - send_byte send_data +;; - set send_data = 0 +;;******************************************************************************** +.proc _send_irq_timer_handler + lda ps2kb::VIA + IO::T2CL ; clear interrupt flag + IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2 + ; no SR reset necessary, will be done in begin_receive or send_byte + + lda send_data + beq @receive + stz send_data + jmp _send_byte + rts +@receive: + lda #ps2kb::STATUS::RECEIVE_ANSWER + sta status + jmp begin_receive +.endproc + + +.proc _process_cmd_answer +@success: + rts +.endproc diff --git a/utility.h65 b/utility.h65 index 0fd8799..765f870 100644 --- a/utility.h65 +++ b/utility.h65 @@ -36,6 +36,35 @@ INCLUDE_UTILITY = 1 .endmacro +;;******************************************************************************** +;; @macro Store a double byte (immediate) at an address +;; @param addr Address, double byte will be loaded in LE to addr, addr+1 +;; @param dbyte Location or value +;; @param reg Register, defaults to A +;; @modifies +;;******************************************************************************** +.macro StoreDByte dbyte,addr,reg + ; .out .sprintf("%x, %x", dbyte, addr) +.if .blank(reg) .or .match(reg, A) + lda #dbyte + sta addr+1 +.elseif .match(reg, X) + ldx #dbyte + stx addr+1 +.elseif .match(reg, Y) + ldy #dbyte + sty addr+1 +.else + .fatal "Invalid reg given to StoreDByte" +.endif +.endmacro + ;;******************************************************************************** ;; @macro Update a byte in memory using a mask ;; @param addr Address of the byte to update @@ -56,6 +85,7 @@ INCLUDE_UTILITY = 1 .endmacro + ;;******************************************************************************** ;; @macro Jump to the subroutine and let the routine return at another location ;; @details