Fun with Arduino 20 Railway Crossing Part 4: Putting it all Together

STD_Crossing_smallIn the previous three videos we prepared the parts that we need: blinking LEDs, the servo for the gate and the train detection sensors. It’s now time to put it all together and create a working railway crossing.

This is the State Transition Diagram that specifies the sequence. Note: follow this link for a UK version. We’ll translate this into code with the 5 step process:

  1. create a switch(state) with as many cases as we have states
  2. create a switch(transition) with as many cases as we have transitions
  3. add an if() test for every event in a state and point to a transition
  4. add code to be executed in the states, this runs every loop()
  5. add code to be executed in the transitions, this runs only once

Read on below the video …

Steps 1 and 2 are straightforward:

switch(state) {
  case 1: // idle
  break;

  case 2: // blinking 
  break;

  case 3: // blinking, gate closing
  break;

  case 4: // blinking, gate opening
  break;
}

switch(transition) { // train detected by sensor 1
  case 12:
    transition = 0;
    state = 2;
  break;

  case 23: // gate delay time has passed
    transition = 0;
    state = 3;
  break;

  case 34:  // train detected by sensor 2
    transition = 0;
    state = 4;
  break;

  case 41: // gate is fully open
    transition = 0;
    state = 1; 
  break;
}

Step 3: add the if() tests in the states.

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;
}

Step 4 is to add code in the states, however, besides the if() tests we don’t need any code.

Step 5: add code it the transitions for the actions that need to take place.

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");
    setpoint = GATE_CLOSED;
    transition = 0;
    state = 3;
  break;

  case 34:
    Serial.println("Train detected, open the gate");
    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;
    transition = 0;
    state = 1; 
  break;
}

This is the code to control the servo, which we place at the end of loop():

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

And this is the code to control the blinking LEDs, it’s also placed at the end of loop():

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);

Now we add our #defines, variable declarations and setup() and combine it all together into one sketch:

#define GATE_SPEED   120 // [ms] lower number is higher servo speed
#define GATE_DELAY  4000 // [ms] delay time before gate closes 
#define GATE_OPEN     60 // servo angle
#define GATE_CLOSED  135 // servo angle
#define BLINK_SPEED  400 // [ms] smaller number is faster blinking
#define SERVO_PIN      3
#define LED1_PIN       6
#define LED2_PIN       7
#define SENSOR1_PIN   10
#define SENSOR2_PIN   11

byte state = 1, transition;
byte led1, led2, 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_servo;

void setup() {
  pinMode(SENSOR1_PIN, INPUT_PULLUP);
  pinMode(SENSOR2_PIN, INPUT_PULLUP);
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  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");
}

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_servo.attach(SERVO_PIN);
      setpoint = GATE_CLOSED;
      transition = 0;
      state = 3;
    break;
  
    case 34:
      Serial.println("Train detected, open the gate");
      gate_servo.attach(SERVO_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;
      gate_servo.detach(); // to avoid servo flutter
      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_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);
}

Time to test … yes … this work fine. That was quite a fun project. Many systems can be described with a State Transition Diagram and the 5 steps to translate them into working code are easy to follow through.

In the next video we are going to look at ‘blink’ again. What? Again? Yes … it is fun to blink some LED’s in a police car, or a fire fighter car, or an ambulance. We’re going to make a ‘step sequencer’ for this, with which we can define any blink pattern, for multiple LEDs, with only some configuration to do but without changing the code. In the process we will come across new coding subjects: an array, which is a collection of variables that can be accessed with a pointer, and the for() { … } loop, which repeats itself a specified number of times.

— 0 —

2 thoughts on “Fun with Arduino 20 Railway Crossing Part 4: Putting it all Together

  1. Hi Rudy, Great videos and training on arduino. I am just a beginner on arduino and have used your sketch to run my components on my crossing components. Works like the real thing. Am I correct that program only works in one direction I.e. sensor 1 turns on the circuit and sensor 2 stops the blinking leds. Or have i got something wrong in my sketch. If the program only works in one direction, is there a way that it can be made to work in both directions. I hope that makes sense. Keep up the good work.
    PS I really loved your train conctroller videos. I was sad when you stopped making them.

    Thanks again
    Paul

    Like

    • Yes, the sketch as it is now only works in one direction.

      To make it work in 2 directions you can add similar states and transitions, but now for the other direction (essentially meaning the other sensor now triggers first) and you’re good to go.

      The TC videos stopped because I ran out of subjects to talk about.

      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 )

Google photo

You are commenting using your Google 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