Arduino Millis and Indexing Code

I’ll take a stab at it.
Whenever checkMachine() function is initiated, we check to see which switch is activated. That tells us which state the machine will go into (startup, state1…..finished). Then, only if the commonTIMER has expired will the associated screen be displayed and the commonTIMER start again. If not, the loop starts over.

You do know the switch we are taking about here is a C++ thing, not an actual physical push button switch :wink:

  • C++ switch and case work together.
    switch (mState) this asks the question what is mState equal to ?
    case STATE1: says I am equal to 1, do you want me ?

  • Every 1ms we jump down to the checkMachine( ) function and look for the current value that mState is equal to, we then we go to that state and execute the code lines therein.

Clearly, I missed that one. I need to review the switch / case concept. Heard of it but not familiar. Need some time on that.

  • BTW if you are not familiar with switch/case read this:
    C++ Switch

  • Lets have a test

:slight_smile:

I’m missing something on the switch / case. I thought with switch / case, you started out with something like:

int day = 2;

switch (day) {
case 1:
cout << “monday”;
break;

case 2:
cout << “tuesday”;
break;
}

// outputs “tuesday” (day 2)

In other words, whatever the int = in the first line, in this case 2, the code flow jumps down to case 2 which is tuesday.

In the example you gave me, i dont see an int number to reference which case to go to. How do i know what the mstate is without the int number to reference? What am i missing?

  • This should read:
    In other words, whenever day = 2 the code flow jumps down to case 2 which is tuesday.

  • If we made variable mState = FINISHED, then mState is actually equal to 5
    mState = FINISHED; //we just put the number 5 into variable mState.

That makes sense. So what changes the mstate variable to say state3?

So what changes the mstate variable to say state3?

We do it in a line of code, example:
mState = STATE3; //make variable mState equal to STATE3 which the C++ compiler has assigned the number 3




  • Going to give you the answers to Post #84, however, you need to understand the answer before we go on.

  • #1 we get here every 1ms
    #2 we get here at power up; then every 8 seconds thereafter.
    #3 we get here 2 seconds after power up; then every 8 seconds thereafter.
    #4 we get here 8 seconds after power up; then every 8 seconds thereafter.
    #5 we get here every 1ms
    #6 we get here 2 seconds after power up; then every 1ms for 2000ms (2 sec.),
    then repeats every 8 seconds thereafter.

I need to go over this. Time out for study break and dinner. To be continued….. Thanks Larry! :+1::+1:

1 Like
  • An Oscilloscope trace showing Question #6 results.

  • This image shows us checking the commonTIMER immediately after making mState = STATE2.

  • Looking at the zoomed inset, each vertical pulse is when we are at point #6 in the flowchart.
    There are 2000 checks before the commonTIMER EXPIRES.

  • In this situation, we don’t need an else { . . . }

Larry, here is where I'm struggling. Referring to Post #84, I understand that when we start up the state machine, mstate will be at 0 which is associated with STARTUP. I got that. I also understand that if and when mstate ever changes to anything else, like STATE1 or STATE2, etc., the code flow jumps to that block of code. I got that. What I'm not getting is, what is changing the variable held in mstate? Thank you for your patience - I'm just stuck on that.

  • This concept is very important to understand, so keep asking questions until you understand the how and why.

  • With reference to the image below from the sketch in Post #17.

  • We change the value of the mState variable in our sketch.
    - For example when we first define the State Machine variables, YELLOW
    - When we close the switch on GPIO D02 (GREEN) we make mstate = STATE1, PINK.
    - When the commonTIMER has EXPIRED, BLUE.

1 Like
  • Please read Post #93, I would like to proceed with something new but will come back to your question two posts from now.
    i.e. after we adjust the sketch from post #17 so we can more concisely solve your confusion.



  • With reference to the sketch below, modified from the sketch in post #17.

  • The STATE1 code in the State Machine has been remove, we now have a blank State Machine.

  • A new a new TIMER object has been made, it is called testLedTIMER

  • The mySwitch code has been changed so this new TIMER is used for timing the testLED on GPIO D12.
    .
    i.e. When the switch on GPIO D02 is closed, the testLED comes ON then goes OFF, the on interval is 2 seconds.

  • Please review the new State Machine look, the new TIMER definition testLedTIMER and the new code that handles the mySwitch pushbutton switch.

  • Upload this new version of the sketch, do you understand the changes that were made as mentioned above ?

//
//================================================^================================================
//                              B a s i c   S k e l e t o n   S k e t c h
//
//  https://forum.arduino.cc/t/arduino-millis-and-indexing-code/1376338/94
//
//  LarryD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       25/04/24    Running code
//
//
//
//
//  Notes:
//
//
//
//

//================================================
#include <Wire.h>

//Use I2C library:     https://github.com/duinoWitchery/hd44780
//LCD Reference:       https://www.arduino.cc/en/Reference/LiquidCrystal

#include <hd44780.h>   //main hd44780 header

//NOTE:
//hd44780_I2Cexp control LCD using I2C I/O expander backpack (PCF8574 or MCP23008)
//hd44780_I2Clcd control LCD with native I2C interface (PCF2116, PCF2119x, etc...)

#include <hd44780ioClass/hd44780_I2Cexp.h> //I2C expander i/o class header

//If you do not know what your I2C address is, first run the "I2C_Scanner" sketch
//OR
//run the "I2CexpDiag" sketch that comes with the hd44780 library
//hd44780_I2Cexp lcd(0x3F);

hd44780_I2Cexp lcd(0x27);


//================================================
#define LEDon              HIGH   //PIN---[220R]---A[LED]K---GND
#define LEDoff             LOW

#define PRESSED            LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED           HIGH

#define CLOSED             LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED             HIGH

#define ENABLED            true
#define DISABLED           false

#define RELAYon            LOW
#define RELAYoff           HIGH


//================================================
#define ONE_SECOND   1000ul              //milliseconds
#define ONE_MINUTE   (ONE_SECOND * 60ul) //60 seconds in one minute 
#define ONE_HOUR     (ONE_MINUTE * 60ul) //60 minutes in one hour
#define ONE_DAY      (ONE_HOUR * 24ul)   //24 hours in one day



//                                   c l a s s   m a k e T I M E R
//================================================^================================================
//
/*
  //========================
  makeTIMER toggleLED =
  {
     //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
     MILLIS/MICROS, 500ul, ENABLED/DISABLED, YES/NO, A0-A5

     //.SpeedAdjustPin defaults to 0 i.e. no speed adjustment is used
     //if .SpeedAdjustPin = A0-A5, a potentiometer on this pin adjusts the TIMER's speed (for diagnostics)
     //class static flag "makeTIMER::normalFlag" can be used to ENABLE/DISABLE adjustable TIMER speed,
     //ENABLE = normal speed, DISABLED = potentiometer controls TIMER speed
  };

  TIMER functions we can access:
  toggleLED.checkTIMER();
  toggleLED.enableRestartTIMER();
  toggleLED.restartTIMER()
  toggleLED.disableTIMER();
  toggleLED.expireTimer();
  toggleLED.setInterval(100ul);

  Static variable access
  makeTIMER::normalFlag = ENABLED/DISABLED  //defaults to DISABLED at power up time i.e. variable speed is allowed
*/

