PDA

View Full Version : Arduino PS2 to XT converter



zinamo
September 26th, 2014, 02:49 PM
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!):
20667

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[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 ;
}
}
}





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)

20673 20674

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)

20675

jharre
September 26th, 2014, 08:07 PM
Great find, zinamo! I'd be interested in seeing pics of your project.

zinamo
September 27th, 2014, 09:18 AM
I added some pics, they are not very good but I hope still usefull :-)

deathshadow
September 28th, 2014, 08:21 PM
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.


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.

deathshadow
September 28th, 2014, 10:52 PM
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!

jharre
September 30th, 2014, 07:35 PM
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.

zinamo
October 1st, 2014, 02:00 PM
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...

zinamo
October 4th, 2014, 02:49 AM
Here is one more pic of the adapter mounted over the arduino:
20788
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 :-)

wesleyfurr
October 4th, 2014, 06:07 AM
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

zinamo
October 10th, 2014, 10:27 AM
@ 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 :-)

staticmem
July 14th, 2015, 09:08 PM
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.



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:



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);
}

irix
August 24th, 2016, 12:32 AM
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.

Dwight Elvey
August 24th, 2016, 01:24 PM
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

Chuck(G)
August 24th, 2016, 01:40 PM
Yup scancode for key down then 0xf0, scancode for key up.

irix
August 25th, 2016, 02:34 AM
Yes, I've noticed that 'key up' sends the key again.
I don't quite get how I can fix this.

deathshadow
August 25th, 2016, 11:17 PM
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...



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.

deathshadow
August 26th, 2016, 01:20 AM
-- 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?

irix
August 26th, 2016, 02:19 AM
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:



#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.

deathshadow
August 26th, 2016, 03:16 AM
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.

deathshadow
August 26th, 2016, 03:53 AM
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.

irix
August 26th, 2016, 06:53 AM
I have no clue on what the script is actually doing, I just grabbed the codes from this topic and put them together.
I would love to learn what does what and why, but for now I really don't know.
I have some basic Basic knowledge, some php knowledge and some lua knowledge, but that's about it.

deathshadow
August 26th, 2016, 07:31 AM
I would love to learn what does what and why, but for now I really don't know.
Ah, well... a project like this is a very good place to start learning C and things like circuit interfacing since the Arduino makes it relatively simple.

You know, normally when I write a website in HTML/CSS/PHP I'll do a breakdown section by section explaining why I chose the tags I did, why/how the CSS works and is applied, and the logic of the underlying PHP code. I can probably do that with what you have in C to better explain what that is doing for you if that might help.

I know PHP and web technologies in general people are able to get by blindly pasting together other people's code, but when you're down at 16 or 32k of flash for program space, 1 or 2k of RAM, and 16mhz or less on a 8 bit RISC processor, doing that can work against you VERY quickly.

For now, the "big" problem is how you are turning your _read into _write to the xt. You're doing read only on the AT side, and the AT really isn't meant for that -- but more of an issue is that the AT returns a slew of values higher than 0x7F that are control codes. Your program is trying to look them up regardless and not acknowledging any control messages.

OH wait, you are trapping 0xF0... ok, that's sloppy.

Strangest part though is this:



_write(0xAA) ;


That's sending a keyup for 0x2A, which on the XT is the "9" key... The device that should be sent the 0xAA is the AT keyboard, and even that doesn't make sense as that's a reset. After the key checks parity and you reply with the ack bit (yes, the host replies to the AT keyboard with the ack bit), the code should be sending 0xFA if everything went fine, or 0xFE as there was a parity error, so we ask for the code again.

I'm out the door in a while, but when I get home tonight I'll take a stab at doing a quick rewrite with my nano here, and see what I come up with... digging through my bin I'm a bit shocked, I'm out of PS2 female plugs... got an overabundance of 5 pin males though since I'm a MIDI guy.

Maybe I'll just see if I can remove one from one of the dead mobo's I have lying around rather than ordering some.

irix
August 27th, 2016, 12:08 AM
I also took a midi cable and used that to connect to the XT.
I've ordered a PS/2 board online that had 4 pins on the other side (easier than soldering myself), I connected an old cd-rom audio cable to it and connected that to the Arduino.

Can't it be just as simple as:

