After a break of around two decades I've recently started building a model railway. One of the issues I've faced is trying to work out how fast I should be running the trains so that their speed reflects reality given the scale at which they are modelled. I'm guessing the details won't interest everyone reading this post but if you are interested then
I've blogged about this from the model railway side on one of my other blogs. Suffice it to say that what I needed was to be able to measure the time it took for a model locomotive to travel a certain distance. Now I could have used a ruler and a stop watch but this would never be very accurate. So being me I turned to a more computer oriented solution: an Arduino powered speed trap!
The brief I set myself was simple:
- two switches to measure the time taken to travel a given distance
- a green LED to signify the train was travelling below a given speed limit
- a red LED to signify the speed limit was being broken
- full speed details passed back to the PC for display
The main question was what form of switches should I use. Physical switches were out as I would never be able to accurately place them on the track in a way that any locomotive would be able to trigger them without incident. A
reed switch would be easy to use and to hide on the layout, but would mean adding a magnet to each locomotive which seemed a bit daft. An
infrared beam across the track would also work, but hiding it on the layout would be difficult. In the end the only sensible idea I could come up with was using a
light dependent resistor (LDR) and watching for a sudden change in resistance as the light was blocked by the moving locomotive.
So on my way home on Thursday I called in at my local Maplin store and bought two of the
smallest and flattest LDRs they had (specifically the 1.8k-4.5k version).
Now while I knew that the more light you shine on an LDR the less resistance it has I wasn't sure of the best way of making use of this information in conjunction with the Arduino. Fortunately the web is awash with information and tutorials and I quickly came across
the solution.
So with my two LDRs, two LEDs and a bunch of resistors I knocked together the following (note that both views were generated at the same time using
Fritzing, I really am impressed by this application).
As you can probably see this is a little more complicated than it needs to be as it uses two resistors for each LED, but this was the best I could manage with the resistors I had.
Of course the hardware is only half of the solution. Without appropriate microcode the Arduino isn't going to do anything useful. Fortunately I'm better at writing software than I am at designing circuits so this half was easier.
The code is (fairly) straightforward. Essentially it's a state machine that (ignoring invalid inputs) follows the following steps:
- wait until sensor 1 is triggered
- when sensor 1 is triggered record the time
- wait until sensor 2 is triggered
- determine the time difference between the two sensors being triggered and use this to calculate the speed of the locomotive
- wait until both sensors have returned to normal then return to step 1
This is easy to implement and the full Arduino sketch is as follows:
/**
* ScaleSpeed
* Copyright (c) Mark A. Greenwood, 2012
* This work is licensed under the Creative Commons
* Attribution-NonCommercial-ShareAlike 3.0 Unported License.
* To view a copy of this license, visit
* http://creativecommons.org/licenses/by-nc-sa/3.0/.
**/
//keep the sketch size down by only compiling debug code into the
//binary when debugging is actually turned on
#define DEBUG 0
//all possible states of the state machine
const byte TRACK_SECTION_CLEAR = 0;
const byte ENGINE_ENTERING_SECTION = 1;
const byte ENGINE_LEAVING_SECTION = 2;
//the current state machine state
byte state = TRACK_SECTION_CLEAR;
//the analog pins used for each sensor
const byte SENSOR_1 = 0;
const byte SENSOR_2 = 1;
//the digital pins for the signalling LEDs
const byte GREEN_LIGHT = 13;
const byte RED_LIGHT = 12;
//the threshold values for each sensor
int sensor1 = 1024;
int sensor2 = 1024;
//intermediate steps to calcualte the scale distance we are measuring
const float scale = 76; //this is OO gauge
const float distance = 74; //measured in mm
const float scaleKilometer = 1000000.0/scale;
//This is the only value we actually need to do the calculation
//We could do this calculation on the computer and pass it across
//or store it in EEPROM. it all depends if we expect to always be
//attached to a computer or if we want to run standalone etc.
const float scaleDistance = distance/scaleKilometer;
//the track speed limit in mph
const float speedLimit = 15;
//the time (in milliseconds from the Arduino starting up that the
//first sensor was last triggered
unsigned long time;
void setup(void) {
//enable output on the digital pins
pinMode(GREEN_LIGHT, OUTPUT);
pinMode(RED_LIGHT, OUTPUT);
//turn on both LEDs to show we are calibrating the sensors
digitalWrite(GREEN_LIGHT, HIGH);
digitalWrite(RED_LIGHT, HIGH);
//configure serial communication
Serial.begin(9600);
//let the user know we are calibrating the sensors
Serial.print("Callibrating...");
while (millis() < 5000) {
//for the first five seconds check and store the lowest light
//level seen on each sensor
sensor1 = min(sensor1, analogRead(SENSOR_1));
sensor2 = min(sensor2, analogRead(SENSOR_2));
}
//the cut off level for triggering the state machine
//is half the resistance seen during calibration
sensor1 = sensor1/2;
sensor2 = sensor2/2;
//we have now finished callibration so tell the user...
Serial.println(" Done");
//... and set the signalling to green (i.e. we haven't yet seen
//anything break the speed limit!
digitalWrite(GREEN_LIGHT, HIGH);
digitalWrite(RED_LIGHT, LOW);
}
void loop(void) {
if (state == TRACK_SECTION_CLEAR) {
//last time we checked the track was clear
if (analogRead(SENSOR_1) < sensor1) {
//but now the first sensor has been triggered so...
//store the time at which the sensor was triggered
time = millis();
//advance into the next state
state = ENGINE_ENTERING_SECTION;
#if (DEBUG)
Serial.println("Train entering measured distance");
#endif
}
}
else if (state == ENGINE_ENTERING_SECTION) {
//the last time we checked the first sensor had triggered but
//the second was yet to trigger
if (analogRead(SENSOR_2) < sensor2) {
//but now the second sensor has triggered as well so...
//get the difference in ms between the two sensors triggering
unsigned long diff = (millis() - time);
//calculate scale speed in kph
//3600000 is number of milliseconds in an hour
float kph = scaleDistance*(3600000.0/(float)diff);
//convert kph to mph
float mph = kph*0.621371;
//report the time and speed to the user
Serial.print("Speed Trap Record: ");
Serial.print(diff);
Serial.print("ms ");
Serial.print(kph);
Serial.print("kph ");
Serial.print(mph);
Serial.println("mph");
if (mph > speedLimit) {
//if the speed we calculated was above the speed limit
//then turn off the green LED and turn on the red one
digitalWrite(GREEN_LIGHT, LOW);
digitalWrite(RED_LIGHT, HIGH);
}
else {
//if the speed we calculated was not above the speed limit
//then turn off the red LED and turn on the green one
digitalWrite(GREEN_LIGHT, HIGH);
digitalWrite(RED_LIGHT, LOW);
}
//move into the next state
state = ENGINE_LEAVING_SECTION;
}
}
else if (state = ENGINE_LEAVING_SECTION) {
//last time we checked both sensors had triggered but both
//had yet to reset back to normal
if (analogRead(SENSOR_1) > sensor1 && analogRead(SENSOR_2) > sensor2) {
//both sensots are now clear so...
//move back to the first state ready for next time
state = TRACK_SECTION_CLEAR;
#if (DEBUG)
Serial.println("Train is clear of measured distance");
#endif
}
}
}
Now that might look like a lot of code but if you remove the code comments there isn't really that much going on. Everything up to line 50 just creates some constants and variables ready for the speed calculations. The
setup
loop in lines 52 to 86 calibrates the two LDRs: we look at the sensors for five seconds and record the lowest light level we see and set the threshold for triggering at half this value. The main
loop
method then simply implements the state machine we outlined above using a set of
if...else
statements.
So the main question is does it work? Testing by simply using my hands to cover the sensors suggested that everything worked well. The last thing to do was to actual time a locomotive. As you can see from this photo it was easy to attach to the track for testing and
I can report that it works really well.
There are many ways in which the code and hardware could be improved. Configuring the distance, speed limit and scale without re-compiling would be useful, as would using a small LCD or multi-segment LED to display the speed, but for now, at least, I'll leave those as exercises for the interested reader.