Automated Signals Using Arduino (Block Detection) - Part 2 of 2

In my last post, I described the operation of my Automated Block Signalling ("ABS") on my model railroad. In this post, I shall describe my method of building my own signal towers and I will share the complete Arduino code which runs the system.

I knew I was going to need a considerable number of operating signal towers, 10 for the upper level, 20 for the middle level (because it is double-track) and a few more for the lowest level. Searching online turned up a handful of vendors that sell operating signal towers and even fewer that sell the modern "Darth Vader" style which is becoming commonplace in the US and is being installed over time by Canadian Pacific which I model. Vendors typically have a small number in stock and pricing per-tower runs between $40 and $70 (Canadian dollars per head). I decided that I would set to work building my own.

Here are prototype images I took from the Internet:



To build the signal "target" (the black contraption which contains the green, yellow and red signal lights) I used two thicknesses of sheet styrene 0.02 inches (for the flat shield which surrounds the lights) and 0.01 inches for the "Darth Vader" shroud.

I shall use pictures to show the steps I follow to make each signal head. I built a jig to make sure all of the parts are identical and to speed up production.

The brass insert with the three holes is where I drill the holes. The strip of 0.02 inch styrene is inserted underneath.



The drilled strip is inserted into a slot which is used to accurately cut the strip.

Unfortunately, the camera angle creates a trapezoidal illusion. The shape is actually rectangular.

The corners are carefully trimmed with an X-ACTO knife and then filed with a fine file.


Styrene 0.10 inches thick shaped for the "Darth Vader" hood before being curved.

Another jig used to bend the hood. A small dab of styrene cement is applied to the curved portion. This softens the material and, when dry, the styrene has taken on the curved shape. I find this much more reliable than applying heat which tends to melt and distort the thin styrene.

I made the shrouds much shallower than the prototype. This is because the prototype shroud is very deep to avoid signal light straying to the sides. The design allows the train crew to see the signal aspect but it is very difficult to see from an oblique angle. This is not practical for operating a model railroad because most of the time one's viewpoint is not directly down the track.


The completed shroud.

The completed signal head before being painted black.


I built several other jigs with which to make the signal tower. They are all mounted on one board (please note that there are some aspects of this board which are designed to build two-headed signal towers which will be the subject of a future blog post).

This board allows me to complete 8 steps of the construction process to ensure the level of precision needed so that the completed signal towers are as close as possible to being the same.

Green, yellow and red LEDs are inserted into this brass plate with the anode (positive side) facing away from the camera. They are all held in place by the toothpick which can be seen being inserted from the right.


Here an already-prepared mast has been inserted into the jig with an already-tinned brass strip touching the anodes of all three LEDs. The tip of a soldering iron is momentarily touched against the far side of the brass strip so the LEDs adhere to it. The excess LED legs are then trimmed so they are all flush with the top of the brass strip. The brass signal mast thus becomes the anode side of the circuit. Three 22 AWG wires (green, yellow and red) are then fed through the inside of the signal mast and soldered to the cathode side of the respective LEDs.

Note that there is a second brass strip on the other side which should be ignored for purposes of this discussion. This was there because at the time I was taking the photograph I was building a double-headed signal tower.


I 3D printed a ladder, two platforms that clip to the signal mast and three components that make up guard rails. This shows the items after being printed but before being separated from the "raft" and before plastic artifacts are trimmed away.


Two completed and operating signal towers in the foreground. The green signal changed to red in a fraction of a second after the picture was taken. Note the green signals in the distance indicating that the next blocks are unoccupied.


Note the red signal facing away from the locomotive and the green signal facing it.





Following is the complete Arduino code, 95% of which is Jimmy's from DIY and Digital Railroad on YouTube and 5% of which is my tweaks to modify some of the terminology so it was easier for me to understand the logic and also to change the behaviour of signal aspects when the signal turned from red to green.


