Code and Life

Programming, electronics and other cool tech stuff

Supported by

Supported by Picotech

Arduino PS/2 Keyboard Tester

Arduino PS/2 tester

Once I got my minimal AVR PS/2 keyboard device built, it quickly became apparent that such a device should be able to respond to rudimentary PS/2 commands if I would like to avoid irritating errors in BIOS and O/S side.

After spending a couple of educating evenings with my PicoScope (the only device I had at hand that could capture several seconds of PS/2 traffic at 100 kHz or more to make sure I detect each individual level change) and trying to understand bit-level PS/2 signals (I’ll maybe do a short post on that effort later), I decided it would be too complicated for debugging my own wanna-be PS/2 compliant device. So I decided to implement a simple PS/2 tester sketch with Arduino.

Basic Arduino Setup

There is already a great Arduino/Teensy library called PS2keyboard that had done most of the thinking work for me – the core of the library is an interrupt routine that is called automatically when the Arduino detects falling edge (logic level going from HIGH to LOW) on the clock pin. In Arduino Uno, pin 3 is attached to INT1, and setting up the interrupt is very simple:


#define CLOCK_PIN_INT 1 // Pin 3 attached to INT1 in Uno
// ...
attachInterrupt(CLOCK_PIN_INT, ps2int_read, FALLING);

Reading data sent by a PS/2 is then just a simple matter of checking the data line state whenever clock goes low. PS2keyboard had this routine already written, so I only needed to implement a similar callback for writing data. This proved to be quite straightforward, as the host (PC) also toggles the data line when clock is low, so when I want to write something, I just initialize a few variables and call:


attachInterrupt(CLOCK_PIN_INT, ps2int_write, FALLING);

The interrupt routine itself looks like this – basically it relies on curbit and parity being initialized to zero when starting write, and writeByte set to the value to send to the PS/2 device. Not that the writeByte is “consumed” by this routine as the interrupt handler shifts it right one bit by one.


static volatile uint8_t writeByte;
static volatile uint8_t curbit = 0, parity = 0, ack;

void ps2int_write() {
  if(curbit < 8) {
    if(writeByte & 1) {
      parity ^= 1;
      digitalWrite(DataPin, HIGH);
    } else
      digitalWrite(DataPin, LOW);

    writeByte >>= 1;
  } else if(curbit == 8) { // parity
    if(parity)
      digitalWrite(DataPin, LOW);
    else
      digitalWrite(DataPin, HIGH);
  } else if(curbit == 9) { // time to let go
    releaseData();
  } else { // time to check device ACK and hold clock again
    holdClock();
    ack = !digitalRead(DataPin);
  }

  curbit++;
}

PS/2 Tester in Action

Sample PS/2 tester session

On the left you can see my Logitech keyboard responding to commands sent by the PS2tester sketch. Commands to be sent to keyboard are sent to Arduino over serial line in hex pairs (“FF”, “ED”, “02”, etc.), which should then be acknowledged by the PS/2 device during write with ACK, and after the write by sending back “acknowledge byte” FA. Data sent to and received from PS/2 device is echoed to serial line so we can see what is going on (sent bytes prepended with “&gt:”).

FF is a command to reset the keyboard, and we can see that both the ACK bit during write, and the FA message sent from keyboard are received successfully. After the keyboard has reset, it should also send “AA” to indicate successful initialization and test.

After reset there are commands to set keyboard LEDs (ED 02 turns on num lock LED) and F2 reads keyboard ID, which should (and does) result in two bytes AB and 83 from the keyboard (after FA, “acknowledge”, of course).

The whole code is just a bit over 200 lines, mostly because I decided to write helper functions and use plenty of comments along the way. Armed with this tool it should be easy to debug and play with PS/2 devices. Coming up next is a post on implementing a fully fledged PS/2 keyboard device with ATtiny!

18 comments

hithesh:

Nice article. Do you have a list of commands that the host can send to device.
What is the keyboard ID. Is it same for all keyboards?

Joonas Pihlajamaa:

Yeah you can find them here: http://www.computer-engineering.org/ps2protocol/ (under the PS/2 keyboard protocol link). Keyboard ID is always the same for all keyboards.

hithesh:

Joonas, thanks for the reply.
Did you happen to write any keyboard emulation code?
I want to know how key codes are transmitted when you press more than one key. Like say “HELLO”. Does the keyboard transmit hex code for H, then 0xF0 and hex code again or
does it transmit the hex codes for each char – H, E, L, L, O and then the 0xF0 for all chars.

