In this topic in the dutch section (Wissels en LEDs schakelen met Arduino Mega) was a similar question to which I did provide a complete software solution including explanations. If you don't understand Dutch, you can use google translate; if that does not make it clear, you can ask here and I try to explain again.
But please read the explanations in the different posts so you understand how the problem was approached.
There was a bug in the full code of post #6 which was fixed in post #17. Full fixed code at the end here.
The OP's requirement was a single button to toggle a turnout and two LEDs to show the position; OP used relays (as suggested above) but it does not matter. Your requirements might differ but it should get you started.
// macro to calculate number of elements in any type of array
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
// button between pin and GND
#define ISPRESSED LOW
// convenience
#define RELAY_ON LOW
#define RELAY_OFF !RELAY_ON
#define LED_ON HIGH
#define LED_OFF !LED_ON
// names for the state of the turnout (straight or thrown)
enum class TURNOUTSTATE
{
STRAIGHT,
THROWN,
};
// the various states for the turnout pulse function
enum class TURNOUTCONTROLSTATES
{
PULSE_ON, // start pulse
WAIT, // wait for given time
PULSE_OFF, // pulse off
COMPLETE, // action complete
};
// structure to hold related information for a button
struct BUTTON
{
const uint8_t pin; // the button pin
uint8_t debouncedState; // debounced button state
uint8_t prevDebouncedState; // previous debounced state
uint8_t lastButtonState; // internal use
uint32_t lastDebounceTime; // internal use
const uint32_t debounceDelay; // delay, internal use
};
// time that button reading must be stable before reporting its state
const uint32_t debounceDelay = 10;
// structure to hold related information for a turnout
struct TURNOUT
{
BUTTON button; // button
TURNOUTSTATE turnoutState; // state of the turnout
uint8_t coilstraightPin; // turnout straight
uint8_t coilthrowPin; // turnout throw
uint8_t ledstraightPin; // led indication straight
uint8_t ledthrownPin; // led indication thrown
TURNOUTCONTROLSTATES smState; // current state of the statemachine
uint32_t pulseStarttime; // remember the time that the pulse started
};
TURNOUT turnouts[] = {
{{2, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, 6, 7, 8, 9, TURNOUTCONTROLSTATES::COMPLETE, 0},
{{3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, A0, A1, A2, A3, TURNOUTCONTROLSTATES::COMPLETE, 0},
};
// duration (in milliseconds) of pulse to activate a turnout coil
const uint32_t turnoutPulseduration = 250;
void setup()
{
Serial.begin(115200);
Serial.print(F("Er zijn "));
Serial.print(NUMELEMENTS(turnouts));
Serial.println(F(" wissels gedefinieerd"));
// setup turnout IO
for (uint8_t cnt = 0; cnt < NUMELEMENTS(turnouts); cnt++)
{
// configure button pin for internal pullup
pinMode(turnouts[cnt].button.pin, INPUT_PULLUP);
// make sure relays will be in off position so we don't accidentally have them activated for a long time
digitalWrite(turnouts[cnt].coilstraightPin, RELAY_OFF);
digitalWrite(turnouts[cnt].coilthrowPin, RELAY_OFF);
// turnout relay pins
pinMode(turnouts[cnt].coilstraightPin, OUTPUT);
pinMode(turnouts[cnt].coilthrowPin, OUTPUT);
// led pins
pinMode(turnouts[cnt].ledstraightPin, OUTPUT);
pinMode(turnouts[cnt].ledthrownPin, OUTPUT);
// set turnout in predefined position and wait till it's complete
turnouts[cnt].smState = TURNOUTCONTROLSTATES::PULSE_ON;
while (setTurnout(turnouts[cnt], turnouts[cnt].turnoutState) == false) {}
}
}
void loop()
{
for (uint8_t cnt = 0; cnt < NUMELEMENTS(turnouts); cnt++)
{
// read the button
debounceButton(turnouts[cnt].button);
if (turnouts[cnt].button.debouncedState != turnouts[cnt].button.prevDebouncedState)
{
turnouts[cnt].button.prevDebouncedState = turnouts[cnt].button.debouncedState;
Serial.print(millis());
Serial.print(F("\t> "));
Serial.print(F("pin "));
Serial.print(turnouts[cnt].button.pin);
Serial.print(F(" "));
if (turnouts[cnt].button.debouncedState == ISPRESSED)
{
// for debugging
Serial.println(F("knop werd ingedrukt"));
// indicate to statemachine that pulse must be generated
turnouts[cnt].smState = TURNOUTCONTROLSTATES::PULSE_ON;
}
else
{
Serial.println(F("knop werd losgelaten"));
}
}
}
// control turnouts if needed
for (uint8_t cnt = 0; cnt < NUMELEMENTS(turnouts); cnt++)
{
// if a turnout needs to be changed or pulse is in progress
if (turnouts[cnt].smState != TURNOUTCONTROLSTATES::COMPLETE)
{
// set the turnout to the opopsite what it currently is
if (turnouts[cnt].turnoutState == TURNOUTSTATE::STRAIGHT)
{
setTurnout(turnouts[cnt], TURNOUTSTATE::THROWN);
}
else
{
setTurnout(turnouts[cnt], TURNOUTSTATE::STRAIGHT);
}
}
}
}
/*
debounce a button
In
reference to button to debounce
Returns:
actual button state
*/
uint8_t debounceButton(BUTTON& btn)
{
// read the state of the switch into a local variable:
byte reading = digitalRead(btn.pin);
// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited long enough
// since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != btn.lastButtonState)
{
// reset the debouncing timer
btn.lastDebounceTime = millis();
}
if ((millis() - btn.lastDebounceTime) > btn.debounceDelay)
{
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
btn.debouncedState = reading;
}
// save the reading. Next time through the loop, it'll be the lastButtonState:
btn.lastButtonState = reading;
// return the actual state of the button
return reading;
}
/*
set turnout
In:
turnout to control
new state for the turnout
Returns:
true if pulse is complete, else false
*/
bool setTurnout(TURNOUT& to, TURNOUTSTATE state)
{
switch (to.smState)
{
case TURNOUTCONTROLSTATES::PULSE_ON:
// for debugging
Serial.print(millis());
Serial.print(F("\tPulse op wissel pin "));
// switch the relay on
if (state == TURNOUTSTATE::STRAIGHT)
{
digitalWrite(to.coilstraightPin, RELAY_ON);
// for debugging
Serial.println(to.coilstraightPin);
}
else
{
digitalWrite(to.coilthrowPin, RELAY_ON);
// for debugging
Serial.println(to.coilthrowPin);
}
// bug ??
// remember the new state of the turnout
//to.turnoutState = state;
// remember the start time of the pulse
to.pulseStarttime = millis();
// go to the next step
to.smState = TURNOUTCONTROLSTATES::WAIT;
break;
case TURNOUTCONTROLSTATES::WAIT:
// if time lapsed
if (millis() - to.pulseStarttime >= turnoutPulseduration)
{
// go to the next step
to.smState = TURNOUTCONTROLSTATES::PULSE_OFF;
// for debugging
Serial.println(F("Pulse beeindigen"));
}
break;
case TURNOUTCONTROLSTATES::PULSE_OFF:
// for debugging
Serial.print(millis());
Serial.print(F("\tPulse op wissel pin "));
// switch the relay off and set the led indication
if (state == TURNOUTSTATE::STRAIGHT)
{
digitalWrite(to.coilstraightPin, RELAY_OFF);
// for debugging
Serial.print(to.coilstraightPin);
Serial.println(F(" beeindigt"));
// set the LEDs
digitalWrite(to.ledstraightPin, LED_ON);
digitalWrite(to.ledthrownPin, LED_OFF);
// for debugging
Serial.print(F("LED op pin "));
Serial.print(to.ledstraightPin);
Serial.println(F(" aan"));
Serial.print(F("LED op pin "));
Serial.print(to.ledthrownPin);
Serial.println(F(" uit"));
}
else
{
digitalWrite(to.coilthrowPin, RELAY_OFF);
// for debugging
Serial.print(to.coilthrowPin);
Serial.println(F(" beeindigt"));
digitalWrite(to.ledthrownPin, LED_ON);
digitalWrite(to.ledstraightPin, LED_OFF);
// for debugging
Serial.print(F("LED op pin "));
Serial.print(to.ledthrownPin);
Serial.println(F(" aan"));
Serial.print(F("LED op pin "));
Serial.print(to.ledstraightPin);
Serial.println(F(" uit"));
}
// remember the new state of the turnout
to.turnoutState = state;
// for debugging
Serial.println(F("wissel gezet"));
// go to next state
to.smState = TURNOUTCONTROLSTATES::COMPLETE;
// indicate pulse process is complete
return true;
break;
case TURNOUTCONTROLSTATES::COMPLETE:
// nothing to do
break;
}
// indicate pulse process is in progress
return false;
}