Week 3 Lab

Syllabus • Lectures • Assignments • Lab info • Docs • 3D Print • Projects

Week 1 LabWeek 2 LabWeek 3 LabWeek 4 Lab

Due Tue., Jan. 31 or Wed., Mar. 1 depending on section

LCD, Keypad, and Interrupts

In this lab, we will learn how to display text and useful information on an liquid crystal display (LCD, specifically HD44780). Then we will read key-punch entry from a keypad via time slicing, and finally we will combine the LCD and keypad into an analog read-in scheme, also employing the use of interrupts.

Exercise 1: LCD Voltmeter

Review the Arduino tutorial on LCD control. LiquidCrystal is a standard library in the Arduino set, so that control is almost embarrassingly easy. See the sample code at the bottom of the tutorial page to get something cooking fast. Use the LCD shield, but be aware that the pin mapping differs from that in the example code. For the shield, Register Select (RS) is on pin 8, Enable (E) on pin 9, and the upper four data lines, D4 through D7, map to Arduino pins 4 through 7, respectively.

While “hello world!” is a staple phrase for the budding programmer, feel the power of your control by making a more personalized message. The seconds-elapsed counter in the example is also a nice touch. Hit the reset button and it will start over.

Now Something Useful: An Analog Meter

Having made some initial progress, it’s time to do something more dynamic, albeit amazingly straightforward. In this application, you will pick two analog input channels (except A0, which is used by the LCD buttons, and not made available on the shield’s pin-header), and report the output voltages with a sensible update rate onto the LCD.

Let’s say you pick A4 and A5. Compose a report on the LCD screen that uses the top row for one and the bottom row for the other. Format a display that has a static part (never needs updating) and a dynamic part, printing something like:

ADC 4: 1.16 V
ADC 5: 3.28 V

Or, if you assign specific meaning to each channel, by hooking up sensors of some form, you could imagine a more informative display, like:

brightness: 2.89
distance: 1.08

The programming is pretty straightforward. You’ll want to convert the 0–1023 analog input into a floating-point voltage spanning 0 to 5 V. A convenient means of testing is to use a potentiometer to set a steady voltage somewhere between 0–5 V, so you can also check with a voltmeter and see if your device is producing reasonable output. You may need to accommodate the fact that VCC may not exactly be 5.00 V for your Arduino.

Floating point numbers are automatically formatted by the lcd.print() command to have two digits after the decimal, so that simply lcd.print(volt_4); does what you want, where volt_4 is an example variable in your program. To manage static vs. dynamic fields, just print the static stuff (including the “V” if used) in the setup() portion of the sketch. In the loop(), just set the cursor to the beginning of the voltage reading and write from there (may be a good idea to clear this field first by writing spaces over the old data; especially relevant if 13.26 becomes 9.87, which would otherwise appear to be 9.876, the six being an erroneous remnant).

For Demonstration and Turn-in

Use your voltmeter for good, not evil. To demonstrate, you could do something as simple as using a couple of potentiometers, or you could hook up photoresistors or photo-sensors, or you may decide to measure any other thing that will put out a voltage constrained between 0 and 5 V. Here’s one example idea: Hook up an LED to a max-current-limiting resistor (like 220 Ω) and a potentiometer (maybe 10 kΩ) in series that lets you increase the resistance. You can report the LED voltage and the current, in milliamps, as judged by the voltage drop across the static resistor (some thought into arrangement and appropriate math will be needed to extract what you want). Now you have a current-voltage curve tracer so you can study the response of any random LED pretty conveniently. I did this and loved calibrating my sense of how bright 1 mA looks, etc. This is just one of countless ideas, but don’t go overboard, as there are other parts of the lab to do. As in the LED example, it is not a requirement to report voltages, per se. The important thing is that you read voltages on the analog inputs, and report related (relevant) quantities on the LCD.

Have the TA check your functionality. Include in the write-up (could put a lengthy comment in the code) a paragraph describing the function of your meter, and include a diagram of your apparatus.

Exercise 2: Keypad and Time Slicing

