Expanding Machine State Pages

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:

consider

// demonstrate multiple lamp timer

enum { StOff, StOn };

struct Light {
    byte    pin;
    byte    onHour;
    byte    onMins;
    byte    offHour;
    byte    offMins;
    const char *desc;
    byte    on;
};

Light light [] = {
    { 10,  1, 30,  12,  0, "lamp0" },
    { 11,  3,  0,   9, 30, "lamp1" },
    { 12, 13,  0,  14, 30, "lamp2" },
    { 11, 11,  0,  14, 30, "lamp1" },
};
#define N_Light     (sizeof(light)/sizeof(Light))

enum { LedOff = HIGH, LedOn = LOW };

unsigned hour;
unsigned mins;

char s [80];

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

    mins = msec / 40;       // accelerate time for testing, 1hr = 2400 msec
    hour = mins / 60;
    hour = 24 < hour ? 1 : hour;
    mins = mins % 60;


    Light *l = light;
    for (unsigned n = 0; n < N_Light; n++, l++)  {
        if (l->on)  {
                    // compare mins range because of accelerated time
            if (hour == l->offHour && (mins - l->offMins) < 5)  {
                digitalWrite (l->pin, LedOff);
                l->on = false;

                sprintf (s, "  %2d:%02d  %3s  %s",
                    hour, mins, l->on ? "on" : "off", l->desc );
                Serial.println (s);
            }
        }
        else {
            if (hour == l->onHour && (mins - l->onMins) < 5)  {
                digitalWrite (l->pin, LedOn);
                l->on = true;

                sprintf (s, "  %2d:%02d  %3s  %s",
                    hour, mins, l->on ? "on" : "off", l->desc );
                Serial.println (s);
            }
        }
    }
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin (115200);
    
    for (unsigned n = 0; n < N_Light; n++)  {
        digitalWrite (light [n].pin, LedOff);
        pinMode      (light [n].pin, OUTPUT);
    }
}

I don't know how much saving you might get, but enum defaults to int type in many compilers. You can specify char or byte to make them smaller.

Nah. Wasteful and slow. Instead, create functions that read and debounce, both.

Hi,
What model controller are you using?
Why do you want to keep memory usage to <16K?

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

Good point. What is the spare 7k for?

@TomGeorge, I'm writing it on an Uno but this will be going on an ATTiny1624, with 16KB flash, same RAM as Uno though. I wanted the 3224 but couldn't find them...
I have the light controller running on an ATTiny85 right now but have to hard code the times, I'd like the user to be able to enter them as per the sketch, but I am at a mental block as to how to expand to four channels without major bloat.

@anon57585045, you mean just include delay(x250) in functions and not as a separate function? I suppose if I never need to change the value that would be fine.

@gcjr I'm looking at your code, will need some time to make sense of it, thanks for the effort!! Much appreciated.

Hi,

So I assume you are going to make a PCB.
Why not use a Nano and plug it into your PCB via headers?

Tom... :smiley: :+1: :coffee: :australia:

Not really. I suppose I am saying you need to completely isolate switch timing from the code that reads them. Right now they are "joined at the hip". That is why you need to call the debounce function so often.

Unfortunately, that decision is fairly baked into the code and won't be extremely easy to change. But I believe the pay off would be big.

