Wissels en LEDs schakelen met Arduino Mega

Ik heb een modelspoorbaan en wil elke wissel bedienen via 1 drukknop en een indicatie van de wisselstand aangeven met 2 LEDs (1x groen = rechtdoor, 1x geel = afbuigend).
Elke keer drukken op de drukknop moet de wisselstand veranderen (dus vanuit de beginstand 1e keer drukken is afbuigend, volgende keer drukken is weer rechtdoor), de LEDs moeten met de wisselstand mee schakelen.
De wissels hebben 2 spoelen die voor het omschakelen zorgen en worden gevoed met 12V DC.
De spoel trek bij omschakelen ongeveer 1A en moet met een puls van max 0,25 sec. omschakelen.
Dit is veel teveel voor een uitgang van een Arduino en zal dus met relais moeten.
Alle onderdelen (Mega, LEDs, relais etc. heb ik).

Wat is de beste methode om dit aan te pakken?
Ik heb van alles geprobeerd maar kom er niet uit.
Als er mensen zijn die mij op het juiste spoor kan zetten, liefst met een stukje sketch zodat ik daar verder mee kan ben ik jullie eeuwig dankbaar.

Mvg, Willem.

Hoeveel trekt je relais? In alle geval moet er bij het gebruik van een relais een diode in sper over je relais gezet. Anders gaat je uitgang niet lang leven. Schakelen de wissels goed bij 12V DC? In het verleden werd er vaak 14V AC voor gebruikt. Zelf gebruikte ik 12V AC wat na gelijkrichting en afvlakking >15V = geeft.

Hoeveel knoppen wil je gebruiken als ingang? Per ingang heb je 2 uitgangen nodig. Persoonlijk zou ik eerder opteren voor een schakelaar per uitgang. Je weet dan wat er moet schakelen. Bij het starten ga je alle wissels eerst moeten in 1 bepaalde stand zetten. Dan pas ben je zeker. Je leds zullen maar aan zijn zo lang het relais wordt bekrachtigd of je moet ook daar IO voorzien.

Heb je een schema van wat je beoogt?

De relais zijn de 8-voudige relais prints welke prima door een Mega geschakeld kunnen worden.
De wissels schakelen prima bij 12 V DC (ondanks dat ze ontworpen zijn voor 14V AC), ik schakel de wissels nu met de originele momentschakelaars (Minitrix) op dezelfde 12V DC.
Ik wil een schakeltableau maken waarop de wissels getekend staan en elke getekende wissel voorzien van 1 drukknop en de 2 leds.
Ik denk dus 1 ingang (drukknop) en 4 uitgangen (2x led, 2x relais) per wissel.
Elk relais schakelt dan 1 wisselstand.
Ik heb (nog) geen schema.

Het onderstaande is waarschijnlijk veel gecompliceerder dan je verwacht had maar ik denk dat het de beste manier is om te bereiken wat je wilt. Aangezien ik niet weet hoeveel kennis je hebt, is de onderstaande uitleg zo compleet mogelijk in de hoop dat een beginner het kan begrijpen.

Deze post gaat over het lezen van de knoppen, de volgende post zal het besturen van de LEDs en de wissels behandelen. Lees het op je gemak door zodat je het kunt begrijpen.

Knoppen##

Het grootste probleem met knoppen is dat ze denderen (in het engels: bounce); als je een knop indrukt zal deze een aantal keren contact maken en als je deze loslaat gebeurt er hetzelfde. Om te voorkomen dat je valse lezingen krijgt moet je wachten todat de lezing stabiel is. Je kunt daarvoor bv de knop twee keer lezen met een korte vertraging ertussen of je kunt de knop constant lezen en iedere keer dat de lezing verander een timer starten; het onderstaande gebruikt dit en is gebaseerd op het debounce voorbeeld dat in de IDE zit. Je kunt ook een bestaande bibliotheek gebruiken als je daar de voorkeur aan geeft.

Niet aangesloten pinnen kunnen signalen opvangen uit de omgeving. Om dit to voorkomen moet je er voor zorgen dat een pin altijd een gedefinieerd niveau heeft; dit kun je bereiken met een weerstand tussen Vcc en pin (pull-up) of een weerstand tussen pin en GND (pull-down). De Mega heeft een ingebouwde pull-up weerstand die je kunt activeren en daar gaan we gebruik van maken; je ult de knop moeten aansluiten tussen de pin en GND. Het effect is dat als je een ingedrukte knop leest de waarde LOW is en niet HIGH. Onderstaande #define is een handige manier om in je programma te controleren of een knop is ingedrukt zonder je verder zorgen te moeten maken of een ingedrute knop HOOG of LAAG geeft.

