Keypad data entry. A beginners guide

A common problem raised in the forum is how to use a keypad to enter a number to be used in a sketch. The examples with the Keypad library show the principles but there is little or no explanation of how they work or how they can be changed

The aim of this thread is to explain the principles of keypad entry, explain how and why some things work or don't work and to provide a way forward for more complicated use of the Keypad library

Let's start with "How do I enter a number using the keypad ?"

Try this small sketch

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

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

void loop()
{
  char key = keypad.getKey();
  if (key)
  {
    Serial.print("key : ");
    Serial.println(key);
  }
}

Change the pin numbers in the arrays and the baud rate to suit your system and upload the code and run it. Pressing a key on the keypad should print its value as defined in the keys array

Congratulations. You can now enter a number and print it.

Actually I am lying, as you can enter and print any value in the keys array even if they are not numbers. Let's improve that. Try this loop() function instead

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

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

void loop()
{
  char key = keypad.getKey();
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    Serial.print("key : ");
    Serial.println(key);
  }
}

Now the program only accepts numbers and prints them
Actually, I lied again. It is reading and printing a character that represents a number rather than an actual number. Try doing something with the "number" like this

void loop()
{
  char key = keypad.getKey();
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    Serial.print("key : ");
    Serial.println(key);
    Serial.print("key * 10 : ");
    Serial.println(key * 10);
    Serial.println();
  }
}

You will see that key * 10 is not what you want. What you are seeing is 10 * the value of the character not 10 times the number. So, how to fix it ? If you look carefully at the value printed by key * 10 you should spot a pattern. This revised loop() function takes advantage of that pattern by subtracting 48 from the value of the character the result of which is a single digit containing the actual number which you can use in the program

void loop()
{
  char key = keypad.getKey();
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    Serial.print("key : ");
    Serial.println(key);
    Serial.print("key * 10 : ");
    Serial.println(key * 10);
    byte keyAsANumber = key - 48;
    Serial.print("keyAsANumber : ");
    Serial.println(keyAsANumber);
    Serial.print("keyAsANumber * 10 : ");
    Serial.println(keyAsANumber * 10);
    Serial.println();
  }
}

If you are wondering where this "magic" number of 48 comes from then search Google for ASCII and all will be revealed. Incidentally, if it helps understand why it works you could also do

    byte keyAsANumber = key - '0';

instead

Future instalments will deal with how to input a multi digit number

OK, so now you enter a single digit, turn it into a number and use that number just like any other variable in your sketch. Before moving on to accepting and using multiple digit numbers let's deal with something that might occur to you. Why not declare the keys array like this

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

then there would be no need to manipulate the input to turn it into a number. Try this sketch and you will see that there is a problem when you run it

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

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

void loop()
{
  byte key = keypad.getKey();
  if (key >= 0 && key <= 9) //only act on numeric keys
  {
    Serial.print("key : ");
    Serial.println(key);
    Serial.print("key * 10 : ");
    Serial.println(key * 10);
    Serial.println();
  }
}

The problem is that the author of the Keypad library chose to return a zero from the getKey() function if no key is pressed and, of course, the sketch cannot tell the difference between the zero returned for no key and the zero returned when you press zero. The library could, of course, be modified to avoid the problem but that is for another day and another thread (maybe)

Another way round it would be to use the waitForKey() function, but that halts the sketch until a key is pressed, so if it is required to do something else whilst waiting, such as timing out if the user does not enter something after a period, then it is impossible. For that reason, the examples in this thread will use getKey()

So, how to read a multi digit input and put it into a numeric variable ? Let's start by restructuring a previous example like this

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;

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

void loop()
{
  readKeypad();
  if (entryComplete)
  {
    Serial.println(keyAsANumber);
  }
}

void readKeypad()
{
  key = keypad.getKey();
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    keyAsANumber = key - 48;
    entryComplete = true;
  }
  else
  {
    entryComplete = false;
  }
}

Note the changes :

The actual reading of a key and dealing with it has been turned into a function. Once it is debugged we can call it as and when required and could, if we wanted to, make it available to other sketches
Variables have been declared as global to make them available throughout the sketch (this is not the place to discuss the merits of local variables over global variables)
A new variable (entryComplete) has been introduced that flags what its name suggests. More on this later
Most of the debugging Serial.print()s have been removed now that we know what is going on

Next, the big change, inputting multi digit numbers

Stand by for the big change to allow input a multi digit number

