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
# 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)
extern xSemaphoreHandle _ledc_sys_lock ;
# else
# define LEDC_MUTEX_LOCK()
# define LEDC_MUTEX_UNLOCK()
# endif
2024-08-24 09:35:32 +00:00
# endif
2023-02-05 22:48:43 +00:00
# include "const.h"
# include "pin_manager.h"
# include "bus_wrapper.h"
# include "bus_manager.h"
2024-03-28 15:03:06 +00:00
extern bool cctICused ;
2023-02-05 22:48:43 +00:00
//colors.cpp
uint32_t colorBalanceFromKelvin ( uint16_t kelvin , uint32_t rgb ) ;
//udp.cpp
uint8_t realtimeBroadcast ( uint8_t type , IPAddress client , uint16_t length , byte * buffer , uint8_t bri = 255 , bool isRGBW = false ) ;
// enable additional debug output
# if defined(WLED_DEBUG_HOST)
# include "net_debug.h"
# define DEBUGOUT NetDebug
# else
# define DEBUGOUT Serial
# endif
# ifdef WLED_DEBUG
# ifndef ESP8266
# include <rom/rtc.h>
# endif
# define DEBUG_PRINT(x) DEBUGOUT.print(x)
# define DEBUG_PRINTLN(x) DEBUGOUT.println(x)
# define DEBUG_PRINTF(x...) DEBUGOUT.printf(x)
2024-02-17 10:33:42 +00:00
# define DEBUG_PRINTF_P(x...) DEBUGOUT.printf_P(x)
2023-02-05 22:48:43 +00:00
# else
# define DEBUG_PRINT(x)
# define DEBUG_PRINTLN(x)
# define DEBUG_PRINTF(x...)
2024-02-17 10:33:42 +00:00
# define DEBUG_PRINTF_P(x...)
2023-02-05 22:48:43 +00:00
# endif
//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))
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 } ) ;
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
for ( unsigned i = 0 ; i < count ( ) ; i + + ) {
if ( pix > = _mappings [ i ] . start & & pix < ( _mappings [ i ] . start + _mappings [ i ] . len ) ) {
return _mappings [ i ] . colorOrder | ( ( _mappings [ i ] . colorOrder > > 4 ) ? 0 : ( defaultColorOrder & 0xF0 ) ) ;
2023-02-05 22:48:43 +00:00
}
}
return defaultColorOrder ;
}
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 ) ;
}
2024-08-22 15:15:12 +00:00
uint8_t * Bus : : allocateData ( size_t size ) {
2023-06-30 19:12:59 +00:00
if ( _data ) free ( _data ) ; // should not happen, but for safety
return _data = ( uint8_t * ) ( size > 0 ? calloc ( size , sizeof ( uint8_t ) ) : nullptr ) ;
}
2023-02-05 22:48:43 +00:00
2023-07-06 19:16:29 +00:00
BusDigital : : BusDigital ( BusConfig & bc , uint8_t nr , const ColorOrderMap & com )
: 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
, _colorOrderMap ( com )
{
2024-08-24 09:35:32 +00:00
if ( ! isDigital ( bc . type ) | | ! bc . count ) return ;
2023-02-05 22:48:43 +00:00
if ( ! pinManager . allocatePin ( bc . pins [ 0 ] , true , PinOwner : : BusDigital ) ) 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 ) ) {
2023-02-05 22:48:43 +00:00
if ( ! pinManager . allocatePin ( bc . pins [ 1 ] , true , PinOwner : : BusDigital ) ) {
2023-07-06 19:16:29 +00:00
cleanup ( ) ;
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 ) ;
if ( _iType = = I_NONE ) return ;
2024-08-24 09:35:32 +00:00
_hasRgb = hasRGB ( bc . type ) ;
_hasWhite = hasWhite ( bc . type ) ;
_hasCCT = hasCCT ( bc . type ) ;
2024-08-22 15:15:12 +00:00
if ( bc . doubleBuffer & & ! allocateData ( bc . count * Bus : : getNumberOfChannels ( bc . type ) ) ) return ;
2023-11-12 11:45:28 +00:00
//_buffering = bc.doubleBuffer;
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
2023-06-30 19:12:59 +00:00
_busPtr = PolyBus : : create ( _iType , _pins , lenToCreate + _skip , nr , _frequencykHz ) ;
2023-02-05 22:48:43 +00:00
_valid = ( _busPtr ! = nullptr ) ;
2024-08-24 09:35:32 +00:00
DEBUG_PRINTF_P ( PSTR ( " %successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d \n " ) , _valid ? " S " : " Uns " , nr , bc . count , bc . type , _pins [ 0 ] , is2Pin ( bc . type ) ? _pins [ 1 ] : 255 , _iType , _milliAmpsPerLed , _milliAmpsMax ) ;
2023-02-05 22:48:43 +00:00
}
2023-11-15 18:37:07 +00:00
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
# ifndef MA_FOR_ESP
# ifdef ESP8266
# define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
# else
# define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
# endif
# endif
//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
2024-08-22 15:15:12 +00:00
uint8_t BusDigital : : estimateCurrentAndLimitBri ( void ) {
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
}
2023-12-29 22:07:29 +00:00
size_t powerBudget = ( _milliAmpsMax - MA_FOR_ESP / BusManager : : getNumBusses ( ) ) ; //80/120mA for ESP power
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
busPowerSum = ( busPowerSum * actualMilliampsPerLed ) / 765 ;
2023-12-29 22:07:29 +00:00
_milliAmpsTotal = busPowerSum * _bri / 255 ;
2023-11-15 18:37:07 +00:00
uint8_t newBri = _bri ;
if ( busPowerSum * _bri / 255 > powerBudget ) { //scale brightness down to stay in current limit
float scale = ( float ) ( powerBudget * 255 ) / ( float ) ( busPowerSum * _bri ) ;
2023-12-29 22:07:29 +00:00
if ( scale > = 1.0f ) return _bri ;
_milliAmpsTotal = ceilf ( ( float ) _milliAmpsTotal * scale ) ;
uint8_t scaleB = min ( ( int ) ( scale * 255 ) , 255 ) ;
2023-11-15 18:37:07 +00:00
newBri = unsigned ( _bri * scaleB ) / 256 + 1 ;
}
return newBri ;
}
2024-08-22 15:15:12 +00:00
void BusDigital : : show ( void ) {
2023-12-29 22:07:29 +00:00
_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 ;
2024-07-09 19:50:27 +00:00
unsigned newBri = estimateCurrentAndLimitBri ( ) ; // will fill _milliAmpsTotal
2023-11-15 18:37:07 +00:00
if ( newBri < _bri ) PolyBus : : setBrightness ( _busPtr , _iType , newBri ) ; // limit brightness to stay within current limits
2024-03-11 10:17:45 +00:00
if ( _data ) {
2024-03-16 11:36:05 +00:00
size_t channels = getNumberOfChannels ( ) ;
2024-03-28 15:03:06 +00:00
int16_t oldCCT = Bus : : _cct ; // temporarily save bus CCT
2023-06-30 19:12:59 +00:00
for ( size_t i = 0 ; i < _len ; i + + ) {
2024-03-16 11:36:05 +00:00
size_t offset = i * channels ;
2024-07-09 19:50:27 +00:00
unsigned co = _colorOrderMap . getPixelColorOrder ( i + _start , _colorOrder ) ;
2023-06-30 19:12:59 +00:00
uint32_t c ;
if ( _type = = TYPE_WS2812_1CH_X3 ) { // map to correct IC, each controls 3 LEDs (_len is always a multiple of 3)
switch ( i % 3 ) {
case 0 : c = RGBW32 ( _data [ offset ] , _data [ offset + 1 ] , _data [ offset + 2 ] , 0 ) ; break ;
case 1 : c = RGBW32 ( _data [ offset - 1 ] , _data [ offset ] , _data [ offset + 1 ] , 0 ) ; break ;
case 2 : c = RGBW32 ( _data [ offset - 2 ] , _data [ offset - 1 ] , _data [ offset ] , 0 ) ; break ;
}
} else {
2024-03-16 11:36:05 +00:00
if ( hasRGB ( ) ) c = RGBW32 ( _data [ offset ] , _data [ offset + 1 ] , _data [ offset + 2 ] , hasWhite ( ) ? _data [ offset + 3 ] : 0 ) ;
else c = RGBW32 ( 0 , 0 , 0 , _data [ offset ] ) ;
}
if ( hasCCT ( ) ) {
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to extract and appy CCT value for each pixel individually even though all buses share the same _cct variable
// TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer
2024-03-28 15:03:06 +00:00
Bus : : _cct = _data [ offset + channels - 1 ] ;
2024-03-16 11:36:05 +00:00
Bus : : calculateCCT ( c , cctWW , cctCW ) ;
2023-06-30 19:12:59 +00:00
}
2024-07-09 19:50:27 +00:00
unsigned pix = i ;
2023-08-25 16:34:11 +00:00
if ( _reversed ) pix = _len - pix - 1 ;
pix + = _skip ;
2024-03-11 10:17:45 +00:00
PolyBus : : setPixelColor ( _busPtr , _iType , pix , c , co , ( cctCW < < 8 ) | cctWW ) ;
2023-06-30 19:12:59 +00:00
}
2023-08-12 10:45:11 +00:00
# if !defined(STATUSLED) || STATUSLED>=0
if ( _skip ) PolyBus : : setPixelColor ( _busPtr , _iType , 0 , 0 , _colorOrderMap . getPixelColorOrder ( _start , _colorOrder ) ) ; // paint skipped pixels black
# endif
for ( int i = 1 ; i < _skip ; i + + ) PolyBus : : setPixelColor ( _busPtr , _iType , i , 0 , _colorOrderMap . getPixelColorOrder ( _start , _colorOrder ) ) ; // paint skipped pixels black
2024-03-28 15:03:06 +00:00
Bus : : _cct = oldCCT ;
2023-11-15 18:37:07 +00:00
} else {
if ( newBri < _bri ) {
2024-07-09 19:50:27 +00:00
unsigned hwLen = _len ;
2023-11-15 18:37:07 +00:00
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 ) ;
2024-03-16 11:36:05 +00:00
if ( hasCCT ( ) ) Bus : : calculateCCT ( c , cctWW , cctCW ) ; // this will unfortunately corrupt (segment) CCT data on every bus
2024-03-11 10:17:45 +00:00
PolyBus : : setPixelColor ( _busPtr , _iType , i , c , 0 , ( cctCW < < 8 ) | cctWW ) ; // repaint all pixels with new brightness
2023-11-15 18:37:07 +00:00
}
}
2023-06-30 19:12:59 +00:00
}
2023-11-12 11:45:28 +00:00
PolyBus : : show ( _busPtr , _iType , ! _data ) ; // faster if buffer consistency is not important (use !_buffering this causes 20% FPS drop)
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-08-22 15:15:12 +00:00
bool BusDigital : : canShow ( void ) 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
}
}
void IRAM_ATTR BusDigital : : setPixelColor ( uint16_t pix , uint32_t c ) {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return ;
2024-03-11 10:17:45 +00:00
uint8_t cctWW = 0 , cctCW = 0 ;
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
2024-03-11 10:17:45 +00:00
if ( _data ) {
2024-03-16 11:36:05 +00:00
size_t offset = pix * getNumberOfChannels ( ) ;
if ( hasRGB ( ) ) {
2023-06-30 19:12:59 +00:00
_data [ offset + + ] = R ( c ) ;
_data [ offset + + ] = G ( c ) ;
_data [ offset + + ] = B ( c ) ;
}
2024-03-16 11:36:05 +00:00
if ( hasWhite ( ) ) _data [ offset + + ] = W ( c ) ;
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio)
2024-03-28 15:03:06 +00:00
if ( hasCCT ( ) ) _data [ offset ] = Bus : : _cct > = 1900 ? ( Bus : : _cct - 1900 ) > > 5 : ( Bus : : _cct < 0 ? 127 : Bus : : _cct ) ; // TODO: if _cct == -1 we simply ignore it
2023-06-30 19:12:59 +00:00
} else {
2023-08-25 16:34:11 +00:00
if ( _reversed ) pix = _len - pix - 1 ;
pix + = _skip ;
2024-07-09 19:50:27 +00:00
unsigned co = _colorOrderMap . getPixelColorOrder ( pix + _start , _colorOrder ) ;
2023-06-30 19:12:59 +00:00
if ( _type = = TYPE_WS2812_1CH_X3 ) { // map to correct IC, each controls 3 LEDs
2024-07-09 19:50:27 +00:00
unsigned pOld = pix ;
2023-06-30 19:12:59 +00:00
pix = IC_INDEX_WS2812_1CH_3X ( pix ) ;
2023-07-19 11:50:09 +00:00
uint32_t cOld = restoreColorLossy ( PolyBus : : getPixelColor ( _busPtr , _iType , pix , co ) , _bri ) ;
2023-06-30 19:12:59 +00:00
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 ;
}
2023-02-14 00:33:06 +00:00
}
2024-03-16 11:36:05 +00:00
if ( hasCCT ( ) ) Bus : : calculateCCT ( c , cctWW , cctCW ) ;
2024-03-11 10:17:45 +00:00
PolyBus : : setPixelColor ( _busPtr , _iType , pix , c , co , ( cctCW < < 8 ) | cctWW ) ;
2023-02-14 00:33:06 +00:00
}
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-08-22 15:15:12 +00:00
uint32_t IRAM_ATTR BusDigital : : getPixelColor ( uint16_t pix ) const {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return 0 ;
2024-03-11 10:17:45 +00:00
if ( _data ) {
2024-03-16 11:36:05 +00:00
size_t offset = pix * getNumberOfChannels ( ) ;
2023-06-30 19:12:59 +00:00
uint32_t c ;
2024-03-16 11:36:05 +00:00
if ( ! hasRGB ( ) ) {
2023-06-30 19:12:59 +00:00
c = RGBW32 ( _data [ offset ] , _data [ offset ] , _data [ offset ] , _data [ offset ] ) ;
} else {
2024-03-16 11:36:05 +00:00
c = RGBW32 ( _data [ offset ] , _data [ offset + 1 ] , _data [ offset + 2 ] , hasWhite ( ) ? _data [ offset + 3 ] : 0 ) ;
2023-02-14 00:33:06 +00:00
}
return c ;
2023-06-30 19:12:59 +00:00
} else {
2023-08-25 16:34:11 +00:00
if ( _reversed ) pix = _len - pix - 1 ;
pix + = _skip ;
2024-07-09 19:50:27 +00:00
unsigned co = _colorOrderMap . getPixelColorOrder ( pix + _start , _colorOrder ) ;
2023-07-19 11:50:09 +00:00
uint32_t c = restoreColorLossy ( PolyBus : : getPixelColor ( _busPtr , _iType , ( _type = = TYPE_WS2812_1CH_X3 ) ? IC_INDEX_WS2812_1CH_3X ( pix ) : pix , co ) , _bri ) ;
2023-06-30 19:12:59 +00:00
if ( _type = = TYPE_WS2812_1CH_X3 ) { // map to correct IC, each controls 3 LEDs
2024-07-09 19:50:27 +00:00
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 ) ;
2023-07-14 13:58:03 +00:00
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-07-14 13:58:03 +00:00
return c ;
2023-02-14 00:33:06 +00:00
}
2023-02-05 22:48:43 +00:00
}
2024-08-22 15:15:12 +00:00
uint8_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 ;
}
void BusDigital : : setColorOrder ( uint8_t colorOrder ) {
// upper nibble contains W swap information
if ( ( colorOrder & 0x0F ) > 5 ) return ;
_colorOrder = colorOrder ;
}
2024-09-05 19:49:27 +00:00
// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056
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 " ) } ,
//{TYPE_WS2812_2CH_X3, "D", PSTR("WS2811 CCT")}, // not implemented
//{TYPE_WS2812_WWA, "D", PSTR("WS2811 WWA")}, // not implemented
{ 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-08-22 15:15:12 +00:00
void BusDigital : : reinit ( void ) {
2023-06-30 19:12:59 +00:00
if ( ! _valid ) return ;
2023-02-05 22:48:43 +00:00
PolyBus : : begin ( _busPtr , _iType , _pins ) ;
}
2024-08-22 15:15:12 +00:00
void BusDigital : : cleanup ( void ) {
2023-02-05 22:48:43 +00:00
DEBUG_PRINTLN ( F ( " Digital Cleanup. " ) ) ;
PolyBus : : cleanup ( _busPtr , _iType ) ;
_iType = I_NONE ;
_valid = false ;
_busPtr = nullptr ;
2023-07-05 21:57:46 +00:00
if ( _data ! = nullptr ) freeData ( ) ;
2023-02-05 22:48:43 +00:00
pinManager . deallocatePin ( _pins [ 1 ] , PinOwner : : BusDigital ) ;
pinManager . deallocatePin ( _pins [ 0 ] , PinOwner : : BusDigital ) ;
}
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
# define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
# 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
2023-07-06 19:16:29 +00:00
BusPwm : : BusPwm ( 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 ;
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 } ;
if ( ! pinManager . allocateMultiplePins ( pins , numPins , PinOwner : : BusPwm ) ) return ;
2024-06-30 08:44:25 +00:00
# ifdef ESP8266
2024-03-07 19:21:56 +00:00
analogWriteRange ( ( 1 < < _depth ) - 1 ) ;
2023-04-27 22:27:19 +00:00
analogWriteFreq ( _frequency ) ;
2024-06-30 08:44:25 +00:00
# else
2024-08-30 13:21:16 +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
2023-02-05 22:48:43 +00:00
_ledcStart = pinManager . allocateLedc ( numPins ) ;
if ( _ledcStart = = 255 ) { //no more free LEDC channels
2024-08-26 22:21:24 +00:00
pinManager . deallocateMultiplePins ( pins , numPins , PinOwner : : BusPwm ) ;
return ;
2023-02-05 22:48:43 +00:00
}
2024-09-03 20:28:21 +00:00
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
2024-09-05 19:49:27 +00:00
if ( dithering ) _depth = 12 ; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering)
2024-06-30 08:44:25 +00:00
# endif
2023-02-05 22:48:43 +00:00
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numPins ; i + + ) {
2024-08-26 22:21:24 +00:00
_pins [ i ] = bc . pins [ i ] ; // store only after allocateMultiplePins() succeeded
2023-02-05 22:48:43 +00:00
# ifdef ESP8266
pinMode ( _pins [ i ] , OUTPUT ) ;
# else
2024-08-27 19:57:00 +00:00
unsigned channel = _ledcStart + i ;
2024-09-05 19:49:27 +00:00
ledcSetup ( channel , _frequency , _depth - ( dithering * 4 ) ) ; // with dithering _frequency doesn't really matter as resolution is 8 bit
2024-08-27 19:57:00 +00:00
ledcAttachPin ( _pins [ i ] , channel ) ;
2024-08-27 12:11:56 +00:00
// LEDC timer reset credit @dedehai
2024-08-27 19:57:00 +00:00
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)
2023-02-05 22:48:43 +00:00
# endif
}
2024-08-24 09:35:32 +00:00
_hasRgb = hasRGB ( bc . type ) ;
_hasWhite = hasWhite ( bc . type ) ;
_hasCCT = hasCCT ( bc . type ) ;
2023-06-30 19:12:59 +00:00
_data = _pwmdata ; // avoid malloc() and use stack
2023-02-05 22:48:43 +00:00
_valid = true ;
2024-08-01 18:25:18 +00:00
DEBUG_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
}
void BusPwm : : setPixelColor ( uint16_t pix , uint32_t c ) {
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-08-22 15:15:12 +00:00
uint32_t BusPwm : : getPixelColor ( uint16_t 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 ;
2024-09-03 20:28:21 +00:00
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
// https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1)
const bool dithering = _needsRefresh ; // avoid working with bitfield
2024-08-27 12:11:56 +00:00
const unsigned numPins = getPins ( ) ;
2024-09-03 20:28:21 +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-05 19:49:27 +00:00
[[maybe_unused]] const unsigned bitShift = dithering * 4 ; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
2024-09-03 20:28:21 +00:00
2024-09-01 09:43:22 +00:00
// use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness
2024-08-30 13:21:16 +00:00
// the formula is based on 12 bit resolution as there is no need for greater precision
2024-09-01 09:43:22 +00:00
// see: https://en.wikipedia.org/wiki/Lightness
2024-08-30 13:21:16 +00:00
unsigned pwmBri = ( unsigned ) _bri * 100 ; // enlarge to use integer math for linear response
if ( pwmBri < 2040 ) {
// linear response for values [0-20]
pwmBri = ( ( pwmBri < < 12 ) + 115043 ) / 230087 ; //adding '0.5' before division for correct rounding
} else {
// cubic response for values [21-255]
2024-08-24 09:35:32 +00:00
pwmBri + = 4080 ;
2024-08-27 19:57:00 +00:00
float temp = ( float ) pwmBri / 29580.0f ;
2024-09-02 15:53:15 +00:00
temp = temp * temp * temp * ( float ) maxBri ;
2024-09-02 20:06:46 +00:00
pwmBri = ( unsigned ) temp ; // pwmBri is in range [0-maxBri]
2024-08-24 09:35:32 +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)
// 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-04 16:03:52 +00:00
unsigned duty = ( _data [ i ] * pwmBri ) / 255 ;
2024-08-24 09:35:32 +00:00
# ifdef ESP8266
2024-09-04 16:03:52 +00:00
if ( _reversed ) duty = maxBri - duty ;
2024-09-03 20:28:21 +00:00
analogWrite ( _pins [ i ] , duty ) ;
2024-08-24 09:35:32 +00:00
# else
2024-09-04 18:54:44 +00:00
int deadTime = 0 ;
2024-09-03 20:28:21 +00:00
if ( _type = = TYPE_ANALOG_2CH & & Bus : : getCCTBlend ( ) = = 0 ) {
// 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
if ( _bri > = 254 & & duty > = maxBri / 2 & & duty < maxBri ) duty - = deadTime < < 1 ; // shorten duty of larger signal except if full on
2024-09-04 18:54:44 +00:00
if ( _reversed ) deadTime = - deadTime ; // need to invert dead time to make phaseshift go the opposite way so low signals dont overlap
2024-09-03 20:28:21 +00:00
}
2024-09-04 16:03:52 +00:00
if ( _reversed ) duty = maxBri - duty ;
2024-09-03 20:28:21 +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-09-03 20:28:21 +00:00
hPoint + = duty + deadTime ; // offset to cascade the signals
if ( hPoint > = maxBri ) hPoint = 0 ; // offset it out of bounds, reset
2024-08-24 09:35:32 +00:00
# endif
}
2023-02-05 22:48:43 +00:00
}
2024-08-22 15:15:12 +00:00
uint8_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 ;
}
2024-09-05 19:49:27 +00:00
// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056
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-08-22 15:15:12 +00:00
void BusPwm : : deallocatePins ( void ) {
2024-08-24 09:35:32 +00:00
unsigned numPins = getPins ( ) ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numPins ; i + + ) {
2023-02-05 22:48:43 +00:00
pinManager . deallocatePin ( _pins [ i ] , PinOwner : : BusPwm ) ;
if ( ! pinManager . isPinOk ( _pins [ i ] ) ) continue ;
# ifdef ESP8266
digitalWrite ( _pins [ i ] , LOW ) ; //turn off PWM interrupt
# else
2024-08-26 22:21:24 +00:00
if ( _ledcStart < WLED_MAX_ANALOG_CHANNELS ) ledcDetachPin ( _pins [ i ] ) ;
2023-02-05 22:48:43 +00:00
# endif
}
# ifdef ARDUINO_ARCH_ESP32
pinManager . deallocateLedc ( _ledcStart , numPins ) ;
# endif
}
2023-07-06 19:16:29 +00:00
BusOnOff : : BusOnOff ( BusConfig & bc )
: Bus ( bc . type , bc . start , bc . autoWhite , 1 , bc . reversed )
, _onoffdata ( 0 )
{
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 ] ;
if ( ! pinManager . allocatePin ( currentPin , true , PinOwner : : BusOnOff ) ) {
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-06-30 19:12:59 +00:00
_data = & _onoffdata ; // avoid malloc() and use stack
2023-02-05 22:48:43 +00:00
_valid = true ;
2024-05-18 09:11:40 +00:00
DEBUG_PRINTF_P ( PSTR ( " %successfully inited On/Off strip with pin %u \n " ) , _valid ? " S " : " Uns " , _pin ) ;
2023-02-05 22:48:43 +00:00
}
void BusOnOff : : setPixelColor ( uint16_t pix , uint32_t c ) {
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 ) ;
2023-06-30 19:12:59 +00:00
_data [ 0 ] = bool ( r | g | b | w ) & & bool ( _bri ) ? 0xFF : 0 ;
2023-02-05 22:48:43 +00:00
}
2024-08-22 15:15:12 +00:00
uint32_t BusOnOff : : getPixelColor ( uint16_t pix ) const {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return 0 ;
2023-06-30 19:12:59 +00:00
return RGBW32 ( _data [ 0 ] , _data [ 0 ] , _data [ 0 ] , _data [ 0 ] ) ;
2023-02-05 22:48:43 +00:00
}
2024-08-22 15:15:12 +00:00
void BusOnOff : : show ( void ) {
2023-02-05 22:48:43 +00:00
if ( ! _valid ) return ;
2023-07-06 19:16:29 +00:00
digitalWrite ( _pin , _reversed ? ! ( bool ) _data [ 0 ] : ( bool ) _data [ 0 ] ) ;
2023-02-05 22:48:43 +00:00
}
2024-08-22 15:15:12 +00:00
uint8_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 ;
}
2024-09-05 19:49:27 +00:00
// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056
std : : vector < LEDType > BusOnOff : : getLEDTypes ( ) {
return {
{ TYPE_ONOFF , " " , PSTR ( " On/Off " ) } ,
} ;
}
2023-02-05 22:48:43 +00:00
2023-07-06 19:16:29 +00:00
BusNetwork : : BusNetwork ( BusConfig & bc )
: 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 ] ) ;
2024-08-22 15:15:12 +00:00
_valid = ( allocateData ( _len * _UDPchannels ) ! = nullptr ) ;
2024-05-18 09:11:40 +00:00
DEBUG_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
}
void BusNetwork : : setPixelColor ( uint16_t pix , uint32_t c ) {
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-08-22 15:15:12 +00:00
uint32_t BusNetwork : : getPixelColor ( uint16_t 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-08-22 15:15:12 +00:00
void BusNetwork : : show ( void ) {
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 ;
}
2024-08-22 15:15:12 +00:00
uint8_t BusNetwork : : getPins ( uint8_t * pinArray ) const {
if ( pinArray ) for ( unsigned i = 0 ; i < 4 ; i + + ) pinArray [ i ] = _client [ i ] ;
2023-02-05 22:48:43 +00:00
return 4 ;
}
2024-09-05 19:49:27 +00:00
// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056
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]
//{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/Aircoookie/WLED/pull/4123)
} ;
}
2024-08-22 15:15:12 +00:00
void BusNetwork : : cleanup ( void ) {
2023-02-05 22:48:43 +00:00
_type = I_NONE ;
_valid = false ;
2023-06-30 19:12:59 +00:00
freeData ( ) ;
2023-02-05 22:48:43 +00:00
}
//utility to get the approx. memory usage of a given BusConfig
uint32_t BusManager : : memUsage ( BusConfig & bc ) {
2024-08-24 09:35:32 +00:00
if ( Bus : : isOnOff ( bc . type ) | | Bus : : isPWM ( bc . type ) ) return 5 ;
2024-01-16 20:02:08 +00:00
2024-07-07 12:18:51 +00:00
unsigned len = bc . count + bc . skipAmount ;
unsigned channels = Bus : : getNumberOfChannels ( bc . type ) ;
unsigned multiplier = 1 ;
2024-08-24 09:35:32 +00:00
if ( Bus : : isDigital ( bc . type ) ) { // digital types
if ( Bus : : is16bit ( bc . type ) ) len * = 2 ; // 16-bit LEDs
2023-07-19 07:22:29 +00:00
# ifdef ESP8266
2023-02-05 22:48:43 +00:00
if ( bc . pins [ 0 ] = = 3 ) { //8266 DMA uses 5x the mem
2024-01-16 20:02:08 +00:00
multiplier = 5 ;
2023-02-05 22:48:43 +00:00
}
2024-06-12 16:00:00 +00:00
# else //ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times)
multiplier = PolyBus : : isParallelI2S1Output ( ) ? 24 : 2 ;
2023-02-05 22:48:43 +00:00
# endif
}
2024-06-12 16:00:00 +00:00
return ( len * multiplier + bc . doubleBuffer * ( bc . count + bc . skipAmount ) ) * channels ;
2023-02-05 22:48:43 +00:00
}
2024-07-07 12:18:51 +00:00
uint32_t BusManager : : memUsage ( unsigned maxChannels , unsigned maxCount , unsigned minBuses ) {
//ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times)
unsigned multiplier = PolyBus : : isParallelI2S1Output ( ) ? 3 : 2 ;
return ( maxChannels * maxCount * minBuses * multiplier ) ;
}
2023-02-05 22:48:43 +00:00
int BusManager : : add ( BusConfig & bc ) {
if ( getNumBusses ( ) - getNumVirtualBusses ( ) > = WLED_MAX_BUSSES ) return - 1 ;
2024-08-24 09:35:32 +00:00
if ( Bus : : isVirtual ( bc . type ) ) {
2023-02-05 22:48:43 +00:00
busses [ numBusses ] = new BusNetwork ( bc ) ;
2024-08-24 09:35:32 +00:00
} else if ( Bus : : isDigital ( bc . type ) ) {
2023-02-05 22:48:43 +00:00
busses [ numBusses ] = new BusDigital ( bc , numBusses , colorOrderMap ) ;
2024-08-24 09:35:32 +00:00
} else if ( Bus : : isOnOff ( bc . type ) ) {
2023-02-05 22:48:43 +00:00
busses [ numBusses ] = new BusOnOff ( bc ) ;
} else {
busses [ numBusses ] = new BusPwm ( bc ) ;
}
return numBusses + + ;
}
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-08-24 09:35:32 +00:00
int capabilities = Bus : : hasRGB ( type . id ) | Bus : : hasWhite ( type . id ) < < 1 | Bus : : hasCCT ( type . id ) < < 2 | Bus : : is16bit ( type . id ) < < 4 ;
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 ;
}
2024-09-05 19:49:27 +00:00
// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056
String BusManager : : getLEDTypesJSONString ( void ) {
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-06-12 16:00:00 +00:00
void BusManager : : useParallelOutput ( void ) {
2024-06-23 12:09:18 +00:00
_parallelOutputs = 8 ; // hardcoded since we use NPB I2S x8 methods
2024-06-12 16:00:00 +00:00
PolyBus : : setParallelI2S1Output ( ) ;
}
2023-02-05 22:48:43 +00:00
//do not call this method from system context (network callback)
2024-08-22 15:15:12 +00:00
void BusManager : : removeAll ( void ) {
2023-02-05 22:48:43 +00:00
DEBUG_PRINTLN ( F ( " Removing all. " ) ) ;
//prevents crashes due to deleting busses while in use.
while ( ! canAllShow ( ) ) yield ( ) ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) delete busses [ i ] ;
2023-02-05 22:48:43 +00:00
numBusses = 0 ;
2024-06-23 12:09:18 +00:00
_parallelOutputs = 1 ;
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-08-22 15:15:12 +00:00
void BusManager : : esp32RMTInvertIdle ( void ) {
2024-06-23 12:09:18 +00:00
bool idle_out ;
unsigned rmt = 0 ;
for ( unsigned u = 0 ; u < numBusses ( ) ; u + + ) {
# 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
if ( u < _parallelOutputs ) continue ;
if ( u > = _parallelOutputs + 8 ) return ; // only 8 RMT channels
rmt = u - _parallelOutputs ;
# endif
2024-09-05 19:49:27 +00:00
if ( busses [ u ] - > getLength ( ) = = 0 | | ! busses [ u ] - > isDigital ( ) | | busses [ u ] - > is2Pin ( ) ) continue ;
2024-06-23 12:09:18 +00:00
//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 ) ;
}
}
# endif
2024-08-22 15:15:12 +00:00
void BusManager : : on ( void ) {
2024-06-23 12:09:18 +00:00
# ifdef ESP8266
//Fix for turning off onboard LED breaking bus
if ( pinManager . getPinOwner ( LED_BUILTIN ) = = PinOwner : : BusDigital ) {
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
uint8_t pins [ 2 ] = { 255 , 255 } ;
2024-09-05 19:49:27 +00:00
if ( busses [ i ] - > isDigital ( ) & & busses [ i ] - > getPins ( pins ) ) {
2024-06-23 12:09:18 +00:00
if ( pins [ 0 ] = = LED_BUILTIN | | pins [ 1 ] = = LED_BUILTIN ) {
BusDigital * bus = static_cast < BusDigital * > ( busses [ i ] ) ;
bus - > reinit ( ) ;
break ;
}
}
}
}
# endif
# ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle ( ) ;
# endif
}
2024-08-22 15:15:12 +00:00
void BusManager : : off ( void ) {
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
if ( pinManager . getPinOwner ( LED_BUILTIN ) = = PinOwner : : BusDigital ) {
for ( unsigned i = 0 ; i < numBusses ; i + + ) if ( busses [ i ] - > isOffRefreshRequired ( ) ) return ;
pinMode ( LED_BUILTIN , OUTPUT ) ;
digitalWrite ( LED_BUILTIN , HIGH ) ;
}
# endif
# ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle ( ) ;
# endif
}
2024-08-22 15:15:12 +00:00
void BusManager : : show ( void ) {
2023-12-29 22:07:29 +00:00
_milliAmpsUsed = 0 ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
2023-02-05 22:48:43 +00:00
busses [ i ] - > show ( ) ;
2023-12-29 22:07:29 +00:00
_milliAmpsUsed + = busses [ i ] - > getUsedCurrent ( ) ;
2023-02-05 22:48:43 +00:00
}
2023-12-29 22:07:29 +00:00
if ( _milliAmpsUsed ) _milliAmpsUsed + = MA_FOR_ESP ;
2023-02-05 22:48:43 +00:00
}
void BusManager : : setStatusPixel ( uint32_t c ) {
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
2023-02-05 22:48:43 +00:00
busses [ i ] - > setStatusPixel ( c ) ;
}
}
2023-06-26 23:57:05 +00:00
void IRAM_ATTR BusManager : : setPixelColor ( uint16_t pix , uint32_t c ) {
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
2024-05-18 09:11:40 +00:00
unsigned bstart = busses [ i ] - > getStart ( ) ;
if ( pix < bstart | | pix > = bstart + busses [ i ] - > getLength ( ) ) continue ;
2023-02-05 22:48:43 +00:00
busses [ i ] - > setPixelColor ( pix - bstart , c ) ;
}
}
2023-07-19 15:25:25 +00:00
void BusManager : : setBrightness ( uint8_t b ) {
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
2023-07-19 15:25:25 +00:00
busses [ i ] - > setBrightness ( b ) ;
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 ) ;
}
uint32_t BusManager : : getPixelColor ( uint16_t pix ) {
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
2024-05-18 09:11:40 +00:00
unsigned bstart = busses [ i ] - > getStart ( ) ;
2024-08-22 15:15:12 +00:00
if ( ! busses [ i ] - > containsPixel ( pix ) ) continue ;
2024-05-18 09:11:40 +00:00
return busses [ i ] - > getPixelColor ( pix - bstart ) ;
2023-02-05 22:48:43 +00:00
}
return 0 ;
}
2024-08-22 15:15:12 +00:00
bool BusManager : : canAllShow ( void ) {
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) {
2023-02-05 22:48:43 +00:00
if ( ! busses [ i ] - > canShow ( ) ) return false ;
}
return true ;
}
Bus * BusManager : : getBus ( uint8_t busNr ) {
if ( busNr > = numBusses ) return nullptr ;
return busses [ busNr ] ;
}
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
2024-08-22 15:15:12 +00:00
uint16_t BusManager : : getTotalLength ( void ) {
2024-07-09 19:50:27 +00:00
unsigned len = 0 ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < numBusses ; i + + ) len + = busses [ i ] - > getLength ( ) ;
2023-02-05 22:48:43 +00:00
return len ;
}
2024-06-12 16:00:00 +00:00
bool PolyBus : : useParallelI2S = false ;
2023-02-05 22:48:43 +00:00
// Bus static member definition
int16_t Bus : : _cct = - 1 ;
uint8_t Bus : : _cctBlend = 0 ;
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 ;
uint8_t BusManager : : numBusses = 0 ;
Bus * BusManager : : busses [ WLED_MAX_BUSSES + WLED_MIN_VIRTUAL_BUSSES ] ;
ColorOrderMap BusManager : : colorOrderMap = { } ;
uint16_t BusManager : : _milliAmpsUsed = 0 ;
2024-06-23 12:09:18 +00:00
uint16_t BusManager : : _milliAmpsMax = ABL_MILLIAMPS_DEFAULT ;
uint8_t BusManager : : _parallelOutputs = 1 ;