From 16074452e7d07d0463ae9ec1ee106185fc4bfb32 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 28 Mar 2026 07:48:16 +0000 Subject: [PATCH 01/19] Add runtime config of DMX output pin --- wled00/cfg.cpp | 6 ++++++ wled00/const.h | 7 +------ wled00/data/settings_sync.htm | 9 ++++++++- wled00/fcn_declare.h | 2 +- wled00/pin_manager.h | 2 +- wled00/set.cpp | 3 +++ wled00/wled.cpp | 5 +---- wled00/xml.cpp | 11 +++++++---- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d01f83ad54..fe6bbafc7e 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -607,6 +607,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { tdd = if_live[F("timeout")] | -1; if (tdd >= 0) realtimeTimeoutMs = tdd * 100; + #ifdef WLED_ENABLE_DMX + CJSON(dmxOutputPin, if_live_dmx[F("dmxOutputPin")]); + #endif #ifdef WLED_ENABLE_DMX_INPUT CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]); CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]); @@ -1124,6 +1127,9 @@ void serializeConfig(JsonObject root) { if_live_dmx[F("addr")] = DMXAddress; if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx["mode"] = DMXMode; + #ifdef WLED_ENABLE_DMX + if_live_dmx[F("dmxOutputPin")] = dmxOutputPin; + #endif #ifdef WLED_ENABLE_DMX_INPUT if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin; if_live_dmx[F("inputTxPin")] = dmxInputReceivePin; diff --git a/wled00/const.h b/wled00/const.h index e49dd2900a..4b92963215 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -652,12 +652,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); // Defaults pins, type and counts to configure LED output #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) - #ifdef WLED_ENABLE_DMX - #define DEFAULT_LED_PIN 1 - #warning "Compiling with DMX. The default LED pin has been changed to pin 1." - #else - #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board - #endif + #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board #else #if defined(WLED_USE_ETHERNET) #define DEFAULT_LED_PIN 4 // GPIO4 seems to be a "safe bet" for all known ethernet boards (issue #5155) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 43baaf6718..a4a76f8805 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -50,7 +50,10 @@ } function hideDMXInput(){gId("dmxInput").style.display="none";} function hideNoDMXInput(){gId("dmxInputOff").style.display="none";} - function hideNoDMXOutput(){gId("dmxOnOffOutput").style.display="none";} + function hideNoDMXOutput(){ + gId("dmxOutputOff").style.display="none"; + gId("dmxOutput").style.display="inline"; + } @@ -180,6 +183,10 @@

Wired DMX Input Pins

DMX Enable: RE+DE
DMX Port:
+

This firmware build does not include DMX Input support.
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ffb2c1202f..f31c27b6bb 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -92,7 +92,7 @@ typedef struct WiFiConfig { } wifi_config; //dmx_output.cpp -void initDMXOutput(); +void initDMXOutput(int outputPin); void handleDMXOutput(); //dmx_input.cpp diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 5f774bb47d..cbc47ce2cc 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -43,7 +43,7 @@ enum struct PinOwner : uint8_t { Relay = 0x87, // 'Rly' == Relay pin from configuration SPI_RAM = 0x88, // 'SpiR' == SPI RAM DebugOut = 0x89, // 'Dbg' == debug output always IO1 - DMX = 0x8A, // 'DMX' == hard-coded to IO2 + DMX = 0x8A, // 'DMX' == DMX output via serial HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32) DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial diff --git a/wled00/set.cpp b/wled00/set.cpp index 925f273aee..b338b6b02a 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -474,6 +474,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) t = request->arg(F("WO")).toInt(); if (t >= -255 && t <= 255) arlsOffset = t; +#ifdef WLED_ENABLE_DMX + dmxOutputPin = request->arg(F("IDMO")).toInt(); +#endif #ifdef WLED_ENABLE_DMX_INPUT dmxInputTransmitPin = request->arg(F("IDMT")).toInt(); dmxInputReceivePin = request->arg(F("IDMR")).toInt(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 87b6388b97..db70592a1e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -443,9 +443,6 @@ void WLED::setup() #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) PinManager::allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif -#ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin - PinManager::allocatePin(2, true, PinOwner::DMX); -#endif DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize()); @@ -559,7 +556,7 @@ void WLED::setup() } #endif #ifdef WLED_ENABLE_DMX - initDMXOutput(); + initDMXOutput(dmxOutputPin); #endif #ifdef WLED_ENABLE_DMX_INPUT dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 0d3468f9ea..cd35121753 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -122,9 +122,11 @@ static void appendGPIOinfo(Print& settingsScript) } } #ifdef WLED_ENABLE_DMX - if (!firstPin) settingsScript.print(','); - settingsScript.print(2); // DMX hardcoded pin - firstPin = false; + if (dmxOutputPin > 0) { + if (!firstPin) settingsScript.print(','); + settingsScript.print(dmxOutputPin); // DMX output pin + firstPin = false; + } #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) if (!firstPin) settingsScript.print(','); @@ -493,7 +495,8 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); #ifdef WLED_ENABLE_DMX - settingsScript.print(SET_F("hideNoDMXOutput();")); // hide "not compiled in" message + settingsScript.print(SET_F("hideNoDMXOutput();")); // hide "not compiled in" message, show output pin field + printSetFormValue(settingsScript,SET_F("IDMO"), dmxOutputPin); #endif #ifndef WLED_ENABLE_DMX_INPUT settingsScript.print(SET_F("hideDMXInput();")); // hide "dmx input" settings From fb8cb5fcb2ff6838234dadd12dd802a62d10b94e Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 28 Mar 2026 07:56:14 +0000 Subject: [PATCH 02/19] Provide a unified interface for DMX output --- wled00/dmx_output.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ wled00/wled.h | 13 +++---------- 2 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 wled00/dmx_output.h diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h new file mode 100644 index 0000000000..d08250443d --- /dev/null +++ b/wled00/dmx_output.h @@ -0,0 +1,44 @@ +#ifndef DMX_OUTPUT_H +#define DMX_OUTPUT_H + +/** + * Unified DMX Output class for all platforms. + * + * On ESP8266: wraps DMXESPSerial (bit-banged DMX via Serial1) + * On ESP32: uses the esp_dmx library + */ + +#if defined(ESP8266) +#include "src/dependencies/dmx/ESPDMX.h" + +class DMXOutput +{ +public: + void init(uint8_t outputPin); + void write(int channel, uint8_t value); + void update(); +private: + DMXESPSerial _dmx; +}; + +#else // ESP32 + +#include + +class DMXOutput +{ +public: + void init(uint8_t outputPin); + void write(int channel, uint8_t value); + void update(); +private: + byte dmxdata[DMX_PACKET_SIZE]; + /* The ESP32 has either 2 or 3 UART ports. + Port 0 is typically used for Serial Monitor, + so we use port 1. */ + dmx_port_t dmxPort = 1; +}; + +#endif // ESP8266 / ESP32 + +#endif // DMX_OUTPUT_H diff --git a/wled00/wled.h b/wled00/wled.h index adc5c3d4e7..82aa1afa42 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -142,11 +142,7 @@ #endif #ifdef WLED_ENABLE_DMX - #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) - #include "src/dependencies/dmx/ESPDMX.h" - #else //ESP32 - #include "src/dependencies/dmx/SparkFunDMX.h" - #endif +#include "dmx_output.h" #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -454,11 +450,8 @@ WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black #ifdef WLED_ENABLE_DMX - #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) - WLED_GLOBAL DMXESPSerial dmx; - #else //ESP32 - WLED_GLOBAL SparkFunDMX dmx; - #endif + WLED_GLOBAL DMXOutput dmx; + WLED_GLOBAL int dmxOutputPin _INIT(-1); // DMX output pin (use -1 for disabled) WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled) // dmx CONFIG WLED_GLOBAL byte DMXChannels _INIT(7); // number of channels per fixture From 5087478c881a0cbc66d3d2d8b28d969ab782babc Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 19 Apr 2026 14:08:02 +0200 Subject: [PATCH 03/19] Update EspDmxOutput to work with new DMXOutput interface --- wled00/dmx_output.cpp | 35 ++++++++++++++++++++++++----------- wled00/dmx_output.h | 6 +----- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index eace2145e6..c42a25ba40 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -1,9 +1,7 @@ #include "wled.h" - +#include "dmx_output.h" /* - * Support for DMX output via serial (e.g. MAX485). - * Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266) - * Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32) + * Support for DMX output via serial (e.g. MAX485). * ESP8266 Library from: * https://github.com/Rickgg/ESP-Dmx * ESP32 Library from: @@ -68,14 +66,29 @@ void handleDMXOutput() dmx.update(); // update the DMX bus } -void initDMXOutput() { - #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) - dmx.init(512); // initialize with bus length - #else - dmx.initWrite(512); // initialize with bus length - #endif +void initDMXOutput(int outputPin) { + if (outputPin < 1) return; + const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX); + if (!pinAllocated) { + DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Failed to allocate pin %d for DMX output\n"), outputPin); + return; + } + DEBUG_PRINTF_P(PSTR("DMXOutput: init: pin %d\n"), outputPin); + dmx.init(outputPin); // set output pin and initialize DMX output +} + +void DMXOutput::init(uint8_t outputPin) { + _dmx.initWrite(outputPin, 512); +} + +void DMXOutput::write(int channel, uint8_t value) { + _dmx.write(channel, value); +} + +void DMXOutput::update() { + _dmx.update(); } #else -void initDMXOutput(){} +void initDMXOutput(int){} void handleDMXOutput() {} #endif diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index d08250443d..fec957ecaf 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -32,11 +32,7 @@ class DMXOutput void write(int channel, uint8_t value); void update(); private: - byte dmxdata[DMX_PACKET_SIZE]; - /* The ESP32 has either 2 or 3 UART ports. - Port 0 is typically used for Serial Monitor, - so we use port 1. */ - dmx_port_t dmxPort = 1; + EspDmxOutput _dmx; }; #endif // ESP8266 / ESP32 From ee3141a33e9b6c0cbcb4660c29b8736a28a2169e Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:06:01 +0200 Subject: [PATCH 04/19] fix(DMX): Fix sync settings page element hiding. --- wled00/data/settings_sync.htm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index a4a76f8805..6c543aee9a 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -188,10 +188,10 @@

Wired DMX Output Pin

DMX TX:
-
This firmware build does not include DMX Input support.
+
This firmware build does not include DMX Input support.
-
-
This firmware build does not include DMX output support.
+
+
This firmware build does not include DMX output support.
From bc80c4a1264997ea2330dbd75c3369ea73b79176 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:14:06 +0200 Subject: [PATCH 05/19] DMX: Rename define from WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT This increases clarity that WLED_ENABLE_DMX_INPUT is not a subset of WLED_ENABLE_DMX but independent. --- platformio_override.sample.ini | 2 +- tools/cdata.js | 2 +- wled00/cfg.cpp | 8 ++++---- wled00/dmx_output.cpp | 2 +- wled00/e131.cpp | 4 ++-- wled00/pin_manager.cpp | 2 +- wled00/pin_manager.h | 2 +- wled00/set.cpp | 4 ++-- wled00/wled.cpp | 4 ++-- wled00/wled.h | 7 +++---- wled00/wled_server.cpp | 10 +++++----- wled00/xml.cpp | 8 ++++---- 12 files changed, 27 insertions(+), 28 deletions(-) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index bf7a1314d6..c5144a6945 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -54,7 +54,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; enable optional built-in features ; -D WLED_ENABLE_PIXART ; -D WLED_ENABLE_USERMOD_PAGE # if created -; -D WLED_ENABLE_DMX +; -D WLED_ENABLE_DMX_OUTPUT ; ; PIN defines - uncomment and change, if needed: ; -D DATA_PINS=2 diff --git a/tools/cdata.js b/tools/cdata.js index 5ae7088b3e..97ed380890 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -433,7 +433,7 @@ writeChunks( method: "plaintext", filter: "html-minify", mangle: (str) => ` -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT ${str.replace(/function FM\(\)[ ]?\{/gms, "function FM() {%DMXVARS%\n")} #else const char PAGE_dmxmap[] PROGMEM = R"=====()====="; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index fe6bbafc7e..d91c659103 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -607,7 +607,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { tdd = if_live[F("timeout")] | -1; if (tdd >= 0) realtimeTimeoutMs = tdd * 100; - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT CJSON(dmxOutputPin, if_live_dmx[F("dmxOutputPin")]); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -733,7 +733,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(otaSameSubnet, ota[F("same-subnet")]); } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT JsonObject dmx = doc["dmx"]; CJSON(DMXChannels, dmx[F("chan")]); CJSON(DMXGap,dmx[F("gap")]); @@ -1127,7 +1127,7 @@ void serializeConfig(JsonObject root) { if_live_dmx[F("addr")] = DMXAddress; if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx["mode"] = DMXMode; - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT if_live_dmx[F("dmxOutputPin")] = dmxOutputPin; #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -1240,7 +1240,7 @@ void serializeConfig(JsonObject root) { #endif ota[F("same-subnet")] = otaSameSubnet; - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT JsonObject dmx = root.createNestedObject("dmx"); dmx[F("chan")] = DMXChannels; dmx[F("gap")] = DMXGap; diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index c42a25ba40..f572c93f1f 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -8,7 +8,7 @@ * https://github.com/sparkfun/SparkFunDMX */ -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT void handleDMXOutput() { diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 6846e5124e..2f6a1d3107 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -114,7 +114,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ return; } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT // does not act on out-of-order packets yet if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { for (uint16_t i = 1; i <= dmxChannels; i++) @@ -409,7 +409,7 @@ static void handleArtnetPollReply(IPAddress ipAddress) { } } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT if (e131ProxyUniverse > 0 && (DMXMode == DMX_MODE_DISABLED || (e131ProxyUniverse < startUniverse || e131ProxyUniverse > endUniverse))) { sendArtnetPollReply(&artnetPollReply, ipAddress, e131ProxyUniverse); } diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 84eaaec1fc..c8657daa6d 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -330,7 +330,7 @@ const char* PinManager::getPinOwnerName(uint8_t gpio) { case PinOwner::Relay: return "Relay"; case PinOwner::SPI_RAM: return "SPI RAM"; case PinOwner::DebugOut: return "Debug"; - case PinOwner::DMX: return "DMX Output"; + case PinOwner::DMX_OUTPUT: return "DMX Output"; case PinOwner::HW_I2C: return "I2C"; case PinOwner::HW_SPI: return "SPI"; case PinOwner::DMX_INPUT: return "DMX Input"; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index cbc47ce2cc..6d4d9de9d3 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -43,7 +43,7 @@ enum struct PinOwner : uint8_t { Relay = 0x87, // 'Rly' == Relay pin from configuration SPI_RAM = 0x88, // 'SpiR' == SPI RAM DebugOut = 0x89, // 'Dbg' == debug output always IO1 - DMX = 0x8A, // 'DMX' == DMX output via serial + DMX_OUTPUT = 0x8A, // 'DMX_OUTPUT'== DMX output via serial HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32) DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial diff --git a/wled00/set.cpp b/wled00/set.cpp index b338b6b02a..04f8e0241c 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -474,7 +474,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) t = request->arg(F("WO")).toInt(); if (t >= -255 && t <= 255) arlsOffset = t; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT dmxOutputPin = request->arg(F("IDMO")).toInt(); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -673,7 +673,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } } - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { int t = request->arg(F("PU")).toInt(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index db70592a1e..c3421c0ca6 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -68,7 +68,7 @@ void WLED::loop() handleImprovWifiScan(); handleNotifications(); handleTransitions(); - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT handleDMXOutput(); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -555,7 +555,7 @@ void WLED::setup() ArduinoOTA.setHostname(cmDNS); } #endif -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT initDMXOutput(dmxOutputPin); #endif #ifdef WLED_ENABLE_DMX_INPUT diff --git a/wled00/wled.h b/wled00/wled.h index 82aa1afa42..cf631d2eff 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -40,7 +40,7 @@ #else #undef WLED_ENABLE_ADALIGHT // disable has priority over enable #endif -//#define WLED_ENABLE_DMX // uses 3.5kb +//#define WLED_ENABLE_DMX_OUTPUT // uses 3.5kb #ifndef WLED_DISABLE_LOXONE #define WLED_ENABLE_LOXONE // uses 1.2kb #endif @@ -141,10 +141,9 @@ #include "src/dependencies/espalexa/EspalexaDevice.h" #endif -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT #include "dmx_output.h" #endif - #ifdef WLED_ENABLE_DMX_INPUT #include "dmx_input.h" #endif @@ -449,7 +448,7 @@ WLED_GLOBAL int arlsOffset _INIT(0); // realtime LE WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT WLED_GLOBAL DMXOutput dmx; WLED_GLOBAL int dmxOutputPin _INIT(-1); // DMX output pin (use -1 for disabled) WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index f7aac7fa50..d7409850e5 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -136,7 +136,7 @@ static void handleStaticContent(AsyncWebServerRequest *request, const String &pa request->send(response); } -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT static String dmxProcessor(const String& var) { String mapJS; @@ -601,7 +601,7 @@ void initServer() }); #endif -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor); }); @@ -742,7 +742,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { else if (url.indexOf( "sync") > 0) subPage = SUBPAGE_SYNC; else if (url.indexOf( "time") > 0) subPage = SUBPAGE_TIME; else if (url.indexOf(F("sec")) > 0) subPage = SUBPAGE_SEC; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT else if (url.indexOf( "dmx") > 0) subPage = SUBPAGE_DMX; #endif else if (url.indexOf( "um") > 0) subPage = SUBPAGE_UM; @@ -786,7 +786,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : strcpy_P(s, PSTR("Sync")); break; case SUBPAGE_TIME : strcpy_P(s, PSTR("Time")); break; case SUBPAGE_SEC : strcpy_P(s, PSTR("Security")); if (doReboot) strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT case SUBPAGE_DMX : strcpy_P(s, PSTR("DMX")); break; #endif case SUBPAGE_UM : strcpy_P(s, PSTR("Usermods")); break; @@ -821,7 +821,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : content = PAGE_settings_sync; len = PAGE_settings_sync_length; break; case SUBPAGE_TIME : content = PAGE_settings_time; len = PAGE_settings_time_length; break; case SUBPAGE_SEC : content = PAGE_settings_sec; len = PAGE_settings_sec_length; break; -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break; #endif case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index cd35121753..5526700db7 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -121,7 +121,7 @@ static void appendGPIOinfo(Print& settingsScript) firstPin = false; } } - #ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX_OUTPUT if (dmxOutputPin > 0) { if (!firstPin) settingsScript.print(','); settingsScript.print(dmxOutputPin); // DMX output pin @@ -203,7 +203,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in settingsScript.print(F("gId('2dbtn').style.display='none';")); #endif - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled settingsScript.print(F("gId('dmxbtn').style.display='';")); #endif } @@ -494,7 +494,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); -#ifdef WLED_ENABLE_DMX +#ifdef WLED_ENABLE_DMX_OUTPUT settingsScript.print(SET_F("hideNoDMXOutput();")); // hide "not compiled in" message, show output pin field printSetFormValue(settingsScript,SET_F("IDMO"), dmxOutputPin); #endif @@ -659,7 +659,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #endif } - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { printSetFormValue(settingsScript,PSTR("PU"),e131ProxyUniverse); From da582e9462fcb70cc33853030c25994f326e5d36 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:26:19 +0200 Subject: [PATCH 06/19] DMX: Introduce DMX_TXPIN_DEFAULT for compile time default DMX TX pin allocation. This is useful for boards with DMX. --- wled00/wled.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index cf631d2eff..794cc245ad 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -450,7 +450,10 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f #ifdef WLED_ENABLE_DMX_OUTPUT WLED_GLOBAL DMXOutput dmx; - WLED_GLOBAL int dmxOutputPin _INIT(-1); // DMX output pin (use -1 for disabled) + #ifndef DMX_TXPIN_DEFAULT + #define DMX_TXPIN_DEFAULT -1 + #endif + WLED_GLOBAL int dmxOutputPin _INIT(DMX_TXPIN_DEFAULT); // DMX output pin (use -1 for disabled) WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled) // dmx CONFIG WLED_GLOBAL byte DMXChannels _INIT(7); // number of channels per fixture From 2ef42098b22ac479a9788428f48d4e7add4592a8 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 19 Apr 2026 13:48:05 +0200 Subject: [PATCH 07/19] DMX: Fix critical buffer overrun This limits the number of used pixels to total to 512 channels used for transmitting via DMX according to the DMX start and spacing settings. --- wled00/dmx_output.cpp | 3 +++ wled00/dmx_output.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index f572c93f1f..e80ccca960 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -26,6 +26,9 @@ void handleDMXOutput() } uint16_t len = strip.getLengthTotal(); + uint16_t maxLen = (DMX_CHANNEL_TOP - DMXStart) / DMXGap; // maximum LEDs that fit into one physical DMX512 universe + if (len > maxLen) len = maxLen; + for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count uint32_t in = strip.getPixelColor(i); // get the colors for the individual fixtures as suggested by Aircoookie in issue #462 diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index fec957ecaf..45c5287f6f 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -8,6 +8,8 @@ * On ESP32: uses the esp_dmx library */ +#define DMX_CHANNEL_TOP 512 + #if defined(ESP8266) #include "src/dependencies/dmx/ESPDMX.h" From df76e379a7a3819c60fa26866508fdd136c10283 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 12 Apr 2026 14:49:27 +0200 Subject: [PATCH 08/19] DMX: Make DMX transmission async to speedup main loop for ESP32x. --- wled00/dmx_output.cpp | 11 +++- wled00/fcn_declare.h | 2 +- wled00/src/dependencies/dmx/SparkFunDMX.cpp | 69 ++++++++++++--------- wled00/src/dependencies/dmx/SparkFunDMX.h | 2 +- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index e80ccca960..578c40ea12 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -10,10 +10,10 @@ #ifdef WLED_ENABLE_DMX_OUTPUT -void handleDMXOutput() +bool handleDMXOutput() { // don't act, when in DMX Proxy mode - if (e131ProxyUniverse != 0) return; + if (e131ProxyUniverse != 0) return 0; uint8_t brightness = strip.getBrightness(); @@ -66,7 +66,12 @@ void handleDMXOutput() } } + #if defined(ESP8266) dmx.update(); // update the DMX bus + return true; + #else + return dmx.update(); // update the DMX bus, if available + #endif } void initDMXOutput(int outputPin) { @@ -93,5 +98,5 @@ void DMXOutput::update() { } #else void initDMXOutput(int){} -void handleDMXOutput() {} +bool handleDMXOutput() {} #endif diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index f31c27b6bb..65da9412e2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -93,7 +93,7 @@ typedef struct WiFiConfig { //dmx_output.cpp void initDMXOutput(int outputPin); -void handleDMXOutput(); +bool handleDMXOutput(); //dmx_input.cpp void initDMXInput(); diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.cpp b/wled00/src/dependencies/dmx/SparkFunDMX.cpp index 064b9ff620..71c87b0890 100644 --- a/wled00/src/dependencies/dmx/SparkFunDMX.cpp +++ b/wled00/src/dependencies/dmx/SparkFunDMX.cpp @@ -3,6 +3,7 @@ SparkFunDMX.h Arduino Library for the SparkFun ESP32 LED to DMX Shield Andy England @ SparkFun Electronics 7/22/2019 +modified version 2026-04-12 Development environment specifics: Arduino IDE 1.6.4 @@ -37,6 +38,7 @@ static const int txPin = 2; // transmit DMX data over this pin (default i //DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements static uint8_t dmxData[dmxMaxChannel+1] = { 0 }; static int chanSize = 0; +static uint16_t hwTxBufSize; // size of the combined TX buffer we requested plus the ESP32 hardware FIFO #if !defined(DMX_SEND_ONLY) static int currentChannel = 0; #endif @@ -111,7 +113,10 @@ void SparkFunDMX::initWrite (int chanQuant) { chanSize = chanQuant + 1; //Add 1 for start code + // chanSize + SOC_UART_FIFO_LEN is the minimum that leads to full async operation. Don't ask me why. + DMXSerial.setTxBufferSize(chanSize + SOC_UART_FIFO_LEN); // //641 = 1ms, 600 = 6ms, 514 = 10ms, 513-SOC_UART_FIFO_LEN=385 = 10ms, 185 = 17ms DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin); + hwTxBufSize = DMXSerial.availableForWrite(); // safest way to check initial TXbufSize IMO, in case ESP lib changes if (enablePin >= 0) { pinMode(enablePin, OUTPUT); digitalWrite(enablePin, HIGH); @@ -136,45 +141,47 @@ void SparkFunDMX::write(int Channel, uint8_t value) { -void SparkFunDMX::update() { +bool SparkFunDMX::update() { if (_READWRITE == _WRITE) { - //Send DMX break - digitalWrite(txPin, HIGH); - DMXSerial.begin(BREAKSPEED, BREAKFORMAT, rxPin, txPin);//Begin the Serial port - DMXSerial.write(0); - DMXSerial.flush(); - delay(1); - DMXSerial.end(); - - //Send DMX data - DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin);//Begin the Serial port - DMXSerial.write(dmxData, chanSize); - DMXSerial.flush(); - DMXSerial.end();//clear our DMX array, end the Hardware Serial port + if (DMXSerial.availableForWrite() >= hwTxBufSize) // only send dmx frame if no other frame is just ongoing i.e. TXbuf is empty + { + //Send DMX break + DMXSerial.updateBaudRate(BREAKSPEED); //change to DMX break settings + DMXSerial.write(0); + DMXSerial.flush(); + + //Send DMX data + DMXSerial.updateBaudRate(DMXSPEED); //change to regular DMX speed + DMXSerial.write(dmxData, chanSize); + + return true; + } + return false; } #if !defined(DMX_SEND_ONLY) else if (_READWRITE == _READ)//In a perfect world, this function ends serial communication upon packet completion and attaches RX to a CHANGE interrupt so the start code can be read again { - if (_startCodeDetected == true) - { - while (DMXSerial.available()) - { - dmxData[currentChannel++] = DMXSerial.read(); - } - if (currentChannel > chanSize) //Set the channel counter back to 0 if we reach the known end size of our packet - { - - portENTER_CRITICAL(&timerMux); - _startCodeDetected = false; - DMXSerial.flush(); - DMXSerial.end(); - portEXIT_CRITICAL(&timerMux); - currentChannel = 0; - } - } + if (_startCodeDetected == true) + { + while (DMXSerial.available()) + { + dmxData[currentChannel++] = DMXSerial.read(); + } + if (currentChannel > chanSize) //Set the channel counter back to 0 if we reach the known end size of our packet + { + portENTER_CRITICAL(&timerMux); + _startCodeDetected = false; + DMXSerial.flush(); + DMXSerial.end(); + portEXIT_CRITICAL(&timerMux); + currentChannel = 0; + } + return true; + } } #endif + return false; } // Function to update the DMX bus diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.h b/wled00/src/dependencies/dmx/SparkFunDMX.h index 73861153b2..7f377a87da 100644 --- a/wled00/src/dependencies/dmx/SparkFunDMX.h +++ b/wled00/src/dependencies/dmx/SparkFunDMX.h @@ -31,7 +31,7 @@ class SparkFunDMX { uint8_t read(int Channel); #endif void write(int channel, uint8_t value); - void update(); + bool update(); private: const uint8_t _startCodeValue = 0xFF; const bool _READ = true; From fe412321caceea992c08334fee54ab6dda12c0b6 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 19 Apr 2026 16:38:59 +0200 Subject: [PATCH 09/19] DMX: Move fcn declarations to dmx_output.h. Remove outdated dmxInput declarations. --- wled00/dmx_output.h | 4 ++++ wled00/fcn_declare.h | 8 -------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index 45c5287f6f..1fe6ec44ee 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -10,6 +10,10 @@ #define DMX_CHANNEL_TOP 512 +void initDMXOutput(int outputPin); +bool handleDMXOutput(); + + #if defined(ESP8266) #include "src/dependencies/dmx/ESPDMX.h" diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 65da9412e2..2da16465a8 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -91,14 +91,6 @@ typedef struct WiFiConfig { } } wifi_config; -//dmx_output.cpp -void initDMXOutput(int outputPin); -bool handleDMXOutput(); - -//dmx_input.cpp -void initDMXInput(); -void handleDMXInput(); - //e131.cpp void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses); From 32806ae297bc673ae3c5796aed7868e4c41b1152 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Sun, 12 Apr 2026 14:54:04 +0200 Subject: [PATCH 10/19] DMX: Output DMX debug timing stats. --- wled00/wled.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c3421c0ca6..94e82da3ba 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -55,6 +55,8 @@ void WLED::loop() static unsigned long maxStripMillis = 0; static size_t avgStripMillis = 0; unsigned long stripMillis; + static unsigned long maxDmxMillis = 0; + static unsigned long avgDmxMillis = 0; #endif handleTime(); @@ -69,8 +71,16 @@ void WLED::loop() handleNotifications(); handleTransitions(); #ifdef WLED_ENABLE_DMX_OUTPUT - handleDMXOutput(); + #ifdef WLED_DEBUG + unsigned long dmxMillis = millis(); #endif + handleDMXOutput(); + #ifdef WLED_DEBUG + dmxMillis = millis() - dmxMillis; + maxDmxMillis = dmxMillis > maxDmxMillis ? dmxMillis : maxDmxMillis; + avgDmxMillis += dmxMillis; + #endif //WLED_DEBUG + #endif //WLED_ENABLE_DMX_OUTPUT #ifdef WLED_ENABLE_DMX_INPUT dmxInput.update(); #endif @@ -318,6 +328,7 @@ void WLED::loop() DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%lu\n"), avgLoopMillis/loops, maxLoopMillis); DEBUG_PRINTF_P(PSTR("UM time[ms]: %u/%lu\n"), avgUsermodMillis/loops, maxUsermodMillis); DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis); + DEBUG_PRINTF_P(PSTR("DMX time[ms]:%u/%lu\n"), avgDmxMillis/loops, maxDmxMillis); } strip.printSize(); server.printStatus(DEBUGOUT); @@ -328,6 +339,8 @@ void WLED::loop() avgLoopMillis = 0; avgUsermodMillis = 0; avgStripMillis = 0; + maxDmxMillis = 0; + avgDmxMillis = 0; debugTime = millis(); } loops++; From 0d0a646d405d1b8e587b46cfc1390d5ffd962b81 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:19:14 +0200 Subject: [PATCH 11/19] DMX: Rewrite DMXOutput. All DMX functions are handled on its own for ESP32 (remove SparkFunDMX dependency) features: * non-blocking DMX update function * maximum refresh rate setting * some more useful functions For ESP8266 stay with DMXESPSerial for now and wrap that in DMXOutput. --- wled00/data/settings_dmx.htm | 3 +- wled00/dmx_output.cpp | 265 ++++++++++++++++---- wled00/dmx_output.h | 71 +++--- wled00/e131.cpp | 5 +- wled00/src/dependencies/dmx/LICENSE.md | 55 ---- wled00/src/dependencies/dmx/SparkFunDMX.cpp | 189 -------------- wled00/src/dependencies/dmx/SparkFunDMX.h | 42 ---- wled00/wled.cpp | 4 +- wled00/wled.h | 2 +- 9 files changed, 262 insertions(+), 374 deletions(-) delete mode 100644 wled00/src/dependencies/dmx/LICENSE.md delete mode 100644 wled00/src/dependencies/dmx/SparkFunDMX.cpp delete mode 100644 wled00/src/dependencies/dmx/SparkFunDMX.h diff --git a/wled00/data/settings_dmx.htm b/wled00/data/settings_dmx.htm index 391c2bdc97..14e8146eb0 100644 --- a/wled00/data/settings_dmx.htm +++ b/wled00/data/settings_dmx.htm @@ -53,7 +53,8 @@

-

Imma firin ma lazer (if it has DMX support)

+

DMX Output Settings

+

For pin settings, go to Sync settings.

Proxy Universe from E1.31 to DMX (0=disabled)
This will disable the LED data output to DMX configurable below

diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 578c40ea12..b789f9c4be 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -1,32 +1,231 @@ +#ifdef WLED_ENABLE_DMX_OUTPUT + #include "wled.h" #include "dmx_output.h" /* * Support for DMX output via serial (e.g. MAX485). * ESP8266 Library from: * https://github.com/Rickgg/ESP-Dmx - * ESP32 Library from: - * https://github.com/sparkfun/SparkFunDMX */ -#ifdef WLED_ENABLE_DMX_OUTPUT -bool handleDMXOutput() -{ +#if defined(ESP8266) + +bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { + if(uartNo == -1) { + uartNo = 1; // only one available on ESP8266 + } + if(uartNo != 1) { + return false; + } + _dmx.init(DMX_CHANNEL_TOP); + _updateRate = updateRate; + return true; +} + +void DMXOutput::write(uint16_t channel, uint8_t value) { + _dmx.write(channel, value); +} +void DMXOutput::writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { + if(channelStart == 0) return; // channel 0 is no valid start channel, because it is special function + for(int i = 0; i < len; i++) { + if(channelStart + i > DMX_CHANNEL_TOP) break; // finish when we reached the DMX channel 512 + write(channelStart + i, values[i]); + } +} + +uint8_t DMXOutput::read(uint16_t channel) { + return _dmx.read(channel); +} +bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { + if(channelStart + len > DMX_CHANNELS) return false; // out of bounds + + for(int i = 0; i < len; i++) { + values[i] = _dmx.read(i); + } + return true; +} + +bool DMXOutput::update() { + if(timeToNextUpdate() <= 0) { + _lastDmxOutMillis = millis(); + _dmx.update(); + return true; + } + return false; +} + +bool DMXOutput::busy() { + return false; +} + +DMXOutput::~DMXOutput() { + _dmx.end(); +} +#else + + /** + * Initialize DMXOutput. + * Use _outputPin_ for TX. + * _updateRate_ specifies update rate in Hz. Use 0 for max. + * Use Serial _uartNo_. Specify -1 for default, which is the highest one available. + */ +bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { + + #if SOC_UART_NUM <= 1 + #error DMX output is not possible on your MCU, as it does not have HardwareSerial(1) + #endif + + if(uartNo == -1) { + uartNo = SOC_UART_NUM - 1; // use last UART as default + } + + if(outputPin < 1) return false; + const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX_OUTPUT); + if(!pinAllocated) { + DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Failed to allocate pin %d for DMX output\n"), outputPin); + return false; + } + DEBUG_PRINTF_P(PSTR("DMXOutput: init: pin %d\n"), outputPin); + + digitalWrite(outputPin, 1); + pinMode(outputPin, OUTPUT); + _outputPin = outputPin; + _updateRate = updateRate; + _dmxSerial = new HardwareSerial(uartNo); + + // DMX_CHANNELS + SOC_UART_FIFO_LEN is the minimum that leads to full async operation. Don't ask me why. + _dmxSerial->setTxBufferSize(DMX_CHANNELS + SOC_UART_FIFO_LEN); //641 = 1ms, 600 = 6ms, 514 = 10ms, 513-SOC_UART_FIFO_LEN=385 = 10ms, 185 = 17ms + _dmxSerial->begin(DMXSPEED, DMXFORMAT, -1, outputPin); + _halTxBufSize = _dmxSerial->availableForWrite(); // safest way to check initial TXbufSize IMO, in case ESP lib changes + + return true; +} + +DMXOutput::~DMXOutput() { + delete _dmxSerial; // end() is implied in delete + pinMode(_outputPin, INPUT); +} + +/** + * Write one DMX _channel_ to _value_ + */ +void DMXOutput::write(uint16_t channel, uint8_t value) { + if(channel > DMX_CHANNEL_TOP) return; // out of bounds + _dmxData[channel] = value; +} + +/** + * Write _len_ Bytes to DMX channels starting at _channelStart_. + * channelStart must be 1 or more. Use write() if you need to access channel 0. + */ +void DMXOutput::writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { + if(channelStart == 0) return; // channel 0 is no valid start channel, because it is special function + for(int i = 0; i < len; i++) { + if(channelStart + i > DMX_CHANNEL_TOP) break; // finish when we reached the DMX channel 512 + write(channelStart + i, values[i]); + } +} + +/** + * Read one DMX _channel_ from output buffer + */ +uint8_t DMXOutput::read(uint16_t channel) { + if(channel > DMX_CHANNEL_TOP) return 0; // out of bounds + return _dmxData[channel]; +} + +/** + * Read _len_ Bytes from DMX channels output buffer data starting at _channelStart_. + * Returns an array[len]. + */ +bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { + if(channelStart + len > DMX_CHANNELS) return false; // out of bounds + + memcpy(values, &_dmxData[channelStart], len); + return true; +} + +/** + * Send a new DMX frame of _dmxData out. + */ +bool DMXOutput::update() { + // Rate limiting & only send dmx frame if no other frame is just ongoing i.e. TXbuf is empty + if((timeToNextUpdate() <= 0) && (_dmxSerial->availableForWrite() >= _halTxBufSize)) { + _lastDmxOutMillis = millis(); + + //Send DMX break + _dmxSerial->updateBaudRate(BREAKSPEED); //change to DMX break settings + _dmxSerial->write(0); + _dmxSerial->flush(); + + //Send DMX data + _dmxSerial->updateBaudRate(DMXSPEED); //change to regular DMX speed + _dmxSerial->write(_dmxData, DMX_CHANNELS); + + return true; + } + return false; +} + +/** + * Whether the UART is busy sending data. + */ +bool DMXOutput::busy() { + // not busy, if FIFO/ring buffer is empty + return _dmxSerial->availableForWrite() != _halTxBufSize; +} +#endif + +/** + * Get last time DMX Output was started in ms. + */ +unsigned long DMXOutput::getLastDmxOut() { + return _lastDmxOutMillis; +} + +/** + * Change update rate to _updateRate_. + */ +void DMXOutput::setUpdateRate(uint8_t updateRate) { + _updateRate = updateRate; +} + +/** + * Returns time in ms to next update to reach a maximum of _updateRate. + * Pay attention that when the last update was at let's say 100.9ms, at 122.0ms this reports as if 22ms had passed. + * To get a definitive max refresh rate, trigger update only on a result of -1. + * Returns negative numbers if the time has passed already. + */ +int DMXOutput::timeToNextUpdate() { + if(_updateRate == 0) return -1; // if refresh rate set to 0, refresh rate is max. + + // treat _updateRate as maximum, so round up the refresh delay + float fdmxFrameTime = 1000.0 / _updateRate; + int dmxFrameTime = (uint16_t)fdmxFrameTime; + if(fdmxFrameTime - dmxFrameTime > 0.0) dmxFrameTime += 1; // if fractional part > 0, add one + + return dmxFrameTime - (millis() - _lastDmxOutMillis); +} + +/** + * Write LED data to DMX output buffer and send out. + */ +bool DMXOutput::handleDMXOutput() { // don't act, when in DMX Proxy mode - if (e131ProxyUniverse != 0) return 0; + if (e131ProxyUniverse != 0) return false; uint8_t brightness = strip.getBrightness(); bool calc_brightness = true; // check if no shutter channel is set - for (unsigned i = 0; i < DMXChannels; i++) - { + for (unsigned i = 0; i < DMXChannels; i++) { if (DMXFixtureMap[i] == 5) calc_brightness = false; } uint16_t len = strip.getLengthTotal(); - uint16_t maxLen = (DMX_CHANNEL_TOP - DMXStart) / DMXGap; // maximum LEDs that fit into one physical DMX512 universe + uint16_t maxLen = (DMX_CHANNELS - DMXStart) / DMXGap; // maximum LEDs that fit into one physical DMX512 universe if (len > maxLen) len = maxLen; for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count @@ -41,62 +240,32 @@ bool handleDMXOutput() for (int j = 0; j < DMXChannels; j++) { int DMXAddr = DMXFixtureStart + j; switch (DMXFixtureMap[j]) { - case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off. - dmx.write(DMXAddr, 0); + case 0: // Set this channel to 0 + write(DMXAddr, 0); break; case 1: // Red - dmx.write(DMXAddr, calc_brightness ? (r * brightness) / 255 : r); + write(DMXAddr, calc_brightness ? (r * brightness) / 255 : r); break; case 2: // Green - dmx.write(DMXAddr, calc_brightness ? (g * brightness) / 255 : g); + write(DMXAddr, calc_brightness ? (g * brightness) / 255 : g); break; case 3: // Blue - dmx.write(DMXAddr, calc_brightness ? (b * brightness) / 255 : b); + write(DMXAddr, calc_brightness ? (b * brightness) / 255 : b); break; case 4: // White - dmx.write(DMXAddr, calc_brightness ? (w * brightness) / 255 : w); + write(DMXAddr, calc_brightness ? (w * brightness) / 255 : w); break; case 5: // Shutter channel. Controls the brightness. - dmx.write(DMXAddr, brightness); + write(DMXAddr, brightness); break; case 6: // Sets this channel to 255. Like 0, but more wholesome. - dmx.write(DMXAddr, 255); + write(DMXAddr, 255); break; } } } - #if defined(ESP8266) - dmx.update(); // update the DMX bus - return true; - #else - return dmx.update(); // update the DMX bus, if available - #endif -} - -void initDMXOutput(int outputPin) { - if (outputPin < 1) return; - const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX); - if (!pinAllocated) { - DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Failed to allocate pin %d for DMX output\n"), outputPin); - return; - } - DEBUG_PRINTF_P(PSTR("DMXOutput: init: pin %d\n"), outputPin); - dmx.init(outputPin); // set output pin and initialize DMX output -} - -void DMXOutput::init(uint8_t outputPin) { - _dmx.initWrite(outputPin, 512); -} - -void DMXOutput::write(int channel, uint8_t value) { - _dmx.write(channel, value); + return update(); // update the DMX bus, if available } -void DMXOutput::update() { - _dmx.update(); -} -#else -void initDMXOutput(int){} -bool handleDMXOutput() {} #endif diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index 1fe6ec44ee..7db34cb0be 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -1,46 +1,51 @@ -#ifndef DMX_OUTPUT_H -#define DMX_OUTPUT_H - /** * Unified DMX Output class for all platforms. * - * On ESP8266: wraps DMXESPSerial (bit-banged DMX via Serial1) - * On ESP32: uses the esp_dmx library + * On ESP8266: wraps DMXESPSerial (DMX via Serial1) + * On ESP32: uses own functions */ -#define DMX_CHANNEL_TOP 512 - -void initDMXOutput(int outputPin); -bool handleDMXOutput(); - +#ifndef DMX_OUTPUT_H +#define DMX_OUTPUT_H #if defined(ESP8266) #include "src/dependencies/dmx/ESPDMX.h" +#endif -class DMXOutput -{ -public: - void init(uint8_t outputPin); - void write(int channel, uint8_t value); - void update(); -private: +#define DMX_CHANNEL_TOP 512 +#define DMX_CHANNELS DMX_CHANNEL_TOP + 1 + +#define DMXSPEED 250000 +#define DMXFORMAT SERIAL_8N2 +#define BREAKSPEED 83000 +#define BREAKFORMAT SERIAL_8N1 + + +class DMXOutput { + public: + ~DMXOutput(); + bool init(uint8_t outputPin, uint8_t updateRate = 44, int8_t uartNo = -1); + void write(uint16_t channel, uint8_t value); + void writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len); + uint8_t read(uint16_t channel); + bool readBytes(uint16_t channelStart, uint8_t values[], uint16_t len); + bool update(); + bool handleDMXOutput(); + unsigned long getLastDmxOut(); + bool busy(); + void setUpdateRate(uint8_t updateRate); + int timeToNextUpdate(); + private: + #if defined(ESP8266) DMXESPSerial _dmx; + #else + HardwareSerial* _dmxSerial; + uint8_t _outputPin; // DMX TX pin + uint8_t _dmxData[DMX_CHANNELS] = {0}; + unsigned int _halTxBufSize; + #endif + uint8_t _updateRate; + unsigned long _lastDmxOutMillis = 0; }; -#else // ESP32 - -#include - -class DMXOutput -{ -public: - void init(uint8_t outputPin); - void write(int channel, uint8_t value); - void update(); -private: - EspDmxOutput _dmx; -}; - -#endif // ESP8266 / ESP32 - #endif // DMX_OUTPUT_H diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 2f6a1d3107..c680dba01a 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -117,9 +117,8 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ #ifdef WLED_ENABLE_DMX_OUTPUT // does not act on out-of-order packets yet if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { - for (uint16_t i = 1; i <= dmxChannels; i++) - dmx.write(i, e131_data[i]); - dmx.update(); + dmxOutput.writeBytes(1, &e131_data[1], dmxChannels); + dmxOutput.update(); } #endif diff --git a/wled00/src/dependencies/dmx/LICENSE.md b/wled00/src/dependencies/dmx/LICENSE.md deleted file mode 100644 index e64bd4ef35..0000000000 --- a/wled00/src/dependencies/dmx/LICENSE.md +++ /dev/null @@ -1,55 +0,0 @@ -SparkFun License Information -============================ - -SparkFun uses two different licenses for our files — one for hardware and one for code. - -Hardware ---------- - -**SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** - -Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). - -You are free to: - -Share — copy and redistribute the material in any medium or format -Adapt — remix, transform, and build upon the material -for any purpose, even commercially. -The licensor cannot revoke these freedoms as long as you follow the license terms. -Under the following terms: - -Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. -ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. -No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. -Notices: - -You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. -No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. - - -Code --------- - -**SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** - -The MIT License (MIT) - -Copyright (c) 2016 SparkFun Electronics - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.cpp b/wled00/src/dependencies/dmx/SparkFunDMX.cpp deleted file mode 100644 index 71c87b0890..0000000000 --- a/wled00/src/dependencies/dmx/SparkFunDMX.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/****************************************************************************** -SparkFunDMX.h -Arduino Library for the SparkFun ESP32 LED to DMX Shield -Andy England @ SparkFun Electronics -7/22/2019 -modified version 2026-04-12 - -Development environment specifics: -Arduino IDE 1.6.4 - -This code is released under the [MIT License](http://opensource.org/licenses/MIT). -Please review the LICENSE.md file included with this example. If you have any questions -or concerns with licensing, please contact techsupport@sparkfun.com. -Distributed as-is; no warranty is given. -******************************************************************************/ - -/* ----- LIBRARIES ----- */ -#if defined(ARDUINO_ARCH_ESP32) - -#include -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2) - -#include "SparkFunDMX.h" -#include - -#define dmxMaxChannel 512 -#define defaultMax 32 - -#define DMXSPEED 250000 -#define DMXFORMAT SERIAL_8N2 -#define BREAKSPEED 83333 -#define BREAKFORMAT SERIAL_8N1 - -static const int enablePin = -1; // disable the enable pin because it is not needed -static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable" -static const int txPin = 2; // transmit DMX data over this pin (default is pin 2) - -//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements -static uint8_t dmxData[dmxMaxChannel+1] = { 0 }; -static int chanSize = 0; -static uint16_t hwTxBufSize; // size of the combined TX buffer we requested plus the ESP32 hardware FIFO -#if !defined(DMX_SEND_ONLY) -static int currentChannel = 0; -#endif - -// Some new MCUs (-S2, -C3) don't have HardwareSerial(2) -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) - #if SOC_UART_NUM < 3 - #error DMX output is not possible on your MCU, as it does not have HardwareSerial(2) - #endif -#endif - -static HardwareSerial DMXSerial(2); - -/* Interrupt Timer for DMX Receive */ -#if !defined(DMX_SEND_ONLY) -static hw_timer_t * timer = NULL; -static portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; -#endif - -static volatile int _interruptCounter = 0; -static volatile bool _startCodeDetected = false; - - -#if !defined(DMX_SEND_ONLY) -/* Start Code is detected by 21 low interrupts */ -void IRAM_ATTR onTimer() { - if ((rxPin >= 0) && (digitalRead(rxPin) == 1)) - { - _interruptCounter = 0; //If the RX Pin is high, we are not in an interrupt - } - else - { - _interruptCounter++; - } - if (_interruptCounter > 9) - { - portENTER_CRITICAL_ISR(&timerMux); - _startCodeDetected = true; - DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin); - portEXIT_CRITICAL_ISR(&timerMux); - _interruptCounter = 0; - } -} - -void SparkFunDMX::initRead(int chanQuant) { - - timer = timerBegin(0, 1, true); - timerAttachInterrupt(timer, &onTimer, true); - timerAlarmWrite(timer, 320, true); - timerAlarmEnable(timer); - _READWRITE = _READ; - if (chanQuant > dmxMaxChannel || chanQuant <= 0) - { - chanQuant = defaultMax; - } - chanSize = chanQuant; - if (enablePin >= 0) { - pinMode(enablePin, OUTPUT); - digitalWrite(enablePin, LOW); - } - if (rxPin >= 0) pinMode(rxPin, INPUT); -} -#endif - -// Set up the DMX-Protocol -void SparkFunDMX::initWrite (int chanQuant) { - - _READWRITE = _WRITE; - if (chanQuant > dmxMaxChannel || chanQuant <= 0) { - chanQuant = defaultMax; - } - - chanSize = chanQuant + 1; //Add 1 for start code - - // chanSize + SOC_UART_FIFO_LEN is the minimum that leads to full async operation. Don't ask me why. - DMXSerial.setTxBufferSize(chanSize + SOC_UART_FIFO_LEN); // //641 = 1ms, 600 = 6ms, 514 = 10ms, 513-SOC_UART_FIFO_LEN=385 = 10ms, 185 = 17ms - DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin); - hwTxBufSize = DMXSerial.availableForWrite(); // safest way to check initial TXbufSize IMO, in case ESP lib changes - if (enablePin >= 0) { - pinMode(enablePin, OUTPUT); - digitalWrite(enablePin, HIGH); - } -} - -#if !defined(DMX_SEND_ONLY) -// Function to read DMX data -uint8_t SparkFunDMX::read(int Channel) { - if (Channel > chanSize) Channel = chanSize; - return(dmxData[Channel - 1]); //subtract one to account for start byte -} -#endif - -// Function to send DMX data -void SparkFunDMX::write(int Channel, uint8_t value) { - if (Channel < 0) Channel = 0; - if (Channel > chanSize) chanSize = Channel; - dmxData[0] = 0; - dmxData[Channel] = value; //add one to account for start byte -} - - - -bool SparkFunDMX::update() { - if (_READWRITE == _WRITE) - { - if (DMXSerial.availableForWrite() >= hwTxBufSize) // only send dmx frame if no other frame is just ongoing i.e. TXbuf is empty - { - //Send DMX break - DMXSerial.updateBaudRate(BREAKSPEED); //change to DMX break settings - DMXSerial.write(0); - DMXSerial.flush(); - - //Send DMX data - DMXSerial.updateBaudRate(DMXSPEED); //change to regular DMX speed - DMXSerial.write(dmxData, chanSize); - - return true; - } - return false; - } -#if !defined(DMX_SEND_ONLY) - else if (_READWRITE == _READ)//In a perfect world, this function ends serial communication upon packet completion and attaches RX to a CHANGE interrupt so the start code can be read again - { - if (_startCodeDetected == true) - { - while (DMXSerial.available()) - { - dmxData[currentChannel++] = DMXSerial.read(); - } - if (currentChannel > chanSize) //Set the channel counter back to 0 if we reach the known end size of our packet - { - portENTER_CRITICAL(&timerMux); - _startCodeDetected = false; - DMXSerial.flush(); - DMXSerial.end(); - portEXIT_CRITICAL(&timerMux); - currentChannel = 0; - } - return true; - } - } -#endif - return false; -} - -// Function to update the DMX bus -#endif -#endif diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.h b/wled00/src/dependencies/dmx/SparkFunDMX.h deleted file mode 100644 index 7f377a87da..0000000000 --- a/wled00/src/dependencies/dmx/SparkFunDMX.h +++ /dev/null @@ -1,42 +0,0 @@ -/****************************************************************************** -SparkFunDMX.h -Arduino Library for the SparkFun ESP32 LED to DMX Shield -Andy England @ SparkFun Electronics -7/22/2019 - -Development environment specifics: -Arduino IDE 1.6.4 - -This code is released under the [MIT License](http://opensource.org/licenses/MIT). -Please review the LICENSE.md file included with this example. If you have any questions -or concerns with licensing, please contact techsupport@sparkfun.com. -Distributed as-is; no warranty is given. -******************************************************************************/ - -#include - - -#ifndef SparkFunDMX_h -#define SparkFunDMX_h - -#define DMX_SEND_ONLY // this disables DMX sending features, and saves us two GPIO pins - -// ---- Methods ---- - -class SparkFunDMX { -public: - void initWrite(int maxChan); -#if !defined(DMX_SEND_ONLY) - void initRead(int maxChan); - uint8_t read(int Channel); -#endif - void write(int channel, uint8_t value); - bool update(); -private: - const uint8_t _startCodeValue = 0xFF; - const bool _READ = true; - const bool _WRITE = false; - bool _READWRITE; -}; - -#endif \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 94e82da3ba..c5b228540d 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -74,7 +74,7 @@ void WLED::loop() #ifdef WLED_DEBUG unsigned long dmxMillis = millis(); #endif - handleDMXOutput(); + dmxOutput.handleDMXOutput(); #ifdef WLED_DEBUG dmxMillis = millis() - dmxMillis; maxDmxMillis = dmxMillis > maxDmxMillis ? dmxMillis : maxDmxMillis; @@ -569,7 +569,7 @@ void WLED::setup() } #endif #ifdef WLED_ENABLE_DMX_OUTPUT - initDMXOutput(dmxOutputPin); + dmxOutput.init(dmxOutputPin); #endif #ifdef WLED_ENABLE_DMX_INPUT dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort); diff --git a/wled00/wled.h b/wled00/wled.h index 794cc245ad..4db45de657 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -449,7 +449,7 @@ WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black #ifdef WLED_ENABLE_DMX_OUTPUT - WLED_GLOBAL DMXOutput dmx; + WLED_GLOBAL DMXOutput dmxOutput; #ifndef DMX_TXPIN_DEFAULT #define DMX_TXPIN_DEFAULT -1 #endif From 8841a88c604ebddf7c654fdfe50f4f4e8239e7d7 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:52:23 +0200 Subject: [PATCH 12/19] DMXOutput: Track initialized state of instance. Add comments about updateRate. --- wled00/dmx_output.cpp | 39 +++++++++++++++++++++++++++++++++------ wled00/dmx_output.h | 3 ++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index b789f9c4be..7e7f9cc28c 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -2,6 +2,9 @@ #include "wled.h" #include "dmx_output.h" +#ifndef ESP8266 +#include "hal/uart_ll.h" +#endif /* * Support for DMX output via serial (e.g. MAX485). * ESP8266 Library from: @@ -9,7 +12,7 @@ */ -#if defined(ESP8266) +#ifdef ESP8266 bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { if(uartNo == -1) { @@ -19,6 +22,7 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { return false; } _dmx.init(DMX_CHANNEL_TOP); + _uartNo = uartNo; _updateRate = updateRate; return true; } @@ -61,6 +65,7 @@ bool DMXOutput::busy() { DMXOutput::~DMXOutput() { _dmx.end(); + _uartNo = -1; } #else @@ -79,6 +84,10 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { if(uartNo == -1) { uartNo = SOC_UART_NUM - 1; // use last UART as default } + if(uartNo == 0) { + DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Cannot run on chips with <=1 hardware UART, or with UART0.")); + return false; + } if(outputPin < 1) return false; const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX_OUTPUT); @@ -93,6 +102,7 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { _outputPin = outputPin; _updateRate = updateRate; _dmxSerial = new HardwareSerial(uartNo); + _uartNo = uartNo; // DMX_CHANNELS + SOC_UART_FIFO_LEN is the minimum that leads to full async operation. Don't ask me why. _dmxSerial->setTxBufferSize(DMX_CHANNELS + SOC_UART_FIFO_LEN); //641 = 1ms, 600 = 6ms, 514 = 10ms, 513-SOC_UART_FIFO_LEN=385 = 10ms, 185 = 17ms @@ -103,8 +113,11 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { } DMXOutput::~DMXOutput() { - delete _dmxSerial; // end() is implied in delete - pinMode(_outputPin, INPUT); + if(_uartNo >= 0) { + delete _dmxSerial; // end() is implied in delete + pinMode(_outputPin, INPUT); + _uartNo = -1; + } } /** @@ -150,16 +163,28 @@ bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) * Send a new DMX frame of _dmxData out. */ bool DMXOutput::update() { + // false if not properly initialized + if(_uartNo < 0) return false; + // Rate limiting & only send dmx frame if no other frame is just ongoing i.e. TXbuf is empty if((timeToNextUpdate() <= 0) && (_dmxSerial->availableForWrite() >= _halTxBufSize)) { + // NOTE: availableForWrite() is not doing what I expect it to do. It may happen, when _updateRate ist too fast, + // that ongoing DMX transmissions are throttled to BREAKSPEED and the whole update() call takes more time, + // because data form a previous run is still in the ring buffer or FIFO. That's why we add an additional flush() + // here which is unnecessary if we had a proper API. + // This flush adds some unnecessary delay (of couple 100 us?!). You can remove it if you guarantee that _updateRate + // is never too high. + //_dmxSerial->flush(); + _lastDmxOutMillis = millis(); - //Send DMX break + // Send DMX break + // Cannot change UART format while running. End and reinit takes much longer than the additional stopbit here. _dmxSerial->updateBaudRate(BREAKSPEED); //change to DMX break settings _dmxSerial->write(0); _dmxSerial->flush(); - //Send DMX data + // Send DMX data _dmxSerial->updateBaudRate(DMXSPEED); //change to regular DMX speed _dmxSerial->write(_dmxData, DMX_CHANNELS); @@ -172,8 +197,9 @@ bool DMXOutput::update() { * Whether the UART is busy sending data. */ bool DMXOutput::busy() { + if(_uartNo < 0) return true; // not initialized // not busy, if FIFO/ring buffer is empty - return _dmxSerial->availableForWrite() != _halTxBufSize; + return _dmxSerial->availableForWrite() < _halTxBufSize; } #endif @@ -198,6 +224,7 @@ void DMXOutput::setUpdateRate(uint8_t updateRate) { * Returns negative numbers if the time has passed already. */ int DMXOutput::timeToNextUpdate() { + if(_uartNo < 0) return INT_MAX; // not initialized if(_updateRate == 0) return -1; // if refresh rate set to 0, refresh rate is max. // treat _updateRate as maximum, so round up the refresh delay diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index 7db34cb0be..cb270e4208 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -18,7 +18,7 @@ #define DMXSPEED 250000 #define DMXFORMAT SERIAL_8N2 #define BREAKSPEED 83000 -#define BREAKFORMAT SERIAL_8N1 +#define BREAKFORMAT SERIAL_8N1 // unused, instead, DMXFORMAT is used class DMXOutput { @@ -44,6 +44,7 @@ class DMXOutput { uint8_t _dmxData[DMX_CHANNELS] = {0}; unsigned int _halTxBufSize; #endif + int8_t _uartNo = -1; uint8_t _updateRate; unsigned long _lastDmxOutMillis = 0; }; From 4996e17139829087ff94ebc93535680983757ac2 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:13:20 +0200 Subject: [PATCH 13/19] DMXOutput: Use uart_ll to get satisfying timing results. --- wled00/dmx_output.cpp | 15 +++------------ wled00/dmx_output.h | 1 - wled00/wled.cpp | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 7e7f9cc28c..d455822ba2 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -107,7 +107,6 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { // DMX_CHANNELS + SOC_UART_FIFO_LEN is the minimum that leads to full async operation. Don't ask me why. _dmxSerial->setTxBufferSize(DMX_CHANNELS + SOC_UART_FIFO_LEN); //641 = 1ms, 600 = 6ms, 514 = 10ms, 513-SOC_UART_FIFO_LEN=385 = 10ms, 185 = 17ms _dmxSerial->begin(DMXSPEED, DMXFORMAT, -1, outputPin); - _halTxBufSize = _dmxSerial->availableForWrite(); // safest way to check initial TXbufSize IMO, in case ESP lib changes return true; } @@ -167,15 +166,7 @@ bool DMXOutput::update() { if(_uartNo < 0) return false; // Rate limiting & only send dmx frame if no other frame is just ongoing i.e. TXbuf is empty - if((timeToNextUpdate() <= 0) && (_dmxSerial->availableForWrite() >= _halTxBufSize)) { - // NOTE: availableForWrite() is not doing what I expect it to do. It may happen, when _updateRate ist too fast, - // that ongoing DMX transmissions are throttled to BREAKSPEED and the whole update() call takes more time, - // because data form a previous run is still in the ring buffer or FIFO. That's why we add an additional flush() - // here which is unnecessary if we had a proper API. - // This flush adds some unnecessary delay (of couple 100 us?!). You can remove it if you guarantee that _updateRate - // is never too high. - //_dmxSerial->flush(); - + if((timeToNextUpdate() <= 0) && !busy()) { _lastDmxOutMillis = millis(); // Send DMX break @@ -194,12 +185,12 @@ bool DMXOutput::update() { } /** - * Whether the UART is busy sending data. + * Whether the DMX is busy sending a frame. */ bool DMXOutput::busy() { if(_uartNo < 0) return true; // not initialized // not busy, if FIFO/ring buffer is empty - return _dmxSerial->availableForWrite() < _halTxBufSize; + return !uart_ll_is_tx_idle(UART_LL_GET_HW(_uartNo)); } #endif diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index cb270e4208..ba73bb655d 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -42,7 +42,6 @@ class DMXOutput { HardwareSerial* _dmxSerial; uint8_t _outputPin; // DMX TX pin uint8_t _dmxData[DMX_CHANNELS] = {0}; - unsigned int _halTxBufSize; #endif int8_t _uartNo = -1; uint8_t _updateRate; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c5b228540d..eed894d3c8 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -569,7 +569,7 @@ void WLED::setup() } #endif #ifdef WLED_ENABLE_DMX_OUTPUT - dmxOutput.init(dmxOutputPin); + dmxOutput.init(dmxOutputPin, 43); #endif #ifdef WLED_ENABLE_DMX_INPUT dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort); From 3a67e3883d2c83013f2b28b27b4c33f4eea3c11a Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:46:53 +0200 Subject: [PATCH 14/19] DMXOutput: Also replace DMXESPSerial by this class. --- wled00/dmx_output.cpp | 95 +++++++-------------- wled00/dmx_output.h | 11 --- wled00/src/dependencies/dmx/ESPDMX.cpp | 109 ------------------------- wled00/src/dependencies/dmx/ESPDMX.h | 31 ------- 4 files changed, 31 insertions(+), 215 deletions(-) delete mode 100644 wled00/src/dependencies/dmx/ESPDMX.cpp delete mode 100644 wled00/src/dependencies/dmx/ESPDMX.h diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index d455822ba2..6213454d34 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -2,73 +2,15 @@ #include "wled.h" #include "dmx_output.h" -#ifndef ESP8266 +#ifdef ESP8266 +#include "uart.cpp" +#else #include "hal/uart_ll.h" #endif /* * Support for DMX output via serial (e.g. MAX485). - * ESP8266 Library from: - * https://github.com/Rickgg/ESP-Dmx */ - -#ifdef ESP8266 - -bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { - if(uartNo == -1) { - uartNo = 1; // only one available on ESP8266 - } - if(uartNo != 1) { - return false; - } - _dmx.init(DMX_CHANNEL_TOP); - _uartNo = uartNo; - _updateRate = updateRate; - return true; -} - -void DMXOutput::write(uint16_t channel, uint8_t value) { - _dmx.write(channel, value); -} -void DMXOutput::writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { - if(channelStart == 0) return; // channel 0 is no valid start channel, because it is special function - for(int i = 0; i < len; i++) { - if(channelStart + i > DMX_CHANNEL_TOP) break; // finish when we reached the DMX channel 512 - write(channelStart + i, values[i]); - } -} - -uint8_t DMXOutput::read(uint16_t channel) { - return _dmx.read(channel); -} -bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { - if(channelStart + len > DMX_CHANNELS) return false; // out of bounds - - for(int i = 0; i < len; i++) { - values[i] = _dmx.read(i); - } - return true; -} - -bool DMXOutput::update() { - if(timeToNextUpdate() <= 0) { - _lastDmxOutMillis = millis(); - _dmx.update(); - return true; - } - return false; -} - -bool DMXOutput::busy() { - return false; -} - -DMXOutput::~DMXOutput() { - _dmx.end(); - _uartNo = -1; -} -#else - /** * Initialize DMXOutput. * Use _outputPin_ for TX. @@ -77,6 +19,13 @@ DMXOutput::~DMXOutput() { */ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { + #ifdef ESP8266 + if(uartNo == -1) uartNo = 1; + if((uartNo != 1) || (outputPin != 2)) { + DEBUG_PRINTF_P(PSTR("DMXOutput: Can only run with UART1, TX pin 2 on ESP8266.")); + return false; + } + #else //not ESP8266 #if SOC_UART_NUM <= 1 #error DMX output is not possible on your MCU, as it does not have HardwareSerial(1) #endif @@ -88,6 +37,7 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Cannot run on chips with <=1 hardware UART, or with UART0.")); return false; } + #endif //ESP8266 or ESP32 if(outputPin < 1) return false; const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX_OUTPUT); @@ -104,15 +54,23 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { _dmxSerial = new HardwareSerial(uartNo); _uartNo = uartNo; + #ifdef ESP8266 + // Sadly no TX buffer. But at least still a TX FIFO. + _dmxSerial->begin(DMXSPEED, DMXFORMAT, SERIAL_TX_ONLY, outputPin); + #else // DMX_CHANNELS + SOC_UART_FIFO_LEN is the minimum that leads to full async operation. Don't ask me why. _dmxSerial->setTxBufferSize(DMX_CHANNELS + SOC_UART_FIFO_LEN); //641 = 1ms, 600 = 6ms, 514 = 10ms, 513-SOC_UART_FIFO_LEN=385 = 10ms, 185 = 17ms _dmxSerial->begin(DMXSPEED, DMXFORMAT, -1, outputPin); + #endif return true; } DMXOutput::~DMXOutput() { if(_uartNo >= 0) { + #ifdef ESP8266 + _dmxSerial->end(); + #endif delete _dmxSerial; // end() is implied in delete pinMode(_outputPin, INPUT); _uartNo = -1; @@ -189,8 +147,19 @@ bool DMXOutput::update() { */ bool DMXOutput::busy() { if(_uartNo < 0) return true; // not initialized - // not busy, if FIFO/ring buffer is empty + + #ifdef ESP8266 + if(uart_tx_fifo_available(_uartNo) == 0) { + // according to uart.cpp this is buggy and actually we have to wait for one transmission (11 baud) after this + // indicates TX is free. This makes every call take 45us which seems acceptable. + delayMicroseconds(11 * 1000000 / DMXSPEED + 1); + return false; + } else + return true; + #else + // not busy if tx idle. HardwareSerial.availableForWrite() didn't work reliable. return !uart_ll_is_tx_idle(UART_LL_GET_HW(_uartNo)); + #endif } #endif @@ -285,5 +254,3 @@ bool DMXOutput::handleDMXOutput() { return update(); // update the DMX bus, if available } - -#endif diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index ba73bb655d..40b5d66d04 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -1,17 +1,10 @@ /** * Unified DMX Output class for all platforms. - * - * On ESP8266: wraps DMXESPSerial (DMX via Serial1) - * On ESP32: uses own functions */ #ifndef DMX_OUTPUT_H #define DMX_OUTPUT_H -#if defined(ESP8266) -#include "src/dependencies/dmx/ESPDMX.h" -#endif - #define DMX_CHANNEL_TOP 512 #define DMX_CHANNELS DMX_CHANNEL_TOP + 1 @@ -36,13 +29,9 @@ class DMXOutput { void setUpdateRate(uint8_t updateRate); int timeToNextUpdate(); private: - #if defined(ESP8266) - DMXESPSerial _dmx; - #else HardwareSerial* _dmxSerial; uint8_t _outputPin; // DMX TX pin uint8_t _dmxData[DMX_CHANNELS] = {0}; - #endif int8_t _uartNo = -1; uint8_t _updateRate; unsigned long _lastDmxOutMillis = 0; diff --git a/wled00/src/dependencies/dmx/ESPDMX.cpp b/wled00/src/dependencies/dmx/ESPDMX.cpp deleted file mode 100644 index a80cad71c8..0000000000 --- a/wled00/src/dependencies/dmx/ESPDMX.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// - - - - - -// ESPDMX - A Arduino library for sending and receiving DMX using the builtin serial hardware port. -// ESPDMX.cpp: Library implementation file -// -// Copyright (C) 2015 Rick -// This work is licensed under a GNU style license. -// -// Last change: Marcel Seerig -// -// Documentation and samples are available at https://github.com/Rickgg/ESP-Dmx -// - - - - - - -/* ----- LIBRARIES ----- */ -#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) - -#include - -#include "ESPDMX.h" - - - -#define dmxMaxChannel 512 -#define defaultMax 32 - -#define DMXSPEED 250000 -#define DMXFORMAT SERIAL_8N2 -#define BREAKSPEED 83333 -#define BREAKFORMAT SERIAL_8N1 - -bool dmxStarted = false; -int sendPin = 2; //default on ESP8266 - -//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements -uint8_t dmxDataStore[dmxMaxChannel+1] = {}; -int channelSize; - - -void DMXESPSerial::init() { - channelSize = defaultMax; - - Serial1.begin(DMXSPEED); - pinMode(sendPin, OUTPUT); - dmxStarted = true; -} - -// Set up the DMX-Protocol -void DMXESPSerial::init(int chanQuant) { - - if (chanQuant > dmxMaxChannel || chanQuant <= 0) { - chanQuant = defaultMax; - } - - channelSize = chanQuant; - - Serial1.begin(DMXSPEED); - pinMode(sendPin, OUTPUT); - dmxStarted = true; -} - -// Function to read DMX data -uint8_t DMXESPSerial::read(int Channel) { - if (dmxStarted == false) init(); - - if (Channel < 1) Channel = 1; - if (Channel > dmxMaxChannel) Channel = dmxMaxChannel; - return(dmxDataStore[Channel]); -} - -// Function to send DMX data -void DMXESPSerial::write(int Channel, uint8_t value) { - if (dmxStarted == false) init(); - - if (Channel < 1) Channel = 1; - if (Channel > channelSize) Channel = channelSize; - if (value < 0) value = 0; - if (value > 255) value = 255; - - dmxDataStore[Channel] = value; -} - -void DMXESPSerial::end() { - channelSize = 0; - Serial1.end(); - dmxStarted = false; -} - -void DMXESPSerial::update() { - if (dmxStarted == false) init(); - - //Send break - digitalWrite(sendPin, HIGH); - Serial1.begin(BREAKSPEED, BREAKFORMAT); - Serial1.write(0); - Serial1.flush(); - delay(1); - Serial1.end(); - - //send data - Serial1.begin(DMXSPEED, DMXFORMAT); - digitalWrite(sendPin, LOW); - Serial1.write(dmxDataStore, channelSize); - Serial1.flush(); - delay(1); - Serial1.end(); -} - -// Function to update the DMX bus - -#endif diff --git a/wled00/src/dependencies/dmx/ESPDMX.h b/wled00/src/dependencies/dmx/ESPDMX.h deleted file mode 100644 index 4585bdd26f..0000000000 --- a/wled00/src/dependencies/dmx/ESPDMX.h +++ /dev/null @@ -1,31 +0,0 @@ -// - - - - - -// ESPDMX - A Arduino library for sending and receiving DMX using the builtin serial hardware port. -// ESPDMX.cpp: Library implementation file -// -// Copyright (C) 2015 Rick -// This work is licensed under a GNU style license. -// -// Last change: Marcel Seerig -// -// Documentation and samples are available at https://github.com/Rickgg/ESP-Dmx -// - - - - - - -#include - - -#ifndef ESPDMX_h -#define ESPDMX_h - -// ---- Methods ---- - -class DMXESPSerial { -public: - void init(); - void init(int MaxChan); - uint8_t read(int Channel); - void write(int channel, uint8_t value); - void update(); - void end(); -}; - -#endif From f2edd8d4523660b0e5f4a3dcf3223167112dc705 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:56:32 +0200 Subject: [PATCH 15/19] DMXOutput: Small improvements and move doc to header. --- wled00/dmx_output.cpp | 62 +++++++++++-------------------------------- wled00/dmx_output.h | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 6213454d34..68a2a33276 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -7,18 +7,13 @@ #else #include "hal/uart_ll.h" #endif -/* - * Support for DMX output via serial (e.g. MAX485). - */ - - /** - * Initialize DMXOutput. - * Use _outputPin_ for TX. - * _updateRate_ specifies update rate in Hz. Use 0 for max. - * Use Serial _uartNo_. Specify -1 for default, which is the highest one available. - */ + bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { + // If already initialized, users have to call end() first. We won't do it for them. + if(_uartNo > 0) + return false; + #ifdef ESP8266 if(uartNo == -1) uartNo = 1; if((uartNo != 1) || (outputPin != 2)) { @@ -66,7 +61,7 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { return true; } -DMXOutput::~DMXOutput() { +void DMXOutput::end() { if(_uartNo >= 0) { #ifdef ESP8266 _dmxSerial->end(); @@ -77,18 +72,15 @@ DMXOutput::~DMXOutput() { } } -/** - * Write one DMX _channel_ to _value_ - */ +DMXOutput::~DMXOutput() { + end(); +} + void DMXOutput::write(uint16_t channel, uint8_t value) { if(channel > DMX_CHANNEL_TOP) return; // out of bounds _dmxData[channel] = value; } -/** - * Write _len_ Bytes to DMX channels starting at _channelStart_. - * channelStart must be 1 or more. Use write() if you need to access channel 0. - */ void DMXOutput::writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { if(channelStart == 0) return; // channel 0 is no valid start channel, because it is special function for(int i = 0; i < len; i++) { @@ -97,18 +89,11 @@ void DMXOutput::writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len } } -/** - * Read one DMX _channel_ from output buffer - */ uint8_t DMXOutput::read(uint16_t channel) { if(channel > DMX_CHANNEL_TOP) return 0; // out of bounds return _dmxData[channel]; } -/** - * Read _len_ Bytes from DMX channels output buffer data starting at _channelStart_. - * Returns an array[len]. - */ bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) { if(channelStart + len > DMX_CHANNELS) return false; // out of bounds @@ -116,9 +101,6 @@ bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) return true; } -/** - * Send a new DMX frame of _dmxData out. - */ bool DMXOutput::update() { // false if not properly initialized if(_uartNo < 0) return false; @@ -142,9 +124,6 @@ bool DMXOutput::update() { return false; } -/** - * Whether the DMX is busy sending a frame. - */ bool DMXOutput::busy() { if(_uartNo < 0) return true; // not initialized @@ -161,28 +140,18 @@ bool DMXOutput::busy() { return !uart_ll_is_tx_idle(UART_LL_GET_HW(_uartNo)); #endif } -#endif -/** - * Get last time DMX Output was started in ms. - */ unsigned long DMXOutput::getLastDmxOut() { return _lastDmxOutMillis; } -/** - * Change update rate to _updateRate_. - */ void DMXOutput::setUpdateRate(uint8_t updateRate) { _updateRate = updateRate; } +uint8_t DMXOutput::getUpdateRate() { + return _updateRate; +} -/** - * Returns time in ms to next update to reach a maximum of _updateRate. - * Pay attention that when the last update was at let's say 100.9ms, at 122.0ms this reports as if 22ms had passed. - * To get a definitive max refresh rate, trigger update only on a result of -1. - * Returns negative numbers if the time has passed already. - */ int DMXOutput::timeToNextUpdate() { if(_uartNo < 0) return INT_MAX; // not initialized if(_updateRate == 0) return -1; // if refresh rate set to 0, refresh rate is max. @@ -195,9 +164,6 @@ int DMXOutput::timeToNextUpdate() { return dmxFrameTime - (millis() - _lastDmxOutMillis); } -/** - * Write LED data to DMX output buffer and send out. - */ bool DMXOutput::handleDMXOutput() { // don't act, when in DMX Proxy mode if (e131ProxyUniverse != 0) return false; @@ -254,3 +220,5 @@ bool DMXOutput::handleDMXOutput() { return update(); // update the DMX bus, if available } + +#endif diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index 40b5d66d04..1be9268743 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -1,5 +1,18 @@ /** * Unified DMX Output class for all platforms. + * + * ESP8266 uses the hw FIFO for sending frames. This is apparently 128 Bytes in size -> the 513 Bytes DMX frame will + * still take a big chunk of processor time. + * For all other architectures, the HAL ring buffer is activated, so that update() calls are almost non-blocking, but + * the first BREAK and MAB pulses of approx 150us in case of this implementation. + * + * ESP8266 uses some timeout in the busy() check that is needed according to the Arduino library code. This means + * calling busy() blocks approx 50us if the TX FIFO is empty to make sure really no transfer is ongoing anymore. + * busy() checks are implemented in the update() and handleDMXOutput() functions. + * + * Reducing the number of DMX channels makes no sense for ESP32, but to save a few Bytes of RAM. + * It would make sense for ESP8266, but is not implemented, yet. One would have to dynamically allocate the _dmxData + * array */ #ifndef DMX_OUTPUT_H @@ -17,17 +30,64 @@ class DMXOutput { public: ~DMXOutput(); + /** + * Initialize DMXOutput. + * Use _outputPin_ for TX. + * _updateRate_ specifies update rate in Hz. Use 0 for max. + * Use Serial _uartNo_. Specify -1 for default, which is the highest one available. + */ bool init(uint8_t outputPin, uint8_t updateRate = 44, int8_t uartNo = -1); + void end(); + /** + * Write one DMX _channel_ to _value_ + */ void write(uint16_t channel, uint8_t value); + /** + * Write _len_ Bytes to DMX channels starting at _channelStart_. + * channelStart must be 1 or more. Use write() if you need to access channel 0. + */ void writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len); + /** + * Read one DMX _channel_ from output buffer + */ uint8_t read(uint16_t channel); + /** + * Read _len_ Bytes from DMX channels output buffer data starting at _channelStart_. + * Returns an array[len]. + */ bool readBytes(uint16_t channelStart, uint8_t values[], uint16_t len); + /** + * Send a new DMX frame of _dmxData out. + */ bool update(); + /** + * Write LED data to DMX output buffer and send out. + */ bool handleDMXOutput(); + /** + * Get last time DMX Output was started in ms. + */ unsigned long getLastDmxOut(); + /** + * Whether the DMX is busy sending a frame. + */ bool busy(); + /** + * Change update rate to _updateRate_. + */ void setUpdateRate(uint8_t updateRate); + /** + * Get current _updateRate_. + */ + uint8_t getUpdateRate(); + /** + * Returns time in ms to next update to reach a maximum of _updateRate. + * Pay attention that when the last update was at let's say 100.9ms, at 122.0ms this reports as if 22ms had passed. + * To get a definitive max refresh rate, trigger update only on a result of -1. + * Returns negative numbers if the time has passed already. + */ int timeToNextUpdate(); + private: HardwareSerial* _dmxSerial; uint8_t _outputPin; // DMX TX pin From c3fc87a44491aa5b7af567db5fc218d6590abd1c Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:30:41 +0200 Subject: [PATCH 16/19] DMXOutput: Minor fixes --- wled00/data/settings_dmx.htm | 2 +- wled00/data/settings_sync.htm | 6 +++--- wled00/dmx_output.cpp | 25 ++++++++++++++----------- wled00/dmx_output.h | 7 +++++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/wled00/data/settings_dmx.htm b/wled00/data/settings_dmx.htm index 14e8146eb0..95c6904ff9 100644 --- a/wled00/data/settings_dmx.htm +++ b/wled00/data/settings_dmx.htm @@ -54,7 +54,7 @@

DMX Output Settings

-

For pin settings, go to Sync settings.

+

For pin settings, go to Sync settings.

Proxy Universe from E1.31 to DMX (0=disabled)
This will disable the LED data output to DMX configurable below

diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 6c543aee9a..7ab7b224f9 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -52,7 +52,7 @@ function hideNoDMXInput(){gId("dmxInputOff").style.display="none";} function hideNoDMXOutput(){ gId("dmxOutputOff").style.display="none"; - gId("dmxOutput").style.display="inline"; + gId("dmxOutput").style.display="block"; } @@ -177,14 +177,14 @@

Realtime

Disable realtime gamma correction:
Realtime LED offset:
-

Wired DMX Input Pins

+

Wired DMX Input Pins

DMX RX: RO
DMX TX: DI
DMX Enable: RE+DE
DMX Port:
diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 68a2a33276..12e5f342b9 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -3,15 +3,16 @@ #include "wled.h" #include "dmx_output.h" #ifdef ESP8266 -#include "uart.cpp" +#include "uart.h" +#include "esp8266_peri.h" #else #include "hal/uart_ll.h" #endif -bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { +bool DMXOutput::init(int8_t outputPin, uint8_t updateRate, int8_t uartNo) { // If already initialized, users have to call end() first. We won't do it for them. - if(_uartNo > 0) + if(_uartNo >= 0) return false; #ifdef ESP8266 @@ -21,9 +22,7 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { return false; } #else //not ESP8266 - #if SOC_UART_NUM <= 1 - #error DMX output is not possible on your MCU, as it does not have HardwareSerial(1) - #endif + static_assert(SOC_UART_NUM > 1, "DMX output is not possible on your MCU, as it does not have HardwareSerial(1)"); if(uartNo == -1) { uartNo = SOC_UART_NUM - 1; // use last UART as default @@ -34,7 +33,7 @@ bool DMXOutput::init(uint8_t outputPin, uint8_t updateRate, int8_t uartNo) { } #endif //ESP8266 or ESP32 - if(outputPin < 1) return false; + if(outputPin < 0) return false; const bool pinAllocated = PinManager::allocatePin(outputPin, true, PinOwner::DMX_OUTPUT); if(!pinAllocated) { DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Failed to allocate pin %d for DMX output\n"), outputPin); @@ -128,9 +127,12 @@ bool DMXOutput::busy() { if(_uartNo < 0) return true; // not initialized #ifdef ESP8266 - if(uart_tx_fifo_available(_uartNo) == 0) { - // according to uart.cpp this is buggy and actually we have to wait for one transmission (11 baud) after this - // indicates TX is free. This makes every call take 45us which seems acceptable. + // uart_tx_fifo_available is inline-only in uart.cpp, reproduce it here: + size_t uart_tx_fifo_available = (USS(_uartNo) >> USTXC) & 0xff + + if(uart_tx_fifo_available == 0) { + // according to uart.cpp there can be one more transmission (11 baud) after tx_fifo is empty, so we'll wait just + // in case. This makes every call take 45us which seems acceptable. delayMicroseconds(11 * 1000000 / DMXSPEED + 1); return false; } else @@ -178,7 +180,8 @@ bool DMXOutput::handleDMXOutput() { } uint16_t len = strip.getLengthTotal(); - uint16_t maxLen = (DMX_CHANNELS - DMXStart) / DMXGap; // maximum LEDs that fit into one physical DMX512 universe + if(DMXGap < 1) DMXGap = 1; // failsafe + uint16_t maxLen = (DMX_CHANNELS - DMXStart) / DMXGap; // maximum LEDs that fit into one physical DMX512 universe if (len > maxLen) len = maxLen; for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index 1be9268743..45e8c802b8 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -1,5 +1,6 @@ /** * Unified DMX Output class for all platforms. + * Maximum update rate is 43Hz for 512 DMX channels. * * ESP8266 uses the hw FIFO for sending frames. This is apparently 128 Bytes in size -> the 513 Bytes DMX frame will * still take a big chunk of processor time. @@ -18,8 +19,10 @@ #ifndef DMX_OUTPUT_H #define DMX_OUTPUT_H +#include + #define DMX_CHANNEL_TOP 512 -#define DMX_CHANNELS DMX_CHANNEL_TOP + 1 +#define DMX_CHANNELS (DMX_CHANNEL_TOP + 1) #define DMXSPEED 250000 #define DMXFORMAT SERIAL_8N2 @@ -36,7 +39,7 @@ class DMXOutput { * _updateRate_ specifies update rate in Hz. Use 0 for max. * Use Serial _uartNo_. Specify -1 for default, which is the highest one available. */ - bool init(uint8_t outputPin, uint8_t updateRate = 44, int8_t uartNo = -1); + bool init(int8_t outputPin, uint8_t updateRate = 43, int8_t uartNo = -1); void end(); /** * Write one DMX _channel_ to _value_ From 8b8cc0720692d7cb36f42d6b2faa06edb4606fa0 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:49:32 +0200 Subject: [PATCH 17/19] Revert "DMX: Rename define from WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT" This reverts commit bc80c4a1264997ea2330dbd75c3369ea73b79176. It does keep the PinManager name change from DMX to DMX_OUTPUT! --- platformio_override.sample.ini | 2 +- tools/cdata.js | 2 +- wled00/cfg.cpp | 8 ++++---- wled00/dmx_input.cpp | 2 +- wled00/dmx_output.cpp | 2 +- wled00/e131.cpp | 4 ++-- wled00/set.cpp | 4 ++-- wled00/wled.cpp | 6 +++--- wled00/wled.h | 7 ++++--- wled00/wled_server.cpp | 10 +++++----- wled00/xml.cpp | 8 ++++---- 11 files changed, 28 insertions(+), 27 deletions(-) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index c5144a6945..bf7a1314d6 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -54,7 +54,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; enable optional built-in features ; -D WLED_ENABLE_PIXART ; -D WLED_ENABLE_USERMOD_PAGE # if created -; -D WLED_ENABLE_DMX_OUTPUT +; -D WLED_ENABLE_DMX ; ; PIN defines - uncomment and change, if needed: ; -D DATA_PINS=2 diff --git a/tools/cdata.js b/tools/cdata.js index 97ed380890..5ae7088b3e 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -433,7 +433,7 @@ writeChunks( method: "plaintext", filter: "html-minify", mangle: (str) => ` -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX ${str.replace(/function FM\(\)[ ]?\{/gms, "function FM() {%DMXVARS%\n")} #else const char PAGE_dmxmap[] PROGMEM = R"=====()====="; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d91c659103..fe6bbafc7e 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -607,7 +607,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { tdd = if_live[F("timeout")] | -1; if (tdd >= 0) realtimeTimeoutMs = tdd * 100; - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX CJSON(dmxOutputPin, if_live_dmx[F("dmxOutputPin")]); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -733,7 +733,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(otaSameSubnet, ota[F("same-subnet")]); } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX JsonObject dmx = doc["dmx"]; CJSON(DMXChannels, dmx[F("chan")]); CJSON(DMXGap,dmx[F("gap")]); @@ -1127,7 +1127,7 @@ void serializeConfig(JsonObject root) { if_live_dmx[F("addr")] = DMXAddress; if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx["mode"] = DMXMode; - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX if_live_dmx[F("dmxOutputPin")] = dmxOutputPin; #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -1240,7 +1240,7 @@ void serializeConfig(JsonObject root) { #endif ota[F("same-subnet")] = otaSameSubnet; - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX JsonObject dmx = root.createNestedObject("dmx"); dmx[F("chan")] = DMXChannels; dmx[F("gap")] = DMXGap; diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 83ab606688..8b67ca14bb 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -125,7 +125,7 @@ bool DMXInput::installDriver() void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum) { -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX //TODO add again once dmx output has been merged // if(inputPortNum == dmxOutputPort) // { diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 12e5f342b9..0fe671ee60 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -1,4 +1,4 @@ -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX #include "wled.h" #include "dmx_output.h" diff --git a/wled00/e131.cpp b/wled00/e131.cpp index c680dba01a..475e05af6d 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -114,7 +114,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ return; } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX // does not act on out-of-order packets yet if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { dmxOutput.writeBytes(1, &e131_data[1], dmxChannels); @@ -408,7 +408,7 @@ static void handleArtnetPollReply(IPAddress ipAddress) { } } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX if (e131ProxyUniverse > 0 && (DMXMode == DMX_MODE_DISABLED || (e131ProxyUniverse < startUniverse || e131ProxyUniverse > endUniverse))) { sendArtnetPollReply(&artnetPollReply, ipAddress, e131ProxyUniverse); } diff --git a/wled00/set.cpp b/wled00/set.cpp index 04f8e0241c..b338b6b02a 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -474,7 +474,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) t = request->arg(F("WO")).toInt(); if (t >= -255 && t <= 255) arlsOffset = t; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX dmxOutputPin = request->arg(F("IDMO")).toInt(); #endif #ifdef WLED_ENABLE_DMX_INPUT @@ -673,7 +673,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } } - #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { int t = request->arg(F("PU")).toInt(); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index eed894d3c8..2f84ebffd3 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -70,7 +70,7 @@ void WLED::loop() handleImprovWifiScan(); handleNotifications(); handleTransitions(); - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX #ifdef WLED_DEBUG unsigned long dmxMillis = millis(); #endif @@ -80,7 +80,7 @@ void WLED::loop() maxDmxMillis = dmxMillis > maxDmxMillis ? dmxMillis : maxDmxMillis; avgDmxMillis += dmxMillis; #endif //WLED_DEBUG - #endif //WLED_ENABLE_DMX_OUTPUT + #endif //WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX_INPUT dmxInput.update(); #endif @@ -568,7 +568,7 @@ void WLED::setup() ArduinoOTA.setHostname(cmDNS); } #endif -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX dmxOutput.init(dmxOutputPin, 43); #endif #ifdef WLED_ENABLE_DMX_INPUT diff --git a/wled00/wled.h b/wled00/wled.h index 4db45de657..38d85024a5 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -40,7 +40,7 @@ #else #undef WLED_ENABLE_ADALIGHT // disable has priority over enable #endif -//#define WLED_ENABLE_DMX_OUTPUT // uses 3.5kb +//#define WLED_ENABLE_DMX // uses 3.5kb #ifndef WLED_DISABLE_LOXONE #define WLED_ENABLE_LOXONE // uses 1.2kb #endif @@ -141,9 +141,10 @@ #include "src/dependencies/espalexa/EspalexaDevice.h" #endif -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX #include "dmx_output.h" #endif + #ifdef WLED_ENABLE_DMX_INPUT #include "dmx_input.h" #endif @@ -448,7 +449,7 @@ WLED_GLOBAL int arlsOffset _INIT(0); // realtime LE WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX WLED_GLOBAL DMXOutput dmxOutput; #ifndef DMX_TXPIN_DEFAULT #define DMX_TXPIN_DEFAULT -1 diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index d7409850e5..f7aac7fa50 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -136,7 +136,7 @@ static void handleStaticContent(AsyncWebServerRequest *request, const String &pa request->send(response); } -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX static String dmxProcessor(const String& var) { String mapJS; @@ -601,7 +601,7 @@ void initServer() }); #endif -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor); }); @@ -742,7 +742,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { else if (url.indexOf( "sync") > 0) subPage = SUBPAGE_SYNC; else if (url.indexOf( "time") > 0) subPage = SUBPAGE_TIME; else if (url.indexOf(F("sec")) > 0) subPage = SUBPAGE_SEC; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX else if (url.indexOf( "dmx") > 0) subPage = SUBPAGE_DMX; #endif else if (url.indexOf( "um") > 0) subPage = SUBPAGE_UM; @@ -786,7 +786,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : strcpy_P(s, PSTR("Sync")); break; case SUBPAGE_TIME : strcpy_P(s, PSTR("Time")); break; case SUBPAGE_SEC : strcpy_P(s, PSTR("Security")); if (doReboot) strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX case SUBPAGE_DMX : strcpy_P(s, PSTR("DMX")); break; #endif case SUBPAGE_UM : strcpy_P(s, PSTR("Usermods")); break; @@ -821,7 +821,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : content = PAGE_settings_sync; len = PAGE_settings_sync_length; break; case SUBPAGE_TIME : content = PAGE_settings_time; len = PAGE_settings_time_length; break; case SUBPAGE_SEC : content = PAGE_settings_sec; len = PAGE_settings_sec_length; break; -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break; #endif case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 5526700db7..cd35121753 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -121,7 +121,7 @@ static void appendGPIOinfo(Print& settingsScript) firstPin = false; } } - #ifdef WLED_ENABLE_DMX_OUTPUT + #ifdef WLED_ENABLE_DMX if (dmxOutputPin > 0) { if (!firstPin) settingsScript.print(','); settingsScript.print(dmxOutputPin); // DMX output pin @@ -203,7 +203,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in settingsScript.print(F("gId('2dbtn').style.display='none';")); #endif - #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled settingsScript.print(F("gId('dmxbtn').style.display='';")); #endif } @@ -494,7 +494,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); printSetFormValue(settingsScript,PSTR("EU"),e131Universe); -#ifdef WLED_ENABLE_DMX_OUTPUT +#ifdef WLED_ENABLE_DMX settingsScript.print(SET_F("hideNoDMXOutput();")); // hide "not compiled in" message, show output pin field printSetFormValue(settingsScript,SET_F("IDMO"), dmxOutputPin); #endif @@ -659,7 +659,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #endif } - #ifdef WLED_ENABLE_DMX_OUTPUT // include only if DMX is enabled + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { printSetFormValue(settingsScript,PSTR("PU"),e131ProxyUniverse); From 8c658ce190a39d51cea35364d21e8723b775bdc4 Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:40:40 +0200 Subject: [PATCH 18/19] DMXOutput: More AI review fixes. --- wled00/dmx_output.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 0fe671ee60..942ba8c471 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -68,6 +68,8 @@ void DMXOutput::end() { delete _dmxSerial; // end() is implied in delete pinMode(_outputPin, INPUT); _uartNo = -1; + + PinManager::deallocatePin(_outputPin, PinOwner::DMX_OUTPUT); } } @@ -128,7 +130,7 @@ bool DMXOutput::busy() { #ifdef ESP8266 // uart_tx_fifo_available is inline-only in uart.cpp, reproduce it here: - size_t uart_tx_fifo_available = (USS(_uartNo) >> USTXC) & 0xff + size_t uart_tx_fifo_available = (USS(_uartNo) >> USTXC) & 0xff; if(uart_tx_fifo_available == 0) { // according to uart.cpp there can be one more transmission (11 baud) after tx_fifo is empty, so we'll wait just From be1c0309167ca9313d788b6f5d8206c7d489499d Mon Sep 17 00:00:00 2001 From: Stefan Knipp <4386179+Mdbelen@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:47:03 +0200 Subject: [PATCH 19/19] DMX: Fix some review findings. --- wled00/const.h | 7 ++++++- wled00/dmx_output.cpp | 2 ++ wled00/dmx_output.h | 11 +++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/wled00/const.h b/wled00/const.h index 4b92963215..e49dd2900a 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -652,7 +652,12 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); // Defaults pins, type and counts to configure LED output #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) - #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board + #ifdef WLED_ENABLE_DMX + #define DEFAULT_LED_PIN 1 + #warning "Compiling with DMX. The default LED pin has been changed to pin 1." + #else + #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board + #endif #else #if defined(WLED_USE_ETHERNET) #define DEFAULT_LED_PIN 4 // GPIO4 seems to be a "safe bet" for all known ethernet boards (issue #5155) diff --git a/wled00/dmx_output.cpp b/wled00/dmx_output.cpp index 942ba8c471..a85255241d 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -47,6 +47,7 @@ bool DMXOutput::init(int8_t outputPin, uint8_t updateRate, int8_t uartNo) { _updateRate = updateRate; _dmxSerial = new HardwareSerial(uartNo); _uartNo = uartNo; + _dmxData[0] = 0; // make sure start code is 0 #ifdef ESP8266 // Sadly no TX buffer. But at least still a TX FIFO. @@ -120,6 +121,7 @@ bool DMXOutput::update() { _dmxSerial->updateBaudRate(DMXSPEED); //change to regular DMX speed _dmxSerial->write(_dmxData, DMX_CHANNELS); + _dmxData[0] = 0; // reset DMX start code to default return true; } return false; diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h index 45e8c802b8..30e966ac49 100644 --- a/wled00/dmx_output.h +++ b/wled00/dmx_output.h @@ -42,12 +42,14 @@ class DMXOutput { bool init(int8_t outputPin, uint8_t updateRate = 43, int8_t uartNo = -1); void end(); /** - * Write one DMX _channel_ to _value_ + * Write one DMX _channel_ to _value_. + * Pay attention about channel 0. This is the DMX start code and does not carry light data. Leave at 0 if unsure. + * Channel 0 will be reset to 0 after every successful update(). */ void write(uint16_t channel, uint8_t value); /** * Write _len_ Bytes to DMX channels starting at _channelStart_. - * channelStart must be 1 or more. Use write() if you need to access channel 0. + * channelStart must be 1 or more. Use write() if you need to access channel 0 (start code). */ void writeBytes(uint16_t channelStart, uint8_t values[], uint16_t len); /** @@ -55,8 +57,8 @@ class DMXOutput { */ uint8_t read(uint16_t channel); /** - * Read _len_ Bytes from DMX channels output buffer data starting at _channelStart_. - * Returns an array[len]. + * Read _len_ Bytes from DMX channels output buffer data starting at _channelStart_ (this may include 0). + * Returns true if read was successful (within bounds). */ bool readBytes(uint16_t channelStart, uint8_t values[], uint16_t len); /** @@ -65,6 +67,7 @@ class DMXOutput { bool update(); /** * Write LED data to DMX output buffer and send out. + * _dmxData[0] will be reset to 0 after every successful run. */ bool handleDMXOutput(); /**