• Please review our updated Terms and Rules here

Arduino PS2 to XT converter

zinamo

Member
Joined
Aug 14, 2014
Messages
31
Location
Italy
Arduino PS2 to XT converter

First of all I want to point out a couple of things:

-Sorry for my bad English but my motherlanguage is Italian.

-I am not the creator of this, all the credit goes to Kestutis Rutkauskas whose blog is: http://kesrut.wordpress.com the reference project can be found here: https://github.com/kesrut/tinyPS2toXTArduino

-IMPORTANT: No one is responible in case of injuries, damage to your equipment/devices, etc..so go on only at own risk.

-My advice is to fully read this few lines and evaluate if you can do this by yourself before starting anything.

So let's begin...

The purpose of this project is to use an Arduino (http://www.arduino.cc/) as a converter that allows common PS/2 to be used on XT type machines; as we know XT machines do not use the AT keyboard protocol; for this reason a simple port adapter is not sufficient but a chip based converter is required.

Till now there were two known solutions, get an original XT keyboard (very high quality products but nowdays very pricy, cannot be used with KVM systems) or the AT2XT converter (http://www.vintage-computer.com/vcforum/showthread.php?26426-AT2XT-keyboard-converter). What we are doing here is a third option faisable with very basic electronics knowledge, cheap components and little time.

First of all you need to have an arduino board and understand how it works.. If you have never seen anything like that go to: http://arduino.cc/ and in a couple of minutes you can learn the basics of this device-don't be scared it's very very easy and there are millions of tutorials on the internet.

Then you also need:
1) some time
2) 1* CONN DIN 5 PIN FEMALE PCB (CP-2350-ND on Digikey or can be easily desoldered from an old dead mobo like I did)
3) 1*CONN MINI-DIN 6 PIN FEMALE PCB (CP-2260-ND on Digikey or can be easily desoldered from an old dead mobo like I did)
4) some small pieces of wire

As for the board I have used an "Arduino Uno" but other models can be used too (little changes to the code may be required,see NOTE 1 at bottom); this board has a detachable Atmega 328 that can be used standalone with few basic components but without the full arduino board after the chip is programmed.

All you have to do assemble a small circuit board according to the given schematic that follows (sorry for the bad quality but was done quickly on paint) and program the arduino with the attached code.
One important advice is to DISCONNECT the USB cable from the arduino before powering the XT, the arduino board will be powered by XT itself BUT without disconnecting USB it won't work! So flash the code and then disconnect the arduino from the USB!

Before soldering anything, my suggestion is to assemble everything on a bredboard and test if it works for your computer then, you can solder the circuit on a perfboard or make your own PCB.

Here comes the scheme (this is bottom view, so keep in mind that connectors are seen from bottom!):
PS2XTarduino.jpg

Here comes the code (sorry I was not able to compact this):

#define ps_clk 2 /* must be interrupt pin (0 interrupt) */
#define ps_data 4
#define xt_clk 3
#define xt_data 5

#define START 1
#define STOP 3
#define PARITY 4
#define INIT 5
#define GROUP1_CNT 85
#define BREAK_GRP1 0xF0
#define MAKE_GRP2 0xE0
#undef DEBUG

byte cycles = 0 ;
unsigned char value = 0 ;
byte state = INIT ;
byte got_data = 0 ;

struct ps2_struct_group
{
unsigned char character ;
unsigned char make ;
unsigned is_char ;
unsigned char xt_make ;
} ;

typedef struct ps2_struct_group ps2_group1_type ;
typedef struct ps2_struct_group ps2_group2_type ;

