// https://forum.arduino.cc/t/model-train-axle-counter-and-speed/1429298/
// Using only two sensor pins, "W" and "E"... counting axles by using the leading axle
// Distance between sensors is a variable "sensorDistance"... adjust as meaasured
#include <LiquidCrystal_I2C.h>
const int sensorPin[] = {2, 3}; // W, E sensor pins
const int numberSensors = sizeof(sensorPin) / sizeof(sensorPin[0]); // number of sensors
unsigned long sensorTime[numberSensors]; // determine direction
unsigned long startTime, endTime; // determine speed
unsigned long timer, quietDuration = 5000, quietEndTime; // determine quiet time after last wheel/axle
float sensorDistance = 0.5; // measured distance of W and E sensor
float timeDifference, speedMilePerHour, speedMsPerInch; // determine speed
int axleCount, i; // counters
bool sensorTripped[] = {false, false}, speedStartStored, speedEndStored, startingSensor; // flags
char direction; // train direction
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup()
{
Serial.begin(115200); // not grandpappy's baud
configureSensorPins();
configureLCD();
}
void loop()
{
if (((quietDuration - timer) / 1000) <= 5)
displayQuietTimeCount(); // count up to quietDuration
if (millis() - timer > quietEndTime) // verify quiet time has elaped (elapsed on startup)
{
timer = millis(); // update timer to "now"
readSensors(); // read sensors and update quiet time
}
// only if all sensors are tripped
if (sensorTripped[0] == true && sensorTripped[1] == true) // all sensors tripped
{
getDirection(); // W or E
getSpeed(); // calculate speed
displayResults(); // to LCD
}
}
void displayQuietTimeCount()
{
lcd.setCursor(13, 0);
lcd.print("Q:");
lcd.setCursor(15, 0);
lcd.print((quietDuration - timer) / 1000); // quiet countdown
}
void displayResults()
{
lcd.setCursor(0, 0);
lcd.print(direction == 'E' ? "EASTBOUND" : "WESTBOUND");
lcd.setCursor(13, 0);
lcd.print("Q:");
speedMsPerInch = timeDifference * (1 / sensorDistance); // calculate ms per ONE inch
speedMilePerHour = (63360UL * speedMsPerInch) / 3600000UL; // in/mi * ms/in / ms/h
lcd.setCursor(10, 1);
lcd.print(speedMilePerHour, 0);
lcd.setCursor(3, 1);
lcd.print(axleCount); // two axles per truck
for (int i = 0; i < numberSensors; i++)
{
sensorTripped[i] = false;
}
}
void getSpeed()
{
timeDifference = endTime - startTime; // of first sensor to pass each detector
}
void getDirection()
{
if (startingSensor == 0) // from readSensors()
direction = 'E'; // eastbound
else
direction = 'W'; // westbound
}
void readSensors()
{
for (int i = 0; i < numberSensors; i++) // monitor all sensors
{
if (digitalRead(sensorPin[i]) == HIGH) // if activity
{
axleCount++; // count each axle/wheel
timer += quietDuration; // add quiet time to timer
// store start time only if not stored
if (speedStartStored == false && speedEndStored == false)
{
startTime = millis(); // store start time for speed calculation
startingSensor = i; // identify starting sensor
speedStartStored = true; // indicate start time is stored
}
// only if start time is stored and end time is not stored and not startingSensor
if (speedStartStored == true && speedEndStored == false && i != startingSensor)
{
endTime = millis(); // store end time for speed calculation
speedEndStored = true; // indicate end time is stored
}
sensorTripped[i] = true; // indicate sensor has been tripped
}
}
}
void formatDisplay()
{
lcd.setCursor(0, 0);
lcd.print("WAITING");
lcd.setCursor(0, 1);
lcd.print("AX:");
lcd.setCursor(6, 1);
lcd.print("SPD:");
lcd.setCursor(13, 1);
lcd.print("MPH");
}
void configureLCD()
{
lcd.init();
lcd.backlight();
lcd.clear();
formatDisplay();
}
void configureSensorPins()
{
for (int i = 0; i < numberSensors; i++)
{
pinMode(sensorPin[i], INPUT);
}
}