[SOLVED] Arduino as PS/2 keyboard - issue (5-10% of commands are dropped)

Dear Arduino community,

I am making an Arduino-based device, which shall be recognized as a pure PS/2 keyboard and shall forward Sega Genesis and Super Nintendo gamepad input as PS/2 key presses for my MS-DOS Laptop (kinda like this thing, only with some of my quality-of-life improvements).

My project is based on a Pro Micro (ATmega32U4) but the problem persists with Arduino Nano (ATmega328).

I’ve written a long and windy code to read Sega 6-button input and send corresponding keys to PS/2 port…

It works, but it somehow fails to deliver 2-5% of PS/2 commands (i.e. either command to “press” key or command to “release”). My gamepad library is 100% reliable (as I tested it by forwarding to HID ATmega32U4-native USB-keyboard output leading to many hours lost to pla… ahem …testing Genesis emulator).

To verify the issue, I’ve designed this simple thing:

Color code is:
Black - DATA
Yellow - CLOCK
Orange - +5V (from PS/2 port)
Red - GND

And fed it the following short code to forward keypresses from REAL keyboard to HOST pc:

#define KB_YELLOW 6
#define KB_BLACK 5
#define HOST_YELLOW 8
#define HOST_BLACK 7

bool YELLOW = 0;
bool BLACK = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(KB_YELLOW, INPUT);
  pinMode(KB_BLACK, INPUT);
  pinMode(HOST_YELLOW, OUTPUT);
  pinMode(HOST_BLACK, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  if (digitalRead(KB_YELLOW) != YELLOW) {
    YELLOW = !YELLOW;
    digitalWrite(HOST_YELLOW, YELLOW);
  }
  if (digitalRead(KB_BLACK) != BLACK) {
    BLACK = !BLACK;
    digitalWrite(HOST_BLACK, BLACK);
  }
}

My test is as follows:

  • I let the pc boot up with REAL keyboard connected directly (so BIOS properly initializes presence of the keyboard).
  • Once loaded – I disconnect REAL keyboard and connect my Arduino device as intermediary.
  • I press keys on the keyboard.

The thing still drops up to 5-10% of commands! The problem seems to persist with longer (2-3 byte keycodes) than shorter ones (i.e. Num-pad arrows are more reliable than “ordinary” arrows, pressing a key is more reliable than release it, etc.). The wire itself seems fine, as I tested it as a direct (passive) extension to the keyboard and it worked.

The questions are:

  • What is the issue? It is the same for Pro Micro (ATmega32U4) and Arduino Nano (ATmega328).
  • Is it the problem with connection? Is it my mistake to connect the leads directly to Arduino?
  • The problem with power? (I tried retaining USB power to the device. Seems to have done nothing) Do I have to filter it somehow? How?
  • Can you advice on emulating a PS/2 keyboard?

Thanks in advance!

I guess the problem is that you only implement the direction keyboard -> PC. But the PC is allowed to pull the clock line low at any time and the peripheral device (keyboard) must stop the transmission until the clock line is released (for I2C the same feature is called clock stretching).

BTW, both signals should be pulled high passively (resistors) and only actively pulled low. If you connect it as described above you may produce a short circuit for a short time. I don't know if your PC (host) likes that.

Well, the above case is the crudest simplification of my issue. :slight_smile: My device code is 500 lines long and is pretty esoteric. I am embarrassed to paste it all.

I tried using Arduino's inbuilt pull-up resistors (INPUT_PULLUP pin mode) as well as using hardware resistors to pull up the signal to VCC. Seems to make little difference.

Also I sniffed PS/2 client-host communication on several computers it kinda looks as follows:

Most HOSTs communicate with the keyboards only during startup and then just stop bothering. If no AKN reply received during startup - PC just stops listening to the PS/2 port. If initialization is OK - PC deems keyboard connected until rebooted.

The only HOST-outbound communication during normal work is the request to toggle caps/scroll/num-lock LEDs, never more.

Only exception I've encountered is with IBM Thinkpads: they spam the keyboard with a constant barrage of 0xF2 requests ("What are you? What are you?!! I WANNA KNOW WHAT ARE YOU!!!"). Must be IBM's implementation of hot-plug feature. Other PCs just act as passive listeners, so HOST-input may be ignored until Caps lock is pressed.

I assume my issue to be with timing, signal noise or lack of power. What might it be?

I tried using Arduino's inbuilt pull-up resistors (INPUT_PULLUP pin mode) as well as using hardware resistors to pull up the signal to VCC. Seems to make little difference.

In the information we received you did neither of it.