Let's take as an example a number like 123
Breaking it down it is (1 * 100) + (2 * 10) + 3
From the previous examples we can input the 1, ,2 and 3 so we are well on our way.

We also need a way for the user to indicate that entry is complete (remember the entryComplete variable from the previous post ?). I have chosen to use entry of '#' as indication that entry is complete. Take a look at this

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned int total = 0;

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

void loop()
{
  entryComplete = false;  //will be checked after reading a key
  readKeypad(); //get a key if available
  if (entryComplete)  //number entry is complete
  {
    Serial.print("number entered : ");
    Serial.println(total);
    Serial.print("10 * number entered : ");
    Serial.println(10 * total); //prove that it is a number
    Serial.println();
    total = 0;  //reset the total
  }
}

void readKeypad()
{
  key = keypad.getKey();
  if (key == '#') //user signal that entry has finished
  {
    entryComplete = true; //flag completion
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.print(keyAsANumber);
  }
}

There are not as many changes as it seems at first
The readKeypad() is called from loop() as before
Before calling it the entryComplete flag is set to false
On return from the function the entryComplete variable is checked. The only way it could have changed is if the user has entered a '#'
If entry is complete the total and proof that it is a number are printed and the total is set back to 0 ready for the next entry

The readKeypad() function is where most of the changes have been made
First the key entered is checked to see whether it is '#'. If so the entryComplete flag is set to true and the function immediately returns to where it was called
If the key is not '#' then it is tested to see if it is a numeric character. If so, then we convert it to a real digit, multiply the total so far by 10 and add the new digit and return to where the function was called

Given entry of 1 then 2 then 3 then this is the same as (1 * 100) + (2 * 10) + 3. It is helpful to work through a couple of examples with pencil and paper to confirm that the method works

The next instalment will deal with getting user input but limiting it to say 3 digits thus removing the need for the user to signal that entry is complete

Suppose that we don't want to allow the user to signal the end of entry. We can make a simple couple of changes to the previous example to count the number of digits entered like this

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned int total = 0;
byte digitCount = 0;

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

void loop()
{
  entryComplete = false;  //will be checked after reading a key
  readKeypad(); //get a key if available
  if (entryComplete)  //number entry is complete
  {
    Serial.print("number entered : ");
    Serial.println(total);
    Serial.print("10 * number entered : ");
    Serial.println(10 * total); //prove that it is a number
    Serial.println();
    total = 0;  //reset the total
  }
}

void readKeypad()
{
  key = keypad.getKey();
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.print(keyAsANumber);
    digitCount++;
  }
  if (digitCount == 3) //user has entered 3 digits
  {
    entryComplete = true; //flag completion
    digitCount = 0;
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
}

I hope that it is obvious how this works. Basically we count the number of digits entered and set entryComplete to true when we have received 3 digits from the user

We can combine the two techniques to allow only 3 digits to be entered and require the user to signal that entry is complete, like this

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned int total = 0;
byte digitCount = 0;

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

void loop()
{
  entryComplete = false;  //will be checked after reading a key
  readKeypad(); //get a key if available
  if (entryComplete)  //number entry is complete
  {
    Serial.print("number entered : ");
    Serial.println(total);
    Serial.print("10 * number entered : ");
    Serial.println(10 * total); //prove that it is a number
    Serial.println();
    total = 0;  //reset the total
  }
}

void readKeypad()
{
  key = keypad.getKey();
  if (key == '#') //user signals complete
  {
    entryComplete = true; //flag completion
    digitCount = 0;
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
  if (digitCount == 3) //user has already entered 3 digits
  {
    return; //don't accept any more
  }
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.print(keyAsANumber);
    digitCount++;
  }
}

Doing that allows the sketch to be changed to allow the user to delete the last digit in the number right back to the first one should an error have been made in entering the digits

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned int total = 0;
byte digitCount = 0;

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

void loop()
{
  entryComplete = false;  //will be checked after reading a key
  readKeypad(); //get a key if available
  if (entryComplete)  //number entry is complete
  {
    Serial.print("number entered : ");
    Serial.println(total);
    Serial.print("10 * number entered : ");
    Serial.println(10 * total); //prove that it is a number
    Serial.println();
    total = 0;  //reset the total
  }
}

void readKeypad()
{
  key = keypad.getKey();
  if (key == '#') //user signals complete
  {
    entryComplete = true; //flag completion
    digitCount = 0;
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
  else if (key == '*' && digitCount > 0)  //user wants to delete a digit
  {
    total = total / 10; //remove the last digit
    digitCount--; //decrement the count of digits entered
    Serial.println();
    Serial.print("digit deleted.  The number entered is now : ");
    Serial.println(total);
  }
  if (digitCount == 3) //user has already entered 3 digits
  {
    return; //don't accept any more
  }
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.println(total);
    digitCount++;
  }
}

