|
|
|
/*
|
|
|
|
RiTCh Lightshow
|
|
|
|
|
|
|
|
Simple Arduino based MP3/DMX player which can be triggered by buttons.
|
|
|
|
|
|
|
|
(c)2024 by H. Wirtz <wirtz@parasitstudio.de>
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
TODO:
|
|
|
|
-
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <SoftwareSerial.h>
|
|
|
|
#include <DFRobotDFPlayerMini.h>
|
|
|
|
#include "DmxSimple.h"
|
|
|
|
#include <EEPROM.h>
|
|
|
|
#include "looper.h"
|
|
|
|
#include <RGBConverter.h>
|
|
|
|
|
|
|
|
#define DEBUG 1
|
|
|
|
|
|
|
|
#define SENSOR_SCHED 10
|
|
|
|
#define WORKER_SCHED 100
|
|
|
|
#define LED_SCHED 50
|
|
|
|
#define LEVEL_CHECK_SCHED 100
|
|
|
|
#define DEBUG_SCHED 500
|
|
|
|
|
|
|
|
#define BUTTON_LONG_PRESS 1000
|
|
|
|
#define MIN_TIME_SWITCH_PRESSED 50
|
|
|
|
#define POTI_DEAD_ZONE 3
|
|
|
|
#define LED_NORMAL_BRIGHTNESS 180
|
|
|
|
#define LED_PLAY_BRIGHTNESS 20
|
|
|
|
#define DMX_MAX_CHANNEL 512
|
|
|
|
#define MAX_VOL_LEVEL 30
|
|
|
|
#define MAX_DMX_LEVEL 255
|
|
|
|
#define MAX_DMX_SPOTS 2
|
|
|
|
#define DMX_FOG_MACHINE_ADDRESS 100
|
|
|
|
|
|
|
|
#define DMX_BRIGHTNESS 0
|
|
|
|
#define DMX_FADE_TIME 1
|
|
|
|
#define DMX_HOLD_TIME 2
|
|
|
|
#define DMX_FOG_LEVEL 3
|
|
|
|
|
|
|
|
#define MIN_DMX_FADE_TIME 1
|
|
|
|
#define MAX_DMX_FADE_TIME 10
|
|
|
|
#define MIN_DMX_HOLD_TIME 5
|
|
|
|
#define MAX_DMX_HOLD_TIME 15
|
|
|
|
|
|
|
|
// Arduino pins
|
|
|
|
#define POTI1_PIN A1
|
|
|
|
#define POTI2_PIN A2
|
|
|
|
#define POTI3_PIN A3
|
|
|
|
#define POTI4_PIN A4
|
|
|
|
#define BUTTON1_PIN 4
|
|
|
|
#define BUTTON2_PIN 5
|
|
|
|
#define BUTTON3_PIN 6
|
|
|
|
#define BUTTON4_PIN 7
|
|
|
|
#define DMX_PIN 8
|
|
|
|
#define LED_PIN 9
|
|
|
|
#define MP3_RX_PIN 10
|
|
|
|
#define MP3_TX_PIN 11
|
|
|
|
#define BUSY_PIN 12
|
|
|
|
|
|
|
|
uint8_t button_state;
|
|
|
|
uint32_t button_time[4] = { 0, 0, 0, 0 };
|
|
|
|
uint8_t poti_level[4] = { 0, 0, 0, 0 };
|
|
|
|
uint8_t brightness = LED_NORMAL_BRIGHTNESS;
|
|
|
|
|
|
|
|
enum {
|
|
|
|
NORMAL,
|
|
|
|
WHITE,
|
|
|
|
OFF
|
|
|
|
};
|
|
|
|
bool fog_state = false;
|
|
|
|
uint8_t light_state = NORMAL;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
uint8_t dmx_type = 4;
|
|
|
|
uint16_t address = 0;
|
|
|
|
uint16_t steps = 0;
|
|
|
|
float diff[4] = { 0.0, 0.0, 0.0, 0.0 };
|
|
|
|
float values[4] = { 0.0, 0.0, 0.0, 0.0 };
|
|
|
|
} dmx_spot;
|
|
|
|
|
|
|
|
dmx_spot spot[MAX_DMX_SPOTS];
|
|
|
|
|
|
|
|
// schedular
|
|
|
|
looper sched;
|
|
|
|
|
|
|
|
// setup audio card
|
|
|
|
SoftwareSerial mySoftwareSerial(MP3_RX_PIN, MP3_TX_PIN); // RX, TX
|
|
|
|
DFRobotDFPlayerMini myDFPlayer;
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
// THREADS
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
void button_check(void) {
|
|
|
|
byte i;
|
|
|
|
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
uint16_t b = button(i);
|
|
|
|
|
|
|
|
if (b > BUTTON_LONG_PRESS && !bitRead(button_state, i)) {
|
|
|
|
// long press
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Button["));
|
|
|
|
Serial.print(i + 1, DEC);
|
|
|
|
Serial.println(F("]: long"));
|
|
|
|
#endif
|
|
|
|
button_time[i] = 0;
|
|
|
|
do_button_long(i);
|
|
|
|
} else if (b > MIN_TIME_SWITCH_PRESSED && !bitRead(button_state, i)) {
|
|
|
|
// short press
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Button["));
|
|
|
|
Serial.print(i + 1, DEC);
|
|
|
|
Serial.println(F("]: short"));
|
|
|
|
#endif
|
|
|
|
button_time[i] = 0;
|
|
|
|
do_button_short(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void level_check(void) {
|
|
|
|
uint8_t i;
|
|
|
|
uint8_t poti_lvl;
|
|
|
|
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
poti_lvl = map(analogRead(poti_pin_by_number(i)), 15, 1023, 0, 255);
|
|
|
|
if (poti_lvl < poti_level[i] - POTI_DEAD_ZONE || poti_lvl > poti_level[i] + POTI_DEAD_ZONE) {
|
|
|
|
poti_level[i] = poti_lvl;
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Poti["));
|
|
|
|
Serial.print(i + 1, DEC);
|
|
|
|
Serial.print(F("]: "));
|
|
|
|
Serial.println(poti_level[i]);
|
|
|
|
#endif
|
|
|
|
do_level(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_led(void) {
|
|
|
|
analogWrite(LED_PIN, LED_PLAY_BRIGHTNESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
void worker(void) {
|
|
|
|
// DMX auto Fade
|
|
|
|
|
|
|
|
if (light_state != NORMAL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (uint8_t s = 0; s < MAX_DMX_SPOTS; s++) {
|
|
|
|
uint8_t diff_counter = 0;
|
|
|
|
|
|
|
|
if (spot[s].steps == 0) {
|
|
|
|
for (uint8_t i = 0; i < spot[s].dmx_type; i++) {
|
|
|
|
if (spot[s].diff[i] != 0.0) {
|
|
|
|
diff_counter++;
|
|
|
|
spot[s].diff[i] = 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (diff_counter != 0) {
|
|
|
|
// start hold timer
|
|
|
|
int8_t start = MIN_DMX_FADE_TIME + MIN_DMX_FADE_TIME * poti_level[1] / 255.0 * 4;
|
|
|
|
spot[s].steps = random(start, start + ((MAX_DMX_HOLD_TIME - start) * poti_level[DMX_HOLD_TIME] / 255.0)) * 1000 / WORKER_SCHED;
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Spot "));
|
|
|
|
Serial.print(s, DEC);
|
|
|
|
Serial.print(F(" holding for "));
|
|
|
|
Serial.print(spot[s].steps * WORKER_SCHED / 1000, DEC);
|
|
|
|
Serial.println(F(" seconds"));
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
// new random values
|
|
|
|
RGBConverter color_converter;
|
|
|
|
uint8_t rgb[3];
|
|
|
|
int8_t start = MIN_DMX_FADE_TIME + MIN_DMX_FADE_TIME * poti_level[1] / 255.0 * 4;
|
|
|
|
|
|
|
|
spot[s].steps = random(start, start + ((MAX_DMX_FADE_TIME - start) * poti_level[DMX_FADE_TIME] / 255.0)) * 1000 / WORKER_SCHED;
|
|
|
|
color_converter.hsvToRgb(random(0, 1024) / 1024.0, 1.0, 1.0, rgb);
|
|
|
|
for (uint8_t i = 0; i < 3; i++)
|
|
|
|
spot[s].diff[i] = (rgb[i] - spot[s].values[i]) / spot[s].steps;
|
|
|
|
spot[s].diff[3] = (random(0, 128) - spot[3].values[3]) / 255.0;
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Spot "));
|
|
|
|
Serial.print(s, DEC);
|
|
|
|
Serial.print(F(" changing color for "));
|
|
|
|
Serial.print(spot[s].steps * WORKER_SCHED / 1000, DEC);
|
|
|
|
Serial.println(F(" seconds"));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (uint8_t i = 0; i < spot[s].dmx_type; i++) {
|
|
|
|
spot[s].values[i] += spot[s].diff[i];
|
|
|
|
DmxSimple.write(spot[s].address + i, uint8_t(spot[s].values[i] * poti_level[DMX_BRIGHTNESS] / 255.0 + 0.5));
|
|
|
|
#ifdef DEBUG
|
|
|
|
/* Serial.print(F("Spot "));
|
|
|
|
Serial.print(s, DEC);
|
|
|
|
Serial.print(F(" color "));
|
|
|
|
Serial.print(i, DEC);
|
|
|
|
Serial.print(F(" step "));
|
|
|
|
Serial.print(spot[s].steps, DEC);
|
|
|
|
Serial.print(F(" diff "));
|
|
|
|
Serial.print(spot[s].diff[i], 4);
|
|
|
|
Serial.print(F(" value "));
|
|
|
|
Serial.println(spot[s].values[i], 4);*/
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spot[s].steps--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fog machine
|
|
|
|
if (fog_state == true) {
|
|
|
|
DmxSimple.write(DMX_FOG_MACHINE_ADDRESS, poti_level[DMX_FOG_LEVEL]);
|
|
|
|
} else {
|
|
|
|
DmxSimple.write(DMX_FOG_MACHINE_ADDRESS, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void test_worker(void) {
|
|
|
|
uint8_t start = MIN_DMX_FADE_TIME + MIN_DMX_FADE_TIME * poti_level[1] / 255.0 * 4;
|
|
|
|
uint8_t fade_time = random(start, start + ((MAX_DMX_FADE_TIME - start) * poti_level[1] / 255.0));
|
|
|
|
Serial.print("start:");
|
|
|
|
Serial.println(start, DEC);
|
|
|
|
Serial.print("MAX_DMX_FADE_TIME");
|
|
|
|
Serial.println(MAX_DMX_FADE_TIME, DEC);
|
|
|
|
Serial.print("POTI1:");
|
|
|
|
Serial.println(poti_level[1], DEC);
|
|
|
|
Serial.print("POTI1/255:");
|
|
|
|
Serial.println(poti_level[1] / 255.0, 3);
|
|
|
|
Serial.print("Fade-Time:");
|
|
|
|
Serial.println(fade_time, DEC);
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
// HELPER FUNCTIONS
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
void do_level(uint8_t p) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("POTI "));
|
|
|
|
Serial.print(p);
|
|
|
|
Serial.print(F(": "));
|
|
|
|
Serial.println(poti_level[p]);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
switch (p) {
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Fog: "));
|
|
|
|
Serial.print(poti_level[DMX_FOG_LEVEL] / 255.0 * 100.0);
|
|
|
|
Serial.println(F("%"));
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_button_long(uint8_t b) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("BUTTON "));
|
|
|
|
Serial.print(b);
|
|
|
|
Serial.println(F(" LONG"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
switch (b) {
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_button_short(uint8_t b) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("BUTTON "));
|
|
|
|
Serial.print(b);
|
|
|
|
Serial.println(F(" SHORT"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
switch (b) {
|
|
|
|
case 0:
|
|
|
|
if (light_state == OFF || light_state == WHITE) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Light: NORMAL"));
|
|
|
|
#endif
|
|
|
|
light_state = NORMAL;
|
|
|
|
} else {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Light: OFF"));
|
|
|
|
#endif
|
|
|
|
light_state = OFF;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (light_state == OFF || light_state == NORMAL) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Light: WHITE"));
|
|
|
|
#endif
|
|
|
|
light_state = WHITE;
|
|
|
|
for (uint8_t s = 0; s < MAX_DMX_SPOTS; s++) {
|
|
|
|
for (uint8_t i = 0; i < spot[s].dmx_type; i++)
|
|
|
|
DmxSimple.write(spot[s].address + i, 255);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Light: OFF"));
|
|
|
|
#endif
|
|
|
|
light_state = OFF;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
fog_state = !fog_state;
|
|
|
|
#ifdef DEBUG
|
|
|
|
if (fog_state)
|
|
|
|
Serial.println(F("Fog: ON"));
|
|
|
|
else
|
|
|
|
Serial.println(F("Fog: OFF"));
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (light_state == OFF) {
|
|
|
|
for (uint8_t s = 0; s < MAX_DMX_SPOTS; s++) {
|
|
|
|
for (uint8_t i = 0; i < spot[s].dmx_type; i++)
|
|
|
|
DmxSimple.write(spot[s].address + i, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t button(byte button_nr) {
|
|
|
|
byte button = button_pin_by_number(button_nr);
|
|
|
|
|
|
|
|
if (digitalRead(button) == LOW) {
|
|
|
|
if (!bitRead(button_state, button_nr)) {
|
|
|
|
bitSet(button_state, button_nr);
|
|
|
|
button_time[button_nr] = millis();
|
|
|
|
} else {
|
|
|
|
if (button_time[button_nr] > millis())
|
|
|
|
return (0xffff - button_time[button_nr] + millis()); // overflow-protection
|
|
|
|
else
|
|
|
|
return (millis() - button_time[button_nr]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (bitRead(button_state, button_nr)) {
|
|
|
|
bitWrite(button_state, button_nr, 0);
|
|
|
|
if (button_time[button_nr] > millis())
|
|
|
|
return (0xffff - button_time[button_nr] + millis()); // overflow-protection
|
|
|
|
else
|
|
|
|
return (millis() - button_time[button_nr]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t button_pin_by_number(byte n) {
|
|
|
|
switch (n) {
|
|
|
|
case 0:
|
|
|
|
return (BUTTON1_PIN);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
return (BUTTON2_PIN);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
return (BUTTON3_PIN);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
return (BUTTON4_PIN);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t poti_pin_by_number(byte n) {
|
|
|
|
switch (n) {
|
|
|
|
case 0:
|
|
|
|
return (POTI1_PIN);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
return (POTI2_PIN);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
return (POTI3_PIN);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
return (POTI4_PIN);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
// SYSTEM
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
void setup() {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.begin(9600);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
#ifdef __AVR_ATmega32U4__
|
|
|
|
while (!Serial) {
|
|
|
|
; // wait for serial port to connect. Needed for Leonardo only
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("<setup begin>"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mySoftwareSerial.begin(9600);
|
|
|
|
|
|
|
|
randomSeed(analogRead(A6));
|
|
|
|
|
|
|
|
// setup poti pins
|
|
|
|
pinMode(POTI1_PIN, INPUT_PULLUP);
|
|
|
|
pinMode(POTI2_PIN, INPUT_PULLUP);
|
|
|
|
pinMode(POTI3_PIN, INPUT_PULLUP);
|
|
|
|
pinMode(POTI4_PIN, INPUT_PULLUP);
|
|
|
|
|
|
|
|
// setup button pins
|
|
|
|
pinMode(BUTTON1_PIN, INPUT_PULLUP);
|
|
|
|
pinMode(BUTTON2_PIN, INPUT_PULLUP);
|
|
|
|
pinMode(BUTTON3_PIN, INPUT_PULLUP);
|
|
|
|
pinMode(BUTTON4_PIN, INPUT_PULLUP);
|
|
|
|
|
|
|
|
// setup mp3 busy pin
|
|
|
|
pinMode(BUSY_PIN, INPUT_PULLUP);
|
|
|
|
|
|
|
|
// setup LED pin
|
|
|
|
pinMode(LED_PIN, OUTPUT);
|
|
|
|
|
|
|
|
// setup DMX
|
|
|
|
DmxSimple.usePin(DMX_PIN);
|
|
|
|
DmxSimple.maxChannel(DMX_MAX_CHANNEL);
|
|
|
|
|
|
|
|
// DMX setup
|
|
|
|
spot[0].address = 1;
|
|
|
|
spot[1].address = 5;
|
|
|
|
|
|
|
|
// setup audio card
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
for (uint8_t n = 0; n < 3; n++) {
|
|
|
|
if (!myDFPlayer.begin(mySoftwareSerial)) { //Use softwareSerial to communicate with mp3.
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Unable use DFPlayer: "));
|
|
|
|
Serial.println(n, DEC);
|
|
|
|
#endif
|
|
|
|
for (uint8_t i = 0; i < 3; i++) {
|
|
|
|
analogWrite(LED_PIN, LED_PLAY_BRIGHTNESS);
|
|
|
|
delay(100);
|
|
|
|
analogWrite(LED_PIN, LED_PLAY_BRIGHTNESS);
|
|
|
|
delay(100);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("DFPlayer Mini online."));
|
|
|
|
#endif
|
|
|
|
myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
|
|
|
|
|
|
|
|
// init schedular
|
|
|
|
sched.addJob(button_check, SENSOR_SCHED);
|
|
|
|
sched.addJob(level_check, LEVEL_CHECK_SCHED);
|
|
|
|
sched.addJob(worker, WORKER_SCHED);
|
|
|
|
sched.addJob(show_led, LED_SCHED);
|
|
|
|
//sched.addJob(test_worker, 1000);
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("<setup end>"));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void loop() {
|
|
|
|
sched.scheduler();
|
|
|
|
}
|