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 —