As before, the entry complete button is '#' but if the user presses '*' then the total is divided by 10 to remove the last digit. This works because the sketch uses an integer for the total. The count of digits entered is decremented but steps are taken to ensure that a digit can only be deleted when digitCount is greater than zero.

Note that even though the readKeypad() function has been changed substantially in the examples since the original the loop() function has remained unchanged. The readKeypad() function is simply called each time through loop() and only when the entry is complete is action taken.

Further possible enhancements would include :
Passing parameters such as length of entry to the readKeypad() function
Returning a value or values from the readKeypad() function
Allowing the user to select the digit to be deleted or amended
Allow entry of floating point numbers
Signalling that data entry is known by the user to be invalid and should be ignored

The next step. Multiple number entry and using the numbers entered.

Take a deep breath and look at this

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned long total = 0;  //changed data type to allow for larger numbers
byte digitCount = 0;
boolean getFirstNumber = true; //flags for data entry
boolean getsecondNumber = false;
unsigned long currentTime;  //current time used when blinking LED
unsigned long onPeriod; //LED on/off periods
unsigned long offPeriod;  //will be entered by the user
unsigned long currentPeriod;  //the current LED period
unsigned long periodStartTime; //time the period started
boolean okToBlink = false;  //will be set to true to start blinking
const byte ledPin = LED_BUILTIN;

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW); //turn off LED initially
  instructions();
}

void loop()
{
  currentTime = millis(); //used for timing blinks
  entryComplete = false;  //will be checked after reading a key
  readKeypad(4); //get a key if available - 4 digits max
  if (entryComplete)  //number entry is complete
  {
    if (getFirstNumber == true)  //first number entry complete
    {
      onPeriod = total; //copy the number entered to another variable
      Serial.print("the on period is ");
      Serial.println(onPeriod);
      getFirstNumber = false;  //flag first number entry is complete
      getsecondNumber = true;  //flag second number is to be entered
      total = 0;  //reset the total
    }
    else if (getsecondNumber == true)  //second number entry complete
    {
      offPeriod = total;  //copy the number entered to another variable
      Serial.print("the off period is ");
      Serial.println(offPeriod);
      getsecondNumber = false; //flag second number entry is complete
      okToBlink = true; //turn on blinking
      periodStartTime = currentTime;  //blinking starts now
      currentPeriod = onPeriod; //LED on for this period
      digitalWrite(ledPin, HIGH);  //turn off the LED
    }
  }
  if (okToBlink == true)  //blink the LED if OK to do so
  {
    blinkLed(); //blink the LED
  }
}

void blinkLed() //blink LED using BlinkWithoutDelay principle
//see https://forum.arduino.cc/index.php?topic=503368.0
{
  if (currentTime - periodStartTime >= currentPeriod)
  {
    if (digitalRead(ledPin)) //LED is on
    {
      currentPeriod = offPeriod;
    }
    else
    {
      currentPeriod = onPeriod;  //LED was off
    }
    digitalWrite(ledPin, !digitalRead(ledPin));
    periodStartTime = currentTime;
  }
}

void readKeypad(byte maxDigits) //added a parameter for max digits to accept
{
  key = keypad.getKey();
  if (key == '#') //user signals complete
  {
    entryComplete = true; //flag completion
    digitCount = 0;
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
  else if (key == '*' && digitCount > 0)  //user wants to delete a digit
  {
    total = total / 10; //remove the last digit
    digitCount--; //decrement the count of digits entered
    Serial.println();
    Serial.print("digit deleted.  The number entered is now : ");
    Serial.println(total);
  }
  if (digitCount == maxDigits) //user has already entered maximum digits
  {
    return; //don't accept any more
  }
  if (key >= '0' && key <= '9') //only act on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.println(total);
    digitCount++;
  }
}

void instructions()
{
  Serial.println("Use the keypad to enter 2 numbers up to 4 characters each");
  Serial.println("When entry is complete for a number click '#' on the keypad\n");
  Serial.println("The first number entered will set how long the LED is on and");
Serial.println("and the second how long the LED is off whilst blinking");
  Serial.println("To delete the last digit entered click '*' on the keypad");
}

Most of it should look familiar and it is heavily commented, but things to note :

