Compare commits

...

6 Commits

Author SHA1 Message Date
326b2f6247 include bit macros 2024-01-07 00:42:26 +01:00
e8696cc312 send commands working 2024-01-07 00:42:05 +01:00
23e5e3cd35 add useful macros 2024-01-07 00:41:12 +01:00
b778b4dcd1 add bit branch/set/reset macros 2024-01-07 00:40:23 +01:00
2a13ce0ca4 rename to keypad_printer 2024-01-07 00:39:51 +01:00
108e5af0b9 fix do all 8 bits 2024-01-03 13:08:04 +01:00
8 changed files with 433 additions and 63 deletions

View File

@ -1,7 +1,7 @@
.include "system/system.h65"
.include "string.h65"
.export home,homeloop
.import printer:absolute
.import keypad_printer:absolute
.import spi_menu:absolute
.code
@ -135,7 +135,7 @@ reset:
lda 0
; beq home
cmp #'A'
jeq printer
jeq keypad_printer
cmp #'B'
jeq spi_menu
cmp #'C'

View File

@ -4,7 +4,7 @@
.ifndef INCLUDE_PRINTER
INCLUDE_PRINTER = 1
.global printer
.global keypad_printer
.import home:absolute
.endif ; guard

View File

@ -1,9 +1,9 @@
.include "printer.h65"
.include "keypad_printer.h65"
.include "lcd.h65"
.include "keypad.h65"
.code
.proc printer
.proc keypad_printer
jsr lcd::clear
@printer_loop:
jsr kp::read

View File

@ -29,9 +29,9 @@ INCLUDE_PS2_KEYBOARD = 1
.include "system.h65"
.scope ps2kb
Import ps2kb,init,begin_receive,send_command,scancode,status,scancode_handler
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_last_bits, send_cmd, send_data, key_read, prev_status
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.
@ -41,22 +41,152 @@ TIMER_RECV = 230 ; 230 ms (@1MHz)
; 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)
; use RA4 to pull the clock low
PULL_REG = IO::RANH
PULL_DDR = IO::DDRA
; use RA4 to pull the clock low
PULL_MASK_CLK = %00010000
; PULL_MASK_DAT = %00001000
NO_DATA = $ff ; indicates that no data byte should be send
; 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
RECEIVE_ANSWER = %01000000
SEND_CMD = %00100000
SEND_DATA = %00010000
SEND_CMD_WAIT = %00001000
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

View File

