Arduino Uno Event keypad Implementation

Hi, I am trying to implement an event keypad using the Uno and 4x4 matrix keypad. I am a little stuck at toggling between two modes Auto and Manual. I have tried implementing the following code so far. So when I press the key C it prints the default mode and if I hold it down it must continuously toggle between the two modes. And after desired mode comes up, I intend to use the key D to press enter and select the mode. I wish to implement this using a 16x2 character LCD.

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

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

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );


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

  keypad.addEventListener(keypadEvent); // Add an event listener for this keypad
}

void loop() {
  char key = keypad.getKey();

  if (key) {
    //Serial.println(key);
  }
}

// Taking care of some special events.
void keypadEvent(KeypadEvent key) {
  switch (keypad.getState()) {
    case PRESSED:
      if (key == 'C') {
        char x[] = "Manual Mode";
        Serial.println(x);
      }
      break;


    case RELEASED:
      if (key == 'C') {
        //do nothing
      }
      break;

    case HOLD:
      if (key == 'C')
      {
        char y[] = "Auto Mode";
        Serial.println(y);
      }
      break;
  }
}
  keypad.addEventListener(keypadEvent); // Add an event listener for this key

Do you have a clue what this means?

void loop() {
  char key = keypad.getKey();

A hint: It means that you do NOT call getKey() in loop().

I am an amateur here

So, ditch event listener call and associated function, until you understand what they are doing, and why you might want to use them.

The reason for using an event listener is so your friend Joe can handle some event while you are busy watching the ballgame and drinking beer. Wouldn’t want to stop doing that to run see if the phone is blinking a light. Might miss something, or spill some beer.

For now, let Joe watch the game and drink the beer, while you deal with the switch presses.

So when I press the key C it prints the default mode

You have, or are trying to implement, an auto mode and a manual mode. You have NOT defined which is the “default” mode.

You are NOT timing how long the key is pressed, so you have NO idea how to distinguish a quick press and release from a put a weight in the key for two weeks.

Makes sense. How can i implement all this?

So is adding the event listener better or using the traditional switch(key) method? So I also have to check whether a key-press is for a long or a short duration?

How can i implement all this?

Start with some real requirements. "if I hold it down, it must continuously toggle..." is NOT a requirement that can be implemented. It is a wish list item.

A requirement that CAN be implemented would say "if the key is held down for more than 17 milliseconds, toggle the mode every 17 milliseconds...".

Once you do this, you can see that you need to record when the switch becomes pressed. From this, you can see that you need to know whether the key is pressed now and whether or not is was pressed the last time you looked. The state change detection example shows how to do that. What the example does doesn't really matter. You can (and should) junk what it actually does when it detects that a change has occurred. Write your own code, which needs to record when the change happens.

Separately, you need to see if the switch IS pressed, and if so, has it been pressed long enough to warrant a change. If it is pressed, and has been pressed long enough, toggle the state (there's a hint) and reset the became-pressed time. Look at the blink without delay example to see how to use millis() and how to determine how long ago an event occurred.

PaulS:  keypad.addEventListener(keypadEvent); // Add an event listener for this key

Do you have a clue what this means?

void loop() {
  char key = keypad.getKey();

A hint: It means that you do NOT call getKey() in loop().

Just so there is no misunderstanding, since the documentation for Keypad is crap on this and even the event example is, in my opinion, not very good:

You absolutely do have to call getKey() if you want a event listener to be triggered. The event listener is not implemented as an interrupt or anything, it is called implicitly as part of getKey().

If you want to use the event listener method, then just stick a plain keypad.getKey() in your loop and do all the keypad processing in the event listener. If you want to use the getKey() method, then don't bother with the event listener.

There have been other folks getting confused and introducing subtle errors into their code when they try to mix both techniques.

Personally I would just stick with getKey(), or getKeys() if you need to detect multiple key presses or states of keys other then PRESSED.

So I must get rid of the void loop & include keypad.getkey in the Event keypad loop. Is this what you are suggesting??

what you are suggesting??

I am suggesting that YOU, NOT the event manager, manage the reading of the keypad. When YOU can deal with the keypad, and you UNDERSTAND how it works, then you can put the event listener back in, and you’ll KNOW what it is supposed to do, and what you don’t need to do.

Read some of the examples that come along with the library and try to understand them - in particular HelloKeypad. Then try to modify them and work from there.

You can also search the internet for other people's examples, but remember that not everything you find will be good quality.

Hi, I have tried the following code. But still seeing a couple of errors. I am getting an error like: exit status 1
‘class Keypad’ has no member named ‘currentState’. How do I implement a similar logic for a keypad. The code I tried implementing was for long and short press for a button using the millis function. Please help !!

#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(14, 15, 16, 17, 18, 19);//RS,EN,D4,D5,D6,D7

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 12, 11}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

#define PRESSED LOW
#define NOT_PRESSED HIGH

const unsigned long shortPress = 100;
const unsigned long  longPress = 500;

unsigned long counter = 0;
bool prevState = NOT_PRESSED;
bool currentState;

void setup()
{
  for (int k = 14; k < 20; k++)
  {
    pinMode(k, OUTPUT); //pins 14-19 are enabled as output
  }
  lcd.begin(16, 2);//initializing LCD
  keypad.setDebounceTime(10);
}

void loop()
{
  char key = keypad.getKey();

  if (key != NO_KEY) // Check for a valid key.
  {
    switch (key)
    {

      case 'C':
        // check the keypad
        keypad.currentState = keypad.getState();

        // has it changed?
        if (keypad.currentState != keypad.prevState) {
          delay(keypad.debounce);
          // update status in case of bounce
          keypad.currentState = keypad.getState();
          if (keypad.currentState == PRESSED) {
            // a new press event occured
            // record when key went down
            keypad.counter = millis();
          }

          if (keypad.currentState == NOT_PRESSED) {
            // but no longer pressed, how long was it down?
            unsigned long currentMillis = millis();
            //if ((currentMillis - keypad.counter >= shortPress) && !(currentMillis - keypad.counter >= longPress)) {
            if ((currentMillis - keypad.counter >= shortPress) && !(currentMillis - keypad.counter >= longPress)) {
              // short press detected.
              handleShortPress();
            }
            if ((currentMillis - keypad.counter >= longPress)) {
              // the long press was detected
              handleLongPress();
            }
          }
          // used to detect when state changes
          keypad.prevState = keypad.currentState;
        }
    }


    void handleShortPress() {
      lcd.setCursor(0, 0);
      lcd.print("Set Mode:");
      lcd.setCursor(10, 0);
      lcd.print("Auto");
    }

    void handleLongPress() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Set Mode:");
      lcd.setCursor(10, 0);
      lcd.print("Manual");
    }

I am getting an error like: exit status 1

That is the LAST thing that the compiler reports, and only means that compilation failed.

Show us ALL of the errors - the EXACT output from the compiler.

'class Keypad' has no member named 'currentState'.

So, why are you trying to assign a value to a non-existent member?

        keypad.currentState = keypad.getState();

keypad.Stop keypad.using keypad.keypad keypad.in keypad.front keypad.everything keypad.except keypad.Keypad keypad.instance keypad.members.

This the complete list of errors:

C:\Users\Umesh\Documents\Arduino\sketch_aug28b\sketch_aug28b.ino: In function 'void loop()':

sketch_aug28b:51: error: 'class Keypad' has no member named 'currentState'

keypad.currentState = keypad.getState();

^

sketch_aug28b:54: error: 'class Keypad' has no member named 'currentState'

if (keypad.currentState != keypad.prevState) {

^

sketch_aug28b:54: error: 'class Keypad' has no member named 'prevState'

if (keypad.currentState != keypad.prevState) {

^

sketch_aug28b:55: error: 'class Keypad' has no member named 'debounce'

delay(keypad.debounce);

^

sketch_aug28b:57: error: 'class Keypad' has no member named 'currentState'

keypad.currentState = keypad.getState();

^

sketch_aug28b:58: error: 'class Keypad' has no member named 'currentState'

if (keypad.currentState == PRESSED) {

^

sketch_aug28b:61: error: 'class Keypad' has no member named 'counter'

keypad.counter = millis();

^

sketch_aug28b:64: error: 'class Keypad' has no member named 'currentState'

if (keypad.currentState == NOT_PRESSED) {

^

sketch_aug28b:68: error: 'class Keypad' has no member named 'counter'

if ((currentMillis - keypad.counter >= shortPress) && !(currentMillis - keypad.counter >= longPress)) {

^

sketch_aug28b:68: error: 'class Keypad' has no member named 'counter'

if ((currentMillis - keypad.counter >= shortPress) && !(currentMillis - keypad.counter >= longPress)) {

^

sketch_aug28b:70: error: 'handleShortPress' was not declared in this scope

handleShortPress();

^

sketch_aug28b:72: error: 'class Keypad' has no member named 'counter'

if ((currentMillis - keypad.counter >= longPress)) {

^

sketch_aug28b:74: error: 'handleLongPress' was not declared in this scope

handleLongPress();

^

sketch_aug28b:78: error: 'class Keypad' has no member named 'prevState'

keypad.prevState = keypad.currentState;

^

sketch_aug28b:78: error: 'class Keypad' has no member named 'currentState'

keypad.prevState = keypad.currentState;

^

sketch_aug28b:84: error: a function-definition is not allowed here before '{' token

void handleShortPress() {

^

sketch_aug28b:91: error: a function-definition is not allowed here before '{' token

void handleLongPress() {

^

sketch_aug28b:97: error: expected '}' at end of input

}

^

sketch_aug28b:97: error: expected '}' at end of input

C:\Users\Umesh\Documents\Arduino\sketch_aug28b\sketch_aug28b.ino: In function ‘void loop()’:

sketch_aug28b:51: error: ‘class Keypad’ has no member named ‘currentState’

keypad.currentState = keypad.getState();

So, why did YOU put “keypad.” in front of currentState?

I removed keypad from places I should not have written it. I tried compiling it gives me an error like this:
GetFileAttributesEx C:\Users\Umesh\AppData\Local\Temp\arduino_build_965388: The system cannot find the file specified.

Error compiling for board Arduino/Genuino Uno.

#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(14, 15, 16, 17, 18, 19);//RS,EN,D4,D5,D6,D7

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 12, 11}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

#define PRESSED LOW
#define NOT_PRESSED HIGH

const unsigned long shortPress = 100;
const unsigned long  longPress = 500;

unsigned long counter = 0;
bool prevState = NOT_PRESSED;
bool currentState;

void setup()
{
  for (int k = 14; k < 20; k++)
  {
    pinMode(k, OUTPUT); //pins 14-19 are enabled as output
  }
  lcd.begin(16, 2);//initializing LCD
  keypad.setDebounceTime(10);
}

void loop()
{
  char key = keypad.getKey();

  if (key != NO_KEY) // Check for a valid key.
  {
    switch (key)
    {

      case 'C':
        // check the keypad
        currentState = keypad.getState();

        // has it changed?
        if (currentState != prevState) {
          delay(keypad.debounce);
          // update status in case of bounce
          currentState = keypad.getState();
          if (currentState == PRESSED) {
            // a new press event occured
            // record when key went down
            counter = millis();
          }

          if (currentState == NOT_PRESSED) {
            // but no longer pressed, how long was it down?
            unsigned long currentMillis = millis();
            //if ((currentMillis - keypad.counter >= shortPress) && !(currentMillis - keypad.counter >= longPress)) {
            if ((currentMillis - counter >= shortPress) && !(currentMillis - counter >= longPress)) {
              // short press detected.
              handleShortPress();
            }
            if ((currentMillis - counter >= longPress)) {
              // the long press was detected
              handleLongPress();
            }
          }
          // used to detect when state changes
          prevState = currentState;
        }
    }


    void handleShortPress() {
      lcd.setCursor(0, 0);
      lcd.print("Set Mode:");
      lcd.setCursor(10, 0);
      lcd.print("Auto");
    }

    void handleLongPress() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Set Mode:");
      lcd.setCursor(10, 0);
      lcd.print("Manual");
    }

sketch_aug28a.ino: In function 'void loop()': sketch_aug28a:56: error: 'class Keypad' has no member named 'debounce' sketch_aug28a:84: error: a function-definition is not allowed here before '{' token sketch_aug28a:91: error: a function-definition is not allowed here before '{' token sketch_aug28a:97: error: expected }' at end of input sketch_aug28a:97: error: expected}' at end of input

You are STILL using keypad. in places you are not supposed to. You are STILL not posting complete compiler output.

The latest code I posted was the one after I removed most of the keypad like you asked. I tried compiling it shows this:

#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(14, 15, 16, 17, 18, 19);//RS,EN,D4,D5,D6,D7

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 12, 11}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

#define PRESSED LOW
#define NOT_PRESSED HIGH

const unsigned long shortPress = 100;
const unsigned long  longPress = 500;

unsigned long counter = 0;
bool prevState = NOT_PRESSED;
bool currentState;

void setup()
{
  for (int k = 14; k < 20; k++)
  {
    pinMode(k, OUTPUT); //pins 14-19 are enabled as output
  }
  lcd.begin(16, 2);//initializing LCD
  keypad.setDebounceTime(10);
}

void loop()
{
  char key = keypad.getKey();

  if (key != NO_KEY) // Check for a valid key.
  {
    switch (key)
    {

      case 'C':
        // check the keypad
        currentState = keypad.getState();

        // has it changed?
        if (currentState != prevState) {
          delay(keypad.debounce);
          // update status in case of bounce
          currentState = keypad.getState();
          if (currentState == PRESSED) {
            // a new press event occured
            // record when key went down
            counter = millis();
          }

          if (currentState == NOT_PRESSED) {
            // but no longer pressed, how long was it down?
            unsigned long currentMillis = millis();
            //if ((currentMillis - keypad.counter >= shortPress) && !(currentMillis - keypad.counter >= longPress)) {
            if ((currentMillis - counter >= shortPress) && !(currentMillis - counter >= longPress)) {
              // short press detected.
              handleShortPress();
            }
            if ((currentMillis - counter >= longPress)) {
              // the long press was detected
              handleLongPress();
            }
          }
          // used to detect when state changes
          prevState = currentState;
        }
    }


    void handleShortPress() {
      lcd.setCursor(0, 0);
      lcd.print("Set Mode:");
      lcd.setCursor(10, 0);
      lcd.print("Auto");
    }

    void handleLongPress() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Set Mode:");
      lcd.setCursor(10, 0);
      lcd.print("Manual");
    }

Compiler output:

C:\Program Files (x86)\Arduino\arduino-builder -dump-prefs -logger=machine -hardware C:\Program Files (x86)\Arduino\hardware -tools C:\Program Files (x86)\Arduino\tools-builder -tools C:\Program Files (x86)\Arduino\hardware\tools\avr -built-in-libraries C:\Program Files (x86)\Arduino\libraries -libraries C:\Users\Umesh\Documents\Arduino\libraries -fqbn=arduino:avr:uno -ide-version=10803 -build-path C:\Users\Umesh\AppData\Local\Temp\arduino_build_965388 -warnings=all -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.avrdude.path=C:\Program Files (x86)\Arduino\hardware\tools\avr -prefs=runtime.tools.arduinoOTA.path=C:\Program Files (x86)\Arduino\hardware\tools\avr -prefs=runtime.tools.avr-gcc.path=C:\Program Files (x86)\Arduino\hardware\tools\avr -verbose C:\Users\Umesh\Documents\Arduino\sketch_aug28b\sketch_aug28b.ino
GetFileAttributesEx C:\Users\Umesh\AppData\Local\Temp\arduino_build_965388: The system cannot find the file specified.

Error compiling for board Arduino/Genuino Uno.

When I copy the code in reply #16, and paste it into the 1.0.5 version of the IDE (so I don't need to save it on my computer), I get:

C:\Users\pjs9486\Documents\Arduino_105\hardware\tools\avr\bin\avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -MMD -DUSB_VID=null -DUSB_PID=null -DARDUINO=105 -IC:\Users\pjs9486\Documents\Arduino_105\hardware\arduino\cores\arduino -IC:\Users\pjs9486\Documents\Arduino_105\hardware\arduino\variants\standard -IC:\Users\pjs9486\Documents\Arduino_105\libraries\LiquidCrystal -IC:\Users\pjs9486\Documents\Arduino\libraries\Keypad C:\Users\pjs9486\AppData\Local\Temp\build4668895475738686793.tmp\sketch_aug28a.cpp -o C:\Users\pjs9486\AppData\Local\Temp\build4668895475738686793.tmp\sketch_aug28a.cpp.o sketch_aug28a.ino: In function 'void loop()': sketch_aug28a:56: error: 'class Keypad' has no member named 'debounce' sketch_aug28a:84: error: a function-definition is not allowed here before '{' token sketch_aug28a:91: error: a function-definition is not allowed here before '{' token sketch_aug28a:97: error: expected }' at end of input sketch_aug28a:97: error: expected}' at end of input

When I start trying to put every { on a line by itself, and use Tools + Auto Format to correct the indenting, I see that the number of { does not match the number of }.

Ok mate, I’ll throw you a fish:

#include <LiquidCrystal.h>
#include <Keypad.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(14, 15, 16, 17, 18, 19); //RS,EN,D4,D5,D6,D7

const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 12, 11}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

const unsigned long LongPress = 1000;

void setup()
{
  Serial.begin(9600);
  lcd.begin(16, 2); //initializing LCD
}

void loop()
{
  if (keypad.getKeys()) // check for keypad activity
  {
    // we won't handle multiple keypresses, just single ones, so just key index 0
    const byte key = keypad.key[0].kchar;
    const byte state = keypad.key[0].kstate; // IDLE, PRESSED, HOLD, RELEASED

    switch (key) {

      case 'C': {
          static unsigned long pressedTime; // static so the value is remembered like a global
          if (state == PRESSED) {
            pressedTime = millis();
          } else if (state == RELEASED) {
            if (millis() - pressedTime > LongPress) {
              handleLongPress();
            } else {
              handleShortPress();
            }
          }
        } break;

      case 'X': {
          // something else
        } break;

    }
  }
}

void handleShortPress() {
  // do we not want an lcd.clear() here as well?
  lcd.setCursor(0, 0);
  lcd.print("Set Mode:");
  lcd.setCursor(10, 0);
  lcd.print("Auto");
  Serial.println("short");
}

void handleLongPress() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set Mode:");
  lcd.setCursor(10, 0);
  lcd.print("Manual");
  Serial.println("long");
}

PaulS has already pointed out your carelessness with pointy brackets {}

keypad.debounce is private to the Keypad class instance, you can’t access it, but I don’t think you should need to manually debounce when using the Keypad library.

LiquidCrystal lcd(14, 15, 16, 17, 18, 19);//RS,EN,D4,D5,D6,D7
...
  for (int k = 14; k < 20; k++)
  {
    pinMode(k, OUTPUT); //pins 14-19 are enabled as output
  }

Creating the lcd has already initialised the pin states. You are only likely to break something by doing it again. The library sample code does not do this, so why are you doing it?

#define PRESSED LOW
#define NOT_PRESSED HIGH
...
bool prevState = NOT_PRESSED;

I get that you want to make you code more literal with those defines but, as your code gets larger, those defines will just add to confusion. Stick with HIGH/LOW true/false;

const unsigned long  longPress = 500;

500 milliseconds is very short for a long press. That would be annoying.

Thanks Arduarn the code works perfectly for me. Thanks to PaulS as well you guys have been a real help.

Cheers!!!

GautamD