We will use a 4×4 matrix-style keypad (thus 8 contacts) to learn about time slicing the input. The general idea is to hook the four row contacts up to Arduino digital inputs, utilizing the internal pull-up resistor via pinMode(pin, INPUT_PULLUP);. The column contacts will be hooked to digital outputs, normally held high, but periodically set low while the row values are checked for contact with the column that’s held low.

The first task is to map out the pinout of your keypad. In a small sampling, I have seen pins 1 through 8 map to sensible arrangements like [C4, C3, C2, C1, R4, R3, R2, R1] (where C represents column and R represents row). But I have also seen [R2, R3, C1, R4, C2, C3, C4, R1] and [R2, R1, C4, C2, C3, C1, R3, R4] in the random sample of keypads sitting in the lab. The way to elucidate the pinout is to use the continuity check (beep) function of a DVM, hook up to two leads at random and press all the buttons. If no beep, move one of the leads to another contact (if this happens, it means you’ve chosen two rows or two columns, so they never contact each other; you have a 43% chance of this happening to you on the first try). Once you’ve found two different pairs of contacts (leaving one lead in place), the mapping will fall out pretty quickly.

Write some code to set up the rows as INPUT_PULLUP pin modes, and the columns as output, setting each column to HIGH in the setup() section.

The loop() must set each column low in turn, checking for each column the state of all four rows. Additionally, you must devise a scheme to keep track of which button (if any) has been pressed, and also if there is a change from last time around the loop (you don’t want auto-repeat: aim for one press; one digit). Finally, you want a delay in the loop to debounce the keypad.

