From 44c6a0d73bd0fbf4c6f9ab4e49cbb50d3b5460d7 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 09:32:23 -0500
Subject: [PATCH 01/14] Proper Art-Net Settings
---
wled00/bus_manager.cpp | 7 +-
wled00/bus_manager.h | 48 ++++++--
wled00/cfg.cpp | 10 +-
wled00/data/settings_leds.htm | 29 +++++
wled00/fcn_declare.h | 2 +-
wled00/set.cpp | 13 ++-
wled00/udp.cpp | 199 +++++++++++++++++++++++++++-------
wled00/xml.cpp | 7 ++
8 files changed, 258 insertions(+), 57 deletions(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index aea8051afa..72b135e95b 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -476,7 +476,10 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_broadcastLock = false;
_valid = true;
- USER_PRINTF(" %u.%u.%u.%u] \n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
+ _artnet_outputs = bc.artnet_outputs;
+ _artnet_leds_per_output = bc.artnet_leds_per_output;
+ _artnet_fps_limit = bc.artnet_fps_limit;
+ USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
}
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
@@ -499,7 +502,7 @@ uint32_t BusNetwork::getPixelColor(uint16_t pix) const {
void BusNetwork::show() {
if (!_valid || !canShow()) return;
_broadcastLock = true;
- realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw);
+ realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw, _artnet_outputs, _artnet_leds_per_output, _artnet_fps_limit);
_broadcastLock = false;
}
diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h
index 9259bd1ff4..ff166d2a13 100644
--- a/wled00/bus_manager.h
+++ b/wled00/bus_manager.h
@@ -54,12 +54,16 @@ struct BusConfig {
uint8_t skipAmount;
bool refreshReq;
uint8_t autoWhite;
+ uint8_t artnet_outputs, artnet_fps_limit;
+ uint16_t artnet_leds_per_output;
+
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; // WLEDMM warning: this means that BusConfig cannot handle nore than 5 pins per bus!
uint16_t frequency;
- BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U) {
+ BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t art_o=1, uint16_t art_l=1, uint8_t art_f=30) {
refreshReq = (bool) GET_BIT(busType,7);
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz;
+ artnet_outputs = art_o; artnet_leds_per_output = art_l; artnet_fps_limit = art_f;
uint8_t nPins = 1; // default = only one pin (clockless LEDs like WS281x)
if ((type >= TYPE_NET_DDP_RGB) && (type < (TYPE_NET_DDP_RGB + 16))) nPins = 4; // virtual network bus. 4 "pins" store IP address
else if ((type > 47) && (type < 63)) nPins = 2; // (data + clock / SPI) busses - two pins
@@ -144,6 +148,9 @@ class Bus {
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() const { return 0; }
virtual uint16_t getFrequency() const { return 0U; }
+ virtual uint8_t get_artnet_fps_limit() const { return 0; }
+ virtual uint8_t get_artnet_outputs() const { return 0; }
+ virtual uint16_t get_artnet_leds_per_output() const { return 0; }
inline uint16_t getStart() const { return _start; }
inline void setStart(uint16_t start) { _start = start; }
inline uint8_t getType() const { return _type; }
@@ -348,12 +355,30 @@ class BusNetwork : public Bus {
return !_broadcastLock;
}
- uint8_t getPins(uint8_t* pinArray) const;
+ uint8_t getPins(uint8_t* pinArray) const;
- uint16_t getLength() const override {
+ uint16_t getLength() const override {
return _len;
}
+ uint8_t get_artnet_fps_limit() const override {
+ return _artnet_fps_limit;
+ }
+
+ uint8_t get_artnet_outputs() const override {
+ return _artnet_outputs;
+ }
+
+ uint16_t get_artnet_leds_per_output() const override {
+ return _artnet_leds_per_output;
+ }
+
+ void setColorOrder(uint8_t colorOrder);
+
+ uint8_t getColorOrder() const {
+ return _colorOrder;
+ }
+
void cleanup();
~BusNetwork() {
@@ -361,12 +386,17 @@ class BusNetwork : public Bus {
}
private:
- IPAddress _client;
- uint8_t _UDPtype;
- uint8_t _UDPchannels;
- bool _rgbw;
- bool _broadcastLock;
- byte *_data;
+ IPAddress _client;
+ uint8_t _UDPtype;
+ uint8_t _UDPchannels;
+ bool _rgbw;
+ bool _broadcastLock;
+ byte *_data;
+ uint8_t _colorOrder = COL_ORDER_RGB;
+ uint8_t _artnet_fps_limit;
+ uint8_t _artnet_outputs;
+ uint16_t _artnet_leds_per_output;
+ const ColorOrderMap &_colorOrderMap;
};
#ifdef WLED_ENABLE_HUB75MATRIX
diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index 4de49f99af..74ef889baf 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -194,13 +194,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully)
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY;
+ uint8_t artnet_outputs = elm["artnet_outputs"] | 0;
+ uint16_t artnet_leds_per_output = elm["artnet_leds_per_output"] | 0;
+ uint8_t artnet_fps_limit = elm["artnet_fps_limit"] | 0;
if (fromFS) {
- BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz);
+ BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip()
} else {
if (busConfigs[s] != nullptr) delete busConfigs[s];
- busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode);
+ busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
busesChanged = true;
}
s++;
@@ -829,6 +832,9 @@ void serializeConfig() {
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("freq")] = bus->getFrequency();
+ ins["artnet_outputs"] = bus->get_artnet_outputs();
+ ins["artnet_fps_limit"] = bus->get_artnet_fps_limit();
+ ins["artnet_leds_per_output"] = bus->get_artnet_leds_per_output();
}
JsonArray hw_com = hw.createNestedArray(F("com"));
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm
index b50e8ecba2..0da47e6350 100644
--- a/wled00/data/settings_leds.htm
+++ b/wled00/data/settings_leds.htm
@@ -219,6 +219,30 @@
gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h
gId("co"+n).style.display = ((t >= 80 && t < 96) || (t >= 40 && t < 48)||(t >= 100 && t < 110)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (t > 28 && t < 32) ? "inline":"none"; // show swap channels dropdown
+ gId("dig"+n+"O").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net output number
+ gId("dig"+n+"L").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net LEDs per output
+ gId("dig"+n+"F").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net FPS limiter
+ if (gId("dig"+n+"F").style.display == "inline") {
+ total_leds = d.getElementsByName("LC"+n)[0].value;
+ outputs = d.getElementsByName("AO"+n)[0].value;
+ leds_per_output = d.getElementsByName("AL"+n)[0].value;
+ fps_limit = d.getElementsByName("AF"+n)[0].value;
+ last_octet = d.getElementsByName("L3"+n)[0].value;
+ if (outputs > 1) {
+ if (t == 82) gId("dig"+n+"W").innerHTML = "
Set your Art-Net Hardware to "+Math.ceil(leds_per_output/170)+" universes per output.";
+ if (t == 83) gId("dig"+n+"W").innerHTML = "
Set your Art-Net Hardware to "+Math.ceil(leds_per_output/128)+" universes per output.";
+ } else if (outputs == 1) {
+ gId("dig"+n+"W").innerHTML = "
WLED-style Art-Net output enabled.";
+ } else {
+ gId("dig"+n+"W").innerHTML = "
You need at least 1 output!";
+ }
+ if (outputs > 1 && fps_limit > 33333/leds_per_output) gId("dig"+n+"W").innerHTML += "
FPS limit may be too high for WS281x pixels.";
+ if (outputs*leds_per_output != total_leds) gId("dig"+n+"W").innerHTML += "
Total LEDs doesn't match outputs * LEDs per output.";
+ if (last_octet == 255) {
+ if (total_leds <= 1024) gId("dig"+n+"W").innerHTML += "
Art-Net is in broadcast mode.";
+ if (total_leds > 1024) gId("dig"+n+"W").innerHTML += "
You are sending a lot of broadcast data. Be cautious.";
+ }
+ }
if (!(t > 28 && t < 32)) d.getElementsByName("WO"+n)[0].value = 0; // reset swapping
gId("dig"+n+"c").style.display = ((t >= 40 && t < 48)||(t >= 100 && t < 110)) ? "none":"inline"; // hide count for analog and HUB75
gId("dig"+n+"r").style.display = (t >= 80) && (t < 100) ? "none":"inline"; // hide reversed for virtual, except for HUB75
@@ -314,6 +338,7 @@
gId("lc").textContent = sLC;
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
+
// memory usage and warnings
gId('m0').innerHTML = memu;
bquot = memu / maxM * 100;
@@ -420,6 +445,10 @@
+
Number of Outputs:
+
LEDs Per Output:
+
FPS Limit:
+
Art-Net Warnings Go Here.
Reversed:
Skip first LEDs:
Off Refresh:
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h
index 694e9c9f55..a2f1bf29a3 100644
--- a/wled00/fcn_declare.h
+++ b/wled00/fcn_declare.h
@@ -241,7 +241,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
//udp.cpp
void notify(byte callMode, bool followUp=false);
-uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false);
+uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false, uint8_t artnet_outouts=1, uint16_t artnet_leds_per_output=1, uint8_t artnet_fps_limit=1);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime();
void handleNotifications();
diff --git a/wled00/set.cpp b/wled00/set.cpp
index 6c9b57c273..449155e27a 100644
--- a/wled00/set.cpp
+++ b/wled00/set.cpp
@@ -95,8 +95,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
}
- uint8_t colorOrder, type, skip, awmode, channelSwap;
- uint16_t length, start;
+ uint8_t colorOrder, type, skip, awmode, channelSwap, artnet_outputs, artnet_fps_limit;
+ uint16_t length, start, artnet_leds_per_output;
uint8_t pins[5] = {255, 255, 255, 255, 255};
autoSegments = request->hasArg(F("MS"));
@@ -110,6 +110,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
bool busesChanged = false;
for (uint8_t s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) {
+ // "48+s" means the ASCII character "0", so 48+1 = ASCII for "1", etc - and "[3]=0" means null-terminate the string.
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
@@ -121,6 +122,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //channel swap
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
+ char ao[4] = "AO"; ao[2] = 48+s; ao[3] = 0; //Art-Net outputs
+ char al[4] = "AL"; al[2] = 48+s; al[3] = 0; //Art-Net LEDs per output
+ char af[4] = "AF"; af[2] = 48+s; af[3] = 0; //Art-Net FPS limit
if (!request->hasArg(lp)) {
DEBUG_PRINT(F("No data for "));
DEBUG_PRINTLN(s);
@@ -167,10 +171,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
channelSwap = Bus::hasWhite(type) ? request->arg(wo).toInt() : 0;
type |= request->hasArg(rf) << 7; // off refresh override
+ artnet_outputs = (request->hasArg(ao)) ? request->arg(ao).toInt() : 1;
+ artnet_leds_per_output = (request->hasArg(al)) ? request->arg(al).toInt() : length;
+ artnet_fps_limit = (request->hasArg(af)) ? request->arg(af).toInt() : 33333/length;
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s];
- busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freqHz);
+ busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freqHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index 0a2d53324b..d6c279df9f 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -759,10 +759,17 @@ void sendSysInfoUDP()
// isRGBW - true if the buffer contains 4 components per pixel
static size_t sequenceNumber = 0; // this needs to be shared across all outputs
-static const size_t ART_NET_HEADER_SIZE = 12;
-static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
+static const byte ART_NET_HEADER[12] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
+static uint_fast16_t framenumber = 0;
+
+#if defined(ARDUINO_ARCH_ESP32P4)
+extern "C" {
+ int p4_mul16x16(uint8_t* outpacket, uint8_t* brightness, uint16_t num_loops, uint8_t* pixelbuffer);
+}
+#endif
+
+uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW, uint8_t outputs, uint16_t leds_per_output, uint8_t fps_limit) {
-uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp;
@@ -838,58 +845,170 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8
case 2: //ArtNet
{
- // calculate the number of UDP packets we need to send
- const size_t channelCount = length * (isRGBW?4:3); // 1 channel for every R,G,B,(W?) value
- const size_t ARTNET_CHANNELS_PER_PACKET = isRGBW?512:510; // 512/4=128 RGBW LEDs, 510/3=170 RGB LEDs
- const size_t packetCount = ((channelCount-1)/ARTNET_CHANNELS_PER_PACKET)+1;
+ static uint_fast16_t artnetlimiter = millis()+(1000/fps_limit);
+ while (artnetlimiter > micros()) {
+ if (ArtNetSkipFrame) {
+ return 0; // Let WLED keep generating effect frames and we output an Art-Net frame when fps_limit is reached.
+ } else {
+ delayMicroseconds(10); // Make WLED obey fps_limit and just delay here until we're ready to send a frame.
+ }
+ }
- uint32_t channel = 0;
- size_t bufferOffset = 0;
+ /*
+ WLED rendering Art-Net data considers itself to be 1 hardware output with many universes - but
+ many Art-Net controllers like the H807SA can be manually set to "X universes per output" or in
+ some cases "X channels per port" - which is the same thing, just expressed differently.
- sequenceNumber++;
+ We need to know the LEDs per output so we can break the pixel data across physically attached universes.
- for (size_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {
+ The H807SA obeys the "510 channels for RGB" rule like WLED and xLights - some other controllers do not care,
+ but we're not supporting those here. If you run into one of these, override ARTNET_CHANNELS_PER_PACKET to 512.
+ */
- if (sequenceNumber > 255) sequenceNumber = 0;
+ #ifdef ARTNET_TIMER
+ uint_fast16_t datatotal = 0;
+ uint_fast16_t packetstotal = 0;
+ #endif
+ uint_fast16_t timer = micros();
- if (!ddpUdp.beginPacket(client, ARTNET_DEFAULT_PORT)) {
- DEBUG_PRINTLN(F("Art-Net WiFiUDP.beginPacket returned an error"));
- return 1; // borked
+ AsyncUDP artnetudp;// AsyncUDP so we can just blast packets.
+
+ const uint_fast16_t ARTNET_CHANNELS_PER_PACKET = isRGBW?512:510; // 512/4=128 RGBW LEDs, 510/3=170 RGB LEDs
+
+ uint_fast16_t bufferOffset = 0;
+ uint_fast16_t hardware_output_universe = 0;
+
+ sequenceNumber++;
+
+ if (sequenceNumber == 0 || sequenceNumber > 255) sequenceNumber = 1;
+
+ for (uint_fast16_t hardware_output = 0; hardware_output < outputs; hardware_output++) {
+
+ if (bufferOffset > length * (isRGBW?4:3)) {
+ // This stop is reached if we don't have enough pixels for the defined Art-Net output.
+ return 1; // stop when we hit end of LEDs
}
- size_t packetSize = ARTNET_CHANNELS_PER_PACKET;
+ // hardware_output_universe = hardware_outputs_universe_start[hardware_output];
- if (currentPacket == (packetCount - 1U)) {
- // last packet
- if (channelCount % ARTNET_CHANNELS_PER_PACKET) {
- packetSize = channelCount % ARTNET_CHANNELS_PER_PACKET;
+ uint_fast16_t channels_remaining = leds_per_output * (isRGBW?4:3);
+
+ while (channels_remaining > 0) {
+
+ uint_fast16_t packetSize = ARTNET_CHANNELS_PER_PACKET;
+
+ if (channels_remaining < ARTNET_CHANNELS_PER_PACKET) {
+ packetSize = channels_remaining;
+ channels_remaining = 0;
+ } else {
+ channels_remaining -= packetSize;
}
- }
- byte header_buffer[ART_NET_HEADER_SIZE];
- memcpy_P(header_buffer, ART_NET_HEADER, ART_NET_HEADER_SIZE);
- ddpUdp.write(header_buffer, ART_NET_HEADER_SIZE); // This doesn't change. Hard coded ID, OpCode, and protocol version.
- ddpUdp.write(sequenceNumber & 0xFF); // sequence number. 1..255
- ddpUdp.write(0x00); // physical - more an FYI, not really used for anything. 0..3
- ddpUdp.write((currentPacket) & 0xFF); // Universe LSB. 1 full packet == 1 full universe, so just use current packet number.
- ddpUdp.write(0x00); // Universe MSB, unused.
- ddpUdp.write(0xFF & (packetSize >> 8)); // 16-bit length of channel data, MSB
- ddpUdp.write(0xFF & (packetSize )); // 16-bit length of channel data, LSB
+ #ifdef ARTNET_TIMER
+ packetstotal++;
+ datatotal += packetSize + 18;
+ #endif
+
+ // set the parts of the Art-Net packet header that change:
+ packet_buffer[12] = sequenceNumber;
+ // packet_buffer[13] = 0; // "The physical input port from which DMX512 data was input. This field is used by the receiving device to discriminate between packets with identical Port-Address that have been generated by different input ports and so need to be merged."
+ packet_buffer[14] = hardware_output_universe;
+ packet_buffer[15] = hardware_output_universe >> 8; // needed for universes > 255
+ packet_buffer[16] = packetSize >> 8;
+ packet_buffer[17] = packetSize;
+
+ #ifdef ARTNET_TESTING_ZEROS
+ bri = 0; // Set all brightness to 0 but keep all calculations the same and keep sending packets.
+ #endif
+
+ #if defined(ARDUINO_ARCH_ESP32P4)
+ p4_mul16x16(packet_buffer+18, &bri, (packetSize >> 4)+1, buffer+bufferOffset);
+ #else
+ if (bri == 255) { // speed hack - don't adjust brightness if full brightness
+ memcpy(packet_buffer+18, buffer+bufferOffset, packetSize);
+ } else {
+ for (uint_fast16_t i = 0; i < packetSize; i+=(isRGBW?4:3)) {
+ // set brightness values in the packet - seems slightly faster than scale8()?
+ // for some reason, doing 3 (or 4) at a time is 200 micros faster than 1 at a time.
+ packet_buffer[i+18] = (buffer[bufferOffset+i] * bri) >> 8;
+ packet_buffer[i+19] = (buffer[bufferOffset+i+1] * bri) >> 8;
+ packet_buffer[i+20] = (buffer[bufferOffset+i+2] * bri) >> 8;
+ if (isRGBW) packet_buffer[i+21] = (buffer[bufferOffset+i+3] * bri) >> 8;
+ }
+ }
+ #endif
- for (size_t i = 0; i < packetSize; i += (isRGBW?4:3)) {
- ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // R
- ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // G
- ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // B
- if (isRGBW) ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // W
+ bufferOffset += packetSize;
+
+ if (!artnetudp.writeTo(packet_buffer,packetSize+18, client, ARTNET_DEFAULT_PORT)) {
+ DEBUG_PRINTLN(F("Art-Net artnetudp.writeTo() returned an error"));
+ return 1; // borked
+ }
+ hardware_output_universe++;
}
+ }
- if (!ddpUdp.endPacket()) {
- DEBUG_PRINTLN(F("Art-Net WiFiUDP.endPacket returned an error"));
+ // Send Art-Net sync. Just reuse the packet and adjust.
+ // This should get re-written on the next run.
+ // After the first sync packet, and assuming 1 sync packet every 4
+ // seconds at least, should keep Art-Net nodes in synchronous mode.
+
+ // This is very much untested and generally not needed unless you
+ // have several Art-Net devices being broadcast to, and should only
+ // be called in that situation.
+
+ // Art-Net broadcast mode (setting Art-Net to 255.255.255.255) should ONLY
+ // be used if you know what you're doing, as that is a lot of pixels being
+ // sent to EVERYTHING on your network, including WiFi devices - and can
+ // overwhelm them if you have a lot of Art-Net data being broadcast.
+
+ #ifdef ARTNET_SYNC_ENABLED
+
+ // This block sends Art-Net "ArtSync" packets. Can't do this with AsyncUDP because it doesn't support source port binding.
+ // Tested on Art-Net qualifier software but not on real hardware with known support for ArtSync.
+ // Doesn't seem to do anything on my gear, so it's disabled.
+
+ // packet_buffer[8] = 0x00; // ArtSync opcode low byte (low byte is same as ArtDmx, 0x00)
+ packet_buffer[9] = 0x52; // ArtSync opcode high byte
+ packet_buffer[12] = 0x00; // Aux1 - Transmit as 0. This is normally the sequence number in ArtDMX packets.
+ // packet_buffer[13] = 0x00; // Aux2 - Transmit as 0 - this should be 0 anyway in the packet already
+
+ #ifdef ARTNET_SYNC_STRICT
+ WiFiUDP artnetsync;
+ artnetsync.begin(ETH.localIP(), ARTNET_DEFAULT_PORT);
+ artnetsync.beginPacket(IPADDR_BROADCAST,ARTNET_DEFAULT_PORT);
+ artnetsync.write(packet_buffer,14);
+
+ if (!artnetsync.endPacket()) {
+ DEBUG_PRINTLN(F("Art-Net Sync Broadcast Strict returned an error"));
return 1; // borked
}
- channel += packetSize;
- }
- } break;
+ #else
+ if (!artnetudp.broadcastTo(packet_buffer,14,ARTNET_DEFAULT_PORT)) {
+ DEBUG_PRINTLN(F("Art-Net Sync Broadcast returned an error"));
+ return 1; // borked
+ }
+ #endif
+
+ #ifdef ARTNET_TIMER
+ packetstotal++;
+ datatotal += 14;
+ #endif
+
+ #endif
+
+ artnetlimiter = micros()+(1000000/fps_limit)-(micros()-timer);
+
+ // This is the proper stop if pixels = Art-Net output.
+
+ #ifdef ARTNET_TIMER
+ float mbps = (datatotal*8)/((micros()-timer)*0.95367431640625f);
+ // the "micros()" calc is just to limit the print to a more random debug output so it doesn't overwhelm the terminal
+ if (micros() % 100 < 3) USER_PRINTF("UDP for %u pixels took %lu micros. %u data in %u total packets. %2.2f mbit/sec at %u FPS.\n",length, micros()-timer, datatotal, packetstotal, mbps, strip.getFps());
+ #endif
+
+ break;
+ }
}
return 0;
}
diff --git a/wled00/xml.cpp b/wled00/xml.cpp
index b90b7bb71c..b7bd1005e9 100644
--- a/wled00/xml.cpp
+++ b/wled00/xml.cpp
@@ -424,6 +424,7 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
if (bus == nullptr) continue;
+ // "48+s" means the ASCII character "0", so 48+1 = ASCII for "1", etc - and "[3]=0" means null-terminate the string.
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
@@ -435,6 +436,9 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //swap channels
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed
+ char ao[4] = "AO"; ao[2] = 48+s; ao[3] = 0; //Art-Net outputs
+ char al[4] = "AL"; al[2] = 48+s; al[3] = 0; //Art-Net LEDs per output
+ char af[4] = "AF"; af[2] = 48+s; af[3] = 0; //Art-Net FPS limit
oappend(SET_F("addLEDs(1);"));
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
@@ -451,6 +455,9 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W
sappend('c',rf,bus->isOffRefreshRequired());
sappend('v',aw,bus->getAutoWhiteMode());
sappend('v',wo,bus->getColorOrder() >> 4);
+ sappend('v',ao,bus->get_artnet_outputs());
+ sappend('v',al,bus->get_artnet_leds_per_output());
+ sappend('v',af,bus->get_artnet_fps_limit());
uint16_t speed = bus->getFrequency();
if (bus->getType() > TYPE_ONOFF && bus->getType() < 48) {
switch (speed) {
From 732ae38005f3abea69007e692b9c6574c64639db Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 12:54:32 -0500
Subject: [PATCH 02/14] Art-Net P4 Tidy
---
wled00/udp.cpp | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index d6c279df9f..d6d1bbfeab 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -772,7 +772,22 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
- WiFiUDP ddpUdp;
+ // For some reason, this is faster outside of the case block...
+ //
+ static byte *packet_buffer = (byte *) heap_caps_calloc_prefer(530, sizeof(byte), 3, MALLOC_CAP_IRAM_8BIT, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); // MALLOC_CAP_TCM seems to have alignment issues.
+ if (packet_buffer[0] != 0x41) memcpy(packet_buffer, ART_NET_HEADER, 12); // copy in the Art-Net header if it isn't there already
+
+ // Volumetric test code
+ // static byte *buffer = (byte *) heap_caps_calloc_prefer(length*3*72, sizeof(byte), 3, MALLOC_CAP_IRAM_8BIT, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT); // MALLOC_CAP_TCM seems to have alignment issues.
+ // memmove(buffer+(length*3),buffer,length*3*7);
+ // memcpy(buffer,buffer_in,length*3);
+ // framenumber++;
+ // if (framenumber >= 8) {
+ // framenumber = 0;
+ // } else {
+ // // return 0;
+ // }
+ // length *= 8;
switch (type) {
case 0: // DDP
From 3012272ed32760330d0dfd2d7d510bfd0ca24c80 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 11:37:36 -0500
Subject: [PATCH 03/14] Art-Net sane defaults if using an old config without
the new values
---
wled00/cfg.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index 74ef889baf..069b303aa0 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -194,9 +194,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully)
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY;
- uint8_t artnet_outputs = elm["artnet_outputs"] | 0;
- uint16_t artnet_leds_per_output = elm["artnet_leds_per_output"] | 0;
- uint8_t artnet_fps_limit = elm["artnet_fps_limit"] | 0;
+ uint8_t artnet_outputs = elm["artnet_outputs"] | 1; // sanity check
+ uint16_t artnet_leds_per_output = elm["artnet_leds_per_output"] | length; // sanity check
+ uint8_t artnet_fps_limit = elm["artnet_fps_limit"] | 24; // sanity check
if (fromFS) {
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
mem += BusManager::memUsage(bc);
From 3f02ba73df6ccd183c1854c811a8bcdae0fcc225 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 13:26:14 -0500
Subject: [PATCH 04/14] Art-Net Color Order Maps, RGBW
---
wled00/bus_manager.cpp | 84 ++++++++++++++++++++++++++++++++----------
wled00/bus_manager.h | 2 +-
wled00/const.h | 3 +-
wled00/udp.cpp | 16 +++-----
4 files changed, 74 insertions(+), 31 deletions(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index 72b135e95b..5399ccc2af 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -448,7 +448,7 @@ uint8_t BusOnOff::getPins(uint8_t* pinArray) const {
}
-BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
+BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite), _colorOrderMap(com) {
_valid = false;
USER_PRINT("[");
switch (bc.type) {
@@ -457,6 +457,11 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
_UDPtype = 2;
USER_PRINT("NET_ARTNET_RGB");
break;
+ case TYPE_NET_ARTNET_RGBW:
+ _rgbw = true;
+ _UDPtype = 2;
+ USER_PRINT("NET_ARTNET_RGBW");
+ break;
case TYPE_NET_E131_RGB:
_rgbw = false;
_UDPtype = 1;
@@ -469,10 +474,11 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
break;
}
_UDPchannels = _rgbw ? 4 : 3;
- _data = (byte *)malloc(bc.count * _UDPchannels);
+ _data = (byte*) heap_caps_calloc_prefer((bc.count * _UDPchannels)+15, sizeof(byte), 3, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM);
+
if (_data == nullptr) return;
- memset(_data, 0, bc.count * _UDPchannels);
_len = bc.count;
+ _colorOrder = bc.colorOrder;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_broadcastLock = false;
_valid = true;
@@ -482,21 +488,61 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
}
-void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
- if (!_valid || pix >= _len) return;
- if (hasWhite()) c = autoWhiteCalc(c);
- if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
- uint16_t offset = pix * _UDPchannels;
- _data[offset] = R(c);
- _data[offset+1] = G(c);
- _data[offset+2] = B(c);
- if (_rgbw) _data[offset+3] = W(c);
+void IRAM_ATTR BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
+ if (!_valid || pix >= _len) return;
+ if (_rgbw) c = autoWhiteCalc(c);
+ if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); // color correction from CCT
+
+ uint16_t offset = pix * _UDPchannels;
+ uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder);
+
+ if (_colorOrder != co || _colorOrder != COL_ORDER_RGB) {
+ switch (co) {
+ case COL_ORDER_GRB:
+ _data[offset] = G(c); _data[offset+1] = R(c); _data[offset+2] = B(c);
+ break;
+ case COL_ORDER_RGB:
+ _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c);
+ break;
+ case COL_ORDER_BRG:
+ _data[offset] = B(c); _data[offset+1] = R(c); _data[offset+2] = G(c);
+ break;
+ case COL_ORDER_RBG:
+ _data[offset] = R(c); _data[offset+1] = B(c); _data[offset+2] = G(c);
+ break;
+ case COL_ORDER_GBR:
+ _data[offset] = G(c); _data[offset+1] = B(c); _data[offset+2] = R(c);
+ break;
+ case COL_ORDER_BGR:
+ _data[offset] = B(c); _data[offset+1] = G(c); _data[offset+2] = R(c);
+ break;
+ }
+ if (_rgbw) _data[offset+3] = W(c);
+ } else {
+ _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c);
+ if (_rgbw) _data[offset+3] = W(c);
+ }
}
-uint32_t BusNetwork::getPixelColor(uint16_t pix) const {
- if (!_valid || pix >= _len) return 0;
- uint16_t offset = pix * _UDPchannels;
- return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0);
+uint32_t IRAM_ATTR BusNetwork::getPixelColor(uint16_t pix) const {
+ if (!_valid || pix >= _len) return 0;
+ uint16_t offset = pix * _UDPchannels;
+ uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder);
+
+ uint8_t r = _data[offset + 0];
+ uint8_t g = _data[offset + 1];
+ uint8_t b = _data[offset + 2];
+ uint8_t w = _rgbw ? _data[offset + 3] : 0;
+
+ switch (co) {
+ case COL_ORDER_GRB: return RGBW32(g, r, b, w);
+ case COL_ORDER_RGB: return RGBW32(r, g, b, w);
+ case COL_ORDER_BRG: return RGBW32(b, r, g, w);
+ case COL_ORDER_RBG: return RGBW32(r, b, g, w);
+ case COL_ORDER_GBR: return RGBW32(g, b, r, w);
+ case COL_ORDER_BGR: return RGBW32(b, g, r, w);
+ default: return RGBW32(r, g, b, w); // default to RGB order
+ }
}
void BusNetwork::show() {
@@ -516,8 +562,8 @@ uint8_t BusNetwork::getPins(uint8_t* pinArray) const {
void BusNetwork::cleanup() {
_type = I_NONE;
_valid = false;
- if (_data != nullptr) free(_data);
- _data = nullptr;
+ // if (_data != nullptr) free(_data);
+ // _data = nullptr;
}
// ***************************************************************************
@@ -1181,7 +1227,7 @@ int BusManager::add(BusConfig &bc) {
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
DEBUG_PRINTF("BusManager::add(bc.type=%u)\n", bc.type);
if (bc.type >= TYPE_NET_DDP_RGB && bc.type < 96) {
- busses[numBusses] = new BusNetwork(bc);
+ busses[numBusses] = new BusNetwork(bc, colorOrderMap);
} else if (bc.type >= TYPE_HUB75MATRIX && bc.type <= (TYPE_HUB75MATRIX + 10)) {
#ifdef WLED_ENABLE_HUB75MATRIX
DEBUG_PRINTLN("BusManager::add - Adding BusHub75Matrix");
diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h
index ff166d2a13..1e1da36722 100644
--- a/wled00/bus_manager.h
+++ b/wled00/bus_manager.h
@@ -337,7 +337,7 @@ class BusOnOff : public Bus {
class BusNetwork : public Bus {
public:
- BusNetwork(BusConfig &bc);
+ BusNetwork(BusConfig &bc, const ColorOrderMap &com);
uint16_t getMaxPixels() const override { return 4096; };
bool hasRGB() const { return true; }
diff --git a/wled00/const.h b/wled00/const.h
index 62fbfb624a..2c6ac1a71b 100644
--- a/wled00/const.h
+++ b/wled00/const.h
@@ -252,7 +252,8 @@
//Network types (master broadcast) (80-95)
#define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus)
#define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused)
-#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused)
+#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus)
+#define TYPE_NET_ARTNET_RGBW 83 //network ArtNet RGB bus (master broadcast bus)
#define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus)
#define IS_DIGITAL(t) (((t) & 0x10) || ((t)==TYPE_HUB75MATRIX)) //digital are 16-31 and 48-63 // WLEDMM added HUB75
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index d6d1bbfeab..447a490a62 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -774,7 +774,7 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
// For some reason, this is faster outside of the case block...
//
- static byte *packet_buffer = (byte *) heap_caps_calloc_prefer(530, sizeof(byte), 3, MALLOC_CAP_IRAM_8BIT, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); // MALLOC_CAP_TCM seems to have alignment issues.
+ static byte *packet_buffer = (byte *) heap_caps_calloc_prefer(530, sizeof(byte), 2, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); // MALLOC_CAP_TCM seems to have alignment issues.
if (packet_buffer[0] != 0x41) memcpy(packet_buffer, ART_NET_HEADER, 12); // copy in the Art-Net header if it isn't there already
// Volumetric test code
@@ -792,6 +792,8 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
switch (type) {
case 0: // DDP
{
+ WiFiUDP ddpUdp;
+
// calculate the number of UDP packets we need to send
size_t channelCount = length * (isRGBW? 4:3); // 1 channel for every R,G,B value
size_t packetCount = ((channelCount-1) / DDP_CHANNELS_PER_PACKET) +1;
@@ -857,16 +859,11 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
case 1: //E1.31
{
} break;
-
- case 2: //ArtNet
+ case 2: //Art-Net
{
static uint_fast16_t artnetlimiter = millis()+(1000/fps_limit);
while (artnetlimiter > micros()) {
- if (ArtNetSkipFrame) {
- return 0; // Let WLED keep generating effect frames and we output an Art-Net frame when fps_limit is reached.
- } else {
- delayMicroseconds(10); // Make WLED obey fps_limit and just delay here until we're ready to send a frame.
- }
+ delayMicroseconds(10); // Make WLED obey fps_limit and just delay here until we're ready to send a frame.
}
/*
@@ -904,8 +901,6 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
return 1; // stop when we hit end of LEDs
}
- // hardware_output_universe = hardware_outputs_universe_start[hardware_output];
-
uint_fast16_t channels_remaining = leds_per_output * (isRGBW?4:3);
while (channels_remaining > 0) {
@@ -1004,6 +999,7 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
return 1; // borked
}
#endif
+ packet_buffer[9] = ART_NET_HEADER[9]; // reset ArtSync opcode high byte
#ifdef ARTNET_TIMER
packetstotal++;
From e9ce495d8368e22f2d393aa96097c435022f7685 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 13:32:45 -0500
Subject: [PATCH 05/14] P4 Assembly for Art-Net
---
wled00/p4_mul16x16.S | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 wled00/p4_mul16x16.S
diff --git a/wled00/p4_mul16x16.S b/wled00/p4_mul16x16.S
new file mode 100644
index 0000000000..ea91165a11
--- /dev/null
+++ b/wled00/p4_mul16x16.S
@@ -0,0 +1,31 @@
+#if defined(ARDUINO_ARCH_ESP32P4)
+.text
+.align 4
+.global p4_mul16x16
+.type p4_mul16x16,@function
+# ESP32-P4 needs -march rv32imafc_zicsr_zifencei_xesppie -mabi ilp32f
+# a0 = out_packet, a1 = brightness, a2 = num_loops, a3 = pixelbuffer
+p4_mul16x16:
+ esp.movx.r.cfg t6 # Enable aligned data access
+ or t6, t6, 2 # Enable aligned data access
+ esp.movx.w.cfg t6 # Enable aligned data access
+ li t6, 8 # put 8 (eventually for vmul bitshift) in temp register 6
+ esp.movx.w.sar t6 # set the numbers of bits to right-shift from t6
+ li t5, 255 # load 255 into t5 for a comparison
+ esp.vldbc.8.ip q1, a1, 0 # load the "B" value into q1 from a1, broadcasting the same value to all 16 values of q1
+ li t1, 0 # start our loop_num counter t1 at 0
+ loop: # "loop" label
+ beq t1, a2, exit # branch to "exit" if loop_num == num_loops
+ esp.vld.128.ip q0, a3, 16 # load 16 "A" values into q0 from a3, then move the pointer by 16 to get a new batch
+ beq a1, t5, skip # If brightness (a1) == 255, jump to "skip"
+ esp.vmul.u8 q2, q0, q1 # C = A*B (q2 = q0 * q1) then >> by esp.movx.w.sar which we set to 8
+ esp.vst.128.ip q2, a0, 16 # store the 16 "C" values into a0, then move the pointer by 16
+ j end_skip # jump to "end_skip"
+ skip: # "skip" label
+ esp.vst.128.ip q0, a0, 16 # just store brightness (q0 from a3) to packet (a0)
+ end_skip: # "end_skip" label
+ addi t1, t1, 1 # increment loop_num counter t1
+ j loop # jump to "loop"
+ exit: # "exit" label
+ ret # return
+#endif
From 7bf3f4aee07e99e3b098250524b3319f6628eed5 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 13:48:17 -0500
Subject: [PATCH 06/14] Fixes for ESP8266
---
wled00/bus_manager.cpp | 5 ++++-
wled00/udp.cpp | 6 +++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index 5399ccc2af..aa0c1f4ca6 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -474,8 +474,11 @@ BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, b
break;
}
_UDPchannels = _rgbw ? 4 : 3;
+ #ifdef ESP32
_data = (byte*) heap_caps_calloc_prefer((bc.count * _UDPchannels)+15, sizeof(byte), 3, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM);
-
+ #else
+ _data = (byte*) calloc((bc.count * _UDPchannels)+15, sizeof(byte));
+ #endif
if (_data == nullptr) return;
_len = bc.count;
_colorOrder = bc.colorOrder;
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index 447a490a62..f6749a65b4 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -774,7 +774,11 @@ uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t len
// For some reason, this is faster outside of the case block...
//
- static byte *packet_buffer = (byte *) heap_caps_calloc_prefer(530, sizeof(byte), 2, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); // MALLOC_CAP_TCM seems to have alignment issues.
+ #ifdef ESP32
+ static byte *packet_buffer = (byte *) heap_caps_calloc_prefer(530, sizeof(byte), 2, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM);
+ #else
+ static byte *packet_buffer = (byte *) calloc(530, sizeof(byte));
+ #endif
if (packet_buffer[0] != 0x41) memcpy(packet_buffer, ART_NET_HEADER, 12); // copy in the Art-Net header if it isn't there already
// Volumetric test code
From e7fb8f57487626b889cbb6145e123e296e908609 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Thu, 7 Nov 2024 17:03:24 -0500
Subject: [PATCH 07/14] IRAM_ATTR_YN for ES8266
---
wled00/bus_manager.cpp | 4 ++--
wled00/udp.cpp | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index aa0c1f4ca6..eb103c174d 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -491,7 +491,7 @@ BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, b
USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
}
-void IRAM_ATTR BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
+void IRAM_ATTR_YN BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return;
if (_rgbw) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); // color correction from CCT
@@ -527,7 +527,7 @@ void IRAM_ATTR BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
}
}
-uint32_t IRAM_ATTR BusNetwork::getPixelColor(uint16_t pix) const {
+uint32_t IRAM_ATTR_YN BusNetwork::getPixelColor(uint16_t pix) const {
if (!_valid || pix >= _len) return 0;
uint16_t offset = pix * _UDPchannels;
uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder);
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index f6749a65b4..75d788a54a 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -768,7 +768,7 @@ extern "C" {
}
#endif
-uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW, uint8_t outputs, uint16_t leds_per_output, uint8_t fps_limit) {
+uint8_t IRAM_ATTR_YN realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW, uint8_t outputs, uint16_t leds_per_output, uint8_t fps_limit) {
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
From 05d441db618ae2597182b83683138d72a9046e18 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 10 Nov 2024 07:15:27 -0500
Subject: [PATCH 08/14] Review Fixes
---
wled00/bus_manager.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h
index 1e1da36722..75c43cafc8 100644
--- a/wled00/bus_manager.h
+++ b/wled00/bus_manager.h
@@ -355,7 +355,7 @@ class BusNetwork : public Bus {
return !_broadcastLock;
}
- uint8_t getPins(uint8_t* pinArray) const;
+ uint8_t getPins(uint8_t* pinArray) const override;
uint16_t getLength() const override {
return _len;
@@ -375,7 +375,7 @@ class BusNetwork : public Bus {
void setColorOrder(uint8_t colorOrder);
- uint8_t getColorOrder() const {
+ uint8_t getColorOrder() const override {
return _colorOrder;
}
@@ -503,4 +503,4 @@ class BusManager {
return j;
}
};
-#endif
\ No newline at end of file
+#endif
From f096da3c8b930e1d9ceecc3b02d60fefc76ccc79 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 10 Nov 2024 07:18:39 -0500
Subject: [PATCH 09/14] Review Fixes
---
wled00/bus_manager.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index eb103c174d..b59610e061 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -487,7 +487,7 @@ BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, b
_valid = true;
_artnet_outputs = bc.artnet_outputs;
_artnet_leds_per_output = bc.artnet_leds_per_output;
- _artnet_fps_limit = bc.artnet_fps_limit;
+ _artnet_fps_limit = max(uint8_t(1), bc.artnet_fps_limit);
USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
}
From 6b2c4aec2fd9f00680ad85a1252bc8dc2036e167 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 10 Nov 2024 08:58:01 -0500
Subject: [PATCH 10/14] Code Review Fixes
---
wled00/udp.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index 75d788a54a..ecd751e914 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -865,7 +865,7 @@ uint8_t IRAM_ATTR_YN realtimeBroadcast(uint8_t type, IPAddress client, uint16_t
} break;
case 2: //Art-Net
{
- static uint_fast16_t artnetlimiter = millis()+(1000/fps_limit);
+ static unsigned long artnetlimiter = micros()+(1000000/fps_limit);
while (artnetlimiter > micros()) {
delayMicroseconds(10); // Make WLED obey fps_limit and just delay here until we're ready to send a frame.
}
@@ -885,7 +885,7 @@ uint8_t IRAM_ATTR_YN realtimeBroadcast(uint8_t type, IPAddress client, uint16_t
uint_fast16_t datatotal = 0;
uint_fast16_t packetstotal = 0;
#endif
- uint_fast16_t timer = micros();
+ unsigned long timer = micros();
AsyncUDP artnetudp;// AsyncUDP so we can just blast packets.
@@ -1012,7 +1012,7 @@ uint8_t IRAM_ATTR_YN realtimeBroadcast(uint8_t type, IPAddress client, uint16_t
#endif
- artnetlimiter = micros()+(1000000/fps_limit)-(micros()-timer);
+ artnetlimiter = timer + (1000000/fps_limit);
// This is the proper stop if pixels = Art-Net output.
From 9223b3c6cd95e6accf6fafe6c02fb66908a2fe33 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 10 Nov 2024 13:34:30 -0500
Subject: [PATCH 11/14] Free BusNetwork _data on cleanup
---
wled00/bus_manager.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index b59610e061..1da748155b 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -565,8 +565,8 @@ uint8_t BusNetwork::getPins(uint8_t* pinArray) const {
void BusNetwork::cleanup() {
_type = I_NONE;
_valid = false;
- // if (_data != nullptr) free(_data);
- // _data = nullptr;
+ if (_data != nullptr) free(_data);
+ _data = nullptr;
}
// ***************************************************************************
From 3828d959e6039e2e7b53740f016cee8b7a303c3f Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 10 Nov 2024 13:39:24 -0500
Subject: [PATCH 12/14] Minor indent fix
---
wled00/bus_manager.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index 1da748155b..053d595783 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -487,7 +487,7 @@ BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, b
_valid = true;
_artnet_outputs = bc.artnet_outputs;
_artnet_leds_per_output = bc.artnet_leds_per_output;
- _artnet_fps_limit = max(uint8_t(1), bc.artnet_fps_limit);
+ _artnet_fps_limit = max(uint8_t(1), bc.artnet_fps_limit);
USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
}
From b62a22e4e644a3efb3a4dbb233a6ae9c5c08f970 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 17 Nov 2024 18:26:04 -0500
Subject: [PATCH 13/14] Remove unused variable
---
wled00/udp.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/wled00/udp.cpp b/wled00/udp.cpp
index ecd751e914..6dd01a3bdf 100644
--- a/wled00/udp.cpp
+++ b/wled00/udp.cpp
@@ -760,7 +760,6 @@ void sendSysInfoUDP()
static size_t sequenceNumber = 0; // this needs to be shared across all outputs
static const byte ART_NET_HEADER[12] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
-static uint_fast16_t framenumber = 0;
#if defined(ARDUINO_ARCH_ESP32P4)
extern "C" {
From db3be8580fd0202e65d527a78a9839d5d4151310 Mon Sep 17 00:00:00 2001
From: Troy <5659019+troyhacks@users.noreply.github.com>
Date: Sun, 17 Nov 2024 18:37:03 -0500
Subject: [PATCH 14/14] Art-Net v2 code review changes
---
wled00/data/settings_leds.htm | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm
index 0da47e6350..a39629b59e 100644
--- a/wled00/data/settings_leds.htm
+++ b/wled00/data/settings_leds.htm
@@ -222,6 +222,10 @@
gId("dig"+n+"O").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net output number
gId("dig"+n+"L").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net LEDs per output
gId("dig"+n+"F").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net FPS limiter
+ gId("dig"+n+"W").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net warnings/info box
+ d.getElementsByName("AO"+n)[0].min = (t == 82 || t == 83) ? 1 : -1; // make sure these fields do not block saving when hidden
+ d.getElementsByName("AL"+n)[0].min = (t == 82 || t == 83) ? 1 : -1;
+ d.getElementsByName("AF"+n)[0].min = (t == 82 || t == 83) ? 1 : -1;
if (gId("dig"+n+"F").style.display == "inline") {
total_leds = d.getElementsByName("LC"+n)[0].value;
outputs = d.getElementsByName("AO"+n)[0].value;
@@ -337,8 +341,6 @@
// update total led count
gId("lc").textContent = sLC;
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
-
-
// memory usage and warnings
gId('m0').innerHTML = memu;
bquot = memu / maxM * 100;