Plaats het onderstaande voor setup().

// button between pin and GND
#define ISPRESSED LOW

Vervolgens combineren we alle mogelijke gegevens voor een knop in een zogenaamde struct. Een struct is zoals de gegevens in bv een telefoon boek met een aantal velden voor een naam en een telefoon nummer en mogelijk wat andere gegevens zoals een adres.
Plaats het volgende na het bovenstaande.

// 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
};

De eerste drie velden zijn relevant voor je hoofd programma, de laatste drie zijn voor intern gebruik in de functie die er voor zorgt dat er niet gereageerd wordt op het denderen vand e knoppen (in het engels: debounce). Ieder knop heeft zijn (haar?) debounceDelay; dit is niet struct noodzakelijk maar het geeft wat meer fleibiliteit als je verschillende (merken) knoppen gebruikt die niet allemaal even goed zijn. In het onderstaande wordt dat niet gebruikt (en het verspilt wat geheugen).

Je kunt nu een array maken voor je knoppen; hieronder een voorbeeld voor 4 knoppen op de pinnen 2..5. Je kunt dit uitbreiden naar wens.

// time that button reading must be stable before reporting its state
const uint32_t debounceDelay = 10;

BUTTON buttons[] = {
  {2, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {4, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {5, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
};

Je zult de debounceDelay misschien moeten aanpassen aan je knoppen; het kan zijn dat je deze langer moet maken als je knoppen langer denderen.

Om te weten hoeveel elementen er in een array zitten kun je de volgende macro gebruiken.

// macro to calculate number of elements in any type of array
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))

In setup() kun je nu de pinnen configureren; de velden van een struct kun je 'aanspreken' met een punt. Het onderstaande toont ook hoe je NUMELEMENTS kunt gebruiken.

void setup()
{
  Serial.begin(115200);
  Serial.print(F("Er zijn "));
  Serial.print(NUMELEMENTS(buttons));
  Serial.println(F(" knoppen gedefinieerd"));

  // setup buttons
  for (uint8_t cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
  {
    // button between pin and GND
    pinMode(buttons[cnt].pin, INPUT_PULLUP);
  }
}

Het volledige programma zier 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

// 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;

// all buttons
BUTTON buttons[] = {
  {2, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {4, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {5, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
};

void setup()
{
  Serial.begin(115200);
  Serial.print(F("Er zijn "));
  Serial.print(NUMELEMENTS(buttons));
  Serial.println(F(" knoppen gedefinieerd"));

  // setup buttons
  for (uint8_t cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
  {
    // button between pin and GND
    pinMode(buttons[cnt].pin, INPUT_PULLUP);
  }
}

void loop()
{
}

Je kunt nu een functie schrijven die de knop leest. Zoals gezegd, deze functie leest een pin (knop) en als de ingang veranderd is wordt een de timer gestart (lees: tijd opgeslagen dat dit gebeurde). Slecht als er gedurende een bepaalde tijd geen verandering is opgetreden wordt de ontdenderde staat van de knop opgeslagen. De functie geeft de huidige (niet ontdenderde) staat van de knop terug maar over het algemeen kun je dit negeren. De functie neemt een referentie naar een BUTTON struct die aangeeft welke knop er gelezen en ontdenderd wordt.

/*
  debounce a button
  In
    reference to button to debounce
  Returns:
    actual button state
*/
byte 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;
}

Je kunt dit bestuderen als je wilt or je kunt het nemen zoals het is.

In loop() kun je nu door het knoppen array lopen en alle knoppen lezen en actie nemen afhankelijk van de status van de knop. Onderstaand voorbeeld demonstreert; je roept debounceButton() aan met een van de elemented van je buttons array en herhaalt dat voor iedere knop in het array.

void loop()
{
  for (uint8_t cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
  {
    int state = debounceButton(buttons[cnt]);
    Serial.print(millis());
    Serial.print(F("\t> "));
    Serial.print(F("pin "));
    Serial.print(buttons[cnt].pin);
    Serial.print(F(" "));
    Serial.println(state == ISPRESSED ? "Ingedrukt" : "Niet ingedrukt");
  }

Voor je wissels wil je echter niet reageren op het feit dat een knop is ingedrukt maar op het feit dat een knop werd ingedrukt; een verandering van de (ontdenderde) staat van de knop (in het engels: state change). Het onderstaande demonstreert.

void loop()
{
 for (uint8_t cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
 {
   debounceButton(buttons[cnt]);
   if (buttons[cnt].debouncedState != buttons[cnt].prevDebouncedState)
   {
     buttons[cnt].prevDebouncedState = buttons[cnt].debouncedState;

     Serial.print(millis());
     Serial.print(F("\t> "));
     Serial.print(F("pin "));
     Serial.print(buttons[cnt].pin);
     Serial.print(F(" "));
     if (buttons[cnt].debouncedState == ISPRESSED)
     {
       Serial.println(F("knop werd ingedrukt"));
     }
     else
     {
       Serial.println(F("knop werd losgelaten"));
     }
   }
 }
}

Het volledige programma tot nu toe.

// 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

// 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;

BUTTON buttons[] = {
  {2, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {4, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
  {5, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay},
};


void setup()
{
  Serial.begin(115200);
  Serial.print(F("Er zijn "));
  Serial.print(NUMELEMENTS(buttons));
  Serial.println(F(" knoppen gedefinieerd"));

  // setup buttons
  for (uint8_t cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
  {
    // button between pin and GND
    pinMode(buttons[cnt].pin, INPUT_PULLUP);
  }
}

void loop()
{
  for (uint8_t cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
  {
    // read the button
    debounceButton(buttons[cnt]);
    if (buttons[cnt].debouncedState != buttons[cnt].prevDebouncedState)
    {
      buttons[cnt].prevDebouncedState = buttons[cnt].debouncedState;

      Serial.print(millis());
      Serial.print(F("\t> "));
      Serial.print(F("pin "));
      Serial.print(buttons[cnt].pin);
      Serial.print(F(" "));
      if (buttons[cnt].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;
}

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);
      }
    }
  }

Volledig programma voor twee knoppen / wissels. Het programma is op zo'n manier geschreven dat het andere zaken die je wilt doen niet blokkeert; om het goed te laten werken mag je in de rest van je programma de delay() functie niet gebruiken omdat dat de duur van de puls langer kan maken en je wisselspoelen kunnen doorbranden !!

Ik heb je setup niet dus kan alleen testen met de seriele monitor.

// 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);
      }
      // 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.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"));
      }
      // go to next state
      to.smState = TURNOUTCONTROLSTATES::COMPLETE;
      break;
    case TURNOUTCONTROLSTATES::COMPLETE:
      // for debugging
      Serial.println(F("wissel gezet"));
      // indicate pulse process is complete
      return true;
      break;
  }

  // indicate pulse process is in progress
  return false;
}

De typisch output in een terminal programma zoals Serial Monitor

Er zijn 2 wissels gedefinieerd
0	Pulse op wissel pin 6
Pulse beeindigen
250	Pulse op wissel pin 6 beeindigt
LED op pin 8 aan
LED op pin 9 uit
wissel gezet
253	Pulse op wissel pin 14
Pulse beeindigen
506	Pulse op wissel pin 14 beeindigt
LED op pin 16 aan
LED op pin 17 uit
wissel gezet
10434	> pin 2 knop werd ingedrukt
10434	Pulse op wissel pin 7
10553	> pin 2 knop werd losgelaten
Pulse beeindigen
10684	Pulse op wissel pin 6 beeindigt
LED op pin 8 aan
LED op pin 9 uit
14277	> pin 2 knop werd ingedrukt
14277	Pulse op wissel pin 6
14448	> pin 2 knop werd losgelaten
Pulse beeindigen
14528	Pulse op wissel pin 6 beeindigt
LED op pin 9 aan
LED op pin 8 uit
17501	> pin 3 knop werd ingedrukt
17501	Pulse op wissel pin 15
17664	> pin 3 knop werd losgelaten
Pulse beeindigen
17752	Pulse op wissel pin 14 beeindigt
LED op pin 16 aan
LED op pin 17 uit

Als je vragen hebt, laat het maar horen.

Bedankt voor je uitgebreide beschrijving.
Ik zit nu thuis met corona, zodra ik me beter voel ga ik ermee aan de slag en laat de resultaten hier weten.

Mvg, Willem

Met 80 IO kan je dus 16 wissels schakelen. Dan gaan er ook 16 ledjes aan zijn. Hoeveel mA zijn ze? Bij 20mA zit je ver boven de 200mA die in totaal bij de ATmega2560 is toegestaan. Je zal dan moeten gebruik maken van transistoren of ULN2803 (schakelt naar GND). Zijn ze 2mA dan kan je met een uitgang minder per wissel. Ene led (met weerstand) tussen +5V en IO en andere led (met weerstand) naar GND. Maak je de IO laag, gaat de led naar 5V aan en schakel je hoog, dan gaat de led naar GND aan.

1 Like

Goede waarschuwing :+1:

Ik heb eea aangesloten maar krijg het volgende te zien op de SerialMonitor:
LEDs
LED's op pin 57 en 58? Een Mega heeft 54 pinnen.

Waar sluit ik de LEDs op aan?

Laat je programma maar zien; in principe heb je die pin nummers zelf in het onderstaande array gezet; iets in deze lijn:

TURNOUT turnouts[] = {
  {{3, !ISPRESSED, !ISPRESSED, !ISPRESSED, 0, debounceDelay}, TURNOUTSTATE::STRAIGHT, 54, 55, 56, 57, TURNOUTCONTROLSTATES::COMPLETE, 0},
};

Bekijk je deze pinout dan is A3 D57 en A4 is D58. Een 2560 is ook 100 pins en heeft 86 IO. Welke pennen zitten op de dwarse header?

Al gevonden, A0, A1, A2 en A3 zaten 1 opgeschoven :frowning:
(dus A1, A2, A3 en A4).

Heb nog wel 1 probleem: de relais schakelen om maar vallen niet af na 250mS.

Elk relais heeft een middenaansluiting (waar ik de 12V op aansluit voor het schakelen van de wissel) en een NO en NC contact.
Ik wil de ene spoel van de wissel aansluiten op het NO contact van het 1e relais en de andere spoel van de wissel op het NO contact van het 2e relais.

Dit is de volledige sketch:

// 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);
      }
      // 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.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"));
      }
      // go to next state
      to.smState = TURNOUTCONTROLSTATES::COMPLETE;
      break;
    case TURNOUTCONTROLSTATES::COMPLETE:
      // for debugging
      Serial.println(F("wissel gezet"));
      // indicate pulse process is complete
      return true;
      break;
  }

  // indicate pulse process is in progress
  return false;
}

Staat in de uitleg van sterretje in post #5.

Wat later wordt dit in post #6

Waar de & aan de TURNOUT is geplakt ipv &to. Jou programma is een copy paste van #6. Wijzig dit eens naar het voorbeeld in #4.

Ik zie dat er een kleine copy/paste fout in zit; mijn fout.

        digitalWrite(to.coilthrowPin, RELAY_OFF);
        // for debugging
        Serial.print(to.coilstraightPin);  <<<<<<<<<<<<
        Serial.println(F(" beeindigt"));

Dat moet to.coilthrowPin zijn :frowning: Het verklaart niet waarom je wisselspoel actief blijft.

Ik denk dat ik de bug gevonden heb; het onderstaande zit op de verkeerde plek.

      // remember the new state of the turnout
      to.turnoutState = state;

Als een resultaat wordt de verkeerde spoel uitgeschakeld (en die was all niet actief) ; sorry daarvoor.

Er zit nog een ander foutje in; je zou iedere keer dat je een wissel omgooit de melding "wissel gezet" moeten krijgen en dat gebeurt niet.

De herziene versie van setTurnout()

/*
  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;
}

Op seriele monitor:

88535 > pin 3 knop werd ingedrukt
88535 Pulse op wissel pin 15
88719 > pin 3 knop werd losgelaten
Pulse beeindigen
88786 Pulse op wissel pin 15 beeindigt
LED op pin 17 aan
LED op pin 16 uit
wissel gezet

En na een tweede druk op dezelfde knop

221802 > pin 3 knop werd ingedrukt
221802 Pulse op wissel pin 14
221988 > pin 3 knop werd losgelaten
Pulse beeindigen
222053 Pulse op wissel pin 14 beeindigt
LED op pin 16 aan
LED op pin 17 uit
wissel gezet

Ik denk dat dat het is; ik hoop dat je geen wisselspoel doorgebrand hebt.

Bedankt weer voor je hulp.
Als ik morgenmiddag tijd heb zal ik eea aanpassen en testen.
En, nee, ik heb geen wisselspoelen verbrand, ik kwam er achter met een multimeter.

:+1:

Normaal zou het geen probleem mogen zijn. In de wissel zitten normaal 'eindcontactjes'. Een contactje schuift over een printbaantje. In de eindstand is er geen printbaantje meer en valt dus de spanning weg over het betreffende spoeltje. Wordt de eindstand niet bereikt is het een ander verhaal. Daarom toch best afschakelen.