Using While Loop To Give User 10 Seconds To Input Values

Hi everybody, I have a timer that controls lights, there is an on time and an off time, currently this is hard coded as the on/off times have never needed to be changed, now I would like to be able to change them.
I am using the AdaFruit RGB LCD Shield, there are two pages on the LCD one is the normal screen that show the current time, the second shows when the user pushes the SELECT button, the screen changes to display the on and off times, at the same time the cursor is set to flash at the 'on' time hour allowing the user to use the up or down buttons to change the on hour. The user has ten seconds to make changes before the screen goes back to the first screen, or ten more seconds whenever a button is pushed (UP, DOWN). This is where I am having trouble I can't wrap my brain around how to delay the code so that it waits for user input for ten seconds, I'm trying to use the while loop but it isn't working, any ideas, advice???

Thanks in advance!!
Below is the code so far:

/*********************
Draft code for timer interface via buttons and LCD display using a DS3231 RTC for
time and UNO as MCU
The user pushes the SELECT button to see on and off times, these times are
adjustable with the UP, DOWN, LEFT, and RIGHT buttons.
**********************/

// Include the following libraries:
#include <RTClib.h>
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>


Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

RTC_DS3231 rtc;              // Create structure for DS3231 RTC.

int timeOnHour = 5;          // Turn on light at hour
int timeOnMinute = 0;        // Turn on light at minute
int timeOffHour = 21;        // Turn off light at hour
int timeOffMinute = 0;       // Turn off light at minute

int timer = millis();        // Timer for while loops, to be set and reset to 10 seconds at each button push.  

// These #defines make it easy to set the backlight color, the LCD is monochrome white.
#define TEAL 0x6  // Display backlight off
#define WHITE 0x7 // Display backlight on


/////////////////////////////////////////SETUP/////////////////////////////////////////

void setup() {

  Serial.begin(9600);
  lcd.begin(16, 2);

  if (! rtc.begin()) {          // Confirms that the RTC is connected and working.    
  lcd.print("RTC not responding");  
  }
  lcd.setBacklight(WHITE);
}

/////////////////////////////////////////LOOP/////////////////////////////////////////

void loop() {

  LCD_Print_Time();
  
  uint8_t buttons = lcd.readButtons();

  if (buttons) {
    timer == 10000;
    while (timer > 0) {
      timer =- 1;
      lcd.clear();
      LCD_Print_Time();
      lcd.setCursor(0,1);
      if (buttons & BUTTON_UP) {
        lcd.print("UP ");
        lcd.setBacklight(WHITE);
      }
      if (buttons & BUTTON_SELECT) {
        lcd.clear();
        display_On_Off_Times();
        lcd.setCursor(8, 0);   // Move cursor to timeOnHour.
        lcd.blink();           // Set cursor to blink over timeOnHour.
        if (buttons & BUTTON_DOWN){
         timeOnHour = timeOnHour - 1;
         display_On_Off_Times();
         }      
        //delay(10000);
        lcd.noBlink();
        lcd.clear();
      }
    }
  }
//lcd.clear();  
  delay(1000);
}

/////////////////////////////////////////FUNCTIONS/////////////////////////////////////////

  void LCD_Print_Time()      // Prints the current time on the LCD screen
{
  DateTime now = rtc.now();
  lcd.setCursor(0, 0);
  lcd.print(now.year(),DEC);
  lcd.print("/");
  lcd.print(now.month(),DEC);
  lcd.print("/");
  lcd.print(now.day(),DEC);
  lcd.print(" ");
  lcd.print(now.hour(),DEC);
  lcd.print(":");
  lcd.print(now.minute(),DEC);
  }

  void display_On_Off_Times() // Displays the current on and off times on the timer
  {
  lcd.setCursor(0, 0);
  lcd.print("Time On:");
  lcd.print(timeOnHour);
  lcd.print(":");
  lcd.print(timeOnMinute);
  lcd.setCursor(0, 1);
  lcd.print("Time Off:");
  lcd.print(timeOffHour);
  lcd.print(":");
  lcd.print(timeOffMinute);  
    
    }

Maybe using a state machine would make things easier?

Suggest you never ever use while( ).

Well you can if you realize why you shouldn’t.

Study this:

Yes, learn to use one Google . . .

a more conventional way would be to have a mode (state) allowing input and a separate timer which would exit that mode and possibly report a message (e.g. "too long") if the time expires

that's inappropriate !

1 Like

Read the Qualification ! :sunglasses: .

1 Like

suggest you never provide a qualification

Whatever you say sir !

Maybe this helps. I could not try it, because i do not own the lcd and also i didn't want to fiddle around getting the libraries to work.
The loop Funktion will now run as fast as possible and the millis() function is used to keep track of the elapsed time.

Please note, that i only copied the relevant parts (setup and loop function), so you need to add your other functions as well.

enum APP_STATE {
	STATE_NORMAL_SCREEN,
	STATE_SELECT_SCREEN,
} state;
unsigned long lastmillis = 0;

void setup() {

  Serial.begin(9600);
  lcd.begin(16, 2);

  if (! rtc.begin()) {          // Confirms that the RTC is connected and working.    
    lcd.print("RTC not responding");  
  }
  lcd.setBacklight(WHITE);
  
  state = STATE_NORMAL_SCREEN; // start with the normal screen
}

/////////////////////////////////////////LOOP/////////////////////////////////////////



void loop() {
	// read the buttons
	uint8_t buttons = lcd.readButtons();
	// process the input based on the current state
	switch(state) {
		case STATE_NORMAL_SCREEN:
			// check if a second has passed and we need to update the lcd screen
			// only update the screen once a second, because the rtc time does not change
			// that fast (maybe it does? if so, remove this if condition)
			if(millis() - lastmillis > 1000) {
				// update to current time, so that the next display happens in one second
				lastmillis = millis(); 
				// the normal screen is displayed, show the current time
				lcd.clear();
				LCD_Print_Time();
			}
			
			// always check if select button is pressed, even if less than a second has passed
			if(buttons & BUTTON_SELECT) {
				// go to the select page next time the loop-function runs
				state = STATE_SELECT_SCREEN;
				// take note of the time when we switch to the select screen
				lastmillis = millis();
			}
			break;
		case STATE_SELECT_SCREEN:
			// first of all check for timeout
			if(millis() - lastmillis > 10000) {
				// more than 10 seconds have elapsed since we switched to the select screen
				// return to the normal screen
				state = STATE_NORMAL_SCREEN;
				// stop blinking
				lcd.noBlink();
				lcd.clear();
				// note: do not update lastmillis here because then the rtc time will be immediately visible
				break; // exit the switch statement
			}
			
			// the select screen is displayed
			// display on and off times and blink the cursor
			lcd.clear();
			display_On_Off_Times();
			lcd.setCursor(8, 0);   // Move cursor to timeOnHour.
			lcd.blink();           // Set cursor to blink over timeOnHour.
			// check if up button is pressed
			if(buttons & BUTTON_UP) {
				// a button was pressed, so remember the time when the button was pressed
				// this will give the user another 10 seconds before switching back to the
				// normal screen
				lastmillis = millis();
				timeOnHour++; // increase the on time
				// update the display
				lcd.clear();
				display_On_Off_Times();
			} else if(buttons & BUTTON_DOWN) {
				// a button was pressed, so remember the time when the button was pressed
				// this will give the user another 10 seconds before switching back to the
				// normal screen
				lastmillis = millis();
				timeOnHour--; // decrease the on time
				// update the display
				lcd.clear();
				display_On_Off_Times();
			}
			break;
	}
}

2 Likes

