Model railroad switch/turnout control

Good evening!

I would like to automate my model railroad with Arduino, and am looking into automatically switching the turnouts. I have a very old railroad system, so-called 'Trix Express'. This system uses electromagnetic coils to switch. See the image below:

I started reading on some switching, but I can't find the right information for me. I would like to do all the switching with 1 Arduino. The turnouts are powered by around 20V AC, and I have 11 total.

  • What would be the best way to solve this? Do I need to have two relays for each switch? Can I use transistors? And how would I wire this?

Look forward to your ideas.

Kind regards,
Sam

Your coils will also work on DC. So you can change that. Test it e.g. with the DC output of your transformer.

The hardware could (in thay case) be something as shown in https://www.gammon.com.au/motors. Replace the motor with the coil, you will need one driver per coil.

You will need a pulse to throw a turnout, you will have to figure out a proper duration of the pulse.

have a look at arduino-relay
the relays are typically rated at 250volt 10amp resistive load so should switch 20volts without problems

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

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.