As before all variables are global and the syntax used is verbose to make reading the code easier for beginners.
The readKeypad() funtion has been revised to take a parameter which sets how many digits can be entered. Note that each number entered could have a different number of maximum digits if required.
The readKeypad() function is called repeatedly as before until data entry is complete, at which point the total variable is copied to another one for use by the program
Boolean variables are used to signal whether data entry was for on time or off time of a blinking LED and hence the variable that the total is copied to
The LED does not start to blink until both the on and off periods have been entered
As written, once the LED is blinking the on/off periods cannot be changed. This maybe the subject of another instalment of this tutorial. We shall see...

Allowing the user to change the periods when the LED is already blinking needs the sketch to be changed a little.

Here is the revised sketch

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned long total = 0;  //changed data type to allow for larger numbers
byte digitCount = 0;
boolean gettingOnPeriod = true; //flags for data entry
boolean gettingOffPeriod = false;
unsigned long currentTime;  //current time used when blinking LED
unsigned long onPeriod; //LED on/off periods
unsigned long offPeriod;  //will be entered by the user
unsigned long currentPeriod;  //the current LED period
unsigned long periodStartTime; //time the period started
boolean ledIsBlinking = false;  //will be set to true to start blinking
const byte ledPin = LED_BUILTIN;

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  reset();
  instructions();
}

void loop()
{
  currentTime = millis(); //used for timing blinks
  if (ledIsBlinking)  //if the LED is blinking
  {
    blinkLed(); //blink the LED
    readKeypad(0, 'A'); //get a key but only accept 'A' to signal stop blinking
    if (entryComplete)
    {
      digitalWrite(ledPin, LOW);  //turn off the LED
      ledIsBlinking = false;  //turn off blinking
      reset();
      instructions();
    }
  }
  else  //LED is not blinking so need to get periods
  {
    readKeypad(4, '#'); //get a key if available - 4 digits max '#' signals entry complete
    if (entryComplete)
    {
      if (gettingOnPeriod)  //first number entry complete
      {
        onPeriod = total; //copy the number entered to another variable
        Serial.print("the on period is ");
        Serial.println(onPeriod);
        gettingOnPeriod = false;  //flag that on period entry is complete
        gettingOffPeriod = true;  //flag second number is to be entered
        total = 0;  //reset the total ready for next entry
      } //end of getting on period
      else if (gettingOffPeriod)  //second number entry complete
      {
        offPeriod = total;  //copy the number entered to another variable
        Serial.print("the off period is ");
        Serial.println(offPeriod);
        Serial.println("the LED is blinking");
        gettingOffPeriod = false; //flag that off period entry is complete
        ledIsBlinking = true; //turn on blinking
        periodStartTime = currentTime;  //blinking starts now
        currentPeriod = onPeriod; //LED on for this period
        digitalWrite(ledPin, HIGH);  //turn on the LED
        Serial.println("press 'A' to start again");
      } //end of getting off period
    }   //end of entry complete
  }     //end of LED not blinking
}       //end of loop()

void readKeypad(byte maxDigits, char completeKey) //max digits to accept and key signalling complete
{
  entryComplete = false;
  key = keypad.getKey();
  if (key == completeKey) //user signals entry is complete
  {
    Serial.print("got 'complete' key : ");
    Serial.println(key);
    entryComplete = true; //flag completion
    digitCount = 0;
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
  else if (key == '*' && digitCount > 0)  //user wants to delete a digit
  {
    total = total / 10; //remove the last digit
    digitCount--; //decrement the count of digits entered
    Serial.println();
    Serial.print("digit deleted.  The number entered is now : ");
    Serial.println(total);
  }
  if (digitCount == maxDigits) //user has already entered maximum digits
  {
    return; //don't accept any more
  }
  if (key >= '0' && key <= '9') //only accept on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.println(total);
    digitCount++;
  }
}

void instructions()
{
  Serial.println("Use the keypad to enter 2 numbers up to 4 characters each");
  Serial.println("When entry is complete for a number click '#' on the keypad\n");
  Serial.println("The first number entered will set how long the LED is on and");
  Serial.println("and the second how long the LED is off whilst blinking");
  Serial.println("To delete the last digit entered click '*' on the keypad");
}

void blinkLed() //blink LED using BlinkWithoutDelay principle
//see https://forum.arduino.cc/index.php?topic=503368.0
{
  if (currentTime - periodStartTime >= currentPeriod)
  {
    if (digitalRead(ledPin)) //LED is on
    {
      currentPeriod = offPeriod;
    }
    else
    {
      currentPeriod = onPeriod;  //LED was off
    }
    digitalWrite(ledPin, !digitalRead(ledPin));
    periodStartTime = currentTime;
  }
}