int Sensor = 10; //Sensor is setup on Pin 10
int GreenLeft = 2; //Green Signal Facing Left on Pin 2 (used to be Green1)
int YellowLeft = 3;
int RedLeft = 4;
int GreenRight = 5; //Green Signal Facing Right on Pin 5 (used to be Green2)
int YellowRight = 6;
int RedRight = 7;//Sets up all of our separate signals on their pins
int BlockLeftSend = 8;//Sets up signal to be sent to previous block (used to be BlockASend)
int BlockRightSend = 9 ;//Sets up Signal to be sent to next block (used to be BlockBSend)
int BlockLeftRec = A0; //Sets up A0 as pin for receiving signal from previous block (used to be BlockARec)
int BlockRightRec = A1; //Sets up A1 as pin for receiving signal from next block(used to be BlockBRec)
int valA1 = 0;
int valA2 = 0;
int valA3 = 0;
int clearcount;

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

  //Establish all of our pins as inputs or outputs

  pinMode(Sensor, INPUT);
  pinMode(GreenLeft, OUTPUT);
  pinMode(YellowLeft, OUTPUT);
  pinMode(RedLeft, OUTPUT);
  pinMode(GreenRight, OUTPUT);
  pinMode(YellowRight, OUTPUT);
  pinMode(RedRight, OUTPUT);
  pinMode(BlockLeftSend, OUTPUT);
  pinMode(BlockRightSend, OUTPUT);
  pinMode(BlockLeftRec, INPUT);
  pinMode(BlockRightRec, INPUT);
}

enum SIGNALSTATES
{
  ST_GG,
  ST_GY,
  ST_YG,
  ST_YY,
  ST_RR,
  ST_YY1,
};//Sets up different signal states to switch to

SIGNALSTATES signalState = ST_GG;//GG is the default signal state

void loop() {
  valA1 = digitalRead(Sensor);//Reads Sensor
  valA2 = analogRead(BlockLeftRec);//Reads Block to left
  valA3 = analogRead(BlockRightRec);//Reads Bock to right
  delay(200);
  
  Serial.println("");
  Serial.print("valA1= ");
  Serial.println(valA1);
  Serial.print("valA2= ");
  Serial.println(valA2);
  Serial.print("valA3= ");
  Serial.println(valA3);  //Allows for values to be read on Serial Monitor
  delay(1);

  switch (signalState)
  {
    case ST_GG:
      signalgg(valA1, valA2, valA3);
      break;
    case ST_GY:
      signalgy(valA1, valA2, valA3);
      break;
    case ST_YG:
      signalyg(valA1, valA2, valA3);
      break;
    case ST_YY:
      signalyy(valA1, valA2, valA3);
      break;
    case ST_RR:
      signalrr(valA1, valA2, valA3);
      break;
    case ST_YY1:
      signalyy1(valA1, valA2, valA3);
      break;
      //This sets up our different loops within the main loop to switch between signal states
  }
}

