//Travis Edrington
//This is arduino code for blinking an RGB strip at a rate matching song for a musical so
// deaf artists can follow the beat.
//This code recives its commands over the SPI interface from a Raspberry Pi.
//This is V2, where it kind of works, plus we are adding a screen
// Debugging switches and macros
#define DEBUG 1 // Switch debug output 0=off, 1=enable for TIMEBUG, 2=enter function, 3=common variables, 4=all variables
#define TIMEBUG 0 //Printing of time 0=off, 1=BPM, 2=Milli Timers, 3=MicroTimers
#define USEDISPLAY 1 //1 for Serial, 2 for Screen, 0 to the Eather
#define SIM 1 //0 is real prgramming, 1 for the 10,11,12 button, 2 For when at https://wokwi.com/projects/376282746342508545
//--------------------------------------//
#if SIM
const int PIN_SPI_TEST_2 = 12;
const int PIN_SPI_TEST_3 = 11;
const int PIN_SPI_TEST_4 = 10;
#if SIM == 2
const int PIN_SPI_TEST_1 = 13;
const int PIN_SPI_TEST_5 = 9;
const int PIN_SPI_TEST_6 = 8;
const int PIN_SPI_TEST_7 = 4;
#endif
byte buttondebounce = 0;
#else
#include <SPI.h>
#endif //end SIM
//--------------------------------------//
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27, if new version please use 0x3F instead.
#if USEDISPLAY == 1 //Serial
#define PRINTS(s) { Serial.print(F(s)); Serial.println(); }
#define PRINT(s,v) { Serial.print(F(s)); Serial.print(": "); Serial.print(v); Serial.println(); }
#define PRINTX(s,v) { Serial.print(F(s)); Serial.print(": "); Serial.print(F("0x")); Serial.print(v, HEX); Serial.println();}
#elif USEDISPLAY == 2 //LCD
#define PRINTS(s) { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F(s)); }
#define PRINT(s,v) { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F(s)); lcd.setCursor(0, 1); lcd.print(v); }
#define PRINTX(s,v) { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F(s)); lcd.setCursor(0, 1); lcd.print(F("0x")); lcd.setCursor(2, 1); lcd.print(v, HEX);}
#else //Ether
#define PRINTS(s)
#define PRINT(s,v)
#define PRINTX(s,v)
#endif //end USEDISPLAY
char databuff[5]; //data buffer
volatile byte indx;
volatile boolean received = false;
volatile byte recv_byte;
/*. For Future Planning, 7 bytes
Commands
S####,# Start at ###.# bpm at beat # of the measure
F#### Flash at ###.# bpm
mHBHBHB Mode Heartbeat
C###### Solid Color in Hex
mREBOOT Reboot arduino, handled by Pi
float metronomeBPM = 30; //Flash rate
float speedrate = 1.0;
float playBPM = metronomeBPM * speedrate;
//metronomeON = 60000 / playBPM * .75; //How many milliseconds between each beat
//metronomeOFF = 60000 / playBPM * .25; //How many milliseconds the light is on
//Indicate Color mode # color # brightness
*/
//--------------------------------------//
//Always used Pins
//--------------------------------------//
// LED Pins
const int PIN_red = 5; //Red
const int PIN_green = 6; //Green
const int PIN_blue = 3; //Blue
const int PIN_BEATS = 7; //Outputs actual beats
// Low Beat
const int lowLEDr = 255; //Orange
const int lowLEDg = 127;
const int lowLEDb = 0;
// 4 Beat
const int H4LEDr = 0; //Cyan
const int H4LEDg = 255;
const int H4LEDb = 255;
// 8 Beat
const int H8LEDr = 255; //Purple
const int H8LEDg = 0;
const int H8LEDb = 255;
//Time related commands
unsigned long previousMillis; // will store last time LED was updated
unsigned long currentMillis; // will store the current time
unsigned long nextEvent;
signed long nextEventDrift = 0; // Help to reduce time drift
#if DEBUG
unsigned long nowmicro;
unsigned long lastmicro;
#endif
byte beat = 0; //0-15, Even is top of beat, odd for lights off
byte mode = 200; //1 is hold color, 2 is run beat mode, 11 is new color hold, 12 is new beat mode
byte lastmode = 0; //for preventing spamming to serial
byte premeasure = 0;
unsigned long nextPulse; //For heartbeat timmer
int delayoffset = 0;
//Wakeup and standby heartbeat
byte brightness = 0;
int heartFade = 5;
const int heartRate = 50; //50 ms * (255/5 = 51) 500ms for a 1 sec cycle
float metronomeON = 325; // = 60000 / playBPM * .75; //How many milliseconds between each beat
float metronomeOFF = 175; // = 60000 / playBPM * .25; //How many milliseconds the light is on
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output for LEDs.
pinMode(PIN_red, OUTPUT);
pinMode(PIN_green, OUTPUT);
pinMode(PIN_blue, OUTPUT);
pinMode(PIN_BEATS, OUTPUT);
#if SIM
pinMode(PIN_SPI_TEST_2, INPUT_PULLUP); //12 Blue
pinMode(PIN_SPI_TEST_3, INPUT_PULLUP); //11 Yellow
pinMode(PIN_SPI_TEST_4, INPUT_PULLUP); //10 Red
#if SIM == 2
pinMode(PIN_SPI_TEST_1, INPUT_PULLUP); //13
pinMode(PIN_SPI_TEST_5, INPUT_PULLUP); //9
pinMode(PIN_SPI_TEST_6, INPUT_PULLUP); //8
pinMode(PIN_SPI_TEST_7, INPUT_PULLUP); //4
#endif
#else
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);
// turn on SPI in slave mode
SPCR |= _BV(SPE);
indx = 0;
received = false;
#endif
#if DEBUG
nowmicro = micros();
lastmicro = nowmicro;
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
#endif
}
void update_BPM() {
#if DEBUG
float metronomeTotal = metronomeON + metronomeOFF;
float playBPM = metronomeTotal * 60000;
PRINT("BPM", playBPM);
PRINT("Pulse On", metronomeON);
PRINT("Pulse Off", metronomeOFF);
#endif
}
void setlights(int redval = 0, int greenval = 0, int blueval = 0) {
analogWrite(PIN_red, redval);
analogWrite(PIN_green, greenval);
analogWrite(PIN_blue, blueval);
}
void offlights() {
analogWrite(PIN_red, 0);
analogWrite(PIN_green, 0);
analogWrite(PIN_blue, 0);
}
void heartbeat(byte RGB = 0) {
if (currentMillis >= nextPulse) {
// change the brightness for next time through the loop:
brightness = brightness + heartFade;
// reverse the direction of the fading at the ends of the fade:
if (brightness <= 0 || brightness >= 200) {
heartFade = -heartFade;
}
switch (RGB) {
case 0: //red
setlights(brightness,0,0);
break;
case 1: //green
setlights(0,brightness,0);
break;
case 2: //blue
setlights(0,0,brightness);
break;
case 3: //half red
setlights(brightness/2,0,0);
break;
case 4: //half green
setlights(0,brightness/2,0);
break;
case 5: //half blue
setlights(0,0,brightness/2);
break;
case 6: //yellow
setlights(brightness,brightness,0);
break;
case 7: //cyan
setlights(0,brightness,brightness);
break;
case 8: //magenta
setlights(brightness,0,brightness);
break;
case 9: //orange
setlights(brightness,brightness/2,0);
break;
default:
setlights(brightness,0,0);
break;
}
nextPulse = currentMillis + heartRate;
}
}
void countbeat() {
//soft beats on 0,2,4,6,8,10,12. Hard beats on 0 and 8
if (beat % 2 == 0) {
digitalWrite(PIN_BEATS, HIGH);
//this gives all even beats
if (beat == 0) { //beat 1
if (premeasure == 1){
setlights(0, 255, 0);
premeasure = 0;
} else {
setlights(H4LEDr, H4LEDg, H4LEDb);
}
} else if (beat == 8) { //beat 5
setlights(H8LEDr, H8LEDg, H8LEDb);
} else {
setlights(lowLEDr, lowLEDg, lowLEDb);
}
//Lights are now on, so set current time to next off
nextEvent = currentMillis + metronomeON + 0.5 - nextEventDrift;
} else { //most of the way though the beat, turn lights off
offlights();
digitalWrite(PIN_BEATS, LOW);
nextEvent = currentMillis + metronomeOFF + 0.5 - nextEventDrift;
}
beat = beat + 1;
if (beat > 15) beat = 0;
#if DEBUG
PRINT(" Beat", beat);
#endif
}
void countbeat(byte LEDr, byte LEDg, byte LEDb) { //For constant color on beat
//soft beats on 0,2,4,,8,10,12.
if (beat % 2 == 0) {
digitalWrite(PIN_BEATS, HIGH);
setlights(LEDr, LEDg, LEDb);
//Lights are now on, so set current time to next off
nextEvent = currentMillis + metronomeON + 0.5 - nextEventDrift;
} else { //most of the way though the beat, turn lights off
offlights();
digitalWrite(PIN_BEATS, LOW);
nextEvent = currentMillis + metronomeOFF + 0.5 - nextEventDrift;
}
beat = beat + 1;
if (beat > 15) beat = 0;
#if DEBUG
PRINT(" BeatO", beat);
#endif
}
// the loop routine runs over and over again forever:
void loop() {
//handle time calculations
previousMillis = currentMillis;
currentMillis = millis();
#if TIMELOG
PRINTS("*****");
nowmicro = micros();
PRINT("Microtime", nowmicro);
PRINT("∆ micros", nowmicro - lastmicro);
lastmicro = nowmicro;
PRINT("current", currentMillis);
PRINT("previous", previousMillis);
PRINT("delta", currentMillis - previousMillis);
PRINT("nextEvent", nextEvent);
PRINT("timetonext", nextEvent - currentMillis);
lastmicro = nowmicro;
#endif
#if SIM
SPSR = 0x21;
if (digitalRead(PIN_SPI_TEST_2) == LOW) { //Blue Button
if (buttondebounce == 0) {
SPSR = 0x80;
SPDR = 200;
#if DEBUG
PRINTS("Blue Button");
#endif
}
buttondebounce = 10;
}
if (digitalRead(PIN_SPI_TEST_3) == LOW) { //Yellow button
if (buttondebounce == 0){
SPSR = 0x80;
SPDR = 12;
#if DEBUG
PRINTS("Yellow Button");
#endif
}
buttondebounce = 10;
}
if (digitalRead(PIN_SPI_TEST_4) == LOW) { //Red button
if (buttondebounce == 0){
SPSR = 0x80;
SPDR = 28;
#if DEBUG
PRINTS("Red Button");
#endif
}
buttondebounce = 10;
}
#if SIM == 2
if (digitalRead(PIN_SPI_TEST_1) == LOW) { //Green button
if (buttondebounce == 0){
SPSR = 0x80;
SPDR = 13;
#if DEBUG
PRINTS("Blue");
#endif
}
buttondebounce = 10;
}
if (digitalRead(PIN_SPI_TEST_5) == LOW) { //Black button
if (buttondebounce == 0){
SPSR = 0x80;
SPDR = 58;
#if DEBUG
PRINTS("Quo");
#endif
}
buttondebounce = 10;
}
if (digitalRead(PIN_SPI_TEST_6) == LOW) { //White button
if (buttondebounce == 0){
SPSR = 0x80;
SPDR = 202;
#if DEBUG
PRINTS("Going 202");
#endif
}
buttondebounce = 10;
}
if (digitalRead(PIN_SPI_TEST_7) == LOW) { //Grey button
if (buttondebounce == 0){
SPSR = 0x80;
SPDR = 206;
#if DEBUG
PRINTS("Going 203");
#endif
}
buttondebounce = 10;
}
#endif
if (buttondebounce > 0)
{
buttondebounce = buttondebounce -1;
}
#endif
if ((mode >= 200) && (mode <= 250)) {
heartbeat(mode-200);
}
if ((SPSR & (1 << SPIF)) != 0) { //We has data
recv_byte = SPDR;
PRINT("Recived", recv_byte);
//**********************
//Here we handle recived data
switch (recv_byte) { //Get byte to deside mode to run on
case 11: //Off
setlights(0, 0, 0);
mode = 11;
break;
case 12: //Red
setlights(255, 0, 0);
mode = 11;
break;
case 13: //Green
setlights(0, 255, 0);
mode = 11;
break;
case 14: //Blue
setlights(0, 0, 255);
mode = 11;
break;
case 15: //Yellow
setlights(255, 255, 0);
mode = 11;
break;
case 16: //Cyan
setlights(0, 255, 255);
mode = 11;
break;
case 17: //Magenta
setlights(255, 0, 255);
mode = 11;
break;
case 18: //White
setlights(255, 255, 255);
mode = 11;
break;
case 19: //White
setlights(255, 127, 0);
mode = 11;
break;
case 22: //Cheer, All Together 100%
metronomeON = 387.9;
metronomeOFF = 129.3;
mode = 12;
break;
case 23: //Cheer, All Together 90%
metronomeON = 431.0;
metronomeOFF = 143.7;
mode = 12;
break;
case 24: //Cheer, All Together 80%
metronomeON = 484.9;
metronomeOFF = 161.6;
mode = 12;
break;
case 28: //Start, Head in Game 100% //120 BPM
metronomeON = 375.0;
metronomeOFF = 125.0;
mode = 12;
break;
case 29: //Start, Head in Game 90%
metronomeON = 416.7;
metronomeOFF = 138.9;
mode = 12;
break;
case 30: //Start, Head in Game 80%
metronomeON = 468.8;
metronomeOFF = 156.3;
mode = 12;
break;
case 34: //Auditions 100%
metronomeON = 286.6;
metronomeOFF = 95.5;
mode = 12;
break;
case 35: //Auditions 90%
metronomeON = 318.5;
metronomeOFF = 106.2;
mode = 12;
break;
case 36: //Auditions 80%
metronomeON = 358.3;
metronomeOFF = 119.4;
mode = 12;
break;
case 40: //Looking for 100%
metronomeON = 321.4;
metronomeOFF = 107.1;
mode = 13;
break;
case 41: //Looking for 90%
metronomeON = 357.1;
metronomeOFF = 119.0;
mode = 13;
break;
case 42: //Looking for 80%
metronomeON = 401.8;
metronomeOFF = 133.9;
mode = 13;
break;
case 46: //Looking for Reprise 100%
metronomeON = 381.4;
metronomeOFF = 127.1;
mode = 12;
break;
case 47: //Looking for Reprise 90%
metronomeON = 423.7;
metronomeOFF = 141.2;
mode = 12;
break;
case 48: //Looking for Reprise 80%
metronomeON = 476.7;
metronomeOFF = 158.9;
mode = 12;
break;
case 52: //Callback 100%
metronomeON = 409.1;
metronomeOFF = 136.4;
mode = 12;
break;
case 53: //Callback 90%
metronomeON = 454.5;
metronomeOFF = 151.5;
mode = 12;
break;
case 54: //Callback 80%
metronomeON = 511.4;
metronomeOFF = 170.5;
mode = 12;
break;
case 58: //Status Quo 100%
metronomeON = 281.3;
metronomeOFF = 93.8;
mode = 12;
break;
case 59: //Status Quo 90%
metronomeON = 312.5;
metronomeOFF = 104.2;
mode = 12;
break;
case 60: //Status Quo 80%
metronomeON = 351.6;
metronomeOFF = 117.2;
mode = 12;
break;
case 64: //Counting On 100%
metronomeON = 326.1;
metronomeOFF = 108.7;
mode = 12;
break;
case 65: //Counting On 90%
metronomeON = 362.3;
metronomeOFF = 120.8;
mode = 12;
break;
case 66: //Counting On 80%
metronomeON = 407.6;
metronomeOFF = 135.9;
mode = 12;
break;
case 70: //Bop 100%
metronomeON = 428.6;
metronomeOFF = 142.9;
mode = 13;
break;
case 71: //Bop 90%
metronomeON = 476.2;
metronomeOFF = 158.7;
mode = 13;
break;
case 72: //Bop 80%
metronomeON = 535.7;
metronomeOFF = 178.6;
mode = 13;
break;
case 76: //Breaking 100%
metronomeON = 378.2;
metronomeOFF = 126.1;
mode = 12;
break;
case 77: //Breaking 90%
metronomeON = 420.2;
metronomeOFF = 140.1;
mode = 12;
break;
case 78: //Breaking
metronomeON = 472.7;
metronomeOFF = 157.6;
mode = 12;
break;
case 111 ... 113: //Speed Flash Mode
metronomeON = 100;
metronomeOFF = 100;
mode = recv_byte;
break;
case 200 ... 250:
heartbeat();
break;
default:
PRINTS("input case error!");
break;
}
//We have updated time. Now lets restart
currentMillis = millis();
nextEvent = currentMillis;
}
//For 11, 12, blink green on start, 13 blink green on 1
if (currentMillis >= nextEvent) {
nextEventDrift = currentMillis - nextEvent;
#if DEBUG
if (lastmode != mode) {
PRINT("Mode", mode);
lastmode = mode;
}
#endif
switch (mode) { //1 is hold color, 2 is run beat mode, 11 is new color hold, 12 is new beat mode
case 0:
countbeat();
break;
case 1: //we are holding color and color is set
break;
case 2: //We are running beat mode, we are at event time, do beat
countbeat();
break;
case 11: //This brings in the new color to hold, reset beat to 0 for next time, make case 1
beat = 0;
mode = 1;
break;
case 12: //Calculate first time pause, turn on lights, make case 2
beat = 0;
premeasure = 0;
countbeat();
mode = 2;
break;
case 13: //For when needed, start beat at 5, which is 8 (beat - 1 * 2)
beat = 8;
premeasure = 1;
countbeat();
mode = 2;
break;
case 101: //Crazy Flash mode Red
countbeat(255,0,0);
break;
case 102: //Crazy Flash mode Green
countbeat(0,255,0);
break;
case 103: //Crazy Flash mode Blue
countbeat(0,0,255);
break;
case 111: //Crazy Flash mode Red Start
beat = 0;
countbeat(255,0,0);
mode = 101;
break;
case 112: //Crazy Flash mode Green Start
beat = 0;
countbeat(0,255,0);
mode = 102;
break;
case 113: //Crazy Flash mode Blue Start
beat = 0;
countbeat(0,0,255);
mode = 103;
break;
case 200 ... 250: //Heartbeat
break;
default:
break;
}
}
}