Arduino railroad crossing, as seen in the following video: http://www.youtube.com/watch?v=2RXiLh64U6o
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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