4-Lane Slot Car Lap Counter

Hey folks,

I have made a 4 Lane Lap counter for 1/32 slot cars. Everything works... almost. First I will explain how it works. Then I will explain my problem and what I think could be wrong.

HOW IT WORKS:

The design is basically a "light bridge" where LEDs shine down onto the track illuminating a photoresistor that provides analog input as the shadow of the car passes over it, thus counting a lap. There is a 4x20 LCD screen for display, a few buttons for user inputs, and a set of four countdown LEDs on top (Red, Red, Yellow, Green).

The Code:

In the setup{}:
Prior to the race there are some menus that interact with buttons to allow the racers to choose how many laps the race will be. Then they are prompted to calibrate the analog inputs by placing their cars on the track over the sensors. The racers are prompted to press a button when ready. Then there is a visual countdown via LEDs on top of the light bridge (start/finish) that will signify the start of the race. Once the green one is illuminated, the race begins.

In the loop{}:
I use the map() function on the voltage output of the photoresistors and a convenient cut-off to see whether or not the car has passed over. During the race, the LCD displays a count-down for each lane's laps remaining, and checks each lane to see if it's racer has finished. Placings after the race end are displayed as the car finishes.

I've posted code below, if you care to look. The code works.

HERE'S the problem:
As I said the code works, but only in testing. Under race conditions, the cars are moving very fast and only over the photoresistors for a fraction of a second. Often times if the car is moving at race speeds, the lap will not be counted. At slower speeds every lap will be counted.

My thoughts for possible reasons:

A.) There's a coding problem: Due to my lack of experience, my code is clunky and not optimized causing the AnalogRead() not to poll the pins frequently enough to pick up the car.

B.) The photoresistor isn't having enough chance to drop the voltage as the car passes over.

C.) The unknown unknown.

I've been working on it for months now, and I just bought some photodiodes to test hypothesis B, but in writing I was thinking about just commenting all the code that deals with anything OTHER than a single lane's count and display to test hypothesis A. If y'all think it's hopeless, it'd be a relief I think and I can chuck the whole proj. Any thoughts would be welcomed.

Many thanks,
G

I can't ser any coding errors so it must be simething else.

If y'all think it's hopeless, it'd be a relief I think and I can chuck the whole proj.

No, we don't think it's hopeless. Yes, it could be A, B or C. That's your questions answered. Need some advice that's actually useful? Post your code, your schematic, links to the specs of components used, pictures of the setup. Read the forum guide so you know how to post those things properly.

Thank you for looking. The forum wont let me post the code because it's too long apparently? I tried using the code markups but it still failed. I will try to post in separate replies but there is a time out limit that doesnt allow me to post frequently. Below is the variable definitions portion.

//============================================================
//                  LIBRARIES FOR LCD
//============================================================
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display

//============================================================
//                  LANE 1 VARIABLES
//============================================================
int lane1Sensor;                    //integer for L1 sensor
int lane1SensorHigh = 0;            //L1 High Calibration Range
int lane1SensorLow = 1023;          //L1 Low Calibration Range
int lane1LapCount=0;                //L1 Total Laps
int lane1LapCheck;
int lane1PreviousState;
int lane1State;                     //L1 Car is Over the sensor
int L1Place=5;                   //L1 Final Placing  
//============================================================
//                  LANE 2 VARIABLES
//============================================================
int lane2Sensor;                    //integer for L2 sensor
int lane2SensorHigh = 0;            //L2 High Calibration Range
int lane2SensorLow = 1023;          //L2 Low Calibration Range
int lane2LapCount=0;                //L2 Total Laps
int lane2LapCheck;
int lane2PreviousState;
int lane2State;                     //L2 Car is Over the sensor
int L2Place=5;                   //L2 Final Placing
//============================================================
//                  LANE 3 VARIABLES
//============================================================
int lane3Sensor;                    //integer for L3 sensor
int lane3SensorHigh = 0;            //L3 High Calibration Range
int lane3SensorLow = 1023;          //L3 Low Calibration Range
int lane3LapCount=0;                //L3 Total Laps
int lane3LapCheck;
int lane3PreviousState;
int lane3State;                     //L3 Car is Over the sensor
int L3Place=5;                   //L3 Final Placing
//============================================================
//                  LANE 4 VARIABLES
//============================================================
int lane4Sensor;                    //integer for L4 sensor
int lane4SensorHigh = 0;            //L4 High Calibration Range
int lane4SensorLow = 1023;          //L4 Low Calibration Range
int lane4LapCount=0;                //L4 Total Laps
int lane4LapCheck;
int lane4PreviousState;
int lane4State;                     //L4 Car is Over the sensor
int L4Place=5;                   //L4 Final Placing
//============================================================
//                 GLOBAL VARIABLES
//============================================================

int enterButton;
int nextButton;
int plusButton;
int RaceReady=0;                //# of Laps chosen and ready for sensor calibration
int RaceStart=0;                //When it goes to 1 race will count down.
int RaceEnd=0;                  //When it goes to 1 checkered flag first place has crossed
int RequestedLaps=0;            //Two digit lap number returned by Lap_Menu function
int Finisher[]={5,5,5,5};      //Used in racer placings

const int lane1LED = 13;            //LANE 1 VISUAL FEEDBACK
const int lane2LED = 12;            //LANE 2 VISUAL FEEDBACK  
const int lane3LED = 8;             //LANE 3 VISUAL FEEDBACK
const int lane4LED = 7;             //LANE 4 VISUAL FEEDBACK

I think You can use more than one code-tag-window and thereby manage. Fil one window, cut from the end untill it passes the checking. Then open a second code tag and cut from the top.
It's okey to attach a large code file as .ino as I've experienced it.

void setup{}

//============================================================
//                 SET UP (Menu and Lane Calibration)
//============================================================
void setup() {
  Serial.begin(9600);               // Commuicate with PC for trouble shooting

  pinMode(2,INPUT);                 //ENTER BUTTON
  pinMode(3,INPUT);                 //NEXT BUTTON
  pinMode(4,INPUT);                 //PLUS BUTTON

  lcd.init();                            //initialize the lcd
  lcd.backlight();                       //open the backlight 
  lcd.setCursor ( 0, 0 );            // go to the top left corner
  lcd.print(" STATE of JEFFERSON "); // write this string on the top row
  lcd.setCursor ( 0, 1 );            // go to the 2nd row
  lcd.print("         XX         "); // pad string with spaces for centering
  lcd.setCursor ( 0, 2 );            // go to the third row
  lcd.print("      SPEEDWAY      "); // pad with spaces for centering
  lcd.setCursor ( 0, 3 );            // go to the fourth row
  lcd.print("   Press  <Enter>   ");

  while(enterButton == LOW){
    enterButton = digitalRead(2);                 //Pin 2 is <Enter>
    if (enterButton == HIGH){
      RequestedLaps = Lap_Menu();
    }
  }
  pinMode(lane1LED, OUTPUT);
  pinMode(lane2LED, OUTPUT);
  pinMode(lane3LED, OUTPUT);
  pinMode(lane4LED, OUTPUT);

//  delay (1000);

//======================================================================
//                        CALIBRATION LOOP
//======================================================================
  while (RaceReady == 0) {
    lcd.setCursor ( 0, 0 );            
    lcd.print("Race Length:        "); 
    lcd.setCursor ( 18, 0);
    lcd.print(RequestedLaps); 
    lcd.setCursor ( 0, 1 );
    lcd.print("  Place cars over   ");
    lcd.setCursor ( 0, 2);
    lcd.print(" the track sensors. ");         
    lcd.setCursor ( 0, 3 );            
    lcd.print("<Enter> begin Race! ");

    enterButton = digitalRead(2);

    if (enterButton == HIGH){
      RaceReady = 1;
    }
      
      digitalWrite(lane1LED, HIGH);
      digitalWrite(lane2LED, HIGH);
      digitalWrite(lane3LED, HIGH);
      digitalWrite(lane4LED, HIGH);

      lane1Sensor = analogRead(A0);           //Lane 1 Sensor Setup
      lane2Sensor = analogRead(A1);           //Lane 2 Sensor Setup
      lane3Sensor = analogRead(A2);           //Lane 3 Sensor Setup
      lane4Sensor = analogRead(A3);           //Lane 4 Sensor Setup
  
      //===========LANE 1 CALIBRATION=======================================
      if (lane1Sensor > lane1SensorHigh) {   //Sets highest value for Lane 1
        lane1SensorHigh = lane1Sensor;
      }
      if (lane1Sensor < lane1SensorLow)  {    //Sets lowest value for Lane 1
        lane1SensorLow = lane1Sensor;
      }
      //===========LANE 2 CALIBRATION=======================================
      if (lane2Sensor > lane2SensorHigh) {   //Sets highest value for Lane 2
        lane2SensorHigh = lane2Sensor;
      }
      if (lane2Sensor < lane2SensorLow)  {    //Sets lowest value for Lane 2
        lane2SensorLow = lane2Sensor;
      }
      //===========LANE 3 CALIBRATION=======================================
      if (lane3Sensor > lane3SensorHigh) {   //Sets highest value for Lane 3
        lane3SensorHigh = lane3Sensor;
      }
      if (lane3Sensor < lane3SensorLow)  {    //Sets lowest value for Lane 3
        lane3SensorLow = lane3Sensor;
      }
      //===========LANE 4 CALIBRATION=======================================
      if (lane4Sensor > lane4SensorHigh) {   //Sets highest value for Lane 4
        lane4SensorHigh = lane4Sensor;
      }
      if (lane4Sensor < lane4SensorLow)  {    //Sets lowest value for Lane 4
        lane4SensorLow = lane4Sensor;
      }
    }
  Serial.print(lane1SensorHigh);
  Serial.print("\t");
  Serial.print(lane1SensorLow);
  Serial.print("\t");
  digitalWrite(lane1LED, HIGH);              
  digitalWrite(lane2LED, LOW);              
  digitalWrite(lane3LED, LOW);               
  digitalWrite(lane4LED, LOW);               

  lcd.setCursor ( 0, 0 );            
  lcd.print("START YOUR ENGINES! "); 
  lcd.setCursor ( 0, 1 );
  lcd.print("                    ");
  lcd.setCursor ( 0, 2 );
  lcd.print("                    ");
  lcd.setCursor ( 0, 3 );
  lcd.print("                    ");
  delay(1000);

  digitalWrite(lane1LED, LOW);              
  digitalWrite(lane2LED, HIGH);              
  digitalWrite(lane3LED, LOW);               
  digitalWrite(lane4LED, LOW);               
  lcd.setCursor ( 0, 1 );
  lcd.print("       READY!       ");
  delay(1000);

  digitalWrite(lane1LED, LOW);              
  digitalWrite(lane2LED, LOW);              
  digitalWrite(lane3LED, HIGH);               
  digitalWrite(lane4LED, LOW);               
  lcd.setCursor ( 0, 2);
  lcd.print("        SET!        ");
  delay(1000);

  digitalWrite(lane1LED, LOW);              
  digitalWrite(lane2LED, LOW);              
  digitalWrite(lane3LED, LOW);               
  digitalWrite(lane4LED, HIGH);               
  lcd.setCursor ( 0, 3 );            
  lcd.print("         GO!        ");
  lcd.setCursor ( 0, 0 );            
  lcd.print("                    "); 
  delay(100);
  digitalWrite(lane4LED, LOW); 
}