When I press A on PS2 translate that into A on XT and when I press B send a B. (may be too simplistic though)
I see a lot of code (which I don't understand (yet)).

inotarobot
August 27th, 2016, 12:59 AM
Ah, well... a project like this is a very good place to start learning C and things like circuit interfacing since the Arduino makes it relatively simple.


Hello and what an interesting topic.

I would like to throw a curved ball in to the mix.

a. my C programming is ultra rusty, and I simply do Not have time to learn it

b. I purchased recently a very early Sirus with monitor BUT no keyboard. As far as I understand it uses very unusual key board and also floppy disk format.

I would really like to know if someone like yourself, or another programmer here can spend 1hr seeing if there is a way to make a XT keyboard work via this Arduino keyboard converter.

regards
David

deathshadow
August 27th, 2016, 10:32 PM
I also took a midi cable and used that to connect to the XT.
I didn't even have to chop one -- I actually have a bin of the plugs unsoldered and ready for action... though most of them are left over from when I was making TRS-80 /similar cassette port cables.


I've ordered a PS/2 board online that had 4 pins on the other side (easier than soldering myself)
Nice approach... I was sitting here questioning the PC/XT timings I'm seeing so I'm turning my Teensy 3.0 into a el-cheapo graphing logic probe -- actually got it down to 0.46ns granularity for one channel, 0.63ns for two at once! There's data and clock -- but was searching my scraps for a 5 pin female...

Completely forgetting this bag a friend sent me two years ago filled with like 30 or so 5 pin female to PS/2 male adapters. NOT the most useful of directions to convert in this day and age, but handy to chop one up for especially since I'm still at the breadboard stage.


When I press A on PS2 translate that into A on XT and when I press B send a B. (may be too simplistic though)
I see a lot of code (which I don't understand (yet)).

Most of the code you are seeing has to deal with the fact that the communications protocols used are two pin clocked serial, and the protocols are DIFFERENT. Lemme see if I can explain it a bit.

Both are clock timed -- what that means is that one side (the keyboard, aka 'client') generates a clock when data is to be passed. Each time the clock ticks off (by pulling the signal low to indicate data is changing, then high when the data is set) the data line sets one bit.

For the AT, that bit pattern is: (0 is low, 1 is high)
Bit 0 == start bit, always 0.
Bit 1..7 == data bits
bit 8 == parity bit. This is "odd" parity.
Bit 9 == stop bit, always 1
bit 10 == acknowledge bit, should be 1... SENT BY OTHER SIDE!

On the XT, that pattern is WAY simpler:

bit 0..1 == start bit, always 1, held high for two cycles
bit 2..8 == data bits
bit 9 == stop bit, always 0.

Also when no data is being sent, the clock line is held high. Also since it's held for two cycles, I'd call it two start bits, not one! That was confusing me a lot.

The XT's data protocol is Dick simple. It is monodirectional in that the keyboard sends messages to the computer and just blindly hopes the computer is there. There is no response and no way for the computer to tell the keyboard anything. The data message itself is just the keyboard scancode on bits 0..6, with bit 7 indicating if it's a keyup (1) or key down (0).

So if you hit the A key and then released, the data would be:
0x1E, 0x9E

for down and release... the release just being the same value with the high bit set.

The AT protocol on the other hand is bidirectional... this required some pretty hefty changes to how things work... some of them not for the better. Not only are the scancodes different, bit 7 is not used for up/down. Instead if bit 7 is set it indicates the rest of the data is a "command code"... 0xFA is acknowledge a message, 0xFE is resend last message, 0xF0 is next press is a release, and so forth.

So that same example of pressing A and releasing sends:

0x1C, 0xF0, 0x1C

Scancode is different, the 0xF0 saying the next code is a keyup message, which then says the same scancode over again.

So you have to decode the AT signalling, if it's just a scancode you translate and send using the XT signalling... but if it's a command code you have to handle it as appropriate... like trapping 0xFA so that you "scancode | 0x80" on the next scancode transmission.

Likewise on the AT side you have to send 0xF0 back, or wait until the keyboard times-out and says "oh well". Ideally one should be actually checking the parity and sending 0xFE if the parity doesn't match.

Checking that parity bit is fun too since it's odd parity. Personally I prefer to calculate that as each bit is received, whereas everyone else prefers to screw around with shifts after the fact...

Me, on the initialize I'd set my parity variable to one (odd parity), then just "if (bit) parity++;" on each data bit, with on parity verification checking "if (parity & 1 == bit) {" to trigger if we need to 0xFE or 0xF0 after the stop bit.

Which is why I'd have an output buffer as well, since the transmission clock is handled by the keyboard we'd need our ISR to handle sending data. To that end we'd probably have to have a routine called by LOOP to trigger pullin g the clock line low when commands are waiting in the buffer.

Really it's the AT side that makes it so complex... though again I'm REALLY questioning the information I have about the XT side since all the charts I have -- like this one (http://www.kbdbabel.org/signaling/kbd_signaling_pcxt.png) -- the math doesn't add up.

Seriously, look at it... how can (9 cycles of 95μs) + 66μs + 30μs + 120μs + what appears to be 25 extra μs of a down-clock at the end == 975μs... that's BS, not μs. My math that comes to 1001μs!

The 5μs of rise/fall time I recognize why it's so high. Under ideal circumstances TTL devices have a rise/fall time of 2.5μs. Atmega rates the digital outs on most of their 8 bit chips at 3ms, and some devices can push it to 4 depending on things like extra resistors for safety and line capacitance. Like all good engineers, the folks who made the PC keyboard spec were smart and Mr. Scott the numbers. Figure out how much it needs, then double it. Likewise it seems to be good practice to wait that full rise/fall time BEFORE you change the data value. So far NO implementation I've seen on the arduino or PIC seems to be doing that, and that could bite you with some keyboards or some devices. If anything there should be a 5us delay after pulling the clock low, and 5us after setting the data, in addition to the peak/trough times. 95us seems to be accurate for each pulse, and the initial PC/XT start pulse does NOT violate this near as I can tell. (I'll be posting up some actual output from my homebrew logic probe later).

As such, MY interpretation of the XT timing is:

5 clocks for each rise/fall
20 clocks deck for each trough
70 clocks ceiling for each peak

So for all clocks except the first -- which is inverted and stays low, that's
5 fall, 20 low, 5 rise, 65 high

A bit off from the oddball 66 number that people seemed to be throwing in there.

So... my interpretation of the XT start "bit" is:

Pull clock low, wait 5Ás
pull data high, wait 90Ás (rest of cycle)
pull clock high, wait 95Ás (entire cycle with rise time)

Which is a far cry from what I'm seeing elsewhere. The peak for the second clock pulse on the start "bit" is out of sync with the rest of the data stream. That combined they add up to more than the 180Ás that two normal pulses would also makes it suspect in my mind... and the data stream I'm seeing says that too.

... although I'm using a cheap AT/XT switchable as my exemplar as I don't have any model F's in my collection.

To that same end, and the charts agree with this visually but don't spell it out, I'm likely going with this for each of the data pulses:

pull clock low, wait 5Ás
set data bit, wait 20Ás
pull clock high, wait 70us (which includes the 5 rise/fall time on data)

It also seems "off" that you hold the stop bit until the next cycle, and that the stop is low. Very interesting approach as it lets you monitor data OR clock for the start of the next cycle.

Though I imagine that the interface has a LOT of tolerance since it likely does NOT take the full 95Ás for the either side to recognize the data... especially not if they can handle a 20Ás low pulse on the clock line.

I think at one point when talking about making something like ADT Pro but for PC's someone (was it Chuck (g)?) mentioned they were able to push the AT keyboard interface WAY past spec for data transmission, so it's probably NOT as important the timings as my brain wants it to be. I mean hell, it's TTL logic, you should be able to blast that sucker as fast as it takes to rise, fall, and trigger! That's the entire reason you'd have a clock line in the first place, otherwise single line transmission would be adequate.

But I'm a real stickler for consistent timings and accurate specifications... both seem to be a lost art.

deathshadow
August 27th, 2016, 10:50 PM
I would like to throw a curved ball in to the mix.
All balls are curved. I'm so slow when it comes to broken English, took me half a minute before I realized you meant a curve-ball.


I would really like to know if someone like yourself, or another programmer here can spend 1hr seeing if there is a way to make a XT keyboard work via this Arduino keyboard converter.
That would involve having the scancode specifications of the Sirius, as well as knowing what bit-wise protocol it is using.

I'd be willing to take a stab, but it's gonna be a lot more than an hour and I'd likely need someone to research the documentation for said device.

It would also likely be more useful to adapt a AT keyboard to it, as they are usually easier to find than XT ones at this point.

Besides, why settle for less than a Model M?

While I never had one, I'm at least aware of the Victor's quirks... the keyboard might be really challenging as I know its mapping was programmable -- you could literally assign any key any value you liked! I was never quite clear if that was done on the keyboard or on the machine itself. If it's anything like the chip inside the PC keyboar,d if they gave it more than just a scratchpad worth of RAM it would have been very easy (and very efficient) to do keyboard side -- just like how the AT keyboard lets you choose between two different mappings. Would also have lessened the load on the host machine itself -- and given the Victor/Sirius 1 much like the Tandy 2000 and DEC Rainbow were in fact better DOS machines than PC's on the spec and performance side, it would surprise me at all if they went that route.

Which again touches on something I'm surprised these converters are NOT doing, setting the AT keyboard to mapping 1 (PC/XT) by sending 0xF0 0x01 at it. Then these translation tables wouldn't even be necessary! Internally that's how the keyboards with the switches on them work, they lock the keyboard into mapping 1 and then use the older/simpler XT protocol since the phsyical wiring is the same... but still ALL PC/AT keyboards to my knowledge support mode 1 (mode 3 is the default). (only mode 2 was dropped on later keyboards, and really, that hybrid of mode 1 and 3 mappings was a confusing mess nobody ever used!)

(the laugh being Linsux STILL forces AT keyboards into XT mapped behavior! That's why the numpad acts so funky/useless if you try to use it as arrows -- a habit I never broke myself of. I'm still useless with the arrow-pad... Most peope chop off the number pad when shrinking keyboards, I would get rid of the arrow-pad area. I just don't use those keys!

irix
August 28th, 2016, 04:48 AM
I do have some Samsung keyboards that do have a spare hole for a XT/AT-switch, the switch itself is not in place.
It also has holes in the print for the switch, but when I connect as if the switch was 'ON' it doesn't work.

Very interesting story about timings, I thought it was just a simple thing like for example an Amiga joystick, just switches that send stuff.

deathshadow
August 29th, 2016, 02:08 PM
Very interesting story about timings, I thought it was just a simple thing like for example an Amiga joystick, just switches that send stuff.

Most joysticks use one wire for each button, in addition to ground... take the 9 pin atari interface that uses 6 wires -- one for each direction, one for the button, and ground... or the Sega expansion to this that recycles the two paddle lines for two more buttons.

Now, think on trying to apply that to a 89 to 104 key keyboard... you really think they can cram 105 separate wires into a keyboard cable?

INTERNALLY a keyboard in fact does this, though they use something called "matrixing" that creates rows and columns of connections that looks for shorts between them. This reduces the number of wires needed for a 101 key keyboard from 102 to only 14... but that's still more wires than you'd really want to put into a keyboard cable -- particularly over any distance as crosstalk can become an issue.

A lot of older microcomputers (pretty much all 8 bit ones) do in fact access said matrix directly since usually the computer was in the keyboard. Usually their internal cable is two, maybe three inches in length at the most.

The problem with directly accessing the keyboard switches is how do you detect when the value changes? Sure, you could hook every key or use a string of gates to connect them to an interrupt, but what if another interrupt is hogging things? If the system is too busy to check the keyboard right that second, you lose keypresses.

Worse, you need to monitor VERY closely and average the responses to avoid something called "bounce" -- where a newly connected switch will often lose contact then regain it. You have to average responses over time in order to eliminate that which means a VERY active polling of the device. That's why -- generally speaking -- most 8 bit home systems were cute toys, but unsuited to long term business data entry use.

To address this "real" systems would give the keyboard it's own separate computer who's only real task was to monitor the matrix and spit out the corresponding keystrokes in serial. You go serial you can get away with as little as two wires, though more is usually a good idea. I liken it to how Commodore handled disk drives, basically giving them their own 6502 processor.

To that end the original PC keyboard had a Intel 8048 microcontroller inside it.... AND put a 8042 on the other end as well to handle serial comms and matrix monitoring so the system CPU didn't have to waste time on it. As all the 8048 has to do is loop endlessly checking the matrix, the odds of it missing a keystroke are pretty damned slim.

Which is part of what makes the Junior's keyboard such a pain in the ass and part of what makes the junior slow -- they skipped the extra processor in the system and instead hook the non-maskable interrupt on the CPU to handle data from the keyboard. Also what makes the internal serial port on the Jr. screw up if you press a key while trying to receive data.

Having a separate processor on each end means cheaper cabling, more reliable transmission, and best of all no risk of missed keypresses unless you fill up the buffer. That last part is the real thing that made such methodology "win out" over all other methods.

Though it would have been nice if they just settled on a common format and specification like RS-232 for it.

deathshadow
August 30th, 2016, 08:02 PM
Alright, my version of this is coming alone nicely, in a measure 50 times cut once kind of way. Got my teensy 3.0 based recording logic probe up to 0.1567μs accuracy, and was able to record from the one PC/XT capable keyboard I have. It's an old "Micro Express" XT/AT switchable I've had forever, used it on real 5150's and 5160's as well as a slew of clones, so I'm pretty confident that it's numbers are compatible.

It's not that I don't trust other people's timing charts, I just don't trust other people's timing charts.

This is the result:
http://www.deathshadow.com/AT2XT/microExpressTimings.html

The above chart is triggered by the clock hitting the teensy's interrupt. (I'm using pin 3 for clock and 4 for data). In the process of responding to that interrupt and setting up to record the elapsed time and data, I did the math on the generated assembly and I'm losing somewhere around 24 or 25 microseconds -- that is SPOT ON with that little sliver of the data line holding low for 3μs... There's our ~27..30 accounting for the rise/fall times.

What I'm coming up with is 92μs per cycle... there's some interesting bits in there too. It looks like they wait a lot longer before flipping the data bit than the graphs I've seen online for this, it almost appears like they wait half the down-pulse on the clock. I think I'm going to go with that, using a full 15ms before I flip to make sure the client sees the change.

Because the code likely makes you lose two or three μs over the write operation, (possibly more if the AT interrupt fires while outputting to XT) I'm going to round down to 30 for the troughs and 60 for the peaks, so 90 total.

So my new timing plan is:

HEADER
pull clock low, wait 15Ás
pull data high, wait 105Ás
pull clock high, wait 60Ás

DATA (repeat 8 times)
pull clock low, wait 15Ás
set data, wait 15Ás
pull clock high, wait 60Ás

END OF DATA
clock low, wait 15Ás
data low, wait 15Ás
clock high, wait 990Ás, then wait until new data available.

Figure in the rise/fall times, and that should be near identical to what this keyboard is generating, and again I've never had this one not work anywhere.

Though what I'm going to do is tie the outputs of the little Atmega 328 DCCDuino Nano I'm using to the Teensy and adjust the timings until they match what the real keyboard is outputting as close as possible. I think that's a reasonable approach for the next step. I also set up a little test to try and find the shortest delay between key-up and key-down as I figure that's the most likely way to find the minimum delay between messages, and the shortest time I found was around 910μs -- below the 1ms used in most people's code... I'm gonna call it 990μs to be the same as ten full data pulses. I think that makes sense -- make the delay between messages the same length as a full message. I may store micros globally and put the wait at the START of the next byte, so that if other processing is going on the processor doesn't waste time with it's thumb up it's proverbial arse waiting around for nothing.

I'm going to be doing the same thing analysis on the AT keyboards input routine just to double-check what everything online says. Nope, not paranoid and taking my time at ALL.

Though... anyone care to donate a model F to the project? Eh, might see if I can pick one up on the cheap somewhere. That or anyone out there with a model F have a Teensy 3.0? I can send you the code.

I'm probably also going to enhance that chart page with some JavaScript to let you alternate between screen width and full sample per pixel data... actually, this is 2016, for modern browsers I shouldn't even need JavaScript for that.

-- edit -- Yeah, didn't need JavaScript for that at all. Won't work in IE8/earlier, but really I could give a rat's ass at this point. In proper modern browsers, you click the full size option and instead of scaling to screen width, it will give you one pixel per sample resolution.

deathshadow
September 1st, 2016, 07:09 PM
Ok, going through all the available codebases I could find on the topic, all the documentation I could find, and pulling genuine timings from actual keyboards with my little home-brew recording logic probe (since it's on/off TTL only I won't call it an oscilloscope) I see a number of worrisome flaws and false assumptions.

1) Timing info seems to be way off. biggest seems to be that real devices wait until HALFWAY through the clock trough to flip the data line... but also the negative clock start on XT is the same width as a normal clock.

2) No buffering... if the interrupt for the AT data fires while the XT is being written to, it will overwrite the variable storing the current keycode!

3) failure to leverage internal timer and use of delayMicroseconds leading to wildly inaccurate timing! Atmega chips have a perfectly good timer interrupt -- HELL, it has THREE of them!... Why not use that to service the XT output side so you have accurate responses? At the 16mhz default that's commonplace now, we could use 240 clock ticks to give us a nice 15μs accuracy... half our clock trough, a quarter our clock peak. Removes worries about execution time OR the interrupts screwing with our bit-banging timings.

4) I've yet to see anyone reprogram the keyboard to use character set 1. Everyone seems to be wasting time on lookup tables or nested conditionals when AT class keyboards -- ALL OF THEM -- have a "mode 1" that will output PC/XT instead of AT scancodes! Only mode 2, aka "extended XT" is dropped on newer devices, modes 1 and 3 (3 is the default) are retained on pretty much all of them I can find.

So... my measure 30 times cut once proceeds apace. Testing the AT side right now to make sure I can indeed set mode 1, acknowledge messages properly instead of leaving the keyboard to timeout, and "play" with other values as needed.

Running two interrupts at once should be fun too... as will the use of two circular buffers, one for commands to be sent to the AT keyboard, one for scancodes to be sent to the XT side.

I'm also playing with the idea of storing the XT scancodes bitwise reversed to save time. Since I have to



if (data & 1) xt_data_high; else xt_data_low;
data >> = 1;


why not just:


if (data & 0x80) xt_data_high; else xt_data_low;
data << = 1;


instead, that way when storing the data I can just:



data = (data << 1) | bit;


instead of the painfully stupid:


data = (data >> 1) | (bit << 7);


... or worse, trying to index it as I've seen some people do:


data |= bit << count;


Though doing so would mean I'd need my AT command codes to be bitwise inverted too. Gah, I hate how atmega processors lack a proper bitwise rotate or shift... what they call ROL and ROR (their only two "shifts") are what us x86 folks would call RCR and RCL... rotate through carry. There is no proper shift, so to shift you have to CLC first... and there is no proper rotate so you have to try and set the carry flag to the low bit first... but I'm one of those guys who thinks RISC can take a sugar frosted **** off the end of my ****!

