Stopwatch for Pinewood Derby

Ok, I’ve got my next project. I’ve been asked to design something to help judge the local pinewood derby. Here are the details:
The track has 4 lanes. Some lanes consistently perform better than others (outer lanes are always faster). So, a simple 1st, 2nd, 3rd, 4th won’t do, I need the number of seconds to a fairly good granularity (hopefully ms).
I can design individual photo receptors and LEDs to trigger when each car starts and when each car finishes.
Ideally, I’d like to figure out if we can run a standard car down each track a couple dozen times to find out the percent variability of each and code that back into the timer.

I could start the timer with a single switch throw, but I’d rather have each lane have its own trigger.

Since I’ve only done one other project with my Spark, I’m not sure how I would run four simultaneous timers. I’m going to read the documentation again around the time functions, but if anyone has any ideas to get me started, I’d appreciate it!

1 Like

HI @sweenig

That sounds like a great project with many benefits! Here’s a link to get you started:

http://makezine.com/video/projects-with-ryan-slaugh-building-a-raspberry-pi-pinewood-derby-race-system/

I have not made one of these scratch but I have used several and even rebuilt one. The typical way is photo transistors in the track with LEDs shining down from above at the finish line end of the track. For the starting line,I have used systems that used laser and phototransistor interrupters and micro-switches. The laser based system takes some time to align but it is much better since the starting line takes beating and the micro-switch is quickly out of alignment. At a district race we had a parent complain about the fact that there is only one starting line for all lanes and the time starts when the first car crosses it, but I didn’t see that as a problem really.

On the software side, we used purchased race software and ran it on a laptop with a projector. It was set up for a serial port connection to the track at the finish line. There was a cable that ran down from the starting line to the finish line to start the timers.

We ran every car, on every lane, twice, summing all the times except the slowest time. So you could have one bad race or one bad start and still win. We would then take the top 8 cars and have a championship race. This is where buying the racing software really helped make it smooth and easy process. Before we bought it, we did all that with spreadsheets and manual data entry in as near to real-time as we could, but it was always slow. You still need a human judge or two to declare a re-race if something goes wrong.

I see the Spark core being able to post to Google spreadsheets as a game changer–maybe you don’t need the expensive race software.

Yeah, I had looked at using a RaspberryPi and may still do that since I’m a little more comfortable with programming it than the Spark (not to mention loads easier to program the web interface). Not having any experience using an arduino, I’m a little lost as to why it’s needed at all.

Using some NTP code developed with the help of the community for my first project, I may just look at setting the time of the start using a photo resistor/led pair, and the end time using 4 pairs of phot resistor/leds. Then just take the delta between the four end times and the start time and I should have a pretty good idea of each track time.

Then it wouldn’t be too hard to add % handicaps to each lane that is faster than the slowest lane after testing each lane a couple dozen times.

NTP may help, but mostly you will need millis(); and to disable interrupts for the race. A race typically takes 3-5 seconds.

I don’t think you will be able to do good science on the lane handicaps: my experience is some cars get faster as the day goes on and things wear smooth and some cars get slower as the day goes on. Once you have the race software setup, for a typical Pack size of 30-40 cars, the racing only takes 45 minutes or so using every car on every lane twice. Announcing which car is in which lane takes the time–running the race is quick.

One other fun thing I did as Cubmaster was I built a car with a wireless camera on it. While the other folks were getting all the cars into the race software, I would run the camera car down the track with camera pointed straight ahead, then off to one side and then off to the other side. Seeing yourself on a big projection screen is a big deal for your average 6-11 year old, so that was a lot of fun.

Have fun with it!

Damn @bko, why weren’t you my Cubmaster when I was a Cubscout!

I remember my first Pinewood Derby, my dad helped me make the car into sort of a VW Beetle shape and I hand painted it with flames and stuff. It was very detailed, but I lost ridiculous bad because it just wasn’t aerodynamic.

I spent the next year trying to find the optimal design, including building my own wind tunnel (smoke machines are awesome). Finally settled on a wedge shape with a hump at the top (to create downforce on the back end) that I sanded smoother than a baby’s bottom and applied several coats of ultra-gloss clear coat to. Beat the pants off everyone that year. :wink:

2 Likes

