Many switches on few pins

A switch matrix on the Arduino: a 4 × 4 membrane keypad scanned by our own code, arrays and nested loops, ghost keys and the diodes that remove them

Arduino Electronics
Many switches on few pins

There will be 15 sensors on the playfield of our pinball machine: targets, lanes and the drain. On top of those there’s the Start button, so the main Arduino has to read 16 switches. An Arduino Uno has 18 pins that we can use freely, and the main Arduino also needs them for the tilt, for the link to the other Unos and for other jobs. If every switch had its own pin, there wouldn’t be enough pins.

That’s why we arrange the switches in a matrix: in rows and columns, like the cells of a table. Then 8 pins are enough for 16 switches. Today we’ll write a program that reads a matrix like this, meet its biggest trap, ghost keys, and fix it with diodes.

What does this mean for the pinball machine?

Top view of the pinball machine: a blue circle marks the three targets on the left wall, and a large blue circle shows the same targets enlarged, with a purple lamp in front of each

On the top view of our pinball machine, a blue circle marks the three targets on the left wall, and the large circle shows the same targets enlarged. Behind each target is a microswitch, and in front of it a lamp. The pinball page explains the other parts of the drawing.

  • The pinball machine's main Arduino reads 15 sensors and the Start button through a 4 × 4 matrix, over 8 pins. A diode will be soldered next to every switch, as in Exercise 4, because the ball and the player can press several switches at the same time.
  • The program scans the whole matrix in less than half a millisecond. The ball's hit on a microswitch lasts a few milliseconds, so the program is sure to see it. We'll measure it in the next lesson. That's why loop() mustn't wait here either.
  • Every switch in the matrix has its own debouncing that reacts to the first contact, as today.
  • We read the matrix with our own code from Exercise 1, not with the Keypad library from Exercise 3. The end of that exercise says why.
  • The tilt and the flipper buttons aren't in the matrix. The tilt has its own pin, and the flipper buttons are connected to the Arduino that drives the flippers, because a flipper must move the moment the button is pressed.

How a matrix works

A 4 × 4 membrane keypad has 16 keys but only 8 legs. Inside there are four conductors in rows (R1 to R4) and four in columns (C1 to C4). Each key sits where one row crosses one column. When you press it, it connects that row with that column.

Schematic of the 4 by 4 keypad: four horizontal rows R1 to R4 on pins D12 to D9 and four vertical columns C1 to C4 on pins D8 to D5. At each crossing there is one key, from 1, 2, 3 and A in the first row to the star, 0, the hash and D in the last

The Arduino reads the keypad row by row:

  1. It connects one row to GND: that row’s pin is an output and sends LOW. The other rows are switched off.
  2. It reads all four columns. The columns have a pull-up resistor, like a button, so they read HIGH. A column that reads LOW is connected to the switched-on row through a pressed key.
  3. It switches that row off and moves on to the next one.
One key close up: key 1 connects column C1 on pin D8, an input (INPUT), with row R1 on pin D12, an output (OUTPUT). Inside the Arduino, in the dashed box, a grey pull-up resistor of 20 to 50 kilohms connects pin D8 to 5 V

The picture shows one key close up. What’s inside the Arduino is drawn in grey (the dashed box): the built-in pull-up resistor of 20 to 50 kΩ connects the pin of column C1 to 5 V. While key 1 is released, no current flows through the resistor and the column reads HIGH. When you press it while row R1 is LOW, the key connects the column with the row, so the column reads LOW.

The row and the column together say which key is pressed: LOW on column C2 while row R1 is switched on means key 2. Reading the whole keypad like this is called scanning. The Arduino scans it in less than half a millisecond, so it repeats this thousands of times a second.

SwitchesEach on its own pinIn a matrix
1616 pins4 rows + 4 columns = 8 pins
6464 pins8 rows + 8 columns = 16 pins

A row that is switched off isn't HIGH

The program doesn't set a row it isn't scanning to HIGH; it turns it into an input (INPUT). Then it isn't connected to anything, and no current can flow through it.

If a switched-off row were HIGH, two pressed keys in the same column would connect a row that sends HIGH with a row that sends LOW. That would be a short circuit between two pins, and it can damage the Arduino.

