2023-02-05 22:48:43 +00:00
/*
* Class implementation for addressing various light types
*/
# include <Arduino.h>
# include <IPAddress.h>
2024-08-24 09:35:32 +00:00
# ifdef ARDUINO_ARCH_ESP32
2025-06-28 04:57:53 +00:00
# include <ESPmDNS.h>
# include "src/dependencies/network/Network.h" // for isConnected() (& WiFi)
2024-08-24 09:35:32 +00:00
# include "driver/ledc.h"
2024-08-30 13:21:16 +00:00
# include "soc/ledc_struct.h"
2024-09-01 10:27:41 +00:00
# if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
# define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS)
# define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock)
2025-08-16 11:05:41 +00:00
extern SemaphoreHandle_t _ledc_sys_lock ;
2024-09-01 10:27:41 +00:00
# else
# define LEDC_MUTEX_LOCK()
# define LEDC_MUTEX_UNLOCK()
# endif
2024-08-24 09:35:32 +00:00
# endif
2024-09-28 14:02:05 +00:00
# ifdef ESP8266
# include "core_esp8266_waveform.h"
# endif
2023-02-05 22:48:43 +00:00
# include "const.h"
# include "pin_manager.h"
# include "bus_manager.h"
2025-01-12 14:17:22 +00:00
# include "bus_wrapper.h"
2025-01-19 20:35:10 +00:00
# include <bits/unique_ptr.h>
2023-02-05 22:48:43 +00:00
2025-06-28 04:57:53 +00:00
extern char cmDNS [ ] ;
2024-03-28 15:03:06 +00:00
extern bool cctICused ;
2025-01-12 14:17:22 +00:00
extern bool useParallelI2S ;
2024-03-28 15:03:06 +00:00
2023-02-05 22:48:43 +00:00
//colors.cpp
uint32_t colorBalanceFromKelvin ( uint16_t kelvin , uint32_t rgb ) ;
//udp.cpp
2025-04-22 20:37:18 +00:00
uint8_t realtimeBroadcast ( uint8_t type , IPAddress client , uint16_t length , const byte * buffer , uint8_t bri = 255 , bool isRGBW = false ) ;
//util.cpp
// PSRAM allocation wrappers
2025-07-23 04:42:06 +00:00
# if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
2025-04-26 18:08:15 +00:00
extern " C " {
void * p_malloc ( size_t ) ; // prefer PSRAM over DRAM
void * p_calloc ( size_t , size_t ) ; // prefer PSRAM over DRAM
void * p_realloc ( void * , size_t ) ; // prefer PSRAM over DRAM
2025-07-23 04:42:06 +00:00
void * p_realloc_malloc ( void * ptr , size_t size ) ; // realloc with malloc fallback, prefer PSRAM over DRAM
2025-04-26 18:08:15 +00:00
inline void p_free ( void * ptr ) { heap_caps_free ( ptr ) ; }
void * d_malloc ( size_t ) ; // prefer DRAM over PSRAM
void * d_calloc ( size_t , size_t ) ; // prefer DRAM over PSRAM
void * d_realloc ( void * , size_t ) ; // prefer DRAM over PSRAM
2025-07-23 04:42:06 +00:00
void * d_realloc_malloc ( void * ptr , size_t size ) ; // realloc with malloc fallback, prefer DRAM over PSRAM
2025-04-26 18:08:15 +00:00
inline void d_free ( void * ptr ) { heap_caps_free ( ptr ) ; }
}
2025-04-22 20:37:18 +00:00
# else
2025-07-23 04:42:06 +00:00
extern " C " {
void * realloc_malloc ( void * ptr , size_t size ) ;
}
2025-04-26 18:08:15 +00:00
# define p_malloc malloc
# define p_calloc calloc
# define p_realloc realloc
2025-07-23 04:42:06 +00:00
# define p_realloc_malloc realloc_malloc
2025-04-26 18:08:15 +00:00
# define p_free free
2025-04-22 20:37:18 +00:00
# define d_malloc malloc
# define d_calloc calloc
# define d_realloc realloc
2025-07-23 04:42:06 +00:00
# define d_realloc_malloc realloc_malloc
2025-04-22 20:37:18 +00:00
# define d_free free
# endif
2023-02-05 22:48:43 +00:00
//color mangling macros
# define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
# define R(c) (byte((c) >> 16))
# define G(c) (byte((c) >> 8))
# define B(c) (byte(c))
# define W(c) (byte((c) >> 24))
2025-01-21 16:50:36 +00:00
static ColorOrderMap _colorOrderMap = { } ;
2024-08-22 15:15:12 +00:00
bool ColorOrderMap : : add ( uint16_t start , uint16_t len , uint8_t colorOrder ) {
if ( count ( ) > = WLED_MAX_COLOR_ORDER_MAPPINGS | | len = = 0 | | ( colorOrder & 0x0F ) > COL_ORDER_MAX ) return false ; // upper nibble contains W swap information
_mappings . push_back ( { start , len , colorOrder } ) ;
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTF_P ( PSTR ( " Bus: Add COM (%d,%d,%d) \n " ) , ( int ) start , ( int ) len , ( int ) colorOrder ) ;
2024-08-22 15:15:12 +00:00
return true ;
2023-02-05 22:48:43 +00:00
}
uint8_t IRAM_ATTR ColorOrderMap : : getPixelColorOrder ( uint16_t pix , uint8_t defaultColorOrder ) const {
2024-08-22 15:15:12 +00:00
// upper nibble contains W swap information
// when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used
2025-01-21 16:50:36 +00:00
for ( const auto & map : _mappings ) {
if ( pix > = map . start & & pix < ( map . start + map . len ) ) return map . colorOrder | ( ( map . colorOrder > > 4 ) ? 0 : ( defaultColorOrder & 0xF0 ) ) ;
2023-02-05 22:48:43 +00:00
}
return defaultColorOrder ;
}
2024-09-06 10:19:04 +00:00
void Bus : : calculateCCT ( uint32_t c , uint8_t & ww , uint8_t & cw ) {
unsigned cct = 0 ; //0 - full warm white, 255 - full cold white
unsigned w = W ( c ) ;
if ( _cct > - 1 ) { // using RGB?
if ( _cct > = 1900 ) cct = ( _cct - 1900 ) > > 5 ; // convert K in relative format
else if ( _cct < 256 ) cct = _cct ; // already relative
} else {
cct = ( approximateKelvinFromRGB ( c ) - 1900 ) > > 5 ; // convert K (from RGB value) to relative format
}
2025-06-29 20:42:33 +00:00
2024-09-06 10:19:04 +00:00
//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 ) ;
ww = ( w * ww ) / 255 ; //brightness scaling
cw = ( w * cw ) / 255 ;
}
2024-08-22 15:15:12 +00:00
uint32_t Bus : : autoWhiteCalc ( uint32_t c ) const {
2024-07-09 19:50:27 +00:00
unsigned aWM = _autoWhiteMode ;
2023-12-09 18:03:33 +00:00
if ( _gAWM < AW_GLOBAL_DISABLED ) aWM = _gAWM ;
2023-02-05 22:48:43 +00:00
if ( aWM = = RGBW_MODE_MANUAL_ONLY ) return c ;
2024-07-09 19:50:27 +00:00
unsigned w = W ( c ) ;
2023-02-05 22:48:43 +00:00
//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 ;
2024-07-09 19:50:27 +00:00
unsigned r = R ( c ) ;
unsigned g = G ( c ) ;
unsigned b = B ( c ) ;
2023-02-14 00:33:06 +00:00
if ( aWM = = RGBW_MODE_MAX ) return RGBW32 ( r , g , b , r > g ? ( r > b ? r : b ) : ( g > b ? g : b ) ) ; // brightest RGB channel
2023-02-05 22:48:43 +00:00
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 ) ;
}
2025-01-21 16:50:36 +00:00
BusDigital : : BusDigital ( const BusConfig & bc , uint8_t nr )
2023-07-06 19:16:29 +00:00
: Bus ( bc . type , bc . start , bc . autoWhite , bc . count , bc . reversed , ( bc . refreshReq | | bc . type = = TYPE_TM1814 ) )
, _skip ( bc . skipAmount ) //sacrificial pixels
, _colorOrder ( bc . colorOrder )
2023-11-15 18:37:07 +00:00
, _milliAmpsPerLed ( bc . milliAmpsPerLed )
, _milliAmpsMax ( bc . milliAmpsMax )
2023-07-06 19:16:29 +00:00
{
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTLN ( F ( " Bus: Creating digital bus. " ) ) ;
if ( ! isDigital ( bc . type ) | | ! bc . count ) { DEBUGBUS_PRINTLN ( F ( " Not digial or empty bus! " ) ) ; return ; }
if ( ! PinManager : : allocatePin ( bc . pins [ 0 ] , true , PinOwner : : BusDigital ) ) { DEBUGBUS_PRINTLN ( F ( " Pin 0 allocated! " ) ) ; return ; }
2023-04-27 22:27:19 +00:00
_frequencykHz = 0U ;
2023-02-05 22:48:43 +00:00
_pins [ 0 ] = bc . pins [ 0 ] ;
2024-08-24 09:35:32 +00:00
if ( is2Pin ( bc . type ) ) {
2024-09-19 19:44:11 +00:00
if ( ! PinManager : : allocatePin ( bc . pins [ 1 ] , true , PinOwner : : BusDigital ) ) {
2023-07-06 19:16:29 +00:00
cleanup ( ) ;
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTLN ( F ( " Pin 1 allocated! " ) ) ;
2023-07-06 19:16:29 +00:00
return ;
2023-02-05 22:48:43 +00:00
}
_pins [ 1 ] = bc . pins [ 1 ] ;
2023-04-27 22:27:19 +00:00
_frequencykHz = bc . frequency ? bc . frequency : 2000U ; // 2MHz clock if undefined
2023-02-05 22:48:43 +00:00
}
_iType = PolyBus : : getI ( bc . type , _pins , nr ) ;
2025-01-19 10:37:57 +00:00
if ( _iType = = I_NONE ) { DEBUGBUS_PRINTLN ( F ( " Incorrect iType! " ) ) ; return ; }
2024-08-24 09:35:32 +00:00
_hasRgb = hasRGB ( bc . type ) ;
_hasWhite = hasWhite ( bc . type ) ;
_hasCCT = hasCCT ( bc . type ) ;
2023-07-06 19:16:29 +00:00
uint16_t lenToCreate = bc . count ;
if ( bc . type = = TYPE_WS2812_1CH_X3 ) lenToCreate = NUM_ICS_WS2812_1CH_3X ( bc . count ) ; // only needs a third of "RGB" LEDs for NeoPixelBus
2024-11-27 14:05:10 +00:00
_busPtr = PolyBus : : create ( _iType , _pins , lenToCreate + _skip , nr ) ;
2025-01-21 19:14:20 +00:00
_valid = ( _busPtr ! = nullptr ) & & bc . count > 0 ;
2025-07-08 11:28:24 +00:00
// fix for wled#4759
if ( _valid ) for ( unsigned i = 0 ; i < _skip ; i + + ) {
PolyBus : : setPixelColor ( _busPtr , _iType , i , 0 , COL_ORDER_GRB ) ; // set sacrificial pixels to black (CO does not matter here)
}
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTF_P ( PSTR ( " Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d) \n " ) ,
2025-01-21 19:14:20 +00:00
_valid ? " S " : " Uns " ,
( int ) nr ,
( int ) bc . count ,
( int ) bc . type ,
( int ) _hasRgb , ( int ) _hasWhite , ( int ) _hasCCT ,
( unsigned ) _pins [ 0 ] , is2Pin ( bc . type ) ? ( unsigned ) _pins [ 1 ] : 255U ,
( unsigned ) _iType ,
( int ) _milliAmpsPerLed , ( int ) _milliAmpsMax
) ;
2023-02-05 22:48:43 +00:00
}
2023-11-15 18:37:07 +00:00
//DISCLAIMER
//The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold.
//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin.
//Stay safe with high amperage and have a reasonable safety margin!
//I am NOT to be held liable for burned down garages or houses!
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
2025-01-30 19:46:26 +00:00
uint8_t BusDigital : : estimateCurrentAndLimitBri ( ) const {
2023-11-15 18:37:07 +00:00
bool useWackyWS2815PowerModel = false ;
byte actualMilliampsPerLed = _milliAmpsPerLed ;
2023-12-29 22:07:29 +00:00
if ( _milliAmpsMax < MA_FOR_ESP / BusManager : : getNumBusses ( ) | | actualMilliampsPerLed = = 0 ) { //0 mA per LED and too low numbers turn off calculation
2023-11-15 18:37:07 +00:00
return _bri ;
}
if ( _milliAmpsPerLed = = 255 ) {
useWackyWS2815PowerModel = true ;
actualMilliampsPerLed = 12 ; // from testing an actual strip
}
2025-01-13 16:22:11 +00:00
unsigned powerBudget = ( _milliAmpsMax - MA_FOR_ESP / BusManager : : getNumBusses ( ) ) ; //80/120mA for ESP power
2023-12-29 22:07:29 +00:00
if ( powerBudget > getLength ( ) ) { //each LED uses about 1mA in standby, exclude that from power budget
powerBudget - = getLength ( ) ;
} else {
powerBudget = 0 ;
}
2023-11-15 18:37:07 +00:00
uint32_t busPowerSum = 0 ;
for ( unsigned i = 0 ; i < getLength ( ) ; i + + ) { //sum up the usage of each LED
uint32_t c = getPixelColor ( i ) ; // always returns original or restored color without brightness scaling
byte r = R ( c ) , g = G ( c ) , b = B ( c ) , w = W ( c ) ;
if ( useWackyWS2815PowerModel ) { //ignore white component on WS2815 power calculation
busPowerSum + = ( max ( max ( r , g ) , b ) ) * 3 ;
} else {
busPowerSum + = ( r + g + b + w ) ;
}
}
if ( hasWhite ( ) ) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
busPowerSum * = 3 ;
busPowerSum > > = 2 ; //same as /= 4
}
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
2025-01-19 09:17:33 +00:00
BusDigital : : _milliAmpsTotal = ( busPowerSum * actualMilliampsPerLed * _bri ) / ( 765 * 255 ) ;
2023-11-15 18:37:07 +00:00
uint8_t newBri = _bri ;
2025-01-19 09:17:33 +00:00
if ( BusDigital : : _milliAmpsTotal > powerBudget ) {
2025-01-13 16:22:11 +00:00
//scale brightness down to stay in current limit
2025-01-19 09:17:33 +00:00
unsigned scaleB = powerBudget * 255 / BusDigital : : _milliAmpsTotal ;
2025-01-13 16:22:11 +00:00
newBri = ( _bri * scaleB ) / 256 + 1 ;
2025-01-19 09:17:33 +00:00
BusDigital : : _milliAmpsTotal = powerBudget ;
2025-01-13 16:22:11 +00:00
//_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * newBri) / (765*255);
2023-11-15 18:37:07 +00:00
}
return newBri ;
}
2024-09-12 06:48:31 +00:00
void BusDigital : : show ( ) {
2025-01-19 09:17:33 +00:00
BusDigital : : _milliAmpsTotal = 0 ;
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return ;
2023-11-15 18:37:07 +00:00
2024-03-11 10:17:45 +00:00
uint8_t cctWW = 0 , cctCW = 0 ;
2025-01-12 14:17:22 +00:00
unsigned newBri = estimateCurrentAndLimitBri ( ) ; // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere())
2025-07-08 11:28:24 +00:00
if ( newBri < _bri ) {
PolyBus : : setBrightness ( _busPtr , _iType , newBri ) ; // limit brightness to stay within current limits
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 + + ) {
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
uint32_t c = restoreColorLossy ( PolyBus : : getPixelColor ( _busPtr , _iType , i , 0 ) , _bri ) ;
if ( hasCCT ( ) ) Bus : : calculateCCT ( c , cctWW , cctCW ) ; // this will unfortunately corrupt (segment) CCT data on every bus
PolyBus : : setPixelColor ( _busPtr , _iType , i , c , 0 , ( cctCW < < 8 ) | cctWW ) ; // repaint all pixels with new brightness
2023-11-15 18:37:07 +00:00
}
2025-07-08 11:28:24 +00:00
}
PolyBus : : show ( _busPtr , _iType , _skip ) ; // faster if buffer consistency is not important (no skipped LEDs)
2023-11-15 18:37:07 +00:00
// restore bus brightness to its original value
// this is done right after show, so this is only OK if LED updates are completed before show() returns
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
if ( newBri < _bri ) PolyBus : : setBrightness ( _busPtr , _iType , _bri ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-12 06:48:31 +00:00
bool BusDigital : : canShow ( ) const {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return true ;
2023-02-05 22:48:43 +00:00
return PolyBus : : canShow ( _busPtr , _iType ) ;
}
2023-07-19 15:25:25 +00:00
void BusDigital : : setBrightness ( uint8_t b ) {
2023-07-19 11:50:09 +00:00
if ( _bri = = b ) return ;
2023-02-05 22:48:43 +00:00
Bus : : setBrightness ( b ) ;
2023-07-18 21:33:28 +00:00
PolyBus : : setBrightness ( _busPtr , _iType , b ) ;
2023-02-05 22:48:43 +00:00
}
//If LEDs are skipped, it is possible to use the first as a status LED.
//TODO only show if no new show due in the next 50ms
void BusDigital : : setStatusPixel ( uint32_t c ) {
2023-07-03 19:13:01 +00:00
if ( _valid & & _skip ) {
2023-02-05 22:48:43 +00:00
PolyBus : : setPixelColor ( _busPtr , _iType , 0 , c , _colorOrderMap . getPixelColorOrder ( _start , _colorOrder ) ) ;
2023-07-03 19:13:01 +00:00
if ( canShow ( ) ) PolyBus : : show ( _busPtr , _iType ) ;
2023-02-05 22:48:43 +00:00
}
}
2024-09-28 13:26:14 +00:00
void IRAM_ATTR BusDigital : : setPixelColor ( unsigned pix , uint32_t c ) {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return ;
2024-03-16 11:36:05 +00:00
if ( hasWhite ( ) ) c = autoWhiteCalc ( c ) ;
2024-03-28 15:03:06 +00:00
if ( Bus : : _cct > = 1900 ) c = colorBalanceFromKelvin ( Bus : : _cct , c ) ; //color correction from CCT
2025-04-22 20:37:18 +00:00
if ( _reversed ) pix = _len - pix - 1 ;
pix + = _skip ;
unsigned co = _colorOrderMap . getPixelColorOrder ( pix + _start , _colorOrder ) ;
if ( _type = = TYPE_WS2812_1CH_X3 ) { // map to correct IC, each controls 3 LEDs
unsigned pOld = pix ;
pix = IC_INDEX_WS2812_1CH_3X ( pix ) ;
uint32_t cOld = restoreColorLossy ( PolyBus : : getPixelColor ( _busPtr , _iType , pix , co ) , _bri ) ;
switch ( pOld % 3 ) { // change only the single channel (TODO: this can cause loss because of get/set)
case 0 : c = RGBW32 ( R ( cOld ) , W ( c ) , B ( cOld ) , 0 ) ; break ;
case 1 : c = RGBW32 ( W ( c ) , G ( cOld ) , B ( cOld ) , 0 ) ; break ;
case 2 : c = RGBW32 ( R ( cOld ) , G ( cOld ) , W ( c ) , 0 ) ; break ;
2024-11-26 19:59:36 +00:00
}
2023-02-14 00:33:06 +00:00
}
2025-04-22 20:37:18 +00:00
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 ) ) ;
}
PolyBus : : setPixelColor ( _busPtr , _iType , pix , c , co , wwcw ) ;
2023-02-05 22:48:43 +00:00
}
2023-07-09 10:32:28 +00:00
// returns original color if global buffering is enabled, else returns lossly restored color from bus
2024-09-28 13:26:14 +00:00
uint32_t IRAM_ATTR BusDigital : : getPixelColor ( unsigned pix ) const {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return 0 ;
2025-04-22 20:37:18 +00:00
if ( _reversed ) pix = _len - pix - 1 ;
pix + = _skip ;
const unsigned co = _colorOrderMap . getPixelColorOrder ( pix + _start , _colorOrder ) ;
uint32_t c = restoreColorLossy ( PolyBus : : getPixelColor ( _busPtr , _iType , ( _type = = TYPE_WS2812_1CH_X3 ) ? IC_INDEX_WS2812_1CH_3X ( pix ) : pix , co ) , _bri ) ;
if ( _type = = TYPE_WS2812_1CH_X3 ) { // map to correct IC, each controls 3 LEDs
unsigned r = R ( c ) ;
unsigned g = _reversed ? B ( c ) : G ( c ) ; // should G and B be switched if _reversed?
unsigned b = _reversed ? G ( c ) : B ( c ) ;
switch ( pix % 3 ) { // get only the single channel
case 0 : c = RGBW32 ( g , g , g , g ) ; break ;
case 1 : c = RGBW32 ( r , r , r , r ) ; break ;
case 2 : c = RGBW32 ( b , b , b , b ) ; break ;
2023-06-30 19:12:59 +00:00
}
2023-02-14 00:33:06 +00:00
}
2025-04-22 20:37:18 +00:00
if ( _type = = TYPE_WS2812_WWA ) {
uint8_t w = R ( c ) | G ( c ) ;
c = RGBW32 ( w , w , 0 , w ) ;
}
return c ;
2023-02-05 22:48:43 +00:00
}
2025-04-22 20:37:18 +00:00
size_t BusDigital : : getPins ( uint8_t * pinArray ) const {
2024-08-24 09:35:32 +00:00
unsigned numPins = is2Pin ( _type ) + 1 ;
2024-08-22 15:15:12 +00:00
if ( pinArray ) for ( unsigned i = 0 ; i < numPins ; i + + ) pinArray [ i ] = _pins [ i ] ;
2023-02-05 22:48:43 +00:00
return numPins ;
}
2025-04-22 20:37:18 +00:00
size_t BusDigital : : getBusSize ( ) const {
2025-07-08 11:28:24 +00:00
return sizeof ( BusDigital ) + ( isOk ( ) ? PolyBus : : getDataSize ( _busPtr , _iType ) : 0 ) ;
2025-01-30 19:46:26 +00:00
}
2023-02-05 22:48:43 +00:00
void BusDigital : : setColorOrder ( uint8_t colorOrder ) {
// upper nibble contains W swap information
if ( ( colorOrder & 0x0F ) > 5 ) return ;
_colorOrder = colorOrder ;
}
2025-04-22 20:37:18 +00:00
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
2024-09-05 19:49:27 +00:00
std : : vector < LEDType > BusDigital : : getLEDTypes ( ) {
return {
{ TYPE_WS2812_RGB , " D " , PSTR ( " WS281x " ) } ,
{ TYPE_SK6812_RGBW , " D " , PSTR ( " SK6812/WS2814 RGBW " ) } ,
{ TYPE_TM1814 , " D " , PSTR ( " TM1814 " ) } ,
{ TYPE_WS2811_400KHZ , " D " , PSTR ( " 400kHz " ) } ,
{ TYPE_TM1829 , " D " , PSTR ( " TM1829 " ) } ,
{ TYPE_UCS8903 , " D " , PSTR ( " UCS8903 " ) } ,
{ TYPE_APA106 , " D " , PSTR ( " APA106/PL9823 " ) } ,
{ TYPE_TM1914 , " D " , PSTR ( " TM1914 " ) } ,
{ TYPE_FW1906 , " D " , PSTR ( " FW1906 GRBCW " ) } ,
{ TYPE_UCS8904 , " D " , PSTR ( " UCS8904 RGBW " ) } ,
{ TYPE_WS2805 , " D " , PSTR ( " WS2805 RGBCW " ) } ,
{ TYPE_SM16825 , " D " , PSTR ( " SM16825 RGBCW " ) } ,
{ TYPE_WS2812_1CH_X3 , " D " , PSTR ( " WS2811 White " ) } ,
2025-01-13 16:22:11 +00:00
//{TYPE_WS2812_2CH_X3, "D", PSTR("WS281x CCT")}, // not implemented
{ TYPE_WS2812_WWA , " D " , PSTR ( " WS281x WWA " ) } , // amber ignored
2024-09-05 19:49:27 +00:00
{ TYPE_WS2801 , " 2P " , PSTR ( " WS2801 " ) } ,
{ TYPE_APA102 , " 2P " , PSTR ( " APA102 " ) } ,
{ TYPE_LPD8806 , " 2P " , PSTR ( " LPD8806 " ) } ,
{ TYPE_LPD6803 , " 2P " , PSTR ( " LPD6803 " ) } ,
{ TYPE_P9813 , " 2P " , PSTR ( " PP9813 " ) } ,
} ;
}
2024-11-23 17:29:46 +00:00
void BusDigital : : begin ( ) {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return ;
2024-11-27 14:00:25 +00:00
PolyBus : : begin ( _busPtr , _iType , _pins , _frequencykHz ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-12 06:48:31 +00:00
void BusDigital : : cleanup ( ) {
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTLN ( F ( " Digital Cleanup. " ) ) ;
2023-02-05 22:48:43 +00:00
PolyBus : : cleanup ( _busPtr , _iType ) ;
_iType = I_NONE ;
_valid = false ;
_busPtr = nullptr ;
2024-09-19 19:44:11 +00:00
PinManager : : deallocatePin ( _pins [ 1 ] , PinOwner : : BusDigital ) ;
PinManager : : deallocatePin ( _pins [ 0 ] , PinOwner : : BusDigital ) ;
2023-02-05 22:48:43 +00:00
}
2024-08-04 15:05:47 +00:00
# ifdef ESP8266
// 1 MHz clock
# define CLOCK_FREQUENCY 1000000UL
# else
// Use XTAL clock if possible to avoid timer frequency error when setting APB clock < 80 Mhz
// https://github.com/espressif/arduino-esp32/blob/2.0.2/cores/esp32/esp32-hal-ledc.c
# ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
# define CLOCK_FREQUENCY 40000000UL
# else
# define CLOCK_FREQUENCY 80000000UL
# endif
# endif
# ifdef ESP8266
# define MAX_BIT_WIDTH 10
# else
# ifdef SOC_LEDC_TIMER_BIT_WIDE_NUM
// C6/H2/P4: 20 bit, S2/S3/C2/C3: 14 bit
2025-02-07 14:02:27 +00:00
# define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
2024-08-04 15:05:47 +00:00
# else
// ESP32: 20 bit (but in reality we would never go beyond 16 bit as the frequency would be to low)
2024-08-30 13:21:16 +00:00
# define MAX_BIT_WIDTH 14
2024-08-04 15:05:47 +00:00
# endif
# endif
2025-01-07 19:33:10 +00:00
BusPwm : : BusPwm ( const BusConfig & bc )
2024-09-05 19:49:27 +00:00
: Bus ( bc . type , bc . start , bc . autoWhite , 1 , bc . reversed , bc . refreshReq ) // hijack Off refresh flag to indicate usage of dithering
2023-07-06 19:16:29 +00:00
{
2024-08-24 09:35:32 +00:00
if ( ! isPWM ( bc . type ) ) return ;
2025-04-22 20:37:18 +00:00
const unsigned numPins = numPWMPins ( bc . type ) ;
2024-09-05 19:49:27 +00:00
[[maybe_unused]] const bool dithering = _needsRefresh ;
2023-04-27 22:27:19 +00:00
_frequency = bc . frequency ? bc . frequency : WLED_PWM_FREQ ;
2024-08-01 18:25:18 +00:00
// duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth
2024-08-04 15:05:47 +00:00
for ( _depth = MAX_BIT_WIDTH ; _depth > 8 ; _depth - - ) if ( ( ( CLOCK_FREQUENCY / _frequency ) > > _depth ) > 0 ) break ;
2023-02-05 22:48:43 +00:00
2024-08-26 22:21:24 +00:00
managed_pin_type pins [ numPins ] ;
for ( unsigned i = 0 ; i < numPins ; i + + ) pins [ i ] = { ( int8_t ) bc . pins [ i ] , true } ;
2025-04-22 20:37:18 +00:00
if ( PinManager : : allocateMultiplePins ( pins , numPins , PinOwner : : BusPwm ) ) {
2023-02-05 22:48:43 +00:00
# ifdef ESP8266
2025-04-22 20:37:18 +00:00
analogWriteRange ( ( 1 < < _depth ) - 1 ) ;
analogWriteFreq ( _frequency ) ;
2023-02-05 22:48:43 +00:00
# else
2025-04-22 20:37:18 +00:00
// for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer
_ledcStart = PinManager : : allocateLedc ( numPins ) ;
if ( _ledcStart = = 255 ) { //no more free LEDC channels
PinManager : : deallocateMultiplePins ( pins , numPins , PinOwner : : BusPwm ) ;
DEBUGBUS_PRINTLN ( F ( " No more free LEDC channels! " ) ) ;
return ;
}
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
if ( dithering ) _depth = 12 ; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering)
2023-02-05 22:48:43 +00:00
# endif
2025-04-22 20:37:18 +00:00
for ( unsigned i = 0 ; i < numPins ; i + + ) {
_pins [ i ] = bc . pins [ i ] ; // store only after allocateMultiplePins() succeeded
# ifdef ESP8266
pinMode ( _pins [ i ] , OUTPUT ) ;
# else
unsigned channel = _ledcStart + i ;
2025-08-16 11:05:41 +00:00
ledcAttach ( _pins [ i ] , _frequency , _depth - ( dithering * 4 ) ) ;
2025-04-22 20:37:18 +00:00
// LEDC timer reset credit @dedehai
uint8_t group = ( channel / 8 ) , timer = ( ( channel / 2 ) % 4 ) ; // same fromula as in ledcSetup()
ledc_timer_rst ( ( ledc_mode_t ) group , ( ledc_timer_t ) timer ) ; // reset timer so all timers are almost in sync (for phase shift)
# endif
}
_hasRgb = hasRGB ( bc . type ) ;
_hasWhite = hasWhite ( bc . type ) ;
_hasCCT = hasCCT ( bc . type ) ;
_valid = true ;
2023-02-05 22:48:43 +00:00
}
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTF_P ( PSTR ( " %successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u \n " ) , _valid ? " S " : " Uns " , bc . type , _frequency , _depth , _pins [ 0 ] , _pins [ 1 ] , _pins [ 2 ] , _pins [ 3 ] , _pins [ 4 ] ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-28 13:26:14 +00:00
void BusPwm : : setPixelColor ( unsigned pix , uint32_t c ) {
2023-02-05 22:48:43 +00:00
if ( pix ! = 0 | | ! _valid ) return ; //only react to first pixel
if ( _type ! = TYPE_ANALOG_3CH ) c = autoWhiteCalc ( c ) ;
2024-03-28 15:03:06 +00:00
if ( Bus : : _cct > = 1900 & & ( _type = = TYPE_ANALOG_3CH | | _type = = TYPE_ANALOG_4CH ) ) {
c = colorBalanceFromKelvin ( Bus : : _cct , c ) ; //color correction from CCT
2023-02-05 22:48:43 +00:00
}
uint8_t r = R ( c ) ;
uint8_t g = G ( c ) ;
uint8_t b = B ( c ) ;
uint8_t w = W ( c ) ;
switch ( _type ) {
case TYPE_ANALOG_1CH : //one channel (white), relies on auto white calculation
_data [ 0 ] = w ;
break ;
case TYPE_ANALOG_2CH : //warm white + cold white
2024-03-28 15:03:06 +00:00
if ( cctICused ) {
_data [ 0 ] = w ;
_data [ 1 ] = Bus : : _cct < 0 | | Bus : : _cct > 255 ? 127 : Bus : : _cct ;
} else {
Bus : : calculateCCT ( c , _data [ 0 ] , _data [ 1 ] ) ;
}
2023-02-05 22:48:43 +00:00
break ;
case TYPE_ANALOG_5CH : //RGB + warm white + cold white
2024-03-28 15:03:06 +00:00
if ( cctICused )
_data [ 4 ] = Bus : : _cct < 0 | | Bus : : _cct > 255 ? 127 : Bus : : _cct ;
else
Bus : : calculateCCT ( c , w , _data [ 4 ] ) ;
2023-02-05 22:48:43 +00:00
case TYPE_ANALOG_4CH : //RGBW
_data [ 3 ] = w ;
case TYPE_ANALOG_3CH : //standard dumb RGB
_data [ 0 ] = r ; _data [ 1 ] = g ; _data [ 2 ] = b ;
break ;
}
}
//does no index check
2024-09-28 13:26:14 +00:00
uint32_t BusPwm : : getPixelColor ( unsigned pix ) const {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return 0 ;
2024-04-26 18:07:27 +00:00
// TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented)
switch ( _type ) {
case TYPE_ANALOG_1CH : //one channel (white), relies on auto white calculation
return RGBW32 ( 0 , 0 , 0 , _data [ 0 ] ) ;
case TYPE_ANALOG_2CH : //warm white + cold white
if ( cctICused ) return RGBW32 ( 0 , 0 , 0 , _data [ 0 ] ) ;
else return RGBW32 ( 0 , 0 , 0 , _data [ 0 ] + _data [ 1 ] ) ;
case TYPE_ANALOG_5CH : //RGB + warm white + cold white
if ( cctICused ) return RGBW32 ( _data [ 0 ] , _data [ 1 ] , _data [ 2 ] , _data [ 3 ] ) ;
else return RGBW32 ( _data [ 0 ] , _data [ 1 ] , _data [ 2 ] , _data [ 3 ] + _data [ 4 ] ) ;
case TYPE_ANALOG_4CH : //RGBW
return RGBW32 ( _data [ 0 ] , _data [ 1 ] , _data [ 2 ] , _data [ 3 ] ) ;
case TYPE_ANALOG_3CH : //standard dumb RGB
return RGBW32 ( _data [ 0 ] , _data [ 1 ] , _data [ 2 ] , 0 ) ;
}
return RGBW32 ( _data [ 0 ] , _data [ 0 ] , _data [ 0 ] , _data [ 0 ] ) ;
2023-02-05 22:48:43 +00:00
}
void BusPwm : : show ( ) {
if ( ! _valid ) return ;
2025-02-01 11:04:24 +00:00
const size_t numPins = getPins ( ) ;
2024-09-28 14:02:05 +00:00
# ifdef ESP8266
const unsigned analogPeriod = F_CPU / _frequency ;
const unsigned maxBri = analogPeriod ; // compute to clock cycle accuracy
2025-01-24 00:36:27 +00:00
constexpr bool dithering = false ;
2024-09-29 14:00:27 +00:00
constexpr unsigned bitShift = 8 ; // 256 clocks for dead time, ~3us at 80MHz
2024-09-28 14:02:05 +00:00
# else
2024-09-03 20:28:21 +00:00
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
2025-04-22 20:37:18 +00:00
// https://github.com/wled/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1)
2024-09-03 20:28:21 +00:00
const bool dithering = _needsRefresh ; // avoid working with bitfield
2025-02-07 14:02:27 +00:00
const unsigned maxBri = ( 1 < < _depth ) ; // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
2024-09-28 14:02:05 +00:00
const unsigned bitShift = dithering * 4 ; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
# endif
2024-12-23 13:57:22 +00:00
// use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
2024-09-01 09:43:22 +00:00
// see: https://en.wikipedia.org/wiki/Lightness
2024-12-23 13:57:22 +00:00
unsigned pwmBri = _bri ;
if ( pwmBri < 21 ) { // linear response for values [0-20]
pwmBri = ( pwmBri * maxBri + 2300 / 2 ) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve
} else { // cubic response for values [21-255]
float temp = float ( pwmBri + 41 ) / float ( 255 + 41 ) ; // 41 is to match offset & slope to linear part
temp = temp * temp * temp * ( float ) maxBri ;
pwmBri = ( unsigned ) temp ; // pwmBri is in range [0-maxBri] C
2024-08-24 09:35:32 +00:00
}
2024-08-27 19:57:00 +00:00
2024-09-03 20:28:21 +00:00
[[maybe_unused]] unsigned hPoint = 0 ; // phase shift (0 - maxBri)
2024-09-02 15:53:15 +00:00
// we will be phase shifting every channel by previous pulse length (plus dead time if required)
2025-02-07 14:02:27 +00:00
// phase shifting is only mandatory when using H-bridge to drive reverse-polarity PWM CCT (2 wire) LED type
2024-09-03 20:28:21 +00:00
// CCT additive blending must be 0 (WW & CW will not overlap) otherwise signals *will* overlap
2024-08-30 13:21:16 +00:00
// for all other cases it will just try to "spread" the load on PSU
2024-09-03 20:28:21 +00:00
// Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is
// also mandatory that both channels use the same timer (pinManager takes care of that).
2024-08-24 09:35:32 +00:00
for ( unsigned i = 0 ; i < numPins ; i + + ) {
2024-09-29 14:13:19 +00:00
unsigned duty = ( _data [ i ] * pwmBri ) / 255 ;
unsigned deadTime = 0 ;
2024-09-28 14:02:05 +00:00
2025-06-20 08:55:53 +00:00
if ( _type = = TYPE_ANALOG_2CH & & Bus : : _cctBlend = = 0 ) {
2024-09-29 14:13:19 +00:00
// add dead time between signals (when using dithering, two full 8bit pulses are required)
deadTime = ( 1 + dithering ) < < bitShift ;
2024-09-04 16:03:52 +00:00
// we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap
2024-09-28 14:02:05 +00:00
if ( _bri > = 254 & & duty > = maxBri / 2 & & duty < maxBri ) {
duty - = deadTime < < 1 ; // shorten duty of larger signal except if full on
}
2024-09-03 20:28:21 +00:00
}
2024-09-29 14:00:27 +00:00
if ( _reversed ) {
if ( i ) hPoint + = duty ; // align start at time zero
duty = maxBri - duty ;
}
2024-09-28 14:02:05 +00:00
# ifdef ESP8266
2024-09-29 03:12:03 +00:00
//stopWaveform(_pins[i]); // can cause the waveform to miss a cycle. instead we risk crossovers.
2024-09-28 14:02:05 +00:00
startWaveformClockCycles ( _pins [ i ] , duty , analogPeriod - duty , 0 , i ? _pins [ 0 ] : - 1 , hPoint , false ) ;
# else
2024-08-27 19:57:00 +00:00
unsigned channel = _ledcStart + i ;
2024-08-30 13:21:16 +00:00
unsigned gr = channel / 8 ; // high/low speed group
unsigned ch = channel % 8 ; // group channel
2024-09-01 09:43:22 +00:00
// directly write to LEDC struct as there is no HAL exposed function for dithering
// duty has 20 bit resolution with 4 fractional bits (24 bits in total)
2024-09-03 20:28:21 +00:00
LEDC . channel_group [ gr ] . channel [ ch ] . duty . duty = duty < < ( ( ! dithering ) * 4 ) ; // lowest 4 bits are used for dithering, shift by 4 bits if not using dithering
LEDC . channel_group [ gr ] . channel [ ch ] . hpoint . hpoint = hPoint > > bitShift ; // hPoint is at _depth resolution (needs shifting if dithering)
2024-09-01 09:43:22 +00:00
ledc_update_duty ( ( ledc_mode_t ) gr , ( ledc_channel_t ) ch ) ;
2024-08-24 09:35:32 +00:00
# endif
2025-01-24 00:36:27 +00:00
2024-09-29 14:00:27 +00:00
if ( ! _reversed ) hPoint + = duty ;
hPoint + = deadTime ; // offset to cascade the signals
2024-09-28 14:02:05 +00:00
if ( hPoint > = maxBri ) hPoint - = maxBri ; // offset is out of bounds, reset
2024-08-24 09:35:32 +00:00
}
2023-02-05 22:48:43 +00:00
}
2025-04-22 20:37:18 +00:00
size_t BusPwm : : getPins ( uint8_t * pinArray ) const {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return 0 ;
2024-08-24 09:35:32 +00:00
unsigned numPins = numPWMPins ( _type ) ;
2024-08-22 15:15:12 +00:00
if ( pinArray ) for ( unsigned i = 0 ; i < numPins ; i + + ) pinArray [ i ] = _pins [ i ] ;
2023-02-05 22:48:43 +00:00
return numPins ;
}
2025-04-22 20:37:18 +00:00
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
2024-09-05 19:49:27 +00:00
std : : vector < LEDType > BusPwm : : getLEDTypes ( ) {
return {
{ TYPE_ANALOG_1CH , " A " , PSTR ( " PWM White " ) } ,
{ TYPE_ANALOG_2CH , " AA " , PSTR ( " PWM CCT " ) } ,
{ TYPE_ANALOG_3CH , " AAA " , PSTR ( " PWM RGB " ) } ,
{ TYPE_ANALOG_4CH , " AAAA " , PSTR ( " PWM RGBW " ) } ,
{ TYPE_ANALOG_5CH , " AAAAA " , PSTR ( " PWM RGB+CCT " ) } ,
//{TYPE_ANALOG_6CH, "AAAAAA", PSTR("PWM RGB+DCCT")}, // unimplementable ATM
} ;
}
2024-09-12 06:48:31 +00:00
void BusPwm : : deallocatePins ( ) {
2025-02-01 11:04:24 +00:00
size_t numPins = getPins ( ) ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numPins ; i + + ) {
2024-09-19 19:44:11 +00:00
PinManager : : deallocatePin ( _pins [ i ] , PinOwner : : BusPwm ) ;
if ( ! PinManager : : isPinOk ( _pins [ i ] ) ) continue ;
2023-02-05 22:48:43 +00:00
# ifdef ESP8266
digitalWrite ( _pins [ i ] , LOW ) ; //turn off PWM interrupt
# else
2025-08-16 11:05:41 +00:00
if ( _ledcStart < WLED_MAX_ANALOG_CHANNELS ) ledcDetach ( _pins [ i ] ) ;
2023-02-05 22:48:43 +00:00
# endif
}
# ifdef ARDUINO_ARCH_ESP32
2024-09-19 19:44:11 +00:00
PinManager : : deallocateLedc ( _ledcStart , numPins ) ;
2023-02-05 22:48:43 +00:00
# endif
}
2025-01-07 19:33:10 +00:00
BusOnOff : : BusOnOff ( const BusConfig & bc )
2023-07-06 19:16:29 +00:00
: Bus ( bc . type , bc . start , bc . autoWhite , 1 , bc . reversed )
2025-02-07 14:02:27 +00:00
, _data ( 0 )
2023-07-06 19:16:29 +00:00
{
2024-09-05 19:49:27 +00:00
if ( ! Bus : : isOnOff ( bc . type ) ) return ;
2023-02-05 22:48:43 +00:00
uint8_t currentPin = bc . pins [ 0 ] ;
2024-09-19 19:44:11 +00:00
if ( ! PinManager : : allocatePin ( currentPin , true , PinOwner : : BusOnOff ) ) {
2023-02-05 22:48:43 +00:00
return ;
}
_pin = currentPin ; //store only after allocatePin() succeeds
pinMode ( _pin , OUTPUT ) ;
2024-08-24 09:35:32 +00:00
_hasRgb = false ;
_hasWhite = false ;
_hasCCT = false ;
2023-02-05 22:48:43 +00:00
_valid = true ;
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTF_P ( PSTR ( " %successfully inited On/Off strip with pin %u \n " ) , _valid ? " S " : " Uns " , _pin ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-28 13:26:14 +00:00
void BusOnOff : : setPixelColor ( unsigned pix , uint32_t c ) {
2023-02-05 22:48:43 +00:00
if ( pix ! = 0 | | ! _valid ) return ; //only react to first pixel
c = autoWhiteCalc ( c ) ;
uint8_t r = R ( c ) ;
uint8_t g = G ( c ) ;
uint8_t b = B ( c ) ;
uint8_t w = W ( c ) ;
2025-02-07 14:02:27 +00:00
_data = bool ( r | g | b | w ) & & bool ( _bri ) ? 0xFF : 0 ;
2023-02-05 22:48:43 +00:00
}
2024-09-28 13:26:14 +00:00
uint32_t BusOnOff : : getPixelColor ( unsigned pix ) const {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return 0 ;
2025-02-07 14:02:27 +00:00
return RGBW32 ( _data , _data , _data , _data ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-12 06:48:31 +00:00
void BusOnOff : : show ( ) {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return ;
2025-02-07 14:02:27 +00:00
digitalWrite ( _pin , _reversed ? ! ( bool ) _data : ( bool ) _data ) ;
2023-02-05 22:48:43 +00:00
}
2025-04-22 20:37:18 +00:00
size_t BusOnOff : : getPins ( uint8_t * pinArray ) const {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return 0 ;
2024-08-22 15:15:12 +00:00
if ( pinArray ) pinArray [ 0 ] = _pin ;
2023-02-05 22:48:43 +00:00
return 1 ;
}
2025-04-22 20:37:18 +00:00
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
2024-09-05 19:49:27 +00:00
std : : vector < LEDType > BusOnOff : : getLEDTypes ( ) {
return {
{ TYPE_ONOFF , " " , PSTR ( " On/Off " ) } ,
} ;
}
2023-02-05 22:48:43 +00:00
2025-01-07 19:33:10 +00:00
BusNetwork : : BusNetwork ( const BusConfig & bc )
2023-07-06 19:16:29 +00:00
: Bus ( bc . type , bc . start , bc . autoWhite , bc . count )
, _broadcastLock ( false )
{
2023-03-11 21:33:06 +00:00
switch ( bc . type ) {
case TYPE_NET_ARTNET_RGB :
_UDPtype = 2 ;
break ;
2024-02-29 08:27:11 +00:00
case TYPE_NET_ARTNET_RGBW :
_UDPtype = 2 ;
break ;
2023-03-11 21:33:06 +00:00
case TYPE_NET_E131_RGB :
_UDPtype = 1 ;
break ;
default : // TYPE_NET_DDP_RGB / TYPE_NET_DDP_RGBW
2023-02-05 22:48:43 +00:00
_UDPtype = 0 ;
2023-03-11 21:33:06 +00:00
break ;
}
2024-08-24 09:35:32 +00:00
_hasRgb = hasRGB ( bc . type ) ;
_hasWhite = hasWhite ( bc . type ) ;
_hasCCT = false ;
_UDPchannels = _hasWhite + 3 ;
2023-02-05 22:48:43 +00:00
_client = IPAddress ( bc . pins [ 0 ] , bc . pins [ 1 ] , bc . pins [ 2 ] , bc . pins [ 3 ] ) ;
2025-06-29 20:42:33 +00:00
# ifdef ARDUINO_ARCH_ESP32
_hostname = bc . text ;
resolveHostname ( ) ; // resolve hostname to IP address if needed
# endif
2025-04-22 20:37:18 +00:00
_data = ( uint8_t * ) d_calloc ( _len , _UDPchannels ) ;
2025-02-07 14:02:27 +00:00
_valid = ( _data ! = nullptr ) ;
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTF_P ( PSTR ( " %successfully inited virtual strip with type %u and IP %u.%u.%u.%u \n " ) , _valid ? " S " : " Uns " , bc . type , bc . pins [ 0 ] , bc . pins [ 1 ] , bc . pins [ 2 ] , bc . pins [ 3 ] ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-28 13:26:14 +00:00
void BusNetwork : : setPixelColor ( unsigned pix , uint32_t c ) {
2023-02-05 22:48:43 +00:00
if ( ! _valid | | pix > = _len ) return ;
2024-08-24 09:35:32 +00:00
if ( _hasWhite ) c = autoWhiteCalc ( c ) ;
2024-03-28 15:03:06 +00:00
if ( Bus : : _cct > = 1900 ) c = colorBalanceFromKelvin ( Bus : : _cct , c ) ; //color correction from CCT
2024-07-09 19:50:27 +00:00
unsigned offset = pix * _UDPchannels ;
2023-02-05 22:48:43 +00:00
_data [ offset ] = R ( c ) ;
_data [ offset + 1 ] = G ( c ) ;
_data [ offset + 2 ] = B ( c ) ;
2024-08-24 09:35:32 +00:00
if ( _hasWhite ) _data [ offset + 3 ] = W ( c ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-28 13:26:14 +00:00
uint32_t BusNetwork : : getPixelColor ( unsigned pix ) const {
2023-02-05 22:48:43 +00:00
if ( ! _valid | | pix > = _len ) return 0 ;
2024-07-09 19:50:27 +00:00
unsigned offset = pix * _UDPchannels ;
2024-08-24 09:35:32 +00:00
return RGBW32 ( _data [ offset ] , _data [ offset + 1 ] , _data [ offset + 2 ] , ( hasWhite ( ) ? _data [ offset + 3 ] : 0 ) ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-12 06:48:31 +00:00
void BusNetwork : : show ( ) {
2023-02-05 22:48:43 +00:00
if ( ! _valid | | ! canShow ( ) ) return ;
_broadcastLock = true ;
2024-08-24 09:35:32 +00:00
realtimeBroadcast ( _UDPtype , _client , _len , _data , _bri , hasWhite ( ) ) ;
2023-02-05 22:48:43 +00:00
_broadcastLock = false ;
}
2025-04-22 20:37:18 +00:00
size_t BusNetwork : : getPins ( uint8_t * pinArray ) const {
2024-08-22 15:15:12 +00:00
if ( pinArray ) for ( unsigned i = 0 ; i < 4 ; i + + ) pinArray [ i ] = _client [ i ] ;
2023-02-05 22:48:43 +00:00
return 4 ;
}
2025-06-29 20:42:33 +00:00
# ifdef ARDUINO_ARCH_ESP32
void BusNetwork : : resolveHostname ( ) {
static unsigned long nextResolve = 0 ;
2025-08-14 17:25:39 +00:00
if ( WLEDNetwork . isConnected ( ) & & millis ( ) > nextResolve & & _hostname . length ( ) > 0 ) {
2025-06-29 20:42:33 +00:00
nextResolve = millis ( ) + 600000 ; // resolve only every 10 minutes
IPAddress clnt ;
if ( strlen ( cmDNS ) > 0 ) clnt = MDNS . queryHost ( _hostname ) ;
else WiFi . hostByName ( _hostname . c_str ( ) , clnt ) ;
if ( clnt ! = IPAddress ( ) ) _client = clnt ;
}
}
# endif
2025-04-22 20:37:18 +00:00
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
2024-09-05 19:49:27 +00:00
std : : vector < LEDType > BusNetwork : : getLEDTypes ( ) {
return {
{ TYPE_NET_DDP_RGB , " N " , PSTR ( " DDP RGB (network) " ) } , // should be "NNNN" to determine 4 "pin" fields
{ TYPE_NET_ARTNET_RGB , " N " , PSTR ( " Art-Net RGB (network) " ) } ,
{ TYPE_NET_DDP_RGBW , " N " , PSTR ( " DDP RGBW (network) " ) } ,
{ TYPE_NET_ARTNET_RGBW , " N " , PSTR ( " Art-Net RGBW (network) " ) } ,
// hypothetical extensions
//{TYPE_VIRTUAL_I2C_W, "V", PSTR("I2C White (virtual)")}, // allows setting I2C address in _pin[0]
//{TYPE_VIRTUAL_I2C_CCT, "V", PSTR("I2C CCT (virtual)")}, // allows setting I2C address in _pin[0]
//{TYPE_VIRTUAL_I2C_RGB, "VVV", PSTR("I2C RGB (virtual)")}, // allows setting I2C address in _pin[0] and 2 additional values in _pin[1] & _pin[2]
2025-04-22 20:37:18 +00:00
//{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/wled/WLED/pull/4123)
2024-09-05 19:49:27 +00:00
} ;
}
2024-09-12 06:48:31 +00:00
void BusNetwork : : cleanup ( ) {
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTLN ( F ( " Virtual Cleanup. " ) ) ;
2025-04-22 20:37:18 +00:00
d_free ( _data ) ;
2025-02-07 14:02:27 +00:00
_data = nullptr ;
2023-02-05 22:48:43 +00:00
_type = I_NONE ;
_valid = false ;
}
//utility to get the approx. memory usage of a given BusConfig
2025-04-22 20:37:18 +00:00
size_t BusConfig : : memUsage ( unsigned nr ) const {
2025-01-30 19:46:26 +00:00
if ( Bus : : isVirtual ( type ) ) {
return sizeof ( BusNetwork ) + ( count * Bus : : getNumberOfChannels ( type ) ) ;
} else if ( Bus : : isDigital ( type ) ) {
2025-04-22 20:37:18 +00:00
return sizeof ( BusDigital ) + PolyBus : : memUsage ( count + skipAmount , PolyBus : : getI ( type , pins , nr ) ) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/ ;
2025-01-30 19:46:26 +00:00
} else if ( Bus : : isOnOff ( type ) ) {
return sizeof ( BusOnOff ) ;
} else {
return sizeof ( BusPwm ) ;
2023-02-05 22:48:43 +00:00
}
}
2025-01-30 19:46:26 +00:00
2025-04-22 20:37:18 +00:00
size_t BusManager : : memUsage ( ) {
2025-01-30 19:46:26 +00:00
// when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers
// front buffers are always allocated per bus
unsigned size = 0 ;
unsigned maxI2S = 0 ;
# if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
unsigned digitalCount = 0 ;
# if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
# define MAX_RMT 4
# else
# define MAX_RMT 8
# endif
# endif
for ( const auto & bus : busses ) {
unsigned busSize = bus - > getBusSize ( ) ;
# if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
if ( bus - > isDigital ( ) & & ! bus - > is2Pin ( ) ) digitalCount + + ;
if ( PolyBus : : isParallelI2S1Output ( ) & & digitalCount > MAX_RMT ) {
unsigned i2sCommonSize = 3 * bus - > getLength ( ) * bus - > getNumberOfChannels ( ) * ( bus - > is16bit ( ) + 1 ) ;
if ( i2sCommonSize > maxI2S ) maxI2S = i2sCommonSize ;
busSize - = i2sCommonSize ;
}
# endif
size + = busSize ;
}
return size + maxI2S ;
2024-07-07 12:18:51 +00:00
}
2025-01-07 19:33:10 +00:00
int BusManager : : add ( const BusConfig & bc ) {
2025-04-22 20:37:18 +00:00
DEBUGBUS_PRINTF_P ( PSTR ( " Bus: Adding bus (p:%d v:%d) \n " ) , getNumBusses ( ) , getNumVirtualBusses ( ) ) ;
unsigned digital = 0 ;
unsigned analog = 0 ;
unsigned twoPin = 0 ;
for ( const auto & bus : busses ) {
if ( bus - > isPWM ( ) ) analog + = bus - > getPins ( ) ; // number of analog channels used
if ( bus - > isDigital ( ) & & ! bus - > is2Pin ( ) ) digital + + ;
if ( bus - > is2Pin ( ) ) twoPin + + ;
}
if ( digital > WLED_MAX_DIGITAL_CHANNELS | | analog > WLED_MAX_ANALOG_CHANNELS ) return - 1 ;
2024-08-24 09:35:32 +00:00
if ( Bus : : isVirtual ( bc . type ) ) {
2025-01-21 16:50:36 +00:00
busses . push_back ( make_unique < BusNetwork > ( bc ) ) ;
2024-08-24 09:35:32 +00:00
} else if ( Bus : : isDigital ( bc . type ) ) {
2025-04-22 20:37:18 +00:00
busses . push_back ( make_unique < BusDigital > ( bc , Bus : : is2Pin ( bc . type ) ? twoPin : digital ) ) ;
2024-08-24 09:35:32 +00:00
} else if ( Bus : : isOnOff ( bc . type ) ) {
2025-01-21 16:50:36 +00:00
busses . push_back ( make_unique < BusOnOff > ( bc ) ) ;
2023-02-05 22:48:43 +00:00
} else {
2025-01-21 16:50:36 +00:00
busses . push_back ( make_unique < BusPwm > ( bc ) ) ;
2023-02-05 22:48:43 +00:00
}
2025-01-30 19:46:26 +00:00
return busses . size ( ) ;
2023-02-05 22:48:43 +00:00
}
2024-09-05 19:49:27 +00:00
// credit @willmmiles
static String LEDTypesToJson ( const std : : vector < LEDType > & types ) {
String json ;
2024-08-22 15:15:12 +00:00
for ( const auto & type : types ) {
2024-09-05 19:49:27 +00:00
// capabilities follows similar pattern as JSON API
2024-09-22 11:56:14 +00:00
int capabilities = Bus : : hasRGB ( type . id ) | Bus : : hasWhite ( type . id ) < < 1 | Bus : : hasCCT ( type . id ) < < 2 | Bus : : is16bit ( type . id ) < < 4 | Bus : : mustRefresh ( type . id ) < < 5 ;
2024-09-05 19:49:27 +00:00
char str [ 256 ] ;
sprintf_P ( str , PSTR ( " {i:%d,c:%d,t: \" %s \" ,n: \" %s \" }, " ) , type . id , capabilities , type . type , type . name ) ;
json + = str ;
2024-08-22 15:15:12 +00:00
}
return json ;
}
2025-04-22 20:37:18 +00:00
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
2024-09-12 06:48:31 +00:00
String BusManager : : getLEDTypesJSONString ( ) {
2024-09-05 19:49:27 +00:00
String json = " [ " ;
json + = LEDTypesToJson ( BusDigital : : getLEDTypes ( ) ) ;
json + = LEDTypesToJson ( BusOnOff : : getLEDTypes ( ) ) ;
json + = LEDTypesToJson ( BusPwm : : getLEDTypes ( ) ) ;
json + = LEDTypesToJson ( BusNetwork : : getLEDTypes ( ) ) ;
//json += LEDTypesToJson(BusVirtual::getLEDTypes());
json . setCharAt ( json . length ( ) - 1 , ' ] ' ) ; // replace last comma with bracket
return json ;
}
2024-08-22 15:15:12 +00:00
2024-09-12 06:48:31 +00:00
void BusManager : : useParallelOutput ( ) {
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTLN ( F ( " Bus: Enabling parallel I2S. " ) ) ;
2024-06-12 16:00:00 +00:00
PolyBus : : setParallelI2S1Output ( ) ;
}
2025-01-30 19:46:26 +00:00
bool BusManager : : hasParallelOutput ( ) {
return PolyBus : : isParallelI2S1Output ( ) ;
}
2023-02-05 22:48:43 +00:00
//do not call this method from system context (network callback)
2024-09-12 06:48:31 +00:00
void BusManager : : removeAll ( ) {
2025-01-19 10:37:57 +00:00
DEBUGBUS_PRINTLN ( F ( " Removing all. " ) ) ;
2023-02-05 22:48:43 +00:00
//prevents crashes due to deleting busses while in use.
while ( ! canAllShow ( ) ) yield ( ) ;
2025-01-21 16:50:36 +00:00
busses . clear ( ) ;
2024-06-12 16:00:00 +00:00
PolyBus : : setParallelI2S1Output ( false ) ;
2023-02-05 22:48:43 +00:00
}
2024-06-23 12:09:18 +00:00
# ifdef ESP32_DATA_IDLE_HIGH
// #2478
// If enabled, RMT idle level is set to HIGH when off
// to prevent leakage current when using an N-channel MOSFET to toggle LED power
2024-09-12 06:48:31 +00:00
void BusManager : : esp32RMTInvertIdle ( ) {
2024-06-23 12:09:18 +00:00
bool idle_out ;
unsigned rmt = 0 ;
2025-01-30 19:46:26 +00:00
unsigned u = 0 ;
for ( auto & bus : busses ) {
if ( bus - > getLength ( ) = = 0 | | ! bus - > isDigital ( ) | | bus - > is2Pin ( ) ) continue ;
2024-06-23 12:09:18 +00:00
# if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM
if ( u > 1 ) return ;
rmt = u ;
# elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, only has 1 I2S bus, supported in NPB
if ( u > 3 ) return ;
rmt = u ;
# elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, has 2 I2S but NPB does not support them ATM
if ( u > 3 ) return ;
rmt = u ;
# else
2025-01-30 19:46:26 +00:00
unsigned numI2S = ! PolyBus : : isParallelI2S1Output ( ) ; // if using parallel I2S, RMT is used 1st
if ( numI2S > u ) continue ;
if ( u > 7 + numI2S ) return ;
rmt = u - numI2S ;
2024-06-23 12:09:18 +00:00
# endif
//assumes that bus number to rmt channel mapping stays 1:1
rmt_channel_t ch = static_cast < rmt_channel_t > ( rmt ) ;
rmt_idle_level_t lvl ;
rmt_get_idle_level ( ch , & idle_out , & lvl ) ;
if ( lvl = = RMT_IDLE_LEVEL_HIGH ) lvl = RMT_IDLE_LEVEL_LOW ;
else if ( lvl = = RMT_IDLE_LEVEL_LOW ) lvl = RMT_IDLE_LEVEL_HIGH ;
else continue ;
rmt_set_idle_level ( ch , idle_out , lvl ) ;
2025-01-30 19:46:26 +00:00
u + +
2024-06-23 12:09:18 +00:00
}
}
# endif
2024-09-12 06:48:31 +00:00
void BusManager : : on ( ) {
2024-06-23 12:09:18 +00:00
# ifdef ESP8266
//Fix for turning off onboard LED breaking bus
2024-09-19 19:44:11 +00:00
if ( PinManager : : getPinOwner ( LED_BUILTIN ) = = PinOwner : : BusDigital ) {
2025-01-30 19:46:26 +00:00
for ( auto & bus : busses ) {
2024-06-23 12:09:18 +00:00
uint8_t pins [ 2 ] = { 255 , 255 } ;
2025-01-30 19:46:26 +00:00
if ( bus - > isDigital ( ) & & bus - > getPins ( pins ) ) {
2024-06-23 12:09:18 +00:00
if ( pins [ 0 ] = = LED_BUILTIN | | pins [ 1 ] = = LED_BUILTIN ) {
2025-02-25 15:06:13 +00:00
BusDigital & b = static_cast < BusDigital & > ( * bus ) ;
b . begin ( ) ;
2024-06-23 12:09:18 +00:00
break ;
}
}
}
}
2025-06-29 20:42:33 +00:00
# else
for ( auto & bus : busses ) if ( bus - > isVirtual ( ) ) {
// virtual/network bus should check for IP change if hostname is specified
// otherwise there are no endpoints to force DNS resolution
BusNetwork & b = static_cast < BusNetwork & > ( * bus ) ;
b . resolveHostname ( ) ;
}
2024-06-23 12:09:18 +00:00
# endif
# ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle ( ) ;
# endif
}
2024-09-12 06:48:31 +00:00
void BusManager : : off ( ) {
2024-06-23 12:09:18 +00:00
# ifdef ESP8266
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be re-initialised on On
2024-09-19 19:44:11 +00:00
if ( PinManager : : getPinOwner ( LED_BUILTIN ) = = PinOwner : : BusDigital ) {
2025-01-30 19:46:26 +00:00
for ( const auto & bus : busses ) if ( bus - > isOffRefreshRequired ( ) ) return ;
2024-06-23 12:09:18 +00:00
pinMode ( LED_BUILTIN , OUTPUT ) ;
digitalWrite ( LED_BUILTIN , HIGH ) ;
}
# endif
# ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle ( ) ;
# endif
}
2024-09-12 06:48:31 +00:00
void BusManager : : show ( ) {
2025-01-21 16:50:36 +00:00
_gMilliAmpsUsed = 0 ;
for ( auto & bus : busses ) {
bus - > show ( ) ;
_gMilliAmpsUsed + = bus - > getUsedCurrent ( ) ;
2023-02-05 22:48:43 +00:00
}
}
2024-09-28 13:26:14 +00:00
void IRAM_ATTR BusManager : : setPixelColor ( unsigned pix , uint32_t c ) {
2025-01-30 19:46:26 +00:00
for ( auto & bus : busses ) {
2025-04-22 20:37:18 +00:00
if ( ! bus - > containsPixel ( pix ) ) continue ;
bus - > setPixelColor ( pix - bus - > getStart ( ) , c ) ;
2023-02-05 22:48:43 +00:00
}
}
void BusManager : : setSegmentCCT ( int16_t cct , bool allowWBCorrection ) {
if ( cct > 255 ) cct = 255 ;
if ( cct > = 0 ) {
//if white balance correction allowed, save as kelvin value instead of 0-255
if ( allowWBCorrection ) cct = 1900 + ( cct < < 5 ) ;
2024-03-16 11:36:05 +00:00
} else cct = - 1 ; // will use kelvin approximation from RGB
2023-02-05 22:48:43 +00:00
Bus : : setCCT ( cct ) ;
}
2024-09-28 13:26:14 +00:00
uint32_t BusManager : : getPixelColor ( unsigned pix ) {
2025-01-30 19:46:26 +00:00
for ( auto & bus : busses ) {
if ( ! bus - > containsPixel ( pix ) ) continue ;
2025-04-22 20:37:18 +00:00
return bus - > getPixelColor ( pix - bus - > getStart ( ) ) ;
2023-02-05 22:48:43 +00:00
}
return 0 ;
}
2024-09-12 06:48:31 +00:00
bool BusManager : : canAllShow ( ) {
2025-01-30 19:46:26 +00:00
for ( const auto & bus : busses ) if ( ! bus - > canShow ( ) ) return false ;
2023-02-05 22:48:43 +00:00
return true ;
}
2025-01-21 16:50:36 +00:00
ColorOrderMap & BusManager : : getColorOrderMap ( ) { return _colorOrderMap ; }
2023-02-05 22:48:43 +00:00
2025-01-12 14:17:22 +00:00
bool PolyBus : : _useParallelI2S = false ;
2024-06-12 16:00:00 +00:00
2023-02-05 22:48:43 +00:00
// Bus static member definition
int16_t Bus : : _cct = - 1 ;
2025-06-20 08:55:53 +00:00
uint8_t Bus : : _cctBlend = 0 ; // 0 - 127
2023-03-11 21:33:06 +00:00
uint8_t Bus : : _gAWM = 255 ;
2023-12-29 22:07:29 +00:00
uint16_t BusDigital : : _milliAmpsTotal = 0 ;
2025-01-21 16:50:36 +00:00
std : : vector < std : : unique_ptr < Bus > > BusManager : : busses ;
uint16_t BusManager : : _gMilliAmpsUsed = 0 ;
uint16_t BusManager : : _gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT ;