;;******************************************************************************** ;; @module ps2_keyboard ;; @ingroup drivers ;; @details ;; Support for a PS2 Keyboard using the shift register of a 6522 VIA ;; @section reading 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 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" ;;******************************************************************************** ;; @brief PS/2 Keyboard ;; This requires the data line to be hooked up to `CB2` and the clock to `CB1`. ;; The VIA will be set to shift in the data into the shift register under the external clock. ;; @see @ref via_hardware_bug "VIA external clock bug" ;; @include keyboard ;; @ingroup drivers ;;******************************************************************************** .scope ps2kb Import ps2kb, init, begin_receive, scancode, status, scancode_handler Import ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler, _send_byte, _send_irq_shift_reg_handler, _send_irq_timer_handler, Import ps2kb, send_command, send_cmd, send_data, cmd_response, response_length, FMT_CMD_FAIL ;; Base address of the VIA the keyboard is conencted to VIA = IO1 ;; @brief #clock cycles to to wait for the last 3 bits after the first 8 have been shifted in ;; @details ;; The last 3 bits take about 230 ms. ;; Calculate the appropriate value using: @f$ N_\text{cycles} = 230 \times f_\text{in MHZ} @f$ ;; - 230 \@ 1MHz ;; - 400 \@ 1.84 MHz ;; @clock_dependent TIMER_RECV = 400 ;; @brief #clock cycles to wait after loading the SR a second time while sending a command to the keyboard ;; @details ;; Enough time must pass for one bit must be shifted out (parity), ;; but at most two (parity+stop). ;; After that, the interrupt must have happened because the keyboard will pull data low. ;; At that point, the shift register needs to be set to input again ;; ;; Values that seem to work: ;; - 230 \@ 1MHz ;; - 400 \@ 1.84 MHz ;; @clock_dependent TIMER_SEND = 400 PULL_REG = IO::RANH PULL_DDR = IO::DDRA ; use RA4 to pull the clock low PULL_MASK_CLK = %00010000 ; using ff because 0 is a possible data byte (set led) NO_DATA = $ff ;; indicates that no data byte should be send NO_RESPONSE = $ff ;; indicates a command did not receive a response (yet) ACK = $fa ;; successful transmission RESEND = $fe ;; unsuccessful transmission .enum STATUS RECEIVE_KEYS = %10000000 ;; keyboard sends scancodes RECEIVE_ANSWER = %01000000 ;; keyboard replies to a command SEND_CMD = %00100000 ;; host sends/sent the command byte SEND_DATA = %00010000 ;; host sends/sent the data byte SEND_RECV = %00001000 ;; keyboard sends additional data byte SEND = SEND_CMD | SEND_DATA | SEND_RECV ;; host is sending something, only used for checking status NONE = %00000000 .endenum .enum TYPEMATIC REPEAT_MASK = %00011111 ;; 00000 = 30Hz, ..., 11111 = 2Hz DELAY250 = %00000000 DELAY500 = %00100000 DELAY750 = %01000000 DELAY1000 = %01100000 .endenum .endscope ; see https://wiki.osdev.org/PS/2_Keyboard .macro ps2kb_CmdEcho lda #$ee ldx #ps2kb::NO_DATA ldy #0 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdSetLeds mask lda #$ed .ifnblank mask ldx #mask .endif ldy #0 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdGetScancodeSet lda #$f0 ldx #0 ldy #1 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdSetScancodeSet set .assert set = 1 .or set = 2 .or set = 3, warning, "ps2kb_CmdSetScancodeSet: invalid scancode set" lda #$f0 ldx #set ldy #0 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdIdentify lda #$f2 ldx #ps2kb::NO_DATA ldy #2 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdTypematicSettings settings lda #$f3 ldx #settings ldy #0 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdEnable lda #$f4 ldx #ps2kb::NO_DATA ldy #0 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdDisable lda #$f5 ldx #ps2kb::NO_DATA ldy #0 jsr ps2kb::send_command .endmacro .macro ps2kb_CmdSetTypematic scancode ldy #0 .ifblank scancode lda #$f7 ldx #ps2kb::NO_DATA .else lda #$fb ldx #scancode .endif jsr ps2kb::send_command .endmacro .macro ps2kb_CmdSetMakeRelease scancode ldy #0 .ifblank scancode lda #$f8 ldx #ps2kb::NO_DATA .else lda #$fc ldx #scancode .endif jsr ps2kb::send_command .endmacro .macro ps2kb_CmdSetMake scancode ldy #0 .ifblank scancode lda #$f9 ldx #ps2kb::NO_DATA .else lda #$fd ldx #scancode .endif jsr ps2kb::send_command .endmacro .macro ps2kb_CmdSelfTest lda #$ff ldx #ps2kb::NO_DATA ldy #1 jsr ps2kb::send_command .endmacro .macro ps2kb_WaitFinishCmd : wai lda ps2kb::status and #ps2kb::STATUS::SEND bne :- .endmacro ;;******************************************************************************** ;; @note You need to manually include `string.h65` when using this macro! ;;******************************************************************************** .macro ps2kb_PrintCmdFailed Printf ps2kb::FMT_CMD_FAIL,ps2kb::send_cmd,ps2kb::send_data,ps2kb::cmd_response,ps2kb::cmd_response+1,ps2kb::cmd_response+2 .endmacro .endif ; guard