Image Map Image Map
Page 2 of 6 FirstFirst 123456 LastLast
Results 11 to 20 of 57

Thread: Arduino PS2 to XT converter

  1. Default

    I found this link after looking at the original github project. I needed this for an old Z80 computer that uses an IBM PC keyboard which I don't have on hand. I programmed my Arduino UNO (copy) but all I saw were CRs when pressing any keys.

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

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

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

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

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

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

    Code:
    static unsigned char code;
    
    void loop()
    {
     unsigned char x;
    
     // gets a key code and sends it over and over until new key pressed
     if ((x = _read()) != 0)
        {
         code = x;
    #ifdef DEBUG
         Serial.println(code, HEX);
    #endif
        }
     else
        _write(code);
    }

  2. #12
    Join Date
    Aug 2016
    Location
    Netherlands
    Posts
    22

    Default

    I've built this Arduino-based solution too (with the help of this topic), but I'm having the double key problem as well.
    Upping the delay time doesn't work for me either, what else can I try? I have no experience in programming the Arduino at all.
    Last edited by irix; August 24th, 2016 at 01:41 AM.

  3. #13

    Default

    I believe the double key is because the ps2 keyboard sends the key twice.
    Once to for key down and once for key up.
    I don't recall how the XT/AT deals with that.
    I don't like the way they are hard pulling the lines to high for the ps2 side.
    On code I wrote for ps2, I did the following:

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

    and

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

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

    Dwight
    Last edited by Dwight Elvey; August 24th, 2016 at 01:40 PM.

  4. #14
    Join Date
    Jan 2007
    Location
    Pacific Northwest, USA
    Posts
    33,716
    Blog Entries
    18

    Default

    Yup scancode for key down then 0xf0, scancode for key up.

  5. #15
    Join Date
    Aug 2016
    Location
    Netherlands
    Posts
    22

    Default

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

  6. #16

    Default

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

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

    Code:
    void _write(unsigned char value) {
    	while (digitalRead(xt_clk) != HIGH);
    	digitalWrite(xt_clk, LOW);
    	delayMicrosections(5);
    	digitalWrite(xt_data, HIGH);
    	delayMicroseconds(110)
    	digitalWrite(xt_clk, HIGH);
    	delayMicroseconds(75);
    	for (byte i = 0; i < 8; i++) {
    		digitalWrite(xt_clk, LOW);
    		delayMicroseconds(5);
    		digitalWrite(xt_data, value & 0x01);
    		delayMicroseconds(20);
    		digitalWrite(xt_clk, HIGH);
    		delayMicroseconds(70);
    		// Of course we're ACTUALLY spending longer than that :(
    		value >>= 1; //  I forget, do sketches support >>=? They SHOULD...
    	}
    	digitalWrite(xt_clk, LOW);
    	delayMicroseconds(5);
    	digitalWrite(xt_data, LOW);
    	delayMicroseconds(20);
    	digitalWrite(xt_clk, HIGH);
    	delayMicroseconds(1005);
    }
    Should work... unless I'm taking the timing charts too literally... though the more I look at it, the more I think this should be taking 0.95ms, not 0.975... since it should be a consistent duty cycle even with the starting clock pulse inverted.
    Last edited by deathshadow; August 25th, 2016 at 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.
    CUTCODEDOWN.COM

  7. #17

    Default

    -- 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 26th, 2016 at 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.
    CUTCODEDOWN.COM

  8. #18
    Join Date
    Aug 2016
    Location
    Netherlands
    Posts
    22

    Default

    I use a compatible Arduino UNO, like in the original opening post of this topic.
    This is the current code I run, which is the original code from GitHub, but changed according to the different changes made in this topic, so here's the full code:

    Code:
    #define ps_clk 3 /* must be interrupt pin (0 interrupt) */
    #define ps_data 4
    #define xt_clk 2
    #define xt_data 5
    
    #define START 1
    #define STOP 3
    #define PARITY 4
    #define INIT 5
    #define GROUP1_CNT 85
    #define BREAK_GRP1 0xF0
    #define MAKE_GRP2 0xE0
    #undef DEBUG
    
    byte cycles = 0 ; 
    unsigned char value = 0 ; 
    byte state = INIT ; 
    byte got_data = 0 ; 
    
    struct ps2_struct_group
    {
      unsigned char character ; 
      unsigned char make ;   
      unsigned is_char ; 
      unsigned char xt_make ; 
    } ; 
    
    typedef struct ps2_struct_group ps2_group1_type ; 
    typedef struct ps2_struct_group ps2_group2_type ; 
    
    ps2_group1_type ps2_group1[] =
    {
      {'a', 0x1C, 1, 0x1E},
      {'b', 0x32, 1, 0x30}, 
      {'c', 0x21, 1, 0x2E}, 
      {'d', 0x23, 1, 0x20},
      {'e', 0x24, 1, 0x12},
      {'f', 0x2B, 1, 0x21}, 
      {'g', 0x34, 1, 0x22},
      {'h', 0x33, 1, 0x23}, 
      {'i', 0x43, 1, 0x17},
      {'j', 0x3B, 1, 0x24}, 
      {'k', 0x42, 1, 0x25}, 
      {'l', 0x4B, 1, 0x26},
      {'m', 0x3A, 1, 0x32}, 
      {'n', 0x31, 1, 0x31},
      {'o', 0x44, 1, 0x18}, 
      {'p', 0x4D, 1, 0x19},
      {'q', 0x15, 1, 0x10},
      {'r', 0x2D, 1, 0x13},
      {'s', 0x1B, 1, 0x1F},
      {'t', 0x2C, 1, 0x14}, 
      {'u', 0x3C, 1, 0x16}, 
      {'v', 0x2A, 1, 0x2F}, 
      {'w', 0x1D, 1, 0x11},
      {'x', 0x22, 1, 0x2D},
      {'y', 0x35, 1, 0x15},
      {'z', 0x1A, 1, 0x2C}, 
      {'0', 0x45, 1, 0x0B},
      {'1', 0x16, 1, 0x02},
      {'2', 0x1E, 1, 0x03},
      {'3', 0x26, 1, 0x04},
      {'4', 0x25, 1, 0x05},
      {'5', 0x2E, 1, 0x06},
      {'6', 0x36, 1, 0x07},
      {'7', 0x3D, 1, 0x08},
      {'8', 0x3E, 1, 0x09},
      {'9', 0x46, 1, 0x0A}, 
      {'`', 0x0E, 1, 0x29},
      {'-', 0x4E, 1, 0x0C},
      {'=', 0x55, 1, 0x0D},
      {'\\', 0x5D, 1, 0x2B},
      {'\b', 0x66, 0, 0x0E}, // backsapce
      {' ', 0x29, 1, 0x39}, // space
      {'\t', 0x0D, 0, 0x0F}, // tab
      {' ', 0x58, 0, 0x3A}, // caps
      {' ', 0x12, 0, 0x2A}, // left shift
      {' ', 0x14, 0, 0x1D}, // left ctrl
      {' ', 0x11, 0, 0x38}, // left alt
      {' ', 0x59, 0, 0x36}, // right shift
      {'\n', 0x5A, 1, 0x1C}, // enter
      {' ', 0x76, 0, 0x01}, // esc
      {' ', 0x05, 0, 0x3B}, // F1
      {' ', 0x06, 0, 0x3C}, // F2
      {' ', 0x04, 0, 0x3D}, // F3
      {' ', 0x0C, 0, 0x3E}, // F4
      {' ', 0x03, 0, 0x3F}, // F5
      {' ', 0x0B, 0, 0x40}, // F6
      {' ', 0x83, 0, 0x41}, // F7
      {' ', 0x0A, 0, 0x42}, // F8
      {' ', 0x01, 0, 0x43}, // f9
      {' ', 0x09, 0, 0x44}, // f10
      {' ', 0x78, 0, 0x57}, // f11
      {' ', 0x07, 0, 0x58}, // f12
      {' ', 0x7E, 0, 0x46}, // SCROLL
      {'[', 0x54, 1, 0x1A},
      {' ', 0x77, 0, 0x45}, // Num Lock
      {'*', 0x7C, 1, 0x37}, // Keypad *
      {'-', 0x7B, 1, 0x4A}, // Keypad -
      {'+', 0x79, 1, 0x4E}, // Keypad +
      {'.', 0x71, 1, 0x53}, // Keypad .
      {'0', 0x70, 1, 0x52}, // Keypad 0
      {'1', 0x69, 1, 0x4F}, // Keypad 1
      {'2', 0x72, 1, 0x50}, // Keypad 2
      {'3', 0x7A, 1, 0x51}, // Keypad 3
      {'4', 0x6B, 1, 0x4B}, // Keypad 4
      {'5', 0x73, 1, 0x4C}, // Keypad 5
      {'6', 0x74, 1, 0x4D}, // Keypad 6
      {'7', 0x6C, 1, 0x47}, // Keypad 7
      {'8', 0x75, 1, 0x48}, // Keypad 8
      {'9', 0x7D, 1, 0x49}, // Keypad 9
      {']', 0x5B, 1, 0x1B},
      {';', 0x4C, 1, 0x27}, 
      {'\'', 0x52, 1, 0x28},
      {',', 0x41, 1, 0x33},
      {'.', 0x49, 1, 0x34},
      {'/', 0x4A, 1, 0x35}, 
    } ; 
    
    ps2_group2_type ps2_group2[] =
    {
      {' ', 0x5B, 0, 0x1F}, // left gui
      {' ', 0x1D, 0, 0x14}, // right ctrl
      {' ', 0x5C, 0, 0x27}, // right gui
      {' ', 0x38, 0, 0x11}, // right alt
      {' ', 0x5D, 0, 0x2F}, // apps
      {' ', 0x52, 0, 0x70}, // insert
      {' ', 0x47, 0, 0x6C}, // home
      {' ', 0x49, 0, 0x7D}, // page up
      {' ', 0x53, 0, 0x71}, // delete
      {' ', 0x4F, 0, 0x69}, // end
      {' ', 0x51, 0, 0x7A}, // page down
      {' ', 0x48, 0, 0x75}, // u arrow
      {' ', 0x4B, 0, 0x6B}, // l arrow
      {' ', 0x50, 0, 0x72}, // d arrow
      {' ', 0x4D, 0, 0x74}, // r arrow
      {' ', 0x1C, 0, 0x5A}, // kp en
    } ;
    
    void setup() 
    {
    #ifdef DEBUG
      Serial.begin(9600) ; 
    #endif
      pinMode(ps_clk, INPUT) ;
      pinMode(ps_data,INPUT) ;  
      pinMode(xt_clk, OUTPUT) ; 
      pinMode(xt_data, OUTPUT) ; 
      digitalWrite(xt_clk, HIGH) ; 
      digitalWrite(xt_data, HIGH) ; 
      attachInterrupt(0, clock, FALLING);
    }
    
    unsigned char _read()
    {
       if (got_data)
       {
        got_data = 0 ; 
        return value ; 
      } 
      return 0 ; 
    }
    
    void _write(unsigned char value)
    { 
       while (digitalRead(xt_clk) != HIGH) ; 
       unsigned char bits[8] ;
       byte p = 0 ; 
       byte j = 0 ;
       for (j=0 ; j < 8; j++)
       {
         if (value & 1) bits[j] = 1 ;
         else bits[j] = 0 ; 
         value = value >> 1 ; 
       }
       digitalWrite(xt_clk, LOW) ; 
       digitalWrite(xt_data, HIGH) ; 
       delayMicroseconds(120) ; 
       digitalWrite(xt_clk, HIGH) ; 
       delayMicroseconds(66) ;
       digitalWrite(xt_clk, LOW) ; 
       delayMicroseconds(30) ;
       digitalWrite(xt_clk, HIGH) ; 
       byte i = 0 ; 
       for (i=0; i < 8; i++)
       {
          digitalWrite(xt_clk, HIGH) ; 
          digitalWrite(xt_data, bits[p]) ; 
          delayMicroseconds(95) ;
          digitalWrite(xt_clk, LOW) ;
          digitalWrite(xt_data, LOW) ; 
          p++ ; 
       } 
       digitalWrite(xt_clk, HIGH) ; 
       digitalWrite(xt_data, LOW) ;  
       delay(1) ;
    }
    
    byte i = 0 ;
    void loop() 
    {
      label_start:
      unsigned char code = _read() ; 
    #ifdef DEBUG
      if (code != 0) Serial.println(code, HEX) ; 
    #endif
      if (code == BREAK_GRP1)
      {
         delay(40) ; 
         unsigned char break_code = _read() ; 
         unsigned char i = 0 ; 
         while (i < GROUP1_CNT)
         {
           if (ps2_group1[i].make == break_code)
           { 
              pinMode(xt_clk, OUTPUT) ; 
              pinMode(xt_data, OUTPUT) ; 
              _write(ps2_group1[i].xt_make | 0x80) ;
              break ;
           }
           i++ ;
         }
        goto label_start;  
      }
      if (code != 0)
      {
        unsigned char i = 0 ; 
        while (i < GROUP1_CNT)
        {
          if (ps2_group1[i].make == code)
          {
    #ifdef DEBUG
             Serial.write(ps2_group1[i].character) ; 
    #endif
             _write(ps2_group1[i].xt_make) ;
             break ;
          }
        i++ ; 
       }
      }
      if (digitalRead(xt_clk) == LOW) // power-on self test
      {
        delay(10) ;
        _write(0xAA) ;
      }
    }
    
    void clock()
    {
       if (state == INIT)
       {
         if (digitalRead(ps_data) == LOW)
         {
           state = START ; 
           cycles = 0 ;
           got_data = 0 ;
           value = 0 ; 
           return ; 
         }
       }
       if (state == START)
       {
         value |= (digitalRead(ps_data) << cycles) ;
         cycles++ ; 
         if (cycles == 8) state = PARITY ;
         return ;  
       }
       if (state == PARITY)
       {
         state = STOP ; 
         return ; 
       }
       if (state == STOP)
       {
         if (digitalRead(ps_data) == HIGH)
         {
           state = INIT ; 
           got_data = 1 ; 
           return ; 
         }
       }  
    }
    It does work, but also gives a keypress on key up.

  9. #19

    Default

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

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

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

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

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

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

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

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

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

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

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

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

    I'd also be worried about the lack of any buffering -- type too fast or receive a multi-byte back to back, and you're gonna get interrupted/corruption halfway through processing.
    Last edited by deathshadow; August 26th, 2016 at 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.
    CUTCODEDOWN.COM

  10. #20

    Default

    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 26th, 2016 at 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.
    CUTCODEDOWN.COM

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •