3
\$\begingroup\$

I recently bought a 4x4 keypad.
It is one like this:
http://www.learningaboutelectronics.com/images/4x4-matrix-keypad-pinout.png

Now every code I found on the net was either using a highly overloaded library or was poorly hacked by a guy in the arduino forums.
I realized that almost all blogs about electronics just seem to copypaste the same code and changing the pictures so I felt like I had to work out something by myself.

Now, the common explained "tactic" was to iterate through every column and give it a LOW signal [for button pressed DOWN] and then check if anything came in at the row sides.
Then use a technique from video game programming to make sure the button would not stay in a pin-down state [so one press would count at a time and not a press occurred every whatsomany nanoseconds].

I was highly uncomfortable with this solution, so I came up with this code:

/* Reverse Keypad Handler
 * 2nd version
 * by clockw0rk @ 26.02.2017
 */

//define rows and columns
byte row [4] = {9,8,7,6};
byte col [4] = {5,4,3,2};

byte counter = 0;
byte colCount;

//16 fields keymapping 4x4 matrix
byte keys [ 0x4 ] [ 0x4 ] =
{
   {0x1,0x2,0x3,0xA},
   {0x4,0x5,0x6,0xB},
   {0x7,0x8,0x9,0xC},
   {0xE,0x0,0xF,0xD}
};


void setup() {
  for(int c = 0; c != 4; c++)
  {
    pinMode (row[c], INPUT_PULLUP);
    pinMode (col[c], OUTPUT);
    digitalWrite(col[c], LOW);
  }

   Serial.begin (9600);  //9600 BAUD for Uno Board Serial Communication
   Serial.println("INITIALISATION FINISHED\nEntering work loop...\n");

}

void loop() {

  // cycle through input pins and check for a signal
  if(digitalRead(row[counter]) == 0)
  {
    checkRoutine();
  }

  //reset counter for each iteration cycle
  counter <3 ? counter++ : counter = 0;

}

void checkRoutine(){

  colCount = 0;

  //debugging: give me the linecount
  Serial.print("[ LOW ] on line ");
  Serial.print(counter);
  Serial.print("\n");

  //now find which number is pressed
  while(colCount < 4 && digitalRead(row[counter]) == 0)
  {
     digitalWrite(col[colCount], HIGH);
     colCount++;
  }

  //remember we incremented in while loop
  Serial.print("Button pressed is [ ");
  Serial.print(keys[counter][colCount -1]);
  Serial.print(" ]\n");


  //reinitialise output
  for(int c = 0; c != 4; c++)
  {
        digitalWrite(col[c], LOW);
  }

  //wait until user releases button
  while(digitalRead(row[counter])==0)
  {;}
}

As you can see, I reversed the idea (kind of) by giving every output a permanent LOW and made sure the program flow is stopped until the user releases the button.

I would like to hear your thougths on this solution. Is it wasting memory or CPU time?
Arduino programming is absolutely fun but I just recently started with it and want to get better so I ask all of you "old veterans" to look over the lines and give me a feedback.

\$\endgroup\$
  • \$\begingroup\$ i would have liked to add the tag "keypad" but it says since i am new here i have to stick with what is already there \$\endgroup\$ – clockw0rk Feb 26 '17 at 22:00
2
\$\begingroup\$

Your general idea is correct - looping over columns to find out if one of them is active, and then only looping over the rows to find out which button was pressed will be somewhat faster (as in: more responsive) than checking each possible button separately.

If I were to suggest any improvements in the method - you could look into using pin change interrupts instead of busy looping. This would provide you with hardware support to detect when a pin goes LOW. This would also allow you to put your arduino to sleep to save power while waiting for a button to be pressed.

You might also need to debounce the keypress since you can get momentary false readings from a switch. This would basically require a small delay (usually less than 100ms) after you detect a keypress, and then rechecking after the delay if that switch is still active.


the comments below are going beyond your question but I decided to add them for some perspective on coding style

As far as your code goes I would suggest putting the entire key-checking logic in a single function that uses local variables for your column and row counters. This primarily helps with reasoning about the code but also has a small positive effect on memory usage overall (memory on the stack will be reclaimed after a return from the function).

Instead of using magic numbers throughout your code you should use constants/macros. This has the benefit of giving the value a name, and having a single place that would require changing instead of multiple values that are not easily ctrl+f'able. Calculating array length can be automated. This eases adding changes further on, and helps enforce correctness by the compiler.

You should also declare your pin arrays as const if possible. This will make optimizing your code easier for the compiler.

A sample of how this would look together:

//define rows and columns
const byte row[] = {9,8,7,6};
const byte rowCount = sizeof(row) / sizeof(row[0]); //old C trick for calculating static array length
const byte col[] = {5,4,3,2};
const byte colCount = sizeof(col) / sizeof(col[0]);

const byte keys [ rowCount ] [ colCount ] =
{
    {0x1,0x2,0x3,0xA},
    {0x4,0x5,0x6,0xB},
    {0x7,0x8,0x9,0xC},
    {0xE,0x0,0xF,0xD}
};
\$\endgroup\$
  • \$\begingroup\$ I much appreciate your comment and marked it as accepted answer. i will try to optimize the code based on your comments. thank you OpiF \$\endgroup\$ – clockw0rk Feb 27 '17 at 15:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.