Model Railroad control panel with green LEDs

Model Railroad Turnout Control Using Servos and an Arduino Nano

This project has always been something I wanted to tackle. However it was intimidating as it involved electronics I had not had any experience in yet.

However sometimes you just have to jump in with both feet and get wet.

So my first step was to order a bunch of components from Amazon:

Here is the list of components with Amazon affiliate links:

And some of the tools I used:

Once I had those I was eager to start. I decided to first tackle the crossover in front of my city street scene as well as my railcar float yard just to the right of that crossover.

First up was to test the switch I decided to use with a servo and a Nano. It’s working!

Next step was to build a control panel. I had a few hiccups along the way but the finished control panel looks really cool on my fascia and it works well.

And then it was a matter of installing the servos. This was relatively easy, other than needing to drill hole for the throw rod from beneath without damaging the already installed and ballasted turnouts!

Coding the sketch used by the Arduino Nano was at first a bit difficult. However I have a Computer Science degree and have done lots of coding in the past and even do some coding still in my current daytime job managing the computers of our clients. So in the end it worked out well. I explain how the code works in this video:

If you would like a copy of the code, here it is. You will need to modify it for your sets of turnouts as I explain in the video.

#include <Servo.h>

const int numServos = 7;    //number of servos
const int numSwitches = 6;  // number of switches (1 switch controls a crossover)

const int minAngle = 70;    //will need to figure these out - but for testing is okay
const int maxAngle = 120;   //will need to figure these out - but for testing is okay
const int throwMin = 0;     //corresponds to the switch position for mininum angle
const int throwMax = 1;     //corresponds to the switch position for maximum angle
const int servoSpeed = 60;  //delay between servo moving 1 degree, which controls the speed

//Define constants for servo names as indexes into array, also corresponds to pin numbers
const int servoFYL = 0;
const int servoFSL = 1;
const int servoFS = 2;
const int servoFT123 = 3;
const int servoFT12 = 4;
const int servoXO1a = 5;  //crossover servo, needs to be at the end
const int servoXO1b = 6;  //crossover servo, needs to be at the end

//define constants for switch names as indexes into array, add number of servos to get the pin number
const int switchFYL = 0;    //pin 7
const int switchFSL = 1;    //pin 8
const int switchFS = 2;     //pin 9
const int switchFT123 = 3;  //pin 10
const int switchFT12 = 4;   //pin 11
const int switchXO1 = 5;    //pin 12: crossover switch, needs to be at the end

Servo servoArray[numServos];
int servoPos[numServos];
int switchPinsArray[numSwitches];

void throwServo(int servoArrayIndex, int throwDirection) {

  if (servoPos[servoArrayIndex] == throwDirection) {
    return;  //exit as servo is already in position
  }

  //throws the servo in the direction given
  if (throwDirection == throwMax) {
    for (int pos = minAngle; pos <= maxAngle; pos++) {  //steps of 1 degree increasing
      servoArray[servoArrayIndex].write(pos);           // tell servo to go to position in variable 'pos'
      delay(servoSpeed);                                // waits for the servo to reach the position
    }
  } else {                                              //throwDirection == throwMin
    for (int pos = maxAngle; pos >= minAngle; pos--) {  //steps of 1 degree decreasing
      servoArray[servoArrayIndex].write(pos);           // tell servo to go to position in variable 'pos'
      delay(servoSpeed);                                // waits for the servo to reach the position
    }
  }
  servoPos[servoArrayIndex] = throwDirection;
}

void setup() {
  //initialize all switches
  for (int i = 0; (i < numSwitches); i++) {
    switchPinsArray[i] = (i + numServos);       //use next pins after servo pins
    pinMode(switchPinsArray[i], INPUT_PULLUP);  // Assign switch pin as input with pullup
  }

  //initialize all servos according to the switch positions
  for (int i = 0; i < numSwitches; i++) {
    int switchPosition = digitalRead(switchPinsArray[i]);
    if (switchPosition == throwMin) {
      servoArray[i].write(minAngle);
    } else {
      servoArray[i].write(maxAngle);
    }
    servoPos[i] = switchPosition;
    servoArray[i].attach(i);
  }

  // //special case for crossover as there is one more servo, read the switch position and move that servo the same way
  int switchPosition = digitalRead(switchPinsArray[switchXO1]);
  if (switchPosition == throwMin) {
    servoArray[servoXO1b].write(minAngle);
  } else {
    servoArray[servoXO1b].write(maxAngle);
  }
  servoPos[servoXO1b] = switchPosition;
  servoArray[servoXO1b].attach(servoXO1b);
}

void loop() {
  //check each switch for its position and throw the appropriate servo(s)
  for (int i = 0; (i < numSwitches); i++) {
    int switchPosition = digitalRead(switchPinsArray[i]);  //find out what the switch is set to
    switch (i) {
      case switchXO1:  //special case for crossovers as we need to throw two servos together
        throwServo(servoXO1a, switchPosition);
        throwServo(servoXO1b, switchPosition);
        break;
      default:
        //switch should throw servo with same array index
        throwServo(i, switchPosition);
        break;
    }
  }
}

Similar Posts