Wissels##
Het is niet altijd duidelijk of HIGH of LOW een actieve staat aangeven. Daarom gebruiken we een paar #defines
om gemakkelijk aan te kunnen geven wanneer een relay aan of uit is; hetzelfde doen we voor een LED. Je zult RELAY_ON en LED_ON moeten aanpassen aan je behoeftes. Het onderstaande gaat ervan uit dat een relais aan is als je een pin LOW maakt en dat een LED an is als je een pin HIGH maakt
// convenience
#define RELAY_ON LOW
#define RELAY_OFF !RELAY_ON
#define LED_ON HIGH
#define LED_OFF !LED_ON
We voegen ook een nieuw type variabele toe om gemakkelijk de staat van een wissel bij te houden. Een enum is niks meer dan een type dat bepaalde (numerieke) waardes bevat.
// names for the state of the turnout (straight or thrown)
enum class TURNOUTSTATE
{
STRAIGHT,
THROWN,
};
Als je de waardes print zul je zien dat STRAIGHT (rechtdoor) gelijkis aan 0 en THROWN (afbuigend) gelijkis aan 1.
We gaan weer gebruik maken van een struct om alle informatie betreffende een wissel bij elkaar te houden.
// structure to hold related information for a turnout
struct TURNOUT
{
BUTTON button; // button
uint8_t turnoutState; // state of the turnout; 0 = straight, 1 = thrown
uint8_t coilstraightPin; // turnout straight
uint8_t coilthrowPin; // turnout throw
uint8_t ledstraightPin; // led indication straight
uint8_t ledthrowPin; // led indication thrown
};
Het enige veld dat misschien uitleg behoeft is button. In plaats van een pin maken we gebruik van de eerdere BUTTON struct. Dit houdt alles netjes bij elkaar en je hoeft je geen zorgen te maken welke knop bij welk wissel hoort.
Het eerste deel van je programma (voor setup()) ziet er nu als volgt uit.
// 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,
};
// 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
};
// 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
};
// time that button reading must be stable before reporting its state
const uint32_t debounceDelay = 10;
TURNOUT turnouts[] = {
{{2, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, 6, 7, 8, 9},
{{3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, A0, A1, A2, A3},
};
Omdat button een struct is zijn er een paar extra accolades toegevoegd. Je kunt de turnouts array uitbreiden naar wens.
In setup() kun je nu all pinnen configureren.
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);
}
}
En loop() moet je nu aanpassen zodat je gebruik maakt van het wissel array (en de button die dar in zit) inplaats van het knoppen array.
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)
{
Serial.println(F("knop werd ingedrukt"));
}
else
{
Serial.println(F("knop werd losgelaten"));
}
}
}
}
Je zult ook de wissels in een gedefinieerde stand willen zetten in setup(). Daarvoor schrijven we weer een functie die we later ook gebruiken om de wissels om te zetten. Je roept deze functie aan nadat je de pinMode voor de LEDs gezet hebt in setup().
We gaan hier gebruik maken van wat een finite state machine genoemd wordt. Er zijn een paar stappen in het proces van het zetten van een wisseld die we eerst weer definieren in een enum
// 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
};
Het kan met wat minder stappen toe maar dit maakt de functie (naar mijn mening) iets gemakkelijker te vlgen.
De nieuwe functie zal de een waarde terug geven (true of false) aan de functie die de finite state machine aanroept. True geeft aan dat de besturing van het wissel compleet is en de LEDs de stand weergeven.
/*
set turnout
In:
turnout to control
new state for the turnout
Returns:
current state of process
*/
TURNOUTCONTROLSTATES setTurnout(TURNOUT &to, TURNOUTSTATE state)
{
return true;
}
En het volledige framework wordt
/*
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)
{
// current state of the statemachine; initial value PULSE_ON
static TURNOUTCONTROLSTATES smState;
switch (smState)
{
case TURNOUTCONTROLSTATES::PULSE_ON:
break;
case TURNOUTCONTROLSTATES::WAIT:
break;
case TURNOUTCONTROLSTATES::PULSE_OFF:
break;
case TURNOUTCONTROLSTATES::COMPLETE:
break;
}
return false;
}
Je kunt nu de stappen invullen.
/*
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)
{
// current state of the statemachine; initial value PULSE_ON
static TURNOUTCONTROLSTATES smState;
// remember the time that the pulse started
static uint32_t pulseStarttime;
switch (smState)
{
case TURNOUTCONTROLSTATES::PULSE_ON:
// for debugging
Serial.print(F("Pulse 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);
}
// remember the new state of the turnout
to.turnoutState = state;
// remember the start time of the pulse
pulseStarttime = millis();
// go to the next step
smState = TURNOUTCONTROLSTATES::WAIT;
break;
case TURNOUTCONTROLSTATES::WAIT:
// if time lapsed
if (millis() - pulseStarttime >= turnoutPulseduration)
{
// go to the next step
smState = TURNOUTCONTROLSTATES::PULSE_OFF;
// for debugging
Serial.println(F("Pulse beeindigen"));
}
break;
case TURNOUTCONTROLSTATES::PULSE_OFF:
// for debugging
Serial.print(F("Pulse 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.coilstraightPin);
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"));
}
// goto next state
smState = TURNOUTCONTROLSTATES::COMPLETE;
break;
case TURNOUTCONTROLSTATES::COMPLETE:
// for debugging
Serial.println(F("wissel gezet"));
// next time that we call the function, start from the beginning
smState = TURNOUTCONTROLSTATES::PULSE_ON;
// indicate pulse process is complete
return true;
break;
}
// indicate pulse process is in progress
return false;
}
En in setup() kun je deze functie op de volgende manier aanroepen.
// set turnouts in predefined position and wait till it's complete
while (setTurnout(turnouts[cnt], turnouts[cnt].turnoutState) == false) {}
Het volledige programma zoals het nu is
// 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
};
// 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
};
// time that button reading must be stable before reporting its state
const uint32_t debounceDelay = 10;
TURNOUT turnouts[] = {
{{2, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, 6, 7, 8, 9},
{{3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, A0, A1, A2, A3},
};
// 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 turnouts in predefined position and wait till it's complete
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)
{
Serial.println(F("knop werd ingedrukt"));
}
else
{
Serial.println(F("knop werd losgelaten"));
}
}
}
}
/*
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)
{
// current state of the statemachine; initial value PULSE_ON
static TURNOUTCONTROLSTATES smState;
// remember the time that the pulse started
static uint32_t pulseStarttime;
switch (smState)
{
case TURNOUTCONTROLSTATES::PULSE_ON:
// for debugging
Serial.print(F("Pulse 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);
}
// remember the new state of the turnout
to.turnoutState = state;
// remember the start time of the pulse
pulseStarttime = millis();
// go to the next step
smState = TURNOUTCONTROLSTATES::WAIT;
break;
case TURNOUTCONTROLSTATES::WAIT:
// if time lapsed
if (millis() - pulseStarttime >= turnoutPulseduration)
{
// go to the next step
smState = TURNOUTCONTROLSTATES::PULSE_OFF;
// for debugging
Serial.println(F("Pulse beeindigen"));
}
break;
case TURNOUTCONTROLSTATES::PULSE_OFF:
// for debugging
Serial.print(F("Pulse 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.coilstraightPin);
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"));
}
// goto next state
smState = TURNOUTCONTROLSTATES::COMPLETE;
break;
case TURNOUTCONTROLSTATES::COMPLETE:
// for debugging
Serial.println(F("wissel gezet"));
// next time that we call the function, start from the beginning
smState = TURNOUTCONTROLSTATES::PULSE_ON;
// indicate pulse process is complete
return true;
break;
}
// indicate pulse process is in progress
return false;
}
Nu is er een probleem; je moet wachten totdat setTurnout() compleet is voordat je wat anders kunt doen. De reden is the twee variabelen in de setTurnout() functie die niet gedeeld kunnen worden als je bv. twee wissels tegelijk wilt zetten.
Daarom zit er momenteel een while-loop in setup(). Nu is het daar niet zo'n probleem tenzij je een paar honderd wissels in een bepaalde stand moet zetten maar als je in loop() nog wat anders wilt doen zoals seinpalen besturen kan het problematisch worden.
Daarom moeten variabelen die in setTurnout() worden gebruikt verplaatst worden naar de TURNOUT struct. Dat zier er nu zo uit.
// 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},
};
Een verdere verandering is dat smState nu ge-initialiseerd wordt met COMPLETE in plaats van PULSE_ON. Het maakt het wat makkelijker om in loop() de puls te genereren; hiervoor veranderen we ook de laatste stap in setTurnout() en gaan niet terug naar de PULSE_IN stap. Je zult die eerste stap nu met de hand moeten zetten voordat je setTurnout() de eerste keer aanroept om een puls te genereren.
case TURNOUTCONTROLSTATES::COMPLETE:
// for debugging
Serial.println(F("wissel gezet"));
// indicate pulse process is complete
return true;
break;
Niet getoond in deze stap is het gebruik van de nieuwe smState en pulseStarttime; die zitten in het volledige programma hieronder.
De laatse stap in setup() kan er nu zo uit zien; deze is nog steeds blokkeren; we zullen dadelijk in loop() zien you je het op een andere manier kunt oplossen.
// prepare to generate pulse
turnouts[cnt].smState = TURNOUTCONTROLSTATES::PULSE_ON;
// set turnout in predefined position and wait till it's complete
while (setTurnout(turnouts[cnt], turnouts[cnt].turnoutState) == false) {}
En in loop() kunnen we nu op de knop reageren en de puls genereren.
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"));
// prepare smState to start pulse
turnouts[cnt].smState = TURNOUTCONTROLSTATES::PULSE_ON;
}
else
{
Serial.println(F("knop werd losgelaten"));
}
}
}
...
...
Dit genereert nog niet de puls, dat doen we met een aparte for-loop.
// 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);
}
}
}