// PS/2 keyboard -> USB HID on Arduino Pro Micro (ATmega32U4, 5 V / 16 MHz). // Educational example: keyboard only, scan code set 2, no LEDs, no host-to-keyboard commands. // For daily use take a complete converter firmware (TMK ibmpc_usb, Soarer's Converter). // // Wiring (PS/2 socket pin -> Pro Micro): // 1 DATA -> pin 4, 5 CLK -> pin 3 (INT0), 4 +5V -> VCC, 3 GND -> GND // 4.7 kOhm pull-up from DATA to +5 V and from CLK to +5 V. // // The main idea vs. a cheap adapter: if a frame is damaged, the "key released" code may be lost, // so on any receive error we release ALL keys instead of leaving one key stuck. #include const uint8_t PIN_CLK = 3; const uint8_t PIN_DATA = 4; // Ring buffer filled in the interrupt, emptied in loop(). volatile uint8_t rx[32]; volatile uint8_t rxHead = 0; volatile uint8_t rxTail = 0; volatile bool rxError = false; void onClockFall() { static uint8_t bitNo = 0; static uint8_t value = 0; static uint8_t ones = 0; static unsigned long lastUs = 0; unsigned long now = micros(); // A PS/2 bit lasts 60-100 us. A gap over 2 ms means we lost sync: start a new frame. if (bitNo != 0 && now - lastUs > 2000) { bitNo = 0; rxError = true; } lastUs = now; uint8_t d = digitalRead(PIN_DATA); if (bitNo == 0) { // start bit must be 0 if (d != 0) { return; } value = 0; ones = 0; } else if (bitNo <= 8) { // 8 data bits, LSB first if (d) { value |= (uint8_t)(1 << (bitNo - 1)); ones++; } } else if (bitNo == 9) { // odd parity if (((ones + d) & 1) == 0) { rxError = true; } } else { // stop bit must be 1 if (d == 1) { uint8_t next = (rxHead + 1) & 31; if (next != rxTail) { rx[rxHead] = value; rxHead = next; } else { rxError = true; // buffer overflow } } else { rxError = true; } bitNo = 0; return; } bitNo++; } // Set 2 scan code -> value for Keyboard.press(): 0x80..0x87 modifiers, 136 + HID usage for other keys. uint8_t mapKey(uint8_t code, bool ext) { if (ext) { switch (code) { case 0x14: return KEY_RIGHT_CTRL; case 0x11: return KEY_RIGHT_ALT; case 0x1F: return KEY_LEFT_GUI; case 0x27: return KEY_RIGHT_GUI; case 0x75: return 136 + 0x52; // Up case 0x72: return 136 + 0x51; // Down case 0x6B: return 136 + 0x50; // Left case 0x74: return 136 + 0x4F; // Right case 0x70: return 136 + 0x49; // Insert case 0x71: return 136 + 0x4C; // Delete case 0x6C: return 136 + 0x4A; // Home case 0x69: return 136 + 0x4D; // End case 0x7D: return 136 + 0x4B; // Page Up case 0x7A: return 136 + 0x4E; // Page Down case 0x5A: return 136 + 0x58; // Keypad Enter case 0x4A: return 136 + 0x54; // Keypad / default: return 0; // E0 12 / E0 59 (fake shifts of Print Screen) and the rest are ignored } } switch (code) { case 0x1C: return 136 + 0x04; // A case 0x32: return 136 + 0x05; // B case 0x21: return 136 + 0x06; // C case 0x23: return 136 + 0x07; // D case 0x24: return 136 + 0x08; // E case 0x2B: return 136 + 0x09; // F case 0x34: return 136 + 0x0A; // G case 0x33: return 136 + 0x0B; // H case 0x43: return 136 + 0x0C; // I case 0x3B: return 136 + 0x0D; // J case 0x42: return 136 + 0x0E; // K case 0x4B: return 136 + 0x0F; // L case 0x3A: return 136 + 0x10; // M case 0x31: return 136 + 0x11; // N case 0x44: return 136 + 0x12; // O case 0x4D: return 136 + 0x13; // P case 0x15: return 136 + 0x14; // Q case 0x2D: return 136 + 0x15; // R case 0x1B: return 136 + 0x16; // S case 0x2C: return 136 + 0x17; // T case 0x3C: return 136 + 0x18; // U case 0x2A: return 136 + 0x19; // V case 0x1D: return 136 + 0x1A; // W case 0x22: return 136 + 0x1B; // X case 0x35: return 136 + 0x1C; // Y case 0x1A: return 136 + 0x1D; // Z case 0x16: return 136 + 0x1E; // 1 case 0x1E: return 136 + 0x1F; // 2 case 0x26: return 136 + 0x20; // 3 case 0x25: return 136 + 0x21; // 4 case 0x2E: return 136 + 0x22; // 5 case 0x36: return 136 + 0x23; // 6 case 0x3D: return 136 + 0x24; // 7 case 0x3E: return 136 + 0x25; // 8 case 0x46: return 136 + 0x26; // 9 case 0x45: return 136 + 0x27; // 0 case 0x5A: return 136 + 0x28; // Enter case 0x76: return 136 + 0x29; // Esc case 0x66: return 136 + 0x2A; // Backspace case 0x0D: return 136 + 0x2B; // Tab case 0x29: return 136 + 0x2C; // Space case 0x4E: return 136 + 0x2D; // - case 0x55: return 136 + 0x2E; // = case 0x54: return 136 + 0x2F; // [ case 0x5B: return 136 + 0x30; // ] case 0x5D: return 136 + 0x31; // backslash case 0x4C: return 136 + 0x33; // ; case 0x52: return 136 + 0x34; // ' case 0x0E: return 136 + 0x35; // ` case 0x41: return 136 + 0x36; // , case 0x49: return 136 + 0x37; // . case 0x4A: return 136 + 0x38; // / case 0x58: return 136 + 0x39; // Caps Lock case 0x05: return 136 + 0x3A; // F1 case 0x06: return 136 + 0x3B; // F2 case 0x04: return 136 + 0x3C; // F3 case 0x0C: return 136 + 0x3D; // F4 case 0x03: return 136 + 0x3E; // F5 case 0x0B: return 136 + 0x3F; // F6 case 0x83: return 136 + 0x40; // F7 case 0x0A: return 136 + 0x41; // F8 case 0x01: return 136 + 0x42; // F9 case 0x09: return 136 + 0x43; // F10 case 0x78: return 136 + 0x44; // F11 case 0x07: return 136 + 0x45; // F12 case 0x12: return KEY_LEFT_SHIFT; case 0x59: return KEY_RIGHT_SHIFT; case 0x14: return KEY_LEFT_CTRL; case 0x11: return KEY_LEFT_ALT; default: return 0; } } void setup() { pinMode(PIN_CLK, INPUT); // external 4.7 kOhm pull-ups do the job pinMode(PIN_DATA, INPUT); Keyboard.begin(); attachInterrupt(digitalPinToInterrupt(PIN_CLK), onClockFall, FALLING); } void loop() { static bool ext = false; static bool brk = false; static uint8_t skip = 0; if (rxError) { // A damaged or lost frame may have been a "release" code. Releasing everything is safer // than a key that repeats until the next press. rxError = false; Keyboard.releaseAll(); ext = false; brk = false; } while (rxTail != rxHead) { uint8_t c = rx[rxTail]; rxTail = (rxTail + 1) & 31; if (skip) { // rest of the Pause sequence skip--; continue; } if (c == 0xE1) { // Pause: E1 14 77 E1 F0 14 F0 77 skip = 7; continue; } if (c == 0xAA || c == 0x00 || c == 0xFF) { // AA: keyboard self-test passed (it was reconnected or reset); 00/FF: keyboard buffer overrun. Keyboard.releaseAll(); ext = false; brk = false; continue; } if (c == 0xE0) { ext = true; continue; } if (c == 0xF0) { brk = true; continue; } uint8_t k = mapKey(c, ext); if (k) { if (brk) { Keyboard.release(k); } else { Keyboard.press(k); } } ext = false; brk = false; } }