Electronic – Arduino PS/2 Keyboard Emulator Issues

arduinokeyboard

Yes, I've searched the Arduino.cc forums and here.  Yes, I've found the articles regarding the ps2dev library.  Yes, I've read (okay, some I skimmed) the definitive PS/2 interface article at this website. Yes, I have this working, kinda.  I need some ideas for making the leap to fully working.  🙂

No, I can't just emulate a USB HID Keyboard and leave it at that–it needs to be PS/2 Keyboard emulation.  Yes, I am sending proper make and break signals–it even handles very complicated keystroke combinations.  As it stands right now, I have code written for my Arduino as posted below (technically a Freeduino 1.22), and I've sent keystrokes via the Serial Monitor or PuTTY terminal, as well as with a handy Python wrapper/driver which sends actual PS/2 scancode information–and generally makes my life much easier–also taking some of the load off the Arduino.

Right now, I have a sketch running on the Arduino which emulates a PS/2 Keyboard.  Naturally, I have to boot my "target" machine (machine that PS/2 Plug goes into), and I see the "handshake" take place.  Boot to WinDoze, open notepad, and drive keystrokes to the screen (succesfully) using my Python "driver".  (The driver simply takes place of the Serial Monitor/PuTTY terminal and reads/writes to the serial port using a module called PySerial.)  This is all done on a AMD in ASUS motherboard "target".

Now, the goal is to get it working on my Intel in Intel motherboard based "target", I plug it in, boot, and no dice.  So, I modified the sketch a bit to try and give myself a heads-up of what's actually going on on my little Ardy friend.  The version after the mods is displayed below.  As I understand it (code was "borrowed" from another Arduino.cc forum post, here) It will try and establish a connection with the "target" over PS/2 first, blinking the onboard LED at a .5 second period until the connection is established.  The Intel target doesn't get past the .5 second period blinks and the Serial Connection is never established with the "host".

My question is this: is there a major difference in the way ps/2 keyboards establish communication with their target machine?  Is it really a design difference or should I be looking for something more basic that's the issue here?  I've heard something about needing pull-up resistors on the data/clock inputs, but that should be handled in the code, especially because it's WORKING on another target, just not the one I need it to work on.

Any ideas?  I'd love to get this working ASAP–I'm going to keep doing debug, any pointers or suggestions would be greatly appreciated.  They will all be given full consideration because I need some fresh eyes on this issue.  Perhaps better implementation in the ps2dev library is needed?

#include "ps2dev.h" // to emulate a PS/2 device

// Orange = 2
// Blue = 3
// Red = 5V (3 in)
// Black = GND (4 in)
// EXT Power, USB for COM only

PS2dev keyboard(3,2); // PS2dev object (2:data, 3:clock)
int enabled = 0; // pseudo variable for state of "keyboard"
boolean serialConnected = false;
int incomingByte = 0;

void ack() {
  //acknowledge commands
  while(keyboard.write(0xFA));
}

int kbdCmd(int command) {
  unsigned char val;
  switch (command) {
  case 0xFF: //reset
    ack();
    //the while loop lets us wait for the host to be ready
    while(keyboard.write(0xAA)!=0);
    break;
  case 0xFE: //resend
    ack();
    break;
  case 0xF6: //set defaults
    //enter stream mode
    ack();
    break;
  case 0xF5: //disable data reporting
    //FM
    enabled = 0;
    ack();
    break;
  case 0xF4: //enable data reporting
    //FM
    enabled = 1;
    ack();
    break;
  case 0xF3: //set typematic rate
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  case 0xF2: //get device id
    ack();
    keyboard.write(0xAB);
    keyboard.write(0x83);
    break;
  case 0xF0: //set scan code set
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  case 0xEE: //echo
    //ack();
    keyboard.write(0xEE);
    break;
  case 0xED: //set/reset LEDs
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  }
}

void connectHost() {
  while (Serial.available() <= 0) {
    Serial.print('A');   // send a capital A
    delay(300);
  }
}

void setup() {
  pinMode(13, OUTPUT);
  //establish serial connection with host
  Serial.begin(9600);
  // establish ps/2 connection with target
  while(keyboard.write(0xAA)!=0){
    digitalWrite(13, HIGH);
    delay(500); 
    digitalWrite(13, LOW);
    delay(500);
  }
  delay(100);  
  
  connectHost();
  Serial.println("\nSerial Host Connected");
  Serial.flush();
}

void loop() {
  unsigned char c;
  if( (digitalRead(3)==LOW) || (digitalRead(2) == LOW)) {
    if(digitalRead(3)==LOW){
      Serial.println("pin 3  is LOW");
    } else {
      Serial.println("pin 2 is LOW");
    }
    while(keyboard.read(&c));
    kbdCmd(c);
    Serial.print("Target: 0x");
    Serial.println(c, HEX);
  }  
  else {//if host device wants to send a command:
    //echo ASCII code from terminal and write to ps/2
    if(Serial.available() > 0) {
      incomingByte = Serial.read();
      keyboard.write(incomingByte);      
      Serial.print("Host: 0x");
      Serial.print(incomingByte, HEX);
      Serial.print(" ");
      Serial.print(incomingByte);
      Serial.print(" ");
      Serial.println(incomingByte, BIN);
    }
  }
}

Best Answer

As I understand, you connect your Arduino to two different target machines and on one it works and on the other it doesn't.

So it seems there is a difference between the initialization requirements of the two machines. On this page at the very bottom there is a listing of a possible initialization sequence. Start by comparing your initialization to that one.

It will be a lot easier by using a logic analyzer. I am using the Intronix Logicport, but there are both cheaper and better ones, though not at the same time.

Tapping into an open-collector bus is a bit cumbersome because you don't see which device is talking. However, if you put in a series resistor at the end where the pullup is not, you can tell by the voltage level which device is holding down the bus. Every open-collector bus (like PS/2) needs pullup resistors, usually they are built in in the PC. You can see the different voltage levels easily on a DSO. With only a LA you have to record twice with different threshold voltages.