Hell, still bugs me most high level languages don't even have the concept of them being three different things! I'd settle for two!

But what's the old joke? CISC was created for people who write programs, RISC was created for people who write compilers?

Ever since I started playing with ARM and AVR, the more I've become convinced that's not a joke. Kind of like every time I play with C and Unix, the more I'm convinced the joke about them being a hoax isn't a joke either.

irix
September 1st, 2016, 10:04 PM
So every AT keyboard has an XT mode, but it lacks of a switch or something to use that mode, is that what you say?

deathshadow
September 1st, 2016, 10:50 PM
So every AT keyboard has an XT mode, but it lacks of a switch or something to use that mode, is that what you say?

It... half has the mode. It can be programmed to output the correct scancodes, but it still does so using the AT wiring and transmission protocol. All mode 1 does is change what scancodes it spits out, not how it spits them out.

On the keyboards that have the switch, they likely force the keyboard into mode 1 for the scancodes, and then use the different output protocol as well. Setting mode 1 from software doesn't change the AT protocol itself -- it's still 11 bits bidirectional with release being a prefixed command code instead of stored on bit 7.

Still, setting that mode will GREATLY simplify the process and reduce the memory footprint, as the whole lookup table thing can be pitched.

irix
September 2nd, 2016, 03:35 AM
As told in a previous post I do have a few Samsung keyboards (they look like IBM). They're with a simple membrame though, but they do have the full Intel cpu on board and a hole where the AT/XT switch could have been. Also on the small pcb in the keyboard there are holes and lines where the switch would have been.
They have 5p regular DIN though, not ps/2. Also when I connect the 2 empty pins (where the switch would have been) it doesn't directly work, but it may change the mode though. Don't know how to test that.

