Programming Servo control for Model Railroad Turnouts

Hi there, I am new to Arduino programming and have found a sketch that does what I need to switch various servo motors connected to various turnouts (sets of points) on my model railroad layout.

In the attached sketch I want to be able to set individually the close pos and open pos in degrees for each servo attached to the servoPins, but at this stage of my knowledge, I dont know how to do it.

Could anybody help me or point me in the right direction?

Rob

TrainServos8.ino (3.07 KB)

Oh yes - you bet :)

Wait a few seconds until I find my old thread --

Sure, just make a composite data type (struct) that contains all the information for one servo. Then create an array of those.

Here it is : Detaching servos after swich position

Its actually Paul DMV that must be credited for the design idea.

I just put the the code into arrays, and did some debugging, to make the detaching work. The idea using detaching of the servos is to let them "float", by removing the servo pulse. They will stay in their position, but they can of course be moved, if you apply external torque.

The big advantage is, that they dont rattle, while they are not pulsed in their reached position.

Thank you for responses so far. As I said, very new to this - how do I set up this array?

Rob

roboz6: Thank you for responses so far. As I said, very new to this - how do I set up this array?

Rob

just download the code from the link I point to. Then read it, and take it from there ;)

@Anders53

I have had a quick browse at the link you kindly pointed to and will have an in depth look and play over the next few days, and come back later.

Again thanks for pointers so far.

Rob

@Anders53

Further to my last post. Had a look at and test ran the code in the last part of the thread you pointed me to and it seems to do everything I want, except I would like a centering function for the servos when you are installing them. I am sure that could be added to the code. How would you go about that?

Rob

Why would you center the servos ?

The code I points to, reads the control switches from the track layout turnouts, to get the actual position of the turnouts from the layout. THEN the servos are attached. Exactly to avoid, that the turnout servos flips back and forth to find a defined position, once you power up your Arduino.

Thats a very important feature.

You could insert the feature from the other sketch, where you are using an extra input with an "override" control switch, which holds all the servos in mid position. Then toggling this switch back to rest postion, the servos are "released" to normal action.

Please note : The code in the sketch uses the term "switch" for a turnout. The term "button" is used for the control switch, which controls the position of the turnout. It can make some confusion for native Shakespear language speaking people 8)

It is assumed that the control switch stays in position, until the turnout must be shifted to the opposite position. The control switch can be either a change over toggle switch or a change over relay contact. Both must switch between GND and +5V levels to indicate the wanted position of the turnout.

@Anders53

Obviously haven't made myself clear. When you are installing the servo motor under the turnout, it is best to set the turnout in the center position, then you would bring up the wire pivot from the servo the arm of which would also be centered. It would make it easier to install the servos to the turnouts that way.

I already have a simple sketch which I can load and center the servo/s - that way I ensure that the servo horn is where I want it to be before I try and install it. But there is no trouble doing this as a separate step then loading and running the other program.

Does any of that make sense to you?

Rob

Yep - now I understand.

Here is the modified code.
Please read and follow what is going on in the simple code.
Then you quickly learn how to modify turnout servo parameters.
Have fun !

/*
 Copyright2015 - SERVO SWITCH CONTROL - SSC - PaulDMV
 Modified 2015 - using arrays - Anders53
 Modified 09-04-2015 - code reduced heavily - by Septillion
 Modified 13-04-2015 - added override to mid position - by Anders53

 A maximum of 11 servo objects can be installed on the nano
 A maximum of eight servo objects can be created from library
 Only change constants (speed, range_low and range_high)
 include the VarSpeedServo.h Library before compiling !

 Define arduino pin usage, which are constants :
 Note that arduino analog pins can be defined as digtial pins
 making for the maximum of 8 servo objects in the library
 Arduino pins 0 & 1 are here reserved for any serial communication
 i.e. for debugging or future inter comm between arduino units
 The pin numbers are those found printed on the arduino board
 They should apply to all Arduino Uno, Nano & Micro varaints
 but can of course be re-arranged to suit the extra A6 & A7 pins
 found on later revisions of Uno & Nano derivatives.
 
 An override toggle switch can be added to input pin 12.
 A LOW (GND) level on this pin will set all servo channels to
 mid position 90° for installation of the servos.
 If the input is left open, the internal PULL_UP will set the 
 input high, which disables the mid position feature.
 
 The internal led is set to blink every time loop() is
 traversed, to indicate activity.
 
*/

#include <VarSpeedServo.h>
const byte NumberServos        =     8;        // the total number of servos
const unsigned long DetachTime =     10000;    // detach servo after 10 seconds
const byte LedLightPin         =     13;       // internal activity LED is on pin 13
const byte TestProbePin        =     12;       // input pin for midpoint setting
byte i                         =     0;        // Loop counter
boolean HoldReadVal, TestProbe =     0;        // input read varaiables
boolean LedState               =     0;        // For toggling the LED

// arrays holding all indexable constants & variables
VarSpeedServo     Myservos[NumberServos];      // Servo objects
unsigned long DetachMillis[NumberServos];      // Detach timer for each servo object
boolean        SwitchState[NumberServos];      // Holds the status for each switch

// Servo nr                    =    {0,  1,  2,  3,  4,  5,  6,  7 }
const byte ButtonPins[]        =    {A0, A1, A2, A3, A4, A5, 2,  3 };
const byte ServoPins[]         =    {4,  5,  6,  7,  8,  9,  10, 11};
const byte RangeLow[]          =    {85, 85, 85, 85, 85, 85, 85, 85};
const byte RangeHigh[]         =    {95, 95, 95, 95, 95, 95, 95, 95};
const byte TravelSpeed[]       =    {2,  2,  2,  2,  2,  2,  2,  2 };

void setup()
{
  pinMode(LedLightPin, OUTPUT);                       // define internal LED output
  pinMode(TestProbePin, INPUT_PULLUP);                // define digital test probe input
  for (i = 0; i < NumberServos; i++)
  {
    pinMode(ServoPins[i], OUTPUT);                    // Define digital pin as output
    pinMode(ButtonPins[i], INPUT_PULLUP);             // Define digital input pin with pull up resistor for noise reduction
    SwitchState[i] = digitalRead(ButtonPins[i]);      // Read the current switch state from the track layout
    if(SwitchState[i])                                // If switch state is high, pre-set servo control accordingly
    {
      Myservos[i].write(RangeHigh[i]);
    }
    else                                              // Switch state is low, pre-set servo control accordingly
    {
      Myservos[i].write(RangeLow[i]);
    }
    Myservos[i].attach(ServoPins[i]);	              // Only after pre-setting the position control, it's safe to attach the servo
  }                                                   // The servo will detach in the loop automatically after 10 seconds if no activity   
}

void loop() // runs forever to detect control buttons and change switches accordingly
{
  digitalWrite(LedLightPin, LedState); 
  LedState = !LedState;
  for (i = 0; i < NumberServos; i++)
  {
    TestProbe = digitalRead(TestProbePin);                     // Read midpoint setting switch input
    if (TestProbe == LOW)                                      // ### Is midpoint switch active LOW ?
    {
      Myservos[i].slowmove(90, 5);                             // ### Yes, midpoint active, set servos to midpoint
    }
    else
    {                                                          // ### No, midpoint idle, control the servos
      HoldReadVal = digitalRead(ButtonPins[i]);                // Read the button state
      if (HoldReadVal != SwitchState[i])                       // *** Has the switch state changed by the button ?
      {
        SwitchState[i] = !SwitchState[i];                      // *** Yes the switch state has changed, save new switch state
        DetachMillis[i] = millis();                            // Initialise the detach timer
        Myservos[i].attach(ServoPins[i]);                      // Attaches the servo on output pin to its servo object
        if (SwitchState[i])                                    // Is the new switch state high (greater than 0) ?
        {
          Myservos[i].slowmove(RangeHigh[i], TravelSpeed[i]);  // Yes, new switch state is high, move the switch accordingly
        }
        else
        {
          Myservos[i].slowmove(RangeLow[i], TravelSpeed[i]);   // No, new switch state is low, move the switch accordingly
        }
      }                                                        // *** No, the switch state has not changed, is it time to detach servo then ?
      else if (Myservos[i].attached() && ((millis() - DetachMillis[i]) > DetachTime))
      {
        Myservos[i].detach();                                  // *** Yes, the time has elapsed, detach the switch servo for a more silent layout
      }
    }
  }
}

@Anders53,

Now that is pretty cool code. Many thanks for assisting me in this way. I have printed out the code and will work my way through it to ensure I understand what is happening and why.

Regards

Thanks roboz.

Fedback much appreciated from a practical test, to catch any code or method errors.

Hi Anders,

I will be setting up a test board with a couple of turnouts and button controls etc., over the next couple of days. I will let you know how it goes.

If all goes well I will then go on to phase 2, which is to replace a number of old solenoid motors with servos controlled by an Arduino loaded with your code.

Cheers

Hi Anders53,

I have tested the second lot of code you modified for the centering function. There is something wrong. Nothing happens when input 12 is grounded to center the servos, and the servos themselves when you ground them by their ‘buttons’ no longer function or move the servos.

By contrast the previous code, before you added in the centering function, works fine. Unfortunately, I don’t have the skills at this point to spot where the problem might be with the code containing the centering function.

Regards

Hi Anders53,

Stupid me! It does work, I forgot that I had altered range and speed in your unmodified sketch. The modified sketch does work when I incorporate my changes.

So grounding pin 12 does center the servos. Once you un-ground the pin 12 though the servos still stay centered until you switch them, then they go to the RangeLow position. Is that what you would have expected? Otherwise seems fine.

Cheers

Rob

:zipper_mouth_face: :slight_smile: Yes the first code runs perfect.
It has been tested, debugged and verified.

The latest code had a bug :roll_eyes:

  • which is now corrected.

I changed the placement of detecting the midpoint switch.
It is now within the loop where the servos are detached after 5 seconds (I reduced the wait timer).
If it works, the servo should go midpoint, if a low is held to pin 12.
Further after 5 seconds, the servos should detach and stop rattling.

It should work now, but my handicap is, that I have no working servos at the mo.

/*
 Copyright2015 - SERVO SWITCH CONTROL - SSC - PaulDMV
 Modified 2015 - using arrays - Anders53
 Modified 09-04-2015 - code reduced heavily - by Septillion
 Modified 13-04-2015 - added override to mid position - by Anders53

 A maximum of 11 servo objects can be installed on the nano
 A maximum of eight servo objects can be created from library
 Only change constants (speed, range_low and range_high)
 include the VarSpeedServo.h Library before compiling !

 Define arduino pin usage, which are constants :
 Note that arduino analog pins can be defined as digtial pins
 making for the maximum of 8 servo objects in the library
 Arduino pins 0 & 1 are here reserved for any serial communication
 i.e. for debugging or future inter comm between arduino units
 The pin numbers are those found printed on the arduino board
 They should apply to all Arduino Uno, Nano & Micro varaints
 but can of course be re-arranged to suit the extra A6 & A7 pins
 found on later revisions of Uno & Nano derivatives.
 
 An override toggle switch can be added to input pin 12.
 A LOW (GND) level on this pin will set all servo channels to
 mid position 90° for installation of the servos.
 If the input is left open, the internal PULL_UP will set the 
 input high, which disables the mid position feature.
 
 The internal led is set to blink every time loop() is
 traversed, to indicate activity.
 
*/

#include <VarSpeedServo.h>
const byte NumberServos        =     8;        // the total number of servos
const unsigned long DetachTime =     5000;    // detach servo after 10 seconds
const byte LedLightPin         =     13;       // internal activity LED is on pin 13
const byte TestProbePin        =     12;       // input pin for midpoint setting
byte i                         =     0;        // Loop counter
boolean HoldReadVal, TestProbe =     0;        // input read varaiables
boolean LedState               =     0;        // For toggling the LED

// arrays holding all indexable constants & variables
VarSpeedServo     Myservos[NumberServos];      // Servo objects
unsigned long DetachMillis[NumberServos];      // Detach timer for each servo object
boolean        SwitchState[NumberServos];      // Holds the status for each switch

// Servo nr                    =    {0,  1,  2,  3,  4,  5,  6,  7 }
const byte ButtonPins[]        =    {A0, A1, A2, A3, A4, A5, 2,  3 };
const byte ServoPins[]         =    {4,  5,  6,  7,  8,  9,  10, 11};
const byte RangeLow[]          =    {85, 85, 85, 85, 85, 85, 85, 85};
const byte RangeHigh[]         =    {95, 95, 95, 95, 95, 95, 95, 95};
const byte TravelSpeed[]       =    {2,  2,  2,  2,  2,  2,  2,  2 };

void setup()
{
  pinMode(LedLightPin, OUTPUT);                       // define internal LED output
  pinMode(TestProbePin, INPUT_PULLUP);                // define digital test probe input
  for (i = 0; i < NumberServos; i++)
  {
    pinMode(ServoPins[i], OUTPUT);                    // Define digital pin as output
    pinMode(ButtonPins[i], INPUT_PULLUP);             // Define digital input pin with pull up resistor for noise reduction
    SwitchState[i] = digitalRead(ButtonPins[i]);      // Read the current switch state from the track layout
    if(SwitchState[i])                                // If switch state is high, pre-set servo control accordingly
    {
      Myservos[i].write(RangeHigh[i]);
    }
    else                                              // Switch state is low, pre-set servo control accordingly
    {
      Myservos[i].write(RangeLow[i]);
    }
    Myservos[i].attach(ServoPins[i]);	              // Only after pre-setting the position control, it's safe to attach the servo
  }                                                   // The servo will detach in the loop automatically after 10 seconds if no activity   
}

void loop() // runs forever to detect control buttons and change switches accordingly
{
  digitalWrite(LedLightPin, LedState);                         // Code activity LED
  LedState = !LedState;                                        // Change LED state
  
  for (i = 0; i < NumberServos; i++)
  {
    TestProbe = digitalRead(TestProbePin);                     // Read midpoint setting switch input
    HoldReadVal = digitalRead(ButtonPins[i]);                  // Read the button state
    if ((HoldReadVal != SwitchState[i]) || !TestProbe)         // Midpoint state or switch state changed by the button ?
    {                                                          // Yes, attach servo and start detach timer
      DetachMillis[i] = millis();                              // Initialise the detach timer
      Myservos[i].attach(ServoPins[i]);                        // Attaches the servo on output pin to its servo object
    }
    if (!TestProbe)                                            // ### Is midpoint switch active LOW ?
    {                                                          // ### Yes, midpoint active, set servos to midpoint
      Myservos[i].slowmove(90, 5);                            
    }                                                          // ### No, midpoint idle, control the servos
    if ((HoldReadVal != SwitchState[i]) && TestProbe)          // *** Has the switch state changed by the button ?
    {
      SwitchState[i] = !SwitchState[i];                        // *** Yes the switch state has changed, save new switch state
      if (SwitchState[i])                                      // Is the new switch state high (greater than 0) ?
      {
        Myservos[i].slowmove(RangeHigh[i], TravelSpeed[i]);    // Yes, new switch state is high, move the switch accordingly
      }
      else
      {
        Myservos[i].slowmove(RangeLow[i], TravelSpeed[i]);     // No, new switch state is low, move the switch accordingly
      }
    }                                                          // *** No, the switch state has not changed, is it time to detach servo then ?
    if (Myservos[i].attached() && ((millis() - DetachMillis[i]) > DetachTime))
    {
      Myservos[i].detach();                                    // *** Yes, the time has elapsed, detach the switch servo for a more silent layout
    }
  }
}

@Anders53,

Thank you for such swift responses!! I will upload and run a test later tonight after Dinner (my Wife wont be impressed if I keep fooling around with this until later) and let you know.

Until later.

Gees ! It still didnt work :fearful:

I wish I had a working servo. Have now ordered a set of the cheapos, just to have them handy for future use.

Now after editing, the latest code is still the one you find in answer # 16

Hi Rob,

Maybe you can take a look at adServo. I know I alterde the code in this tread as well but I like the adServo code more. It’s a servo controller like you want. I made a PCB for it as well but you can use it with a bare Arduino if you like. The mean difference is you can set the servo point on the fly. You don’t need to alter the Arduino code to change the angle or speed of a servo. This is in my opinion way more convenient.

Instead of 1 button per servo it uses 2, like the normal coil turnouts. At the moment I’m trying to add DCC digital control as well but for now the analog part (the way you want to use it) is fully functional. The code is for 4 servo’s but can be extended to 8. (But I don’t think 8 is necessary most of the time. Extending the servo cable to more then 40cm is a bad idea. Better to use a second Arduino.)

To use it just connect everything. (See ButtonMap for the buttons and ServoMap for the pins.) If you press the mode button (pin 10) for 3 seconds all servo’s go to the mid point and the led flashes (pin 11).

1 flash => With the buttons you can set position 1
2 flashes => You can set position 2
3 flashes => Servo’s move at the set speeds, with the buttons you can alter the speed.

Cycle thru the different modes but short pressing the mode button. Save by pressing the button for 3 sec. If you want to alter just 1 servo just alter 1 servo, the servo’s you don’t move in programming mode are not altered (even though they go to mid point).

Download: Firmware adServo v3
Download: Manual adServo v1b (Dutch only for the moment)
More info: adServo @ Beneluxspoor (in Dutch)

Timo