Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(DMXGap,dmx[F("gap")]);
CJSON(DMXStart, dmx["start"]);
CJSON(DMXStartLED,dmx[F("start-led")]);
CJSON(DMXNumFixtures, dmx[F("num-fixtures")]);

JsonArray dmx_fixmap = dmx[F("fixmap")];
for (int i = 0; i < dmx_fixmap.size(); i++) {
Expand Down Expand Up @@ -1235,6 +1236,7 @@ void serializeConfig(JsonObject root) {
dmx[F("gap")] = DMXGap;
dmx["start"] = DMXStart;
dmx[F("start-led")] = DMXStartLED;
dmx[F("num-fixtures")] = DMXNumFixtures;

JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (unsigned i = 0; i < 15; i++) {
Expand Down
4 changes: 2 additions & 2 deletions wled00/data/settings_dmx.htm
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ <h2>Imma firin ma lazer (if it has DMX support)</h2><!-- TODO: Change to somethi

Proxy Universe <input name=PU type=number min=0 max=63999 required> from E1.31 to DMX (0=disabled)<br>
<i>This will disable the LED data output to DMX configurable below</i><br><br>
<i>Number of fixtures is taken from LED config page</i><br>

Channels per fixture (15 max): <input type="number" min="1" max="15" name="CN" maxlength="2" onchange="mMap();"><br />
Start channel: <input type="number" min="1" max="512" name="CS" maxlength="2"><br />
Spacing between start channels: <input type="number" min="1" max="512" name="CG" maxlength="2" onchange="mMap();"> [ <a href="javascript:alert('if set to 10, first fixture will start at 10,\nsecond will start at 20 etc.\nRegardless of the channel count.\nMakes memorizing channel numbers easier.');">info</a> ]<br>
<div id="gapwarning" style="color: orange; display: none;">WARNING: Channel gap is lower than channels per fixture.<br />This will cause overlap.</div>
<button type="button" onclick="location.href='/dmxmap';">DMX Map</button><br>
DMX fixtures start LED: <input type="number" min="0" max="1500" name="SL">
DMX fixtures start LED: <input type="number" min="0" max="1500" name="SL"><br>
Number of fixtures: <input type="number" min="1" max="512" name="NF">
<h3>Channel functions</h3>
<div id="dmxchannels"></div>
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
Expand Down
31 changes: 26 additions & 5 deletions wled00/dmx_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,36 @@
/*
* 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)
* Change the output pin in src/dependencies/dmx/EspDmxOutput.cpp, if needed (ESP32/S3)
* ESP8266 Library from:
* https://github.com/Rickgg/ESP-Dmx
* ESP32 Library from:
* https://github.com/sparkfun/SparkFunDMX
* https://github.com/someweisguy/esp_dmx
*/

#ifdef WLED_ENABLE_DMX

#define MAX_DMX_RATE 44 // max DMX update rate in Hz

void handleDMXOutput()
{
// don't act, when in DMX Proxy mode
if (e131ProxyUniverse != 0) return;

// Ensure segments exist before accessing strip data
if (strip.getSegmentsNum() == 0) return;

// Rate limiting
static unsigned long last_dmx_time = 0;
const unsigned long dmxFrameTime = (1000UL + MAX_DMX_RATE - 1) / MAX_DMX_RATE;
if (millis() - last_dmx_time < dmxFrameTime) return;

uint8_t brightness = strip.getBrightness();

// Skip DMX entirely if strip is off
if (brightness == 0) return;

last_dmx_time = millis();

bool calc_brightness = true;

Expand All @@ -28,9 +43,15 @@ void handleDMXOutput()
}

uint16_t len = strip.getLengthTotal();
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
if (len == 0) return;

// OPTIMIZATION: Only process the LEDs that actually need DMX output
// Limit to configured number of fixtures instead of processing all LEDs
uint16_t dmxEndLED = DMXStartLED + DMXNumFixtures;
if (dmxEndLED > len) dmxEndLED = len;

for (int i = DMXStartLED; i < dmxEndLED; i++) {
uint32_t in = strip.getPixelColor(i);
byte w = W(in);
byte r = R(in);
byte g = G(in);
Expand Down
4 changes: 4 additions & 0 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t>=0 && t < MAX_LEDS) {
DMXStartLED = t;
}
t = request->arg(F("NF")).toInt();
if (t>0 && t<=512) {
DMXNumFixtures = t;
}
for (int i=0; i<15; i++) {
String argname = "CH" + String((i+1));
t = request->arg(argname).toInt();
Expand Down
93 changes: 93 additions & 0 deletions wled00/src/dependencies/dmx/EspDmxOutput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/******************************************************************************
* EspDmxOutput.cpp
*
* DMX output via `esp_dmx` on ESP32.
* Keeps the minimal API WLED uses: initWrite(), write(), update().
******************************************************************************/

/* ----- LIBRARIES ----- */
#if defined(ARDUINO_ARCH_ESP32)

#include <Arduino.h>
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2)

#include "EspDmxOutput.h"
#include <esp_dmx.h>

#define dmxMaxChannel 512
#define defaultMax 32

// 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 constexpr dmx_port_t dmxPort = DMX_NUM_2;

// Pin defaults. Change these if needed for your hardware.
static const int txPin = 2; // transmit DMX data over this pin (default is GPIO2)
static const int rxPin = DMX_PIN_NO_CHANGE; // RX unused for DMX output
static const int rtsPin = DMX_PIN_NO_CHANGE; // RS485 DE/RE pin (UART RTS). Set to a GPIO to control transceiver direction.

// DMX value array and size. Entry 0 holds start code, so we need 512+1 elements.
static uint8_t dmxData[DMX_PACKET_SIZE] = {0};
// Maximum slot count configured by initWrite() (includes start code slot 0).
static size_t maxSize = 0;
// Current transmit size (includes start code slot 0). This grows as channels are written.
static size_t txSize = 1;
static bool dmxInstalled = false;

void EspDmxOutput::initWrite(int chanQuant) {
if (chanQuant > dmxMaxChannel || chanQuant <= 0) chanQuant = defaultMax;
maxSize = static_cast<size_t>(chanQuant) + 1; // +1 for start code
txSize = 1; // start with just start code

// Configure the driver if needed.
if (!dmx_driver_is_installed(dmxPort)) {
dmx_config_t config = DMX_CONFIG_DEFAULT;
dmxInstalled = dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
} else {
dmxInstalled = true;
}

if (dmxInstalled) {
dmx_set_pin(dmxPort, txPin, rxPin, rtsPin);

// Ensure NULL start code (slot 0).
dmxData[0] = DMX_SC;
dmx_write_slot(dmxPort, 0, DMX_SC);
}
}
Comment on lines +42 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix boolean logic for dmx_driver_install return value handling.

Line 50 incorrectly assigns dmx_driver_install return value directly to dmxInstalled. Since dmx_driver_install returns esp_err_t (where ESP_OK = 0 indicates success), successful installation would set dmxInstalled = false, preventing all subsequent DMX operations.

-    dmxInstalled = dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
+    dmxInstalled = (dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT) == ESP_OK);
🤖 Prompt for AI Agents
In wled00/src/dependencies/dmx/EspDmxOutput.cpp around lines 42 to 62, the code
assigns the esp_err_t return from dmx_driver_install directly to the bool
dmxInstalled which treats ESP_OK (0) as false; instead capture the return into
an esp_err_t variable and set dmxInstalled to (err == ESP_OK). Optionally log or
handle non-ESP_OK errors before proceeding so dmxInstalled correctly reflects
success and subsequent DMX setup runs only when installation succeeded.


void EspDmxOutput::write(int Channel, uint8_t value) {
if (!dmxInstalled) return;

// Allow slot 0 writes, but start code is enforced in update().
if (Channel < 0) Channel = 0;
if (Channel > dmxMaxChannel) Channel = dmxMaxChannel;

const size_t neededSize = static_cast<size_t>(Channel) + 1;
if (neededSize > txSize) txSize = neededSize;
if (maxSize > 0 && txSize > maxSize) txSize = maxSize;
if (txSize > DMX_PACKET_SIZE) txSize = DMX_PACKET_SIZE;

dmxData[Channel] = value;
dmx_write_slot(dmxPort, static_cast<size_t>(Channel), value);
}

void EspDmxOutput::update() {
if (!dmxInstalled) return;

// Always send the break signal first
dmx_write_slot(dmxPort, 0, DMX_SC);

// Send the frame currently staged in the driver buffer.
dmx_send(dmxPort, txSize);
}

#endif
#endif


17 changes: 17 additions & 0 deletions wled00/src/dependencies/dmx/EspDmxOutput.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* EspDmxOutput.h
*
* WLED DMX output implementation for ESP32 using the `esp_dmx` library.
*/
#pragma once

#include <inttypes.h>

class EspDmxOutput {
public:
void initWrite(int maxChan);
void write(int channel, uint8_t value);
void update();
};


182 changes: 0 additions & 182 deletions wled00/src/dependencies/dmx/SparkFunDMX.cpp

This file was deleted.

Loading