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.
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.
#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...");
}
}
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.
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
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.
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?
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.
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….
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:
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.