Help with a 4x4 keypad

Hi,

First, I am new: to electrical engineering (even the basics), arduino, and these forums. But I'm a competent programmer of many languages and platforms, so the learning curve here is mostly with the electrical components.

I have an UNO, a 20x4 LCD display, and a 4x4 matrix keypad. I have run several sketches successfully and have the LCD working having soldered the header pins myself! Woot!

The keypad has stumped me. First I tried what came intuitively: assuming everything is OFF and expecting a row and column ON when a key is pressed. So I connected the pins, set them to INPUT, and wrote their values to Serial every few milliseconds. Not only did I get nowhere when pressing keys, but there seemed to be erratic values being read, especially if I came even close to touching the keypad, its backing, or the ribbon cable attached to its 8-pin connector.

I also tried INPUT_PULLUP to no avail. The output was less erratic, but I was definitely not getting keypress readings.

A few moments ago, I found this post here:

... and it helped a ton. I reviewed the last poster's code and I understand what is being done. Instead of copying the code, I wrote it myself to achieve the same objective. HEY IT WORKS!!

But I don't understand why. Why do the rows (or columns) have to be set as OUTPUT, set HIGH, and then toggled LOW one at a time before checking the columns (or rows) for a corresponding LOW?

Any explanation is appreciated!

-Phillip

You know which column you are reading, you know which row was commanded low, thus if it reads back low you know where the intersection is.

Thanks. I get that. That is precisely what the code achieves.
But why is that the strategy that works?

... as opposed to all on INPUT and looking for a row/column pair that each change value?

Correct me if I get anything wrong, but I have read some more on matrix keypads. I think I understand some more, and this is perhaps why it works how it does.

First, there is no POWER connection to the keypad, but, of course, to detect a press a SIGNAL (voltage and current, right?) needs to be detected. Therefore, to provide POWER, the keypad provides a natural separation: rows and columns. Use one set to provide the power and the other set to read a signal.

So that explains providing power to each row one at a time and then reading the columns one at a time for a signal, which would therefore indicate the button pushed as that could be the only completed circuit.

So, why LOW instead of HIGH?

I guess this answer is simple: the contacts break a circuit instead of completing a circuit. Therefore, it is not actually a completed circuit that gets signaled, but a broken circuit. All unpressed buttons are allowing the current from the row to proceed through the columns. The pressed button breaks its circuit, and that column goes LOW.

Does that sound right?

Thanks!

-Phillip

You have the incorrect understanding.

Here is how a matrix keypad work. If you strip it apart, you will find rows and columns just as bare metal foils crossing one another like streets cross avenues in a perfect city block we can build with legos. Whenever you press a key, that key's row connects to that key's column. Otherwise they are kept separate by springy caps. So if you press 2, the second column and the first row are connected. You may press multiple keys simultaneously and they are all identified with connectivity between the row and the column pins. If you understand this, move on to how to detect connectivity.

I give you two wire leads, and you as a microcontroller, how do you tell if lead 1 is indeed connected to lead 2? If you supply lead 1 with 5V and read lead 2 and find 5V, and then supply lead 1 with 0V and read lead 2 and find 0V, you may be confident enough to call that they are connected, although you didn't test other voltages or frequently enough. You could be mocked. If another microcontroller reads lead 1, where you put voltages on, and outputs the same voltage to lead 2, where you read back, it mocks you. Don't worry though, you are not being mocked. So if you put 0V on lead 1 and read lead 2, and you receive 0V, you are certain the two leads are connected, thus indicating a button is pressed.

This is how matrix keypad scans. You hold a column to 0V, you sense all 4 rows, the row that is 0V is connected to the column you hold to 0V. If you do find it, you have a button pressed. If you don't, proceed to hold the next column to 0V and sense all rows. But don't forget to return the previous column to input.

A bit of detail: all rows are set to not just input pins for sensing, but also with their internal pull-up resistors enabled. So the internal pull-up resistors will hold the row to 5V until you press a button on the row and the button's column is being set to 0V. Then that row reads 0V instead of 5V, indicating connectivity.

Legos? I like my streets made with asphalt or concrete.
:wink:

Thanks for the breakdown. I think the key part I failed to grasp is the functionality of the pull-up resistor. And a button press actually provides the continuity, not a break, to get the 0V reading.

Would it hold true that if the ATmega were designed with pull-down resistors, we would invert the HIGH / LOW logic? … to set inputs to pull-down to 0V, set outputs to LOW at 0V. Then set the outputs to 5V to read columns, and a column reading at 5V would give the intersection and button.

Thanks
-Phillip

Yes, if pulldown are used, you output 5v to column and expect connectivity to be 5v or HIGH.

Great! Thank you, liudr; you have been helpful.

I spent some time today reading up on pull-up and pull-down resistors.

