mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-24 01:18:06 +11:00
recipes/sms/kbd: PS/2 keyboard adapter for the SMS!
This commit is contained in:
parent
23354eba94
commit
57e7b3ca05
38
README.md
38
README.md
@ -6,14 +6,12 @@ Collapse OS is a z80 kernel and a collection of programs, tools and
|
|||||||
documentation that allows you to assemble an OS that, when completed, will be
|
documentation that allows you to assemble an OS that, when completed, will be
|
||||||
able to:
|
able to:
|
||||||
|
|
||||||
1. Run on an extremely minimal and improvised architecture.
|
1. Run on minimal and improvised machines.
|
||||||
2. Communicate through a improvised serial interface linked to some kind of
|
2. Interface through improvised means (serial, keyboard, display).
|
||||||
improvised terminal.
|
|
||||||
3. Edit text files.
|
3. Edit text files.
|
||||||
4. Compile assembler source files for a wide range of MCUs and CPUs.
|
4. Compile assembler source files for a wide range of MCUs and CPUs.
|
||||||
5. Write files to a wide range of flash ICs and MCUs.
|
5. Read and write from a wide range of storage devices.
|
||||||
6. Access data storage from improvised systems.
|
6. Replicate itself.
|
||||||
7. Replicate itself.
|
|
||||||
|
|
||||||
Additionally, the goal of this project is to be as self-contained as possible.
|
Additionally, the goal of this project is to be as self-contained as possible.
|
||||||
With a copy of this project, a capable and creative person should be able to
|
With a copy of this project, a capable and creative person should be able to
|
||||||
@ -21,27 +19,6 @@ manage to build and install Collapse OS without external resources (i.e.
|
|||||||
internet) on a machine of her design, built from scavenged parts with low-tech
|
internet) on a machine of her design, built from scavenged parts with low-tech
|
||||||
tools.
|
tools.
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
The project unfinished but is progressing well! Highlights:
|
|
||||||
|
|
||||||
* Self replicates: Can assemble itself from within itself, given enough RAM and
|
|
||||||
storage.
|
|
||||||
* Has a shell that can poke memory, I/O, call arbitrary code from memory.
|
|
||||||
* Can "upload" code from serial link into memory and execute it.
|
|
||||||
* Can manage multiple "block devices".
|
|
||||||
* Can read and write to SD cards.
|
|
||||||
* A z80 assembler, written in z80 that is self-assembling and can assemble the
|
|
||||||
whole project.
|
|
||||||
* Compact:
|
|
||||||
* Kernel: 3K binary, 1800 SLOC.
|
|
||||||
* ZASM: 4K binary, 2300 SLOC, 16K RAM usage to assemble kernel or itself.
|
|
||||||
* Extremely flexible: Kernel parts are written as loosely knit modules that
|
|
||||||
are bound through glue code. This makes the kernel adaptable to many unforseen
|
|
||||||
situations.
|
|
||||||
* From a GNU environment, can be built with minimal tooling: only
|
|
||||||
[libz80][libz80], make and a C compiler are needed.
|
|
||||||
|
|
||||||
## Organisation of this repository
|
## Organisation of this repository
|
||||||
|
|
||||||
* `kernel`: Pieces of code to be assembled by the user into a kernel.
|
* `kernel`: Pieces of code to be assembled by the user into a kernel.
|
||||||
@ -55,9 +32,10 @@ The project unfinished but is progressing well! Highlights:
|
|||||||
|
|
||||||
Each folder has a README with more details.
|
Each folder has a README with more details.
|
||||||
|
|
||||||
## More information
|
## Status
|
||||||
|
|
||||||
Go to [Collapse OS' website](https://collapseos.org) for more information on the
|
The project unfinished but is progressing well! See [Collapse OS' website][web]
|
||||||
project.
|
for more information.
|
||||||
|
|
||||||
[libz80]: https://github.com/ggambetta/libz80
|
[libz80]: https://github.com/ggambetta/libz80
|
||||||
|
[web]: https://collapseos.org
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
; kbd - implement GetC for PS/2 keyboard
|
; kbd - implement GetC for PS/2 keyboard
|
||||||
;
|
;
|
||||||
; Status: Work in progress. See recipes/rc2014/ps2
|
; It reads raw key codes from a FetchKC routine and returns, if appropriate,
|
||||||
|
; a proper ASCII char to type. See recipes rc2014/ps2 and sms/kbd.
|
||||||
;
|
;
|
||||||
; *** Defines ***
|
; *** Defines ***
|
||||||
; The port of the device where we read scan codes. See recipe rc2014/ps2.
|
; Pointer to a routine that fetches the last typed keyword in A. Should return
|
||||||
; KBD_PORT
|
; 0 when nothing was typed.
|
||||||
|
; KBD_FETCHKC
|
||||||
|
|
||||||
; *** Variables ***
|
; *** Variables ***
|
||||||
.equ KBD_SKIP_NEXT KBD_RAMSTART
|
.equ KBD_SKIP_NEXT KBD_RAMSTART
|
||||||
|
; Pointer to a routine that fetches the last typed keyword in A. Should return
|
||||||
|
; 0 when nothing was typed.
|
||||||
.equ KBD_RAMEND KBD_SKIP_NEXT+1
|
.equ KBD_RAMEND KBD_SKIP_NEXT+1
|
||||||
|
|
||||||
kbdInit:
|
kbdInit:
|
||||||
@ -16,7 +20,7 @@ kbdInit:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
kbdGetC:
|
kbdGetC:
|
||||||
in a, (KBD_PORT)
|
call KBD_FETCHKC
|
||||||
or a
|
or a
|
||||||
jr z, .nothing
|
jr z, .nothing
|
||||||
|
|
||||||
|
102
kernel/sms/kbd.asm
Normal file
102
kernel/sms/kbd.asm
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
; kbd - implement FetchKC for SMS PS/2 adapter
|
||||||
|
;
|
||||||
|
; Implements KBD_FETCHKC for the adapter described in recipe sms/kbd. It does
|
||||||
|
; so for both Port A and Port B (you hook whichever you prefer).
|
||||||
|
|
||||||
|
; FetchKC on Port A
|
||||||
|
smskbdFetchKCA:
|
||||||
|
; Before reading a character, we must first verify that there is
|
||||||
|
; something to read. When the adapter is finished filling its '164 up,
|
||||||
|
; it resets the latch, which output's is connected to TL. When the '164
|
||||||
|
; is full, TL is low.
|
||||||
|
; Port A TL is bit 4
|
||||||
|
in a, (0xdc)
|
||||||
|
and 0b00010000
|
||||||
|
jr nz, .nothing
|
||||||
|
|
||||||
|
push bc
|
||||||
|
in a, (0x3f)
|
||||||
|
; Port A TH output, low
|
||||||
|
ld a, 0b11011101
|
||||||
|
out (0x3f), a
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
in a, (0xdc)
|
||||||
|
; bit 3:0 are our dest bits 3:0. handy...
|
||||||
|
and 0b00001111
|
||||||
|
ld b, a
|
||||||
|
; Port A TH output, high
|
||||||
|
ld a, 0b11111101
|
||||||
|
out (0x3f), a
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
in a, (0xdc)
|
||||||
|
; bit 3:0 are our dest bits 7:4
|
||||||
|
rlca \ rlca \ rlca \ rlca
|
||||||
|
and 0b11110000
|
||||||
|
or b
|
||||||
|
ex af, af'
|
||||||
|
; Port A/B reset
|
||||||
|
ld a, 0xff
|
||||||
|
out (0x3f), a
|
||||||
|
ex af, af'
|
||||||
|
pop bc
|
||||||
|
ret
|
||||||
|
|
||||||
|
.nothing:
|
||||||
|
xor a
|
||||||
|
ret
|
||||||
|
|
||||||
|
; FetchKC on Port B
|
||||||
|
smskbdFetchKCB:
|
||||||
|
; Port B TL is bit 2
|
||||||
|
in a, (0xdd)
|
||||||
|
and 0b00000100
|
||||||
|
jr nz, .nothing
|
||||||
|
|
||||||
|
push bc
|
||||||
|
in a, (0x3f)
|
||||||
|
; Port B TH output, low
|
||||||
|
ld a, 0b01110111
|
||||||
|
out (0x3f), a
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
in a, (0xdc)
|
||||||
|
; bit 7:6 are our dest bits 1:0
|
||||||
|
rlca \ rlca
|
||||||
|
and 0b00000011
|
||||||
|
ld b, a
|
||||||
|
in a, (0xdd)
|
||||||
|
; bit 1:0 are our dest bits 3:2
|
||||||
|
rlca \ rlca
|
||||||
|
and 0b00001100
|
||||||
|
or b
|
||||||
|
ld b, a
|
||||||
|
; Port B TH output, high
|
||||||
|
ld a, 0b11110111
|
||||||
|
out (0x3f), a
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
in a, (0xdc)
|
||||||
|
; bit 7:6 are our dest bits 5:4
|
||||||
|
rrca \ rrca
|
||||||
|
and 0b00110000
|
||||||
|
or b
|
||||||
|
ld b, a
|
||||||
|
in a, (0xdd)
|
||||||
|
; bit 1:0 are our dest bits 7:6
|
||||||
|
rrca \ rrca
|
||||||
|
and 0b11000000
|
||||||
|
or b
|
||||||
|
ex af, af'
|
||||||
|
; Port A/B reset
|
||||||
|
ld a, 0xff
|
||||||
|
out (0x3f), a
|
||||||
|
ex af, af'
|
||||||
|
pop bc
|
||||||
|
ret
|
||||||
|
|
||||||
|
.nothing:
|
||||||
|
xor a
|
||||||
|
ret
|
||||||
|
|
@ -26,6 +26,7 @@ are other recipes related to the RC2014:
|
|||||||
* [Writing to a AT28 from Collapse OS](eeprom/README.md)
|
* [Writing to a AT28 from Collapse OS](eeprom/README.md)
|
||||||
* [Accessing a MicroSD card](sdcard/README.md)
|
* [Accessing a MicroSD card](sdcard/README.md)
|
||||||
* [Assembling binaries](zasm/README.md)
|
* [Assembling binaries](zasm/README.md)
|
||||||
|
* [Interfacing a PS/2 keyboard](ps2/README.md)
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
|
@ -35,3 +35,8 @@ init:
|
|||||||
call stdioInit
|
call stdioInit
|
||||||
call shellInit
|
call shellInit
|
||||||
jp shellLoop
|
jp shellLoop
|
||||||
|
|
||||||
|
KBD_FETCHKC:
|
||||||
|
in a, (KBD_PORT)
|
||||||
|
ret
|
||||||
|
|
||||||
|
@ -74,7 +74,6 @@
|
|||||||
; - 1: receiving data
|
; - 1: receiving data
|
||||||
; - 2: awaiting parity bit
|
; - 2: awaiting parity bit
|
||||||
; - 3: awaiting stop bit
|
; - 3: awaiting stop bit
|
||||||
; it reaches 11, we know we're finished with the frame.
|
|
||||||
; R19: Register used for parity computations and tmp value in some other places
|
; R19: Register used for parity computations and tmp value in some other places
|
||||||
; R20: data being sent to the 595
|
; R20: data being sent to the 595
|
||||||
; Y: pointer to the memory location where the next scan code from ps/2 will be
|
; Y: pointer to the memory location where the next scan code from ps/2 will be
|
||||||
@ -167,9 +166,14 @@ loop:
|
|||||||
brts processbit ; flag T set? we have a bit to process
|
brts processbit ; flag T set? we have a bit to process
|
||||||
cp YL, ZL ; if YL == ZL, buffer is empty
|
cp YL, ZL ; if YL == ZL, buffer is empty
|
||||||
brne sendTo595 ; YL != ZL? our buffer has data
|
brne sendTo595 ; YL != ZL? our buffer has data
|
||||||
|
|
||||||
|
; nothing to do. Before looping, let's check if our communication timer
|
||||||
|
; overflowed.
|
||||||
in r16, TIFR
|
in r16, TIFR
|
||||||
sbrc r16, TOV0
|
sbrc r16, TOV0
|
||||||
rjmp processbitReset ; Timer0 overflow? reset processbit
|
rjmp processbitReset ; Timer0 overflow? reset processbit
|
||||||
|
|
||||||
|
; Nothing to do for real.
|
||||||
rjmp loop
|
rjmp loop
|
||||||
|
|
||||||
; Process the data bit received in INT0 handler.
|
; Process the data bit received in INT0 handler.
|
||||||
|
@ -12,6 +12,13 @@ platform and this is where most of my information comes from.
|
|||||||
This platform is tight on RAM. It has 8k of it. However, if you have extra RAM,
|
This platform is tight on RAM. It has 8k of it. However, if you have extra RAM,
|
||||||
you can put it on your cartridge.
|
you can put it on your cartridge.
|
||||||
|
|
||||||
|
## Related recipes
|
||||||
|
|
||||||
|
This recipe is for installing a minimal Collapse OS system on the SMS. There
|
||||||
|
are other recipes related to the SMS:
|
||||||
|
|
||||||
|
* [Interfacing a PS/2 keyboard](kbd/README.md)
|
||||||
|
|
||||||
## Gathering parts
|
## Gathering parts
|
||||||
|
|
||||||
* [zasm][zasm]
|
* [zasm][zasm]
|
||||||
@ -47,7 +54,8 @@ D-Pad is used as follow:
|
|||||||
* Start button is like pressing Return.
|
* Start button is like pressing Return.
|
||||||
|
|
||||||
Of course, that's not a fun way to enter text, but using the D-Pad is the
|
Of course, that's not a fun way to enter text, but using the D-Pad is the
|
||||||
easiest way to get started. I'm working on a PS/2 keyboard adapter for the SMS.
|
easiest way to get started which doesn't require soldering. Your next step after
|
||||||
|
that would be to [build a PS/2 keyboard adapter!](kbd/README.md)
|
||||||
|
|
||||||
[smspower]: http://www.smspower.org
|
[smspower]: http://www.smspower.org
|
||||||
[everdrive]: https://krikzz.com
|
[everdrive]: https://krikzz.com
|
||||||
|
25
recipes/sms/kbd/Makefile
Normal file
25
recipes/sms/kbd/Makefile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
PROGNAME = ps2ctl
|
||||||
|
AVRDUDEMCU ?= t45
|
||||||
|
AVRDUDEARGS ?= -c usbtiny -P usb
|
||||||
|
TARGETS = $(PROGNAME).hex os.sms
|
||||||
|
ZASM = ../../../tools/zasm.sh
|
||||||
|
KERNEL = ../../../kernel
|
||||||
|
|
||||||
|
# Rules
|
||||||
|
|
||||||
|
.PHONY: send all clean
|
||||||
|
|
||||||
|
all: $(TARGETS)
|
||||||
|
@echo Done!
|
||||||
|
|
||||||
|
send: $(PROGNAME).hex
|
||||||
|
avrdude $(AVRDUDEARGS) -p $(AVRDUDEMCU) -U flash:w:$<
|
||||||
|
|
||||||
|
$(PROGNAME).hex: $(PROGNAME).asm
|
||||||
|
avra -o $@ $<
|
||||||
|
|
||||||
|
os.sms: glue.asm
|
||||||
|
$(ZASM) $(KERNEL) < $< > $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGETS) *.eep.hex *.obj os.bin
|
117
recipes/sms/kbd/README.md
Normal file
117
recipes/sms/kbd/README.md
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# PS/2 keyboard on the SMS
|
||||||
|
|
||||||
|
Using the shell with a D-pad on the SMS is doable, but not fun at all! We're
|
||||||
|
going to build an adapter for a PS/2 keyboard to plug as a SMS controller.
|
||||||
|
|
||||||
|
The PS/2 logic will be the same as the [RC2014's PS/2 adapter][rc2014-ps2] but
|
||||||
|
instead of interfacing directly with the bus, we interface with the SMS'
|
||||||
|
controller subsystem (that is, what we poke on ports `0x3f` and `0xdc`).
|
||||||
|
|
||||||
|
How will we achieve that? A naive approach would be "let's limit ourselves to
|
||||||
|
7bit ASCII and put `TH`, `TR` and `TL` as inputs". That could work, except that
|
||||||
|
the SMS will have no way reliable way (except timers) of knowing whether polling
|
||||||
|
two identical values is the result of a repeat character or because there is no
|
||||||
|
new value yet.
|
||||||
|
|
||||||
|
On the AVR side, there's not way to know whether the value has been read, so we
|
||||||
|
can't to like on the RC2014 and reset the value to zero when a `RO` request is
|
||||||
|
made.
|
||||||
|
|
||||||
|
We need communication between the SMS and the PS/2 adapter to be bi-directional.
|
||||||
|
That bring the number of usable pins down to 6, a bit low for a proper character
|
||||||
|
range. So we'll fetch each character in two 4bit nibbles. `TH` is used to select
|
||||||
|
which nibble we want.
|
||||||
|
|
||||||
|
`TH` going up also tells the AVR MCU that we're done reading the character and
|
||||||
|
that the next one can come up.
|
||||||
|
|
||||||
|
As always, the main problem is that the AVR MCU is too slow to keep up with the
|
||||||
|
rapid z80 polling pace. In the RC2014 adapter, I hooked `CE` directly on the
|
||||||
|
AVR, but that was a bit tight because the MCU is barely fast enough to handle
|
||||||
|
this signal properly. I did that because I had no proper IC on hand to build a
|
||||||
|
SR latch.
|
||||||
|
|
||||||
|
In this recipe, I do have a SR latch on hand, so I'll use it. `TH` triggering
|
||||||
|
will also trigger that latch, indicating to the MCU that it can load the next
|
||||||
|
character in the '164. When it's done, we signal the SMS that the next char is
|
||||||
|
ready by reseting the latch. That means that we have to hook the latch's output
|
||||||
|
to `TR`.
|
||||||
|
|
||||||
|
Nibble selection on `TH` doesn't involve the AVR at all. All 8 bits are
|
||||||
|
pre-loaded on the '164. We use a 4-channel multiplexer to make `TH` select
|
||||||
|
either the low or high bits.
|
||||||
|
|
||||||
|
## Gathering parts
|
||||||
|
|
||||||
|
* A SMS that can run Collapse OS
|
||||||
|
* A PS/2 keyboard. A USB keyboard + PS/2 adapter should work, but I haven't
|
||||||
|
tried it yet.
|
||||||
|
* A PS/2 female connector. Not so readily available, at least not on digikey. I
|
||||||
|
de-soldered mine from an old motherboard I had laying around.
|
||||||
|
* A SMS controller you can cannibalize for the DB-9 connection. A stock DB-9
|
||||||
|
connector isn't deep enough.
|
||||||
|
* ATtiny85/45/25 (main MCU for the device)
|
||||||
|
* 74xx164 (shift register)
|
||||||
|
* 74xx157 (multiplexer)
|
||||||
|
* A NOR SR-latch. I used a 4043.
|
||||||
|
* Proto board, wires, IC sockets, etc.
|
||||||
|
* [AVRA][avra]
|
||||||
|
|
||||||
|
## Historical note
|
||||||
|
|
||||||
|
As I was building this prototype, I was wondering how I would debug it. I could
|
||||||
|
obviously not hope for it to work as a keyboard adapter on the first time, right
|
||||||
|
on port A, driving the shell. I braced myself mentally for a logic analyzer
|
||||||
|
session and some kind of arduino-based probe to test bit banging results.
|
||||||
|
|
||||||
|
And then I thought "why not use the genesis?". Sure, driving the shell with the
|
||||||
|
D-pad isn't fun at all, but it's possible. So I hacked myself a temporary debug
|
||||||
|
kernel with a "a" command doing a probe on port B. It worked really well!
|
||||||
|
|
||||||
|
It was a bit less precise than logic analyzers and a bit of poking-around and
|
||||||
|
crossing-fingers was involved, but overall, I think it was much less effort
|
||||||
|
than creating a full test setup.
|
||||||
|
|
||||||
|
There's a certain satisfaction to debug a device entirely on your target
|
||||||
|
machine...
|
||||||
|
|
||||||
|
## Building the PS/2 interface
|
||||||
|
|
||||||
|
(schematic incoming, I have yet to scan it.)
|
||||||
|
|
||||||
|
The PS/2-to-AVR part is indentical to the rc2014/ps2 recipe. Refer to this
|
||||||
|
recipe.
|
||||||
|
|
||||||
|
We control the '164 from the AVR in a similar way to what we did in rc2014/ps2,
|
||||||
|
that is, sharing the DATA line with PS/2 (PB1). We clock the '164 with PB3.
|
||||||
|
Because the '164, unlike the '595, is unbuffered, no need for special RCLK
|
||||||
|
provisions.
|
||||||
|
|
||||||
|
Most of the wiring is between the '164 and the '157. Place them close. The 4
|
||||||
|
outputs on the '157 are hooked to the first 4 lines on the DB-9 (Up, Down, Left,
|
||||||
|
Right).
|
||||||
|
|
||||||
|
In my prototype, I placed a 1uf decoupling cap next to the AVR. I used a 10K
|
||||||
|
resistor as a pull-down for the TH line (it's not always driven).
|
||||||
|
|
||||||
|
If you use a 4043, don't forget to wire EN. On the '157, don't forget to wire
|
||||||
|
~G.
|
||||||
|
|
||||||
|
The code expects a SR-latch that works like a 4043, that is, S and R are
|
||||||
|
triggered high, S makes Q high, R makes Q low. R is hooked to PB4. S is hooked
|
||||||
|
to TH (and also the A/B on the '157). Q is hooked to PB0 and TL.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The code in this recipe is set up to listen to the keyboard on port B, leaving
|
||||||
|
port A to drive, for example, an Everdrive with a D-pad. Unlike the generic
|
||||||
|
SMS recipe, this kernel has no character selection mechanism. It acts like a
|
||||||
|
regular shell, taking input from the keyboard.
|
||||||
|
|
||||||
|
`kernel/sms/kbd.asm` also has a FetchKC implementation for port A if you prefer.
|
||||||
|
Just hook it on. I've tried it, it works.
|
||||||
|
|
||||||
|
Did you get there? Feels pretty cool huh?
|
||||||
|
|
||||||
|
[rc2014-ps2]: ../../rc2014/ps2
|
||||||
|
[avra]: https://github.com/hsoft/avra
|
57
recipes/sms/kbd/glue.asm
Normal file
57
recipes/sms/kbd/glue.asm
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
; 8K of onboard RAM
|
||||||
|
.equ RAMSTART 0xc000
|
||||||
|
; Memory register at the end of RAM. Must not overwrite
|
||||||
|
.equ RAMEND 0xfdd0
|
||||||
|
|
||||||
|
jp init
|
||||||
|
|
||||||
|
.fill 0x66-$
|
||||||
|
retn
|
||||||
|
|
||||||
|
#include "err.h"
|
||||||
|
#include "core.asm"
|
||||||
|
#include "parse.asm"
|
||||||
|
|
||||||
|
#include "sms/kbd.asm"
|
||||||
|
.equ KBD_RAMSTART RAMSTART
|
||||||
|
.equ KBD_FETCHKC smskbdFetchKCA
|
||||||
|
#include "kbd.asm"
|
||||||
|
|
||||||
|
.equ VDP_RAMSTART KBD_RAMEND
|
||||||
|
#include "sms/vdp.asm"
|
||||||
|
|
||||||
|
.equ STDIO_RAMSTART VDP_RAMEND
|
||||||
|
#include "stdio.asm"
|
||||||
|
|
||||||
|
.equ SHELL_RAMSTART STDIO_RAMEND
|
||||||
|
.equ SHELL_EXTRA_CMD_COUNT 0
|
||||||
|
#include "shell.asm"
|
||||||
|
|
||||||
|
init:
|
||||||
|
di
|
||||||
|
im 1
|
||||||
|
|
||||||
|
ld sp, RAMEND
|
||||||
|
|
||||||
|
; Initialize the keyboard latch by "dummy reading" once. This ensures
|
||||||
|
; that the adapter knows it can fill its '164.
|
||||||
|
; Port B TH output, high
|
||||||
|
ld a, 0b11110111
|
||||||
|
out (0x3f), a
|
||||||
|
nop
|
||||||
|
; Port A/B reset
|
||||||
|
ld a, 0xff
|
||||||
|
out (0x3f), a
|
||||||
|
|
||||||
|
call kbdInit
|
||||||
|
call vdpInit
|
||||||
|
|
||||||
|
ld hl, kbdGetC
|
||||||
|
ld de, vdpPutC
|
||||||
|
call stdioInit
|
||||||
|
call shellInit
|
||||||
|
jp shellLoop
|
||||||
|
|
||||||
|
.fill 0x7ff0-$
|
||||||
|
.db "TMR SEGA", 0x00, 0x00, 0xfb, 0x68, 0x00, 0x00, 0x00, 0x4c
|
||||||
|
|
345
recipes/sms/kbd/ps2ctl.asm
Normal file
345
recipes/sms/kbd/ps2ctl.asm
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
.include "tn45def.inc"
|
||||||
|
|
||||||
|
; Receives keystrokes from PS/2 keyboard and send them to the '164. On the PS/2
|
||||||
|
; side, it works the same way as the controller in the rc2014/ps2 recipe.
|
||||||
|
; However, in this case, what we have on the other side isn't a z80 bus, it's
|
||||||
|
; the one of the two controller ports of the SMS through a DB9 connector.
|
||||||
|
|
||||||
|
; The PS/2 related code is copied from rc2014/ps2 without much change. The only
|
||||||
|
; differences are that it pushes its data to a '164 instead of a '595 and that
|
||||||
|
; it synchronizes with the SMS with a SR latch, so we don't need PCINT. We can
|
||||||
|
; also afford to run at 1MHz instead of 8.
|
||||||
|
|
||||||
|
; *** Register Usage ***
|
||||||
|
;
|
||||||
|
; GPIOR0 flags:
|
||||||
|
; 0 - when set, indicates that the DATA pin was high when we received a
|
||||||
|
; bit through INT0. When we receive a bit, we set flag T to indicate
|
||||||
|
; it.
|
||||||
|
;
|
||||||
|
; R16: tmp stuff
|
||||||
|
; R17: recv buffer. Whenever we receive a bit, we push it in there.
|
||||||
|
; R18: recv step:
|
||||||
|
; - 0: idle
|
||||||
|
; - 1: receiving data
|
||||||
|
; - 2: awaiting parity bit
|
||||||
|
; - 3: awaiting stop bit
|
||||||
|
; R19: Register used for parity computations and tmp value in some other places
|
||||||
|
; R20: data being sent to the '164
|
||||||
|
; Y: pointer to the memory location where the next scan code from ps/2 will be
|
||||||
|
; written.
|
||||||
|
; Z: pointer to the next scan code to push to the 595
|
||||||
|
;
|
||||||
|
; *** Constants ***
|
||||||
|
.equ CLK = PINB2
|
||||||
|
.equ DATA = PINB1
|
||||||
|
.equ CP = PINB3
|
||||||
|
; SR-Latch's Q pin
|
||||||
|
.equ LQ = PINB0
|
||||||
|
; SR-Latch's R pin
|
||||||
|
.equ LR = PINB4
|
||||||
|
|
||||||
|
; init value for TCNT0 so that overflow occurs in 100us
|
||||||
|
.equ TIMER_INITVAL = 0x100-100
|
||||||
|
|
||||||
|
; *** Code ***
|
||||||
|
|
||||||
|
rjmp main
|
||||||
|
rjmp hdlINT0
|
||||||
|
|
||||||
|
; Read DATA and set GPIOR0/0 if high. Then, set flag T.
|
||||||
|
; no SREG fiddling because no SREG-modifying instruction
|
||||||
|
hdlINT0:
|
||||||
|
sbic PINB, DATA ; DATA clear? skip next
|
||||||
|
sbi GPIOR0, 0
|
||||||
|
set
|
||||||
|
reti
|
||||||
|
|
||||||
|
main:
|
||||||
|
ldi r16, low(RAMEND)
|
||||||
|
out SPL, r16
|
||||||
|
ldi r16, high(RAMEND)
|
||||||
|
out SPH, r16
|
||||||
|
|
||||||
|
; init variables
|
||||||
|
clr r18
|
||||||
|
out GPIOR0, r18
|
||||||
|
|
||||||
|
; Setup int0
|
||||||
|
; INT0, falling edge
|
||||||
|
ldi r16, (1<<ISC01)
|
||||||
|
out MCUCR, r16
|
||||||
|
; Enable INT0
|
||||||
|
ldi r16, (1<<INT0)
|
||||||
|
out GIMSK, r16
|
||||||
|
|
||||||
|
; Setup buffer
|
||||||
|
clr YH
|
||||||
|
ldi YL, low(SRAM_START)
|
||||||
|
clr ZH
|
||||||
|
ldi ZL, low(SRAM_START)
|
||||||
|
|
||||||
|
; Setup timer. We use the timer to clear up "processbit" registers after
|
||||||
|
; 100us without a clock. This allows us to start the next frame in a
|
||||||
|
; fresh state. at 1MHZ, no prescaling is necessary. Each TCNT0 tick is
|
||||||
|
; already 1us long.
|
||||||
|
ldi r16, (1<<CS00) ; no prescaler
|
||||||
|
out TCCR0B, r16
|
||||||
|
|
||||||
|
; init DDRB
|
||||||
|
sbi DDRB, CP
|
||||||
|
cbi PORTB, LR
|
||||||
|
sbi DDRB, LR
|
||||||
|
|
||||||
|
sei
|
||||||
|
|
||||||
|
loop:
|
||||||
|
brts processbit ; flag T set? we have a bit to process
|
||||||
|
cp YL, ZL ; if YL == ZL, buffer is empty
|
||||||
|
brne sendTo164 ; YL != ZL? our buffer has data
|
||||||
|
|
||||||
|
; nothing to do. Before looping, let's check if our communication timer
|
||||||
|
; overflowed.
|
||||||
|
in r16, TIFR
|
||||||
|
sbrc r16, TOV0
|
||||||
|
rjmp processbitReset ; Timer0 overflow? reset processbit
|
||||||
|
|
||||||
|
; Nothing to do for real.
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
; Process the data bit received in INT0 handler.
|
||||||
|
processbit:
|
||||||
|
in r19, GPIOR0 ; backup GPIOR0 before we reset T
|
||||||
|
andi r19, 0x1 ; only keep the first flag
|
||||||
|
cbi GPIOR0, 0
|
||||||
|
clt ; ready to receive another bit
|
||||||
|
|
||||||
|
; We've received a bit. reset timer
|
||||||
|
rcall resetTimer
|
||||||
|
|
||||||
|
; Which step are we at?
|
||||||
|
tst r18
|
||||||
|
breq processbits0
|
||||||
|
cpi r18, 1
|
||||||
|
breq processbits1
|
||||||
|
cpi r18, 2
|
||||||
|
breq processbits2
|
||||||
|
; step 3: stop bit
|
||||||
|
clr r18 ; happens in all cases
|
||||||
|
; DATA has to be set
|
||||||
|
tst r19 ; Was DATA set?
|
||||||
|
breq loop ; not set? error, don't push to buffer
|
||||||
|
; push r17 to the buffer
|
||||||
|
st Y+, r17
|
||||||
|
rcall checkBoundsY
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
processbits0:
|
||||||
|
; step 0 - start bit
|
||||||
|
; DATA has to be cleared
|
||||||
|
tst r19 ; Was DATA set?
|
||||||
|
brne loop ; Set? error. no need to do anything. keep r18
|
||||||
|
; as-is.
|
||||||
|
; DATA is cleared. prepare r17 and r18 for step 1
|
||||||
|
inc r18
|
||||||
|
ldi r17, 0x80
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
processbits1:
|
||||||
|
; step 1 - receive bit
|
||||||
|
; We're about to rotate the carry flag into r17. Let's set it first
|
||||||
|
; depending on whether DATA is set.
|
||||||
|
clc
|
||||||
|
sbrc r19, 0 ; skip if DATA cleared.
|
||||||
|
sec
|
||||||
|
; Carry flag is set
|
||||||
|
ror r17
|
||||||
|
; Good. now, are we finished rotating? If carry flag is set, it means
|
||||||
|
; that we've rotated in 8 bits.
|
||||||
|
brcc loop ; we haven't finished yet
|
||||||
|
; We're finished, go to step 2
|
||||||
|
inc r18
|
||||||
|
rjmp loop
|
||||||
|
processbits2:
|
||||||
|
; step 2 - parity bit
|
||||||
|
mov r1, r19
|
||||||
|
mov r19, r17
|
||||||
|
rcall checkParity ; --> r16
|
||||||
|
cp r1, r16
|
||||||
|
brne processbitError ; r1 != r16? wrong parity
|
||||||
|
inc r18
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
processbitError:
|
||||||
|
clr r18
|
||||||
|
ldi r19, 0xfe
|
||||||
|
rcall sendToPS2
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
processbitReset:
|
||||||
|
clr r18
|
||||||
|
rcall resetTimer
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
; Send the value of r20 to the '164
|
||||||
|
sendTo164:
|
||||||
|
sbis PINB, LQ ; LQ is set? we can send the next byte
|
||||||
|
rjmp loop ; Even if we have something in the buffer, we
|
||||||
|
; can't: the SMS hasn't read our previous
|
||||||
|
; buffer yet.
|
||||||
|
; We disable any interrupt handling during this routine. Whatever it
|
||||||
|
; is, it has no meaning to us at this point in time and processing it
|
||||||
|
; might mess things up.
|
||||||
|
cli
|
||||||
|
sbi DDRB, DATA
|
||||||
|
|
||||||
|
ld r20, Z+
|
||||||
|
rcall checkBoundsZ
|
||||||
|
ldi r16, 8
|
||||||
|
|
||||||
|
sendTo164Loop:
|
||||||
|
cbi PORTB, DATA
|
||||||
|
sbrc r20, 7 ; if leftmost bit isn't cleared, set DATA high
|
||||||
|
sbi PORTB, DATA
|
||||||
|
; toggle CP
|
||||||
|
cbi PORTB, CP
|
||||||
|
lsl r20
|
||||||
|
sbi PORTB, CP
|
||||||
|
dec r16
|
||||||
|
brne sendTo164Loop ; not zero yet? loop
|
||||||
|
|
||||||
|
; release PS/2
|
||||||
|
cbi DDRB, DATA
|
||||||
|
sei
|
||||||
|
|
||||||
|
; Reset the latch to indicate that the next number is ready
|
||||||
|
sbi PORTB, LR
|
||||||
|
cbi PORTB, LR
|
||||||
|
rjmp loop
|
||||||
|
|
||||||
|
resetTimer:
|
||||||
|
ldi r16, TIMER_INITVAL
|
||||||
|
out TCNT0, r16
|
||||||
|
ldi r16, (1<<TOV0)
|
||||||
|
out TIFR, r16
|
||||||
|
ret
|
||||||
|
|
||||||
|
; Send the value of r19 to the PS/2 keyboard
|
||||||
|
sendToPS2:
|
||||||
|
cli
|
||||||
|
|
||||||
|
; First, indicate our request to send by holding both Clock low for
|
||||||
|
; 100us, then pull Data low
|
||||||
|
; lines low for 100us.
|
||||||
|
cbi PORTB, CLK
|
||||||
|
sbi DDRB, CLK
|
||||||
|
rcall resetTimer
|
||||||
|
|
||||||
|
; Wait until the timer overflows
|
||||||
|
in r16, TIFR
|
||||||
|
sbrs r16, TOV0
|
||||||
|
rjmp PC-2
|
||||||
|
; Good, 100us passed.
|
||||||
|
|
||||||
|
; Pull Data low, that's our start bit.
|
||||||
|
cbi PORTB, DATA
|
||||||
|
sbi DDRB, DATA
|
||||||
|
|
||||||
|
; Now, let's release the clock. At the next raising edge, we'll be
|
||||||
|
; expected to have set up our first bit (LSB). We set up when CLK is
|
||||||
|
; low.
|
||||||
|
cbi DDRB, CLK ; Should be starting high now.
|
||||||
|
|
||||||
|
; We will do the next loop 8 times
|
||||||
|
ldi r16, 8
|
||||||
|
; Let's remember initial r19 for parity
|
||||||
|
mov r1, r19
|
||||||
|
|
||||||
|
sendToPS2Loop:
|
||||||
|
; Wait for CLK to go low
|
||||||
|
sbic PINB, CLK
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
; set up DATA
|
||||||
|
cbi PORTB, DATA
|
||||||
|
sbrc r19, 0 ; skip if LSB is clear
|
||||||
|
sbi PORTB, DATA
|
||||||
|
lsr r19
|
||||||
|
|
||||||
|
; Wait for CLK to go high
|
||||||
|
sbis PINB, CLK
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
dec r16
|
||||||
|
brne sendToPS2Loop ; not zero? loop
|
||||||
|
|
||||||
|
; Data was sent, CLK is high. Let's send parity
|
||||||
|
mov r19, r1 ; recall saved value
|
||||||
|
rcall checkParity ; --> r16
|
||||||
|
|
||||||
|
; Wait for CLK to go low
|
||||||
|
sbic PINB, CLK
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
; set parity bit
|
||||||
|
cbi PORTB, DATA
|
||||||
|
sbrc r16, 0 ; parity bit in r16
|
||||||
|
sbi PORTB, DATA
|
||||||
|
|
||||||
|
; Wait for CLK to go high
|
||||||
|
sbis PINB, CLK
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
; Wait for CLK to go low
|
||||||
|
sbic PINB, CLK
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
; We can now release the DATA line
|
||||||
|
cbi DDRB, DATA
|
||||||
|
|
||||||
|
; Wait for DATA to go low. That's our ACK
|
||||||
|
sbic PINB, DATA
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
; Wait for CLK to go low
|
||||||
|
sbic PINB, CLK
|
||||||
|
rjmp PC-1
|
||||||
|
|
||||||
|
; We're finished! Enable INT0, reset timer, everything back to normal!
|
||||||
|
rcall resetTimer
|
||||||
|
clt ; also, make sure T isn't mistakely set.
|
||||||
|
sei
|
||||||
|
ret
|
||||||
|
|
||||||
|
; Check that Y is within bounds, reset to SRAM_START if not.
|
||||||
|
checkBoundsY:
|
||||||
|
tst YL
|
||||||
|
breq PC+2
|
||||||
|
ret ; not zero, nothing to do
|
||||||
|
; YL is zero. Reset Y
|
||||||
|
clr YH
|
||||||
|
ldi YL, low(SRAM_START)
|
||||||
|
ret
|
||||||
|
|
||||||
|
; Check that Z is within bounds, reset to SRAM_START if not.
|
||||||
|
checkBoundsZ:
|
||||||
|
tst ZL
|
||||||
|
breq PC+2
|
||||||
|
ret ; not zero, nothing to do
|
||||||
|
; ZL is zero. Reset Z
|
||||||
|
clr ZH
|
||||||
|
ldi ZL, low(SRAM_START)
|
||||||
|
ret
|
||||||
|
|
||||||
|
; Counts the number of 1s in r19 and set r16 to 1 if there's an even number of
|
||||||
|
; 1s, 0 if they're odd.
|
||||||
|
checkParity:
|
||||||
|
ldi r16, 1
|
||||||
|
lsr r19
|
||||||
|
brcc PC+2 ; Carry unset? skip next
|
||||||
|
inc r16 ; Carry set? We had a 1
|
||||||
|
tst r19 ; is r19 zero yet?
|
||||||
|
brne checkParity+1 ; no? loop and skip first LDI
|
||||||
|
andi r16, 0x1 ; Sets Z accordingly
|
||||||
|
ret
|
||||||
|
|
Loading…
Reference in New Issue
Block a user