Materials

Each participant gets:

  • an Arduino Uno and a USB cable
  • a 4 × 4 membrane keypad and 8 jumper wires with pins on both ends
  • a large breadboard, four small buttons, four diodes and jumper wires

For the demonstration in Exercise 1, the leader needs a two-channel oscilloscope with two probes, a small breadboard and a few jumper wires.

We don’t need the circuit from the last lesson any more: take it apart and put the parts back in the box.

Wiring rules

  1. While we wire, the Arduino is unplugged from the USB cable.
  2. Upload the program first, and only then connect the keypad. The Arduino may still have some old program on it in which the keypad’s pins are outputs, for example the traffic light program. If a program like that ran while you pressed keys, it could connect two outputs. So upload the program from Exercise 1 while nothing is connected to the Arduino yet.
  3. Before switching on, the leader checks the wiring.

Exercise 1: Scanning the keypad

The program

This program reads the keypad and prints the character of every key you press in the Serial Monitor. We’ll use the same program in Exercises 2 and 4 too.

Plan before you program:
function getKey:
for each row:
switch the row on: output, LOW
for each column:
read the column
if the key has changed and the debounce time is over:
remember the new state and the time
if the key has just been pressed:
switch the row off
return its character
switch the row off
return NO_KEY: no key has just been pressed
repeat forever:
ask getKey whether a key has just been pressed
if it has: print its character
// C++
const unsigned long DEBOUNCE_TIME = 50;   // after a change, we don't look at that key for 50 ms
const char NO_KEY = 0;                    // getKey() returns this when no key has just been pressed

const int rows[4] = {12, 11, 10, 9};      // legs 1 to 4 of the keypad: rows R1 to R4
const int columns[4] = {8, 7, 6, 5};      // legs 5 to 8 of the keypad: columns C1 to C4

// The character of each key: the first number is the row, the second the column.
const char keys[4][4] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

bool wasPressed[4][4];                    // what each key was like last time (none at the start)
unsigned long lastChange[4][4];           // when each key last changed

// Scans the keypad and returns the character of the key just pressed, or NO_KEY.
char getKey() {
  for (int r = 0; r < 4; r++) {        // each row in turn
    pinMode(rows[r], OUTPUT);          // switch the row on: an output...
    digitalWrite(rows[r], LOW);        // ...connected to GND
    delayMicroseconds(10);             // wait for the voltage on the columns to settle

    for (int c = 0; c < 4; c++) {      // each column in turn
      bool pressed = digitalRead(columns[c]) == LOW;
      if (pressed != wasPressed[r][c] && millis() - lastChange[r][c] >= DEBOUNCE_TIME) {
        wasPressed[r][c] = pressed;
        lastChange[r][c] = millis();
        if (pressed) {                 // the key has just been pressed
          pinMode(rows[r], INPUT);     // switch the row off before returning
          return keys[r][c];           // return the character and leave the function
        }
      }
    }

    pinMode(rows[r], INPUT);           // switch the row off before the next one
  }
  return NO_KEY;                       // no key has just been pressed
}

void setup() {
  for (int r = 0; r < 4; r++) {
    pinMode(rows[r], INPUT);           // the row is switched off
  }
  for (int c = 0; c < 4; c++) {
    pinMode(columns[c], INPUT_PULLUP); // we read the column, like a button
  }
  Serial.begin(9600);
}

void loop() {
  char key = getKey();                 // the character of the key just pressed, or NO_KEY
  if (key != NO_KEY) {
    Serial.println(key);
  }
}

Upload the program while nothing is connected to the Arduino yet.

What’s new in the program?

  • const int rows[4] = {12, 11, 10, 9}; – An array: one variable with several values, here four pin numbers. We get to each value by its position in square brackets, and we count from zero: rows[0] is 12, and rows[3] is 9.
  • const char keys[4][4] – A two-dimensional array: a table with four rows and four columns. keys[1][2] is the character in row 1 and column 2, so '6', because here too we count from zero. The type char stores one character, written in single quotes: '6', '#'.
  • bool wasPressed[4][4]; and unsigned long lastChange[4][4]; – Each of the 16 keys remembers its own state and the time of its last change, like a single button in the lesson on buttons. Arrays written above setup() are full of zeros at the start, so no key is pressed.
  • char getKey() – Our own function that returns a value, like readStepCount() in lesson 2.1. char in front of the name says that the function returns one character. We’ve moved the whole scan into the function, so loop() is short. We named it getKey, because that’s also the name of the library’s function in Exercise 3. That’s why loop() looks almost the same in both exercises.
  • const char NO_KEY = 0; – A character that means no key. getKey() returns it when no key has just been pressed, and then loop() prints nothing.
  • for (int r = 0; r < 4; r++) and inside it for (int c = 0; c < 4; c++) – A loop inside a loop. For each of the four rows, the inner loop goes through all four columns, so when the whole keypad is scanned, the body of the inner loop runs 16 times, once for each key.
  • return keys[r][c]; – return returns the character and leaves the function at once, even though both loops are still in the middle of their work. That’s why we switch the row off first. Otherwise it would stay switched on, and the program would see its keys while scanning other rows too.
  • If two new keys appear in the same scan, getKey() returns the first one. The second one’s state hasn’t been remembered yet, so on the next pass through loop() the function sees it as new again and returns it then. That way the program doesn’t miss any key.
  • pinMode(rows[r], OUTPUT); … pinMode(rows[r], INPUT); – We change a row’s pin between output and input while the program runs, not only in setup(). As an output with LOW, the row is switched on, and as an input it is switched off.
  • delayMicroseconds(10); – Waits 10 microseconds, a hundredth of a millisecond, for the voltage on the columns to settle after the row is switched on. It doesn’t make the program noticeably slower.
  • Each key’s debouncing is the same as in Exercise 3 of the lesson on buttons: the first change counts at once, and then we don’t look at that key for 50 ms.

Wiring

We look at the keypad’s legs from the front, with the keys facing us: legs 1 to 4 are the rows, and legs 5 to 8 the columns. Connect them in order to pins D12 to D5. Push one end of a jumper wire into the keypad’s connector and the other end straight into the Arduino’s pin. We don’t need the breadboard yet.

Keypad leg12345678
ConductorR1R2R3R4C1C2C3C4
Arduino pinD12D11D10D9D8D7D6D5
Wiring the keypad: the keypad's legs 1 to 4, the rows, go to pins D12 to D9, and legs 5 to 8, the columns, to pins D8 to D5

Most 4 × 4 membrane keypads have their legs in this order. If yours is different, the program will print the wrong characters. The section If something doesn’t work says how to fix that.

Plug in the USB cable, open the Serial Monitor (9600 baud) and press all the keys in turn. Does the program print the right character for every key?

Demonstration: an oscilloscope on the columns

The leader does this demonstration, and everyone watches. An oscilloscope draws how the voltage on a pin changes over time. Unlike a multimeter, it also sees changes that last only a few microseconds, so with it we can watch the scan as it happens. Our oscilloscope has two channels, CH1 and CH2, so it draws two pins at the same time: columns C1 and C2. The parts of our oscilloscope and the basic instructions are on the Oscilloscope page.

The ground clip goes only on GND

Each probe has a tip and a small crocodile clip for ground. Inside the oscilloscope, the grounds of both probes are connected to each other. If one clip were on GND and the other on some other pin, the oscilloscope would connect that pin to GND. That would be a short circuit. So both clips go on GND, and only the probe tips go on pins.

Getting ready:

  1. Unplug the USB cable. The leader doesn’t plug the wires of columns C1 and C2 straight into the Arduino, but through a breadboard: the wire from the keypad goes into its own row of the breadboard, and another wire goes from the same row to pin D8 (or D7). Into each of those two rows the leader pushes one more short wire, and hooks the probe tip onto its free end. One more wire connects the Arduino’s GND to a third row of the breadboard, for the grounds.
  2. Hook the tip of the CH1 probe onto column C1, and the tip of the CH2 probe onto column C2. Clip both ground clips onto the row with GND.
  3. The switch on the probe (1X or 10X) and the probe setting in the oscilloscope’s menu must be the same, for both channels. Usually it’s 10X.
  4. Set both channels to 2 V per division. Move CH1 to the top half of the screen and CH2 to the bottom half, so the traces don’t overlap.
  5. Set the time base to 50 µs per division.
  6. The trigger tells the oscilloscope when to start drawing. Set the source to CH1, the falling edge and the level to about 2.5 V: the oscilloscope draws from the moment the voltage on CH1 drops below 2.5 V. Use the MODE button to choose Auto mode, in which the oscilloscope draws even when there’s no trigger.
  7. Plug in the USB cable.

