Multiple track servo/switches with button control

I have made a prototype track switching device (Turnout) that uses a servo to move the switch from straight to turn and vise versa. I modified code that I found on the web and it works fine with one switch ( code included below ). I use two momentary buttons as activators, one for straight and one for turn.

The problem...
I have 6 switches to install so I want to expand the code. A Nano should be able to handle 3 servos ( rather than one Nano/servo per turnout ). First step was to add a second switch(turnout) to my prototype. Can't figure out how to structure the state setup for a second set of buttons and turnout/servo.

I am using a Nano with SG90 servos.

I started by trying to create a second "switchState" ( using switchStateA and switchStateB)

eg. switch (switchStateA , switchStateB)
But that does not work..

Here is the code that works;

#include <Servo.h>

Servo switch1; //declares my servo switch

const int straight1 = 11; // Black Button to go straight 
const int turn1 = 12; // Red Button to turn
int pos = 0;
int posstraight=40; // servo angle for straight
int posturn=135;  // servo angle for turn 
int straightstate = 0;
int turnstate = 0;


void setup()
{
  Serial.begin(9600); //starts the serial monitor
 
  switch1.attach(6);  // attaches the servo on pin 6 

  pinMode(straight1, INPUT);
  pinMode(turn1, INPUT);
  switch1.write(90);  // servo Start at point middle

}
  enum SWITCHSTATES   // Setting up the different Switch states
{
  ST_OFF1,
  ST_OFF2,
  ST_STRAIGHT,
  ST_TURN,
};

SWITCHSTATES switchState = ST_OFF1; //Sets default state to off1


void loop() {

straightstate = digitalRead(straight1); // read pin 11
turnstate = digitalRead (turn1); // read pin 12
delay (200);

switch (switchState)
{
  case ST_OFF1:
  switchoff1(straightstate); //sets up change to ST_OFF1
  break;
  
  case ST_OFF2:
  switchoff2(turnstate); //sets up change to ST_OFF2
  break;
  
  case ST_STRAIGHT:
  switchstraight(straightstate); //sets up change to ST_STRAIGHT
  break;
  
  case ST_TURN:
  switchturn(turnstate); //sets up change to ST_TURN
  break;
  }  
}
void switchoff1(int straight){
  switch1.write(posstraight);

  if (straightstate == HIGH){
    switchState = ST_TURN;
  }
 }

void switchturn(int straight1){
  for (pos = posstraight; pos <= posturn; pos += 1){ // goes from 40 degrees to 135 degrees
    switch1.write(pos);
    delay(15);

  }
  switchState = ST_OFF2;
                                   
}

void switchoff2(int turn1){
  switch1.write(posturn);

  if (turnstate == HIGH){
    switchState = ST_STRAIGHT;
  }
}

void switchstraight(int turn){
  for (pos=posturn; pos >= posstraight; pos -= 1){ // goes from 135 degrees to 40 degrees
    switch1.write(pos);
    delay(15);
      
  }
    switchState = ST_OFF1;
                                     

  }


Thanks

Hello
This is the step to take a view into OPP.
Design an object containing all information wrt a railway switch, like servo, button, etc. One method=function will process the information.
You will have on piece of code for processing and one object coded in a structured array to be extended easily.
Therefore it is highly recommented to take a view into some powerful C++ instructions like STRUCT, ENUM, ARRAY, CASE/SWITCH,UDT, RANGE-BASED-FOR-LOOP, etc.
Have a nice day and enjoy coding in C++.

If you mean 6 servos that you want to run off one Nano, that is definitely possible (although depending on your power source you may run into brownouts). Just list your servos at the beginning of your code. You have two options for this:

Servo switch1;
Servo switch2;
//etc.

void setup()
{
  servo1.attach(servo1Pin);
  servo2.attach(servo1Pin);
// you still need the other code in here
}

or you could make an array of servos:

Servo servos[numberOfServos];
int servoPins[numberOfServos] = {servo1Pin, servo2Pin, etc.};

void setup()
{
  for(int i =0; i < numberOfServos; i++)
  {
    servo[i].attach(servoPins[i]);
  }
  // you still need the other code in here
}

I think this is the simplest way to start doing this. You could also create an array of your button pins to read and servo states:

int buttonPins[numberOfButtons] = { button1Pin, button2Pin, etc.};
bool buttonState = { false, false, etc.}; // Here use a bool to save memory (not actually an issue here, but good practice) false = not pressed, true = pressed

void readButtons()
{
  for(int i = 0; i < numberOfButtons; i++)
  {
    if(digitalRead(buttonPins[i]) // digitalRead gives either a 1 or 0 for HIGH or LOW, this if statement means if buttonPins[i] = 1
    {
      buttonState[i] = true;
    }
  }
}

Once we've read buttons, we can set our servos. I suggest doing this off yet another array whose values we set when we check buttons. These will move to the position we tell them - no need to do the for loop you have in your copied code unless you want to slow them down (in that case substitute the for loop in for my write.servoi below)

bool servoState[numberOfServos] = {false, false, etc.}
int straightPosition = 45;
int turnPosition = 135;

void setServoPosition()
{
  for(int i = 0; i < numberOfServos; i++)
  {
    if(buttonState[i*2])  // Checking the straight button
    {
      servoState[i] = false; // servo should be at 45deg
    }
    else if(buttonState[i*2+1])  // Checking the turn button
    {
      servoState[i] = true; // servo should be at 135deg
    }
  }
}
void writeServoPosition()
{
  for(int i = 0; i < numberOfServos; i++)
  {
    switch(servoState[i])
    {
      case 1:
        servos[i].write(turnPosition);
        break;
      case 0:
        servos[i].write(straightPosition);
        break;
    }
  }
}

To sum up my previous post, youe overall code will end up being something like this:

#include <Servo.h>
Servo servos[numberOfServos];
int servoPins[numberOfServos] = {servo1Pin, servo2Pin, etc.};
bool servoState[numberOfServos] = {false, false, etc.}

int straightPosition = 40;
int turnPosition = 135;

int buttonPins[numberOfButtons] = { button1Pin, button2Pin, etc.};
bool buttonState = { false, false, etc.}; // Here use a bool to save memory (not actually an issue here, but good practice) false = not pressed, true = pressed

void setup()
{
  for(int i =0; i < numberOfServos; i++)
  {
    servo[i].attach(servoPins[i]);
  }
  for(int i =0; i < numberOfButtons; i++)
  {
    pinMode(buttonPins[i], INPUT);
  }
}

void loop()
{
  resetButtonBools();  // We don't want to save the previous button press so need to reset all to false
  readButtons();
  setServoPosition();
  writeServoPosition();
}

void resetButtonBools()
{
  for(int i = 0; i < numberOfButtons; i++)
  {
    buttonState[i] = false;
  }
}

void readButtons()
{
  for(int i = 0; i < numberOfButtons; i++)
  {
    if(digitalRead(buttonPins[i]) // digitalRead gives either a 1 or 0 for HIGH or LOW, this if statement means if buttonPins[i] = 1
    {
      buttonState[i] = true;
    }
  }
}

void setServoPosition()
{
  for(int i = 0; i < numberOfServos; i++)
  {
    if(buttonState[i*2])  // Checking the straight button
    {
      servoState[i] = false; // servo should be at 45deg
    }
    else if(buttonState[i*2+1])  // Checking the turn button
    {
      servoState[i] = true; // servo should be at 135deg
    }
  }
}

void writeServoPosition()
{
  for(int i = 0; i < numberOfServos; i++)
  {
    switch(servoState[i])
    {
      case 1:
        servos[i].write(turnPosition);
        break;
      case 0:
        servos[i].write(straightPosition);
        break;
    }
  }
}

You will need to define or replace number of servos and buttons, as well as fill in that many falses or pin numbers into the arrays, but it should compile, maybe?

consider

#define MyHW
#ifdef MyHW
struct Servo {
    Servo (void)  { }
    void attach (byte pin)  { }
    void write  (int  pos)  {
        Serial.print   (" write ");
        Serial.println (pos);
    }
};

#else
# include <Servo.h>
#endif

struct Turnout  {
    const byte  ServoPin;
    const byte  NormButPin;
    const byte  RevButPin;
    const int   posNorm;
    const int   posRev;
    int         pos;
    int         state;
    byte        normButState;
    byte        revButState;
    Servo       servo;
};

Turnout turnouts [] = {
    { 6, 11, 12, 40, 135 },
    { 7, A1, A2, 40, 135 }
};

#define Nturnouts   (sizeof(turnouts)/sizeof(Turnout))

enum { NORM, REV };


// -----------------------------------------------------------------------------
void
swTurnout (
    Turnout *t )
{
    int targ = (REV == t->state ? t->posRev : t->posNorm);
    int dir  = targ - t->pos;

    while (t->pos != targ)  {
        t->pos += 0 < dir ? 1 : -1;
        t->servo.write  (t->pos);
        delay (15);
    }
}

// -----------------------------------------------------------------------------
void loop()
{
    Turnout *t = & turnouts [0];
    for (unsigned n = 0; n < Nturnouts; n++, t++)  {
        byte  but = digitalRead (t->NormButPin);
        if (t->normButState != but)  {
            t->normButState = but;
            if (LOW == but)  {
                t->state = NORM;
                swTurnout (t);
            }
        }

        but = digitalRead (t->RevButPin);
        if (t->revButState != but)  {
            t->revButState = but;
            if (LOW == but)  {
                t->state = REV;
                swTurnout (t);
            }
        }

    }
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin(9600); //starts the serial monitor

    Turnout *t = & turnouts [0];
    for (unsigned n = 0; n < Nturnouts; n++, t++)  {
        t->servo.attach (t->ServoPin);
        t->pos  = REV == t->state ? t->posRev : t->posNorm;
        t->servo.write  (t->pos);

        pinMode (t->NormButPin, INPUT_PULLUP);
        pinMode (t->RevButPin,  INPUT_PULLUP);

        t->normButState = digitalRead (t->NormButPin);
        t->revButState  = digitalRead (t->RevButPin);
    }
}

My MobaTools library was original written with such using in mind ( hence the name :wink: ).
A sketch to control some turnouts with servos and two buttons per servo should be fairly simple:

#include <MobaTools.h>

const byte straight[]       = { 8,10 };           // Black Button to go straight
const byte turnoutCnt = sizeof(straight) / sizeof(straight[0]);
const byte turn[turnoutCnt] = { 9,11 };           // Red Button to turn
const int posstraight[turnoutCnt] = { 40,40 };    // servo angle for straight
const int posturn[turnoutCnt]     = { 135,135 };  // servo angle for turn

MoToServo turnout[turnoutCnt];                    //declares my servo turnouts
const byte servoPin[turnoutCnt] = { 6,7 };        // servo pins

void setup()
{
    Serial.begin(9600); //starts the serial monitor

    for ( byte i = 0; i < turnoutCnt; i++ ) {
        turnout[i].attach(servoPin[i], true); // attaches the servo to the pins
        turnout[i].setSpeed(15);              // can be adjusted to your needs
        pinMode(straight[i], INPUT_PULLUP);   // connect the switch between pin and gnd
        pinMode(turn[i], INPUT_PULLUP);

    }
}

void loop() {

    for ( byte i = 0; i < turnoutCnt; i++ ) {
        if ( !digitalRead(straight[i]) ) turnout[i].write(posstraight[i]);
        if ( !digitalRead (turn[i]) ) turnout[i].write(posturn[i]);
    }
}

With a nano it should be easy to extend to up to 6 servos. Only extend the arrays as appropiate.

Hello klausts
Here comes a C++ proposal a.m. Try and check it out.

/* BLOCK COMMENT
  ATTENTION: This Sketch contains elements of C++.
  https://www.learncpp.com/cpp-tutorial/
  Many thanks to LarryD
  https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
  https://forum.arduino.cc/t/multiple-track-servo-switches-with-button-control/972199
  Tested with Arduino: Mega[X] - UNO [ ] - Nano [ ]
*/
#define ProjectName "Multiple track servo/switches with button control"
// HARDWARE AND TIMER SETTINGS
#include <Servo.h>
// YOU MAY NEED TO CHANGE THESE CONSTANTS TO YOUR HARDWARE AND NEEDS
constexpr byte ButtonPins[] {A0, A1, A2, A3};   // portPin o---|button|---GND
constexpr byte ServoPins[] {9, 10, 11, 12};
// CONSTANT DEFINITION
enum {One, Two, Three, Four};
// VARIABLE DECLARATION AND DEFINITION
unsigned long currentTime;
struct TIMER {              // has the following members
  unsigned long duration;   // memory for interval time
  unsigned long stamp;      // memory for actual time
  bool onOff;               // control for start/stop
};
struct BUTTON {             // has the following members
  byte pin;                 // port pin
  bool statusQuo;           // current state
  TIMER scan;               // see timer struct
};
enum Turn {StraightTrack, DivergingTrack};
struct  TURNOUT {
  BUTTON control_;
  byte servoPin;
  bool turn;
  int  directions[2];
  Servo device;
};
TURNOUT turnOuts[] {
  {ButtonPins[One], false, 20, 0, true, ServoPins[One], StraightTrack, {45, 135}},
  {ButtonPins[Two], false, 20, 0, true, ServoPins[Two], StraightTrack, {45, 135}},
  {ButtonPins[Three], false, 20, 0, true, ServoPins[Three], StraightTrack, {45, 135}},
  {ButtonPins[Four], false, 20, 0, true, ServoPins[Four], StraightTrack, {45, 135}},
};
// time handler
bool timerEvent (TIMER &timer) {
  return (currentTime - timer.stamp >= timer.duration && timer.onOff);
}
// button handler
enum {Released, Pressed, Idle};
int readKnop( BUTTON &knop) {
  if (!timerEvent(knop.scan)) return Idle;
  knop.scan.stamp = currentTime;
  int stateNew = !digitalRead(knop.pin);
  if (knop.statusQuo == stateNew) return Idle;
  return (knop.statusQuo = stateNew);
}
// -------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Serial.println(F("."));
  Serial.print(F("File   : ")), Serial.println(__FILE__);
  Serial.print(F("Date   : ")), Serial.println(__DATE__);
  Serial.print(F("Project: ")), Serial.println(ProjectName);
  pinMode (LED_BUILTIN, OUTPUT);  // used as heartbeat indicator
  //  https://www.learncpp.com/cpp-tutorial/for-each-loops/
  for (auto Input : ButtonPins) pinMode(Input, INPUT_PULLUP);
  for (auto &turnOut : turnOuts) turnOut.device.attach(turnOut.servoPin);// pinMode(turnOut.servoPin, OUTPUT);
}
void loop () {
  currentTime = millis();
  digitalWrite(LED_BUILTIN, (currentTime / 500) % 2);
  for (auto &turnOut : turnOuts) if (readKnop(turnOut.control_) == Pressed) turnOut.device.write(turnOut.directions[turnOut.turn = !turnOut.turn]);
}

Have a nice day and enjoy coding in C++.

@MicroBahner , danke,

In the sketch that I shared, the servo goes to an OFF mode once it has set the position.
When I run your sketch, the servos twitch and move as soon as I plug in the arduino. When I use a button they both move to the other position and the slowly twitch back to the first (either 40 or 135). Eventually my computer disconnects the USB because there is too much draw.

In my test bed I have a 10K resistor between GND and the D pins for the momentary button switch and just do a digitalRead instead of a INPUT_PULLUP and that was working also in my sketch. ??

I appreciate the //commenting in your sketch. It helps to figure things out.

As I wrote in the comment, my sketch assumes that the switch is connected between the D-pins and Gnd, and there is no additional resistor. I think this design is more easy, especially if you have many buttons/switches, because you don't need the resistor.
If you want to stay with your resistor and the switches connected to 5V, you must do some little changes to the sketch:

  • Change INPUT_PULLUP to INPUT
  • and remove the '!' from the digitalRead statements:
        if ( digitalRead(straight[i]) ) turnout[i].write(posstraight[i]);
        if ( digitalRead (turn[i]) ) turnout[i].write(posturn[i]);

I had read the notes and tried it without the resistors but that did not work for me.
That's when I put the resistors back and changed the INPUT_PULLUP to INPUT. The part that was missing was to remove the ! in the if statement. What does the "!" do in the code?

Now everything seems to work. Great!!

How would I now add the third turnout to the sketch?
(I am trying to understand the syntax.)
By adding the third pins to "straight" and "turn" as well as the third servoPin... ?
Anything else?

why do you have 4 states?

there's been several suggested approaches including what i suggested in reply #5

If you use INPUT_PULLUP and connect the switches to Gnd, digitalRead returns HIGH when the Button is NOT pressed, and LOW when the button is pressed.
The '!' inverts the value returned by digitalRead, so it reads TRUE when the input is LOW (=Button is pressed).

I prefer using INPUT_PULLUP because there is no need for an external resistor. But you must not only remove the resistor, but also connect the switch to gnd and not to 5V,

There are 5 arrays defined: The pins for the two Buttons and the servo and and the two endpoints for the servos. The only thing you have to do, is extending these arrays with the appropriate values, eg:

const byte straight[]       = { 8,10,12 };           // Black Button to go straight
const byte turnoutCnt = sizeof(straight) / sizeof(straight[0]);
const byte turn[turnoutCnt] = { 9,11,13 };           // Red Button to turn
const int posstraight[turnoutCnt] = { 40,40,40 };    // servo angle for straight
const int posturn[turnoutCnt]     = { 135,135,135 };  // servo angle for turn

MoToServo turnout[turnoutCnt];                    //declares my servo turnouts
const byte servoPin[turnoutCnt] = { 5,6,7 };        // servo pins

All 5 arrays must contain the same number of elements. The limit is the count of pins of the Nano. But you can also use the analog pins A0..A5 as digital pins. A6 and A7 are analog inputs only.

Edit: Do you know structs? Then you can combine the 5 values in a structure and define an array of this structure.

@gcjr
I was referring to @MicroBahner 's Sketch. My original sketch (top of thread) had states. His does not so I am not sure about your question.

In my test with what @MicroBahner provided I have a solution that works. I just want to verify that I understand the code enough to add that third switch.

I looked at your sketch and appreciate your suggestion but I am not well enough versed in C++ to understand all of it. Without //commented code I find it difficult.

why do you have 4 states?

it's never obvious what needs to be explained.

both paul and i presented code using an array of user defined struct variables containing multiple fields instead of multiple independent arrays.

fields within a struct can be accessed either directly using a "." (e.g. turnOuts [2].servoPin) or using a ptr (e.g. t->servo).

one advantage of a struct is that a single line can initialize all the fields are are usually grouped in a table, additional servos just need additional line

it's interesting that you had no questions regarding the uncommented code

still curious about the 4 states

I got the 4 states from this Youtube video.

I changed a bit of the code ( using digital read rather than analog).
This was my first venture into enum and switch, case commands so I followed what was prescribed.
Arrays are a whole other thing.. hope to get there at some point.

My goal here is to move ahead with the switches so we can install them on our club setup and remove the old school above track switches on a new section to illustrate this new possibility on a low budget.

Thanks

Yes, that's definitely an advantage, and that's why I asked him if he knows about structs.
And of course the MobaTools version can easily be changed to use a struct :sunglasses: ( still the INPUT_PULLUP version - and with additional comment :wink: ) :

#include <MobaTools.h>

struct turnoutData {
    const byte straight;          // Black Button to go straight
    const byte turn;              // Red Button to turn
    const byte servoPin;          // pin to connect servo to
    const int posstraight;        // servo angle for straight
    const int posturn;            // servo angle for turn
};
turnoutData myTOs[] = {
    {  8, 9, 5, 40, 135 },        // pin Straight, pin turn, pin servo, angle straight, angle turn
    { 10, 11, 6, 40, 135 },       // you can add more turnouts here
    { 12, 13, 7, 40, 135 }
};
const byte turnoutCnt = sizeof(myTOs) / sizeof(myTOs[0]); // number of configured turnouts

MoToServo turnout[turnoutCnt];                    //declares my servo turnouts

void setup()
{
    Serial.begin(9600); //starts the serial monitor

    for ( byte i = 0; i < turnoutCnt; i++ ) {
        turnout[i].attach(myTOs[i].servoPin, true); // attaches the servo to the pins
        turnout[i].setSpeed(15);                    // speed can be adjusted to your needs
        pinMode(myTOs[i].straight, INPUT_PULLUP);   // connect the switch between pin and gnd
        pinMode(myTOs[i].turn, INPUT_PULLUP);
    }
}

void loop() {

    for ( byte i = 0; i < turnoutCnt; i++ ) {
        // loop through all servos/Buttons
        if ( !digitalRead(myTOs[i].straight) ) turnout[i].write(myTOs[i].posstraight);  // if straight is pressed go to posstraight
        if ( !digitalRead (myTOs[i].turn ) )   turnout[i].write(myTOs[i].posturn);      // if turn is pressed go to posturn
    }
}

looks like the original code boils down to following.

#define MyHW
#ifdef MyHW
struct Servo {
    Servo (void)  { };
    void attach (int pin)  { };
    void write (int pos)  {
#if 1
        Serial.print (" write ");
        Serial.println (pos);
#endif
    };
};

const int ServoPin    = 6;
const int StraightPin = A1;
const int TurnPin     = A2;

#else
# include <Servo.h>

const int ServoPin    = 6;
const int StraightPin = 11; // Black Button to go straight
const int TurnPin = 12; // Red Button to turn
#endif

Servo switch1; //declares my servo switch

const int PosStraight   = 40; // servo angle for straight
const int PosTurn       = 135;  // servo angle for turn

int pos     = 0;
int posTarg = 90;

enum { Off = HIGH, On = LOW };

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    switch1.attach (ServoPin);
    pinMode (StraightPin, INPUT);
    pinMode (TurnPin, INPUT);
}

// -----------------------------------------------------------------------------
void loop () {
    if (On == digitalRead (StraightPin))
        posTarg = PosStraight;
    else if (On == digitalRead (TurnPin))
        posTarg = PosTurn;

    while (pos != posTarg)  {
        pos += posTarg > pos ? 1 : -1;
        switch1.write (pos);
        delay (15);
    }
}

Hello,
I have built a prototype with 6 buttons to control 3 servos and applied the above code. Have made minor adjustments for the the two position angles.
using arduino nano, SG90 servos, breadboard and button switches. Power is from the usb cable of the computer.

Everything works fine.

The problem;
When this is then transferred to the actual train layout everything works fine until I power up another device on the layout like turning a transformer on and off or running a my turntable motor.

The servos will randomly move ( not stay in their set position).
The servos and arduino are on a separate power line. I tried powering them from a USB 5v power supply not connected in any way to the power lines that run the other devices.. same issue?

I have spent some time trying to research "noise" "bounce" "interference" with servos. not much luck.

Any ideas?

Should this be a new topic?

I'm surprised it works at all. Servos pull a lot of current (~one amp each) and USB power gets you 500mA. Breadboards have very thin tracks and are not suited to passing large current, even if the Nano could provide it. You can sometimes get a lightly loaded single servo to work with a USB powered Arduino. Three servos is really too much.