Resident driver — joystick + keypad

Updated 2 Sep 2026

← All Voltmace Delta 14B Driver versions

Install the resident joystick driver

Point BYTEV at the OSBYTE intercept at &0A0D. Called from the BASIC via CALL &A00, after it has saved the previous BYTEV (line 50) and patched the chain slot (line 1840) and sensitivity thresholds (line 1850).

0A00 .install
PHA ; Preserve A
0A01 LDA #&0d ; BYTEV low byte -> &0D...
0A03 STA bytev ; store it
0A06 LDA #&0a ; ...high byte -> &0A...
0A08 STA bytev_hi ; ...so every OSBYTE now enters the handler at &0A0D
0A0B PLA ; Restore A
0A0C RTS ; Return to the BASIC caller

BYTEV handler: map the joystick and keypad to INKEY

As variant A, but joystick_map entries >= &10 test the Delta 14B keypad matrix through the User VIA instead of an analogue channel.

0A0D .osbyte_intercept
CMP #&81 ; Only intercept OSBYTE &81 (INKEY / read key)
0A0F BNE chain ; other reason codes -> chain to the previous handler
0A11 PHA ; Preserve A (reason code)...
0A12 TYA ; Preserve Y (INKEY high byte)...
0A13 PHA ; on the stack
0A14 TXA ; A := X = the INKEY key number being tested
0A15 PHA ; ...(X also preserved)
0A16 LDY #0 ; No match yet...
0A18 STY result_flag ; ...clear the result flag
0A1B .scan_map←1← 0A27 BNE
CMP joystick_map,y ; Does the tested key match this map entry?
0A1E BNE scan_next ; no -> skip it
0A20 JSR read_input ; yes -> read the mapped input for it
0A23 .scan_next←1← 0A1E BNE
INY ; Step over the 2-byte entry...
0A24 INY ; twice
0A25 CPY #&48 ; end of the map?
0A27 BNE scan_map ; no -> keep scanning
0A29 LDA result_flag ; Did any mapped input read as active?
0A2C BEQ not_pressed ; no -> return the normal INKEY result
0A2E TAX ; yes: return "key pressed" (X=Y=&FF)...
0A2F TAY ; X and Y
0A30 PLA ; drop the saved X...
0A31 PLA ; ...Y...
0A32 PLA ; ...A
0A33 RTS ; and return, claiming the OSBYTE
0A34 .not_pressed←1← 0A2C BEQ
PLA ; Restore X...
0A35 TAX ; into X
0A36 PLA ; Restore Y...
0A37 TAY ; into Y
0A38 PLA ; Restore A
0A39 .chain←1← 0A0F BNE
JMP chain_to_old_bytev ; Pass the call to the previous BYTEV (chain slot below)

Test one joystick or keypad input

For the matched entry: entries below &10 read an analogue channel (OSBYTE &80 / ADVAL) against the thresholds; entries >= &10 strobe a keypad column through User VIA port B and test a row bit. A hit sets result_flag.

