When repurposing old (or new) PS/2 keyboards as macro keyboards (because they are much easier to interface with than USB keyboards), there are three free LEDs that can also be repurposed, e.g., for various status purposes (say, indicating a macro key action is in progress or indicating a mode the macro keyboard is in, like an extra slow macro key mode or an extra fast macro key mode. Or/and special flashing in some error conditions).
Here it is documented how these LEDs can be controlled.
Hardware requirements
There aren’t any hardware requirements! At least not if using a standard (5 V) microcontroller to interface to the PS/2 keyboard. The required open collector output can be simulated by changing the I/O port to output (with a value of 0) for the (driving) ‘0’ and changing the I/O port to input for the ‘1’ (the pull-up resistors in the keyboard takes care of the proper voltage level). In essence, the I/O port direction becomes the signal as seen from the microcontroller side. This is similar to how it was implemented in hardware for the original IBM PC.
There needs to be hardware that can pull the two signal lines (CLK and DATA) low, but not pull them high (they must not be driven high as that would cause a conflict when the keyboard sends information in the other direction—the normal operation of the keyboard when the user presses keys). This is similar to how an SPI or I²C interface works.
A naive approach (or if the I/O direction can not be changed), is to implement it with discrete BJTs (open collector), FETs (open drain), using TTL chips or CMOS chips. In all cases there must be a pull-up resistor somewhere (that is already the case for a PS/2 keyboard), and it should have a sufficiently low value to satisfy timing requirements. In the original IBM PC, a TTL 74LS125 with three-state outputs was used for this purpose. The input was fixed to ground, and the controlling signal was to the (inverting) enable pin. The pull-up resistor was 4.7 kΩ.
Note that the BJT solution is inverting (e.g., a high on the I/O port used to drive the circuit for CLK will result in a (driving) low on CLK on the PS/2 interface side), so this must be accounted for, e.g., in the software. A floating input to the BJT circuit is OK, but it is best to set it to a driving low when the microcontroller program initialises the hardware (I/O ports). Note that many diagrams, incl. in the first source, do not show the base resistor. It is required to limit the current; otherwise, the microcontroller may be damaged. Its value can be on the order of 10 kΩ.
Note that if the CLK signal is connected to an interrupt capable pin, the software has to account for that; asserting the CLK will generate an interrupt that must be ignored (to not be interpreted as something the keyboard sends).
Protocol, bit level
Before sending the first bit to the keyboard, a certain protocol is required to initiate sending information to the keyboard:
- Hold CLK low for at least 100 µsec (more than one bit time), for instance 150 µsec (0.15 millisecond).
- Assert the DATA signal low
- Release the CLK signal (setting it to high)
- Hold the DATA signal low until the keyboard starts producing clock signals on CLK. That is, a falling edge on CLK. This can take up to a whopping 6 ms (6000 µsec) (but it can be much shorter), with some keyboards not going over 750 µsec. 750 µsec corresponds to about 11 bit times (a full frame). Note that the waiting for the falling edge on CLK is automatically included in sending the first bit (see below).
The keyboard samples on the rising edge of the CLK signal (in normal operation, when the keyboard sends key codes as a result of key presses, the significant edge is the falling edge), so the DATA signal can be set up shortly after the falling edge of the CLK signal.
So the sequence to send a single bit (say, a data bit) is:
- Wait for a falling edge of CLK
- Set up the DATA signal
- Nothing more to do, other than wait! Sufficient time must pass so that the data is sampled by the keyboard on the rising edge of CLK (on the order of 50 µsec later)
Note: For 1., if implemented in a bit-banging busy wait way (the most common) with a microcontroller, it is crucial to have a timeout, otherwise the program may hang, e.g., if the keyboard is not connected…
Protocol, byte level
Similar (but not identical) to asynchronous serial communication, when sending to the keyboard, a data frame consists of:
- 8 data bits with the least significant bit (LSB) send first
- Parity bit, always odd parity (an even number of ‘1’ data bits results in a parity bit of 1 (a more round-about way of saying it is that the number of ‘1’ bits, including the parity bit itself, is odd…))
- Stop bit. Always ‘1’. This also frees up the DATA signal (as the next data bit will be from the keyboard). This also marks the end of the time that the host will assert any of the signals.
- Acknowledge bit. Always ‘0’. This is send from the keyboard (reverse flow of information).
Note 1: When sending to the keyboard, a start bit is not send (in contrast to when the keyboard is sending). There are still 11 pulses on CLK (corresponding to 11 bits), but that is due to the extra bit at the end, the acknowledge bit.
Or an alternative interpretation is that the asserted low DATA line is some sort of start bit – but it is a very weird start bit (very long, variable length, up to about 90 bit times long (about 8 frames), and does not work unless CLK has the right polarities at the right times), and does not fit with how the rest of the bits are clocked (from the keyboard) – it could be interpreted as the host providing the first (rising) CLK edge, but that is a bit of a stretch.Note 2: In most cases, after the keyboard has received a command byte, it will send back an acknowledge byte. Thus if we want to send more than one command byte (that is the case for setting the LEDs), we can’t send the second command byte immediately after the first. One way is to simply to ignore what the keyboard sends back and wait a sufficient amount of time before blindly sending the second command byte, say 2 ms (2000 µsec). It is not known how robust this method is. For instance, if the keyboard takes longer than expected to send the acknowledge byte for the first command byte, it could result in a transmit error and thus disabling the keyboard…
Note 3: When the keyboard sends, it also uses odd parity.
Parity bit
The parity bit must be set correct. Otherwise, the keyboard will ignore the command byte.
Protocol, message level
Only two commands are needed for this purpose, reset, 0xFF (not strictly necessary as power on does the same) and 0xED followed by a byte where the last three bits (bit 0, bit 1, and bit 2) represent the three LEDs:
- Bit 0: Scroll Lock
- Bit 1: Num Lock
- Bit 2: Caps Lock
Note that an acknowledge byte is send by the keyboard for each of the two bytes.
It may or may not be necessary to disable the keyboard before sending the commands to set the LEDs. Also, a transmit error may result in the keyboard becoming disabled. In that case, command 0xF4 can enable it.
Could the built-in asynchronous serial port in a microcontroller be used to encode and decode the signal to the PS/2 keyboard?
That is, hardware support for the task, including generating and checking for the parity bit.
The signals are similar to asynchronous serial communication, but it
is different enough that it would be cumbersome to try to use an asynchronous serial port. Though it requires more CPU cycles on the microcontroller and increases the complexity somewhat, the microcontroller for a macro keyboard is very lightly loaded, so this is usually not a problem.One problem is that the baud rate is not fixed, only specified to be in the range 10,000 to 16,600 (thus, the bit time is in the range 60 µsec to 100 µsec). Indeed, the baud rate does vary between keyboards. One keyboard appeared to use the standard baud rate 14,400 whereas some other ones were measured to use 13,300 baud and 12,000 baud, respectively. The baud rate would have to be determined first. One way is to do it during RESET. Sending the RESET command only requires the initiating sequence as described above (near “Before sending the first bit to the keyboard”) as the command is all ‘1’s (0xFF) and the parity bit also happens to be ‘1’ (the DATA line does not need to be driven low for any of the bits and can just be left as is). Another way is to measure it beforehand for the particular keyboard. This is feasible if keyboards are not changed often. This has the advantage that it is not necessary to connect the CLK signal.
Another problem is the missing start bit when sending to the keyboard. Starting the UART at the right time would require being able to see into the future (the UART needs to be started before the keyboard starts to outputting the first CLK cycle (for the first data bit)).
Conclusion: The emphasis here is setting the LEDs, but using the async serial hardware in a microcontroller for receiving from the keyboard (when the user types) using a baud rate that has been determined somehow is definitely feasible. When combined with interrupts for received bytes, it would put minimum load on the microcontroller and the software complexity (and size) would be reduced.
Could the built-in SPI or I²C support in a microcontroller be used to encode and decode the signal to the PS/2 keyboard?
Seen from afar, they seem closer matched than asynchronous serial communication, at least for sending to the keyboard (no start bit and the variable waiting time before the first data bit is not a problem as it is controlled by synchronous nature of it (the CLK signal)).
However, the parity bit would have to be computed in software. And it results in more 8 bits on the wire. How does the more than 8 bits (9, 10, or 11 bits, depending on the mode and how it is implemented) map to the hardware? For instance, the SPI hardware in the AVR microcontrollers only works with 8 bits at a time.
This is an open question.
Results
Controlling the LEDs in the bit-banging way (without hardware support or use of interrupt capable I/O pins) was successfully tested with three different PS/2 keyboards, including a very old one from 1989 using the DIN-5 interface.
The microcontroller platform was Arduino Leonardo, and it was also demonstrated that the Arduino environment is perfectly suitable for full-blown use of C++, incl. polymorphic calls (sub classes were used to represent (and hide the details) of different ways of using the required open collector outputs).
Both the BJT open collector external circuit and the microcontroller emulation of open collector worked successfully.
References
- The PS/2 Mouse/Keyboard Protocol by Adam Chapweske. 2003-05-09. (Though this may now go into an infinite loop, never loading. Possibly related to cookies or login. A workaround is not currently known). In PDF format. It contains the most detailed information about sending commands to PS/2 keyboards (though it could have been written in a clearer and more succinct way). It also contains the command byte to send to change the LEDs and the definition of which bits correspond to which LEDs.
Note: This reference exists in many places, many of which are broken (e.g., all links with www.computer-engineering.org (listed as the original source) are broken (the DNS domain does not exist any more). A version (2003-05-09) on Internet Archive.). Another place is The PS/2 Keyboard Interface (different title, but essentially the same content. Slightly older revision: 2003-04-01. In HTML format.). And there is The AT-PS/2 Keyboard Interface (yet another title, but essentially the same content). From 2001. In PDF format. - PS/2. Wikipedia. It does cover sending to the keyboard at the bit level, but it is too vague. E.g., the width of the initial CLK negative pulse is not specified.
- Keyboard commands. Very detailed description of the commands that PS/2 keyboards accept and the possible responses from the keyboard, incl. different versions and non-standard commands. It only covers the message level, not the signal level.
- PS/2 Keyboard. A rare find (the search engines will not return this one): Very detailed information about the command level of PS/2 keyboards, incl. the responses the keyboard is expected to send. It includes a list of 17 commands with a detailed description of the parameters. It only covers the message level, not the signal level. But it seems to be in error for the reset command (0xFF); empirically, a keyboard both sends an acknowledge byte (0xFA) almost immediately (e.g., after 500 µsec) and the result (0xAA – ‘self-test passed’) several hundred milliseconds after that (e.g., after 495 milliseconds).
- 1. Keyboard scancodes. Another rare find. It lists command codes for setting extra LEDs for some obscure keyboards (like Logitech Internet keyboard). It also actually lists the scan codes, incl. the later added left and right Windows and context menu keys (paraphrased): “The Microsoft keyboard adds 0xE0 0x5B (LeftWindow), 0xE0 0x5C (RightWindow), and 0xE0 0x5D (Menu)”. For the last number in the sequences, the decimal numbers are 91, 92, and 93, respectively. 0xE0 (decimal 224) is the standard for the scan code sequences with a length of 2.
Though empirically (observed with two different keyboards, one of them a USB one in PS/2 mode), they seem to instead be 0xE0 0x1F (LeftWindow), 0xE0 0x27 (RightWindow), and 0xE0 0x2B (context menu) (decimal 31, 39, and 47, respectively)