Walking Through the 1602 LCD Keypad Shield for Arduino

Some months back, a student at Ecole Polytechnique came to me asking for help with an Arduino shield, that he’d acquired on Amazon: a “LCD Kuman 1602 v3”.

Arduino 1602 LCD Keypad
Arduino 1602 LCD Keypad

The shield is a clever little combination of a 2 lines / 16 characters LCD display and 5 buttons, that snap on top of an Arduino Uno, and makes for a nice little development platform. The student had tried various Arduino sketches, downloaded off from various places of the Internet and which ought to work – but, which didn’t, much to both his surprise and frustration.

So we walked through it together, and (of course) got it to work – but, this walk-through revealed some easy-to-make misunderstandings from reading rand on publicly available tutorials. I thought I’d document those.

Serial vs Parallel?

1602 is the model number for the LCD module – and, there are a ton of 1602 LCD modules out there – here’s an example of the specification of the 1602 LCD module (archived from https://www.openhacks.com/uploadsproductos/eone-1602a1.pdf).

  • Fortunately, one doesn’t need to memorise (or, even, read) all this to use it: there’s a library for that which is one.
  • Unfortunately, there’s a little more to it than just what’s in the specification.

On the left below is a photo of two 1620 LCD modules I have laying around – they look pretty identical, right? However flipping them over, they look almost, but not quite, identical: one of them has a little red “board” riding on it.

Two 1602 LCD Modules - Front
Two 1602 LCD Modules – Front

Two 1602 LCD Modules - Back
Two 1602 LCD Modules – Back

 

To simplify things, the “basic” 1602 LCD module has a “parallel interface” – meaning, that it requires the use of several of the digital pins (6, to be precise) of an Arduino board, plus of course power and ground. Some 1602 LCD modules do, however, come with “a little red board” pre-soldered on, which essentially is a parallel-to-serial converter. That’s pretty cool as it reduces the number of digital pins required from the Arduino board to just two (again, plus power and ground).

This is of course significant, since you’d use two different libraries when communicating with the LCD: either you use the LiquidCrystal library (for “parallel” 1602 LCD modules), or the LiquidCrystal_I2C library (for 1602 modules with a parallel-to-serial converter). And, just typing “1602 LCD Module Arduino” into a search engine will return sketches for either, often without much explanation. That did explain away why some of the sketches downloaded off the interned didn’t work.

Thus, first thing: check if there’s a “little board on the back of the display” and pick library accordingly.

In case of this LCD keypad shield, there’s no “little red board” on the back, and the proper library is simply LiquidCrystal. But, that’s just the first step…

Which Pins?

As shown on the picture, the LCD module is soldered onto a circuit board / shield – so the next step is to map the pin-out from the display module (typically, the display module will have labels printed on the pins on its header) and to the pins on the shield that connect to the Arduino.

It might be tempting to do so by just eye-balling the traces on the back of the shield:

But, with multilayered printed circuit boards, that’s not a great strategy: I simply used a multi-meter, which gave the following

1602 LCD Module Pin Arduino Pin
D7 D7
D6 D6
D5 D5
D4 D4
RS D8
E D9

 

No other pins are connected – this is a 4-bit parallel display module.

All this should allow us to write text to the display … but there’s one little caveat left…

Printing to the LCD Keypad Shield

The sketch is rather simple, and is included below:

 
 #include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
//    LiquidCrystal lcd(RS, enable, d4, d5, d6, d7); //
//
// The parameters to this initialisation function are the pins on 
// the Arduino board that are connected to the pins on the LCD Module, 
// i.e., the pins on the right column in the table above.
//
// The first parameter is the “RS” — which, literally, means 
//      “Register Select”, and which is used to tell the
//       module if “that which comes over the bus (the set of data pins)
//       corresponds to “data to print” or “control instructions”
//
// The second parameter is “Enable Signal"
//       the remaining parameters are the 4 digital pins forming the bus.
//
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int i = 0;
void setup() {
      // set up the LCD's number of columns and rows:
      lcd.begin(16, 2);
}

void loop() {
      lcd.setCursor(0, 0);
      lcd.print("Hello World");
      lcd.setCursor(0, 1);
      lcd.print(i);
      i++;
}

The Arduino reference will do a much better job at explaining the details of the various lcd.____ functions, than what I can do here.

Compiling, then uploading, this program to an Arduino should yield “Hello World” on the top line of the display, and an ever increasing figure on the bottom….however, this was the result:

The little blue box on the top left of the shield (pointed to by the screw driver) is to blame. That’s a potentiometer (a variable resistor) used to adjust the display contrast. As a rule, they come “waaaaay turned down”, so there’s seemingly nothing displayed.

This particular type of variable resistor is furthermore a high-precision, fine-geared kind, so it takes about 10 or so complete turns (clockwise) before the contrast becomes so that the display is readable:

(Oh, and do note the negative number in the picture, clearly the integer I counting up is overflowing nicely … ).

This is actually an (in my experience) inevitable source of first-timer-frustration: brashly giving a potentiometer 10 turns is not intuitive, especially as most potentiometers encountered are of this nature, which can be turned barely one turn (& as most beginners are trying to “be delicate”).

And Then the Keys?

There are 6 keys on this Arduino shield. The right-most one is labeled RST:


This button is simply mapped to the RST button of the Arduino, and (expectedly) will reset the Arduino. (Honestly, while the symmetry may be esthetically pleasing, it really is not the cleverest interface design to put reset just next to the other buttons).

For the other 5 buttons, they all map to one pin – to the analog pin A0, which when read will give different values depending on which button is pressed. This is achieved by a circuit of resistors connected like so, and A0 measuring the potential as indicated on the diagram:

I’m not including the values of the resistors, as that’s really not interesting for what we’re doing: writing a sketch using the keypad. What is interesting is what values one read from A0 when a button is pressed. To this end, I use a small sketch like this, which simply writes the value read on A0 on the (in this case) LCD:


#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int adc_key_in;
void setup() {
      lcd.begin(16, 2);
 }

void loop() {
      adc_key_in = analogRead(0);
      lcd.setCursor(0, 0);
      lcd.print("A0:");
      lcd.setCursor(0, 1);
      lcd.print(adc_key_in);
}

If trying to work out the values for a keypad of this nature, but without a LCD display connected at the same time, one can use the serial console for the same effect:

int adc_key_in;

void setup() {
      Serial.begin(9600);
      Serial.flush();
}

void loop() {
      adc_key_in = analogRead(0);
      Serial.print("A0:");
      Serial.println(adc_key_in);
}

Either way, this allows recording the values corresponding to a keypress. Given that this is an all-analog input, it may be that pressing the same key twice does not yield exactly the same value. This may be because (for example) that resistor values change slightly depending on temperature, or slight variations in the power supply voltage, etc. … so it’s highly recommended to device an interval of values, for each given button.

Here’s a bit of code that I use with a similar keypad – these values may be a good starting point, but I really do recommend testing for each new keypad: it’s another of those frustrating errors to have to chase down if one gets the wrong intervals:


 #define btnRIGHT 0
 #define btnUP 1
 #define btnDOWN 2
 #define btnLEFT 3
 #define btnSELECT 4
 #define btnNONE 5
 #define btnUNKNOWN 6

int readkeypad(){
      int adc_key_in = analogRead(0); //
      int ret = btnUNKNOWN;

      if (adc_key_in < 50) ret = btnRIGHT;
      if ((adc_key_in > 500) && (adc_key_in < 1150)) ret = btnNONE;
      if ( (adc_key_in > 120) && (adc_key_in < 150) ) ret = btnUP;
      if ( (adc_key_in > 250) && (adc_key_in < 350) ) ret = btnDOWN;
      if ( (adc_key_in > 450) && (adc_key_in < 500) ) ret = btnLEFT;
      if ( (adc_key_in > 700) && (adc_key_in < 750) ) ret = btnSELECT;

      return ret;
 }

Oh, and just to answer the inevitable question

“Why declare the variable ret, instead of just at the end of each if (…)… statement returning the corresponding value?

There is a very good reason for that: I tend to write functions so as to be able to embed #ifded-enabled code that writes stuff to the serial monitor just before returning – for debugging purposes. That’s a lot easier this way.

And finally, a complete program using both the keyboard and the LCD module, which yields this satisfying result:

#include <LiquidCrystal.h>

#define btnRIGHT 0
#define btnUP 1
#define btnDOWN 2
#define btnLEFT 3
#define btnSELECT 4
#define btnNONE 5
#define btnUNKNOWN 6

int readkeypad(){
      int adc_key_in = analogRead(0); //
      int ret = btnUNKNOWN;

      if (adc_key_in < 50) ret = btnRIGHT;
      if ((adc_key_in > 500) && (adc_key_in < 1150)) ret = btnNONE;
      if ( (adc_key_in > 120) && (adc_key_in < 150) ) ret = btnUP;
      if ( (adc_key_in > 250) && (adc_key_in < 350) ) ret = btnDOWN;
      if ( (adc_key_in > 450) && (adc_key_in < 500) ) ret = btnLEFT;
      if ( (adc_key_in > 700) && (adc_key_in < 750) ) ret = btnSELECT;

      return ret;
}

// Just for ease of displaying
const char *button[7] ={"Right ", "Up ", "Down ", "Left ", "Select ", " ", "??????"};

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
      lcd.begin(16, 2);
}

void loop() {
      lcd.setCursor(0, 0);
      lcd.print("Button Pressed:");
      lcd.setCursor(0, 1);
      lcd.print(button[readkeypad()]);
}