Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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; // Subtract 1 to round up
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (millis() - last_dmx_time < dmxFrameTime) return;
Comment thread
Stijn-Jacobs marked this conversation as resolved.

uint8_t brightness = strip.getBrightness();

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

Choose a reason for hiding this comment

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

Some fixtures have a manual mode in case they don't receive a DMX signal. For example they could jump to a static color, where they should be off. This line should be removed IMO.

Copy link
Copy Markdown
Author

@Stijn-Jacobs Stijn-Jacobs Apr 17, 2026

Choose a reason for hiding this comment

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

Hmm this is an interesting one, I agree with you. If you have DMX on in the build, we can assume you intend to use it with DMX and we can always output zero values to the bus, even if this means impacting performance negatively.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good spot @Mdbelen I totally agree


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
Copy link
Copy Markdown

@Mdbelen Mdbelen Apr 17, 2026

Choose a reason for hiding this comment

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

This is a dead define, as DMXOutput::init calls EspDmxOutput::initWrite always with 512. Should be removed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Well 44hz is the actual hard limit of what the spec can handle. The limiting was there in place to increase performance dramatically before using the old driver, and thus was ported from there. It should have less impact right now, but will still have some negative impact on the actual framerate you will achieve on a microcontroller.

I think at the moment fixtures with different channel numbers are not really properly supported globally (while you ofcourse can set it to the value of the fixture with the highest fixture count, and setup your channels properly on the fixtures).

I think its a good point you mention, I'll also do some more testing with 512 channels and see if it impacts the framerate as it did before.


// 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;
Comment on lines +23 to +30
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can use either HardwareSerial1 or HardwareSerial2 - we just can't use the same one for both DMX input and DMX output (or any other conflicting usage)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Previously, I got the impression that SparkFunDMX was only used with HardwareSerial2, i.e. all ESPs with < 3 UARTs like the S2 would fallback to ESPDMX bitbanging, right? Do you know why that was, was serial1 reserved for something like IR, NPB, etc and is this not needed anymore? Just asking out of interest I guess.


// 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};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do we need this? This is not read ever, is it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right, reminiscence of the old driver. I'll remove it.

// 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 thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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