ps2_group1_type ps2_group1[] =
{
{'a', 0x1C, 1, 0x1E},
{'b', 0x32, 1, 0x30},
{'c', 0x21, 1, 0x2E},
{'d', 0x23, 1, 0x20},
{'e', 0x24, 1, 0x12},
{'f', 0x2B, 1, 0x21},
{'g', 0x34, 1, 0x22},
{'h', 0x33, 1, 0x23},
{'i', 0x43, 1, 0x17},
{'j', 0x3B, 1, 0x24},
{'k', 0x42, 1, 0x25},
{'l', 0x4B, 1, 0x26},
{'m', 0x3A, 1, 0x32},
{'n', 0x31, 1, 0x31},
{'o', 0x44, 1, 0x18},
{'p', 0x4D, 1, 0x19},
{'q', 0x15, 1, 0x10},
{'r', 0x2D, 1, 0x13},
{'s', 0x1B, 1, 0x1F},
{'t', 0x2C, 1, 0x14},
{'u', 0x3C, 1, 0x16},
{'v', 0x2A, 1, 0x2F},
{'w', 0x1D, 1, 0x11},
{'x', 0x22, 1, 0x2D},
{'y', 0x35, 1, 0x15},
{'z', 0x1A, 1, 0x2C},
{'0', 0x45, 1, 0x0B},
{'1', 0x16, 1, 0x02},
{'2', 0x1E, 1, 0x03},
{'3', 0x26, 1, 0x04},
{'4', 0x25, 1, 0x05},
{'5', 0x2E, 1, 0x06},
{'6', 0x36, 1, 0x07},
{'7', 0x3D, 1, 0x08},
{'8', 0x3E, 1, 0x09},
{'9', 0x46, 1, 0x0A},
{'`', 0x0E, 1, 0x29},
{'-', 0x4E, 1, 0x0C},
{'=', 0x55, 1, 0x0D},
{'\\', 0x5D, 1, 0x2B},
{'\b', 0x66, 0, 0x0E}, // backsapce
{' ', 0x29, 1, 0x39}, // space
{'\t', 0x0D, 0, 0x0F}, // tab
{' ', 0x58, 0, 0x3A}, // caps
{' ', 0x12, 0, 0x2A}, // left shift
{' ', 0x14, 0, 0x1D}, // left ctrl
{' ', 0x11, 0, 0x38}, // left alt
{' ', 0x59, 0, 0x36}, // right shift
{'\n', 0x5A, 1, 0x1C}, // enter
{' ', 0x76, 0, 0x01}, // esc
{' ', 0x05, 0, 0x3B}, // F1
{' ', 0x06, 0, 0x3C}, // F2
{' ', 0x04, 0, 0x3D}, // F3
{' ', 0x0C, 0, 0x3E}, // F4
{' ', 0x03, 0, 0x3F}, // F5
{' ', 0x0B, 0, 0x40}, // F6
{' ', 0x83, 0, 0x41}, // F7
{' ', 0x0A, 0, 0x42}, // F8
{' ', 0x01, 0, 0x43}, // f9
{' ', 0x09, 0, 0x44}, // f10
{' ', 0x78, 0, 0x57}, // f11
{' ', 0x07, 0, 0x58}, // f12
{' ', 0x7E, 0, 0x46}, // SCROLL
{'[', 0x54, 1, 0x1A},
{' ', 0x77, 0, 0x45}, // Num Lock
{'*', 0x7C, 1, 0x37}, // Keypad *
{'-', 0x7B, 1, 0x4A}, // Keypad -
{'+', 0x79, 1, 0x4E}, // Keypad +
{'.', 0x71, 1, 0x53}, // Keypad .
{'0', 0x70, 1, 0x52}, // Keypad 0
{'1', 0x69, 1, 0x4F}, // Keypad 1
{'2', 0x72, 1, 0x50}, // Keypad 2
{'3', 0x7A, 1, 0x51}, // Keypad 3
{'4', 0x6B, 1, 0x4B}, // Keypad 4
{'5', 0x73, 1, 0x4C}, // Keypad 5
{'6', 0x74, 1, 0x4D}, // Keypad 6
{'7', 0x6C, 1, 0x47}, // Keypad 7
{'8', 0x75, 1, 0x48}, // Keypad 8
{'9', 0x7D, 1, 0x49}, // Keypad 9
{']', 0x5B, 1, 0x1B},
{';', 0x4C, 1, 0x27},
{'\'', 0x52, 1, 0x28},
{',', 0x41, 1, 0x33},
{'.', 0x49, 1, 0x34},
{'/', 0x4A, 1, 0x35},
} ;

ps2_group2_type ps2_group2[] =
{
{' ', 0x5B, 0, 0x1F}, // left gui
{' ', 0x1D, 0, 0x14}, // right ctrl
{' ', 0x5C, 0, 0x27}, // right gui
{' ', 0x38, 0, 0x11}, // right alt
{' ', 0x5D, 0, 0x2F}, // apps
{' ', 0x52, 0, 0x70}, // insert
{' ', 0x47, 0, 0x6C}, // home
{' ', 0x49, 0, 0x7D}, // page up
{' ', 0x53, 0, 0x71}, // delete
{' ', 0x4F, 0, 0x69}, // end
{' ', 0x51, 0, 0x7A}, // page down
{' ', 0x48, 0, 0x75}, // u arrow
{' ', 0x4B, 0, 0x6B}, // l arrow
{' ', 0x50, 0, 0x72}, // d arrow
{' ', 0x4D, 0, 0x74}, // r arrow
{' ', 0x1C, 0, 0x5A}, // kp en
} ;

void setup()
{
#ifdef DEBUG
Serial.begin(9600) ;
#endif
pinMode(ps_clk, INPUT) ;
pinMode(ps_data,INPUT) ;
pinMode(xt_clk, OUTPUT) ;
pinMode(xt_data, OUTPUT) ;
digitalWrite(xt_clk, HIGH) ;
digitalWrite(xt_data, HIGH) ;
attachInterrupt(0, clock, FALLING);
}

