[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Using a keypad matrix

Using a keypad matrix

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (23,002 posts)  [Biography] bio   Forum Administrator
Date Tue 20 Feb 2018 11:06 PM (UTC)

Amended on Fri 16 Mar 2018 10:52 PM (UTC) by Nick Gammon

Message
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




- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (23,002 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Fri 16 Mar 2018 09:31 PM (UTC)

Amended on Fri 16 Mar 2018 09:33 PM (UTC) by Nick Gammon

Message
Making a message generator


As a moderator on a number of forums I find that I frequently have to do "canned" responses (like, "please post your schematic", or "format your code by pressing Ctrl+K").

Whilst such responses may look robotic, you get tired of typing them in from scratch each time, and you can afford to take the time when setting them up to make them pleasantly-worded, and include helpful hyperlinks.

I used a spare Arduino Leonardo (or Arduino Micro) to generate such messages. You use your keypad matrix as shown above, connect up the 8 wires (eg. to pins 2 through to 9) and then type in suitable message.

The code is below:


// Author: Nick Gammon
// Date: 16 March 2018

#include <Keyboard.h>
#include <Keypad_Matrix.h>

const byte ROWS = 4;
const byte COLS = 4;

// what is on the keypad keys
char  keys[ROWS][COLS] = {
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' },
};

// how much to slow down keyboard output (so keys aren't missed)
const unsigned long DELAY_BETWEEN_KEYS = 10;

// keypad matrix wiring
byte rowPins[ROWS] = { 2, 3, 4, 5 };    // connect to the row pinouts of the keypad
byte colPins[COLS] = { 6, 7, 8, 9 }; // connect to the column pinouts of the keypad

// light the LED on key-down
const byte ledPin = 13;
// when we last lit the LED
unsigned long ledLit;

// helper macros to output strings with "F" macro around them
#define MSG(arg)                \
  do                            \
    myKeyboard.print (F (arg)); \
  while (false)

#define MSGLN(arg)                \
  do                              \
    myKeyboard.println (F (arg)); \
  while (false)

// do a newline easily
#define NEWLINE()       \
  do                       \
    myKeyboard.println (); \
  while (false)

// to avoid compiler warnings about unused arguments
#define UNUSED(expr) (void)(expr)

// Create the Keypad
Keypad_Matrix kpd = Keypad_Matrix (makeKeymap (keys), rowPins, colPins, ROWS, COLS);

// class for printing to our keyboard with a delay between each key
class my_Keyboard_class : public Print
{
  public:
  virtual size_t write (uint8_t c);
}; // end of class my_Keyboard_class

size_t my_Keyboard_class::write (uint8_t character)
{
  Keyboard.write (character);
  delay (DELAY_BETWEEN_KEYS);
  return 1;
} // end of my_Keyboard_class::write

my_Keyboard_class myKeyboard;

void setup ()
{
  // initialize control over the keyboard:
  Keyboard.begin ();

  // set up various pins
  pinMode (ledPin, OUTPUT);

  // initialize the keypad handler
  kpd.begin ();
  kpd.setKeyDownHandler (keyDown);
  kpd.setKeyUpHandler   (keyUp);

} // end of setup

// for things like multiple arrows, eg: specialKey (KEY_LEFT_ARROW, 2);
void specialKey (const uint8_t whichKey, const int n)
{
  for (int i = 0; i < n; i++)
    {
    Keyboard.press (whichKey);
    delay (5);
    Keyboard.releaseAll ();
    delay (5);
    } // end of for
} // end of specialKey

// messages for holding down A and then pressing a number, * or #
void do_A_messages (const char which)
{
  switch (which)
    {
    case '1': MSG ("Sending message for A+1"); break;
    case '2': MSG ("Sending message for A+2"); break;
    case '3': MSG ("Sending message for A+3"); break;
    case '4': MSG ("Sending message for A+4"); break;
    case '5': MSG ("Sending message for A+5"); break;
    case '6': MSG ("Sending message for A+6"); break;
    case '7': MSG ("Sending message for A+7"); break;
    case '8': MSG ("Sending message for A+8"); break;
    case '9': MSG ("Sending message for A+9"); break;
    case '0': MSG ("Sending message for A+0"); break;
    case '*': MSG ("Sending message for A+*"); break;
    case '#': MSG ("Sending message for A+#"); break;
    } // end of switching on which key
} // end of do_A_messages

// messages for holding down B and then pressing a number, * or #
// Note: Using MSGLN means each one will end with a newline
void do_B_messages (const char which)
{
  switch (which)
    {
    case '1': MSGLN ("Sending message for B+1"); break;
    case '2': MSGLN ("Sending message for B+2"); break;
    case '3': MSGLN ("Sending message for B+3"); break;
    case '4': MSGLN ("Sending message for B+4"); break;
    case '5': MSGLN ("Sending message for B+5"); break;
    case '6': MSGLN ("Sending message for B+6"); break;
    case '7': MSGLN ("Sending message for B+7"); break;
    case '8': MSGLN ("Sending message for B+8"); break;
    case '9': MSGLN ("Sending message for B+9"); break;
    case '0': MSGLN ("Sending message for B+0"); break;
    case '*': MSGLN ("Sending message for B+*"); break;
    case '#': MSGLN ("Sending message for B+#"); break;
    } // end of switching on which key
} // end of do_B_messages

// messages for holding down C and then pressing a number, * or #
void do_C_messages (const char which)
{
  switch (which)
    {
    case '1': MSG ("Sending message for C+1"); break;
    case '2': MSG ("Sending message for C+2"); break;
    case '3': MSG ("Sending message for C+3"); break;
    case '4': MSG ("Sending message for C+4"); break;
    case '5': MSG ("Sending message for C+5"); break;
    case '6': MSG ("Sending message for C+6"); break;
    case '7': MSG ("Sending message for C+7"); break;
    case '8': MSG ("Sending message for C+8"); break;
    case '9': MSG ("Sending message for C+9"); break;
    case '0': MSG ("Sending message for C+0"); break;
    case '*': MSG ("Sending message for C+*"); break;
    case '#': MSG ("Sending message for C+#"); break;
    } // end of switching on which key
} // end of do_C_messages

// messages for holding down D and then pressing a number, * or #
void do_D_messages (const char which)
{
  switch (which)
    {
    case '1': MSG ("Sending message for D+1"); break;
    case '2': MSG ("Sending message for D+2"); break;
    case '3': MSG ("Sending message for D+3"); break;
    case '4': MSG ("Sending message for D+4"); break;
    case '5': MSG ("Sending message for D+5"); break;
    case '6': MSG ("Sending message for D+6"); break;
    case '7': MSG ("Sending message for D+7"); break;
    case '8': MSG ("Sending message for D+8"); break;
    case '9': MSG ("Sending message for D+9"); break;
    case '0': MSG ("Sending message for D+0"); break;
    case '*': MSG ("Sending message for D+*"); break;
    case '#': MSG ("Sending message for D+#"); break;
    } // end of switching on which key

} // end of do_D_messages

// message for pressing a key with no modifier key
void do_unshifted_messages (const char which)
{
  switch (which)
    {
    case '1': MSG ("Sending message for 1"); break;
    case '2': MSG ("Sending message for 2"); break;
    case '3': MSG ("Sending message for 3"); break;
    case '4': MSG ("Sending message for 4"); break;
    case '5': MSG ("Sending message for 5"); break;
    case '6': MSG ("Sending message for 6"); break;
    case '7': MSG ("Sending message for 7"); break;
    case '8': MSG ("Sending message for 8"); break;
    case '9': MSG ("Sending message for 9"); break;
    case '0': MSG ("Sending message for 0"); break;
    case '*': MSG ("Sending message for *"); break;
    case '#': MSG ("Sending message for #"); break;
    } // end of switching on which key
} // end of do_unshifted_messages

// do action on key-up
void keyUp (const char which)
{
  if (kpd.isKeyDown ('A'))
    do_A_messages (which);
  else if (kpd.isKeyDown ('B'))
    do_B_messages (which);
  else if (kpd.isKeyDown ('C'))
    do_C_messages (which);
  else if (kpd.isKeyDown ('D'))
    do_D_messages (which);
  else
    do_unshifted_messages (which);
} // end of keyUp

// on key-down turn on the LED
void keyDown (const char which)
{
  UNUSED (which);
  digitalWrite (ledPin, HIGH);
  ledLit = millis ();
} // end of keyDown

void loop ()
{
  kpd.scan ();
  // turn LED off after 200 ms
  if (millis () - ledLit >= 200)
    digitalWrite (ledPin, LOW);
} // end of loop



The above code is included in the "examples" folder of the library mentioned earlier.

Even without the diodes you can use the keypad with two keys pressed simultaneously. You need the diodes if you want to press three or more at once.

The general idea is to use the keys on the left (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, * and #) to generate a message, with A, B, C, D being used as modifier keys (like Ctrl, Shift, Alt on a normal keyboard).

Thus typing "1" might send a certain message but "A+1" would send a different message, and "B+1" a third message and so on.

This lets you store 60 different messages (unshifted, A+n, B+n, C+n and D+n) where you have 12 choices for "n".

As you can see in the code, you just have to modify the messages for each combination to suit your circumstances.

The messages are printed using the F() macro, which means that the message strings are kept in program memory, and not copied into RAM. I found, for example, that with some fairly lengthy messages that I use as a moderator, I still only used 17 KB of 28 KB available on my Leonardo (the rest was for the bootloader).

If you install the diodes you can have even more combinations, eg. B+C+n could send something different.

I made a special class "my_Keyboard_class" whose sole purpose was to slow down the delivery of keystrokes to the computer it is connected to. Without that sometimes keystrokes would be lost, because they are sent too quickly to the host computer. You may need to tweak the DELAY_BETWEEN_KEYS constant to suit your situation.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


35,426 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]