Wednesday, January 25, 2023

PS/2 Keyboard - the Retro Way!

I have seen a lot of overpowered PS/2 keyboard interfaces lately: microcontrollers, bit-banged PIAs, PIAs with shift registers. All sorts of complex solutions. Here is my simple contribution, using retro hardware.

It is important to note that the PS/2 protocol is basically just an isosynchronous serial connection with open-collector i/o. Isosynchronous is basically the same as the normal asynchronous serial connection, but there is also a clock signal. To simply implement this protocol, all we need is an ACIA or UART that supports a 1x clock mode and a buffer to make the signals open-collector. The 1x clock requirement rules out the common MOS 6551 (16X clock required), but includes many common alternatives such as the MC6850, Signetics 2651 and Zilog SIOs.

Next, the signals to the keyboard need to be buffered and converted to open-collector outputs. The 74LS05 open-collector hex inverter is a simple choice for this. Since this inverts the signals and the PS/2 data is already at the correct polarity, we need to invert the TX data signal 2x: invert the signal, pull it up, and invert it again. Here is an example for the MC6850:

Sorry for the weird orientation of the schematic; it is part of another project.

Notice that the clock from the PS/2 device is connected to the ACIA as the RxClk, but is also inverted, pulled up and used as the ACIA TxClk. The RTS signal from the ACIA can be used to pull the PS/2 clock low for commands to the keyboard (untested). The data line from the PS/2 device is pulled up and connected directly to the ACIA, while the command data to the keyboard is inverted twice with open collector output.

If you only want to receive data from the keyboard, you can just connect up the clock (to ACIA RxClk) and the data line (to ACIA RxData) through non-inverting buffers (to protect the ACIA). Too simple!

Once you have the data from the ACIA, you will unfortunately need to use a small lookup table to translate the PS/2 keyboard codes to ASCII. A small PROM on the data lines of the ACIA could also work, but you would also need a tranceiver to do writes to the ACIA and a decoder to make sure the data moves through the PROM or tranceiver at the right times.

The one issue with this technique is that when using the 1x clock mode on the MC6850, I think that the the ACIA needs one more clock cycle than the PS/2 protocol provides per byte to indicate that there is a byte available (this could just be an issue with my clock phasing though). As a result, the input buffer full flag does not get set until the start of the next byte. Unfortunately, this means you are always one byte behind the keyboard. That may seem like a non-starter, but in reality, it does not affect much because the proper key code does get sent when the key is released. So every key press/release cycle sends you:

[previous key code]...[key code][break code]
instead of:
[key code]...[break code][keycode]
So, the key code for the current key is available when it is released, or when repeating. Not perfect, but it is much simpler than many of the solutions out there. It definitely needs more testing, and I would love to hear your results. This makes me want to try interfacing with a PS/2 keyboard and a character LCD with a single ACIA.

MC6800 assembly available for a test program: