Model Railway Turntable - in a spin so much Im dizzy

EEPROM reading and updating added

Upload the sketch version below.

Example

  • Power up the project.
    When we are sitting at position 0 (Track#1 position will be 750) , use the rotary encoder to move the stepper to 650.

  • Press and hold the SetPosSw
    Press and hold the EncoderSw
    Hold both for > 5 seconds
    Serial Monitor will display:

EEPROM address = 1      DATA read = 650
  • Power cycle the Arduino

  • Go to Track #1, what does the serial monitor say this position is now ?


  • Now try to set Track #1 so it goes back to 750 again ?
  • Does this all make sense ?

//================================================^================================================
//
//  https://forum.arduino.cc/t/model-railway-turntable-in-a-spin-so-much-im-dizzy/1263988
//
//  HLD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       24/05/25    Started writing sketch
//  1.10       24/05/25    Added code to read the Rotary Encoder
//  1.20       24/05/25    Added Stepping Motor code
//  1.30       24/05/27    Added the State Machine named "resetMachine"
//  1.40       24/05/27    Added Homing after zeroing the motor
//  1.50       24/05/27    Added LED effects when Zeroing and Homing
//  1.60       24/05/27    When the motor stops stepping, turn off motor coil currents
//  1.70       24/05/27    Added LED action when calibrating the Motor position
//  1.80       24/05/28    Added added debug macros, added some sketch comments
//  1.90       24/05/29    Added stepping limits of 0-4096 for the motor stepping range
//  2.00       24/05/30    Added added rotary encoder stepping, with limits, to motor control
//  2.10       24/05/30    CW and CCW switches now make motor go to the next/previous stored position
//  2.20       24/05/31    Calibration mode added, hold encoder switch at power up time
//  2.30       23/05/31    5 Track turntable needs 10 positions so you can turn the train engine 180 degrees
//  2.40       23/05/31    Add EEPROM stuff
//

//#include <Wire.h>

#include <EEPROM.h>

//                                           M A C R O S
//================================================^================================================
#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 OPTOclosed         HIGH   //use with "HIGH when closed" opto devices
#define OPTOopened         LOW    //use with "LOW when open" opto devices 

//========================================================================  <-------<<<<<<<
//these macros are used for debug purposes
//
#define DEBUG                     //comment this line to stop printing to the Serial Monitor,

//debug Macros
//========================
#ifdef DEBUG
#define SERIALBEGIN(...)    Serial.begin(__VA_ARGS__)

#define DPRINT(...)         Serial.print(__VA_ARGS__)
#define DPRINTLN(...)       Serial.println(__VA_ARGS__)

#define DPRINTF(...)        Serial.print(F(__VA_ARGS__))   //for text only, using the F macro
#define DPRINTLNF(...)      Serial.println(F(__VA_ARGS__)) //for text only, using the F macro plus new line

#else
#define SERIALBEGIN(...)

#define DPRINT(...)
#define DPRINTLN(...)

#define DPRINTF(...)
#define DPRINTLNF(...)
#endif


//                                            G P I O s
//================================================^================================================
//
//a structure to define input objects, switches or sensors
struct makeInput
{
  const byte pin;                  //the digital input pin number
  unsigned long switchTime;        //the time the switch was closed
  byte lastState;                  //the state the input was last in
  byte counter;                    //a counter used to validate a switch change in state
}; //END of   struct makeInput

const byte inputPins[]           = {4, 14, 15, 16, 17};

//Digital Inputs
//define the input connected to a PB switch

//========================
makeInput EncoderSw =
{
  4, 0, OPENED, 0                  //pin, switchTime, lastState, counter
};

//========================
makeInput DeleteSw =
{
  14, 0, OPENED, 0                 //pin, switchTime, lastState, counter
};

//========================
makeInput SetPosSw =
{
  15, 0, OPENED, 0                 //pin, switchTime, lastState, counter
};

//========================
makeInput CCW_Sw =
{
  16, 0, OPENED, 0                 //pin, switchTime, lastState, counter
};

//========================
makeInput CW_Sw =
{
  17, 0, OPENED, 0                 //pin, switchTime, lastState, counter
};

//========================
makeInput OptoSensor =
{
  18, 0, OPTOopened, 0            //pin, switchTime, lastState, counter
};


byte filter                      = 10;
//TIMER "switches" runs every 5ms.
//5ms * 10 = 50ms is needed to validate a switch change in state.
//A switch change in state is valid "only after" 10 identical changes are detected.
//This is used to filter out EMI noise in the system


const byte EncoderOutA           = 3;  //there is a pull-up resistor inside the rotary encoder
const byte EncoderOutB           = 2;  //there is a pull-up resistor inside the rotary encoder


//OUTPUTS
//===================================
const byte outputPins[]          = {13, 12, 11, 10, 9, 8, 7, 6, 5};

const byte heartbeatLED          = 13;
const byte cwLED                 = 12;
const byte ccwLED                = 11;
const byte deleteLED             = 10;
const byte setPosLED             =  9;

//stepping motor
const byte IN4                   = 8;
const byte IN3                   = 7;
const byte IN2                   = 6;
const byte IN1                   = 5;


//                                        V A R I A B L E S
//================================================^================================================
//
//                                         "Stepping Motor"                         2 8 B Y J - 4 8
//a 5-wire unipolar stepper motor that runs on 5V
//1/2 steps per revolution ~4096
//Stepping Motor stepping pattern                       8 half (1/2) steps then repeats
//index                                0       1       2       3       4       5       6       7       8
int lookup[]                     = {B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001, B00000};
//index 8 in the array is B00000
//we use this step pattern to turn off the motor current when the motor is stopped

//Stepper Motor inputs
//                  IN1, Bit 0 _____
//                  IN2, Bit 1 ____ |
//                  IN3, Bit 2 ___ ||
//                  IN4, Bit 3 __ |||
//                               ||||
//ex. step pattern             B01000
//IN1 is bit 0
//IN2 is bit 1
//IN3 is bit 2
//IN4 is bit 3

int stepIndex                       = 0;      //where we are in the Look-Up table
int savedStepIndex;                           //a copy of stepIndex when powering off the motor

//================================================
//if enabled, the CW and CCW switches can move the motor so you
//can check the track exact locations
bool calibrationFlag                = DISABLED;

byte homeFlashCounter;                        //used in flashing the LEDs at the HOME position

int homingCounter                   = 0;      //number of steps away from the motor zero position

const unsigned long CW_CCW_Interval = 5000ul; //5 seconds to verify if CW and CCW still being held

//================================================
const byte maxPos                   = 10;
int  positionIndex                  = -1;     //the storedPostions array index, -1 at reset
//array of stored positions
//index                                0  1  2  3  4  5  6  7  8  9
long storedPostions[maxPos]         = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

long destinationPosition            = 0;         //motor position we want to go to
long currentMotorPosition           = 0;         //number of steps away from the HOME position

const int homeSteps                 = 100;       //number of CW steps from the motor zero position

const int stepsPerRev               = 4096;      //the number of 1/2 steps per RPM
const unsigned long motorSpeed      = 3000ul;    //microseconds larger the number, slower the the motor movement

//================================================
//EEPROM stuff
const byte eeValidFlag                = B10101010; //HEX 0xAA, this pattern will signify the EEPROM data is valid
const byte eeStartAddress             = 0;
int eeAddress                         = 0;
byte eeReadData                       = 0;
byte eewriteDataEEPROM                = 0;
const unsigned long encoderSwHoldTime = 5000ul;    //hold encoder switch for this time to store the current position

//================================================^================================================
//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//TIMER objects are non-blocking
struct makeTIMER
{
#define MILLIS             1
#define MICROS             1000    //we can use this value to divide into a variable to get milliseconds

#define ENABLED            true
#define DISABLED           false

#define YES                true
#define NO                 false

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

  //these are the bare minimum "members" needed when defining a TIMER
  int                      TimerType;      //what kind of TIMER is this? MILLIS/MICROS
  unsigned long            Time;           //when the TIMER started
  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

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

      //========================
      else
      {
        //this TIMER has not 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()

}; //END of   struct makeTIMER


//                             D e f i n e   a l l   t h e   T I M E R S
//================================================^================================================
/*example
  //========================
  makeTIMER toggleLED =
  {
     MILLIS/MICROS, 0, 500ul, ENABLED/DISABLED, YES/NO  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
  };

  Functions we can access:
  toggleLED.checkTIMER();
  toggleLED.enableRestartTIMER();
  toggleLED.disableTIMER();
  toggleLED.expireTimer();
  toggleLED.setInterval(100ul);
*/

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

//========================
makeTIMER switchesTIMER =
{
  MILLIS, 0, 5ul, ENABLED, YES          //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER encoderTIMER =
{
  MILLIS, 0, 5ul, ENABLED, YES          //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER machineTIMER =
{
  MILLIS, 0, 5ul, ENABLED, YES          //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER eepromCommonTIMER =
{
  MILLIS, 0, 1000ul, DISABLED, YES      //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER resetCommonTIMER =
{
  MILLIS, 0, 1000ul, DISABLED, YES      //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER moveToCommonTIMER =
{
  MILLIS, 0, 1000ul, DISABLED, YES      //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER stepperTIMER =
{
  MICROS, 0, motorSpeed, DISABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER deleteLedTIMER =
{
  MILLIS, 0, 100ul, DISABLED, YES       //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};


//                                   S t a t e   M a c h i n e s
//================================================^================================================
//the States in our machine (use names that mean something to you)

//================================================     resetMachine State Machine
enum STATES : byte   //save RAM, use bytes
{
  ResetStartup,      //do power up stuff
  Check_CW_CCW_Sw,   //checking to see if CW and CCW are closed
  CW_CCW_Timing,     //wait to see if the switches have been closed long enough
  ResetMotor,        //wait here while motor is going CCW and looking for Opto Sensor
  MovingHome,        //set up things to home the motor
  HomingWait,        //wait here while the motor is homing, waiting for the Opto Sensor to close
  ResetFinished      //flash LEDs, reset the "Reset State Machine"
};

STATES resetMachine = ResetStartup;

//================================================     moveToMachine State Machine
enum POSITIONS : byte   //save RAM, use bytes
{
  MoveToStartup,        //do power up stuff
  MoveToWait1,          //1st place to wait
  GoCW,                 //moving motor CW  to next stored position
  GoCCW,                //moving motor CCW to next stored position
  MoveToFinished        //
};

POSITIONS moveToMachine = MoveToStartup;


//================================================     eepromMachine State Machine
enum EEPROMtype : byte  //save RAM, use bytes
{
  eepromStartup,        //do power up stuff
  eepromWait1,          //1st place to wait
  eepromTiming,         //wait to see if the switch have been closed long enough
  eepromFinished        //
};

EEPROMtype eepromMachine = eepromStartup;


//                                           s e t u p ( )
//================================================^================================================             s e t u p ( )
void setup()
{
  //this delay prevents setup() from running twice when the serial monitor printing.
  delay(500);

  SERIALBEGIN(115200);

  //======================
  //use INPUT_PULLUP so the pin does not float, floating pins can cause faulty readings
  //configure the input pins
  for (byte x = 0; x < sizeof(inputPins); x++)
  {
    pinMode(inputPins[x], INPUT_PULLUP);
  }

  //======================
  //configure the output pins
  for (byte x = 0; x < sizeof(outputPins); x++)
  {
    digitalWrite(outputPins[x], LOW);
    pinMode(outputPins[x], OUTPUT);
  }

  //======================
  //get the data at the staring EEPROM address
  EEPROM.get(eeStartAddress, eeReadData);

  //is the EEPROM data valid ?
  if (eeReadData == eeValidFlag)
  {
    //the data is valid, retrieve it
    //first DATA address after eeStartAddress
    eeAddress = eeStartAddress + 1;

    //get the track positions from EEPROM
    for (byte x = 0; x < maxPos / 2; x++)
    {
      //get the data for this array element
      EEPROM.get(eeAddress, storedPostions[x]);

      DPRINT(storedPostions[x]);
      DPRINTF(" ");

      //ready for the next address
      eeAddress = eeAddress + sizeof(storedPostions[x]);
    }

    DPRINTLNF("\n");
    DPRINTLNF("EEPROM data RETRIEVED");
  }

  //there was no valid data in the EEPROM, use the default values
  else
  {
    //use the calibration mode to determine each track value (make sure you do a zero reset first)
    storedPostions[0]     = 750;                                   //1st track
    storedPostions[1]     = 1100;                                  //2nd track
    storedPostions[2]     = 1900;                                  //3rd track
    storedPostions[3]     = 2250;                                  //4th track
    storedPostions[4]     = 2600;                                  //5th track

    //first DATA address after eeStartAddress
    eeAddress = eeStartAddress + 1;

    //update the EEPROM DATA
    for (byte x = 0; x < maxPos / 2; x++)
    {
      EEPROM.put(eeAddress, storedPostions[x]);

      DPRINT(storedPostions[x]);
      DPRINTF(" ");

      //ready for the next address
      eeAddress = eeAddress + sizeof(storedPostions[x]);
    }

    //DATA is now valid in our EEPROM
    EEPROM.update(eeStartAddress, eeValidFlag);

    DPRINTLNF("\n");
    DPRINTLN("EEPROM data UPDATED with default values");
  }

  //fill out the the array 180 degree values
  storedPostions[5]     = storedPostions[0] + stepsPerRev / 2;   //1st track + 180 degrees
  storedPostions[6]     = storedPostions[1] + stepsPerRev / 2;   //2nd track + 180 degrees
  storedPostions[7]     = storedPostions[2] + stepsPerRev / 2;   //3rd track + 180 degrees
  storedPostions[8]     = storedPostions[3] + stepsPerRev / 2;   //4th track + 180 degrees
  storedPostions[9]     = storedPostions[4] + stepsPerRev / 2;   //5th track + 180 degrees

  //======================
  //do we want to enter calibration mode ?
  if (digitalRead(EncoderSw.pin) == CLOSED)
  {
    //allow the motor to be controlled by the CW and CCW switches
    calibrationFlag = ENABLED;

    DPRINTLNF("Entering Calibration Mode. \n");
  }

  else
  {
    DPRINTLNF("Power Up Normal");
  }

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
void loop()
{
  //========================================================================  T I M E R  heartbeat
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED)
  {
    //when this TIMER expires, toggle the heartbeat LED
    //this LED is a rudimentary way of displaying if the sketch code is blocking or stuttering

    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);

  } //END of this   TIMER

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED)
  {
    //when this TIMER expires, check if any switch/sensor has changed state

    checkSwitches();

  } //END of this   TIMER

  //========================================================================  T I M E R  encoder
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to read the Rotary Encoder ?
  if (encoderTIMER.checkTIMER() == EXPIRED)
  {
    //when this TIMER expires, check the rotary encode to see if it has been turned

    readRotaryEncoder();

  } //END of this   TIMER

  //========================================================================  T I M E R  machine
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to service our State Machines ?
  if (machineTIMER.checkTIMER() == EXPIRED)
  {
    //when this TIMER expires, service any/all the State Machines

    //check all the "State Machines"
    checkMachines();

  } //END of this   TIMER

  //========================================================================  T I M E R  stepper
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to move the Stepping Motor ?
  if (stepperTIMER.checkTIMER() == EXPIRED)
  {
    //when this TIMER expires, step the motor
    //there are 4 exclusionary events (top/down priority) that move the stepper motor
    //i.e. only one event can be operated on at a time
    // 1. HomingWait:        Motor going HOME
    // 2. ResetMotor:        Motor going to ZERO
    // 3. GoCW               Motor going to the next CW  saved position
    // 4. GoCCW              Motor going to the next CCW saved position
    //
    // Calibration Mode
    // CW switch closed:  Make motor go CW  (max 4096)
    // CCW switch closed: Make motor go CCW (min 0)

    //========================                       H o m i n g W a i t
    //are we waiting for the motor to home ?
    if (resetMachine == HomingWait)
    {
      oneStepCW();

      //we have gone 1 more CW step
      homingCounter++;
      //DPRINTLN(homingCounter);

      //========================
      //are we finished homing ?
      if (homingCounter >= homeSteps)
      {
        //motor is now sitting at HOME
        currentMotorPosition = 0;
        Serial.println(currentMotorPosition);

        //setting up toggling of the LEDs every 500ms
        homeFlashCounter = 0;
        //LED toggle duration is 500ms
        resetCommonTIMER.setInterval(500ul);
        //restart the common TIMER
        resetCommonTIMER.enableRestartTIMER();

        //all LEDs OFF
        digitalWrite(cwLED, LEDoff);
        digitalWrite(ccwLED, LEDoff);
        digitalWrite(deleteLED, LEDoff);
        digitalWrite(setPosLED, LEDoff);

        //we are at HOME, reset the "Reset State Machine"
        resetMachine = ResetFinished;

        savePower();

        //we are done, disable this TIMER
        stepperTIMER.disableTIMER();
      }
    }

    //========================                       R e s e t M o t o r
    //are we resetting the motor position to zero ?
    else if (resetMachine == ResetMotor)
    {
      oneStepCCW();

      //========================
      //has the OptoSensor gone CLOSED ?
      if (OptoSensor.lastState == OPTOclosed)
      {
        //we are at position ZERO
        currentMotorPosition = 0;

        //it is now time to HOME the motor
        //next state in the "Reset State Machine"
        resetMachine = MovingHome;

        //we are done, disable this TIMER
        stepperTIMER.disableTIMER();
      }
    }

    //========================                       G o C W
    //are we moving to the next CW stored position ?
    else if (moveToMachine == GoCW)
    {
      oneStepCW();

      //advance the Motor Position
      currentMotorPosition++;
      DPRINT(currentMotorPosition);
      DPRINTF("\t");
      DPRINTLN(destinationPosition);

      //have we arrived at the stored position
      if (currentMotorPosition == destinationPosition)
      {
        savePower();

        //next state in the "MoveTo State Machine"
        moveToMachine = MoveToFinished;

        //we are done, disable this TIMER
        stepperTIMER.disableTIMER();
      }
    }

    //========================                       G o C C W
    //are we moving to the next CCW stored position ?
    else if (moveToMachine == GoCCW)
    {
      oneStepCCW();

      //advance the Motor Position
      currentMotorPosition--;
      DPRINT(currentMotorPosition);
      DPRINTF("\t");
      DPRINTLN(destinationPosition);

      //have we arrived at the stored position
      if (currentMotorPosition == destinationPosition)
      {
        savePower();

        //next state in the "MoveTo State Machine"
        moveToMachine = MoveToFinished;

        //we are done, disable this TIMER
        stepperTIMER.disableTIMER();
      }
    }

    //========================                       C W   s w i t c h   c l o s e d
    //when the CW switch is closed AND the CCW switch is OPENED
    //else if (calibrationFlag == ENABLED && currentMotorPosition < (storedPostions[4] + stepsPerRev) && CW_Sw.lastState == CLOSED &&
    //         CCW_Sw.lastState == OPENED)
    else if (calibrationFlag == ENABLED && currentMotorPosition < stepsPerRev && CW_Sw.lastState == CLOSED &&
             CCW_Sw.lastState == OPENED)
    {
      oneStepCW();

      //advance the Motor Position
      currentMotorPosition++;
      DPRINTLN(currentMotorPosition);
    }

    //========================                       C C W   s w i t c h   c l o s e d
    //when the CCW switch is closed AND the CW switch is OPENED
    else if (calibrationFlag == ENABLED && currentMotorPosition > 0 && CCW_Sw.lastState == CLOSED &&
             CW_Sw.lastState == OPENED)
    {
      oneStepCCW();

      //reduce the Motor Position
      currentMotorPosition--;
      DPRINTLN(currentMotorPosition);
    }

  } //END of this   TIMER

  //========================================================================  T I M E R  deleteLed
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to toggle the Delete LED ?
  if (deleteLedTIMER.checkTIMER() == EXPIRED)
  {
    //toggle the Delete LED
    digitalWrite(deleteLED, digitalRead(deleteLED) == HIGH ? LOW : HIGH);

  } //END of this   TIMER

  //================================================
  //other non blocking code goes here
  //================================================

} //END of   loop()


//                                   c h e c k M a c h i n e s ( )
//================================================^================================================
void checkMachines()
{
  //========================================================================  "EEPROM State Machine"
  switch (eepromMachine)
  {
    //========================          e e p r o m S t a r t u p
    case eepromStartup:
      {
        //do startup stuff here

        //next state in the "EEPROM State Machine"
        eepromMachine = eepromWait1;
      }
      break;

    //========================          e e p r o m W a i t 1
    case eepromWait1:
      {
        //sit here and wait
      }
      break;

    //========================          e e p r o m T i m i n g
    case eepromTiming:
      {
        //a switch has been OPENED ?
        if (digitalRead(EncoderSw.pin) == OPENED || SetPosSw.lastState == OPENED)
        {
          //a switch was let go too soon
          eepromMachine = eepromWait1;

          DPRINTLNF("Encoder switch or SetPosSw was released too soon");
        }

        //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //has the switch been closed long enough ?
        if (eepromCommonTIMER.checkTIMER() == EXPIRED)
        {
          //nothing to save at "reset / powerup time" ?
          if (positionIndex > -1)
          {
            //are we within the addresses range of the stored EEPROM track postions ?
            if (positionIndex < maxPos / 2)
            {
              //first update the SRAM variable
              storedPostions[positionIndex] = currentMotorPosition;
              //DPRINTF("Updating storedPositions[");
              //DPRINT(positionIndex);
              //DPRINTF("]");

              //calculate the EEPROM address for this position ?
              int address = (eeStartAddress + 1) + positionIndex * sizeof(long);

              //we only have 100,000 EEPROM write cycles per EEPROM location
              //when the EEPROM already has the same data, there is no updating, hence saving write cycles
              EEPROM.put(address, currentMotorPosition);

              //confirm the DATA was written
              DPRINTF("EEPROM address = ");
              DPRINT(address);
              DPRINTF("\tDATA read = ");
              long DATA;
              EEPROM.get(address, DATA);
              DPRINTLN(DATA);
            }

            //next state in the "EEPROM State Machine"
            eepromMachine = eepromFinished;
          }
        }
      }
      break;

    //========================          e e p r o m F i n i s h e d
    case eepromFinished:
      {
        //next state in the "EEPROM State Machine"
        eepromMachine = eepromStartup;
      }
      break;

  } //END of  "EEPROM State Machine"

  //========================================================================  "MoveTo State Machine"
  switch (moveToMachine)
  {
    //========================          M o v e T o S t a r t u p
    case MoveToStartup:
      {
        //do startup stuff here

        //next state in the "MoveTo State Machine"
        moveToMachine = MoveToWait1;
      }
      break;

    //========================          M o v e T o W a i t 1
    case MoveToWait1:
      {
        //wait here
      }
      break;

    //========================          G o C W
    case GoCW:
      {

      }
      break;

    //========================          G o C C W
    case GoCCW:
      {

      }
      break;

    //========================          M o v e T o F i n i s h e d
    case MoveToFinished:
      {
        DPRINTF("We have arrived at positionIndex #");
        //DPRINTLN(positionIndex + 1);
        DPRINTLN(positionIndex);

        digitalWrite(cwLED, LEDoff);
        digitalWrite(ccwLED, LEDoff);

        //next state in the "MoveTo State Machine"
        moveToMachine = MoveToStartup;
      }
      break;

  } //END of  "MoveTo State Machine"

  //========================================================================  "Reset State Machine"
  switch (resetMachine)
  {
    //========================          R e s e t S t a r t u p
    case ResetStartup:
      {
        //do startup stuff here

        //next state in the "Reset State Machine"
        resetMachine = Check_CW_CCW_Sw;
      }
      break;

    //================================================ <-------<<<<<<<
    //When the motor is stopped,
    //this State Machine will sit
    //in this state
    //========================          C h e c k _ C W _ C C W _ S w
    case Check_CW_CCW_Sw:
      {
        //are both CW and CCW switches CLOSED ?
        if (digitalRead(CW_Sw.pin) == CLOSED && digitalRead(CCW_Sw.pin) == CLOSED)
        {
          //cancel what is happening in the MoveTo State Machine
          moveToMachine = MoveToStartup;
          //back to rest position
          positionIndex = -1;

          DPRINTLNF("Both switches are closed");

          //disable any motor movement
          stepperTIMER.disableTIMER();

          resetCommonTIMER.setInterval(CW_CCW_Interval);
          resetCommonTIMER.enableRestartTIMER();

          //next state in the "Reset State Machine"
          resetMachine = CW_CCW_Timing;
        }
      }
      break;

    //========================          C W _ C C W _ T i m i n g
    case CW_CCW_Timing:
      {
        //has a switch been released ?
        if (digitalRead(CW_Sw.pin) == OPENED || digitalRead(CCW_Sw.pin) == OPENED)
        {
          digitalWrite(cwLED, LEDoff);
          digitalWrite(ccwLED, LEDoff);

          //back to checking for the 2 switches CLOSED
          //next state in the "Reset State Machine"
          resetMachine = Check_CW_CCW_Sw;

          break;
        }

        //has the TIMER expired ?
        if (resetCommonTIMER.checkTIMER() == EXPIRED)
        {
          //during motor zeroing, turn ON the LEDs
          digitalWrite(cwLED, LEDon);
          digitalWrite(ccwLED, LEDon);
          digitalWrite(deleteLED, LEDon);
          digitalWrite(setPosLED, LEDon);

          restorePower();

          //allow the motor to move
          stepperTIMER.enableRestartTIMER();

          //next state in the "Reset State Machine"
          resetMachine = ResetMotor;
        }
      }
      break;

    //========================          R e s e t M o t o r
    case ResetMotor:
      {
        //wait here until the motor is at the zero position
      }
      break;

    //========================          M o v i n g H o m e
    case MovingHome:
      {
        DPRINTLNF("Motor is at Zero");
        DPRINTLNF("Motor is now Homing");

        //start out at the first element on our Lookup table
        stepIndex = 0;

        //initialize the number of steps away from the motor zero position
        homingCounter = 0;

        //allow the motor to move
        stepperTIMER.enableRestartTIMER();

        //next state in the "Reset State Machine"
        resetMachine = HomingWait;
      }
      break;


    //========================          H o m i n g W a i t
    case HomingWait:
      {
        //wait here while homing the motor
      }
      break;


    //========================          R e s e t F i n i s h e d
    case ResetFinished:
      {
        //we are at the Motor Home position, toggle the LEDs
        if (homeFlashCounter < 4)
        {
          //has the common TIMER expired ?
          if (resetCommonTIMER.checkTIMER() == EXPIRED)
          {
            //toggle the LEDs
            digitalWrite(cwLED, digitalRead(cwLED) == HIGH ? LOW : HIGH);
            digitalWrite(ccwLED, digitalRead(ccwLED) == HIGH ? LOW : HIGH);
            digitalWrite(deleteLED, digitalRead(deleteLED) == HIGH ? LOW : HIGH);
            digitalWrite(setPosLED, digitalRead(setPosLED) == HIGH ? LOW : HIGH);

            //ready next flash
            homeFlashCounter++;
          }

          break;
        }

        //all LEDs OFF
        digitalWrite(cwLED, LEDoff);
        digitalWrite(ccwLED, LEDoff);
        digitalWrite(deleteLED, LEDoff);
        digitalWrite(setPosLED, LEDoff);

        //we are now finished with motor calibration
        DPRINTLNF("Motor is Home");
        DPRINTLNF("System has Reset");

        //we are done, reset this State Machine
        //next state in the "Reset State Machine"
        resetMachine = ResetStartup;
      }
      break;

  } //END of  "Reset State Machine"

} //END of   checkMachines()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
void checkSwitches()
{
  byte currentState;

  //========================================================================  Calibration Mode ?
  //this switch is ignored in calibration mode
  if (calibrationFlag != ENABLED)
  {
    //========================================================================  EncoderSw.pin
    //only proceed if the "EEPROM State Machine" is in the waiting state
    if (eepromMachine == eepromWait1)
    {
      currentState = digitalRead(EncoderSw.pin);

      //===================================
      //has this switch changed state ?
      if (EncoderSw.lastState != currentState)
      {
        EncoderSw.counter++;

        //is this change in state stable ?
        if (EncoderSw.counter >= filter)
        {
          //get ready for the next sequence
          EncoderSw.counter = 0;

          //update to this new state
          EncoderSw.lastState = currentState;

          //================================================  Switch CLOSED ?
          //did this switch close ?
          if (currentState == CLOSED)
          {
            DPRINTLNF("Encoder switch closed");

            //we need SetPosSw held before we can update the EEPROM position
            if (SetPosSw.lastState == CLOSED)
            {
              //next "EEPROM State Machine" state
              eepromMachine = eepromTiming;

              //time we need to hold this switch
              eepromCommonTIMER.setInterval(encoderSwHoldTime);

              //enable the common TIMER
              eepromCommonTIMER.enableRestartTIMER();
            }
          }

          //========================
          //did this switch open ?
          else if (currentState == OPENED)
          {
            DPRINTLNF("Encoder switch opened");
          }
        }
      }

      //===================================
      //a valid switch change has not been confirmed
      else
      {
        EncoderSw.counter = 0;
      }

    }
  } //END of   if (calibrationFlag != ENABLED)

  //END of EncoderSw.pin

  //========================================================================  Calibration Mode ?
  if (calibrationFlag != ENABLED)
  {
    //proceed as normal

    //========================================================================  CW_Sw.pin
    //only proceed if the "Reset State Machine" not busy AND
    //we are not currently moving to the next position
    if (resetMachine == Check_CW_CCW_Sw && moveToMachine == MoveToWait1)
    {
      currentState = digitalRead(CW_Sw.pin);

      //===================================
      //has this switch changed state ?
      if (CW_Sw.lastState != currentState)
      {
        CW_Sw.counter++;

        //is this change in state stable ?
        if (CW_Sw.counter >= filter)
        {
          //get ready for the next sequence
          CW_Sw.counter = 0;

          //update to this new state
          CW_Sw.lastState = currentState;

          //================================================  Switch CLOSED ?
          //did this switch close ?
          if (currentState == CLOSED)
          {
            positionIndex++;
            //do not go past the last stored count
            if (positionIndex >= maxPos)
            {
              //stay where we are
              positionIndex = positionIndex - 1;

              DPRINTLNF("Upper limit reached");
            }

            //are we already at the position ?
            if (currentMotorPosition != storedPostions[positionIndex])
            {
              //DPRINTF("Postion index is = ");
              //DPRINT(positionIndex);
              //DPRINTF("\t");
              //DPRINTLN(storedPostions[positionIndex]);

              //the new motor position we want to go
              destinationPosition = storedPostions[positionIndex];

              restorePower();

              //next state in the moveToMachine State Machine
              moveToMachine = GoCW;

              //movement is now allowed, the Motor will move CW
              stepperTIMER.enableRestartTIMER();

              digitalWrite(cwLED, LEDon);
            }
          }

          //========================
          //did this switch open ?
          else if (currentState == OPENED)
          {
          }
        }
      }

      //===================================
      //a valid switch change has not been confirmed
      else
      {
        CW_Sw.counter = 0;
      }

      //END of CW_Sw.pin
    }

    //========================================================================  CCW_Sw.pin
    //only proceed if the "Reset State Machine" not busy AND
    //we are not currently moving to the next position
    if (resetMachine == Check_CW_CCW_Sw && moveToMachine == MoveToWait1)
    {
      currentState = digitalRead(CCW_Sw.pin);

      //===================================
      //has this switch changed state ?
      if (CCW_Sw.lastState != currentState)
      {
        CCW_Sw.counter++;

        //is this change in state stable ?
        if (CCW_Sw.counter >= filter)
        {
          //get ready for the next sequence
          CCW_Sw.counter = 0;

          //update to this new state
          CCW_Sw.lastState = currentState;

          //================================================  Switch CLOSED ?
          //did this switch close ?
          if (currentState == CLOSED)
          {
            //are we at power up  time "OR" at position 0 ?
            if (currentMotorPosition == 0 ||  currentMotorPosition <= storedPostions[0])
            {
              DPRINTLNF("Lower limit reached");
            }

            else
            {
              //get ready for the next position
              positionIndex--;
              //do not go below index 0 ?
              if (positionIndex < 0)
              {
                //stay where we are
                positionIndex = 0;
              }

              //the new motor position we want to go
              destinationPosition = storedPostions[positionIndex];

              restorePower();

              //next state in the moveToMachine State Machine
              moveToMachine = GoCCW;

              //movement is now allowed, the Motor will move CW
              stepperTIMER.enableRestartTIMER();

              digitalWrite(ccwLED, LEDon);
            }
          }
        }

        //========================
        //did this switch open ?
        else if (currentState == OPENED)
        {
        }
      }
    }

    //===================================
    //a valid switch change has not been confirmed
    else
    {
      CCW_Sw.counter = 0;
    }

    //END of CCW_Sw.pin

  } //END of   if (calibrationFlag != ENABLED)

  //========================================================================  In Calibration Mode
  //we are in calibration mode
  else
  {
    //========================================================================  CW_Sw.pin
    //only proceed if the "Reset State Machine" is sitting in the Check_CW_CCW_Sw state
    if (resetMachine == Check_CW_CCW_Sw)
    {
      currentState = digitalRead(CW_Sw.pin);

      //===================================
      //has this switch changed state ?
      if (CW_Sw.lastState != currentState)
      {
        CW_Sw.counter++;

        //is this change in state stable ?
        if (CW_Sw.counter >= filter)
        {
          //get ready for the next sequence
          CW_Sw.counter = 0;

          //update to this new state
          CW_Sw.lastState = currentState;

          //========================
          //did this switch close ?
          if (currentState == CLOSED)
          {
            DPRINTLNF("Motor going Clockwise");

            restorePower();

            //movement is now allowed and the Motor will move CW
            stepperTIMER.enableRestartTIMER();

            digitalWrite(cwLED, LEDon);
          }

          //========================
          //did this switch open ?
          else if (currentState == OPENED)
          {
            DPRINTLNF("Motor Stopped");

            savePower();

            //the TIMER is now disabled and the Motor will stop
            stepperTIMER.disableTIMER();

            digitalWrite(cwLED, LEDoff);
          }
        }
      }
    }

    //===================================
    //a valid switch change has not been confirmed
    else
    {
      CW_Sw.counter = 0;
    }

    //END of CW_Sw.pin

    //========================================================================  CCW_Sw.pin
    //only proceed if the "Reset State Machine" is sitting in the Check_CW_CCW_Sw state
    if (resetMachine == Check_CW_CCW_Sw)
    {
      currentState = digitalRead(CCW_Sw.pin);

      //===================================
      //has this switch changed state ?
      if (CCW_Sw.lastState != currentState)
      {
        CCW_Sw.counter++;

        //is this change in state stable ?
        if (CCW_Sw.counter >= filter)
        {
          //get ready for the next sequence
          CCW_Sw.counter = 0;

          //update to this new state
          CCW_Sw.lastState = currentState;

          //========================
          //did this switch close ?
          if (currentState == CLOSED)
          {
            DPRINTLNF("Motor going Counter Clockwise");

            restorePower();

            //movement is now allowed and the Motor will move CCW
            stepperTIMER.enableRestartTIMER();

            digitalWrite(ccwLED, LEDon);
          }

          //========================
          //did this switch open ?
          else if (currentState == OPENED)
          {
            DPRINTLNF("Motor Stopped");

            savePower();

            //the TIMER is now disabled and the Motor will stop
            stepperTIMER.disableTIMER();

            digitalWrite(ccwLED, LEDoff);
          }
        }
      }
    }

    //===================================
    //a valid switch change has not been confirmed
    else
    {
      CCW_Sw.counter = 0;
    }

    //END of CCW_Sw.pin
  }

  //========================================================================  DeleteSw.pin
  currentState = digitalRead(DeleteSw.pin);

  //===================================
  //has this switch changed state ?
  if (DeleteSw.lastState != currentState)
  {
    DeleteSw.counter++;

    //is this change in state stable ?
    if (DeleteSw.counter >= filter)
    {
      //get ready for the next sequence
      DeleteSw.counter = 0;

      //update to this new state
      DeleteSw.lastState = currentState;

      //========================
      //did this switch close ?
      if (currentState == CLOSED)
      {
        DPRINTLNF("Delete switch closed");

        deleteLedTIMER.enableRestartTIMER();
      }

      //========================
      //did this switch open ?
      else if (currentState == OPENED)
      {
        DPRINTLNF("Delete switch opened");

        digitalWrite(deleteLED, LEDoff);

        deleteLedTIMER.disableTIMER();
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else
  {
    DeleteSw.counter = 0;
  }

  //END of DeleteSw.pin

  //========================================================================  SetPosSw.pin
  currentState = digitalRead(SetPosSw.pin);

  //===================================
  //has this switch changed state ?
  if (SetPosSw.lastState != currentState)
  {
    SetPosSw.counter++;

    //is this change in state stable ?
    if (SetPosSw.counter >= filter)
    {
      //get ready for the next sequence
      SetPosSw.counter = 0;

      //update to this new state
      SetPosSw.lastState = currentState;

      //========================
      //did this switch close ?
      if (currentState == CLOSED)
      {
        DPRINTLNF("Set switch closed");

        //toggle LED
        //digitalWrite(setPosLED, digitalRead(setPosLED) == HIGH ? LOW : HIGH);

        digitalWrite(setPosLED, LEDon);
      }

      //========================
      //did this switch open ?
      else if (currentState == OPENED)
      {
        DPRINTLNF("Set switch opened");

        digitalWrite(setPosLED, LEDoff);
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else
  {
    SetPosSw.counter = 0;
  }

  //END of SetPosSw.pin

  //========================================================================  OptoSensor.pin
  currentState = digitalRead(OptoSensor.pin);

  //===================================
  //has this switch changed state ?
  if (OptoSensor.lastState != currentState)
  {
    OptoSensor.counter++;

    //is this change in state stable ?
    if (OptoSensor.counter >= filter)
    {
      //get ready for the next sequence
      OptoSensor.counter = 0;

      //update to this new state
      OptoSensor.lastState = currentState;

      //========================
      //did this switch close ?
      //if (currentState == CLOSED)
      if (currentState == OPTOclosed)
      {
        DPRINTLNF("Opto Sensor has Closed");
      }

      //========================
      //did this switch open ?
      //else if (currentState == OPENED)
      else if (currentState == OPTOopened)
      {
        DPRINTLNF("Opto Sensor has Opened");
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else
  {
    OptoSensor.counter = 0;
  }

  //END of OptoSensor.pin

} //END of   checkSwitches()


//                               r e a d R o t a r y E n c o d e r ( )
//================================================^================================================
void readRotaryEncoder()
{
  static byte lastStateA = digitalRead(EncoderOutA);
  byte currentStateA;

  currentStateA = digitalRead(EncoderOutA);

  //=========================================
  //has there been a state change ?
  if (lastStateA != currentStateA)
  {
    //update to the new state
    lastStateA = currentStateA;

    //========================
    //when the outputB state is different from the outputA state
    //we are rotating clockwise CW
    if (digitalRead(EncoderOutB) != currentStateA)
    {
      //CW direction
      //============
      //don't let the motor go more than stepsPerRrev
      if ((currentMotorPosition + 10) <= (storedPostions[4] + stepsPerRev))
      {
        //make the motor go CW
        restorePower();

        for (byte x = 0; x < 10; x++)
        {
          oneStepCW();

          delayMicroseconds(2000ul);
        }

        savePower();

        //increment the Motor Position
        currentMotorPosition = currentMotorPosition + 10;
        DPRINT(currentMotorPosition);
        DPRINTLNF("\t Motor not calibrated");
      }
    }

    //========================
    //we are rotating counter clock wise CCW
    else
    {
      //CCW direction
      //============
      //don't let the motor go less than 0
      if ((currentMotorPosition - 10) >= 0)
      {
        //make the motor go CCW
        restorePower();

        for (byte x = 0; x < 10; x++)
        {
          oneStepCCW();

          delayMicroseconds(2000ul);
        }

        savePower();

        //decrement the Motor Position
        currentMotorPosition = currentMotorPosition - 10;
        DPRINT(currentMotorPosition);
        DPRINTLNF("\t Motor not calibrated");
      }
    }
  }

} //END of  readRotaryEncoder()


//                                       s t e p M o t o r ( )
//================================================^================================================
void stepMotor(int output)
{
  digitalWrite(IN1, bitRead(lookup[output], 0));
  digitalWrite(IN2, bitRead(lookup[output], 1));
  digitalWrite(IN3, bitRead(lookup[output], 2));
  digitalWrite(IN4, bitRead(lookup[output], 3));

} //END of  stepMotor()


//                                       o n e S t e p C W ( )
//================================================^================================================
void oneStepCW()
{
  //get ready for the next step
  stepIndex++;

  //should we go back to the beginning index ?
  if (stepIndex > 7)
  {
    stepIndex = 0;
  }

  //move the motor
  stepMotor(stepIndex);

} //END of  oneStepCW()


//                                      o n e S t e p C C W ( )
//================================================^================================================
void oneStepCCW()
{
  //get ready for the next step
  stepIndex--;

  //should we go back to the ending index ?
  if (stepIndex < 0)
  {
    stepIndex = 7;
  }

  //move the motor
  stepMotor(stepIndex);

} //END of  oneStepCCW()


//                                       s a v e P o w e r ( )
//================================================^================================================
void savePower()
{
  //to save power, turn off stepper coils
  //save the current stepIndex
  savedStepIndex = stepIndex;
  stepIndex = 8;
  stepMotor(stepIndex);

} //END of   savePower()


//                                    r e s t o r e P o w e r ( )
//================================================^================================================
void restorePower()
{
  //power up, turn on stepper coils
  //restore the saved Motor stepIndex
  stepIndex = savedStepIndex;
  stepMotor(stepIndex);

} //END of   restorePower()


//                                      d e b u g P r i n t ( )
//================================================^================================================
void debugPrint()
{
  //use this line to halt the sketch and print certain variables
  //debugPrint();  //                                       D E B U G   H A L T
  //print these variables to the serial monitor and HALT
  //  DPRINTLNF("\nCurrent debug variables selected");
  //  DPRINTF("currentMotorPosition = ");
  //  DPRINTLN(currentMotorPosition);
  //  DPRINTF("Index 0 = ");
  //  DPRINTLN(storedPostions[0]);
  //  DPRINTF("Index 5 = ");
  //  DPRINTLN(storedPostions[5]);
  //  DPRINTF("180 degrees = ");
  //  DPRINTLN(stepsPerRev / 2 );
  //  DPRINTLN(eeReadData,HEX);

  //stop code execution
  while (1);

} //END of   debugPrint()


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