Wednesday, 17 February 2016

Laptimer Arduino Sketch

Sketch for the laptimer used here

Simply copy below into the Arduino sketch program.

/* Lap Timer for Team Barnato karting races by Matt Wall
Use at own risk, Sketch is in the public domain but no profit shall
be gained from using it.

 Pin allocation.
 d0 - No Connection
 d1 - display RS
 d2 - IR signal from IR receiver
 d3 - PWM signal for display backlight
 d4 - display data pin - d4
 d5 - display data pin - d5
 d6 - display data pin - d6
 d7 - display data pin - d7
 d8 - display Enable
 d9 - no Connection
 d10 - Sd Card CS
 d11 - Sd Card MOSI
 D12 - Sd Card MISO
 D13 - Sd Card CLK / SCK
 */
#include <LiquidCrystal.h>// include the LCD library
#include <SdFat.h> // include the new sd card library
SdFat sd;
float lapdelay = 20000;// set the default lap delay to 20 seconds unless updated from SD card file.
SdFile myFile; // file to read SD card lap delay data
float decade;// to be used in reading sd card
float temp;// to be used in reading sd card
float number;// to be used in reading sd card
int backlight = 3;//PWM for display backlight to be connected to pin 3
int lapcount = 0;// start with a lap count of 0
long start = 500;//set values (system will random asign if left blank) will be changed later
long finish = 0;//set values (system will random asign if left blank) will be changed later
long laptime = 0;//set values (system will random asign if left blank) will be changed later
long elapsed1 = 0;//set values (system will random asign if left blank) will be changed later
long elapsed2 = 0;//set values (system will random asign if left blank) will be changed later
long oldlaptime = 0;//set values (system will random asign if left blank) will be changed later
long delta = 0;//set values (system will random asign if left blank) will be changed later
#define IRpin_PIN      PIND // assign pin 2 raw mapping to IRpin 2
#define IRpin          2

#define MAXhighPULSE 2000 // Time counts to reset tokens to zero if no beam detected incase of false trigger, 2000 * RESOLUTION = 40ms
#define MAXlowPULSE 28 // the maximum pulse count we need. 560us / RESOLUTION = 28
#define MINlowPULSE 18 // the minimum pulse count we need. 360us / RESOLUTION = 18

#define RESOLUTION 20 // Multiplier - will be used to take reading of IRpin every 20ish microseconds
int lowpulse = 0; // start with 0 for the pulse length.
int highpulse = 0; // start with 0 for the pulse length.
int token = 0; // start with 0 count for tokens. We'll need 3 tokens to trigger the timer.

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(1, 8, 4, 5, 6, 7);
const int chipSelect = 10; //choose the chip select pin for the SD card
SdFile myfile;

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  pinMode(backlight, OUTPUT);// Output for the PWM on the backlight
 // pinMode(10, OUTPUT);// must be set fot the SD Card to work
  analogWrite(backlight,80);
  lcd.print("  Team Barnato"); // Print a message to the LCD.
  delay(2000);// Wait 2 seconds before clearing the LCD.

  if (!sd.begin(chipSelect, SPI_HALF_SPEED)){// start up the sd card
    lcd.clear();
    lcd.print("SD Card failed!");// Incase no SD card or problem with SD Card
    delay(10000);
  }
  else{ //SD Card is ok
    lcd.clear();
    lcd.print("   SD Card OK");// tell the world the sd card is ok
    delay(2000);//give people time to read the message
  }
  if (sd.exists("times.csv")){//check if the file to record the lap times in already exists?
    lcd.clear();
    lcd.print("    File OK");//tell the world it exists
    delay(2000);//give people time to read the message
  }
  else{ //it doesn't exist
    lcd.clear();
    lcd.print(" Creating File");//tell the world your creating the file
    myfile.open("times.csv", O_RDWR | O_CREAT | O_APPEND);// create the file
    myfile.close();// save the file
    delay(2000);//give people time to read the message
    if (sd.exists("times.csv")){// re check that the file exists after creating
    lcd.clear();
    lcd.print("    File OK");//tell the world it exists
    delay(2000);//give people time to read the message
   }
    else{// file still not available
      lcd.clear();
      lcd.print("File Error");// tell the world the is a problem
      delay(5000);// give people time to read the message
    }
  }

  if (myFile.open("lapdelay.txt",O_READ)) // Open SD Card and retreive lap delay information.
    {
      lapdelay = 0; // if the file is ok on the sd card then set lapdelay at zero ready for reading the correct delay
      decade = pow(10, (myFile.available() - 1));
      number = 0;
      while(myFile.available())
      {
       temp = (myFile.read() - '0');
       number = temp*decade+number;
       decade = decade/10;
      }
      lapdelay = number*1000;
      myFile.close();
    }
  lcd.clear();
  lcd.print("   Target Lap");
  lcd.setCursor(4, 1);
  lcd.print(lapdelay/1000);
  lcd.print(" secs");
  delay(2000);
  lcd.clear();
  lcd.print(" Ready To Race");
  lcd.setCursor(0, 1);
  lcd.print(" -- Out  Lap --");
}

void displayResult(){
  /*This procedure will calculate the laptime, delta time, display the result to the LCD screen, record the lap time to SD card (not yet working)
   and finally set a system delay to allow you to pass the start / finish straight without re triggering from other beams*/
  laptime=finish-start;// Calculate lap time
  if (lapcount>0){// will display times after completing the first flying lap.
    //This section works out the actual lap time
    float m1,s1,ms1;// create variables for hours,minutes,seconds and milliseconds
    unsigned long over1;// create variable for the calculation over spill
    elapsed1=laptime;
    m1=int(elapsed1/60000); //Calculate the minutes
    over1=elapsed1%60000; // Work out the remainder
    s1=int(over1/1000);// Calculate the seconds
    ms1=over1%1000;//work out the milliseconds
    lcd.clear();
    lcd.setCursor(0, 0);// Set cursor to top left position.
    lcd.print("LAP");//Print text to LCD
    lcd.setCursor(9, 0);
    if (oldlaptime<laptime)// If the last lap was less than the new lap then the delta needs to be a plus value
    {
      delta=laptime-oldlaptime;
      lcd.print("+");//shows these values on screen if new lap was slower then previous lap
    }
    else// If the last lap was more than the new lap then the delta needs to be a minus value
    {
      delta=oldlaptime-laptime;
      lcd.print("-");// shows these if your new lap was quicker then the last
    }
    float s2,ms2;// create variables for seconds and milliseconds
    unsigned long over2;// create variable for the calculation over spill
    s2=int(delta/1000); // work out the delta time
    ms2=delta%1000; //work out the milliseconds
    lcd.print(s2,0);//print delta seconds
    lcd.print(".");
    if (ms2<100){lcd.print("0");}// if milliseconds is below 100 we need to add a zero
    if (ms2<10){lcd.print("0");} // if milliseconds is below 10 we need to add another zero
    lcd.print(ms2,0);
    lcd.setCursor(0, 1);// move cursor to second line first character
    lcd.print(lapcount);
    lcd.setCursor(8, 1);// move cursor to second line 8th character
    lcd.print(m1,0);
    lcd.print(":");
    lcd.print(s1,0);
    lcd.print(".");
   if (ms1<100){lcd.print("0");}// if milliseconds is below 100 we need to add a zero
    if (ms1<10){lcd.print("0");} // if milliseconds is below 10 we need to add another zero
    lcd.print(ms1,0);
    if (myfile.open("times.csv", O_RDWR | O_CREAT | O_APPEND)){// Open SD Card File
      myfile.print("Lap,");//Start line with Lap
      myfile.print(lapcount);//add the lap count to the file
      myfile.print(",");//add a comma for use in excel later
      myfile.println(laptime);//save laptime and start new line
      myfile.close();// close and save the file
    }
  }
  else { // this section is only used during the first flying lap.
    lcd.clear();
    lcd.print("   First  Lap");
    lcd.setCursor(0,1);
    lcd.print(" Timer  Running");
  }
  // This line reserved for entering the save to SD card routine
  start=finish; // set the new lap start time to the same value as the last lap finish time
  oldlaptime=laptime;// store new lap time as old lap time for next lap
  lapcount++;// increase lap count by 1
  token = 0; // reset token counter ready for next lap
  delay(lapdelay);// delay to prevent re triggering if other beams are present.
}

void loop() {
  highpulse = lowpulse = 0; // start out with no pulse length
  while (IRpin_PIN & (1 << IRpin)) { // while irpin is high - no beam is beam is being received.

    highpulse++;
    delayMicroseconds(RESOLUTION);// wait some time, the multiplier.

    if (highpulse >= MAXhighPULSE) { /* check to see if no signal has been received for over 40ms (we should
     recieve 3 tokens in 4ms, so if we only receive one or two tokens in 40ms it must be a false signal),
     then reset the token count if true as no/false signal is being received.
     */
      token = 0;
      highpulse = 0;// reset high pulse counter
    }
  }
  while (! (IRpin_PIN & _BV(IRpin))) {// while irpin is low, signal being received
    lowpulse++;
    delayMicroseconds(RESOLUTION);
  }
  if ((lowpulse >= MINlowPULSE)  && (lowpulse <= MAXlowPULSE)) {// was the pulse length what we expected?
    token ++; // if so then count 1 token, we need 3 tokens to trigger
  }
  if (token == 3) { // If we receive 3 correct pulses then trigger the lap timer.
    finish=millis();// Record the time
    displayResult();// run through the display procedure
  }
}

16 comments:

  1. Hi, is there any image with the wiring, its been impossible for me ive burn a few sensors without success, can you sir help me out, thanks in advance

    ReplyDelete
    Replies
    1. Hi Jorge,
      There is a link at the top of this page which will take you to all the images I have, or click here
      http://teambarnato.blogspot.co.uk/2016/02/ir-laptimer-using-arduino.html

      Delete
  2. Matt thank you so much !! your are amazing!!

    ReplyDelete
  3. Hi Matt, First of all thank you very much for your sharing.

    I have been looking for lap timer for a while. So everything was ready and waiting on my table, i updaload your sketch and wired up everything, it started to work in 5 minutes. Great !!! ..

    But i cloudnt test it yet. may be you can answer these quesitons before..
    - How much fast can i go without reading problem? im using traxxas erevo, average speed is 60km/s.

    - if one of my friend decided to use same code i think i have to change a value both receiver and transmitter side, right? which value should i change for another user?

    ReplyDelete
    Replies
    1. Hi, glad this was of some use. I assume your speed should have read 60kph not 60kps? the maximum speed will depend on what IR LED's you use (beam angle) and how far you are from the transmitter. You can easily expect 200kph to work so you should be fine. I assume you have the transmitter mounted on the car and the receiver on the trackside (opposite to my example)? I would recommend dropping the 6ms break in the transmission to 1200us and this will help with faster signal reception, but it will use more power from the batteries.

      If your running 2 transmitters the yes you'll need to change the code in both the 2nd transmitter and receiver. just use some values outside the range of the first one. I'll have a look at the code and recommend something shortly.

      Delete
    2. So a good set of values for the second transmitter would be as follows:-
      Change the transmitter to:-
      pulseIR(780);
      delayMicroseconds(1000);
      pulseIR(780);
      delayMicroseconds(1000);
      pulseIR(780);
      delayMicroseconds(5000);

      Then change the receiver sketch as follows:-
      #define MAXlowPULSE 45 // the maximum pulse count we need. 900us / RESOLUTION = 45
      #define MINlowPULSE 35 // the minimum pulse count we need. 700us / RESOLUTION = 35

      This should be enough difference between the 2 cars

      Hope this helps

      Delete
    3. Thank you very much for sharing your information.. You are the man.

      Im using 20x4 lcd. I think your code is just keeping last lap info and shows difference between last lap and current lap. im planing to do some improvements,
      -store and show Best lap
      -create a new file for each start on SD card
      -warning sound for new Best lap

      I'll share them here if I succeed.
      Best regards

      Delete
  4. I presume that due to the fact that on each lap the receiver could pick up the transmitter at a different point in the pulse stream, this has an inherent inaccuracy of around 5 hundredths of a second? If that is the case is there any way of reducing that such as by leaving the signal high rather than low for the 40mS and then recording the time at which the signal was first detected before then validating the signal by counting the off pulses? By the way this is the perfect solution for me if only I can convince myself that it is accurate.
    Thanks

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  5. Hi Dave,
    The 40ms is only a reset time if transmission stream isn't received (its not the time for the receiver to get 3 pulses). The total transmission time for each repeat is just under 10ms, so the accuracy would be 10 thousandths of a second.
    This could be shortened by removing the 6ms break and replacing it with another 1200us break. Then the total time for each repeat would be under 5ms or 5 thousandths of a second. A further increase could be to reduce the 1200ms breaks to 600us and then your accuracy would be in the order of 2 thousandths of a second. Beware though, reducing the LED of time will significantly increase the power consumption and reduce battery life. My example code has the LED on for about 7% of the time so the batteries can last in the order of 40ish hours.
    The transmission graphs can be found here http://teambarnato.blogspot.co.uk/2016/02/ir-laptimer-using-arduino.html

    ReplyDelete
    Replies
    1. OK, so I did some more checking last night and I got my accuracy figures slightly wrong. I'll try to explain.
      When you first enter the beam you need to count 3 identical length pulses. This takes at a minimum (with my code) 3.6ms, however this is the same every lap so the trigger point would be in the same place. The accuracy of the system is set by the maximum break between pulses. If you enter the beam at the start of one of these breaks then the system will wait until it receives the next pulse before starting to count. In my code the maximum break is 6ms currently, so the worst accuracy is 6ms. As stated above this 6ms could be reduced to 1.2ms to match the other breaks and the accuracy would then be at worst case 1.2ms.
      By the way, professional systems (AIM) use similar code and has a maximum break of 9.3ms so they are not as accurate.

      Delete
  6. Thanks for your response, I have no idea where I got the 40ms from,as you say it's 6ms which is not a concern. I have actually decided to try a magnetic reed switch as the trigger since the track I normally go to has a magnetic strip but if that fails I will look again at your ir version. Also looking at gps timing but suspect that might be a bit more challenging, might make for easy sector times though. Parts aren't the post so I will let you know how I get on.

    ReplyDelete
  7. Hi Dave,
    Its definitely a good idea to try and use the tracks magnetic strip. I've looked into this as well before opting to go with the IR system. What I found was that a Reed switch was too slow (being mechanical) and your speed over the strip is too quick for the reed switch to react, also I'm not sure there's enough magnetic strength in the strip to operate a reed switch even when stationary but I've never checked this. The pro systems use a Hall Effect sensor (an electronic reed switch) to detect the strip but again I wouldn't know how strong the magnetic force is from the strip to specify the right hall effect switch. Another thing to consider is how many magnetic strips the circuit has? Some have 1 which keeps things easy but some have 2 or 3 strips around the track for intermediate timing etc. You would need a way to tell the timer how many strips are in the track and which one is the start and finish strip. E.g. if you leave the pits you may hit an intermediate strip first, but if you start from the grid then you may hit the start/finish strip first. Maybe have a button to press just before the start/finish line?
    GPS systems are probably the lowest accuracy (and the most difficult to build) as a good GPS sensor will only achieve around 2-5 meters accuracy at best (My 6ms accuracy works out at 17cm at 60mph) and will only really work on outdoor tracks. It’s still better than hand held stop watches by a considerable amount though. There are GPS lap timers available for smart phones so you could always buy a cheap smart phone with GPS (or use a standalone Bluetooth GPS unit) and use that as your lap timer?
    Which ever way you decide to go, keep us posted as its always good to share knowledge and I’d be very interested to see if you could get the magnetich pick up option working?

    ReplyDelete
  8. I also thought Hall sensor but I have seen pictures of the Alfano sensor cut open for repair and it does in fact use a reed switch. I also have a cheap latimer and that uses a reed switch and it works on my home track.
    The gps may just become a logger for analysis afterwards, the intention is to use it at 10hz for accuracy. What I have found when using a 5hz logger in the past is that although the absolute accuracy is as you say mot very high, the relative accuracy is,so that points within one lap are very accurate relative to each other so I think lap times should be quite accurate.

    ReplyDelete
    Replies
    1. I've always stayed clear of the reed switch for the reason above but I've never cut open a pro system, I always assumed (wrongly) that they would use a Hall Effect. This is good news. I think I'll look into this a bit more as this would simplify things a bit. Would just need a small box to house the reed switch and velcro it it the kart floor pan (or some more permanent method if its to stay on one Kart).
      On your cheaper lap timer how do you set it for more than one magnetic strip? My latest version is using a 2.7" touch screen so some menu options are possible. I've got a few thoughts on how to do this by pressing the touch screen just before crossing the start finish line and then using the lap time minus 3 or 4 seconds so switch the sensor back on again maybe? Will have a think as the intermediate timings may also be useful if the track supports it.

      Delete
    2. The reed switch I have on order which looks very like the one on my cheap timer is sealed in a plastic block with 2 mounting holes. Needs to be mounted using plastic bolts and rubber grommets to absorb the vibration, will only work if the flooring is non magnetic.
      The laptimer I have is very basic being a handheld stopwatch with the reed switch wired in parallel with the lap button so it couldn't handle multiple strips. Being able to set a number of strips per lap would be a solution but you would probably need a reset button in case a strip is not detected.

      Delete