-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Non-blocking, unified, dependency-less DMXOutput #5522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 18 commits
8841a88
4996e17
3a67e38
1607445
fb8cb5f
5087478
ee3141a
bc80c4a
da582e9
2ef4209
df76e37
fe41232
32806ae
0d0a646
f2edd8d
c3fc87a
8b8cc07
8c658ce
be1c030
d5e1354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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="block"; | ||
| } | ||
| </script> | ||
| </head> | ||
| <body> | ||
|
|
@@ -174,17 +177,21 @@ <h3>Realtime</h3> | |
| Disable realtime gamma correction: <input type="checkbox" name="RG"><br> | ||
| Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required> | ||
| <div id="dmxInput"> | ||
| <h4>Wired DMX Input Pins</h4> | ||
| <h3>Wired DMX Input Pins</h3> | ||
| DMX RX: <input name="IDMR" type="number" min="-1" max="99">RO<br/> | ||
| DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/> | ||
| DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/> | ||
| DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/> | ||
| </div> | ||
| <div id="dmxOutput" style="display: none"> | ||
| <h3>Wired DMX Output Pin</h3> | ||
| DMX TX: <input name="IDMO" type="number" min="-1" max="99"><br/> | ||
| </div> | ||
|
Comment on lines
+205
to
+208
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add the missing reboot-required hint for This setting is only applied during 💡 Proposed change <div id="dmxOutput" style="display: none">
<h3>Wired DMX Output Pin</h3>
DMX TX: <input name="IDMO" type="number" min="-1" max="99"><br/>
+ <i class="warn">Reboot required to apply changes.</i>
</div>🤖 Prompt for AI Agents |
||
| <div id="dmxInputOff"> | ||
| <br><i class="warn">This firmware build does not include DMX Input support. <br></i> | ||
| <br><i class="warn">This firmware build does not include DMX Input support.</i> | ||
| </div> | ||
| <div id="dmxOnOffOutput"> | ||
| <br><i class="warn">This firmware build does not include DMX output support. <br></i> | ||
| <div id="dmxOutputOff"> | ||
| <br><i class="warn">This firmware build does not include DMX output support.</i> | ||
| </div> | ||
| </div> | ||
| <div class="sec"> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,191 @@ | ||
| #ifdef WLED_ENABLE_DMX | ||
|
|
||
| #include "wled.h" | ||
| #include "dmx_output.h" | ||
| #ifdef ESP8266 | ||
| #include "uart.h" | ||
| #include "esp8266_peri.h" | ||
| #else | ||
| #include "hal/uart_ll.h" | ||
| #endif | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /* | ||
| * 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) | ||
| * ESP8266 Library from: | ||
| * https://github.com/Rickgg/ESP-Dmx | ||
| * ESP32 Library from: | ||
| * https://github.com/sparkfun/SparkFunDMX | ||
| */ | ||
| bool DMXOutput::init(int8_t outputPin, uint8_t updateRate, int8_t uartNo) { | ||
|
|
||
| #ifdef WLED_ENABLE_DMX | ||
| // 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)) { | ||
| DEBUG_PRINTF_P(PSTR("DMXOutput: Can only run with UART1, TX pin 2 on ESP8266.")); | ||
| return false; | ||
| } | ||
| #else //not ESP8266 | ||
| 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 | ||
| } | ||
| if(uartNo == 0) { | ||
| DEBUG_PRINTF_P(PSTR("DMXOutput: Error: Cannot run on chips with <=1 hardware UART, or with UART0.")); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this restriction necessary?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. I'm inclined to say I only updated some module here and these restrictions applied before too, even more restrictive. SparkFunDMX would throw an error if HardwareSerial(2) didn't exist.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point. We really need a resource manager, there is a growing list of features that have hardware resource conflicts. This is something that came up in discussions several times, it is only a question of when and how. |
||
| return false; | ||
| } | ||
| #endif //ESP8266 or ESP32 | ||
|
|
||
| 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); | ||
| 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); | ||
| _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 | ||
|
|
||
| void handleDMXOutput() | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| void DMXOutput::end() { | ||
| if(_uartNo >= 0) { | ||
| #ifdef ESP8266 | ||
| _dmxSerial->end(); | ||
| #endif | ||
| delete _dmxSerial; // end() is implied in delete | ||
| pinMode(_outputPin, INPUT); | ||
| _uartNo = -1; | ||
|
|
||
| PinManager::deallocatePin(_outputPin, PinOwner::DMX_OUTPUT); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| DMXOutput::~DMXOutput() { | ||
| end(); | ||
| } | ||
|
|
||
| void DMXOutput::write(uint16_t channel, uint8_t value) { | ||
| if(channel > DMX_CHANNEL_TOP) return; // out of bounds | ||
| _dmxData[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) { | ||
| if(channel > DMX_CHANNEL_TOP) return 0; // out of bounds | ||
| return _dmxData[channel]; | ||
| } | ||
|
|
||
| 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; | ||
|
Comment on lines
+99
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a non-overflowing bounds check before
🛡️ Proposed fix bool DMXOutput::readBytes(uint16_t channelStart, uint8_t values[], uint16_t len) {
- if(channelStart + len > DMX_CHANNELS) return false; // out of bounds
+ if(channelStart > DMX_CHANNELS) return false;
+ if(len > DMX_CHANNELS - channelStart) return false; // out of bounds
memcpy(values, &_dmxData[channelStart], len);
return true;
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| 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) && !busy()) { | ||
| _lastDmxOutMillis = millis(); | ||
|
|
||
| // 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 | ||
| _dmxSerial->updateBaudRate(DMXSPEED); //change to regular DMX speed | ||
| _dmxSerial->write(_dmxData, DMX_CHANNELS); | ||
|
|
||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| bool DMXOutput::busy() { | ||
| if(_uartNo < 0) return true; // not initialized | ||
|
|
||
| #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; | ||
|
|
||
| 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 | ||
| 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 | ||
| } | ||
|
|
||
| unsigned long DMXOutput::getLastDmxOut() { | ||
| return _lastDmxOutMillis; | ||
| } | ||
|
|
||
| void DMXOutput::setUpdateRate(uint8_t updateRate) { | ||
| _updateRate = updateRate; | ||
| } | ||
| uint8_t DMXOutput::getUpdateRate() { | ||
| return _updateRate; | ||
| } | ||
|
|
||
| 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 | ||
| 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); | ||
| } | ||
|
|
||
| bool DMXOutput::handleDMXOutput() { | ||
| // don't act, when in DMX Proxy mode | ||
| if (e131ProxyUniverse != 0) return; | ||
| 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(); | ||
| 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; | ||
|
Mdbelen marked this conversation as resolved.
|
||
|
|
||
| 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 | ||
|
|
@@ -40,42 +198,32 @@ void 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; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| dmx.update(); // update the DMX bus | ||
| return update(); // update the DMX bus, if available | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
| #else | ||
| void initDMXOutput(){} | ||
| void handleDMXOutput() {} | ||
| #endif | ||
Uh oh!
There was an error while loading. Please reload this page.