How to interface a PS2 keyboard

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:

PS2 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.

Open Collector Circuit

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.

Packet bits

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:

Keyboard to 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.

Host to Keyboard

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:

Host Commands

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.

Leave a Reply