unsigned char _read()
{
if (got_data)
{
got_data = 0 ;
return value ;
}
return 0 ;
}

void _write(unsigned char value)
{
while (digitalRead(xt_clk) != HIGH) ;
unsigned char bits[8] ;
byte p = 0 ;
byte j = 0 ;
for (j=0 ; j < 8; j++)
{
if (value & 1) bits[j] = 1 ;
else bits[j] = 0 ;
value = value >> 1 ;
}
digitalWrite(xt_clk, LOW) ;
digitalWrite(xt_data, HIGH) ;
delayMicroseconds(120) ;
digitalWrite(xt_clk, HIGH) ;
delayMicroseconds(66) ;
digitalWrite(xt_clk, LOW) ;
delayMicroseconds(30) ;
digitalWrite(xt_clk, HIGH) ;
byte i = 0 ;
for (i=0; i < 8; i++)
{
digitalWrite(xt_clk, HIGH) ;
digitalWrite(xt_data, bits[p]) ;
delayMicroseconds(95) ;
digitalWrite(xt_clk, LOW) ;
digitalWrite(xt_data, LOW) ;
p++ ;
}
digitalWrite(xt_clk, HIGH) ;
digitalWrite(xt_data, LOW) ;
delay(1) ;
}

byte i = 0 ;
void loop()
{
label_start:
unsigned char code = _read() ;
#ifdef DEBUG
if (code != 0) Serial.println(code, HEX) ;
#endif
if (code == BREAK_GRP1)
{
delay(10) ;
unsigned char break_code = _read() ;
unsigned char i = 0 ;
while (i < GROUP1_CNT)
{
if (ps2_group1.make == break_code)
{
pinMode(xt_clk, OUTPUT) ;
pinMode(xt_data, OUTPUT) ;
_write(ps2_group1.xt_make | 0x80) ;
break ;
}
i++ ;
}
goto label_start;
}
if (code != 0)
{
unsigned char i = 0 ;
while (i < GROUP1_CNT)
{
if (ps2_group1.make == code)
{
#ifdef DEBUG
Serial.write(ps2_group1.character) ;
#endif
_write(ps2_group1.xt_make) ;
break ;
}
i++ ;
}
}
if (digitalRead(xt_clk) == LOW) // power-on self test
{
delay(10) ;
_write(0xAA) ;
}
}

void clock()
{
if (state == INIT)
{
if (digitalRead(ps_data) == LOW)
{
state = START ;
cycles = 0 ;
got_data = 0 ;
value = 0 ;
return ;
}
}
if (state == START)
{
value |= (digitalRead(ps_data) << cycles) ;
cycles++ ;
if (cycles == 8) state = PARITY ;
return ;
}
if (state == PARITY)
{
state = STOP ;
return ;
}
if (state == STOP)
{
if (digitalRead(ps_data) == HIGH)
{
state = INIT ;
got_data = 1 ;
return ;
}
}
}






Here comes a link with an archive with the code and an higher quality pic of the schematic:

http://www.adrive.com/public/DFY4fm/PS2toXTArduino-master.zip


As the creator of this project specifies on the github page, this project is under development so there may be something that does not work flawlessly (some keyboard keys can be misplaced depending on your keyboard layout) but you can easily edit the code to fix those minor issues. Feel free to contribute on this thread..

Personally I have built the adapter and it works in a very satisfying way.. it only took a while for me to figure out the cabling but with the schematics should be clear enought..


NOTE 1: at the beginning of the code the digital pins used are initialized.. the pins may depend on your arduino board.. Kestutis Rutkauskas the creator of this project used an Arduino Leonardo so it's code began with:

#define ps_clk 3 /* must be interrupt pin (0 interrupt) */
#define ps_data 4
#define xt_clk 2
#define xt_data 5

Here are some pics of my adapter at early stage (sorry for bad quality pics but at that time I was on hurry)

2014-09-16 11.07.29.jpg 2014-09-16 11.07.39.jpg

Here is my final solution (it is a board that looks like a module for the arduino, this was done for easy installation.. not a very clean board but does it's job)

2014-09-25 16.34.20.jpg
 
Last edited:
I might take a stab at rewriting that code and trying it out with a teensy. (since I have several spares, 2.0 and 3.0 -- though teensy 3 would be overkill).

There's a few things I'd clean up.

1) use SWITCH/CASE instead of the IF/RETURN on the clock ISR.

2) use a flat array of XT keycodes indexed by AT value -- even though you would have blanks in the array, the loss of an entire column of that data would more than make up for it in terms of memory footprint, and it would be WAY faster than that 'loop searching for it'.