Use the serial monitor to report keypress activity, and make sure you have the mapping right. I found it handy to print all new digits on the same line until some special key (e.g., #) is pressed, which can behave as an “enter,” producing a line feed.

A good way to convince yourself that you have solid performance is to go through the exact same key sequence three or four times in a row and make sure the serial monitor output looks identical each time: no missed characters, no extras, nothing out of order, etc.

When you have robust operation, you can call this section done: have a TA check functionality, and turn in code that has a comment section describing which keypad you used (model number?) and the pinout you deduced.

Exercise 3: Analog Keypad

In this exercise, you will prepare the keypad for use via analog input(s) in a way that does not require a whopping eight digital inputs, but uses four analog inputs instead.

You will connect four different-valued resistors between +5 V (provided by the Arduino board) and each of the row connections on the keypad. Aim for roughly factor-of-two differences. Now connect four of the same-valued resistors between the column contacts and ground. When a key is pressed, a voltage divider is set up between the column resistor (Rc) and the row resistor (Rr), so that the voltage produced is VCC×Rc/(Rc + Rr), where VCC is the supply voltage (5 V). Make sure your resistor selections give you four pretty distinct voltages.

Calculate the expected analog input levels associated with your resistor choices (in both 5 V scales and the 0–1023 scale). Measure the voltages that result when you press the buttons and make sure they do what you expect.

Hook the four voltages into A1 through A4 on the Arduino.

Save your previous keypad program to a new sketch for the analog version, and strip out all the time slicing business. In its place, you will poll the four analog inputs in turn, and test to see what row number (if any) is pressed for the column associated with that analog input. A handy mechanism is the daisy-chained else...if construction:

if (in_volt < 125)
  row = 0;
else if (in_volt < 325)
  row = 1;
else if (in_volt < 510)
  row = 2;
else if (in_volt < 700)
  row = 3;
  row = 4;
return row;


I found it convenient to put the above bit in a subroutine that checks for row number pressed, where in_volt is an argument passed to the function. You can simplify operations by calling this function with analogRead(COL1) as the argument, for instance (e.g., no need to create a variable to hold the analog value: just read and send straight to a routine to ascertain what row is pressed).

As before, send the result back via Serial so you can see what buttons are being pressed. Verify robust operation, and have a TA check for solid performance. Turn in a schematic of your resistor/keypad network, calculations of expected voltage and analogRead() values, and the code that ultimately worked.

Exercise 4: LCD, Keypad, Interrupt Party

Now we will pull our achievements from the preceding exercises together, adding a new concept of interrupts, to result in a moderately complex interactive system.

Here’s the goal: Using the LCD shield and a 4×4 keypad employing 4-channel analog input, make a device that will have some busy operation taking place on the upper line of the LCD (like some fast-moving status bar, character, or whatever), while allowing a keypad interrupt to write text onto the lower line of the LCD. For instance, I made a row of periods shoot across the top line, replaced by colons on the way back (so it looked like a dot making a loop to the end of the display and back), then erase when it gets to the start and begin again. Then the keypad simply reports numbers I press on the bottom row of the LCD, using the # key as a clear/return. If I reach the end of the display, I wrap back to the beginning and overwrite the characters already there.

But lots of other options exist for an interface. The key elements are: busy top line, keypad via analog input producing interrupts, controlling the display on the bottom line according to the keys pressed.

Getting it Done

You already know how to write to the LCD. You already have an analog-input keypad worked out. Now just feed the analog inputs onto the appropriate pins on the shield (use the M-F jumpers in the front of room 3574).

The new part is the interrupt business. In software, it’s pretty straightforward. Make an interrupt service function that is essentially your analog keypad polling/checking bit. Attach the interrupt on pin 2 or 3 (interrupt 0 or 1) to the function by something like: attachInterrput(0,get_keypress,FALLING);. Instead of reporting the keypress to Serial, you’ll write to the LCD display. You’ll need to keep track of where the cursor is, so each time a new key is pressed and you return to the interrupt service, you’ll remember where you need to write the next character. It is no problem to call other functions from the interrupt function. Caution: Pins 2/3 might not be where you expect them to be on the LCSD shield. Check the pinout for the shield to get it right.

You may want to add a bit of delayMicroseconds() (delay won’t work here) into the interrupt function for debounce (so you read the voltage only after giving it time to stabilize). Since delayMicroseconds() only goes up to 16383, you may find it necessary to tack a few together, each 15 ms or so. Experiment.

On the hardware front, you need to detect a change (really, departure from zero) in the analog output resulting from the pressing of any of the keys. For this, we use a comparator. We’ll use the ‘311 comparator in the 8-pin DIP package, which is obsolete, or the 14-pin DIP LM339N with 4 comparators. If you have the LM311: Pins 1 and 4 are tied to ground. Pin 8 is +5 V. Pin 2 will take a reference voltage, and pin 3 will take the analog input from the keypad column. The output is on pin 7, and should be pulled up to +5 V via a pull-up resistor in the 2–10 kΩ range. If you have the LM339N, check the datasheet.

Set the threshold to something between ground and the lowest analog voltage produced by pressing a button in some row. Verify that when no button is pressed, the comparator output sits at VCC, and that when any button in the column associated with that analog output/comparator is pressed, the comparator output drops to zero (or thereabouts).

Once this works for one channel, set up a comparator for each of the four columns, all sharing the same reference voltage. Also, tie the outputs together for all four (open collector permits this), using a single pull-up resistor for all. Verify that the resulting single output responds to any and all keypresses on the keypad.

Hook the combined comparator output to pin 2 of Arduino (middle pin of 9 on the LCD shield).

The Software

Okay, all the pieces are in place. Now you need a program that:

  1. Invokes the LiquidCrystal Library and instantiates an LCD with the appropriate pinout for the shield.
  2. Defines your keypad (analog pins for various columns, characters as a function of row and column, etc.).
  3. Starts the LCD.
  4. Attaches an interrupt (in setup())
  5. Starts Serial for debugging (but may be jettisoned once working).
  6. Has a loop that does nothing more than keeps the top line of the LCD occupied with some task (after all, to demonstrate interrupts, we should have something that begs to be interrupted).
  7. Has an interrupt service function that checks which key has been pressed (after all, something happened to get here), and modifies the second line of the LCD display in some way appropriate to the keys pressed.

Once you have all this working, it is worth a little bit of fooling around to see how the interrupt routine impacts timing. Do you notice any slowdown of the top-line activity? What if you stick a delay() of a couple seconds into the interrupt service? What if you make a for loop to pile up enough 15 ms delays via delayMicroseconds() to notice?

To Turn In

As usual, have a TA check that your project is functional and robust, and that the interface works as intended. Since all projects will be a little different, include a writeup (possibly as a big comment chunk in code) that describes the intended functionality, complete with subtle details. And, of course, turn in the code itself together with a diagram of the setup. Each team member should contribute a description of their role in all of the exercises in the lab.