What is a keypad?
Keypads are cheap and readily available. For example, this 4 x 4 keypad:
You can get these keypads for around $US 1 from eBay (especially the "membrane" type). Ones with real keys might be $US 5.
A cheap keypad (that is, without diodes, explained later) will be wired up like this:
The pull-up resistors won't be there, but I have shown them to illustrate how the scanning software works. You can simulate them by setting the columns to INPUT_PULLUP mode.
Preferably wire in diodes (computer keyboards would have diodes) like this:
If necessary you could prise the keypad open, break the traces next to each key, and solder in a surface-mounted diode (like the 1N4148) to provide the diode protection.
No switch pressed
In this situation the four inputs (Col 1 to Col 4) would all read HIGH.
Ready to test a row
The program now programs Row 1 to be LOW and OUTPUT and everything else to be an input.
Press a switch
If we now press the switch in Row 1, Column 1, we see that Column 1 will read LOW and all the other columns will read HIGH due to the pull-up resistors.
So what the code would do is:
- In sequence, set one row to OUTPUT and LOW and the other rows to INPUT.
- Check each column.
- Any column reading LOW is pressed.
- Set that row back to INPUT and move on to the next row.
In time you can deduce which of the 16 switches are closed. (The timing is quite fast, it would only be a few milliseconds, if that).
I measured around 300 µs for doing a scan with no keys pressed. If that time seems a bit long, you could always do a time check, and scan every 5 ms or so.
If one or more keys are pressed it will take a bit longer, as the code needs to call the handler function to deal with the keypress. However this only happens on a change of status (eg. going from not-pressed to pressed).
Why the diodes?
Without diodes you can get "ghost" presses. For example:
You can see there that switches are closed in row 1 and row 2 in column 2, and also row 2 in column 1. This lets the current drag down column 1 when row 1 is being tested, even though the switch in row 1, column 1 is not pressed.
If you trace out the current flow with the diodes in place, you will see back-to-back diodes which will stop the current flowing back to column 1.
Suggested diodes are small-signal fast-switching diodes like the 1N4148. You can buy 200 of them on eBay for around $US 1.
Below is how I modified the Jaycar keypad matrix by prising off the back, and soldering in a 1N4148 for each switch.
It is tedious to do, because for each switch you have to:
- Cut the trace at a suitable place, making a small gap
- Remove the solder resist coating with a file or similar
- Tin the resulting bare tracks
- Position the diode oriented the correct way (the negative side (cathode) goes towards the rows, the positive side towards the columns). There will be a small line etched on the diode on the cathode side.
- Somehow hold it in place (eg. with sticky tape)
- Solder both legs of the diode
In case you want to modify this particular keypad at home, I have marked with a blue dot the orientation of each diode. The dot is the cathode (there is a line etched on my diodes at the cathode end).
Example code for dedicated keyboard
The code below shows how you might make a dedicated 87-key keyboard. It's not a library, it is designed to be used stand-alone, and output changes in key state via the serial port.
With a Atmega328P (like in the Arduino Uno) you have 20 general input/output ports (digital 0 to 13, and analog 0 to 5). This gives us 19 ports for the keypad matrix and one spare port for the serial output. You may need to use SoftwareSerial and in particular a send-only version of SoftwareSerial (which only uses one port) which is available here: https://github.com/nickgammon/SendOnlySoftwareSerial.
An alternative would be to use an 74HC165 input shift register for the inputs (columns) which would reduce the number of ports needed for the columns from 8 to 3 (MISO, SCK, LOAD). In that case you would definitely need the external pull-up resistors because the 74HC165 would not provide a built-in pull-up.
// Keypad_Decoder
//
// Author: Nick Gammon
// Date: 17th February 2018
// Outputs to Serial in the format: 0b1nnnnnnn for a key down and 0b0nnnnnnn for a key-up
// nnnnnnn will be the current row/column combination
// Also every HEARTBEAT_TIME outputs 0xFF if all keys are up (heartbeat)
#include <limits.h> /* for CHAR_BIT */
const byte ROWS = 4;
const byte COLS = 4;
const bool ENABLE_PULLUPS = true; // make false if you are using external pull-ups
const unsigned long DEBOUNCE_TIME = 10; // milliseconds
const unsigned long HEARTBEAT_TIME = 2000; // milliseconds
const bool DEBUGGING = false; // make true for human-readable output
// define here where each row and column is connected to
const byte rowPins [ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
const byte colPins [COLS] = {2, 3, 4, 5}; //connect to the column pinouts of the keypad
// See: http://c-faq.com/misc/bitsets.html
#define BITMASK(b) (1 << ((b) % CHAR_BIT))
#define BITSLOT(b) ((b) / CHAR_BIT)
#define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b))
#define BITCLEAR(a, b) ((a)[BITSLOT(b)] &= ~BITMASK(b))
#define BITTEST(a, b) ((a)[BITSLOT(b)] & BITMASK(b))
// number of items in an array
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
// total number of keys
const byte TOTAL_KEYS = ROWS * COLS;
// remember previous setting of each key
char lastKeySetting [(TOTAL_KEYS + CHAR_BIT - 1) / CHAR_BIT]; // one bit each, 0 = up, 1 = down
unsigned long lastKeyTime [TOTAL_KEYS]; // when that key last changed
unsigned long lastHeartbeat; // when we last sent the heartbeat
void setup ()
{
Serial.begin (115200);
while (!Serial) { } // wait for Serial to become ready (Leonardo etc.)
// set each column to input-pullup (optional)
if (ENABLE_PULLUPS)
for (byte i = 0; i < COLS; i++)
pinMode (colPins [i], INPUT_PULLUP);
} // end of setup
void loop ()
{
byte keyNumber = 0;
unsigned long now = millis (); // for debouncing
// check each row
for (byte row = 0; row < ROWS; row++)
{
// set that row to OUTPUT and LOW
pinMode (rowPins [row], OUTPUT);
digitalWrite (rowPins [row], LOW);
// check each column to see if the switch has driven that column LOW
for (byte col = 0; col < COLS; col++)
{
// debounce - ignore if not enough time has elapsed since last change
if (now - lastKeyTime [keyNumber] >= DEBOUNCE_TIME)
{
bool keyState = digitalRead (colPins [col]) == LOW; // true means pressed
if (keyState != (BITTEST (lastKeySetting, keyNumber) != 0)) // changed?
{
lastKeyTime [keyNumber] = now; // remember time it changed
// remember new state
if (keyState)
BITSET (lastKeySetting, keyNumber);
else
BITCLEAR (lastKeySetting, keyNumber);
if (DEBUGGING)
{
Serial.print (F("Key "));
Serial.print (keyNumber);
if (keyState)
Serial.println (F(" down."));
else
Serial.println (F(" up."));
} // if debugging
else
Serial.write ((keyState ? 0x80 : 0x00) | keyNumber);
} // if key state has changed
} // debounce time up
keyNumber++;
} // end of for each column
// put row back to high-impedance (input)
pinMode (rowPins [row], INPUT);
} // end of for each row
// Send a heartbeat code (0xFF) every few seconds in case
// the receiver loses an occasional keyup.
// Only send if all keys are not pressed (presumably the normal state).
if (now - lastHeartbeat >= HEARTBEAT_TIME)
{
lastHeartbeat = now;
bool allUp = true;
for (byte i = 0; i < ARRAY_SIZE (lastKeySetting); i++)
if (lastKeySetting [i])
allUp = false;
if (allUp)
{
if (DEBUGGING)
Serial.println (F("No keys pressed."));
else
Serial.write (0xFF);
} // end of all keys up
} // end if time for heartbeat
} // end of loop
Code available from GitHub:
https://github.com/nickgammon/Keypad_Decoder/blob/master/Keypad_Decoder.ino
The code above will handle any number of key-presses simultaneously, as it simply outputs a byte for key-down and another byte for key-up. The receiver would need to interpret those to work out which keys are down at a certain time. For example, Shift+Ctrl+C might be "shift down", "ctrl down", "C down" followed by the codes for them all going up.
The byte is formatted like this:
1nnnnnnn -> key nnnnnnn has gone down
0nnnnnnn -> key nnnnnnn has gone up
In addition, there is a "heartbeat" every couple of seconds of the value 0xFF which indicates that all keys are up. This is designed to handle the situation where the receiver might miss a key-up, and thus for the rest of the session think that (say) the Ctrl key is down.
The receiving processor can use an interrupt-driven reading routine so that the overhead of receiving keyboard data would be low.
There is also provision for debouncing, as the code ignore key state changes which happen in quick succession.
If you set DEBUGGING to true you get human-readable output, like this:
No keys pressed.
Key 1 down.
Key 6 down.
Key 10 down.
Key 10 up.
Key 1 up.
Key 6 up.
No keys pressed.
No keys pressed.
For converting the key codes to actual keyboard keys I suggest running in debugging mode and then pressing each key on the keyboard in turn. Make a note of which key code corresponds to which key. You could then put that into an array which converts the raw "scan codes" into a character (eg. code 17 might be 'A').
Mapping keys to rows/columns
The matrix does not have to "physically" exist, in that you don't necessarily have to have a keyboard with 8 columns and 11 rows. One possible mapping for a keyboard would be:
Each box represents 8 keys, so that could be one column of the matrix. There are 11 boxes, so they are the 11 rows. That gives you 88 possible keys (in my case I had 87 keys so there is one spare).
In this example, the letter 'A' would be row 7, column 2.
Library to handle multiple keys at once
I have written a library to handle multiple simultaneous keys being pressed. This would let you implement something like "Shift+Ctrl+C". On a small keypad you might use the A/B/C/D as shift keys, and thus get quite a few possible combinations, even with only two keys pressed at once. With only two keys pressed you don't need diodes.
That would give you 12 actions (0 to 9, plus "*" and "#") on their own. Then you could press "A" and then one of those, giving you another 12. Since you have four modifier keys (A/B/C/D) you could therefore get 60 unique actions.
This library is available here: https://github.com/nickgammon/Keypad_Matrix
Just use the "clone or download" button to get a .zip file with all of the library files in it, and unzip that into your "libraries" folder inside your Arduino sketchbook folder.
/*
*
* Example of using Keypad_Matrix with a 4x4 keypad matrix.
*
*/
#include <Keypad_Matrix.h>
const byte ROWS = 4;
const byte COLS = 4;
// how the keypad has its keys laid out
const char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'},
};
const byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
const byte colPins[COLS] = {2, 3, 4, 5}; //connect to the column pinouts of the keypad
// Create the Keypad
Keypad_Matrix kpd = Keypad_Matrix( makeKeymap (keys), rowPins, colPins, ROWS, COLS );
void keyDown (const char which)
{
Serial.print (F("Key down: "));
Serial.println (which);
}
void setup()
{
Serial.begin (115200);
Serial.println ("Starting.");
kpd.begin ();
kpd.setKeyDownHandler (keyDown);
}
void loop()
{
kpd.scan ();
// do other stuff here
}
This library lets you handle key-down or key-up events (or both), plus it lets you test if other keys are pressed. For example, in the key-down handler you could add this:
if (kpd.isKeyDown ('A'))
Serial.println ("A is down as well.");
There is also provision for setting up custom row and column handlers, in case you want to use a shift register or expansion chip, rather than the normal Arduino ports.
References
|