diff --git a/system/keyboard.h65 b/system/keyboard.h65 new file mode 100644 index 0000000..8e3fa97 --- /dev/null +++ b/system/keyboard.h65 @@ -0,0 +1,23 @@ +;;******************************************************************************** +;; @module keyboard +;; @type drive +;; @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 keycode - 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. +;;******************************************************************************** +.ifndef INCLUDE_KEYBOARD +INCLUDE_KEYBOARD = 1 +.include "system.h65" + +.scope kb +Import kb,init,irq_shift_reg_handler,irq_timer_handler,keycode,key_read +KB_IO = IO1 + +.endscope +.endif diff --git a/system/keyboard.s65 b/system/keyboard.s65 new file mode 100644 index 0000000..17c369f --- /dev/null +++ b/system/keyboard.s65 @@ -0,0 +1,106 @@ +.include "keyboard.h65" +.include "string.h65" +.include "lcd.h65" +Export kb,init,irq_shift_reg_handler,irq_timer_handler,keycode,key_read + +.bss +key_read: .res 2 +keycode: .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 + + ; 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 #1 + 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 + ; enable shift register interrupts + lda #(IO::IRQ::IRQ | IO::IRQ::SR) + sta kb::KB_IO + IO::IER + ; reset SR + stz kb::KB_IO + IO::SR + + ; rotate bit 2 (last bit of keycode) into the carry + lda key_read+1 + ror + ror + ror + lda key_read ; not affecting carry + rol ; rotate carry into byte, rotate startbit into carry + ; TODO byte is inverted, maybe consider wasting 256 bytes for a bit reverse lookup table? + sta keycode + + stz key_read + stz key_read+1 + rts +.endproc