► Games

► Music

► Clocks


► Tools

► Tutorials

By processor

► ATtiny85

► ATtiny84

► ATtiny841

► ATtiny2313

► ATtiny861

► ATmega328

► ATmega1284

About Me

About Me


RSS feed

Simple PS/2 Keyboard Interface

14th August 2016

The simplest way to connect a QWERTY keyboard to an Arduino project is to use a PS/2 keyboard, which has a relatively straightforward serial interface. This article describes a simple PS/2 keyboard interface, which was designed as the basis for a project I hope to describe in a future article.


Adafruit PS/2 and USB miniature keyboard.

There are already some excellent PS/2 Keyboard libraries for the Arduino [1], but I wanted a simple interface that I could modify to integrate it with my application. This code supports a US keyboard, and only handles the Shift modifier key, although it should be quite easy to add support for Ctrl and Alt.

I tested it with the Adafruit PS/2 keyboard [2], shown above, a compact low-cost keyboard that also supports USB. Alternatively there are low-cost PS/2 keyboards available on eBay.


PS/2 keyboards use a two-wire serial protocol, using a clock and data, where the data consists of a '0' start bit, eight data bits, a parity bit, and a '1' stop bit. Unfortunately they don't give the ASCII code of the key, but a scan code approximately related to the position of the key on the keyboard [3]. The modifier keys, such as Shift, behave just like the other keys, so for example we need to take special action to decide whether we want '2' or '@' when the '2' key is pressed.

When a key is pressed its scan code is transmitted. When the key is released the break code 0xF0 is transmitted, followed by the key's code again. Finally, if a key is held down it autorepeats after a delay, transmitting its scan code repeatedly. Even the Shift key autorepeats!

The keymap

The program uses a string, Keymap, stored in program memory, to convert between scan codes and ASCII characters:

const char Keymap[] PROGMEM = 
// Without shift
"             \011`      q1   zsaw2  cxde43   vftr5  nbhgy6   mju78  ,kio09"
"  ./l;p-   \' [=    \015] \\        \010  1 47   0.2568\033  +3-*9      "
// With shift
"             \011~      Q!   ZSAW@  CXDE$#   VFTR%  NBHGY^   MJU&*  <KIO)("
"  >?L:P_   \" {+    \015} |        \010  1 47   0.2568\033  +3-*9       ";

The nth position in the string gives the ASCII character for scan code n. The first 132 characters give the unshifted ASCII characters, and the second 132 characters give the ASCII characters for the same scan codes with Shift held down. I've represented unused scan codes by a space in the Keymap string. Nonprinting ASCII codes, like Enter (ASCII code 13), are represented by an octal escape sequence in the Keymap string, such as \015.

Currently the scan codes corresponding to the function keys F1 to F12 are unassigned, but you could handle these by replacing the appropriate space characters in the table by the ASCII codes you want to use. Also, the keys on the numeric keypad currently return the same ASCII characters as the corresponding normal keys, but you could change this if you want.

The program

The PS/2 keyboard connects to the Arduino or ATmega328 by four wires: Clock, Data, +5V, and Gnd. Clock connects to the Arduino via pin 2 (PD2/INT0), and the Data line connects to pin 4 (PD4). Why 4 rather than 3? Because I wanted to leave pin 3 free for possible use by digitalwrite().

All the work is done by an interrupt service routine which is called on the falling edge of the clock signal. First we need to set up the pins and interrupt:

  EICRA = 2<<ISC00;                       // INT0 on falling edge
  PORTD = PORTD | 1<<PORTD4 | 1<<PORTD2;  // Enable pullups on PD2 and PD4
  EIMSK = EIMSK | 1<<INT0;                // Enable INT0 interrupt

Then here's the interrupt service routine:

ISR(INT0_vect) {
  static int ScanCode = 0, ScanBit = 1, Break = 0, Modifier = 0, Shift = 0;
  if (PIND & 1<<PIND4) ScanCode = ScanCode | ScanBit;
  ScanBit = ScanBit << 1;
  if (ScanBit != 0x800) return;
  // Process scan code
  if ((ScanCode & 0x401) != 0x400) return; // Invalid start/stop bit
  int s = (ScanCode & 0x1FE) >> 1;
  ScanCode = 0, ScanBit = 1;
  if (s == 0xAA) return;                   // BAT completion code
  if (s == 0xF0) { Break = 1; return; }
  if (s == 0xE0) { Modifier = 1; return; }
  if (Break) {
    if ((s == 0x12) || (s == 0x59)) Shift = 0;
    Break = 0; Modifier = 0; return;
  if ((s == 0x12) || (s == 0x59)) Shift = 1;
  if (Modifier) return;
  char c = pgm_read_byte(&Keymap[s + KeymapSize*Shift]);
  if (c == 32 && s != 0x29) return;

On successive clock pulses it simply shifts 11 bits into the variable ScanCode. When all 11 bits have been received it removes the start, stop, and parity bits, and checks for the break, modifier, and shift key codes. It then looks up the scan code in the Keymap table. Invalid scan codes represented by a space in the KeyMap table are ignored, unless the code is 0x29, which corresponds to the actual Space key. Finally the ASCII character is printed to the serial monitor. In an actual application you would replace the call to Serial.print() by a call to your code to handle the key.

Some special keys on the keyboard, such as the cursor keys, are preceded by the additional modifier scan code 0xE0. Currently these are ignored by the line:

  if (Modifier) return;

If you want to detect these keys insert a check for their scan codes in this line.

Here's the whole PS/2 Keyboard program: PS/2 Keyboard Program.


10th September 2016: At power-on PS/2 keyboards perform a diagnostic self-test referred to as BAT (Basic Assurance Test) and then emit the success code 0xAA. I've added the line:

  if (s == 0xAA) return;                   // BAT completion code

to ignore the BAT code. 

  1. ^ PS2 Keyboard Library on PJRC.
  2. ^ Miniature Keyboard - Microcontroller Friendly PS/2 and USB on Adafruit.
  3. ^ Keyboard Scan Codes: Set 2 on

blog comments powered by Disqus