Skip to content

Commit

Permalink
Merge pull request #179 from troyhacks/Art-Net-Improvements-v2
Browse files Browse the repository at this point in the history
Art-Net Improvements
* AsyncUDP instead of WiFiUDP so it's much faster
* Support for professional Art-Net gear with many outputs
* Color-order remapping
* ESP32-P4 SIMD assembly optimization for brightness calc, a bit faster
* Full GUI support for all settings
* GUI guidance for setup
  • Loading branch information
softhack007 authored Nov 18, 2024
2 parents e8d8a1c + 0fc22f8 commit fb259d1
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 81 deletions.
90 changes: 71 additions & 19 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -469,37 +474,84 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
break;
}
_UDPchannels = _rgbw ? 4 : 3;
_data = (byte *)malloc(bc.count * _UDPchannels);
#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;
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;
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 = 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]);
}

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_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

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_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);

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() {
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;
}

Expand Down Expand Up @@ -1186,7 +1238,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");
Expand Down
52 changes: 41 additions & 11 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -330,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; }
Expand All @@ -348,25 +355,48 @@ 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 {
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 override {
return _colorOrder;
}

void cleanup();

~BusNetwork() {
cleanup();
}

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
Expand Down Expand Up @@ -474,4 +504,4 @@ class BusManager {
return j;
}
};
#endif
#endif
10 changes: 8 additions & 2 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"] | 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);
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++;
Expand Down Expand Up @@ -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"));
Expand Down
3 changes: 2 additions & 1 deletion wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,34 @@
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
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;
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 = "<br />Set your Art-Net Hardware to "+Math.ceil(leds_per_output/170)+" universes per output.";
if (t == 83) gId("dig"+n+"W").innerHTML = "<br />Set your Art-Net Hardware to "+Math.ceil(leds_per_output/128)+" universes per output.";
} else if (outputs == 1) {
gId("dig"+n+"W").innerHTML = "<br />WLED-style Art-Net output enabled.";
} else {
gId("dig"+n+"W").innerHTML = "<br />You need at least 1 output!";
}
if (outputs > 1 && fps_limit > 33333/leds_per_output) gId("dig"+n+"W").innerHTML += "<br />FPS limit may be too high for WS281x pixels.";
if (outputs*leds_per_output != total_leds) gId("dig"+n+"W").innerHTML += "<br />Total LEDs doesn't match outputs * LEDs per output.";
if (last_octet == 255) {
if (total_leds <= 1024) gId("dig"+n+"W").innerHTML += "<br />Art-Net is in broadcast mode.";
if (total_leds > 1024) gId("dig"+n+"W").innerHTML += "<br />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
Expand Down Expand Up @@ -318,7 +346,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;
Expand Down Expand Up @@ -425,6 +452,10 @@
<span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI()"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI()"/>
<div id="dig${i}O" style="display:inline"><br>Number of Outputs: <input type="number" name="AO${i}" min="1" max="255" value="1" oninput="UI()"></div>
<div id="dig${i}L" style="display:inline"><br>LEDs Per Output: <input type="number" name="AL${i}" min="1" max="65535" value="1" oninput="UI()"></div>
<div id="dig${i}F" style="display:inline"><br>FPS Limit: <input type="number" name="AF${i}" min="1" max="255" value="30" oninput="UI()"></div>
<div id="dig${i}W" style="display:inline"><br>Art-Net Warnings Go Here.</div>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${i}f" style="display:inline"><br><span id="ref${i}">Off Refresh</span>: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
Expand Down
2 changes: 1 addition & 1 deletion wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
31 changes: 31 additions & 0 deletions wled00/p4_mul16x16.S
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit fb259d1

Please sign in to comment.