/*================================================================================* Pinewood Derby Timer Version 3.10 - 12 Dec 2020 www.dfgtec.com/pdt Flexible and affordable Pinewood Derby timer that interfaces with the following software: - PD Test/Tune/Track Utility - Grand Prix Race Manager software Refer to the website for setup and usage instructions. Copyright (C) 2011-2020 David Gadberry This 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 1 // number of lanes #define GATE_RESET 0 // Enable closing start gate to reset timer //#define LED_DISPLAY 1 // Enable lane place/time displays //#define DUAL_DISP 1 // dual displays per lane (4 lanes max) //#define DUAL_MODE 1 // dual display mode //#define LARGE_DISP 1 // utilize large Adafruit displays (see website) #define SHOW_PLACE 1 // Show place mode #define PLACE_DELAY 3 // Delay (secs) when displaying place/time #define MIN_BRIGHT 0 // minimum display brightness (0-15) #define MAX_BRIGHT 15 // maximum display brightness (0-15) /*-----------------------------------------* - 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.10" // 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 #define SMSG_TINFO 'I' // <- request timer information /*-----------------------------------------* - 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 = 10; // status LED (blue) byte STATUS_LED_G = 11; // 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 flag unsigned 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 status int serial_data; // serial data byte mode; // current program mode float 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]; #endif #ifdef DUAL_MODE // uses 8x8 matrix displays Adafruit_8x8matrix disp_8x8[MAX_DISP]; #endif void 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 resistor digitalWrite(START_SOL, LOW); #ifdef LED_DISPLAY for (int n=0; 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 bounce if (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_TINFO)) // get timer information { send_timer_info(); } 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 (unsigned long)(PLACE_DELAY * 1000)) { dbg(fDebug, "display_race_results"); for (int n=0; n 0) { disp_mat[lane].clear(); disp_mat[lane].drawColon(false); #ifdef DUAL_DISP #ifdef DUAL_MODE disp_8x8[lane+4].clear(); disp_8x8[lane+4].setTextSize(1); disp_8x8[lane+4].setRotation(3); disp_8x8[lane+4].setCursor(2, 0); #else disp_mat[lane+4].clear(); disp_mat[lane+4].drawColon(false); #endif #endif display_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 0.3F) // deadband to prevent flickering { // between levels dbg(fDebug, "led: BRIGHT"); display_level = new_level; for (int n=0; n