The demonstration:

  1. No key is pressed. Both traces are flat lines at 5 V.
  2. A participant holds key 1. The leader uses the MODE button to choose Normal mode: now the oscilloscope only draws when CH1 drops below 2.5 V, so the picture stands still. CH1 shows short drops to 0 V, one after another. The distance between two drops is one scan of the whole keypad. Is it shorter than half a millisecond, that is, than 10 divisions?
  3. A participant holds keys 1 and 2. The drops on CH1 and CH2 happen at the same time.
  4. A participant holds keys 1 and 5. The drop on CH2 starts about when the drop on CH1 ends.
  5. Catching a press. The participant lets go of all the keys, and the leader uses the MODE button to choose Single mode. The oscilloscope waits. When the participant presses key 1, the oscilloscope draws the moment of the press and stops. If the contacts bounce when the key is pressed, some of the first drops are missing or shorter than the rest.

The oscilloscope’s channel tells us the column, and the time of the drop tells us the row. That’s also how the program knows which key is pressed.

Which character is keys[2][1]?
'8'. We count from zero, so [2] is the third row ('7', '8', '9', 'C'), and [1] the second column in it.
Why are both columns at 5 V on the oscilloscope while no key is pressed?
The columns are inputs with a pull-up resistor (INPUT_PULLUP), and it holds them at 5 V. A switched-on row at LOW could pull a column down to 0 V only through a pressed key.
You hold key 1 all the time. Why is column C1 at 0 V only in short drops?
Key 1 connects column C1 with row R1, and R1 is LOW only while the program is scanning it. The rest of the time R1 is switched off (INPUT), so the pull-up holds C1 at 5 V. Each drop is a moment when the program switches row R1 on, once in every scan.
Why do the drops of keys 1 and 2 happen at the same time, while the drop of key 5 comes right after the drop of key 1?
Keys 1 and 2 are in the same row, R1, so their columns drop at the same moment: while R1 is switched on. Key 5 is in row R2, and the program switches it on right after row R1. The oscilloscope’s channel tells us the key’s column, and the time of the drop tells us its row.
How can you read from the oscilloscope how long one scan of the keypad takes?
Measure the distance between two neighbouring drops of the same key: count the divisions and multiply them by the time per division. For example, 4 divisions of 50 µs are 200 µs. Many oscilloscopes measure this distance themselves, as the period or the frequency.
What would a multimeter show on column C1 while you hold key 1?
A bit less than 5 V. The drops are too short for the multimeter to follow, so it shows the average voltage. The column is at 5 V most of the time and at 0 V only briefly, so the average is closer to 5 V. That’s why we need an oscilloscope for fast changes.

Exercise 2: Ghost keys

The program and the wiring stay the same. Press key 1 and hold it. Then press and hold key 2, and while you hold both, press key 4 as well. What does the program print?

Besides 1, 2 and 4, the program also prints 5, although nobody touched key 5. That’s a ghost key.

Left, without diodes: while row R2 is switched on, current from column C2 goes through key 2 into row R1, through key 1 back into column C1 and through key 4 into row R2, so column C2 reads LOW as if key 5 were pressed. Right, with diodes: key 1's diode stops the current and there is no ghost key

Look at the left side of the drawing. The program has switched on row R2 and is reading column C2. Key 5 isn’t pressed, but the current has found a way round (in red): from column C2 through key 2 into row R1, then through key 1 into column C1 and through key 4 into row R2. So column C2 reads LOW, and the program thinks key 5 is pressed.

The right side of the drawing shows how to fix this: next to each key there’s a diode, which lets current through in one direction only, from the column to the row. Current from column C2 still gets through key 2 into row R1, but the way round would have to go through key 1 backwards, from row R1 to column C1. Key 1’s diode doesn’t allow that (green tick), so column C2 stays at 5 V and there is no ghost key. We’ll add the diodes in Exercise 4.