A good solid read needs current flow which is not provided by INPUT (low) but can be provided by INPUT_PULLUP.

The read itself only takes a tiny bit of current (which can be used for digital-analog reading) that makes INPUT a kind of 3rd state neither HIGH nor LOW (which allows apps like Charlieplexing). Some really cool tricks hinge on the controller being able to switch pin states with speed and precision. You can literally change a circuit in under a microsecond but more practically figure 10 usecs or more, 50 or more if you want to keep a healthy margin and not push it.

Current flow can be a pulldown (through resistor, usually 10K or more) from the pin to ground. if you feed a wire 5V it becomes charged even with just a ghost of current and stay there when the supply is turned off if it's not pulled down. That's what a floating pin does, you won't get a solid read.
OTOH, I've used that to get very fast low res analog out of digital pins by loading the wire and then seeing how many reads at several per usec to take the pin from HIGH to LOW. The load up took very short time and the many tiny-bit-off-current reads stretched that out -- so there is the flow and the app counts it to pin state change.

INPUT_PULLUP is through 20K to 50K resistance. Easy to take LOW and there's the flow with no easy way for the pin to float. Perhaps the app is pin safety?

Just wanted to add to this topic If anyone else is looking for code for keypads.

I wrote this to save space and not have to download the keypad.h lib. I don’t wait for more then one key press, I use input_pullup to save having to add resistors to the circuit.

I use a 2x16 lcd for output pretty standard in the kits. I used a mega2560r3 but just change the IO pins if you want to use a uno.

#include <LiquidCrystal.h>
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 2, en = 3, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

byte ledPin = 13;

// for keypad pins on the mega 2560 using the io pins at the bottom connector so the header can just plug
// right on the board with a pin header.
const int kpPin1 = 23, kpPin2 = 25, kpPin3 = 27, kpPin4 = 29;
const int kpPin5 = 31, kpPin6 = 33, kpPin7 = 35, kpPin8 = 37;


void setup() {
  Serial.begin(9600);
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("Mega 2560 R3        ");
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // set keypad pins
  pinMode(kpPin1, OUTPUT); pinMode(kpPin2, OUTPUT); pinMode(kpPin3, OUTPUT); pinMode(kpPin4, OUTPUT);
  digitalWrite(kpPin1, HIGH); digitalWrite(kpPin2, LOW); digitalWrite(kpPin3, LOW); digitalWrite(kpPin4, LOW);
  pinMode(kpPin5, INPUT_PULLUP); pinMode(kpPin6, INPUT_PULLUP);
  pinMode(kpPin7, INPUT_PULLUP); pinMode(kpPin8, INPUT_PULLUP);


}

void loop() {
  lcd.setCursor(0, 1);
  lcd.print(readKeypad());
  delay(100);
  lcd.print("       ");
}

int readKeypad(void) {
  int readval = 0;
  // Move the low pin across the 1-4 pins while reading for a high on the input pullup pins. 
  // I dont wait to read all pins, just want to get the first keypress and get out. 
  digitalWrite(kpPin1, LOW); digitalWrite(kpPin2, HIGH); digitalWrite(kpPin3, HIGH); digitalWrite(kpPin4, HIGH);
  if (digitalRead(kpPin5) == 0) return 1;
  if (digitalRead(kpPin6) == 0) return 2;
  if (digitalRead(kpPin7) == 0) return 3;
  if (digitalRead(kpPin8) == 0) return 10;
  digitalWrite(kpPin1, HIGH); digitalWrite(kpPin2, LOW); digitalWrite(kpPin3, HIGH); digitalWrite(kpPin4, HIGH);
  if (digitalRead(kpPin5) == 0) return 4;
  if (digitalRead(kpPin6) == 0) return 5;
  if (digitalRead(kpPin7) == 0) return 6;
  if (digitalRead(kpPin8) == 0) return 11;
  digitalWrite(kpPin1, HIGH); digitalWrite(kpPin2, HIGH); digitalWrite(kpPin3, LOW); digitalWrite(kpPin4, HIGH);
  if (digitalRead(kpPin5) == 0) return 7;
  if (digitalRead(kpPin6) == 0) return 8;
  if (digitalRead(kpPin7) == 0) return 9;
  if (digitalRead(kpPin8) == 0) return 12;
  digitalWrite(kpPin1, HIGH); digitalWrite(kpPin2, HIGH); digitalWrite(kpPin3, HIGH); digitalWrite(kpPin4, LOW);
  if (digitalRead(kpPin5) == 0) return 14;
  if (digitalRead(kpPin6) == 0) return 0;
  if (digitalRead(kpPin7) == 0) return 15;
  if (digitalRead(kpPin8) == 0) return 13;
  return 16; // return 16 for no key pressed
}