race void loop {}

//======================================================================
//                         RACE LOOP
//======================================================================
void loop() {          
  Serial.begin(9600);
  // =============================Reads Track Sensors
  lane1Sensor = analogRead(A0);             
  lane2Sensor = analogRead(A1);
  lane3Sensor = analogRead(A2);             
  lane4Sensor = analogRead(A3);

  // =============================Maps Voltage to an number 0-1023  
  int lane1Trigger = map (lane1Sensor, lane1SensorLow, lane1SensorHigh, 0, 1023); 
  int lane2Trigger = map (lane2Sensor, lane2SensorLow, lane2SensorHigh, 0, 1023);
  int lane3Trigger = map (lane3Sensor, lane3SensorLow, lane3SensorHigh, 0, 1023);
  int lane4Trigger = map (lane4Sensor, lane4SensorLow, lane4SensorHigh, 0, 1023);
  Serial.print(lane1Trigger);  //debugging 
  Serial.print("\t");
  Serial.print(lane1Sensor);
  Serial.print("\n");          //debugging           

  // =============================Calculates Remaining Laps
  int L1LapsRemaining = (RequestedLaps+1)-lane1LapCount;
  int L2LapsRemaining = (RequestedLaps+1)-lane2LapCount;
  int L3LapsRemaining = (RequestedLaps+1)-lane3LapCount;
  int L4LapsRemaining = (RequestedLaps+1)-lane4LapCount;

  // =============================Avoids Negative Remaining Laps
  if (L1LapsRemaining <= 0){                       
    L1LapsRemaining = 0;
    Finisher[0]=0;
  }
  if (L2LapsRemaining <= 0){
    L2LapsRemaining = 0;
    Finisher[1]=0;
  }
  if (L3LapsRemaining <= 0){
    L3LapsRemaining = 0;
    Finisher[2]=0;
  }
  if (L4LapsRemaining <= 0){
    L4LapsRemaining = 0;
    Finisher[3]=0;
  }

  //===============================Check for Race End
  if ((L1LapsRemaining==0)||(L2LapsRemaining==0)||(L3LapsRemaining==0)||(L4LapsRemaining==0)){
      RaceEnd=1;
      int CurrentPlacing = 0;                      
      for(int f=0;f<4;f++){
        if (Finisher[f] == 0){
          CurrentPlacing=CurrentPlacing+1;
        }
      }
      if ((L1LapsRemaining == 0)&&(L1Place==5)){
        L1Place=CurrentPlacing;
      }
      if ((L2LapsRemaining == 0)&&(L2Place==5)){
        L2Place=CurrentPlacing;
      }
      if ((L3LapsRemaining == 0)&&(L3Place==5)){
        L3Place=CurrentPlacing;
      }
      if ((L4LapsRemaining == 0)&&(L4Place==5)){
        L4Place=CurrentPlacing;
      }

  }

  //===============================Displays Remaining Laps to LCD
  
  if (L1LapsRemaining ==0){
    lcd.setCursor ( 0, 0 );            
    lcd.print("L1 Place:           ");
    lcd.setCursor ( 10, 0 );            
    lcd.print(L1Place);
    digitalWrite(lane1LED, HIGH);
  }
  if (L1LapsRemaining !=0){
    lcd.setCursor ( 0, 0 );            
    lcd.print("L1 Laps Left:        ");
    lcd.setCursor ( 14, 0 );
    lcd.print(L1LapsRemaining);
  }            
  
  if (L2LapsRemaining ==0){
    lcd.setCursor ( 0, 1 );            
    lcd.print("L2 Place:           ");
    lcd.setCursor ( 10, 1 );            
    lcd.print(L2Place);
    digitalWrite(lane2LED, HIGH);
  }
  if (L2LapsRemaining !=0){
    lcd.setCursor ( 0, 1 );            
    lcd.print("L2 Laps Left:       ");
    lcd.setCursor ( 14, 1 );
    lcd.print(L2LapsRemaining);
  } 
             
  if (L3LapsRemaining ==0){
    lcd.setCursor ( 0, 2 );            
    lcd.print("L3 Place:           ");
    lcd.setCursor ( 10, 2 );            
    lcd.print(L3Place);
    digitalWrite(lane3LED, HIGH);
  }
  if (L3LapsRemaining !=0){
    lcd.setCursor ( 0, 2 );            
    lcd.print("L3 Laps Left:       ");
    lcd.setCursor ( 14, 2 );
    lcd.print(L3LapsRemaining);
  }  
            
  if (L4LapsRemaining ==0){
    lcd.setCursor ( 0, 3 );            
    lcd.print("L4 Place:           ");
    lcd.setCursor ( 10, 3 );            
    lcd.print(L4Place);
    digitalWrite(lane4LED, HIGH);
  }
  if (L4LapsRemaining !=0){
    lcd.setCursor ( 0, 3 );            
    lcd.print("L4 Laps Left:       ");
    lcd.setCursor ( 14, 3 );
    lcd.print(L4LapsRemaining);
  }            

//CHANGED TRIGGERS FROM 500 to 900 4/21/20

  // ============LANE 1 LAP COUNT =============================================
  if(lane1Trigger < 900){                          // Car IS over the sensor
    lane1State = 1;
  }
  else{                                             // Car NOT over the sensor      
    lane1State = 0;
  }
  if(lane1State != lane1PreviousState){
    lane1LapCheck = lane1LapCheck +1;
    lane1PreviousState = lane1State;
  }
  if(lane1LapCheck == 2){  
    lane1LapCount = lane1LapCount +1;
    lane1LapCheck = 0;
  }
  // ============LANE 2 LAP COUNT =============================================
  if(lane2Trigger < 900){                             // Car IS over the sensor
    lane2State = 1;
  }
  else{                                             // Car NOT over the sensor      
    lane2State = 0;
  }
  if(lane2State != lane2PreviousState){
    lane2LapCheck = lane2LapCheck +1;
    lane2PreviousState = lane2State;
  }
  if(lane2LapCheck == 2){  
    lane2LapCount = lane2LapCount +1;
    lane2LapCheck = 0;
  }
  // ============LANE 3 LAP COUNT =============================================
  if(lane3Trigger < 900){                             // Car IS over the sensor
    lane3State = 1;
  }
  else{                                             // Car NOT over the sensor      
    lane3State = 0;
  }
  if(lane3State != lane3PreviousState){
    lane3LapCheck = lane3LapCheck +1;
    lane3PreviousState = lane3State;
  }
  if(lane3LapCheck == 2){  
    lane3LapCount = lane3LapCount +1;
    lane3LapCheck = 0;
  }
  // ============LANE 4 LAP COUNT =============================================
  if(lane4Trigger < 900){                             // Car IS over the sensor
    lane4State = 1;
  }
  else{                                             // Car NOT over the sensor      
    lane4State = 0;
  }
  if(lane4State != lane4PreviousState){
    lane4LapCheck = lane4LapCheck +1;
    lane4PreviousState = lane4State;
  }
  if(lane4LapCheck == 2){  
    lane4LapCount = lane4LapCount +1;
    lane4LapCheck = 0;
  }

}