Truly, it is one of the most fun activities in scouting. I like the idea of running each car up to 8 times. It gives each boy the chance to see his car go down the track a bunch of times, which is the most important part of the competition: participation and seeing something you worked on work. The added benefit is that each car is affected by the best lanes and worst lanes. After pondering on it all day, I think that will definitely be the way to go. Now to build the timer…

@bko, any reason you went with a laser up top as opposed to the same thing at the bottom of the track?

Yeah, I was going to combine NTP with millis() to timestamp each event as it happens. Is there any documentation around event driven functions in the Spark similar to edge detection on the RaspberryPi that I can look at for some starters? I come from the PHP world, so microcontrollers are a new hobby for me.

The laser at the top starts the race–all the cars start at the same time. This laser goes across the track, perpendicular to the direction of travel and the first car to break the beam starts the timer.

At the bottom you need per lane sensors to stop the timer per car and while you could have a laser per lane, it was fine with LEDs for us.

@timb is probably a better person to talk about interrupts for edge detection on input pins.

I’ll write something up in the morning about using interrupts for edge detection. I’ve got some code going that works with the Sharp Digital IR Proximity Detection Sensors: http://www.pololu.com/product/1134

Which you might want to look into for lane timing. Personally, I’d cut a notch in each lane of the track at the finish line and install them underneath so they were pointing up and detected when the car ran over it or run a bar over the track and have them pointing down. They also have 5cm versions which could be installed on the sides of each lane as well. These sensors are great as they have integrated digital filter and they encode the IR signal to greatly reduce false positives. You get a TTL level pulse within a microsecond of the beam detecting an object.

1 Like

Also, I’d use micros() instead for better precision. I’ll get you demo code that shows how to do precise timing of a GPIO event with and without interrupts. The key is to use low level GPIO commands instead of digitalRead And to read all of the pins by reading the entire port register, so you don’t waste time looping through each one.

1 Like

I like the idea of running all cars on all lanes twice and averaging the results.

I’ve done some speed measurement with lasers and they work nicely, but I kind of think it would be better to use something that was modulated and filtered… but not reflected. Everyone’s paint job and car shape comes into play if you are reflecting the sensor. Simply breaking a beam should be the way to go… and it also should be known to everyone exactly how the track works before they create their design. Someone with a needle shaped front end is going to have problems.

Interrupting on edge detection and capturing the micros() or millis() counter would be best.

I don’t like the idea of starting all cars on one beam. Whichever car consistently breaks the beam first is going to have shorter times.

Also the exact distance from the top track beam and bottom track beam should be precisely the same on all tracks. You can still average them for good results, but if they are very very close to begin with, all the better.

I think one way to test each track for consistency and repeatability might be to make a test car that is much heavier than normal. That way little bumps and friction don’t affect it that much. As the car goes down it randomly may bump a wall slowing it down. The heavier it is the less those random things will attribute to the timing. This would just be to verify each track is consistently reporting times.

OK, you guys got me going again–I do love Pinewood Derby!

The laser goes across the track at the starting line and is a beam-break system. We used this one from Microwizard.

The photo transistor end is fixed rigidly but the laser end is held on by a magnet and you can slide it up/down/left/right to get the beam in the hole.

The slot in the track in front of each car has a dowel that is attached to the starting bar. All of the dowels rotate out of the way at the time. The first car to start down the track and break the laser beam starts the timer. You would think that first car to break the beam always wins but that is definitely not the case since there are two phases to race, the steep downhill accelerating phase and the flatter coasting phase. Many races are won in the flatter coasting phase.

As I said earlier, we initially had the starting system where rotating the starting bar hit a microswitch to start the race. This does not work as well for a variety of reasons.

The length of each lane is identical. Bumping the center guide strip will slow you down so having the wheels aligned perfectly with just a little toe-in for stability helps a lot. The finish line sensor works the way Tim described with a slot in the track except instead of proximity sensors, it was beam-break with LEDs above. The Sharp proximity sensors are nice and would work great.

+1 on using micros() and turning off interrupts.

Hey @sweenig Your Pack should have a rule book that says how the race is run; it should be available in advance. Not having this is a recipe for angry parents in my experience! The rule book should say how the race is timed so everyone knows what is going to happen.

