//Ninja Timer //Course timer for Ultimate Ninja Athlete Association course //by John Park //for Adafruit Industries //Timer logic by Tod Kurt // ////MIT License //////////////////////////////////////////////////////////////////////////////// /* Hardware: -Arduino M0 Pro -NeoPixel 60 strips, x5 meters -2x quad Logic Level Shifters with 1k resistors -Relay Feather Wing -12V Car Horn -Arcade buttons x6 -Patch Shield and CAT5 connectors/cables for buttons Functions: -Power on, goes to 00:00.0 in minutes, seconds, tenths -Press start button, the digits blink four times -This includes three short and one long beep from the horn -Timer starts counting up -If course stop button is pressed, stop the time and hold, blast the horn -If desk stop button is pressed, stop the time and hold, blast horn -If desk pause is pressed, stop the time but allow resume on second press -If desk reset is pressed, reset to 00:00.0 */ //////////////////////////////////////////////////////////////////////////////// #include #include #define NUMPIXELS 57 // # of LEDs in a strip (some are actually 56, some 57 // due to dots), each strip is a digit #define DATAPIN0 4 //digit 0 NeoPixel 60 strip far RIGHT #define DATAPIN1 5 //digit 1 plus lower colon dot #define DATAPIN2 6 //digit 2 plus upper colon dot #define DATAPIN3 7 //digit 3 plus decimal dot #define DATAPIN4 8 //digit 4 far LEFT Adafruit_NeoPixel strip[] = { //here is the variable for the multiple strips Adafruit_NeoPixel(NUMPIXELS, DATAPIN0, NEO_GRB + NEO_KHZ800), Adafruit_NeoPixel(NUMPIXELS, DATAPIN1, NEO_GRB + NEO_KHZ800), Adafruit_NeoPixel(NUMPIXELS, DATAPIN2, NEO_GRB + NEO_KHZ800), Adafruit_NeoPixel(NUMPIXELS, DATAPIN3, NEO_GRB + NEO_KHZ800), Adafruit_NeoPixel(NUMPIXELS, DATAPIN4, NEO_GRB + NEO_KHZ800) }; const int bright = 200; //brightness for all pixels 0-255 range, 32 being dim #define HORNPIN 10 //car horn: pin 10 on Arduino runs to Power Relay Feather //pin 10 (jumpered underneath relay board) #define CSTARTPIN 11 //course start button pin using internal pullup resistor //connected via RJ-45 jack J1, conductor 2 (GND conductor 1) #define CSTOPPIN 12 //course stop button pin " //jack J2, conductor 2 (GND conductor 1) #define DRESETPIN A0 //desk RESET button pin //jack J3, conductor 1 (GND conductor 8) #define DSTARTPIN A1 //desk START button pin //jack J3, conductor 2 (GND conductor 8) #define DPAUSEPIN A2 //desk PAUSE button pin //jack J3, conductor 3 (GND conductor 8) #define DSTOPPIN A3 //desk STOP button pin //jack J3, conductor 4 (GND conductor 8) //Button states for reading the pushbuttons, since pullup: off = HIGH, on = LOW int cStartState = 1; int cStopState = 1; int dStartState = 1; int dStopState = 1; int dPauseState = 1; int dResetState = 1; //variables for debouncing int cStartLastState = 1; int cStopLastState = 1; int dStartLastState = 1; int dStopLastState = 1; int dPauseLastState = 1; int dResetLastState = 1; int runningMode=0; //counter to tell overall state since reset //0 means it is a clean start, 1 means it's already running or ran int pauseMode=0;//counter for using pause to stop and start timer int stoppedMode=0; int go = 0; //start counter once blinking/beeping is done int dispColor = 3; //color to call out for display, feeds other voids volatile bool isNinjaCounting = false; // true when start button pressed volatile uint32_t ninjaMillisTime = 0; // the actual counter value // how often the inputs are readInputs const int inputsMillis = 10; // how often the counter is updated const int counterMillis = 10; // how often the display is updated const int displayMillis = 100; // task prototypes void readInputs(); void updateCounter(); void updateDisplay(); void debugDisplay(); //void startSequence(); //void stopSequence(); Scheduler runner; // change these repeat times to appropriate ones for real application Task t1(inputsMillis, TASK_FOREVER, &readInputs); Task t2(counterMillis, TASK_FOREVER, &updateCounter); Task t3(displayMillis, TASK_FOREVER, &updateDisplay); Task t4(1000, TASK_FOREVER, &debugDisplay); //Task t5(); //Task t6(); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void setup() { delay(2000); //while(!Serial); //for Leonardo & similar //initialize serial for debugging Serial.begin(115200); Serial.println("NinjaTimer TEST"); Serial.println("'s' to start timer"); Serial.println("'x' to stop timer"); Serial.println("'p' to pause timer"); Serial.println("'r' to restart timer"); runner.init(); runner.addTask(t1); runner.addTask(t2); runner.addTask(t3); runner.addTask(t4); t1.enable(); // t2.enable(); t2.disable(); t3.enable(); t4.enable(); //NeoPixel array setup for(int s=0;s 0 ) { int ch = Serial.read(); if( ch == 's' ) { startCounter(); } else if( ch == 'x' ) { stopCounter(); } else if( ch == 'p' ) { pauseCounter(); } else if( ch == 'r' ) { resetCounter(); } } } //END void readInputs() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // task called periodically by TaskScheduler void updateCounter() { if( isNinjaCounting ) { // FIXME: this doesn't take into account overhead ninjaMillisTime += counterMillis; } } //END void updateCounter() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Actually update the display. // May also do other effects like blinking // task called periodically by TaskScheduler void updateDisplay() { int tenths = (ninjaMillisTime / 100) % 10; int secs = (ninjaMillisTime / 1000) % 60; int mins = (ninjaMillisTime / 1000 / 60) % 60 ; int secsTens = secs/10; //get the tens place of seconds int secsOnes = secs%10; //get the ones place of seconds int minsTens = mins/10; //get the tens place of minutes int minsOnes = mins%10; //get the ones place of minutes //write to NeoPixel seven segments, colors: //0 = off //1 = red //2 = green //3 = blue //4 = white //5 = yellow // digitWrite(0, tenths, dispColor); strip[0].show(); digitWrite(1, secsOnes, dispColor); strip[1].show(); digitWrite(2, secsTens, dispColor); strip[2].show(); digitWrite(3, minsOnes, dispColor); strip[3].show(); digitWrite(4, minsTens, dispColor); strip[4].show(); char dispbuf[10]; sprintf(dispbuf, "%02d:%02d.%01d", mins, secs, tenths); Serial.print("DISPLAY: "); Serial.print(ninjaMillisTime); Serial.print(" mm:ss.t: "); Serial.println(dispbuf); } //END void updateDisplay() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // A slower-updating display that logs to the serial port, // useful for debugging // task called periodically by TaskScheduler void debugDisplay() { Serial.print("DEBUG: time:"); Serial.print(ninjaMillisTime); Serial.print(" isNinjaCounting:"); Serial.println(isNinjaCounting); } //END void debugDisplay() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // starts the counter running // via START button void startCounter() { t2.enable();//this needs to be enabled here to deal with beep delay Serial.println("START"); isNinjaCounting = true; } //END void startCounter() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // stops the counter running // via STOP button // does NOT reset display void stopCounter() { Serial.println("STOP"); isNinjaCounting = false; } //END void stopCounter() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // pauses counter // via PAUSE button void pauseCounter() { Serial.println("PAUSE"); isNinjaCounting = false; } //END void pauseCounter() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // reset counter to zero, stops counter // via RESET button (or boot) void resetCounter() { Serial.println("RESET"); ninjaMillisTime = 0; stopCounter(); } //END void resetCounter() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void digitWrite(int digit, int val, int col){ //use this to light up a digit //'digit' is which one (right to left, 0 indexed) //'val' is the value to set on the digit //'col' is the color to use, R,G,B or W //example: // digitWrite(0, 4, 2); //this would set the digit //on the far right to a "4" in green. /* // Letters are the standard naming, numbers are based upon the wiring sequence A 2 ---------- | | | | F 1 | | B 3 | | | G 7 | ---------- | | | | E 6 | | C 4 | | | D 5 | ---------- dp 8 */ //these are the numeric character definitions, //if last argument is a 0, the segment is off if (val==0){ // "0" //segments A,B,C,D,E,F segLight(digit,1,col); segLight(digit,2,col); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,col); segLight(digit,6,col); segLight(digit,7,0); segLight(digit,8,col); } if (val==1){ // "1" //segments A,B,C,D,E,F segLight(digit,1,0); segLight(digit,2,0); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,0); segLight(digit,6,0); segLight(digit,7,0); segLight(digit,8,col); } if (val==2){ // "2" //segments A,B,C,D,E,F segLight(digit,1,0); segLight(digit,2,col); segLight(digit,3,col); segLight(digit,4,0); segLight(digit,5,col); segLight(digit,6,col); segLight(digit,7,col); segLight(digit,8,col); } if (val==3){ // "3" //segments A,B,C,D,E,F segLight(digit,1,0); segLight(digit,2,col); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,col); segLight(digit,6,0); segLight(digit,7,col); segLight(digit,8,col); } if (val==4){ // "4" //segments A,B,C,D,E,F segLight(digit,1,col); segLight(digit,2,0); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,0); segLight(digit,6,0); segLight(digit,7,col); segLight(digit,8,col); } if (val==5){ // "5" //segments A,B,C,D,E,F segLight(digit,1,col); segLight(digit,2,col); segLight(digit,3,0); segLight(digit,4,col); segLight(digit,5,col); segLight(digit,6,0); segLight(digit,7,col); segLight(digit,8,col); } if (val==6){ // "6" //segments A,B,C,D,E,F segLight(digit,1,col); segLight(digit,2,col); segLight(digit,3,0); segLight(digit,4,col); segLight(digit,5,col); segLight(digit,6,col); segLight(digit,7,col); segLight(digit,8,col); } if (val==7){ // "7" //segments A,B,C,D,E,F segLight(digit,1,0); segLight(digit,2,col); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,0); segLight(digit,6,0); segLight(digit,7,0); segLight(digit,8,col); } if (val==8){ // "8" //segments A,B,C,D,E,F segLight(digit,1,col); segLight(digit,2,col); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,col); segLight(digit,6,col); segLight(digit,7,col); segLight(digit,8,col); } if (val==9){ // "9" //segments A,B,C,D,E,F segLight(digit,1,col); segLight(digit,2,col); segLight(digit,3,col); segLight(digit,4,col); segLight(digit,5,col); segLight(digit,6,0); segLight(digit,7,col); segLight(digit,8,col); } } //END void digitWrite(int digit, int val, int col) //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void segLight(char digit, int seg, int col){ //'digit' picks which neopixel strip //'seg' calls a segment //'col' is color /* //always blank the LEDs first for(int i = 0; i