Photo resistors are slow to respond and analogRead() is a relatively slow process. Also you may run into a problem with some residual voltage on the ADC from pin A0 (say) when you take the first reading from pin A1. It is a good idea to read the pin twice and discard the first reading - for example

lane1Sensor = analogRead(A0);
lane1Sensor = analogRead(A0);            
lane2Sensor = analogRead(A1);
lane2Sensor = analogRead(A1);
// etc

but of course that slows the process further.

Photo transistors and digitalRead() would be much more suitable.

It would probably also be a good idea to use interrupts to capture the very short signal when a car crosses a detector.

...R

Assembling the code and looking at loop I notice there is a lot of Serial.print every loop turn. Cut that down.
I also question the use of map function. If the analog input is > minimum + a certain margin, than You have a presence.
Also be careful using lcd.print in loop. Both that and Serial.print push data into buffers and if a buffer gets filled the controller execution will be stuck there until there is place for the new characters. Being stuck a car might pass the sensor.....

functions() for the menu

//======================================================================
//                              Lap_Menu() Function
//======================================================================
int Lap_Menu()
{
  int ChosenLaps =0;
  lcd.setCursor ( 0, 0 );            
  lcd.print("A Race for Laps:    "); 
  lcd.setCursor ( 0, 1 );            
  lcd.print(" - 25 Laps? <Enter> "); 
  lcd.setCursor ( 0, 2 );            
  lcd.print(" - Custom? <Next>   "); 
  lcd.setCursor ( 0, 3 );            
  lcd.print("                    ");
  delay(500);    
  while(ChosenLaps == 0){
    enterButton=digitalRead(2);                   //Pin 2 is <Enter>
    nextButton=digitalRead(3);                    //Pin 3 is <Next>

    if (enterButton == HIGH) {
      ChosenLaps = 25;
//      break;
    }
    if (nextButton == HIGH) {
      ChosenLaps = Custom_Number();
//      break;
    }
  }
  delay(500);
  return ChosenLaps;
}

