Dear Sirs,
I have started a thread in Project Guidance section ([SOLVED] Arduino as PS/2 keyboard - issue (5-10% of commands are dropped) - Project Guidance - Arduino Forum), some time ago. May I try this question here?
I’ll try to be brief. I am trying to consolize an MS-DOS laptop by emulating PS/2 keyboard, i.e I'm trying to make this a thing:
To achieve it I’ve soldered this thing (based on Pro Micro, Atmega32u4) to act as a PS/2 keyboard:
I’ve made a code based on ps2dev library from here https://github.com/Harvie/ps2dev:
#include <Keyboard.h> // Emulate USB keyboard
#include <ps2dev.h> // Emulate a PS/2 device
PS2dev keyboard(A3, A2); // Clock, Data
// Define SEGA gamepad pins
#define UP_OR_Z 9
#define DOWN_OR_Y 7
#define LEFT_OR_X 5
#define RIGHT_OR_MODE 4
#define B_OR_A 8
#define SEL 6
#define C_OR_START 3
// Buttons database follows:
byte PS2_key_code[102][2][3] = { // Array [Button No.][MAKE or BREAK][byte No.]
{{0x14}, {0xF0, 0x14}}, // 0 - L CTRL
{{0x12}, {0xF0, 0x12}}, // 1 - L SHFT
// ...other 98 keys are omitted due to 9000 characters forum limitation...
{{0xE0, 0x2F}, {0xE0, 0xF0, 0x2F}}, // 101 - APPS
};
byte USB_key_code[102] = { // Array [Button No.]
0x80, // 0 - L CTRL
0x81, // 1 - L SHFT
// ...other 98 keys are omitted due to 9000 characters forum limitation...
0xED, // 101 - APPS
};
unsigned long sega_timer = 0; // Needed to track delay after SEGA checked its buttons
byte sega_pin[] = {UP_OR_Z, DOWN_OR_Y, LEFT_OR_X, RIGHT_OR_MODE, B_OR_A, C_OR_START};
// Buttons UP, DOWN, LEFT, RIGHT, B, C, A, START, Z, Y, X, MODE
boolean button_matrix[12]; // Array shows which buttons are pressed
boolean prev_button_matrix[12]; // Cache Array to pick up updates
byte key_press[] = {83, 82, 81, 80, 2, 0, 48, 45, 31, 32, 33, 44}; // List buttons to press
void segaRead() {
digitalWrite(SEL, LOW); // Set SEL to LOW to read additional buttons
delayMicroseconds(20);
digitalWrite(SEL, HIGH);
delayMicroseconds(20);
digitalWrite(SEL, LOW);
delayMicroseconds(20);
// Read START and A
for (byte i = 0; i < 2; i++) {
button_matrix[i + 6] = !digitalRead(sega_pin[i + 4]);
}
digitalWrite(SEL, HIGH); // Set SEL to HIGH to read primary buttons
delayMicroseconds(20);
// Read UP, DOWN, LEFT, RIGHT, B, C
for (byte i = 0; i < 6; i++) {
button_matrix[i] = !digitalRead(sega_pin[i]);
}
digitalWrite(SEL, LOW); // Set SEL to LOW to read additional buttons
delayMicroseconds(20);
digitalWrite(SEL, HIGH); // Set SEL to LOW to read additional buttons
delayMicroseconds(20);
// Read Z, Y, X, MODE
for (byte i = 0; i < 4; i++) {
button_matrix[i + 8] = !digitalRead(sega_pin[i]);
}
digitalWrite(SEL, LOW); // Set SEL to LOW to read additional buttons
delayMicroseconds(20);
digitalWrite(SEL, HIGH); // Set SEL to LOW to read additional buttons
// delayMicroseconds(20);
}
void PS2_press_key(byte key_number) {
for (byte i = 0; i < 3; i++) {
if (PS2_key_code[key_number][0][i] != 0x00) { // If keycode is not BLANK - Send key byte
keyboard.write(PS2_key_code[key_number][0][i]); // send byte though ps2dev library
}
}
}
void PS2_release_key(byte key_number) {
for (byte i = 0; i < 3 ; i++) {
if (PS2_key_code[key_number][1][i] != 0x00) { // If keycode is not BLANK - Send key byte
keyboard.write(PS2_key_code[key_number][1][i]); // send byte though ps2dev library
}
}
}
void setup() {
// Serial.begin(19200); // ps2dev SPAMs debugging data though here. Very annying! Makes controller lag
Keyboard.begin(); // starts 32u4 USB keyboard service
USBCON |= (1 << OTGPADE); //enables VBUS pad to check if USB is connected
//Prepare SEGA SEL pin
pinMode(SEL, OUTPUT);
digitalWrite(SEL, HIGH);
// Set all SEGA pins to INPUT
for (byte i = 0; i < 6; i++) {
pinMode(sega_pin[i], INPUT_PULLUP);
}
}
void loop() {
unsigned char leds; // next lines need it dunno why
keyboard.keyboard_handle(&leds); // ps2dev command. Allows to read HOST inputs and answer those
if (millis() >= sega_timer) {
sega_timer = millis() + 8;
segaRead(); // read buttonpresses
for (byte i = 0; i < 12; i++) {
if (button_matrix[i] == 1 && prev_button_matrix[i] == 0) { // Gamepad button pressed
if (USBSTA & (1 << VBUS)) Keyboard.press(USB_key_code[key_press[i]]);
else PS2_press_key(key_press[i]);
}
if (button_matrix[i] == 0 && prev_button_matrix[i] == 1) { // Gamepad button relesed
if (USBSTA & (1 << VBUS))Keyboard.release(USB_key_code[key_press[i]]);
else PS2_release_key(key_press[i]);
}
}
memcpy(prev_button_matrix, button_matrix, sizeof button_matrix); // Buffer the state of SEGA butons for further comparison (i.e. check if changed)
}
}
The problem is that the resulting device just not reliable! Most of my PCs fail to accept 100% of keyboard bytes (either key-PRESS or RELEASE). I don’t know why:
• 2 inbred Compaq LTE 5000 laptops (like the one in the photo): sometimes commands (especially longer, 2-3 byte long ones) are not transferred successfully. I can’t complete a single 30-second racing lap without steering getting stuck.
• 2 identical IBM Thinkpads 560. Thing fails to initialize during bootup. The quirk of those laptops is that they are trying to initialize PS/2 devices by constantly spamming 0xF2 requests. After about average 2 minutes since booting – Arduino-as-PS/2-keyboard initializes and works as bad as with those Compaq LTE 5000 laptops above.
• 2 Desktop computers: one hot late-90s PC (Win 98, Pentium 800, VIA Apollo Pro 133 Chipset, Voodoo3 video) and 2011-2012 Win10 piece of crap. Keyboard initializes and works flawlessly 100% on both desktops. Though kinda pointless as these two PCs have USB ports.
Can you advice why is that?
Please advise why PS/2 commands most of the times succeed to be accepted but sometimes not?
What may be the reason of such non-100% reliability? Power? Static? My lame programming?
Feel free to glance at my original message of earlier iteration of the project here.
Please spare an advice, if you have any.
UPD. Fixed a small logical bug in the code above. Does not make it better with the old laptops.