Well, I finally finished it! The pinewood derby timer is done. Check it out!
First, I had to finish the wiring for the lights and the sensors, and then I had to finish the arch.
Then, I had to program it. Fortunately, https://www.dfgtec.com/ has a whole section on programming the board, and what settings you should change or adjust for your needs. Super helpful site!
Here is my final code:
/*================================================================================*
Pinewood Derby Timer Version 3.00 – 31 Oct 2016
www.dfgtec.com/pdtFlexible and affordable Pinewood Derby timer that interfaces with the
following software:
– PD Test/Tune/Track Utility
– Grand Prix Race Manager softwareRefer to the website for setup and usage instructions.
Copyright (C) 2011-2018 David GadberryThis work is licensed under the Creative Commons Attribution-NonCommercial-
ShareAlike 3.0 Unported License. To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
Creative Commons, 444 Castro Street, Suite 900, Mountain View, California,
94041, USA.
*================================================================================*//*—————————————–*
– TIMER CONFIGURATION –
*—————————————–*/
#define NUM_LANES 4 // number of lanes#define LED_DISPLAY 1 // Enable lane place/time displays
//#define DUAL_DISP 1 // dual displays (front/back) per lane (4 lanes max)
//#define LARGE_DISP 1 // utilize large Adafruit displays (see website)
#define SHOW_PLACE 1 // Show place mode
#define PLACE_DELAY 4 // Delay (secs) when displaying place/time
#define MIN_BRIGHT 4 // minimum display brightness (0-15)
#define MAX_BRIGHT 10 // maximum display brightness (0-15)#define GATE_RESET 1 // Enable closing start gate to reset timer
/*—————————————–*
– END –
*—————————————–*/
#ifdef LED_DISPLAY // LED control libraries
#include “Wire.h”
#include “Adafruit_LEDBackpack.h”
#include “Adafruit_GFX.h”
#endif/*—————————————–*
– static definitions –
*—————————————–*/
#define PDT_VERSION “3.00” // software version
#define MAX_LANE 6 // maximum number of lanes (Uno)
#define MAX_DISP 8 // maximum number of displays (Adafruit)#define mREADY 0 // program modes
#define mRACING 1
#define mFINISH 2
#define mTEST 3#define START_TRIP LOW // start switch trip condition
#define NULL_TIME 99.999 // null (non-finish) time
#define NUM_DIGIT 4 // timer resolution (# of decimals)
#define DISP_DIGIT 4 // total number of display digits#define PWM_LED_ON 220
#define PWM_LED_OFF 255
#define char2int(c) (c – ‘0’)//
// serial messages <- to timer
// -> from timer
//
#define SMSG_ACKNW ‘.’ // -> acknowledge message#define SMSG_POWER ‘P’ // -> start-up (power on or hard reset)
#define SMSG_CGATE ‘G’ // <- check gate
#define SMSG_GOPEN ‘O’ // -> gate open#define SMSG_RESET ‘R’ // <- reset
#define SMSG_READY ‘K’ // -> ready#define SMSG_SOLEN ‘S’ // <- start solenoid
#define SMSG_START ‘B’ // -> race started
#define SMSG_FORCE ‘F’ // <- force end
#define SMSG_RSEND ‘Q’ // <- resend race data#define SMSG_LMASK ‘M’ // <- mask lane
#define SMSG_UMASK ‘U’ // <- unmask all lanes#define SMSG_GVERS ‘V’ // <- request timer version
#define SMSG_DEBUG ‘D’ // <- toggle debug on/off
#define SMSG_GNUML ‘N’ // <- request number of lanes/*—————————————–*
– pin assignments –
*—————————————–*/
byte BRIGHT_LEV = A0; // brightness level
byte RESET_SWITCH = 8; // reset switch
byte STATUS_LED_R = 9; // status LED (red)
byte STATUS_LED_B = 11; // status LED (blue)
byte STATUS_LED_G = 10; // status LED (green)
byte START_GATE = 12; // start gate switch
byte START_SOL = 13; // start solenoid// Display # 1 2 3 4 5 6 7 8
int DISP_ADD [MAX_DISP] = {0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77}; // display I2C addresses// Lane # 1 2 3 4 5 6
byte LANE_DET [MAX_LANE] = { 2, 3, 4, 5, 6, 7}; // finish detection pins/*—————————————–*
– global variables –
*—————————————–*/
boolean fDebug = false; // debug flag
boolean ready_first; // first pass in ready state flag
boolean finish_first; // first pass in finish state flagunsigned long start_time; // race start time (microseconds)
unsigned long lane_time [MAX_LANE]; // lane finish time (microseconds)
int lane_place [MAX_LANE]; // lane finish place
boolean lane_mask [MAX_LANE]; // lane mask statusint serial_data; // serial data
byte mode; // current program modefloat display_level = -1.0; // display brightness level
#ifdef LARGE_DISP
unsigned char msgGateC[] = {0x6D, 0x41, 0x00, 0x0F, 0x07}; // S=CL
unsigned char msgGateO[] = {0x6D, 0x41, 0x00, 0x3F, 0x5E}; // S=OP
unsigned char msgLight[] = {0x41, 0x41, 0x00, 0x00, 0x07}; // == L
unsigned char msgDark [] = {0x41, 0x41, 0x00, 0x00, 0x73}; // == d
#else
unsigned char msgGateC[] = {0x6D, 0x48, 0x00, 0x39, 0x38}; // S=CL
unsigned char msgGateO[] = {0x6D, 0x48, 0x00, 0x3F, 0x73}; // S=OP
unsigned char msgLight[] = {0x48, 0x48, 0x00, 0x00, 0x38}; // == L
unsigned char msgDark [] = {0x48, 0x48, 0x00, 0x00, 0x5e}; // == d
#endif
unsigned char msgDashT[] = {0x40, 0x40, 0x00, 0x40, 0x40}; // —-
unsigned char msgDashL[] = {0x00, 0x00, 0x00, 0x40, 0x00}; // –
unsigned char msgBlank[] = {0x00, 0x00, 0x00, 0x00, 0x00}; // (blank)#ifdef LED_DISPLAY // LED display control
Adafruit_7segment disp_mat[MAX_DISP];
#endifvoid initialize(boolean powerup=false);
void dbg(int, const char * msg, int val=-999);
void smsg(char msg, boolean crlf=true);
void smsg_str(const char * msg, boolean crlf=true);/*================================================================================*
SETUP TIMER
*================================================================================*/
void setup()
{
/*—————————————–*
– hardware setup –
*—————————————–*/
pinMode(STATUS_LED_R, OUTPUT);
pinMode(STATUS_LED_B, OUTPUT);
pinMode(STATUS_LED_G, OUTPUT);
pinMode(START_SOL, OUTPUT);
pinMode(RESET_SWITCH, INPUT);
pinMode(START_GATE, INPUT);
pinMode(BRIGHT_LEV, INPUT);digitalWrite(RESET_SWITCH, HIGH); // enable pull-up resistor
digitalWrite(START_GATE, HIGH); // enable pull-up resistordigitalWrite(START_SOL, LOW);
#ifdef LED_DISPLAY
for (int n=0; n<MAX_DISP; n++)
{
disp_mat[n] = Adafruit_7segment();
disp_mat[n].begin(DISP_ADD[n]);
disp_mat[n].clear();
disp_mat[n].drawColon(false);
disp_mat[n].writeDisplay();
}
#endiffor (int n=0; n<MAX_LANE; n++)
{
pinMode(LANE_DET[n], INPUT);digitalWrite(LANE_DET[n], HIGH); // enable pull-up resistor
}
set_display_brightness();/*—————————————–*
– software setup –
*—————————————–*/
Serial.begin(9600);
smsg(SMSG_POWER);/*—————————————–*
– check for test mode –
*—————————————–*/
if (digitalRead(RESET_SWITCH) == LOW)
{
mode = mTEST;
test_pdt_hw();
}/*—————————————–*
– initialize timer –
*—————————————–*/
initialize(true);
unmask_all_lanes();
}
/*================================================================================*
MAIN LOOP
*================================================================================*/
void loop()
{
process_general_msgs();switch (mode)
{
case mREADY:
timer_ready_state();
break;
case mRACING:
timer_racing_state();
break;
case mFINISH:
timer_finished_state();
break;
}
}
/*================================================================================*
TIMER READY STATE
*================================================================================*/
void timer_ready_state()
{
if (ready_first)
{
set_status_led();
clear_displays();ready_first = false;
}if (serial_data == int(SMSG_SOLEN)) // activate start solenoid
{
digitalWrite(START_SOL, HIGH);
smsg(SMSG_ACKNW);
}if (digitalRead(START_GATE) == START_TRIP) // timer start
{
start_time = micros();digitalWrite(START_SOL, LOW);
smsg(SMSG_START);
delay(100);mode = mRACING;
}return;
}/*================================================================================*
TIMER RACING STATE
*================================================================================*/
void timer_racing_state()
{
int lanes_left, finish_order, lane_status[NUM_LANES];
unsigned long current_time, last_finish_time;
set_status_led();
clear_displays();finish_order = 0;
last_finish_time = 0;lanes_left = NUM_LANES;
for (int n=0; n<NUM_LANES; n++)
{
if (lane_mask[n]) lanes_left–;
}while (lanes_left)
{
current_time = micros();for (int n=0; n<NUM_LANES; n++) lane_status[n] = bitRead(PIND, LANE_DET[n]); // read status of all lanes
for (int n=0; n<NUM_LANES; n++)
{
if (lane_time[n] == 0 && lane_status[n] == HIGH && !lane_mask[n]) // car has crossed finish line
{
lanes_left–;lane_time[n] = current_time – start_time;
if (lane_time[n] > last_finish_time)
{
finish_order++;
last_finish_time = lane_time[n];
}
lane_place[n] = finish_order;update_display(n, lane_place[n], lane_time[n], SHOW_PLACE);
}
}serial_data = get_serial_data();
if (serial_data == int(SMSG_FORCE) || serial_data == int(SMSG_RESET) || digitalRead(RESET_SWITCH) == LOW) // force race to end
{
lanes_left = 0;
smsg(SMSG_ACKNW);
}
}send_race_results();
mode = mFINISH;
return;
}
/*================================================================================*
TIMER FINISHED STATE
*================================================================================*/
void timer_finished_state()
{
if (finish_first)
{
set_status_led();
finish_first = false;
}if (GATE_RESET && digitalRead(START_GATE) != START_TRIP) // gate closed
{
delay(500); // ignore any switch bounceif (digitalRead(START_GATE) != START_TRIP) // gate still closed
{
initialize(); // reset timer
}
}if (serial_data == int(SMSG_RSEND)) // resend race data
{
smsg(SMSG_ACKNW);
send_race_results();
}set_display_brightness();
display_race_results();return;
}
/*================================================================================*
PROCESS GENERAL SERIAL MESSAGES
*================================================================================*/
void process_general_msgs()
{
int lane;
char tmps[50];
serial_data = get_serial_data();if (serial_data == int(SMSG_GVERS)) // get software version
{
sprintf(tmps, “vert=%s”, PDT_VERSION);
smsg_str(tmps);
}else if (serial_data == int(SMSG_GNUML)) // get number of lanes
{
sprintf(tmps, “numl=%d”, NUM_LANES);
smsg_str(tmps);
}else if (serial_data == int(SMSG_DEBUG)) // toggle debug
{
fDebug = !fDebug;
dbg(true, “toggle debug = “, fDebug);
}else if (serial_data == int(SMSG_CGATE)) // check start gate
{
if (digitalRead(START_GATE) == START_TRIP) // gate open
{
smsg(SMSG_GOPEN);
}
else
{
smsg(SMSG_ACKNW);
}
}else if (serial_data == int(SMSG_RESET) || digitalRead(RESET_SWITCH) == LOW) // timer reset
{
if (digitalRead(START_GATE) != START_TRIP) // only reset if gate closed
{
initialize();
}
else
{
smsg(SMSG_GOPEN);
}
}else if (serial_data == int(SMSG_LMASK)) // lane mask
{
delay(100);
serial_data = get_serial_data();lane = serial_data – 48;
if (lane >= 1 && lane <= NUM_LANES)
{
lane_mask[lane-1] = true;dbg(fDebug, “set mask on lane = “, lane);
}
smsg(SMSG_ACKNW);
}else if (serial_data == int(SMSG_UMASK)) // unmask all lanes
{
unmask_all_lanes();
smsg(SMSG_ACKNW);
}return;
}
/*================================================================================*
TEST PDT FINISH DETECTION HARDWARE
*================================================================================*/
void test_pdt_hw()
{
int lane_status[NUM_LANES];
char ctmp[10];
smsg_str(“TEST MODE”);
set_status_led();
delay(2000);/*—————————————–*
show status of lane detectors
*—————————————–*/
while(true)
{
for (int n=0; n<NUM_LANES; n++)
{
lane_status[n] = bitRead(PIND, LANE_DET[n]); // read status of all lanesif (lane_status[n] == HIGH)
{
update_display(n, msgDark); smsg_str(“Dark”); smsg_str(n);
}
else
{
update_display(n, msgLight); smsg_str(“Light”); smsg_str(n);
}
}if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
delay(1000);
break;
}
delay(100);
}/*—————————————–*
show status of start gate switch
*—————————————–*/
while(true)
{
if (digitalRead(START_GATE) == START_TRIP)
{
update_display(0, msgGateO);
}
else
{
update_display(0, msgGateC);
}if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
delay(1000);
break;
}
delay(100);
}/*—————————————–*
show pattern for brightness adjustment
*—————————————–*/
while(true)
{
#ifdef LED_DISPLAY
set_display_brightness();for (int n=0; n<NUM_LANES; n++)
{
sprintf(ctmp,”%04d”, (int)display_level);disp_mat[n].clear();
disp_mat[n].writeDigitNum(0, char2int(ctmp[0]), false);
disp_mat[n].writeDigitNum(1, char2int(ctmp[1]), false);
disp_mat[n].writeDigitNum(3, char2int(ctmp[2]), false);
disp_mat[n].writeDigitNum(4, char2int(ctmp[3]), false);disp_mat[n].drawColon(false);
disp_mat[n].writeDisplay();#ifdef DUAL_DISP
disp_mat[n+4].clear();disp_mat[n+4].writeDigitNum(0, char2int(ctmp[0]), false);
disp_mat[n+4].writeDigitNum(1, char2int(ctmp[1]), false);
disp_mat[n+4].writeDigitNum(3, char2int(ctmp[2]), false);
disp_mat[n+4].writeDigitNum(4, char2int(ctmp[3]), false);disp_mat[n+4].drawColon(false);
disp_mat[n+4].writeDisplay();
#endif
}
#endifif (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
break;
}
delay(1000);
}
}
/*================================================================================*
SEND RACE RESULTS TO COMPUTER
*================================================================================*/
void send_race_results()
{
float lane_time_sec;
for (int n=0; n<NUM_LANES; n++) // send times to computer
{
lane_time_sec = (float)(lane_time[n] / 1000000.0); // elapsed time (seconds)if (lane_time_sec == 0) // did not finish
{
lane_time_sec = NULL_TIME;
}Serial.print(n+1);
Serial.print(” – “);
Serial.println(lane_time_sec, NUM_DIGIT); // numbers are rounded to NUM_DIGIT
// digits by println function
}return;
}
/*================================================================================*
RACE FINISHED – DISPLAY PLACE / TIME FOR ALL LANES
*================================================================================*/
void display_race_results()
{
unsigned long now;
static boolean display_mode;
static unsigned long last_display_update = 0;
if (!SHOW_PLACE) return;now = millis();
if (last_display_update == 0) // first cycle
{
last_display_update = now;
display_mode = false;
}if ((now – last_display_update) > (unsigned long)(PLACE_DELAY * 1000))
{
dbg(fDebug, “display_race_results”);for (int n=0; n<NUM_LANES; n++)
{
update_display(n, lane_place[n], lane_time[n], display_mode);
}display_mode = !display_mode;
last_display_update = now;
}return;
}
/*================================================================================*
SEND MESSAGE TO DISPLAY
*================================================================================*/
void update_display(int lane, unsigned char msg[])
{#ifdef LED_DISPLAY
disp_mat[lane].clear();
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
#endiffor (int d = 0; d<=4; d++)
{
disp_mat[lane].writeDigitRaw(d, msg[d]);
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitRaw(d, msg[d]);
#endif
}disp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].writeDisplay();
#endif
#endifreturn;
}
/*================================================================================*
UPDATE LANE PLACE/TIME DISPLAY
*================================================================================*/
void update_display(int lane, int display_place, unsigned long display_time, int display_mode)
{
int c;
char ctime[10], cplace[4];
double display_time_sec;
boolean showdot;// dbg(fDebug, “led: lane = “, lane);
// dbg(fDebug, “led: plce = “, display_place);
// dbg(fDebug, “led: time = “, display_time);#ifdef LED_DISPLAY
if (display_mode)
{
if (display_place > 0)
{
disp_mat[lane].clear();
disp_mat[lane].drawColon(false);sprintf(cplace,”%1d”, display_place);
disp_mat[lane].writeDigitNum(3, char2int(cplace[0]), false);disp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
disp_mat[lane+4].drawColon(false);
disp_mat[lane+4].writeDigitNum(3, char2int(cplace[0]), false);
disp_mat[lane+4].writeDisplay();
#endif
}
else // did not finish
{
update_display(lane, msgDashL);
}
}
else
{
if (display_time > 0)
{
disp_mat[lane].clear();
disp_mat[lane].drawColon(false);
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
disp_mat[lane+4].drawColon(false);
#endifdisplay_time_sec = (double)(display_time / (double)1000000.0); // elapsed time (seconds)
dtostrf(display_time_sec, (DISP_DIGIT+1), DISP_DIGIT, ctime); // convert to string// Serial.print(“ctime = [“); Serial.print(ctime); Serial.println(“]”);
c = 0;
for (int d = 0; d<DISP_DIGIT; d++)
{
#ifdef LARGE_DISP
showdot = false;
#else
showdot = (ctime[c + 1] == ‘.’);
#endif
disp_mat[lane].writeDigitNum(d + int(d / 2), char2int(ctime[c]), showdot); // time
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitNum(d + int(d / 2), char2int(ctime[c]), showdot); // time
#endifc++; if (ctime[c] == ‘.’) c++;
}#ifdef LARGE_DISP
disp_mat[lane].writeDigitRaw(2, 16);
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitRaw(2, 16);
#endif
#endifdisp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].writeDisplay();
#endif
}
else // did not finish
{
update_display(lane, msgDashT);
}
}
#endifreturn;
}
/*================================================================================*
CLEAR LANE PLACE/TIME DISPLAYS
*================================================================================*/
void clear_displays()
{
dbg(fDebug, “led: CLEAR”);for (int n=0; n<NUM_LANES; n++)
{
if (mode == mRACING || mode == mTEST)
{
update_display(n, msgBlank); // racing
}
else
{
update_display(n, msgDashT); // ready
}
}return;
}
/*================================================================================*
SET LANE DISPLAY BRIGHTNESS
*================================================================================*/
void set_display_brightness()
{
float new_level;#ifdef LED_DISPLAY
new_level = long(1023 – analogRead(BRIGHT_LEV)) / 1023.0F * 15.0F;
new_level = min(new_level, (float)MAX_BRIGHT);
new_level = max(new_level, (float)MIN_BRIGHT);if (fabs(new_level – display_level) > 0.3F) // deadband to prevent flickering
{ // between levels
dbg(fDebug, “led: BRIGHT”);display_level = new_level;
for (int n=0; n<NUM_LANES; n++)
{
disp_mat[n].setBrightness((int)display_level);
#ifdef DUAL_DISP
disp_mat[n+4].setBrightness((int)display_level);
#endif
}
}
#endifreturn;
}
/*================================================================================*
SET TIMER STATUS LED
*================================================================================*/
void set_status_led()
{
int r_lev, b_lev, g_lev;dbg(fDebug, “status led = “, mode);
r_lev = PWM_LED_OFF;
b_lev = PWM_LED_OFF;
g_lev = PWM_LED_OFF;if (mode == mREADY) // blue
{
b_lev = PWM_LED_ON;
}
else if (mode == mRACING) // green
{
g_lev = PWM_LED_ON;
}
else if (mode == mFINISH) // red
{
r_lev = PWM_LED_ON;
}
else if (mode == mTEST) // yellow
{
r_lev = PWM_LED_ON;
g_lev = PWM_LED_ON;
}analogWrite(STATUS_LED_R, r_lev);
analogWrite(STATUS_LED_B, b_lev);
analogWrite(STATUS_LED_G, g_lev);return;
}
/*================================================================================*
READ SERIAL DATA FROM COMPUTER
*================================================================================*/
int get_serial_data()
{
int data = 0;if (Serial.available() > 0)
{
data = Serial.read();
dbg(fDebug, “ser rec = “, data);
}return data;
}
/*================================================================================*
INITIALIZE TIMER
*================================================================================*/
void initialize(boolean powerup)
{
for (int n=0; n<NUM_LANES; n++)
{
lane_time[n] = 0;
lane_place[n] = 0;
}start_time = 0;
set_status_led();
digitalWrite(START_SOL, LOW);// if power up and gate is open -> goto FINISH state
if (powerup && digitalRead(START_GATE) == START_TRIP)
{
mode = mFINISH;
}
else
{
mode = mREADY;smsg(SMSG_READY);
delay(100);
}
Serial.flush();ready_first = true;
finish_first = true;return;
}
/*================================================================================*
UNMASK ALL LANES
*================================================================================*/
void unmask_all_lanes()
{
dbg(fDebug, “unmask all lanes”);for (int n=0; n<NUM_LANES; n++)
{
lane_mask[n] = false;
}return;
}
/*================================================================================*
SEND DEBUG TO COMPUTER
*================================================================================*/
void dbg(int flag, const char * msg, int val)
{
char tmps[50];
if (!flag) return;smsg_str(“dbg: “, false);
smsg_str(msg, false);if (val != -999)
{
sprintf(tmps, “%d”, val);
smsg_str(tmps);
}
else
{
smsg_str(“”);
}return;
}
/*================================================================================*
SEND SERIAL MESSAGE (CHAR) TO COMPUTER
*================================================================================*/
void smsg(char msg, boolean crlf)
{
if (crlf)
{
Serial.println(msg);
}
else
{
Serial.print(msg);
}return;
}
/*================================================================================*
SEND SERIAL MESSAGE (STRING) TO COMPUTER
*================================================================================*/
void smsg_str(const char * msg, boolean crlf)
{
if (crlf)
{
Serial.println(msg);
}
else
{
Serial.print(msg);
}return;
}
A little long for posting here, but I wanted to post any changes I had made in case it helps someone else! Now the timers work, alternating between places and time per track/car! This was a really fun project, and I highly recommend it to anyone looking for a fun electronics project to work on.
I might still dress it up a bit, but it is fully functional as is, now.
Linux – keep it simple.