3) trap the multi-byte codes by checking bit 7 and using the proper "way it works" of 0xEv or 0xFv where v is the number of bytes that follows. Dunno if you noticed but:

Keypad Enter:
E0 5A

Pause/Break:
E1 14 77
E1 F0 14
F0 77

low nybble + 1 == number of bytes that follows when bit 7 is set.

4) Get parity checking in there.

5) Not really seeing a reason to waste memory and overhead on storing the bits in an array and looping twice on the WRITE command... just & 1 inside the output loop and do a shr where timing isn't as critical.

Code:
void _write(unsigned char value) {
	while (digitalRead(xt_clk) != HIGH);
	digitalWrite(xt_clk, LOW);
	digitalWrite(xt_data, HIGH);
	delayMicroseconds(120);
	digitalWrite(xt_clk, HIGH);
	delayMicroseconds(66);
	digitalWrite(xt_clk, LOW);
	delayMicroseconds(30);
	digitalWrite(xt_clk, HIGH);
	for (byte i = 0; i < 8; i++) {
		digitalWrite(xt_clk, HIGH);
		digitalWrite(xt_data, value & 1);
		delayMicroseconds(95);
		digitalWrite(xt_clk, LOW);
		digitalWrite(xt_data, LOW);
		value >>= 1;
	}
	digitalWrite(xt_clk, HIGH);
	digitalWrite(xt_data, LOW);
	delay(1);
}

Though isn't the clock high before the loop redundant to the one inside it? Or is it immediately pulled down by the hardware for some reason I'm unfamiliar with? (Not really up on how the XT keyboard handles things).

Might be fun to see if it could be dumbed down to run on something like a ATtiny20.
 
Ok, this is good for a laugh. I started playing with building one of these on a breadboard with a teensy, when suddenly it hit me...

Not a single one of my 8088 class machines takes a PC/XT keyboard. PCjr, Sharp PC-7000... assortment of Tandy 1000's... I've got nothing to test it on!
 
Not a single one of my 8088 class machines takes a PC/XT keyboard.

Well, you could always build a second one to translate XT to PS/2 and use them back-to-back! :)

Zinamo, I tried your code and it worked on an XT, but it looked like it had keybounce. Every character showed up doubled. When I typed 'dir', I got 'ddiirr'. Found that with some judicious stabbing at the keys, it was possible to get it right - but it was tough. Perhaps tomorrow I can spend some time with your code and figure out what is going on. I tried a couple IBM keyboards and an HP. All acted the same.

Ultimately, I'd like to use something like this to be able to use a PS/2 keyboard on an IBM PCjr. That's going to be a little crazy because of some of the oddball scan code combinations.
 
Hello jharre,
well first of all I don't know where this issue comes from but initially I had the same exact problem, under kesrut (the creator of this project) suggestion, I solved it by altering the delay value in this portion of the code:

if (code == BREAK_GRP1)
{
delay(10) ;

the default value set by kesrut was 4, I replaced it with 10 and it worked, you can try using 1, 2, 20, 50, 100 to see if it is working better.. if no, you can also try deleting the delay command.. just remember to disconnect USB after each update before starting the XT :) let me know if it is working...
 
Here is one more pic of the adapter mounted over the arduino:
2014-10-03 20.58.30.jpg
the two wires you see on top are used because my board was less wide that the arduino itself so I had to find a solution :)
 
This is an interesting concept...I may have to give this a go myself...an excuse to buy a new electronic toy. :) Leaves me also wondering what else is possible...such as will it work in reverse to use an XT keyboard to drive an AT machine? Or how about for slightly oddball machines like the early Tandy 1000's and 2000...and I'm sure there are others.

Wesley
 
@ jharre: was the issue you had solved?

@ wesleyfurr: what you say is very interesting mostly because as far as I know older XT keyboards where built in very high quality.. and also the use on other machine would be good I have wondered if it is possible to build such an adapter for using an AT keyboard with an Olivetti M24/AT&T 6300.. that would be awesome.. but I think I am not able to do that.. but if someone is up to the challenge :)
 
I found this link after looking at the original github project. I needed this for an old Z80 computer that uses an IBM PC keyboard which I don't have on hand. I programmed my Arduino UNO (copy) but all I saw were CRs when pressing any keys.

I then had a search for the clocking/data protocol used by the XT and found the following which I used as my reference:

http://www.avrfreaks.net/comment/825221#comment-825221

I see others here have had various timing related issues so maybe what I present here might be of some help. From what I could work out the _write() function has issues with timing. In the replacement _write() function below I enabled the value=0xaa line then fed the XT clock and data to an analogue oscilliscope so that I could see what was really happening. Sending out the same value over and over is needed on a scope that does not have data storage.

