An Arduino Powered (Scale) Speed Trap

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:
  1. wait until sensor 1 is triggered
  2. when sensor 1 is triggered record the time
  3. wait until sensor 2 is triggered
  4. determine the time difference between the two sensors being triggered and use this to calculate the speed of the locomotive
  5. 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.

2 comments:

  1. Mark this is stunning..blows my little brain anyway!
    I'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.

    ReplyDelete
    Replies
    1. Glad your enjoying enough to want the next instalment!

      The 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 :)

      Delete