Train Lap Counter
d. bodnar 4-10-2021 - 5-12-2022

This model train lap counter is designed to increment a counter each time the train passes a sensor.  The counter can easily go into the 10's of millions of counts even if your loco may not be up to the challenge!

In addition the counter will ignore the sensor for 20 seconds after a train is detected to prevent additional counts should there be gaps between cars.

The count will be retained if there is a power failure and restored when power is back.

 

Hardware
The display is made up of 2 four digit LED matrix displays.  They are available from
Amazon and other sources.  They use a MAX7219 controller and can be addressed by using an Arduino library.

The sensor is also available from Amazon.  The unit has 3 wires that need to be connected, +5 volts (red), ground (black) and sensor output (yellow).  The small screw (circled in green) can be used to adjust the range of the sensor.  The red LED (yellow circle) lights when an object is detected.

An Arduino Pro Mini provides the control of the unit and is connected to the sensor and display. 

There are two LEDs, one red and one blue, that give status information - this is explained below.

Schematic
 
Setup
Place the sensor next to the track at a distance that is sure to trigger it, an inch or two works well.  If necessary adjust the sensitivity with the screw on the back of the sensor.   Make sure that the black, red and yellow wires go to the black, red and white wires, respectively, on the extension cables.

Apply power to the unit and run the train past the sensor.  When the count number is incremented you will see the blue LED begin to flash.  It will flash for about 20 seconds indicating that it will not react to the sensor for that time.  Once the LED stops flashing the unit is ready to detect again.

The red LED will light if power is lost.  It indicates that the current count has been written to memory.  Note that the display will be readable for a time after the power is lost.

 

Troubleshooting
If the counter stops incrementing is the display is off the unit can be reset by pressing the small button on top of the Arduino (circled in yellow in this photo).  The top of the clear enclosure can be removed by removing the two pieces of black tape that hold it on.

Software
 
// This Version working for power failure & EEPROM read/write---  
// d bodnar  4-09-2021

 
#include <EEPROM.h>
//unsigned long storage; //create an unsigned long variable to store our result
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
//unsigned long nn = 0;//227654321;
unsigned long TestValue = 31888; // was -1         max of 32767 for integers
String thisString = String(TestValue);
#define MAX_DEVICES 8
#define CLK_PIN   13
#define DATA_PIN  11
#define CS_PIN    10
const int IRSensorPin = 2;    // the number of the IR sensor pin
int buttonState;             // the current reading from the input pin
int lastButtonState = HIGH;   // the previous reading from the input pin
int reading = 0;
int Power = 0;
long PowerVal = 0;
int LED_red = 5;
int LED_blue = 4;
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

void setup(void)
{
  pinMode(LED_red, OUTPUT);
  pinMode(LED_blue, OUTPUT);
  P.begin();
  Serial.begin(9600);
  pinMode(IRSensorPin, INPUT);
  TestValue = EEPROM_readlong(0x00); //read it and put it in our newly created unsigned long variable
  Serial.print("From SETUP - TestValue==");
  Serial.println(TestValue); //print it to check
  Serial.println(" d. bodnar 4-9-2021  V3.999");
  digitalWrite(LED_blue, LOW);
}

void loop(void)
{
  check_power_fail();
  reading = digitalRead(IRSensorPin);
  if (reading != lastButtonState) {
    digitalWrite(LED_blue, HIGH);
    Serial.println("just before increment");
    TestValue = TestValue + 1;

    if (reading != buttonState) {
      //  TestValue = TestValue + 1;
      digitalWrite(LED_blue, LOW);
      for (int longPause = 0; longPause <= 125; longPause++) {  // 125 about 20 seconds
        digitalWrite(LED_blue, LOW);
        delay(100);
        check_power_fail();
        digitalWrite(LED_blue, HIGH);
        delay(100);
      }
      digitalWrite(LED_blue, LOW);
      TestValue = TestValue - 1;

    }
    lastButtonState = reading;
  }
  Serial.print("TestValue = "); Serial.println(TestValue);
  thisString = String(TestValue);
  int howLong = thisString.length();
  thisString = "" + thisString;
  for (int xx = 0; xx <= (10 - howLong); xx++) {
    thisString = " " + thisString;
  }
  P.print(thisString);
  delay(200);
}

// read double word from EEPROM, give starting address
unsigned long EEPROM_readlong(int address)
{
  //use word read function for reading upper part
  unsigned long dword = EEPROM_readint(address);
  //shift read word up
  dword = dword << 16;
  // read lower word from EEPROM and OR it into double word
  dword = dword | EEPROM_readint(address + 2);
  return dword;
}

void check_power_fail() {
  PowerVal = analogRead(Power);
  Serial.print("PowerVal = ");  Serial.println(PowerVal);
  if (PowerVal <= 500) {
    digitalWrite(LED_red, HIGH);
    EEPROM_writelong(0x00, TestValue); //write unsigned long number u want
    Serial.println("Wrote Test Value to EEPROM");
  }
  else {
    digitalWrite(LED_red, LOW);
  }
}

//write word to EEPROM
void EEPROM_writeint(int address, int value)
{
  EEPROM.write(address, highByte(value));
  EEPROM.write(address + 1 , lowByte(value));
}

//write long integer into EEPROM
void EEPROM_writelong(int address, unsigned long value)
{
  //truncate upper part and write lower part into EEPROM
  EEPROM_writeint(address + 2, word(value));
  //shift upper part down
  value = value >> 16;
  //truncate and write
  EEPROM_writeint(address, word(value));
}

unsigned int EEPROM_readint(int address)
{
  unsigned int word = word(EEPROM.read(address), EEPROM.read(address + 1));
  return word;
}

Revision with larger display

Two large panels are joined together in this lap counter.  Mounting holes are indicated by the red arrows.

The back of the board shows the circuitry.

Two holes in the acrylic cover allow access to the reset button which is at the end of the  pencil - a toothpick or pencil can be inserted in the hole in the acrylic to do a reset should that ever be needed

The lower hole, at the end of the small screwdriver, can be used to adjust the brightness.  It is normally set to a medium or low brightness.  Note that using high or maximum brightness will cause the unit and power supply to run hot.

 

TrainCounterEEPROM--POT-LEDdisplayP10-v1-8
 
// This Version working for power failure & EEPROM read/write---
// d bodnar  4-09-2021
/// working well 7-2-2021


#include <SPI.h>
#include <DMD2.h>
#include <fonts/Arial_Black_16.h>
//#include <fonts/Droid_Sans_12.h>
//#include <fonts/Droid_Sans_16.h>// not bad
//#include <fonts/Arial14.h>
//#include <fonts/SystemFont5x7.h> // small
//#include <fonts/Droid_Sans_24.h> // too high


const unsigned long COUNTDOWN_FROM = 518812;
unsigned long counter = COUNTDOWN_FROM;

SoftDMD dmd(2, 1); // DMD controls the entire display
DMD_TextBox box(dmd, 0, 1);  // "box" provides a text box to automatically write to/scroll the display
#include <EEPROM.h>
unsigned long TestValue = 12340;/// 3877206; // was -1         max of 32767 for integers
String thisString = String(TestValue);
const int IRSensorPin = 2;    // the number of the IR sensor pin
int buttonState;             // the current reading from the input pin
int lastButtonState = HIGH;   // the previous reading from the input pin
int reading = 0;
int Power = 0; // pin a0 to show when power is lost
long PowerVal = 0;
int LED_red = 5;
int LED_blue = 4;
//MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
int potValue = analogRead(A1); //POT on pin a1 to set brightness


void setup(void)
{
  pinMode(LED_red, OUTPUT);
  pinMode(LED_blue, OUTPUT);
  //  P.begin();
  Serial.begin(9600);
  pinMode(IRSensorPin, INPUT);
  TestValue = EEPROM_readlong(0x00); //read it and put it in our newly created unsigned long variable
  Serial.print("From SETUP - TestValue==");
  Serial.println(TestValue); //print it to check
  Serial.println(" d. bodnar 5-10-2022  V1.8");
  digitalWrite(LED_blue, LOW);
  dmd.setBrightness(25);
  dmd.selectFont(Arial_Black_16);
  //  dmd.selectFont(Arial14);
  //      dmd.selectFont(Droid_Sans_24);


  dmd.begin();
  box.print("  Ready");
}

void loop(void) {
  potValue = analogRead(A1) / 4; //POT
  // Serial.print("pot="); Serial.println(potValue);
  dmd.setBrightness(potValue);

  check_power_fail();
  reading = digitalRead(IRSensorPin);
  Serial.println(reading);
  int flag = 0;
  if (reading != lastButtonState) {
    digitalWrite(LED_blue, HIGH);
    box.print("   TRAIN ");
    delay(1000);
    Serial.println("just before increment");
    Serial.println(TestValue);
    thisString = String(TestValue);
    // if (flag == 1) {
    ////     box.print(thisString);
    //   flag=1;
    // }
    if (reading != buttonState) {
      box.print("            ");

      box.print(thisString);

      TestValue = TestValue + 1;
      digitalWrite(LED_blue, LOW);
      for (int longPause = 0; longPause <= 15; longPause++) {  // 125 about 20 seconds
        digitalWrite(LED_blue, LOW);
        delay(100);
        check_power_fail();
        digitalWrite(LED_blue, HIGH);
        delay(100);
      }
      digitalWrite(LED_blue, LOW);
      // TestValue = TestValue - 1;
    }
    lastButtonState = reading;
  }
  Serial.print("TestValue = "); Serial.println(TestValue);
  delay(200);
}

// read double word from EEPROM, give starting address
unsigned long EEPROM_readlong(int address)
{
  //use word read function for reading upper part
  unsigned long dword = EEPROM_readint(address);
  //shift read word up
  dword = dword << 16;
  // read lower word from EEPROM and OR it into double word
  dword = dword | EEPROM_readint(address + 2);
  return dword;
}

void check_power_fail() {
  PowerVal = analogRead(Power);
  Serial.print("PowerVal = ");  Serial.println(PowerVal);
  if (PowerVal <= 500) {
    box.print("Power -- ");
    digitalWrite(LED_red, HIGH);
    EEPROM_writelong(0x00, TestValue); //write unsigned long number u want
    Serial.println("Wrote Test Value to EEPROM");

  }
  else {
    digitalWrite(LED_red, LOW);
  }
}

//write word to EEPROM
void EEPROM_writeint(int address, int value)
{
  EEPROM.write(address, highByte(value));
  EEPROM.write(address + 1 , lowByte(value));
}

//write long integer into EEPROM
void EEPROM_writelong(int address, unsigned long value)
{
  //truncate upper part and write lower part into EEPROM
  EEPROM_writeint(address + 2, word(value));
  //shift upper part down
  value = value >> 16;
  //truncate and write
  EEPROM_writeint(address, word(value));
}

unsigned int EEPROM_readint(int address)
{
  unsigned int word = word(EEPROM.read(address), EEPROM.read(address + 1));
  return word;
}