Go Down

Topic: Up counter using millis() and Arduino Uno (Read 1 time) previous topic - next topic

GautamD

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.

GautamD


UKHeliBob

Code: [Select]

if (index == 0)
{
  //get a new value from the user
}
else
{
  //get the value from the EEPROM
}
Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

GautamD

Code: [Select]

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?

UKHeliBob

Quote
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
Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

GautamD


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.


1.  retrieve the previously stored number and display it
Achieved it through current code

2.  get a new number from the user and save it
Achieved it through current code.

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!!

GautamD


GautamD

I've tried the following now.

Code: [Select]

#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;
}

UKHeliBob

Have a play with this
Code: [Select]

#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
Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

GautamD

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?

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?

UKHeliBob

Quote
Could some part from this code be used in the code I've written?
I would suggest that you do not try to use parts of my code in yours.  Instead, why not change my code to do what you want ?  What do you want it to do that it doesn't do now ?

Quote
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?
You could certainly do that.
Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

GautamD

Bob I've understood how your code works and I guess its a state machine. But the problem is I've never tried a simplest of codes that could have used a state machine, for example blinking an led. The thing is its not just the 3 codes that I posted earlier I want to combine, but there are 5 more pieces of code that I plan to combine along with those 3. My project is due soon, so using the state machine and rewriting the codes could be quite tedious for me. The only obstacle I'm facing is getting the EEPROM value as valid when enter is pressed. Everything else works fine. If you'd help me with this problem I'm facing, I might proceed to combining codes faster. I shall try to learn how to implement a state machine from your code later.

Regards, Gautam

GautamD

but there are 5 more pieces of code that I plan to combine along with those 3.
Those 5 codes are already written and working.

GautamD

I actually did try your code. Made some minor changes too.

Code: [Select]

#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}; //L1, L2, L3, L4
byte colPins[COLS] = {7, 6, 5, 4}; //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_SAVED_INTERVAL,
  GET_NEW_INTERVAL,
  SAVE_NEW_INTERVAL,
  CONFIRM_RUN,
  RUN
};

byte currentState = GET_SAVED_COUNT;
unsigned long targetCount;
unsigned long targetInterval;
unsigned long startTime;
unsigned long currentTime;
unsigned long interval;
unsigned long count;
char key;
const byte entryMaxSize = 5;
static char digits[entryMaxSize + 1];
static byte index;
unsigned long numberFromKeypad;

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);
}

void loop()
{
  switch (currentState)
  {
    case GET_SAVED_COUNT:
      EEPROM.get(0, targetCount);
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Set Count:");
      lcd.setCursor(11, 0);
      lcd.print(targetCount);
      waitKeyRelease();
      while (key != 'D' && key != 'A') //'D' to set count ,'A' for new count
      {
        key = keypad.getKey();
        if (key == 'D')
        {
          currentState = GET_SAVED_INTERVAL;
        }
        else
        {
          currentState = GET_NEW_COUNT;
        }
      }
      break;
    //
    case GET_SAVED_INTERVAL:
      EEPROM.get(100, targetInterval);
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Set Intv:");
      lcd.setCursor(10, 0);
      lcd.print(targetInterval);
      waitKeyRelease();
      while (key != 'D' && key != '*') //'D' to set interval ,'*' for new count
      {
        key = keypad.getKey();
        if (key == 'D')
        {
          currentState = CONFIRM_RUN;
        }
        else
        {
          currentState = GET_NEW_INTERVAL;
        }
      }
      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_SAVED_INTERVAL;
      break;
    //
    case CONFIRM_RUN:
      lcd.clear();
      lcd.print("Ready to Count");
      delay(300);
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Count:");
      lcd.setCursor(7, 0);
      lcd.print(targetCount);
      lcd.setCursor(0, 1);
      lcd.print("Interval = ");
      lcd.setCursor(11, 1);
      lcd.print(targetInterval);
      Serial.println(F("A to accept and run"));
      Serial.println(F("B to renter values"));
      waitKeyRelease();
      //'D' to start counting, 'A' to set new count, '*' to set new interval
      while (key != 'D' && key != 'A' && key != '*')
      {
        key = keypad.getKey();
        if (key == 'D')
        {
          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 tempVal;
unsigned long getNumber()
{
  waitKeyRelease();
  numberFromKeypad = 0;
  while (key != 'D')
  {
    key = keypad.getKey();
    // 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';
        tempVal = atol(digits);
        numberFromKeypad = atol(digits);
        char displayText[17] = "";
        snprintf(displayText, sizeof(displayText), "Set Count: %5lu", tempVal);
        lcd.setCursor(0, 0);
        lcd.print(displayText);
      }
    }
    else
    {
      Countwarning();
    }
  }
  return numberFromKeypad;
}

void waitKeyRelease()
{
  while (key != NO_KEY)
  {
    key = keypad.getKey();
  }
}

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
}


UKHeliBob

Quote
there are 5 more pieces of code that I plan to combine along with those 3
Do these other pieces of code need to be running all the time or is it OK for the code written so far to to block execution ?

The most positive change that you could make would be to read the value of key each time through loop() and to replace the blocking while loops with tests of the value of key using ifs.

Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

Go Up