Type Like it’s 1987: a Hardware Driven PS/2 Interface.
In 1987 IBM released the PS/2 keyboard, an update of the earlier IBM AT keyboard with a smaller connector, extra features, and support for a mouse using the same connector and protocol. This rapidly became the standard for PC compatibles. You’ll still find PS/2 ports on modern motherboards, even though USB has been around for about 25 years.
Because the PS/2 keyboard is so common, you can get your hands on them for nothing. Unlike USB the PS/2 protocol is trivial to implement. Well, trivial as far as hardware protocols go, I suppose. If you want to use a keyboard in a project, PS/2 is clearly the best option.
I designed and prototyped a PS/2 interface using a handful of logic ICs a few years back. I got it working, though there were a few issues I never got around to fixing. I ended up mothballing the project.
Five, (Six? Seven? You lose track after a while.) years later I finally decided to dust off my notes, get it working again, and see if I could improve it a little.
PS/2 Protocol
You can’t build an interface to something if you don’t know what it expects, when it expects it, and how to interpret it.
PS/2 keyboards are so common this knowledge is well-known. Many examples of connecting a PS/2 keyboard to microcontrollers exist, complete with code samples. I won’t be explaining any of that; I’m going to focus on standalone hardware.
Software Protocol
I’m interested in the hardware required to interface the keyboard, not the software. A small amount of the software protocol still needs explanation, since it has a direct impact on how the hardware is designed and tested.
There are actually three software modes the PS/2 keyboard is supposed to support. Mode 1 is based on the original IBM XT keyboard, mode 3 is PS/2 specific, and mode 2 is based on the IBM AT keyboard.
I presented them out of order, because only mode 2 is relevant. Mode 2 is the only set guaranteed to exist. I’d actually be surprised if you got a modern PS/2 keyboard that worked with mode 1 or 3. It also means you can use an AT keyboard on a PS/2 system (or the other way around) with a plug adapter. You wouldn’t get the advanced features of the PS/2 protocol, but it would work as a keyboard, which is what’s important here.
Since it’s so common, there are many, many, many, places to find the key scancodes themselves. I will not bother trying to re-list them here; there is no real danger of them going extinct. The only trap here is that the scancodes are not ASCII or any other character encoding. Also note that many keys send more than one byte, and a few of them have unusual sequences compared to the other keys (e.g. “pause/break” has no break code. Kind of ironic).
PS/2 keyboards are “intelligent”; that is the keyboard has it’s own microcontroller. This allows for some neat features like automatic repeat, break codes, data buffering, and a limited amount of host-to-keyboard communication. This all optional; for the most part you can reasonably expect to plug a PS/2 keyboard in and it will just work.
Hardware Protocol
At the hardware level, the PS/2 protocol is pretty simple. It strongly resembles I2C, with two open-collector lines: one for clock, one for data.
Transmissions are packet oriented. One packet consists of an 8 bit scancode, 1 bit parity, 1 bit start, 1 bit stop. Clock rates are a glacial 10-16KHz. Data is sent least significant bit first (“little-endian“).
Data is sent from the keyboard when the clock signal is high. Data is read by the host when the clock is low. Unfortunately this also means you can’t just tweak an I2C interface to work with the PS/2 protocol; the handshake sequence is incompatible.
Holding the clock line low pauses transmission, similar to I2C clock stretching. Unlike I2C, interrupting a PS/2 packet will cause the re-transmission of the entire sequence, not just the interrupted packet. Pausing between packets will allow the sequence to proceed as usual.
Connector-wise a mini-DIN 6 is used. You can get sockets pretty cheap, mine is from Adafruit though Digikey sells a near identical model at a slightly lower price. Digikey has a few cheaper options, if you’re willing to do a little soldering.
Keep in mind, the plug and socket are a mirror image of each other.
The actual socket. Complete with awful masking tape and pen labels.
Being from the TTL era, the PS/2 keyboard is a 5V system. As another sign of it’s TTL origins the keyboard is allowed to draw a fantastic 257mA. You’ll have to use your own discretion here. I doubt anything halfway modern would even hit 100mA, though the possibility definitely exists. My chosen keyboard only takes ~5mA.
Circuit Design
Designing the hardware interface circuit was fairly straightforward. Both signals are single-ended, TTL level, open-collector, and slow. No special signals must be detected by the hardware. All packets are exactly the same length. Flow control only requires the clock to be grounded. Therefore the receiver needs no “intelligence” and only has to detect eleven bits have been shifted.
I’m sure many people would be satisfied to use an actual counter to explicitly count out eleven bits. There are a few schematics/RTL diagrams that attest to that. I’ve used the packet itself as the counter, so to speak.
I won’t claim credit for this idea, though I can’t quite track down the sources that led me here. I’m sure this is at least in part based on someone else’s circuit, I just don’t know who. Maybe it is a little more original than I thought…
My schematic was first made an a much older version of KiCad. Turns out a lot changed over the years, including some of the part symbols. I had to do untangling, rearranging, and a little bit of reverse engineering. Then I had to add my new modifications. As far as I can tell, this schematic is correct, though there’s a small chance a couple of signals are crossed.
Two HC74s and one HC595 are connected to form an 11 bit shift register. This is arranged so that the HC595 holds the actual data in a complete byte. Clock and data are directly derived from the keyboard input.
HC-series logic requires a fast clock edge- less than 500ns. Violating this results in meta-stability issues, including rapid oscillations. HC132 Schmitt trigger NAND gates clean up the slow signals to something more acceptable. Any inverting Schmitt trigger would work (e.g. the 74HC14), I just had an HC132 to hand.
That manual reset circuit is designed to keep the clock line low when the button is pressed. The circuit works without Q2, but the keyboard is much faster than your fingers. Without the Q2 you would only get the first byte of each packet. Adding Q2 lets you toggle through multi-byte transmissions. I didn’t have this interlock the first time around, which meant I could only get the first byte of any sequence.
One important thing to note about this circuit is that the PS/2 signals are inverted by the NAND gates. I wired the LEDs to light on a zero, so the display is correct. This is trivial to correct in software, assuming the programmer actually knows about it. Having inverted signals is crucial to how the circuit works, so I have no real motivation to change it.
Circuit Demonstrator
I didn’t plan to spend a lot of time on this circuit. All I wanted to do was dust off an old project, confirm it worked, and fix the bug it had.
For once that is literally, exactly what happened. It took me a couple of tries to get the display wired right, but that only took a few minutes. There were some loose wires and misplaced connections (AKA the Breadboard’s Curse), but I didn’t spend even an hour making it work.
I think it took me longer to find all the parts. One of these days I’ll properly organize all my stuff. One of these days…
Everything fits on a standard-sized breadboard.
Except the keyboard, of course.
You can see the entire sequence for pause:
E1 14 77 E1 F0 14 F0 77
The only meaningful difference between my schematic and my real circuit is I’m using an HC594 instead of an HC595. The only difference between the two is the function of pin 13. In the HC594 this clears the output register; in the HC595 this enables the output. Wire pin 13 to +5V for the HC594. Keep it at ground for the HC595.
Operation is as follows:
- All registers are cleared to a zero state and the clock line is pulled high.
- A zero data bit is used to indicate the start of a new transmission. This is inverted to a one by passing through the HC132.
- Eleven clocks shift eleven bits through the register chain.
- Upon reaching the 11th register, the start bit turns on Q1 to hold the clock line low.
- Pressing the button clears all the registers while keeping the clock line low with Q2.
- Releasing the button allows the clock line to return high.
You can see my hardware trick for detecting the end of the packet. Clearing the registers sets them to zero, but the inverted start bit is guaranteed to be a 1. This 1 ripples through until it hits U3A, turning on Q1, holding the clock low, and therefore pausing the transmission. As far as I can tell this works 100% reliably.
Q2 could be replaced by OR-ing the output from U3A and the drive to Q2 together with an actual logic gate. I decided against this because why add another 14 pin DIP for a single gate when a 3 pin transistor does the job just as well? I suspect there’s a way to use the two NAND gates in the reset circuit to do the same thing.
Finishing Up
Building things is frequently an exercise of iterated trial-and-error. You run simulations, calculate variables, plot charts, etc. Then you build a prototype that shows exactly how wrong you are. Knowing that, you go back to update your model, build a new prototype, so on, so forth. Eventually you get something that’s good enough.
One of the great things about engineering though, is that solved problems tend to stay solved. I knew this circuit would work, so even starting from scratch meant I could build it in a few minutes. I think there was only one serious error (a misplaced wire).
Having got my years old design working, I could then fix the bug where only the first byte would ever be received. This went very quickly, working the first time. Now I can “click through” each byte as slowly as I want.
I completely ignored the possibility of the host-to-keyboard communication PS/2 provides. Adding it is straightforward, but you’d need a host CPU to use it so there’s no point. Demonstrating it can be ignored is more valuable.
Despite being intended for keyboards, the exact same protocol was used for PS/2 mice. There’s no reason why this circuit wouldn’t work with a PS/2 mouse. Maybe I should dig one up to experiment with.
When I was writing this up, I was absolutely certain someone beat me to this design. I know the idea of using the inverted start bit came from somewhere else, but I just can’t pin down where. So much time has passed there’s a good chance that source has disappeared. Or maybe I can’t search worth shit. Hey, it’s always a possibility.
Building the PS/2 keyboard interface was a fun little side project, but it’s not useful on it’s own. I guess you could use it to check if an old keyboard is working?
As you might expect, this isn’t the end. I have plans to use this hardware “core” in a bigger project. That will have to wait for later, though I think it’s pretty obvious what I’m planning.
Have a question? Comment? Insight? Post below!