;;******************************************************************************** ;; @module ps2_keyboard ;; @type driver ;; @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" .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 VIA = IO1 ; Enough time must pass for 3 bits to be shifted in. ; TIMER_RECV = 230 ; 230 ms (@1MHz) TIMER_RECV = 400 ; 230 ms (@1MHz) ; 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 SR needs to be set to input again ; TIMER_SEND = 230 ; 180 ms (@1MHz) TIMER_SEND = 400 ; 180 ms (@1MHz) 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 .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