A ghost key appears when three keys are pressed in three corners of a rectangle in the matrix. The fourth corner is then a ghost key.

You hold keys 5, 6 and 8. Which ghost key will appear?
Key 9. Keys 5 and 6 are in row R2, keys 5 and 8 in column C2. The fourth corner of that rectangle is row R3 and column C3, and that’s key 9. Try it!

Ghost keys on a computer

A computer keyboard is a matrix too. Cheap keyboards have no diodes, so some combinations of three or more keys don't work properly: the computer either sees a key you didn't press or doesn't see one you did. That's why gaming keyboards often have a diode next to every key, so the computer correctly sees any combination of keys.

Exercise 3: The keypad with the Keypad library (optional)

Do this exercise if you have time left. Keypads are so common that there’s a ready-made library for them, like Bounce2 for the button in the lesson on buttons. It’s called Keypad. We’ll write a program that does the same as the program from Exercise 1, but we’ll leave the scanning and the debouncing to the library. Then we’ll see what the library does with ghost keys.

The wiring stays the same, so you don’t need to disconnect the keypad. The library, too, has only one pin as an output at any moment, so it can’t connect two outputs.

Installing the library

  1. In the Arduino IDE, open the Library Manager: the icon with books on the left, or the menu Sketch ⇒ Include Library ⇒ Manage Libraries…
  2. Type Keypad in the search box.
  3. Install the library called just Keypad, by Mark Stanley and Alexander Brevig. The search also shows many other libraries with the word Keypad in their name, but we don’t need them.

Installing needs the internet. If the workshop has none, the leader installs the library on every computer beforehand.

Plan before you program:
repeat forever:
ask the library whether a key has just been pressed
if it has: print its character

The second part of the plan is the same as in Exercise 1; we only ask the library instead of our function. The first part of the plan, the function getKey, is now in the library.

The program with the library

// C++
#include <Keypad.h>                    // the library for keypads

byte rows[4] = {12, 11, 10, 9};        // the same pins as in Exercise 1
byte columns[4] = {8, 7, 6, 5};

char keys[4][4] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

Keypad keypad(makeKeymap(keys), rows, columns, 4, 4);

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

void loop() {
  char key = keypad.getKey();          // the character of the key just pressed, or NO_KEY
  if (key != NO_KEY) {
    Serial.println(key);
  }
}

Upload the program, open the Serial Monitor and press all the keys in turn. The program must print the same characters as the program from Exercise 1.

What’s new in the program?

  • #include <Keypad.h> – Includes the library in the program. If the library isn’t installed, compiling gives the error Keypad.h: No such file or directory.
  • byte rows[4] = {12, 11, 10, 9}; – The type byte stores a whole number from 0 to 255, which is enough for a pin number. The library wants the pins in an array of type byte, and without const, so here we can’t write const int as in Exercise 1.
  • Keypad keypad(makeKeymap(keys), rows, columns, 4, 4); – keypad is an object, like start in the lesson on buttons. We tell the library the characters of the keys, the row pins, the column pins, and how many rows and columns there are. makeKeymap() turns our table of characters into the form the library wants.
  • setup() has only Serial.begin(9600); – The library sets up the pins, so we don’t write pinMode().
  • char key = keypad.getKey(); – The library’s getKey() function does the same as ours from Exercise 1: it scans the keypad and returns the character of the key that has just been pressed, and if there’s no such key, it returns NO_KEY. We write keypad. in front of it, because it’s a function of the object. Apart from that, loop() is the same as in Exercise 1.
  • There’s no getKey() function of our own, no NO_KEY constant, and no wasPressed and lastChange arrays. The library has all of that.

Advantages of the library:

  • The program is much shorter and easier to read. All that’s left from Exercise 1 is the pins, the table of characters and loop().
  • Debouncing is already built in, so we don’t have to write it.
  • The library knows more than presses: for every key it knows whether it has just been pressed, whether someone has been holding it for longer than half a second, or whether it has just been released. That comes in handy for a combination lock or a menu.
  • A keypad of another size, for example a 4 × 3 phone keypad, only needs other numbers and another table of characters.
  • Many people use the library, so most of its bugs have already been found and fixed. The menu File ⇒ Examples ⇒ Keypad has ready-made examples.