Part of the problem in the original code is the lack of any delay in the data loop between digitalWrite(xt_clk, LOW) and digitalWrite(xt_clk, HIGH). The only delay produced would come from the inline code loop itself.

I've not played around with other delays outside this function or tried to improve the code generally. My aim was to get it working for my use. It may not work for others but it now looks correct on my scope.

Code:
void _write(unsigned char value)
{
  //value=0xaa;  // <---------------------- force this value for testing
 
   while (digitalRead(xt_clk) != HIGH) ;
   unsigned char bits[8] ;
   byte p = 0 ;
   byte j = 0 ;
   for (j=0 ; j < 8; j++)
   {
     if (value & 1) bits[j] = 1 ;
     else bits[j] = 0 ;
     value = value >> 1 ;
   }
   digitalWrite(xt_clk, LOW);
   digitalWrite(xt_data, HIGH); // start bit (2 data bit time)
   delayMicroseconds(120) ;
   digitalWrite(xt_clk, HIGH) ;
   delayMicroseconds(66) ;
   digitalWrite(xt_clk, LOW) ;
   delayMicroseconds(30) ;
//don't need this here as happens in loop:   digitalWrite(xt_clk, HIGH) ;
   byte i = 0 ;
   for (i=0; i < 8; i++)
   {
      digitalWrite(xt_data, bits[p]);
      delayMicroseconds(30);
      digitalWrite(xt_clk, HIGH);
      delayMicroseconds(66);
      digitalWrite(xt_clk, LOW) ;
      delayMicroseconds(30);
      p++ ;
   }
   digitalWrite(xt_data, LOW);
   delayMicroseconds(30);
   digitalWrite(xt_clk, HIGH);
   delayMicroseconds(66);
   delay(1);
}

For testing purposes only I used this loop() function:

Code:
static unsigned char code;

void loop()
{
 unsigned char x;

 // gets a key code and sends it over and over until new key pressed
 if ((x = _read()) != 0)
    {
     code = x;
#ifdef DEBUG
     Serial.println(code, HEX);
#endif
    }
 else
    _write(code);
}
 
I've built this Arduino-based solution too (with the help of this topic), but I'm having the double key problem as well.
Upping the delay time doesn't work for me either, what else can I try? I have no experience in programming the Arduino at all.
 
Last edited:
I believe the double key is because the ps2 keyboard sends the key twice.
Once to for key down and once for key up.
I don't recall how the XT/AT deals with that.
I don't like the way they are hard pulling the lines to high for the ps2 side.
On code I wrote for ps2, I did the following:

pinMode(pin, INPUT_PULLUP ); // is an output high

and

digitalWrite(pin, LOW );
pinMode(pin, OUTPUT ): // for an output 0

The buss is suppose to be bidirectional. This code
works with the Arduino as bidirectional using the soft
pullups in the chip.

Dwight
 
Last edited:
I don't recall how the XT/AT deals with that.

PS/2 is AT, no difference, and PC/XT handles it the same way. There are only minor signaling differences between AT and PC/XT class keyboards, the messages are for the most part the same idea. Every key up and key down is sent separately.

Yes, I've noticed that 'key up' sends the key again.
I don't quite get how I can fix this.

The problem likely lies in your read routine stripping off if the keyup or keydown is the message type... could you re-upload your entire code someplace where I could have a look? the link from the open of this thread is dead now.

Dimes to dollars, whatever it is you are passing back from _read is wrong, because you stripped off the high bit.

Though looking at the write routine there's a LOT of "code for nothing" in there... and I'm not sure the clock timing actually makes sense compared to the timing graphs... the 30 low is peak to peak, including the extra spacing for rise/fall time... so those 30's should only be 23's, and the 66's should be 72's since that's the full duty time... based on the 5us rise/fall allotment. (which they seem to round off to six... gah, whoever made that pic can't do basic math or something)

I think those 66's in the timing charts are wrong, and the code isn't including the allotments for signal rise/fall times the timing charts are...

Code:
void _write(unsigned char value) {
	while (digitalRead(xt_clk) != HIGH);
	digitalWrite(xt_clk, LOW);
	delayMicrosections(5);
	digitalWrite(xt_data, HIGH);
	delayMicroseconds(110)
	digitalWrite(xt_clk, HIGH);
	delayMicroseconds(75);
	for (byte i = 0; i < 8; i++) {
		digitalWrite(xt_clk, LOW);
		delayMicroseconds(5);
		digitalWrite(xt_data, value & 0x01);
		delayMicroseconds(20);
		digitalWrite(xt_clk, HIGH);
		delayMicroseconds(70);
		// Of course we're ACTUALLY spending longer than that :(
		value >>= 1; //  I forget, do sketches support >>=? They SHOULD...
	}
	digitalWrite(xt_clk, LOW);
	delayMicroseconds(5);
	digitalWrite(xt_data, LOW);
	delayMicroseconds(20);
	digitalWrite(xt_clk, HIGH);
	delayMicroseconds(1005);
}

