Up counter using millis() and Arduino Uno

My setup consists of an Arduino Uno, 4x4 matrix keypad, 16x2 LCD display. I’m using the i2c communication protocol. To run the auto counter with 99,999 being the max count, first the count is set through a piece of code. Next the interval in milliseconds is set through another code and finally the counter is implemented through a third and a separate code. Following are the codes that I’m implementing and all three are functional alone. Please suggest if any of the codes need to be corrected or any other improvements needed.

  1. Code to set the 5 digit count:
#include <EEPROM.h>
#include <Wire.h>
#include <hd44780.h>                       // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header

hd44780_I2Cexp lcd; // declare lcd object: auto locate & config exapander chip

// LCD geometry
const byte LCD_COLS = 16;
const byte LCD_ROWS = 2;

#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] = {11, 10, 9, 8}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {7, 6, 5, 4}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
bool getNumber = true;
unsigned long count;

const byte entryMaxSize = 5;
static char digits[entryMaxSize + 1];
static byte index;

void setup()
{
  Serial.begin(115200);
  lcd.begin(LCD_COLS, LCD_ROWS);
  lcd.setCursor(3, 0);
  lcd.print("  PRIYA");
  lcd.setCursor(3, 1);
  lcd.print("ELECTRONICS");
  delay(100);
  for (int positionCounter = 0; positionCounter < 40; positionCounter++)
  {
    lcd.scrollDisplayLeft();
    delay(120);
  }
  delay(800);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set Count:");
  lcd.setCursor(11, 0);
  lcd.print(EEPROM.get(0, count)); // <<<<<<<<< read from EEPROM & print in it
}

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 'A': initializeCounter(); break;
      case 'D': setCount(); break;
      default: getCount(state, key);
    }
  }
}

void initializeCounter()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set Count:");
  index = 0; // reset the counter
  getNumber = true;
  lcd.setCursor(11, 0);
  lcd.print(EEPROM.get(0, count)); // <<<<<<<<< read from EEPROM here & print it
}

unsigned long tempcount;             // temporary count created by key entry
void getCount(const byte state, char key)
{
  if (state == PRESSED)
  {
    if (getNumber)
    {
      // if not 5 characters yet
      if (index < entryMaxSize)
      {
        // add key to userinput array and increment counter
        if ( key >= '0' && key <= '9' ) // key is of type char and has a value between 0 and 9 so do something with it.
        {
          digits[index++] = key;
          digits[index] = '\0';
          tempcount = atol(digits);
          char displayText[17] = "";
          snprintf(displayText, sizeof(displayText), "Set Count: %5lu", tempcount);
          lcd.setCursor(0, 0);
          lcd.print(displayText);
        }
      }
      else
      {
        Countwarning();
      }
    }
  }
}

void setCount()
{
  if (getNumber)
  {
    if (index == 0)
    {
      invalidCount();
    }
    else
    {
      lcd.clear();
      lcd.print("Count Set: ");
      for (byte i = 0; i < index; i++)
      {
        lcd.print(digits[i]);
        getNumber = false;
      }
      unsigned long count = atol(digits);
      Serial.print(count);
      EEPROM.put(0, count); // <<<<<<<<< save to EEPROM here
    }
  }
}

void invalidCount()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Invalid Count!!");
  lcd.setCursor(0, 1);
  lcd.print("Press A"); // suggesting the user to enter the count again
  getNumber = false;
}

void Countwarning()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("5 Digits Only!!"); // warning for the user if more than permitted digits are entered
  lcd.setCursor(0, 1);
  lcd.print("Press A"); // suggesting the user to enter the count again
  getNumber = false;
}
  1. Code to set the interval in milliseconds:
#include <EEPROM.h>
#include <Wire.h>
#include <hd44780.h>                       // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header

hd44780_I2Cexp lcd; // declare lcd object: auto locate & config exapander chip

// LCD geometry
const byte LCD_COLS = 16;
const byte LCD_ROWS = 2;

#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] = {11, 10, 9, 8}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {7, 6, 5, 4}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
bool acquireInterval = true;
unsigned long interval;

const byte entryMaxSize2 = 4;
static char digits2[entryMaxSize2 + 1];
static byte index2;

void setup()
{
  Serial.begin(115200);
  lcd.begin(LCD_COLS, LCD_ROWS);
  lcd.setCursor(0, 0);
  lcd.print("Set Intv:");
  lcd.setCursor(10, 0);
  lcd.print(EEPROM.get(100, interval)); // <<<<<<<<< read from EEPROM here & print it
  lcd.print("ms");
}

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 '*': initializeInterval(); break;
      case 'D': setInterval(); break;
      default: getInterval(state, key);
    }
  }
}

