Running a Small Layout with an Uno

Arduino Uno R3

Arduino Uno R3

A reader on another thread had questions about running multiple turnouts on a small layout with an Uno. Primarily he wants to control turnouts with a control panel and feedback indicators.  I thought that his needs are pretty typical for small layouts, and would be a good example for others planning and building on a similar scale.

So this post is bottom-up exercise in planning and implementing single UNO control on a small layout. I hope it will be helpful in planning and building your layout.

The Requirements

The layout has 5 turnouts that will be controlled with push buttons. Control is toggle-style: each button push causes the turnout to toggle between positions. Turnouts are run by micro servos. The control panel will have a layout map with turnout push buttons and LED position indicators.

First Decisions

The UNO is the first board many of us encounter and is certainly up to the task of managing 5 turnouts and a control panel, with capacity to spare. There are other Arduino boards suitable for model railroading use; but the UNO is cost-effective and easy to work with.

That said, the issue you have to contend with eventually is the number of connections required to support all the devices and LEDS that will be attached to the microcontroller.

Arduino boards provide three different types of connections/pins:

  • Digital: digital pins are basic on/off binary connections. In output mode they are either fully on (emitting 5 volts, with a max current of 40 mA) or off. In input mode they read the incoming current as either HIGH (on) or LOW (off). When used as inputs, digital pins may need pull-up or pull-down resistors so that they will function correctly.
  • Digital PWM: some, but not all, digital pins are also capable of PWM – pulse width modulation.  The output of a PWM pin, if not fully on or off, is a series of pulses that cause the output to to on a percentage of the time.  The pulses are used to control servos and other PWM devices. PWM with LEDS lets you vary their brightness between 0 and 100%, supporting a variety of interesting lighting effects.
  • Analog: Analog pins are primarily used to read sensors that produce a variable output. Pin inputs are fed to the on-board ADC (analog-to-digital converter) for conversion to a number between 0 and 1024. Current and temperature sensors are example of analog devices read through analog pins / ADC. What you may not know about analog pins is that they can also be used as basic digital pins.

It is essential in early planning to determine all the devices to be connected to the microcontroller and the type of connection each requires to determine what connection issues you will need to resolve. In some cases, going to the UNO’s bigger cousin, the MEGA, will solve connection limits.  But even the MEGA has limits. In some cases it makes sense to use pin-multiplying techniques using external chips to drive banks of LEDS, servos or other devices, even when you otherwise have enough pins.

A Connection Plan

The UNO has 14 digital connections, numbered 0 to 13, of which 6 (pins 3, 5, 6, 9, 10, and 11 ) are PWM. It also has 6 analog connections that can be used as digital connections with digitalRead() and digitalWrite() when referenced in the sketch as A0 through A5. That is a grand total of 20 digital pins, of which 6 are PWM.

Of those, two pins are generally off limits on an UNO: digital pins 0 and 1. They are Serial RX and TX respectively, and should be avoided for other uses on any UNO where you expect to use the USB interface — that would be most of the time. On boards without the built-in USB, pins 0 and 1 are fair game.

For five servos we need five digital pins. Even though PWM is used to control servos, the Arduino servo library creates the necessary pulses on any digital pin. However, using the library disables PWM functionality on pins 9 & 10, so some of the UNO’s native PWM capacity is sacrificed in any case.

For the five turnout control buttons we need five basic digital pins. For the control panel indicators, assuming one LED for each leg of each turnout, we need 10 basic digital pins.

That means we need 20 connections; but with pins 0 & 1 reserved for the USB interface, we’re short 2 connections. Many would want to use bi-color red/green LEDS so the state of each leg is continuously shown. That would require 10 additional basic digital connections.

Need Connections? No Problem!

That’s OK. Its exactly what I expected to happen when I started in on this exercise. Both inputs and outputs can be multiplied with external chips and boards; multiplying the basic digital outputs is far and away the easiest to implement, because the circuits are simple and the compiler includes native software support without add-on libraries.

I would use shift registers to control all the control panel indicators. That reduces the connection load on the UNO to just 3 connections for all LED indicators, no matter how many you end up with. With that, and the 10 pins needed to read the buttons and control the servos, the total digital pins requirement is 13. That leaves 5 available pins, one analog and up to three digital PWM, for other uses.

There is a great tutorial on controlling LEDs with shift registers, and chaining multiple registers together, on the Arduino website. That tutorial is mandatory if you are unfamiliar with shift registers; the circuit(s) shown are what you will be using for control panel LED indicators. The balance of this post assumes the basic knowledge contained in that tutorial.

Build A Control Board

Use a shift register chain to run your panel indicators. I use a TI 74HC595 chips that I buy from DigiKey. At less than $.50 each in lots of 10, they are a bargain. $5.00 worth of chips supports 80 outputs. Its the cheapest way I know of to extend the capabilities of an Arduino and run a lot of low current devices.

Decide how many shift registers you need — the 74HC595 has 8 outputs — and create a board based on the tutorial circuits to hold the chips and provide connection points for your LEDS. A single LED for each leg of each turnout (to show which leg is open to traffic) will need 10 connections—use two shift registers (use the two-chip circuit in the tutorial). Add a third chip (after the tutorial you will know how to do that) and your connection count rises to 24, enough to support red/green bi-color LEDS on each turnout leg on the panel. With 4 connections left, who knows what else you might light up?

Each push button will need a 10kΩ pull-down resistor connecting its pin to ground.  The purpose of the pull-down resistor is to drain stray current and keep the pin LOW unless the button is pushed and full current flow is applied.  See the diagram below. The resistor should be placed as close to the pin as possible.

Servo Control by Button

Basic Servo Control by Button

Power Issues

While you can run one or two servos off the power supplied by the UNO’s +5 volt pin, more servos than that would exceed the board’s power handling capabilities. Accordingly, you will need to supply power to the servos from a separate power source. VERY IMPORTANT: the ground for the servo power source must be tied to UNO ground or the servos will not work correctly.

One Sketch To Run it All

Alright. The control panel is built and wired; the servos are installed (see this post for my latest methods) and connected to power and the UNO. What you need now is a sketch to run the control panel and the servos.

Here is a demonstration sketch to get you started:

//////////////////////////////////////////
//
// Small Layout turnout control
// Demonstration Sketch
//
//////////////////////////////////////////

#include <Servo.h> // include the Servo library

////////////////////////////////////////
//
// Definitions
//
////////////////////////////////////////

////////////////////////////////////////
// Basic parameters; adjust for your actual setup
#define NUMBER_OF_TURNOUTS 5
#define NUMBER_OF_SHIFT_REGISTERS 3
#define STEP_DELAY 70  // servo movement step delay, in milliseconds
///////////////////////////////////////

///////////////////////////////////////
// Data Structures
///////////////////////////////////////

//////////////////////////////////////
// TURNOUT_DEF holds all configuration
// information about turnouts and panel LEDS
//////////////////////////////////////
typedef struct TURNOUT_DEF {
  uint8_t button_pin; // Digital or analog pin for the button associated with this turnout
  uint8_t servo_pin; // Digital pin for the servo associated with this turnout
  int pos_main; // servo position for the MAIN leg, in degrees
  int pos_div; // servo position for the DIVERGENT leg, in degrees
  int panel_LED_main_green; // The position(s)of panel LEDS in the shift register chain
  int panel_LED_main_red; // Example assumes a bi-color (red/green) LED for each turnout leg
  int panel_LED_div_green; // modify these elements to reflect the actual LEDS you are using
  int panel_LED_div_red;
};

/////////////////////////////////////
// TURNOUT_DATA is wrapper structure holding
// both configuration and runtime data for turnout operation
/////////////////////////////////////
typedef struct TURNOUT_DATA {
  TURNOUT_DEF data; // configuration
  bool is_moving;
  byte alignment;
  int pos_now;
  int target_pos;
  unsigned long last_move;
};

// Alignment state values
#define ALIGN_NONE 0
#define ALIGN_MAIN  1
#define ALIGN_DIVERGENT 2


// pin ids for shift register chain controlling panel LEDS
#define LATCH_PIN 7
#define CLOCK_PIN 8
#define DATA_PIN 9

//////////////////////////////////////////
//
// Global variables
//
//////////////////////////////////////////

//////////////////////////////////////////
// TURNOUT_DATA Array
// * A0, A1, etc refer to analog pins which are used for buttons in this example
// * Replace pos_main (93) and pos_div (117) with real values for each turnout
// * LEDS are identified by their output position in the shift register chain;
// the identifier is a number between 0 and (NUMBER_OF_SHIFT_REGISTERS * 8) - 1. 
// Example assumes LEDS are connected to shift register outputs sequentially 
// from the first output of first register. You can connect LEDS to any output in
// any order; just set the identifiers accordingly.
//
// Only the TURNOUT_DEF part of the TURNOUT_DATA structure has to be initialized here; 
// The remaining elements are managed internally and are initialized automatically
//////////////////////////////////////////

TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = {
  {{A0, 2, 93, 117, 0, 1, 2, 3}},
  {{A1, 3, 93, 117, 4, 5, 6, 7}},
  {{A2, 4, 93, 117, 8, 9, 10, 11}},
  {{A3, 5, 93, 117, 12, 13, 14, 15}},
  {{A4, 6, 93, 117, 16, 17, 18, 19}}
};

