Skip to content

Instantly share code, notes, and snippets.

@DorianRudolph
Last active January 4, 2024 03:36
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save DorianRudolph/ca283dfdfd185bc812b7 to your computer and use it in GitHub Desktop.
Save DorianRudolph/ca283dfdfd185bc812b7 to your computer and use it in GitHub Desktop.
Arduino PS2 to USB HID Keyboard Converter

To make the programs work you need to open the file hardware/arduino/avr/cores/arduino/USBAPI.h located in your Arduino folder and make the Keyboard_::sendReport method public.

The PS2 protocol is implemented with information from: http://retired.beyondlogic.org/keyboard/keybrd.htm

USB scancodes are taken from: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-14.html

Some of the code to receive the PS2 scan codes is taken from the PS2Keyboard library: http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html

Information on how to use the KeyReport.modifiers field can be taken from: http://www.usb.org/developers/hidpage/HID1_11.pdf (page 56)

Copyright (c) 2015, Dorian Rudolph
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
//Program that outputs all PS2 scancodes via serial
#define BUFFER_SIZE 45
static volatile uint8_t buffer[BUFFER_SIZE];
static volatile uint8_t head, tail;
#define DATA_PIN 10
#define IRQ_PIN 3
// The ISR for the external interrupt
void ps2interrupt(void) {
static uint8_t bitcount=0;
static uint8_t incoming=0;
static uint32_t prev_ms=0;
uint32_t now_ms;
uint8_t n, val;
val = digitalRead(DATA_PIN);
now_ms = millis();
if (now_ms - prev_ms > 250) {
bitcount = 0;
incoming = 0;
}
prev_ms = now_ms;
n = bitcount - 1;
if (n <= 7) {
incoming |= (val << n);
}
bitcount++;
if (bitcount == 11) {
uint8_t i = head + 1;
if (i >= BUFFER_SIZE) i = 0;
if (i != tail) {
buffer[i] = incoming;
head = i;
}
bitcount = 0;
incoming = 0;
}
}
static inline uint8_t get_scan_code(void) {
uint8_t c, i;
i = tail;
if (i == head) return 0;
i++;
if (i >= BUFFER_SIZE) i = 0;
c = buffer[i];
tail = i;
return c;
}
void setup() {
Serial.begin(9600);
attachInterrupt(0, ps2interrupt, FALLING);
pinMode(IRQ_PIN, INPUT_PULLUP);
pinMode(DATA_PIN, INPUT_PULLUP);
head = 0;
tail = 0;
}
void loop() {
uint8_t scan_code = get_scan_code();
if (scan_code) {
Serial.println(scan_code, HEX);
}
}
//Program that also outputs all PS2 scancodes via serial and can also send commands to the keyboard
#define BUFFER_SIZE 45
static volatile uint8_t buffer[BUFFER_SIZE];
static volatile uint8_t head, tail;
static volatile uint8_t sendBits, msg, bitCount, setBits;
#define DATA_PIN 10
#define IRQ_PIN 3
// The ISR for the external interrupt
void ps2interrupt(void) {
static uint8_t bitcount=0;
static uint8_t incoming=0;
static uint32_t prev_ms=0;
uint32_t now_ms;
uint8_t n, val;
if (!sendBits){
val = digitalRead(DATA_PIN);
now_ms = millis();
if (now_ms - prev_ms > 250) {
bitcount = 0;
incoming = 0;
}
prev_ms = now_ms;
n = bitcount - 1;
if (n <= 7) {
incoming |= (val << n);
}
bitcount++;
if (bitcount == 11) {
uint8_t i = head + 1;
if (i >= BUFFER_SIZE) i = 0;
if (i != tail) {
buffer[i] = incoming;
head = i;
}
bitcount = 0;
incoming = 0;
}
} else {
--sendBits;
uint8_t b = bitCount - 1;
if (b == 8){
digitalWrite(DATA_PIN, !(setBits & 1));
} else if (b == 9) {
pinMode(DATA_PIN, INPUT_PULLUP);
} else if (b < 8) {
bool bt = (msg >> b) & 1;
digitalWrite(DATA_PIN, bt);
setBits += bt;
}
++bitCount;
}
}
static inline uint8_t get_scan_code(void) {
uint8_t c, i;
i = tail;
if (i == head) return 0;
i++;
if (i >= BUFFER_SIZE) i = 0;
c = buffer[i];
tail = i;
return c;
}
void setup() {
Serial.begin(9600);
attachInterrupt(0, ps2interrupt, FALLING);
pinMode(IRQ_PIN, INPUT_PULLUP);
pinMode(DATA_PIN, INPUT_PULLUP);
head = 0;
tail = 0;
sendBits = 0;
}
void send_msg(uint8_t m) {
noInterrupts();
pinMode(IRQ_PIN, OUTPUT);
digitalWrite(IRQ_PIN, LOW);
delayMicroseconds(60);
pinMode(IRQ_PIN, INPUT_PULLUP);
msg = m;
bitCount = 0;
sendBits = 12;
setBits = 0;
pinMode(DATA_PIN, OUTPUT);
digitalWrite(DATA_PIN, LOW);
interrupts();
}
bool send_led;
uint8_t led;
void loop() {
uint8_t scan_code = get_scan_code();
if (scan_code) {
Serial.println(scan_code, HEX);
if (scan_code == 0x58){
send_msg(0xED);
led ^= 4;
send_led = true;
} else if (scan_code == 0xFA && send_led) {
send_msg(led);
send_led = false;
}
}
}
//Complete Program for an Arduino to act as an PS2 to USB converter
#define DATA_PIN 10
#define IRQ_PIN 3
#define BUFFER_SIZE 45
static volatile uint8_t buffer[BUFFER_SIZE];
static volatile uint8_t head, tail;
static volatile uint8_t sendBits, msg, bitCount, setBits;
KeyReport report;
uint8_t K[255], KE[255];
uint8_t leds;
bool send_leds;
void setup_keymaps(){
K[0x1C] = 4;
K[0x32] = 5;
K[0x21] = 6;
K[0x23] = 7;
K[0x24] = 8;
K[0x2B] = 9;
K[0x34] = 10;
K[0x33] = 11;
K[0x43] = 12;
K[0x3B] = 13;
K[0x42] = 14;
K[0x4B] = 15;
K[0x3A] = 16;
K[0x31] = 17;
K[0x44] = 18;
K[0x4D] = 19;
K[0x15] = 20;
K[0x2D] = 21;
K[0x1B] = 22;
K[0x2C] = 23;
K[0x3C] = 24;
K[0x2A] = 25;
K[0x1D] = 26;
K[0x22] = 27;
K[0x35] = 28;
K[0x1A] = 29;
K[0x45] = 39;
K[0x16] = 30;
K[0x1E] = 31;
K[0x26] = 32;
K[0x25] = 33;
K[0x2E] = 34;
K[0x36] = 35;
K[0x3D] = 36;
K[0x3E] = 37;
K[0x46] = 38;
K[0x0E] = 53;
K[0x4E] = 45;
K[0x55] = 46;
K[0x5D] = 49;
K[0x66] = 42;
K[0x29] = 44;
K[0x0D] = 43;
K[0x58] = 227; //CAPS 57
K[0x12] = 225;
K[0x14] = 224;
K[0x11] = 226;
K[0x59] = 229;
K[0x5A] = 40;
K[0x76] = 41;
K[0x05] = 58;
K[0x06] = 59;
K[0x04] = 60;
K[0x0C] = 61;
K[0x03] = 62;
K[0x0B] = 63;
K[0x83] = 64;
K[0x0A] = 65;
K[0x01] = 66;
K[0x09] = 67;
K[0x78] = 68;
K[0x07] = 69;
K[0x7E] = 57;//Scroll 71
K[0x54] = 47;
K[0x77] = 83;
K[0x7C] = 85;
K[0x7B] = 86;
K[0x79] = 87;
K[0x71] = 99;
K[0x70] = 98;
K[0x69] = 89;
K[0x72] = 90;
K[0x7A] = 91;
K[0x6B] = 92;
K[0x73] = 93;
K[0x74] = 94;
K[0x6C] = 95;
K[0x75] = 96;
K[0x7D] = 97;
K[0x5B] = 48;
K[0x4C] = 51;
K[0x52] = 52;
K[0x41] = 54;
K[0x49] = 55;
K[0x4A] = 56;
K[0x61] = 100;
KE[0x1F] = 227;
KE[0x14] = 228;
KE[0x27] = 231;
KE[0x11] = 230;
KE[0x2F] = 101;
KE[0x7c] = 70;
KE[0x70] = 73;
KE[0x6C] = 74;
KE[0x7D] = 75;
KE[0x71] = 76;
KE[0x69] = 77;
KE[0x7A] = 78;
KE[0x75] = 82;
KE[0x6B] = 80;
KE[0x72] = 81;
KE[0x74] = 79;
KE[0x4A] = 84;
KE[0x5A] = 88;
}
void ps2interrupt(void) {
static uint8_t bitcount=0;
static uint8_t incoming=0;
static uint32_t prev_ms=0;
uint32_t now_ms;
uint8_t n, val;
if (!sendBits){
val = digitalRead(DATA_PIN);
now_ms = millis();
if (now_ms - prev_ms > 250) {
bitcount = 0;
incoming = 0;
}
prev_ms = now_ms;
n = bitcount - 1;
if (n <= 7) {
incoming |= (val << n);
}
bitcount++;
if (bitcount == 11) {
uint8_t i = head + 1;
if (i >= BUFFER_SIZE) i = 0;
if (i != tail) {
buffer[i] = incoming;
head = i;
}
bitcount = 0;
incoming = 0;
}
} else {
--sendBits;
uint8_t b = bitCount - 1;
if (b == 8){
digitalWrite(DATA_PIN, !(setBits & 1));
} else if (b == 9) {
pinMode(DATA_PIN, INPUT_PULLUP);
} else if (b < 8) {
bool bt = (msg >> b) & 1;
digitalWrite(DATA_PIN, bt);
setBits += bt;
}
++bitCount;
}
}
static inline uint8_t get_scan_code(void) {
uint8_t c, i;
i = tail;
if (i == head) return 0;
i++;
if (i >= BUFFER_SIZE) i = 0;
c = buffer[i];
tail = i;
return c;
}
void setup_ps2(){
attachInterrupt(0, ps2interrupt, FALLING);
pinMode(IRQ_PIN, INPUT_PULLUP);
pinMode(DATA_PIN, INPUT_PULLUP);
head = 0;
tail = 0;
sendBits = 0;
}
void setup() {
setup_keymaps();
setup_ps2();
Keyboard.begin();
delay(1000);
}
bool ext, brk;
int skip;
void report_add(uint8_t k) {
uint8_t i;
if (k >= 224) {
report.modifiers |= 1 << (k - 224);
} else if (report.keys[0] != k && report.keys[1] != k &&
report.keys[2] != k && report.keys[3] != k &&
report.keys[4] != k && report.keys[5] != k) {
for (i = 0; i < 6; ++i) {
if (report.keys[i] == 0) {
report.keys[i] = k;
break;
}
}
}
}
void report_remove(uint8_t k) {
uint8_t i;
if (k >= 224) {
report.modifiers &= ~(1 << (k - 224));
} else {
for (i = 0; i < 6; ++i) {
if (report.keys[i] == k) {
report.keys[i] = 0;
break;
}
}
}
}
void send_msg(uint8_t m) {
noInterrupts();
pinMode(IRQ_PIN, OUTPUT);
digitalWrite(IRQ_PIN, LOW);
delayMicroseconds(60);
pinMode(IRQ_PIN, INPUT_PULLUP);
msg = m;
bitCount = 0;
sendBits = 12;
setBits = 0;
pinMode(DATA_PIN, OUTPUT);
digitalWrite(DATA_PIN, LOW);
interrupts();
}
void loop() {
uint8_t k = get_scan_code(), k2;
if (k) {
if (skip) {
--skip;
} else {
if (k == 0xE0) {
ext = true;
} else if (k == 0xF0) {
brk = true;
} else if (k == 0xFA) {
if (send_leds) {
send_leds = false;
send_msg(leds);
}
} else {
if (k == 0xE1) {
k2 = 72;
skip = 7;
brk = true;
report_add(k2);
Keyboard.sendReport(&report);
} else {
k2 = ext ? KE[k] : K[k];
}
if (k2){
if (brk){
report_remove(k2);
if (k2 == 83 || k2 == 71 || k2 == 57){
send_leds = true;
if (k2 == 83) {
leds ^= 2;
} else if (k2 == 71) {
leds ^= 1;
} else if (k2 == 57) {
leds ^= 4;
}
send_msg(0xED);
}
} else {
report_add(k2);
}
Keyboard.sendReport(&report);
}
brk = false;
ext = false;
}
}
}
}
@Slumber86
Copy link