@ -2,22 +2,26 @@
.include "string.h65"
.include "lcd.h65"
.include "parity.h65"
Export ps2kb,init,begin_receive,send_command,scancode,status,scancode_handler
Export ps2kb, init, begin_receive, scancode, status, scancode_handler
Export ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler, _send_byte, _send_irq_shift_reg_handler, _send_irq_timer_handler,
Export ps2kb, send_last_bits, send_cmd, send_data, key_read, prev_status
Export ps2kb, send_command, send_cmd, send_data, cmd_response, response_length, FMT_CMD_FAIL
.bss
status: .res 1
prev_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
status: .res 1 ; current status
prev_status: .res 1 ; status before sending command
send_last_bits: .res 1 ; last bits to load after 8 bits were shifted out
send_cmd: .res 1 ; command to send/last sent
send_data: .res 1 ; data byte to send/last sent or NO_DATA
expect_data_length: .res 1 ; number of data bytes to expect from keyboard
response_length: .res 1 ; number of response bytes from keyboard
cmd_response: .res 3 ; responses from keyboard
key_read: .res 2 ; first 8 bits, last 3 bits from scancode
scancode: .res 1 ; last received scancode
scancode_handler: .res 2 ; pointer to a function that handles new scancodes
.code
; these are macros the save time during the interrupt handler (no jsr, rts)
;;********************************************************************************
;; @macro Enable the clock signal from the keyboard
;; @modifies: A
@ -113,13 +117,14 @@ scancode_handler: .res 2
stz key_read
stz key_read+1
stz scancode
bit status
; 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_RECV
sta ps2kb::VIA + IO::T2CL
; set to RECEIVE_KEYS only if RECEIVE_ANSWER is not set
bit status
bvs @status_set
lda #ps2kb::STATUS::RECEIVE_KEYS
sta status
@ -157,7 +162,7 @@ scancode_handler: .res 2
IO_DisableIRQ ps2kb::VIA, IO::IRQ::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::T2
; start timer, low order count already in latch after init
; start timer, low order count already in latch after begin_receive
lda #>ps2kb::TIMER_RECV
sta ps2kb::VIA + IO::T2CH
rts
@ -201,41 +206,68 @@ scancode_handler: .res 2
sta scancode
; check parity
; CalculateOddParity
; eor key_read+1 ; if bit 0 is 1 - parity error
; and #1 ; bit 1 is still D7
; bne @parity_error
CalculateOddParity
eor key_read+1 ; if bit 0 is 1 - parity error
and #1 ; bit 1 is still D7
bne @parity_error
@handle_scancode:
; check what to do with the scancode
bit status
bcc @status_not_receive_keys
bpl @status_not_receive_keys ; RECEIVE_KEYS
jmp (scancode_handler)
@status_not_receive_keys:
bvc @status_ignore
bvc @status_dont_handle ; RECEIVE_ANSWER
jmp _process_cmd_answer
@status_ignore:
@status_dont_handle:
rts
@parity_error: ; TODO handle somehow
lda #$ff
@parity_error: ; TODO handle somehow
lda #$fe ; fe means error/resend for commands
sta scancode
DEBUG_LED_ON 2
rts
bra @handle_scancode
.endproc
;;********************************************************************************
;; @function Send a command to the keyboard
;; @details
;; No checks are done on the validity of the command and data byte.
;; You can send anything you want to the keyboard.
;; `send_command` will return immediately, as sending the command is asynchronous.
;; The command is done if `ps2kb::status & ps2kb::STATUS::SEND == 0`.
;; @section Response
;; The answers will be in the byte array ps2kb::cmd_response.
;; If the command did not have a data byte, cmd_response is:
;; - 0: command response
;; - 1: NO_RESPONSE or additional response byte (identify keyboard command only)
;; - 2: NO_RESPONSE or additional response byte (identify keyboard command only)
;; If the command did have a data byte, cmd_response is:
;; - 0: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 1: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 2: NO_RESPONSE or additional response byte
;;
;; If ANY of the bytes in cmd_response is 0xFE, the command failed.
;;
;; @modifies: A,X,Y
;; @param A: The command byte
;; @param X: The data byte or NO_DATA if only the command byte should be sent
;; @param Y: The number of data bytes expected to receive. Must be one of {0, 1, 2}
;;********************************************************************************
.proc send_command
sty expect_data_length
stx send_data
stz response_length
ldy #ps2kb::NO_RESPONSE
sty cmd_response
sty cmd_response+1
sty cmd_response+2
ldy status
sty prev_status
ldy #ps2kb::STATUS::SEND_CMD
sty status
sta send_cmd ; store if it needs to be resent
stx send_data
jmp _send_byte
.endproc
@ -278,8 +310,9 @@ scancode_handler: .res 2
lda #<ps2kb::TIMER_SEND
sta ps2kb::VIA + IO::T2CL
; setup interrupt handlers
StoreDByte _send_irq_shift_reg_handler, $3000
StoreDByte _send_irq_timer_handler, $3006
StoreDByte _send_irq_timer_handler, $3060
pla
Reverse A
pha
@ -287,7 +320,7 @@ scancode_handler: .res 2
ror ; Parity -> C
lda #$ff
ror ; Parity -> bit 7, rest = 1
sta ps2kb::send_last_bits ; loaded into SR by irq handler
sta send_last_bits ; loaded into SR by irq handler
pla
sta ps2kb::VIA + IO::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
@ -306,7 +339,7 @@ scancode_handler: .res 2
;; - start timer 2
;;********************************************************************************
.proc _send_irq_shift_reg_handler
lda ps2kb::send_last_bits
lda send_last_bits
sta ps2kb::VIA + IO::SR
IO_DisableIRQ ps2kb::VIA, IO::IRQ::SR
@ -319,34 +352,120 @@ scancode_handler: .res 2
;;********************************************************************************
;; @function Send databyte or reveice the keyboards answer
;; @modifies: A, X, Y
;; @function Setup VIA to receive the keyboard's answer
;; @modifies: A
;; @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
;; - pull clock low
;; - or status with RECEIVE_ANSWER
;; - begin receive
;;********************************************************************************
.proc _send_irq_timer_handler
lda ps2kb::VIA + IO::T2CL ; clear interrupt flag
lda ps2kb::VIA + IO::T2CL ; clear interrupt flag
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
lda ps2kb::send_data
beq @receive
stz ps2kb::send_data
jmp _send_byte
rts
@receive:
_DisableClock ; disable keyboard while setting up receive
lda #ps2kb::STATUS::RECEIVE_ANSWER
sta ps2kb::status
; disable shift register
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
sta ps2kb::VIA + IO::ACR
_DisableClock ; disable keyboard while setting up receive
lda status
ora #ps2kb::STATUS::RECEIVE_ANSWER
sta status
jmp ps2kb::begin_receive
.endproc
;;********************************************************************************
;; @function Process the response of a command
;; @details
;; Stores the answer in the `ps2kb::cmd_response` array.
;; If the response is $FE or command related transmissions are done (see below),
;; no further data will be sent or received and the `ps2kb::status` takes the previous value.
;;
;; - store response
;; - if response == 0xFE -> stop
;; - if status == SEND_CMD -> send databyte
;; - if statusk
;; You can send anything you want to the keyboard.
;; `send_command` will return immediately, as sending the command is asynchronous.
;; The command is done if `ps2kb::status & ps2kb::STATUS::SEND == 0`.
;; @section Response
;; The answers will be in the byte array ps2kb::cmd_response.
;; If the command did not have a data byte, cmd_response is:
;; - 0: command response
;; - 1: NO_RESPONSE or additional response byte (identify keyboard command only)
;; - 2: NO_RESPONSE or additional response byte (identify keyboard command only)
;; If the command did have a data byte, cmd_response is:
;; - 0: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 1: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 2: NO_RESPONSE or additional response byte
;;
;; If ANY of the bytes in cmd_response is 0xFE, the command failed.
;;
;; @modifies: A,X,Y
;; @param A: The command byte
;; @param X: The data byte or NO_DATA if only the command byte should be sent
;; @param Y: The number of data bytes expected to receive. Must be one of {0, 1, 2}
;;********************************************************************************
.proc _process_cmd_answer
jmp ($3200)
@store_response:
ldx response_length
lda scancode
sta cmd_response,x
inx
stx response_length
stz scancode
; check for resend
cmp #$fe
beq @cmd_fail
; received something useful, check state
lda status
bit #ps2kb::STATUS::SEND_CMD
bne @cmd_sent
bit #ps2kb::STATUS::SEND_DATA
bne @everything_sent
; status must be SEND_RECV
@receive_data_response:
dec expect_data_length
bmi @cmd_done ; no more expected data
rts ; wait for another data byte
@cmd_sent:
; check if a data byte needs to be sent
lda send_data
beq @send_data ; if 0 (would fall through the cmp check TODO would it??)
cmp #ps2kb::NO_DATA
beq @everything_sent ; if not NO_DATA
@send_data:
lda #ps2kb::STATUS::SEND_DATA
sta status
lda send_data
jmp ps2kb::_send_byte ; send the data
@everything_sent: ; check if additonal bytes are expected
dec expect_data_length
bmi @cmd_done ; expect_data_length was 0
lda #(ps2kb::STATUS::SEND_RECV | ps2kb::STATUS::RECEIVE_ANSWER)
sta status
rts
@cmd_fail: ; keyboard wont expect data byte/send an additional response byte
@cmd_done:
; restore previous status
lda prev_status
sta status
; disable the clock if the previous was not RECEIVE_KEYS
cmp #ps2kb::STATUS::RECEIVE_KEYS
beq @rts
_DisableClock
@rts:
rts
.endproc
.rodata
;; Can be used by other programs
FMT_CMD_FAIL: .asciiz "PS/2 Cmd fail: %x-%x > %x%x%x "