void initializeInterval()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set Intv:");
  index2 = 0; // reset the interval
  acquireInterval = true;
  lcd.setCursor(10, 0);
  lcd.print(EEPROM.get(100, interval)); // <<<<<<<<< read from EEPROM here & print it
  lcd.print("ms");
}

unsigned long tempinterval;             // temporary interval created by key entry
void getInterval(const byte state, char key)
{
  if (state == PRESSED)
  {
    if (acquireInterval)
    {
      // if not 4 characters yet
      if (index2 < entryMaxSize2)
      {
        //add key to userinput array and increment counter
        if ( key >= '0' && key <= '9') // key is of type char and has a value between 0 and 9 so do something with it.
        {
          digits2[index2++] = key;
          digits2[index2] = '\0';
          tempinterval = atol(digits2);
          if (tempinterval > 1000)
          {
            invalidInterval();
          }
          else
          {
            char displayText[18] = "";
            snprintf(displayText, sizeof(displayText), "Set Intv: %4lums", tempinterval);
            lcd.setCursor(0, 0);
            lcd.print(displayText);
          }
        }
      }
      else
      {
        Intervalwarning();
      }
    }
  }
}

void setInterval()
{
  if (acquireInterval)
  {
    if (index2 == 0)
    {
      invalidInterval();
    }
    else
    {
      lcd.clear();
      lcd.print("Intv Set: ");
      for (byte i2 = 0; i2 < index2; i2++)
      {
        lcd.print(digits2[i2]);
        acquireInterval = false;
      }
      unsigned long interval = atol(digits2);
      lcd.print("ms");
      Serial.print(interval);
      EEPROM.put(100, interval); // <<<<<<<<< save to EEPROM here
    }
  }
}

void invalidInterval()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Invalid Interval!!");
  lcd.setCursor(0, 1);
  lcd.print("Press *"); // suggesting the user to enter the interval again
  acquireInterval = false;
}

void Intervalwarning()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("4 Digits Only!!"); // warning for the user if more than permitted digits are entered
  lcd.setCursor(0, 1);
  lcd.print("Press *"); // suggesting the user to enter the interval again
  acquireInterval = false;
}

so just for me to understand, there's no problem right?

roberthbaratheon:
so just for me to understand, there's no problem right?

Hi Robert, the third piece of code works just fine. But I want to know whether I've used the variables 'm' and 'j' correctly. Also there is a slight improvement needed for the codes where i set the count and the interval before I implement the counter. feel free to use any of the codes you might want to implement for yourself.

Gautam

If you have any questions please do ask.

I want to know whether I've used the variables 'm' and 'j' correctly.

If you gave them meaningful names then it would be easier to follow your code

  while ((now - prevMillis) >= j)

If j is the period between increments then this test would be better as if rather than while, but you need to change the logic too in order to only increment the count at the end of the period.

UKHeliBob:
If you gave them meaningful names then it would be easier to follow your code

Extremely sorry for that, I forgot to change them.

UKHeliBob:

  while ((now - prevMillis) >= j)

If j is the period between increments then this test would be better as if rather than while

Yes I am using 'j' as an interval between successive counts. The above test with if gives a very large delay. For example when I set the count to 16,000 and read it from EEPROM as 'm' & set the interval as 1ms and read it as 'j', I expected that at the end of time check I'd get 16,000ms which is 16 seconds but what I got was way large like almost 50 seconds off the value I expected. Using while gives me only 2 to 3 ms off.

UKHeliBob:
but you need to change the logic too in order to only increment the count at the end of the period.

Would you please suggest something

Would you please suggest something

Forget about the reading and saving of the interval and count for now and go back to the simple BlinkWithoutDelay example. Change it to increment a variable at a fixed interval until the upper limit is reached. Note that the example uses an if to determine whether the period has ended, not a while

Once you have got that working add code to get the user input for the start value and test it thoroughly.

Once that works add the code to get the interval value from the user and test that.

Do not try to solve everything at once.

To my mind your method of getting numerical input from the user looks very odd. It would be more usual to accept input one character at a time and either add it to the target variable by converting it to a number and manipulating the target ( (target * 10) + new digit) or to put each character into an array of chars, zero terminate the array and convert it to an integer when entry is complete using the atoi() function.

I'll keep the Up counter code away for now. I need help with the first two codes. Setting the count and interval.

Gautam

See my previous post

Are you even going to listen what problem I'm having with the functionality of the code or are you going to say I said this and that?!

GautamD:
Are you even going to listen what problem I'm having with the functionality of the code or are you going to say I said this and that?!

From your original post

Please suggest if any of the codes need to be corrected or any other improvements needed.

From my post #6

To my mind your method of getting numerical input from the user looks very odd. It would be more usual to accept input one character at a time and either add it to the target variable by converting it to a number and manipulating the target ( (target * 10) + new digit) or to put each character into an array of chars, zero terminate the array and convert it to an integer when entry is complete using the atoi() function.

Have you tried either of the methods I suggested ?

GautamD:
Are you even going to listen what problem I'm having with the functionality of the code or are you going to say I said this and that?!

Sorry for being rude Bob. I think I've not clearly specified my requirements and posted all 3 codes just like that. I want to run an Up counter for which I'm taking the count through code. I've obtained the code through the methods you suggested i.e. using a c-string and atol and saved it to EEPROM. Next, I'm setting the interval using the code2, used a c-string and atol and saved it to EEPROM. The third code involves using the count from memory and interval and running the up counter. But what I want is to combine all 3 codes, so that I can use the Count and interval that I've set and stored. That's the reason I've used the variables 'm' & 'j'. Whenever I turn on the controller, the Up Counter should use the count & interval form memory and counting must take place.

That's the reason I've used the variables 'm' & 'j'. Whenever I turn on the controller, the Up Counter should use the count & interval form memory and counting must take place.

I really do not understand the relationship between count and interval, as store in EEPROM, and 'm' and 'j'. Perhaps you could provide a handy cheat sheet.

But what I want is to combine all 3 codes, so that I can use the Count and interval that I've set and stored.

You have one code that stores a count value in EEPROM. You have another code to store an interval value in EEPROM. You have a third code that deals with a count value and an interval value.

It should be trivial to combine the codes, and add the missing pieces to read the data from the EEPROM. What have you tried? Do you even understand what the three codes are doing?

PaulS:
What have you tried? Do you even understand what the three codes are doing?

I very well understand what those codes are doing. Just because two variables went unnamed and it caused confusion I'll change that.

  1. The count set in code1 is stored into EEPROM from location zero onwards.
  2. The interval set in code2 is stored into EEPROM from location hundred onwards.
  3. The code3 where the counter actually runs uses the variables 'm' & 'j'. The count from location zero is read into m and later m is used for comparison.
  4. The interval from location hundred is read into j and it is used as interval between successive counts.

I wanted to use m & j as program variables to store the EEPROM data temporarily, was it the correct way to do it?

Where are you stuck ? Do the individual programs work ?

If so, then combining them should not be difficult. The combined program will be doing one of 3 things at any one time so you need a method of switching between them, perhaps entry from the keyboard to change state and if/else or switch/case to control which block of code should be run at that time.

First of all Bob extremely sorry for being rude. You as well as PaulS have helped me alot in the past. So I'm open to every form of criticism. And that has significantly helped me to improve my codes.

The code3 that is where the counter runs is working just fine. I'm having a problem with the codes where I set the count and interval. The problem is fairly simple and I hope you guys will help me solve it in no time.

The following description of the problem is for both count & interval:

  1. when I power on the controller, as per the code in setup() the count/interval stored in EEPROM is displayed. If I wish to proceed with the count/ interval that is already set, then, I press the enter key. But what happens is that a message is displayed which says invalid count/interval.

  2. My code always asks for a keypad input for count/interval, only then it considers them valid and on pressing the enter key they are stored into the EEPROM.

  3. The same thing happens when I press the 'A' key which is used to set a count. On pressing 'A', the stored count from EEPROM is displayed, but on pressing enter if I wish to proceed with that count an 'invalid count' message is displayed. Keypad input is expected, always!

  4. The same stuff happens for setting the interval through the '*' key.

Thats where I'm stuck.

I very well understand what those codes are doing

So, you could put (some of) the code in a function called saveCount(). You could put (some of) the code in a function called saveInterval(). You could put (some of) the code in a function called readCount(). You could put (some of) the code in a function called readInterval().

Then, the only (moderately) challenging part is determining where to, in the final program, call readCount(), saveCount(), readInterval(), and saveInterval(). Reading the stored values should happen in setup(). Saving the values should happen only when they change. Using the saved values should be exactly like using the non-saved values.

Thats where I'm stuck.

Post (again, if necessary) the code that you are stuck with.

UKHeliBob:
If so, then combining them should not be difficult. The combined program will be doing one of 3 things at any one time so you need a method of switching between them, perhaps entry from the keyboard to change state and if/else or switch/case to control which block of code should be run at that time.

Thats exactly what I want to do after the problem is solved.

PaulS:
Post (again, if necessary) the code that you are stuck with.

PaulS you may refer the code1 or code2, both have a similar problem.