I have run district wide races with the winners from many Packs and it is very competitive–amongst the parents that is! We also used fitting boxes made to the specs in the rule book so that the car had to fit completely in the box with nothing outside except in the vertical direction (+z). And of course accurate scales and a test weight. To avoid angry parents (“well it weighed in OK at our Pack!”) the test weight goes on the scale before every car that gets weighed to check the scale.

Running a Pack race is fun, but running a District wide race felt more like work.

Alright, so I’m leaning toward 1 LED/photoresistor pair (or possibly 4 pairs) up top and 4 LED/photoresistor pairs of at the bottom. The top could be triggered by the first car to pass the beam or could be triggered by the release handle (handle would probably be more fair since it’s a static piece and guaranteed to happen at the same time for every car). That takes care of the physical layout.

So, it sounds like the code would loop through using micros() and reading the entire port register. Mode A would be to wait for the top pin’s edge, which would grab a timestamp and enter Mode B. Mode B would be waiting for the edges of the four bottom pins’ edges, which would grab a timestamp and enter Mode C. Mode C would report the deltas and wait for a button push to enter Mode A (waiting for the race to start again). Sound reasonable?

@timb I’d love to see the demo code you mention. Sounds like it should be pretty easy to mold into what I need.

I still think interrupts would be faster.

Polling the port would also require you to mask off each bit and know when a bit changes. If a change is detected, you save the micros() counter associated to a Race Lane. While you are going through each bit, 1, 2, 3, 4, a previous lane may trigger but you don’t detect it because you were off looking at another bit. Because of this I don’t know how reading the entire port at once helps.

The interrupts would catch all edges, first thing and only thing you do in the interrupt service routine is save the micros() count into an appropriate global variable associated with the race lane. In your main loop you can take your time looping and when you detect all 4 race lanes have end times, you can enter your Mode C. You also can have a fail safe timeout running in your main loop that prevents a stuck car from messing up the reporting of the other lanes, say after 10 seconds.

http://docs.spark.io/#/firmware/interrupts-attachinterrupt

Pretty sure the last time I looked it seemed like we could attach at least 4 interrupts at once… but probably more. Does anybody know for sure how many? If we still can’t save the micros() count inside an ISR, we’ll need to figure that out.

Read code comments :wink:

#include "application.h"

void blink(void);
int ledPin = D7;
volatile int state = LOW;

void setup()
{
  // attach interrupts to any of these pins  
  // D0, D1, D2, D3, D4
  // A0, A1, A3, A4, A5, A6, A7
  pinMode(ledPin, OUTPUT);
  
  // works for any combination of the first 7 shown here, 
  // if you also add A3, D2 stopped working.  If you enable
  // them all at the same time only 5 or 6 of them work.
  pinMode(D0, INPUT_PULLUP);
  pinMode(D1, INPUT_PULLUP);
  pinMode(D2, INPUT_PULLUP);
  pinMode(D3, INPUT_PULLUP);
  pinMode(D4, INPUT_PULLUP);
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  //pinMode(A3, INPUT_PULLUP);
  //pinMode(A4, INPUT_PULLUP);
  //pinMode(A5, INPUT_PULLUP);
  //pinMode(A6, INPUT_PULLUP);
  //pinMode(A7, INPUT_PULLUP);
  
  attachInterrupt(D0, blink, CHANGE);
  attachInterrupt(D1, blink, CHANGE);
  attachInterrupt(D2, blink, CHANGE);
  attachInterrupt(D3, blink, CHANGE);
  attachInterrupt(D4, blink, CHANGE);
  attachInterrupt(A0, blink, CHANGE);
  attachInterrupt(A1, blink, CHANGE);
  //attachInterrupt(A3, blink, CHANGE);
  //attachInterrupt(A4, blink, CHANGE);
  //attachInterrupt(A5, blink, CHANGE);
  //attachInterrupt(A6, blink, CHANGE);
  //attachInterrupt(A7, blink, CHANGE);
}

void loop()
{
  digitalWrite(ledPin, state);
}

void blink()
{
  state = !state;
}