void reset()
{
  Serial.println("resetting for new period inputs");
  Serial.println();
  gettingOnPeriod = true; //flags for data entry
  gettingOffPeriod = false;
  onPeriod = 0; //reset the periods
  offPeriod = 0;
  total = 0;
  digitalWrite(ledPin, LOW); //turn off LED
}

Changes made as follows

instructions() and reset() functions added so that they can be used when required from anywhere in the sketch
Some of the variable names have been changed to reflect what is going on in the sketch
The syntax of testing boolean variables has been shortened which takes advantage of the more descriptive variable names, for instance

 if (ledIsBlinking)

The readKeypad() function has been changed to allow passing a key value to indicate that number entry is complete a parameter. This is not strictly necessary but has been done as an example. The key used to delete a digit, or even abort entry completely, could also be passed as a parameter if required
Note that the readKeypad() function is quite happy to accept 0 as the number of digits to accept
The logic of the loop() function has become more complicated which results in lines like these

      } //end of getting off period
    }   //end of entry complete
  }     //end of LED not blinking
}       //end of loop()

which in my opinion is less than ideal. The sketch is a "state machine" which I feel would be better written using switch/case to separate the code into named sections which are more easy to understand and I may post an example of that next, although it is getting away from the core subject of this tutorial

Same functionality as in post #6 but using enums and switch/case to control program flow

#include <Keypad.h>

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

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

enum ledStatus
{
  BLINKING,
  OFF
};
ledStatus currentLedStatus = OFF;

enum entryStatus
{
  GETTING_ON_PERIOD,
  GETTING_OFF_PERIOD,
  NO_ENTRY
};
entryStatus currentEntryAction = GETTING_ON_PERIOD;

char key;
byte keyAsANumber;
boolean entryComplete;
unsigned long total = 0;  //changed data type to allow for larger numbers
byte digitCount = 0;
boolean gettingOnPeriod = true; //flags for data entry
boolean gettingOffPeriod = false;
unsigned long currentTime;  //current time used when blinking LED
unsigned long onPeriod; //LED on/off periods
unsigned long offPeriod;  //will be entered by the user
unsigned long currentPeriod;  //the current LED period
unsigned long periodStartTime; //time the period started
const byte ledPin = LED_BUILTIN;

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  reset();
  instructions();
}

void loop()
{
  currentTime = millis(); //used for timing blinks
  ledStatusAction();
}

void ledStatusAction()  //take action based on the LED status
{
  switch (currentLedStatus)
  {
    case BLINKING:  //currently blinking so user may want to stop
      blinkLed();
      readKeypad(0, 'A'); //get a key but only accept 'A' to signal stop blinking
      if (entryComplete)
      {
        digitalWrite(ledPin, LOW);  //turn off the LED
        reset();
        instructions();
        currentLedStatus = OFF;
        currentEntryAction = GETTING_ON_PERIOD;
      }
      break;
    case OFF: //currently not blinking so get on and off periods from user
      readKeypad(4, '#'); //get a key if available - 4 digits max '#' signals entry complete
      if (entryComplete)
      {
        actOnEntry();
      }
      break;
  }
}

void actOnEntry() //get on or off period based on current state
{
  switch (currentEntryAction)
  {
    case GETTING_ON_PERIOD: //on period entry is complete
      onPeriod = total; //copy the number entered to another variable
      Serial.print("the on period is ");
      Serial.println(onPeriod);
      currentEntryAction = GETTING_OFF_PERIOD;  //next state
      total = 0;  //reset the total ready for next entry
      break;
    case GETTING_OFF_PERIOD:  //on period entry is complete so start blinking
      offPeriod = total;  //copy the number entered to another variable
      Serial.print("the off period is ");
      Serial.println(offPeriod);
      Serial.println("the LED is blinking");
      currentEntryAction = NO_ENTRY;  //stop getting input from keypad
      currentLedStatus = BLINKING;  //flag LED as blinking
      periodStartTime = currentTime;  //blinking starts now
      currentPeriod = onPeriod; //LED on for this period
      digitalWrite(ledPin, HIGH);  //turn on the LED
      Serial.println("press 'A' to start again");
      break;
  }
}

