├── library.properties ├── src ├── llrmt.h ├── ll_priority.h ├── ll_registry.h ├── ll_encoder.h ├── ll_strip_core.h ├── ll_strip_pixels.h ├── ll_encoder.cpp ├── llrgb.h ├── ll_priority.cpp ├── ll_registry.cpp ├── ll_led_timings.h ├── ll_strip_pixels.cpp ├── LiteLED.h ├── LiteLED.cpp └── ll_strip_core.cpp ├── LICENSE ├── keywords.txt ├── examples ├── rainbow │ └── rainbow.ino ├── blink │ └── blink.ino ├── matrix_test │ └── matrix_test.ino └── periman_test │ └── periman_test.ino ├── README.md ├── docs └── LiteLED Architecture.md └── Using LiteLED.md /library.properties: -------------------------------------------------------------------------------- 1 | name=LiteLED 2 | version=3.0.1 3 | author=Xylopyrographer 4 | maintainer=Xylopyrographer 5 | sentence=High performance library for driving one or more WS2812 and other types of RGB LED strips. 6 | paragraph=Provides hardware-accelerated control with support for driving multiple LED strips; arbitrary colour orders; DMA transfers; interrupt priority; and buffer allocation to either internal RAM or PSRAM. Requires an ESP32 SoC with an RMT peripheral. Works concurrently with the WiFi system when run on dual-core versions. Drives RGBW strips with limitations on the W channel. 7 | category=Display 8 | url=https://github.com/Xylopyrographer/LiteLED 9 | architectures=esp32 10 | providesIncludes=LiteLED.h 11 | -------------------------------------------------------------------------------- /src/llrmt.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | ESP32 RMT-based driver for various types of RGB LED strips 4 | 5 | Header that includes the modular components: 6 | - ll_led_timings.h : LED timing definitions for various strip types 7 | - ll_priority.h : Interrupt priority management 8 | - ll_encoder.h : RMT encoder callback 9 | - ll_strip_core.h : Core strip initialization and lifecycle 10 | - ll_strip_pixels.h : Pixel manipulation operations 11 | */ 12 | 13 | #ifndef __LLRMT_H__ 14 | #define __LLRMT_H__ 15 | 16 | // Include all modular components 17 | #include "ll_led_timings.h" 18 | #include "ll_priority.h" 19 | #include "ll_encoder.h" 20 | #include "ll_strip_core.h" 21 | #include "ll_strip_pixels.h" 22 | 23 | /* 24 | All functionality is now provided through the modular headers above. 25 | This file maintains backward compatibility for existing code that includes llrmt.h 26 | */ 27 | 28 | #endif /* __LLRMT_H__ */ 29 | 30 | // --- EOF --- // 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023-2025 Xylopyrographer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | #class (KEYWORD1) 2 | LiteLED KEYWORD1 3 | LiteLED_Utils KEYWORD1 4 | 5 | #function and method (KEYWORD2) 6 | begin KEYWORD2 7 | brightness KEYWORD2 8 | clear KEYWORD2 9 | fill KEYWORD2 10 | fillRandom KEYWORD2 11 | getActiveInstanceCount KEYWORD2 12 | getBrightness KEYWORD2 13 | getGpioPin KEYWORD2 14 | getPixel KEYWORD2 15 | getPixelC KEYWORD2 16 | isDmaSupported KEYWORD2 17 | isPrioritySupported KEYWORD2 18 | resetOrder KEYWORD2 19 | resetOrder KEYWORD2 20 | setOrder KEYWORD2 21 | setOrder KEYWORD2 22 | setPixel KEYWORD2 23 | setPixels KEYWORD2 24 | show KEYWORD2 25 | 26 | #constant (LITERAL1) 27 | #LITERAL_NAME LITERAL1 28 | DMA_DEFAULT LITERAL1 29 | DMA_DEFAULT LITERAL1 30 | DMA_OFF LITERAL1 31 | DMA_ON LITERAL1 32 | LED_STRIP_APA106 LITERAL1 33 | LED_STRIP_SK6812 LITERAL1 34 | LED_STRIP_SM16703 LITERAL1 35 | LED_STRIP_TYPE_MAX LITERAL1 36 | LED_STRIP_WS2812 LITERAL1 37 | LED_STRIP_WS2812_RGB LITERAL1 38 | ORDER_BGR LITERAL1 39 | ORDER_BRG LITERAL1 40 | ORDER_GBR LITERAL1 41 | ORDER_GRB LITERAL1 42 | ORDER_MAX LITERAL1 43 | ORDER_RBG LITERAL1 44 | ORDER_RGB LITERAL1 45 | PRIORITY_DEFAULT LITERAL1 46 | PRIORITY_HIGH LITERAL1 47 | PRIORITY_LOW LITERAL1 48 | PRIORITY_MED LITERAL1 49 | PSRAM_AUTO LITERAL1 50 | PSRAM_DISABLE LITERAL1 51 | PSRAM_ENABLE LITERAL1 52 | -------------------------------------------------------------------------------- /src/ll_priority.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Priority Management 4 | 5 | Manages RMT interrupt priority allocation for multiple concurrent displays. 6 | Provides fallback mechanism when requested priorities are unavailable. 7 | */ 8 | 9 | #ifndef __LL_PRIORITY_H__ 10 | #define __LL_PRIORITY_H__ 11 | 12 | #include 13 | #include "esp32-hal-log.h" 14 | 15 | // Maximum number of distinct interrupt priority levels 16 | #define LL_MAX_PRIORITY_ATTEMPTS 4 17 | 18 | // Priority fallback order: DEFAULT first (most compatible), then HIGH, MEDIUM, LOW 19 | extern const int ll_priority_fallbacks[ LL_MAX_PRIORITY_ATTEMPTS ]; 20 | 21 | // Priority tracking state 22 | extern bool ll_priority_used[ LL_MAX_PRIORITY_ATTEMPTS ]; 23 | extern uint8_t ll_active_channels; 24 | 25 | // Check if a specific interrupt priority level is available 26 | bool ll_is_priority_available( int priority ); 27 | 28 | // Find the best available priority, preferring the requested one 29 | int ll_find_best_available_priority( int requested_priority ); 30 | 31 | // Mark a priority level as used (allocate) 32 | void ll_mark_priority_used( int priority ); 33 | 34 | // Mark a priority level as free (release) 35 | void ll_mark_priority_free( int priority ); 36 | 37 | // Reset all priority tracking (for debugging/recovery) 38 | void ll_reset_priority_tracking( void ); 39 | 40 | // Convert priority number to human-readable string 41 | const char *ll_priority_to_string( int priority ); 42 | 43 | #endif /* __LL_PRIORITY_H__ */ 44 | 45 | // --- EOF --- // 46 | -------------------------------------------------------------------------------- /examples/rainbow/rainbow.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define LED_GPIO 14 4 | #define LED_COUNT 60 5 | 6 | LiteLED strip(LED_STRIP_WS2812, false); 7 | 8 | void setup() { 9 | strip.begin(LED_GPIO, LED_COUNT); 10 | strip.brightness(50); 11 | } 12 | 13 | void loop() { 14 | static uint8_t hue = 0; 15 | 16 | for (size_t i = 0; i < LED_COUNT; i++) { 17 | // Create rainbow effect 18 | uint8_t pixelHue = hue + (i * 255 / LED_COUNT); 19 | strip.setPixel(i, HSVtoRGB(pixelHue, 255, 255)); 20 | } 21 | 22 | strip.show(); 23 | hue += 2; 24 | delay(20); 25 | } 26 | 27 | // Helper function to convert HSV to RGB 28 | crgb_t HSVtoRGB(uint8_t h, uint8_t s, uint8_t v) { 29 | uint8_t region, remainder, p, q, t; 30 | uint8_t r, g, b; 31 | 32 | if (s == 0) { 33 | return ((uint32_t)v << 16) | ((uint32_t)v << 8) | v; 34 | } 35 | 36 | region = h / 43; 37 | remainder = (h - (region * 43)) * 6; 38 | 39 | p = (v * (255 - s)) >> 8; 40 | q = (v * (255 - ((s * remainder) >> 8))) >> 8; 41 | t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; 42 | 43 | switch (region) { 44 | case 0: r = v; g = t; b = p; break; 45 | case 1: r = q; g = v; b = p; break; 46 | case 2: r = p; g = v; b = t; break; 47 | case 3: r = p; g = q; b = v; break; 48 | case 4: r = t; g = p; b = v; break; 49 | default: r = v; g = p; b = q; break; 50 | } 51 | 52 | return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; 53 | } -------------------------------------------------------------------------------- /src/ll_registry.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Registry - Leverages Peripheral Manager 4 | 5 | This module provides tracking that Peripheral Manager doesn't handle: 6 | - RMT channel -> LiteLED instance mapping (for deinit callback) 7 | 8 | GPIO/bus tracking is handled directly by Peripheral Manager. 9 | */ 10 | 11 | #ifndef __LL_REGISTRY_H__ 12 | #define __LL_REGISTRY_H__ 13 | 14 | #include "LiteLED.h" 15 | #include "esp32-hal-periman.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | // Maximum number of concurrent LiteLED instances = max RMT channels 22 | #define LL_MAX_INSTANCES 8 23 | 24 | // Initialize the LiteLED integration with Peripheral Manager 25 | esp_err_t ll_registry_init( void ); 26 | 27 | // Internal: Register channel -> instance mapping (called from LiteLED::begin) 28 | esp_err_t ll_register_channel_instance( rmt_channel_handle_t channel, LiteLED* instance ); 29 | 30 | // Internal: Unregister channel mapping (called from LiteLED::free) 31 | void ll_unregister_channel_instance( rmt_channel_handle_t channel ); 32 | 33 | // Get LiteLED instance from GPIO pin (queries periman + channel map) 34 | LiteLED* ll_get_instance_by_gpio( uint8_t gpio ); 35 | 36 | // Get active instance count (queries periman) 37 | uint8_t ll_registry_get_active_count( void ); 38 | 39 | // Peripheral Manager deinit callback - called when GPIO is reassigned 40 | bool ll_periman_deinit_callback( void *bus_handle ); 41 | 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | 46 | #endif /* __LL_REGISTRY_H__ */ 47 | 48 | 49 | // --- EOF --- // 50 | -------------------------------------------------------------------------------- /examples/blink/blink.ino: -------------------------------------------------------------------------------- 1 | // blink example 2 | // Using the LiteLED library 3 | // - blinks a colour RGB LED attached to a GPIO pin of the ESP32 4 | 5 | #include 6 | 7 | // Choose the LED type from the list below. 8 | // Comment out all but one LED_TYPE. 9 | #define LED_TYPE LED_STRIP_WS2812 10 | // #define LED_TYPE LED_STRIP_SK6812 11 | // #define LED_TYPE LED_STRIP_APA106 12 | // #define LED_TYPE LED_STRIP_SM16703 13 | 14 | #define LED_TYPE_IS_RGBW 0 // if the LED is an RGBW type, change the 0 to 1 15 | 16 | #define LED_GPIO 42 // change this number to be the GPIO pin connected to the LED 17 | 18 | #define LED_BRIGHT 30 // sets how bright the LED is. O is off; 255 is burn your eyeballs out (not recommended) 19 | 20 | // pick the colour you want from the list here and change it in setup() 21 | static const crgb_t L_RED = 0xff0000; 22 | static const crgb_t L_GREEN = 0x00ff00; 23 | static const crgb_t L_BLUE = 0x0000ff; 24 | static const crgb_t L_WHITE = 0xe0e0e0; 25 | 26 | LiteLED myLED( LED_TYPE, LED_TYPE_IS_RGBW ); // create the LiteLED object; we're calling it "myLED" 27 | 28 | void setup() { 29 | myLED.begin( LED_GPIO, 1 ); // initialze the myLED object. Here we have 1 LED attached to the LED_GPIO pin 30 | myLED.brightness( LED_BRIGHT ); // set the LED photon intensity level 31 | myLED.setPixel( 0, L_GREEN, 1 ); // set the LED colour and show it 32 | delay( 2000 ); 33 | } 34 | 35 | void loop() { 36 | // flash the LED 37 | myLED.brightness( 0, 1 ); // turn the LED off 38 | delay( 1000 ); 39 | 40 | myLED.brightness( LED_BRIGHT, 1 ); // turn the LED on 41 | delay( 1000 ); 42 | } 43 | -------------------------------------------------------------------------------- /src/ll_encoder.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED RMT Encoder Callback 4 | 5 | The encoder callback is called by the RMT driver when it needs more symbols to send. 6 | It pulls bytes from the LED data buffer and encodes them into RMT symbols with 7 | brightness scaling applied. 8 | */ 9 | 10 | #ifndef __LL_ENCODER_H__ 11 | #define __LL_ENCODER_H__ 12 | 13 | #include "LiteLED.h" 14 | #include "llrgb.h" 15 | #include "ll_led_timings.h" 16 | 17 | /* 18 | The encoder_callback() function is called by the RMT driver when it needs more symbols to send. 19 | The encoder_callback() pulls bytes of data from the LED data buffer and encodes them into RMT symbols, 20 | which are then stored into the allocated RMT memory block. The RMT driver then pulls the symbols 21 | from the memory block and sends them to the GPIO pin via the RMT hardware peripheral. 22 | 23 | Parameters: 24 | data - [in] LED buffer data (provided via the "simple_encoder" handle) 25 | data_size - [in] Size of LED buffer (provided via the "simple_encoder" handle) 26 | symbols_written - [in] Number of symbols already written (provided by the RMT driver) 27 | symbols_free - [in] Number of free symbol spaces available (provided by the RMT driver) 28 | symbols - [out] Output buffer for RMT symbols 29 | done - [out] Indicates end of transmission 30 | arg - [in] User argument (led_strip_t pointer, provided via the "simple_encoder" handle) 31 | 32 | Returns: 33 | Number of symbols written to the output buffer 34 | */ 35 | IRAM_ATTR size_t led_encoder_cb( const void* data, size_t data_size, 36 | size_t symbols_written, size_t symbols_free, 37 | rmt_symbol_word_t *symbols, bool *done, void *arg ); 38 | 39 | #endif /* __LL_ENCODER_H__ */ 40 | 41 | // --- EOF --- // 42 | -------------------------------------------------------------------------------- /src/ll_strip_core.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Core Strip Operations 4 | 5 | Core LED strip initialization, configuration, and lifecycle management: 6 | - Initialization and configuration 7 | - Installation and resource allocation 8 | - Cleanup and resource deallocation 9 | - Data transmission (flush) 10 | - Debug output 11 | */ 12 | 13 | #ifndef __LL_STRIP_CORE_H__ 14 | #define __LL_STRIP_CORE_H__ 15 | 16 | #include "LiteLED.h" 17 | #include "ll_led_timings.h" 18 | #include "ll_priority.h" 19 | #include "ll_encoder.h" 20 | #include "esp32-hal-log.h" 21 | 22 | // Configuration constants 23 | #define LL_MEM_BLOCK_SIZE_DEFAULT ( ( size_t)SOC_RMT_MEM_WORDS_PER_CHANNEL ) /* Size of memory block if no DMA */ 24 | #define LL_MEM_BLOCK_SIZE_DMA 1024 /* Size of memory block with DMA */ 25 | #define LL_ENCODER_MIN_CHUNK_SIZE 64 /* Minimum RMT symbol space for encoder */ 26 | 27 | // Utility macros 28 | #define COLOR_SIZE( strip ) ( 3 + ( (strip)->is_rgbw != 0 ) ) 29 | #define PIXEL_SIZE( strip ) ( COLOR_SIZE( strip ) * (strip)->length ) 30 | 31 | // Initialize LED strip configuration structures 32 | esp_err_t led_strip_init( led_strip_t *strip ); 33 | 34 | // Modify strip configuration for DMA and interrupt priority 35 | esp_err_t led_strip_init_modify( led_strip_t *strip, ll_dma_t use_dma, ll_priority_t priority ); 36 | 37 | // Install LED strip and allocate resources (buffer, RMT channel, encoder) 38 | esp_err_t led_strip_install( led_strip_t *strip ); 39 | 40 | // Free all resources used by the strip 41 | esp_err_t led_strip_free( led_strip_t *strip ); 42 | 43 | // Transmit LED buffer data to the strip 44 | esp_err_t led_strip_flush( led_strip_t *strip ); 45 | 46 | // Dump strip configuration for debugging 47 | void led_strip_debug_dump( led_strip_t *strip ); 48 | 49 | #endif /* __LL_STRIP_CORE_H__ */ 50 | 51 | // --- EOF --- // 52 | -------------------------------------------------------------------------------- /src/ll_strip_pixels.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Pixel Operations 4 | 5 | LED strip pixel manipulation functions: 6 | - Set/get individual pixels 7 | - Set multiple pixels 8 | - Fill operations 9 | - Clear operations 10 | - Brightness control 11 | - Color order management 12 | */ 13 | 14 | #ifndef __LL_STRIP_PIXELS_H__ 15 | #define __LL_STRIP_PIXELS_H__ 16 | 17 | #include "LiteLED.h" 18 | #include "llrgb.h" 19 | #include "ll_led_timings.h" 20 | #include "ll_strip_core.h" 21 | #include "esp32-hal-log.h" 22 | 23 | // Set global brightness for all LEDs (0-255) 24 | esp_err_t led_strip_set_brightness( led_strip_t *strip, uint8_t num ); 25 | 26 | // Get current brightness value 27 | uint8_t led_strip_get_brightness( led_strip_t *strip ); 28 | 29 | // Set individual pixel color (respects color order) 30 | esp_err_t led_strip_set_pixel( led_strip_t *strip, size_t num, rgb_t color ); 31 | 32 | // Get individual pixel color (respects color order) 33 | rgb_t led_strip_get_pixel( led_strip_t *strip, size_t num ); 34 | 35 | // Set multiple pixels from rgb_t array 36 | esp_err_t led_strip_set_pixels( led_strip_t *strip, size_t start, size_t len, rgb_t* data ); 37 | 38 | // Set multiple pixels from crgb_t (color code) array 39 | esp_err_t led_strip_set_pixels_c( led_strip_t *strip, size_t start, size_t len, crgb_t* data ); 40 | 41 | // Fill entire strip with single color 42 | esp_err_t led_strip_fill( led_strip_t *strip, rgb_t color ); 43 | 44 | // Fill strip with random colors 45 | esp_err_t led_strip_fill_random( led_strip_t *strip ); 46 | 47 | // Clear strip (set all to black) 48 | esp_err_t led_strip_clear( led_strip_t *strip, size_t num_bytes ); 49 | 50 | // Set custom color order for the strip 51 | esp_err_t led_strip_set_color_order( led_strip_t *strip, color_order_t led_order ); 52 | 53 | // Reset to default color order based on LED type 54 | esp_err_t led_strip_set_default_color_order( led_strip_t *strip ); 55 | 56 | #endif /* __LL_STRIP_PIXELS_H__ */ 57 | 58 | // --- EOF --- // 59 | -------------------------------------------------------------------------------- /src/ll_encoder.cpp: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED RMT Encoder Callback Implementation 4 | */ 5 | 6 | #include "ll_encoder.h" 7 | 8 | size_t led_encoder_cb( const void* data, size_t data_size, 9 | size_t symbols_written, size_t symbols_free, 10 | rmt_symbol_word_t *symbols, bool *done, void *arg ) { 11 | 12 | // We need a minimum of 8 symbol spaces to encode a byte. We only 13 | // need one to encode a reset, but it's simpler to simply demand that 14 | // there are 8 symbol spaces free to write anything. 15 | if ( symbols_free < 8 ) { 16 | return 0; 17 | } 18 | 19 | led_strip_t *strip = ( led_strip_t* )arg; // Cast arg to led_strip_t* 20 | uint8_t *data_bytes = ( uint8_t* )data; 21 | uint8_t photons = strip->brightness; // Get the brightness value 22 | 23 | // Retrieve the current position from enc_pos 24 | size_t data_pos = strip->stripCfg.enc_pos; 25 | 26 | if ( data_pos < data_size ) { 27 | // Encode a data byte with brightness scaling 28 | uint8_t currentByte = scale8_video( data_bytes[ data_pos ], photons ); 29 | 30 | // Convert the byte to RMT symbols (8 bits = 8 symbols) 31 | size_t symbol_pos = 0; 32 | for ( int bitmask = 0x80; bitmask != 0; bitmask >>= 1 ) { 33 | symbols[ symbol_pos++ ] = ( currentByte & bitmask ) ? 34 | led_params[ strip->type ].led_1 : 35 | led_params[ strip->type ].led_0; 36 | } 37 | 38 | // Update the current position in the buffer 39 | strip->stripCfg.enc_pos++; 40 | 41 | // We should have written 8 symbols 42 | return symbol_pos; 43 | } 44 | else { 45 | // All bytes have been encoded. 46 | // Encode the reset symbol, and we're done. 47 | symbols[ 0 ] = led_params[ strip->type ].led_reset; 48 | strip->stripCfg.enc_pos = 0; // Reset the position in the buffer 49 | *done = 1; // Indicate end of the transaction 50 | return 1; // We only wrote one symbol 51 | } 52 | } 53 | 54 | // --- EOF --- // 55 | -------------------------------------------------------------------------------- /src/llrgb.h: -------------------------------------------------------------------------------- 1 | /* 2 | Helper stuff for the LiteLED library 3 | - Ported from FastLED 4 | */ 5 | 6 | #ifndef __LLRGB_H__ 7 | #define __LLRGB_H__ 8 | 9 | #define LIB8STATIC __attribute__ ((unused)) static inline 10 | #define LIB8STATIC_ALWAYS_INLINE __attribute__ ((always_inline)) static inline 11 | typedef uint8_t fract8; // ANSI: unsigned short _Fract 12 | 13 | // scale one byte by a second one, which is treated as 14 | // the numerator of a fraction whose denominator is 256 15 | // In other words, it computes i * (scale / 256) 16 | LIB8STATIC_ALWAYS_INLINE uint8_t scale8( uint8_t i, fract8 scale ) { 17 | return ( ( ( uint16_t ) i ) * ( 1 + ( uint16_t )( scale ) ) ) >> 8; 18 | } 19 | 20 | // The "video" version of scale8 guarantees that the output will 21 | // be only be zero if one or both of the inputs are zero. If both 22 | // inputs are non-zero, the output is guaranteed to be non-zero. 23 | // This makes for better 'video'/LED dimming, at the cost of 24 | // several additional cycles. 25 | LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video( uint8_t i, fract8 scale ) { 26 | return ( ( ( int ) i * ( int ) scale ) >> 8 ) + ( ( i && scale ) ? 1 : 0 ); 27 | } 28 | 29 | // RGB color representation (array style) 30 | typedef struct { 31 | union { 32 | uint8_t r; 33 | uint8_t red; 34 | }; 35 | union { 36 | uint8_t g; 37 | uint8_t green; 38 | }; 39 | union { 40 | uint8_t b; 41 | uint8_t blue; 42 | }; 43 | } rgb_t; 44 | 45 | // RGB color representation (colour code style) 46 | // where colour code = 0xRRGGBB 47 | typedef uint32_t crgb_t; 48 | 49 | // This allows testing a RGB for zero-ness 50 | static inline bool rgb_is_zero( rgb_t a ) { 51 | return !( a.r | a.g | a.b ); 52 | } 53 | 54 | // Create rgb_t color from 24-bit color code 0xRRGGBB 55 | static inline rgb_t rgb_from_code( crgb_t color_code ) { 56 | rgb_t res = { 57 | .r = ( uint8_t )( ( color_code >> 16 ) & 0xff ), 58 | .g = ( uint8_t )( ( color_code >> 8 ) & 0xff ), 59 | .b = ( uint8_t )( color_code & 0xff ), 60 | }; 61 | return res; 62 | } 63 | 64 | // Create rgb_t color from values 65 | static inline rgb_t rgb_from_values( uint8_t r, uint8_t g, uint8_t b ) { 66 | rgb_t res = { 67 | .r = r, 68 | .g = g, 69 | .b = b, 70 | }; 71 | return res; 72 | } 73 | 74 | // Convert RGB color to 24-bit color code 0x00RRGGBB 75 | static inline crgb_t rgb_to_code( rgb_t color ) { 76 | return ( ( crgb_t )color.r << 16 ) | ( ( crgb_t )color.g << 8 ) | color.b; 77 | } 78 | 79 | // Get the 'luma' of a RGB color - aka roughly how much light the RGB pixel 80 | // is putting out (from 0 to 255). 81 | static inline uint8_t rgb_luma( rgb_t a ) { 82 | return scale8( a.r, 54 ) + scale8( a.g, 183 ) + scale8( a.b, 18 ); 83 | } 84 | 85 | #endif /* __LLRGB_H__ */ 86 | 87 | // --- EOF --- // 88 | -------------------------------------------------------------------------------- /src/ll_priority.cpp: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED RMT Interrupt Priority Management Implementation 4 | */ 5 | 6 | #include "ll_priority.h" 7 | 8 | // Priority fallback order: DEFAULT first (most compatible), then others 9 | const int ll_priority_fallbacks[ LL_MAX_PRIORITY_ATTEMPTS ] = { 0, 1, 2, 3 }; 10 | 11 | // Priority tracking state 12 | bool ll_priority_used[ LL_MAX_PRIORITY_ATTEMPTS ] = { false, false, false, false }; 13 | uint8_t ll_active_channels = 0; 14 | 15 | bool ll_is_priority_available( int priority ) { 16 | /* Checks if a specific interrupt priority is available */ 17 | if ( priority < 0 || priority >= LL_MAX_PRIORITY_ATTEMPTS ) { 18 | return false; 19 | } 20 | return !ll_priority_used[ priority ]; 21 | } 22 | 23 | int ll_find_best_available_priority( int requested_priority ) { 24 | /* Finds the best available priority, preferring the requested one */ 25 | // First check if requested priority is available 26 | if ( ll_is_priority_available( requested_priority ) ) { 27 | return requested_priority; 28 | } 29 | 30 | // Try fallback priorities in order 31 | for ( int i = 0; i < LL_MAX_PRIORITY_ATTEMPTS; i++ ) { 32 | if ( ll_is_priority_available( ll_priority_fallbacks[ i ] ) ) { 33 | return ll_priority_fallbacks[ i ]; 34 | } 35 | } 36 | 37 | // No priority available 38 | return -1; 39 | } 40 | 41 | void ll_mark_priority_used( int priority ) { 42 | /* Marks a priority as used */ 43 | if ( priority >= 0 && priority < LL_MAX_PRIORITY_ATTEMPTS ) { 44 | ll_priority_used[ priority ] = true; 45 | ll_active_channels++; 46 | log_d( "Priority %s (%d) marked as used. Active channels: %d", 47 | ll_priority_to_string( priority ), priority, ll_active_channels ); 48 | } 49 | } 50 | 51 | void ll_mark_priority_free( int priority ) { 52 | /* Marks a priority as free */ 53 | if ( priority >= 0 && priority < LL_MAX_PRIORITY_ATTEMPTS && ll_priority_used[ priority ] ) { 54 | ll_priority_used[ priority ] = false; 55 | if ( ll_active_channels > 0 ) { 56 | ll_active_channels--; 57 | } 58 | log_d( "Priority %s (%d) marked as free. Active channels: %d", 59 | ll_priority_to_string( priority ), priority, ll_active_channels ); 60 | } 61 | } 62 | 63 | void ll_reset_priority_tracking( void ) { 64 | /* Resets all priority tracking - useful for debugging or error recovery */ 65 | for ( int i = 0; i < LL_MAX_PRIORITY_ATTEMPTS; i++ ) { 66 | ll_priority_used[ i ] = false; 67 | } 68 | ll_active_channels = 0; 69 | log_d( "Priority tracking reset. All priorities now available." ); 70 | } 71 | 72 | const char *ll_priority_to_string( int priority ) { 73 | /* Converts interrupt priority number to human-readable string */ 74 | switch ( priority ) { 75 | case 0: 76 | return "DEFAULT"; 77 | case 1: 78 | return "HIGH"; 79 | case 2: 80 | return "MEDIUM"; 81 | case 3: 82 | return "LOW"; 83 | default: 84 | return "UNKNOWN"; 85 | } 86 | } 87 | 88 | // --- EOF --- // 89 | -------------------------------------------------------------------------------- /examples/matrix_test/matrix_test.ino: -------------------------------------------------------------------------------- 1 | // Matrix panel test 2 | // Using the LiteLED library 3 | // Runs a series of tests on an square RGB LED matrix panel 4 | 5 | #include 6 | 7 | // Choose the LED type from the list below. 8 | // Un-comment the line below that matches your LED strip type. 9 | // #define LED_TYPE LED_STRIP_WS2812 10 | // #define LED_TYPE LED_STRIP_SK6812 11 | // #define LED_TYPE LED_STRIP_APA106 12 | // #define LED_TYPE LED_STRIP_SM16703 13 | 14 | #define LED_TYPE_IS_RGBW 0 // if the LED matrix uses RGBW type LED's, change the 0 to 1 15 | 16 | #define LED_GPIO 99 // change this number to be the GPIO pin connected to DIN of the matrix panel 17 | 18 | // Change the MTRIX_X_SIZE #define below to match the size of one side of the matrix panel 19 | // Only square panels are supported. 20 | // The panel must be wired "row by row" fashion, not "serpintine". 21 | #define MTRIX_X_SIZE 8 22 | 23 | #define MTRIX_LEDS ( MTRIX_X_SIZE * MTRIX_X_SIZE ) // total number of LED's in the matrix panel 24 | 25 | #define A_DELAY 75 // # of ms delay between animations within a test 26 | #define TST_PAUSE 1000 // # of ms to pause between tests 27 | #define REPEAT_DELAY 3000 // # of ms to delay before repeating all tests 28 | 29 | static const uint8_t currBright = 20; // change this to set the brightness level of the matrix 30 | 31 | size_t c = 0; // picker for the colour of the LEDs for the currrent test 32 | 33 | static const crgb_t L_BLACK = 0x000000; 34 | static const crgb_t colors[] = { 35 | 0x2f0000, /* red */ 36 | 0x002f00, /* green */ 37 | 0x00002f, /* blue */ 38 | 0x0f0f0f /* white */ 39 | }; // the tests will cycle through the colours above; add more if you'd like 40 | #define COLORS_TOTAL ( sizeof( colors ) / sizeof( crgb_t ) ) 41 | 42 | LiteLED myDisplay( LED_TYPE, LED_TYPE_IS_RGBW ); // create the LiteLED object 43 | 44 | void setup() { 45 | myDisplay.begin( LED_GPIO, MTRIX_LEDS ); // initialze the myDisplay object. 46 | myDisplay.brightness( currBright ); 47 | } 48 | 49 | void loop() { 50 | // fill the display with a color 51 | myDisplay.fill( colors[ c ], 1 ); // fill the matrix with a single colour and show it 52 | delay( TST_PAUSE ); 53 | 54 | // flash the display 55 | for ( uint8_t k = 0; k < 3; k++ ) { 56 | myDisplay.brightness( 0, 1 ); 57 | delay( 250 ); 58 | 59 | myDisplay.brightness( currBright, 1 ); 60 | delay( 250 ); 61 | } 62 | delay( TST_PAUSE ); 63 | myDisplay.clear( 1 ); 64 | 65 | // pixel walk - sequentially light up each pixel 66 | for ( size_t pxlNum = 0; pxlNum < MTRIX_LEDS; pxlNum++ ) { 67 | myDisplay.setPixel( pxlNum, colors[ c ], 1 ); 68 | delay( A_DELAY ); // pause before moving to the next pixel 69 | myDisplay.setPixel( pxlNum, L_BLACK ); // turn off the pixel we just light up 70 | } 71 | myDisplay.clear( 1 ); // clear the LED buffer, setting all colours to black and show it 72 | 73 | delay( TST_PAUSE ); 74 | 75 | // row test - sequentially light up each row 76 | for ( size_t i = 0; i < MTRIX_X_SIZE; i++ ) { 77 | for ( size_t j = 0; j < MTRIX_X_SIZE; j++ ) { 78 | size_t pxl = i * MTRIX_X_SIZE + j; 79 | myDisplay.setPixel( pxl, colors[ c ] ); 80 | } 81 | myDisplay.show(); 82 | delay( A_DELAY * 2 ); // pause before moving to the next row 83 | myDisplay.clear(); 84 | } 85 | myDisplay.clear( 1 ); 86 | 87 | delay( TST_PAUSE ); 88 | 89 | // column test - sequentially light up each column 90 | for ( size_t i = 0; i < MTRIX_X_SIZE; i++ ) { 91 | for ( size_t j = 0; j < MTRIX_X_SIZE; j++ ) { 92 | size_t pxl = i + j * MTRIX_X_SIZE; 93 | myDisplay.setPixel( pxl, colors[ c ] ); 94 | } 95 | myDisplay.show(); 96 | delay( A_DELAY *2 ); // pause before moving to the next column 97 | myDisplay.clear(); 98 | } 99 | myDisplay.clear( 1 ); 100 | 101 | if ( ++c >= COLORS_TOTAL ) { 102 | c = 0; 103 | delay( REPEAT_DELAY ); 104 | } 105 | else { 106 | delay ( TST_PAUSE ); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /examples/periman_test/periman_test.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Example demonstrating LiteLED with Peripheral Manager integration 3 | Shows pin conflict detection and handling 4 | 5 | Note: It is not necessary to attach a display to run the example. 6 | Just ensure the assigned pins do not conflict with other hardware on your board. 7 | */ 8 | 9 | #include 10 | 11 | // GPIO pins for LED strips - modify these for your hardware setup 12 | #define STRIP1_GPIO_PIN 5 // GPIO pin for first LED strip 13 | #define STRIP2_GPIO_PIN 18 // GPIO pin for second LED strip 14 | #define CONFLICT_TEST_PIN STRIP1_GPIO_PIN // GPIO pin to test conflict detection (same as STRIP1) 15 | 16 | // LED strip configuration - modify these for your hardware setup 17 | #define STRIP1_LED_TYPE LED_STRIP_WS2812 // Type of LED strip for strip 1 (WS2812, APA106, etc.) 18 | #define STRIP2_LED_TYPE LED_STRIP_WS2812_RGB // Type of LED strip for strip 2 (WS2812, APA106, etc.) 19 | #define STRIP1_NUM_LEDS 10 // Number of LEDs in first strip 20 | #define STRIP2_NUM_LEDS 20 // Number of LEDs in second strip 21 | 22 | LiteLED strip1( STRIP1_LED_TYPE, false ); 23 | LiteLED strip2( STRIP2_LED_TYPE, false ); 24 | 25 | void setup() { 26 | Serial.begin( 115200 ); 27 | delay( 1000 ); 28 | 29 | Serial.println( "LiteLED Peripheral Manager Integration Test" ); 30 | Serial.println( "=============================================" ); 31 | 32 | // Show initial GPIO availability 33 | Serial.printf( "GPIO %d available: %s\n", STRIP1_GPIO_PIN, LiteLED::isGpioAvailable( STRIP1_GPIO_PIN ) ? "YES" : "NO" ); 34 | Serial.printf( "GPIO %d available: %s\n", STRIP2_GPIO_PIN, LiteLED::isGpioAvailable( STRIP2_GPIO_PIN ) ? "YES" : "NO" ); 35 | Serial.printf( "Active instances: %d\n", LiteLED::getActiveInstanceCount() ); 36 | 37 | Serial.printf( "\n--- Creating first strip on GPIO %d ---\n", STRIP1_GPIO_PIN ); 38 | esp_err_t result1 = strip1.begin( STRIP1_GPIO_PIN, STRIP1_NUM_LEDS ); 39 | if ( result1 == ESP_OK ) { 40 | Serial.println( "Strip 1 initialized successfully" ); 41 | } 42 | else { 43 | Serial.printf( "Strip 1 failed: %s\n", esp_err_to_name( result1 ) ); 44 | } 45 | 46 | Serial.printf( "GPIO %d available: %s\n", STRIP1_GPIO_PIN, LiteLED::isGpioAvailable( STRIP1_GPIO_PIN ) ? "YES" : "NO" ); 47 | Serial.printf( "Active instances: %d\n", LiteLED::getActiveInstanceCount() ); 48 | 49 | Serial.printf( "\n--- Attempting to create second strip on same GPIO %d ---\n", CONFLICT_TEST_PIN ); 50 | esp_err_t result2 = strip2.begin( CONFLICT_TEST_PIN, STRIP2_NUM_LEDS ); // This should fail 51 | if ( result2 == ESP_OK ) { 52 | Serial.println( "Strip 2 initialized successfully" ); 53 | } 54 | else { 55 | Serial.printf( "Strip 2 failed (expected): %s\n", esp_err_to_name( result2 ) ); 56 | } 57 | 58 | Serial.printf( "\n--- Creating second strip on GPIO %d ---\n", STRIP2_GPIO_PIN ); 59 | result2 = strip2.begin( STRIP2_GPIO_PIN, STRIP2_NUM_LEDS ); 60 | if ( result2 == ESP_OK ) { 61 | Serial.println( "Strip 2 initialized successfully" ); 62 | } 63 | else { 64 | Serial.printf( "Strip 2 failed: %s\n", esp_err_to_name( result2 ) ); 65 | } 66 | 67 | Serial.printf( "GPIO %d available: %s\n", STRIP1_GPIO_PIN, LiteLED::isGpioAvailable( STRIP1_GPIO_PIN ) ? "YES" : "NO" ); 68 | Serial.printf( "GPIO %d available: %s\n", STRIP2_GPIO_PIN, LiteLED::isGpioAvailable( STRIP2_GPIO_PIN ) ? "YES" : "NO" ); 69 | Serial.printf( "Active instances: %d\n", LiteLED::getActiveInstanceCount() ); 70 | 71 | Serial.println( "\n--- Testing strip operations ---" ); 72 | 73 | // Test strip 1 74 | if ( strip1.isValid() ) { 75 | Serial.println( "Strip 1 is valid, setting colors..." ); 76 | strip1.fill( {255, 0, 0}, false ); // Red 77 | strip1.show(); 78 | Serial.printf( "Strip 1 GPIO: %d\n", strip1.getGpioPin() ); 79 | } 80 | 81 | // Test strip 2 82 | if ( strip2.isValid() ) { 83 | Serial.println( "Strip 2 is valid, setting colors..." ); 84 | strip2.fill( {0, 255, 0}, false ); // Green 85 | strip2.show(); 86 | Serial.printf( "Strip 2 GPIO: %d\n", strip2.getGpioPin() ); 87 | } 88 | 89 | Serial.println( "\nSetup complete!" ); 90 | } 91 | 92 | void loop() { 93 | // Cycle colors on both strips if they're valid 94 | static uint32_t lastUpdate = 0; 95 | static uint8_t colorIndex = 0; 96 | 97 | if ( millis() - lastUpdate > 1000 ) { 98 | lastUpdate = millis(); 99 | 100 | rgb_t colors[] = { 101 | {255, 0, 0}, // Red 102 | {0, 255, 0}, // Green 103 | {0, 0, 255}, // Blue 104 | {255, 255, 0}, // Yellow 105 | {255, 0, 255}, // Magenta 106 | {0, 255, 255} // Cyan 107 | }; 108 | 109 | if ( strip1.isValid() ) { 110 | strip1.fill( colors[ colorIndex ], true ); 111 | } 112 | 113 | if ( strip2.isValid() ) { 114 | strip2.fill( colors[ ( colorIndex + 3 ) % 6 ], true ); // Offset colors 115 | } 116 | 117 | colorIndex = ( colorIndex + 1 ) % 6; 118 | 119 | // Periodically show status 120 | if ( colorIndex == 0 ) { 121 | Serial.printf( "Status - Active instances: %d, Strip1 valid: %s, Strip2 valid: %s\n", 122 | LiteLED::getActiveInstanceCount(), 123 | strip1.isValid() ? "YES" : "NO", 124 | strip2.isValid() ? "YES" : "NO" ); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/ll_registry.cpp: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Minimal Registry - Leverages Peripheral Manager 4 | 5 | Delegates GPIO tracking to the Peripheral Manager. 6 | We only maintain a minimal mapping needed for the deinit callback: 7 | RMT channel -> LiteLED instance pointer (for invalidation on GPIO reassignment) 8 | */ 9 | 10 | #include 11 | #include "ll_registry.h" 12 | #include "esp32-hal-log.h" 13 | #include 14 | 15 | // Minimal tracking: only RMT channel -> instance mapping for deinit callback 16 | typedef struct { 17 | rmt_channel_handle_t channel; 18 | LiteLED *instance; 19 | } ll_channel_map_t; 20 | 21 | static ll_channel_map_t g_channel_map[ LL_MAX_INSTANCES ]; 22 | static bool g_ll_periman_initialized = false; 23 | 24 | #if !CONFIG_DISABLE_HAL_LOCKS 25 | static SemaphoreHandle_t g_channel_map_mutex = NULL; 26 | #define CHANNEL_MAP_LOCK() \ 27 | do { \ 28 | if (g_channel_map_mutex != NULL) { \ 29 | while (xSemaphoreTake(g_channel_map_mutex, portMAX_DELAY) != pdPASS) {} \ 30 | } \ 31 | } while(0) 32 | #define CHANNEL_MAP_UNLOCK() \ 33 | do { \ 34 | if (g_channel_map_mutex != NULL) { \ 35 | xSemaphoreGive(g_channel_map_mutex); \ 36 | } \ 37 | } while(0) 38 | #else 39 | #define CHANNEL_MAP_LOCK() 40 | #define CHANNEL_MAP_UNLOCK() 41 | #endif 42 | 43 | // Initialize LiteLED's integration with Peripheral Manager 44 | esp_err_t ll_registry_init( void ) { 45 | if ( g_ll_periman_initialized ) { 46 | return ESP_OK; 47 | } 48 | 49 | // Initialize channel map 50 | memset( g_channel_map, 0, sizeof( g_channel_map ) ); 51 | 52 | #if !CONFIG_DISABLE_HAL_LOCKS 53 | g_channel_map_mutex = xSemaphoreCreateMutex(); 54 | if ( g_channel_map_mutex == NULL ) { 55 | log_d( "LiteLED: Failed to create channel map mutex" ); 56 | return ESP_ERR_NO_MEM; 57 | } 58 | #endif 59 | 60 | // Set flag BEFORE registering callback to prevent re-entry issues 61 | g_ll_periman_initialized = true; 62 | 63 | // Register our deinit callback with Peripheral Manager 64 | perimanSetBusDeinit( ESP32_BUS_TYPE_RMT_TX, ll_periman_deinit_callback ); 65 | 66 | log_d( "LiteLED: Peripheral Manager integration initialized" ); 67 | return ESP_OK; 68 | } 69 | 70 | // Internal: Register channel -> instance mapping (called from LiteLED::begin) 71 | esp_err_t ll_register_channel_instance( rmt_channel_handle_t channel, LiteLED* instance ) { 72 | if ( !channel || !instance ) { 73 | return ESP_ERR_INVALID_ARG; 74 | } 75 | 76 | // Ensure registry is initialized (registers deinit callback with Peripheral Manager) 77 | esp_err_t init_result = ll_registry_init(); 78 | if ( init_result != ESP_OK ) { 79 | return init_result; 80 | } 81 | 82 | CHANNEL_MAP_LOCK(); 83 | 84 | // Find empty slot 85 | for ( int i = 0; i < LL_MAX_INSTANCES; i++ ) { 86 | if ( g_channel_map[i].channel == NULL ) { 87 | g_channel_map[i].channel = channel; 88 | g_channel_map[i].instance = instance; 89 | CHANNEL_MAP_UNLOCK(); 90 | return ESP_OK; 91 | } 92 | } 93 | 94 | CHANNEL_MAP_UNLOCK(); 95 | log_d( "LiteLED: Channel map full" ); 96 | return ESP_ERR_NO_MEM; 97 | } 98 | 99 | // Internal: Unregister channel mapping (called from LiteLED::free) 100 | void ll_unregister_channel_instance( rmt_channel_handle_t channel ) { 101 | if ( !channel ) { 102 | return; 103 | } 104 | 105 | CHANNEL_MAP_LOCK(); 106 | 107 | for ( int i = 0; i < LL_MAX_INSTANCES; i++ ) { 108 | if ( g_channel_map[i].channel == channel ) { 109 | g_channel_map[i].channel = NULL; 110 | g_channel_map[i].instance = NULL; 111 | break; 112 | } 113 | } 114 | 115 | CHANNEL_MAP_UNLOCK(); 116 | } 117 | 118 | // Internal: Find instance by channel (for deinit callback) 119 | static LiteLED* ll_find_instance_by_channel( rmt_channel_handle_t channel ) { 120 | if ( !channel ) { 121 | return NULL; 122 | } 123 | 124 | CHANNEL_MAP_LOCK(); 125 | 126 | for ( int i = 0; i < LL_MAX_INSTANCES; i++ ) { 127 | if ( g_channel_map[i].channel == channel ) { 128 | LiteLED* instance = g_channel_map[i].instance; 129 | CHANNEL_MAP_UNLOCK(); 130 | return instance; 131 | } 132 | } 133 | 134 | CHANNEL_MAP_UNLOCK(); 135 | return NULL; 136 | } 137 | 138 | // Get LiteLED instance pointer from GPIO (queries periman) 139 | LiteLED* ll_get_instance_by_gpio( uint8_t gpio ) { 140 | // Check if pin is assigned to RMT_TX with LiteLED 141 | if ( perimanGetPinBusType( gpio ) != ESP32_BUS_TYPE_RMT_TX ) { 142 | return NULL; 143 | } 144 | 145 | // Check the extra_type to verify it's LiteLED 146 | const char* extra_type = perimanGetPinBusExtraType( gpio ); 147 | if ( extra_type == NULL || strcmp( extra_type, "LiteLED" ) != 0 ) { 148 | return NULL; 149 | } 150 | 151 | // Get the RMT channel handle from periman 152 | rmt_channel_handle_t channel = ( rmt_channel_handle_t )perimanGetPinBus( gpio, ESP32_BUS_TYPE_RMT_TX ); 153 | if ( !channel ) { 154 | return NULL; 155 | } 156 | 157 | // Look up instance in our minimal channel map 158 | return ll_find_instance_by_channel( channel ); 159 | } 160 | 161 | // Get count of active LiteLED instances (queries periman) 162 | uint8_t ll_registry_get_active_count( void ) { 163 | uint8_t count = 0; 164 | 165 | // Iterate through all possible GPIO pins 166 | for ( int gpio = 0; gpio < SOC_GPIO_PIN_COUNT; gpio++ ) { 167 | if ( perimanGetPinBusType( gpio ) == ESP32_BUS_TYPE_RMT_TX ) { 168 | const char* extra_type = perimanGetPinBusExtraType( gpio ); 169 | if ( extra_type != NULL && strcmp( extra_type, "LiteLED" ) == 0 ) { 170 | count++; 171 | } 172 | } 173 | } 174 | 175 | return count; 176 | } 177 | 178 | // Peripheral Manager deinit callback - called when GPIO is being reassigned 179 | bool ll_periman_deinit_callback( void *bus_handle ) { 180 | if ( !bus_handle ) { 181 | log_d( "LiteLED: Deinit callback called with NULL handle" ); 182 | return false; 183 | } 184 | 185 | rmt_channel_handle_t channel = ( rmt_channel_handle_t )bus_handle; 186 | 187 | // Find the LiteLED instance for this channel 188 | LiteLED* instance = ll_find_instance_by_channel( channel ); 189 | if ( !instance ) { 190 | // Return true to allow periman to proceed - this might be an RMT channel 191 | // that wasn't created by LiteLED (e.g., from Arduino's internal RMT usage) 192 | log_d( "LiteLED: Deinit callback: No LiteLED instance found for RMT channel %p", channel ); 193 | return true; 194 | } 195 | 196 | // Find which GPIO this channel is assigned to (for logging) 197 | uint8_t gpio = GPIO_NUM_NC; 198 | for ( int pin = 0; pin < SOC_GPIO_PIN_COUNT; pin++ ) { 199 | if ( perimanGetPinBusType( pin ) == ESP32_BUS_TYPE_RMT_TX ) { 200 | void* pin_bus_handle = perimanGetPinBus( pin, ESP32_BUS_TYPE_RMT_TX ); 201 | if ( pin_bus_handle == bus_handle ) { 202 | gpio = pin; 203 | break; 204 | } 205 | } 206 | } 207 | 208 | log_d( "LiteLED: GPIO %u being forcibly reassigned - invalidating instance %p", gpio, instance ); 209 | 210 | // Mark the LiteLED instance as invalid (prevents further operations) 211 | instance->invalidate(); 212 | 213 | // Remove from our channel map 214 | ll_unregister_channel_instance( channel ); 215 | 216 | // Note: The RMT channel cleanup will be handled by the peripheral that's 217 | // taking over the GPIO. We just mark our instance as invalid. 218 | 219 | log_d( "LiteLED: Cleanup completed for GPIO %u", gpio ); 220 | return true; 221 | } 222 | 223 | // --- EOF --- // 224 | -------------------------------------------------------------------------------- /src/ll_led_timings.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED LED Timing Definitions 4 | 5 | RMT symbol timing definitions for various LED strip types: 6 | - WS2812 / WS2812B 7 | - WS2812 RGB variant 8 | - APA106 9 | - SM16703 10 | - SK6812 11 | */ 12 | 13 | #ifndef __LL_LED_TIMINGS_H__ 14 | #define __LL_LED_TIMINGS_H__ 15 | 16 | #include "LiteLED.h" 17 | 18 | #define RMT_LED_STRIP_RESOLUTION_HZ 10000000 /* 10 MHz resolution, 1 tick = 0.1us (led strip needs a high resolution) */ 19 | 20 | // WS2812 / WS2812B timing (most common addressable LEDs) 21 | static const rmt_symbol_word_t ws2812_zero = { 22 | static_cast( 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T0H=0.3us 23 | 1, // .level0 24 | static_cast( 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T0L=0.9us 25 | 0 // .level1 26 | }; 27 | 28 | static const rmt_symbol_word_t ws2812_one = { 29 | static_cast( 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T1H=0.9us 30 | 1, // .level0 31 | static_cast( 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T1L=0.3us 32 | 0 // .level1 33 | }; 34 | 35 | static const rmt_symbol_word_t ws2812_reset = { /* WS2812 reset is 50uS */ 36 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2 ), //.duration0 = 25us 37 | 0, // .level0 38 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2 ), //.duration1 = 25us 39 | 0 // .level1 40 | }; 41 | 42 | // APA106 timing 43 | static const rmt_symbol_word_t apa106_zero = { 44 | static_cast( 0.35 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T0H=0.35us 45 | 1, // .level0 46 | static_cast( 1.36 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T0L=1.36us 47 | 0 // .level1 48 | }; 49 | 50 | static const rmt_symbol_word_t apa106_one = { 51 | static_cast( 1.36 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T1H=1.36us 52 | 1, // .level0 53 | static_cast( 0.35 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T1L=0.35us 54 | 0 // .level1 55 | }; 56 | 57 | static const rmt_symbol_word_t apa106_reset = { /* APA106 reset is 50uS */ 58 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2 ), //.duration0 = 25us 59 | 0, // .level0 60 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2 ), //.duration1 = 25us 61 | 0 // .level1 62 | }; 63 | 64 | // SM16703 timing 65 | static const rmt_symbol_word_t sm16703_zero = { 66 | static_cast( 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T0H=0.3us 67 | 1, // .level0 68 | static_cast( 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T0L=0.9us 69 | 0 // .level1 70 | }; 71 | 72 | static const rmt_symbol_word_t sm16703_one = { 73 | static_cast( 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T1H=0.9us 74 | 1, // .level0 75 | static_cast( 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T1L=0.3us 76 | 0 // .level1 77 | }; 78 | 79 | static const rmt_symbol_word_t sm16703_reset = { /* SM16703 reset is 210uS */ 80 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 210 / 2 ), //.duration0 = 105us 81 | 0, // .level0 82 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 210 / 2 ), //.duration1 = 105us 83 | 0 // .level1 84 | }; 85 | 86 | // SK6812 timing (RGBW LEDs) 87 | static const rmt_symbol_word_t sk6812_zero = { 88 | static_cast( 0.32 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T0H=0.32us 89 | 1, // .level0 90 | static_cast( 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T0L=0.9us 91 | 0 // .level1 92 | }; 93 | 94 | static const rmt_symbol_word_t sk6812_one = { 95 | static_cast( 0.64 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration0 = T1H=0.64us 96 | 1, // .level0 97 | static_cast( 0.4 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000 ), // .duration1 = T1L=0.4us 98 | 0 // .level1 99 | }; 100 | 101 | static const rmt_symbol_word_t sk6812_reset = { /* SK6812 reset is 90uS */ 102 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 90 / 2 ), //.duration0 = 45us 103 | 0, // .level0 104 | static_cast( RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 90 / 2 ), //.duration1 = 45us 105 | 0 // .level1 106 | }; 107 | 108 | // LED type parameter structures (combine timing + color order) 109 | static const led_params_t led_ws2812 = { 110 | .led_0 = ws2812_zero, 111 | .led_1 = ws2812_one, 112 | .led_reset = ws2812_reset, 113 | .order = ORDER_GRB // Standard WS2812 color order 114 | }; 115 | 116 | static const led_params_t led_ws2812_rgb = { 117 | /* Some "WS2812" LEDs have non-standard RGB colour order */ 118 | .led_0 = ws2812_zero, 119 | .led_1 = ws2812_one, 120 | .led_reset = ws2812_reset, 121 | .order = ORDER_RGB 122 | }; 123 | 124 | static const led_params_t led_apa106 = { 125 | .led_0 = apa106_zero, 126 | .led_1 = apa106_one, 127 | .led_reset = apa106_reset, 128 | .order = ORDER_RGB 129 | }; 130 | 131 | static const led_params_t led_sm16703 = { 132 | .led_0 = sm16703_zero, 133 | .led_1 = sm16703_one, 134 | .led_reset = sm16703_reset, 135 | .order = ORDER_RGB 136 | }; 137 | 138 | static const led_params_t led_sk6812 = { 139 | .led_0 = sk6812_zero, 140 | .led_1 = sm16703_one, 141 | .led_reset = sm16703_reset, 142 | .order = ORDER_GRB 143 | }; 144 | 145 | // LED parameters lookup table indexed by led_strip_type_t 146 | static const led_params_t led_params[] = { 147 | [ LED_STRIP_WS2812 ] = led_ws2812, 148 | [ LED_STRIP_WS2812_RGB ] = led_ws2812_rgb, 149 | [ LED_STRIP_SK6812 ] = led_sk6812, 150 | [ LED_STRIP_APA106 ] = led_apa106, 151 | [ LED_STRIP_SM16703 ] = led_sm16703 152 | }; 153 | 154 | // String names for LED types (for debugging) 155 | static const char *led_type[] = { 156 | "ws2812", 157 | "ws2812_rgb", 158 | "sk6812", 159 | "apa106", 160 | "sm16703" 161 | }; 162 | 163 | // String names for color orders (for debugging) 164 | static const char *col_ord[] = { 165 | "order_rgb", 166 | "order_rbg", 167 | "order_grb", 168 | "order_gbr", 169 | "order_brg", 170 | "order_bgr", 171 | "order_max" 172 | }; 173 | 174 | // Color order management 175 | static color_order_t custom_color_order = ORDER_GRB; 176 | static bool use_custom_color_order = false; 177 | 178 | #endif /* __LL_LED_TIMINGS_H__ */ 179 | 180 | // --- EOF --- // 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiteLED 2 | 3 | ## v3.0.1 4 | 5 | ## What is it? 6 | 7 | LiteLED is an arduino-esp32 library for the Espressif ESP32 series of SoC's for controlling WS2812B, SK6812, APA106 and SM16703 intelligent "clockless" RGB colour LED's. 8 | 9 | It provides hardware-accelerated LED control with support for driving multiple LED strips, arbitrary colour orders, DMA transfers, interrupt priority, and buffer allocation to either internal RAM or PSRAM. 10 | 11 | 12 | ## Features 13 | 14 | **Global brightness** 15 | 16 | - The intensity of all LED's in a strip be set at once. This is non-destructive to the colour value in the LED buffer. 17 | - Makes flashing the LED strip very easy. 18 | - Allows for auto dimming applications. 19 | 20 | 21 | **Works with WiFi** 22 | 23 | - When used with a dual core ESP32 SoC, LiteLED is compatible with concurrent use of the WiFi system. 24 | 25 | 26 | **Multi-Display Support** 27 | 28 | - Up to eight LED strips can be driven. 29 | 30 | **Configurable Color Order** 31 | 32 | - Though the RGB colour order for the supported LED types is supposed to be standard, some are not. The library lets you set any R, G, and B colour order for any LED type to manage those cases. No need to modify colour definitions in the sketch. 33 | 34 | **DMA Support** 35 | 36 | - On supported ESP32 models, can optionally use RMT DMA for driving the LED strip. 37 | 38 | **Interrupt Priority Support** 39 | 40 | - The priority of the routines that service the LED strip can be modified for performance tuning. 41 | 42 | **PSRAM Support** 43 | 44 | - The LED buffer can optionally be placed in PSRAM. 45 | 46 | **Robust Error Checking and Fallbacks** 47 | 48 | The library is quite fault tolerant, providing: 49 | 50 | - Automatic conflict resolution. 51 | - Intelligent interrupt priority management with fallback mechanisms. 52 | - Pre-flight conflict detection to prevent initialization failures. 53 | - Integration with ESP32 Peripheral Manager for safe GPIO usage. 54 | - Resource tracking and monitoring in multi-display applications. 55 | 56 | **Thread Safe** 57 | 58 | - Though not extensively tested, LiteLED should also be thread-safe. 59 | 60 | ## Compatibility 61 | 62 | ### SoC Models 63 | 64 | LiteLED has been tested on the following SoC's: 65 | 66 | * ESP32 67 | * ESP32-C3 68 | * ESP32-S2 69 | * ESP32-S3 70 | 71 | Would appreciate feedback on use of this library on other models. 72 | 73 | LiteLED uses the RMT peripheral of the ESP32 to send data to the LED strip and therefore the target device must have an RMT peripheral. As the family of ESP32 models grows, this is not always the case. 74 | 75 | Among SoC's that do have an RMT peripheral, the implementation also varies. This may place restrictions on the availability and combinations of configurations available when using the library. Note that these restrictions are fundamental to how the RMT peripheral is implemented in the SoC and are not a restriction of the library. 76 | 77 | ### Compatibility with arduino-esp32 Core Versions 78 | 79 | There have been a number of "interesting" changes with regard to how the RMT peripheral is supported across the arduino-esp32 core, starting with core version 3.0.0. 80 | 81 | Creating a single library that supports core versions from 3.0.0 to 3.0.7 proved to be a bit of a challenge so the decision was made to exclude a limited number of core releases from the 3.0.x stream. The TLDR version of this is LiteLED requires methods that are not available in the excluded versions. Fire up a note in the library GitHub Discussions page if you're curious. 82 | 83 | The table below summarizes compatibility with arduino-esp32 core versions from 2.0.0 onward. 84 | 85 | | **core version** | **Compatible
LiteLED
Version** | **Note** | 86 | |:----------------:|:------------------------------------:|:--------:| 87 | | < 2.0.3 | None | 1 | 88 | | 2.0.3 to 2.0.17 | 1.2.1 | | 89 | | 3.0.0 to 3.0.2 | None | 1 | 90 | | 3.0.3 to 3.0.7 | 1.2.1 | 2 | 91 | | 3.1.0 and later | 2.0.0 and greater | 3 | 92 | 93 | **Notes:** 94 | 95 | 1. Not compatible. 96 | 2. Requires the `-DESP32_ARDUINO_NO_RGB_BUILTIN` workaround discussed on the arduino-esp32 GitHub site [here](https://github.com/espressif/arduino-esp32/pull/9941) and as shown in the example [here](https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT/Legacy_RMT_Driver_Compatible). 97 | 3. Fully compatible. **But highly recommend using v3+**. 98 | 99 | ### Breaking Change 100 | 101 | As the underlying method of using the RMT peripheral was completely redone starting with arduino-esp32 core version 3.0.0, this has caused a breaking change in the method used to declare the LiteLED object starting at LiteLED version 2.0.0. 102 | 103 | ## API Document 104 | 105 | The API information has been moved to a new *Using LiteLED.md* document. 106 | 107 | It can be found in the same place as this README.md file. 108 | 109 | --- 110 | 111 | ## Acknowledgement 112 | 113 | A good chunk of LiteLED is based on the `led_strip` driver from the Uncle Rus [esp-idf-lib](https://github.com/UncleRus/esp-idf-lib). Full credit and recognition to the team that supplies and supports this incredible resource. 114 | 115 | Starting with library version 2, the RMT driver is based on the espressif-idf example found [here](https://github.com/espressif/esp-idf/tree/a6c3a9cb/examples/peripherals/rmt/led_strip_simple_encoder). 116 | 117 | And a tip of the hat to those who submitted PR's and suggestions for improvement. 118 | 119 | 120 | ## Support and Contributions 121 | 122 | For issues, feature requests, or contributions, please visit the library repository. 123 | 124 | --- 125 | 126 | ## Version History 127 | 128 | ### v3.0.1 129 | 130 | - Fix. Improve robustness of Peripheral Manager integration. 131 | 132 | ### v3.0.0 133 | 134 | - Feat: Add support for RMT DMA on compatible chips. 135 | - Feat: Add support for using PSRAM for the LED buffer. 136 | - Feat: Add ability to set the RMT interrupt priority where supported by the arduino-esp32 core. 137 | - Feat: Add integration with the arduino-esp32 Peripheral Manager. 138 | - Feat: Add multiple initialization options with `begin()` overloads 139 | - Feat: Add enhanced error handling and validation. 140 | - Feat: Add static utility methods for GPIO checking 141 | - Fix. Greatly improved handling of multiple strips. 142 | - Fix. Debug logging levels are a bit less aggressive. Refer to the *Logging* section in the *Using LiteLED.md* document. 143 | - Fix. The usual kind of fixes applied after spending too many late hours working on previous versions. 144 | - Fix: gobs of architectural changes to the files that make up the library. 145 | - Doc: Create separate *Using LiteLED.md* document. 146 | - Doc: Update this document. 147 | 148 | ### v2.0.2 149 | 150 | - Improved performance when concurrently driving multiple strips. 151 | - The `library.properties` file is correctly updated to reflect this version. Missed doing that last time so v2.0.1 was never found by most everyone. My bad. 152 | 153 | ### v2.0.1 154 | 155 | - Bug fix. Driving multiple strips now works as expected. 156 | 157 | ### v2.0.0 158 | 159 | - Significant rewrite bringing compatibility with arduino-esp32 core version 3.1+. Refer to the *Compatibility* section above. 160 | - **There is a breaking change with this release.** Refer to the *Breaking Change* section above. 161 | - Added new `begin()` method to: 162 | - support RMT DMA access if available in the ESP32 model. 163 | - set the interrupt priority for the RMT driver. 164 | - Added `fillRandom()` method to fill the strip with random colours. 165 | - Added `setOrder()` method to set a custom LED colour order. 166 | - Added `resetOrder()` method to reset the LED colour order to its default. 167 | - Improved error checking and reporting. 168 | 169 | ### v1.2.1 170 | 171 | - Bug fix: `setPixels()`, with `crgb_t` data, would not flush the buffer to the LED's when the parameter `show` was `true`. 172 | - Bug fix: `setPixels()`, with `rgb_t` data, would always start at `0` regardless of the `start` value provided. 173 | 174 | ### v1.2.0 175 | 176 | - add method to get the brightness value of the strip. 177 | 178 | ### v1.1.0 179 | 180 | - add methods to get the colour of a LED in the strip. 181 | 182 | ### v1.0.0 183 | - initial release. 184 | 185 | --- 186 | 187 | ## License 188 | 189 | LiteLED is provided under the terms of the MIT license: 190 | 191 | **MIT License** 192 | 193 | Permission is hereby granted, free of charge, to any person obtaining a copy 194 | of this software and associated documentation files (the "Software"), to deal 195 | in the Software without restriction, including without limitation the rights 196 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 197 | copies of the Software, and to permit persons to whom the Software is 198 | furnished to do so, subject to the following conditions: 199 | 200 | The above copyright notice and this permission notice shall be included in all 201 | copies or substantial portions of the Software. 202 | 203 | **THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 204 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 205 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 206 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 207 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 208 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 209 | SOFTWARE.** 210 | 211 | 212 | -------------------------------------------------------------------------------- /src/ll_strip_pixels.cpp: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Pixel Operations Implementation 4 | */ 5 | 6 | #include "ll_strip_pixels.h" 7 | #include 8 | 9 | esp_err_t led_strip_set_brightness( led_strip_t *strip, uint8_t num ) { 10 | /* Sets the intensity of all LED's in the strip */ 11 | if ( !( strip && strip->buf ) ) { 12 | log_d( "Error: Strip not initialized." ); 13 | return ESP_ERR_INVALID_ARG; 14 | } 15 | strip->brightness = num; 16 | return ESP_OK; 17 | } 18 | 19 | uint8_t led_strip_get_brightness( led_strip_t *strip ) { 20 | /* Returns the LED strip brightness value */ 21 | if ( !( strip && strip->buf ) ) { 22 | log_d( "Error: Strip not initialized." ); 23 | return 0; 24 | } 25 | return strip->bright_act; 26 | } 27 | 28 | esp_err_t led_strip_set_pixel( led_strip_t *strip, size_t num, rgb_t color ) { 29 | /* Sets the color of an individual LED in the strip as per the specified LED color order */ 30 | if ( !( strip && strip->buf && num <= strip->length ) ) { 31 | log_d( "Error: Strip not initialized or LED number out of bounds." ); 32 | return ESP_ERR_INVALID_ARG; 33 | } 34 | 35 | color_order_t order = use_custom_color_order ? custom_color_order : led_params[ strip->type ].order; 36 | size_t idx = num * COLOR_SIZE( strip ); 37 | 38 | switch ( order ) { 39 | case ORDER_RGB: 40 | strip->buf[ idx ] = color.r; 41 | strip->buf[ idx + 1 ] = color.g; 42 | strip->buf[ idx + 2 ] = color.b; 43 | if ( strip->is_rgbw ) { 44 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 45 | } 46 | break; 47 | case ORDER_RBG: 48 | strip->buf[ idx ] = color.r; 49 | strip->buf[ idx + 1 ] = color.b; 50 | strip->buf[ idx + 2 ] = color.g; 51 | if ( strip->is_rgbw ) { 52 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 53 | } 54 | break; 55 | case ORDER_GRB: 56 | strip->buf[ idx ] = color.g; 57 | strip->buf[ idx + 1 ] = color.r; 58 | strip->buf[ idx + 2 ] = color.b; 59 | if ( strip->is_rgbw ) { 60 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 61 | } 62 | break; 63 | case ORDER_GBR: 64 | strip->buf[ idx ] = color.g; 65 | strip->buf[ idx + 1 ] = color.b; 66 | strip->buf[ idx + 2 ] = color.r; 67 | if ( strip->is_rgbw ) { 68 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 69 | } 70 | break; 71 | case ORDER_BRG: 72 | strip->buf[ idx ] = color.b; 73 | strip->buf[ idx + 1 ] = color.r; 74 | strip->buf[ idx + 2 ] = color.g; 75 | if ( strip->is_rgbw ) { 76 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 77 | } 78 | break; 79 | case ORDER_BGR: 80 | strip->buf[ idx ] = color.b; 81 | strip->buf[ idx + 1 ] = color.g; 82 | strip->buf[ idx + 2 ] = color.r; 83 | if ( strip->is_rgbw ) { 84 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 85 | } 86 | break; 87 | default: 88 | /* Default is to set RGB colour order */ 89 | strip->buf[ idx ] = color.r; 90 | strip->buf[ idx + 1 ] = color.g; 91 | strip->buf[ idx + 2 ] = color.b; 92 | if ( strip->is_rgbw ) { 93 | strip->buf[ idx + 3 ] = strip->auto_w ? rgb_luma( color ) : 0; 94 | } 95 | log_d( "Error: Invalid color order specifier. Default RGB order set." ); 96 | return ESP_ERR_INVALID_ARG; 97 | } 98 | return ESP_OK; 99 | } 100 | 101 | rgb_t led_strip_get_pixel( led_strip_t *strip, size_t num ) { 102 | /* Gets the color value of a specified LED in the strip */ 103 | rgb_t res; 104 | if ( !( strip && strip->buf && num <= strip->length ) ) { 105 | log_d( "Error: Invalid argument or strip not initialized." ); 106 | res.r = res.g = res.b = 0; 107 | return res; 108 | } 109 | 110 | size_t idx = num * COLOR_SIZE( strip ); 111 | color_order_t order = use_custom_color_order ? custom_color_order : led_params[ strip->type ].order; 112 | 113 | switch ( order ) { 114 | case ORDER_RGB: 115 | res.r = strip->buf[ idx ]; 116 | res.g = strip->buf[ idx + 1 ]; 117 | res.b = strip->buf[ idx + 2 ]; 118 | break; 119 | case ORDER_RBG: 120 | res.r = strip->buf[ idx ]; 121 | res.b = strip->buf[ idx + 1 ]; 122 | res.g = strip->buf[ idx + 2 ]; 123 | break; 124 | case ORDER_GRB: 125 | res.g = strip->buf[ idx ]; 126 | res.r = strip->buf[ idx + 1 ]; 127 | res.b = strip->buf[ idx + 2 ]; 128 | break; 129 | case ORDER_GBR: 130 | res.g = strip->buf[ idx ]; 131 | res.b = strip->buf[ idx + 1 ]; 132 | res.r = strip->buf[ idx + 2 ]; 133 | break; 134 | case ORDER_BRG: 135 | res.b = strip->buf[ idx ]; 136 | res.r = strip->buf[ idx + 1 ]; 137 | res.g = strip->buf[ idx + 2 ]; 138 | break; 139 | case ORDER_BGR: 140 | res.b = strip->buf[ idx ]; 141 | res.g = strip->buf[ idx + 1 ]; 142 | res.r = strip->buf[ idx + 2 ]; 143 | break; 144 | default: 145 | log_d( "Error: Invalid colour order specifier. Defaults used." ); 146 | res.r = res.g = res.b = 0; 147 | } 148 | return res; 149 | } 150 | 151 | esp_err_t led_strip_set_pixels( led_strip_t *strip, size_t start, size_t len, rgb_t* data ) { 152 | /* Sets a range of pixels to colors defined in a buffer */ 153 | if ( !( strip && strip->buf && len && start + len <= strip->length && data ) ) { 154 | log_d( "Error: Strip not initialized or LED number out of bounds." ); 155 | return ESP_ERR_INVALID_ARG; 156 | } 157 | for ( size_t i = 0; i < len; i++ ) { 158 | if ( esp_err_t res = ( led_strip_set_pixel( strip, i + start, data[ i ] ) ) != ESP_OK ) { 159 | log_d( "Error: Failed to set pixel %d - %s.", i, esp_err_to_name( res ) ); 160 | return res; 161 | } 162 | } 163 | return ESP_OK; 164 | } 165 | 166 | esp_err_t led_strip_set_pixels_c( led_strip_t *strip, size_t start, size_t len, crgb_t* data ) { 167 | /* Sets a range of pixels to colors defined in a buffer (color code format) */ 168 | if ( !( strip && strip->buf && len && start + len <= strip->length ) ) { 169 | log_d( "Error: Strip not initialized or LED number out of bounds." ); 170 | return ESP_ERR_INVALID_ARG; 171 | } 172 | for ( size_t i = 0; i < len; i++ ) { 173 | if ( esp_err_t res = led_strip_set_pixel( strip, i + start, rgb_from_code( data[ i ] ) ) != ESP_OK ) { 174 | log_d( "Error: Failed to set pixel %d - %s.", i, esp_err_to_name( res ) ); 175 | return res; 176 | } 177 | } 178 | return ESP_OK; 179 | } 180 | 181 | esp_err_t led_strip_fill( led_strip_t *strip, rgb_t color ) { 182 | /* Sets all LED's in the strip to a single color */ 183 | if ( !( strip && strip->buf ) ) { 184 | log_d( "Error: Strip not initialized." ); 185 | return ESP_ERR_INVALID_ARG; 186 | } 187 | size_t num_pixels = strip->length; 188 | esp_err_t res = ESP_OK; 189 | for ( size_t i = 0; i < num_pixels; i++ ) { 190 | res = led_strip_set_pixel( strip, i, color ); 191 | if ( res != ESP_OK ) { 192 | log_d( "Error: Failed to set pixel %d - %s.", i, esp_err_to_name( res ) ); 193 | break; 194 | } 195 | } 196 | return res; 197 | } 198 | 199 | esp_err_t led_strip_fill_random( led_strip_t *strip ) { 200 | /* Fills the LED strip with random colors */ 201 | if ( !( strip && strip->buf ) ) { 202 | log_d( "Error: Strip not initialized." ); 203 | return ESP_ERR_INVALID_ARG; 204 | } 205 | esp_err_t res = ESP_OK; 206 | for ( size_t i = 0; i < strip->length; i++ ) { 207 | res = led_strip_set_pixel( strip, i, rgb_from_code( ( esp_random() & 0xFFFFFF ) ) ); 208 | if ( res != ESP_OK ) { 209 | log_d( "Error: Failed to set pixel %d - %s.", i, esp_err_to_name( res ) ); 210 | return res; 211 | } 212 | } 213 | return ESP_OK; 214 | } 215 | 216 | esp_err_t led_strip_clear( led_strip_t *strip, size_t num_bytes ) { 217 | /* Sets the color of all LED's in the strip to black */ 218 | if ( !( strip && num_bytes <= PIXEL_SIZE( strip ) ) ) { 219 | log_d( "Error: Strip not initialized or buffer size out of bounds." ); 220 | return ESP_ERR_INVALID_ARG; 221 | } 222 | memset( strip->buf, 0, num_bytes ); 223 | return ESP_OK; 224 | } 225 | 226 | esp_err_t led_strip_set_color_order( led_strip_t *strip, color_order_t led_order ) { 227 | /* Sets the color order of the LED's in the strip */ 228 | custom_color_order = led_order; 229 | use_custom_color_order = true; 230 | log_d( "Setting a custom color order to: %s.", col_ord[ custom_color_order ] ); 231 | log_d( "Custom color order now set to: %s.", use_custom_color_order ? "true" : "false" ); 232 | return ESP_OK; 233 | } 234 | 235 | esp_err_t led_strip_set_default_color_order( led_strip_t *strip ) { 236 | /* Sets the color order of the LED's in the strip to the value defined by the LED type parameters */ 237 | custom_color_order = led_params[ strip->type ].order; 238 | use_custom_color_order = false; 239 | log_d( "Setting the color order to its default: %s.", col_ord[ custom_color_order ] ); 240 | log_d( "Custom color order now set to: %s.", use_custom_color_order ? "true" : "false" ); 241 | return ESP_OK; 242 | } 243 | 244 | // --- EOF --- // 245 | -------------------------------------------------------------------------------- /src/LiteLED.h: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | ESP32 RMT-based driver for various types of RGB LED strips 4 | */ 5 | 6 | #ifndef __LITELED_H__ 7 | #define __LITELED_H__ 8 | 9 | #include 10 | 11 | // check for ESP32 12 | static_assert( ARDUINO_ARCH_ESP32, "LiteLED: This library requires an ESP32 family microcontroller." ); 13 | 14 | // check for arduino-esp32 core compatibility 15 | static_assert( !( ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL( 2, 0, 3 ) || 16 | ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL( 3, 0, 0 ) && 17 | ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL( 3, 0, 2 ) ) ), 18 | "LiteLED: This library is not compatible with this version of the arduino-esp32 core. See the library documentation for options." ); 19 | 20 | static_assert( !( ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL( 2, 0, 3 ) && ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL( 2, 0, 17 ) ) || 21 | ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL( 3, 0, 3 ) && ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL( 3, 1, 0 ) ) ), 22 | "LiteLED: LiteLED version 1.2.1 is required for this version of the arduino-esp32 core. See the library documentation for options." ); 23 | 24 | // check for RMT support 25 | #ifndef SOC_RMT_SUPPORTED 26 | #define SOC_RMT_SUPPORTED 0 27 | #endif 28 | static_assert( SOC_RMT_SUPPORTED, "LiteLED: Use of this library requires an ESP32 with an RMT peripheral." ); 29 | 30 | // check if the RMT supports DMA 31 | #ifdef SOC_RMT_SUPPORT_DMA 32 | #define LL_DMA_SUPPORT SOC_RMT_SUPPORT_DMA 33 | #else 34 | #define LL_DMA_SUPPORT 0 35 | #warning "LiteLED: Selected ESP32 model does not support RMT DMA access. Use of RMT DMA will be disabled." 36 | #endif 37 | 38 | // check for RMT interrupt priority support 39 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 5, 1,2 ) 40 | #define LL_INT_PRIORITY_SUPPORT 1 41 | #else 42 | #define LL_INT_PRIORITY_SUPPORT 0 43 | #warning "LiteLED: This version of the core does not support setting of RMT interrupt priority. Default will be used." 44 | #endif 45 | 46 | #include "driver/rmt_tx.h" 47 | #include "llrgb.h" 48 | 49 | // Forward declaration for C linkage 50 | #ifdef __cplusplus 51 | extern "C" { 52 | #endif 53 | #include "esp32-hal-periman.h" 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | 58 | enum led_strip_type_t { 59 | /* note: if this enum is modified, must also 60 | change led_type[] in 'llrmt.h' to match */ 61 | LED_STRIP_WS2812 = 0, 62 | LED_STRIP_WS2812_RGB, 63 | LED_STRIP_SK6812, 64 | LED_STRIP_APA106, 65 | LED_STRIP_SM16703, 66 | LED_STRIP_TYPE_MAX 67 | }; 68 | 69 | enum color_order_t { 70 | /* note: if this enum is modified, must also 71 | change col_ord[] in 'llrmt.h' to match */ 72 | ORDER_RGB = 0, 73 | ORDER_RBG, 74 | ORDER_GRB, 75 | ORDER_GBR, 76 | ORDER_BRG, 77 | ORDER_BGR, 78 | ORDER_MAX // not a valid colour order, used to mark the end of the enum 79 | }; 80 | 81 | typedef struct { 82 | rmt_tx_channel_config_t led_chan_config; /* RMT channel configuration for the LED strip. */ 83 | rmt_transmit_config_t led_tx_config; /* RMT transmit configuration */ 84 | rmt_channel_handle_t led_chan = NULL; /* RMT channel allocated by the RMT driver */ 85 | rmt_simple_encoder_config_t led_encoder_cfg; /* RMT encoder configuration */ 86 | rmt_encoder_handle_t led_encoder = NULL; /* RMT encoder handle */ 87 | size_t enc_pos; /* position in the LED data buffer */ 88 | } led_strip_cfg_t; 89 | 90 | typedef struct { 91 | rmt_symbol_word_t led_0; 92 | rmt_symbol_word_t led_1; 93 | rmt_symbol_word_t led_reset; 94 | color_order_t order; 95 | } led_params_t; 96 | 97 | typedef struct { 98 | uint8_t *buf; 99 | size_t length; 100 | uint8_t brightness; 101 | uint8_t bright_act; 102 | uint8_t gpio; 103 | uint8_t type; 104 | bool is_rgbw; 105 | bool auto_w; 106 | bool use_psram; 107 | led_strip_cfg_t stripCfg; 108 | } led_strip_t; 109 | 110 | 111 | // defines for setting the led encoder DMA usage 112 | enum ll_dma_t : uint32_t { 113 | DMA_ON = 1, 114 | DMA_OFF = 0, 115 | DMA_DEFAULT = DMA_OFF // Default is OFF to preserve DMA channels for user applications 116 | }; 117 | 118 | // defines for setting the led encoder callback interrupt priority level 119 | enum ll_priority_t : int { 120 | PRIORITY_DEFAULT = 0, 121 | PRIORITY_HIGH = 1, 122 | PRIORITY_MED = 2, 123 | PRIORITY_LOW = 3 124 | }; 125 | 126 | // defines for PSRAM buffer allocation preference 127 | enum ll_psram_t : uint32_t { 128 | PSRAM_ENABLE = 1, 129 | PSRAM_DISABLE = 0, 130 | PSRAM_AUTO = 2 // Automatically use PSRAM if available 131 | }; 132 | 133 | // Inline helper functions for capability checking 134 | namespace LiteLED_Utils { 135 | // Check if DMA is supported on this chip at compile time 136 | constexpr bool isDmaSupported() { 137 | return LL_DMA_SUPPORT != 0; 138 | } 139 | 140 | // Check if interrupt priority setting is supported at compile time 141 | constexpr bool isPrioritySupported() { 142 | return LL_INT_PRIORITY_SUPPORT != 0; 143 | } 144 | } 145 | 146 | class LiteLED { 147 | public: 148 | // @brief Class constructor. Set the LED parameters for the RMT driver 149 | // @param led_type Enumerated value for the type of LED's in the strip 150 | // @param rgbw Set true if the strip is RGBW type 151 | // @return 'ESP_OK' on success 152 | LiteLED( led_strip_type_t led_type, bool rgbw ); 153 | ~LiteLED(); 154 | 155 | // @brief Initialize the strip 156 | // @param data_pin GPIO pin connected to the DIN pin of the strip 157 | // @param length Number of LED's in the strip 158 | // @param auto_w Optional. Only used for RGBW strips. Set false to not use the automatic W channel value set by the library 159 | // @return 'ESP_OK' on success 160 | esp_err_t begin( uint8_t data_pin, size_t length, bool auto_w = true ); 161 | 162 | // @brief Initialize the strip with PSRAM option 163 | // @param data_pin GPIO pin connected to the DIN pin of the strip 164 | // @param length Number of LED's in the strip 165 | // @param psram_flag Enumerated value that sets the PSRAM usage preference for the LED buffer 166 | // @param auto_w Optional. Only used for RGBW strips. Set false to not use the automatic W channel value set by the library 167 | // @return 'ESP_OK' on success 168 | esp_err_t begin( uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true ); 169 | 170 | // @brief Initialize the strip with DMA, interrupt priority and PSRAM options 171 | // @param data_pin GPIO pin connected to the DIN pin of the strip 172 | // @param length Number of LED's in the strip 173 | // @param dma_flag Enumerated value that sets the DMA usage of the led encoder 174 | // @param priority Enumerated value that sets the interrupt priority of led encoder callback 175 | // @param psram_flag Enumerated value that sets the PSRAM usage preference for the LED buffer 176 | // @param auto_w Optional. Only used for RGBW strips. Set false to not use the automatic W channel value set by the library 177 | // @return 'ESP_OK' on success 178 | esp_err_t begin( uint8_t data_pin, size_t length, ll_dma_t dma_flag, ll_priority_t priority, ll_psram_t psram_flag, bool auto_w = true ); 179 | 180 | // @brief Flush the the LED buffer to the strip 181 | esp_err_t show(); 182 | 183 | // @brief Set color of single LED in strip, optionally flush the buffer to the strip 184 | // @param num Position of the LED in the strip, 0-based 185 | // @param color rgb_t or crgb_t Color to set the LED to 186 | // @param show Optional. Set true to flush the buffer to the strip before returning 187 | // @return 'ESP_OK' on success 188 | esp_err_t setPixel( size_t num, rgb_t color, bool show = false ); 189 | esp_err_t setPixel( size_t num, crgb_t color, bool show = false ); 190 | 191 | // @brief Set colors of multiple consecutive LEDs, optionally flush the buffer to the strip 192 | // @param start First LED index, 0-based 193 | // @param len The number of consecutive LEDs in the strip to which we are writing 194 | // @param data Pointer to data. Layout must match the color type 195 | // @param show Optional. Set true to flush the buffer to the strip before returning 196 | // @return 'ESP_OK' on success 197 | esp_err_t setPixels( size_t start, size_t len, rgb_t *data, bool show = false ); 198 | esp_err_t setPixels( size_t start, size_t len, crgb_t *data, bool show = false ); 199 | 200 | // @brief Set the entire strip to a color, optionally flush the buffer to the LEDs 201 | // @param color rgb_t or crgb_t Colour value to set the strip to 202 | // @param show Optional. Set true to flush the buffer to the strip before returning 203 | // @return 'ESP_OK' on success 204 | esp_err_t fill( rgb_t color, bool show = false ); 205 | esp_err_t fill( crgb_t color, bool show = false ); 206 | 207 | // @brief Clear the strip buffer, optionally flush the buffer to the strip 208 | // @param show Optional. Set true to flush the buffer to the strip before returning 209 | // @return 'ESP_OK' on success 210 | esp_err_t clear( bool show = 0 ); 211 | 212 | // @brief Set the intensity of the LEDs, optionally flush the buffer to the strip 213 | // @param bright Brightness value, 0-255 214 | // @param show Optional. Set true to set strip intensity to 'bright' before returning 215 | // @return 'ESP_OK' on success 216 | esp_err_t brightness( uint8_t bright, bool show = false ); 217 | 218 | // @brief Get the intensity value of the LEDs 219 | // @return The 'bright' value of the strip 220 | uint8_t getBrightness(); 221 | 222 | // @brief Get, in rgb_t format, the color of a single LED in the strip 223 | // @param num Position of the LED in the strip, 0-based 224 | // @return The rgb_t color value of the LED 225 | rgb_t getPixel( size_t num ); 226 | 227 | // @brief Get, in crgb_t format, the color of a single LED in strip 228 | // @param num Position of the LED in the strip, 0-based 229 | // @return The crgb_t color value of the LED 230 | crgb_t getPixelC( size_t num ); 231 | 232 | // @brief Fill the strip buffer with random colors, optionally flush the buffer to the strip 233 | // @param show Optional. Set true to flush the buffer to the strip before returning. False if ommited. 234 | // @return 'ESP_OK' on success 235 | esp_err_t fillRandom( bool show = false ); 236 | 237 | // @brief Set a custom order of the LED colors 238 | // @param led_order. Enumerated value of the color order of LED's in the strip. 239 | // @return 'ESP_OK' on success 240 | esp_err_t setOrder( color_order_t led_order = ORDER_GRB ); 241 | 242 | // @brief Reset the color order of the LED's in the strip to its default value 243 | // @param None. 244 | // @return 'ESP_OK' on success 245 | esp_err_t resetOrder(); 246 | 247 | // @brief Check if this LiteLED instance is still valid 248 | // @return true if the instance is valid and can be used, false if pin was reassigned 249 | bool isValid() const; 250 | 251 | // @brief Get the GPIO pin number used by this instance 252 | // @return GPIO pin number, or -1 if not initialized 253 | int getGpioPin() const { 254 | return theStrip.gpio >= 0 ? ( int )theStrip.gpio : -1; 255 | } 256 | 257 | // @brief Static method to check if a GPIO is available for LiteLED use 258 | // @param gpio_pin GPIO pin number to check 259 | // @return true if available, false if in use by another peripheral 260 | static bool isGpioAvailable( uint8_t gpio_pin ); 261 | 262 | // @brief Static method to get count of active LiteLED instances 263 | // @return Number of currently active LiteLED instances 264 | static uint8_t getActiveInstanceCount(); 265 | 266 | // @brief Invalidate this instance (called by registry on forced cleanup) 267 | void invalidate() { 268 | valid_instance = false; 269 | } 270 | 271 | private: 272 | led_strip_t theStrip; // LED strip object for this class 273 | bool valid_instance; // Fast validity check flag 274 | esp_err_t free(); 275 | 276 | // @brief Check and handle potential pin conflicts before operations 277 | // @return ESP_OK if safe to proceed, error code otherwise 278 | inline esp_err_t ll_checkPinState() const { 279 | return valid_instance ? ESP_OK : ESP_ERR_INVALID_STATE; 280 | } 281 | 282 | }; // class LiteLED 283 | 284 | #endif /* __LITELED_H__ */ 285 | 286 | // --- EOF --- // 287 | -------------------------------------------------------------------------------- /src/LiteLED.cpp: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | ESP32 RMT-based driver for various types of RGB LED strips 4 | */ 5 | 6 | #include 7 | #include "driver/rmt_tx.h" 8 | #include "LiteLED.h" 9 | #include "llrmt.h" 10 | #include "ll_registry.h" 11 | 12 | // Static flag to print capability info only once 13 | static bool ll_capability_logged = false; 14 | 15 | // Log hardware capabilities once during first initialization 16 | static void ll_log_capabilities() { 17 | if ( !ll_capability_logged ) { 18 | ll_capability_logged = true; 19 | #if !LL_DMA_SUPPORT 20 | log_d( "LiteLED: RMT DMA not supported on this ESP32 model (will use non-DMA mode)" ); 21 | #endif 22 | #if !LL_INT_PRIORITY_SUPPORT 23 | log_d( "LiteLED: RMT interrupt priority setting not supported in this core version (will use default priority)" ); 24 | #endif 25 | } 26 | } 27 | 28 | // constructor 29 | LiteLED::LiteLED( led_strip_type_t led_type, bool rgbw ) { 30 | // populate the LED strip type structure 31 | theStrip.type = led_type; 32 | theStrip.is_rgbw = rgbw; 33 | theStrip.brightness = 255; 34 | theStrip.bright_act = 255; 35 | theStrip.buf = NULL; 36 | theStrip.use_psram = false; // Default to internal RAM 37 | valid_instance = false; // Not valid until begin() succeeds 38 | } 39 | 40 | // destructor 41 | LiteLED::~LiteLED() { 42 | // delete all resources used by the strip 43 | LiteLED::free(); 44 | } 45 | 46 | esp_err_t LiteLED::begin( uint8_t data_pin, size_t length, bool auto_w ) { 47 | /* add the data pin & length to the structure */ 48 | ll_log_capabilities(); // Log hardware capabilities once 49 | 50 | // If already initialized, clean up first to prevent resource leaks 51 | if ( valid_instance ) { 52 | log_d( "LiteLED: Instance already initialized, cleaning up before reinitializing" ); 53 | free(); 54 | } 55 | 56 | theStrip.gpio = ( gpio_num_t )data_pin; 57 | theStrip.length = length; 58 | theStrip.auto_w = auto_w; 59 | theStrip.use_psram = false; // Default to internal RAM for simple begin() 60 | 61 | // Check if pin is valid for Peripheral Manager 62 | if ( !perimanPinIsValid( data_pin ) ) { 63 | log_d( "LiteLED: GPIO %u is not valid", data_pin ); 64 | return ESP_ERR_INVALID_ARG; 65 | } 66 | 67 | // Check current pin usage 68 | peripheral_bus_type_t current_type = perimanGetPinBusType( data_pin ); 69 | if ( current_type != ESP32_BUS_TYPE_INIT ) { 70 | const char *current_usage = perimanGetTypeName( current_type ); 71 | log_d( "LiteLED: GPIO %u is already in use by %s", data_pin, current_usage ); 72 | return ESP_ERR_INVALID_STATE; 73 | } 74 | 75 | esp_err_t res = led_strip_init( &theStrip ); 76 | if ( res != ESP_OK ) { 77 | log_d( "Failed to initialize strip. Result = %s", esp_err_to_name( res ) ); 78 | return res; 79 | } 80 | res = led_strip_install( &theStrip ); 81 | if ( res != ESP_OK ) { 82 | log_d( "Failed to install strip. Result = %s", esp_err_to_name( res ) ); 83 | return res; 84 | } 85 | 86 | // Register channel -> instance mapping BEFORE registering with Peripheral Manager 87 | // This ensures the deinit callback can find the instance if triggered 88 | res = ll_register_channel_instance( theStrip.stripCfg.led_chan, this ); 89 | if ( res != ESP_OK ) { 90 | log_d( "LiteLED: Failed to register channel instance" ); 91 | led_strip_free( &theStrip ); 92 | return res; 93 | } 94 | 95 | // Register with Peripheral Manager 96 | if ( !perimanSetPinBus( data_pin, ESP32_BUS_TYPE_RMT_TX, ( void * )theStrip.stripCfg.led_chan, -1, -1 ) ) { 97 | log_d( "LiteLED: Failed to register GPIO %u with Peripheral Manager", data_pin ); 98 | ll_unregister_channel_instance( theStrip.stripCfg.led_chan ); 99 | led_strip_free( &theStrip ); 100 | return ESP_ERR_INVALID_STATE; 101 | } 102 | 103 | // Set extra type identifier 104 | perimanSetPinBusExtraType( data_pin, "LiteLED" ); 105 | 106 | valid_instance = true; // Mark instance as valid 107 | return ESP_OK; 108 | } 109 | 110 | esp_err_t LiteLED::begin( uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w ) { 111 | /* add the data pin & length to the structure */ 112 | ll_log_capabilities(); // Log hardware capabilities once 113 | 114 | // If already initialized, clean up first to prevent resource leaks 115 | if ( valid_instance ) { 116 | log_d( "LiteLED: Instance already initialized, cleaning up before reinitializing" ); 117 | free(); 118 | } 119 | 120 | theStrip.gpio = ( gpio_num_t )data_pin; 121 | theStrip.length = length; 122 | theStrip.auto_w = auto_w; 123 | 124 | // Determine PSRAM usage 125 | if ( psram_flag == PSRAM_AUTO ) { 126 | // Auto mode: use PSRAM if available 127 | #if CONFIG_SPIRAM 128 | theStrip.use_psram = psramFound(); 129 | #else 130 | theStrip.use_psram = false; 131 | #endif 132 | } 133 | else { 134 | theStrip.use_psram = ( psram_flag == PSRAM_ENABLE ); 135 | } 136 | 137 | // Check if pin is valid for Peripheral Manager 138 | if ( !perimanPinIsValid( data_pin ) ) { 139 | log_d( "LiteLED: GPIO %u is not valid", data_pin ); 140 | return ESP_ERR_INVALID_ARG; 141 | } 142 | 143 | // Check current pin usage 144 | peripheral_bus_type_t current_type = perimanGetPinBusType( data_pin ); 145 | if ( current_type != ESP32_BUS_TYPE_INIT ) { 146 | const char *current_usage = perimanGetTypeName( current_type ); 147 | log_d( "LiteLED: GPIO %u is already in use by %s", data_pin, current_usage ); 148 | return ESP_ERR_INVALID_STATE; 149 | } 150 | 151 | esp_err_t res = led_strip_init( &theStrip ); 152 | if ( res != ESP_OK ) { 153 | log_d( "Failed to initialize strip. Result = %s", esp_err_to_name( res ) ); 154 | return res; 155 | } 156 | 157 | res = led_strip_install( &theStrip ); 158 | if ( res != ESP_OK ) { 159 | log_d( "Failed to install strip. Result = %s", esp_err_to_name( res ) ); 160 | return res; 161 | } 162 | 163 | // Register channel -> instance mapping BEFORE registering with Peripheral Manager 164 | // This ensures the deinit callback can find the instance if triggered 165 | res = ll_register_channel_instance( theStrip.stripCfg.led_chan, this ); 166 | if ( res != ESP_OK ) { 167 | log_d( "LiteLED: Failed to register channel instance" ); 168 | led_strip_free( &theStrip ); 169 | return res; 170 | } 171 | 172 | // Register with Peripheral Manager 173 | if ( !perimanSetPinBus( data_pin, ESP32_BUS_TYPE_RMT_TX, ( void * )theStrip.stripCfg.led_chan, -1, -1 ) ) { 174 | log_d( "LiteLED: Failed to register GPIO %u with Peripheral Manager", data_pin ); 175 | ll_unregister_channel_instance( theStrip.stripCfg.led_chan ); 176 | led_strip_free( &theStrip ); 177 | return ESP_ERR_INVALID_STATE; 178 | } 179 | 180 | // Set extra type identifier 181 | perimanSetPinBusExtraType( data_pin, "LiteLED" ); 182 | 183 | valid_instance = true; // Mark instance as valid 184 | return ESP_OK; 185 | } 186 | 187 | esp_err_t LiteLED::begin( uint8_t data_pin, size_t length, ll_dma_t dma_flag, ll_priority_t priority, ll_psram_t psram_flag, bool auto_w ) { 188 | /* add the data pin & length to the structure */ 189 | ll_log_capabilities(); // Log hardware capabilities once 190 | 191 | // If already initialized, clean up first to prevent resource leaks 192 | if ( valid_instance ) { 193 | log_d( "LiteLED: Instance already initialized, cleaning up before reinitializing" ); 194 | free(); 195 | } 196 | 197 | theStrip.gpio = ( gpio_num_t )data_pin; 198 | theStrip.length = length; 199 | theStrip.auto_w = auto_w; 200 | 201 | // Determine PSRAM usage 202 | if ( psram_flag == PSRAM_AUTO ) { 203 | // Auto mode: use PSRAM if available 204 | #if CONFIG_SPIRAM 205 | theStrip.use_psram = psramFound(); 206 | #else 207 | theStrip.use_psram = false; 208 | #endif 209 | } 210 | else { 211 | theStrip.use_psram = ( psram_flag == PSRAM_ENABLE ); 212 | } 213 | 214 | // Check if pin is valid for Peripheral Manager 215 | if ( !perimanPinIsValid( data_pin ) ) { 216 | log_d( "LiteLED: GPIO %u is not valid", data_pin ); 217 | return ESP_ERR_INVALID_ARG; 218 | } 219 | 220 | // Check current pin usage 221 | peripheral_bus_type_t current_type = perimanGetPinBusType( data_pin ); 222 | if ( current_type != ESP32_BUS_TYPE_INIT ) { 223 | const char *current_usage = perimanGetTypeName( current_type ); 224 | log_d( "LiteLED: GPIO %u is already in use by %s", data_pin, current_usage ); 225 | return ESP_ERR_INVALID_STATE; 226 | } 227 | 228 | esp_err_t res = led_strip_init( &theStrip ); 229 | if ( res != ESP_OK ) { 230 | log_d( "Failed to initialize strip. Result = %s", esp_err_to_name( res ) ); 231 | return res; 232 | } 233 | res = led_strip_init_modify( &theStrip, dma_flag, priority ); 234 | if ( res != ESP_OK ) { 235 | log_d( "Failed to set strip DMA or interrupt priority. Result = %s", esp_err_to_name( res ) ); 236 | return res; 237 | } 238 | res = led_strip_install( &theStrip ); 239 | if ( res != ESP_OK ) { 240 | log_d( "Failed to install strip. Result = %s", esp_err_to_name( res ) ); 241 | return res; 242 | } 243 | 244 | // Register channel -> instance mapping BEFORE registering with Peripheral Manager 245 | // This ensures the deinit callback can find the instance if triggered 246 | res = ll_register_channel_instance( theStrip.stripCfg.led_chan, this ); 247 | if ( res != ESP_OK ) { 248 | log_d( "LiteLED: Failed to register channel instance" ); 249 | led_strip_free( &theStrip ); 250 | return res; 251 | } 252 | 253 | // Register with Peripheral Manager 254 | if ( !perimanSetPinBus( data_pin, ESP32_BUS_TYPE_RMT_TX, ( void * )theStrip.stripCfg.led_chan, -1, -1 ) ) { 255 | log_d( "LiteLED: Failed to register GPIO %u with Peripheral Manager", data_pin ); 256 | ll_unregister_channel_instance( theStrip.stripCfg.led_chan ); 257 | led_strip_free( &theStrip ); 258 | return ESP_ERR_INVALID_STATE; 259 | } 260 | 261 | // Set extra type identifier 262 | perimanSetPinBusExtraType( data_pin, "LiteLED" ); 263 | 264 | valid_instance = true; // Mark instance as valid 265 | return ESP_OK; 266 | } 267 | 268 | esp_err_t LiteLED::show() { 269 | // Check if instance is still valid (in case of forced cleanup) 270 | if ( !isValid() ) { 271 | log_d( "LiteLED: Instance is no longer valid (pin may have been reassigned)" ); 272 | return ESP_ERR_INVALID_STATE; 273 | } 274 | 275 | esp_err_t _res = led_strip_flush( &theStrip ); 276 | if ( _res != ESP_OK ) { 277 | log_d( "Error in 'show()'. Cannot flush strip. Result = %s", esp_err_to_name( _res ) ); 278 | return _res; 279 | } 280 | theStrip.bright_act = theStrip.brightness; 281 | return _res; 282 | } 283 | 284 | esp_err_t LiteLED::setPixel( size_t num, rgb_t color, bool show ) { 285 | esp_err_t _res = ll_checkPinState(); 286 | if ( _res != ESP_OK ) { 287 | return _res; 288 | } 289 | 290 | if ( ( _res = led_strip_set_pixel( &theStrip, num, color ) ) != ESP_OK ) { 291 | return _res; 292 | } 293 | if ( show ) { 294 | _res = LiteLED::show(); 295 | } 296 | return _res; 297 | } 298 | 299 | esp_err_t LiteLED::setPixel( size_t num, crgb_t color, bool show ) { 300 | esp_err_t _res = ll_checkPinState(); 301 | if ( _res != ESP_OK ) { 302 | return _res; 303 | } 304 | 305 | if ( ( _res = led_strip_set_pixel( &theStrip, num, rgb_from_code( color ) ) ) != ESP_OK ) { 306 | return _res; 307 | } 308 | if ( show ) { 309 | _res = LiteLED::show(); 310 | } 311 | return _res; 312 | } 313 | 314 | esp_err_t LiteLED::setPixels( size_t start, size_t len, rgb_t* data, bool show ) { 315 | esp_err_t _res = ll_checkPinState(); 316 | if ( _res != ESP_OK ) { 317 | return _res; 318 | } 319 | 320 | _res = led_strip_set_pixels( &theStrip, start, len, data ); 321 | if ( _res != ESP_OK ) { 322 | return _res; 323 | } 324 | if ( show ) { 325 | _res = LiteLED::show(); 326 | } 327 | return _res; 328 | } 329 | 330 | esp_err_t LiteLED::setPixels( size_t start, size_t len, crgb_t* data, bool show ) { 331 | esp_err_t _res = ll_checkPinState(); 332 | if ( _res != ESP_OK ) { 333 | return _res; 334 | } 335 | 336 | _res = led_strip_set_pixels_c( &theStrip, start, len, data ); 337 | if ( _res != ESP_OK ) { 338 | return _res; 339 | } 340 | if ( show ) { 341 | _res = LiteLED::show(); 342 | } 343 | return _res; 344 | } 345 | 346 | esp_err_t LiteLED::fill( rgb_t color, bool show ) { 347 | esp_err_t _res = ll_checkPinState(); 348 | if ( _res != ESP_OK ) { 349 | return _res; 350 | } 351 | 352 | _res = led_strip_fill( &theStrip, color ); 353 | if ( _res != ESP_OK ) { 354 | return _res; 355 | } 356 | if ( show ) { 357 | _res = LiteLED::show(); 358 | } 359 | return _res; 360 | } 361 | 362 | esp_err_t LiteLED::fill( crgb_t color, bool show ) { 363 | return LiteLED::fill( rgb_from_code( color ), show ); 364 | } 365 | 366 | esp_err_t LiteLED::clear( bool show ) { 367 | esp_err_t _res = ll_checkPinState(); 368 | if ( _res != ESP_OK ) { 369 | return _res; 370 | } 371 | 372 | led_strip_clear( &theStrip, PIXEL_SIZE( &theStrip ) ); 373 | if ( show ) { 374 | _res = LiteLED::show(); 375 | } 376 | return _res; 377 | } 378 | 379 | esp_err_t LiteLED::brightness( uint8_t bright, bool show ) { 380 | esp_err_t _res = ll_checkPinState(); 381 | if ( _res != ESP_OK ) { 382 | return _res; 383 | } 384 | 385 | _res = led_strip_set_brightness( &theStrip, bright ); 386 | if ( _res != ESP_OK ) { 387 | return _res; 388 | } 389 | if ( show ) { 390 | _res = LiteLED::show(); 391 | } 392 | return _res; 393 | } 394 | 395 | uint8_t LiteLED::getBrightness( ) { 396 | return led_strip_get_brightness( &theStrip ); 397 | } 398 | 399 | rgb_t LiteLED::getPixel( size_t num ) { 400 | rgb_t _res = led_strip_get_pixel( &theStrip, num ); 401 | return _res; 402 | } 403 | 404 | crgb_t LiteLED::getPixelC( size_t num ) { 405 | crgb_t _res = rgb_to_code( led_strip_get_pixel( &theStrip, num ) ); 406 | return _res; 407 | } 408 | 409 | esp_err_t LiteLED::fillRandom( bool show ) { 410 | esp_err_t _res = ll_checkPinState(); 411 | if ( _res != ESP_OK ) { 412 | return _res; 413 | } 414 | 415 | _res = led_strip_fill_random( &theStrip ); 416 | if ( _res != ESP_OK ) { 417 | return _res; 418 | } 419 | if ( show ) { 420 | _res = led_strip_flush( &theStrip ); 421 | } 422 | return _res; 423 | } 424 | 425 | esp_err_t LiteLED::setOrder( color_order_t led_order ) { 426 | led_strip_set_color_order( &theStrip, led_order ); 427 | return ESP_OK; 428 | } 429 | 430 | esp_err_t LiteLED::resetOrder() { 431 | led_strip_set_default_color_order( &theStrip ); 432 | return ESP_OK; 433 | } 434 | 435 | bool LiteLED::isValid() const { 436 | // Fast check using flag - for external callers who want detailed validation 437 | if ( !valid_instance ) { 438 | return false; 439 | } 440 | // Secondary validation for robustness 441 | return ( theStrip.buf != NULL && theStrip.stripCfg.led_chan != NULL ); 442 | } 443 | 444 | bool LiteLED::isGpioAvailable( uint8_t gpio_pin ) { 445 | if ( !perimanPinIsValid( gpio_pin ) ) { 446 | return false; 447 | } 448 | peripheral_bus_type_t current_type = perimanGetPinBusType( gpio_pin ); 449 | return ( current_type == ESP32_BUS_TYPE_INIT ); 450 | } 451 | 452 | uint8_t LiteLED::getActiveInstanceCount() { 453 | return ll_registry_get_active_count(); 454 | } 455 | 456 | esp_err_t LiteLED::free() { 457 | if ( !theStrip.buf ) { 458 | return ESP_ERR_INVALID_ARG; 459 | } 460 | 461 | // Mark instance as invalid immediately 462 | valid_instance = false; 463 | 464 | // Unregister channel -> instance mapping 465 | ll_unregister_channel_instance( theStrip.stripCfg.led_chan ); 466 | 467 | // Unregister from Peripheral Manager 468 | if ( theStrip.gpio < GPIO_NUM_MAX ) { // Check if GPIO is valid 469 | if ( !perimanSetPinBus( theStrip.gpio, ESP32_BUS_TYPE_INIT, NULL, -1, -1 ) ) { 470 | log_d( "LiteLED: Failed to unregister GPIO %u from Peripheral Manager", theStrip.gpio ); 471 | } 472 | } 473 | 474 | // Proceed with normal cleanup 475 | return led_strip_free( &theStrip ); 476 | } 477 | 478 | // --- EOF --- 479 | -------------------------------------------------------------------------------- /src/ll_strip_core.cpp: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | LiteLED Core Strip Operations Implementation 4 | */ 5 | 6 | #include "ll_strip_core.h" 7 | 8 | esp_err_t led_strip_init( led_strip_t *strip ) { 9 | /* Initializes all structures and variables required for the library */ 10 | if ( !( strip && strip->length > 0 && strip->type < LED_STRIP_TYPE_MAX ) ) { 11 | log_d( "Error: Invalid arguments." ); 12 | return ESP_ERR_INVALID_ARG; 13 | } 14 | 15 | // Populate the stripCfg struct with the necessary values 16 | strip->stripCfg.led_chan = NULL; 17 | strip->stripCfg.led_encoder = NULL; 18 | strip->stripCfg.led_chan_config = { 19 | .gpio_num = ( gpio_num_t )strip->gpio, 20 | .clk_src = RMT_CLK_SRC_DEFAULT, 21 | .resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ, 22 | .mem_block_symbols = LL_MEM_BLOCK_SIZE_DEFAULT, 23 | .trans_queue_depth = 4, /* Number of transactions that can be pending in the background */ 24 | #if LL_INT_PRIORITY_SUPPORT 25 | .intr_priority = 0, /* Callback interrupt priority, default is 0 */ 26 | #endif 27 | .flags = { 28 | .invert_out = 0, /* Do not invert output */ 29 | .with_dma = DMA_DEFAULT, /* Do not use DMA by default */ 30 | .io_loop_back = 0, /* No loopback */ 31 | .io_od_mode = 0, /* Open drain */ 32 | } 33 | }; 34 | 35 | log_d( "Defining the led encoder configuration." ); 36 | strip->stripCfg.led_encoder_cfg = { 37 | led_encoder_cb, /* The led encoder function */ 38 | strip, /* Args for the led encoder function */ 39 | ( size_t )LL_ENCODER_MIN_CHUNK_SIZE, /* Explicitly set - default is 64 */ 40 | }; 41 | 42 | log_d( "Setting the RMT transmit configuration." ); 43 | strip->stripCfg.led_tx_config = { 44 | .loop_count = 0, 45 | .flags = { 46 | .eot_level = 0, 47 | .queue_nonblocking = 0, // Use blocking mode for reliability 48 | } 49 | }; 50 | 51 | log_d( "RMT driver configuration successful." ); 52 | return ESP_OK; 53 | } 54 | 55 | esp_err_t led_strip_init_modify( led_strip_t *strip, ll_dma_t use_dma, ll_priority_t priority ) { 56 | /* Sets user defined DMA and interrupt priority settings for the LED strip */ 57 | esp_err_t res = ESP_OK; 58 | 59 | #if LL_INT_PRIORITY_SUPPORT 60 | strip->stripCfg.led_chan_config.intr_priority = priority; 61 | log_d( "Setting the RMT interrupt priority to %d.", priority ); 62 | #else 63 | // Interrupt priority setting not supported - use default (0) 64 | // This is not an error condition, just a limitation 65 | log_d( "Setting RMT interrupt priority not supported with this core version. Using default." ); 66 | #endif 67 | 68 | #if LL_DMA_SUPPORT 69 | strip->stripCfg.led_chan_config.flags.with_dma = use_dma; 70 | log_d( "Setting the RMT DMA usage to %s.", use_dma ? "ON" : "OFF" ); 71 | if ( use_dma ) { 72 | strip->stripCfg.led_chan_config.mem_block_symbols = ( size_t )LL_MEM_BLOCK_SIZE_DMA; 73 | log_d( "RMT DMA enabled. Setting the memory block size to %d.", strip->stripCfg.led_chan_config.mem_block_symbols ); 74 | } 75 | else { 76 | strip->stripCfg.led_chan_config.mem_block_symbols = ( size_t )LL_MEM_BLOCK_SIZE_DEFAULT; 77 | log_d( "RMT DMA disabled. Setting the memory block size to %d.", strip->stripCfg.led_chan_config.mem_block_symbols ); 78 | } 79 | #else 80 | strip->stripCfg.led_chan_config.flags.with_dma = DMA_OFF; 81 | strip->stripCfg.led_chan_config.mem_block_symbols = LL_MEM_BLOCK_SIZE_DEFAULT; 82 | log_d( "RMT DMA not supported on this ESP32 model. Disabling DMA." ); 83 | #endif 84 | 85 | return res; 86 | } 87 | 88 | esp_err_t led_strip_install( led_strip_t *strip ) { 89 | /* Installs the LED strip and allocates the necessary resources */ 90 | size_t buffer_size = strip->length * COLOR_SIZE( strip ); 91 | 92 | // Allocate buffer based on PSRAM preference 93 | if ( strip->use_psram ) { 94 | #if CONFIG_SPIRAM 95 | // Check if PSRAM is actually available at runtime 96 | if ( psramFound() ) { 97 | // Try to allocate in PSRAM first 98 | strip->buf = ( uint8_t* )heap_caps_calloc( strip->length, COLOR_SIZE( strip ), MALLOC_CAP_SPIRAM ); 99 | if ( strip->buf ) { 100 | log_d( "LED buffer allocated in PSRAM (%d bytes)", buffer_size ); 101 | } 102 | else { 103 | // Fall back to internal RAM if PSRAM allocation fails 104 | log_d( "PSRAM allocation failed, falling back to internal RAM" ); 105 | strip->buf = ( uint8_t* )calloc( strip->length, COLOR_SIZE( strip ) ); 106 | if ( strip->buf ) { 107 | log_d( "LED buffer allocated in internal RAM (%d bytes)", buffer_size ); 108 | } 109 | } 110 | } 111 | else { 112 | // PSRAM compiled in but not available at runtime 113 | log_d( "PSRAM requested but not found at runtime, using internal RAM" ); 114 | strip->buf = ( uint8_t* )calloc( strip->length, COLOR_SIZE( strip ) ); 115 | if ( strip->buf ) { 116 | log_d( "LED buffer allocated in internal RAM (%d bytes)", buffer_size ); 117 | } 118 | } 119 | #else 120 | // PSRAM not compiled in 121 | log_d( "PSRAM requested but support not compiled in, using internal RAM" ); 122 | strip->buf = ( uint8_t* )calloc( strip->length, COLOR_SIZE( strip ) ); 123 | if ( strip->buf ) { 124 | log_d( "LED buffer allocated in internal RAM (%d bytes)", buffer_size ); 125 | } 126 | #endif 127 | } 128 | else { 129 | // Use regular internal RAM allocation 130 | strip->buf = ( uint8_t* )calloc( strip->length, COLOR_SIZE( strip ) ); 131 | if ( strip->buf ) { 132 | log_d( "LED buffer allocated in internal RAM (%d bytes)", buffer_size ); 133 | } 134 | } 135 | 136 | if ( !strip->buf ) { 137 | log_d( "Error: Failed to allocate buffer - ESP_ERR_NO_MEM." ); 138 | return ESP_ERR_NO_MEM; 139 | } 140 | 141 | esp_err_t res = ESP_OK; 142 | 143 | #if LL_INT_PRIORITY_SUPPORT 144 | int original_priority = strip->stripCfg.led_chan_config.intr_priority; 145 | 146 | // Pre-flight conflict detection 147 | log_d( "Checking interrupt priority availability for priority %s (%d)", 148 | ll_priority_to_string( original_priority ), original_priority ); 149 | 150 | int best_priority = ll_find_best_available_priority( original_priority ); 151 | if ( best_priority < 0 ) { 152 | log_d( "Error: No interrupt priorities available! Maximum number of RMT channels may be exceeded" ); 153 | log_d( " Active channels: %d. May need to reduce the number of concurrent displays or reduce RMT DMA usage", ll_active_channels ); 154 | return ESP_ERR_NO_MEM; 155 | } 156 | 157 | if ( best_priority != original_priority ) { 158 | log_d( "Requested priority %s (%d) not available, using %s (%d) instead", 159 | ll_priority_to_string( original_priority ), original_priority, 160 | ll_priority_to_string( best_priority ), best_priority ); 161 | strip->stripCfg.led_chan_config.intr_priority = best_priority; 162 | } 163 | else { 164 | log_d( "Requested priority %s (%d) is available", 165 | ll_priority_to_string( original_priority ), original_priority ); 166 | } 167 | #endif 168 | 169 | // Try to create RMT TX channel with the selected priority 170 | res = rmt_new_tx_channel( &strip->stripCfg.led_chan_config, &strip->stripCfg.led_chan ); 171 | 172 | // If we still get a conflict despite pre-flight checks, use the original fallback mechanism 173 | if ( res == ESP_ERR_INVALID_ARG ) { 174 | #if LL_INT_PRIORITY_SUPPORT 175 | log_d( "Error: Unexpected priority conflict after pre-flight checks. Attempting to fallback..." ); 176 | 177 | for ( int i = 0; i < LL_MAX_PRIORITY_ATTEMPTS; i++ ) { 178 | if ( ll_priority_fallbacks[ i ] != strip->stripCfg.led_chan_config.intr_priority ) { 179 | strip->stripCfg.led_chan_config.intr_priority = ll_priority_fallbacks[ i ]; 180 | res = rmt_new_tx_channel( &strip->stripCfg.led_chan_config, &strip->stripCfg.led_chan ); 181 | 182 | if ( res == ESP_OK ) { 183 | log_d( "Fallback successful with priority %s (%d)", 184 | ll_priority_to_string( ll_priority_fallbacks[ i ] ), ll_priority_fallbacks[ i ] ); 185 | break; 186 | } 187 | else if ( res != ESP_ERR_INVALID_ARG ) { 188 | log_d( "Error: Fallback priority %s (%d) failed with error: %s", 189 | ll_priority_to_string( ll_priority_fallbacks[ i ] ), ll_priority_fallbacks[ i ], 190 | esp_err_to_name( res ) ); 191 | break; 192 | } 193 | } 194 | } 195 | #endif 196 | } 197 | 198 | // Mark the priority as used if successful 199 | if ( res == ESP_OK ) { 200 | #if LL_INT_PRIORITY_SUPPORT 201 | ll_mark_priority_used( strip->stripCfg.led_chan_config.intr_priority ); 202 | #endif 203 | log_d( "RMT TX channel created successfully with priority %s (%d)", 204 | ll_priority_to_string( strip->stripCfg.led_chan_config.intr_priority ), 205 | strip->stripCfg.led_chan_config.intr_priority ); 206 | } 207 | 208 | if ( res != ESP_OK ) { 209 | if ( res == ESP_ERR_INVALID_ARG ) { 210 | log_d( "Error: Failed to create RMT TX channel - unable to resolve interrupt priority conflict." ); 211 | log_d( " Solutions: 1) Use PRIORITY_DEFAULT for all displays, 2) Reduce number of concurrent displays, 3) Use simple begin() method" ); 212 | } 213 | else { 214 | log_d( "Error: Failed to create RMT TX channel - %s.", esp_err_to_name( res ) ); 215 | } 216 | return res; 217 | } 218 | 219 | if ( ( res = rmt_new_simple_encoder( &strip->stripCfg.led_encoder_cfg, &strip->stripCfg.led_encoder ) ) != ESP_OK ) { 220 | log_d( "Error: Failed to create LED encoder - %s.", esp_err_to_name( res ) ); 221 | return res; 222 | } 223 | 224 | log_d( "Enabling the RMT TX channel." ); 225 | if ( ( res = rmt_enable( strip->stripCfg.led_chan ) ) != ESP_OK ) { 226 | log_d( "Error: Failed to enable RMT TX channel - %s.", esp_err_to_name( res ) ); 227 | return res; 228 | } 229 | 230 | log_d( "LED strip sucessfully configured and installed." ); 231 | 232 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE 233 | led_strip_debug_dump( strip ); 234 | #endif 235 | 236 | return res; 237 | } 238 | 239 | esp_err_t led_strip_free( led_strip_t *strip ) { 240 | /* Deletes all resources used by the strip */ 241 | if ( !( strip && strip->buf ) ) { 242 | log_d( "Error: Attempting to free uninitialized strip." ); 243 | return ESP_ERR_INVALID_ARG; 244 | } 245 | 246 | #if LL_INT_PRIORITY_SUPPORT 247 | // Release the interrupt priority before freeing resources 248 | int priority = strip->stripCfg.led_chan_config.intr_priority; 249 | ll_mark_priority_free( priority ); 250 | #endif 251 | 252 | esp_err_t res = ESP_OK; 253 | if ( ( res = rmt_tx_wait_all_done( strip->stripCfg.led_chan, portMAX_DELAY ) ) != ESP_OK ) { 254 | log_d( "Error: Fail on wait for RMT TX to finish - %s.", esp_err_to_name( res ) ); 255 | return res; 256 | } 257 | if ( ( res = rmt_disable( strip->stripCfg.led_chan ) ) != ESP_OK ) { 258 | log_d( "Error: Fail on disable RMT TX channel - %s.", esp_err_to_name( res ) ); 259 | return res; 260 | } 261 | if ( ( rmt_del_channel( strip->stripCfg.led_chan ) ) != ESP_OK ) { 262 | log_d( "Error: Fail on delete RMT TX channel - %s.", esp_err_to_name( res ) ); 263 | return res; 264 | } 265 | if ( ( rmt_del_encoder( strip->stripCfg.led_encoder ) ) != ESP_OK ) { 266 | log_d( "Error: Fail on delete RMT encoder - %s.", esp_err_to_name( res ) ); 267 | return res; 268 | } 269 | 270 | free( strip->buf ); 271 | strip->buf = NULL; 272 | return res; 273 | } 274 | 275 | esp_err_t led_strip_flush( led_strip_t *strip ) { 276 | /* Pushes all data from the LED buffer to the LED strip */ 277 | esp_err_t res = ESP_OK; 278 | if ( ( res = rmt_transmit( strip->stripCfg.led_chan, strip->stripCfg.led_encoder, strip->buf, PIXEL_SIZE( strip ), &strip->stripCfg.led_tx_config ) ) != ESP_OK ) { 279 | log_d( "Error: Fail on 'rmt_transmit()'. Result = %s", esp_err_to_name( res ) ); 280 | return res; 281 | } 282 | if ( ( res = rmt_tx_wait_all_done( strip->stripCfg.led_chan, portMAX_DELAY ) ) != ESP_OK ) { 283 | log_d( "Error: Fail on 'rmt_tx_wait_all_done()'. Result = %s", esp_err_to_name( res ) ); 284 | return res; 285 | } 286 | return res; 287 | } 288 | 289 | void led_strip_debug_dump( led_strip_t *strip ) { 290 | /* Dumps the LED strip configuration data to the debug monitor */ 291 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE 292 | if ( strip ) { 293 | log_printf( "\n" ); 294 | log_printf( "============= LiteLED Debug Report =============\n" ); 295 | log_printf( "LED strip object at: %p\n", strip ); 296 | log_printf( " type: %s\n", led_type[ strip->type ] ); 297 | log_printf( " is_rgbw: %d\n", strip->is_rgbw ); 298 | log_printf( " auto_w: %d\n", strip->auto_w ); 299 | log_printf( " brightness: %d\n", strip->brightness ); 300 | log_printf( " bright_act: %d\n", strip->bright_act ); 301 | log_printf( " length: %d\n", strip->length ); 302 | log_printf( " gpio: %d\n", strip->gpio ); 303 | log_printf( " buf: %p\n", strip->buf ); 304 | log_printf( " buf color size: %d\n", COLOR_SIZE( strip ) ); 305 | log_printf( " buf bytes: %d\n", PIXEL_SIZE( strip ) ); 306 | // Check if buffer is in PSRAM or internal RAM 307 | if ( strip->buf ) { 308 | #if defined(SOC_EXTRAM_DATA_LOW) && defined(SOC_EXTRAM_DATA_HIGH) 309 | bool is_psram = heap_caps_get_allocated_size( strip->buf ) > 0 && 310 | ( ( ( uint32_t )strip->buf >= SOC_EXTRAM_DATA_LOW ) && 311 | ( ( uint32_t )strip->buf < SOC_EXTRAM_DATA_HIGH ) ); 312 | log_printf( " buf location: %s\n", is_psram ? "PSRAM" : "Internal RAM" ); 313 | #else 314 | // ESP32-C3 and other variants without external RAM support 315 | log_printf( " buf location: Internal RAM (no PSRAM support)\n" ); 316 | #endif 317 | } 318 | log_printf( " led_chan_config at: %p\n", &strip->stripCfg.led_chan_config ); 319 | log_printf( " .gpio_num: %d\n", strip->stripCfg.led_chan_config.gpio_num ); 320 | log_printf( " .clk_src: %d\n", strip->stripCfg.led_chan_config.clk_src ); 321 | log_printf( " .resolution_hz: %d\n", strip->stripCfg.led_chan_config.resolution_hz ); 322 | log_printf( " .mem_block_symbols: %d\n", strip->stripCfg.led_chan_config.mem_block_symbols ); 323 | log_printf( " .trans_queue_depth: %d\n", strip->stripCfg.led_chan_config.trans_queue_depth ); 324 | #if LL_INT_PRIORITY_SUPPORT 325 | log_printf( " .intr_priority: %d\n", strip->stripCfg.led_chan_config.intr_priority ); 326 | #endif 327 | log_printf( " .flags\n" ); 328 | log_printf( " .invert_out: %d\n", strip->stripCfg.led_chan_config.flags.invert_out ); 329 | log_printf( " .with_dma: %d\n", strip->stripCfg.led_chan_config.flags.with_dma ); 330 | log_printf( " .io_loop_back: %d\n", strip->stripCfg.led_chan_config.flags.io_loop_back ); 331 | log_printf( " .io_od_mode: %d\n", strip->stripCfg.led_chan_config.flags.io_od_mode ); 332 | log_printf( " led_tx_config:\n" ); 333 | log_printf( " .loop_count: %d\n", strip->stripCfg.led_tx_config.loop_count ); 334 | log_printf( " .flags\n" ); 335 | log_printf( " .queue_nonblocking: %d\n", strip->stripCfg.led_tx_config.flags.queue_nonblocking ); 336 | log_printf( " .eot_level: %d\n", strip->stripCfg.led_tx_config.flags.eot_level ); 337 | log_printf( " led_chan: %p\n", strip->stripCfg.led_chan ); 338 | log_printf( " led_encoder_cfg: %p\n", &strip->stripCfg.led_encoder_cfg ); 339 | log_printf( " led_encoder: %p\n", strip->stripCfg.led_encoder ); 340 | log_printf( " led_encoder chunk size: %d\n", strip->stripCfg.led_encoder_cfg.min_chunk_size ); 341 | log_printf( " -----------------------------------------\n" ); 342 | log_printf( " led color order: %s\n", col_ord[ led_params[ strip->type ].order ] ); 343 | log_printf( " led custom color order: %s\n", col_ord[ custom_color_order ] ); 344 | log_printf( " led use custom color order: %s\n", use_custom_color_order ? "true" : "false" ); 345 | log_printf( " -----------------------------------------\n" ); 346 | log_printf( " interrupt priority status (total active: %d):\n", ll_active_channels ); 347 | for ( int i = 0; i < LL_MAX_PRIORITY_ATTEMPTS; i++ ) { 348 | log_printf( " %s (%d): %s\n", 349 | ll_priority_to_string( i ), i, 350 | ll_priority_used[ i ] ? "USED" : "FREE" ); 351 | } 352 | log_printf( "================================================\n" ); 353 | log_printf( "\n" ); 354 | } 355 | else { 356 | log_printf( "Error: LED strip object is NULL." ); 357 | } 358 | #endif 359 | } 360 | 361 | // --- EOF --- // 362 | -------------------------------------------------------------------------------- /docs/LiteLED Architecture.md: -------------------------------------------------------------------------------- 1 | # LiteLED Architecture 2 | 3 | ## Overview 4 | 5 | LiteLED starting with v3.0.0 uses a modular architecture to separate concerns and improve maintainability. The library consists of five specialized modules, each handling a specific aspect of the LED strip driver functionality. 6 | 7 | --- 8 | 9 | ## Module Hierarchy and Dependencies 10 | 11 | ``` 12 | LiteLED.h (Main Public API) 13 | ├─> llrmt.h (Compatibility Header - includes all low-level modules) 14 | │ ├─> ll_led_timings.h (LED Timing Constants) 15 | │ ├─> ll_priority.h/.cpp (Priority Management) 16 | │ ├─> ll_encoder.h/.cpp (RMT Encoder Callback) 17 | │ ├─> ll_strip_core.h/.cpp (Core Strip Operations) 18 | │ └─> ll_strip_pixels.h/.cpp (Pixel Manipulation) 19 | ├─> ll_registry.h/.cpp (Minimal Instance Tracking) 20 | ├─> esp32-hal-periman.h (ESP32 Peripheral Manager - Direct GPIO Management) 21 | └─> llrgb.h (RGB Color Utilities) 22 | ``` 23 | 24 | --- 25 | 26 | ## Core Files 27 | 28 | ### `LiteLED.h` / `LiteLED.cpp` 29 | 30 | **Purpose:** Main public API and high-level class interface 31 | 32 | **Responsibilities:** 33 | 34 | - Defines the `led_strip_t` structure (LED strip object) 35 | - Provides the user-facing C++ class interface 36 | - Platform compatibility checks (ESP32, Arduino core version) 37 | - Hardware capability detection (RMT, DMA, interrupt priority support) 38 | - Public methods for LED strip control (begin, show, setPixel, fill, etc.) 39 | 40 | **Key Dependencies:** 41 | 42 | - `llrmt.h` - Low-level RMT operations 43 | - `ll_registry.h` - Minimal instance tracking for cleanup callbacks 44 | - `esp32-hal-periman.h` - ESP32 Peripheral Manager for GPIO conflict prevention 45 | - `llrgb.h` - Color manipulation utilities 46 | 47 | **Relationship:** This is the top-level interface that users interact with. It wraps all lower-level modules into an easy-to-use API. 48 | 49 | --- 50 | 51 | ### `llrmt.h` (Compatibility Header) 52 | 53 | **Purpose:** Single include point for all low-level RMT modules 54 | 55 | **Responsibilities:** 56 | 57 | - Includes all five modular headers in correct order 58 | - Maintains backward compatibility with existing code 59 | - Acts as the abstraction layer between high-level API and implementation 60 | 61 | **Contents:** 62 | 63 | ```cpp 64 | #include "ll_led_timings.h" // Timing constants 65 | #include "ll_priority.h" // Priority management 66 | #include "ll_encoder.h" // Encoder callback 67 | #include "ll_strip_core.h" // Core operations 68 | #include "ll_strip_pixels.h" // Pixel operations 69 | ``` 70 | 71 | **Relationship:** Central hub that aggregates all low-level functionality. 72 | 73 | --- 74 | 75 | ## Low-Level Modules 76 | 77 | ### 1. `ll_led_timings.h` 78 | 79 | **Purpose:** LED strip timing definitions and RMT symbol constants 80 | 81 | **Responsibilities:** 82 | 83 | - Defines RMT symbol timing for different LED strip types: 84 | - WS2812/WS2812B (GRB) 85 | - APA106 (RGB) 86 | - SM16703 (RGB) 87 | - SK6812 (RGBW) 88 | - Stores timing values for logical 0, logical 1, and reset pulses 89 | - Provides `led_params[]` lookup array indexed by LED type 90 | - Manages colour order configuration (standard and custom) 91 | 92 | **Key Data Structures:** 93 | 94 | ```cpp 95 | typedef struct { 96 | rmt_symbol_word_t led_0; // Symbol for bit 0 97 | rmt_symbol_word_t led_1; // Symbol for bit 1 98 | rmt_symbol_word_t led_reset; // Reset/latch symbol 99 | } led_timing_params_t; 100 | 101 | extern led_timing_params_t led_params[4]; // Indexed by led_type_t 102 | ``` 103 | 104 | **Dependencies:** 105 | 106 | - `LiteLED.h` (for type definitions) 107 | - ESP-IDF RMT driver headers 108 | 109 | **Relationship:** Provides timing constants consumed by encoder and core modules. Pure data, no functions. 110 | 111 | --- 112 | 113 | ### 2. `ll_priority.h` / `ll_priority.cpp` 114 | 115 | **Purpose:** RMT interrupt priority allocation and tracking 116 | 117 | **Responsibilities:** 118 | 119 | - Tracks which interrupt priorities are in use across all active LED strips 120 | - Provides priority availability checking before allocation 121 | - Implements fallback mechanism when requested priority is unavailable 122 | - Maintains priority usage state for up to 4 distinct priority levels 123 | - Provides debug functions to query and display priority status 124 | 125 | **Key Functions:** 126 | 127 | ```cpp 128 | bool ll_is_priority_available(int priority); 129 | int ll_find_best_available_priority(int preferred_priority); 130 | void ll_mark_priority_used(int priority); 131 | void ll_mark_priority_free(int priority); 132 | void ll_reset_priority_tracking(); 133 | const char* ll_priority_to_string(int priority); 134 | ``` 135 | 136 | **Key State:** 137 | 138 | ```cpp 139 | bool ll_priority_used[4]; // Tracks which priorities are in use 140 | uint8_t ll_active_channels; // Count of active LED strips 141 | const int ll_priority_fallbacks[]; // Fallback order: DEFAULT, HIGH, MEDIUM, LOW 142 | ``` 143 | 144 | **Dependencies:** 145 | 146 | - `Arduino.h` 147 | - ESP32 HAL logging 148 | 149 | **Relationship:** Used by `ll_strip_core` during initialization to prevent priority conflicts when multiple LED strips are active. 150 | 151 | --- 152 | 153 | ### 3. `ll_encoder.h` / `ll_encoder.cpp` 154 | 155 | **Purpose:** RMT encoder callback for converting LED data to RMT symbols 156 | 157 | **Responsibilities:** 158 | 159 | - Implements the encoder callback invoked by ESP-IDF RMT driver 160 | - Converts LED buffer bytes into RMT symbol sequences 161 | - Applies brightness scaling using `scale8_video()` 162 | - Handles data transmission completion and reset signal generation 163 | - Manages encoder state (`enc_pos` - current position in LED buffer) 164 | 165 | **Key Function:** 166 | 167 | ```cpp 168 | IRAM_ATTR size_t led_encoder_cb(const void* data, size_t data_size, 169 | size_t symbols_written, size_t symbols_free, 170 | rmt_symbol_word_t *symbols, bool *done, void *arg); 171 | ``` 172 | 173 | **Algorithm:** 174 | 175 | 1. Check if sufficient symbol space is available (minimum 8 for one byte) 176 | 2. Read next byte from LED buffer 177 | 3. Apply brightness scaling via `scale8_video()` 178 | 4. Convert byte to 8 RMT symbols (one per bit) 179 | 5. Lookup correct timing from `led_params[]` based on strip type 180 | 6. Write symbols to RMT output buffer 181 | 7. When all data sent, append reset symbol and signal completion 182 | 183 | **Dependencies:** 184 | 185 | - `LiteLED.h` (for `led_strip_t` structure) 186 | - `llrgb.h` (for `scale8_video()`) 187 | - `ll_led_timings.h` (for `led_params[]`) 188 | 189 | **Performance:** Marked `IRAM_ATTR` to place in fast instruction RAM for zero-wait-state execution during RMT transmission. 190 | 191 | **Relationship:** Called by ESP-IDF RMT driver during transmission. This is the performance-critical path that runs in interrupt context. 192 | 193 | --- 194 | 195 | ### 4. `ll_strip_core.h` / `ll_strip_core.cpp` 196 | 197 | **Purpose:** Core LED strip lifecycle management 198 | 199 | **Responsibilities:** 200 | 201 | - LED strip initialization and configuration 202 | - RMT channel allocation and configuration 203 | - Memory allocation (PSRAM vs internal RAM with fallback) 204 | - Priority management integration with pre-flight conflict detection 205 | - Channel installation, enabling, and cleanup 206 | - Debug reporting and diagnostics 207 | 208 | **Key Functions:** 209 | 210 | ```cpp 211 | esp_err_t led_strip_init(led_strip_t *strip); 212 | esp_err_t led_strip_init_modify(led_strip_t *strip); 213 | esp_err_t led_strip_install(led_strip_t *strip); 214 | esp_err_t led_strip_free(led_strip_t *strip); 215 | esp_err_t led_strip_flush(led_strip_t *strip); 216 | void led_strip_debug_dump(led_strip_t *strip); 217 | ``` 218 | 219 | **Initialization Sequence:** 220 | 221 | 1. **`led_strip_init()`:** Configure RMT encoder and transmit settings 222 | 2. **`led_strip_init_modify()`:** Apply user customizations (priority, DMA, memory) 223 | 3. **`led_strip_install()`:** 224 | - Allocate LED buffer (PSRAM preferred, internal RAM fallback) 225 | - Check priority availability (pre-flight checks) 226 | - Create RMT TX channel with ESP-IDF driver 227 | - Handle priority conflicts with automatic fallback 228 | - Register priority usage 229 | - Enable RMT channel 230 | 231 | **Memory Management:** 232 | 233 | - Attempts PSRAM allocation first (preferred for large buffers) 234 | - Falls back to internal RAM if PSRAM unavailable 235 | - Tracks allocation location for debugging 236 | 237 | **Priority Conflict Handling:** 238 | 239 | - Pre-flight check using `ll_is_priority_available()` 240 | - Automatic fallback to best available priority if conflict detected 241 | - Logs all priority decisions for debugging 242 | 243 | **Dependencies:** 244 | 245 | - `LiteLED.h` 246 | - `ll_led_timings.h` 247 | - `ll_priority.h` 248 | - `ll_encoder.h` 249 | - ESP-IDF RMT driver 250 | 251 | **Relationship:** This is the main initialization and setup module. Called by `LiteLED.cpp` during strip creation. Uses all other low-level modules. 252 | 253 | --- 254 | 255 | ### 5. `ll_strip_pixels.h` / `ll_strip_pixels.cpp` 256 | 257 | **Purpose:** Pixel-level colour manipulation operations 258 | 259 | **Responsibilities:** 260 | 261 | - Individual pixel get/set operations 262 | - Bulk pixel operations (fill, fill random, clear) 263 | - Color order translation (RGB, RBG, GRB, GBR, BRG, BGR) 264 | - RGBW white channel handling with auto-white calculation 265 | - Brightness control (global and per-operation) 266 | 267 | **Key Functions:** 268 | 269 | ```cpp 270 | // Individual pixel operations 271 | esp_err_t led_strip_set_pixel(led_strip_t *strip, uint16_t pixel_num, uint8_t r, uint8_t g, uint8_t b); 272 | esp_err_t led_strip_set_pixel_rgbw(led_strip_t *strip, uint16_t pixel_num, uint8_t r, uint8_t g, uint8_t b, uint8_t w); 273 | esp_err_t led_strip_get_pixel(led_strip_t *strip, uint16_t pixel_num, uint8_t *r, uint8_t *g, uint8_t *b); 274 | 275 | // Bulk operations 276 | esp_err_t led_strip_set_pixels(led_strip_t *strip, uint16_t start_pixel, uint16_t count, const uint8_t *pixels); 277 | esp_err_t led_strip_fill(led_strip_t *strip, uint16_t start, uint16_t count, uint8_t r, uint8_t g, uint8_t b); 278 | esp_err_t led_strip_fill_random(led_strip_t *strip, uint16_t start, uint16_t count); 279 | esp_err_t led_strip_clear(led_strip_t *strip); 280 | 281 | // Brightness control 282 | esp_err_t led_strip_set_brightness(led_strip_t *strip, uint8_t brightness); 283 | uint8_t led_strip_get_brightness(led_strip_t *strip); 284 | 285 | // Color order management 286 | void led_strip_set_color_order(color_order_t order); 287 | ``` 288 | 289 | **Color Order Translation:** 290 | 291 | - Supports the six possible colour orders (RGB, RBG, GRB, GBR, BRG, BGR) 292 | - Uses lookup table for efficient translation 293 | - Custom colour order overrides strip-specific defaults 294 | - Essential for hardware compatibility (eg: WS2812 uses GRB, APA106 uses RGB) 295 | 296 | **RGBW Support:** 297 | 298 | - Auto-white calculation: extracts common component from RGB 299 | - Manual white channel control available 300 | - Handles 4-byte RGBW buffer layout 301 | 302 | **Dependencies:** 303 | 304 | - `LiteLED.h` 305 | - `ll_led_timings.h` (for colour order state) 306 | 307 | **Relationship:** Provides all pixel manipulation functionality called by `LiteLED.cpp` public methods. Works with the LED buffer allocated by `ll_strip_core`. 308 | 309 | --- 310 | 311 | ## Supporting Modules 312 | 313 | ### `ll_registry.h` / `ll_registry.cpp` 314 | 315 | **Purpose:** Minimal instance tracking for cleanup callbacks 316 | 317 | **Size:** ~44 lines (header) + ~200 lines (implementation) 318 | 319 | **Responsibilities:** 320 | 321 | - Maintains minimal mapping of RMT channels to LiteLED instances 322 | - Provides deinit callback for ESP32 Peripheral Manager integration 323 | - Handles forced cleanup when GPIO pins are reassigned to other peripherals 324 | - Thread-safe access to instance mappings 325 | - GPIO availability checking and instance counting (delegates to Peripheral Manager) 326 | 327 | **Key Functions:** 328 | 329 | ```cpp 330 | esp_err_t ll_registry_init(void); // Initialize registry 331 | esp_err_t ll_register_channel_instance(rmt_channel_handle_t channel, LiteLED* instance); // Register mapping 332 | void ll_unregister_channel_instance(rmt_channel_handle_t channel); // Unregister mapping 333 | LiteLED* ll_get_instance_by_gpio(uint8_t gpio); // Query by GPIO 334 | uint8_t ll_registry_get_active_count(void); // Count active instances 335 | bool ll_periman_deinit_callback(void *bus_handle); // Cleanup callback 336 | ``` 337 | 338 | **Architecture Changes (v3.0.0):** 339 | 340 | - **Simplified Design:** Registry now only tracks RMT channel → instance mappings 341 | - **Delegates GPIO Management:** All GPIO tracking/conflict detection handled by ESP32 Peripheral Manager 342 | - **Minimal Memory Footprint:** Reduced from full registry entries to simple channel mapping 343 | - **Direct Peripheral Manager Integration:** `LiteLED.cpp` calls `perimanSetPinBus()` directly 344 | 345 | **Integration with ESP32 Peripheral Manager:** 346 | 347 | - Registers pins as `ESP32_BUS_TYPE_RMT_TX` with extra type "LiteLED" 348 | - Provides `ll_periman_deinit_callback()` for forced cleanup scenarios 349 | - Automatically invalidates LiteLED instances when GPIO pins are reassigned 350 | - Ensures system stability during peripheral conflicts 351 | 352 | **Dependencies:** 353 | 354 | - `LiteLED.h` (forward declaration) 355 | - `esp32-hal-periman.h` (ESP32 Peripheral Manager) 356 | - ESP32 HAL logging and threading primitives 357 | 358 | **Relationship:** Called by `LiteLED.cpp` during initialization and cleanup. Provides the bridge between LiteLED instances and the ESP32 Peripheral Manager for conflict detection and forced cleanup handling. 359 | 360 | --- 361 | 362 | ### `llrgb.h` 363 | 364 | **Purpose:** RGB colour manipulation utilities 365 | 366 | **Size:** ~100 lines (header only, inline functions) 367 | 368 | **Responsibilities:** 369 | 370 | - Fast 8-bit integer math for colour operations 371 | - Brightness scaling with gamma correction 372 | - Color blending and interpolation 373 | - HSV to RGB conversion 374 | - Bit manipulation utilities 375 | 376 | **Key Functions:** 377 | 378 | ```cpp 379 | uint8_t scale8(uint8_t value, uint8_t scale); // Linear scaling 380 | uint8_t scale8_video(uint8_t value, uint8_t scale); // Video-style dimming 381 | uint8_t qadd8(uint8_t a, uint8_t b); // Saturating add 382 | uint8_t qsub8(uint8_t a, uint8_t b); // Saturating subtract 383 | ``` 384 | 385 | **Performance:** All inline for zero-overhead abstraction. 386 | 387 | **Relationship:** Used by `ll_encoder` for brightness scaling. Can be used by user code for colour manipulation. 388 | 389 | --- 390 | 391 | ## Data Flow 392 | 393 | ### Initialization Flow 394 | ``` 395 | User Code 396 | └─> LiteLED::begin() 397 | ├─> Check GPIO availability [perimanPinIsValid, perimanGetPinBusType] 398 | ├─> led_strip_init() [ll_strip_core] 399 | │ ├─> Configure RMT encoder [ll_encoder] 400 | │ └─> Configure RMT transmit 401 | ├─> led_strip_init_modify() [ll_strip_core] 402 | │ ├─> Set priority 403 | │ ├─> Set DMA usage 404 | │ └─> Set memory block size 405 | ├─> led_strip_install() [ll_strip_core] 406 | │ ├─> Allocate LED buffer (PSRAM/internal) 407 | │ ├─> Check priority availability [ll_priority] 408 | │ ├─> Create RMT TX channel (ESP-IDF) 409 | │ ├─> Mark priority used [ll_priority] 410 | │ └─> Enable RMT channel 411 | ├─> Register with Peripheral Manager [perimanSetPinBus, perimanSetPinBusExtraType] 412 | └─> Register channel mapping [ll_register_channel_instance] 413 | ``` 414 | 415 | ### Pixel Update Flow 416 | 417 | ``` 418 | User Code 419 | └─> LiteLED::setPixel(n, r, g, b) 420 | └─> led_strip_set_pixel() [ll_strip_pixels] 421 | ├─> Translate color order [ll_led_timings] 422 | ├─> Calculate buffer offset 423 | └─> Write RGB to buffer 424 | └─> LiteLED::show() 425 | └─> led_strip_flush() [ll_strip_core] 426 | └─> rmt_transmit() (ESP-IDF) 427 | └─> led_encoder_cb() [ll_encoder] (interrupt context) 428 | ├─> Read from LED buffer 429 | ├─> Apply brightness [llrgb::scale8_video] 430 | ├─> Lookup timing [ll_led_timings] 431 | └─> Write RMT symbols 432 | ``` 433 | 434 | ### Cleanup Flow 435 | 436 | ``` 437 | User Code 438 | └─> LiteLED::~LiteLED() or LiteLED::free() 439 | ├─> Mark instance invalid [valid_instance = false] 440 | ├─> Unregister channel mapping [ll_unregister_channel_instance] 441 | ├─> Unregister from Peripheral Manager [perimanSetPinBus] 442 | └─> led_strip_free() [ll_strip_core] 443 | ├─> Disable and delete RMT channel (ESP-IDF) 444 | ├─> Mark priority free [ll_priority] 445 | └─> Free LED buffer 446 | ``` 447 | --- 448 | 449 | ## Module Interaction Summary 450 | 451 | | Module | Calls | Called By | Key Responsibility | 452 | |--------|-------|-----------|-------------------| 453 | | `LiteLED` | All modules, Peripheral Manager | User code | Public API | 454 | | `llrmt` | All ll_* modules | `LiteLED` | Module aggregation | 455 | | `ll_led_timings` | None | `ll_encoder`, `ll_strip_pixels` | Data provider | 456 | | `ll_priority` | None | `ll_strip_core` | Priority tracking | 457 | | `ll_encoder` | `llrgb`, `ll_led_timings` | ESP-IDF RMT (interrupt) | Data encoding | 458 | | `ll_strip_core` | `ll_priority`, `ll_encoder` | `LiteLED` | Lifecycle management | 459 | | `ll_strip_pixels` | `ll_led_timings` | `LiteLED` | Pixel operations | 460 | | `ll_registry` | Peripheral Manager | `LiteLED` | Minimal instance tracking | 461 | | `Peripheral Manager` | None | `LiteLED`, `ll_registry` | GPIO conflict prevention | 462 | | `llrgb` | None | `ll_encoder`, User code | Color math | 463 | 464 | --- 465 | 466 | ## Design Principles 467 | 468 | ### Separation of Concerns 469 | 470 | Each module has a single, well-defined responsibility: 471 | 472 | - **Timings:** Static data 473 | - **Priority:** State tracking 474 | - **Encoder:** Data transformation 475 | - **Core:** Lifecycle management 476 | - **Pixels:** Buffer manipulation 477 | 478 | ### Minimal Dependencies 479 | 480 | Modules depend only on what they need: 481 | 482 | - **Timings:** Header-only, no dependencies 483 | - **Priority:** Self-contained state management 484 | - **Encoder:** Depends only on timing data and color math 485 | - **Core:** Orchestrates all modules 486 | - **Pixels:** Minimal dependency on timing data 487 | 488 | ### Performance-Critical Paths 489 | 490 | - **Encoder:** Placed in IRAM for zero-wait-state execution 491 | - **RGB utilities:** Inline functions for zero overhead 492 | - **Timing data:** Constant lookup (compile-time optimized) 493 | 494 | ### Thread Safety 495 | 496 | - **Registry:** Mutex-protected for multi-threaded access (channel mappings only) 497 | - **Peripheral Manager:** Built-in thread safety for GPIO tracking and conflict detection 498 | - **Priority:** Single-threaded during initialization (by design) 499 | - **Encoder:** Read-only access to timing data (no locking needed) 500 | 501 | --- 502 | 503 | ## Benefits of Modular Architecture 504 | 505 | 1. **Maintainability:** Each module can be understood and modified independently 506 | 2. **Testability:** Modules can be unit tested in isolation 507 | 3. **Reusability:** Modules can be used in other projects (e.g., `llrgb.h`) 508 | 4. **Conflict Prevention:** Direct ESP32 Peripheral Manager integration prevents GPIO conflicts with other peripherals 509 | 5. **System Stability:** Forced cleanup callbacks ensure graceful handling when pins are reassigned 510 | 6. **Compile Time:** Only changed modules need recompilation 511 | 7. **Documentation:** Each module can be documented separately 512 | 8. **Extensibility:** New LED types can be added to `ll_led_timings.h` without modifying other code 513 | 514 | --- 515 | 516 | ## Version History 517 | 518 | **v3.0.0** 519 | - Initial release for library version 3.0.0 520 | - Modular architecture implementation 521 | - ESP32 Peripheral Manager integration for GPIO conflict prevention 522 | - Simplified `ll_registry` to minimal instance tracking (delegates GPIO management to Peripheral Manager) 523 | - Direct `perimanSetPinBus()` calls from `LiteLED.cpp` for resource management 524 | 525 | 526 | 527 | 528 | -------------------------------------------------------------------------------- /Using LiteLED.md: -------------------------------------------------------------------------------- 1 | 2 | # Using LiteLED 3 | 4 | ## Table of Contents 5 | - [Quick Start](#quick-start) 6 | - [Basic Example](#basic-example) 7 | - [Colour Representation](#colour-representation) 8 | - [Regarding RGBW Strips](#regarding-rgbw-strips) 9 | - [Classes and Types](#classes-and-types) 10 | - [LiteLED Class](#liteled-class) 11 | - [Enumerations](#enumerations) 12 | - [`led_strip_type_t`](#led_strip_type_t) 13 | - [`color_order_t`](#color_order_t) 14 | - [`ll_dma_t`](#ll_dma_t) 15 | - [`ll_priority_t`](#ll_priority_t) 16 | - [`ll_psram_t`](#ll_psram_t) 17 | - [Structures](#structures) 18 | - [`rgb_t`](#rgb_t) 19 | - [`crgb_t`](#crgb_t) 20 | - [Library API](#library-api) 21 | - [Constructor](#constructor) 22 | - [`LiteLED()`](#liteled) 23 | - [Destructor](#destructor) 24 | - [`~LiteLED()`](#liteled-1) 25 | - [Initialization Methods](#initialization-methods) 26 | - [`begin()` - Basic](#begin---basic) 27 | - [`begin()` - PSRAM](#begin---psram) 28 | - [`begin()` - Full Configuration](#begin---full-configuration) 29 | - [Display Control Methods](#display-control-methods) 30 | - [`show()`](#show) 31 | - [`clear()`](#clear) 32 | - [`brightness()`](#brightness) 33 | - [`getBrightness()`](#getbrightness) 34 | - [Pixel Manipulation Methods](#pixel-manipulation-methods) 35 | - [`setPixel()` - `rgb_t`](#setpixel---rgb_t) 36 | - [`setPixel()` - `crgb_t`](#setpixel---crgb_t) 37 | - [`setPixels()` - `rgb_t` Array](#setpixels---rgb_t-array) 38 | - [`setPixels()` - `crgb_t` Array](#setpixels---crgb_t-array) 39 | - [`fill()` - `rgb_t`](#fill---rgb_t) 40 | - [`fill()` - `crgb_t`](#fill---crgb_t) 41 | - [`fillRandom()`](#fillrandom) 42 | - [Pixel Reading Methods](#pixel-reading-methods) 43 | - [`getPixel()`](#getpixel) 44 | - [`getPixelC()`](#getpixelc) 45 | - [Colour Order Methods](#colour-order-methods) 46 | - [`setOrder()`](#setorder) 47 | - [`resetOrder()`](#resetorder) 48 | - [Instance Management Methods](#instance-management-methods) 49 | - [`isValid()`](#isvalid) 50 | - [`getGpioPin()`](#getgpiopin) 51 | - [`isGpioAvailable()` - Static](#isgpioavailable---static) 52 | - [`getActiveInstanceCount()` - Static](#getactiveinstancecount---static) 53 | - [Advanced Features](#advanced-features) 54 | - [Multi-Display Support](#multi-display-support) 55 | - [Simple Multi-Display Setup](#simple-multi-display-setup) 56 | - [Advanced Multi-Display Configuration](#advanced-multi-display-configuration) 57 | - [Automatic Priority Management](#automatic-priority-management) 58 | - [Priority Levels](#priority-levels) 59 | - [GPIO Management](#gpio-management) 60 | - [Best Practices for Multi-Display](#best-practices-for-multi-display) 61 | - [PSRAM for Large Arrays](#psram-for-large-arrays) 62 | - [Instance Validation](#instance-validation) 63 | - [Utilities](#utilities) 64 | - [LiteLED\_Utils](#liteled_utils) 65 | - [`isDmaSupported()`](#isdmasupported) 66 | - [`isPrioritySupported()`](#isprioritysupported) 67 | - [Complete Example](#complete-example) 68 | - [Integration with Existing Code](#integration-with-existing-code) 69 | - [Related Configuration](#related-configuration) 70 | - [`DMA_DEFAULT` Behaviour](#dma_default-behaviour) 71 | - [Colour Utility Functions](#colour-utility-functions) 72 | - [`rgb_from_code()`](#rgb_from_code) 73 | - [`rgb_from_values()`](#rgb_from_values) 74 | - [`rgb_to_code()`](#rgb_to_code) 75 | - [`rgb_is_zero()`](#rgb_is_zero) 76 | - [`rgb_luma()`](#rgb_luma) 77 | - [`scale8()` and `scale8_video()`](#scale8-and-scale8_video) 78 | - [Performance Considerations](#performance-considerations) 79 | - [Transmission Time](#transmission-time) 80 | - [Memory Usage](#memory-usage) 81 | - [Optimization Tips](#optimization-tips) 82 | - [Troubleshooting](#troubleshooting) 83 | - [No LEDs Light Up](#no-leds-light-up) 84 | - [Wrong Colours](#wrong-colours) 85 | - [Flickering or Glitches](#flickering-or-glitches) 86 | - [GPIO Already in Use Error](#gpio-already-in-use-error) 87 | - [Memory Allocation Failed](#memory-allocation-failed) 88 | - [Compile-Time Assertions](#compile-time-assertions) 89 | - [Compile-Time Warnings](#compile-time-warnings) 90 | - [Error Handling](#error-handling) 91 | - [Return Status Codes](#return-status-codes) 92 | - [Error Checking Pattern](#error-checking-pattern) 93 | - [Common Error Scenarios](#common-error-scenarios) 94 | - [GPIO Already in Use](#gpio-already-in-use) 95 | - [Out of Bounds LED Index](#out-of-bounds-led-index) 96 | - [Memory Allocation Failure](#memory-allocation-failure) 97 | - [Logging](#logging) 98 | - [Usage Examples](#usage-examples) 99 | - [Basic Example - Solid Colour](#basic-example---solid-colour) 100 | - [Brightness Ramping](#brightness-ramping) 101 | - [Rainbow Pattern](#rainbow-pattern) 102 | - [Multiple Strips](#multiple-strips) 103 | - [RGBW Strip with Auto White](#rgbw-strip-with-auto-white) 104 | - [Large Array with PSRAM](#large-array-with-psram) 105 | - [High-Performance with DMA](#high-performance-with-dma) 106 | - [Version History](#version-history) 107 | 108 | 109 | 110 | --- 111 | 112 | # Quick Start 113 | 114 | ## Basic Example 115 | 116 | **Initialization Sequence** 117 | 118 | The correct calling sequence for initialization is: 119 | 120 | 1. **Create LiteLED object** (constructor) 121 | 2. **Call a begin() method** to initialize hardware 122 | 3. **Optionally set brightness** 123 | 4. **Set pixel colours** 124 | 5. **Call show()** to update the LED strip 125 | 126 | ```cpp 127 | #include 128 | 129 | // Define LED strip parameters 130 | #define LED_TYPE LED_STRIP_WS2812 131 | #define LED_GPIO 14 132 | #define LED_COUNT 30 133 | #define LED_IS_RGBW 0 134 | 135 | // Create LiteLED object 136 | LiteLED strip(LED_TYPE, LED_IS_RGBW); 137 | 138 | void setup() { 139 | // Initialize the strip 140 | strip.begin(LED_GPIO, LED_COUNT); 141 | 142 | // Set brightness (0-255) 143 | strip.brightness(50); 144 | 145 | // Set all LEDs to red and show 146 | strip.fill(rgb_from_code(0xFF0000), true); 147 | } 148 | 149 | void loop() { 150 | // Your animation code here 151 | } 152 | ``` 153 | 154 | --- 155 | 156 | # Colour Representation 157 | 158 | The intensity and colour of a LED is defined by setting a value for each of its red, blue and green channels. Values range between `0` and `255`, where `0` is off and `255` is full on. By adjusting the values of each channel, different colours and intensities result. 159 | 160 | With LiteLED, colours are defined in two ways: 161 | 162 | _**As an RGB colour structure**_ 163 | 164 | In this way colours are defined as a structure of type `rgb_t` where a member of the structure represents the intensity of the red, blue and green channels for a particular LED. Members can be accessed using either `.r, .b, .g` or `.red, .blue, .green` notation. 165 | 166 | **Example:** 167 | 168 | Define a colour: 169 | 170 | `rgb_t myColour = { .r = 47, .g = 26, .b = 167 };` 171 | 172 | Set the green channel of a colour variable: 173 | 174 | `myColour.green = 76;` 175 | 176 | 177 | _**As an RGB colour code**_ 178 | 179 | In this way colours are defined as type `crgb_t` where the colour is represented by a 24-bit value within which eight bits are assigned for the intensity of the red, blue and green channels for a particular LED in the form `0xRRGGBB`. 180 | 181 | **Example:** 182 | 183 | Define a colour: 184 | 185 | `crgb_t myOtherColour = 0xff0000; // pure red` 186 | 187 | `crbg_t yetAnotherColour = 0xafafaf; // white-ish` 188 | 189 | 190 | **Notes:** 191 | 192 | 1. Though not required, hex notation is typically used when defining `crgb_t` colours as it makes the values for each of the channels easier to see. 193 | 194 | 2. Once defined, a colour cannot be accessed as the other type. For example, 195 | 196 | ```c++ 197 | crgb_t myOtherColour = 0xff0000; 198 | myOtherColour.blue = 123; // oops - no can do 199 | ``` 200 | will produce an error at line 2 as `myOtherColour` is defined as type `crgb_t` and the statement is attempting to change the blue channel using `rgb_t` notation. 201 | 202 | See also the *Kibbles and Bits* section below. 203 | 204 | ## Regarding RGBW Strips 205 | 206 | LiteLED can drive RGBW strips like SK6812 RGBW types however there is no direct method for setting the value of the W channel. By default LiteLED will automatically set the value of the W channel based on some behind the scenes magic derived from the R, G, and B values for that LED. Thus by default the R, G, B, and W LED's will illuminate based on the values set. 207 | 208 | This behaviour can be disabled when initializing the strip in the `begin()` method. When disabled, the value of the W channel is set to 0 and the white LED will not illuminate. Given that RGBW strips are available with many choices for the colour temperature of the W LED, give it a shot both ways and pick the one that looks good to you. 209 | 210 | LiteLED does not support RGBWW (dual white channel) type strips. 211 | 212 | --- 213 | 214 | # Classes and Types 215 | 216 | ## LiteLED Class 217 | 218 | The main class for controlling LED strips. 219 | 220 | ```cpp 221 | class LiteLED { 222 | public: 223 | // Constructor 224 | LiteLED(led_strip_type_t led_type, bool rgbw); 225 | 226 | // Destructor 227 | ~LiteLED(); 228 | 229 | // Initialization methods 230 | esp_err_t begin(uint8_t data_pin, size_t length, bool auto_w = true); 231 | esp_err_t begin(uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true); 232 | esp_err_t begin(uint8_t data_pin, size_t length, ll_dma_t dma_flag, ll_priority_t priority, ll_psram_t psram_flag, bool auto_w = true); 233 | 234 | // Display control 235 | esp_err_t show(); 236 | esp_err_t clear(bool show = false); 237 | esp_err_t brightness(uint8_t bright, bool show = false); 238 | uint8_t getBrightness(); 239 | 240 | // Pixel manipulation 241 | esp_err_t setPixel(size_t num, rgb_t color, bool show = false); 242 | esp_err_t setPixel(size_t num, crgb_t color, bool show = false); 243 | esp_err_t setPixels(size_t start, size_t len, rgb_t *data, bool show = false); 244 | esp_err_t setPixels(size_t start, size_t len, crgb_t *data, bool show = false); 245 | esp_err_t fill(rgb_t color, bool show = false); 246 | esp_err_t fill(crgb_t color, bool show = false); 247 | esp_err_t fillRandom(bool show = false); 248 | 249 | // Pixel reading 250 | rgb_t getPixel(size_t num); 251 | crgb_t getPixelC(size_t num); 252 | 253 | // Color order control 254 | esp_err_t setOrder(color_order_t led_order = ORDER_GRB); 255 | esp_err_t resetOrder(); 256 | 257 | // Instance management 258 | bool isValid() const; 259 | int getGpioPin() const; 260 | static bool isGpioAvailable(uint8_t gpio_pin); 261 | static uint8_t getActiveInstanceCount(); 262 | }; 263 | ``` 264 | 265 | If you're curious about how the library is structured, take a look at the `LiteLED Architecture.md` file in the `docs` folder of the [library repository](https://github.com/Xylopyrographer/LiteLED). 266 | 267 | --- 268 | 269 | # Enumerations 270 | 271 | 272 | ## `led_strip_type_t` 273 | 274 | Supported LED strip types. 275 | 276 | ```cpp 277 | LED_STRIP_WS2812 // WS2812/WS2812B (GRB colour order) 278 | LED_STRIP_WS2812_RGB // WS2812 variant with RGB colour order 279 | LED_STRIP_SK6812 // SK6812 (GRB colour order, with RGBW support) 280 | LED_STRIP_APA106 // APA106 (RGB colour order) 281 | LED_STRIP_SM16703 // SM16703 (RGB colour order) 282 | 283 | ``` 284 | 285 | **Description**: 286 | 287 | Defines the LED strip type, which determines timing parameters and default colour order. 288 | 289 | --- 290 | 291 | ## `color_order_t` 292 | 293 | LED colour byte ordering. 294 | 295 | ```cpp 296 | ORDER_RGB. // Red, Green, Blue 297 | ORDER_RBG // Red, Blue, Green 298 | ORDER_GRB // Green, Red, Blue 299 | ORDER_GBR // Green, Blue, Red 300 | ORDER_BRG // Blue, Red, Green 301 | ORDER_BGR // Blue, Green, Red 302 | ``` 303 | 304 | **Description**: 305 | 306 | Specifies the byte order for transmitting colour data to LEDs. Most WS2812 strips use GRB order by default. 307 | 308 | **Default Colour Orders by LED Type**: 309 | 310 | - WS2812: `ORDER_GRB` 311 | - WS2812_RGB: `ORDER_RGB` 312 | - SK6812: `ORDER_GRB` 313 | - APA106: `ORDER_RGB` 314 | - SM16703: `ORDER_RGB` 315 | 316 | --- 317 | 318 | ## `ll_dma_t` 319 | 320 | ```cpp 321 | DMA_ON // Enable DMA for RMT transfers 322 | DMA_OFF // Disable DMA (use internal RMT memory) 323 | DMA_DEFAULT // Default behaviour - equivalent to DMA_OFF 324 | 325 | ``` 326 | 327 | **Description**: 328 | 329 | Controls whether the RMT peripheral uses DMA for data transfers. DMA can improve performance for long LED strips. 330 | 331 | **Availability**: 332 | 333 | Only supported on ESP32 variants with RMT DMA support. The library will issue a compile-time warning if DMA is not available on the selected chip. 334 | 335 | **Note**: 336 | 337 | - Some ESP32 models do not support RMT DMA. Check the data sheet of the SoC. 338 | - When using DMA, the total number of available RMT channels will be reduced. 339 | 340 | --- 341 | 342 | ## `ll_priority_t` 343 | 344 | RMT interrupt priority level 345 | 346 | ```cpp 347 | PRIORITY_DEFAULT // Default interrupt priority 348 | PRIORITY_HIGH // High priority 349 | PRIORITY_MED // Medium priority 350 | PRIORITY_LOW // Low priority 351 | ``` 352 | 353 | **Description:** Sets the interrupt priority for the RMT encoder callback. Higher priority ensures more precise timing for LED updates. 354 | 355 | **Availability:** Only supported with arduino-esp32 core v3.0.2 or greater. The library will issue a compile-time warning if not available. 356 | 357 | --- 358 | 359 | ## `ll_psram_t` 360 | 361 | PSRAM buffer allocation preference. 362 | 363 | ```cpp 364 | PSRAM_DISABLE // Allocate LED buffer in internal RAM 365 | PSRAM_ENABLE // Allocate LED buffer in PSRAM (if available) 366 | PSRAM_AUTO // Automatically use PSRAM if available 367 | ``` 368 | 369 | **Description**: Controls whether the LED buffer is allocated in internal RAM or external PSRAM. PSRAM is useful for large LED arrays (hundreds to thousands of LEDs). 370 | 371 | **Recommendations**: 372 | 373 | - **Small arrays** (<100 LEDs): Use `PSRAM_DISABLE` for best performance 374 | - **Large arrays** (>500 LEDs): Use `PSRAM_ENABLE` or `PSRAM_AUTO` 375 | - **Unknown requirements**: Use `PSRAM_AUTO` for automatic selection 376 | 377 | --- 378 | 379 | # Structures 380 | 381 | ## `rgb_t` 382 | 383 | RGB colour representation (struct style). 384 | 385 | ```cpp 386 | typedef struct { 387 | union { 388 | uint8_t r; 389 | uint8_t red; 390 | }; 391 | union { 392 | uint8_t g; 393 | uint8_t green; 394 | }; 395 | union { 396 | uint8_t b; 397 | uint8_t blue; 398 | }; 399 | } rgb_t; 400 | ``` 401 | 402 | **Description**: Represents an RGB colour with 8-bit values per channel (0-255). 403 | 404 | **Access Methods**: 405 | 406 | ```cpp 407 | rgb_t color; 408 | color.r = 255; // or color.red = 255; 409 | color.g = 128; // or color.green = 128; 410 | color.b = 0; // or color.blue = 0; 411 | ``` 412 | --- 413 | 414 | ## `crgb_t` 415 | 416 | RGB colour code (32-bit hex format). 417 | 418 | ```cpp 419 | typedef uint32_t crgb_t; 420 | ``` 421 | 422 | **Description**: Represents an RGB colour as a 32-bit integer in format `0x00RRGGBB`. 423 | 424 | **Examples**: 425 | 426 | ```cpp 427 | crgb_t red = 0xFF0000; 428 | crgb_t green = 0x00FF00; 429 | crgb_t blue = 0x0000FF; 430 | crgb_t white = 0xFFFFFF; 431 | ``` 432 | 433 | --- 434 | 435 | # Library API 436 | 437 | ## Constructor 438 | 439 | ### `LiteLED()` 440 | 441 | Creates a new LiteLED instance. 442 | 443 | ```cpp 444 | LiteLED(led_strip_type_t led_type, bool rgbw); 445 | ``` 446 | 447 | **Parameters**: 448 | 449 | - `led_type`: Type of LED strip (see [`led_strip_type_t`](#led_strip_type_t)) 450 | - `rgbw`: Set to `true` for RGBW strips, `false` for RGB strips 451 | 452 | **Example**: 453 | 454 | ```cpp 455 | // Create RGB WS2812 strip 456 | LiteLED strip(LED_STRIP_WS2812, false); 457 | 458 | // Create RGBW SK6812 strip 459 | LiteLED rgbwStrip(LED_STRIP_SK6812, true); 460 | ``` 461 | 462 | **Notes**: 463 | 464 | - Does not allocate hardware resources (call a `begin()` method to initialize) 465 | - Default brightness is 255 (full brightness) 466 | - Multiple instances can be created on different GPIO pins 467 | 468 | --- 469 | 470 | ## Destructor 471 | 472 | ### `~LiteLED()` 473 | 474 | Destroys the LiteLED instance and frees all resources. 475 | 476 | ```cpp 477 | ~LiteLED(); 478 | ``` 479 | 480 | **Description**: 481 | 482 | Automatically called when the object goes out of scope. 483 | 484 | Frees: 485 | 486 | - LED colour buffer (RAM or PSRAM) 487 | - RMT channel 488 | - RMT encoder 489 | - Peripheral Manager GPIO registration 490 | - Registry tracking entry 491 | 492 | --- 493 | 494 | ## Initialization Methods 495 | 496 | ### `begin()` - Basic 497 | 498 | Initialize LED strip with default settings. 499 | 500 | ```cpp 501 | esp_err_t begin(uint8_t data_pin, size_t length, bool auto_w = true); 502 | ``` 503 | 504 | **Parameters**: 505 | 506 | - `data_pin`: GPIO pin connected to LED strip data input (DIN) 507 | - `length`: Number of LEDs in the strip 508 | - `auto_w`: For RGBW strips, automatically calculate white channel (default: `true`) 509 | 510 | **Returns**: 511 | 512 | - `ESP_OK`: Success 513 | - `ESP_ERR_INVALID_ARG`: Invalid GPIO pin 514 | - `ESP_ERR_INVALID_STATE`: GPIO pin already in use 515 | - `ESP_ERR_NO_MEM`: Failed to allocate memory 516 | 517 | **Example**: 518 | 519 | ```cpp 520 | LiteLED strip( LED_STRIP_WS2812, false ); 521 | if ( strip.begin( 14, 60 ) == ESP_OK ) { 522 | // Strip initialized successfully 523 | } 524 | ``` 525 | 526 | **Notes**: 527 | 528 | - Allocates LED buffer in internal RAM 529 | - Uses default RMT settings (no DMA, default priority) 530 | - Registers GPIO with Peripheral Manager 531 | 532 | --- 533 | 534 | ### `begin()` - PSRAM 535 | 536 | Initialize LED strip with PSRAM control. 537 | 538 | ```cpp 539 | esp_err_t begin(uint8_t data_pin, size_t length, ll_psram_t psram_flag, bool auto_w = true); 540 | ``` 541 | 542 | **Parameters**: 543 | 544 | - `data_pin`: GPIO pin connected to LED strip data input 545 | - `length`: Number of LEDs in the strip 546 | - `psram_flag`: PSRAM allocation preference (see [`ll_psram_t`](#ll_psram_t)) 547 | - `auto_w`: For RGBW strips, automatically calculate white channel (default: `true`) 548 | 549 | **Returns**: 550 | 551 | Same as basic `begin()` 552 | 553 | **Example**: 554 | 555 | ```cpp 556 | // Large strip using PSRAM 557 | LiteLED largeStrip(LED_STRIP_WS2812, false); 558 | largeStrip.begin(14, 1000, PSRAM_AUTO); 559 | 560 | // Small strip forcing internal RAM 561 | LiteLED smallStrip(LED_STRIP_WS2812, false); 562 | smallStrip.begin(15, 30, PSRAM_DISABLE); 563 | ``` 564 | 565 | --- 566 | 567 | ### `begin()` - Full Configuration 568 | 569 | Initialize LED strip with all advanced options. 570 | 571 | ```cpp 572 | esp_err_t begin( uint8_t data_pin, size_t length, ll_dma_t dma_flag, 573 | ll_priority_t priority, ll_psram_t psram_flag, bool auto_w = true ); 574 | ``` 575 | 576 | **Parameters**: 577 | 578 | - `data_pin`: GPIO pin connected to LED strip data input 579 | - `length`: Number of LEDs in the strip 580 | - `dma_flag`: DMA usage setting (see [`ll_dma_t`](#ll_dma_t)) 581 | - `priority`: Interrupt priority (see [`ll_priority_t`](#ll_priority_t)) 582 | - `psram_flag`: PSRAM allocation preference (see [`ll_psram_t`](#ll_psram_t)) 583 | - `auto_w`: For RGBW strips, automatically calculate white channel (default: `true`) 584 | 585 | **Returns**: 586 | 587 | Same as basic `begin()` 588 | 589 | **Example**: 590 | 591 | ```cpp 592 | // High-performance configuration with DMA 593 | LiteLED perfStrip(LED_STRIP_WS2812, false); 594 | perfStrip.begin(14, 500, DMA_ON, PRIORITY_HIGH, PSRAM_AUTO); 595 | ``` 596 | 597 | **Notes**: 598 | 599 | - DMA only works where supported by the ESP32 variant 600 | - Use of DMA will reduce the total number of available RMT channels 601 | - Setting interrupt priority is only supported when using arduino-esp32 core v3.2.0 and greater 602 | - Invalid options are automatically handled (library issues warnings) 603 | 604 | --- 605 | 606 | ## Display Control Methods 607 | 608 | ### `show()` 609 | 610 | Send the LED buffer data to the strip. 611 | 612 | ```cpp 613 | esp_err_t show(); 614 | ``` 615 | 616 | **Returns**: 617 | 618 | - `ESP_OK`: Success 619 | - Error codes from RMT transmit operation 620 | 621 | **Description**: 622 | 623 | LiteLED maintains a buffer in memory that holds the colour data for each of the LED's in the strip. 624 | 625 | This data does not affect the colour of the LED's until a `show()` or a method that calls `show()` is used which transmits the LED buffer to the physical LED strip using the RMT peripheral. 626 | 627 | This is a blocking operation that waits for transmission to complete. 628 | 629 | **Example**: 630 | 631 | ```cpp 632 | strip.setPixel(0, rgb_from_code(0xFF0000)); // Set first LED to red 633 | strip.setPixel(1, rgb_from_code(0x00FF00)); // Set second LED to green 634 | strip.show(); // Update the strip 635 | ``` 636 | 637 | --- 638 | 639 | ### `clear()` 640 | 641 | Set all LEDs to black (off), optionally update strip. 642 | 643 | ```cpp 644 | esp_err_t clear(bool show = false); 645 | ``` 646 | 647 | **Parameters**: 648 | 649 | - `show`: If `true`, immediately update the strip (default: `false`) 650 | 651 | **Returns**: 652 | 653 | `ESP_OK` on success 654 | 655 | **Example**: 656 | 657 | ```cpp 658 | // Clear buffer but don't update strip yet 659 | strip.clear(); 660 | 661 | // Clear buffer and immediately update strip 662 | strip.clear(true); 663 | ``` 664 | 665 | --- 666 | 667 | ### `brightness()` 668 | 669 | Set global brightness level, optionally update strip. 670 | 671 | ```cpp 672 | esp_err_t brightness(uint8_t bright, bool show = false); 673 | ``` 674 | 675 | **Parameters**: 676 | 677 | - `bright`: Brightness level (0-255, where 0=off, 255=full brightness) 678 | - `show`: If `true`, immediately update the strip (default: `false`) 679 | 680 | **Returns**: 681 | 682 | `ESP_OK` on success 683 | 684 | **Description**: Sets global brightness without modifying the colour values in the buffer. This is applied during transmission to the LED strip. 685 | 686 | **Example**: 687 | 688 | ```cpp 689 | // Set brightness to 50% 690 | strip.brightness(128); 691 | strip.show(); 692 | 693 | // Set brightness to 20% and update immediately 694 | strip.brightness(51, true); 695 | ``` 696 | 697 | **Notes**: 698 | 699 | - Valid range is 0-255 700 | - Brightness scaling uses `scale8_video()` for smooth dimming 701 | - Setting brightness to 0 turns off all LEDs 702 | - Colour values in the buffer remain unchanged 703 | 704 | --- 705 | 706 | ### `getBrightness()` 707 | 708 | Get current brightness level. 709 | 710 | ```cpp 711 | uint8_t getBrightness(); 712 | ``` 713 | 714 | **Returns**: Current brightness value (0-255) 715 | 716 | **Example**: 717 | 718 | ```cpp 719 | uint8_t current = strip.getBrightness(); 720 | Serial.printf("Current brightness: %d\n", current); 721 | ``` 722 | 723 | **Notes:** 724 | 725 | The method returns the actual operating intensity value of the strip. That is, it returns the brightness value used the last time a `show()` or method that calls `show()` was called. 726 | 727 | If `getBrightness()` is used after using `brightness()` but before a `show()` or method that calls `show()` was used, the return value will be what was set with the `brightness()` method before the last call to `show()`. 728 | 729 | --- 730 | 731 | ## Pixel Manipulation Methods 732 | 733 | ### `setPixel()` - `rgb_t` 734 | 735 | Set single LED colour using `rgb_t` structure. 736 | 737 | ```cpp 738 | esp_err_t setPixel(size_t num, rgb_t color, bool show = false); 739 | ``` 740 | 741 | **Parameters**: 742 | 743 | - `num`: LED index (0-based) 744 | - `color`: Colour as `rgb_t` structure 745 | - `show`: If `true`, immediately update the strip (default: `false`) 746 | 747 | **Returns**: 748 | 749 | - `ESP_OK`: Success 750 | - `ESP_ERR_INVALID_ARG`: LED index out of bounds or strip not initialized 751 | 752 | **Example**: 753 | 754 | ```cpp 755 | rgb_t red = rgb_from_values(255, 0, 0); 756 | strip.setPixel(0, red); 757 | strip.show(); 758 | 759 | // Set and show immediately 760 | strip.setPixel(5, rgb_from_values(0, 255, 0), true); 761 | ``` 762 | 763 | --- 764 | 765 | ### `setPixel()` - `crgb_t` 766 | 767 | Set the colour of a single LED using 32-bit 768 | code. 769 | 770 | ```cpp 771 | esp_err_t setPixel(size_t num, crgb_t color, bool show = false); 772 | ``` 773 | 774 | **Parameters**: 775 | 776 | - `num`: LED index (0-based) 777 | - `color`: Colour as `crgb_t` 32-bit code (`0x00RRGGBB`) 778 | - `show`: If `true`, immediately update the strip (default: `false`) 779 | 780 | **Returns**: 781 | 782 | Same as `rgb_t` version 783 | 784 | **Example**: 785 | 786 | ```cpp 787 | strip.setPixel(0, 0xFF0000); // Red 788 | strip.setPixel(1, 0x00FF00); // Green 789 | strip.setPixel(2, 0x0000FF); // Blue 790 | strip.show(); 791 | ``` 792 | 793 | --- 794 | 795 | ### `setPixels()` - `rgb_t` Array 796 | 797 | Set multiple consecutive LEDs from an `rgb_t` array. 798 | 799 | ```cpp 800 | esp_err_t setPixels(size_t start, size_t len, rgb_t *data, bool show = false); 801 | ``` 802 | 803 | **Parameters**: 804 | 805 | - `start`: First LED index (0-based) 806 | - `len`: Number of LEDs to set 807 | - `data`: Pointer to array of `rgb_t` colours 808 | - `show`: If `true`, immediately update the strip (default: `false`) 809 | 810 | **Returns**: 811 | 812 | - `ESP_OK`: Success 813 | - `ESP_ERR_INVALID_ARG`: Invalid parameters or out of bounds 814 | 815 | **Example**: 816 | 817 | ```cpp 818 | rgb_t rainbow[] = { 819 | rgb_from_code(0xFF0000), // Red 820 | rgb_from_code(0xFF7F00), // Orange 821 | rgb_from_code(0xFFFF00), // Yellow 822 | rgb_from_code(0x00FF00), // Green 823 | rgb_from_code(0x0000FF), // Blue 824 | rgb_from_code(0x8B00FF) // Violet 825 | }; 826 | strip.setPixels(0, 6, rainbow, true); 827 | ``` 828 | 829 | --- 830 | 831 | ### `setPixels()` - `crgb_t` Array 832 | 833 | Set multiple consecutive LEDs from a `crgb_t` array. 834 | 835 | ```cpp 836 | esp_err_t setPixels(size_t start, size_t len, crgb_t *data, bool show = false); 837 | ``` 838 | 839 | **Parameters**: 840 | 841 | - `start`: First LED index (0-based) 842 | - `len`: Number of LEDs to set 843 | - `data`: Pointer to array of 32-bit colour codes 844 | - `show`: If `true`, immediately update the strip (default: `false`) 845 | 846 | **Returns**: 847 | 848 | Same as `rgb_t` version 849 | 850 | **Example**: 851 | 852 | ```cpp 853 | crgb_t colors[] = {0xFF0000, 0x00FF00, 0x0000FF}; 854 | strip.setPixels(10, 3, colors); 855 | strip.show(); 856 | ``` 857 | 858 | --- 859 | 860 | ### `fill()` - `rgb_t` 861 | 862 | Fill entire strip with a single colour. 863 | 864 | ```cpp 865 | esp_err_t fill(rgb_t color, bool show = false); 866 | ``` 867 | 868 | **Parameters**: 869 | 870 | - `color`: Colour as `rgb_t` structure 871 | - `show`: If `true`, immediately update the strip (default: `false`) 872 | 873 | **Returns**: 874 | 875 | `ESP_OK` on success 876 | 877 | **Example**: 878 | 879 | ```cpp 880 | // Fill with blue 881 | strip.fill(rgb_from_values(0, 0, 255), true); 882 | 883 | // Fill with custom colour 884 | rgb_t purple = {128, 0, 128}; 885 | strip.fill(purple); 886 | strip.show(); 887 | ``` 888 | 889 | --- 890 | 891 | ### `fill()` - `crgb_t` 892 | 893 | Fill entire strip with a single colour code. 894 | 895 | ```cpp 896 | esp_err_t fill(crgb_t color, bool show = false); 897 | ``` 898 | 899 | **Parameters**: 900 | 901 | - `color`: Colour as 32-bit code (`0x00RRGGBB`) 902 | - `show`: If `true`, immediately update the strip (default: `false`) 903 | 904 | **Returns**: 905 | 906 | `ESP_OK` on success 907 | 908 | **Example**: 909 | 910 | ```cpp 911 | // Fill with red and show immediately 912 | strip.fill(0xFF0000, true); 913 | ``` 914 | 915 | --- 916 | 917 | ### `fillRandom()` 918 | 919 | Fill strip with random colours. 920 | 921 | ```cpp 922 | esp_err_t fillRandom(bool show = false); 923 | ``` 924 | 925 | **Parameters**: 926 | 927 | - `show`: If `true`, immediately update the strip (default: `false`) 928 | 929 | **Returns**: 930 | 931 | `ESP_OK` on success 932 | 933 | **Description**: 934 | 935 | Fills each LED with a random RGB colour using the ESP32's hardware random number generator. 936 | 937 | **Example**: 938 | 939 | ```cpp 940 | // Fill with random colours 941 | strip.fillRandom(true); 942 | 943 | // Create random pattern every second 944 | void loop() { 945 | strip.fillRandom(true); 946 | delay(1000); 947 | } 948 | ``` 949 | **Note:** 950 | 951 | - Each colour channel of the LED is set independently to a random value between 5 and 255. Thus when using this method, the strip can be quite bright. The `brightness()` method can be used beforehand to lower the strip intensity. 952 | 953 | --- 954 | 955 | ## Pixel Reading Methods 956 | 957 | ### `getPixel()` 958 | 959 | Get colour of a single LED as `rgb_t` structure. 960 | 961 | ```cpp 962 | rgb_t getPixel(size_t num); 963 | ``` 964 | 965 | **Parameters**: 966 | 967 | - `num`: LED index (0-based) 968 | 969 | **Returns**: 970 | 971 | `rgb_t` structure with LED colour, or black (`{0,0,0}`) if invalid index 972 | 973 | **Example**: 974 | 975 | ```cpp 976 | rgb_t color = strip.getPixel(5); 977 | Serial.printf("LED 5: R=%d, G=%d, B=%d\n", color.r, color.g, color.b); 978 | ``` 979 | 980 | **Note**: 981 | 982 | - Returns the colour value in the buffer, not accounting for brightness scaling. 983 | 984 | --- 985 | 986 | ### `getPixelC()` 987 | 988 | Get colour of a single LED as 32-bit colour code. 989 | 990 | ```cpp 991 | crgb_t getPixelC(size_t num); 992 | ``` 993 | 994 | **Parameters**: 995 | 996 | - `num`: LED index (0-based) 997 | 998 | **Returns**: 32-bit colour code (`0x00RRGGBB`), or `0x000000` if invalid index 999 | 1000 | **Example**: 1001 | 1002 | ```cpp 1003 | crgb_t color = strip.getPixelC(10); 1004 | if (color == 0xFF0000) { 1005 | Serial.println("LED 10 is red"); 1006 | } 1007 | ``` 1008 | 1009 | --- 1010 | 1011 | ## Colour Order Methods 1012 | 1013 | ### `setOrder()` 1014 | 1015 | Set custom colour byte order for the LED strip. 1016 | 1017 | ```cpp 1018 | esp_err_t setOrder(color_order_t led_order = ORDER_GRB); 1019 | ``` 1020 | 1021 | **Parameters**: 1022 | 1023 | - `led_order`: Colour order (see [`color_order_t`](#color_order_t)) 1024 | 1025 | **Returns**: 1026 | 1027 | `ESP_OK` on success 1028 | 1029 | **Description**: 1030 | 1031 | Overrides the default colour order for the LED strip type. Useful for LED strips that don't match standard specifications. 1032 | 1033 | **Example**: 1034 | 1035 | ```cpp 1036 | // Force RGB order instead of default GRB 1037 | strip.setOrder(ORDER_RGB); 1038 | 1039 | // Some LED strips might need BGR 1040 | strip.setOrder(ORDER_BGR); 1041 | ``` 1042 | 1043 | **Notes**: 1044 | 1045 | - This method overrides the order set by the LED strip type. As all LED strip types have a defined colour order it is not required to call this method. It was added to address LED's with non-standard colour orders. 1046 | 1047 | - Can be called any time after declaring the LiteLED object and takes effect after a call to `show()` or any call that invokes `show()` and remains in effect until another call to `setOrder()` or `resetOrder()` is made. 1048 | 1049 | - FWIW, originally intended to be a "one and done" call, multiple calls can be made for creative effect. 1050 | 1051 | --- 1052 | 1053 | ### `resetOrder()` 1054 | 1055 | Reset colour order to the default for the LED strip type. 1056 | 1057 | ```cpp 1058 | esp_err_t resetOrder(); 1059 | ``` 1060 | 1061 | **Returns**: 1062 | 1063 | `ESP_OK` on success 1064 | 1065 | **Example**: 1066 | 1067 | ```cpp 1068 | // Restore default colour order 1069 | strip.resetOrder(); 1070 | ``` 1071 | 1072 | **Notes:** 1073 | 1074 | - Can be called any time after declaring the LiteLED object and takes effect after a call to `show()` or any call that invokes `show()` and remains in effect until another call to `setOrder()` or `resetOrder()` is made. 1075 | 1076 | --- 1077 | 1078 | ## Instance Management Methods 1079 | 1080 | ### `isValid()` 1081 | 1082 | Check if this LiteLED instance is still valid. 1083 | 1084 | ```cpp 1085 | bool isValid() const; 1086 | ``` 1087 | 1088 | **Returns**: 1089 | 1090 | - `true`: Instance is valid and GPIO pin is still owned by this instance 1091 | - `false`: GPIO pin was reassigned or instance is invalid 1092 | 1093 | **Description**: 1094 | 1095 | Checks with the LiteLED registry and Peripheral Manager to ensure the GPIO pin is still allocated to this instance. 1096 | 1097 | **Example**: 1098 | 1099 | ```cpp 1100 | if (!strip.isValid()) { 1101 | Serial.println("Warning: LED strip instance is no longer valid!"); 1102 | // Reinitialize or handle error 1103 | } 1104 | ``` 1105 | 1106 | **Use Case**: 1107 | 1108 | Useful in complex applications where GPIOs might be dynamically reassigned. 1109 | 1110 | --- 1111 | 1112 | ### `getGpioPin()` 1113 | 1114 | Get the GPIO pin number used by this instance. 1115 | 1116 | ```cpp 1117 | int getGpioPin() const; 1118 | ``` 1119 | 1120 | **Returns**: 1121 | 1122 | - GPIO pin number if initialized 1123 | - `-1` if not initialized 1124 | 1125 | **Example**: 1126 | 1127 | ```cpp 1128 | int pin = strip.getGpioPin(); 1129 | if (pin >= 0) { 1130 | Serial.printf("Strip using GPIO %d\n", pin); 1131 | } 1132 | ``` 1133 | 1134 | --- 1135 | 1136 | ### `isGpioAvailable()` - Static 1137 | 1138 | Check if a GPIO pin is available for LiteLED use. 1139 | 1140 | ```cpp 1141 | static bool isGpioAvailable(uint8_t gpio_pin); 1142 | ``` 1143 | 1144 | **Parameters:** 1145 | 1146 | - `gpio_pin`: GPIO pin number to check 1147 | 1148 | **Returns:** 1149 | 1150 | - `true`: GPIO is available 1151 | - `false`: GPIO is in use by another peripheral 1152 | 1153 | **Description:** 1154 | 1155 | Checks with the Peripheral Manager to see if the GPIO is free. 1156 | 1157 | **Example:** 1158 | 1159 | ```cpp 1160 | if (LiteLED::isGpioAvailable(14)) { 1161 | Serial.println("GPIO 14 is available for LED strip"); 1162 | } else { 1163 | Serial.println("GPIO 14 is already in use"); 1164 | } 1165 | ``` 1166 | 1167 | --- 1168 | 1169 | ### `getActiveInstanceCount()` - Static 1170 | 1171 | Get count of active LiteLED instances. 1172 | 1173 | ```cpp 1174 | static uint8_t getActiveInstanceCount(); 1175 | ``` 1176 | 1177 | **Returns**: 1178 | 1179 | Number of currently active LiteLED instances 1180 | 1181 | **Example**: 1182 | 1183 | ```cpp 1184 | uint8_t count = LiteLED::getActiveInstanceCount(); 1185 | Serial.printf("Active LED strip instances: %d\n", count); 1186 | ``` 1187 | 1188 | --- 1189 | 1190 | # Advanced Features 1191 | 1192 | ## Multi-Display Support 1193 | 1194 | LiteLED provides advanced support for managing multiple LED displays simultaneously with automatic interrupt priority management and conflict resolution. 1195 | 1196 | A maximum of eight displays are supported as at the time of writing that is the maximum number of available RMT channels of any ESP32 SoC. 1197 | 1198 | ### Simple Multi-Display Setup 1199 | 1200 | The recommended approach for most applications: 1201 | 1202 | ```cpp 1203 | LiteLED display1(LED_STRIP_WS2812, false); 1204 | LiteLED display2(LED_STRIP_WS2812, false); 1205 | LiteLED display3(LED_STRIP_WS2812, false); 1206 | 1207 | void setup() { 1208 | // Simple initialization - automatic priority management 1209 | display1.begin(14, 100); 1210 | display2.begin(27, 100); 1211 | display3.begin(26, 100); 1212 | 1213 | Serial.printf("Active displays: %d\n", LiteLED::getActiveInstanceCount()); 1214 | } 1215 | ``` 1216 | 1217 | ### Advanced Multi-Display Configuration 1218 | 1219 | For applications with specific requirements: 1220 | 1221 | ```cpp 1222 | void setup() { 1223 | // High priority display for time-critical applications 1224 | display1.begin(14, 100, DMA_OFF, PRIORITY_HIGH, PSRAM_DISABLE); 1225 | 1226 | // Automatic priority selection (recommended) 1227 | display2.begin(27, 500, DMA_OFF, PRIORITY_DEFAULT, PSRAM_AUTO); 1228 | 1229 | // Simple initialization (best for most cases) 1230 | display3.begin(26, 200); 1231 | } 1232 | ``` 1233 | 1234 | ### Automatic Priority Management 1235 | 1236 | LiteLED v3.0.0+ includes intelligent priority conflict resolution: 1237 | 1238 | - **Pre-flight checks**: Verifies priority availability before allocation 1239 | - **Smart fallback**: Automatically selects alternative priorities when conflicts occur 1240 | - **Resource tracking**: Monitors and manages interrupt priority usage 1241 | - **Graceful degradation**: Continues operation even when requested priorities are unavailable 1242 | 1243 | **Note:** 1244 | 1245 | - Interrupt priority support requires arduino-esp32 core version 3.2.0 or greater. 1246 | 1247 | #### Priority Levels 1248 | 1249 | | Priority | Enumeration | Description | 1250 | |:----------:|:----------:|-------------| 1251 | | 0 | `PRIORITY_DEFAULT` | Default priority (recommended) | 1252 | | 1 | `PRIORITY_HIGH` | High priority for time-critical displays | 1253 | | 2 | `PRIORITY_MED` | Medium priority | 1254 | | 3 | `PRIORITY_LOW` | Low priority for background displays | 1255 | 1256 | ### GPIO Management 1257 | 1258 | Integration with ESP32 Peripheral Manager ensures safe GPIO usage: 1259 | 1260 | ```cpp 1261 | // Check GPIO availability before use 1262 | if (LiteLED::isGpioAvailable(gpio_pin)) { 1263 | display.begin(gpio_pin, num_leds); // Safe to use 1264 | } else { 1265 | Serial.printf("GPIO %d is already in use\n", gpio_pin); 1266 | } 1267 | 1268 | // Get count of active instances 1269 | uint8_t active = LiteLED::getActiveInstanceCount(); 1270 | Serial.printf("Currently managing %d displays\n", active); 1271 | ``` 1272 | 1273 | ### Best Practices for Multi-Display 1274 | 1275 | 1. **Use simple initialization** when possible: 1276 | 1277 | ```cpp 1278 | display.begin(gpio, leds); // Recommended 1279 | ``` 1280 | 1281 | 2. **Check initialization results**: 1282 | 1283 | ```cpp 1284 | esp_err_t result = display.begin(gpio, leds); 1285 | if (result != ESP_OK) { 1286 | Serial.printf("Failed: %s\n", esp_err_to_name(result)); 1287 | } 1288 | ``` 1289 | 1290 | 3. **Monitor resources**: 1291 | 1292 | ```cpp 1293 | Serial.printf("Active: %d\n", LiteLED::getActiveInstanceCount()); 1294 | ``` 1295 | --- 1296 | 1297 | ## PSRAM for Large Arrays 1298 | 1299 | PSRAM is useful for LED arrays with hundreds of LEDs: 1300 | 1301 | ```cpp 1302 | // Auto-detect and use PSRAM if available 1303 | LiteLED bigStrip(LED_STRIP_WS2812, false); 1304 | bigStrip.begin(14, 2000, PSRAM_AUTO); 1305 | 1306 | // Force PSRAM usage 1307 | bigStrip.begin(14, 2000, PSRAM_ENABLE); 1308 | 1309 | // Force internal RAM (fastest) 1310 | bigStrip.begin(14, 100, PSRAM_DISABLE); 1311 | ``` 1312 | 1313 | **PSRAM Considerations**: 1314 | 1315 | - **Slightly slower** than internal RAM (~5-10% performance impact) 1316 | - **Essential for large arrays** (>500 LEDs) on ESP32 with limited RAM 1317 | - **Automatically detected** with `PSRAM_AUTO` 1318 | 1319 | --- 1320 | 1321 | ### Instance Validation 1322 | 1323 | In dynamic applications where GPIO pins might be reassigned: 1324 | 1325 | ```cpp 1326 | void loop() { 1327 | if (!strip.isValid()) { 1328 | Serial.println("Warning: Strip instance invalidated!"); 1329 | // Attempt to reinitialize 1330 | if (strip.begin(14, 60) == ESP_OK) { 1331 | Serial.println("Strip reinitialized"); 1332 | } 1333 | } 1334 | 1335 | // Normal operation 1336 | strip.fillRandom(true); 1337 | delay(1000); 1338 | } 1339 | ``` 1340 | 1341 | --- 1342 | 1343 | # Utilities 1344 | 1345 | ## LiteLED_Utils 1346 | 1347 | The `LiteLED_Utils` namespace provides compile-time utility functions for querying hardware capabilities. These functions allow developers to check platform support for various features before configuring their LED strips. 1348 | 1349 | ### `isDmaSupported()` 1350 | 1351 | **Description:** 1352 | 1353 | Checks at compile time whether the current ESP32 chip supports RMT DMA (Direct Memory Access) for LED strip operations. 1354 | 1355 | **Syntax:** 1356 | 1357 | ```cpp 1358 | constexpr bool LiteLED_Utils::isDmaSupported() 1359 | ``` 1360 | 1361 | **Parameters:** 1362 | 1363 | None 1364 | 1365 | **Returns:** 1366 | 1367 | - `true` - RMT DMA is supported on this ESP32 model 1368 | - `false` - RMT DMA is not supported on this ESP32 model 1369 | 1370 | **Notes:** 1371 | 1372 | - This is a `constexpr` function, so the result is determined at compile time 1373 | - On chips without DMA support (ESP32, ESP32-C2), this returns `false` 1374 | - On chips with DMA support (ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2), this returns `true` 1375 | - If you attempt to use `DMA_ON` on unsupported hardware, the library will automatically fall back to `DMA_OFF` with a warning 1376 | 1377 | **Example Usage:** 1378 | 1379 | ```cpp 1380 | #include 1381 | 1382 | void setup() { 1383 | Serial.begin(115200); 1384 | 1385 | // Query DMA support at compile time 1386 | if (LiteLED_Utils::isDmaSupported()) { 1387 | Serial.println("This chip supports RMT DMA"); 1388 | } 1389 | else { 1390 | Serial.println("This chip does not support RMT DMA"); 1391 | } 1392 | 1393 | // Use in conditional initialization 1394 | LiteLED strip(LED_STRIP_WS2812, false); 1395 | if (LiteLED_Utils::isDmaSupported()) { 1396 | // Enable DMA for better performance on supported chips 1397 | strip.begin(14, 100, DMA_ON, PRIORITY_DEFAULT, PSRAM_AUTO); 1398 | } 1399 | else { 1400 | // Use non-DMA mode on chips without support 1401 | strip.begin(14, 100, DMA_OFF, PRIORITY_DEFAULT, PSRAM_AUTO); 1402 | } 1403 | } 1404 | ``` 1405 | 1406 | --- 1407 | 1408 | ### `isPrioritySupported()` 1409 | 1410 | **Description:** 1411 | Checks at compile time whether the current ESP-IDF/arduino-esp32 core version supports setting RMT interrupt priority levels. 1412 | 1413 | **Syntax:** 1414 | 1415 | ```cpp 1416 | constexpr bool LiteLED_Utils::isPrioritySupported() 1417 | ``` 1418 | 1419 | **Parameters:** 1420 | 1421 | None 1422 | 1423 | **Returns:** 1424 | 1425 | - `true` - Interrupt priority setting is supported (ESP-IDF 5.1.2 or later) 1426 | - `false` - Interrupt priority setting is not supported (older ESP-IDF versions) 1427 | 1428 | **Notes:** 1429 | 1430 | - This is a `constexpr` function, so the result is determined at compile time 1431 | - Requires ESP-IDF version 5.1.2 or higher for support 1432 | - If priority setting is not supported, the library will use the default priority (0) and log a note 1433 | - Attempting to set priority on unsupported versions is safe - the library gracefully falls back to defaults 1434 | 1435 | **Example Usage:** 1436 | 1437 | ```cpp 1438 | #include 1439 | 1440 | void setup() { 1441 | Serial.begin(115200); 1442 | 1443 | // Query interrupt priority support at compile time 1444 | if (LiteLED_Utils::isPrioritySupported()) { 1445 | Serial.println("This core version supports interrupt priority setting"); 1446 | } else { 1447 | Serial.println("This core version uses default interrupt priority"); 1448 | } 1449 | 1450 | // Use in conditional configuration 1451 | LiteLED strip(LED_STRIP_WS2812, false); 1452 | if (LiteLED_Utils::isPrioritySupported()) { 1453 | // Set custom priority on supported cores 1454 | strip.begin(14, 100, DMA_OFF, PRIORITY_HIGH, PSRAM_AUTO); 1455 | } else { 1456 | // Priority parameter is ignored on older cores (uses default) 1457 | strip.begin(14, 100, DMA_OFF, PRIORITY_DEFAULT, PSRAM_AUTO); 1458 | } 1459 | } 1460 | ``` 1461 | 1462 | --- 1463 | 1464 | ### Complete Example 1465 | 1466 | Here's a comprehensive example showing how to use both utility functions together: 1467 | 1468 | ```cpp 1469 | #include 1470 | 1471 | #define LED_GPIO 14 1472 | #define LED_COUNT 100 1473 | 1474 | LiteLED myStrip(LED_STRIP_WS2812, false); 1475 | 1476 | void setup() { 1477 | Serial.begin(115200); 1478 | delay(2000); 1479 | 1480 | // Display hardware capabilities 1481 | Serial.println("=== LiteLED Hardware Capabilities ==="); 1482 | Serial.printf("RMT DMA Support: %s\n", 1483 | LiteLED_Utils::isDmaSupported() ? "YES" : "NO"); 1484 | Serial.printf("Interrupt Priority Support: %s\n", 1485 | LiteLED_Utils::isPrioritySupported() ? "YES" : "NO"); 1486 | Serial.printf("DMA_DEFAULT value: %s\n", 1487 | (DMA_DEFAULT == DMA_ON) ? "DMA_ON" : "DMA_OFF"); 1488 | Serial.println("=====================================\n"); 1489 | 1490 | // Smart initialization based on capabilities 1491 | ll_dma_t dma_setting = DMA_DEFAULT; // Let the library choose the best default 1492 | ll_priority_t priority = PRIORITY_DEFAULT; 1493 | 1494 | // Optionally enable DMA on supported hardware if you want the performance boost 1495 | if (LiteLED_Utils::isDmaSupported() && LED_COUNT > 300) { 1496 | dma_setting = DMA_ON; // DMA is beneficial for large strips 1497 | Serial.println("Enabling DMA for large LED strip"); 1498 | } 1499 | 1500 | // Optionally set higher priority on supported cores if timing is critical 1501 | if (LiteLED_Utils::isPrioritySupported()) { 1502 | priority = PRIORITY_HIGH; 1503 | Serial.println("Setting high interrupt priority"); 1504 | } 1505 | 1506 | // Initialize with optimal settings 1507 | esp_err_t result = myStrip.begin(LED_GPIO, LED_COUNT, dma_setting, priority, PSRAM_AUTO); 1508 | 1509 | if (result == ESP_OK) { 1510 | Serial.println("LED strip initialized successfully!"); 1511 | myStrip.brightness(50); 1512 | myStrip.fill(0x00FF00, true); // Green 1513 | } 1514 | else { 1515 | Serial.printf("LED strip initialization failed: %s\n", esp_err_to_name(result)); 1516 | } 1517 | } 1518 | 1519 | void loop() { 1520 | // Your LED animation code here 1521 | } 1522 | ``` 1523 | ### Integration with Existing Code 1524 | 1525 | These utility functions are particularly useful when: 1526 | 1527 | 1. **Writing portable code** that runs on multiple ESP32 variants 1528 | 2. **Creating libraries** that build on top of LiteLED 1529 | 3. **Performance optimization** where you want to enable DMA only on supported chips 1530 | 4. **Debugging** to understand platform limitations 1531 | 5. **Compile-time configuration** using `if constexpr` in C++17 or later 1532 | 1533 | ### Related Configuration 1534 | 1535 | #### `DMA_DEFAULT` Behaviour 1536 | 1537 | The `DMA_DEFAULT` enum value is always set to `DMA_OFF` to preserve DMA channels for user applications. This is a conservative default that works on all ESP32 chips. Users who want DMA performance benefits should explicitly use `DMA_ON` after checking `isDmaSupported()`. 1538 | 1539 | --- 1540 | 1541 | ## Colour Utility Functions 1542 | 1543 | A number of functions are included to assist in manipulation and conversion of colours. 1544 | 1545 | ### `rgb_from_code()` 1546 | 1547 | Convert a 32-bit colour code to `rgb_t` structure. 1548 | 1549 | ```cpp 1550 | static inline rgb_t rgb_from_code(crgb_t color_code); 1551 | ``` 1552 | 1553 | **Parameters**: 1554 | 1555 | - `color_code`: 32-bit colour in format `0x00RRGGBB` 1556 | 1557 | **Returns**: `rgb_t` structure with separated R, G, B values 1558 | 1559 | **Example**: 1560 | 1561 | ```cpp 1562 | rgb_t red = rgb_from_code(0xFF0000); 1563 | // red.r = 255, red.g = 0, red.b = 0 1564 | ``` 1565 | 1566 | --- 1567 | 1568 | ### `rgb_from_values()` 1569 | 1570 | Create an `rgb_t` colour from individual channel values. 1571 | 1572 | ```cpp 1573 | static inline rgb_t rgb_from_values(uint8_t r, uint8_t g, uint8_t b); 1574 | ``` 1575 | 1576 | **Parameters**: 1577 | 1578 | - `r`: Red value (0-255) 1579 | - `g`: Green value (0-255) 1580 | - `b`: Blue value (0-255) 1581 | 1582 | **Returns**: `rgb_t` structure 1583 | 1584 | **Example**: 1585 | 1586 | ```cpp 1587 | rgb_t purple = rgb_from_values(128, 0, 128); 1588 | ``` 1589 | 1590 | --- 1591 | 1592 | ### `rgb_to_code()` 1593 | 1594 | Convert an `rgb_t` structure to a 32-bit colour code. 1595 | 1596 | ```cpp 1597 | static inline crgb_t rgb_to_code(rgb_t color); 1598 | ``` 1599 | 1600 | **Parameters**: 1601 | 1602 | - `color`: `rgb_t` structure 1603 | 1604 | **Returns**: 32-bit colour code in format `0x00RRGGBB` 1605 | 1606 | **Example**: 1607 | 1608 | ```cpp 1609 | rgb_t color = {255, 128, 0}; 1610 | crgb_t code = rgb_to_code(color); // Returns 0xFF8000 1611 | ``` 1612 | 1613 | --- 1614 | 1615 | ### `rgb_is_zero()` 1616 | 1617 | Check if an RGB colour is black (all channels zero). 1618 | 1619 | ```cpp 1620 | static inline bool rgb_is_zero(rgb_t a); 1621 | ``` 1622 | 1623 | **Parameters**: 1624 | 1625 | - `a`: `rgb_t` colour to test 1626 | 1627 | **Returns**: `true` if all colour channels are 0, `false` otherwise 1628 | 1629 | **Example**: 1630 | 1631 | ```cpp 1632 | rgb_t black = {0, 0, 0}; 1633 | if (rgb_is_zero(black)) { 1634 | // Colour is black 1635 | } 1636 | ``` 1637 | 1638 | --- 1639 | 1640 | ### `rgb_luma()` 1641 | 1642 | Calculate the perceived brightness (luma) of an RGB colour. 1643 | 1644 | ```cpp 1645 | static inline uint8_t rgb_luma(rgb_t a); 1646 | ``` 1647 | 1648 | **Parameters**: 1649 | 1650 | - `a`: `rgb_t` color 1651 | 1652 | **Returns**: Luma value (0-255) 1653 | 1654 | **Description**: Calculates perceptual brightness using the formula: 1655 | 1656 | ``` 1657 | Luma = (R * 54 + G * 183 + B * 18) / 256 1658 | ``` 1659 | 1660 | This approximates human eye sensitivity where green contributes most to perceived brightness. 1661 | 1662 | **Example**: 1663 | 1664 | ```cpp 1665 | rgb_t color = {100, 200, 50}; 1666 | uint8_t brightness = rgb_luma(color); // Returns ~158 1667 | ``` 1668 | 1669 | --- 1670 | 1671 | ### `scale8()` and `scale8_video()` 1672 | 1673 | Scale an 8-bit value by a fractional amount. 1674 | 1675 | ```cpp 1676 | static inline uint8_t scale8(uint8_t i, fract8 scale); 1677 | static inline uint8_t scale8_video(uint8_t i, fract8 scale); 1678 | ``` 1679 | 1680 | **Parameters**: 1681 | 1682 | - `i`: Value to scale (0-255) 1683 | - `scale`: Scale factor (0-255, represents 0.0-1.0) 1684 | 1685 | **Returns**: 1686 | 1687 | Scaled value (0-255) 1688 | 1689 | **Difference**: 1690 | 1691 | - `scale8()`: Fast scaling, may produce zero for non-zero inputs 1692 | - `scale8_video()`: Guarantees non-zero output if both inputs are non-zero (better for LED dimming) 1693 | 1694 | **Example**: 1695 | 1696 | ```cpp 1697 | uint8_t half_bright = scale8_video(255, 128); // Returns 128 1698 | uint8_t dim = scale8_video(255, 10); // Returns ~10 1699 | ``` 1700 | 1701 | --- 1702 | 1703 | 1704 | # Performance Considerations 1705 | 1706 | ## Transmission Time 1707 | 1708 | LED strip update time depends on the number of LEDs: 1709 | 1710 | | LED Count | Transmission Time (approx.) | 1711 | |-----------|------------------------------| 1712 | | 30 LEDs | ~1 ms | 1713 | | 60 LEDs | ~2 ms | 1714 | | 150 LEDs | ~5 ms | 1715 | | 300 LEDs | ~10 ms | 1716 | | 600 LEDs | ~20 ms | 1717 | | 1000 LEDs | ~33 ms | 1718 | 1719 | **Formula**: Time ≈ (LED_COUNT × 30µs) 1720 | 1721 | ## Memory Usage 1722 | 1723 | **RGB Strips**: 3 bytes per LED 1724 | ``` 1725 | 30 LEDs = 90 bytes 1726 | 60 LEDs = 180 bytes 1727 | 300 LEDs = 900 bytes 1728 | 1000 LEDs = 3 KB 1729 | ``` 1730 | 1731 | **RGBW Strips**: 4 bytes per LED 1732 | ``` 1733 | 30 LEDs = 120 bytes 1734 | 60 LEDs = 240 bytes 1735 | 300 LEDs = 1.2 KB 1736 | 1000 LEDs = 4 KB 1737 | ``` 1738 | 1739 | --- 1740 | 1741 | # Optimization Tips 1742 | 1743 | 1. **Use DMA for large arrays** (300+ LEDs on supported chips) 1744 | 2. **Use PSRAM for very large arrays** (1000+ LEDs) 1745 | 3. **Batch updates**: Set multiple pixels before calling `show()` 1746 | 4. **Avoid frequent `show()` calls**: Maximum practical update rate is ~60 Hz 1747 | 5. **Use `show` parameter**: Methods like `setPixel()` have optional `show` parameter for immediate update 1748 | 1749 | **Good Practice**: 1750 | 1751 | ```cpp 1752 | // Efficient: Batch updates 1753 | for (size_t i = 0; i < LED_COUNT; i++) { 1754 | strip.setPixel(i, color); 1755 | } 1756 | strip.show(); // Single update 1757 | 1758 | // Inefficient: Update after each pixel 1759 | for (size_t i = 0; i < LED_COUNT; i++) { 1760 | strip.setPixel(i, color, true); // Don't do this! 1761 | } 1762 | ``` 1763 | 1764 | --- 1765 | 1766 | # Troubleshooting 1767 | 1768 | ## No LEDs Light Up 1769 | 1770 | 1. **Check GPIO connection**: Verify DIN pin connection 1771 | 2. **Check power supply**: LEDs need adequate power (5V, sufficient current) 1772 | 3. **Check LED type**: Ensure `LED_STRIP_*****` matches your LED type 1773 | 4. **Verify initialization**: Check `begin()` return value 1774 | 5. **Call `show()`**: LED buffer must be transmitted with `show()` 1775 | 1776 | ## Wrong Colours 1777 | 1778 | 1. **Check colour order**: Try different `setOrder()` values 1779 | 2. **Check LED type**: Some "WS2812" clones use RGB instead of GRB 1780 | 3. **Power supply issues**: Insufficient power can cause colour errors 1781 | 1782 | ## Flickering or Glitches 1783 | 1784 | 1. **Add 0.1µF capacitor** between ESP32 GND and LED strip GND 1785 | 1. **Use shorter wires** for DIN connection (< 15cm recommended) 1786 | 1. **Add 330Ω resistor** in series with DIN line 1787 | 1. **Use a level shifter** between the GPIO pin and the DIN line if the strip power supply is greater than 3.3V. 1788 | 1. **Check power supply quality**: Use stable supply 1789 | 1790 | ## GPIO Already in Use Error 1791 | 1792 | 1. **Check for duplicate instances** using same GPIO 1793 | 2. **Free previous instance** before reusing GPIO 1794 | 3. **Use `isGpioAvailable()`** to verify if GPIO is free 1795 | 1796 | ## Memory Allocation Failed 1797 | 1798 | 1. **Reduce LED count** if using internal RAM 1799 | 2. **Enable PSRAM**: Use `PSRAM_AUTO` or `PSRAM_ENABLE` 1800 | 3. **Check available heap**: Use `ESP.getFreeHeap()` 1801 | 1802 | ## Compile-Time Assertions 1803 | 1804 | The library performs the following compile-time checks and will halt compilation if any of the following conditions are found.: 1805 | 1806 | * **ESP32 platform:** target is not an ESP32 microcontroller. 1807 | * **RMT peripheral:** target ESP32 does not have an RMT peripheral. 1808 | * **arduino-esp32 core version:** the version of the arduino-esp32 core is incompatible with this version of the library. 1809 | 1810 | ## Compile-Time Warnings 1811 | 1812 | When compiling for chips or core versions with limited capabilities, you'll see compile-time warnings: 1813 | 1814 | - `"LiteLED: Selected ESP32 model does not support RMT DMA access. Use of RMT DMA will be disabled."` 1815 | - `"LiteLED: This version of the core does not support setting of RMT interrupt priority. Default will be used."` 1816 | 1817 | These warnings inform you at build time of platform limitations, while the utility functions allow you to query these capabilities programmatically. 1818 | 1819 | --- 1820 | 1821 | ## Error Handling 1822 | 1823 | ### Return Status Codes 1824 | 1825 | The LiteLED methods that write to the LED string buffer or set/reset the colour order return a status code of `esp_err_t ` type on completion. Checking this code and taking action is optional and is an exercise left to the developer. 1826 | 1827 | If things go as normal, the return code is `ESP_OK` which is of type `int` with a value of `0`. So a quick check would be, if the return code is anything other than `0`, something went amok. 1828 | 1829 | Full description of these codes can be found on the Espressif ESP-IDF site [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/error-codes.html?highlight=error%20handling). 1830 | 1831 | If you're really interested in diving deeper, head over to the Espressif [ESP-IDF Error Handling](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/error-handling.html?highlight=error%20handling) docs. 1832 | 1833 | 1834 | | Error Code | Value | Description | 1835 | |------------|:-------:|-------------| 1836 | | `ESP_OK` | `0` | Success | 1837 | | `ESP_ERR_INVALID_ARG` | `0x102` | Invalid argument (e.g., invalid GPIO, out-of-bounds LED index) | 1838 | | `ESP_ERR_INVALID_STATE` | `0x103` | Invalid state (e.g., GPIO already in use) | 1839 | | `ESP_ERR_NO_MEM` | `0x101` | Memory allocation failed | 1840 | | `ESP_FAIL` | `-1` | Generic failure | 1841 | 1842 | 1843 | ### Error Checking Pattern 1844 | 1845 | ```cpp 1846 | esp_err_t err = strip.begin(14, 60); 1847 | if (err != ESP_OK) { 1848 | Serial.printf("Failed to initialize LED strip: %s\n", esp_err_to_name(err)); 1849 | // Handle error 1850 | } 1851 | ``` 1852 | 1853 | ### Common Error Scenarios 1854 | 1855 | #### GPIO Already in Use 1856 | 1857 | ```cpp 1858 | // First strip uses GPIO 14 1859 | LiteLED strip1(LED_STRIP_WS2812, false); 1860 | strip1.begin(14, 30); // OK 1861 | 1862 | // Attempting to use same GPIO fails 1863 | LiteLED strip2(LED_STRIP_WS2812, false); 1864 | esp_err_t err = strip2.begin(14, 60); // Returns ESP_ERR_INVALID_STATE 1865 | ``` 1866 | 1867 | **Solution**: 1868 | 1869 | Use different GPIO pins for each strip, or free the first strip before reusing the GPIO. 1870 | 1871 | --- 1872 | 1873 | #### Out of Bounds LED Index 1874 | 1875 | ```cpp 1876 | LiteLED strip(LED_STRIP_WS2812, false); 1877 | strip.begin(14, 30); // 30 LEDs (indices 0-29) 1878 | 1879 | esp_err_t err = strip.setPixel(30, 0xFF0000); // Returns ESP_ERR_INVALID_ARG 1880 | ``` 1881 | 1882 | **Solution**: 1883 | 1884 | Ensure LED indices are within valid range (0 to length-1). 1885 | 1886 | --- 1887 | 1888 | #### Memory Allocation Failure 1889 | 1890 | ```cpp 1891 | // Very large LED array might fail 1892 | LiteLED hugeStrip(LED_STRIP_WS2812, false); 1893 | esp_err_t err = hugeStrip.begin(14, 10000, PSRAM_DISABLE); // May return ESP_ERR_NO_MEM 1894 | ``` 1895 | 1896 | **Solution**: 1897 | 1898 | Use PSRAM for large arrays, reduce LED count, or free unused memory. 1899 | 1900 | --- 1901 | 1902 | ## Logging 1903 | 1904 | To assist in debugging, a number of status and error messages can be sent to the serial port via the esp32 `log_'x'` facility. 1905 | 1906 | All log messages from the library are sent at the `log_d` level except a very nerdy and long LiteLED Debug Report which is sent at the `log_v` level. 1907 | 1908 | To see these messages, in the Arduino IDE, set the *Core Debug Level* from the *Tools* menu to either *Debug* or *Verbose* 1909 | 1910 | If using PlatformIO or pioarduino, add `-DCORE_DEBUG_LEVEL=` to the `build_flags` section the `platformio.ini` file setting where `` is either `4` (debug) or `5` (verbose). 1911 | 1912 | If something is not behaving as you think, or you're just curious, set the log level to Debug or Verbose, recompile and upload the sketch and review the messages. 1913 | 1914 | --- 1915 | 1916 | # Usage Examples 1917 | 1918 | ## Basic Example - Solid Colour 1919 | 1920 | ```cpp 1921 | #include 1922 | 1923 | #define LED_GPIO 14 1924 | #define LED_COUNT 30 1925 | 1926 | LiteLED strip(LED_STRIP_WS2812, false); 1927 | 1928 | void setup() { 1929 | Serial.begin(115200); 1930 | 1931 | if (strip.begin(LED_GPIO, LED_COUNT) == ESP_OK) { 1932 | Serial.println("LED strip initialized"); 1933 | 1934 | // Set all LEDs to blue at 50% brightness 1935 | strip.brightness(128); 1936 | strip.fill(0x0000FF, true); 1937 | } else { 1938 | Serial.println("Failed to initialize LED strip"); 1939 | } 1940 | } 1941 | 1942 | void loop() { 1943 | // Nothing to do 1944 | } 1945 | ``` 1946 | 1947 | --- 1948 | 1949 | ## Brightness Ramping 1950 | 1951 | Smooth brightness transitions: 1952 | 1953 | ```cpp 1954 | void rampBrightness(LiteLED &strip, uint8_t target, uint8_t step = 1) { 1955 | uint8_t current = strip.getBrightness(); 1956 | 1957 | if (current < target) { 1958 | for (uint8_t b = current; b <= target; b += step) { 1959 | strip.brightness(b, true); 1960 | delay(10); 1961 | } 1962 | } else { 1963 | for (uint8_t b = current; b >= target; b -= step) { 1964 | strip.brightness(b, true); 1965 | delay(10); 1966 | } 1967 | } 1968 | } 1969 | 1970 | // Usage 1971 | rampBrightness(strip, 255, 5); // Fade up to full brightness 1972 | delay(2000); 1973 | rampBrightness(strip, 0, 5); // Fade down to off 1974 | ``` 1975 | 1976 | --- 1977 | 1978 | ## Rainbow Pattern 1979 | 1980 | ```cpp 1981 | #include 1982 | 1983 | #define LED_GPIO 14 1984 | #define LED_COUNT 60 1985 | 1986 | LiteLED strip(LED_STRIP_WS2812, false); 1987 | 1988 | void setup() { 1989 | strip.begin(LED_GPIO, LED_COUNT); 1990 | strip.brightness(50); 1991 | } 1992 | 1993 | void loop() { 1994 | static uint8_t hue = 0; 1995 | 1996 | for (size_t i = 0; i < LED_COUNT; i++) { 1997 | // Create rainbow effect 1998 | uint8_t pixelHue = hue + (i * 255 / LED_COUNT); 1999 | strip.setPixel(i, HSVtoRGB(pixelHue, 255, 255)); 2000 | } 2001 | 2002 | strip.show(); 2003 | hue += 2; 2004 | delay(20); 2005 | } 2006 | 2007 | // Helper function to convert HSV to RGB 2008 | crgb_t HSVtoRGB(uint8_t h, uint8_t s, uint8_t v) { 2009 | uint8_t region, remainder, p, q, t; 2010 | uint8_t r, g, b; 2011 | 2012 | if (s == 0) { 2013 | return ((uint32_t)v << 16) | ((uint32_t)v << 8) | v; 2014 | } 2015 | 2016 | region = h / 43; 2017 | remainder = (h - (region * 43)) * 6; 2018 | 2019 | p = (v * (255 - s)) >> 8; 2020 | q = (v * (255 - ((s * remainder) >> 8))) >> 8; 2021 | t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; 2022 | 2023 | switch (region) { 2024 | case 0: r = v; g = t; b = p; break; 2025 | case 1: r = q; g = v; b = p; break; 2026 | case 2: r = p; g = v; b = t; break; 2027 | case 3: r = p; g = q; b = v; break; 2028 | case 4: r = t; g = p; b = v; break; 2029 | default: r = v; g = p; b = q; break; 2030 | } 2031 | 2032 | return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; 2033 | } 2034 | ``` 2035 | 2036 | --- 2037 | 2038 | ## Multiple Strips 2039 | 2040 | ```cpp 2041 | #include 2042 | 2043 | #define STRIP1_GPIO 14 2044 | #define STRIP2_GPIO 27 2045 | #define LED_COUNT 30 2046 | 2047 | LiteLED strip1(LED_STRIP_WS2812, false); 2048 | LiteLED strip2(LED_STRIP_WS2812, false); 2049 | 2050 | void setup() { 2051 | Serial.begin(115200); 2052 | 2053 | // Initialize first strip 2054 | if (strip1.begin(STRIP1_GPIO, LED_COUNT) == ESP_OK) { 2055 | Serial.println("Strip 1 initialized"); 2056 | strip1.fill(0xFF0000, true); // Red 2057 | } 2058 | 2059 | // Initialize second strip 2060 | if (strip2.begin(STRIP2_GPIO, LED_COUNT) == ESP_OK) { 2061 | Serial.println("Strip 2 initialized"); 2062 | strip2.fill(0x0000FF, true); // Blue 2063 | } 2064 | 2065 | Serial.printf("Active instances: %d\n", LiteLED::getActiveInstanceCount()); 2066 | } 2067 | 2068 | void loop() { 2069 | // Alternate colours 2070 | delay(1000); 2071 | strip1.fill(0x00FF00, true); // Green 2072 | strip2.fill(0xFF00FF, true); // Magenta 2073 | 2074 | delay(1000); 2075 | strip1.fill(0xFF0000, true); // Red 2076 | strip2.fill(0x0000FF, true); // Blue 2077 | } 2078 | ``` 2079 | 2080 | --- 2081 | 2082 | ## RGBW Strip with Auto White 2083 | 2084 | ```cpp 2085 | #include 2086 | 2087 | #define LED_GPIO 14 2088 | #define LED_COUNT 30 2089 | 2090 | LiteLED rgbwStrip(LED_STRIP_SK6812, true); // RGBW mode 2091 | 2092 | void setup() { 2093 | // Enable automatic white channel calculation 2094 | rgbwStrip.begin(LED_GPIO, LED_COUNT, true); 2095 | rgbwStrip.brightness(100); 2096 | } 2097 | 2098 | void loop() { 2099 | // Set pure white - auto_w will calculate W channel 2100 | rgbwStrip.fill(rgb_from_values(255, 255, 255), true); 2101 | delay(1000); 2102 | 2103 | // Set colour (no white channel) 2104 | rgbwStrip.fill(rgb_from_values(255, 0, 0), true); 2105 | delay(1000); 2106 | } 2107 | ``` 2108 | 2109 | --- 2110 | 2111 | ## Large Array with PSRAM 2112 | 2113 | ```cpp 2114 | #include 2115 | 2116 | #define LED_GPIO 14 2117 | #define LED_COUNT 1000 // Large array 2118 | 2119 | LiteLED bigStrip(LED_STRIP_WS2812, false); 2120 | 2121 | void setup() { 2122 | Serial.begin(115200); 2123 | 2124 | // Use PSRAM for large buffer 2125 | esp_err_t err = bigStrip.begin(LED_GPIO, LED_COUNT, PSRAM_AUTO); 2126 | 2127 | if (err == ESP_OK) { 2128 | Serial.println("Large strip initialized successfully"); 2129 | Serial.printf("Using %d LEDs\n", LED_COUNT); 2130 | 2131 | // Create gradient effect 2132 | for (size_t i = 0; i < LED_COUNT; i++) { 2133 | uint8_t brightness = (i * 255) / LED_COUNT; 2134 | bigStrip.setPixel(i, rgb_from_values(brightness, 0, 255 - brightness)); 2135 | } 2136 | bigStrip.show(); 2137 | } else { 2138 | Serial.printf("Failed to initialize: %s\n", esp_err_to_name(err)); 2139 | } 2140 | } 2141 | 2142 | void loop() { 2143 | // Nothing to do 2144 | } 2145 | ``` 2146 | 2147 | --- 2148 | 2149 | ## High-Performance with DMA 2150 | 2151 | ```cpp 2152 | #include 2153 | 2154 | #define LED_GPIO 14 2155 | #define LED_COUNT 300 2156 | 2157 | LiteLED perfStrip(LED_STRIP_WS2812, false); 2158 | 2159 | void setup() { 2160 | Serial.begin(115200); 2161 | 2162 | // Use DMA and high priority for best performance 2163 | esp_err_t err = perfStrip.begin(LED_GPIO, LED_COUNT, DMA_ON, 2164 | PRIORITY_HIGH, PSRAM_AUTO); 2165 | 2166 | if (err == ESP_OK) { 2167 | Serial.println("High-performance strip initialized"); 2168 | } else { 2169 | Serial.printf("Init failed: %s\n", esp_err_to_name(err)); 2170 | } 2171 | } 2172 | 2173 | void loop() { 2174 | // Fast animation updates 2175 | static uint8_t offset = 0; 2176 | 2177 | for (size_t i = 0; i < LED_COUNT; i++) { 2178 | uint8_t pos = (i + offset) & 0xFF; 2179 | perfStrip.setPixel(i, rgb_from_values(pos, 0, 255 - pos)); 2180 | } 2181 | 2182 | perfStrip.show(); 2183 | offset += 2; 2184 | delay(10); 2185 | } 2186 | ``` 2187 | 2188 | --- 2189 | 2190 | # Version History 2191 | 2192 | **v3.0.0** 2193 | - Initial release for LiteLED library version 3.0.0. 2194 | 2195 | 2196 | --------------------------------------------------------------------------------