//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//
//These TIMER objects are non-blocking
class makeTIMER
{
#define MILLIS             0
#define MICROS             1

#define ENABLED            true
#define DISABLED           false

#define YES                true
#define NO                 false

#define STILLtiming        0
#define EXPIRED            1
#define TIMERdisabled      2

  private:
  public:

    static bool              s_normalFlag;    //when ENABLED, adjustable TIMERs run at normal speed

    unsigned long            Time;            //when the TIMER started

    //these "members" are needed to define a TIMER
    byte                     TimerType;       //what kind of TIMER is this? MILLIS/MICROS
    unsigned long            Interval;        //delay time which we are looking for
    bool                     TimerFlag;       //is the TIMER enabled ? ENABLED/DISABLED
    bool                     Restart;         //do we restart this TIMER   ? YES/NO
    byte                     SpeedAdjustPin;  //a potentiometer on this pin, A0-A5, adjusts TIMER speed


    //================================================
    //constructor with no parameters
    makeTIMER()
    {
      TimerType = MILLIS;
      Interval = 1000ul;
      TimerFlag = ENABLED;
      Restart = YES;
      SpeedAdjustPin = 0;

      Time = 0;
    }

    //================================================
    //constructor with parameters
    makeTIMER(byte _TimerType, unsigned long _Interval,
              bool _TimerFlag, bool _Restart, byte _SpeedAdjustPin = 0)
    {
      TimerType = _TimerType;
      Interval = _Interval;
      TimerFlag = _TimerFlag;
      Restart = _Restart;
      SpeedAdjustPin = _SpeedAdjustPin;

      Time = 0;
    }

    //================================================
    //condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
    //function to check the state of our TIMER        ex: if(myTimer.checkTIMER() == EXPIRED);
    byte checkTIMER()
    {
      //========================
      //is this TIMER enabled ?
      if (TimerFlag == ENABLED)
      {
        //============
        //is this an adjustable TIMER OR is the "normalSpeed" switch closed ?
        if (SpeedAdjustPin == 0 || s_normalFlag == ENABLED)
        {
          //============
          //this TIMER "is not" speed adjustable,
          //has this TIMER expired ?
          if (getTime() - Time >= Interval)
          {
            //============
            //should this TIMER restart again?
            if (Restart == YES)
            {
              //restart this TIMER
              Time = getTime();
            }

            //this TIMER has expired
            return EXPIRED;
          }
        }

        //============
        //this TIMER is speed adjustable
        else
        {
          //============
          //for diagnostics, we use a potentiometer to adjust TIMER speed,
          //has this TIMER expired ?
          if (getTime() - Time >= Interval / adjustInterval())
          {
            //============
            //should this TIMER restart again?
            if (Restart == YES)
            {
              //restart this TIMER
              Time = getTime();
            }

            //this TIMER has expired
            return EXPIRED;
          }
        }

        return STILLtiming;

      } //END of   if (TimerFlag == ENABLED)

      //========================
      else
      {
        //this TIMER is disabled
        return TIMERdisabled;
      }

    } //END of   checkTime()

    //================================================
    //function to enable and restart this TIMER       ex: myTimer.enableRestartTIMER();
    void enableRestartTIMER()
    {
      TimerFlag = ENABLED;

      //restart this TIMER
      Time = getTime();

    } //END of   enableRestartTIMER()

    //================================================
    //function to disable this TIMER                  ex: myTimer.disableTIMER();
    void disableTIMER()
    {
      TimerFlag = DISABLED;

    } //END of    disableTIMER()

    //================================================
    //function to restart this TIMER                  ex: myTimer.restartTIMER();
    void restartTIMER()
    {
      Time = getTime();

    } //END of    restartTIMER()

    //================================================
    //function to force this TIMER to expire          ex: myTimer.expireTimer();
    void expireTimer()
    {
      //force this TIMER to expire
      Time = getTime() - Interval;

    } //END of   expireTimer()

    //================================================
    //function to set the Interval for this TIMER     ex: myTimer.setInterval(100);
    void setInterval(unsigned long value)
    {
      //set the Interval
      Interval = value;

    } //END of   setInterval()

    //================================================
    //function to return the current time
    unsigned long getTime()
    {
      //return the time             i.e. millis() or micros()
      //========================
      if (TimerType == MILLIS)
      {
        return millis();
      }

      //========================
      else
      {
        return micros();
      }

    } //END of   getTime()


    //================================================
    //for diagnostics, a potentiometer on an analog pin is used to adjust TIMER speed, thanks alto777
    unsigned int adjustInterval()
    {
      unsigned int Speed = analogRead(SpeedAdjustPin);

      //using integer math to save on memory
      Speed = 1 + (Speed * 14) / 1023;  //Speed will have a range from 1 to 15

      return Speed;

    } //END of   adjustInterval()

}; //END of   class makeTIMER

//================================================
//initialize the static "s_normalFlag" variable,
//when ENABLED, adjustable TIMERs run at normal speed
bool makeTIMER::s_normalFlag = DISABLED;


//                                T I M E R   D e f i n i t i o n s
//================================================^================================================
//
//========================
//example: uses default library values
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
//    MILLIS,    1000ul,    ENABLED,      YES,       0
//makeTIMER testTIMER{};

//========================
makeTIMER heartbeatTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 500ul, ENABLED, YES, 0
};

//========================  (5ms * s_filter) i.e. 5ms * 10 = 50ms for checking a valid switch operation
makeTIMER switchesTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 5ul, ENABLED, YES, 0
};

//========================
makeTIMER machineTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MICROS, 1000ul, ENABLED, YES, 0
};

//========================
makeTIMER commonTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 500ul, DISABLED, NO, 0
};

//========================
makeTIMER testLedTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 2000ul, DISABLED, NO, 0
};


//                                  c l a s s    m a k e I n p u t
//================================================^================================================
//
//a class to define "Input" objects, switches or sensors

//================================================
class makeInput
{
#define NOTvalidated       0
#define VALIDATED          1
#define NOchange           2

  private:

  public:

    static byte s_filter;
    //say the above validating "s_filter" variable is set to 10
    //if we scan "inputs" every 5ms
    //i.e. we sample our inputs every 5ms looking for a change in state.
    //5ms * 10 = 50ms is needed to validate a switch change in state.
    //i.e. a switch change in state is valid "only after" 10 identical changes are detected.
    //This technique is used to filter out EMI (spikes), noise, etc.
    //i.e. we ignore switch changes in state that are less than 50ms.