Should work... unless I'm taking the timing charts too literally... though the more I look at it, the more I think this should be taking 0.95ms, not 0.975... since it should be a consistent duty cycle even with the starting clock pulse inverted.
 
Last edited:
-- edit -- nevermind... forgot how SIMPLE the XT keymapping was. Bit 7 off is keypress, bit 7 on is release, and NOTHING more complex than that for commands... easy-peasy.

Playing with this right now since I've got a DCCduino Nano ($1.25 at328p arduino nano knockoff) sitting in a breadboard anyways.

Ah the "joys" of being limited to 2k of RAM and a language that offers no static constants in program space.

... uhm... I'm looking at the code some people are using on PIC and arduino and can't help but think... wouldn't it be easier to just set mode 1 so it returns PC/XT keycodes?
 
Last edited:
I use a compatible Arduino UNO, like in the original opening post of this topic.
This is the current code I run, which is the original code from GitHub, but changed according to the different changes made in this topic, so here's the full code:

Code:
#define ps_clk 3 /* must be interrupt pin (0 interrupt) */
#define ps_data 4
#define xt_clk 2
#define xt_data 5

#define START 1
#define STOP 3
#define PARITY 4
#define INIT 5
#define GROUP1_CNT 85
#define BREAK_GRP1 0xF0
#define MAKE_GRP2 0xE0
#undef DEBUG

byte cycles = 0 ; 
unsigned char value = 0 ; 
byte state = INIT ; 
byte got_data = 0 ; 

struct ps2_struct_group
{
  unsigned char character ; 
  unsigned char make ;   
  unsigned is_char ; 
  unsigned char xt_make ; 
} ; 

typedef struct ps2_struct_group ps2_group1_type ; 
typedef struct ps2_struct_group ps2_group2_type ; 

ps2_group1_type ps2_group1[] =
{
  {'a', 0x1C, 1, 0x1E},
  {'b', 0x32, 1, 0x30}, 
  {'c', 0x21, 1, 0x2E}, 
  {'d', 0x23, 1, 0x20},
  {'e', 0x24, 1, 0x12},
  {'f', 0x2B, 1, 0x21}, 
  {'g', 0x34, 1, 0x22},
  {'h', 0x33, 1, 0x23}, 
  {'i', 0x43, 1, 0x17},
  {'j', 0x3B, 1, 0x24}, 
  {'k', 0x42, 1, 0x25}, 
  {'l', 0x4B, 1, 0x26},
  {'m', 0x3A, 1, 0x32}, 
  {'n', 0x31, 1, 0x31},
  {'o', 0x44, 1, 0x18}, 
  {'p', 0x4D, 1, 0x19},
  {'q', 0x15, 1, 0x10},
  {'r', 0x2D, 1, 0x13},
  {'s', 0x1B, 1, 0x1F},
  {'t', 0x2C, 1, 0x14}, 
  {'u', 0x3C, 1, 0x16}, 
  {'v', 0x2A, 1, 0x2F}, 
  {'w', 0x1D, 1, 0x11},
  {'x', 0x22, 1, 0x2D},
  {'y', 0x35, 1, 0x15},
  {'z', 0x1A, 1, 0x2C}, 
  {'0', 0x45, 1, 0x0B},
  {'1', 0x16, 1, 0x02},
  {'2', 0x1E, 1, 0x03},
  {'3', 0x26, 1, 0x04},
  {'4', 0x25, 1, 0x05},
  {'5', 0x2E, 1, 0x06},
  {'6', 0x36, 1, 0x07},
  {'7', 0x3D, 1, 0x08},
  {'8', 0x3E, 1, 0x09},
  {'9', 0x46, 1, 0x0A}, 
  {'`', 0x0E, 1, 0x29},
  {'-', 0x4E, 1, 0x0C},
  {'=', 0x55, 1, 0x0D},
  {'\\', 0x5D, 1, 0x2B},
  {'\b', 0x66, 0, 0x0E}, // backsapce
  {' ', 0x29, 1, 0x39}, // space
  {'\t', 0x0D, 0, 0x0F}, // tab
  {' ', 0x58, 0, 0x3A}, // caps
  {' ', 0x12, 0, 0x2A}, // left shift
  {' ', 0x14, 0, 0x1D}, // left ctrl
  {' ', 0x11, 0, 0x38}, // left alt
  {' ', 0x59, 0, 0x36}, // right shift
  {'\n', 0x5A, 1, 0x1C}, // enter
  {' ', 0x76, 0, 0x01}, // esc
  {' ', 0x05, 0, 0x3B}, // F1
  {' ', 0x06, 0, 0x3C}, // F2
  {' ', 0x04, 0, 0x3D}, // F3
  {' ', 0x0C, 0, 0x3E}, // F4
  {' ', 0x03, 0, 0x3F}, // F5
  {' ', 0x0B, 0, 0x40}, // F6
  {' ', 0x83, 0, 0x41}, // F7
  {' ', 0x0A, 0, 0x42}, // F8
  {' ', 0x01, 0, 0x43}, // f9
  {' ', 0x09, 0, 0x44}, // f10
  {' ', 0x78, 0, 0x57}, // f11
  {' ', 0x07, 0, 0x58}, // f12
  {' ', 0x7E, 0, 0x46}, // SCROLL
  {'[', 0x54, 1, 0x1A},
  {' ', 0x77, 0, 0x45}, // Num Lock
  {'*', 0x7C, 1, 0x37}, // Keypad *
  {'-', 0x7B, 1, 0x4A}, // Keypad -
  {'+', 0x79, 1, 0x4E}, // Keypad +
  {'.', 0x71, 1, 0x53}, // Keypad .
  {'0', 0x70, 1, 0x52}, // Keypad 0
  {'1', 0x69, 1, 0x4F}, // Keypad 1
  {'2', 0x72, 1, 0x50}, // Keypad 2
  {'3', 0x7A, 1, 0x51}, // Keypad 3
  {'4', 0x6B, 1, 0x4B}, // Keypad 4
  {'5', 0x73, 1, 0x4C}, // Keypad 5
  {'6', 0x74, 1, 0x4D}, // Keypad 6
  {'7', 0x6C, 1, 0x47}, // Keypad 7
  {'8', 0x75, 1, 0x48}, // Keypad 8
  {'9', 0x7D, 1, 0x49}, // Keypad 9
  {']', 0x5B, 1, 0x1B},
  {';', 0x4C, 1, 0x27}, 
  {'\'', 0x52, 1, 0x28},
  {',', 0x41, 1, 0x33},
  {'.', 0x49, 1, 0x34},
  {'/', 0x4A, 1, 0x35}, 
} ; 

ps2_group2_type ps2_group2[] =
{
  {' ', 0x5B, 0, 0x1F}, // left gui
  {' ', 0x1D, 0, 0x14}, // right ctrl
  {' ', 0x5C, 0, 0x27}, // right gui
  {' ', 0x38, 0, 0x11}, // right alt
  {' ', 0x5D, 0, 0x2F}, // apps
  {' ', 0x52, 0, 0x70}, // insert
  {' ', 0x47, 0, 0x6C}, // home
  {' ', 0x49, 0, 0x7D}, // page up
  {' ', 0x53, 0, 0x71}, // delete
  {' ', 0x4F, 0, 0x69}, // end
  {' ', 0x51, 0, 0x7A}, // page down
  {' ', 0x48, 0, 0x75}, // u arrow
  {' ', 0x4B, 0, 0x6B}, // l arrow
  {' ', 0x50, 0, 0x72}, // d arrow
  {' ', 0x4D, 0, 0x74}, // r arrow
  {' ', 0x1C, 0, 0x5A}, // kp en
} ;

void setup() 
{
#ifdef DEBUG
  Serial.begin(9600) ; 
#endif
  pinMode(ps_clk, INPUT) ;
  pinMode(ps_data,INPUT) ;  
  pinMode(xt_clk, OUTPUT) ; 
  pinMode(xt_data, OUTPUT) ; 
  digitalWrite(xt_clk, HIGH) ; 
  digitalWrite(xt_data, HIGH) ; 
  attachInterrupt(0, clock, FALLING);
}

unsigned char _read()
{
   if (got_data)
   {
    got_data = 0 ; 
    return value ; 
  } 
  return 0 ; 
}

void _write(unsigned char value)
{ 
   while (digitalRead(xt_clk) != HIGH) ; 
   unsigned char bits[8] ;
   byte p = 0 ; 
   byte j = 0 ;
   for (j=0 ; j < 8; j++)
   {
     if (value & 1) bits[j] = 1 ;
     else bits[j] = 0 ; 
     value = value >> 1 ; 
   }
   digitalWrite(xt_clk, LOW) ; 
   digitalWrite(xt_data, HIGH) ; 
   delayMicroseconds(120) ; 
   digitalWrite(xt_clk, HIGH) ; 
   delayMicroseconds(66) ;
   digitalWrite(xt_clk, LOW) ; 
   delayMicroseconds(30) ;
   digitalWrite(xt_clk, HIGH) ; 
   byte i = 0 ; 
   for (i=0; i < 8; i++)
   {
      digitalWrite(xt_clk, HIGH) ; 
      digitalWrite(xt_data, bits[p]) ; 
      delayMicroseconds(95) ;
      digitalWrite(xt_clk, LOW) ;
      digitalWrite(xt_data, LOW) ; 
      p++ ; 
   } 
   digitalWrite(xt_clk, HIGH) ; 
   digitalWrite(xt_data, LOW) ;  
   delay(1) ;
}