The disadvantages are the same as with the Bounce2 library: it has to be installed on every computer, we can’t see how it works, and it takes up more memory. This program is about 830 bytes bigger than the program from Exercise 1.

Ghost keys with the library

Repeat the experiment from Exercise 2: press and hold key 1, then key 2, and while you hold both, press key 4 as well. What does the program print now?

The program prints only 1. There’s no ghost key 5, but there are no keys 2 and 4 either, although you really pressed them.

The library doesn’t remove ghost keys. It also scans a keypad without diodes, so it too reads LOW for key 5, like our program in Exercise 2. From the voltages on the pins, there’s no way to tell whether key 5 is really pressed or the current has found a way round. No program can do that.

We don’t see the ghost key because of the way the library’s getKey() works. The library keeps the pressed keys in a list, and getKey() only reports a new key in the first place on the list. While you hold key 1, it’s the first, so getKey() reports neither 2, nor 4, nor the ghost 5. The function getKeys() from the same library reports all the pressed keys, so besides 1, 2 and 4 it would also report the ghost key 5.

At most, a program could notice that three keys in the corners of a rectangle are pressed and not trust the fourth, like some cheap computer keyboards, which then prefer not to see one key. The Keypad library doesn’t even do that. Only diodes really remove ghost keys, and we’ll add them in Exercise 4.

Did the Keypad library remove the ghost key?
No. It also reads LOW for key 5. It doesn’t print the ghost key only because, while you hold key 1, getKey() doesn’t report any other key, so it doesn’t report the real keys 2 and 4 either. With the function getKeys(), the ghost key 5 would appear.

Why we don’t use it in the pinball machine

For a keypad that a person presses, the Keypad library is a good choice. Still, the pinball machine’s main Arduino will read the matrix with our code from Exercise 1, because the library does three things that get in the pinball machine’s way:

  • It scans the keypad at most once every 10 ms. If 10 ms haven’t passed since the last scan, getKey() returns NO_KEY straight away, without even looking at the keypad. That’s how the library debounces the keys. A finger holds a key much longer, so that doesn’t matter. The ball’s hit on a microswitch lasts only a few milliseconds and can pass completely between two scans. Then the program doesn’t see it at all. Our program scans the whole matrix on every pass through loop().
  • The library’s getKey() reports only one key. While you hold key 1, the library’s getKey() doesn’t report other keys, as you saw in the experiment with ghost keys. Our getKey() from Exercise 1 does. In the pinball machine, the ball and the player often press several switches at the same time. The library also has a function getKeys() for several keys, but with it the program isn’t short any more.
  • It scans in the opposite direction. Our program switches on the rows and reads the columns, so current flows through a pressed key from the column to the row. The library switches on the columns (LOW) and reads the rows (pull-up), so current flows from the row to the column. In Exercise 4 we’ll add diodes that only let current through from the column to the row. With them, the library wouldn’t see any key until we swapped the rows and columns in the program.
Why can the Keypad library miss the ball's hit in the pinball machine?
The library scans the matrix at most once every 10 ms. The ball’s hit lasts only a few milliseconds, so it can start and end between two scans. Then no scan sees the microswitch pressed. Our program scans the whole matrix in less than half a millisecond, so it sees every hit that lasts longer than that.

Exercise 4: Diodes against ghost keys

A diode is a one-way valve for current: it lets it through in one direction only, from the anode to the cathode. You already know one diode well: an LED is a diode that lights up as it does this. On an ordinary diode, the cathode is marked with a stripe.

In our matrix, current flows through a pressed key from the column, which the pull-up resistor holds at 5 V, to the switched-on row at LOW. So we put a diode next to each key: the anode towards the key, and the cathode towards the row. Current then flows through a pressed key just as before. But the way round has to go through key 1 backwards, from the row to the column, and the diode doesn’t allow that. Look at the right side of the drawing from Exercise 2, repeated here:

Left, without diodes: while row R2 is switched on, current from column C2 goes through key 2 into row R1, through key 1 back into column C1 and through key 4 into row R2, so column C2 reads LOW as if key 5 were pressed. Right, with diodes: key 1's diode stops the current and there is no ghost key

We can’t add diodes to the keypad, so we’ll build a small 2 × 2 matrix on the breadboard, with four buttons and four diodes.

Wiring

The program is the one from Exercise 1. If you did Exercise 3, first upload the program from Exercise 1 again, because the library scans in the opposite direction and wouldn’t see any key with these diodes. Then unplug the USB cable and pull the wires out of the keypad. Our small matrix is connected to the first two rows and the first two columns, so to the program its buttons are keys 1, 2, 4 and 5. The other pins stay unconnected, and the program doesn’t see any key on them.

PartArduino pinOn the breadboard
Button 1–across the centre gap, in e3, e5, f3, f5
Button 2–across the centre gap, in e9, e11, f9, f11
Button 4–across the centre gap, in e15, e17, f15, f17
Button 5–across the centre gap, in e21, e23, f21, f23
Diodes–h5–h8, h11–h14, h17–h20 and h23–h26; stripe (cathode) at the bottom, in rows 8, 14, 20 and 26
Column C1D8wire from D8 to a3, wire from b3 to b15
Column C2D7wire from D7 to a9, wire from c9 to c21
Row R1D12wire from D12 to f8, wire from i8 to i14
Row R2D11wire from D11 to f20, wire from i20 to i26
Schematic of the 2 by 2 matrix: buttons SW1 to SW4 are keys 1, 2, 4 and 5. Each button connects its column, C1 on D8 or C2 on D7, through its own diode D1 to D4 to its row, R1 on D12 or R2 on D11. The diodes' cathodes face the rows
The 2 by 2 matrix on the breadboard: four buttons across the centre gap in rows 3 and 5, 9 and 11, 15 and 17, and 21 and 23, diodes from h5 to h8, h11 to h14, h17 to h20 and h23 to h26, the columns D8 to a3 and D7 to a9 with wires b3 to b15 and c9 to c21, the rows D12 to f8 and D11 to f20 with wires i8 to i14 and i20 to i26
  • Each button connects its two rows of the breadboard, for example button 1 rows 3 and 5. Row 3 leads to the column, and row 5 through the diode to the row of the matrix.
  • The matrix doesn’t need GND on the breadboard. The switched-on row of the matrix, a pin with LOW, is the same as GND for the keys.
  • A diode keeps about half a volt across itself, so the column of a pressed key doesn’t drop all the way to 0 V. That’s still well below 1.5 V, so the Arduino is sure to read LOW.

Plug in the USB cable and open the Serial Monitor. Press each button: does the program print 1, 2, 4 and 5? Then press and hold buttons 1, 2 and 4. Does the program also print 5?

Experiments

1. The diode is the protection. Unplug the USB cable, take out button 1’s diode and put a wire from h5 to h8 in its place. Plug in the USB cable and hold buttons 1, 2 and 4 again. What happens? Put the diode back.

2. A diode the wrong way round. Turn button 2’s diode round, so that the stripe is at the top, in h11. Does button 2 work? Turn it back.

Why is it button 1's diode that matters in experiment 1?
The way round goes through key 1 backwards, from row R1 to column C1. That’s why it’s button 1’s diode that stops the current, so there’s no ghost key. Through the diodes of buttons 2 and 4, the current on that way flows in the direction they let it through. Without button 1’s diode the way round is open, so the ghost key 5 appears again.
Why doesn't button 2 work with its diode the wrong way round?
A diode the wrong way round lets current through only from the row to the column. A pressed button needs to let current through from the column to the row, and the reversed diode doesn’t allow that. So the column stays HIGH, and the program doesn’t see the key.