The only HOST-outbound communication during normal work is the request to toggle caps/scroll/num-lock LEDs, never more.

How do you check that? How do you check if the PC is pulling the clock line low (and not the keyboard side)?

Only exception I've encountered is with IBM Thinkpads: they spam the keyboard with a constant barrage of 0xF2 requests ("What are you? What are you?!! I WANNA KNOW WHAT ARE YOU!!!"). Must be IBM's implementation of hot-plug feature. Other PCs just act as passive listeners, so HOST-input may be ignored until Caps lock is pressed.

You do know that IBM developed PS/2, don't you? If IBM is doing that it might be the way to actually do it.

Please, don't argue for the sake of arguing. I plead for help, not justice.

I'm not a noob any more. I am proud to admit having several year experience of tinkering, therefore I can safely state successfully reaching the qualification of a fully-fledged... lamer.

I have cobbled up the device and coded 500 lines of an almost complete implementation of PS/2 keyboard into it (only typematic thing is left to implement). The weird thing that it initializes on most computers and works 95-97% of the time. Have no idea why not 100%. It would be easier if it did not work at all! During gameplay, once every 20-30 key-presses the thing just fails to press or (what's worse!) to release a key, which sends poor Jazz Jackrabbit or Commander Keen to their inevitable demise. Irritating as heck!

This project lasts about half of a year. Once I have an idea - I try it... to fail miserably. I tired any trick in the box that I know of. But, as mentioned before, me being not a noob, but a lamer, I might have missed something too obvious to notice. Therefore I have made the simplest code possible, borrowing timing and commands of a real working keyboard to illustrate the issue and ask for help as my last resort.

I may paste all of 500 lines if you want to. My spaghetti-code does not seem to work better than the dozen of lines pasted afore.

Please HELP! :astonished: :stuck_out_tongue_closed_eyes: :astonished:

Please find by short replies to you comments below:

I tried using Arduino's inbuilt pull-up resistors (INPUT_PULLUP pin mode) as well as using hardware resistors to pull up the signal to VCC. Seems to make little difference.

In the information we received you did neither of it.

I am oversimplifying the code as much as possible. I wish the code to have the least influence on the operation. Switching between INPUT_PULLUP and OUTPUT + LOW shall have more influence on the timing than simple switching between OUTPUT HIGH and LOW. Not that it makes noticable diference, sadly.

The only HOST-outbound communication during normal work is the request to toggle caps/scroll/num-lock LEDs, never more.

How do you check that? How do you check if the PC is pulling the clock line low (and not the keyboard side)?

Well, I still have the Arduino with it's serial terminal for debugging. I may easily intercept and decode the communication between HOST and DEVICE. Strangely enough, I've never seen 0xFE (communication error, repeat last message) request form the HOST. As if the missing key-press disappears completely (or most BIOS coders are too lazy to implement parity check).

Only exception I've encountered is with IBM Thinkpads: they spam the keyboard with a constant barrage of 0xF2 requests ("What are you? What are you?!! I WANNA KNOW WHAT ARE YOU!!!"). Must be IBM's implementation of hot-plug feature. Other PCs just act as passive listeners, so HOST-input may be ignored until Caps lock is pressed.

You do know that IBM developed PS/2, don't you? If IBM is doing that it might be the way to actually do it.

PS/2 protocol is not hot-pluggable according to documentation. Interesting that IBM Thinkpads are the only computers I failed to fool into thinking that my 500-line coded Arduino is a real keyboard. They work with real keyboard, though.

I am thinking into rewiring the whole thing with beefier wires. Maybe that is the problem?

To clarify that the issue is not with the power, I rewired the PS/2 connector using 0.75mm2 mains power cables (even had to narrow those down to shove them into Arduino's standard pin-hole). Also I ditched Genesis pad for passive Kempston (Atari) joystick:

Still no success. Thing just drops commands from time to time. Must be some kind of Arduino quirk. I regret having no oscilloscope to visualize the signals.

If you had encountered such project, please offer advice!

P.S. I am almost desperate enough to start studying PIC chips :frowning: People succeed in making PS/2 keyboards from those.

P.S. I am almost desperate enough to start studying PIC chips :frowning: People succeed in making PS/2 keyboards from those.

Feel free to do so (BTW it's now the same company manufacturing the ATmegas and the PICs), I doubt that changing the chip will help you in any way.

Well, I still have the Arduino with it's serial terminal for debugging. I may easily intercept and decode the communication between HOST and DEVICE.

Sure you do, but I'm talking about the connection HOST -> Arduino. In that connection you don't check if the HOST is pulling the clock line low. Am I wrong?

Dear pylon,

Thank you for your sympathy to this project. Since I now have simplified the hardware (ditched weird bank-switching Sega gamepad for a passive Atari joystick and removed PS/2-pathrough as seen on my photo above), I could streamline the code. I also substituted my spaghettified creativity for Arduino ps2dev.h (By Harvie from the Library Manager). Now I am not as embarrassed to paste my code online:

#include <Keyboard.h> // Emulate USB keyboard
#include <ps2dev.h> // Emulate a PS/2 device
PS2dev keyboard(A3, A2); // Clock, Data

// Define ATARI gamepad pins
#define JOY_UP 9
#define JOY_DOWN 7
#define JOY_LEFT 5
#define JOY_RIGHT 4
#define JOY_FIRE 8

// 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 Arduino forum limitations...
  {{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 Arduino forum limitations...
  0xED, // 101 - APPS
};

unsigned long joystick_timer = 0; // Needed to track delay after ATARI checked its buttons

byte joystick_pin[] = {JOY_UP, JOY_DOWN, JOY_LEFT, JOY_RIGHT, JOY_FIRE};
boolean button_matrix[5]; // Array shows which buttons are pressed
boolean prev_button_matrix[5]; // Cache Array to pick up updates
byte key_press[] = {83, 82, 81, 80, 44}; // List buttons to press - Up, Down, Left, Right, Enter
// Buttons UP, DOWN, LEFT, RIGHT, FIRE.

void joystickRead() {
  // Read UP, DOWN, LEFT, RIGHT, FIRE
  for (byte i = 0; i < 5; i++) {
    button_matrix[i] = !digitalRead(joystick_pin[i]); // LOW means button pressed
  }
}

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 annoying!

  Keyboard.begin(); // starts Arduino Micro USB keyboard service
  USBCON |= (1 << OTGPADE); //enables VBUS pad to check if USB is connected

  // Set all ATARI pins to INPUT
  for (byte i = 0; i < 5; i++) {
    pinMode(joystick_pin[i], INPUT_PULLUP);
  }
}

void loop() {
  unsigned long test_time = micros();
  boolean test_change = 0;

  unsigned char leds; // next ps2dev command needs it. Dunno why
  keyboard.keyboard_handle(&leds); // ps2dev command. Allows to read HOST inputs and answer those

  memcpy(prev_button_matrix, button_matrix, sizeof button_matrix); // Buffer the state of joystick buttons for further comparison (i.e. check if changed)

  if (millis() >= joystick_timer) { // check button states every 8 ms. (twice per frame)
    joystick_timer = millis() + 8;
    joystickRead(); // read buttonpresses
  }

  for (byte i = 0; i < 5; 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]]); // If USB is connected - act as USB keyboard
      else
        PS2_press_key(key_press[i]); // If USB is not connected - act as PS/2 keyboard
    }

    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]]); // If USB is connected - act as USB keyboard
      else
        PS2_release_key(key_press[i]); // If USB is not connected - act as PS/2 keyboard
    }
  }
}

Please pardon for listing all keys I could find on my keyboard (All except “pause/break” key… Nobody likes “pause/break” key!). It is the remnant of my initial high hopes for the project.
The rest should be quite compact and easy to follow.

I am not quite happy with ps2dev library – I’d say 2000 microsecond delay between bytes is more reliable than library’s 1000. Lack of parity check and separate key press and key-release routines are inconvenient... My spaghetti should have offered a bit more flexibility.

Still, I cannot complete a single 30-second DOS car-racing lap without steering getting stuck (RELEASE code failure) and smashing me into a nearby wall.

I tried every trick in the book including binary PORT manipulation for CLOCK and DATA! Reliability-wise it just doesn’t care! I’m tired. l‘ll move this thread to Device Hacking section of the forum. I hope I’ll get some help there. It would be sad to give up this far.

Solved the issue accidentally! The problem is that both ps2dev.h and available documentation lack in-deep PS/2 implementation.

A am making this message in case someone else encounters same issue.

For reliable PS/2 communication you have to add the following line before sending PS/2 commands trough ps2dev.h:

while (digitalRead(YOUR_CLOCK_PIN_NUMBER) == 0) {}

More details available in this thread: [SOLVED] Arduino as PS/2 keyboard - Reliability (~5% of commands are dropped). - #3 by Mr_Volo - Device Hacking - Arduino Forum

For reliable PS/2 communication you have to add the following line before sending PS/2 commands trough ps2dev.h:

Exactly what I wrote in answers #3 and #6.