Basically, all the instances of this

      if(buttonSelectState == HIGH) {     // Check if any buttons pressed and if so go to select screen.
        Debounce();                       // Debounce

would be

      if(buttonRead(buttonSelect) ) {     // Check if any buttons pressed and if so go to select screen.

All the debounce logic can be inside 'buttonRead'.
See what I mean?

@TomGeorge , I typically have my Uno configured to program ICs with a ZIF, then another breadboard with a ZIF and the test components, to actually run the uploaded code, there may be better methods but this works for me.
Eventually I'll make a PCB, the ATTiny85 is on a proto board and I have a PCB and circuit already designed for it, but I'll skip that and go with the ATTiny1624.

@aarg I see!! Yes thanks, that is a change I'll make for sure!

@GaGa111 Here's some more code for your consideration. It lets you interact with channel settings using the serial monitor. I only implemented the most basic functionality to demonstrate the approach but you can cycle through 3 states by entering "R" (sorta simulating a right button press) and then making changes to that state by entering "U" (simulating an up button press). To keep things as simple as possible you can only increment the HourOn time for a channel but importantly you can change which channel you are editing, and you have 4 separate channels. So again you enter "R" to cycle through the 3 states, and "U" to make a change on the currently selected state.

Here's what the serial monitor will show at startup and then after some serial input (indicated by the "Input" lines). In this example I'll change the HourOn time for channel 0 to be 1, and then change to channel 1 to show how it has a different HourOn.

At initial startup the current state, channel, and HourOn for the channel are shown.

State = STATE_NORMAL_SCREEN
  Channel = 0
  Hour On = 0

I then enter an "R" to move to the change channel state, then another "R" to move to the change HourOn state, then a "U" to increment the HourOn for the current channel. At this point channel 0's HourOn is now set to 1.

---------
Input = R
---------
State = STATE_CHANGE_CHANNEL_SCREEN
  Channel = 0
  Hour On = 0
---------
Input = R
---------
State = STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL
  Channel = 0
  Hour On = 0
---------
Input = U
---------
State = STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL
  Channel = 0
  Hour On = 1

At this point we'll go change which channel settings are displayed. We do this by entering two "R" 's to get to the change channel state, then "U" to switch over to channel 1 and you can see how its HourOn is still 0. So at this point channel 0 has an HourOn = 1 while channel 1 HourOn = 0.

---------
Input = R
---------
State = STATE_NORMAL_SCREEN
  Channel = 0
  Hour On = 1
---------
Input = R
---------
State = STATE_CHANGE_CHANNEL_SCREEN
  Channel = 0
  Hour On = 1
---------
Input = U
---------
State = STATE_CHANGE_CHANNEL_SCREEN
  Channel = 1
  Hour On = 0

Here's the complete Arduino Uno code:

struct CHANNEL_SETTINGS
{
    int LightIsActive; // 1 means the light is turned on, 0 means it's off.
    int HourOn; // Note you can use bytes rather than ints to reduce size, see Arduino variable sizes https://learn.sparkfun.com/tutorials/data-types-in-arduino/all
    int HourOff;
    int MinuteOn;
    int MinuteOff;
};

enum APP_STATE
{
    STATE_NORMAL_SCREEN,
    STATE_CHANGE_CHANNEL_SCREEN,               // Allows user to change the current channel.
    STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL,  // Allows user to change ON_HOUR for the current channel.
};

// Define an array of CHANNEL_SETTINGS structs to hold the settings for each channel.
// This array is the real key to making handling of different settings for all the channels easy.
const int NUMBER_OF_CHANNELS = 4;
CHANNEL_SETTINGS channelSettings[NUMBER_OF_CHANNELS];

APP_STATE state = STATE_NORMAL_SCREEN;
int currentChannelIndex = 0;

void setup()
{
    Serial.begin(9600);
    printCurrentSettings();
}

void loop()
{
    // Just some fake button state flags for this example.
    int hadButtonPress = 0;
    int buttonUpState = 0;
    int buttonRightState = 0;

    // You type into the serial monitor to simulate pressing a button.
    if (Serial.available())
    {
        hadButtonPress = 1;

        String input = Serial.readString();
        input.trim(); // Remove any carriage return and line feed characters and any spaces.
        input.toUpperCase();

        Serial.println("---------");
        Serial.print("Input = "); Serial.println(input);
        Serial.println("---------");

        if (input == "U") buttonUpState = 1;
        else if (input == "R") buttonRightState = 1;
    }

    // Right keypresses just cycle through the available states.
    // Up keypresses make changes to that state.
    switch (state)
    {
        case STATE_NORMAL_SCREEN:
            if (buttonRightState == 1)
            {
                state = STATE_CHANGE_CHANNEL_SCREEN;
            }
            break;

        case STATE_CHANGE_CHANNEL_SCREEN:
            if (buttonRightState)
            {
                state = STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL;
            }
            if (buttonUpState)
            {
                // Increment the current channel but make sure it stays within the allowed range 0-3.
                currentChannelIndex++;
                if (currentChannelIndex == NUMBER_OF_CHANNELS) currentChannelIndex = 0;
            }
            break;

        case STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL:
            if (buttonRightState)
            {
                state = STATE_NORMAL_SCREEN;
            }
            if (buttonUpState)
            {
                // Increment HourOn for current channel but make sure it stays within the allowed range 0-23.
                channelSettings[currentChannelIndex].HourOn++;
                if (channelSettings[currentChannelIndex].HourOn == 24) channelSettings[currentChannelIndex].HourOn = 0;
            }
            break;

        default:
            break;
    }

    if (hadButtonPress)
    {
        // Clear the fake buttons.
        hadButtonPress = 0;
        buttonUpState = 0;
        buttonRightState = 0;

        printCurrentSettings();
    }
}

void printCurrentSettings()
{
    Serial.print("State = ");

    switch (state)
    {
        case STATE_NORMAL_SCREEN:
            Serial.println("STATE_NORMAL_SCREEN");
            break;
        case STATE_CHANGE_CHANNEL_SCREEN:
            Serial.println("STATE_CHANGE_CHANNEL_SCREEN");
            break;
        case STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL:
            Serial.println("STATE_ON_HOUR_SCREEN_FOR_CURRENT_CHANNEL");
            break;
        default:
            Serial.println("UNKNOWN");
            break;
    }

    Serial.print("  Channel = "); Serial.println(currentChannelIndex);
    Serial.print("  Hour On = "); Serial.println(channelSettings[currentChannelIndex].HourOn);
}

Thank you @dparson !!! Your (ar)ray of wisdom was exactly what I needed get it to work!
@anon57585045 , I looked into your suggestion re adding debounce to the buttonstate, I was hoping to be able to alter the library files for the MCP23008, however it looks like it inherits that functionality from Wire.h, and I'm not touching that ha ha ha!

Currently the code is 9202 Bytes, I have so much space left!

I still have to update the EEPROM, DAC, and display but the below code now allows the user to update the time on/off for four channels.

Thank-you all who posted, I really needed the help as I don't have anyone else that knows code!!!
Below is where it's at:

/*********************
Draft code (v3) 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;              // Number of channel.

int increment = 1;           // Amount to increment up or down the time on/off variables.

int timeOnHour[3];           // Turn on light at hour.
int timeOnMinute[3];         // Turn on light at minute.
int timeOffHour[3];          // Turn off light at hour.
int timeOffMinute[3];        // 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, 0B11111000);          // Use address 0X20, and GPIO 0 to 4 are set to 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 minute_Now = now.minute();
 int hour_Now   = now.hour();

 long milli_Time_Now = Time_To_Millis(hour_Now, minute_Now);  // Convert time from RTC to millis          
 long milli_Time_On  = Time_To_Millis(timeOnHour[0], timeOnMinute[0]); 
 long milli_Time_Off = Time_To_Millis(timeOffHour[0], timeOffMinute[0]);
  
 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.   
//      channelNum = 0;
      if(ButtonUpState == HIGH) {         // Actions to complete when up button pressed.                                        
        Debounce();                       // Debounce
        lastmillis = millis();            // Reset millis countdown
        timeOnHour[channelNum] = Increment_Up_24(timeOnHour[channelNum]); // 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
        timeOnHour[channelNum] = Increment_Down_24(timeOnHour[channelNum]);        
//        EEPROMWriteInt(0, timeOnHour1);
        lcd.clear();
        display_On_Off_Times();           // Update the display
      }
      if(ButtonRightState == HIGH) {
        Debounce();                       // Debounce
        state = STATE_ON_MINUTE_SCREEN;
      }      
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce           
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
//        lcd.clear(); 
       Transition_Screen();     
       state = STATE_TO_ON_HOUR_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(timeOnHour[channelNum] > 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
        timeOnMinute[channelNum] = Increment_Up_60(timeOnMinute[channelNum]);
//        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
        timeOnMinute[channelNum] = Increment_Down_60(timeOnMinute[channelNum]);
//        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;
        }
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce           
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
//        lcd.clear(); 
       Transition_Screen();     
       state = STATE_TO_ON_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
        timeOffHour[channelNum] = Increment_Up_24(timeOffHour[channelNum]); // 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
        timeOffHour[channelNum] = Increment_Down_24(timeOffHour[channelNum]);
//        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;
        }           
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce           
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
       Transition_Screen();     
       state = STATE_TO_ON_HOUR_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(timeOffHour[channelNum] > 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
        timeOffMinute[channelNum] = Increment_Up_60(timeOffMinute[channelNum]);
//        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
        timeOffMinute[channelNum] = Increment_Down_60(timeOffMinute[channelNum]);
//        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;
        }           
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce   
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
       Transition_Screen();     
       state = STATE_TO_ON_HOUR_SCREEN;       
       }       
       break;     
   }  
}

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


int Increment_Up_24(int var_To_Increment)// Increments a 0-24 hour variable up.
{
   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)// Increments a 0-24 hour variable down.
{
    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)// Increments a 0-60 hour variable up.
{
  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)// Increments a 0-60 hour variable down.
{
  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 from 00:00 to millis.
{      // 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(); 
  if(now.hour() == 0 && now.minute() == 0 && now.second() == 1){ // Remove trailing digit after roll-over from 23:59.
   lcd.clear();    
    }
  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(timeOnHour[channelNum]);
  lcd.print(":");
  if (timeOnMinute[channelNum] < 10){
    lcd.print("0");
    } 
  lcd.print(timeOnMinute[channelNum]);
  lcd.setCursor(0, 3);
  lcd.print("Time Off:");
  lcd.print(timeOffHour[channelNum]);
  lcd.print(":");
  if (timeOffMinute[channelNum] < 10) {
    lcd.print("0");
    }
  lcd.print(timeOffMinute[channelNum]);   
    }

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

You would not have to do anything like that, for my suggestion.

@anon57585045 , really? I don't know how I would implement it then, any hints you can drop?

After using 9.2 Megabytes?

Darn it, that's a typo! Thanks, I'll edit that.

To see how debounce ( and proper button handling in general ) works, look at a good keypad and/or button library.

It might even behoove you to use one.

@anon57585045 , I'll look through some keypad/button libraries, thanks.

Everything was working great until I updated the EEPROM. I've got rudimentary code working, but there are two spots of odd behavior.
The first is when updating 'time off hour' on channel one, I can only choose numbers between 0 and 2, when scrolling with either the up or down buttons, any more button pushes result in the number displayed staying at either 0 or 2.
The second issue is 'time on minute' for channel 4, which is displaying similar behavior, except it is restricted to 59 - 01, and in addition when 01 is selected the cursor jumps to 'time off hour' as if the select button was pushed, very strange.
What's odd is that the code is identical for each channel, just that the arrays are cycling to different Integers.
I noticed this behavior after adding the EEPROM update and am wondering if that is where things are going awry. Currently I am using a function that stores 2 byte integers with no 'gaps' so to speak between them, could that be an issue? Any ideas??

Code:

/*********************
Draft code (v3) 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;              // Number of channel.

int increment = 1;           // Amount to increment up or down the time on/off variables.

int timeOnHour[3];           // Turn on light at hour.
int timeOnMinute[3];         // Turn on light at minute.
int timeOffHour[3];          // Turn off light at hour.
int timeOffMinute[3];        // Turn off light at minute.

int chanStat[3];               // 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, 0B11111000);          // Use address 0X20, and GPIO 0 to 4 are set to input (buttons) and 5 to 7 are output.
 mcp.setRegister(REG_GPPU, 0B1111000); // sets GPIO 0 to 4 with internal pull up resistors
 
timeOnHour[0] = EEPROMReadInt(0);      // Read variables stored in EEPROM.
timeOnHour[1] = EEPROMReadInt(2);      // Read variables stored in EEPROM.
timeOnHour[2] = EEPROMReadInt(4);      // Read variables stored in EEPROM.
timeOnHour[4] = EEPROMReadInt(6);      // Read variables stored in EEPROM.

timeOffHour[0] = EEPROMReadInt(8);     // Read variables stored in EEPROM.
timeOffHour[1] = EEPROMReadInt(10);    // Read variables stored in EEPROM.
timeOffHour[2] = EEPROMReadInt(12);    // Read variables stored in EEPROM.
timeOffHour[4] = EEPROMReadInt(14);    // Read variables stored in EEPROM.

timeOnMinute[0] = EEPROMReadInt(16);    // Read variables stored in EEPROM.
timeOnMinute[1] = EEPROMReadInt(18);    // Read variables stored in EEPROM.
timeOnMinute[2] = EEPROMReadInt(20);    // Read variables stored in EEPROM.
timeOnMinute[4] = EEPROMReadInt(22);    // Read variables stored in EEPROM.

timeOffMinute[0] = EEPROMReadInt(24);    // Read variables stored in EEPROM.
timeOffMinute[1] = EEPROMReadInt(26);    // Read variables stored in EEPROM.
timeOffMinute[2] = EEPROMReadInt(28);    // Read variables stored in EEPROM.
timeOffMinute[4] = EEPROMReadInt(30);    // 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 minute_Now = now.minute();
 int hour_Now   = now.hour();

 long milli_Time_Now = Time_To_Millis(hour_Now, minute_Now);  // Convert time from RTC to millis          
 long milli_Time_On_0  = Time_To_Millis(timeOnHour[0], timeOnMinute[0]); 
 long milli_Time_Off_0 = Time_To_Millis(timeOffHour[0], timeOffMinute[0]);
 long milli_Time_On_1  = Time_To_Millis(timeOnHour[1], timeOnMinute[1]); 
 long milli_Time_Off_1 = Time_To_Millis(timeOffHour[1], timeOffMinute[1]); 
 long milli_Time_On_2  = Time_To_Millis(timeOnHour[2], timeOnMinute[2]); 
 long milli_Time_Off_2 = Time_To_Millis(timeOffHour[2], timeOffMinute[2]);
 long milli_Time_On_3  = Time_To_Millis(timeOnHour[3], timeOnMinute[3]); 
 long milli_Time_Off_3 = Time_To_Millis(timeOffHour[3], timeOffMinute[3]);

 if ((milli_Time_Now >= milli_Time_On_0) && (milli_Time_Now < milli_Time_Off_0)) { // Channel 0 daytime actions
    dac.setVoltage(4006, false); 
    chanStat[0] = 1; 
    } 
 if (milli_Time_Now < milli_Time_On_0 || milli_Time_Now >= milli_Time_Off_0)  { // Channel 0 nightime actions
    dac.setVoltage(0, false);
    chanStat[0] = 0; 
    }
 if ((milli_Time_Now >= milli_Time_On_1) && (milli_Time_Now < milli_Time_Off_1)) { // Channel 0 daytime actions
    //dac.setVoltage(4006, false); 
    chanStat[1] = 1; 
    } 
 if (milli_Time_Now < milli_Time_On_1 || milli_Time_Now >= milli_Time_Off_1)  { // Channel 0 nightime actions
    //dac.setVoltage(0, false);
    chanStat[1] = 0; 
    }
 if ((milli_Time_Now >= milli_Time_On_2) && (milli_Time_Now < milli_Time_Off_2)) { // Channel 0 daytime actions
    //dac.setVoltage(4006, false); 
    chanStat[2] = 1; 
    } 
 if (milli_Time_Now < milli_Time_On_2 || milli_Time_Now >= milli_Time_Off_2)  { // Channel 0 nightime actions
    //dac.setVoltage(0, false);
    chanStat[2] = 0; 
    }
 if ((milli_Time_Now >= milli_Time_On_3) && (milli_Time_Now < milli_Time_Off_3)) { // Channel 0 daytime actions
    //dac.setVoltage(4006, false); 
    chanStat[3] = 1; 
    //Serial.println("Chan1 = on");
    } 
 if (milli_Time_Now < milli_Time_On_3 || milli_Time_Now >= milli_Time_Off_3)  { // Channel 0 nightime actions
    //dac.setVoltage(0, false);
    chanStat[3] = 0; 
    }

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();
      channelNum = 0;
      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
        timeOnHour[channelNum] = Increment_Up_24(timeOnHour[channelNum]); // Increment time up one hour
        EEPROMWriteInt(0, timeOnHour[0]);
        EEPROMWriteInt(2, timeOnHour[1]);
        EEPROMWriteInt(4, timeOnHour[2]);
        EEPROMWriteInt(6, timeOnHour[3]);                        
        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
        timeOnHour[channelNum] = Increment_Down_24(timeOnHour[channelNum]);        
        EEPROMWriteInt(0, timeOnHour[0]);
        EEPROMWriteInt(2, timeOnHour[1]);
        EEPROMWriteInt(4, timeOnHour[2]);
        EEPROMWriteInt(6, timeOnHour[3]);
        lcd.clear();
        display_On_Off_Times();           // Update the display
      }
      if(ButtonRightState == HIGH) {
        Debounce();                       // Debounce
        lastmillis = millis();            // Reset millis countdown            
        state = STATE_ON_MINUTE_SCREEN;
      }      
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce 
       lastmillis = millis();             // Reset millis countdown                 
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
       Transition_Screen();     
       state = STATE_TO_ON_HOUR_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(timeOnHour[channelNum] > 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
        timeOnMinute[channelNum] = Increment_Up_60(timeOnMinute[channelNum]);
        EEPROMWriteInt(8, timeOnMinute[0]);
        EEPROMWriteInt(10, timeOnMinute[1]);
        EEPROMWriteInt(12, timeOnMinute[2]);
        EEPROMWriteInt(14, timeOnMinute[3]);        
        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
        timeOnMinute[channelNum] = Increment_Down_60(timeOnMinute[channelNum]);
        EEPROMWriteInt(8, timeOnMinute[0]);
        EEPROMWriteInt(10, timeOnMinute[1]);
        EEPROMWriteInt(12, timeOnMinute[2]);
        EEPROMWriteInt(14, timeOnMinute[3]);     
        lcd.clear();
        display_On_Off_Times();           // Update the display
      }  
      if(ButtonRightState == HIGH) {
        Debounce();                       // Debounce        
        lastmillis = millis();            // Reset millis countdown       
        state = STATE_OFF_HOUR_SCREEN;
        }
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce  
       lastmillis = millis();             // Reset millis countdown               
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
       Transition_Screen();     
       state = STATE_TO_ON_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
        timeOffHour[channelNum] = Increment_Up_24(timeOffHour[channelNum]); // Increment time up one hour
        EEPROMWriteInt(16, timeOffHour[0]);
        EEPROMWriteInt(18, timeOffHour[1]);
        EEPROMWriteInt(20, timeOffHour[2]);
        EEPROMWriteInt(22, timeOffHour[3]);     
        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
        timeOffHour[channelNum] = Increment_Down_24(timeOffHour[channelNum]);
        EEPROMWriteInt(16, timeOffHour[0]);
        EEPROMWriteInt(18, timeOffHour[1]);
        EEPROMWriteInt(20, timeOffHour[2]);
        EEPROMWriteInt(22, timeOffHour[3]);                   
        lcd.clear();
        display_On_Off_Times();           // Update the display
      }
        
      if(ButtonRightState == HIGH) {
        Debounce();                       // Debounce 
        lastmillis = millis();            // Reset millis countdown               
        state = STATE_OFF_MINUTE_SCREEN;
        }           
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce 
       lastmillis = millis();             // Reset millis countdown                 
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
       Transition_Screen();     
       state = STATE_TO_ON_HOUR_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(timeOffHour[channelNum] > 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
        timeOffMinute[channelNum] = Increment_Up_60(timeOffMinute[channelNum]);
        EEPROMWriteInt(24, timeOffMinute[0]);
        EEPROMWriteInt(26, timeOffMinute[1]);
        EEPROMWriteInt(28, timeOffMinute[2]);
        EEPROMWriteInt(30, timeOffMinute[3]);  
        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
        timeOffMinute[channelNum] = Increment_Down_60(timeOffMinute[channelNum]);
        EEPROMWriteInt(24, timeOffMinute[0]);
        EEPROMWriteInt(26, timeOffMinute[1]);
        EEPROMWriteInt(28, timeOffMinute[2]);
        EEPROMWriteInt(30, timeOffMinute[3]);        
        lcd.clear();
        display_On_Off_Times();           // Update the display
      }  
      if(ButtonRightState == HIGH) {
        Debounce();                       // Debounce 
        lastmillis = millis();            // Reset millis countdown               
        state = STATE_TO_ON_HOUR_SCREEN;
        }           
      if(buttonSelectState == HIGH){
       Debounce();                        // Debounce  
       lastmillis = millis();             // Reset millis countdown        
       channelNum += 1;
       if (channelNum >3){
        channelNum = 0;
        } 
       Transition_Screen();     
       state = STATE_TO_ON_HOUR_SCREEN;       
       }       
       break;     
   }  
}

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

int Increment_Up_24(int var_To_Increment)// Increments a 0-24 hour variable up.
{
   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)// Increments a 0-24 hour variable down.
{
    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)// Increments a 0-60 hour variable up.
{
  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)// Increments a 0-60 hour variable down.
{
  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 from 00:00 to millis.
{      // 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(); 
  if(now.hour() == 0 && now.minute() == 0 && now.second() == 1){ // Remove trailing digit after roll-over from 23:59.
   lcd.clear();    
    }
  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 (chanStat[0] == 0) {
    lcd.print("Off");
    }
  if (chanStat[0] == 1) {
    lcd.print("On");
    } 
  lcd.setCursor(10, 2);
  lcd.print("2:");
  if (chanStat[1] == 0) {
    lcd.print("Off");
    }
  if (chanStat[1] == 1) {
    lcd.print("On");
    }  
  lcd.setCursor(0, 3);
  lcd.print("3:");
  if (chanStat[2] == 0) {
    lcd.print("Off");
    }
  if (chanStat[2] == 1) {
    lcd.print("On");
    }
  lcd.setCursor(10, 3);
  lcd.print("4:");
  if (chanStat[3] == 0) {
    lcd.print("Off");
    }
  if (chanStat[3] == 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: ");
  if(channelNum == 0){
    lcd.print("1");
    }
  if(channelNum == 1){
    lcd.print("2");
    }
  if(channelNum == 2){
    lcd.print("3");
    }  
  if(channelNum == 3){
    lcd.print("4");  
    }
  lcd.setCursor(0, 2);
  lcd.print("Time On:");
  lcd.print(timeOnHour[channelNum]);
  lcd.print(":");
  if (timeOnMinute[channelNum] < 10){
    lcd.print("0");
    } 
  lcd.print(timeOnMinute[channelNum]);
  lcd.setCursor(0, 3);
  lcd.print("Time Off:");
  lcd.print(timeOffHour[channelNum]);
  lcd.print(":");
  if (timeOffMinute[channelNum] < 10) {
    lcd.print("0");
    }
  lcd.print(timeOffMinute[channelNum]);   
    }

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

@GaGa111 I noticed the arrays you are initializing are sized as 3 but you are attempting to store 4 elements within them.

int timeOnHour[3];  // Should be a 4 because you want 4 elements in the array

I also noticed when assigning values to the arrays that you used index 4 rather than index 3

timeOnHour[0] = EEPROMReadInt(0);
timeOnHour[1] = EEPROMReadInt(2);
timeOnHour[2] = EEPROMReadInt(4);
timeOnHour[4] = EEPROMReadInt(6);  // Should be index 3

Not sure if these are causing the problems you mentioned but maybe they are related.

One other thing to help with programming in general, if you find yourself duplicating the exact same code, especially if it's more than a couple of lines, consider putting that code into a separate function. For example in the STATE_ON_HOUR_SCREEN keypress logic for ButtonUpState and ButtonDownState only 1 line of the 9 lines is different, so you could make troubleshooting and debugging easier on yourself if you remove the duplication that you're having to look through.

     // ...
      if(ButtonUpState == HIGH) {         // Actions to complete when up button pressed.                                        
        timeOnHour[channelNum] = Increment_Up_24(timeOnHour[channelNum]); // Increment time up one hour
        handleOnHourButtonPress();
      }
           
      if(ButtonDownState == HIGH) {       // Actions to complete when down button pressed.
        timeOnHour[channelNum] = Increment_Down_24(timeOnHour[channelNum]);        
        handleOnHourButtonPress();
      }
     // ...
}

void handleOnHourButtonPress()
{
        Debounce();                       // Debounce  (do you need this comment?)
        lastmillis = millis();            // Reset millis countdown (do you need this one?)

        EEPROMWriteInt(0, timeOnHour[0]);
        EEPROMWriteInt(2, timeOnHour[1]);
        EEPROMWriteInt(4, timeOnHour[2]);
        EEPROMWriteInt(6, timeOnHour[3]);

        lcd.clear();
        display_On_Off_Times();           // Update the display (do you need this comment? Your function name is good and with good variable and function names you can reduce the number of comments, making your code readable rather than making your comments readable.  Lots of unnecessary comments can make debugging harder because you've got to look through them in addition to the really import stuff...the actual code the mcu runs)
}