An improved key-reader library for the SainSmart LCD Keypad Shield (for Arduino)

The LCD Keypad shield from SainSmart is a bargain. For ~$8 (and free shipping, at the time of this post) you get 32 character (16×2) blue/white LCD panel with a built-in keypad with 5 momentary switches. This provides a quick solution for interaction with Arduino projects. To control the LCD part of the shield, one can use the Arduino supplied LiquidCrystal library (included with Arduino IDE). In the Key_Grab demo (v. 0.2) on the SainSmart website is included a library, DFR_Key, for reading which keys are pressed on the keypad. However, the library is not written well. This post describes both the problem and the solution.

First, a little background is necessary about how the keypad signals a key is pressed. Only a single analog-in pin is used and the shield uses a voltage divider to send a voltage that corresponds to a given key. To detect a key, one has only to read the voltage at the analog pin and determine if it falls within a certain range. For example, a voltage of 0 to 0.7 V signals the UP key was pressed. The DFR_Key library contains a getKey() function that does this and returns an integer from 0 to 5 that represents the key pressed (0 is supposed to mean no key, but as written means the last key was just released).

You’d expect that when you call getKey() it would read the voltage at the pin, figure out what key that represented, and send back the integer representing the particular key. So really, it should just be translating the voltage to an integer from 0 to 5 (none, select, left, up, down, right, respectively). But, there is some “extra functionality” the library provides that is the problem. Here’s how the logic of the getKey() function works:

Has a minimum time passed since the last time I checked for a key press?

If not, do nothing – return no value at all!

If so, read the voltage at the pin. Is this voltage different than the voltage I read the last time I checked for a keypress?

If not, return -1.

If they’re different, it means a key was pressed right? Determine which key was pressed and return an integer from 0 to 5 representing that key.

The first bit of “extra functionality” is what the library refers to as the refreshRate. The idea is that you don’t want to check for a key press too quickly, else the function might return that the key is being held down when in fact it’s just a normal press. This makes some sense if you simply dump a getKey() call in your Arduino sketch’s loop() function. You would then check the returned value to see if it actually did a key press. If it decided enough time hadn’t pass it would return….oh wait! It doesn’t return anything in this case. That’s not appropriate behavior. When the program says getKey(), it should expect a key code sent back. Therefore, it should be the job of the programmer to only call getKey() at appropriate intervals (say every 100 ms).

That brings up the next problem. There are five keys on the keypad. Add in an extra code for “no key being pressed” and you get the six integers from 0 to 5. Perfectly reasonable. So what’s this -1 return value for? The library defines the alias SAMPLE_WAIT for this value (as opposed to NO_KEY, and UP_KEY, etc.). I’m not sure what the meaning was supposed to be, but this value is returned when the getKey() function determines that the key being pressed is the same as the key that was detected upon the last call to getKey(). At first, it would seem to be letting you know that the last key pressed is being held down. But 1: you don’t know what the last key pressed was, unless you’re tracking it yourself. And 2: that may not even be true – if you make two getKey() calls 60 s apart, whose to say whether the key was being held the whole time; all we know is that the same key was pressed as last time. So it’s telling us the same key was pressed as before and according to point 1, I’m keeping track of what that key was. If it had just returned the actual key code, I could have figured this out for myself by comparing it to my stored value of the last key pressed.

Now to the final problem. If you follow the logic stated above carefully, you’ll see that to determine if the same key was pressed, the getKey() function compares the voltage read at the analog pin to the voltage it read last time it detected a key was pressed. But the voltage does not directly correspond to a given key – a key press can be signaled by a range of voltages. For example, assume on the first call to getKey() it reads a value of 200 mV. That corresponds to the UP_KEY and it returns 3. On the next call to getKey() assume it reads a value of 205 mV. The user is pressing the up key again (or holding it down) but the voltage is slightly different. The getKey() function compares the new value, 205 mV, to the old value, 200 mV, and thinks a new key is being pressed. So instead of returning -1 for “same key is being pressed” it returns 3 for “up key is being pressed”.

Actually, this should be the behavior! Upon every call, the getKey() function should just return an integer representing the key that is being pressed at the time the call is made. But because the authors meant for it to return -1 when there was no change in the key being pressed, and meant for it to be called on every loop() iteration, the Key_Grab demo flickers the screen like crazy. Actually, it works fine when the UNO board is removed from all sources of noise and interference. But put it next to a piece of electronics (e.g. your computer) and the noise causes the voltage supplied to the analog pin to fluctuate, new key presses to be registered, and the demo to update the screen on every loop() iteration. It’s the constant updating that causes the flicker which makes it appear as though you got ripped off $10.

How can we fix this mess? Actually, the fixes are simple and actually reduce the code in the library.

  1. Remove all of the logic relating to the “refreshRate”. The programmer will be forced to decide how often to call getKey().
  2. Remove the comparison of the current key press to the previous key press. The programmer can decide if the user is pressing the same key by recording the last pressed key.

Here is the derived library which I’ve called LCD_Keypad_Reader.cpp (feel free to rename). The original SainSmart DFR_Key library didn’t come with a license so I’m not putting one on this derived work either. I don’t really care what you do with this code, but it’s possible that SainSmart could impose some kind of limitations.

#include "Arduino.h" 
#include "LCD_Keypad_Reader.h" 

static int DEFAULT_KEY_PIN = 0;  
static int DEFAULT_THRESHOLD = 5; 

// The Sainsmart keypad uses a voltage divider to deliver a voltage  
// between 0 and 5 V that corresponds to the key being pressed in  
// order to use only a single input pin. The values below are from 0 to  
// 1023 because the Arduino uses a 10 bit resolution. 
static int UPKEY_ARV = 144; // 0.720 V, that's read "analogue read value" 
static int DOWNKEY_ARV = 329; // 1.645 V 
static int LEFTKEY_ARV = 505; // 2.525 V 
static int RIGHTKEY_ARV = 0; // 0 V 
static int SELKEY_ARV = 742; // 3.710 V 
static int NOKEY_ARV = 1023; // 5.115 V 

LCD_Keypad_Reader::LCD_Keypad_Reader() 
{     
  _keyPin = DEFAULT_KEY_PIN; 
  _threshold = DEFAULT_THRESHOLD; 
  _curInput = NO_KEY; 
  _curKey = NO_KEY; 
} 

int LCD_Keypad_Reader::getKey() 
{ 
  _curInput =  analogRead(_keyPin); 
  _curKey = categorizeKey(_curInput); 
  return _curKey; 
} 

int LCD_Keypad_Reader::categorizeKey(int analogKeyValue){ 
  int categorizedKeyValue = 0; 

  if (analogKeyValue > UPKEY_ARV - _threshold && analogKeyValue < UPKEY_ARV + _threshold ){ 
      categorizedKeyValue = UP_KEY; 
  } 
  else if (analogKeyValue > DOWNKEY_ARV - _threshold && analogKeyValue < DOWNKEY_ARV + _threshold ){ 
      categorizedKeyValue = DOWN_KEY; 
  } 
  else if (analogKeyValue > RIGHTKEY_ARV - _threshold && analogKeyValue < RIGHTKEY_ARV + _threshold ){ 
      categorizedKeyValue = RIGHT_KEY; 
  } 
  else if (analogKeyValue > LEFTKEY_ARV - _threshold && analogKeyValue < LEFTKEY_ARV + _threshold ){  
      categorizedKeyValue = LEFT_KEY; 
  } 
  else if (analogKeyValue > SELKEY_ARV - _threshold && analogKeyValue < SELKEY_ARV + _threshold ){ 
      categorizedKeyValue = SELECT_KEY; 
  } 
  else{ 
    categorizedKeyValue = NO_KEY; 
  } 

  return categorizedKeyValue; 
}

And here is the header file, LCD_Keypad_Reader.h

#ifndef LCD_Keypad_Reader_h 
#define LCD_Keypad_Reader_h 

#include "Arduino.h" 

#define SAMPLE_WAIT -1 
#define NO_KEY 0 
#define UP_KEY 3 
#define DOWN_KEY 4 
#define LEFT_KEY 2 
#define RIGHT_KEY 5 
#define SELECT_KEY 1 

class LCD_Keypad_Reader 
{ 
  public: 
    LCD_Keypad_Reader(); 
    int getKey(); 
    int categorizeKey(int); 
  private: 
    int _keyPin; 
    int _threshold; 
    int _curInput; 
    int _curKey; 
}; 

#endif