    unsigned long switchTime;       //the time the switch was closed
    byte counter;                   //a counter used for validating a switch change in state

    //these "members" are needed to define an "Input"
    byte pin;                       //the digital input pin number
    byte lastState;                 //the state the input was last in


    //================================================
    //constructor with parameters
    makeInput(byte _pin, byte _lastState)
    {
      pin = _pin;
      lastState = _lastState;

      switchTime = 0;
      counter = 0;

      pinMode(pin, INPUT_PULLUP);
    }

    //================================================
    //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
    //check to see if the input object has had a valid state change
    byte validChange()
    {
      byte currentState = digitalRead(pin);

      //===================================
      //has there been an input change in state ?
      if (lastState != currentState)
      {
        //we have had another similar change in state
        counter++;

        //is the "change in state" stable ?
        if (counter >= s_filter)
        {
          //an input change has been validated
          //get ready for the next scanning sequence
          counter = 0;

          //update to this new state
          lastState = currentState;

          if (currentState == CLOSED)
          {
            //capture the time when the switch closed
            switchTime = millis();
          }

          return VALIDATED;
        }

        return NOTvalidated;
      }

      //===================================
      //there has not been an input change in state
      counter = 0;

      return NOchange;

    } //END of   validChange()

}; //END of   class makeInput

//================================================
//a change in state is confirmed/validated when 10 identical state changes in a row are seen
byte makeInput::s_filter = 10;


//                                    S t a t e   M a c h i n e
//================================================^================================================
//
//the states in our State Machine
enum STATES : byte
{
  STARTUP, STATE1, STATE2, STATE3, STATE4, FINISHED
};

STATES mState = STARTUP;


//                              G P I O s   A n d   V a r i a b l e s
//================================================^================================================
//

//Analogs
//================================================
//

//INPUTS
//================================================
//

//========================          GPIO 2
makeInput mySwitch =
{
  //.pin, .lastState
  2, OPENED
};


//OUTPUTS
//================================================
//
const byte testLED                = 12;
const byte heartbeatLED           = 13;

//VARIABLES
//================================================
//
const char* screens[4][2] =
{
  //          111111               111111
  //0123456789012345     0123456789012345
  {"    Screen 1:   ",  " Hello,  World! "},
  {"    Screen 2:   ",  " Arduino Rocks! "},
  {"    Screen 3:   ",  " Using millis() "},
  {"    Screen 4:   ",  "  I2C LCD Demo  "}
};

byte screenIndex                  = 0;        //0 to 3 is valid


//Timing Stuff
//========================
const unsigned long shortPushTime = 500ul;
const unsigned long longPushTime  = 2000ul;


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(115200);

  digitalWrite(heartbeatLED, LEDoff);
  pinMode(heartbeatLED, OUTPUT);

  digitalWrite(testLED, LEDoff);
  pinMode(testLED, OUTPUT);

  //================================================
  //LCD stuff
  lcd.begin(16, 2);
  lcd.clear();

  lcd.setCursor(0, 0);
  //                   111111
  //         0123456789012345
  //             Skeleton
  lcd.print("    Skeleton    ");

  lcd.setCursor(0, 1);
  //                   111111
  //         0123456789012345
  //              Sketch
  lcd.print("     Sketch     ");

  //time to read LCD
  delay(2000ul);

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
//
void loop()
{
  //========================================================================
  //Print the time it takes to return to this same spot.
  //Comment the next 3 lines when no longer needed
  //static unsigned long startTime;
  //Serial.println(micros() - startTime);
  //startTime = micros();

  //========================================================================  T I M E R  heartbeatLED
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED)
  {
    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED)
  {
    checkSwitches();
  }

  //========================================================================  T I M E R  machine
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to service our State Machine ?
  if (machineTIMER.checkTIMER() == EXPIRED)
  {
    checkMachine();
  }

  //========================================================================  T I M E R  testLed
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if enabled, is it time to turn OFF the testLED ?
  if (testLedTIMER.checkTIMER() == EXPIRED)
  {
    digitalWrite(testLED, LEDoff);

    //we are finished with this TIMER
    testLedTIMER.disableTIMER();
  }


  //================================================
  //       Other non blocking code goes here
  //================================================


} //END of   loop()


//                                    c h e c k M a c h i n e ( )
//================================================^================================================
//
void checkMachine()
{
  //================================================
  //service the current "state"
  switch (mState)
  {
    //========================
    case STARTUP:
      {

      }
      break;

    //========================
    case STATE1:
      {

      }
      break;

    //========================
    case STATE2:
      {

      }
      break;

    //========================
    case STATE3:
      {

      }
      break;

    //========================
    case STATE4:
      {

      }
      break;

    //========================
    case FINISHED:
      {

      }
      break;

  } //END of  switch/case

} //END of   checkMachine()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
//
//we have access to:
//object.validChange()    - checks to see if there was a valid state change
//object.pin              - input hardware pin number
//object.lastState        - the state the input was/is in
//object.switchTime       - the millis() value when the switch closes

void checkSwitches()
{
  //========================================================================  mySwitch
  //was there a valid input change ?
  //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
  if (mySwitch.validChange() == VALIDATED)
  {
    //======================== 
    //was this switch closed ?
    if (mySwitch.lastState == CLOSED)
    {
      digitalWrite(testLED, LEDon);

      //enable and restart the testLedTIMER
      testLedTIMER.enableRestartTIMER();
    }

    //========================
    //this switch was opened
    else
    {
      //Do nothing
    }
    
  } //END of mySwitch

  //========================================================================  Next Switch

} //END of   checkSwitches()


//                                   d i s p l a y S c r e e n ( )
//================================================^================================================
//
void displayScreen(int index)
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(screens[index][0]);
  lcd.setCursor(0, 1);
  lcd.print(screens[index][1]);

} //END of   displayScreen()


//================================================^================================================

Larry, I believe I do understand the changes you made.

  1. You cleared out any code under State Machine - blank - does nothing from STARTUP to FINISHED
  2. You created a new timer / object called testLedTIMER
  3. There is new code now associated with mySwitch and it is the new timer / object named testLedTIMER. Anytime the pushbutton on D2 is pressed (closed), the testLED comes on for 2 seconds and then goes off and the timer (testLedTIMER) is disabled.
    Did I get that right?
  • Great, making millis( ) based TIMERs are very easy as you can see, especially when you have a C++ "class" like makeTIMER.



Test Time ( Again ! )

:scream:

  • Well let's see if you can follow my lead.

With reference to the flowchart page from Post #67

  • Below is a new sketch.

  • Add code to STATE2, STATE3 and STATE4 so we get the results as in the flowchart.

  • The code for STARTUP and STATE1 has been added for you.

  • Note: after the 4th LCD screen delay times out, repeat the screen sequence.

