Model Train Axle counter and Speed

HI,

I’m working on a simple circuit for an HO Model Train layout. I’m using 3 TCRT5000 IR sensors placed .5” apart in a 3D printed piece that fits under the track. The outer sensors will detect direction (East, West) and the time it takes to go from one to the other for the speed of the train. The center sensor I’m using to count the amount of axles in the train. When done I’d like to display the direction, speed and axle count on the LCD display.

The code I’ve written works perfect for showing the direction and the speed but I can’t get the axle count to work??? It shows 3 axles in one direction and 2 in the other no matter how many travel in front of the sensor. :frowning:

I was trying using interrupts to catch the direction using the first sensor (store direction and start time for speed calc), then start counting the axles with the second sensor. Then catch the third sensor (for finish time) to calculate the speed. I need to keep counting the axles until none of the 3 sensors show any activity for 5 seconds to know that the train is gone. Then subtract finish time from start time to calc speed. This needs to happen in both directions.

I’m either doing this wrong or I’m missing something here? Here’s the code I’m using…

Any help would be greatly appreciated and help me expand my coding knowledge. :slight_smile:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// User-defined: Distance between Sensor 1 and 3 in inches
const float SENSOR_DISTANCE_IN = 1.0;  // 1 inch between Sensors

// Pin Definitions
const int SENSOR_W = 2;  // Interrupt pin
const int SENSOR_AXLE = 4;
const int SENSOR_E = 3;  // Interrupt pin

LiquidCrystal_I2C lcd(0x27, 16, 2);

volatile unsigned long startTime = 0;
volatile unsigned long endTime = 0;
volatile bool trainPassing = false;
volatile char direction = ' ';  // 'E' for East, 'W' for West

int axleCount = 0;
bool axleDetected = false;


void setup() {
  pinMode(SENSOR_W, INPUT);
  pinMode(SENSOR_E, INPUT);
  pinMode(SENSOR_AXLE, INPUT);

  Serial.begin(9600);

// Trigger on LOW to HIGH
  attachInterrupt(digitalPinToInterrupt(SENSOR_W), triggerWest, RISING);
  attachInterrupt(digitalPinToInterrupt(SENSOR_E), triggerEast, RISING);

  lcd.init();
  lcd.backlight();
  lcd.print("Ready...");
}

void triggerWest() {
  if (!trainPassing) {
    startTime = millis();
    direction = 'E';
    Serial.println(direction);
    trainPassing = true;
  } else if (direction == 'W') {
    endTime = millis();
  }
}

void triggerEast() {
  if (!trainPassing) {
    startTime = millis();
    direction = 'W';
    Serial.println(direction);
    trainPassing = true;
  } else if (direction == 'E') {
    endTime = millis();
  }
}

void loop() {
  // Count Axles
  if (trainPassing && digitalRead(SENSOR_AXLE) == HIGH && !axleDetected) {
    axleCount++;
    Serial.println(axleCount);
    axleDetected = true;
    Serial.println("Train Passing");
    Serial.println(trainPassing);
  } else if (digitalRead(SENSOR_AXLE) == LOW) {
    axleDetected = false;
  }

  // Calculate and Display Results once train passes
  if (endTime > 0) {
    float timeSec = (endTime - startTime) / 1000.0;
    // Speed in mph: (Distance_in / Time_sec) * (3600 / 63360)
    float speedMPH = (SENSOR_DISTANCE_IN / timeSec) * 2;

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Dir: ");
    lcd.print(direction == 'E' ? "EAST" : "WEST");
    lcd.setCursor(0, 1);
    lcd.print("Ax:");
    lcd.print(axleCount);
    lcd.print(" Spd:");
    lcd.print(speedMPH, 1);
    lcd.print("mph");

    // Reset for next train after a delay
    delay(5000);  // wait 5 seconds to make sure train has cleared the sensor
    trainPassing = false;
    endTime = 0;
    axleCount = 0;
    lcd.clear();
    lcd.print("Ready...");
  }
}
   

What are you using to shield the IR output of one sensor from the IR input of the next sensor?

I would guess this means it detects first entering axle and last leaving axle... and you are using the leading sensor to start the count going in the (3-count) direction, but the lagging sensor to start the count going in the (2-count) direction.... and the "missed the rest of the axles" is because it looks like one big axle due to physical properties, or timing.

Hi, @bobcap1957
I am currently working on a similar unit, though not counting axles.
Inspired by LittleWicketRailway.

I'm aiming just use his method of carriage detection.
Here is my display, just working on detection.

Tom.... :smiley: :+1: :coffee: :australia:

what about considering a different algorithm using just 2 sensors.

one requirement is that the 2 sensors must be less than the distance between axles

the algorithm is to recognize one sensor being active and ignoring it until the other sensor becomes active when the same axle passes over it.

recognizing which sensor become active first determines the direction. And knowing the distance between sensors and the time between events allows computing the speed

how do these sensors distinguish between axles, trucks and cars?

I’m trying to get the picture right so please bear with me.

The sensors are half an inch apart and the axles in a truck set are about 1 inch apart?

Can you turn the sensors off and on quickly? If you DIY an IR led and detector the answer is yes. Checking each sensor once per ms one after the other is possible to do with non-blocking code.

Using floats for time, micros() is seconds to 6 places and integer calculations tend to run magnitudes faster (except for powers of 10 calcs) than float and be more precise. Print val/1000000 then ‘.’ then val%1000000 [EDIT: there’s a loop to print leading 0’s not shown there, a few lines] for maths-impaired humans to read seconds but work in unsigned micros().