117
util/bit_macros.h65 Normal file
View File

@ -0,0 +1,117 @@
;;********************************************************************************
;; @macro Use the bbs instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to test
;; @param label: The label to jump to when the bit is set
;;********************************************************************************
.macro bbs mask,addr,label
.if mask = %00000001
bbs0 addr,label
.elseif mask = %00000010
bbs1 addr,label
.elseif mask = %00000100
bbs2 addr,label
.elseif mask = %00001000
bbs3 addr,label
.elseif mask = %00010000
bbs4 addr,label
.elseif mask = %00100000
bbs5 addr,label
.elseif mask = %01000000
bbs6 addr,label
.elseif mask = %10000000
bbs7 addr,label
.else
.fatal .sprintf("bbs macro got invalid mask: %x", mask)
.endif
.endmacro
;;********************************************************************************
;; @macro Use the bbr instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to test
;; @param label: The label to jump to when the bit is clear
;;********************************************************************************
.macro bbr mask,addr,label
.if mask = %00000001
bbr0 addr,label
.elseif mask = %00000010
bbr1 addr,label
.elseif mask = %00000100
bbr2 addr,label
.elseif mask = %00001000
bbr3 addr,label
.elseif mask = %00010000
bbr4 addr,label
.elseif mask = %00100000
bbr5 addr,label
.elseif mask = %01000000
bbr6 addr,label
.elseif mask = %10000000
bbr7 addr,label
.else
.fatal .sprintf("bbr macro got invalid mask: 0x%x", mask)
.endif
.endmacro
;;********************************************************************************
;; @macro Use the smb instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to update
;;********************************************************************************
.macro smb mask,addr
.if mask = %00000001
smb0 addr
.elseif mask = %00000010
smb1 addr
.elseif mask = %00000100
smb2 addr
.elseif mask = %00001000
smb3 addr
.elseif mask = %00010000
smb4 addr
.elseif mask = %00100000
smb5 addr
.elseif mask = %01000000
smb6 addr
.elseif mask = %10000000
smb7 addr
.else
.fatal .sprintf("smb macro got invalid mask: 0x%x", mask)
.endif
.endmacro
;;********************************************************************************
;; @macro Use the rmb instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to update
;;********************************************************************************
.macro rmb mask,addr
.if mask = %00000001
rmb0 addr
.elseif mask = %00000010
rmb1 addr
.elseif mask = %00000100
rmb2 addr
.elseif mask = %00001000
rmb3 addr
.elseif mask = %00010000
rmb4 addr
.elseif mask = %00100000
rmb5 addr
.elseif mask = %01000000
rmb6 addr
.elseif mask = %10000000
rmb7 addr
.else
.fatal .sprintf("rmb macro got invalid mask: 0x%x", mask)
.endif
.endmacro

View File

@ -8,7 +8,7 @@
;; @returns Y: The number of set bits in the byte
;;********************************************************************************
.proc count_set_bits
ldx #8
ldx #9
ldy #0 ; number of set bits
@loop:
dex

View File

@ -7,6 +7,8 @@ INCLUDE_UTILITY = 1
.feature string_escapes
.feature underline_in_numbers
.include "bit_macros.h65"
.macro DEBUG_LED_OFF nr
pha
lda IO1 + IO::RA
@ -204,4 +206,6 @@ _n_genlabel .set 0
.endif
.endmacro
.endif ; guard