0A3C .read_input←1← 0A20 JSR
PHA ; Preserve A...
0A3D TYA ; Preserve Y...
0A3E PHA ; on the stack
0A3F CMP #&10 ; Analogue entry (< &10) or keypad entry?
0A41 BPL test_keypad ; >= &10 -> the keypad path
0A43 INY ; Point at the entry parameter
0A44 LDA joystick_map,y ; ADC channel in the top nibble...
0A47 LSR ; ...shift it down...
0A48 LSR ; ...shift it down...
0A49 LSR ; ...shift it down...
0A4A LSR ; ...shift it down...
0A4B TAX ; ...into X for OSBYTE &80
0A4C LDA joystick_map,y ; reload the parameter
0A4F AND #1 ; low bit picks which threshold (push vs pull)
0A51 BEQ test_high_threshold ; else test the high threshold
0A53 LDA #&80 ; OSBYTE &80: read ADC channel X (ADVAL, Y=high byte)...
0A55 JSR osbyte ; call OSBYTE
0A58 CPY threshold_lo ; past the low threshold?
0A5B BCC read_done ; no -> not active
0A5D BCS active ; yes -> active
0A5F .test_high_threshold←1← 0A51 BEQ
LDA #&80 ; OSBYTE &80: read ADC channel X...
0A61 JSR osbyte ; call OSBYTE
0A64 CPY threshold_hi ; past the high threshold?
0A67 BCC active ; no -> active
0A69 BCS read_done ; yes -> not active
0A6B .test_keypad←1← 0A41 BPL
INY ; Point at the entry parameter
0A6C LDA joystick_map,y ; top nibble = the column strobe...
0A6F AND #&f0 ; mask the top nibble
0A71 STA col_strobe ; ...saved
0A74 LDA joystick_map,y ; low nibble = the row bit mask...
0A77 AND #&0f ; mask the low nibble
0A79 LDY #&f0 ; DDRB = &F0: strobes out, rows in...
0A7B STY user_via_ddrb ; set it
0A7E STA row_mask ; save the row mask
0A81 LDY col_strobe ; Drive the column strobe...
0A84 STY user_via_orb ; ...onto port B
0A87 LDY user_via_orb ; Read the rows back...
0A8A TYA ; into A
0A8B AND row_mask ; ...and mask this row bit
0A8E BEQ active ; low (pressed) -> not-active path...
0A90 BNE read_done ; high -> active
0A92 .active←3← 0A5D BCS← 0A67 BCC← 0A8E BEQ
LDA #&ff ; Flag a hit...
0A94 STA result_flag ; ...in result_flag
0A97 .read_done←3← 0A5B BCC← 0A69 BCS← 0A90 BNE
PLA ; Restore Y...
0A98 TAY ; into Y
0A99 PLA ; Restore A
0A9A RTS ; Done
0A9B .row_mask←2← 0A7E STA← 0A8B AND
EQUB &EA ; Scratch: keypad row bit mask (&EA at rest)
0A9C .col_strobe←2← 0A71 STA← 0A81 LDY
EQUB &EA ; Scratch: keypad column strobe (&EA at rest)
0A9D .result_flag←3← 0A18 STY← 0A29 LDA← 0A94 STA
EQUB &EA ; Result byte (&EA at rest): the handler zeroes it, then sets it to &FF if a mapped input reads active
; Unused
0A9E .unused_b
EQUB &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00, &00
; 2-byte entries <INKEY key, input descriptor>. The key byte is ; 0 here; the BASIC (PROCASSEM, lines 1780-1830) pokes each with ; the user's chosen INKEY value. The fixed descriptor decodes ; as:
0AB0 .joystick_map←5Used as index base by← 0A1B CMP← 0A44 LDA← 0A4C LDA← 0A6C LDA← 0A74 LDA
EQUB &00, ; joystick axis: ADC channel 1, high threshold &10
0AB2 EQUB &00, &11 ; joystick axis: ADC channel 1, low threshold
0AB4 EQUB &00, ; joystick axis: ADC channel 2, high threshold &20
0AB6 EQUB &00, &21 ; joystick axis: ADC channel 2, low threshold
0AB8 EQUB &00, ; joystick axis: ADC channel 3, high threshold &30
0ABA EQUB &00, &31 ; joystick axis: ADC channel 3, low threshold
0ABC EQUB &00, ; joystick axis: ADC channel 4, high threshold &40
0ABE EQUB &00, &41 ; joystick axis: ADC channel 4, low threshold
0AC0 EQUB &00, &51 ; keypad: strobe column &50, test row bit &1
0AC2 EQUB &00, &51 ; keypad: strobe column &50, test row bit &1
0AC4 EQUB &00, &51 ; keypad: strobe column &50, test row bit &1
0AC6 EQUB &00, &31 ; keypad: strobe column &30, test row bit &1
0AC8 EQUB &00, &61 ; keypad: strobe column &60, test row bit &1
0ACA EQUB &00, &32 ; keypad: strobe column &30, test row bit &2
0ACC EQUB &00, &52 ; keypad: strobe column &50, test row bit &2
0ACE EQUB &00, &62 ; keypad: strobe column &60, test row bit &2
0AD0 EQUB &00, &34 ; keypad: strobe column &30, test row bit &4
0AD2 EQUB &00, &54 ; keypad: strobe column &50, test row bit &4
0AD4 EQUB &00, &64 ; keypad: strobe column &60, test row bit &4
0AD6 EQUB &00, &38 ; keypad: strobe column &30, test row bit &8
0AD8 EQUB &00, &58 ; keypad: strobe column &50, test row bit &8
0ADA EQUB &00, &68 ; keypad: strobe column &60, test row bit &8
0ADC EQUB &00, &D1 ; keypad: strobe column &D0, test row bit &1
0ADE EQUB &00, &D1 ; keypad: strobe column &D0, test row bit &1
0AE0 EQUB &00, &D1 ; keypad: strobe column &D0, test row bit &1
0AE2 EQUB &00, &B1 ; keypad: strobe column &B0, test row bit &1
0AE4 EQUB &00, &E1 ; keypad: strobe column &E0, test row bit &1
0AE6 EQUB &00, &B2 ; keypad: strobe column &B0, test row bit &2
0AE8 EQUB &00, &D2 ; keypad: strobe column &D0, test row bit &2
0AEA EQUB &00, &E2 ; keypad: strobe column &E0, test row bit &2
0AEC EQUB &00, &B4 ; keypad: strobe column &B0, test row bit &4
0AEE EQUB &00, &D4 ; keypad: strobe column &D0, test row bit &4
0AF0 EQUB &00, &E4 ; keypad: strobe column &E0, test row bit &4
0AF2 EQUB &00, &B8 ; keypad: strobe column &B0, test row bit &8
0AF4 EQUB &00, &D8 ; keypad: strobe column &D0, test row bit &8
0AF6 EQUB &00, &E8 ; keypad: strobe column &E0, test row bit &8
0AF8 EQUB &00, &00 ; end-of-map marker
; RTS placeholder; the BASIC (line 1840) patches this to JMP ; <previous BYTEV> so non-INKEY OSBYTEs are chained.
0AFA .chain_to_old_bytev←1← 0A39 JMP
EQUB &60, &00, &00
0AFD .threshold_lo←1← 0A58 CPY
EQUB &00 ; Low sensitivity threshold (BASIC line 1850: SL%)
0AFE .threshold_hi←1← 0A64 CPY
EQUB &00 ; High sensitivity threshold (BASIC line 1850: SH%)
0AFF .unused_b_end
EQUB &00 ; Unused