Project tutorial
Race Photogate

Race Photogate © GPL3+

Two sets of timing gates for racing cars in a school solar car challenge that track and report the speed of each car in each of two lanes.

  • 998 views
  • 1 comment
  • 2 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)
4966285
Solder Wire, Lead Free
Gorilla Glue
Hy gluegun
Hot glue gun (generic)
To attach the sensor to the paper covering the sheet metal.
Quick setting epoxy resin
To glue the neodymium magnets onto the PVC pipe
3drag
3D Printer (generic)
OPTIONAL to make LCD cover and protective wedge

Apps and online services

About this project

After making a small photogate for timing Hot Wheels cars, I was asked to help our middle school with their Solar Car Challenge and make a two-lane photogate for the preliminary races that all students participate in. (Their cars are roughly 4 inches wide, 10 inches long and 4-5 inches high.)

The project uses lasers and a phototransistor to trigger when to start and stop the timing and a 16x2 LCD screen to display the status and results. (The race locations had too much ambient natural and artificial light, and the distances across the photogate were too great to expect infrared detectors to work with great accuracy.)

The phototransistor sends a different voltage to the Arduino if the laser is pointing on it, and a white LED turns on when the laser is properly aligned.

During alignment, the LCD screen shows the status of each of the phototransistors and they show "OK" if the lasers are aligned.

One of the biggest issues during the transport and reassembling of the photogate was having the light sensors damaged or misaligned so much that fine adjustments wouldn't be enough to align the lasers. To mitigate that, the sensors were placed on sheet metal and neodymium magnets were put on the PVC pipe with epoxy to allow large adjustments of the sensor. They were also attached with an easy-to-remove clip so they could be replaced or repaired if needed without disassembling the entire structure, cut wire or remove solder from connections. Also, the dirt cheap lasers were less focused, which aided alignment.

An optional Bluetooth module was added to the project so the results could be read via an Android phone app used by the time recorder a distance away from the track. The app was also used to remotely reset the race and let the individual physically at the track know the times were successfully recorded.

The A B C D red squares turn from red to green as the lasers are aligned and as the corresponding photogate LED turns on.

At the end of the race, the Reset Race button is enabled so a person sitting away from the track to finish recording times and control when the next race starts.

There is also a physical reset button on the outside of the project box to manually reset the Arduino if something goes wrong.

The building of the photogate structure out of PVC pipe is hopefully self-explanatory. The connections were not glued to allow easier maintenance, and a wire clip between right and left halves of each gate was added to allow partial disassembly for easier moving and storage.

Below shows CAT 5 female connectors were put in the back end of the front pair of photogates and the front of the back pair so different lengths of regular CAT 5 cable could be used during testing and during the race. It also allows the wires to be completely disconnected during the moving and storage of the photogates.

Code

Laser_Photogate-2_lanesArduino
Code to calibrate lasers, time, display results and send them via optional Bluetooth module
/* PhototransistorVoltage Solar Car Timer by Nelson Farrier
 * Power each phototransistor with 5v and GND
 * Connect the 2 phototransistors yellow wires 
 *       into A0 & A1 to detect start and stop of car.
 * Use 4 pins (below) to indicate laser alignment
 * Use 4 analog pins (A0-A3) for detecting photo transistor voltage
 */
#include <LiquidCrystal_I2C.h>  // Must be downloaded separtely & put in your Arduino's libraries folder

/*----- LCD Pin configuration-----*/
//GND - GND
//VCC - 5V
//SDA - ANALOG Pin 4
//SCL - ANALOG pin 5

LiquidCrystal_I2C lcd(0x27, 16, 2);

const boolean testing = false;

const int align1Pin = 8;  // pin to show laser is on 1st mark
const int align2Pin = 9;  // pin to show laser is on 2nd mark
const int align3Pin = 10;  // pin to show laser is on 1st mark
const int align4Pin = 11;  // pin to show laser is on 2nd mark

const int interval = 10;  // accuracy of timer in milliseconds
float threshold = 0.75;    // voltage where phototransistor shows car is in the way
float v1, v2, v3, v4, timer1Count, timer2Count;
boolean A, B, C, D, doneLtiming, doneRtiming, triggeredL, triggeredR, overLmtL, overLmtR, statusLCDshown, statusRshown, statusLshown, finishLshown, finishRshown;  // A & C on one lane, B & D on other
int count=0, msg = 0;
char inSerial[15];
const int StartL = 1;
const int StartR = 2;
const int FinishL = 3;
const int FinishR = 4;
const int leftLane = 0;
const int rightLane = 1;
const float overtime = 60000; // 1 minute

// bluetooth codes
const int Reset = 9;
const int Other = 10;

void setup()                                 // Built-in initialization block
{
  Serial.begin(9600);                        // Set data rate to 9600 bps
  pinMode(align1Pin,OUTPUT);
  pinMode(align2Pin,OUTPUT);
  pinMode(align3Pin,OUTPUT);
  pinMode(align4Pin,OUTPUT);
  Serial.println("!Waiting to connect to Bluetooth...");      
  lcd.begin();
  lcd.clear();
  lcd.setCursor(16,2);   
  lcd.backlight();
  lcd.setCursor(0 ,0); 
}

void loop() {  
  lcd.setCursor(16,2);   // initialize the lcd for 16 chars 2 lines, turn on backlight
  lcd.backlight();

  A = B = C = D = doneLtiming = doneRtiming= triggeredL = triggeredR = overLmtL = overLmtR = statusLCDshown = false;  // A & C on one lane, B & D on other
  finishLshown = finishRshown = statusLshown = statusRshown = false;
  boolean wait = true;
  waitForAlignment();
  timer1Count = 0;
  timer2Count = 0;
  count = 0;
  
  delay(1000);
  Serial.print("!Ready...");       
  lcd.clear();
  lcd.setCursor(0 ,0); //Start at character 4 on line 0
  lcd.print("Ready...");
  delay(1000);
  
  Serial.println("Set...");
  lcd.print("Set...");
  
  while ((!doneLtiming) || (!doneRtiming)) {
    if (triggeredL || triggeredR) {
      if (!statusLCDshown) {
      lcd.setCursor(0 ,1);
      lcd.print("Race Started!");
      if (testing) { 
        Serial.println(A); 
        Serial.println(B); 
        Serial.println(C); 
        Serial.println(D); 
        }
      statusLCDshown = true;
      }
      if ((triggeredL) && (!statusLshown)) {
        Serial.println("Race started in left lane");
        statusLshown = true;
      }
      if ((triggeredR) && (!statusRshown)) {
        Serial.println("Race started in right lane");
        statusRshown = true;
      }
      
    }     

    if (triggeredL && !doneLtiming) {             //  add interval to timer because L lane has started 
      timer1Count = timer1Count+interval; 
    }
    if (triggeredR && !doneRtiming) {             //  add interval to timer because R lane has started 
      timer2Count = timer2Count+interval; 
    }
    if (timer1Count > overtime) {
      doneLtiming = overLmtL = true;
    }
    if (timer2Count > overtime) {
      doneRtiming = overLmtR = true;
    }
    v1 = volts(A0);                    
    v2 = volts(A1);                    
    v3 = volts(A2);                    
    v4 = volts(A3);                    
    if (v1 < threshold) { digitalWrite(align1Pin, LOW); }   // turn off (and leave off) LED's as they are triggered
    if (v2 < threshold) { digitalWrite(align2Pin, LOW); }
    if (v3 < threshold) { digitalWrite(align3Pin, LOW); }
    if (v4 < threshold) { digitalWrite(align4Pin, LOW); }

    if (!triggeredL) {
      triggeredL = (v1 < threshold) || (v3 < threshold);       // initial trigger L (front or back)
      A = (v1 < threshold);
      C = (v3 < threshold);
    }
    else {
      if (A) {                                                 // final trigger L (front or back)
        if (v3 < threshold) { 
          doneLtiming = true; 
          if (!finishLshown) {
            Serial.println("Left lane finished"); 
            showLaneTime(leftLane,timer1Count,overLmtL);
            finishLshown = true; 
          }  
        }
       }
      if (C) {
        if (v1 < threshold) { 
          doneLtiming = true; 
          if (!finishLshown) {
            Serial.println("Left lane finished"); 
            showLaneTime(leftLane,timer1Count,overLmtL);
            finishLshown = true;
          }   
        }
      }
    }
    
    if (!triggeredR) {
      triggeredR = (v2 < threshold) || (v4 < threshold);        // initial trigger R (front or back)
      B = (v2 < threshold);
      D = (v4 < threshold);
    }
    else {
      if (B) {                                                  // final trigger R (front or back)
        if (v4 < threshold) { 
          doneRtiming = true; 
          if (!finishRshown) {
            Serial.println("Right lane finished"); 
            showLaneTime(rightLane,timer2Count,overLmtR);
            finishRshown = true;
          }   
        }
        }
      if (D) {
        if (v2 < threshold) { 
          doneRtiming = true; 
          if (!finishRshown) {
            Serial.println("Right lane finished"); 
            showLaneTime(rightLane,timer2Count,overLmtR);
            finishRshown = true;
          }   
        }
        }
      }

    delay(interval);                 // Delay for defined time
  }

  // end timer count and display results
  flashScreen();
  delay(80);
  if (testing) {
    Serial.print(triggeredL);
    Serial.print(triggeredR);
    Serial.print(doneLtiming);
    Serial.println(doneRtiming);
  }
  Serial.println("!Race Complete");  
  showLaneTime(leftLane,timer1Count,overLmtL);
  showLaneTime(rightLane,timer2Count,overLmtR);
  Serial.println("");                  // print blank line
       
//loop until bluetooth "Reset" sent or rebooted


while (wait) {
  int i=0;
  int m=0;
  delay(500);
  if (Serial.available() > 0) {
  while (Serial.available() > 0) {
  inSerial[i]=Serial.read();
  i++;
  }
  inSerial[i]='\0';
  msg = Check_Protocol(inSerial);
  wait = (! msg == Reset);
  if (msg == Reset) {
    flashLED(8);
  }
  }
}
}

