Fun with Arduino 42 Railway Crossing Multi Track Two Way

In post & video 20 we made a railway crossing, but it was just one way. There have been requests for a two way railway crossing and also for two tracks. Well … why limit ourselves to two tracks … here’s a software download for a multi track two way crossing.

For every track two sensors need to be placed, one left and one right from the sensor. The distance of the sensors to the gate should be such that there is enough time to close the gate before the train is there.

Some types of sensor can trigger multiple times with one train, like optical sensors that see every wagon. To deal with this an ‘end of train’ detection is built in. If the departure sensor is triggered, a timer is started. If the sensor triggers again before the timer was at zero, the timer resets again. When the timer reaches zero, the software decides this was the end of train.

Such timer is not 100% fail safe. When a train stands still while the optical sensor beam is right in between two wagons, ‘end of train’ may be given falsely. Chances of this happening are very slim though and at the moment I don’t know of another way to detect ‘end of train’ without using additional sensors. A workaround is to place the sensor beam diagonal.

Of course with multiple tracks it is possible another train is coming while the cycle that blinks the LEDs and closes the gate is already running. To handle this, a train counter is introduced, which is incremented each time a new train is detected and is decremented when an ‘end_of_train’ is detected. When the counter reaches zero the gates open and the cycle stops.

#define GATE_SPEED          120 // [ms] lower number is higher servo speed
#define BLINK_SPEED         400 // [ms] smaller number is faster blinking
#define GATE_DELAY         2000 // [ms] time between start blinking and gate closing
#define END_OF_TRAIN_DELAY 2000 // [ms] time to wait before deciding this was the end of the train
#define GATE_OPEN_ANGLE      90
#define GATE_CLOSED_ANGLE    10
#define SERVO_PIN            12
#define LED1_PIN              2
#define LED2_PIN              3
#define NUM_SENSORS           4 // two sensors per track, one left and one right of the gate
byte sensor_pin[NUM_SENSORS]  = {8,9,10,11}; // sensor pin numbers

byte state = 1, train_counter, n;
byte led1, led2, blink_enabled;
byte    angle = GATE_OPEN_ANGLE;
byte setpoint = GATE_OPEN_ANGLE;
byte sensor_state[NUM_SENSORS];       // 0 idle, 1 detect arrival, 2 detect departure, 3 detect end of train
byte end_of_train[NUM_SENSORS];       // 0 idle, 1 end of train detected
unsigned long time_to_blink;
unsigned long time_to_close_gate;
unsigned long time_for_servo_step;
unsigned long time_end_of_train[NUM_SENSORS];

#include <Servo.h>
Servo gate_servo;

void setup() {
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  for (byte i = 0; i < NUM_SENSORS; i++) pinMode(sensor_pin[i], INPUT_PULLUP);
  gate_servo.attach(SERVO_PIN);
  gate_servo.write(angle);
  Serial.begin(9600);
  Serial.println("Railway Crossing Control Ready");
  Serial.println();
  Serial.println("Waiting for train");
  for (byte i = 0; i < NUM_SENSORS; i++) sensor_state[i] = 1; // enable sensors for train detection
}

