4x3 Keypad Input

So my project I want to make is a keypad with a little OLED on it that I can enter the date I bought my fuel, how much the fuel was and then later on in the project add the function to be able to go back and add the mileage to the fuel when I go to fill up again so I can calculate my MPG per tank - and then plot it on a graph via the storage through SD media. But thats looking too far ahead right now, this is going to be a learning experience for me as well as hopefully a cool gadget I can keep in my car instead of a piece of paper and a pen.

Right now I have made two sketches: a simple keypad sketch that serial prints the char inputs from the keypad line by line when they are pressed and a combined sketch where the OLED waits for me to enter my number and then press # to display the converted character to number output (for example 1 and 2 entered on the keypad would, through the conversion of char to int give the 12 I wanted).

The problem with the second sketch is that there is no room there for the decimal place which I need for the price and accurate liter records. So I have gone back to the simple serial print and I am trying to create a way to enter the digits on the keypad, have them display on the serial monitor when entered, be able to use the decimal place as you would expect and then use the # as a null to store the entered number.

I've read about using atof() function to convert a character array but then I would'nt be able to display the number and decimal when entered right?

Thanks for any suggestions.

Have a great day.

Use CTRL T to format your code.
Attach your ‘complete’ sketch between code tags, use the </> icon in the posting menu.
[code]Paste your sketch here[/code]

#include <Keypad.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display


Adafruit_SSD1306 display(128, 32);  // Create display


#include <Fonts/FreeMonoBold12pt7b.h>  // Add a custom font
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font

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

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

int FirstDigit;

void setup()  // Start of setup
{

  delay(100);  // This delay is needed to let the display to initialize

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C

  display.clearDisplay();  // Clear the buffer

  display.setTextColor(WHITE);  // Set color of the text

  display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3

  display.setTextWrap(false);  // By default, long lines of text are set to automatically “wrap” back to the leftmost column.
  // To override this behavior (so text will run off the right side of the display - useful for
  // scrolling marquee effects), use setTextWrap(false). The normal wrapping behavior is restored
  // with setTextWrap(true).

  display.dim(0);  //Set brightness (0 is maximun and 1 is a little dim)

}  // End of setup






void loop()  // Start of loop
{
  char key = keypad.getKey();
  display.clearDisplay();  // Clear the display so we can refresh

  display.drawRect(0, 0, 128, 32, WHITE);  // Draw rectangle (x,y,width,height,color)
  // It draws from the location to down-right

  display.setFont(&FreeMono9pt7b);  // Set a custom font
  display.setTextSize(0);  // Set text size. We are using a custom font so you should always use the text size of 0

  // Print text:
  display.setCursor(8, 15); // (x,y)
  display.println("Litres:");  // Text or value to print

  display.setCursor (90, 15);
  FirstDigit = GetNumber();
  display.println(FirstDigit);

  display.display();  // Print everything we set previously





}  // End of loop

int GetNumber()
{
  int num = 0;
  char key = keypad.getKey();
  while (key != '#')
  {
    switch (key)
    {
      //case NO_KEY:
      //break;

      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
        num = num * 10 + (key - '0');
        break;

      case '*':
        num = '.';
        break;
    }

    key = keypad.getKey();
  }

  return num;
}

You do realize what happens to 'num' when you press the *

case '*':
num = '.';
break;

This is how you usually get a key press:

if (key != NO_KEY)
{
// do your stuff
}

I thought it just wouldnt work. This code is not a final product. Seeing as num is an int I thought it just wouldnt work and I would have to learn about character arrays which I dont understand. I thought maybe some1 could guide me.

Here is a starting point:

//3x4keypad.ino

//Version 2
//https://forum.arduino.cc/index.php?topic=631783.0

#include <Keypad.h>

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


//                    R  R  R  R
//                    1  2  3  4
byte rowPins[ROWS] = {5, 4, 3, 2}; 

//                    C  C  C
//                    1  2  3
byte colPins[COLS] = {8, 7, 6}; 

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

//a character array to store our number
char num[20];

//**********************************************************************************
void setup()  // Start of setup
{
  Serial.begin(9600);
}  // End of setup


//**********************************************************************************
void loop()  // Start of loop
{
  GetNumber();

}  // End of loop


//**********************************************************************************
void GetNumber()
{
  static byte offset = 0;

  char key = keypad.getKey();
  if (key != NO_KEY)
  {
    //echo key pressed to serial monitor
    Serial.print("Key pressed was = ");
    Serial.println(key);

    switch (key)
    {
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':

        num[offset] = key;
        offset++;
        //add a null terminator for the currently entered ASCII number
        num[offset] = 0;

        Serial.println("");
        Serial.print("ASCII number so far = ");
        Serial.println(num);

        //convert the ASCII number to an actual floating point number
        Serial.print("Floating point number so far = ");
        float temp;
        temp = atof(num);
        Serial.println(temp, 4); //to 4 decimal places
        //do some math to prove we can use the floating point number
        Serial.print("Floating point number X 2 = ");
        Serial.println(temp * 2, 4);
        Serial.println("");

        break;

      case '*':
        num[offset] = '.';
        offset++;

        break;

      case '#':
        offset = 0;
        Serial.println("");

        break;

    } //END of switch/case

  } //END of   if (key != NO_KEY)

} //END of    GetNumber()

