mirror of
https://github.com/hsoft/collapseos.git
synced 2024-09-07 14:58:45 +10:00
207 lines
4.6 KiB
NASM
207 lines
4.6 KiB
NASM
; pad - read input from MD controller
|
|
;
|
|
; Conveniently expose an API to read the status of a MD pad A. Moreover,
|
|
; implement a mechanism to input arbitrary characters from it. It goes as
|
|
; follow:
|
|
;
|
|
; * Direction pad select characters. Up/Down move by one, Left/Right move by 5\
|
|
; * Start acts like Return
|
|
; * A acts like Backspace
|
|
; * B changes "character class": lowercase, uppercase, numbers, special chars.
|
|
; The space character is the first among special chars.
|
|
; * C confirms letter selection
|
|
;
|
|
; This module is currently hard-wired to sms/vdp, that is, it calls vdp's
|
|
; routines during padGetC to update character selection.
|
|
;
|
|
; *** Consts ***
|
|
;
|
|
.equ PAD_CTLPORT 0x3f
|
|
.equ PAD_D1PORT 0xdc
|
|
|
|
.equ PAD_UP 0
|
|
.equ PAD_DOWN 1
|
|
.equ PAD_LEFT 2
|
|
.equ PAD_RIGHT 3
|
|
.equ PAD_BUTB 4
|
|
.equ PAD_BUTC 5
|
|
.equ PAD_BUTA 6
|
|
.equ PAD_START 7
|
|
|
|
; *** Variables ***
|
|
;
|
|
; Button status of last padUpdateSel call. Used for debouncing.
|
|
.equ PAD_SELSTAT PAD_RAMSTART
|
|
; Current selected character
|
|
.equ PAD_SELCHR @+1
|
|
; When non-zero, will be the next char returned in GetC. So far, only used for
|
|
; LF that is feeded when Start is pressed.
|
|
.equ PAD_NEXTCHR @+1
|
|
.equ PAD_RAMEND @+1
|
|
|
|
; *** Code ***
|
|
|
|
padInit:
|
|
ld a, 0xff
|
|
ld (PAD_SELSTAT), a
|
|
xor a
|
|
ld (PAD_NEXTCHR), a
|
|
ld a, 'a'
|
|
ld (PAD_SELCHR), a
|
|
ret
|
|
|
|
; Put status for port A in register A. Bits, from MSB to LSB:
|
|
; Start - A - C - B - Right - Left - Down - Up
|
|
; Each bit is high when button is unpressed and low if button is pressed. For
|
|
; example, when no button is pressed, 0xff is returned.
|
|
padStatus:
|
|
; This logic below is for the Genesis controller, which is modal. TH is
|
|
; an output pin that swiches the meaning of TL and TR. When TH is high
|
|
; (unselected), TL = Button B and TR = Button C. When TH is low
|
|
; (selected), TL = Button A and TR = Start.
|
|
push bc
|
|
ld a, 0b11111101 ; TH output, unselected
|
|
out (PAD_CTLPORT), a
|
|
in a, (PAD_D1PORT)
|
|
and 0x3f ; low 6 bits are good
|
|
ld b, a ; let's store them
|
|
; Start and A are returned when TH is selected, in bits 5 and 4. Well
|
|
; get them, left-shift them and integrate them to B.
|
|
ld a, 0b11011101 ; TH output, selected
|
|
out (PAD_CTLPORT), a
|
|
in a, (PAD_D1PORT)
|
|
and 0b00110000
|
|
sla a
|
|
sla a
|
|
or b
|
|
pop bc
|
|
ret
|
|
|
|
; From a pad status in A, update current char selection and return it.
|
|
; Sets Z if current selection was unchanged, unset if changed.
|
|
padUpdateSel:
|
|
call padStatus
|
|
push hl ; --> lvl 1
|
|
ld hl, PAD_SELSTAT
|
|
cp (hl)
|
|
ld (hl), a
|
|
pop hl ; <-- lvl 1
|
|
jr z, .nothing ; nothing changed
|
|
bit PAD_UP, a
|
|
jr z, .up
|
|
bit PAD_DOWN, a
|
|
jr z, .down
|
|
bit PAD_LEFT, a
|
|
jr z, .left
|
|
bit PAD_RIGHT, a
|
|
jr z, .right
|
|
bit PAD_BUTB, a
|
|
jr z, .nextclass
|
|
jr .nothing
|
|
.up:
|
|
ld a, (PAD_SELCHR)
|
|
inc a
|
|
jr .setchr
|
|
.down:
|
|
ld a, (PAD_SELCHR)
|
|
dec a
|
|
jr .setchr
|
|
.left:
|
|
ld a, (PAD_SELCHR)
|
|
dec a \ dec a \ dec a \ dec a \ dec a
|
|
jr .setchr
|
|
.right:
|
|
ld a, (PAD_SELCHR)
|
|
inc a \ inc a \ inc a \ inc a \ inc a
|
|
jr .setchr
|
|
.nextclass:
|
|
; Go to the beginning of the next "class" of characters
|
|
push bc
|
|
ld a, (PAD_SELCHR)
|
|
ld b, '0'
|
|
cp b
|
|
jr c, .setclass ; A < '0'
|
|
ld b, ':'
|
|
cp b
|
|
jr c, .setclass
|
|
ld b, 'A'
|
|
cp b
|
|
jr c, .setclass
|
|
ld b, '['
|
|
cp b
|
|
jr c, .setclass
|
|
ld b, 'a'
|
|
cp b
|
|
jr c, .setclass
|
|
ld b, ' '
|
|
; continue to .setclass
|
|
.setclass:
|
|
ld a, b
|
|
pop bc
|
|
; continue to .setchr
|
|
.setchr:
|
|
; check range first
|
|
cp 0x7f
|
|
jr nc, .tooHigh
|
|
cp 0x20
|
|
jr nc, .setchrEnd ; not too low
|
|
; too low, probably because we overdecreased. Let's roll over
|
|
ld a, '~'
|
|
jr .setchrEnd
|
|
.tooHigh:
|
|
; too high, probably because we overincreased. Let's roll over
|
|
ld a, ' '
|
|
; continue to .setchrEnd
|
|
.setchrEnd:
|
|
ld (PAD_SELCHR), a
|
|
jp unsetZ
|
|
.nothing:
|
|
; Z already set
|
|
ld a, (PAD_SELCHR)
|
|
ret
|
|
|
|
; Repeatedly poll the pad for input and returns the resulting "input char".
|
|
; This routine takes a long time to return because it waits until C, B or Start
|
|
; was pressed. Until this is done, this routine takes care of updating the
|
|
; "current selection" directly in the VDP.
|
|
padGetC:
|
|
ld a, (PAD_NEXTCHR)
|
|
or a
|
|
jr nz, .nextchr
|
|
call padUpdateSel
|
|
jp z, padGetC ; nothing changed, loop
|
|
; pad status was changed, let's see if an action button was pressed
|
|
ld a, (PAD_SELSTAT)
|
|
bit PAD_BUTC, a
|
|
jr z, .advance
|
|
bit PAD_BUTA, a
|
|
jr z, .backspace
|
|
bit PAD_START, a
|
|
jr z, .return
|
|
; no action button pressed, but because our pad status changed, update
|
|
; VDP before looping.
|
|
ld a, (PAD_SELCHR)
|
|
call vdpConv
|
|
call vdpSpitC
|
|
jp padGetC
|
|
.return:
|
|
ld a, LF
|
|
ld (PAD_NEXTCHR), a
|
|
; continue to .advance
|
|
.advance:
|
|
ld a, (PAD_SELCHR)
|
|
; Z was already set from previous BIT instruction
|
|
ret
|
|
.backspace:
|
|
ld a, BS
|
|
; Z was already set from previous BIT instruction
|
|
ret
|
|
.nextchr:
|
|
; We have a "next char", return it and clear it.
|
|
cp a ; ensure Z
|
|
ex af, af'
|
|
xor a
|
|
ld (PAD_NEXTCHR), a
|
|
ex af, af'
|
|
ret
|