Good lord 8 responses in less than half an hour!!
LightuC I'll look into state machines.
LarryD I used 'while' as it was the closest I could think to something that would work, I'll read your link, thanks!
gcjr, I think you are talking about a state machine also?
LightuC I see your last post (#9), I'm just on the way out the door and will get to it when back, thank-you so much for putting in the effort with that code!

1 Like

while( ) can block other code execution during that condition.

This can make your sketch unresponsive/run_slowly.

while( ) or do( ) should be used in situations where you know it will finish quickly, example less than milliseconds.

You should always attempt to write non blocking code so as to service all items as fast as possible.

BTW, for general testing, delay(1000); is okay but it is blocking also so for speed handle this differently.

i have an application that by default displays a various information, but displays a menu for a limited period of time whenever a menu button (menu, select, up, down) is pressed. it remains in the menu mode until a timer expires, after which it returns to a non-menu mode where is it displays the default screen

Sharing it might help the OP. :thinking:

the real question is wether or not the program needs to do something else while waiting for the 10 seconds. if it does not, then you can use a blocking while loop.

3 Likes

presumably, the time is normally displayed. but in update mode, the digits being updated, hr, min, sec are flashing.

a timer can be reset/started whenever a button is pressed and if the timer expires, the mode is returned to normal and the display no longer flashes.

The best explanation is that some processors require additional tasks to occur behind the scenes. for example, the esp32 triggers several upkeep tasks between calling the loop function.
The Arduino wrapper hides all this maintenance hoping that the new programmer won't block the code for too long.

arduino-esp32/cores/esp32/main.cpp

    setup();
    for(;;) {
#if CONFIG_FREERTOS_UNICORE
        yieldIfNecessary();   //<<< tasks that may need to be done between loops
#endif
        if(loopTaskWDTEnabled){
            esp_task_wdt_reset(); //<<< tasks that may need to be done between loops
        }
        loop(); // the loop
        if (serialEventRun) serialEventRun(); //<<< tasks that may need to be done between loops
    }

So even with a few seconds let alone 10 seconds while loop that blocks the progress of the code, it could be devastating to the other processes that need to occur the new Arduino programmer doesn't know about.
I Second the avoidance of blocking code like especially using it for 10 second delay:

while(){
}
for (;;){
}

Delay is not so bad as it handles some of the tasks while it stops the progress but not all.

nina-fw/arduino/cores/esp32/delay.c

void delay(uint32_t ms)
{
  vTaskDelay(ms / portTICK_PERIOD_MS);  //<<< other tasks are handled here during the dealy
}

I had to learn this the hard way while trying to program an ESP8266 a few years ago. the ESP would keep resetting out of the blue...

Z

1 Like

you could call yield() in your while loop. (not saying I like blocking code - just that in some cases you could be fine with it)

may be something like this

1 Like

Blocking code isn't an issue really, the microcontroller talks to a DAC but the DAC only needs to know when to turn things on or off and doesn't need constant reminding while it is in either state.
Another point is all the code needs to be less than 16Kb as the microcontroller is an ATTiny1624, right now I have an ATTiny85 running the lights it doesn't have the space to run a display at the same time.

I'm going to look into the machine state recommendation now, you guys are awesome thanks!

Thank-you all, LightuCs (post #9) was the answer I was looking for, all I had to do was copy/paste it into the IDE and it worked the first time!
I had no idea what state machines were so had no idea to google that, we are all here to learn I hope!

2 Likes

consider (start with loop())

i had to simulate your libraries and added mode
at least one oversite was not calling readButtons() within the while loop and the test for BUTTON_DOWN was within the condition for BUTTON_SELECT. did you intend that both buttons were pressed at the same time?

/*********************
Draft code for timer interface via buttons and LCD display using a DS3231 RTC for
time and UNO as MCU
The user pushes the SELECT button to see on and off times, these times are
adjustable with the UP, DOWN, LEFT, and RIGHT buttons.
**********************/
// Include the following libraries:
#define MyHW
#ifdef MyHW
enum { WHITE };
enum {
    BUTTON_NONE   = 0,
    BUTTON_SELECT = 1 << 0,
    BUTTON_UP     = 1 << 1,
    BUTTON_DOWN   = 1 << 2,
};

byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)
byte butState [N_BUT];

struct Adafruit_RGBLCDShield  {
    Adafruit_RGBLCDShield (void)  {
        for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
            pinMode (pinsBut [n], INPUT_PULLUP);
            butState [n] = digitalRead (pinsBut [n]);
        }
    }

    void begin        (int col, int row) { }
    void blink        (void) { }
    void clear        (void) { Serial.println (); }
    void noBlink      (void) { }
    int  readButtons  (void) {
        for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
            byte but = digitalRead (pinsBut [n]);

            if (butState [n] != but)  {
                butState [n] = but;

                delay (10);     // debounce

                if (LOW == but)  {
                    int  res = 1 << n;
                    Serial.println ();
                    Serial.print   (__func__);
                    Serial.print   (" press ");
                    Serial.print   (res);
                    return res;
                }
            }
        }
        return BUTTON_NONE;
    }

    void setCursor    (int x, int y) { if (0 == x) Serial.println (); }
    void setBacklight (int x) { }

    void print (const char *s)     { Serial.print   (s); }
    void print (int i)             { Serial.print   (i); }
    void print (int i, int fmt)    { Serial.print   (i, fmt); }
};

struct DateTime  {
    int  _year;
    int  _month;
    int  _day;
    int  _hour;
    int  _minute;
    int  _second;

    DateTime (void) { 
        _year   = 2020;
        _month  = 9;
        _day    = 5;
        _hour   = 18;
        _minute = 38;
        _second = 50;
    }
    int  year ()    { return _year; }
    int  month ()   { return _month; }
    int  day ()     { return _day; }
    int  hour ()    { return _hour; }
    int  minute ()  { return _minute; }
};

struct RTC_DS3231  {
    DateTime  dt;

    RTC_DS3231   (void) { }
    int begin    (void) { return 0; }
    DateTime now (void) { return dt; } 
};

// -----------------------------------------------------------------------------
#else
# include <RTClib.h>
# include <Wire.h>
# include <Adafruit_RGBLCDShield.h>
# include <utility/Adafruit_MCP23017.h>
#endif

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
RTC_DS3231 rtc;              // Create structure for DS3231 RTC.

int timeOnHour = 5;          // Turn on light at hour
int timeOnMinute = 0;        // Turn on light at minute
int timeOffHour = 21;        // Turn off light at hour
int timeOffMinute = 0;       // Turn off light at minute
int timer = millis();        // Timer for while loops, to be set and reset to 10 seconds at each button push.
// These #defines make it easy to set the backlight color, the LCD is monochrome white.
#define TEAL 0x6  // Display backlight off
#define WHITE 0x7 // Display backlight on

enum { Normal, Update };
int mode = Normal;

unsigned long Timeout = 5000;
unsigned long msecLst;

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin(9600);
    lcd.begin(16, 2);
    // Confirms that the RTC is connected and working.
    if (! rtc.begin()) {
        lcd.print("RTC not responding");
    }
    lcd.setBacklight(WHITE);
}

// -----------------------------------------------------------------------------
void loop()
{
    unsigned long msec = millis ();

    if ( (msec - msecLst) > Timeout)
        mode = Normal;

    if (Normal == mode)
        display_On_Off_Times();
    else
        LCD_Print_Time();

    switch (lcd.readButtons())  {
    case BUTTON_SELECT:
        mode    = Update;
        msecLst = msec;
        break;

    case BUTTON_UP:
    case BUTTON_DOWN:
        mode    = Update;
        msecLst = msec;
        break;
    }
}

// -----------------------------------------------------------------------------
// Prints the current time on the LCD screen
void LCD_Print_Time()
{
    DateTime now = rtc.now();
    lcd.setCursor(0, 0);
    lcd.print(now.year(),DEC);
    lcd.print("/");
    lcd.print(now.month(),DEC);
    lcd.print("/");
    lcd.print(now.day(),DEC);
    lcd.print(" ");
    lcd.print(now.hour(),DEC);
    lcd.print(":");
    lcd.print(now.minute(),DEC);
}

// -----------------------------------------------------------------------------
// Displays the current on and off times on the timer
void display_On_Off_Times()
{
    lcd.setCursor(0, 0);
    lcd.print("Time On:");
    lcd.print(timeOnHour);
    lcd.print(":");
    lcd.print(timeOnMinute);
    lcd.setCursor(0, 1);
    lcd.print("Time Off:");
    lcd.print(timeOffHour);
    lcd.print(":");
    lcd.print(timeOffMinute);
}
1 Like