Programming arduino to control railroad level crossing

Hii

I'm working on my final project for high school and my theme is model railway with locomotive on remote control.
I found this code somewhere on the internet and it works for 1 level crossing but I have 2 crossings.
I have 2 sensors which can output both digital and analog signals, I'm using digital ones.
The shape of tracks you can see on image 1.
Sensors are 1 and 2, level crossings are 3 and 4.

I'm going to control 1 level crossing with arduino uno and second one with dasduino core.
Issue I'm having is I can't take 2 outputs from 1 sensor so I was thinking of using some 2 pins on arduino as output but as input on dasduino.

It should work like this
Sensor 1 detects a locomotive, LEDs start to blink (2 LEDs), after 2/3 seconds servo motors close the gate and wait until sensor 2 detects that locomotive has passed that level crossing then servo motors open the gate, and LEDs stop blinking after servo motors have opened the gate. This code does all that. If I stop locomotive just after sensor 2 so level crossing 4 is active (LEDs blinking, servos closing the gate), and if I go backwards with locomotive the code should check are leds blinking or servos closing or have closed the gate on level crossing 4 and if it is true it should start procedure to open the gate and stop blinking when gate is open on level crossing 4, but it should check are LEDs blinking or servos closing/have closed the gate (they shouldn't blink, the gate should be open) but if sensor 2 detects a locomotive is going backwards then it should activate procedure to close the level crossing 1 if locomotive goes all the way through that level crossing 1 in reverse then sensor 1 detects locomotive has passed that crossing it should open the gate and stop blinking.
When sensor 2 detects a train LEDs start to blink on level crossing 4, after 2/3 seconds servos close the gate and its closed until sensor 1 detects that locomotive has passed that level crossing when servos open the gate and LEDs stop blinking.

And it should be able to go over and over but in both direction as I have explained for level crossing 1 because locomotive will be able to go backwards.

I hope you will be able to understand me, and I apologize for any language mistakes I might have made.

1

#define GATE_SPEED   70  // [ms] lower number is higher servo speed
#define GATE_DELAY  3000 // [ms] delay time before gate closes 
#define GATE_OPEN     0  // servo angle
#define GATE_CLOSED  90  // servo angle
#define BLINK_SPEED  500 // [ms] smaller number is faster blinking
#define SERVO1_PIN    6
#define SERVO2_PIN    7
#define LED1_PIN      10
#define LED2_PIN      11
#define LED3_PIN      12
#define LED4_PIN      13
#define SENSOR1_PIN   3
#define SENSOR2_PIN   4

byte state = 1, transition;
byte led1, led2, led3, led4, blink_enabled;
byte angle    = GATE_OPEN;
byte setpoint = GATE_OPEN;
unsigned long time_to_blink;
unsigned long time_to_close_gate;
unsigned long time_for_servo;

#include <Servo.h>
Servo gate_servo1, gate_servo2;

void setup() {
  pinMode(SENSOR1_PIN, INPUT_PULLUP);
  pinMode(SENSOR2_PIN, INPUT_PULLUP);
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
  gate_servo1.attach(SERVO1_PIN);
  gate_servo2.attach(SERVO2_PIN);
  gate_servo1.write(angle);
  gate_servo2.write(angle);
  Serial.begin(9600);
  Serial.println("Railway Crossing Control Ready");
  Serial.println();
  Serial.println("Waiting for train");
}

void loop() {

  switch(state) {
    case 1: // idle
      if(digitalRead(SENSOR1_PIN) == LOW) transition = 12;
    break;
  
    case 2: // blinking, gate still open
      if (millis() > time_to_close_gate) transition = 23;
    break;
  
    case 3: // blinking, gate closing
      if(digitalRead(SENSOR2_PIN) == LOW) transition = 34;
    break;
  
    case 4: // blinking, gate opening
      if(angle == GATE_OPEN) transition = 41;
    break;
  }

  switch(transition) {
    case 12: //
      Serial.println("Train detected, start blinking");
      blink_enabled = 1;
      time_to_close_gate = millis() + (unsigned long)GATE_DELAY;
      transition = 0;
      state = 2;
    break;
  
    case 23: //
      Serial.println("Time to close the gate");
      gate_servo1.attach(SERVO1_PIN);
      gate_servo2.attach(SERVO2_PIN);
      setpoint = GATE_CLOSED;
      transition = 0;
      state = 3;
    break;
  
    case 34:
      Serial.println("Train detected, open the gate");
      gate_servo1.attach(SERVO1_PIN);
      gate_servo2.attach(SERVO2_PIN);
      setpoint = GATE_OPEN;        
      transition = 0;
      state = 4;
    break;
  
    case 41:
      Serial.println("Gate is open, stop blinking");
      Serial.println();
      Serial.println("Waiting for train");
      blink_enabled = 0;
      led1 = 0;
      led2 = 0;
      led3 = 0;
      led4 = 0;
      gate_servo1.detach(); // to avoid servo flutter
      gate_servo2.detach();
      transition = 0;
      state = 1; 
    break;
  }

  if (millis() > time_for_servo) {
    time_for_servo = millis() + (unsigned long)GATE_SPEED;
    if (angle < setpoint) angle++;
    if (angle > setpoint) angle--;
    gate_servo1.write(angle);
    gate_servo2.write(angle);
  }
    
  if(blink_enabled == 1) {
    if(millis() > time_to_blink) {
      time_to_blink = millis() + (unsigned long)BLINK_SPEED;
      led1 = !led1;
      led2 = !led1;
      led3 = !led3;
      led4 = !led3;
    }
  }
  digitalWrite(LED1_PIN, led1);
  digitalWrite(LED2_PIN, led2);
  digitalWrite(LED3_PIN, led3);
  digitalWrite(LED4_PIN, led4);
}

I am not sure what sensors you are using. Would be helpful to explain what they are but it is possible to wire signal outputs to multiple inputs. It is not wise to do it without knowing what they are.
Also I am not sure you need another arduino. I think one could do the job. Looks like software to me.

it seems you need a better state machine

More sensors are required if you want to detect the direction.
More states are required for the second crossing.

having built my own model RR speed trap (for measuring speed) and having simulated your code, i suggest

  • use descriptive state values: Idle, PreBlink, Closed, PostBlink instead of numeric values

  • there's certainly no need in your case to have separate switch statements or the states and transitions. they can be combined

    switch (state) {
    case Idle: // idle
        if (LOW == s1 || LOW == s2)  {
            Serial.println ("  Train detected, start blinking");
            blink_enabled = 1;
            time_to_close_gate = msec + (unsigned long)GATE_DELAY;
            sN = LOW == s1 ? s2 : s1;
            state = PreBlink;
        }
        break;
  • the gate isn't lifted when the train reaches the 2nd sensor. it gets lifted when neither sensor detects a train or car for some period of time. so while in the active state, Closed, capture a time stamp and lift the gate when that timer expires

  • in order to detect trains in both directions, both sensors need to be monitored. then wait for neither sensor to be active (see above)

I'm using TCRT 5000 infrared obstacle sensor.
This one, but without potentiometer and LM393.

https://www.instructables.com/TCRT5000-Infrared-Reflective-Sensor-How-It-Works-a/

Okay, I decided I will not complicate it. Locomotive will not go backwards after sensors. So I need just to code dasduino to close the gate when sensor 2 detects a locomotive and when sensor 1 detects locomotive to open the gate. I think I can't do it with arduino only because all together I need 4 output pins for servo motors, 2 input pins for sensors, 8 output pins for LEDs.

I think I would be able to do it myself if I understood the complete code but I don't since there is so many switch statements.

Hello bicanict

Post a detailed circuit diagram to see how we can help.

as i said, you shouldn't close the gate when the locomotive reaches the 2nd sensor, what about the rest of the train?

you should only close the gate after both sensors are inactive

Why not? Can't you wire the sensor to as many inputs, on as many processors, as you want?

As long as all of the devices share a common ground, one output can be fed to as many inputs as needed.

If the systems do not enjoy a common ground, you could drive as many optoisolators as you need. If there were a great number, you might have to use a transistor switch to amplify the digital output so it could drive them. But two should work OK.

a7

I will have only locomotive there won't be like wagons behind locomotive.

does that really matter?
won't what i suggest work with both a single loco or a long train?
what is the proper approach to do this for a train, whether full-size or model?

@bicanict doesn't this also simplify the code as well as work in both directions because the trigger is either input going LOW and all that needs to be done is to wait for neither input to be LOW?

... or is it?

Split the route into segments which can be checked for a train inside. A segment is occupied from the leading sensor detecting an entry (going on) to the ending sensor going on (leaving) then off (has left). Close a crossing as long as anything is in that segment.

Use an port extender for all signals (sensors, LEDs...) of every single crossing, to reduce the line count to the controller and to allow for easy addition of further crossings.

Later you can build a class with all signals of a crossing and its individual transition state, so that an array of crossing objects can be implemented to handle all crossings in the same way in a single loop.

As the code is, both SERVOS raise and lower together. The "open" initiating sensor is also the "close" initiating sensor. This simulation could use the blue and black button as the direction sensors for the second level crossing. A left-to-right button press or right-to-left button press could be recognized as direction.

https://wokwi.com/projects/364096889426571265

Hello xfpd

Post the sketch in code tags for mobil users.

Same sketch as post #1... I just wanted to show the sketch in action... I added comments/suggestions in post #14

#define GATE_SPEED   70  // [ms] lower number is higher servo speed
#define GATE_DELAY  3000 // [ms] delay time before gate closes 
#define GATE_OPEN     0  // servo angle
#define GATE_CLOSED  90  // servo angle
#define BLINK_SPEED  500 // [ms] smaller number is faster blinking
#define SERVO1_PIN    6
#define SERVO2_PIN    7
#define LED1_PIN      10
#define LED2_PIN      11
#define LED3_PIN      12
#define LED4_PIN      13
#define SENSOR1_PIN   3
#define SENSOR2_PIN   4

byte state = 1, transition;
byte led1, led2, led3, led4, blink_enabled;
byte angle    = GATE_OPEN;
byte setpoint = GATE_OPEN;
unsigned long time_to_blink;
unsigned long time_to_close_gate;
unsigned long time_for_servo;

#include <Servo.h>
Servo gate_servo1, gate_servo2;

void setup() {
  pinMode(SENSOR1_PIN, INPUT_PULLUP);
  pinMode(SENSOR2_PIN, INPUT_PULLUP);
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
  gate_servo1.attach(SERVO1_PIN);
  gate_servo2.attach(SERVO2_PIN);
  gate_servo1.write(angle);
  gate_servo2.write(angle);
  Serial.begin(9600);
  Serial.println("Railway Crossing Control Ready");
  Serial.println();
  Serial.println("Waiting for train (green button)");
}

void loop() {

  switch (state) {
    case 1: // idle
      if (digitalRead(SENSOR1_PIN) == LOW) transition = 12;
      break;

    case 2: // blinking, gate still open
      if (millis() > time_to_close_gate) transition = 23;
      break;

    case 3: // blinking, gate closing
      if (digitalRead(SENSOR2_PIN) == LOW) transition = 34;
      break;

    case 4: // blinking, gate opening
      if (angle == GATE_OPEN) transition = 41;
      break;
  }

  switch (transition) {
    case 12: //
      Serial.println("Train detected, start blinking");
      blink_enabled = 1;
      time_to_close_gate = millis() + (unsigned long)GATE_DELAY;
      transition = 0;
      state = 2;
      break;

    case 23: //
      Serial.println("Time to close the gate");
      gate_servo1.attach(SERVO1_PIN);
      gate_servo2.attach(SERVO2_PIN);
      setpoint = GATE_CLOSED;
      transition = 0;
      state = 3;
      break;

    case 34:
      Serial.println("Train detected, open the gate");
      gate_servo1.attach(SERVO1_PIN);
      gate_servo2.attach(SERVO2_PIN);
      setpoint = GATE_OPEN;
      transition = 0;
      state = 4;
      break;

    case 41:
      Serial.println("Gate is open, stop blinking");
      Serial.println();
      Serial.println("Waiting for train");
      blink_enabled = 0;
      led1 = 0;
      led2 = 0;
      led3 = 0;
      led4 = 0;
      gate_servo1.detach(); // to avoid servo flutter
      gate_servo2.detach();
      transition = 0;
      state = 1;
      break;
  }

  if (millis() > time_for_servo) {
    time_for_servo = millis() + (unsigned long)GATE_SPEED;
    if (angle < setpoint) angle++;
    if (angle > setpoint) angle--;
    gate_servo1.write(angle);
    gate_servo2.write(angle);
  }

  if (blink_enabled == 1) {
    if (millis() > time_to_blink) {
      time_to_blink = millis() + (unsigned long)BLINK_SPEED;
      led1 = !led1;
      led2 = !led1;
      led3 = !led3;
      led4 = !led3;
    }
  }
  digitalWrite(LED1_PIN, led1);
  digitalWrite(LED2_PIN, led2);
  digitalWrite(LED3_PIN, led3);
  digitalWrite(LED4_PIN, led4);
}

May be with Google translate you can make good use of what’s on this site

https://www.locoduino.org/

consider

const byte PinSense1 = A1;
const byte PinSense2 = A2;

const byte PinLed1 = 13;
const byte PinLed2 = 12;
byte PinSenseN;

const unsigned long MsecPeriod = 500;
unsigned long msecLst;

const unsigned long MsecPeriod2 = 1500;
unsigned long msecLst2;


enum { Idle, Active, Wait };
int state = Idle;

enum { Off = HIGH, On = LOW };

// -----------------------------------------------------------------------------
void
loop (void)
{
    unsigned long msec = millis ();
    if (Idle != state && msec - msecLst >= MsecPeriod)  {
        msecLst = msec;

        digitalWrite (PinLed1, ! digitalRead (PinLed1));
        digitalWrite (PinLed2, ! digitalRead (PinLed1));
    }

    byte s1 = digitalRead (PinSense1);
    byte s2 = digitalRead (PinSense2);
    byte sN = digitalRead (PinSenseN);

    switch (state) {
    case Idle :
        if (LOW == s1 || LOW == s2)  {
            PinSenseN = LOW == s1 ? PinSense2 : PinSense1;
            state = Active;
            Serial.println (" Active");
        }
        break;

    case Active :
        if (LOW == sN)  {
            state = Wait;
            Serial.println ("  Wait");
        }
        break;

    case Wait :
        if (LOW == s1 || LOW == s2)  {
            msecLst2 = msec;
        }
        else if (msec - msecLst2 >= MsecPeriod2)  {
            digitalWrite (PinLed1, Off);
            digitalWrite (PinLed2, Off);
            Serial.println ("Idle");
            state = Idle;
        }
        break;
    }
}

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

    pinMode (PinSense1, INPUT_PULLUP);
    pinMode (PinSense2, INPUT_PULLUP);

    digitalWrite (PinLed1, Off);
    digitalWrite (PinLed2, Off);
    pinMode (PinLed1, OUTPUT);
    pinMode (PinLed2, OUTPUT);
}

Hi all.

I just wanted to say that I programmed arduino successfully and it works just the way it should. Thanks for all the help, I appreciate it.

Hello

Post the solution for the forum members who had helped.