//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