// servo objects
Servo servos[NUMBER_OF_TURNOUTS];

// array to hold shift register state bytes
byte panel_LEDS[NUMBER_OF_SHIFT_REGISTERS];

void setup() 
{
  // Setup pins for shift register chain
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
    
  // initialize each turnout 
  for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){
    // attach the servo
    servos[i].attach(turnouts[i].data.servo_pin);
    // set the pin mode for the button pin
    pinMode(turnouts[i].data.button_pin, INPUT);
    // test and position the turnout by moving
    // to divergent then to main positions
    servos[i].write(turnouts[i].data.pos_div);
    turnouts[i].pos_now = turnouts[i].data.pos_div;
    setTurnout(i, ALIGN_MAIN);
    }
} // end of setup

void loop() 
{ 
  // get elapsed milliseconds at loop start
  unsigned long currentMillis = millis();

  // loop through the turnouts array
  for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){
    if (turnouts[i].is_moving) {
      // if sufficient time has elapsed since the last move
      if ( (currentMillis - turnouts[i].last_move) >= STEP_DELAY ) {
        // move the turnout one degree
        turnouts[i].last_move = currentMillis;
        if (turnouts[i].pos_now < turnouts[i].target_pos) { // if the new angle is higher
          servos[i].write(++turnouts[i].pos_now);
        } else {  // otherwise the new angle is equal or lower
          if (turnouts[i].pos_now != turnouts[i].target_pos) { // not already at destination
            servos[i].write(--turnouts[i].pos_now);
          }
        }
      }
      // if target position reached, stop turnout motion
      if (turnouts[i].pos_now == turnouts[i].target_pos) {
        turnouts[i].is_moving = false;
        turnouts[i].last_move = 0;
        setIndicators(i);
      }
    } else {
      // if a turnout is NOT in motion, check to see if its button is pressed
      int button_state = digitalRead(turnouts[i].data.button_pin);
      if(button_state == HIGH){
        // toggle position
        if(turnouts[i].alignment == ALIGN_MAIN){
          setTurnout(i, ALIGN_DIVERGENT);
        } else {
          setTurnout(i, ALIGN_MAIN);
        }
      }
    }
  }
}// end of main loop

////////////////////////////////////////////////////////////////
// Supporting Functions
////////////////////////////////////////////////////////////////

void setTurnout(int id, int align){
    // Set indicators to show turnout in motion
    turnouts[id].alignment = ALIGN_NONE;
    setIndicators(id);
    // Set values to trigger motion on next loop iteration
    switch(align){
        case ALIGN_MAIN:
          turnouts[id].is_moving = true;
          turnouts[id].last_move = 0;
          turnouts[id].target_pos = turnouts[id].data.pos_main;
          turnouts[id].alignment = ALIGN_MAIN;
          break;
        case ALIGN_DIVERGENT:
          turnouts[id].is_moving = true;
          turnouts[id].last_move = 0;
          turnouts[id].target_pos = turnouts[id].data.pos_div;
          turnouts[id].alignment = ALIGN_DIVERGENT;
          break;
      }
}

void setIndicators(int id){
  switch(turnouts[id].alignment){
    case ALIGN_NONE: // means the turnout is in motion and not aligned
      panelWrite(turnouts[id].data.panel_LED_main_red, HIGH);
      panelWrite(turnouts[id].data.panel_LED_main_green, LOW);
      panelWrite(turnouts[id].data.panel_LED_div_red, HIGH);
      panelWrite(turnouts[id].data.panel_LED_div_green, LOW);
      break;
    case ALIGN_MAIN:
      panelWrite(turnouts[id].data.panel_LED_div_green, LOW);
      panelWrite(turnouts[id].data.panel_LED_div_red, HIGH);
      panelWrite(turnouts[id].data.panel_LED_main_green, HIGH);
      panelWrite(turnouts[id].data.panel_LED_main_red, LOW);
      break;
    case ALIGN_DIVERGENT:
      panelWrite(turnouts[id].data.panel_LED_div_green, HIGH);
      panelWrite(turnouts[id].data.panel_LED_div_red, LOW);
      panelWrite(turnouts[id].data.panel_LED_main_green, LOW);
      panelWrite(turnouts[id].data.panel_LED_main_red, HIGH);
      break;
  }
}
/////////////////////////////////////////////////
// Shift Register Functions
/////////////////////////////////////////////////
void panelWrite(int id, byte state) {
  int reg = floor(id / 8);
  int pos = id % 8;
  bitWrite(panel_LEDS[reg], pos, state);
  panelRefresh();
}

void panelRefresh(){
  // Prepare to shift by turning off the output
  digitalWrite(LATCH_PIN, LOW);
  // shift all bits out in MSB (most significant bit first) order
  for(int i = (NUMBER_OF_SHIFT_REGISTERS - 1); i>=0; i--) {
    // shift out the bits
    shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, panel_LEDS[i]);
  }
  // turn on the output to activate
  digitalWrite(LATCH_PIN, HIGH);
}

The sketch compiles into a compact package leaving plenty of memory and resources for additional functionality.

The main loop is built around a simple multitasking model, allowing you to control task timing and balance multiple competing tasks.  In this case, the main benefit of this methodology is control of the movement rate of turnout servos. On my test loop this methodology allows block occupancy detection to work in the background along with signal logic.

UP995 at Signal 34. Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.

UP995 at Signals 3 & 4 on the Test Loop. Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.

Troubleshooting

If you build the circuits accurately, everything should work out of the box (as it were). I am an obsessive tester — so I test things at critical stages as I build them.  Build and test your shift register circuits on a breadboard first, before soldering everything to a prototyping board.

To test the sketch I set up a simulation on a breadboard.

To test the sketch for this post I set up a 3 servo simulation on a breadboard.

Getting the connections to the UNO correct is key. If the LEDs don’t light at all, check all three connections, plus power connections; LEDS are polarized, so make sure the anodes are connected to incoming power and the cathodes to ground. Tracing power flow with a multi-tester should help you find problem areas quickly.

If your LEDs light but alternately flash odd/even, you probably reversed the LATCH_PIN and CLOCK_PIN connections.

For 3 or more servos, you must have an independent power supply to power them. BE SURE TO CONNECT GROUND FROM THE SERVO POWER SUPPLY TO ARDUINO GROUND.

Do Something with Unused Outputs

Old style hard wiring, without the help of a microcontroller, is a messy business at best, which tends to limit what you can do. DCC can help bridge the gap, but layout control is something of an afterthought for DCC (it was designed for locomotive control only) and is, frankly, awkward to use (my opinion; your mileage may vary).

To get the kind of functionality we have here without Arduinos — turnout control with a synchronous control panel — it would have to be hard wired. Or you could connect a full computer and run JMRI; that would require DCC (plus stationary decoders for the turnouts and a USB->DCC interface) to work . You would have to use stall-motor type turnout motors or add microcontrollers (Peco now sells a “smart” turnout system using servos and custom controllers) to interface with servo motors. So if you want to use servos instead of stall-motors, you can go Peco or do it the Arduino way from the start. As much as I appreciate Peco’s efforts here, the openness of the Arduino platform is a big part of what makes it useful and cost-effective.

Adding another function to a small layout — lets say a signal to protect a turnout — is a matter of connecting the signal leads to unused outlets and writing some code to run it.  If you’ve gotten this far, you’ll spend far more time thinking about exactly how the signal should run in conjunction with the turnout it is protecting, than you will connecting the device and adding code to the sketch.  Once you get into this way to doing things, I guarantee you’ll never look back.

So what will it be? Signals?  A fire simulation (using PWM and LEDS)? A lighted structure? Let your imagination run wild!


UPDATE

I starting working with shift registers a while back.  They are so effective my habit is to always use shift registers with LEDs, such as for signals, structure lighting and control panel applications.  The habit is so ingrained that I forget to mention the other important reason for using them: power management.

The power handling capability of an UNO is limited to 40 mA per pin and 400 mA for the entire board. LEDs typically draw between 20 and 30 mA.  So if you are directly powering 20 LEDs from an UNO, you are exceeding its power handling capacity and will burn out the board.

In the example given in the post, no more than half the LEDs well be lit at any one time so we are unlikely to exceed the 400 mA limit overall. A few more lit LEDS, though, would put it over the top.  Shift registers duck the problem altogether because each register is powered—and supplies power to connected devices—independently.  Other than the total load on your power supply, adding devices/lights to shift registers or even adding additional registers, puts no significant power load on the UNO.

Above I mentioned that an independent power supply is needed to run the servos.  From a “best practices” perspective, one should always power external devices with an independent power supply. Arduino’s current handling limits are tight; always using external power supplies avoids that problem.


UPDATE

Several readers asked if I have a fritzing image of the 3 servo demo.  I did not, but I put one together for those who might find this helpful.

3 Servo Demo

This circuit is intended to work with the above sketch, with the turnouts variable shortened to 3 elements instead of 5:

TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = {
  {{A0, 2, 93, 117, 0, 1, 2, 3}},
  {{A1, 3, 93, 117, 4, 5, 6, 7}},
  {{A2, 4, 93, 117, 8, 9, 10, 11}}
};

Always cross check all connections on a circuit like this against appropriate data sheets before powering it up for the first time! Be sure to connect the ground of your Arduino to the ground of your external power supply.