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