Keyboard input for Particle

@ScruffR,

I am looking to implement via the USB of (PS/2) keyboard input. There are several threads on this in the community but these examples are focused on mouse support or mouse and keyboard support. I notice these use PS2communication framework which is your work? - are you aware of a simple keyboard input via the core USB example? Otherwise I am not sure how to connect a keyboard to the Core device - the .h file refers to using D0 and D1 - I don’t believe these are being used the same way as the non power/ground pins on a USB so I could not just wire up a USB socket to the Core pins? Many Thanks

@armor, I’d be happy to help.

I’ve changed your question into a seperate thread to keep things wrapped together :wink:

To start with, I don’t quite understand:

  • Do you want to connect a USB keyboard to the USB port of the Core?
    This would not work, due to lacking USB host support on it.
  • Or do you want to use a PS/2 keyboard (USB with legacy support will do too)?
  • And do you want to forward the keyboard input to a computer or not?

If you would like a different/better fitting topic title, just let me know, I’ll change it.

Thank you for moving this to a new thread.

I did want to connect a USB keyboard to the USB port of the Core - that approach is out because of the lack of USB host support on the Core.

I do not want to forward the keyboard input to another computer - the keyboard input is required to enable faster input of wifi credentials and device name as part of local user management of the Core.

I am investigating using a IR remote keypad but also wanted to try a plug in keyboard.

Excuse my ignorance here - the PS/2 keyboard is something differ from a USB keyboard more than just the plug on the end of the connecting wire? This might a better solution as I would want other USB devices being inserted so using a PS/2 socket - which aren’t that common these days would work. IS this what you meant by ‘USB with legacy support will do too’?

It is true that USB is very different to PS/2, but a lot of (not too fancy) USB keyboards/mice do come with a legacy mode.
If they realize that after power up no USB enumeration is taking place, they assume to be connected to an old PC and switch into PS/2 mode.

So if this would be good for you, there shouldn’t be to much trouble in using my PS2Communication lib.
The hardest bit would be to provide scancode translation for your keyboard layout.

On the hand, if you only want this for “one time” credentials input, it’s a sledgehammer on a nut :wink:
For this CLI or serial might be just as usable and a lot easier.


Just a little sketch for you to play with, if you got a PS/2 capable keyboard.
I got a USB socket with female jumper connectors out of an old computer to connect a logitech wireless receiver to the Core and it works quite well :wink:

// This #include statement was automatically added by the Spark IDE.
#include "PS2Communication/PS2Communication.h"

#pragma SPARK_NO_PREPROCESSOR
#include "application.h"

SYSTEM_MODE(SEMI_AUTOMATIC);

/**********************************************************************************
 * Pin mapping for USB connector
 * 
 * D0  ... WHITE
 * D1  ... GREEN
 * Vin ... RED
 * GND ... BLACK
 *
 **********************************************************************************/
 
#define PARTICLE_BUILD_IDE      // this would be nice to be provided by Particle ;-)
#if defined(PARTICLE_BUILD_IDE)
#include "PS2Communication/PS2Communication.h"
#else
#define noSPARK_USB_MOUSE    // this should be defined externally in USB HID mode
#include "PS2Communication.h"
#endif

#define REPEATTIME     25

#define ps2_ACK              0xFA  // command was acknowledged
#define ps2_ERROR            0xFC  // command was not acknowledged