//**********************************************************************************

Thank you so much larryd!

With a slight tweak to the code and the serial to OLED changes I have it working in terms of displaying the input and then it wiping on the # input.

#include <Keypad.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display


Adafruit_SSD1306 display(128, 32);  // Create display


#include <Fonts/FreeMonoBold12pt7b.h>  // Add a custom font
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font

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


//                    R  R  R  R
//                    1  2  3  4
byte rowPins[ROWS] = {5, 4, 3, 2}; 

//                    C  C  C
//                    1  2  3
byte colPins[COLS] = {8, 7, 6}; 

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

//a character array to store our number
char num[20];

void setup()  // Start of setup
{

  delay(100);  // This delay is needed to let the display to initialize

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C

  display.clearDisplay();  // Clear the buffer

  display.setTextColor(WHITE);  // Set color of the text

  display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3

  display.setTextWrap(false);  // By default, long lines of text are set to automatically “wrap” back to the leftmost column.
  // To override this behavior (so text will run off the right side of the display - useful for
  // scrolling marquee effects), use setTextWrap(false). The normal wrapping behavior is restored
  // with setTextWrap(true).

  display.dim(0);  //Set brightness (0 is maximun and 1 is a little dim)

  Serial.begin(9600);

}  // End of setup






void loop()  // Start of loop
{
  display.clearDisplay();  // Clear the display so we can refresh
  
  display.drawRect(0, 0, 128, 32, WHITE);  // Draw rectangle (x,y,width,height,color
  
  display.setFont(&FreeMono9pt7b);  // Set a custom font
  
  display.setTextSize(0);  // Set text size. We are using a custom font so you should always use the text size of 0
  
  display.setCursor(8, 15); // (x,y)
  
  display.println("Litres:");  // Text or value to print
  
  display.setCursor (90, 15);
  
  GetNumber();
  
  display.println(num);
  
  Serial.println(num);

  display.display();  // Print everything we set previously





}  // End of loop

void GetNumber()
{
  static byte offset = 0;
  display.setCursor (90, 15);
  char key = keypad.getKey();
  if (key != NO_KEY)
  {
    switch (key)
    {
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':

        num[offset] = key;
        offset++;
        //add a null terminator for the currently entered ASCII number
        num[offset] = 0;
        float temp;
        temp = atof(num);
        display.display();
        break;

      case '*':
        num[offset] = '.';
        offset++;
        display.display();
        break;

      case '#':
        offset = 0;
        memset(num, 0, sizeof num);
        break;

    } //END of switch/case

  } //END of   if (key != NO_KEY)

} //END of    GetNumber()

Would you be able to point me in the right direction as to how to create 3 “headings” in which I could store the data entered with the # key?

I was thinking maybe counting how many times the # was pressed and using that to look for the heading name. For example, if the # is not pressed you are on the date input. if the # has been pressed once you are on the liters input, if # has been pressed twice you are on the price paid input. if three times then it saves the three values, or closes the menu to start with… baby steps.

I do not have a OLED to play with so you’ll have to do the heavy lifting in that area.

Do you have 3 free I/O pins available?

If so, these could connect to LEDs, would indicate 1st, 2nd, or 3rd then back to 1st.

The LEDs advance each time you press #.

If no free I/O, you can make an OLED input menu that goes from 1, 2, 3 then back to 1.

The data input menu advances each time the # is pressed.

When you press the #, the floating point conversion, of the number in the array, would be saved in a float variable.

So I went looking through my old class code for an assignment where we had to make a menu and I have tried to adapt it to fit my new requirements. However, I don’t think the typedef void (* GenericFP)() operates like it used to and how I understand it. Back when I used it before I used it to point to a function in an array to run a set of code in a menu; so in position 0 it would be recalling light level and printing it to an LCD, position 2 the humidity etc. Now I just get this error:

fuel_menu:64:3: error: ‘Date’ was not declared in this scope

{&Date, &Price, &Litres, &Miles};

^

fuel_menu:64:10: error: ‘Price’ was not declared in this scope

{&Date, &Price, &Litres, &Miles};

^

fuel_menu:64:18: error: ‘Litres’ was not declared in this scope

{&Date, &Price, &Litres, &Miles};

^

fuel_menu:64:27: error: ‘Miles’ was not declared in this scope

{&Date, &Price, &Litres, &Miles};

^

exit status 1
‘Date’ was not declared in this scope

Is there a new way to think about the function pointers for arduino or will I have to back date my IDE to use this. Please help.

///////////////////////////////////////////////////////////////
////////////////////////Included Libraries/////////////////////
///////////////////////////////////////////////////////////////

#include <Wire.h>
#include <Keypad.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display

///////////////////////////////////////////////////////////////
//////////////////////////LCD Setup////////////////////////////
///////////////////////////////////////////////////////////////

Adafruit_SSD1306 display(128, 32);  // Create display
#include <Fonts/FreeMonoBold12pt7b.h>  // Add a custom font
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font

///////////////////////////////////////////////////////////////
////////////////////////Keypad Setup///////////////////////////
///////////////////////////////////////////////////////////////


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


//                    R  R  R  R
//                    1  2  3  4
byte rowPins[ROWS] = {5, 4, 3, 2}; 

//                    C  C  C
//                    1  2  3
byte colPins[COLS] = {8, 7, 6}; 

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

char num[20];

///////////////////////////////////////////////////////////////
////////////////////////Global Variables///////////////////////
///////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////
////////////////////////Button Variables///////////////////////
///////////////////////////////////////////////////////////////

bool ButtonFlag = LOW;                                        
                                                                
///////////////////////////////////////////////////////////////
/////////////////////////Menu Variables////////////////////////
///////////////////////////////////////////////////////////////

const int NoofMenus = 4;                                      //Instantiate NoOfMenus to be a constant integer of 4. This is how many menus I will have in have in my code. I have used constant int here for the same reason as before.

typedef void (* GenericFP)();                                 //Here I am creating an alias for void. I am saying that I want GenericFP to work as a void when I call 'GenericFP'. This enables me to use pointers in arrays to call functions, as shown below:
GenericFP ControlSystemFunctions[NoofMenus] = 
{&Date, &Price, &Litres, &Miles};
                                                              //Using GenericFP as described above in conjunction with this array, I can point to a location in my array and call a function as if I were typing 'void function();'.
                                                              //I can call ControlSystemFunctions[number between 0-3] in my code and it will perform the function assosiated with that memory location in the array.
/////////Menu Variables////////
int FunctionPointer = 0;                                      //This determines the Control System Function which is selected out of the GenericFP array. Initially this is set as 0.

///////////////////////////////////////////////////////////////
//////////////////////////////Setep////////////////////////////
///////////////////////////////////////////////////////////////

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

///////////////////////////////////////////////////////////////
////////////////////////////Main Loop//////////////////////////
///////////////////////////////////////////////////////////////

void loop()                                                   
{
  ControlSystemFunctions[FunctionPointer]();                  //Run the code determined by the function pointer.
  ButtonCheck();                                              
  ReadFlag();                                                 
  ResetFlag();                                                //After every loop I want to ensure the flag is reset to ensure that on the next loop any change in state of the flags can be seen again.
}

///////////////////////////////////////////////////////////////
///////////////////////////Menu System/////////////////////////
///////////////////////////////////////////////////////////////

// Button Checks
void ButtonCheck()
{
  static byte offset = 0;
  char key = keypad.getKey();
  if (key = '#')
  {
    ButtonFlag = HIGH;
  }
}

void ReadFlag()                                               //This function essentially tells my control system what to do when a button is pressed.
{
  if (ButtonFlag == HIGH)                                     //This 'if' structure acts as the functionality behind my scroll button.                                 
  {
      FunctionPointer = (FunctionPointer + 1) % NoofMenus;    //Assign FunctionPointer a new, incrimented value so long as it is not bigger than the number of menus. 
                                                                //'% NoofMenus' ensures that when the FunctionPointer reaches 8, the next scroll puts the counter back to 0. This is due to the remainder of 8 % 8 being 0.
  }
}

void ResetFlag()                                             //This resets the flags on both programmable buttons in both system states, normal and alternate (or extra).
{
  ButtonFlag = LOW;
}

///////////////////////////////////////////////////////////////
///////////////////////////Functions///////////////////////////
///////////////////////////////////////////////////////////////

void Date()
{
  Serial.println("Date");
}

void Price()
{
  Serial.println("Price");
}
void Litres()
{
  Serial.println("Litres");
}
void Miles()
{
  Serial.println("Miles");
}

Try:
typedef void (* GenericFP)(int);
GenericFP ControlSystemFunctions[NoofMenus] = {&Date, &Price, &Litres, &Miles};

Leaves me with just this error now:

In function 'void loop()':

fuel_menu:83:43: error: too few arguments to function

ControlSystemFunctionsFunctionPointer; //Run the code determined by the function pointer.

^

exit status 1
too few arguments to function

Ohhh I fixed it. removed the curley brackets after FunctionPointer sqauer brackets. Silly man me!

Edit 2. It doesnt fix it. It makes the function pointer work in terms of I can see it change from 0 through 3, but the functions dont actually get called without the curlies :confused:

You a legend! :smiley: I have managed to get my project up to the last bit now... saving to the SD card :open_mouth: The one thing I haven't actually done before!!! :open_mouth:

Ive learnt a lot up to this point already so thanks :smiley:

An expert is born! :slight_smile: