From 8c64cddc12a54991976f719911a8743f8061c385 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Feb 2026 14:38:46 +0100 Subject: [PATCH 1/6] bugfix in CCT calculation, extended CCT blend mode with negative values - CCT from RGB has to be calculated from original color to be accurate - negative blend values create "exclusive" zones where only one channel is on, blending happens in the center, total is always 255 (non additive) --- wled00/bus_manager.cpp | 81 ++++++++++++++++++++++------------- wled00/bus_manager.h | 13 +++--- wled00/data/settings_leds.htm | 3 +- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 671b99a608..7dafd770a8 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -78,30 +78,49 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format } - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); + // CCT blending modes (_cctBlend): + // blend<0: ww: ▓▓▒░__ cw:__░▒▓▓ | blend=0: ww: ▓▒▒░░ cw: ░░▒▒▓ | blend>0 ww: ▓▓▓▒░ cw:░▒▓▓▓ + int32_t ww_val, cw_val; + if (_cctBlend < 0) { + uint16_t range = 255 - 2 * (uint8_t)(-_cctBlend); + ww_val = range ? ((int32_t)(255 + _cctBlend - cct) * 255) / range : (cct < 128 ? 255 : 0); + cw_val = 255 - ww_val; + } else { + ww_val = _cctBlend ? ((int32_t)(255 - cct) * 255) / (255 - _cctBlend) : 255 - cct; + cw_val = _cctBlend ? ((int32_t) cct * 255) / (255 - _cctBlend) : cct; + } + ww = (uint8_t)(ww_val < 0 ? 0 : ww_val > 255 ? 255 : ww_val); + cw = (uint8_t)(cw_val < 0 ? 0 : cw_val > 255 ? 255 : cw_val); ww = (w * ww) / 255; //brightness scaling cw = (w * cw) / 255; } -uint32_t Bus::autoWhiteCalc(uint32_t c) const { +// calculates white channel and CCT values based on given settings +uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const { unsigned aWM = _autoWhiteMode; if (_gAWM < AW_GLOBAL_DISABLED) aWM = _gAWM; - if (aWM == RGBW_MODE_MANUAL_ONLY) return c; + CRGBW cIn = c; // save original color for CCT calculation unsigned w = W(c); - //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) - if (w > 0 && aWM == RGBW_MODE_DUAL) return c; - unsigned r = R(c); - unsigned g = G(c); - unsigned b = B(c); - if (aWM == RGBW_MODE_MAX) return RGBW32(r, g, b, r > g ? (r > b ? r : b) : (g > b ? g : b)); // brightest RGB channel - w = r < g ? (r < b ? r : b) : (g < b ? g : b); - if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode - return RGBW32(r, g, b, w); + if (aWM != RGBW_MODE_MANUAL_ONLY) { + unsigned r = R(c); // note: using uint8_t generates larger code + unsigned g = G(c); + unsigned b = B(c); + if (aWM == RGBW_MODE_DUAL && w > 0) { + //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) + } else if (aWM == RGBW_MODE_MAX) { + w = r > g ? (r > b ? r : b) : (g > b ? g : b); // brightest RGB channel + } else { + w = r < g ? (r < b ? r : b) : (g < b ? g : b); // darkest RGB channel + if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode + } + c = RGBW32(r, g, b, w); + } + if (_hasCCT) { + cIn.w = w; // need original rgb values in case CCT is derived from RGB + calculateCCT(cIn, ww, cw); + } + return c; } @@ -240,9 +259,11 @@ void BusDigital::setStatusPixel(uint32_t c) { } } +// note: using WLED_O2_ATTR makes this function ~7% faster at the expense of 600 bytes of flash void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; - if (hasWhite()) c = autoWhiteCalc(c); + uint8_t cctWW = 0, cctCW = 0; + if (hasWhite()) c = autoWhiteCalc(c, cctWW, cctCW); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT c = color_fade(c, _bri, true); // apply brightness @@ -271,8 +292,6 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { } uint16_t wwcw = 0; if (hasCCT()) { - uint8_t cctWW = 0, cctCW = 0; - Bus::calculateCCT(c, cctWW, cctCW); wwcw = (cctCW<<8) | cctWW; if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); } @@ -441,7 +460,8 @@ BusPwm::BusPwm(const BusConfig &bc) void BusPwm::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); + uint8_t CCTww, CCTcw; + if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c, CCTww, CCTcw); // TODO: use cw ad ww below if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } @@ -456,14 +476,18 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) { _data[0] = w; _data[1] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; } else { - Bus::calculateCCT(c, _data[0], _data[1]); + _data[0] = CCTww; + _data[1] = CCTcw; } break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white if (cctICused) _data[4] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; - else - Bus::calculateCCT(c, w, _data[4]); + else { + w = CCTww; + _data[4] = CCTcw; + } + // fall through to set RGBW channels case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB @@ -619,9 +643,7 @@ BusOnOff::BusOnOff(const BusConfig &bc) void BusOnOff::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - c = autoWhiteCalc(c); - uint8_t r = R(c), g = G(c), b = B(c), w = W(c); - _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; + _data = (c > 0) && bool(_bri) ? 0xFF : 0; // if any color channel is on and brightness is not zero, set to on } uint32_t BusOnOff::getPixelColor(unsigned pix) const { @@ -681,7 +703,8 @@ BusNetwork::BusNetwork(const BusConfig &bc) void BusNetwork::setPixelColor(unsigned pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (_hasWhite) c = autoWhiteCalc(c); + uint8_t ww, cw; // dummy, unused + if (_hasWhite) c = autoWhiteCalc(c, ww, cw); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT unsigned offset = pix * _UDPchannels; _data[offset] = R(c); @@ -1413,8 +1436,8 @@ uint8_t PolyBus::_parallelBusItype = 0; // type I_NONE uint8_t PolyBus::_2PchannelsAssigned = 0; #endif // Bus static member definition -int16_t Bus::_cct = -1; -uint8_t Bus::_cctBlend = 0; // 0 - 127 +int16_t Bus::_cct = -1; // -1 means use approximateKelvinFromRGB(), 0-255 is standard, >1900 use colorBalanceFromKelvin() +int8_t Bus::_cctBlend = 0; // -128 to +127 uint8_t Bus::_gAWM = 255; uint16_t BusDigital::_milliAmpsTotal = 0; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 2d3c5f58f1..c40da690d5 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -201,9 +201,9 @@ class Bus { static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } static inline uint8_t getGlobalAWMode() { return _gAWM; } static inline void setCCT(int16_t cct) { _cct = cct; } - static inline uint8_t getCCTBlend() { return (_cctBlend * 100 + 64) / 127; } // returns 0-100, 100% = 127. +64 for rounding - static inline void setCCTBlend(uint8_t b) { // input is 0-100 - _cctBlend = (std::min((int)b,100) * 127 + 50) / 100; // +50 for rounding, b=100% -> 127 + static inline int8_t getCCTBlend() { return (_cctBlend * 100 + (_cctBlend >= 0 ? 64 : -64)) / 127; } // returns -100 t +100, +/-100% = +/-127. +/-64 for rounding + static inline void setCCTBlend(int8_t b) { // input is -100 to +100 + _cctBlend = (std::max(-100, std::min(100, (int)b)) * 127 + (b >= 0 ? 50 : -50)) / 100; // +/-50 for rounding, b=+/-100% -> +/-127 //compile-time limiter for hardware that can't power both white channels at max #ifdef WLED_MAX_CCT_BLEND if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; @@ -232,13 +232,14 @@ class Bus { // [0,255] is the exact CCT value where 0 means warm and 255 cold // [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin()) static int16_t _cct; - // _cctBlend determines WW/CW blending: + // _cctBlend determines WW/CW blending, see calculateCCT() + // < 0 - linear blending in center, single white at both ends, single white zone extends with decreased value (-127 min) // 0 - linear (CCT 127 => 50% warm, 50% cold) // 63 - semi additive/nonlinear (CCT 127 => 66% warm, 66% cold) // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) - static uint8_t _cctBlend; + static int8_t _cctBlend; - uint32_t autoWhiteCalc(uint32_t c) const; + uint32_t autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const; }; diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index dd4ec7cc8e..7c048df250 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -1137,7 +1137,8 @@

White management


Calculate CCT from RGB:
CCT IC used (Athom 15W):
- CCT additive blending: %
+ CCT blending (±100%): %
+ Positive: additive blend, Negative: exclusive blend
WARNING: When using H-bridge for reverse polarity (2-wire) CCT LED strip
make sure this value is 0.
(ESP32 variants only, ESP8266 does not support H-bridges)

Advanced

From 15303f91e0d29862017c82fc24d34ed2cdab2d14 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Feb 2026 17:43:29 +0100 Subject: [PATCH 2/6] Fixed CCT brightness ad calculate colorbalance before white --- wled00/bus_manager.cpp | 40 +++++++++++++++++++++++++--------------- wled00/bus_manager.h | 2 +- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 7dafd770a8..1a88fb746f 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -224,15 +224,21 @@ void BusDigital::applyBriLimit(uint8_t newBri) { if (newBri < 255) { _NPBbri = newBri; // store value so it can be updated in show() (must be updated even if ABL is not used) - uint8_t cctWW = 0, cctCW = 0; + uint16_t wwcw = 0; unsigned hwLen = _len; if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus for (unsigned i = 0; i < hwLen; i++) { uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); + if (hasCCT()) { + uint8_t cctWW, cctCW; + Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) + cctCW = ((cctCW + 1) * _bri) & 0xFF00; // apply brightess to CCT (leave it in upper byte for 16bit NeoPixelBus value) + cctWW = ((cctWW + 1) * _bri) >> 8; + wwcw = cctCW | cctWW; + } c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far - if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); - PolyBus::setPixelColor(_busPtr, _iType, i, c, co, (cctCW<<8) | cctWW); // repaint all pixels with new brightness + PolyBus::setPixelColor(_busPtr, _iType, i, c, co, wwcw); // repaint all pixels with new brightness } } @@ -262,11 +268,18 @@ void BusDigital::setStatusPixel(uint32_t c) { // note: using WLED_O2_ATTR makes this function ~7% faster at the expense of 600 bytes of flash void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; + if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT uint8_t cctWW = 0, cctCW = 0; + uint16_t wwcw = 0; if (hasWhite()) c = autoWhiteCalc(c, cctWW, cctCW); - if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT c = color_fade(c, _bri, true); // apply brightness + if (hasCCT()) { + wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightess to CCT (store CW in upper byte) + wwcw |= ((cctWW + 1) * _bri) >> 8; + if (_type == TYPE_WS2812_WWA) c = RGBW32(wwcw, wwcw >> 8, 0, W(c)); // ww,cw, 0, w + } + if (BusManager::_useABL) { // if using ABL, sum all color channels to estimate current and limit brightness in show() uint8_t r = R(c), g = G(c), b = B(c); @@ -290,11 +303,7 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - uint16_t wwcw = 0; - if (hasCCT()) { - wwcw = (cctCW<<8) | cctWW; - if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); - } + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } @@ -460,12 +469,13 @@ BusPwm::BusPwm(const BusConfig &bc) void BusPwm::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - uint8_t CCTww, CCTcw; - if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c, CCTww, CCTcw); // TODO: use cw ad ww below if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } + uint8_t cctWW, cctCW; + if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c, cctWW, cctCW); // TODO: use cw ad ww below uint8_t r = R(c), g = G(c), b = B(c), w = W(c); + // note: no color scaling, brightness is applied in show() switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation @@ -476,16 +486,16 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) { _data[0] = w; _data[1] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; } else { - _data[0] = CCTww; - _data[1] = CCTcw; + _data[0] = cctWW; + _data[1] = cctCW; } break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white if (cctICused) _data[4] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; else { - w = CCTww; - _data[4] = CCTcw; + w = cctWW; + _data[4] = cctCW; } // fall through to set RGBW channels case TYPE_ANALOG_4CH: //RGBW diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c40da690d5..d5ac31a670 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -201,7 +201,7 @@ class Bus { static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } static inline uint8_t getGlobalAWMode() { return _gAWM; } static inline void setCCT(int16_t cct) { _cct = cct; } - static inline int8_t getCCTBlend() { return (_cctBlend * 100 + (_cctBlend >= 0 ? 64 : -64)) / 127; } // returns -100 t +100, +/-100% = +/-127. +/-64 for rounding + static inline int8_t getCCTBlend() { return (_cctBlend * 100 + (_cctBlend >= 0 ? 64 : -64)) / 127; } // returns -100 to +100, +/-100% = +/-127. +/-64 for rounding static inline void setCCTBlend(int8_t b) { // input is -100 to +100 _cctBlend = (std::max(-100, std::min(100, (int)b)) * 127 + (b >= 0 ? 50 : -50)) / 100; // +/-50 for rounding, b=+/-100% -> +/-127 //compile-time limiter for hardware that can't power both white channels at max From e328e483720c038a6c25784e77e7f8bac3b839f0 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Feb 2026 19:27:11 +0100 Subject: [PATCH 3/6] bugfix, removed todo --- wled00/bus_manager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 1a88fb746f..e19f7b5f0f 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -233,9 +233,8 @@ void BusDigital::applyBriLimit(uint8_t newBri) { if (hasCCT()) { uint8_t cctWW, cctCW; Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) - cctCW = ((cctCW + 1) * _bri) & 0xFF00; // apply brightess to CCT (leave it in upper byte for 16bit NeoPixelBus value) - cctWW = ((cctWW + 1) * _bri) >> 8; - wwcw = cctCW | cctWW; + wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightess to CCT (leave it in upper byte for 16bit NeoPixelBus value) + wwcw |= ((cctWW + 1) * _bri) >> 8; } c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far PolyBus::setPixelColor(_busPtr, _iType, i, c, co, wwcw); // repaint all pixels with new brightness @@ -473,7 +472,7 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) { c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } uint8_t cctWW, cctCW; - if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c, cctWW, cctCW); // TODO: use cw ad ww below + if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c, cctWW, cctCW); uint8_t r = R(c), g = G(c), b = B(c), w = W(c); // note: no color scaling, brightness is applied in show() From 3905b2c33406f1c6e0111e2c13313524c807ece3 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Feb 2026 20:02:22 +0100 Subject: [PATCH 4/6] bugfixes --- wled00/bus_manager.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index e19f7b5f0f..82ba34e80a 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -79,14 +79,16 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { } // CCT blending modes (_cctBlend): - // blend<0: ww: ▓▓▒░__ cw:__░▒▓▓ | blend=0: ww: ▓▒▒░░ cw: ░░▒▒▓ | blend>0 ww: ▓▓▓▒░ cw:░▒▓▓▓ + // blend<0: ww: ▓▓▒░__ | blend=0: ww: ▓▒▒░░ | blend>0 ww: ▓▓▓▒░ + // cw: __░▒▓▓ | cw: ░░▒▒▓ | cw: ░▒▓▓▓ int32_t ww_val, cw_val; if (_cctBlend < 0) { uint16_t range = 255 - 2 * (uint8_t)(-_cctBlend); - ww_val = range ? ((int32_t)(255 + _cctBlend - cct) * 255) / range : (cct < 128 ? 255 : 0); + if (range > 255) range = 255; // prevent overflow + ww_val = range ? ((int32_t)(255 + _cctBlend - cct) * 255) / range : (cct < 128 ? 255 : 0); // exclusive blending cw_val = 255 - ww_val; } else { - ww_val = _cctBlend ? ((int32_t)(255 - cct) * 255) / (255 - _cctBlend) : 255 - cct; + ww_val = _cctBlend ? ((int32_t)(255 - cct) * 255) / (255 - _cctBlend) : 255 - cct; // additive blending cw_val = _cctBlend ? ((int32_t) cct * 255) / (255 - _cctBlend) : cct; } ww = (uint8_t)(ww_val < 0 ? 0 : ww_val > 255 ? 255 : ww_val); @@ -233,8 +235,8 @@ void BusDigital::applyBriLimit(uint8_t newBri) { if (hasCCT()) { uint8_t cctWW, cctCW; Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) - wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightess to CCT (leave it in upper byte for 16bit NeoPixelBus value) - wwcw |= ((cctWW + 1) * _bri) >> 8; + wwcw = ((cctCW + 1) * newBri) & 0xFF00; // apply brightess to CCT (leave it in upper byte for 16bit NeoPixelBus value) + wwcw |= ((cctWW + 1) * newBri) >> 8; } c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far PolyBus::setPixelColor(_busPtr, _iType, i, c, co, wwcw); // repaint all pixels with new brightness From a328495dcafcd6e0023acf36bbeb26742963fa58 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 21 Feb 2026 15:49:17 +0100 Subject: [PATCH 5/6] allow new exclusive blending for 2-wire CCT --- 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 82ba34e80a..52ba19bdef 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -565,7 +565,7 @@ void BusPwm::show() { unsigned duty = (_data[i] * pwmBri) / 255; unsigned deadTime = 0; - if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend == 0) { + if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend <= 0) { // add dead time between signals (when using dithering, two full 8bit pulses are required) deadTime = (1+dithering) << bitShift; // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap From eafd5add01342c6affdcb6764eedd81e1821a1b2 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 21 Feb 2026 16:34:22 +0100 Subject: [PATCH 6/6] adding comments --- wled00/bus_manager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 52ba19bdef..3a978609c5 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -231,11 +231,11 @@ void BusDigital::applyBriLimit(uint8_t newBri) { if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus for (unsigned i = 0; i < hwLen; i++) { uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped - uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); + uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); // Note: if ABL would be calculated as a seperate loop (as it was before) it is slower but could use original color, making it more color-accurate if (hasCCT()) { uint8_t cctWW, cctCW; - Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) - wwcw = ((cctCW + 1) * newBri) & 0xFF00; // apply brightess to CCT (leave it in upper byte for 16bit NeoPixelBus value) + Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) | Note: if using "accurate" white calculation mode, approximateKelvinFromRGB can be very inaccurate (white is subtracted) + wwcw = ((cctCW + 1) * newBri) & 0xFF00; // apply brightness to CCT (leave it in upper byte for 16bit NeoPixelBus value) wwcw |= ((cctWW + 1) * newBri) >> 8; } c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far @@ -276,7 +276,7 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { c = color_fade(c, _bri, true); // apply brightness if (hasCCT()) { - wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightess to CCT (store CW in upper byte) + wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightness to CCT (store CW in upper byte) wwcw |= ((cctWW + 1) * _bri) >> 8; if (_type == TYPE_WS2812_WWA) c = RGBW32(wwcw, wwcw >> 8, 0, W(c)); // ww,cw, 0, w }