Forum etiquette

Our mission ...

This forum is part of our mission to promote the preservation of vintage computers through education and outreach. (In real life we also run events and have a museum.) We encourage you to join us, participate, share your knowledge, and enjoy.

This forum has been around in this format for over 15 years. These rules and guidelines help us maintain a healthy and active community, and we moderate the forum to keep things on track. Please familiarize yourself with these rules and guidelines.

Remain civil and respectful

There are several hundred people who actively participate here. People come from all different backgrounds and will have different ways of seeing things. You will not agree with everything you read here. Back-and-forth discussions are fine but do not cross the line into rude or disrespectful behavior.

Conduct yourself as you would at any other place where people come together in person to discuss their hobby. If you wouldn't say something to somebody in person, then you probably should not be writing it here.

This should be obvious but, just in case: profanity, threats, slurs against any group (sexual, racial, gender, etc.) will not be tolerated.

Stay close to the original topic being discussed
  • If you are starting a new thread choose a reasonable sub-forum to start your thread. (If you choose incorrectly don't worry, we can fix that.)
  • If you are responding to a thread, stay on topic - the original poster was trying to achieve something. You can always start a new thread instead of potentially "hijacking" an existing thread.

Contribute something meaningful

To put things in engineering terms, we value a high signal to noise ratio. Coming here should not be a waste of time.
  • This is not a chat room. If you are taking less than 30 seconds to make a post then you are probably doing something wrong. A post should be on topic, clear, and contribute something meaningful to the discussion. If people read your posts and feel that their time as been wasted, they will stop reading your posts. Worse yet, they will stop visiting and we'll lose their experience and contributions.
  • Do not bump threads.
  • Do not "necro-post" unless you are following up to a specific person on a specific thread. And even then, that person may have moved on. Just start a new thread for your related topic.
  • Use the Private Message system for posts that are targeted at a specific person.

"PM Sent!" messages (or, how to use the Private Message system)

This forum has a private message feature that we want people to use for messages that are not of general interest to other members.

In short, if you are going to reply to a thread and that reply is targeted to a specific individual and not of interest to anybody else (either now or in the future) then send a private message instead.

Here are some obvious examples of when you should not reply to a thread and use the PM system instead:
  • "PM Sent!": Do not tell the rest of us that you sent a PM ... the forum software will tell the other person that they have a PM waiting.
  • "How much is shipping to ....": This is a very specific and directed question that is not of interest to anybody else.

Why do we have this policy? Sending a "PM Sent!" type message basically wastes everybody else's time by making them having to scroll past a post in a thread that looks to be updated, when the update is not meaningful. And the person you are sending the PM to will be notified by the forum software that they have a message waiting for them. Look up at the top near the right edge where it says 'Notifications' ... if you have a PM waiting, it will tell you there.

Copyright and other legal issues

We are here to discuss vintage computing, so discussing software, books, and other intellectual property that is on-topic is fine. We don't want people using these forums to discuss or enable copyright violations or other things that are against the law; whether you agree with the law or not is irrelevant. Do not use our resources for something that is legally or morally questionable.

Our discussions here generally fall under "fair use." Telling people how to pirate a software title is an example of something that is not allowable here.

Reporting problematic posts

If you see spam, a wildly off-topic post, or something abusive or illegal please report the thread by clicking on the "Report Post" icon. (It looks like an exclamation point in a triangle and it is available under every post.) This send a notification to all of the moderators, so somebody will see it and deal with it.

If you are unsure you may consider sending a private message to a moderator instead.

New user moderation

New users are directly moderated so that we can weed spammers out early. This means that for your first 10 posts you will have some delay before they are seen. We understand this can be disruptive to the flow of conversation and we try to keep up with our new user moderation duties to avoid undue inconvenience. Please do not make duplicate posts, extra posts to bump your post count, or ask the moderators to expedite this process; 10 moderated posts will go by quickly.

New users also have a smaller personal message inbox limit and are rate limited when sending PMs to other users.

Other suggestions
  • Use Google, books, or other definitive sources. There is a lot of information out there.
  • Don't make people guess at what you are trying to say; we are not mind readers. Be clear and concise.
  • Spelling and grammar are not rated, but they do make a post easier to read.
See more
See less

Arduino PS2 to XT converter

  • Filter
  • Time
  • Show
Clear All
new posts

    Originally posted by Dwight Elvey View Post
    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.

    Originally posted by irix View Post
    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);
    	digitalWrite(xt_data, HIGH);
    	digitalWrite(xt_clk, HIGH);
    	for (byte i = 0; i < 8; i++) {
    		digitalWrite(xt_clk, LOW);
    		digitalWrite(xt_data, value & 0x01);
    		digitalWrite(xt_clk, HIGH);
    		// Of course we're ACTUALLY spending longer than that :(
    		value >>= 1; //  I forget, do sketches support >>=? They SHOULD...
    	digitalWrite(xt_clk, LOW);
    	digitalWrite(xt_data, LOW);
    	digitalWrite(xt_clk, HIGH);
    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 by deathshadow; August 25, 2016, 11:31 PM.
    From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


      -- 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 by deathshadow; August 26, 2016, 01:40 AM.
      From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


        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) ; 
          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() 
          unsigned char code = _read() ; 
        #ifdef DEBUG
          if (code != 0) Serial.println(code, HEX) ; 
          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) ; 
                 _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:

          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:

          ... 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 by deathshadow; August 26, 2016, 03:22 AM.
          From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


            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 by deathshadow; August 26, 2016, 07:07 AM.
            From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


              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.


                Originally posted by irix View Post
                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.
                From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


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


                    Originally posted by deathshadow View Post
                    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.



                      Originally posted by irix View Post
                      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.

                      Originally posted by irix View Post
                      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.

                      Originally posted by irix View Post
                      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 -- 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 5s
                      pull data high, wait 90s (rest of cycle)
                      pull clock high, wait 95s (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 180s 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 5s
                      set data bit, wait 20s
                      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 95s for the either side to recognize the data... especially not if they can handle a 20s 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.
                      Last edited by deathshadow; August 27, 2016, 10:59 PM.
                      From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


                        Originally posted by inotarobot View Post
                        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.

                        Originally posted by inotarobot View Post
                        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!
                        From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


                          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.


                            Originally posted by irix View Post
                            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.
                            From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


                              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:

                              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:

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

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

                              END OF DATA
                              clock low, wait 15s
                              data low, wait 15s
                              clock high, wait 990s, 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.
                              Last edited by deathshadow; August 30, 2016, 08:22 PM.
                              From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.


                                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.
                                From time to time the accessibility of a website must be refreshed with the blood of owners and designers. It is its natural manure.