Now that you system is assembled, it's time to upload the timer code.
First, you'll need to download the TaskScheduler library, which is used by the Arduino sketch to do multiple things at once (such as run the timer while listening for button presses). Go here and download the zip file. Uncompress and move the TaskScheduler folder in your libraries folder
https://github.com/arkhipenko/TaskScheduler
Then, download the file below, upzip it, and place the directory in your Arduino sketches folder. Open it into Arduino IDE, plug in your Arduino to your computer, and then upload the code to the Arduino.
//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 <Adafruit_NeoPixel.h> #include <TaskScheduler.h> #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<5;s++){ strip[s].begin(); // Initialize pins for output strip[s].setBrightness(bright); //full brightness 255 strip[s].show(); // Turn all LEDs off delay(200); } //flash dashes for(int t=0;t<5;t++){ digitWrite(t,8,0);//blank strip[t].show(); segLight(t, 7, dispColor); strip[t].show(); } delay(1000); //Relay pin setup pinMode(HORNPIN, OUTPUT); //Button pin setup pinMode(CSTARTPIN, INPUT_PULLUP); pinMode(CSTOPPIN, INPUT_PULLUP); pinMode(DSTARTPIN, INPUT_PULLUP); pinMode(DSTOPPIN, INPUT_PULLUP); pinMode(DPAUSEPIN, INPUT_PULLUP); pinMode(DRESETPIN, INPUT_PULLUP); } //END void setup() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void loop() { runner.execute(); } //END void loop() //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // read all the button inputs, change state of program appropriately // task called periodically by TaskScheduler void readInputs() { //check the state of the buttons cStartState= digitalRead(CSTARTPIN); cStopState = digitalRead(CSTOPPIN); dStartState = digitalRead(DSTARTPIN); dStopState = digitalRead(DSTOPPIN); dPauseState = digitalRead(DPAUSEPIN); dResetState = digitalRead(DRESETPIN); //Course Start // if(cStartState!=cStartLastState){ //debounce - state has changed if (cStartState==LOW) { //the course start button is pushed if (runningMode==0){//normal start dispColor=2; //color is green //sound the horn four times delay(100);//slight delay after pressing for(int h = 0; h < 3; h++){ //three horn blasts //flash the display and beep the horn for(int t=0;t<5;t++){ digitWrite(t,8,0);//blank strip[t].show(); } delay(100); for(int t=0;t<5;t++){ digitWrite(t,0,dispColor);//0s strip[t].show(); } digitalWrite(HORNPIN, HIGH); delay(250); for(int t=0;t<5;t++){ digitWrite(t,8,0);//blank strip[t].show(); } digitalWrite(HORNPIN,LOW); delay(750); } startCounter(); digitalWrite(HORNPIN, HIGH); delay(500); digitalWrite(HORNPIN,LOW); runningMode = 1;//indicates it is running or has run } else if(runningMode==1){ //start button disabled Serial.println("already started"); } } cStartLastState = cStartState; //for debounce } //Course Stop // if(cStopState!=cStopLastState){ if (runningMode==1){ //only stop if started if (cStopState==LOW) { //the course stop button is pushed dispColor=1; //red stopCounter(); digitalWrite(HORNPIN, HIGH); delay(1500); digitalWrite(HORNPIN,LOW); stoppedMode=1; } cStopLastState=cStopState; } } //Desk control box Start // if(dStartState != dStartLastState){ if (dStartState==LOW) { //the desk start button is pushed if (runningMode==0){//normal start dispColor=2; //color is green //sound the horn four times delay(100); for(int h = 0; h < 3; h++){ //flash the display and beep the horn for(int t=0;t<5;t++){ digitWrite(t,8,0);//blank strip[t].show(); } delay(100); for(int t=0;t<5;t++){ digitWrite(t,0,dispColor);//0s strip[t].show(); } digitalWrite(HORNPIN, HIGH); delay(250); for(int t=0;t<5;t++){ digitWrite(t,8,0);//blank strip[t].show(); } digitalWrite(HORNPIN,LOW); delay(750); } startCounter(); digitalWrite(HORNPIN, HIGH); delay(500); digitalWrite(HORNPIN,LOW); runningMode = 1;//indicates it is running or has run } else if(runningMode==1){ //start button disabled Serial.println("nope start"); } } dStartLastState=dStartState; } //Desk Stop // if(dStopState!=dStopLastState){ if (runningMode==1){//only can stop if started if (dStopState==LOW) { //the desk stop button is pushed dispColor=1; //red stopCounter(); digitalWrite(HORNPIN, HIGH); delay(1500); digitalWrite(HORNPIN,LOW); stoppedMode=1; } } dStopLastState=dStopState; } //Pause // if(dPauseState!=dPauseLastState){ //for debouncing if(runningMode==1&&stoppedMode==0){//only makes sense to pause if running if (dPauseState==LOW) { //the pause button is pushed if (pauseMode==0) { //it was previously running dispColor=5; //yellow pauseCounter(); pauseMode=1; Serial.println("paused"); } else if (pauseMode==1){//it was previously pause, start up again dispColor=2; //green startCounter(); pauseMode=0; Serial.println("restarted"); } } } dPauseLastState=dPauseState; //for debouncing } //Reset // if(dResetState!=dResetLastState){ //for debouncing if (dResetState==LOW) { //the reset button is pushed dispColor=3;//blue //flash dashes for(int t=0;t<5;t++){ digitWrite(t,8,0);//blank strip[t].show(); segLight(t, 7, 4);//white dashes strip[t].show(); delay(170); } runningMode=0; pauseMode=0; stoppedMode=0; resetCounter(); } dResetLastState=dResetState; //for debouncing } if( Serial.available() > 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<56; i++){ strip[0].setPixelColor(i, 0, 0, 0); } */ int color[3]; //color sets if (col==0){ //off color[0]={0}; color[1]={0}; color[2]={0}; } if (col==1){ //red color[0]={255}; color[1]={0}; color[2]={0}; } if (col==2){ //green color[0]={0}; color[1]={255}; color[2]={0}; } if (col==3){ //blue color[0]={0}; color[1]={0}; color[2]={255}; } if (col==4){ //white -- careful with this one, 3x power consumption color[0]={255}; color[1]={255}; color[2]={255}; } if (col==5){ //yellow color[0]={200}; color[1]={120}; color[2]={0}; } //sets are 0-7, //8-15, 16-23, 24-31, 32-39, 40-47, 48-55 //seg F if(seg==1){ //light first 8 for(int i=0; i<8; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip(digit).show(); } //seg A if(seg==2){ //light second 8 for(int i=8; i<16; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip(digit).show(); } //seg B if(seg==3){ for(int i=16; i<24; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip(digit).show(); } //seg C if(seg==4){ for(int i=24; i<33; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip.show(); } //seg D if(seg==5){ for(int i=32; i<41; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip(digit).show(); } //seg E if(seg==6){ for(int i=40; i<49; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip(digit).show(); } //seg G if(seg==7){ for(int i=48; i<57; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip(digit).show(); } //seg dp if(seg==8){ for(int i=56; i<57; i++){ strip[digit].setPixelColor(i,color[0],color[1],color[2]); } //strip.show(); } } //END void segLight(char digit, int seg, int col) ////////////////////////////////////////////////////////////////////////////////
Now, you can put your timer to the test! Unplug it from your computer, and then connect the box cables to your digit panels, car horn, and three button Cat 5e/Cat 6 cables.
Next, plug the AC power cord into an outlet. It will power on and display all "0"s in blue after a couple of seconds.
Press course start button or desk control start button to begin your run. The horn will sounds four times, and then start counting.
At the end of your run, dive for the course stop button. The horn will sound and your time will be displayed in red.
At any time, the desk controller box can be used to pause a run by pressing the yellow button, and restarting the run by pressing it a second time. This is useful if a dog, toddler, or knife wielding actual honest to goodness ninja shows up.
Finally, the blue button on the desk controller box can be pressed to reset the timer in anticipation of the next course run!
Page last edited March 10, 2017
Text editor powered by tinymce.