#define ps2kbd_RESET         0xFF  
#define ps2kbd_RESEND        0xFE  // Keyboard responds by resending the last-sent byte
#define ps2kbd_SETKEYMK      0xFD  // *Disable break codes and typematic repeat for specified keys
#define ps2kbd_SETKEYMKBK    0xFC  // *Disables typematic repeat for specified keys
#define ps2kbd_SETKEYTPMATIC 0xFB  // *Disable break codes for specified keys
#define ps2kbd_RESETALL      0xFA  // *Sets all keys to generate scan codes on make, break, and typematic repeat
#define ps2kbd_SETALLMK      0xF9  // *Disable break codes and typematic repeat for all keys
#define ps2kbd_SETALLMKBK    0xF8  // *Disable typematic repeat for all keys
#define ps2kbd_SETALLTPMC    0xF7  // *Disable break codes for all keys
#define ps2kbd_SETDFT        0xF6  // Load default typematic rate/delay (10.9cps / 500ms), key types (all keys typematic/make/break), and scan code set (2)
#define ps2kbd_DISABLE       0xF5  // Keyboard stops scanning, loads default values
#define ps2kbd_ENABLE        0xF4  // Re-enables keyboard after disable
#define ps2kbd_SETTPMCDELAY  0xF3  // Set typematic rate and delay (see table at http://www.computer-engineering.org/ps2keyboard/)
#define ps2kbd_READID        0xF2  // *Keyboard responds by sending a two-byte device ID of 0xAB, 0x83
#define ps2kbd_SETSCANSET    0xF0  // *Needs one argument byte from host 0x01, 0x02, 0x03 - if argument is 0x00, after "ack" sends current scan code set
#define ps2kbd_ECHO          0xEE  // The keyboard responds with "Echo" (0xEE).
#define ps2kbd_SETLEDS       0xED  // Needs one argument byte (binary 0 0 0 0 0 Caps Num Scroll)
                                   // *) Originally available in PS/2 keyboards only.
// Table of scan codes
// http://www.computer-engineering.org/ps2keyboard/scancodes2.html

// scancode, key, with shift, with ctrl, with altgr (=ctrl+alt)
char ScanCodes[256][4] = { 
    {'\0','\0','\0','\0'},//0x00
    ... // stripped down for post 
    {'q','Q','\0','\0'},//0x15
    {'1','\0','\0','\0'},//0x16
    {'\0','\0','\0','\0'},//0x17
    {'\0','\0','\0','\0'},//0x18
    {'\0','\0','\0','\0'},//0x19
    {'z','\0','\0','\0'},//0x1A
    {'s','\0','\0','\0'},//0x1B
    {'a','A','\0','\0'},//0x1C
    {'w','\0','\0','\0'},//0x1D
    {'2','\0','\0','\0'},//0x1E
    {'\0','\0','\0','\0'},//0x1F
    {'\0','\0','\0','\0'},//0x20
    {'c','C','\0','\0'},//0x21
    {'x','\0','\0','\0'},//0x22
    {'d','D','\0','\0'},//0x23
    {'e','\0','\0','\0'},//0x24
    {'4','\0','\0','\0'},//0x25
    {'3','\0','\0','\0'},//0x26
    {'\0','\0','\0','\0'},//0x27
    {'\0','\0','\0','\0'},//0x28
    {' ','\0','\0','\0'},//0x29
    {'v','\0','\0','\0'},//0x2A
    {'f','\0','\0','\0'},//0x2B
    {'t','\0','\0','\0'},//0x2C
    {'r','\0','\0','\0'},//0x2D
    {'5','\0','\0','\0'},//0x2E
    {'\0','\0','\0','\0'},//0x2F
    {'\0','\0','\0','\0'},//0x30
    {'n','\0','\0','\0'},//0x31
    {'b','B','\0','\0'},//0x32
    {'h','\0','\0','\0'},//0x33
    {'g','\0','\0','\0'},//0x34
    {'y','\0','\0','\0'},//0x35
    {'6','\0','\0','\0'},//0x36
    {'\0','\0','\0','\0'},//0x37
    {'\0','\0','\0','\0'},//0x38
    {'\0','\0','\0','\0'},//0x39
    {'m','\0','\0','\0'},//0x3A
    {'j','\0','\0','\0'},//0x3B
    {'u','\0','\0','\0'},//0x3C
    {'7','\0','\0','\0'},//0x3D
    {'8','\0','\0','\0'},//0x3E
    {'\0','\0','\0','\0'},//0x3F
    {'\0','\0','\0','\0'},//0x40
    {'\0','\0','\0','\0'},//0x41
    {'k','\0','\0','\0'},//0x42
    {'i','\0','\0','\0'},//0x43
    {'o','\0','\0','\0'},//0x44
    {'0','\0','\0','\0'},//0x45
    {'9','\0','\0','\0'},//0x46
    ... // stripped down for post 
    {'\0','\0','\0','\0'} //0xFF
};