//
//================================================^================================================
//                              B a s i c   S k e l e t o n   S k e t c h
//
//  https://forum.arduino.cc/t/arduino-millis-and-indexing-code/1376338/17
//
//  LarryD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       25/04/24    Running code
//
//
//
//
//  Notes:
//
//
//
//

//================================================
#include <Wire.h>

//Use I2C library:     https://github.com/duinoWitchery/hd44780
//LCD Reference:       https://www.arduino.cc/en/Reference/LiquidCrystal

#include <hd44780.h>   //main hd44780 header

//NOTE:
//hd44780_I2Cexp control LCD using I2C I/O expander backpack (PCF8574 or MCP23008)
//hd44780_I2Clcd control LCD with native I2C interface (PCF2116, PCF2119x, etc...)

#include <hd44780ioClass/hd44780_I2Cexp.h> //I2C expander i/o class header

//If you do not know what your I2C address is, first run the "I2C_Scanner" sketch
//OR
//run the "I2CexpDiag" sketch that comes with the hd44780 library
//hd44780_I2Cexp lcd(0x3F);

hd44780_I2Cexp lcd(0x27);


//================================================
#define LEDon              HIGH   //PIN---[220R]---A[LED]K---GND
#define LEDoff             LOW

#define PRESSED            LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED           HIGH

#define CLOSED             LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED             HIGH

#define ENABLED            true
#define DISABLED           false

#define RELAYon            LOW
#define RELAYoff           HIGH


//================================================
#define ONE_SECOND   1000ul              //milliseconds
#define ONE_MINUTE   (ONE_SECOND * 60ul) //60 seconds in one minute 
#define ONE_HOUR     (ONE_MINUTE * 60ul) //60 minutes in one hour
#define ONE_DAY      (ONE_HOUR * 24ul)   //24 hours in one day



//                                   c l a s s   m a k e T I M E R
//================================================^================================================
//
/*
  //========================
  makeTIMER toggleLED =
  {
     //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
     MILLIS/MICROS, 500ul, ENABLED/DISABLED, YES/NO, A0-A5

     //.SpeedAdjustPin defaults to 0 i.e. no speed adjustment is used
     //if .SpeedAdjustPin = A0-A5, a potentiometer on this pin adjusts the TIMER's speed (for diagnostics)
     //class static flag "makeTIMER::normalFlag" can be used to ENABLE/DISABLE adjustable TIMER speed,
     //ENABLE = normal speed, DISABLED = potentiometer controls TIMER speed
  };

  TIMER functions we can access:
  toggleLED.checkTIMER();
  toggleLED.enableRestartTIMER();
  toggleLED.restartTIMER()
  toggleLED.disableTIMER();
  toggleLED.expireTimer();
  toggleLED.setInterval(100ul);

  Static variable access
  makeTIMER::normalFlag = ENABLED/DISABLED  //defaults to DISABLED at power up time i.e. variable speed is allowed
*/

//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//
//These TIMER objects are non-blocking
class makeTIMER
{
#define MILLIS             0
#define MICROS             1

#define ENABLED            true
#define DISABLED           false

#define YES                true
#define NO                 false

#define STILLtiming        0
#define EXPIRED            1
#define TIMERdisabled      2

  private:
  public:

    static bool              s_normalFlag;    //when ENABLED, adjustable TIMERs run at normal speed

    unsigned long            Time;            //when the TIMER started

    //these "members" are needed to define a TIMER
    byte                     TimerType;       //what kind of TIMER is this? MILLIS/MICROS
    unsigned long            Interval;        //delay time which we are looking for
    bool                     TimerFlag;       //is the TIMER enabled ? ENABLED/DISABLED
    bool                     Restart;         //do we restart this TIMER   ? YES/NO
    byte                     SpeedAdjustPin;  //a potentiometer on this pin, A0-A5, adjusts TIMER speed


    //================================================
    //constructor with no parameters
    makeTIMER()
    {
      TimerType = MILLIS;
      Interval = 1000ul;
      TimerFlag = ENABLED;
      Restart = YES;
      SpeedAdjustPin = 0;

      Time = 0;
    }

    //================================================
    //constructor with parameters
    makeTIMER(byte _TimerType, unsigned long _Interval,
              bool _TimerFlag, bool _Restart, byte _SpeedAdjustPin = 0)
    {
      TimerType = _TimerType;
      Interval = _Interval;
      TimerFlag = _TimerFlag;
      Restart = _Restart;
      SpeedAdjustPin = _SpeedAdjustPin;

      Time = 0;
    }

    //================================================
    //condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
    //function to check the state of our TIMER        ex: if(myTimer.checkTIMER() == EXPIRED);
    byte checkTIMER()
    {
      //========================
      //is this TIMER enabled ?
      if (TimerFlag == ENABLED)
      {
        //============
        //is this an adjustable TIMER OR is the "normalSpeed" switch closed ?
        if (SpeedAdjustPin == 0 || s_normalFlag == ENABLED)
        {
          //============
          //this TIMER "is not" speed adjustable,
          //has this TIMER expired ?
          if (getTime() - Time >= Interval)
          {
            //============
            //should this TIMER restart again?
            if (Restart == YES)
            {
              //restart this TIMER
              Time = getTime();
            }

            //this TIMER has expired
            return EXPIRED;
          }
        }

        //============
        //this TIMER is speed adjustable
        else
        {
          //============
          //for diagnostics, we use a potentiometer to adjust TIMER speed,
          //has this TIMER expired ?
          if (getTime() - Time >= Interval / adjustInterval())
          {
            //============
            //should this TIMER restart again?
            if (Restart == YES)
            {
              //restart this TIMER
              Time = getTime();
            }

            //this TIMER has expired
            return EXPIRED;
          }
        }

        return STILLtiming;

      } //END of   if (TimerFlag == ENABLED)

      //========================
      else
      {
        //this TIMER is disabled
        return TIMERdisabled;
      }

    } //END of   checkTime()

    //================================================
    //function to enable and restart this TIMER       ex: myTimer.enableRestartTIMER();
    void enableRestartTIMER()
    {
      TimerFlag = ENABLED;

      //restart this TIMER
      Time = getTime();

    } //END of   enableRestartTIMER()

    //================================================
    //function to disable this TIMER                  ex: myTimer.disableTIMER();
    void disableTIMER()
    {
      TimerFlag = DISABLED;

    } //END of    disableTIMER()

    //================================================
    //function to restart this TIMER                  ex: myTimer.restartTIMER();
    void restartTIMER()
    {
      Time = getTime();

    } //END of    restartTIMER()

