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.

24 thoughts on “Running a Small Layout with an Uno

  1. Hi Robin, Thank you for such an excellent & informative site, is it possible to download (or e-mail me) a larger image of your “Test sketch for 3 Servo’s on a breadboard ” on thenscaler/?p=942 . Many thanks Terry

  2. Hi Robin, thank you for a very informative article’ I am very new to Arduino but learning every day, I hope you dont mind my asking but have you got a Fritzing image of “To test the sketch for this post I set up a 3 servo simulation on a breadboard”, that you could post or email me. Regards Tony

  3. Hi Robin, I have read your article with great interest and have tried out your code to control 3 servos attached to an Uno using 3 push button switches. I especially like the way the program doesn’t have to wait until finishing a move before it starts another – very neat. Do you have any suggestions for integrating this with a pwm driver card over I2C. I would like to use one or more 16 channel driver cards based on the pca9685 (such as the Adafruit 16-channel-pwm-servo-driver) to control all the turnout servos on my layout. I would like to have a mimic panel with push buttons and leds with shift in registers for the buttons and shift out registers for the led indicators. I can see how to integrate the buttons and leds into the code but I’m struggling with how to control the servos. The driver uses the Adafruit_PWMServoDriver.h Library and not the Servo.h Library. The way that the commands are sent to the pwm driver to control servo position are in the form of setPWM(channel, on, off), this function sets the start (on) and end (off) of the high segment of the PWM pulse on a specific channel. You specify the ‘tick’ value between 0..4095 when the signal will turn on, and when it will turn off. Channel indicates which of the 16 PWM outputs should be updated with the new values. I have dialled in the appropriate servomin and servomax pulse lengths I need using the adafruit driver example sketches. My servomin and servo max postions will relate to pos_main and pos_div in your example.

    Using the Adafruit driver, one does not attach a servo to a particular pin. In your example your TURNOUT_DATA array associates a particular button with a particular servo e.g. Button A0 controls the servo on pin 2. So I’m guessing I don’t need include the digital pin number in the array since this is only used to attach the servos to the pins – right?

    Since the Adafruit driver doesn’t use the write command, I’m unsure how to incorporate the pwmset command and its arguments into the “loop through the turnouts array” section of the main loop.

    Do you have any pointers or can you point me in the direction of someone who may have already done something similar to what I’m trying to achieve?

    Any help would be appreciated.

    Regards, Rick

    • Hi Rick,

      As it happens I recently purchased and installed the Adafruit 16 Channel PWM board on the section of the layout currently under construction. What I like about the board is 1) it takes care of all PWM maintenance, freeing the microcontroller to do other things; 2) each port is individually addressable; 3) it uses the shared I2C bus and does not consume any pins; and 4) You can chain multiple boards together to expand the system.

      While the Adafruit library for the board is different from the standard servo library, its pretty easy to adapt. The core issue in adapting it to sketches written for the standard servo library is deciding whether to express the desired position of a servo in degrees of arc (as you would with the standard servo library), or in the pulse length. If you stick to degrees (which I am doing for now), then the old call to servo.write() is converted to two lines:

      pulselen = map(angle, 0, 180, SERVOMIN, SERVOMAX);
      pwm.setPWM(servonum, 0, pulselen);

      The first line maps the angle (which should be between 0 and 180 degrees), to a pulse length between SERVOMIN and SERVOMAX. Here I’m using the SERVOMIN and SERVOMAX as limiting terms so that we don’t try to set the servo outside its legal range (I think that is what the developers of the library had in mind). Since I use servos in the middle of their travel range–typically between 60 degrees and 110 degrees–I have not found it necessary to individually adjust the travel limits of servos; so I stick to Adafruit’s suggested default values. The second line sets the pulse length that was calculated by the map() function.

      You can use the TURNOUT_DATA structure exactly as shown with one change — use the servo_pin element to represent the port on the board where the servo is attached, instead of using it to represent a microcontroller pin. Change the name to servo_port if like, and use it in your call to setPWM(), as you iterate through the turnouts array:

      // 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
      pulselen = map(++turnouts[i].pos_now, 0, 180, SERVOMIN, SERVOMAX);
      pwm.setPWM(turnouts[i].data.servo_port, 0, pulselen);

      } else { // otherwise the new angle is equal or lower
      if (turnouts[i].pos_now != turnouts[i].target_pos) { // not already at destination
      pulselen = map(--turnouts[i].pos_now, 0, 180, SERVOMIN, SERVOMAX);
      pwm.setPWM(turnouts[i].data.servo_port, 0, pulselen);

      }
      }
      } ... continuing as in the original

      The only other change I can think of is elimination of the pin attachment line in setup, since the board can be initialized staticly outside of setup and the loop, as Adafruit does in their example sketch. So with these few changes you should be able to adapt the demo sketch to your board.

      If you want to eliminate the map() step, you can save target position information (pos_main, pos_div) as a pulse length, instead of degrees, and move the servo in the loop with one line like this:

      pwm.setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now);

      The advantage of going that route is that you can move the servo by changing the pulse length one unit at a time — which should result in very smooth, fluid motion by the servo. I suspect that is the optimum way to go.

      I hope this helps. I’ll be blogging about this board as I refine my implementation, so stay tuned. Best, Robin

      • Robin, top man. That’s extremely helpful. I’m fairly new to messing about with Arduinos and coding and I knew that there had to be a simple elegant solution. I was trying to work it out in my head and got stuck in a loop somewhere. I’ve started to read up on how to code in C so that I can understand how the various functions, commands and data arrays etc. all fit together. Up to now I’ve been looking at other people’s sketches and trying to figure out what’s going on and trying to adapt them to my needs without a basic understanding of the language. So, I’ve ‘gone back to school’ if you like, to be able to understand why the code is written the way it is and so on. I’m a building physics consulting engineer by profession and my background is more mechanically biased. However, there is a small part of my brain that thinks it understands electronics and it’s trying to burst out and lend a hand with limited success. At least I haven’t blown anything up yet, although I did damage one of my servos slightly when I first fired up the servo driver with the example sketch without adjusting the pulse lengths and hadn’t switched my power supply from 12V to 5V. Luckily, when I took it apart the only thing broken was one of the small end stops which is set just beyond the normal 180 deg of travel to stop you mashing the gears. Reassembled it and it worked fine. I just need to remember not to drive it right to the end.

        I really appreciate your input and look forward to reading your blog when you’ve implemented your system. I’ll let you know how I get on with my set up. Eventually I will have about 30 turnouts with servo control so I’ll chain at least two 16 channel boards with maybe a third for other moving accessories on the layout.

        Regards, Rick

          • Hi Robin, well I had a go and integrated the code and it works great. I used the pulse length to define the pos_main and pos_div positions and the servos move very smoothly indeed. To move the servos I used the following:

            pwm.setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now); //and,

            pwm.setPWM(turnouts[i].data.servo_port, 0, –turnouts[i].pos_now); //to drive them back the other way

            One thing I found was that the servos were a lot slower than before so I decreased the STEP_DELAY to about 30 ms and that was about right for me, bearing in mind that I model in OO gauge here in the UK so the turnout blades need to move further than in n gauge.

            Next step – shift in registers.

            Regards, Rick

          • Excellent. It makes sense that the servo would run slower when using pulse length to position them, so I’m not surprised you needed to adjust the delay.

            Good luck with shift registers. I consider them a basic building block and could not imagine building an Arduino-controlled layout without them. If you need to control common anode devices, or high current devices, put a Darlington Driver chip between the shift register and the devices you are controlling – I use the ULN2803A chip from STMicroelectronics with my 5 volt shift registers.

            Best, Robin

  4. Robin, thanks for the tip. At the moment the shift registers will be just for button/switch inputs and indicator outputs but who knows where things will go. After much head scratching I managed to work out how to write and integrate the code to capture the button presses via the shift in register(s). I was especially impressed by the way the shift register/pin combination is addressed using the Floor and modulo functions (once I’d worked out what was going on with the help of a spreadsheet). I uploaded the software and set it to work. I only had 2 servos and 2 push buttons to hand but 3 sets of leds and initially button 1 didn’t do anything, button 2 was controlling servo 1 and servo 2 was wagging its horn back and forth. I checked my connections and then realised that I should have started the button numbers at 1 and not 0 in the TURNOUT_DATA array. This fixed the issue and buttons 1 and 2 controlled servos 1 and 2, respectively. The wagging servo horn had bothered me until I realised that I needed to ground out the unused input pins on the register. I plugged one of the servos into port 3 on the driver card and sure enough no more wagging and led indicating pos_main.

    One thing that I had to modify is that I had to declare all the shift register functions before the setup and main loop code for them to be able to be called. The other supporting functions were able to stay where they were. This puzzled me as functions should be able to be declared before or after but a quick scan of the forums reassured me that I was not the only one to have similar issues.

    My next step is to modify the code to accept multiple servo driver cards pwm1, pwm2 etc. I would like to have the pwm(n) address in the TURNOUT_DATA array and then reference it in the pwm.setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now); command. However, I tried that in the form of pwm(turnouts[i].data.pwm_adr).setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now); and the compiler didn’t like that and told me that the “‘pwm’ function not declared in scope”. I tried various ways of declaring it but couldn’t make it work. Looks like I’ll need to go back to head scratching for a while. I want to try to avoid adding multiple if else statements (and repeating code) to the main loop dependent on the turnout number to point at the corresponding pwm number.

    Oh the joys of tinkering.

    Regards, Rick

    • Instead of using shift-in, consider using a 4051 mux/demux IC for managing and polling multiple inputs. Here some basic info from the Arduino Playground: http://playground.arduino.cc/Learning/4051.

      Why go that route? Its incredibly simple and there is no code overhead (no shiftin, shiftout or other specialized functions required). A 4051 mux/demux is basically an electronic version of a rotary switch. You use a 3 bit address (binary 0 to 7) to set the switch to point to a particular input/output then read or write to it as if the ultimate input/output were directly attached. So, assuming digital pins 2, 3, and 4 are the addressing pins (4 being the most significant bit), and pin 5 is the shared I/O connection to the 4051, the code to poll 8 buttons would look something like this:

      for(int i = 0; i < 8; i++){ // set the address digitalwrite(2, bitRead(i, 0)); digitalwrite(3, bitRead(i, 1)); digitalwrite(4, bitRead(i, 2)); // read it int buttonstate = digitalread(5); // do something with the button state }

      The switching process is, for all intents and purposes, instant so performance is excellent. Multiple 4051's can be stacked in an array for more complex switching operations. I am using 4051's to create switching fabric for accessing 24 CT current sensors in the high density part of my layout currently under construction.

      As to the issue of function declarations, the Arduino IDE employs a relaxed syntax that does not require you to declare your functions in any particular place. However, if the compiler is confused by some aspect of the code, it will behave as if a formal declaration is missing. I've seen this behavior arise where there are unbalanced braces or missing semicolons -- but that is rare, since a more concrete error is usually flagged. One situation can arise, however, where the compiler will ALWAYS be confused with function definitions--- and that is where your function is redefining a library function. This happens if you give a function the same name as a library function. In that case, when the compiler encounters the function being used, it doesn't know which function definition to apply unless the function was previously declared to override the library function. So that is what I would look for if that particular error arises. Also, make sure your function calls include all required arguments, or that could confuse the compiler as well.

      Best, Robin

    • I let your issue with multiple driver cards percolate a bit.

      Try this-

      Create a static array of driver objects before setup like this:

      Adafruit_PWMServoDriver pwm[3];

      In setup, initialize the drivers this way (assuming you’ve set the addresses with solder jumpers sequentially):

      pwm[0] = Adafruit_PWMServoDriver(0x40);
      pwm[0].begin();
      pwm[1] = Adafruit_PWMServoDriver(0x41);
      pwm[1].begin();
      pwm[2] = Adafruit_PWMServoDriver(0x42);
      pwm[2].begin();

      Finally, store the array index of the applicable driver card (a number between 0 and 2) in the TURNOUT_DATA structure

      turnouts[i].data.pwm_adr = 0; // for the first driver card

      Now you should be able to control it this way:

      pwm[turnouts[i].data.pwm_adr].setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now);

      This could also be done with pointers rather than an array, but I think this is simpler.

      Let me know if this helps. R.

      • Robin, yep that fixed it, brilliant. Many thanks. Simple and elegant as usual.

        With reference to the shift in registers for capturing button presses. I’m quite happy with the way I’ve got it working with the CD4021 using the following code for the shift in register functions:

        void buttonRefresh() {
        //Pulse the latch pin:
        //set it to 1 to collect parallel data
        digitalWrite(SHI_LATCH_PIN, HIGH);
        delayMicroseconds(20);
        //set it to 0 to transmit data serially
        digitalWrite(SHI_LATCH_PIN, LOW);
        // shift all bits in, in MSB (most significant bit first) order
        for(int i = (NUMBER_OF_SHIFT_IN_REGISTERS – 1); i>=0; i–) {
        //shift in the bits
        panel_buttons[i] = shiftIn(SHI_DATA_PIN, SHI_CLOCK_PIN, MSBFIRST);
        }
        }

        int panelRead(int id){
        buttonRefresh();
        int breg = floor(id / 8);
        int bpos = id % 8;
        int bstate;
        bstate = bitRead(panel_buttons[breg], bpos);
        return bstate;
        }

        I will however have a play with the 4051 mux/demux IC to see how it goes.

        Regards, Rick

        • That looks good to me. The 4051 is just another way to go about it. Since button/switch states are either high or low, that works well with a digital shift register. The 4051 can do the same things (albeit differently) — but really comes into its own when multiplexing analog sensors. R

  5. Thank you, your article is very helpfull.

    Note: The Fritzing image of the 3 servo demo does not show the Gnd connection of the servos power source to the UNO Gnd

      • Hi Robin,

        I am very grateful for your sketch and explanations. I have successfully applied this to my own project with some minor modifications.

        However I an unable to modify the case ALIGN_NONE. Instead of a steady red LED while the turnout is moving, I would prefer flashing red LEDs.

        I considered applying the ‘Blink_without_Delay’ sketch but I can’t do it.

        What would you kindly suggest?

        • That’s an interesting idea. Fortunately the main loop is a timing loop — like blink_with_delay — so it can be done.

          Think of this as a repeat of the basic turnout motion problem — you have an array of turnout data that you step through and if a turnout is in motion and sufficient time has elapsed, you move it the next increment. Blinking panel indicators can be handled similarly; you’ll need to keep track of the timing and run through the panel data the same way you are handling turnout motion.

          To make it work, you would set the turnout alignment to ALIGN_NONE when motion starts, then would change the alignment to MAIN or DIVERGENT after the motion is completed. My sketch does not work that way – it uses ALIGN_NONE only briefly to set (then forget) the panel indicators. You would need to add a panel_state (HIGH/LOW) & panel_last_state_change (unsigned long — millis() at last state change) elements to the turnout array.

          Instead of trying to handle panel indicator blinking at the same time as you are handling motion, do a second pass through the turnouts array after the motion pass completes. This time you look for any turnout with alignment ALIGN_NONE, and if sufficient time has elapsed flip the state of the associated indicator.

          Anyway, that’s my quick take on your issue. R.

          • Hello Robin,

            Are you able to send me your sketch on how to handle multiple current sensors and signals at the same time without using the delay? I am at that point now. Thanks! – Scott

          • Hi Scott,

            I’ve been trying to decide how best to help. There is a companion post http://thenscaler.com/?p=971 that talks about my signals framework, but doesn’t go into multi-tasking. For an introduction to my multitasking technique, see http://thenscaler.com/?page_id=661.

            I’ve just started a github repository https://github.com/rpsimonds/thenscaler for sharing code. At the moment I have not posted what you need.

            The code that I have that would tie it all together for you is the test loop code. However, I need to clean it up a bit before I share it. I will post an additional reply when the code is posted.

            Best, Robin

    • I have placed the testloop code in the Testloop branch of my github repository: https://github.com/rpsimonds/thenscaler/tree/Testloop.

      This is “intermediate” stage code in that it has been cleaned up and is lightly documented. Its not plug-and-play as a whole for obvious reasons (it should compile so long as you have the necessary libraries installed), but internal functions such as current sensing functions are readily transferable to other sketches. What should be fairly easy to discern overall is the structure of the code and my solutions to basic problems.

      Let me know if you have any questions. R.

Leave a Reply

Your email address will not be published. Required fields are marked *