// dataPin D0, clkPin D1
PS2Communication* PS2;

char report[255];

uint32_t long ms; 

void ps2Read();

inline void dumpACK()
{
  delay(WAIT4PS2REPLY);
  while (PS2->available())
  {
    delay(WAIT4PS2REPLY);
    PS2->read();
  }
}

void setup()
{
  //WiFi.off();                 // don't need it unless we call connectCloud()
  Spark.connect();
  
  pinMode(D7, OUTPUT);

  Serial.begin(115200);

  PS2 = new PS2Communication(D0, D1);
  
  PS2->reset();
  
  ms = millis();
}

int leds = 0b00000100;
int flip = 0b00000110;

void loop()
{
  char c;
  
  while (PS2->available())
  {
    switch (c = PS2->read())
    {
      case 0xF0:     // break code
        while(PS2->available() < 1);
        PS2->read(); // flush it following
        break;
      case 0xE0:     // extended code
        while(PS2->available() < 1);
        PS2->read(); // flush it following
        break;
      case 0xE1:     // pause key
        while(PS2->available() < 2);
        PS2->read(); // flush it following
        PS2->read(); // flush it following
        break;
      default:
        if (ScanCodes[c][0])
          Serial.write(ScanCodes[c][0]);
        else
        {
          sprintf(report, " %02X ", c);
          Serial.print(report);
        }
        break;
    }
  }

  // blink Caps/Num alternately
  // clears kbd buffer and interferes with typematic
/*  
  if (millis() - ms > 500)
  {
    ms = millis();
    PS2->write(ps2kbd_SETLEDS);
    delay(WAIT4PS2REPLY);
    PS2->read();  // dump ACK
    PS2->write(leds);
    delay(WAIT4PS2REPLY);
    PS2->read();  // dump ACK
    leds ^= flip;
  }
*/
}
1 Like

I found an example of a PS2Keyboard library written a few years ago and then adapted for Arduino and then a Teensy I have tried to adapt/port this to the Spark Core (using what I saw you had included in PS2Communication).

My understanding of C++ is where I am struggling. The keyboard I have works fine - except that I am struggling to understand why some keys are not working; 1, 2, q, w, a, s, z, x, esc and F1. I am clearly missing something in the way the scan codes are being converted to an ascii character or a defined code for PS2_F1. I will post the code on Github tomorrow. Perhaps you can see what is not going on that should be - the conversion of scan codes seems to me to be overly complicated.

Are you using my PS2Communication library or PS2Keyboard now?

For my own lib I know that above sample works for standard, unmodified (shift, ctrl, altgr) keys.
But for modified and extended keys the lib works but the sample would need extending to support these keys, but that would not be too hard.

When I published my lib I asked if there was some interest in dedicated PS2Mouse and PS2Keyboard classes, but there never was any call for it, so I didn’t bother :wink:

I have tried the code you posted :confused:. I have included a PS2->begin(); in the setup and I am now getting a read() returned value of 0X00 each cycle and nothing else.

Also, is there a way of setting up the pins for data and clock. At the moment these appear to be hard coded in PS2Communication.h?

PS2Communication* PS2;

char report[255];

void setup()
{
    Serial.begin(9600);
    delay(1000);
    Serial.println("Keyboard 2 test:");
    PS2->begin();
}

void loop()
{
  uint8_t c;
  while (PS2->available())
  {
    switch (c = PS2->read())
    {
        case 0x00:      // for debug
            Serial.print("0x00 ");
            break;
        case 0xF0:     // break code
            while(PS2->available() < 1);
            PS2->read(); // flush it following
            break;
        case 0xE0:     // extended code - need to handle this as 2nd byte is read and PS2 key selected
            while(PS2->available() < 1);
            switch (c = PS2->read())    // read next scan code
            {
                case 0x71:  // PS2 Delete
                    Serial.print("[DEL]");
                    break;
                case 0xF0:  // break code on extended
                    while(PS2->available() < 1);
                    PS2->read(); // flush it following
                    break;
                default:
                    break;
            }
            break;
        case 0xE1:     // pause key
            while(PS2->available() < 2);
            PS2->read(); // flush following byte
            PS2->read(); // flush following byte
            break;
        default:        // scan code read is none of the above - therefore a character
        if (ScanCodes[c][0] && c >= 0 && c < 132)   // not handling shift or control or Alt
          Serial.write(ScanCodes[c][0]);
        else
        {
          sprintf(report, " %02X ", c);
          Serial.print(report);
        }
        break;
    }
  }
}