void readKeypad(byte maxDigits, char completeKey) //max digits to accept and key signalling complete
{
  entryComplete = false;
  key = keypad.getKey();
  if (key == completeKey) //user signals entry is complete
  {
    Serial.print("got 'complete' key : ");
    Serial.println(key);
    entryComplete = true; //flag completion
    digitCount = 0;
    Serial.println();
    Serial.println("entry is complete");
    return; //exit the function
  }
  else if (key == '*' && digitCount > 0)  //user wants to delete a digit
  {
    total = total / 10; //remove the last digit
    digitCount--; //decrement the count of digits entered
    Serial.println();
    Serial.print("digit deleted.  The number entered is now : ");
    Serial.println(total);
  }
  if (digitCount == maxDigits) //user has already entered maximum digits
  {
    return; //don't accept any more
  }
  if (key >= '0' && key <= '9') //only accept on numeric keys
  {
    keyAsANumber = key - 48;
    total = total * 10;
    total = total + keyAsANumber; //add the new number
    Serial.println(total);
    digitCount++;
  }
}

void instructions()
{
  Serial.println("Use the keypad to enter 2 numbers up to 4 characters each");
  Serial.println("When entry is complete for a number click '#' on the keypad\n");
  Serial.println("The first number entered will set how long the LED is on and");
  Serial.println("and the second how long the LED is off whilst blinking");
  Serial.println("To delete the last digit entered click '*' on the keypad");
}

void blinkLed() //blink LED using BlinkWithoutDelay principle
//see https://forum.arduino.cc/index.php?topic=503368.0
{
  if (currentTime - periodStartTime >= currentPeriod)
  {
    if (digitalRead(ledPin)) //LED is on
    {
      currentPeriod = offPeriod;
    }
    else
    {
      currentPeriod = onPeriod;  //LED was off
    }
    digitalWrite(ledPin, !digitalRead(ledPin));
    periodStartTime = currentTime;
  }
}

void reset()
{
  Serial.println("resetting for new period inputs");
  Serial.println();
  gettingOnPeriod = true; //flags for data entry
  gettingOffPeriod = false;
  onPeriod = 0; //reset the periods
  offPeriod = 0;
  total = 0;
  digitalWrite(ledPin, LOW); //turn off LED
}

Note too that most code has been moved to appropriately named functions which should also help with understanding it

What if you want to input a floating point number via the keypad ? It is easy enough to add a key for the decimal point to the keypad definition but the simple (10 * total ) + new digit trick will not work

It is possible to imagine collecting the digits before the decimal point and after the decimal point then combining them into a single float, but there is another way

Take a look at this

#include <Keypad.h>

const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] =
{
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', '.'}  //note that the decimal point key has been added
};

byte colPins[COLS] = {10, 11, 12, A4}; //column pins
byte rowPins[ROWS] = {2, 4, 7, 8}; //row pins

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

char key;
boolean entryComplete;
const byte MAX_CHARS = 10;
char inputBuffer[MAX_CHARS];
byte inputIndex = 0;
float floatTotal = 0;
unsigned int intTotal;

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

void loop()
{
  entryComplete = false;  //will be checked after reading a key
  readKeypad(); //get a key if available
  if (entryComplete)  //number entry is complete
  {
    Serial.print("number entered (float) : ");
    Serial.println(floatTotal, 3);
    intTotal = floatTotal;  //convert float to integer
    Serial.print("number entered (integer) : ");
    Serial.println(intTotal);
    floatTotal = 0;  //reset the total
    inputIndex = 0; //and the index to the input buffer
  }
}

void readKeypad()
{
  key = keypad.getKey();
  if (key == '#') //user signal that entry has finished
  {
    entryComplete = true; //flag completion
    Serial.println();
    Serial.println("entry is complete");
    floatTotal = atof(inputBuffer);  //convert buffer to a float
    return; //exit the function
  }
  if (key >= '0' && key <= '9' || key == '.') //only act on numeric or '.' keys
  {
    inputBuffer[inputIndex] = key;  //put the key value in the buffer
    if (inputIndex != MAX_CHARS - 1)
    {
      inputIndex++; //increment the array
    }
    inputBuffer[inputIndex] = '\0';  //terminate the string
    Serial.println(inputBuffer);
  }
}

Instead of accumulating the total as each digit is entered they (the digit characters) are stored in an array and when data entry is complete the array of chars is converted into a number. This will work for both floats and integers and the technique can be used in all of the examples in this thread, although note that the number entered will actually be converted into a float. If this is a problem it can be converted to an integer as in the example

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.