void signalgg(int valA1, int valA2, int valA3) {
  digitalWrite(GreenLeft, LOW); //Turns Green facing Left On
  digitalWrite(YellowLeft, HIGH);
  digitalWrite(RedLeft, HIGH);
  digitalWrite(GreenRight, LOW); //Turns Green facing Right On
  digitalWrite(YellowRight, HIGH);
  digitalWrite(RedRight, HIGH);
  digitalWrite(BlockLeftSend, LOW); //No signal from this block
  digitalWrite(BlockRightSend, LOW); //No signal from this block
  Serial.println("No Train");//Allows text to be seen in serial monitor
  delay(1);
  clearcount = 0;

  if ((valA1 > 0) && (valA2 < 750) && (valA3 > 750)) {
    signalState = ST_GY;//Block clear but Block to Right occupied
  }
  if ((valA1 > 0) && (valA2 > 750) && (valA3 < 750)) {
    signalState = ST_YG;//Block clear but Block to Right occupied
  }
  if ((valA1 > 0) && (valA2 > 750) && (valA3 > 750)) {
    signalState = ST_YY;//Block clear but Blocks to Left and Right are occupied
  }
  if (valA1 < 1) {
    signalState = ST_RR;//Block occupied
  }
}
void signalgy(int valA1, int valA2, int valA3) {
  digitalWrite(GreenLeft, LOW); //Turns Green facing Left On
  digitalWrite(YellowLeft, HIGH);
  digitalWrite(RedLeft, HIGH);
  digitalWrite(GreenRight, HIGH);
  digitalWrite(YellowRight, LOW);// Turns Yellow facing Righ On
  digitalWrite(RedRight, HIGH);
  digitalWrite(BlockLeftSend, LOW); //No signal from this block
  digitalWrite(BlockRightSend, LOW); //No signal from this block
  Serial.println("Approach with Caution");//Allows text to be seen in serial monitor
  delay(1);


  if ((valA1 > 0) && (valA2 < 750) && (valA3 < 750)) {
    signalState = ST_GG;//Block clear
  }
  if ((valA1 > 0) && (valA2 > 750) && (valA3 < 750)) {
    signalState = ST_YG;//Block clear but Block to Right occupied
  }
  if ((valA1 > 0) && (valA2 > 750) && (valA3 > 750)) {
    signalState = ST_YY;//Block clear but Blocks to Left and Right are occupied
  }
  if (valA1 < 1) {
    signalState = ST_RR;//Block occupied
  }
}
void signalyg(int valA1, int valA2, int valA3) {
  digitalWrite(GreenLeft, HIGH);
  digitalWrite(YellowLeft, LOW);//Turns Yellow facing Left On
  digitalWrite(RedLeft, HIGH);
  digitalWrite(GreenRight, LOW); //Turns Green facing Right On
  digitalWrite(YellowRight, HIGH);
  digitalWrite(RedRight, HIGH);
  digitalWrite(BlockLeftSend, LOW); //No signal from this block
  digitalWrite(BlockRightSend, LOW); //No signal from this block
  Serial.println("No Train Ahead");//Allows text to be seen in serial monitor
  delay(1);


  if ((valA1 > 0) && (valA2 < 750) && (valA3 > 750)) {
    signalState = ST_GY;//Block clear but Block to Left occupied
  }
  if ((valA1 > 0) && (valA2 < 750) && (valA3 < 750)) {
    signalState = ST_GG;//Block clear
  }
  if ((valA1 > 0) && (valA2 > 750) && (valA3 > 750)) {
    signalState = ST_YY;//Block clear but Blocks to Left and Right are occupied
  }
  if (valA1 < 1) {
    signalState = ST_RR;//Block occupied
  }
}
void signalyy(int valA1, int valA2, int valA3) {
  digitalWrite(GreenLeft, HIGH);
  digitalWrite(YellowLeft, LOW);//Turns Yellow facing Left On
  digitalWrite(RedLeft, HIGH);
  digitalWrite(GreenRight, HIGH);
  digitalWrite(YellowRight, LOW);//Turns Yellow facing Right On
  digitalWrite(RedRight, HIGH);
  digitalWrite(BlockLeftSend, LOW); //No signal from this block
  digitalWrite(BlockRightSend, LOW); //No signal from this block
  Serial.println("No Train");//Allows text to be seen in serial monitor
  delay(1);


  if ((valA1 > 0) && (valA2 < 750) && (valA3 > 750)) {
    signalState = ST_GY;//Block clear but Block to Left occupied
  }
  if ((valA1 > 0) && (valA2 > 750) && (valA3 < 750)) {
    signalState = ST_YG;//Block clear but Block to Right occupied
  }
  if ((valA1 > 0) && (valA2 < 750) && (valA3 < 750)) {
    signalState = ST_GG;//Block clear but Blocks to Left and Right are occupied
  }
  if (valA1 < 1) {
    signalState = ST_RR;//Block occupied
  }
}
void signalrr(int valA1, int valA2, int valA3) {
  digitalWrite(GreenLeft, HIGH);
  digitalWrite(YellowLeft, HIGH);
  digitalWrite(RedLeft, LOW);//Turns Red facing Left On
  digitalWrite(GreenRight, HIGH);
  digitalWrite(YellowRight, HIGH);
  digitalWrite(RedRight, LOW);//Turns Red facing Right On
  digitalWrite(BlockLeftSend, HIGH); //signal from this block
  digitalWrite(BlockRightSend, HIGH); //signal from this block
  Serial.println("Block is Occupied");//Allows text to be seen in serial monitor
  delay(1);

  if (valA1 > 0) {
    signalState = ST_YY1; //switches over to the transition yellow signal state
  }
}

void signalyy1(int valA1, int valA2, int valA3) {
  digitalWrite(GreenLeft, HIGH);
  digitalWrite(YellowLeft, LOW);//Turns Yellow facing Left On
  digitalWrite(RedLeft, HIGH);
  digitalWrite(GreenRight, HIGH);
  digitalWrite(YellowRight, LOW);//Turns Yellow facing Right On
  digitalWrite(RedRight, HIGH);
  digitalWrite(BlockLeftSend, HIGH); //signal from this block
  digitalWrite(BlockRightSend, HIGH); //signal from this block
  Serial.println("Train just left block");//Allows text to be seen in serial monitor
  delay(1);

  if ((valA1 > 0) && (clearcount < 5)) {
    clearcount++;
  }
  else if ((valA1 < 1) && (clearcount < 5)) {
    clearcount = 0; //checks for false positives of clear track
  }
  else if ((valA1 > 0) && (clearcount > 4)) {
    signalState = ST_YY;//goes to signal state YY where occupancy status is checked
  }
}