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/data/settings_dmx.htm b/wled00/data/settings_dmx.htm index 391c2bdc97..95c6904ff9 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/data/settings_sync.htm b/wled00/data/settings_sync.htm index 12eb3e8d07..6b06d72b72 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -68,7 +68,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"; + } @@ -192,19 +195,22 @@

Network DMX input


Disable realtime gamma correction:
Realtime LED offset:
-
-

Wired DMX Input

+

Wired DMX Input

DMX RX Pin: RO
DMX TX Pin: DI
DMX Enable Pin: RE+DE
DMX Port:
Reboot required to apply changes.
+
-
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.
diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 8a1aedef00..373556867a 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -125,7 +125,7 @@ bool DMXInput::installDriver() void DMXInput::init(int8_t rxPin, int8_t txPin, int8_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 eace2145e6..a85255241d 100644 --- a/wled00/dmx_output.cpp +++ b/wled00/dmx_output.cpp @@ -1,33 +1,193 @@ +#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 -/* - * 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.")); + 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; + _dmxData[0] = 0; // make sure start code is 0 + + #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); + } +} + +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; +} + +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); + + _dmxData[0] = 0; // reset DMX start code to default + 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; + 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 +200,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 diff --git a/wled00/dmx_output.h b/wled00/dmx_output.h new file mode 100644 index 0000000000..30e966ac49 --- /dev/null +++ b/wled00/dmx_output.h @@ -0,0 +1,106 @@ +/** + * 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. + * 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 +#define DMX_OUTPUT_H + +#include + +#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 // unused, instead, DMXFORMAT is used + + +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(int8_t outputPin, uint8_t updateRate = 43, int8_t uartNo = -1); + void end(); + /** + * 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 (start code). + */ + 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_ (this may include 0). + * Returns true if read was successful (within bounds). + */ + 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. + * _dmxData[0] will be reset to 0 after every successful run. + */ + 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 + uint8_t _dmxData[DMX_CHANNELS] = {0}; + int8_t _uartNo = -1; + uint8_t _updateRate; + unsigned long _lastDmxOutMillis = 0; +}; + +#endif // DMX_OUTPUT_H diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 6846e5124e..475e05af6d 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 // 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/fcn_declare.h b/wled00/fcn_declare.h index ffb2c1202f..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(); -void 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); 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 7bdd5cfc20..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, hard-coded to IO2 + 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 fb516ac7d6..819b94176d 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/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 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 064b9ff620..0000000000 --- a/wled00/src/dependencies/dmx/SparkFunDMX.cpp +++ /dev/null @@ -1,182 +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. -******************************************************************************/ - -/* ----- 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; -#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 - - DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin); - 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 -} - - - -void 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 !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; - } - } - } -#endif -} - -// 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 73861153b2..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); - void 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 eb6019e6bf..e660678653 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 - handleDMXOutput(); + #ifdef WLED_DEBUG + unsigned long dmxMillis = millis(); #endif + dmxOutput.handleDMXOutput(); + #ifdef WLED_DEBUG + dmxMillis = millis() - dmxMillis; + maxDmxMillis = dmxMillis > maxDmxMillis ? dmxMillis : maxDmxMillis; + avgDmxMillis += dmxMillis; + #endif //WLED_DEBUG + #endif //WLED_ENABLE_DMX #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++; @@ -445,9 +458,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()); @@ -561,7 +571,7 @@ void WLED::setup() } #endif #ifdef WLED_ENABLE_DMX - initDMXOutput(); + dmxOutput.init(dmxOutputPin, 43); #endif #ifdef WLED_ENABLE_DMX_INPUT dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort); diff --git a/wled00/wled.h b/wled00/wled.h index b96264c25c..e15f58c0b7 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,11 @@ 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 dmxOutput; + #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 diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 812ef8c207..1ae2a97243 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(','); @@ -505,7 +507,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