The data from 2 sensors is all you need including axle count. If they are farther apart than any whole set of (2 or 3?) axles then one sensor can read each axle pass before the next does the same. The first sensor tracks when the first axle starts to pass over and keeps count of axles, the second sensor catches the first axle start over and axles=count;count=0; and time between sensors is end - start in micros() for precision. Just sensing each axle pass over will take many ms.

Last of all, get rid of all delay() calls, the 5 second delay… can’t a boxcar roll through that?

My thought was, the first sensor catches the direction and time to calculate the speed. Then the middle one is only used to count the wheels as they pass through the beam. Then the last Sensor would be used to get the finish time to the speed calculation. Once the train has passed over all sensors and they are idle for 5 seconds, reset the variables and wait for the next train no matter which direction.

Let me see if I have this right..

We are sitting in “WaitForTrain” state..

We get a high or low saying that a train has arrived. This gives direction and.. Now we’re in “Read speed state”. But at the same time we are in “Counting axles state” as well?

How do we know when the train has passed and we need to go back to “WaitForTrain” state?

-jim lee

Sensor order (E/W or W/E) gives direction.

speed = 0.5 inches per (time difference in ms)... converted to miles per hour

Five seconds of "no sensor change"

When PC mice had balls, the ball turned slotted wheels through 2 sensor light-bridges where the sensor pairs were spaced so one saw 1/2 a slot/blade out of phase with the other, makes a 2-bit Gray Code giving direction and step.

DIY rotary encoder --- might apply to wheel sets?

I wonder if your 3 sensors line up with the wheels to give 3 or 4 bit Gray Code?

Gray codes are widely used to prevent spurious output from electromechanical switches

If the moving wheels return Gray code, a state machine could stay on top of that. Train cars had a wheel truck at each end last I recall but Engines are a different story. If there’s a pattern to use….

thanks for the photo showing that the sensors are aligned across the tops of the rails.

how long does it take to update the LCD? can that be blocking the code counting axles in loop(), especially with multiple calls to print.

what about reducing it to a single call (see code below)

in general, only 2 sensors are needed to determine direction; just need to recognize which one becomes active first.

you can reset the count if there are no sensor pulses for several seconds; there's no need for a trainPassing flag to enable counting

look this over. it's less complicated and shows how only 2 sensors are needed

const byte Pins [] = { A1, A3 };
const int Npin     = sizeof(Pins);
      byte pinState [Npin];

int           pinCnt  [Npin];
unsigned long pinMsec [Npin];
bool          east;

char s [90];

// -----------------------------------------------------------------------------
unsigned long msec0;

void loop () {
    unsigned long msec = millis ();

    for (int n = 0; n < Npin; n++)  {
        byte pin = digitalRead (Pins [n]);

        if (pinState [n] != pin)  {
            pinState [n]  = pin;
            delay (30);

            if (LOW == pin)  {
                pinCnt [n]++;
                pinMsec [n] = msec;
            }
        }
    }

    // update display
    const float DistIn = 0.7;
    if (msec - msec0 > 1000)  {
        msec0 = msec;

        if (pinCnt [0] == pinCnt [1])  {
            unsigned long dMsec = abs (pinMsec [1] - pinMsec [0]);
            int           spd   = 1000 *DistIn / dMsec;

            sprintf (s,"axles: %3d, spd %6d, %c, %6lu",
                            pinCnt[0], spd, east ? 'E' : 'W', dMsec);
            Serial.println (s);
        }
    }

    // not when both equal
    east  = pinCnt [0] > pinCnt [1];
}

// -----------------------------------------------------------------------------
void setup () {
    Serial.begin (9600);

    for (int n = 0; n < Npin; n++)  {
        pinMode (Pins [n], INPUT_PULLUP);
        pinState [n] = digitalRead (Pins [n]);
    }
}

It's a bad idea to use Serial.println() in your interrupt code.

But I don't think interrupts should be used here anyway. Any Arduino should be fast enough to read the sensors using digitalRead() in loop().

I agree with others: third sensor is not needed.

Also trainPassing variable not needed. If direction is not blank then a train is passing. Once 5 seconds has passed since last detection, print results to screen and set direction to blank for next train.

I think that the problem with your code is the following line, and the block of code that follows it.

// Calculate and Display Results once train passes
  if (endTime > 0) {

The value of 'endTime' becomes greater than zero when the first axle of the train passes the third sensor.

This means that further counting of axles is prevented until the whole of the block of code after the 'if' statement has been executed. This includes calculating and displaying the results, resetting the results and a 5 second delay.

As I do not have your hardware, I used a second Arduino to generate simulated sensor pulses.

Here is what I saw on an oscilloscope when I tested your code:

  • Channel 1 - Yellow trace - Arduino pin 2, SENSOR_W.
  • Channel 2 - Red trace - Arduino pin 4, SENSOR_AXLE.
  • Channel 3 - Blue trace - Arduino pin3, SENSOR_E.
  • Channel 4 - Green trace - Serial output.

For a group of ten axles, only the first axle gets counted, as can be seen from the green trace:

Commenting out the 'if' block of code, allows all the axles to be counted, however the count now never gets reset:

Had to smile.
Trying to picture Politically Correct pet mouse before a visit to the vets :slightly_smiling_face:
Feisty little devils.

2 Likes

The IR sensors are set to just above the rail head so it only sees the bottom of the wheel. I’m actually counting the wheels but Defect detectors on the REAL railroads do the same and transmit them as axle count.

I only added them to see if I could isolate the problem. :frowning: