You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RiTCh_Lightshow/RiTCh_Lightshow.ino

569 lines
14 KiB

/*
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 DMX_COLOR_CHANGER_SCHED 50
#define LED_SCHED 50
#define LEVEL_CHECK_SCHED 50
#define DEBUG_SCHED 500
#define BUTTON_LONG_PRESS 1000
#define MIN_TIME_SWITCH_PRESSED 30
#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 DMX_FOG_MACHINE_ADDRESS 100
#define MAX_RAND_WHITE_LEVEL 150
#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 15
#define MIN_DMX_HOLD_TIME 3
#define MAX_DMX_HOLD_TIME 30
#define ON_OFF_FADE_TIME 1.5
// 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 = OFF;
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;
#define MAX_DMX_SPOTS 6
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 dmx_auto_colorchanger(void) {
// DMX auto Fade
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
if (light_state != NORMAL)
continue;
int8_t start = MIN_DMX_HOLD_TIME + MIN_DMX_HOLD_TIME * poti_level[DMX_HOLD_TIME] / 255.0 * MIN_DMX_HOLD_TIME;
spot[s].steps = random(start, start + ((MAX_DMX_HOLD_TIME - start) * poti_level[DMX_HOLD_TIME] / 255.0)) * 1000 / DMX_COLOR_CHANGER_SCHED;
#ifdef DEBUG
Serial.print(F("Spot "));
Serial.print(s, DEC);
Serial.print(F(" holding for "));
Serial.print(spot[s].steps * DMX_COLOR_CHANGER_SCHED / 1000, DEC);
Serial.println(F(" seconds"));
#endif
} else {
// new random values
if (light_state != NORMAL)
continue;
RGBConverter color_converter;
uint8_t rgb[3];
uint8_t start = MIN_DMX_FADE_TIME + MIN_DMX_FADE_TIME * poti_level[DMX_FADE_TIME] / 255.0 * MIN_DMX_FADE_TIME;
color_converter.hsvToRgb(random(0, 1024) / 1024.0, 1.0, 1.0, rgb);
if (spot[s].dmx_type > 3)
set_rgbw(&spot[s], random(start, start + ((MAX_DMX_FADE_TIME - start) * poti_level[DMX_FADE_TIME] / 255.0)), rgb, random(0, MAX_RAND_WHITE_LEVEL));
else
set_rgb(&spot[s], random(start, start + ((MAX_DMX_FADE_TIME - start) * poti_level[DMX_FADE_TIME] / 255.0)), rgb);
#ifdef DEBUG
Serial.print(F("Spot "));
Serial.print(s, DEC);
Serial.print(F(" changing color to R["));
Serial.print(spot[s].values[0], 2);
Serial.print(F("] G["));
Serial.print(spot[s].values[1], 2);
Serial.print(F("] B["));
Serial.print(spot[s].values[2], 2);
if (spot[s].dmx_type > 3) {
Serial.print(F("] W["));
Serial.print(spot[s].values[3], 2);
}
Serial.print(F("] for "));
Serial.print(spot[s].steps * DMX_COLOR_CHANGER_SCHED / 1000, DEC);
Serial.println(F(" seconds"));
#endif
}
} /* else {
for (uint8_t i = 0; i < spot[s].dmx_type; i++) {
if (spot[s].diff[i] != 0.0) {
spot[s].values[i] += spot[s].diff[i];
if (spot[s].values[i] > 255.0)
spot[s].values[i] = 255.0;
else if (spot[s].values[i] < 0)
spot[s].values[i] = 0.0;
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--;
}
dmx_commit();
// 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 dmx_commit(void) {
float l = pow(poti_level[DMX_BRIGHTNESS] / 255.0, 3);
for (uint8_t s = 0; s < MAX_DMX_SPOTS; s++) {
for (uint8_t i = 0; i < spot[s].dmx_type; i++) {
//if (spot[s].diff[i] != 0.0) {
spot[s].values[i] += spot[s].diff[i];
if (spot[s].values[i] > 255.0)
spot[s].values[i] = 255.0;
else if (spot[s].values[i] < 0)
spot[s].values[i] = 0.0;
DmxSimple.write(spot[s].address + i, uint8_t(spot[s].values[i] * l + 0.5));
}
// }
}
}
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++) {
uint8_t rgb[3] = { 255, 255, 255 };
for (uint8_t i = 0; i < spot[s].dmx_type; i++)
if (spot[s].dmx_type > 3)
set_rgbw(&spot[s], ON_OFF_FADE_TIME, rgb, 255);
else
set_rgb(&spot[s], ON_OFF_FADE_TIME, rgb);
}
} 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++) {
uint8_t rgb[3] = { 0, 0, 0 };
for (uint8_t i = 0; i < spot[s].dmx_type; i++)
if (spot[s].dmx_type > 3)
set_rgbw(&spot[s], ON_OFF_FADE_TIME, rgb, 0);
else
set_rgb(&spot[s], ON_OFF_FADE_TIME, rgb);
}
}
}
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);
}
void set_rgbw(dmx_spot* spot, float fade, uint8_t* rgb, uint8_t w) {
spot->steps = fade * 1000 / DMX_COLOR_CHANGER_SCHED;
spot->diff[3] = (w - spot->values[3]) / spot->steps;
set_rgb(spot, fade, rgb);
}
void set_rgb(dmx_spot* spot, float fade, uint8_t* rgb) {
spot->steps = fade * 1000 / DMX_COLOR_CHANGER_SCHED;
for (uint8_t i = 0; i < 3; i++)
spot->diff[i] = (rgb[i] - spot->values[i]) / spot->steps;
}
//--------------------------------------------------------------------------------
// SYSTEM
//--------------------------------------------------------------------------------
void setup() {
#ifdef DEBUG
Serial.begin(9600);
#endif
#ifdef __AVR_ATmega32U4__
for (uint8_t i = 0; i <= 10; i++) {
if (!Serial)
delay(100);
else
break;
}
#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; // Stairville LED Par56 MKII RGBA 10mm SI https://images.thomann.de/pics/atg/atgdata/document/manual/274646_c_274644_274646_375069_v3_de_online.pdf
spot[1].address = 5; // Stairville LED Par56 MKII RGBA 10mm SI
DmxSimple.write(9, 0); // Init PAR64, channel: 9, DIP 1: on, DIP 4: on, DIP 8: off
spot[2].address = 10; // Stairville PAR 64 https://images.thomann.de/pics/atg/atgdata/document/manual/c_193245_v2_r3_de_online.pdf
DmxSimple.write(15, 41); // Init LED Bar for 3-segment-mode, channel: 15
spot[3].address = 17; // Stairville LED BAR RGB 252 - Segment 1 https://images.thomann.de/pics/prod/255346_manual.pdf
spot[4].address = 20; // Stairville LED BAR RGB 252 - Segment 2
spot[5].address = 23; // Stairville LED BAR RGB 252 - Segment 3
// 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(dmx_auto_colorchanger, DMX_COLOR_CHANGER_SCHED);
sched.addJob(show_led, LED_SCHED);
//sched.addJob(test_worker, 1000);
#ifdef DEBUG
Serial.println(F("<setup end>"));
#endif
}
void loop() {
sched.scheduler();
}