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
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
/** * 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.
Mark this is stunning..blows my little brain anyway!
ReplyDeleteI'll try and learn from you. Stimulating? This reads like a mega suduko.
What code is this?
Why isn't your track to scale?
Barking mad you are. Don't argue cos I'm older and it takes an older barking mad to recognise a kindred spirit.
I see that 3D printers can now make a ball race. perchance they could print your track and scenery.
I'm hooked.
Next instalment ASAP please.
Glad your enjoying enough to want the next instalment!
DeleteThe code is a weird mixture of C and C++. It's the standard Arduino code though so it follows any of the tutorials you might find. Essentially when the Arduino is powered up it calls the setup method and then it just keeps on calling the loop method until you pull the power.
The track not being to scale is, I agree weird, but something we kind of have to live with in the UK. According to Wikipedia this happened because OO describes models with a scale of 4mm = 1 foot (1:76) running on HO scale 1:87 (3.5mm = 1 foot) track (16.5mm/0.650"). This combination came about as early clockwork mechanisms and electric motors were difficult to fit within HO scale models of British prototypes which are smaller than equivalent European and US locomotives. A quick and cheap solution was to enlarge the scale of the model to 4mm to the foot but keep the 3.5mm to the foot gauge track. This also allowed more space to model the external valve gear. The resulting HO track gauge of 16.5mm represents 4 feet 1.5 inches at 4mm to the foot scale, this is 7 inches under scale or is approximately 2.33mm too narrow.
Oh and don't worry I already have a 3D printing post planned :)