Joonas Pihlajamaa:

Yes the sample code in this project almost perfectly emulates a PS/2 keyboard apart from multiple keys (initialization code etc.).

The make/break codes are sent when those event happen. From the viewpoint of individual key (e.g. space), the make code (0x29) is sent when key is pressed down. Then while the key is down, new make codes are sent in intervals defined by the typematic rate (PS/2 has commands to set these and default values, this repeat is not in the code I wrote). When the key is raised, a break code is sent (0xF0, 0x29).

When you type “HELLO”, you most likely release the previous key before typing the next, so the sequence is something like make(H) .. break(H) … make(E) . break(E) .. make(L) . break(L) . make(L) …

Of course a fast typist may sometimes have two keys pressed, so I’m quite sure the PC could handle make(H) .. make(E) . break(H) .. break(E) as well (i.e. interleaved presses). The order of make codes is likely the relevant part.

hithesh:

From your comment, my code is very similar.
Do you know the interval between make and break? I am using 40msecs now.
What’s the minimum interval between two key makes?
I am using 50msecs now.

hithesh:

One more quesion – how do you have the clock and data connected to the Micro-controller pins. Are the pins configured as Open drain and then connected to NPN transistors with pull ups or do you have the open-drain pins directly pulled up to 5v?

Joonas Pihlajamaa:

Directly with internal pullups enabled when mcu is not holding them down. One just has to be careful with the order of turning pullups on/off and switching between input and output (see the code for “better” order)

Joonas Pihlajamaa:

I don’t know minimum working values, it could even work without delays at all, but I cannot draw much conclusions from single PC so I’ve used short delays just to be sure (see my recent knock-booter code for the exact delay, can’t remember it right now)

hithesh:

Where did you get the specs for PS2 timing?
In my case the data changes at the rising edge of the clock(same instant).
I”m not sure if this is acceptable.

Joonas Pihlajamaa:

From the same place I linked above. Probably any time when clock is up is OK, I’m quite sure the PC samples it on falling clock or short time afterwards. However, I wanted to get a “perfect” waveform so I did it in the middle (like the reading). :)

hithesh:

I got it working. Now, when I print two words in a loop, some of the alphabets are skipped sometimes.
I tried changing the time interval between make and break key transmission. Doesn’t make much difference.
What was your clock frequency?

Joonas Pihlajamaa:

12.5 kHz. One possible caveat I can think of is that the PC pulls the clock line low periodically (probably for internal processing), inhibiting communications. Any characters sent at that point are aborted if you scan for clock low, but not re-sent unless you have code in place to do that.

Sometimes that might even abort a “key release”, resulting in unpleasant character repetition forever… (I’m not completely sure how PC side handles break codes that never appear, so it might not happen, but I did encounter /something/ similar in the past)

andres:

Hi, i’m testing this sketch on 2 keyboards: one old that i had in my computer, and a new one that i bought specially for my project. The thing is that the old one works perfectly, but the new one sends “FF” to the host constantly. And if i ignore the FFs the scan codes are rarely consistent, sometimes doesnt even gives me a scan code at all. On a computer, the keyboard works perfect, but I’d like to use the new keyboard for my project. What could be the cause of this behavior? Could it be a faulty keyboard?

Joonas Pihlajamaa:

Hmm, first idea was that maybe the voltage level is somehow different? E.g. 5V vs. 3.3V? Also, with pullup resistors, does FF mean ground voltage – maybe the connection with data line breaks for a short while? Faulty keyboard is possible, but also it could be that electrically it is somehow slightly different, which causes the problems.

My 5 cents, nothing certain. :)

andres:

Well, I went to the store and replaced it with another of the same model, and it does the same. So not a falty keyboard. Maybe I could try changing 5v for 3.3v.

andres:

The old keyboard works perfect at 5v and at 3.3v. But the new one doesnt work well at any condition.

Keyboard Tester:

You can also go for an online testing with Keyboard Tester that takes quickly tests your Keyboard and Mouse of any brand, any type and also from any time, saving you a lot of time. I tried and it was not only helpful, but fun too.
P.S.: Online Keyboard Testers are free.

Andrés C:

Hi, did you find some information about this? I have the same issue: old keyboard working, new one not working.
By the way, I’m Andrés too, another coincidence.