Ok here’s some code that kind of works… lol. I think there may be an issue with the noInterrupts() / interrupts() functions. Or the way I’m implementing things… The first results seem good, but then the more you run it the more it flakes a bit. Some of the start/end times get very short like some bouncing of the inputs is re-triggering. I’m not seeing how unless interrupts are queuing up and firing again as soon as interrupts() is run… Not sure, but feel free to play. I’ll keep looking at it:

#include "application.h"

void lane1(void);
void lane2(void);
void lane3(void);
void lane4(void);
int ledPin = D7;
volatile int state = LOW;
volatile uint32_t timeStart1;
volatile uint32_t timeStart2;
volatile uint32_t timeStart3;
volatile uint32_t timeStart4;
volatile uint32_t timeEnd1;
volatile uint32_t timeEnd2;
volatile uint32_t timeEnd3;
volatile uint32_t timeEnd4;

void setup()
{
  Serial.begin(115200);
  while(!Serial.available()) SPARK_WLAN_Loop();

  Serial.println("Waiting for all four inputs to CHANGE...\n");
  timeStart1 = micros(); // reset!
  timeStart2 = timeStart1;
  timeStart3 = timeStart1;
  timeStart4 = timeStart1;
  timeEnd1 = timeStart1;
  timeEnd2 = timeStart1;
  timeEnd3 = timeStart1;
  timeEnd4 = timeStart1;

  // D0, D1, D2, D3, D4
  // A0, A1, A3, A4, A5, A6, A7
  pinMode(ledPin, OUTPUT);
  
  pinMode(D0, INPUT_PULLUP);
  pinMode(D1, INPUT_PULLUP);
  pinMode(D2, INPUT_PULLUP);
  pinMode(D3, INPUT_PULLUP);
  //pinMode(D4, INPUT_PULLUP);
  //pinMode(A0, INPUT_PULLUP);
  //pinMode(A1, INPUT_PULLUP);
  
  //pinMode(A3, INPUT_PULLUP);
  //pinMode(A4, INPUT_PULLUP);
  //pinMode(A5, INPUT_PULLUP);
  //pinMode(A6, INPUT_PULLUP);
  //pinMode(A7, INPUT_PULLUP);
  
  attachInterrupt(D0, lane1, FALLING);
  attachInterrupt(D1, lane2, FALLING);
  attachInterrupt(D2, lane3, FALLING);
  attachInterrupt(D3, lane4, FALLING);
  //attachInterrupt(D4, blink, CHANGE);
  //attachInterrupt(A0, blink, CHANGE);
  //attachInterrupt(A1, blink, CHANGE);
  
  //attachInterrupt(A3, blink, CHANGE);
  //attachInterrupt(A4, blink, CHANGE);
  //attachInterrupt(A5, blink, CHANGE);
  //attachInterrupt(A6, blink, CHANGE);
  //attachInterrupt(A7, blink, CHANGE);
}

void loop()
{
  digitalWrite(ledPin, state);

  // still needs a timeout...
  if(timeEnd1 != timeStart1 && timeEnd2 != timeStart2 && timeEnd3 != timeStart3 && timeEnd4 != timeStart4) {
  	
  	Serial.println("======= Lane Times in seconds =======");
  	Serial.print("Lane 1: "); 
  	Serial.print(timeEnd1); Serial.print(" "); 
  	Serial.print(timeStart1); Serial.print(" "); Serial.println((double)(timeEnd1 - timeStart1)/1000000.0,6);

  	Serial.print("Lane 2: "); 
  	Serial.print(timeEnd2); Serial.print(" "); 
  	Serial.print(timeStart2); Serial.print(" "); Serial.println((double)(timeEnd2 - timeStart2)/1000000.0,6);

  	Serial.print("Lane 3: "); 
  	Serial.print(timeEnd3); Serial.print(" "); 
  	Serial.print(timeStart3); Serial.print(" "); Serial.println((double)(timeEnd3 - timeStart3)/1000000.0,6);

  	Serial.print("Lane 4: "); 
  	Serial.print(timeEnd4); Serial.print(" "); 
  	Serial.print(timeStart4); Serial.print(" "); Serial.println((double)(timeEnd4 - timeStart4)/1000000.0,6);
  	
  	// prevent false triggering from last input bouncing while we reset
  	noInterrupts();
  	delay(5000); // this is much longer than it needs to be... 
  	timeStart1 = micros(); // reset!
  	timeStart2 = timeStart1;
  	timeStart3 = timeStart1;
  	timeStart4 = timeStart1;
  	timeEnd1 = timeStart1;
  	timeEnd2 = timeStart1;
  	timeEnd3 = timeStart1;
  	timeEnd4 = timeStart1;
  	interrupts(); // go!
  	Serial.println("\n\nWaiting for all four inputs to CHANGE...\n");
  	
  }
}

void lane1()
{
  // if not captured already, save time.
  // this inherently debounces the input.
  if(timeEnd1 == timeStart1) timeEnd1 = micros();
  state = !state;
}

void lane2()
{
  // if not captured already, save time.
  // this inherently debounces the input.
  if(timeEnd2 == timeStart2) timeEnd2 = micros();
  state = !state;
}

void lane3()
{
  // if not captured already, save time.
  // this inherently debounces the input.
  if(timeEnd3 == timeStart3) timeEnd3 = micros();
  state = !state;
}

void lane4()
{
  // if not captured already, save time.
  // this inherently debounces the input.
  if(timeEnd4 == timeStart4) timeEnd4 = micros();
  state = !state;
}

Just take a wire connected to GND and poke the D0, D1, D2, D3 holes on the breadboard to act as the trigger.

Ok! 1:41am… Pinewood Derby code keeping me up!

These interrupts and timing really don’t work like they should. I had to get in there are TAKE OVER!!! When I get some sleep I’ll explain WHY I had to do what I did. EDIT: I updated the header with some info… please read. Also I’ll add that at one point I tried detaching the interrupts as soon as they fired the first time, and after re-attaching them they would randomly have their Pending Registers already set and they would fire off immediately. I still would like to scrub the spark_wiring_interrupts.cpp to see if it there’s something that can fix this behavior.

This works beautifully now. You ground D2 to start the race timer. Then you ground A6, A7, D3 and D4 to capture their times and stop the race and display times. If you short A6, A7, D3 & D4 together and ground them all at the same time, you’ll notice they fire off about ~180ns apart! Definitely not going to see that precision with polling. Also, based on the winning order, you can easily adjust the times to gain even more precision!

Sample Output (inputs grounded together):

GND D2 to Start Race, GND A6, A7, D3 & D4 to End Race
=====================================================

Waiting for A6, A7, D3 & D4 to go LOW...

======= Lane Times in seconds =======
Lane 1: 2.89765485
Lane 2: 2.89765671
Lane 3: 2.89766039
Lane 4: 2.89765853

GND D2 to Start Race, GND A6, A7, D3 & D4 to End Race
=====================================================

Waiting for A6, A7, D3 & D4 to go LOW...

======= Lane Times in seconds =======
Lane 1: 1.83023608
Lane 2: 1.83023425
Lane 3: 1.83023976
Lane 4: 1.83023790

GND D2 to Start Race, GND A6, A7, D3 & D4 to End Race
=====================================================

Waiting for A6, A7, D3 & D4 to go LOW...

======= Lane Times in seconds =======
Lane 1: 3.50006700
Lane 2: 3.50006886
Lane 3: 3.50007254
Lane 4: 3.50007068

Interrupt Driven Pinewood Derby Code:

// Interrupt Driven Pinewood Derby Timer
// BDub @ Technobly.com 3/19/2014
//
// All inputs are pulled high with internal pullups.
// GND D2 to reset and run the timer for all lanes.
// GND A6, A7, D3 and D4 to stop the timer for each
// lane and display the results.
//
// D0-D3 were initially chosen, but because 
// individual control of dis/en'abling their
// interrupt handlers was not available, A6, A7
// D3 and D4 were chosen instead.  D0, D1, D2
// A0, A1, A3, A4 are all tied to one interrupt
// handler, so for the one remaining input any 
// of these will do.  D2 was chosen to allow
// for the most flexibility of the remaining inputs.
// 
// EXTI_ClearITPendingBit() is necessary to clear
// the interrupt Pending register, or interrupts 
// will fire immediately after enabling the 
// interrupt handlers again.
//
// DWT->CYCCNT was used for the timer instead of 
// micros() which wraps this hardware counter and
// returns the number of microseconds associated
// with it; because it's not clean to handle the 
// case where micros() wraps at 59.652323 seconds.
// This give us 72x more resolution on the timing
// anyway and it's super easy to deal with wrapping.
// It just works out through subtraction of unsigned
// 32-bit variables; as long as you don't time 
// something longer than 59.652323 seconds.
//
//====================================================