sendReport has been moved to /libraries/Keyboard/src/Keybard.h

@Bercut39
Copy link

"KeyReport" does not a name of type...

@Bercut39
Copy link

Bercut39 commented Apr 27, 2018

my logitech F510 gamepad does not work with USB host shield 2.0. Work all my mice only :-). Could somebody help me?

@hmeijdam
Copy link

in the file "/libraries/Keyboard/src/Keybard.h" I moved the line
void sendReport(KeyReport* keys);
three rows down so it no more sits in the "private:" section but in the "public:" section

Then I added:
#include <Keyboard.h>
to the top of the main program code

it compiled without error (Arduino 1.6.9 used) for a Leonardo

Copy link

ghost commented Jan 31, 2020

Don't run this program on BIOS PC

@kolsys
Copy link

kolsys commented Apr 19, 2020

Hi. I was make some refactoring and fix interruption for Arduino Pro Mini. It's currently not requiring to patch the Keyboard.h.
https://github.com/kolsys/PS2USBConverter/tree/master

@therealprocyon
Copy link

Don't run this program on BIOS PC

Why not?

@ThePolarCat
Copy link

Hi, I get a lot of errors when compiling:

Arduino: 1.8.19 (Mac OS X), Board: "Arduino Uno"

In file included from /Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:3:0:
/Users/Mustafa/Documents/Arduino/libraries/Keyboard/src/Keyboard.h:29:2: warning: #warning "Using legacy HID core (non pluggable)" [-Wcpp]
#warning "Using legacy HID core (non pluggable)"
^~~~~~~
PS2toUSB:14:1: error: 'KeyReport' does not name a type
KeyReport report;
^~~~~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino: In function 'void setup()':
PS2toUSB:198:3: error: 'Keyboard' not found. Does your sketch include the line '#include <Keyboard.h>'?
Keyboard.begin();
^~~~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino: In function 'void report_add(uint8_t)':
PS2toUSB:208:5: error: 'report' was not declared in this scope
report.modifiers |= 1 << (k - 224);
^~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:208:5: note: suggested alternative: 'remove'
report.modifiers |= 1 << (k - 224);
^~~~~~
remove
PS2toUSB:209:14: error: 'report' was not declared in this scope
} else if (report.keys[0] != k && report.keys[1] != k &&
^~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:209:14: note: suggested alternative: 'remove'
} else if (report.keys[0] != k && report.keys[1] != k &&
^~~~~~
remove
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino: In function 'void report_remove(uint8_t)':
PS2toUSB:224:5: error: 'report' was not declared in this scope
report.modifiers &= ~(1 << (k - 224));
^~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:224:5: note: suggested alternative: 'remove'
report.modifiers &= ~(1 << (k - 224));
^~~~~~
remove
PS2toUSB:227:11: error: 'report' was not declared in this scope
if (report.keys[i] == k) {
^~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:227:11: note: suggested alternative: 'remove'
if (report.keys[i] == k) {
^~~~~~
remove
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino: In function 'void loop()':
PS2toUSB:271:11: error: 'Keyboard' not found. Does your sketch include the line '#include <Keyboard.h>'?
Keyboard.sendReport(&report);
^~~~~~~~
PS2toUSB:271:32: error: 'report' was not declared in this scope
Keyboard.sendReport(&report);
^~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:271:32: note: suggested alternative: 'remove'
Keyboard.sendReport(&report);
^~~~~~
remove
PS2toUSB:293:11: error: 'Keyboard' not found. Does your sketch include the line '#include <Keyboard.h>'?
Keyboard.sendReport(&report);
^~~~~~~~
PS2toUSB:293:32: error: 'report' was not declared in this scope
Keyboard.sendReport(&report);
^~~~~~
/Users/Mustafa/Documents/Arduino/Arduino PS2 to USB HID Keyboard Converter/PS2toUSB/PS2toUSB.ino:293:32: note: suggested alternative: 'remove'
Keyboard.sendReport(&report);
^~~~~~
remove
Multiple libraries were found for "Keyboard.h"
Used: /Users/Mustafa/Documents/Arduino/libraries/Keyboard
Not used: /Applications/Arduino.app/Contents/Java/libraries/Keyboard
exit status 1
'KeyReport' does not name a type

Can please suggest any modification to get this working? you already put too much work into this probably its worth another look at it.
Thanks.

@ThePolarCat
Copy link

It turned out that the Keyboard library doesn't work on UNO.
I'm looking into updating the firmware with this.
https://mitchtech.net/arduino-usb-hid-keyboard/

@DorianRudolph
Copy link
Author

Hey, I wrote this a very long time ago and am not really interested in maintaining this anymore. But some others have posted fixes in this thread. And you are right, the Uno cannot act as HID out of the box. I used this with the Leonardo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment