Hi all, I have a light timer that I am working on and need some advice on how to move forward. Currently the LCD screen (16x4) displays the time and the state of the lights (on/off), right now I only have one channel programmed and would like to add another, and eventually four in total. My problem is I can't figure out how to add more machine states without eating up a lot of memory, I would like to stay below 16KB if possible and am at 9KB already. Any advice?
Below is the current code:
/*********************
Draft code (v2) for timer interface via buttons and LCD display using a DS3231 RTC for
time and UNO as MCU
The user pushes the SELECT and RIGHT 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 <LiquidCrystal.h>
#include <StateMachine.h>
#include <EEPROM.h>
#include <pu2clr_mcp23008.h> // Library for MCP23008 I2C GPIO port expander
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac; // Create structure for DAC
const int ldrPin = A0; // Pin for LDR to control LCD backlight
int ldrValue;
const int lcdBacklight = 9; // LCD backlight
const int ldrCutoffValue = 550;
int channelNum = 1; // Number of channel.
int increment = 1; // Amount to increment up or down the time on/off variables.
int timeOnHour1; // Turn on light at hour.
int timeOnMinute1; // Turn on light at minute.
int timeOffHour1; // Turn off light at hour.
int timeOffMinute1; // Turn off light at minute.
int chan1Stat; // Variable for channel one on/off status.
int timer = millis(); // Timer for while loops, to be set and reset to 10 seconds at each button push.
unsigned long lastmillis = 0;
const int buttonLeft = 3; // Physical second left button
const int buttonUp = 4; // Physical left button
const int buttonSelect = 5; // Physical center button
const int buttonDown = 6; // Physical right button
const int buttonRight = 7; // Physical second right button
int buttonLeftState = 0; // State variable for left button
int buttonUpState = 0; // State variable for up button
int buttonSelectState = 0; // State variable for select button
int buttonDownState = 0; // State variable for down button
int buttonRightState = 0; // State variable for right button
RTC_DS3231 rtc; // Create structure for DS3231 RTC.
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
MCP mcp; // Create object for MCP23008
enum APP_STATE {
STATE_NORMAL_SCREEN, // Normal state is the default state that displays time of day.
STATE_TO_ON_HOUR_SCREEN, // Transition state between normal screen and 'on hour' screen.
STATE_ON_HOUR_SCREEN, // This state allows the user to change the time on hour.
STATE_ON_MINUTE_SCREEN, // This state allows the user to change the time on minute.
STATE_OFF_HOUR_SCREEN, // This state allows the user to change the off hour.
STATE_OFF_MINUTE_SCREEN, // This state allows the user to change the off minute.
STATE_TO_NORMAL_SCREEN // Transition state to normal screen.
} state;
/////////////////////////////////////////SETUP/////////////////////////////////////////
void setup() {
pinMode(lcdBacklight, OUTPUT);
lcd.begin(16, 4);
Serial.begin(9600);
dac.begin(0x60); // Start DAC
mcp.setup(0x20); // Use address 0x20 for MCP23008
mcp.setup(0x20, 0B11111000); // GPIO 0 to 4 are input (buttons) and 5 to 7 are output.
mcp.setRegister(REG_GPPU, 0B1111000); // sets GPIO 0 to 4 with internal pull up resistors
timeOnHour1 = EEPROMReadInt(0); // Read variables stored in EEPROM.
timeOnMinute1 = EEPROMReadInt(2); // Read variables stored in EEPROM.
timeOffHour1 = EEPROMReadInt(4); // Read variables stored in EEPROM.
timeOffMinute1 = EEPROMReadInt(6);// Read variables stored in EEPROM.
if (! rtc.begin()) { // Confirms that the RTC is connected and working.
lcd.print("RTC not responding");
}
}
/////////////////////////////////////////LOOP/////////////////////////////////////////
void loop() {
ldrValue = analogRead(ldrPin);
if (ldrValue < ldrCutoffValue) {
digitalWrite(lcdBacklight, HIGH);
}
else {
digitalWrite(lcdBacklight, LOW);
}
buttonUpState = mcp.gpioRead(buttonUp);
buttonSelectState = mcp.gpioRead(buttonSelect);
buttonDownState = mcp.gpioRead(buttonDown);
buttonLeftState = mcp.gpioRead(buttonLeft);
buttonRightState = mcp.gpioRead(buttonRight);
DateTime now = rtc.now();
int minute1 = now.minute();
int hour1 = now.hour();
long milli_Time_Now = Time_To_Millis(hour1, minute1);
long milli_Time_On = Time_To_Millis(timeOnHour1, timeOnMinute1);
long milli_Time_Off = Time_To_Millis(timeOffHour1, timeOffMinute1);
if ((milli_Time_Now >= milli_Time_On) && (milli_Time_Now < milli_Time_Off)) { // Daytime actions
dac.setVoltage(4006, false);
chan1Stat = 1;
//Serial.println("Chan1 = on");
}
if (milli_Time_Now < milli_Time_On || milli_Time_Now >= milli_Time_Off) { // Nightime actions
dac.setVoltage(0, false);
chan1Stat = 0;
//Serial.println("Chan1 = off");
}
switch(state) {
case STATE_NORMAL_SCREEN:
if(millis() - lastmillis > 500) { // Update normal screen every second.
lastmillis = millis();
LCD_Normal_Screen(); // While normal screen is displayed show time.
LCD_Channel_Status(); // Display channel status
}
if(buttonSelectState == HIGH) { // Check if any buttons pressed and if so go to select screen.
Debounce(); // Debounce
state = STATE_TO_ON_HOUR_SCREEN;
lastmillis = millis(); // Take note of the time when we switch to the select screen.
}
break;
case STATE_TO_ON_HOUR_SCREEN:
Transition_Screen();
state = STATE_ON_HOUR_SCREEN;
break;
case STATE_TO_NORMAL_SCREEN:
lastmillis = millis(); // Reset millis
lcd.clear();
state = STATE_NORMAL_SCREEN;
break;
case STATE_ON_HOUR_SCREEN:
if(millis() - lastmillis > 10000) { // If more than 10 seconds elapse return to normal screen.
state = STATE_TO_NORMAL_SCREEN;
lcd.noBlink();
break; // Exit the switch statement if 10 seconds have elapsed without buttons pushed.
}
lcd.setCursor(8, 2); // Move cursor to timeOnHour1.
lcd.blink(); // Set cursor to blink over timeOnHour1.
if(buttonUpState == HIGH) { // Actions to complete when up button pressed.
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOnHour1 = Increment_Up_24(timeOnHour1); // Increment time up one hour
EEPROMWriteInt(0, timeOnHour1);
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonDownState == HIGH) { // Actions to complete when down button pressed.
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOnHour1 = Increment_Down_24(timeOnHour1);
EEPROMWriteInt(0, timeOnHour1);
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonRightState == HIGH) {
Debounce(); // Debounce
state = STATE_ON_MINUTE_SCREEN;
}
break;
case STATE_ON_MINUTE_SCREEN:
if(millis() - lastmillis > 10000) { // If more than 10 seconds elapse return to normal screen.
state = STATE_TO_NORMAL_SCREEN;
lcd.noBlink();
break; // Exit the switch statement if 10 seconds have elapsed without buttons pushed.
}
if(timeOnHour1 > 9) { // moves cursor over if timeOffHour 2 digits wide, then move flashing cursor over one.
lcd.setCursor(11, 2);
}
else {lcd.setCursor(10, 2); // Move cursor to timeOnMinute1.
}
lcd.blink(); // Set cursor to blink over timeOnMinute1.
if(buttonUpState == HIGH) { // Actions to complete when up button pressed .
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOnMinute1 = Increment_Up_60(timeOnMinute1);
EEPROMWriteInt(2, timeOnMinute1); // Writes variable to EEPROM
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonDownState == HIGH) { // Actions to complete when down button pressed.
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOnMinute1 = Increment_Down_60(timeOnMinute1);
EEPROMWriteInt(2, timeOnMinute1); // Writes variable to EEPROM
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonRightState == HIGH) {
Debounce(); // Debounce
state = STATE_OFF_HOUR_SCREEN;
}
break;
case STATE_OFF_HOUR_SCREEN:
if(millis() - lastmillis > 10000) { // If more than 10 seconds elapse return to normal screen.
state = STATE_TO_NORMAL_SCREEN;
lcd.noBlink();
break; // Exit the switch statement if 10 seconds have elapsed without buttons pushed.
}
lcd.setCursor(9, 3);
lcd.blink(); // Set cursor to blink over timeOffHour1.
if(buttonUpState == HIGH) { // Actions to complete when up button pressed .
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOffHour1 = Increment_Up_24(timeOffHour1); // Increment time up one hour
EEPROMWriteInt(4, timeOffHour1); // Writes variable to EEPROM
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonDownState == HIGH) { // Actions to complete when down button pressed.
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOffHour1 = Increment_Down_24(timeOffHour1);
EEPROMWriteInt(4, timeOffHour1); // Writes variable to EEPROM
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonRightState == HIGH) {
Debounce(); // Debounce
state = STATE_OFF_MINUTE_SCREEN;
}
break;
case STATE_OFF_MINUTE_SCREEN:
if(millis() - lastmillis > 10000) { // If more than 10 seconds elapse return to normal screen.
state = STATE_TO_NORMAL_SCREEN;
lcd.noBlink();
break; // Exit the switch statement if 10 seconds have elapsed without buttons pushed.
}
if(timeOffHour1 > 9) { // moves cursor over if timeOffHour 2 digits wide, then move flashing cursor over one.
lcd.setCursor(12, 3);
}
else {lcd.setCursor(11, 3); // Move cursor to timeOffMinute1.
}
lcd.blink(); // Set cursor to blink over timeOffMinute1.
if(buttonUpState == HIGH) { // Actions to complete when up button pressed.
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOffMinute1 = Increment_Up_60(timeOffMinute1);
EEPROMWriteInt(6, timeOffMinute1);// Writes variable to EEPROM
// Serial.println(EEPROMReadInt(6), HEX);
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonDownState == HIGH) { // Actions to complete when down button pressed.
Debounce(); // Debounce
lastmillis = millis(); // Reset millis countdown
timeOffMinute1 = Increment_Down_60(timeOffMinute1);
EEPROMWriteInt(6, timeOffMinute1);// Writes variable to EEPROM
// Serial.println(EEPROMReadInt(6), HEX);
lcd.clear();
display_On_Off_Times(); // Update the display
}
if(buttonRightState == HIGH) {
Debounce(); // Debounce
state = STATE_TO_ON_HOUR_SCREEN;
}
break;
}
}
/////////////////////////////////////////FUNCTIONS/////////////////////////////////////////
int Increment_Up_24(int var_To_Increment)
{
var_To_Increment += increment; // Increase the hour
if(var_To_Increment > 24){ // Restrict hour values to 1-24
var_To_Increment = 1;
}
if(var_To_Increment < 1){
var_To_Increment = 24;
}
return var_To_Increment;
}
int Increment_Down_24(int var_To_Increment)
{
var_To_Increment -= increment; // Decrease the hour
if(var_To_Increment > 24){ // Restrict hour values to 1-24
var_To_Increment = 0;
}
if(var_To_Increment < 0){
var_To_Increment = 24;
}
return var_To_Increment;
}
int Increment_Up_60(int var_To_Increment)
{
var_To_Increment += increment; // Increase the minute
if(var_To_Increment > 59){ // Restrict minute values to 0-59
var_To_Increment = 0;
}
if(var_To_Increment < 0){
var_To_Increment = 59;
}
return var_To_Increment;
}
int Increment_Down_60(int var_To_Increment)
{
var_To_Increment -= increment; // Increase the minute
if(var_To_Increment > 59){ // Restrict minute values to 0-59
var_To_Increment = 0;
}
if(var_To_Increment < 0){
var_To_Increment = 59;
}
return var_To_Increment;
}
long Time_To_Millis(int hours_, int minutes_)
{ // Converts time to millis.
long hourToConvert = long(hours_);
long minuteToConvert = long(minutes_);
long millis_Time = ((hourToConvert * 3600000) + (minuteToConvert * 60000));
return millis_Time;
}
void Debounce() // Debounce value to stop button entries skipping.
{
delay(250);
}
void Transition_Screen() // Clears and redraws the LCD during transitions.
{
lastmillis = millis(); // Reset millis
lcd.clear();
display_On_Off_Times(); // The select on/off time screen is displayed.
delay(100); // Debounce
}
void LCD_Normal_Screen() // 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(":");
if(now.minute() < 10){
lcd.print("0");
}
lcd.print(now.minute(),DEC);
}
void LCD_Channel_Status() // Print channels status to LCD
{
lcd.setCursor(3, 1);
lcd.print("Channels:");
lcd.setCursor(0, 2);
lcd.print("1:");
if (chan1Stat == 0) {
lcd.print("Off");
}
if (chan1Stat == 1) {
lcd.print("On");
}
}
void display_On_Off_Times() // Displays the current on and off times on the timer.
{
lcd.setCursor(0, 0);
LCD_Normal_Screen();
lcd.setCursor(0, 1);
lcd.print("Channel: ");
lcd.print(channelNum);
lcd.setCursor(0, 2);
lcd.print("Time On:");
lcd.print(timeOnHour1);
lcd.print(":");
if (timeOnMinute1 < 10){
lcd.print("0");
}
lcd.print(timeOnMinute1);
lcd.setCursor(0, 3);
lcd.print("Time Off:");
lcd.print(timeOffHour1);
lcd.print(":");
if (timeOffMinute1 < 10) {
lcd.print("0");
}
lcd.print(timeOffMinute1);
}
void EEPROMWriteInt(int p_address, int p_value) // This function will write a 2 byte integer to the eeprom at the specified.
{
byte lowByte = ((p_value >> 0) & 0xFF);
byte highByte = ((p_value >> 8) & 0xFF);
EEPROM.write(p_address, lowByte);
EEPROM.write(p_address + 1, highByte);
}
unsigned int EEPROMReadInt(int p_address) // This function will read a 2 byte integer from the eeprom at the specified address.
{
byte lowByte = EEPROM.read(p_address);
byte highByte = EEPROM.read(p_address + 1);
return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}
Below is a DIY UML diagram of current and planned functionality, the top area labelled 'Channel 1' is in the code above: