//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.
// 10/04/23 2:05 am
// 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
#define USEDISPLAY 1 //1 for Serial, 2 for Screen, 0 to the Eather
#define SIM 1 //0 is PI, 1 For when at https://wokwi.com/projects/376282746342508545
//--------------------------------------//
#if SIM
const byte PIN_SPI_TEST_1 = 12; //Red Button
const byte PIN_SPI_TEST_2 = 11; //Yellow Button
const byte PIN_SPI_TEST_3 = 10; //Blue Button
byte buttondebounce = 0;
#else
#include <SPI.h>
char SPI_BUFF [10] = "X";
volatile byte pos;
volatile boolean process_it;
#endif //end SIM
//--------------------------------------//
#if USEDDISPLAY == 2
#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.
#endif
#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();}
#define PRINTB(s,v) { Serial.print(F(s)); Serial.print(": "); Serial.print(F("b")); Serial.print(v, BIN); Serial.println();}
#define PRINTE1(s) { Serial.println(F("%%%%%%%%%%%%%%%%%%%%%")); Serial.print(F(s)); digitalWrite(PIN_ERR_1, HIGH); Serial.println(); }
#define PRINTE2(s) { Serial.println(F("%%%%%%%%%%%%%%%%%%%%%")); Serial.print(F(s)); digitalWrite(PIN_ERR_2, HIGH); 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);}
#define PRINTB(s,v) { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F(s)); lcd.setCursor(0, 1); lcd.print(F("b")); lcd.setCursor(2, 1); lcd.print(v, BIN);}
#define PRINTE1(s) { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F(s)); digitalWrite(PIN_ERR_1, HIGH); }
#define PRINTE2(s) { lcd.init(); lcd.backlight(); lcd.setCursor(0, 1); lcd.print(F(s)); digitalWrite(PIN_ERR_2, HIGH); }
#else //Ether
#define PRINTS(s)
#define PRINT(s,v)
#define PRINTX(s,v)
#define PRINTB(s,v)
#define PRINTE1(s)
#define PRINTE2(s)
#endif //end USEDISPLAY
//--------------------------------------//
//Always used Pins
//--------------------------------------//
// LED Pins
const byte PIN_red = 5; //Red
const byte PIN_green = 6; //Green
const byte PIN_blue = 3; //Blue
const byte PIN_ERR_1 = 8; //Error 1
const byte PIN_ERR_2 = 9; //Error 2
const byte PIN_BEATS = 7; //Outputs actual beats
//--------------------------------------//
//Normally used Constants
//--------------------------------------//
//Colors
/*
red 0
green 1
blue 2
yellow 3
cyan 4
magenta 5
white 6
orange 7
*/
const byte COLOR_LIST[8][3] = {{255,0,0},{0,255,0},{0,0,255},{255,255,0},{0,255,255},{255,0,255},{255,255,255},(255,127,0)};
//const byte COLOR_LIST[8][3] = {{1,2,3},{4,5,6},{7,8,9},{255,255,0},{0,255,255},{255,0,255},{255,255,255},(255,127,0)};
//Heartbeat
const int heartRate = 50; //50 ms * (255/5 = 51) 500ms for a 1 sec cycle
//--------------------------------------//
//Normally used Variables
//--------------------------------------//
//Time related variables
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
//Beat related variables
byte beat = 0; //0-15, Even is top of beat, odd for lights off
byte mode = 200; //0-99 Unique commands, 100-199 song ident
byte lastmode = 0; //for preventing spamming to serial
float metronomeRatio = 0.25; //% of time a flash is on
float metronomeTOTAL = 1000; //Time of 1 sycle
//Heartbeat related variables
float brightness = 0.0;
float heartFade = 0.025;
unsigned long nextPulse; //When to next update heartbeat
//analysis related variables
bool buttoneval = false;
char COMMAND_BUFF [10] = "X";
//--------------------------------------//
//Start of Voids
//--------------------------------------//
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_1, INPUT_PULLUP);
pinMode(PIN_SPI_TEST_2, INPUT_PULLUP);
pinMode(PIN_SPI_TEST_3, INPUT_PULLUP);
#else
//From https://gammon.com.au/spi
// turn on SPI in slave mode
SPCR |= bit (SPE);
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);
// get ready for an interrupt
pos = 0; // buffer empty
process_it = false;
// now turn on interrupts
SPI.attachInterrupt();
#endif
#if USEDISPLAY == 1 //Serial
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
#elif USEDISPLAY == 2 //Display
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27, if new version please use 0x3F instead.
lcd.init(); //initialize the lcd
lcd.backlight(); //open the backlight
#endif
PRINTS("SETUP Complete");
Serial.print("Color: ");
Serial.print(COLOR_LIST[0][0]);
Serial.print(COLOR_LIST[0][1]);
Serial.print(COLOR_LIST[0][2]);
} // End Setup
//--------------------------------------//
//Functions
//--------------------------------------//
//Convert char to byte
byte CtB(char value = '0'){
byte localbyte = 0;
localbyte = value-'0';
return localbyte;
}
//Set Lights
void setlightsS(byte color_select = 0) { //_S_olid full color from list
#if DEBUG >= 2
PRINTS("Lights On 1")
#endif
analogWrite(PIN_red, COLOR_LIST[color_select][0]);
analogWrite(PIN_green, COLOR_LIST[color_select][1]);
analogWrite(PIN_blue, COLOR_LIST[color_select][2]);
}
void setlightsB(byte color_select = 0, float color_bright = 255) { //color list with _B_rightness
#if DEBUG >= 2
PRINTS("Lights On 1")
#endif
analogWrite(PIN_red, COLOR_LIST[color_select][0] * color_bright);
analogWrite(PIN_green, COLOR_LIST[color_select][1] * color_bright);
analogWrite(PIN_blue, COLOR_LIST[color_select][2] * color_bright);
}
void setlightsM(byte redval = 0, byte greenval = 0, byte blueval = 0) { //Set color Manually
#if DEBUG >= 2
PRINTS("Lights On 3")
#endif
analogWrite(PIN_red, redval);
analogWrite(PIN_green, greenval);
analogWrite(PIN_blue, blueval);
}
void offlights() {
#if DEBUG >= 2
PRINTS("Lights Off")
#endif
analogWrite(PIN_red, 0);
analogWrite(PIN_green, 0);
analogWrite(PIN_blue, 0);
}
//Heart Beat
void heartbeat(byte color_select = 0) {
if (millis() >= nextPulse) {
// change the brightness for next time through the loop:
brightness = min(max(brightness + heartFade, 0.0), 1.0);
// reverse the direction of the fading at the ends of the fade:
if (brightness <= 0.0 || brightness >= 1.0) {
heartFade = -heartFade;
}
setlightsB(color_select, brightness*brightness);
nextPulse = millis() + heartRate;
}
}
void checkbuttons() {
bool button_val = false;
/*
String s1 = String(digitalRead(PIN_SPI_TEST_1));
String s2 = String(digitalRead(PIN_SPI_TEST_2));
String s3 = String(digitalRead(PIN_SPI_TEST_3));
Serial.println(s1 + s2 + s3);
*/
if (digitalRead(PIN_SPI_TEST_1) == LOW) { //Red Button, HB Red
if (buttondebounce == 0) {
buttoneval = true;
strncpy(COMMAND_BUFF,"C0C",10);
//strncpy(COMMAND_BUFF,"mHB2m",10);
#if DEBUG
PRINTS("Blue Button");
#endif
}
buttondebounce = 13;
}
if (digitalRead(PIN_SPI_TEST_2) == LOW) { //Yellow button, 120 BPM
if (buttondebounce == 0){
buttoneval = true;
strncpy(COMMAND_BUFF,"C1C",10);
//strncpy(COMMAND_BUFF,"S1200,1S",10);
#if DEBUG
PRINTS("Yellow Button");
#endif
}
buttondebounce = 12;
}
if (digitalRead(PIN_SPI_TEST_3) == LOW) { //Blue button, Hold Blue
if (buttondebounce == 0){
buttoneval = true;
strncpy(COMMAND_BUFF,"C2C",10);
#if DEBUG
PRINTS("Red Button");
#endif
}
buttondebounce = 11;
}
if (buttondebounce == 10){
buttondebounce = buttondebounce -1;
}
else if (buttondebounce > 0)
{
buttondebounce = buttondebounce -1;
}
}
#if SIM
#else
//--------------------------------------//
//SPI Handler
//--------------------------------------//
// SPI interrupt routine
ISR (SPI_STC_vect) {
byte c = SPDR; // grab byte from SPI Data Register
// add to buffer if room
if (pos < (sizeof (SPI_BUFF) - 1))
SPI_BUFF [pos++] = c;
// example: newline means time to process buffer
if (c == '\n')
process_it = true;
} // end of interrupt routine SPI_STC_vect
void checkSPI() {
if (process_it)
{
SPI_BUFF [pos] = 0;
PRINT("Buffer", SPI_BUFF);
COMMAND_BUFF = SPI_BUFF
pos = 0;
process_it = false;
} // end of flag set
}
#endif
//--------------------------------------//
//Main Loop
//--------------------------------------//
void loop()
{
previousMillis = currentMillis;
currentMillis = millis();
#if SIM
checkbuttons();
#else
checkSPI();
#endif
char firstchar = COMMAND_BUFF[0]; //first charactor says what to do
switch (firstchar) {
case ' ': //empty, no new commands
PRINTS("Empty");
break;
case 'X': //First boot
//PRINTS("X");
break;
case 'S': //Start flashing!
break;
case 'F':
break;
case 'm':
break;
case 'C':
setlightsS(CtB(COMMAND_BUFF[1]));
break;
case 'P':
break;
case 'v':
break;
default:
PRINTE2("Inv 1st Char");
break;
}
strncpy(COMMAND_BUFF,"X",10);
//mode time
switch (mode){
default:
break;
}
} //End Main Loop