void loop() {

  for (byte i = 0; i < NUM_SENSORS; i++) {
    if(sensor_state[i] == 1) { // detect arrival of new train
      if(!digitalRead(sensor_pin[i])) { // train detected
        train_counter++;
        sensor_state[i] = 0;
        if(i%2) n = i - 1; else n = i + 1;
        sensor_state[n] = 2; // buddy sensor departure detection enabled
        Serial.print("Arrival:   ");
        Serial.println(i);
        Serial.print("Trains:    ");
        Serial.println(train_counter);
      }
    }
    else if(sensor_state[i] > 1) {
      if(!digitalRead(sensor_pin[i])) { // departure detected
        time_end_of_train[i] = millis() + (unsigned long)END_OF_TRAIN_DELAY;
        if(i%2) n = i - 1; else n = i + 1;
        sensor_state[n] = 1; // buddy sensor enabled again
        if(sensor_state[i] == 2) {
          Serial.print("Departure: ");
          Serial.println(i);
        }
        sensor_state[i] = 3;
      }
      if(sensor_state[i] == 3) // decide if end of train has passed based on a timer
        if(millis() > time_end_of_train[i]) end_of_train[i] = 1; 
      if(end_of_train[i]) { // this takes care train_counter-- is executed only once
        train_counter--;
        end_of_train[i] = 0;
        sensor_state[i] = 1;
        Serial.print("Trains:    ");
        Serial.println(train_counter);
      }
    }
  }

  switch (state) {
    case 1: // gate open, not blinking, waiting for train arrival
      if(train_counter) state = 12;
    break;

    case 12: // train arrival detected, blinking
      Serial.println("Binking started");
      blink_enabled = 1;
      time_to_close_gate = millis() + (unsigned long)GATE_DELAY;
      state = 2;
    break;
      
    case 2: // blinking, wait until it's time to close the gate
      if (millis() > time_to_close_gate) state = 23; // gate delay time has passed
    break;

    case 23: // close the gate
      Serial.println("Gate closing");
      gate_servo.attach(SERVO_PIN);
      setpoint = GATE_CLOSED_ANGLE;
      state = 3;
    break;

    case 3: // gate is closing, blinking
      if(angle == setpoint) {
        Serial.println("Gate closed");
        state = 4;
      }
    break;
    
    case 4: // gate fullly closed, blinking, waiting for train departure
      if(train_counter == 0) state = 45;
    break;

    case 45: // train departure detected, open the gate, blinking
      Serial.println("Gate opening");
      gate_servo.attach(SERVO_PIN);
      setpoint = GATE_OPEN_ANGLE;        
      state = 5;
    break;

    case 5: // wait until gate is fully opened, blinking
      if(train_counter) state = 23;
      if (angle == setpoint) state = 51;
    break;

    case 51: // gate is fully opened, stop blinking
//      for (byte i = 0; i < NUM_SENSORS; i++) sensor_state[i] = 1;
      blink_enabled = 0;
      led1 = 0;
      led2 = 0;
      gate_servo.detach(); // to avoid servo flutter
      state = 1; 
      Serial.println("Gate open, blinking stopped");
      Serial.println();
      Serial.println("Waiting for train");
    break;
  }

  if (millis() > time_for_servo_step) {
    time_for_servo_step = millis() + (unsigned long)GATE_SPEED;
    if (angle < setpoint) angle++;
    if (angle > setpoint) angle--;
    gate_servo.write(angle);
  }
    
  if(blink_enabled == 1) {
    if(millis() > time_to_blink) {
      time_to_blink = millis() + (unsigned long)BLINK_SPEED;
      led1 = !led1;
      led2 = !led1;
    }
  }

  digitalWrite(LED1_PIN, led1);
  digitalWrite(LED2_PIN, led2);
}

— 0 —

21 thoughts on “Fun with Arduino 42 Railway Crossing Multi Track Two Way

  1. Hello, and a very huge thank you for this arduino grade crossing update that you are so wonderfully giving to the model railroading community. It is people like you with such great talent and knowledge who are willing to share with us for free. On behalf of myself, my friends and the model railroading community, thank you and stay safe.

    Liked by 1 person

  2. Thanks Rudy for the sketch as this is what I was looking for.
    I slightly modified it for the use of 2 sensors. Why? I am using 2 tracks in a railroad crossing situation with 2 servo and a MP3 playing some bell sound and singing birds every time a loco passes. To avoid false triggering I 3D printed some supports that allow me to install 2 modified sensors: an IR emitting LED at 45° of the IR receiver.
    I tested the whole bunch with Hall sensors and also with not modified IR sensors and everything works exactly as I expected but … when the modified IR sensors are in use I am in trouble.
    Due to the modification (IR emitting LED in front of the IR receiver) the whole sketch is started at power up. So somewhere something has to be inverted. But what?

    Like

    • Hi. At several places in the code you’ll find digital read statements. The have an exclamation mark ! in front of them. The ! means ‘NOT’. If you take them out you’ll have your signal reads inverted.

      Like

  3. got this working great put the extra servo and 2 more leds into the code and just delete 2 sensors and adjusted the code for them. a brilliant piece of work Rudy.

    Like

  4. Rudy
    Can you explain how this bit of the code works please
    void loop() {

    for (byte i = 0; i < NUM_SENSORS; i++) {
    if(sensor_state[i] == 1) { // detect arrival of new train
    if(!digitalRead(sensor_pin[i])) { // train detected
    train_counter++;
    sensor_state[i] = 0;
    if(i%2) n = i – 1; else n = i + 1;
    sensor_state[n] = 2; // buddy sensor departure detection

    i think
    it takes the number of the sensor ie 0,1,2, or 3
    and it works out that n = its buddy number using " if(i%2) n = i – 1; else n = i + 1;"
    using modulo operator i can understand if sensor 0 then buddy would be sensor 1 and the other way around. but i cant work out how we get the sensor 2 then buddy = 3 and the other way around. is it to do with the else statment?

    regards

    john

    Like

    • Sensor numbers are in pairs: 0,1 then 2,3 then 4,5. The modulo 2 statement tells us it’s odd or even, for any number the result is 0 (even) or 1 (odd). If the number is odd, then one is subtracted to find its buddy (if the triggered sensor was 3, then its buddy is 2). Else, the triggered sensor was even and then we add one to get to the buddy.

      Liked by 1 person

      • Rudy
        Thank you so much i am fairly new to coding so some of the code structure is new to me. i think this code is amazing. i did enjoy the previous versions you did which i must admit i found easier to understand and follow and liked the use of the transition() between the states.
        it would be great for newer models and arduino users if you could do a video for this new code as it would teach us all a lot more. i know for me i like to learn from others and not just put the code to use without understanding it.
        The parts are now being 3d printed to house the servo’s and led housing on the way as well, i am looking forward to seeing this on my layout.

        Like

  5. Hello, Rudy, Excellent videos and better yet are your explanations of how the code works. I have been able to get almost all of the Fun with Arduino projects to work as you described. How ever I am having some trouble with #20 and #42 Railway crossing. First I down loaded RBO_Railway_Crossing_Multi_Track_Two_Way.ino and I loaded it to an arduino nano and it seemed to work OK however. When I had a long train activate the Departure sensor while the Arrival sensor is still activated, lights flash and never stop even after train is long gone. The message that prints out seems to indicate that the Trains count just goes from 1 to 255 and then repeats. See below.

    I noticed in the video with #20 you tested with a train that did not cover both sensors at the same time. So maybe you would not have seen this problem.

    I also noticed that the first line in Case 51 is commented out. I tried removing the comment “//” but the result was the same.

    Short train works OK;
    Waiting for train
    Arrival: 1
    Trains: 1
    Binking started
    Gate closing
    Gate closed
    Departure: 0
    Trains: 0
    Gate opening
    Gate open, blinking stopped

    Long train does not work correctly;
    Waiting for train
    Arrival: 1
    Trains: 1
    Binking started
    Gate closing
    Gate closed
    Departure: 0
    Arrival: 1
    Trains: 2
    Departure: 0
    Arrival: 1
    Trains: 3
    Departure: 0
    Arrival: 1
    Trains: 4
    Departure: 0
    Arrival: 1
    Trains: 5
    Departure: 0
    Arrival: 1
    Trains: 6

    I want to thank you for the most exultant work and for sharing so we all can learn.
    Milton

    Like

    • Hi Milton. It seems like you ran into a situation which I never tested. I never thought trains would be that long. 🙂 Looks like we start looking at the entry sensor too soon, we have to wait looking at that sensor until the train has departed. I’ll look into it.

      Like

    • Hi Milton,
      I have no means of testing available right now. Could you try the following:

      Find the line that says:
      sensor_state[n] = 1; // buddy sensor enabled again
      With me this is line 71, but your line numbers may differ some.

      Cut this line away there and paste it between the lines that read:
      if(end_of_train[i]) { // this takes care train_counter– is executed only once
      train_counter–;
      With me these are lines 80 and 81.

      Like

      • Hi Rudy. Thank you for you help. I have made the changes that you recommended and every thing seems to work as it should. Have a good day.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s