Up counter using millis() and Arduino Uno

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

I'll assume that you are referring to the code in the original post in this thread. I've completely lost track of what the problem is with that code.

In any case, I can't imagine loading one sketch to have the user enter a count, loading another sketch to have them enter an interval, and the loading a third sketch to use the data stored in EEPROM by the first two sketches.

What i believe is causing the error message to be displayed.

Following is w.r.t. code1 i.e. where count is set.

1.flag is initialized at the start.

bool getNumber = true;

2.In setup() the value from EEPROM read into count and displayed.

lcd.print(EEPROM.get(0, count)); // <<<<<<<<< read from EEPROM & print in it

If user wishes to proceed with this count he can press enter key. Currently error message is displayed.
Only after keypad input and enter key, count considered valid.

  1. If user presses A'' 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
}
  1. Code to set the count by pressing enter(user may proceed with previous count or set a new count):
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
    }
  }
}

PaulS:
In any case, I can't imagine loading one sketch to have the user enter a count, loading another sketch to have them enter an interval, and the loading a third sketch to use the data stored in EEPROM by the first two sketches.

Someone on the forum suggested to me that before combining codes, trying to get them running individually and error free. Only then proceed ahead to combine them.

GautamD:
What i believe is causing the error message to be displayed.

  1. Code to set the count by pressing enter(user may proceed with previous count or set a new count):
void setCount()

{
  if (getNumber)
  {
    if (index == 0)
    {
      invalidCount();
    }

then, I press the enter key.

I assume you mean 'D'

But what happens is that a message is displayed which says invalid count/interval

When you press 'D' the setCount() function is called. Almost the first thing it checks is whether index is zero. If it is you get the "Invalid" message. What should the value of index be when setCount() is called ?

I see that you have reached the same conclusion while I was typing this, so what is the answer ?

UKHeliBob:
I assume you mean 'D'

Yes

UKHeliBob:
When you press 'D' the setCount() function is called. Almost the first thing it checks is whether index is zero. If it is you get the "Invalid" message. What should the value of index be when setCount() is called ?

I see that you have reached the same conclusion while I was typing this, so what is the answer ?

greater than zero

So does this mean setCount() must only be called when index is greater than zero and anything between 1 & 5?

greater than zero

The only place where index is incremented from its initial value of zero is in the getCount() function. If that function has not been called before calling setCount() then you will inevitably get the "Invalid" message

So the default: getCount() must come before setCount() in void loop()?

When the 'D' key is pressed, and setCount() is called, when index is 0, this means that the value stored in count, as read from EEPROM, is the value to be used. So, setCount() shouldn't be presenting an error, nor should it save the value to EEPROM, since it is the value already there.

Okay, i get it. So if the index is zero when 'D' is pressed that means the count from memory is to be used. But if the index is found to be any number other than zero between 1 to 5, then the count entered via keypad must be stored and used later on.

How do I implement this, use a flag?

if (index == 0)
{
  //get a new value from the user
}
else
{
  //get the value from the EEPROM
}

UKHeliBob:

if (index == 0)

{
  //get a new value from the user
}
else
{
  //get the value from the EEPROM
}

This would be a part of getCount() ?

The setCount() would set the count directly from memory or if count is set via keypad, do atol(), then store it to the memory as well as set it?

This would be a part of getCount() ?

Wherever is appropriate for your program logic. You may need to restructure the program.

I have found your function names confusing

getCount() - get count from user or get the count from the EEPROM ?
setCount() - save the count to EEPROM or get it from the EEPROM and set the count variable to that value ?

It seems to me that you need to be able to do the following

  1. retrieve the previously stored number and display it
  2. get a new number from the user and save it
  3. prompt the user to use the current number whether just entered or retrieved from EEPROM

As per current code:

getCount(): Only taking input from user via keypresses

setCount(): Coverts the count received from user using atol(), stores it to EEPROM & sets it.

UKHeliBob:

  1. retrieve the previously stored number and display it

Achieved it through current code

UKHeliBob:
2. get a new number from the user and save it

Achieved it through current code.

UKHeliBob:
3. prompt the user to use the current number whether just entered

Done

Prompt the user to use the number retrieved from EEPROM : NOT YET ACHIEVED!!

I hope there's no confusion now.

I've tried the following now.

#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; //three 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;
bool countfromMem = 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);
  readCount();
  checkForCount();
}