nc_mike
September 2nd, 2016, 05:29 AM
Seems like a fun project/challenge, but it it is for a real IBM 5160 PC/XT, wouldn't it just be easier to pop in a pair of BIOS chips with the 5/86 bios or flash one, which provided native PS/2 keyboard support rather than go through all of this, or just get a 5160 MOB with the right BIOS?

Mike

Great Hierophant
September 2nd, 2016, 07:11 AM
I believe that the 1000's protocol is almost identical to the XT protocol except for the timing and the use of a stop bit as opposed to a start bit. Have you ever looked into it deathshadow?

I do not believe that the 5160 can really handle the 11-bit AT/PS2 protocol. The 1986 IBM PC/XT Technical Reference indicates it uses the 9-bit XT protocol, so you still need a keyboard that can switch between the two.

deathshadow
September 2nd, 2016, 11:05 AM
Also when I connect the 2 empty pins (where the switch would have been) it doesn't directly work, but it may change the mode though. Don't know how to test that.

It probably does not -- when they omit the switch they usually cheap out with a smaller ROM, leaving off the code required to output the 10 bit (that people call 9 bit) monodirectional XT data format instead of the 11 bit AT format.


but it it is for a real IBM 5160 PC/XT, wouldn't it just be easier to pop in a pair of BIOS chips with the 5/86 bios or flash one, which provided native PS/2 keyboard support rather than go through all of this, or just get a 5160 MOB with the right BIOS?

1) Pretty sure a 286 BIOS isn't gonna fly on a 8088.

2) PC/XT's hardware interface is monodirectional -- System side is read only. AT interface is bidirectional and requires an ACK bit to be set to function properly. The two aren't just different scancodes and bit encoding, the hardware interfaces themselves are fundamentally different!

Pretty sure the PC/XT hardware would be incapable of even acknowledging the AT keyboard's power on and test ok message, much less setting the mode or ack bits.

3) AT's have a intel 8048 processor dedicated to handling the keyboard input. The PC/XT uses a shift-in parallel out chip along with a 8255. Not even close to the same hardware.

So no, it would not be "easier" -- much less how is burning a ROM and installing it easier?!? Much less that's ASSUMING you know what you are plugging it into, which I can't guarantee since I've got three XT clones here and one keyboard -- and christmas only knows what the clones did or what ROM's they'd be compatible with.

I swear, every time someone uses the word "easier" I start to think I have an entirely different definition of the word from the rest of the world!


I believe that the 1000's protocol is almost identical to the XT protocol except for the timing and the use of a stop bit as opposed to a start bit. Have you ever looked into it deathshadow?

It's on my to-do list. Since I've got a perfectly good working 1000SX keyboard here, I'll be able to investigate what it REALLY outputs since again, the data I'm finding online doesn't make sense and/or doesn't seem to line up with reality... though the most complete and accurate data (which is still pretty inaccurate) seems to be about going the other direction; from every other type of keyboard to AT which... doesn't seem to me like the most useful of things to do.

I'm also thinking on seeing what I can figure out about the Junior... if any computer ever desperately needed some lovin' from a model M...

But... PC/XT first. I'm actually having trouble reading from the AT interface side of things reliably. None of the code I have that claims to work is doing a damned thing, nor is my own code... I can read it fine with my little homebrew scope, but in terms of translating it into useful data it goes tits up to the point of the DCCduino going off to never never land.

Gonna try reading it using a teensy instead. Be a laugh if my one dollar Arduino nano knockoff is the actual stumbling block. You'd THINK Atmega 328p is Atmega 328p... but NO... (and yes, it's a newer one with the CH430, not the old FTDI knockoff that the driver updates now brick!)

nc_mike
September 2nd, 2016, 07:10 PM
What do you mean "Pretty sure a 286 BIOS isn't gonna fly on a 8088"? I've got the 5/86 BIOS in my IBM 5160 PC/XT and use a PS/2 keyboard - an IBM Model-M with the optional 5 pin DIN connector instead of the PS/2 connector, attached to my IBM PC/XT system and it works beautifully with no other mods needed. The 5/86 BIOS was the last native IBM BIOS update for PC/XTs that added support for 101-key PS/2 style keyboards. I don't understand your statement - can you please clarify?

NC_Mike

krebizfan
September 2nd, 2016, 08:34 PM
What do you mean "Pretty sure a 286 BIOS isn't gonna fly on a 8088"? I've got the 5/86 BIOS in my IBM 5160 PC/XT and use a PS/2 keyboard - an IBM Model-M with the optional 5 pin DIN connector instead of the PS/2 connector, attached to my IBM PC/XT system and it works beautifully with no other mods needed. The 5/86 BIOS was the last native IBM BIOS update for PC/XTs that added support for 101-key PS/2 style keyboards. I don't understand your statement - can you please clarify?

NC_Mike

You are one of the lucky people to get an autosensing Model M which works with both XT and AT systems. Not all Model M keyboards do that.

irix
September 3rd, 2016, 04:12 AM
I have a Model M as well, but never tested it on an XT. How can I know if it does autosense? Just test, or can you see it (type number or something)

nc_mike
September 3rd, 2016, 04:45 AM
It has nothing to do with autosenese, that is nonsense. Either your XT BIOS is late enough to support 101-key keyboards or or isn't. I have many IBM Model-M keyboards - they all work. You have to make sure you use a 5 pin DIN cord, and not use a PS/2 to 5-pin DIN adapter.

Mike

irix
September 3rd, 2016, 04:59 AM
It has nothing to do with autosenese, that is nonsense. Either your XT BIOS is late enough to support 101-key keyboards or or isn't. I have many IBM Model-M keyboards - they all work. You have to make sure you use a 5 pin DIN cord, and not use a PS/2 to 5-pin DIN adapter.

Mike

It's a Tulip Compact, they are very specific about keyboards, so I don't think it will work. I don't have a 5p DIN cord, only PS/2.

nc_mike
September 3rd, 2016, 06:04 AM
It's a Tulip Compact, they are very specific about keyboards, so I don't think it will work. I don't have a 5pin DIN cord, only PS/2.

Right, that was my point. If its for a regular IBM 5160 its easy to check for the 5/86 BIOS. If its a clone then you'd have to find out some other way to check the BIOS or you might run into such limitations. A lot of people, even with the right BIOS, try using a PS/2 to 5-pin DIN adapter, and when it doesn't work they give up, not realizing they can't use an adapter. Many of the Model-M keyboard have removable cords. For my XTs I just picked up a $5 regular 5-pin din cord and after that it worked; it didn't with the adapter.

Regards,
Mike

irix
September 4th, 2016, 12:42 AM
Somebody did build a PIC based converter, maybe that will help in making the Arduino solution happen?

This is the link: http://www.vcfed.org/forum/showthread.php?26426-AT2XT-keyboard-converter

deathshadow
September 4th, 2016, 04:03 PM
I have NEVER heard of an AT keyboard working on a PC/XT regardless of BIOS unless it had a switch on it to do so. That's definitely news to me... Though it might be auto-sense related to that endless string of 0xAA with the parity wrong that times out eventually. If that fails to get a "resend" message could that be the trigger?

... and since the PS/2 to AT converter cable is pin to pin other than dropping the (officially "Not used") reset line, just how would that NOT work exactly?!? It's four lines with NO changes, no circuitry. Data, Clock, Ground, +5v. That's it. It's the same protocol on the same wiring, just different plugs!

... and @nc_mike, I think you lost me at 5/86... With an AT keyboard I assumed you meant AT/later BIOS... is the 86 the year number or something? While I worked on a few early PC's and XT's, I was really more of a clone guy. Sounds like something even LESS likely to work on a clone. I never even heard of an XT BIOS that supports 101 key keyboards, so I assumed you were talking about an AT class or later one!

Also not sure how that would even work given the hardware wouldn't even EXIST on the system side to handle AT signalling.

Side note, figured out why I was having trouble reading the AT keyboard, it wasn't the keyboard, or my wiring, or my code... it was another broke-ass crappy USB cable between the Arduino and the computer I was using to debug. Laughably it was the cable from a real Arduino that failed... the cheap cable that came for free with the knockoff DCCDuino Nano? Works flawless.

I swear, one of these days I'm going to track down the creators of USB and SATA plugs and pull a Jay and Silent Bob... How many people wanna kick some ass? I'm one step away from stripping the ends off all my USB cables and device jacks and replacing them with old-fashioned 4 pin DIN! At least that would be reliable and not break if you accidentally sneeze in the same room...

bhtooefr
September 4th, 2016, 04:48 PM
Early IBM Model Ms actually supported the XT explicitly (and IBM even sold a variant without the LEDs specifically for XTs), and they would (on the latest XT BIOS) switch to XT mode automatically.

deathshadow
September 4th, 2016, 06:16 PM
Early IBM Model Ms actually supported the XT explicitly (and IBM even sold a variant without the LEDs specifically for XTs), and they would (on the latest XT BIOS) switch to XT mode automatically.
Very interesting, and explains why all of them -- even the clones -- still have vestiges of that in the form of the "mode 1" character set, and the original model M's "Mode 2" which was XT scancodes but with less remap and the extended 101 support.

Mode 1 did remap and multiple keystrokes to make a 101 act like a 89.

It would have to mostly be a keyboard side thing -- interesting they had a auto-detect of some sort (and it would require detection given the system side hardware difference) that clone makers didn't copy. On the software side it's not exactly rocket science, and it calls into question why so many clone keyboards had the switch at all.

Well, except if you didn't have that latest ROM or were on a clone that wouldn't take it... then the mechanical switch makes sense.

nc_mike
September 5th, 2016, 02:37 AM
I have NEVER heard of an AT keyboard working on a PC/XT regardless of BIOS unless it had a switch on it to do so. That's definitely news to me...

Wellllll...that's what we're all here for - to help and inform each other :-)

https://pcpartpicker.com/b/krYrxr

32976

irix
October 12th, 2016, 01:50 AM
I was quite busy with other projects, but I'm really interested in completing this one. Did anybody get any further on this?

zinamo
November 6th, 2016, 11:09 AM
Wellllll...that's what we're all here for - to help and inform each other :-)

https://pcpartpicker.com/b/krYrxr

32976

Wow what an amazing machine!

ACAutomates
October 14th, 2017, 12:38 PM
Damn! I built the adapter with a DCCDuino Arduino Nano clone and I get the double keys problem, with every possible combinations of programs listed here...
Finally, is there something to do with it?
I'm really hoping to find a PS2 to XT adapter

kesrut
November 3rd, 2017, 12:57 AM
Hello,

I am original author of adapter ..
I released new version of adapter for Arduino: https://github.com/kesrut/pcxtkbd

Which fixes many problems.. Hope you will enjoy!

Report if you have any problems!

www.kestutis.org

shift838
June 14th, 2018, 12:37 PM
would this new code work on a Teensy 3.2 or will it need converted?

PePe-fr
July 24th, 2018, 12:35 PM
I made a test this afternoon (same problem as many : one old XT with monitor, but without keyboard).
I'll perhaps look after a real XT keyboard later but for now I've got a good PS/2 keyboard which works on my XT !

Thanks.

PS : be aware that the latest code has some changes compared to older version. Il particular, the arduino pins for the XT clock and the PS/2 clock have been inverted.
If you just use the schematic of the first post along with the lastest program but without inverting those pins, it won't work.

PePe-fr
August 8th, 2018, 12:09 AM
Hi,

I use a PS/2 keyboard with my XT computer, with an Arduino Micro interface module (fun to use a board as interface which is much faster than the computer itself...).

I have suspicions on eventual crashes caused by this layout... I need further testing, but I have quite frequent hangouts of the PC, particularily when I use the french keyboard layout.
I'll try to stay in QWERTY and test... anyone has stability issues with this setup ? I also observed that when keypresses are very fast (mostly in games) the arduino system gets completely lost.

Otherwise I'll perhaps try this system : http://www.vcfed.org/forum/showthread.php?26426-AT2XT-keyboard-converter

It should be much more stable... but there's "logistics" problems for me as I don't own an universal programmer and I don't really want to buy one just for this little project.

Ruud
August 8th, 2018, 11:22 AM
PS : be aware that the latest code has some changes compared to older version. Il particular, the arduino pins for the XT clock and the PS/2 clock have been inverted.
If you just use the schematic of the first post along with the lastest program but without inverting those pins, it won't work.
What do you mean with "inverted"? Do I have to place inverter gates like 74LS04 or 74LS14 between the pins of the Arduino and the connectors?

PePe-fr
August 8th, 2018, 12:50 PM
No !

I mean that pin 3 & 4 are inverted between the schematic and the latest arduino program. ;)

Ruud
August 9th, 2018, 06:15 AM
I mean that pin 3 & 4 are inverted between the schematic and the latest arduino program. ;)
Ah, je comprend. En Anglais c'est "swapped".
I understand. In English it is "swapped" .
:)