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.