//======================================================================
//                              Custom_Number() Function
//======================================================================
int Custom_Number(){
  int ChosenNumber=0;
  int firstDigit=0;
  int secondDigit=0;
  lcd.setCursor ( 0, 0 );            
  lcd.print("Custom Number:    00"); 
  lcd.setCursor ( 0, 1 );            
  lcd.print(" Press <+>    +10   "); 
  lcd.setCursor ( 0, 2 );            
  lcd.print(" Press <Next> +1    "); 
  lcd.setCursor ( 0, 3 );            
  lcd.print(" When done: <Enter> ");

    while ((enterButton==LOW)){
      enterButton=digitalRead(2);
      nextButton=digitalRead(3);                    //Pin 3 is <Next>
      plusButton=digitalRead(4);                    //Pin 4 is <Plus>
      if (plusButton == HIGH) {
        firstDigit = firstDigit + 1;
        lcd.setCursor ( 0, 0 );            
        lcd.print("Custom Number:    ");
        lcd.setCursor ( 18, 0 );
        lcd.print(firstDigit);
        lcd.setCursor ( 19, 0 );
        lcd.print(secondDigit);
        
        delay(500);       
        if (firstDigit>9){
          firstDigit = 0;
          lcd.setCursor ( 0, 0 );            
          lcd.print("Custom Number:    ");
          lcd.setCursor ( 18, 0 );
          lcd.print(firstDigit);
          lcd.setCursor ( 19, 0 );
          lcd.print(secondDigit);
          delay(500); 
        }
      }
      else if (nextButton == HIGH) {
        secondDigit = secondDigit + 1;
        lcd.setCursor ( 0, 0 );            
        lcd.print("Custom Number:    ");
        lcd.setCursor ( 18, 0 );
        lcd.print(firstDigit);
        lcd.setCursor ( 19, 0 );
        lcd.print(secondDigit);
        lcd.setCursor(0,2);                 // Weird Code Gremlin Correction that...
        lcd.print(" ");                     // ...just keeps a zero from appearing
        delay(500);      
        if (secondDigit>9){
          secondDigit = 0;
          lcd.setCursor ( 0, 0 );            
          lcd.print("Custom Number:    ");
          lcd.setCursor ( 18, 0 );
          lcd.print(firstDigit);
          lcd.setCursor ( 19, 0 );
          lcd.print(secondDigit);
          delay(500); 
        }
    }
  }  

  delay(500);
  ChosenNumber = 10*firstDigit + secondDigit;
  return ChosenNumber;
}

Hi,
I agree with @Railroader, using analog inputs and map functions will slow the program response time down.

Try serial printing at 115200 baud rather than 9600 baud, you may notice some difference there.

Definitely photo-transistors and digital inputs will help.
Then interrupts to follow.

Tom... :slight_smile:

The forum wont let me post the code because it's too long apparently?

Yes, and the reasons are pretty clear as soon as you read the code. Everything is repeated 4 times. Lane 1: this, that, the other. Lane 2: this, that, the other.... You really need to learn about arrays!

Thank you all. It's nice to hear that it's not hopeless. Just a lot more work and learning!

I'll try to get my mojo back. Very new as you can tell from my clunky code.

G

That's not "clunky code". It's the easy way to quadrouple a task. Keeping things mostly make them work.
Using indexed arrays will shorten the code but make it slightly slower and a bit more difficult to read and maintain.

Does the LEDs have any stuff focusing the light to the receiver? A tube can do fine.

4 white LEDs 3.5" above the track. No bleed over. When cars aren't calibrated the >60hz flicker will cause a lap count down.

Railroader, your thoughts on arrays are similar to my own. I'm concerned about having conditional loops in the race loop slowing the code even further. Also considered structures and had a bubble sorter for placings but that made my brain hurt.

Tom, could you explain what an "interrupt" is? I think that puting the analog reads in the void loop in a conditional while{ a car is not passing over any sensor}. Then when triggered, it stores the lap count, exits the loop and checks for race end and placings. After that it goes back to the while{} at the start of the void loop.

That while construction sounds not so good. A car can pass at any time and You neve know when there is time for the "administrative work" like counting, displaying.
An interrupt would be a happening triggered by the hardware. It's a short piece of code that f ex tells that a car just passed by. Loop will read the message and reset it.

By Arduino standards a single lap of the circuit will take forever. But there is a brief instant in each lap when the Arduino needs to record the passage of a car.

If you use interrupts for this then the rest of the code can be as slow and unwieldy as you want - so long it as it takes the trouble to check the value from each interrupt at least once per lap. The interrupt can record the value of millis() when it happens which will give you very precise lap timing.

The code in this link shows how I used an interrupt to measure the speed of a small DC motor. It would not be too difficult to adapt it to your car racing system.

Separately, the downsides of having repetitive code rather than using arrays is {A} the risk of making typos that are hard to find because all the repeats look the same and {B} the inconvenience when something needs to be changed and you have to do it 4 times rather than once. Using arrays would add a trivial amount to the time the code takes to execute but will make it so much easier to maintain. And with this project there is ample time for the code.

If you are prepared to extend your learning a little further this is a classic case where a STRUCT would be a useful way to bring together all the variables for a specific lane. Then there could be an array of 4 structs to represent all the lane.

...R

@Robin2: Thank you for the example. I will look at it. Regarding structures: I discovered them and thought they would work very well for my application, but I was running into significant difficulty with them, although I don't recall what it was now. I feel certain it was a knowledge deficit. Regarding trouble shooting and type-o's in my inelegant style: that's why I have such extensive commenting so I can find the multitudinous corrections that are necessary when I tweak something. I will take your advice and look again at arrays and structures. No promises there. I'm a self-learner and a smidge on the "slow" side myself.

@Railroader. I know you're right. I was just grasping at straws. I'll check out robin2s code and see if I can deduce what an interrupt is.

Thank you guys for helping me. It's very kind to help. I'll try to pay it forward where I can.

Railroader:
That's not "clunky code"

It definitely is.

Railroader:
It's the easy way to quadrouple a task.

Correct. Its the lazy way.

Railroader:
Keeping things mostly make them work.

Huh?

Railroader:
Using indexed arrays will shorten the code but make it slightly slower

Correct. It would make the code, in this case, 75% shorter. And about 0.000...1% slower.

Railroader:
and a bit more difficult to read and maintain.

The opposite of that.