#include <Servo.h>
#include <EEPROM.h>
// THERE IS STILL A DUPLICATION OF EFFORT IN REPORTING TURNOUTS; we have serialUpdateReport and ReportTurnout. Figure out whether wee need two, or should use 1
// ALSO NEED TO ADD CONDITIONAL SERIAL OUTPUT TO SUPPRESS EXCESSIVE REPORTING, or add a switch in the switch case to turn on/off some reporting. Like a loud/normal/quiet control?
// Other than that, this seems like it's ready to go, at least as a beta. Now to decide whether to unfold it as a series of lessons for development. Probably not. Not enough time, nor audience.
//SECM Main Section
/***************************************************************************************************
Arduino Turnout Control
A program to utilize a classic Nano or similar Arduino processor to
receive input from 8 digital sources and drive 8 digital outputs.
- Inputs may be driven by any mechanism that delivers a 1/0 logic level.
Suggestions include Touch-Toggles, TTP223 touch sensors, and SPST switches.
Momentary pushbuttons could also be used, but would require a software latch.
- Outputs will be servo pulse outputs; future changes could allow 1/0
logic levels to control Tortoises or other stall motor machines.
- Serial is supported for configuration; RS485 may be supported in future
- A6/A7 are available to provide configuration option inputs if desired
**************************************************************************************************/
/* A note on pin assignments for a Nano
D0/D1 should be reserved for serial, either for Serial Monitor or other comms
D2 - reserved for RS485 direction control in future
D3 - D10 assigned to servo or tortoise outputs
D11, D12 assigned to digital inputs
D13 - reserved (for LED)
A0 - A5 assigned to digital inputs
A6, A7 - reserved for options selection; exact configuration TBD
N.B. If the target is a Mega 2560, then the user will have to determine what pin assignments make sense
*/
#define __TTSVERSION__ "1.0.1"
const bool HOME = 0; //alleviates having to think about ones and zeros when evaluating logic
const bool THROWN = 1; //
struct TURNOUTS { //values that are static and must be stored for use after next reset/power up; serial function writeline(Turnout) must be updated if this structure changes
uint8_t servoPin; //where we send the pulse to position the servo
uint8_t buttonPin; //where we get the setting for the servo from(i.e. the Touch Toggle connection)
int16_t homePosition; //the angle needed for 'homing' the turnout
int16_t thrownPosition; //the angle needed for 'throwing' the turnout
int8_t ServoSpeed; //initially zero, causes ASAP movement; positive values are degrees/update, negative are updates/degree, allowing a broad range
};
struct TURNOUTV { //values that are volatile, initialized after every reset/power up
bool state; //commanded state of the servo (Touch Toggle)
Servo ourServo; //a holder for the instance of the servo
};
struct TURNOUT {
TURNOUTS S; //static elements
TURNOUTV V; //volatile elements
};
TURNOUT Turnouts[] = { //items shown are for 8 servos on a classic Nano; clearly, the list would change for different Arduinos; serial function writeline(Turnout) must be updated if this structure changes
{{2, 3, 35, 145, 0}, {}}, //a servo on pin 2, button on pin 3; moves from 45 to 135; default speed ASAP; note the volatile elements don't get initialized
{{4, 5, 35, 145, 0}, {}}, //a servo on pin 4, button on pin 5
{{6, 7, 35, 145, 0}, {}},
{{8, 9, 35, 145, 0}, {}},
{{10, 11, 35, 145, 0}, {}},
{{14, 15, 35, 145, 0}, {}},
{{16, 17, 35, 145, -15}, {}},
{{18, 19, 35, 145, 15}, {}},
};
/* A note on servo speeds
Servo pulses are emitted every 20 ms.
To move very quickly, change the number of degrees to move between every pulse. The fastest movement, which I call ASAP, changes
the pulse width from beginning to end in one fell swoop, and is designated by a speed value of 0.
To move very slowly, the best we can do is to change one degree at a time, and do so only every 2nd, or 3rd, or 4th, etc. update cycle.
To do any better than this, we would need to change the whole code to use the microsecond settings for pulse width, instead of degrees. While not insurmountable,
this is much less transparent than using degrees, to the new user. Under the hood, of course, the Servo.h software uses microseconds, so there will always be some amount
of inaccuracy, or slop, in the code when using degrees.
*/
const uint8_t NumTurnouts = sizeof(Turnouts) / sizeof(TURNOUT); //automatically determines the number of turnouts represented in the Turnouts array - user just adds/deletes rows as desired
uint8_t PresTurnout = 0; //counter used to indicate which turnout is presently active in update
uint8_t EditTurnout = 0; //counter used to indicate which turnout is presently active for serial editing; due to update scanning, we cannot presume it's the same as PresTurnout! Changes when updateTurnout completes a move
const int16_t MAXPOS = 175; //limit for servo angles, used when editing
const int16_t MINPOS = 5; //limit for servo angles, used when editing
const int8_t MAXSPEED = 20; //upper limit for speed setting; 20 indicates that, on every servo update, the angle will change by 20 degrees
const int8_t MINSPEED = -20; //lower limit for speed setting; -20 indicates changing the angle by 1 degree every 20 servo pulse updates, or 2.5 degrees per second aggregate
void setup() {
serialStartup(); //opens port, emits hello message
readEEPROM(); //reads settings from EEPROM; function also will write EEPROM after a dialog, if the EEPROM did not appear to have been configured
serialMenu(); //emits the command menu for configuring turnouts
for (uint8_t i = 0; i < NumTurnouts;) { //so, for each turnout in the array, do the following actions
pinMode(Turnouts[EditTurnout].S.buttonPin, INPUT_PULLUP); //make the button input pullup to ensure consistant readings, else the attached turnout will flicker if the input isn't connected.
Turnouts[i].V.state = THROWN; //the button will read as 'HOME' in update, so pre-force state to 'THROWN' to trigger an update
i = updateTurnout(i); //call the update function
if (i == 0) break; //since update is doing the incrementing of i, we need to break the loop when i wraps around to zero.
}//end of for loop
}//end of setup ******************************************************************************************************
void loop() {
//note how, since loop repeats endlessly, we don't need a for loop in loop. By this mechanism, loop automatically cycles through updating all turnouts, moving them only if their state input has changed, or if their end limits are user-modified
serialCheck(); //handle any user commands/requests
PresTurnout = updateTurnout(PresTurnout); //updateTurnout will return the same turnout number if it's not done with the update, else it returns the next turnout to be managed
}//end of loop *******************************************************************************************************************
//SERIALS
/* The serial routines to date:
serialStartup() //well, duh!
SerialHello() //emits hello message, code ID, and misc stuff
SerialMenu() //emits a block of text identifying what editing functions are active
SerialCheck() //handles all the serial editing functions
SerialUpdateReport() //reports the current status of a TO that's just been moved
*/
void serialStartup() {
Serial.begin(115200); //configures the connection to Serial Monitor, the terminal window for the IDE
Serial.flush(); //swallows any startup trash
serialHello(); //report program name, version, date
}//end serialStartup **************************************************************************************************
void serialHello() { //Just a series of Serial.print() statements
//print Hello banner here
Serial.println(F("----------------------------------------------------------------"));
Serial.println(F(" Touch-Toggle Servo Control"));
Serial.println(F(" Author: Camsysca"));
Serial.print(F(" ")); Serial.print(__DATE__);
Serial.print(F(" File: ")); Serial.println(__FILE__);
Serial.print(F(" Version: ")); Serial.println(__TTSVERSION__); //should come from rev control
}//end serialHello **************************************************************************************************
void serialMenu() { //Just a series of Serial.print() statements
Serial.print(F("---------------------------------------------------------------------\n"));
Serial.print(F("| ? display (this) user menu A...Z select turnouts 0...25 |\n"));
Serial.print(F("| * describe selected turnout & copy designated turnout |\n"));
Serial.print(F("| = test position ~ swap home/thrown positions |\n"));
Serial.print(F("| \\ decrease speed by 1 / increase speed by 1 |\n"));
Serial.print(F("| [ decrease position by 1 ] increase position by 1 |\n"));
Serial.print(F("| { decrease position by 5 } increase position by 5 |\n"));
Serial.print(F("| ^ print table line for code @ print table for code |\n"));
Serial.print(F("| < write turnout settings > recover turnout settings |\n"));
Serial.print(F("| # write EEPROM settings $ recover EEPROM settings |\n"));
Serial.print(F("| ! dump EEPROM content, hex fmt |\n")); //unused: %+-()|`'";:,.
Serial.print(F("---------------------------------------------------------------------\n"));
}//end serialMenu **************************************************************************************************
void serialCheck() { //check serial - if character received, process
if (Serial.available() > 0) { //cases listed in same order as they appear in the serial Menu; edit to maintain that order!
char cmd = Serial.read();//simply reads a single char from serial;
switch (cmd) {
// characters not in use ()-.,;:'"| and space
case '+'://increased debugging reporting
Serial.println(F("+ Not yet implemented"));
break;
case '-'://decreased debugging reporting
Serial.println(F("- Not yet implemented"));
break;
case '|'://normal debugging reporting
Serial.println(F("| Not yet implemented"));
break;
case '?'://print the user menu
serialMenu();
break;
case 'a' ... 'z': //morph lower case to upper, so the user can use either
cmd = cmd - 0x20; //fall through is expected //convert to upper case
case 'A' ... 'Z': //commands for 26 turnouts
if ((cmd - 'A') < NumTurnouts) EditTurnout = cmd - 'A'; //convert the char to number, if it's a valid turnout set EditTurnout
break;
case '*': //print the setup of the present turnout
reportTurnout(EditTurnout);
break;
case '&'://copy designated turnout to present turnout; must be followed by a designator, valid A-Z, within 10 ms (so for example enter "&f" in SM), or will be ignored.
delay(10);//wait for char to be queued, just in case
if (Serial.available()) {
char des = Serial.read();
switch (des) {
case 'a' ... 'z':
des = des - 0x20;
case 'A' ... 'Z':
des = des - 'A';
//if des is from 0 - NumTurnouts, do the swap
if (des < NumTurnouts) {
Turnouts[EditTurnout].S.homePosition = Turnouts[des].S.homePosition; //copy home
Turnouts[EditTurnout].S.thrownPosition = Turnouts[des].S.thrownPosition; //copy thrown
Turnouts[EditTurnout].S.ServoSpeed = Turnouts[des].S.ServoSpeed; //copy speed
break;
default:
break;
}
}
}
break;
case '='://move the points to center and then return, for testing positioning
adjustPosition(0);//0 is shorthand for this functionality
break;
case '~'://swap homePosition and thrownPosition, then let updateTurnout do it's thing
{
int tempPos = Turnouts[EditTurnout].S.thrownPosition;
Turnouts[EditTurnout].S.thrownPosition = Turnouts[EditTurnout].S.homePosition;
Turnouts[EditTurnout].S.homePosition = tempPos;
}
break;
case '\\'://decrease speed setting by 1
adjustSpeed(-1);
break;
case '/'://increase speed setting by 1
adjustSpeed(1);
break;
case '['://decrease present position by 1
adjustPosition(-1);
break;
case ']'://increase present position by 1
adjustPosition(1);
break;
case '{'://decrease present position by 5
adjustPosition(-5);
break;
case '}'://increase present position by 5
adjustPosition(5);
break;
case '^': //print the position table row for EditTurnout, exactly as it appears in the definitions before setup. That way, it can be copied verbatim...
writeline(EditTurnout);
break;
case '@': //print the complete position table, preserving EditTurnout.
writelines();
break;
case '<'://retrieve turnout from EEPROM
readTurnout(EditTurnout); //
break;
case '>'://write turnout to EEPROM
writeTurnout(EditTurnout); //function utilizes update, so will only write locations that need to change
break;
case '!': //dump EEPROM listing
dumpEEPROM(0, EEPROM.length());
break;
case '$'://retrieve settings from EEPROM
readEEPROM(); //
break;
case '#'://write settings to EEPROM
writeEEPROM(); //function utilizes update, so will only write locations that need to change
break;
case 0x0d://swallow CR
case 0x0a://swallow LF
break;
default: //anything else, we report
Serial.print(F("Character ")); Serial.print(cmd); Serial.print(F(" hex value: ")); Serial.print(cmd, HEX); Serial.println(F(" has no function")); //advise about unrecognized command character
break;
}
}
}//end serialCheck **************************************************************************************************
void serialUpdateReport(uint8_t to) { //emit single line report of turnout update action
//of the form "Turnout NN moved to HOME/THROWN (NNN)" where NNN is the angle being set per the Turnout table entry
Serial.print(F("Turnout ")); Serial.print(to); Serial.print(F(" moved to "));
if (Turnouts[to].V.state == HOME) Serial.print(F(" HOME ")); else Serial.print(F("THROWN"));
Serial.print(F(" (")); Serial.print(Turnouts[to].V.ourServo.read()); Serial.print(F(")\n"));
}//end serialUpdateReport **************************************************************************************************
void writeline(uint8_t to) { //print the line of the turnout indicated; code MUST be updated if the Turnout structure changes
//output line must look like: {{2, 3, 45, 135, 0}, {}}, //pin, pin, val, val, spd
Serial.print(F("{{")); Serial.print(Turnouts[to].S.servoPin);
Serial.print(F(", ")); Serial.print(Turnouts[to].S.buttonPin);
Serial.print(F(", ")); Serial.print(Turnouts[to].S.homePosition);
Serial.print(F(", ")); Serial.print(Turnouts[to].S.thrownPosition);
Serial.print(F(", ")); Serial.print(Turnouts[to].S.ServoSpeed);
Serial.print(F("}, {}},\n"));//
} //end writeline **************************************************************************************************
void writelines() {
for (uint8_t to = 0; to < NumTurnouts; to++) { //cycle EditTurnout, printing each line of the turnout table as we go
writeline(to);
}
} //end writelines **************************************************************************************************
void adjustPosition(int8_t offset) {
//uses combination of EditTurnout and state to identify which value to modify in the table(adjusted by offset value, with defined limits applied), and then let updateTurnout do it's thing
//special value is 0, which causes turnout points to go to the position halfway between home and thrown, and then let updateTurnout do it's thing
switch (offset) {
case 0: //special - go to middle, return
{
int tmp = (Turnouts[EditTurnout].S.homePosition + Turnouts[EditTurnout].S.thrownPosition) / 2;
Turnouts[EditTurnout].V.ourServo.attach(Turnouts[EditTurnout].S.servoPin); //we know we're not where we're supposed to be, so ensure servo is enabled
Turnouts[EditTurnout].V.ourServo.write(tmp); //set position
delay(tmp * 5);
}
break;
case 1 ... 10:
case -10 ... -1:
if (Turnouts[EditTurnout].V.state == HOME) {
Turnouts[EditTurnout].S.homePosition = Turnouts[EditTurnout].S.homePosition + offset;
if (Turnouts[EditTurnout].S.homePosition < MINPOS) Turnouts[EditTurnout].S.homePosition = MINPOS;
else if (Turnouts[EditTurnout].S.homePosition > MAXPOS) Turnouts[EditTurnout].S.homePosition = MAXPOS;
}
else {
Turnouts[EditTurnout].S.thrownPosition = Turnouts[EditTurnout].S.thrownPosition + offset;
if (Turnouts[EditTurnout].S.thrownPosition < MINPOS) Turnouts[EditTurnout].S.thrownPosition = MINPOS;
else if (Turnouts[EditTurnout].S.thrownPosition > MAXPOS) Turnouts[EditTurnout].S.thrownPosition = MAXPOS;
}
break;
default:
break;
}
}//end adjustPosition **************************************************************************************************
void adjustSpeed(int8_t sp) {
//adjust the present speed setting by the passed value, then limit to the range MINSPEED <= speed <= MAXSPEED
Turnouts[EditTurnout].S.ServoSpeed = Turnouts[EditTurnout].S.ServoSpeed + sp; //change value
if (Turnouts[EditTurnout].S.ServoSpeed > MAXSPEED) Turnouts[EditTurnout].S.ServoSpeed = MAXSPEED; //check against high limit
if (Turnouts[EditTurnout].S.ServoSpeed < MINSPEED) Turnouts[EditTurnout].S.ServoSpeed = MINSPEED; //check against low limit
}// end adjustSpeed **************************************************************************************************
void reportTurnout(uint8_t tt) {
Serial.print(F(" Turnout ")); Serial.print(tt); //remind the user which turnout this config is for
Serial.print(F(" Speed: ")); Serial.print(Turnouts[tt].S.ServoSpeed); //Serial.println(); //print the speed setting
Serial.print(F(" Servo Pin: ")); Serial.print(Turnouts[tt].S.servoPin); //print the servo pin
Serial.print(F(" Buttn Pin: ")); Serial.print(Turnouts[tt].S.buttonPin); //Serial.println(); //print the button pin
Serial.print(F(" Home A: ")); Serial.print(Turnouts[tt].S.homePosition); //print the home angle
Serial.print(F(" Thrown A: ")); Serial.print(Turnouts[tt].S.thrownPosition); //Serial.println(); //print the thrown angle
Serial.print(F(" State: ")); Turnouts[tt].V.state == HOME ? Serial.print(F(" HOME ")) : Serial.print(F("THROWN ")); //print the state
Serial.print(F(" Position: ")); Serial.println(Turnouts[tt].V.ourServo.read()); //print the state
}//end reportTurnout **********************************************************************************************
//SEC UPDATES
/*
logic for moving presTurnout is as follows:
if TT hasn't changed and our position is consistent with one of the states, don't move, and reset update counter to 0
if TT has changed and we're at origin, initiate moving towards new state, and initialize update counter if speed is < 0
if TT hasn't changed, but our position is not consistent with either state, (continue) move towards new state and increment update counter if speed < 0
determine what our state is
if state not changed and position == stateposition
IDLE so just detach, in case we came from movement previous pass
else (either TT changed OR position is inconsistent)
if TT state changed
BEGIN attach servo, change position dependent on speed
else
MOVING change position dependent on speed (detail - only get here if speed not 0)
utilize waitForPulseEnd(presTurnout)
*/
uint8_t updateTurnout(uint8_t To) { //Okay, let's start the routine; check TT input
bool oldState = Turnouts[To].V.state; //save old state for change check
Turnouts[To].V.state = digitalRead(Turnouts[To].S.buttonPin); //read new state
//for clarity, expanded logic for the movement condition
bool S = oldState == Turnouts[To].V.state;//Has the state changed?
if (!S) EditTurnout = To; //ensure that when editing, it's always the most recently moved turnout that is edited
bool H = Turnouts[To].V.state == HOME && (Turnouts[To].V.ourServo.read() == Turnouts[To].S.homePosition); //are we home and actually home?
bool T = Turnouts[To].V.state == THROWN && (Turnouts[To].V.ourServo.read() == Turnouts[To].S.thrownPosition);//are we thrown and actually thrown?
// Serial.print(S);Serial.print(H);Serial.println(T);
if (S && (H || T)) { //IDLE, since TT hasn't changed state and position is consistent with state
if (Turnouts[To].V.ourServo.attached()) waitForPulseEnd(To); //if we're attached, wait to ensure a pulse has been emitted
Turnouts[To].V.ourServo.detach(); //ensures movement stopped, power consumption at minimum
To = (To + 1) % NumTurnouts; //increment To; % (aka modulo) arithmetic treturns value to 0 if we get to numTurnouts, which is what's needed
}//end IDLE
else {//we are MOVING
if (moveit(To)) //returns true if move completed, i.e. if state position == position, false otherwise
serialUpdateReport(To); //emit report of turnout change
}//end MOVING
return To;
} //end of UpdateTurnout
int8_t passcount = 0; //used in case of slow movement, to track how many updates have happened between changes of position.
void waitForPulseEnd(uint8_t to) { //monitors the servo output, watching for pulse end, must only be called for a servo instance that is 'attached'.
//must wait for high, then wait for low; could potentially delay software for 20 ms, as we can't sync to the interrupt; good thing nothing else is happening!
while (!digitalRead(Turnouts[to].S.servoPin));//waits while signal low; will fall through if already high.
while (digitalRead(Turnouts[to].S.servoPin));//waits while signal high
}//end of waitForPulseEnd
bool moveit(uint8_t To) {
bool retval = false;//default reply is 'not done yet'
Turnouts[To].V.ourServo.attach(Turnouts[To].S.servoPin); //we know we're not where we're supposed to be, so ensure servo is enabled
switch (Turnouts[To].S.ServoSpeed) {
case 0: //MOVE ASAP
//move ASAP - zero indicates a move immediate to opposite side
if (Turnouts[To].V.state == HOME) {//HOME
Turnouts[To].V.ourServo.write(Turnouts[To].S.homePosition);
}//end HOME
else {//THROWN
Turnouts[To].V.ourServo.write(Turnouts[To].S.thrownPosition);
} //end THROWN
delay(abs(Turnouts[To].S.homePosition - Turnouts[To].S.thrownPosition) * 10); //delay proportional to the distance; 10 results in 500 ms for a 50 degree move
//end ASAP
break;
case MINSPEED ... -1: //move 1 degree every n pulse ends
/* pass counter value cycles between 0 and speed value, which is negative. Logic is:
if pass counter < 0, add 1 and exit
else by defn pass counter is 0, so we must calculate new position,
only if not already arrived,(this should never happen, why guard against it?)
set pass counter to speed,
wait for negative transition, and write new value
*/
if (passcount < 0) passcount = passcount + 1;
else {//can only get here if passcount = 0 and we're in negative speed
int16_t pos = Turnouts[To].V.ourServo.read(); //get current position; needs to be local variable
int16_t target = Turnouts[To].V.state == HOME ? Turnouts[To].S.homePosition : Turnouts[To].S.thrownPosition; //determine target position
if (pos != target) {
pos = pos > target ? pos - 1 : pos + 1; //calc new position
/* constrain pos to within the included limits home to thrown.
if we're going home
then if home > thrown ensure pos <= home else ensure pos >= home
else if home > thrown ensure pos >= thrown else ensure pos <= thrown
*/
// constrain(pos, min(Turnouts[To].S.homePosition, Turnouts[To].S.thrownPosition), max(Turnouts[To].S.homePosition, Turnouts[To].S.thrownPosition)); //limits position change to within range of setting
if (Turnouts[To].V.state == HOME) {//if we're going home
if (Turnouts[To].S.homePosition > Turnouts[To].S.thrownPosition) { //we're going up
if (pos > Turnouts[To].S.homePosition) pos = Turnouts[To].S.homePosition;
}
else { //going down
if (pos < Turnouts[To].S.homePosition) pos = Turnouts[To].S.homePosition;
}
}
else {//state is thrown
if (Turnouts[To].S.thrownPosition > Turnouts[To].S.homePosition) {//we're moving up
if (pos > Turnouts[To].S.thrownPosition) pos = Turnouts[To].S.thrownPosition;
}
else { //going down
if (pos < Turnouts[To].S.thrownPosition) pos = Turnouts[To].S.thrownPosition;
}
}
passcount = Turnouts[To].S.ServoSpeed; //set up passcounter for next countdown
waitForPulseEnd(To); //wait to ensure pulse has been emitted
Turnouts[To].V.ourServo.write(pos); //change position by N degrees
}
}
break;
case 1 ... MAXSPEED: //move by n degrees after every end of pulse
{ /* Calculate new position, wait until we see negative edge, then update the position by N degrees
First, we need to determine what the next value should be for position.
- read back current position - call that position
- determine where we are going based on the home/thrown status flag, and the relevant position; call that target
- know how much to add/subtract from that position - call that delta
only if not already there, (this should never happen, why guard against it?)
if target < delta, pos - delta; else pos + delta
constrain(position, min(home,thrown), max(home,thrown))
wait for negative edge, then write result
*/
int16_t pos = Turnouts[To].V.ourServo.read(); //get current position; needs to be local variable
int16_t target = Turnouts[To].V.state == HOME ? Turnouts[To].S.homePosition : Turnouts[To].S.thrownPosition; //determine target position
if (pos != target) {
pos = pos > target ? pos - Turnouts[To].S.ServoSpeed : pos + Turnouts[To].S.ServoSpeed; //calc new position
// constrain(pos, min(Turnouts[To].S.homePosition, Turnouts[To].S.thrownPosition), max(Turnouts[To].S.homePosition, Turnouts[To].S.thrownPosition)); //limits position change to within range of setting
if (Turnouts[To].V.state == HOME) {//if we're going home
if (Turnouts[To].S.homePosition > Turnouts[To].S.thrownPosition) { //we're going up
if (pos > Turnouts[To].S.homePosition) pos = Turnouts[To].S.homePosition;
}
else { //going down
if (pos < Turnouts[To].S.homePosition) pos = Turnouts[To].S.homePosition;
}
}
else {//state is thrown
if (Turnouts[To].S.thrownPosition > Turnouts[To].S.homePosition) {//we're moving up
if (pos > Turnouts[To].S.thrownPosition) pos = Turnouts[To].S.thrownPosition;
}
else { //going down
if (pos < Turnouts[To].S.thrownPosition) pos = Turnouts[To].S.thrownPosition;
}
}
waitForPulseEnd(To); //wait to ensure pulse has been emitted
Turnouts[To].V.ourServo.write(pos); //change position by N degrees
}
}
break;
default: //any non-valid speed ignored
Serial.print(F("Invalid speed selection detected: ")); Serial.println(Turnouts[To].S.ServoSpeed);
break;
}//end of switch selector
// Serial.print(F("Turnout: ")); Serial.print(To); Serial.print(F(" State: ")); Serial.print(Turnouts[To].V.state);
// Serial.print(F(" Position: ")); Serial.print(Turnouts[To].V.ourServo.read()); Serial.print(F(" (Home: ")); Serial.print(Turnouts[To].S.homePosition);
// Serial.print(F(" Thrown: ")); Serial.print(Turnouts[To].S.thrownPosition); Serial.println(F(")"));
reportTurnout(To);
if (Turnouts[To].V.state == HOME) {
if (Turnouts[To].S.homePosition == Turnouts[To].V.ourServo.read()) retval = true;
}
else {
if (Turnouts[To].S.thrownPosition == Turnouts[To].V.ourServo.read()) retval = true;
}
return retval;
}//end moveit
//
//SEC EEPROM
/* Notes about the stored content of the EEPROM:
Byte addresses 0 and 1 must contain 0xAA, 0x55 respectively, or the EEPROM is presumed to be empty. This is the "signature" referred to.
byte address 2 tells us how many Turnout entries are in the table
byte address 3 tells us how many bytes make up each Turnout entry in the table
Values for each turnout variable that must be stored are then stored sequentially.
EEPROM layout:
uint16_t signature
uint8_t sizeTable
uint8_t sizeTurnout
2d array of bytes - [tableSize}{turnoutSize]
public EEPROM functions to date:
readEEPROM() - read entire EEPROM.
readTurnout(TO) - read current Turnout from EEPROM,
writeEEPROM() - write entire EEPROM, and
writeTurnout(TO) - write current Turnout to EEPROM,
dumpEEPROM(add, len) - print, in hex, in 32 byte lines, EEPROM content starting at add, running for length bytes.
All other functions should be considered private to the EEPROM managment code.
It behooves the user to write regularly, to avoid loss of settings should a read be necessary!
*/
const uint16_t SIGNATURE = 0x55aa;
const uint16_t EEPBASE = 0; //EEPROM starts at address zero in Nano, Uno, and Mega processors
const uint16_t SIGLOC = EEPBASE;
const uint16_t TABSIZLOC = SIGLOC + sizeof(SIGNATURE);
const uint16_t ITMSIZLOC = TABSIZLOC + sizeof(NumTurnouts);
const uint16_t TABLOC = ITMSIZLOC + sizeof(NumTurnouts);
void readEEPROM() {//reads settings from EEPROM; function also will write EEPROM after a dialog, if the EEPROM did not appear to have been configured
if (checkEEPROMSignature() && getTurnoutNum() && getTurnoutSize()) {
Serial.print(F("Reading Turnout Array\n"));
readTurnoutArray();//read in the array
}
else {
Serial.print(F("Writing EEPROM\n"));
writeEEPROM();
}
}//end readEEPROM *********************************************************************************************************
bool checkEEPROMSignature() { //verifies that two byte signature is present in locations 0,1 of EEPROM
uint16_t EEPSig;
EEPROM.get(SIGLOC, EEPSig);
if (SIGNATURE == EEPSig) return true;
else {
Serial.print(F("Signature error\n"));
return false;
}
}//end checkEEPROMSignature *********************************************************************************************************
bool getTurnoutNum() { //reads stored number from EEPROM; must match NumTurnouts
uint8_t sizeTable;
EEPROM.get(TABSIZLOC, sizeTable);
if (sizeTable == NumTurnouts) return true;
else {
Serial.print(F("TurnoutNum incorrect\n"));
return false;
}
}//end getTurnoutNum *********************************************************************************************************
bool getTurnoutSize() { //reads stored number from EEPROM; must match sizeof(Turnout)
uint8_t sizeTurnout;
EEPROM.get(ITMSIZLOC, sizeTurnout);
if (sizeTurnout == sizeof(TURNOUT)) return true;
else {
Serial.print(F("TURNOUT size error\n"));
return false;
}
}//end getTurnoutSize *********************************************************************************************************
void readTurnoutArray() { //reads data into Turnouts[] based on two sizes read
for (uint8_t to = 0; to < NumTurnouts; to++) {
readTurnout(to);
}
}//end readTurnoutArray *********************************************************************************************************
void readTurnout(uint8_t to) { //called numTurnout times by readTurnoutArray()
EEPROM.put(TABLOC + to * sizeof(TURNOUT), Turnouts[to]); //perform read
}//end readTurnout *********************************************************************************************************
void writeEEPROM() { //write signature, table size, element size, and table contents to EEPROM
EEPROM.put(SIGLOC, SIGNATURE); //write sig
EEPROM.put(TABSIZLOC, NumTurnouts); //write number of elements
EEPROM.put(ITMSIZLOC, sizeof(TURNOUT)); //write number of bytes in an element
for (uint8_t to = 0; to < NumTurnouts; to++) { //write Turnouts
writeTurnout(to);
}
}//end writeEEPROM *********************************************************************************************************
void writeTurnout(uint8_t to) { //writes specified Turnout to EEPROM if loop detects a change; also called repetitively by writeEEPROM()
EEPROM.put(TABLOC + to * sizeof(TURNOUT), Turnouts[to]); //perform write
} //end writeTurnout *********************************************************************************************************
void dumpEEPROM(uint16_t start, uint16_t length) { //utility to display EEPROM content on request. Call from SERIALS
uint8_t dat;
for (uint16_t add = start; add < start + length; add++) {
if (add % 32 == 0) Serial.print(F("\n "));
dat = EEPROM.read(add);
if (dat < 16) Serial.print('0');
Serial.print(dat, HEX);
if (add % 32 != 31) Serial.print(' ');
}
Serial.println();
}//end dumpEEPROM *************************************************************************************************************