Compared to the graphics subsystem, Phoenix's keyboard interface is much more straightforward. Home computers of the early '80s usually had a built-in keyboard, but building my own keyboard seemed a bit much. Instead I opted for an external keyboard with PS/2 connector: still widely available (albeit usually using an adapter), and easy to interface, especially compared to USB.
The PS/2 interface contains just 4 wires: ground, 5 V power, clock and data. The clock and data signals form a synchronous serial signal. The bus is bidirectional (so the computer can send data to the keyboard, e.g. to update the state of the caps lock and num lock LEDs), but in this project I'm only using only the keyboard->computer direction. As a result the caps lock indicator LED won't work.
As the controller I used an Atmel ATtiny85 microcontroller. Compared to the ATmega324 and ATmega328p used earlier in this project, the ATtiny does live up to its name: it only has 8 pins (5 of which usable as I/O pins), 8 KB of program memory, and just 512 bytes of RAM. Nevertheless that's way more than enough for a keyboard controller.
The PS/2 protocol uses a start bit, 8 data bits, parity bit and stop bit. The approach I took was very simplistic: just wait in a busy loop for transitions on the clock line, and after 9 bits (start bit + 8 data bits) are received, process the received byte. There's a simple lookup table translating the scan codes sent by the keyboard to their corresponding ASCII values.
There's also a few state variables keeping track of the state of the modifier keys (shift, ctrl, etc.) as well as to handle key release events (scan code 0xF0 followed by the scan code of the key being released).
To interface the keyboard controller with the Z80 CPU, I used a 74HC595 shift register. It takes 3 lines to hook it up to the ATtiny (data, clock, latch). Data is written by clocking it one bit at a time, and then latched into the register by pulsing the latch pin.
When a key is pressed, the ATtiny writes the ASCII value of the key into the shift register, and when a key is released, a value of 0x00 is written, so at any point the shift register contains the ASCII value of the currently pressed key, or 0x00 if no keys are being pressed. The Z80 can poll the contents of the shift register using the "IN" instruction.
The 8 data output lines of the shift register are hooked up directly to the Z80's data bus. However, they are only enabled when the 74HC595's "G" (gate, or output enable) pin is active (low). When the Z80 "IN" instruction is executed, IORQ and RD are simultaneously active (low). By hooking up IORQ and RD to an OR-gate, the desired behavior is achieved: only when they are both low, the shift register's output will be enabled.
Note that as long as the shift register is the only device attached this way, there's no need for address decoding logic and reading from any I/O address will work.
Pressing the A key on the keyboard (scan code 0x1C). There are 11 clock cycles in the PS/2 protocol: 1 start bit, 8 data bits, a parity bit, and one stop bit. The data is sent to the shift register during the parity bit. |
Zooming in: the ASCII value for the "a" key 0x61 is being sent to the shift register. |
The AVR C program for the keyboard controller is available on GitHub. The Z80 code to read a key is as follows:
getchar:
; Wait for previous key to be released.
LD A, (lastkey)
LD B, A
getchar1:
IN A, (0xFE)
CP B
JR Z, getchar1
; Wait for a key to be pressed.
getchar2:
OR A
JR NZ, getchar3
IN A, (0xFE)
JR getchar2
getchar3:
LD (lastkey), A
RET
lastchar:
DB 0x00
No comments:
Post a Comment