If something doesn’t work

  • The program prints nothing ⇒ Choose 9600 baud. Are all the wires in the keypad’s connector? Do they go in order from legs 1 to 8 to D12 to D5?
  • The program prints the wrong characters, for example 4 when you press 2 ⇒ On your keypad, the rows and columns are swapped. In the program, swap the numbers in the arrays: make rows {8, 7, 6, 5} and columns {12, 11, 10, 9}.
  • The program prints the characters in reverse order, for example D when you press 1 ⇒ The wires are the wrong way round in the connector. Connect leg 1 to D12 and leg 8 to D5.
  • A whole row or a whole column of keys doesn’t work ⇒ One wire isn’t pushed in properly. Find out from the table which one, and check it.
  • In Exercise 4 one button doesn’t work ⇒ Is its diode the wrong way round? The stripe must be at the bottom, in row 8, 14, 20 or 26. Is the button across the centre gap?
  • In Exercise 4 a ghost key still appears ⇒ A diode is missing or bridged by a wire. Check that all four diodes are in place.
  • The program won’t upload ⇒ Check that the board Arduino Uno and the right port are selected in the Arduino IDE.

Extra challenges

  1. A combination lock. Let the program remember the last four keys pressed. When you press #, let it print Open if they were 1, 9, 7 and 0, and Wrong otherwise. Tip: store the four characters in an array char code[4];.
  2. How many keys at once? Every half a second, let the program print how many keys are pressed right now. On the keypad without diodes, try to find four keys that you can press at once without a ghost key appearing.
  3. How long does a scan take? micros() works like millis(), but returns microseconds. Use it to measure how long one scan of the whole keypad takes. Is it shorter than the ball’s hit, which lasts a few milliseconds?

What did we learn?

  • In a matrix, the switches are in rows and columns. 16 switches in a 4 × 4 matrix need only 8 pins.
  • The Arduino scans the matrix: it switches one row on (LOW), reads all the columns (INPUT_PULLUP), switches the row off and moves on to the next one.
  • On the oscilloscope, the column of a pressed key drops to 0 V briefly once in every scan, while that key’s row is switched on.
  • A row that is switched off is an input (INPUT), not HIGH. Otherwise two keys in the same column would connect two outputs.
  • An array is a variable with several values. We get to a value by its position, and we count from zero: rows[0].
  • A two-dimensional array is a table: keys[row][column]. The type char stores one character.
  • A loop inside a loop goes through all the rows, and in each of them through all the columns.
  • Three pressed keys in three corners of a rectangle give a ghost key in the fourth corner, because the current finds a way round.
  • The Keypad library scans the keypad for us, so the program is much shorter, but it doesn’t remove ghost keys. We don’t use it in the pinball machine, because it scans at most once every 10 ms and can miss a short hit of the ball.
  • A diode lets current through only from the anode to the cathode. The stripe on a diode is the cathode.
  • A diode next to each key stops the way round, so there are no ghost keys.

Quick quiz

1. How many pins does a matrix with 8 rows and 8 columns need, and how many switches can it have?
8 + 8 = 16 pins, and it can have 8 × 8 = 64 switches.
2. How does the program know which key is pressed?
The program switches on one row at a time and reads all the columns in each. If a column reads LOW while a row is switched on, the key where that row crosses that column is pressed.
3. Why is a switched-off row INPUT and not HIGH?
Two pressed keys in the same column would connect a row at HIGH with a row at LOW. That’s a short circuit between two pins. An input isn’t connected to anything, so no current flows through it.
4. Why do we upload the program first and only then connect the keypad?
An old program on the Arduino may use the same pins as outputs, for example the traffic light program. If it ran while we pressed keys, it could connect an output at HIGH with an output at LOW. Our program keeps the rows as inputs until it scans them, so it’s safe.
5. What is rows[0] in the program from Exercise 1?
  1. We count from zero, so rows[0] is the first value in the array {12, 11, 10, 9}: the pin of row R1.
6. How many times does the body of the inner loop run when getKey() scans the whole keypad?
16 times. The outer loop goes through 4 rows, and the inner one through 4 columns for each row: 4 × 4 = 16, once for each key.
7. When does a ghost key appear?
When three keys are pressed in three corners of a rectangle in the matrix. The current then finds a way round through those three keys, so the column of the fourth corner reads LOW, as if the fourth key were pressed too.
8. Which way round do we put the diode next to a key, and why?
The anode faces the key and the column, and the cathode, with the stripe, faces the row. That way the diode lets current through from the column to the switched-on row, as it flows through a pressed key, and stops current that would go backwards on the way round.