    //================================================
    //function to force this TIMER to expire          ex: myTimer.expireTimer();
    void expireTimer()
    {
      //force this TIMER to expire
      Time = getTime() - Interval;

    } //END of   expireTimer()

    //================================================
    //function to set the Interval for this TIMER     ex: myTimer.setInterval(100);
    void setInterval(unsigned long value)
    {
      //set the Interval
      Interval = value;

    } //END of   setInterval()

    //================================================
    //function to return the current time
    unsigned long getTime()
    {
      //return the time             i.e. millis() or micros()
      //========================
      if (TimerType == MILLIS)
      {
        return millis();
      }

      //========================
      else
      {
        return micros();
      }

    } //END of   getTime()


    //================================================
    //for diagnostics, a potentiometer on an analog pin is used to adjust TIMER speed, thanks alto777
    unsigned int adjustInterval()
    {
      unsigned int Speed = analogRead(SpeedAdjustPin);

      //using integer math to save on memory
      Speed = 1 + (Speed * 14) / 1023;  //Speed will have a range from 1 to 15

      return Speed;

    } //END of   adjustInterval()

}; //END of   class makeTIMER

//================================================
//initialize the static "s_normalFlag" variable,
//when ENABLED, adjustable TIMERs run at normal speed
bool makeTIMER::s_normalFlag = DISABLED;


//                                T I M E R   D e f i n i t i o n s
//================================================^================================================
//
//========================
//example: uses default library values
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
//    MILLIS,    1000ul,    ENABLED,      YES,       0
//makeTIMER testTIMER{};

//========================
makeTIMER heartbeatTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 500ul, ENABLED, YES, 0
};

//========================  (5ms * s_filter) i.e. 5ms * 10 = 50ms for checking a valid switch operation
makeTIMER switchesTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 5ul, ENABLED, YES, 0
};

//========================
makeTIMER machineTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MICROS, 1000ul, ENABLED, YES, 0
};

//========================
makeTIMER commonTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 500ul, DISABLED, NO, 0
};

//========================
makeTIMER testLedTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 2000ul, DISABLED, NO, 0
};


//                                  c l a s s    m a k e I n p u t
//================================================^================================================
//
//a class to define "Input" objects, switches or sensors

//================================================
class makeInput
{
#define NOTvalidated       0
#define VALIDATED          1
#define NOchange           2

  private:

  public:

    static byte s_filter;
    //say the above validating "s_filter" variable is set to 10
    //if we scan "inputs" every 5ms
    //i.e. we sample our inputs every 5ms looking for a change in state.
    //5ms * 10 = 50ms is needed to validate a switch change in state.
    //i.e. a switch change in state is valid "only after" 10 identical changes are detected.
    //This technique is used to filter out EMI (spikes), noise, etc.
    //i.e. we ignore switch changes in state that are less than 50ms.

    unsigned long switchTime;       //the time the switch was closed
    byte counter;                   //a counter used for validating a switch change in state

    //these "members" are needed to define an "Input"
    byte pin;                       //the digital input pin number
    byte lastState;                 //the state the input was last in


    //================================================
    //constructor with parameters
    makeInput(byte _pin, byte _lastState)
    {
      pin = _pin;
      lastState = _lastState;

      switchTime = 0;
      counter = 0;

      pinMode(pin, INPUT_PULLUP);
    }

    //================================================
    //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
    //check to see if the input object has had a valid state change
    byte validChange()
    {
      byte currentState = digitalRead(pin);

      //===================================
      //has there been an input change in state ?
      if (lastState != currentState)
      {
        //we have had another similar change in state
        counter++;

        //is the "change in state" stable ?
        if (counter >= s_filter)
        {
          //an input change has been validated
          //get ready for the next scanning sequence
          counter = 0;

          //update to this new state
          lastState = currentState;

          if (currentState == CLOSED)
          {
            //capture the time when the switch closed
            switchTime = millis();
          }

          return VALIDATED;
        }

        return NOTvalidated;
      }

      //===================================
      //there has not been an input change in state
      counter = 0;

      return NOchange;

    } //END of   validChange()

}; //END of   class makeInput

//================================================
//a change in state is confirmed/validated when 10 identical state changes in a row are seen
byte makeInput::s_filter = 10;


//                                    S t a t e   M a c h i n e
//================================================^================================================
//
//the states in our State Machine
enum STATES : byte
{
  STARTUP, STATE1, STATE2, STATE3, STATE4, FINISHED
};

STATES mState = STARTUP;


//                              G P I O s   A n d   V a r i a b l e s
//================================================^================================================
//

//Analogs
//================================================
//

//INPUTS
//================================================
//

//========================          GPIO 2
makeInput mySwitch =
{
  //.pin, .lastState
  2, OPENED
};


//OUTPUTS
//================================================
//
const byte testLED                = 12;
const byte heartbeatLED           = 13;

//VARIABLES
//================================================
//
const char* screens[4][2] =
{
  //          111111               111111
  //0123456789012345     0123456789012345      //screenIndex value
  {"    Screen 1:   ",  " Hello,  World! "},   //0
  {"    Screen 2:   ",  " Arduino Rocks! "},   //1
  {"    Screen 3:   ",  " Using millis() "},   //2
  {"    Screen 4:   ",  "  I2C LCD Demo  "}    //3
};

//the index of the screen now being displayed on the LCD
byte screenIndex                  = 0;        


//Timing Stuff
//========================
const unsigned long shortPushTime = 500ul;
const unsigned long longPushTime  = 2000ul;


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(115200);

  digitalWrite(heartbeatLED, LEDoff);
  pinMode(heartbeatLED, OUTPUT);

  digitalWrite(testLED, LEDoff);
  pinMode(testLED, OUTPUT);

  //================================================
  //LCD stuff
  lcd.begin(16, 2);
  lcd.clear();

  lcd.setCursor(0, 0);
  //                   111111
  //         0123456789012345
  //             Skeleton
  lcd.print("    Skeleton    ");

  lcd.setCursor(0, 1);
  //                   111111
  //         0123456789012345
  //              Sketch
  lcd.print("     Sketch     ");

  //time to read LCD
  delay(2000ul);

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
//
void loop()
{
  //========================================================================
  //Print the time it takes to return to this same spot.
  //Comment the next 3 lines when no longer needed
  //static unsigned long startTime;
  //Serial.println(micros() - startTime);
  //startTime = micros();

  //========================================================================  T I M E R  heartbeatLED
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED)
  {
    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED)
  {
    checkSwitches();
  }

  //========================================================================  T I M E R  machine
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to service our State Machine ?
  if (machineTIMER.checkTIMER() == EXPIRED)
  {
    checkMachine();
  }

  //========================================================================  T I M E R  testLed
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if enabled, is it time to turn OFF the testLED ?
  if (testLedTIMER.checkTIMER() == EXPIRED)
  {
    digitalWrite(testLED, LEDoff);

    //we are finished with this TIMER
    testLedTIMER.disableTIMER();
  }


  //================================================
  //       Other non blocking code goes here
  //================================================


} //END of   loop()


//                                    c h e c k M a c h i n e ( )
//================================================^================================================
//
void checkMachine()
{
  //================================================
  //service the current "state"
  switch (mState)
  {
    //========================
    case STARTUP:
      {
        //first screen
        screenIndex = 0;
        displayScreen(screenIndex);

        //set the display interval time
        commonTIMER.setInterval(2000ul);

        //start TIMER
        commonTIMER.enableRestartTIMER();
        
        //next state
        mState = STATE1;
      }
      break;

    //========================
    case STATE1:
      {
        //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to go to the next screen ?
        if (commonTIMER.checkTIMER() == EXPIRED)
        {
          //second screen
          screenIndex++;
          displayScreen(screenIndex);

          //set the display interval time
          commonTIMER.setInterval(2000ul);

          //start TIMER
          commonTIMER.enableRestartTIMER();

          //next state
          mState = STATE2;
        }
      }
      break;

    //========================
    case STATE2:
      {

      }
      break;

    //========================
    case STATE3:
      {

      }
      break;

    //========================
    case STATE4:
      {

      }
      break;

    //========================
    case FINISHED:
      {
        //Do something
      }
      break;

  } //END of  switch/case

} //END of   checkMachine()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
//
//we have access to:
//object.validChange()    - checks to see if there was a valid state change
//object.pin              - input hardware pin number
//object.lastState        - the state the input was/is in
//object.switchTime       - the millis() value when the switch closes

void checkSwitches()
{
  //========================================================================  mySwitch
  //was there a valid input change ?
  //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
  if (mySwitch.validChange() == VALIDATED)
  {
    //========================
    //was this switch closed ?
    if (mySwitch.lastState == CLOSED)
    {
      digitalWrite(testLED, LEDon);

      //enable and restart the testLedTIMER
      testLedTIMER.enableRestartTIMER();
    }

    //========================
    //this switch was opened
    else
    {
      //Do nothing
    }

  } //END of mySwitch

  //========================================================================  Next Switch

} //END of   checkSwitches()


//                                   d i s p l a y S c r e e n ( )
//================================================^================================================
//
void displayScreen(int index)
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(screens[index][0]);
  lcd.setCursor(0, 1);
  lcd.print(screens[index][1]);

} //END of   displayScreen()


//================================================^================================================

Here is the code I added and uploaded. It did seem to work. It displayed Screens 1-4 for 2 seconds each and repeated. All without the dreaded delay()! Awesome!

//
//================================================^================================================
//                              B a s i c   S k e l e t o n   S k e t c h
//
//  https://forum.arduino.cc/t/arduino-millis-and-indexing-code/1376338/17
//
//  LarryD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       25/04/24    Running code
//
//
//
//
//  Notes:
//
//
//
//

//================================================
#include <Wire.h>

//Use I2C library:     https://github.com/duinoWitchery/hd44780
//LCD Reference:       https://www.arduino.cc/en/Reference/LiquidCrystal

#include <hd44780.h>  //main hd44780 header

//NOTE:
//hd44780_I2Cexp control LCD using I2C I/O expander backpack (PCF8574 or MCP23008)
//hd44780_I2Clcd control LCD with native I2C interface (PCF2116, PCF2119x, etc...)

#include <hd44780ioClass/hd44780_I2Cexp.h>  //I2C expander i/o class header

//If you do not know what your I2C address is, first run the "I2C_Scanner" sketch
//OR
//run the "I2CexpDiag" sketch that comes with the hd44780 library
//hd44780_I2Cexp lcd(0x3F);

hd44780_I2Cexp lcd(0x27);


//================================================
#define LEDon HIGH  //PIN---[220R]---A[LED]K---GND
#define LEDoff LOW

#define PRESSED LOW  //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED HIGH

#define CLOSED LOW  //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED HIGH

#define ENABLED true
#define DISABLED false

#define RELAYon LOW
#define RELAYoff HIGH


//================================================
#define ONE_SECOND 1000ul               //milliseconds
#define ONE_MINUTE (ONE_SECOND * 60ul)  //60 seconds in one minute
#define ONE_HOUR (ONE_MINUTE * 60ul)    //60 minutes in one hour
#define ONE_DAY (ONE_HOUR * 24ul)       //24 hours in one day



//                                   c l a s s   m a k e T I M E R
//================================================^================================================
//
/*
  //========================
  makeTIMER toggleLED =
  {
     //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
     MILLIS/MICROS, 500ul, ENABLED/DISABLED, YES/NO, A0-A5

     //.SpeedAdjustPin defaults to 0 i.e. no speed adjustment is used
     //if .SpeedAdjustPin = A0-A5, a potentiometer on this pin adjusts the TIMER's speed (for diagnostics)
     //class static flag "makeTIMER::normalFlag" can be used to ENABLE/DISABLE adjustable TIMER speed,
     //ENABLE = normal speed, DISABLED = potentiometer controls TIMER speed
  };

  TIMER functions we can access:
  toggleLED.checkTIMER();
  toggleLED.enableRestartTIMER();
  toggleLED.restartTIMER()
  toggleLED.disableTIMER();
  toggleLED.expireTimer();
  toggleLED.setInterval(100ul);

  Static variable access
  makeTIMER::normalFlag = ENABLED/DISABLED  //defaults to DISABLED at power up time i.e. variable speed is allowed
*/

//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//
//These TIMER objects are non-blocking
class makeTIMER {
#define MILLIS 0
#define MICROS 1

#define ENABLED true
#define DISABLED false

#define YES true
#define NO false

#define STILLtiming 0
#define EXPIRED 1
#define TIMERdisabled 2

private:
public:

  static bool s_normalFlag;  //when ENABLED, adjustable TIMERs run at normal speed

  unsigned long Time;  //when the TIMER started

  //these "members" are needed to define a TIMER
  byte TimerType;          //what kind of TIMER is this? MILLIS/MICROS
  unsigned long Interval;  //delay time which we are looking for
  bool TimerFlag;          //is the TIMER enabled ? ENABLED/DISABLED
  bool Restart;            //do we restart this TIMER   ? YES/NO
  byte SpeedAdjustPin;     //a potentiometer on this pin, A0-A5, adjusts TIMER speed


  //================================================
  //constructor with no parameters
  makeTIMER() {
    TimerType = MILLIS;
    Interval = 1000ul;
    TimerFlag = ENABLED;
    Restart = YES;
    SpeedAdjustPin = 0;

    Time = 0;
  }

  //================================================
  //constructor with parameters
  makeTIMER(byte _TimerType, unsigned long _Interval,
            bool _TimerFlag, bool _Restart, byte _SpeedAdjustPin = 0) {
    TimerType = _TimerType;
    Interval = _Interval;
    TimerFlag = _TimerFlag;
    Restart = _Restart;
    SpeedAdjustPin = _SpeedAdjustPin;

    Time = 0;
  }

  //================================================
  //condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
  //function to check the state of our TIMER        ex: if(myTimer.checkTIMER() == EXPIRED);
  byte checkTIMER() {
    //========================
    //is this TIMER enabled ?
    if (TimerFlag == ENABLED) {
      //============
      //is this an adjustable TIMER OR is the "normalSpeed" switch closed ?
      if (SpeedAdjustPin == 0 || s_normalFlag == ENABLED) {
        //============
        //this TIMER "is not" speed adjustable,
        //has this TIMER expired ?
        if (getTime() - Time >= Interval) {
          //============
          //should this TIMER restart again?
          if (Restart == YES) {
            //restart this TIMER
            Time = getTime();
          }

          //this TIMER has expired
          return EXPIRED;
        }
      }

      //============
      //this TIMER is speed adjustable
      else {
        //============
        //for diagnostics, we use a potentiometer to adjust TIMER speed,
        //has this TIMER expired ?
        if (getTime() - Time >= Interval / adjustInterval()) {
          //============
          //should this TIMER restart again?
          if (Restart == YES) {
            //restart this TIMER
            Time = getTime();
          }

          //this TIMER has expired
          return EXPIRED;
        }
      }

      return STILLtiming;

    }  //END of   if (TimerFlag == ENABLED)

    //========================
    else {
      //this TIMER is disabled
      return TIMERdisabled;
    }

  }  //END of   checkTime()

  //================================================
  //function to enable and restart this TIMER       ex: myTimer.enableRestartTIMER();
  void enableRestartTIMER() {
    TimerFlag = ENABLED;

    //restart this TIMER
    Time = getTime();

  }  //END of   enableRestartTIMER()

  //================================================
  //function to disable this TIMER                  ex: myTimer.disableTIMER();
  void disableTIMER() {
    TimerFlag = DISABLED;

  }  //END of    disableTIMER()

  //================================================
  //function to restart this TIMER                  ex: myTimer.restartTIMER();
  void restartTIMER() {
    Time = getTime();

  }  //END of    restartTIMER()

  //================================================
  //function to force this TIMER to expire          ex: myTimer.expireTimer();
  void expireTimer() {
    //force this TIMER to expire
    Time = getTime() - Interval;

  }  //END of   expireTimer()

  //================================================
  //function to set the Interval for this TIMER     ex: myTimer.setInterval(100);
  void setInterval(unsigned long value) {
    //set the Interval
    Interval = value;

  }  //END of   setInterval()

  //================================================
  //function to return the current time
  unsigned long getTime() {
    //return the time             i.e. millis() or micros()
    //========================
    if (TimerType == MILLIS) {
      return millis();
    }

    //========================
    else {
      return micros();
    }

  }  //END of   getTime()


  //================================================
  //for diagnostics, a potentiometer on an analog pin is used to adjust TIMER speed, thanks alto777
  unsigned int adjustInterval() {
    unsigned int Speed = analogRead(SpeedAdjustPin);

    //using integer math to save on memory
    Speed = 1 + (Speed * 14) / 1023;  //Speed will have a range from 1 to 15

    return Speed;

  }  //END of   adjustInterval()

};  //END of   class makeTIMER

//================================================
//initialize the static "s_normalFlag" variable,
//when ENABLED, adjustable TIMERs run at normal speed
bool makeTIMER::s_normalFlag = DISABLED;


//                                T I M E R   D e f i n i t i o n s
//================================================^================================================
//
//========================
//example: uses default library values
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
//    MILLIS,    1000ul,    ENABLED,      YES,       0
//makeTIMER testTIMER{};

//========================
makeTIMER heartbeatTIMER = {
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 500ul, ENABLED, YES, 0
};

//========================  (5ms * s_filter) i.e. 5ms * 10 = 50ms for checking a valid switch operation
makeTIMER switchesTIMER = {
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 5ul, ENABLED, YES, 0
};

//========================
makeTIMER machineTIMER = {
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MICROS, 1000ul, ENABLED, YES, 0
};

//========================
makeTIMER commonTIMER = {
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 500ul, DISABLED, NO, 0
};

//========================
makeTIMER testLedTIMER = {
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 2000ul, DISABLED, NO, 0
};


//                                  c l a s s    m a k e I n p u t
//================================================^================================================
//
//a class to define "Input" objects, switches or sensors

//================================================
class makeInput {
#define NOTvalidated 0
#define VALIDATED 1
#define NOchange 2

private:

public:

  static byte s_filter;
  //say the above validating "s_filter" variable is set to 10
  //if we scan "inputs" every 5ms
  //i.e. we sample our inputs every 5ms looking for a change in state.
  //5ms * 10 = 50ms is needed to validate a switch change in state.
  //i.e. a switch change in state is valid "only after" 10 identical changes are detected.
  //This technique is used to filter out EMI (spikes), noise, etc.
  //i.e. we ignore switch changes in state that are less than 50ms.

  unsigned long switchTime;  //the time the switch was closed
  byte counter;              //a counter used for validating a switch change in state

  //these "members" are needed to define an "Input"
  byte pin;        //the digital input pin number
  byte lastState;  //the state the input was last in


  //================================================
  //constructor with parameters
  makeInput(byte _pin, byte _lastState) {
    pin = _pin;
    lastState = _lastState;

    switchTime = 0;
    counter = 0;

    pinMode(pin, INPUT_PULLUP);
  }

  //================================================
  //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
  //check to see if the input object has had a valid state change
  byte validChange() {
    byte currentState = digitalRead(pin);

    //===================================
    //has there been an input change in state ?
    if (lastState != currentState) {
      //we have had another similar change in state
      counter++;

      //is the "change in state" stable ?
      if (counter >= s_filter) {
        //an input change has been validated
        //get ready for the next scanning sequence
        counter = 0;

        //update to this new state
        lastState = currentState;

        if (currentState == CLOSED) {
          //capture the time when the switch closed
          switchTime = millis();
        }

        return VALIDATED;
      }

      return NOTvalidated;
    }

    //===================================
    //there has not been an input change in state
    counter = 0;

    return NOchange;

  }  //END of   validChange()

};  //END of   class makeInput

//================================================
//a change in state is confirmed/validated when 10 identical state changes in a row are seen
byte makeInput::s_filter = 10;


//                                    S t a t e   M a c h i n e
//================================================^================================================
//
//the states in our State Machine
enum STATES : byte {
  STARTUP,
  STATE1,
  STATE2,
  STATE3,
  STATE4,
  FINISHED
};

STATES mState = STARTUP;


