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()
//
//================================================^================================================