Skip to content

Instantly share code, notes, and snippets.

@cdarringer
Created October 11, 2013 03:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cdarringer/6929060 to your computer and use it in GitHub Desktop.
Save cdarringer/6929060 to your computer and use it in GitHub Desktop.
Arduino railroad crossing, as seen in the following video: http://www.youtube.com/watch?v=2RXiLh64U6o
/*
Railroad crossing controller
*/
#include <Servo.h>
// Constants
const int CYCLE_PERIOD_MS = 10;
const int SENSOR_DELTA_THRESHOLD = 20;
const int BELL_RING_PERIOD = 400;
const int BELL_RING_CYCLES = BELL_RING_PERIOD / CYCLE_PERIOD_MS;
const int BELL_TONE_FREQUENCY = 300;
const int BELL_DURATION = 180;
const int LIGHT_BLINK_PERIOD = 1000;
const int LIGHT_BLINK_CYCLES = LIGHT_BLINK_PERIOD / CYCLE_PERIOD_MS;
const int LIGHT_BLINK_HALF_CYCLES = LIGHT_BLINK_CYCLES / 2;
const float CROSSING_POSITION_RAISED = 110.0;
const float CROSSING_POSITION_LOWERED = 15.0;
const float CROSSING_DEGREES_PER_CYCLE = 0.1;
const int TRAIN_SPEED_MS_PER_SECTION = 1000;
const int TRAIN_SECTIONS_TO_CROSSING = 13;
const int TIME_GATE_WARNING_BEFORE_TRAIN_MS = 7000;
const int TIME_GATE_DOWN_BEFORE_TRAIN_MS = 5000;
const int TIME_GATE_DOWN_AFTER_TRAIN_MS = 2000;
const int TIME_GATE_DOWN_BEFORE_TRAIN_CYCLES = TIME_GATE_DOWN_BEFORE_TRAIN_MS / CYCLE_PERIOD_MS;
const int TIME_GATE_DOWN_AFTER_TRAIN_CYCLES = TIME_GATE_DOWN_AFTER_TRAIN_MS / CYCLE_PERIOD_MS;
const int PIN_SENSOR = 0;
const int PIN_LEFT_LIGHT = 2;
const int PIN_RIGHT_LIGHT = 3;
const int PIN_STATUS_LIGHT_ARRIVAL = 4;
const int PIN_STATUS_LIGHT_DEPARTURE = 5;
const int PIN_BUZZER = 6;
const int PIN_SERVO = 7;
// Enums and globals
enum sensorState {
out, entering, in, exiting
}
sensorState = out;
enum crossingState {
up, warning, lowering, down, raising
}
crossingState = up;
Servo gateServo;
int sensorThreshold = 0;
long trainArrivalCountDownMS = 0;
long trainDepartureCountDownMS = 0;
int bellCycleCounter = 0;
int leftLightCycleCounter = 0;
int rightLightCycleCounter = LIGHT_BLINK_HALF_CYCLES;
float crossingPosition = CROSSING_POSITION_RAISED;
/*
One time initializations
*/
void setup()
{
pinMode(PIN_LEFT_LIGHT, OUTPUT);
pinMode(PIN_RIGHT_LIGHT, OUTPUT);
pinMode(PIN_STATUS_LIGHT_ARRIVAL, OUTPUT);
pinMode(PIN_STATUS_LIGHT_DEPARTURE, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
// we assume that a train is not in the sensor at startup
sensorThreshold = analogRead(PIN_SENSOR) + SENSOR_DELTA_THRESHOLD;
gateServo.attach(PIN_SERVO);
gateServo.write(CROSSING_POSITION_RAISED);
Serial.begin(9600);
}
/*
Main control loop.
Check the sensor, update outputs, and then wait the cycle period.
*/
void loop()
{
checkSensor();
updateTimers();
updateStatusLights();
updateCrossingState();
updateBells();
updateLights();
updateGatePosition();
delay(CYCLE_PERIOD_MS);
}
/*
Check sensor and update transitions or states
*/
void checkSensor()
{
if (isTrainPresentAtSensor()) {
switch (sensorState) {
case in:
// still in...
break;
case entering:
Serial.println("\nsensor -> in");
sensorState = in;
break;
case out:
case exiting:
Serial.println("\nsensor -> entering");
sensorState = entering;
break;
}
} else {
switch (sensorState) {
case in:
case entering:
Serial.println("\nsensor -> exiting");
sensorState = exiting;
break;
case out:
break;
case exiting:
Serial.println("\nsensor -> out");
sensorState = out;
break;
}
}
}
/*
Set the timers when a train arrives,
increment the departure timer if a train is still in the sensor,
decrement the timers if a train is on its way
*/
void updateTimers() {
switch (sensorState) {
case in:
trainDepartureCountDownMS += CYCLE_PERIOD_MS;
break;
case entering:
// is this a new train or an additional train?
if (trainDepartureCountDownMS > 0) {
// it is an additional train...
// leave the arrival time as-is, add the time it takes for the new train to arrive on the departure timer
Serial.println("\nAdditional train detected...");
trainDepartureCountDownMS = calculateTrainArrivalTimeMS() + CYCLE_PERIOD_MS + TIME_GATE_DOWN_AFTER_TRAIN_MS;
// Serial.print("Arrival: ");
// Serial.print(trainArrivalCountDownMS);
// Serial.print("Departure: ");
// Serial.println(trainDepartureCountDownMS);
} else {
// it is a new train...
Serial.println("\nNew train detected...");
trainArrivalCountDownMS = calculateTrainArrivalTimeMS();
trainDepartureCountDownMS = trainArrivalCountDownMS + CYCLE_PERIOD_MS + TIME_GATE_DOWN_AFTER_TRAIN_MS;
// Serial.print("Arrival: ");
// Serial.print(trainArrivalCountDownMS);
// Serial.print("Departure: ");
// Serial.println(trainDepartureCountDownMS);
}
break;
case out:
case exiting:
// do nothing additional
break;
}
if (trainArrivalCountDownMS > 0) {
trainArrivalCountDownMS -= CYCLE_PERIOD_MS;
}
if (trainDepartureCountDownMS > 0) {
trainDepartureCountDownMS -= CYCLE_PERIOD_MS;
}
}
/*
Manage crossing state transitions
*/
void updateCrossingState() {
switch (crossingState) {
case up:
if ((trainDepartureCountDownMS > 0) && (trainArrivalCountDownMS < TIME_GATE_WARNING_BEFORE_TRAIN_MS)) {
Serial.println("\ngate up -> warning");
crossingState = warning;
}
break;
case warning:
if (trainArrivalCountDownMS < TIME_GATE_DOWN_BEFORE_TRAIN_MS) {
Serial.println("\ngate warning -> lowering");
crossingState = lowering;
}
break;
case lowering:
if (crossingPosition <= CROSSING_POSITION_LOWERED) {
Serial.println("\ngate lowering -> down");
crossingState = down;
} else {
crossingPosition -= CROSSING_DEGREES_PER_CYCLE;
}
break;
case down:
if (trainDepartureCountDownMS <= 0) {
Serial.println("\n(expected train departure from gate)");
Serial.println("\ngate down -> raising");
crossingState = raising;
}
break;
case raising:
if (crossingPosition >= CROSSING_POSITION_RAISED) {
Serial.println("\ngate raising -> up");
crossingState = up;
} else {
crossingPosition += CROSSING_DEGREES_PER_CYCLE;
}
break;
}
}
/*
We have one light to show the arrival countdown timer > 0 and
another light to show the departure countdown timer > 0
*/
void updateStatusLights() {
if (trainArrivalCountDownMS > 0) {
digitalWrite(PIN_STATUS_LIGHT_ARRIVAL, HIGH);
} else {
digitalWrite(PIN_STATUS_LIGHT_ARRIVAL, LOW);
}
if (trainDepartureCountDownMS > 0) {
digitalWrite(PIN_STATUS_LIGHT_DEPARTURE, HIGH);
} else {
digitalWrite(PIN_STATUS_LIGHT_DEPARTURE, LOW);
}
}
/*
Helper to determine if the train is in the sensor
*/
boolean isTrainPresentAtSensor() {
int sensorValue = analogRead(PIN_SENSOR);
// Serial.println(sensorValue);
if (analogRead(PIN_SENSOR) > sensorThreshold) {
Serial.print("S");
return true;
} else {
return false;
}
}
/*
We do a very simplistic calculation with the assumption that all
trains move at the same speed. If we had a better sensor we could
figure out how fast an individual train was going
*/
int calculateTrainArrivalTimeMS() {
return TRAIN_SPEED_MS_PER_SECTION * TRAIN_SECTIONS_TO_CROSSING;
}
/*
Bells only ring during the initial warning and
when the gate is lowering
*/
void updateBells()
{
switch (crossingState) {
case warning:
case lowering:
if (bellCycleCounter == 0) {
Serial.print("(B!)");
tone(PIN_BUZZER, BELL_TONE_FREQUENCY, BELL_DURATION);
bellCycleCounter = BELL_RING_CYCLES;
} else {
bellCycleCounter--;
}
break;
}
}
/*
Lights flash continuously when the gate is not up
*/
void updateLights()
{
switch (crossingState) {
case up:
digitalWrite(PIN_LEFT_LIGHT, LOW);
digitalWrite(PIN_RIGHT_LIGHT, LOW);
break;
case warning:
case lowering:
case down:
case raising:
if (leftLightCycleCounter == 0) {
Serial.print("(LL)");
digitalWrite(PIN_LEFT_LIGHT, HIGH);
digitalWrite(PIN_RIGHT_LIGHT, LOW);
leftLightCycleCounter = LIGHT_BLINK_CYCLES;
rightLightCycleCounter--;
} else if (rightLightCycleCounter == 0) {
Serial.print("(RL)");
digitalWrite(PIN_LEFT_LIGHT, LOW);
digitalWrite(PIN_RIGHT_LIGHT, HIGH);
rightLightCycleCounter = LIGHT_BLINK_CYCLES;
leftLightCycleCounter--;
} else {
leftLightCycleCounter--;
rightLightCycleCounter--;
}
break;
}
}
/*
Manage incremental changes to the servo position
*/
void updateGatePosition()
{
switch (crossingState) {
case lowering:
if (crossingPosition > CROSSING_POSITION_LOWERED) {
crossingPosition -= CROSSING_DEGREES_PER_CYCLE;
gateServo.write(crossingPosition); // Move to next position
}
break;
case raising:
if (crossingPosition < CROSSING_POSITION_RAISED) {
crossingPosition += CROSSING_DEGREES_PER_CYCLE;
gateServo.write(crossingPosition); // Move to next position
}
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment