How to use decimal places with keypad entry

Hi everyone :smiley: ,

I have a code which I wrote with help from some nice people on the forum a few years ago. It has been working fine up until now, but I need some help with adding something to it.

The code is used to control up to 16 relays in a infinite sequence with a Mega 2560, where I can adjust the time between every relay, and how long the relay stays on for. The relays control blower motors.

I have it so that I can enter seconds for the delay between each relay, as well as seconds for each relay to be on.

What I need now is help with decimal points for the delay between each relay. How will I do this?
I've been looking around and read about floating numbers, and various other options, but I'm not sure which one will work for my application. I would like two decimal points and was wondering how can I get the display to show 000.00 and when I enter the number on the keypad it comes in from right to left and replaces the 0s.

The hardware that I have is a Mega, 3.2' screen, relays and a 4x4 membrane keypad.
The code that I'm working with at the moment includes the following for the keypad.

// Keypad
const byte ROWS = 4; // Four rows
const byte COLS = 4; // Four columns
char keys[ROWS][COLS] =
{
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS );
unsigned int InputNumber;
int pressedKeyCount = 0;
char key = keypad.getKey();
void IncreaseNewTime(char key)
{
  if (CurrentMode == SetOffTimeMode || CurrentMode == SetOnTimeMode)
  {
    InputNumber = InputNumber * 10 + key - '0';
    pressedKeyCount ++;
    myGLCD.setColor(VGA_RED);
    myGLCD.printNumI(InputNumber, CENTER, 84);
    if (pressedKeyCount > 5)
      InputNumber = 0,
      pressedKeyCount = 0,
      tooManyNumbers();
  }
}

void SaveNewTime()
{
  if (CurrentMode == SetOffTimeMode)
  {
    offTime = InputNumber;
    offTimeSaved();
  }
  if (CurrentMode == SetOnTimeMode)
  {
    onTime = InputNumber;
    onTimeSaved();
  }
}

void keypadEvent (KeypadEvent key)
{
  if (keypad.getState() == PRESSED)
  {
    switch (key)
    {
      case 'A':
        CurrentMode = TitleMode;
        title();
        InputNumber = 0;
        break;
      case 'B':
        CurrentMode = SetOffTimeMode;
        offTimeChange();
        InputNumber = 0;
        break;
      case 'C':
        CurrentMode = SetOnTimeMode;
        onTimeChange();
        InputNumber = 0;
        break;
      case 'D':
        examplesPage();
        break;
      case '0' ... '9':
        IncreaseNewTime(key);
        break;
      case '*':
        if (InputNumber > 0)
        {
          InputNumber = 0;
          reset();
        }
        break;
      case '#':
        if (InputNumber > 0)
        {
          SaveNewTime();
          InputNumber = 0;
        }
        break;
    }
  }
}

I hope someone can give me some advise.

Thanks a lot

You basically have to do the same trick after the decimal as before, that is the x10 thing before you add the next digit, to "bump" the previous digits to the left.

BUT you also have to keep track of how many decimals are entered, and then divide that accumulated decimal part by 10^howManyDecimals.

So if you want 123.456 say, the 123 part is what you already do. Then the * say means the decimal point. You start a new accumulator on the right, so you get 456 (four hundred and fifty six). You would have kept count that you have 3 decimal places, so you divide by 10^3, that is 1000, so your 456 becomes .456. Then you add the left and right parts, 123 + .456 = 123.456.

12Stepper:
You basically have to do the same trick after the decimal as before, that is the x10 thing before you add the next digit, to "bump" the previous digits to the left.

BUT you also have to keep track of how many decimals are entered, and then divide that accumulated decimal part by 10^howManyDecimals.

So if you want 123.456 say, the 123 part is what you already do. Then the * say means the decimal point. You start a new accumulator on the right, so you get 456 (four hundred and fifty six). You would have kept count that you have 3 decimal places, so you divide by 10^3, that is 1000, so your 456 becomes .456. Then you add the left and right parts, 123 + .456 = 123.456.

Thanks for the quick reply,
So I'll have to capture two entries from the keypad (123 and then .456), and then save them as one? Do you have a basic example of what you mean? Sorry, trying to make sense of how you see it.

LanceRoberts:
Thanks for the quick reply,
So I'll have to capture two entries from the keypad (123 and then .456), and then save them as one?

Effectively yes, but invisible to the user. When you hit * or whatever as the decimal you keep typing, but inside it's keeping the parts separate, then adds them when you hit say # to mean you're done.

You would type 123*456#

LanceRoberts:
Do you have a basic example of what you mean?

The sketch below uses a function "getKeypadFloat()" twice to give a float to each of two variables. There's a variable int debug = 1; which if left as 1 will serial print various debug stuff. Change that to 0 to turn it off.

Disclaimer: Not for a moment saying this is the best or only or even a good way to do this, but it works for me. YMMV.

// with help from https://stackoverflow.com/questions/39232634/how-to-input-a-multi-digit-integer-into-an-arduino-using-a-4x4-keypad

#include <Keypad.h>

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

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

void setup()
{
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  Serial.begin(9600);

  float myValueFromKeypad1 = getKeypadFloat();
  float myValueFromKeypad2 = getKeypadFloat();
  Serial.println(" ");
  Serial.print("I received ");
  Serial.print(myValueFromKeypad1, 2);
  Serial.print(", and then I received ");
  Serial.println(myValueFromKeypad2, 3);
}

void loop()
{
}//loop

float getKeypadFloat()
{
  int debug = 1;
  float beforeDecimal = 0;                                // the number accumulator
  float afterDecimal = 0;
  byte howManyDecimals = 0;
  float finalValue;
  int keyval;                                     // the key press
  int isNum;
  int isStar;
  int isHash;


  Serial.println("Enter digits, * as decimal point, then decimals, # to end");
  if (debug)Serial.print("So far: ");

  // accumulate the part before the decimal
  do
  {
    keyval = keypad.getKey();                          // input the key
    isNum = (keyval >= '0' && keyval <= '9');         // is it a digit?
    if (isNum)
    {
      if (debug)Serial.print(keyval - '0');
      beforeDecimal = beforeDecimal * 10 + keyval - '0';               // accumulate the input number
    }

    isStar = (keyval == '*');
    if (debug)if (isStar)  Serial.print(".");

    //display but ignore illegal presses
    if (debug)
    {
      if (keyval == 'A' || keyval == 'B' || keyval == 'C' || keyval == 'D' || keyval == '#')
        Serial.print("?");
    }

  } while (!isStar || !keyval);                          // until a * or while no key pressed

  // accumulate the part after the decimal

  do
  {
    keyval = keypad.getKey();                          // input the key
    isNum = (keyval >= '0' && keyval <= '9');         // is it a digit?
    if (isNum)
    {
      if (debug)Serial.print(keyval - '0');
      afterDecimal = afterDecimal * 10 + keyval - '0';                 // accumulate the input number
      howManyDecimals++; // increment for later use
    }

    isHash = (keyval == '#');

    //display but ignore illegal presses
    if (debug)
    {
      if (keyval == 'A' || keyval == 'B' || keyval == 'C' || keyval == 'D' || keyval == '*')
        Serial.print("?");
    }

  } while (!isHash || !keyval);                          // until # or while no key pressed

  finalValue = beforeDecimal + (afterDecimal / pow(10, howManyDecimals));

  if (debug) Serial.println(" ");
  if (debug)Serial.print("Returning ");
  if (debug)Serial.println(finalValue, howManyDecimals);

  return finalValue;

}//getKeypadFloat

12Stepper:
Effectively yes, but invisible to the user. When you hit * or whatever as the decimal you keep typing, but inside it's keeping the parts separate, then adds them when you hit say # to mean you're done.

You would type 123*456#

The sketch below uses a function "getKeypadFloat()" twice to give a float to each of two variables. There's a variable int debug = 1; which if left as 1 will serial print various debug stuff. Change that to 0 to turn it off.

Disclaimer: Not for a moment saying this is the best or only or even a good way to do this, but it works for me. YMMV.

Ok, I think I understand it now, but unfortunately I'm already using the "*" button as a Cancel button, and the "#" is my Save button. The "A", "B", "C" and "D" buttons are also being used for various menus. That's why I was hoping to have a floating number which the keypad merely changes the 0s, in 000.00, as you enter on the keypad, and allows you to save it afterwards, including the "." in the value.

I'll think of a way to use your suggestion, and maybe remove one of my menus, but I really need them all.

Thanks a lot!

Why not collect a 5 digit integer and divide it by 100 before using it ?

The screen output could show 123.45 during number entry even though the program is collecting 12345 in the first instance

Am I right that your pre-decimal-digits are of fixed length? I see a counter in there... Perhaps you could use that fact to kick it into the post-decimal-digits, but not actually press a key to mean the decimals, and end automatically after 2 decimals with a counter on the right hand side too?

OR, even easier, if you want 123.45, type in 12345 and divide by 100 if it's always the same number of decimals.

(Was typing that as UKHelibob made same suggestion)

UKHeliBob:
Why not collect a 5 digit integer and divide it by 100 before using it ?

The screen output could show 123.45 during number entry even though the program is collecting 12345 in the first instance

I wish I had more time to study the language so that it makes sense to me like it does for you guys. It sounds so easy when you say it.
At which point would I divide it by 100 so that it shows in real-time on the screen as I'm entering the number?

12Stepper:
Am I right that your pre-decimal-digits are of fixed length? I see a counter in there... Perhaps you could use that fact to kick it into the post-decimal-digits, but not actually press a key to mean the decimals, and end automatically after 2 decimals with a counter on the right hand side too?

OR, even easier, if you want 123.45, type in 12345 and divide by 100 if it's always the same number of decimals.

(Was typing that as UKHelibob made same suggestion)

Yes, it will be a fixed length, 123.45, only 5 key presses allowed.

Hang on a sec... are you actually saying that you want decimals of seconds for relay timings?

Assuming you do actually need that, why not just work in milliseconds?

In fact, I won't be surprised if the value you type in in seconds gets used as milliseconds anyway. You only posted a snippet, but I wonder if offTimeSaved() simply uses the whole seconds you typed in as InputNumber which then becomes offTime, and uses that as milliseconds?

if (CurrentMode == SetOffTimeMode)
  {
    offTime = InputNumber;
    offTimeSaved(); 
  }

I didn't see the function offTimeSaved() in the snippet. If it works internally in milliseconds, it would be possible to change things so you type in milliseconds in the first place, 5.5s would be typed as 5500ms.

12Stepper:
Hang on a sec... are you actually saying that you want decimals of seconds for relay timings?

Assuming you do actually need that, why not just work in milliseconds?

In fact, I won't be surprised if the value you type in in seconds gets used as milliseconds anyway. You only posted a snippet, but I wonder if offTimeSaved() simply uses the whole seconds you typed in as InputNumber which then becomes offTime, and uses that as milliseconds?

if (CurrentMode == SetOffTimeMode)

{
   offTime = InputNumber;
   offTimeSaved();
 }




I didn't see the function offTimeSaved() in the snippet. If it works internally in milliseconds, it would be possible to change things so you type in milliseconds in the first place, 5.5s would be typed as 5500ms.

Sorry, forgot to post that section of the code, I do in fact x 1000 in order to bring the entered number from seconds to milliseconds. I guess I could take that out and just work with milliseconds, never thought about it. I'll have to increase my key press counter, but it could work. A bit more training for the people that use it.

void ManageBlowers() {
if (millis() - BlowerStartTime > offTime * 1000UL)
    {
    StartBlower(CurrentBlower);
    CurrentBlower++;
    if (CurrentBlower >= BlowerCount)
      CurrentBlower=0;
    }
for (int i = 0; i < BlowerCount;i++)
  {
  if(millis()-Blowers[i].StartedTime > onTime * 1000UL)
    {
    StopBlower(i);
    }
  }
}

You COULD save yourself a lot of trouble and just enter values in tenths of a second. The only code change would be multiplying by 100UL to get milliseconds instead of multiplying by 1000UL to get milliseconds. Do you really need timing more precise than a tenth of a second?

How about looking at this the other way round.

What level of timing precision to you need ?

johnwasser:
You COULD save yourself a lot of trouble and just enter values in tenths of a second. The only code change would be multiplying by 100UL to get milliseconds instead of multiplying by 1000UL to get milliseconds. Do you really need timing more precise than a tenth of a second?

Thanks for the suggestion. We use the timer/relay system on industrial boilers, and it is based on the amount of fuel that is burnt. The smaller boilers I need a delay of say 30 to 60 minutes, and the very large boilers I need a delay of as low as 0.72 seconds between each relay. So there is a wide range, and I'll only really need the extreme accuracy when it's on the very big power station boilers where the decimals are very important.

Try this sketch (tested on UNO, I2CLCD, and 4x4 keypad).
1. 000.00 appears on bottomline of LCD.
2. Enter:1 2 3 4 5; check that digits shift to the left without disturbing the decimal point as as you enter digits from the Keypad.
3. At the end of 5-digit entry, the float value is obtained, stored in variable x, and shown at the RHS of bottomline of LCD.

#include<Keypad.h>
#include<LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

char myData[7] = {'0', '0', '0', '.', '0', '0', '\0'};
int arrayTracker = 5;
byte counter = 0;
const byte rows = 4;   //four rows
const byte cols = 4;  //four columns
char keys[rows][cols] = {   //2-dimensional array contains ASCII codesfor the labels of Keys
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[rows] = {9, 8, 7, 6}; //R1R2R3R4 connection Pin = 9876
byte colPins[cols] = {5, 4, 3, 2};  //C1C2C3C4 connection Pin = 5432
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols); //Library Function

void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);       //cursor position
  lcd.print("Enter Code:");
  lcd.setCursor(0, 1);
  lcd.print(myData);
}

void loop()
{
  char input = keypad.getKey();  //scan keypad
  if (input != 0x00)       //if scan code is non-zer0
  {
    myData[arrayTracker] = input;   //store the ASCII code of the lable of key in array
    showOnLCD();
    counter++;
    if (counter == 5)
    {
      float x = atof(myData);
      lcd.setCursor(8, 1);
      lcd.print(x, 2);
      while (1);
    }
  }
}

void showOnLCD()
{
  lcd.setCursor(0, 1);
  lcd.print(myData);
  if (counter != 4)
  {
    myData[0] = myData[1];
    myData[1] = myData[2];
    myData[2] = myData[4];
    myData[4] = myData[5];
  }
}

GolamMostafa:
Try this sketch (tested on UNO, I2CLCD, and 4x4 keypad).
1. 000.00 appears on bottomline of LCD.
2. Enter:1 2 3 4 5; check that digits shift to the left without disturbing the decimal point as as you enter digits from the Keypad.
3. At the end of 5-digit entry, the float value is obtained, stored in variable x, and shown at the RHS of bottomline of LCD.

Ok great, thanks a lot. I'll give it a go and let you know what happens.

GolamMostafa:
Try this sketch (tested on UNO, I2CLCD, and 4x4 keypad).
1. 000.00 appears on bottomline of LCD.
2. Enter:1 2 3 4 5; check that digits shift to the left without disturbing the decimal point as as you enter digits from the Keypad.
3. At the end of 5-digit entry, the float value is obtained, stored in variable x, and shown at the RHS of bottomline of LCD.

Still trying to figure out how I am going to integrate your code into mine, proving to be quite difficult because of the different names that you used compared to mine, and my minimal coding knowledge. I might have to go the millisecond route if I cant get this integration done.

LanceRoberts:
Still trying to figure out how I am going to integrate your [GolamMostafa's] code into mine

Code needs to be maintainable in the long run: you might not be the one doing the next upgrade, so my advice is to do it the simplest way that meets the requirements. That would be the ms route, and just make sure you document the code for others to maintain. May as you say need a bit of TLC with the users, but just make sure you document the usage too.

[side note]Must say though, as an engineer if 40 years' standing (graduated next week 1979, in fact) I'm pretty concerned about power station boiler control being coded by someone with self-confessed "minimal coding knowledge". That's just a weeeee bit scary to me.[/side note]

12Stepper:
Code needs to be maintainable in the long run: you might not be the one doing the next upgrade, so my advice is to do it the simplest way that meets the requirements. That would be the ms route, and just make sure you document the code for others to maintain. May as you say need a bit of TLC with the users, but just make sure you document the usage too.

[side note]Must say though, as an engineer if 40 years' standing (graduated next week 1979, in fact) I'm pretty concerned about power station boiler control being coded by someone with self-confessed "minimal coding knowledge". That's just a weeeee bit scary to me.[/side note]

Thanks for the advise.
Our system has nothing to do with the running of the boiler/power station, so whatever I decide with my timer will have no affect on it. Our machines are standalone, and is not connected to any of their control systems. All I need my system to do is switch the relays on and off in a timed sequence.

LanceRoberts:
Our machines are standalone, and is not connected to any of their control systems.

OK well that's good to know :wink:

I was going off this:

LanceRoberts:
it's on the very big power station boilers where the decimals are very important.

... where I took "on the" to mean your stuff was part of the control system.