#include "application.h"

void startRace(void);
void lane1(void);
void lane2(void);
void lane3(void);
void lane4(void);
int ledPin = D7;
bool showStartMsg = false;
bool raceEnded = true; // start off assuming the race has not started.
volatile uint32_t timeStart1;
volatile uint32_t timeStart2;
volatile uint32_t timeStart3;
volatile uint32_t timeStart4;
volatile uint32_t timeEnd1;
volatile uint32_t timeEnd2;
volatile uint32_t timeEnd3;
volatile uint32_t timeEnd4;
uint32_t startTime;
const uint32_t DISQUALIFIED_TIME = 10 * 1000;  // in milliseconds (10 seconds)

void setup()
{
  Serial.begin(115200);
  while(!Serial.available()) SPARK_WLAN_Loop(); // Open terminal and press ENTER

  Serial.println("GND D2 to Start Race, GND A6, A7, D3 & D4 to End Race");
  Serial.println("=====================================================\n");

  pinMode(ledPin, OUTPUT); // debug LED
  
  pinMode(D2, INPUT_PULLUP); // startRace
  pinMode(A6, INPUT_PULLUP); // lane1
  pinMode(A7, INPUT_PULLUP); // lane2
  pinMode(D3, INPUT_PULLUP); // lane3
  pinMode(D4, INPUT_PULLUP); // lane4
  
  attachInterrupt(D2, startRace, FALLING); // startRace
  attachInterrupt(A6, lane1, FALLING); // lane1
  attachInterrupt(A7, lane2, FALLING); // lane2
  attachInterrupt(D3, lane3, FALLING); // lane3
  attachInterrupt(D4, lane4, FALLING); // lane4
  
  // Disable lanes by default, keep startRace enabled
  NVIC_DisableIRQ(EXTI0_IRQn); // A6 "Lane 1"
  NVIC_DisableIRQ(EXTI1_IRQn); // A7 "Lane 2"
  NVIC_DisableIRQ(EXTI4_IRQn); // D3 "Lane 3"
  NVIC_DisableIRQ(EXTI3_IRQn); // D4 "Lane 4"
}

void loop()
{
  if(!raceEnded) {
    if((timeEnd1 != timeStart1 && 
       timeEnd2 != timeStart2 && 
       timeEnd3 != timeStart3 && 
       timeEnd4 != timeStart4) || 
       (millis() - startTime) > DISQUALIFIED_TIME) {

      double tempTime;
      
      Serial.println("======= Lane Times in seconds =======");
      Serial.print("Lane 1 (A6): "); 
      //Serial.print(timeEnd1); Serial.print(" "); 
      //Serial.print(timeStart1); Serial.print(" "); 
      tempTime = (double)(timeEnd1 - timeStart1)/72000000.0;
      if(tempTime != 0.0) Serial.println(tempTime,8);
      else Serial.println("DISQUALIFIED!");
      delay(50);

      Serial.print("Lane 2 (A7): "); 
      //Serial.print(timeEnd2); Serial.print(" "); 
      //Serial.print(timeStart2); Serial.print(" "); 
      tempTime = (double)(timeEnd2 - timeStart2)/72000000.0;
      if(tempTime != 0.0) Serial.println(tempTime,8);
      else Serial.println("DISQUALIFIED!");
      delay(50);

      Serial.print("Lane 3 (D3): "); 
      //Serial.print(timeEnd3); Serial.print(" "); 
      //Serial.print(timeStart3); Serial.print(" "); 
      tempTime = (double)(timeEnd3 - timeStart3)/72000000.0;
      if(tempTime != 0.0) Serial.println(tempTime,8);
      else Serial.println("DISQUALIFIED!");
      delay(50);

      Serial.print("Lane 4 (D4): "); 
      //Serial.print(timeEnd4); Serial.print(" "); 
      //Serial.print(timeStart4); Serial.print(" "); 
      tempTime = (double)(timeEnd4 - timeStart4)/72000000.0;
      if(tempTime != 0.0) Serial.println(tempTime,8);
      else Serial.println("DISQUALIFIED!");
      delay(50);
      
      raceEnded = true; // prevents results from being displayed over and over  
      EXTI_ClearITPendingBit(EXTI_Line5); // D2 "startRace"
      NVIC_EnableIRQ(EXTI9_5_IRQn); // D2
      Serial.println("\nGND D2 to Start Race, GND A6, A7, D3 & D4 to End Race");
      Serial.println("=====================================================\n");
    }
  }

  if(showStartMsg) {
    Serial.println("Waiting for A6, A7, D3 & D4 to go LOW...\n");
    showStartMsg = false;
    raceEnded = false;
    startTime = millis(); // Capture the rough start time, for disqualification timer
  }
}

void lane1()
{
  NVIC_DisableIRQ(EXTI0_IRQn); // A6
  timeEnd1 = DWT->CYCCNT; //micros();
  
  //digitalWrite(ledPin, HIGH);
  //delayMicroseconds(200000);
  //digitalWrite(ledPin, LOW);
}

void lane2()
{
  NVIC_DisableIRQ(EXTI1_IRQn); // A7
  timeEnd2 = DWT->CYCCNT; //micros();
  
  //digitalWrite(ledPin, HIGH);
  //delayMicroseconds(200000);
  //digitalWrite(ledPin, LOW);
}

void lane3()
{
  NVIC_DisableIRQ(EXTI4_IRQn); // D3
  timeEnd3 = DWT->CYCCNT; //micros();
  
  //digitalWrite(ledPin, HIGH);
  //delayMicroseconds(200000);
  //digitalWrite(ledPin, LOW);
}

void lane4()
{
  NVIC_DisableIRQ(EXTI3_IRQn); // D4
  timeEnd4 = DWT->CYCCNT; //micros();
  
  //digitalWrite(ledPin, HIGH);
  //delayMicroseconds(200000);
  //digitalWrite(ledPin, LOW);
}

void startRace()
{
  NVIC_DisableIRQ(EXTI9_5_IRQn); // D2
  timeStart1 = DWT->CYCCNT; // reset!
  timeStart2 = timeStart1;
  timeStart3 = timeStart1;
  timeStart4 = timeStart1;
  timeEnd1 = timeStart1;
  timeEnd2 = timeStart1;
  timeEnd3 = timeStart1;
  timeEnd4 = timeStart1;
  EXTI_ClearITPendingBit(EXTI_Line0); // A6 "Lane 1"
  EXTI_ClearITPendingBit(EXTI_Line1); // A7 "Lane 2"
  EXTI_ClearITPendingBit(EXTI_Line4); // D3 "Lane 3"
  EXTI_ClearITPendingBit(EXTI_Line3); // D4 "Lane 4"
  NVIC_EnableIRQ(EXTI0_IRQn); // A6 "Lane 1"
  NVIC_EnableIRQ(EXTI1_IRQn); // A7 "Lane 2"
  NVIC_EnableIRQ(EXTI4_IRQn); // D3 "Lane 3"
  NVIC_EnableIRQ(EXTI3_IRQn); // D4 "Lane 4"
  showStartMsg = true;
  
  //digitalWrite(ledPin, HIGH);
  //delayMicroseconds(200000);
  //digitalWrite(ledPin, LOW);
}
1 Like

@BDub, you are a rock star! I modified the messages a bit but in my testing it works just the way you describe. I can only think of one tweak: what if there are only 3 racers? I can see it would be easy to manually trigger the empty lane, but there should be a cool way to do it.

Here are my thoughts: turn the raceEnded variable into 4 variables, one for each lane. Turn the if(!raceEnded) branch into four branches so that the times post as soon as each car finishes. This provides 2 advantages: 1) if there are less than 4 racers, we don’t have to manually trigger anything just to get the times to display and 2) the times show up in the order that they finish.

Thoughts?

My next question is going to be how to get the spark serial output to be read into my raspberry pi, so i can post the results to apache instead of displaying them in the terminal window. Can anybody point me to the documentation on installing the spark serial driver in linux?

That sounds good, AND add a timeout so that the start of the race can be re-triggered if less than 4 cars finish.