In this post I will discuss some of the basic concepts needed to add PS2 type keyboard support to your next project. For the examples I will be using an Arduino Uno, but the idea is to generally keep the post generic so that it’s also useful on the platform your are using.
Physical Interface
The PS2 connector is a 6-pin mini-DIN type connector which you often see in either purple (keyboard) or green (mouse). The PS2 name comes from “Personal System 2” which was one of the first IBM computers to use the interface to connect a keyboard and mouse. Although there are no difference in the pin-out of the green and purple connectors, the protocol they use differs and is therefore are not interchangeable. Typically the peripheral would have the male part of the connector with the following pin-out:
Electrical Interface
The keyboard is powered by 5V and the standard dictates that it should not consume more than 275mA. Data and clock lines use an open collector interface, which means either end (host or device) can only pull the lines low. Pull-up resistors are required to keep the lines high, if you have dealt with I2C before then this should sound familiar. Using this approach either end of the line can signal by pulling it low. The line is thus high during idle which means the signals are active low.
Protocol
The keyboard uses a bi-directional serial protocol that shifts data in and out with a clock signal which is always generated by the keyboard. Clock rates are typically somewhere between 15kHz and 16kHz. Each data packet consist of a start bit (0), 8 data bits, a parity bit (odd parity) and a stop bit (1). Data is send with the LSB (Least Significant Bit) first.
Data from the keyboard to the host is sampled on the falling clock edge while data from the host to the keyboard is sampled on the rising clock edge. The host can prevent the keyboard from sending data by pulling the clock line low.
If the host wants to send data to the keyboard it needs to first signal the keyboard with a request to send. The keyboard will then start to generate the clock pulses for the host to shift data in on.
Keyboard To Host
When the user presses a key, the keyboard will generate a value known as the scan code for the relevant key and send it to the host. When the key is released the keyboard generates a second scan code which is also send to the host. So the host will be informed when a key is pressed and when it is released, this comes in handy when for example the user press and hold shift plus another key.
The diagram below shows the transfer of data from the keyboard to the host:
Host To Keyboard
It’s also possible for the host to send data to the keyboard. This is normally in the form of commands to reset, change some settings or control the LED’s on the keyboard. As mentioned the keyboard always generate the clock, so for the host to send data it needs to signal the keyboard to start generating clock pulses. The host can signal the keyboard by pulling the clock line low for at least 100 microseconds, then pulling the data line low followed by releasing the clock line again. This first rising edge of the clock can be seen as the start bit. The keyboard will then start generating clock pulses, and sample the data line on the following ten (8 data + 1 parity + 1 stop) rising clock edges. After receiving the stop bit the keyboard will respond with an
acknowledgement bit by pulling the data line low and generating another clock pulse. Since the acknowledge is send from the keyboard it should be sampled on the falling edge of the extra clock pulse.
Scan Codes
As mentioned before when pressing a key the keyboard generates a scan code value to identify which key was pressed or released. Pressing a key generates what is known as a “make code” and releasing it generates a “break code”. These codes are not ASCII character values of the keys as one might have thought but rather identifies where the key is on the keyboard. Several sets of scan codes exist and most keyboards use what is known as set 2. In this set the break code value is generally the same as the make code value but preceded by the value 0xF0. For example the key ‘K’ on your keyboard has a set 2 make code of 0x42. When you press this key the keyboard will send 0x42 and when you release the key it will send 0xF0 followed by 0x42.
Most of the set 2 make codes consist of only a single byte but there are some extended codes which are longer. These keys will first generate the value 0xE0 followed by the make code. The arrow keys are examples of extended codes, pressing the right arrow key will generate 0xE0 followed by 0x74 and releasing it will generate 0xE0 0xF0 0x74. Notice how the keyboard again first sends 0xE0 before the break code (0xF0 0x74).
Below is a list of the make codes for some keys, but note that depending on what region your keyboard comes from it might differ slightly:
Keyboard Commands
The host uses commands and accompanying arguments to communicate with the keyboard. After the keyboard receives a command it will respond with 0xFA to acknowledge the received command (sometime even if it does not understand the command). The host can then send more argument bytes depending on the command. Below are some of the commands that can be used:
Example Code
Below is a very basic Arduino example you can use as a starting point. I have added a fair amount of comments and in general the code should be very easy to understand. One thing to note is that although this example should work fine, it is not at all optimized and there are a lot of room for improvement. The idea behind it is more to complement the theory described above.
/*
* Example code for PS2 Keyboard Article
* Jan Swanepoel, 2019
*/
// Set which pins are clock and data
#define pinClock 8
#define pinData 9
uint8_t clkPrev;
uint8_t keyCodeReceived;
// Parity check
// Return true for odd parity.
bool ParityCheck(uint16_t data)
{
uint8_t count = 0;
// Test first 10 bits, since start is always 0 it does not matter to include it
for (int i = 0; i < 10; i++)
if (data & (1 << i))
count++;
// Check if odd
if (count % 2)
return true;
return false;
}
// Listen for scan codes from keyboard
bool Listen(uint8_t* keyCode)
{
static uint16_t rxCode = 0; // 16 bit variable use to store the received bits
static uint8_t rxBitCount = 0; // Keep track of number of bits clocked
uint8_t clkNow = digitalRead(pinClock); // Get current clock level
// Look for falling clock edge
if (clkNow < clkPrev)
{
if (digitalRead(pinData) > 0)
rxCode = rxCode | 0x0400; // Sets the 11th bit
else
rxCode = rxCode & 0x0BFF; // Clears the 11th bit
rxBitCount++;
if (rxBitCount == 11) // New 11 bit data received from keyboard?
{
if (ParityCheck(rxCode)) // Retrieve keyCode if parity bit is valid
{
rxCode = rxCode >> 1; // Remove start bit
*keyCode = rxCode & 0x00FF; // Remove parity & stop bits and assign the key to keyCode
clkPrev = clkNow;
rxBitCount = 0;
rxCode = 0;
return true;
}
else
{
// Parity check failed
}
rxCode = 0;
rxBitCount = 0;
}
rxCode = rxCode >> 1; // Shift rxCode to ready for next bit
}
clkPrev = clkNow;
return false;
}
void Command(uint8_t cmdCode)
{
uint16_t cmd = cmdCode; // Cast to 16 bit
if (!ParityCheck(cmd))
cmd = cmd | 0x0100; // Update parity bit
cmd |= 0x0200; // Add stop bit
// Set pins to output
pinMode(pinClock, OUTPUT);
pinMode(pinData, OUTPUT);
// Start Request to Send
// Pull clock low for 100us
digitalWrite(pinClock, LOW);
delayMicroseconds(100);
// Pull data low (which represents the start bit)
digitalWrite(pinData, LOW);
delayMicroseconds(20);
// Release clock
pinMode(pinClock, INPUT_PULLUP);
// Write 8 data bits + 1 parity bit + 1 stop bit
for (int i = 0; i < 10; i++)
{
// Wait for keyboard to take clock low
while(digitalRead(pinClock) > 0);
// Load bit
digitalWrite(pinData, cmd & 0x0001);
cmd = cmd >> 1;
// Wait for keyboard to take clock high (sample bit)
while(digitalRead(pinClock) < 1);
}
// Release data line (which keeps it high for the stop bit)
pinMode(pinData, INPUT_PULLUP);
// Wait for keyboard to take clock low
while(digitalRead(pinClock) > 0);
//Wait for data low, which would be the ack from keyboard
while(digitalRead(pinData) > 0);
}
void setup()
{
uint8_t ack = 0;
Serial.begin(57600);
// Set pins to input with pull-up resistors
pinMode(pinClock, INPUT_PULLUP);
pinMode(pinData, INPUT_PULLUP);
delay(100);
// Switch on CAPS, NUM and SCROLL lock LEDs
Command(0xED); // Send command
while(!Listen(&ack)); // Wait for acknowledge
Command(0x07); // Send argument
while(!Listen(&ack)); // Wait for acknowledge
}
void loop()
{
if (Listen(&keyCodeReceived))
Serial.println(keyCodeReceived, HEX);
}
For this example to work you should connect the PS2 clock line to pin 8 and PS2 data line to pin 9 of your Arduino Uno. You should also connect the 5V and ground pins from your Arduino to the PS2 5V and ground pins. After flashing the code, start up the serial monitor and when typing on the keyboard you should see the make and break codes being displayed. On start-up the program will also send a command to the keyboard to switch on all three LED’s.
Notice that when setting the pins as inputs we use INPUT_PULLUP to enable the internal pull up resistors. This is because as we mentioned earlier the signal and clock lines are open collector lines and requires a pull up resistor to work. If you are not using an Arduino make sure to enable them for your microcontroller or if it does not have them add them externally. Something around 1K to 10K should be fine.
In the end
So like always, I hope this post can be helpful should you every have the need to interface a PS2 type keyboard to your project.