//-----------------------------------------

void showLaneTime(int lane, float timerCount, boolean overLmt) {
if (lane == leftLane) {
  Serial.print("Left Lane Time:  ");       
  lcd.setCursor(0,0);
  lcd.print("L Time=");       
} else {
  Serial.print("Right Lane Time: ");       
  lcd.setCursor(0,1);
  lcd.print("R Time=");       

}
  if (!overLmt) {
    Serial.print(timerCount/1000);       // Display timerCount in #.## format
    Serial.println(" sec");             
    lcd.print(timerCount/1000);       
    lcd.println(" sec  ");             
  }
  else {
    Serial.print("past limit");
    lcd.println(" past lmt  ");             
  }
}

                                        
float volts(int adPin)                       // Measures volts at adPin
{                                            // Returns floating point voltage
 return float(analogRead(adPin)) * 5.0 / 1024.0;
}    

void flashScreen() {
    for(int i = 0; i< 3; i++)
  {
    lcd.backlight();
    delay(150);
    lcd.noBacklight();
    delay(150);
  }
  lcd.backlight(); // finish with backlight on  
}

int Check_Protocol(char inStr[]){
  int i=0;
  int m=0;
  uint32_t c=0;  // color of bounce
  
  Serial.println(inStr);
  if (!strcmp(inStr,"Reset")){    //Reset system  
    //Serial.println("Reset");
    return Reset;
    for(m=0;m<11;m++){
      inStr[m]=0;}
      i=0;
    }

  if (!strcmp(inStr,"other")){     
    Serial.println("Other");
    return Other;
    for(m=0;m<11;m++){
      inStr[m]=0;}
    i=0;
    }
  else{
    for(m=0;m<11;m++){
      inStr[m]=0;}
  i=0;
  }
}

void flashLED(int pinNum) {
    digitalWrite(pinNum, LOW);
    delay(200);
    digitalWrite(pinNum, HIGH);
    delay(200);
    digitalWrite(pinNum, LOW);
    delay(200);
    digitalWrite(pinNum, HIGH);
    delay(200);
    digitalWrite(pinNum, LOW);
    delay(200);
    digitalWrite(pinNum, HIGH);
    delay(200);
    digitalWrite(pinNum, LOW);
}

void  waitForAlignment() {
  boolean Start1 = false;
  boolean Start2 = false;
  boolean Finish1 = true;
  boolean Finish2 = true;
  Serial.println("!Checking Laser Alignment...");       // wait for either phototransistor to be dimmed
  lcd.clear();
  lcd.setCursor(0 ,0);     //Start at character 1 on line 0
  lcd.print("Checking Laser");
  lcd.setCursor(0 ,1);     //Start at character 1 on line 1
  lcd.print("Alignment...");
  delay(2000);
  boolean done = false;
  lcd.clear();
  while (!done) {
    v1 = volts(A0);
    digitalWrite(align1Pin, !(v1 < threshold));
    Start1 = !(v1 < threshold);
    alignmentCheck(Start1,StartL,v1);
    if (testing) {
      Serial.print( " ");                    
      Serial.print(v1);                  // Display measured A0 volts
      Serial.println(" volts ");       
    }

    v2 = volts(A1);
    digitalWrite(align2Pin, !(v2 < threshold));
    Start2 = !(v2 < threshold);
    alignmentCheck(Start2,StartR,v2);
    if (testing) {
      Serial.print( " ");                    
      Serial.print(v2);                  // Display measured A1 volts
      Serial.println(" volts ");       
    }

    v3 = volts(A2);
    digitalWrite(align3Pin, !(v3 < threshold));
    Finish1 = !(v3 < threshold);
    alignmentCheck(Finish1,FinishL,v3);
    if (testing) {
      Serial.print( " ");                     
      Serial.print(v3);                 // Display measured A2 volts
      Serial.println(" volts ");       
    }

    v4 = volts(A3);
    digitalWrite(align4Pin, !(v4 < threshold));
    Finish2 = !(v4 < threshold);
    alignmentCheck(Finish2,FinishR,v4);
    if (testing) {
      Serial.print( " ");                     
      Serial.print(v4);                 // Display measured A3 volts
      Serial.println(" volts ");        
    }
    
    Serial.println("  ");       
    delay(500);                               // Delay for 0.5 seconds
    done = (Start1 && Start2 && Finish1 && Finish2);
  }
  Serial.println("!Lasers Aligned");       // wait for either phototransistor to be dimmed
  lcd.clear();
  lcd.setCursor(0 ,0); //Start at character 4 on line 0
  lcd.print("Lasers Aligned");
  delay(2000);
}

void alignmentCheck(boolean aligned, int section, float v) {
    String fullTextOut, smTextOut;
    if (section == StartL) { 
      fullTextOut = "Left Start"; 
      smTextOut = "A"; 
      lcd.setCursor(0, 0);
      }
    if (section == StartR) {
      fullTextOut = "Right Start"; 
      smTextOut = "B"; 
      lcd.setCursor(8, 0);
      }
    if (section == FinishL) { 
      fullTextOut = "Left Finish"; 
      smTextOut = "C"; 
      lcd.setCursor(0, 1);
     }
    if (section == FinishR) { 
      fullTextOut = "Right Finish"; 
      smTextOut = "D"; 
      lcd.setCursor(8, 1);
      }
    if (aligned) {
      Serial.println(fullTextOut+" Aligned");                  
      lcd.print(smTextOut+"=OK   ");                            
    }
    else {
      Serial.print(fullTextOut+"=");                     // Display "Ax = "
      lcd.print(smTextOut+"=");
      Serial.print(v);                  // Display measured Ax volts
      Serial.println(" volts ");        // Display " volts" & newline
      lcd.print(v);                     // Display measured Ax volts
      lcd.print("v ");                  // Display " volts" & newline
    }
}
Solar Car Challenge (optional phone app for Bluetooth connection)Scheme
MIT App Inventor Code (.aia) for Android Phone to see photogate results via Bluetooth and to reset photogate
No preview (download only).

Custom parts and enclosures

PVC protective wedge
An optional triangular wedge can be made to prevent any car from sideswiping the center laser base of the final photogate--either from wood or with this 3D printed version.
LCD screen cover
An optional cover for the enclosure with mounting posts for a 16x2 Arduino LCD screen.

Schematics

Laser Photogate 2 Lane Schematic
Laser Photogate Schematic for 2 Lanes
photogate4_kQztTXBPnO.fzz

Comments

Similar projects you might like

Hot Wheels Car Photogate

Project tutorial by nfarrier

  • 2,716 views
  • 2 comments
  • 10 respects

Android Phone Control of NeoPixel Display

Project tutorial by Bryce K and Lucas W

  • 1,709 views
  • 1 comment
  • 6 respects

Rover for Cave Exploration

Project tutorial by ACool

  • 3,564 views
  • 2 comments
  • 23 respects

Arduino Noticeboard

Project tutorial by msr048

  • 2,594 views
  • 0 comments
  • 15 respects

'Roger Bot' the Pet Rover

Project showcase by hannu_hell

  • 2,316 views
  • 0 comments
  • 13 respects

Health Kit: Humidity and Temperature Control

Project tutorial by Diana Khalipina

  • 3,417 views
  • 2 comments
  • 32 respects
Add projectSign up / Login