#include <Arduino.h>
#include <esp_dmx.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <EEPROM.h> // Include EEPROM library
/* DMX pins */
int transmitPin = 17;
int receivePin = 16;
int enablePin = 21;
/* DMX port */
dmx_port_t dmxPort = 1;
/* DMX data storage */
byte data[DMX_PACKET_SIZE];
/* DMX connection status */
bool dmxIsConnected = false;
unsigned long lastUpdate = millis();
/* PWM pins */
const int pwmPins[] = {25, 26, 32, 33};
const int pwmChannelCount = sizeof(pwmPins) / sizeof(pwmPins[0]);
/* Test button */
const int testButtonPin = 14;
/* Save button */
const int saveButtonPin = 4; // Button for saving addresses
/* LED to indicate safety status */
const int safetyLedPin = 13; // Pin for safety indicator LED
/* EEPROM addresses */
const int EEPROM_DMX_START_ADDRESS = 0; // EEPROM address for DMX start channel
const int EEPROM_SAFETY_CHANNEL_ADDRESS = 1; // EEPROM address for safety channel
/* OLED Display */
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// I2C pins
int sda_pin = 27;
int scl_pin = 22;
// DMX start channel
byte lastDmxStartChannel = 1;
// Rotary Encoder
const int encoderPinA = 34;
const int encoderPinB = 35;
const int encoderButtonPin = 23;
volatile int encoderPos = 0;
bool buttonPressed = false;
bool doubleClickDetected = false;
// Safety channel
byte safetyChannel = 10;
bool safetyActive = false;
// Double-click detection variables
unsigned long lastButtonClick = 0;
bool firstClick = false;
const int doubleClickThreshold = 300; // Time in ms to detect a double click
// State to determine if we're adjusting the safety channel or DMX start channel
bool adjustSafetyChannel = false;
/* NTC Temperature Sensor */
const int ntcPin = 0; // Analog pin for NTC sensor
const float seriesResistor = 10000; // 10k ohm series resistor
const float nominalResistance = 10000; // 10k ohm thermistor
const float nominalTemperature = 24; // 25°C reference temperature
const float betaCoefficient = 3950; // Beta value for thermistor
const float referenceResistance = 10000; // 10k ohm reference resistor
// Function to calculate temperature from NTC sensor
float readTemperature() {
int adcValue = analogRead(ntcPin);
float resistance = seriesResistor / (1023.0 / adcValue - 1);
float steinhart;
steinhart = resistance / nominalResistance; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= betaCoefficient; // 1/B * ln(R/Ro)
steinhart += 1.0 / (nominalTemperature + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert to get temperature in Kelvin
steinhart -= 273.15; // Convert to Celsius
return steinhart;
}
// Function to save DMX start address and safety channel to EEPROM
void saveAddressesToEEPROM() {
// Save DMX start channel to EEPROM
EEPROM.write(EEPROM_DMX_START_ADDRESS, lastDmxStartChannel);
// Save safety channel to EEPROM
EEPROM.write(EEPROM_SAFETY_CHANNEL_ADDRESS, safetyChannel);
// Commit changes to ensure they are written to EEPROM
EEPROM.commit();
Serial.print("Saved DMX Start Channel: ");
Serial.println(lastDmxStartChannel);
Serial.print("Saved Safety Channel: ");
Serial.println(safetyChannel);
}
// Function to load DMX start address and safety channel from EEPROM
void loadAddressesFromEEPROM() {
// Read DMX start channel from EEPROM
lastDmxStartChannel = EEPROM.read(EEPROM_DMX_START_ADDRESS);
if (lastDmxStartChannel == 0xFF) { // Default value if EEPROM was never written
lastDmxStartChannel = 1;
}
// Read safety channel from EEPROM
safetyChannel = EEPROM.read(EEPROM_SAFETY_CHANNEL_ADDRESS);
if (safetyChannel == 0xFF) { // Default value if EEPROM was never written
safetyChannel = 10;
}
Serial.print("Loaded DMX Start Channel from EEPROM: ");
Serial.println(lastDmxStartChannel);
Serial.print("Loaded Safety Channel from EEPROM: ");
Serial.println(safetyChannel);
}
void IRAM_ATTR handleEncoder() {
int newEncoderPinA = digitalRead(encoderPinA);
int newEncoderPinB = digitalRead(encoderPinB);
if (newEncoderPinA != newEncoderPinB) {
encoderPos++;
} else {
encoderPos--;
}
}
void IRAM_ATTR handleButton() {
unsigned long currentTime = millis();
// Detect double click
if (currentTime - lastButtonClick < doubleClickThreshold && firstClick) {
doubleClickDetected = true;
firstClick = false;
} else {
firstClick = true;
lastButtonClick = currentTime;
}
buttonPressed = true;
}
void setup() {
Serial.begin(115200);
// Set I2C pins
Wire.setPins(sda_pin, scl_pin);
Wire.begin();
// Initialize EEPROM with size 2 (1 byte for DMX start, 1 byte for safety channel)
EEPROM.begin(2);
// Load previously saved addresses from EEPROM
loadAddressesFromEEPROM();
// Setup OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("OLED failed!"));
for (;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// Setup DMX
dmx_config_t config = DMX_CONFIG_DEFAULT;
dmx_personality_t personalities[] = {
{1, "Default Personality"}
};
int personality_count = 1;
dmx_driver_install(dmxPort, &config, personalities, personality_count);
// Set DMX pins
dmx_set_pin(dmxPort, transmitPin, receivePin, enablePin);
// Set PWM pins as output
for (int i = 0; i < pwmChannelCount; i++) {
pinMode(pwmPins[i], OUTPUT);
}
// Setup test button
pinMode(testButtonPin, INPUT_PULLUP);
// Setup save button
pinMode(saveButtonPin, INPUT_PULLUP); // Button for saving addresses
// Setup safety indicator LED
pinMode(safetyLedPin, OUTPUT); // Set the safety LED pin as output
// Set rotary encoder pins
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);
pinMode(encoderButtonPin, INPUT_PULLUP);
// Attach interrupts for the encoder
attachInterrupt(digitalPinToInterrupt(encoderPinA), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderButtonPin), handleButton, FALLING);
}
void loop() {
dmx_packet_t packet;
/* Check for DMX packet */
if (dmx_receive(dmxPort, &packet, DMX_TIMEOUT_TICK)) {
unsigned long now = millis();
if (!packet.err) {
if (!dmxIsConnected) {
Serial.println("DMX is connected!");
dmxIsConnected = true;
}
/* Read DMX data */
dmx_read(dmxPort, data, packet.size);
// Update safety channel state
if (data[safetyChannel - 1] > 127) {
safetyActive = true;
} else {
safetyActive = false;
}
// Control PWM outputs based on DMX values
for (int i = 0; i < pwmChannelCount; i++) {
int dmxValue = data[i + lastDmxStartChannel];
if (safetyActive) {
if (dmxValue > 128) {
digitalWrite(pwmPins[i], HIGH); // Turn output HIGH
} else {
digitalWrite(pwmPins[i], LOW); // Turn output LOW
}
} else {
digitalWrite(pwmPins[i], LOW); // Turn all outputs OFF if safety is inactive
}
}
// Control safety indicator LED based on safetyActive state
if (safetyActive) {
digitalWrite(safetyLedPin, HIGH); // Turn LED on if safety is active
} else {
digitalWrite(safetyLedPin, LOW); // Turn LED off if safety is inactive
}
// Periodic update for debugging
if (now - lastUpdate > 1000) {
Serial.printf("Start channel: %d, Safety channel value: %d\n", lastDmxStartChannel, data[safetyChannel - 1]);
lastUpdate = now;
}
} else {
Serial.println("A DMX error occurred.");
}
} else if (dmxIsConnected) {
Serial.println("DMX was disconnected.");
dmx_driver_delete(dmxPort);
while (true) yield();
}
// Display DMX start channel, safety status, and temperature on OLED
float temperature = readTemperature(); // Read temperature from NTC sensor
display.clearDisplay();
display.setCursor(0, 0);
display.print("Mode: ");
display.println(adjustSafetyChannel ? "Safety" : "DMX");
display.print("DMX CN: ");
display.println(lastDmxStartChannel);
display.print("Safe CN: ");
display.println(safetyChannel);
display.print("A: ");
display.println(safetyActive ? "Yes" : "No");
display.setCursor(0, 48);
display.print("Temp: ");
display.print(temperature);
display.println(" C");
display.display();
// Handle rotary encoder input
if (encoderPos != 0) {
if (adjustSafetyChannel) {
safetyChannel += encoderPos;
if (safetyChannel < 1) safetyChannel = 1;
if (safetyChannel > 512) safetyChannel = 512;
} else {
lastDmxStartChannel += encoderPos;
if (lastDmxStartChannel < 1) lastDmxStartChannel = 1;
if (lastDmxStartChannel > 512) lastDmxStartChannel = 512;
}
encoderPos = 0;
}
// Handle button press for switching modes
if (buttonPressed) {
if (doubleClickDetected) {
// Toggle between adjusting DMX start channel and safety channel
adjustSafetyChannel = !adjustSafetyChannel;
Serial.println(adjustSafetyChannel ? "Switching to Safety Channel Mode" : "Switching to DMX Start Channel Mode");
doubleClickDetected = false;
}
buttonPressed = false;
}
// Handle save button press for saving both addresses to EEPROM
if (digitalRead(saveButtonPin) == LOW) {
saveAddressesToEEPROM(); // Save both DMX and safety addresses to EEPROM
delay(500); // Debounce delay to prevent multiple triggers
}
// Test button to toggle PWM channels
if (digitalRead(testButtonPin) == LOW) {
for (int i = 0; i < pwmChannelCount; i++) {
digitalWrite(pwmPins[i], HIGH);
}
} else {
for (int i = 0; i < pwmChannelCount; i++) {
digitalWrite(pwmPins[i], LOW);
}
}
}