void readCount()
{
  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 checkForCount()
{
  if (count > 0)
  {
    countfromMem = true;
    }
    else
    {
      countfromMem = false;
      index = 0; // reset counter
          getNumber = true;
      }
  }

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

void initializeCounter()
{
readCount();
checkForCount();
}

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 (countfromMem)
  {
    lcd.clear();
    lcd.print("Count Set: ");
    lcd.print(count);
    countfromMem = false;
    }
  else if (getNumber)
  {
    if (count == 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
  countfromMem = false;
  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
  countfromMem = false;
  getNumber = false;
}

Have a play with this

#include <EEPROM.h>

#include <Keypad.h>

const byte ROWS = 4; //4 rows
const byte COLS = 4; //4 columns
char keys[ROWS][COLS] =
{
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {2, 3, 4, 5}; //L1, L2, L3, L4
byte colPins[COLS] = {6, 7, 8, 9}; //R1, R2, R3, R4

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

enum states
{
  GET_SAVED_COUNT,
  GET_NEW_COUNT,
  SAVE_NEW_COUNT,
  GET_INTERVAL,
  CONFIRM_RUN,
  RUN
};

byte currentState = GET_SAVED_COUNT;
unsigned long targetCount;
unsigned long startTime;
unsigned long currentTime;
unsigned long interval;
unsigned long count;
char key;
unsigned long numberFromKeypad;

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

void loop()
{
  switch (currentState)
  {
    case GET_SAVED_COUNT:
      EEPROM.get(0, targetCount);
      Serial.print(F("\nSaved target count = "));
      Serial.println(targetCount);
      Serial.println(F("Key A to accept count"));
      Serial.println(F("Key B to input new target count"));
      waitKeyRelease();
      while (key != 'A' && key != 'B')
      {
        key = keypad.getKey();
        if (key == 'A')
        {
          currentState = GET_INTERVAL;
        }
        else
        {
          currentState = GET_NEW_COUNT;
        }
      }
      break;
    //
    case GET_INTERVAL:
      Serial.println(F("\nEnter interval"));
      Serial.println(F("Key A to accept"));
      interval = getNumber();
      currentState = CONFIRM_RUN;
      break;
    //
    case GET_NEW_COUNT:
      Serial.println(F("\nEnter new target count"));
      Serial.println(F("Key A to accept"));
      targetCount = getNumber();
      EEPROM.put(0, targetCount);
      currentState = GET_INTERVAL;
      break;
    case CONFIRM_RUN:
      Serial.println(F("\nReady to run"));
      Serial.print(F("Target count = "));
      Serial.println(targetCount);
      Serial.print(F("Interval = "));
      Serial.println(interval);
      Serial.println(F("A to accept and run"));
      Serial.println(F("B to renter values"));
      waitKeyRelease();
      while (key != 'A' && key != 'B')
      {
        key = keypad.getKey();
        if (key == 'A')
        {
          count = 0;
          startTime = millis();
          currentState = RUN;
          Serial.println(F("\nRunning"));
        }
        else
        {
          currentState = GET_SAVED_COUNT;
        }
      }
      break;
    //
    case RUN:
      currentTime = millis();
      if (currentTime - startTime >= interval)
      {
        Serial.println(count);
        startTime = currentTime;
        count++;
        if (count >= targetCount)
        {
          currentState = GET_SAVED_COUNT;
        }
      }
      break;
  }
}

unsigned long getNumber()
{
  waitKeyRelease();
  numberFromKeypad = 0;
  while (key != 'A')
  {
    key = keypad.getKey();
    if (key >= '0' && key <= '9')
    {
      numberFromKeypad = (numberFromKeypad * 10) + key - '0';
      Serial.print(F("Number so far = "));
      Serial.println(numberFromKeypad);
    }
  }
  return numberFromKeypad;
}
void waitKeyRelease()
{
  while (key != NO_KEY)
  {
    key = keypad.getKey();
  }
}

It illustrates one approach to the project but there is still work for you to do on it.

Output is to the Serial monitor, not an LCD
The keypad definition and pin numbers need to be changed to suit your setup
There is no bounds checking on input so you can input ridiculous values
There are several instances of blocking code in the program. They could be eliminated but they may not matter in your application
You cannot interrupt the count once started but because it uses millis() for timing it would be easy to add
Note that it has not been exhaustively tested

Thanks for the code Bob, it works. Though at some places there were instances of a blocking code as you mentioned earlier. Code can still be modified as per my requirements. Otherwise it works the way I wanted. Could some part from this code be used in the code I've written?

UKHeliBob:
You cannot interrupt the count once started but because it uses millis() for timing it would be easy to add

Are you saying that I might be able to increment / decrement the count by a small value while it is running, may be by using push buttons?