//                              G P I O s   A n d   V a r i a b l e s
//================================================^================================================
//

//Analogs
//================================================
//

//INPUTS
//================================================
//

//========================          GPIO 2
makeInput mySwitch = {
  //.pin, .lastState
  2, OPENED
};


//OUTPUTS
//================================================
//
const byte testLED = 12;
const byte heartbeatLED = 13;

//VARIABLES
//================================================
//
const char* screens[4][2] = {
  //          111111               111111
  //0123456789012345     0123456789012345      //screenIndex value
  { "    Screen 1:   ", " Hello,  World! " },  //0
  { "    Screen 2:   ", " Arduino Rocks! " },  //1
  { "    Screen 3:   ", " Using millis() " },  //2
  { "    Screen 4:   ", "  I2C LCD Demo  " }   //3
};

//the index of the screen now being displayed on the LCD
byte screenIndex = 0;


//Timing Stuff
//========================
const unsigned long shortPushTime = 500ul;
const unsigned long longPushTime = 2000ul;


//                                           s e t u p ( )
//================================================^================================================
//
void setup() {
  Serial.begin(115200);

  digitalWrite(heartbeatLED, LEDoff);
  pinMode(heartbeatLED, OUTPUT);

  digitalWrite(testLED, LEDoff);
  pinMode(testLED, OUTPUT);

  //================================================
  //LCD stuff
  lcd.begin(16, 2);
  lcd.clear();

  lcd.setCursor(0, 0);
  //                   111111
  //         0123456789012345
  //             Skeleton
  lcd.print("    Skeleton    ");

  lcd.setCursor(0, 1);
  //                   111111
  //         0123456789012345
  //              Sketch
  lcd.print("     Sketch     ");

  //time to read LCD
  delay(2000ul);

}  //END of   setup()


//                                            l o o p ( )
//================================================^================================================
//
void loop() {
  //========================================================================
  //Print the time it takes to return to this same spot.
  //Comment the next 3 lines when no longer needed
  //static unsigned long startTime;
  //Serial.println(micros() - startTime);
  //startTime = micros();

  //========================================================================  T I M E R  heartbeatLED
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED) {
    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED) {
    checkSwitches();
  }

  //========================================================================  T I M E R  machine
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to service our State Machine ?
  if (machineTIMER.checkTIMER() == EXPIRED) {
    checkMachine();
  }

  //========================================================================  T I M E R  testLed
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if enabled, is it time to turn OFF the testLED ?
  if (testLedTIMER.checkTIMER() == EXPIRED) {
    digitalWrite(testLED, LEDoff);

    //we are finished with this TIMER
    testLedTIMER.disableTIMER();
  }


  //================================================
  //       Other non blocking code goes here
  //================================================


}  //END of   loop()


//                                    c h e c k M a c h i n e ( )
//================================================^================================================
//
void checkMachine() {
  //================================================
  //service the current "state"
  switch (mState) {
    //========================
    case STARTUP:
      {
        //first screen
        screenIndex = 0;
        displayScreen(screenIndex);

        //set the display interval time
        commonTIMER.setInterval(2000ul);

        //start TIMER
        commonTIMER.enableRestartTIMER();

        //next state
        mState = STATE1;
      }
      break;

    //========================
    case STATE1:
      {
        //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to go to the next screen ?
        if (commonTIMER.checkTIMER() == EXPIRED) {
          //second screen
          screenIndex++;
          displayScreen(screenIndex);

          //set the display interval time
          commonTIMER.setInterval(2000ul);

          //start TIMER
          commonTIMER.enableRestartTIMER();

          //next state
          mState = STATE2;
        }
      }
      break;

    //========================
    case STATE2:
      {

        //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to go to the next screen ?
        if (commonTIMER.checkTIMER() == EXPIRED) {
          //second screen
          screenIndex++;
          displayScreen(screenIndex);

          //set the display interval time
          commonTIMER.setInterval(2000ul);

          //start TIMER
          commonTIMER.enableRestartTIMER();

          //next state
          mState = STATE3;
        }
      }
      break;

    //========================
    case STATE3:
      {

 //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to go to the next screen ?
        if (commonTIMER.checkTIMER() == EXPIRED) {
          //second screen
          screenIndex++;
          displayScreen(screenIndex);

          //set the display interval time
          commonTIMER.setInterval(2000ul);

          //start TIMER
          commonTIMER.enableRestartTIMER();

          //next state
          mState = STATE4;
        }

      }
      break;

    //========================
    case STATE4:
      {

//condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to go to the next screen ?
        if (commonTIMER.checkTIMER() == EXPIRED) {
          //second screen
          screenIndex++;
          displayScreen(screenIndex);

          //set the display interval time
          commonTIMER.setInterval(2000ul);

          //start TIMER
          commonTIMER.enableRestartTIMER();

          //next state
          mState = STARTUP;
        }



      }
      break;

    //========================
    case FINISHED:
      {
        //Do something
      }
      break;

  }  //END of  switch/case

}  //END of   checkMachine()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
//
//we have access to:
//object.validChange()    - checks to see if there was a valid state change
//object.pin              - input hardware pin number
//object.lastState        - the state the input was/is in
//object.switchTime       - the millis() value when the switch closes

void checkSwitches() {
  //========================================================================  mySwitch
  //was there a valid input change ?
  //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
  if (mySwitch.validChange() == VALIDATED) {
    //========================
    //was this switch closed ?
    if (mySwitch.lastState == CLOSED) {
      digitalWrite(testLED, LEDon);

      //enable and restart the testLedTIMER
      testLedTIMER.enableRestartTIMER();
    }

    //========================
    //this switch was opened
    else {
      //Do nothing
    }

  }  //END of mySwitch

  //========================================================================  Next Switch

}  //END of   checkSwitches()


//                                   d i s p l a y S c r e e n ( )
//================================================^================================================
//
void displayScreen(int index) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(screens[index][0]);
  lcd.setCursor(0, 1);
  lcd.print(screens[index][1]);

}  //END of   displayScreen()


//================================================^================================================

One thing I don't get yet. It has to do with the LCD screenIndex:

const char* screens[4][2] = {
  //          111111               111111
  //0123456789012345     0123456789012345      //screenIndex value
  { "    Screen 1:   ", " Hello,  World! " },  //0
  { "    Screen 2:   ", " Arduino Rocks! " },  //1
  { "    Screen 3:   ", " Using millis() " },  //2
  { "    Screen 4:   ", "  I2C LCD Demo  " }   //3
};

//the index of the screen now being displayed on the LCD
byte screenIndex = 0;

I assume the [4] in the first line of this code above is the number of screens but what is the [2]?

How many strings are in each row of that 2-dimensional array of strings?

So are you saying the [2] is the:
“Screen 1” , “Hello World”
That is 2 strings or groups of text. Is that what you think it is?