byte i = 0 ;
void loop() 
{
  label_start:
  unsigned char code = _read() ; 
#ifdef DEBUG
  if (code != 0) Serial.println(code, HEX) ; 
#endif
  if (code == BREAK_GRP1)
  {
     delay(40) ; 
     unsigned char break_code = _read() ; 
     unsigned char i = 0 ; 
     while (i < GROUP1_CNT)
     {
       if (ps2_group1[i].make == break_code)
       { 
          pinMode(xt_clk, OUTPUT) ; 
          pinMode(xt_data, OUTPUT) ; 
          _write(ps2_group1[i].xt_make | 0x80) ;
          break ;
       }
       i++ ;
     }
    goto label_start;  
  }
  if (code != 0)
  {
    unsigned char i = 0 ; 
    while (i < GROUP1_CNT)
    {
      if (ps2_group1[i].make == code)
      {
#ifdef DEBUG
         Serial.write(ps2_group1[i].character) ; 
#endif
         _write(ps2_group1[i].xt_make) ;
         break ;
      }
    i++ ; 
   }
  }
  if (digitalRead(xt_clk) == LOW) // power-on self test
  {
    delay(10) ;
    _write(0xAA) ;
  }
}

void clock()
{
   if (state == INIT)
   {
     if (digitalRead(ps_data) == LOW)
     {
       state = START ; 
       cycles = 0 ;
       got_data = 0 ;
       value = 0 ; 
       return ; 
     }
   }
   if (state == START)
   {
     value |= (digitalRead(ps_data) << cycles) ;
     cycles++ ; 
     if (cycles == 8) state = PARITY ;
     return ;  
   }
   if (state == PARITY)
   {
     state = STOP ; 
     return ; 
   }
   if (state == STOP)
   {
     if (digitalRead(ps_data) == HIGH)
     {
       state = INIT ; 
       got_data = 1 ; 
       return ; 
     }
   }  
}

It does work, but also gives a keypress on key up.
 
Ok, like a lot of these codebases I'm seeing, you're using a lookup table to handle the translation... did you know if you sent 0xF0 0x01 the AT (and by extension PS/2) keyboards will enter "mode 1" where it returns XT keycodes instead of the extended ones? That whole table could be axed... well, unless you dislike how it handles extended codes and the keypad and want to do your own handling of that.

Your clock routine has no provision for write handling on clock...

Something I'm finding really odd is I don't see any of these keyboard handlers sending acknowledges... Isn't it supposed to go tits-up if you don't send 0xFA back if ok... much less shouldn't parity be checked for 0xFE to resend the data?

... and how are any of these handling the power on/reset state of endless 0xAA with parity broken until you send 0xFE back? Shouldn't these NEVER work in the first place since the keyboard would be locked until you ack the error?

Or does the keyboard side really not give a flying purple fish? Or is that only something you need to worry about to make it hot-pluggable?

Really, the PS/2 side of the code doesn't make any sense to me given how it's supposed to work. It's like you're blindly ignoring all the command codes.

... and that means you're not looking for 0xF0, the command that says "the next byte is a key up" -- what you need to do is trap that (check if it's set) then for your XT data if it's set, do value |= 0x80 to set the keyup bit high in the XT data.

Have a look at the internals of PS2KeyboardExt2:
http://playground.arduino.cc/Main/PS2KeyboardExt2

It's got a lot more going on, and for good reason.

I've always worked off this txt file for the AT/XT differences:
https://www.cs.cmu.edu/~jmcm/info/key2.txt

... and based on what that says, ALL these implementations have giant gaps, some of which should be preventing them from working whatsoever; like the lack of sending ack/err! That's why it pulls high at the end to detect the other end pulling low.

In fact, I'd be surprised if your write method works at all, since your bit output has to be based on the DEVICE clock -- that's why you have to have the bits output by _write in your ISR... which you don't.

I'd also be worried about the lack of any buffering -- type too fast or receive a multi-byte back to back, and you're gonna get interrupted/corruption halfway through processing.
 
Last edited:
Oh, interesting... if you reply back with 0xFA immediately, it will send any next byte immediately. If you do NOT send an acknowledge or retry, it times out in 1 second...

Meaning that chart linked earlier in this thread was ignorant of how the interface worked. That whole 10ms delay can be axed if you just bother responding properly.
 
Last edited:
Back
Top