If you look in the code above, you will notice this line PS2 = new PS2Communication(D0, D1);
The lib comes with a default setting, but if you insist in using other pins, you can - just have a look in the header file to find out which pins can be used.

One thing that definetly confuses me about your code there is, that you do reference ScanCodes[][] but you haven’t declared it, so I guess this would not build as is.

So please just try my original and go from there
https://dl.dropboxusercontent.com/u/60050879/PS2KbdTest.ino

I haven’t yet filled the whole ScanCodes[][] table, but for the printable non-shifted chars you should be good to get started.

If you want, you can use this file for building a more complete table and then just export it as a text file and copy-paste it into the sketch
https://dl.dropboxusercontent.com/u/60050879/MoreScanCodes.xlsx

Just like you didn’t complete the definition of ScanCodes[ ][ ], I didn’t include them. But that is not the issue - I can’t see why the read() is returning 0. I will try the original. Thanks for your help.

I do have ScanCodes[][] complete, but “stripped [it] down for post”, to transport the idea without making the code tooooooooo long :wink:


But the point why it didn’t work is, that you haven’t got an instance of PS2. You are missing PS2 = new PS2Communication();

I thought you put that in the begin() method :sleeping:.

Well, that has established why the keys for Tab, 1, 2, F1, Esc, Caps, a, s, z and x aren’t returning a scan code or letter - the keyboard is faulty! Nothing to do with the software. Once I get a working keyboard I can progress. Hey Ho. Thanks again.

Putting it in the PS2Communication::begin() method would not work, because for calling a non-static method you need to have an object instance to be constructed first.

And it was done in the sample sketch :wink:

I am trying to compile a series of modules including this keyboard handling component and I need to implement shift+key to get upper case characters. I have extended the scancodes table to include the shift + key characters and I have had some success. Do you have an idea how to crack this? Version below sets a flag when shift key pressed down but then does not seem to pick up that shift is on when the next key press is received. I have tried waiting on the next key press when shift is pressed with while(!PS2->available()) following by PS2->read() but this only sometimes works and repeats the lower case character. Code before setup() as per the example you provided.

void setup()
{
Serial.begin(9600);
delay(1000);
Serial.println("Keyboard2 test: ");
PS2 = new PS2Communication(D3, D4);
PS2->begin();
}

void loop()
{
char c;
c = keyRead();
if (c==0x0D) Serial.println("[Enter]"); //CR
else if (c==0x08) Serial.print("[BS]");
else Serial.print©;
}
//
char keyRead()
{
uint8_t c = 0x00;
char k;
boolean shift = false;
while (PS2->available())
{
switch (c = PS2->read())
{
case 0xF0: // break code
while(PS2->available() < 1);
c = PS2->read(); // check if SHIFT Break
if (c == 0x12 || c == 0x59)
{
shift = false;
}
break;
case 0xE0: // extended code
while(PS2->available() < 1);
PS2->read(); // flush it following
break;
case 0xE1: // pause key
while(PS2->available() < 2);
PS2->read(); // flush it following
PS2->read(); // flush it following
break;
default:
if (ScanCodes[c][0]) //not zero
{
if (shift) k = ScanCodes[c][1];
else k = ScanCodes[c][0];
break;
}
else if (!shift && (c == 0x12 || c == 0x59))
{
shift = true;
}
else
{
k = 0;
}
break;
}
}
return k;
}

I’d have to find a PS/2 keyboard an test this.

If I don’t report back within a few days, give me another ping :wink:

Thanks - if I make progress I will share this back with you. :grinning: