Model railroad switch and signal control

Hi folks. This a presently pretty successful project that controls 2 servos simultaneously to open or close model railroad track switches on a button push, and changes the colors of led signals as it does.

It works!

I've been told that instead of using an analog input for the single button with a 1K pull down resistor, I should have used a digital input. I was also informed that this script is "needlessly complex". Since I based it on others peoples work and I just modified it, they are probably correct.

I'll probably like to expand it in the future with a few more servos and some sensors. It also might be fun to automate some things once the sensors are in. leds have 1k resistors on positive for 5 volts.

Since I am really brand new at this, any suggestions from which I might learn some things would be deeply appreciated.

Thanks! Script follows:

//Controls Two Turnouts And LED Signals
//Activates two servos at once and turns signal leds from green, yellow, to red
//Modified by Artie Langston 7-16-2023
//kd0gy@arrl.net
/*****************************************************************/
const int button = A0;
const int AredledPin = 8;   //the A red led connect to pin8
const int AyelledPin = 9;   //the A yellow led connect to pin9
const int AgrnledPin = 10;  //the A green led connect to pin10
const int BredledPin = 11;  //the B red led connect to pin11
const int ByelledPin = 12;  //the B yellow led connect to pin12
const int BgrnledPin = 13;  //the B green led connect to pin13
/*************************************************************/
int start1 = 90;
int stop1 = 165;
#include <Servo.h>
Servo servo_one;  // create servo object to control servo one
Servo servo_two;  // create servo object to control servo two
int pos = 90;  // variable to store the servo position
/*****************************************************************/
void setup() {
  Serial.begin(9600);
  servo_one.attach(6);
  servo_two.attach(7);
  pinMode(AredledPin, OUTPUT);  //initialize the A led pin as output
  pinMode(AyelledPin, OUTPUT);  //initialize the A led pin as output
  pinMode(AgrnledPin, OUTPUT);  //initialize the A led pin as output
  pinMode(BredledPin, OUTPUT);  //initialize the B led pin as output
  pinMode(ByelledPin, OUTPUT);  //initialize the B led pin as output
  pinMode(BgrnledPin, OUTPUT);  //initialize the B led pin as output
}
enum SWITCHSTATES {
  ST_OFF1,
  ST_OFF2,
  ST_STRAIGHT,
  ST_TURN,
};
/**************************************/
SWITCHSTATES switchState = ST_OFF1;
/**************************************/
void loop() {
  int buttonRead = analogRead(button);
  delay(200);
  switch (switchState) {

    case ST_OFF1:
      switchoff1(buttonRead);          //sets up changes to ST_OFF1
      digitalWrite(AgrnledPin, LOW);   //turn the A the green led on
      digitalWrite(AyelledPin, HIGH);  //turn the A the yellow led off
      digitalWrite(AredledPin, HIGH);  //turn the A red led off
      digitalWrite(BgrnledPin, HIGH);  //turn the B green led on
      digitalWrite(ByelledPin, HIGH);  //turn the B yellow led off
      digitalWrite(BredledPin, LOW);   //turn the B red led off
      break;

    case ST_OFF2:
      switchoff2(buttonRead);          //sets up changes to ST_OFF2
      digitalWrite(AyelledPin, HIGH);  //turn the A yellow led on
      digitalWrite(ByelledPin, HIGH);  //turn the B yellow led on
      break;

    case ST_STRAIGHT:
      switchstraight(buttonRead);      //sets up changes to ST_STRAIGHT
      digitalWrite(AgrnledPin, LOW);   //turn the A green led on
      digitalWrite(AyelledPin, LOW);   //turn the A yellow led on
      digitalWrite(AredledPin, HIGH);  //turn the A red led off
      digitalWrite(BgrnledPin, LOW);   //turn the B green led on
      digitalWrite(ByelledPin, LOW);   //turn the B yellow led on
      digitalWrite(BredledPin, HIGH);  //turn the B red led off
      break;

    case ST_TURN:
      switchturn(buttonRead);          // sets up changes to ST_TURN
      digitalWrite(AgrnledPin, HIGH);  //turn the A green led on
      digitalWrite(AyelledPin, LOW);   //turn the A yellow led on
      digitalWrite(AredledPin, LOW);   //turn the A red led off
      digitalWrite(BgrnledPin, LOW);   //turn the B green led on
      digitalWrite(ByelledPin, LOW);   //turn the B green led on
      digitalWrite(BredledPin, HIGH);  //turn the B red led off
      break;
  }
}
/**************************************************************/
void switchoff1(int buttonRead) {
  servo_one.write(start1);
  servo_two.write(start1);

  if (buttonRead > 500) {
    switchState = ST_TURN;           //switches to different state
    digitalWrite(AgrnledPin, HIGH);  //turn the A green led on
    digitalWrite(AyelledPin, LOW);   //turn the A yellow led off
    digitalWrite(AredledPin, LOW);   //turn the A red led off
    digitalWrite(BgrnledPin, LOW);   //turn the B green led on
    digitalWrite(ByelledPin, LOW);   //turn the B yellow led on
    digitalWrite(BredledPin, HIGH);  //turn the B red led off
  }
}
/****************************************************************/
void switchturn(int buttonRead) {
  for (pos = start1; pos <= stop1; pos += 1) {  // goes from 90 degrees to 165 degrees in steps of 1 degree
    servo_one.write(pos);  // tell servo to go to position in variable 'pos'
    delay(3);              // waits 3ms for the servo to reach the position

    servo_two.write(pos);  // tell servo to go to position in variable 'pos'
    delay(3);              // waits 3ms for the servo to reach the position
  }
  switchState = ST_OFF2;  //chages to ST_OFF2
}
/*********************************************************************************/
void switchoff2(int buttonRead) {
  servo_one.write(stop1);
  servo_two.write(stop1);

  if (buttonRead > 500) {
    switchState = ST_STRAIGHT;  //switches to Straight
  }
}
/**********************************************************************/
void switchstraight(int buttonRead) {
  for (pos = stop1; pos >= start1; pos -= 1) {  // goes from 165 degrees to 90 degrees
    servo_one.write(pos);                       // tell servo to go to position in variable 'pos'
    delay(3);                                   // waits 3ms for the servo to reach the position

    servo_two.write(pos);   // tell servo to go to position in variable 'pos'
    delay(3);               // waits 3ms for the servo to reach the position
    switchState = ST_OFF1;  //chages to ST_OFF1
  }
}

In setup() add

pinMode(A0, INPUT);

In loop() change

int buttonRead = analogRead(button);

by

int buttonRead = digitalRead(button);

Regards

1 Like

Hello bassweasel

In general:

  1. You should not use magic names and comments. The I/O pins love to have a functional name.
  2. Arrays and structs are your friends.
    Don't duplicate code in your sketch. Write code once - use it multiple times.

Do you have experience with programming in C++?

This model railroad switch and signal control can easily be realised with an object.
A structured array contains all information, such as pin addresses for the I/O devices, as well as the information for the timing.
A single service takes care of this information and initiates the intended action.
The structured array makes the sketch scalable until all I/O pins are used up without having to adapt the code for the service.

It is cool stuff, isn´t it?

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

p.s.

You might offer a track plan to show all signals and turnouts marked with their names.

1 Like

Do you have experience with programming in C++?

I learned some Apple BASIC back in 1984. :slight_smile: So, I'm pretty clueless. I take it that rather than focusing on Arduino scripting, I need to study C++? I'm retired and my focus will probably be on some fun model railroad and amateur radio SDR projects, although, I find some of the robotics stuff exciting.

This is just an O gage layout on a 6 by 8 foot board for post war Marx trains, so it presently has a limit on it's expansion. I'm using servos on a couple of Lionel Fastrack manual switches, because I'm way too stingy to buy the electric ones. :slight_smile:

Thanks!

Many thanks!

Hello bassweasel

There is no need.
It is highly recommended in case of coding proper sketch with identical hardware used.

To get started you may modify the BlinkWithOutDelay example of the IDE by using an array for more LEDs.

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

Thank you!

Most analogue pins on AVR based boards (Mega, Uno, classic Nano) are also digital pins; exceptions are A6 and A7 on a classic Nano.
Hence, seeing that you're using A0, you can indeed use digitalRead(A0) and you will get a LOW or a HIGH back.

Be aware that inputs can be floating; that means that if they are not connected to anything they can give random values (both with analogRead() and digitalRead()); it's not clear if you use a pull-down / pull-up resistor to prevent that or not. If you don't, I suggest that you use pinMode(A0, INPUT_PULLUP) in setup() and wire the button between A0 and GND. In that case, if the button is not pressed you will read a HIGH with digitalRead(A0) and a LOW if the button is pressed.

It's unusual for LEDs to be wired differently :wink: Agrnled is switched on by writing a LOW to the pin but e.g. Bredled is switched off by writing a LOW.
Now this more than likely is a matter copy/paste error. But incorrect comments are worse than no comments.

You have e.g. the following two lines

digitalWrite(AgrnledPin, LOW);   //turn the A green led on
...
...
digitalWrite(AgrnledPin, HIGH);  //turn the A green led on

Ask yourself which one is true?

To prevent this type of issues, you can add the following at the top of your code; it assumes that HIGH means ON

#define ON HIGH
#define OFF !ON

and if you want to switch the Agrnled on

digitalWrite(AgrnledPin, ON);

and to switch it off

digitalWrite(AgrnledPin, OFF);

You do not even have to add comments because the code is now self-explaining.

You can do a similar thing for buttons (based on use of INPUT_PULLUP)

#define ISPRESSED LOW

and use it like

// if the button is pressed
if(buttonRead == ISPRESSED)
{
}

// if button is not pressed
if(buttonRead == !ISPRESSED)
{
}

Last comment
You have functions that take buttonRead as a parameter; e.g. you call switchstraight() with the parameter as switchstraight(buttonRead);. There is nothing wrong with that but the function itself uses the same variable name

void switchoff2(int buttonRead)
{
...
...
if (buttonRead > 500) {
...
...
}
...
...
}

The use of the same variable name for different variables in the same piece of code is discouraged as it can cause confusion.

Here is the original sketch in action...

Thanks so much! I'll study this carefully.

In this case, the little scale model railroad signals have 3 leds each, red, yellow and green, with 1 common positive lead and a 1K resistor, and 3 negative leads - one for each color. Thus, to choose each different color the respective pin must go low.

OK, in that case it would be

#define ON LOW
#define OFF !ON

In general each led should have it's own resistor. Two reasons

  1. In simplistic terms, LED brightness depends on colour.
  2. If you want to switch two LEDs inside that RGB LED on, you will have a problem if they have a different Vf (forward voltage).

In this case, each model railroad signal came with 1 common positive lead and just one resistor for positive, and a separate negative lead for each color, so the wiring is wrong. My bad for not showing a schematic! It's not possible to rewire them without ruining them.

This is cool! Thanks!

Fixed (same link)

Cool! Thanks!

I've tried to make this a little cleaner. I'll check the button ideas on the breadboard tomorrow, Thanks so much, everyone! I need to study up, and learn my definitions, such as arrays, etc. so I can start using them.

//Controls Two Turnouts And LED Signals
//Activates two servos and turns signal leds from green, yelLOW, to red
//Modified by Artie Langston 7-16-2023
//kd0gy@arrl.net
/*****************************************************************/
const int button = A0;
const int rs1 = 8;   
const int ys1 = 9;   
const int gs1 = 10;  
const int rs2 = 11;  
const int ys2 = 12;  
const int gs2 = 13;
#define ON LOW
#define OFF HIGH
/*************************************************************/
int start1 = 90;
int stop1 = 165;
#include <Servo.h>
Servo sw1;  
Servo sw2;  
int pos = 90; 
/*****************************************************************/
void setup() {
  Serial.begin(9600);
  pinMode(A0, INPUT);
  sw1.attach(6);
  sw2.attach(7);
  pinMode(rs1, OUTPUT);
  pinMode(ys1, OUTPUT); 
  pinMode(gs1, OUTPUT);  
  pinMode(rs2, OUTPUT);  
  pinMode(ys2, OUTPUT);  
  pinMode(gs2, OUTPUT); 
}
enum SWITCHSTATES {
  ST_OFF1,
  ST_OFF2,
  ST_STRAIGHT,
  ST_TURN,
};
/**************************************/
SWITCHSTATES switchState = ST_OFF1;
/**************************************/
void loop() {
  int buttonRead = digitalRead(button);
  delay(200);
  switch (switchState) {

    case ST_OFF1:
      switchoff1(buttonRead);         
      digitalWrite(gs1, ON);  
      digitalWrite(ys1, OFF);  
      digitalWrite(rs1, OFF);  
      digitalWrite(rs2, OFF);  
      digitalWrite(ys2, OFF);  
      digitalWrite(gs2, ON);   
      break;

    case ST_OFF2:
      switchoff2(buttonRead); 
      digitalWrite(ys1, ON);  
      digitalWrite(ys2, ON);  
      break;

    case ST_STRAIGHT:
      switchstraight(buttonRead); 
      digitalWrite(gs1, ON);
      digitalWrite(ys1, ON); 
      digitalWrite(rs1, OFF);  
      digitalWrite(gs1, ON);   
      digitalWrite(ys1, ON);   
      digitalWrite(rs1, OFF);  
      break;

    case ST_TURN:
      switchturn(buttonRead);          
      digitalWrite(gs1, OFF);  
      digitalWrite(ys1, ON);  
      digitalWrite(rs1, ON);  
      digitalWrite(gs2, ON);   
      digitalWrite(ys2, ON);   
      digitalWrite(rs2, OFF); 
      break;
  }
}
/**************************************************************/
void switchoff1(int buttonRead) {
  sw1.write(start1);
  sw2.write(start1);

  if (buttonRead > 500) {
    switchState = ST_TURN;           
    digitalWrite(gs1, OFF);  
    digitalWrite(ys1, ON);   
    digitalWrite(rs1, ON);  
    digitalWrite(gs2, ON);   
    digitalWrite(ys2, ON);   
    digitalWrite(rs2, OFF);  
  }
}
/****************************************************************/
void switchturn(int buttonRead) {
  for (pos = start1; pos <= stop1; pos += 1) {  
    sw1.write(pos);
    delay(3);              
    sw2.write(pos);  
    delay(3);              
  }
  switchState = ST_OFF2;  
}
/*****************************************************************************************/
void switchoff2(int buttonRead) {
  sw1.write(stop1);
  sw2.write(stop1);

  if (buttonRead > 500) {
    switchState = ST_STRAIGHT;  //switches to Straight
  }
}
/***************************************************************************************/
void switchstraight(int buttonRead) {
  for (pos = stop1; pos >= start1; pos -= 1) {  
    sw1.write(pos);                       
    delay(3);                                   
    sw2.write(pos);   
    delay(3);               
    switchState = ST_OFF1;  
  }
}

This sketch seems to be stuck in the "ST_OFF1" state.

Somehow, it functions perfectly. Since I borrowed most of the script, I can't tell you why it does. But it works. That's what's frustrating as a beginner.

This is where I got the basis for the sketch. Apparently, a lot of people have used it. Sadly, at this point, I am like a dog who has been taught a trick he's not capable of understanding. :slight_smile:

1 Like

Use what works, and you can't go wrong (mostly). : )

(I probably have my button wired differently)

Hi, @bassweasel

Please show us a circuit diagram?
An image of a hand drawn schematic will be fine, include ALL power supplies, component names and pin labels.

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

1 Like