# TasmotaLED Library Documentation ## Executive Summary TasmotaLED is a lightweight, high-performance library for controlling addressable LED strips on ESP32 microcontrollers. It serves as a streamlined replacement for the NeoPixelBus library, focusing on efficient pixel pushing with minimal memory overhead while supporting multiple hardware acceleration methods (RMT, SPI, I2S). **Key Features:** - Hardware-accelerated LED control via RMT (preferred), SPI, or I2S - Support for WS2812 and SK6812 LED strips - 3-byte (RGB) and 4-byte (RGBW) pixel formats - Flexible pixel ordering (GRB, RGB, RBG, BRG, BGR, GBR) - Zero-copy buffer management for optimal performance - ESP32 platform exclusive (ESP8266 not supported) **Version:** 1.0 **Author:** Stephan Hadinger **License:** GNU General Public License v3.0 **Copyright:** 2024 --- ## Table of Contents 1. [Architecture Overview](#architecture-overview) 2. [Class Hierarchy](#class-hierarchy) 3. [LED Type Encoding System](#led-type-encoding-system) 4. [Hardware Acceleration](#hardware-acceleration) 5. [API Reference](#api-reference) 6. [Configuration Guide](#configuration-guide) 7. [Performance Characteristics](#performance-characteristics) 8. [Usage Examples](#usage-examples) 9. [Integration with Tasmota](#integration-with-tasmota) 10. [Troubleshooting](#troubleshooting) --- ## Architecture Overview ### Design Philosophy TasmotaLED is designed with the following principles: 1. **Minimal Memory Footprint**: Uses only two buffers (work and show) with no buffer swapping overhead 2. **Hardware Acceleration First**: Leverages ESP32's RMT, SPI, or I2S peripherals for precise timing 3. **Zero-Copy Operations**: Direct buffer manipulation without intermediate copies 4. **NeoPixelBus Compatibility**: Similar API for easy migration from NeoPixelBus 5. **Compile-Time Optimization**: Hardware support can be selectively enabled/disabled ### System Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Application Layer │ │ (Tasmota Light Driver) │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ TasmotaLED Class │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ _buf_work │ │ _buf_show │ │ Pixel Format │ │ │ │ (editable) │─▶│ (internal) │ │ Conversion │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ TasmotaLEDPusher (Abstract) │ └────────────────────┬────────────────────────────────────────┘ │ ┌────────────┼────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │ RMT Pusher │ │ SPI │ │ I2S │ │ (Primary) │ │ Pusher │ │ Pusher │ └──────┬───────┘ └────┬─────┘ └────┬─────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────┐ │ ESP32 Hardware Peripherals │ │ (RMT / SPI / I2S Controllers) │ └─────────────────────────────────────────┘ ``` ### Memory Management TasmotaLED uses a dual-buffer system: 1. **Work Buffer (`_buf_work`)**: - Directly accessible by application - Stores pixels in WRGB or RGB format (0xWWRRGGBB or 0xRRGGBB) - Can be modified at any time - Size: `pixel_count × pixel_size` bytes 2. **Show Buffer (`_buf_show`)**: - Internal buffer for hardware transmission - Stores pixels in LED strip format (e.g., GRB, GRBW) - Populated during `Show()` call with format conversion - Size: `pixel_count × pixel_size` bytes **Memory Allocation Example:** - 512 RGB pixels: 2 × 512 × 3 = 3,072 bytes (~3 KB) - 512 RGBW pixels: 2 × 512 × 4 = 4,096 bytes (~4 KB) --- ## Class Hierarchy ### Class Diagram ``` ┌─────────────────────────────────────────────────────────────┐ │ TasmotaLED │ ├─────────────────────────────────────────────────────────────┤ │ - _type: uint16_t │ │ - _pixel_order: uint8_t │ │ - _w_before: bool │ │ - _pixel_reverse: bool │ │ - _timing: uint8_t │ │ - _started: bool │ │ - _dirty: bool │ │ - _raw_format: bool │ │ - _pixel_count: uint16_t │ │ - _pixel_size: uint8_t │ │ - _buf_work: uint8_t* │ │ - _buf_show: uint8_t* │ │ - _pixel_matrix: const uint8_t(*)[3] │ │ - _pusher: TasmotaLEDPusher* │ ├─────────────────────────────────────────────────────────────┤ │ + TasmotaLED(type, num_leds) │ │ + ~TasmotaLED() │ │ + Begin(): bool │ │ + Show(): void │ │ + SetPixelColor(index, wrgb): void │ │ + GetPixelColor(index): uint32_t │ │ + ClearTo(rgbw, first, last): void │ │ + SetPixelCount(num_leds): void │ │ + SetPixelSubType(type): void │ │ + SetPusher(pusher): void │ │ + CanShow(): bool │ │ + Pixels(): uint8_t* │ │ + PixelCount(): uint16_t │ │ + PixelSize(): uint8_t │ │ + GetType(): uint8_t │ │ + IsDirty(): bool │ │ + Dirty(): void │ │ + SetPixelReverse(bool): void │ │ + SetRawFormat(raw): void │ └─────────────────────────────────────────────────────────────┘ │ │ uses ▼ ┌─────────────────────────────────────────────────────────────┐ │ TasmotaLEDPusher (Abstract) │ ├─────────────────────────────────────────────────────────────┤ │ # _initialized: bool │ │ # _err: esp_err_t │ │ # _pixel_count: uint16_t │ │ # _pixel_size: uint16_t │ │ # _led_timing: const TasmotaLED_Timing* │ ├─────────────────────────────────────────────────────────────┤ │ + Begin(pixel_count, pixel_size, timing): bool │ │ + Push(buf): bool = 0 │ │ + CanShow(): bool = 0 │ │ + SetPixelCount(pixel_count): bool = 0 │ │ + Initialized(): bool │ │ + Error(): esp_err_t │ │ + static ResolveHardware(hw): uint32_t │ │ + static Create(hw, gpio): TasmotaLEDPusher* │ └─────────────────────────────────────────────────────────────┘ △ │ ┌─────────────────┼─────────────────┐ │ │ │ ┌─────────┴──────────┐ ┌────┴─────────┐ ┌─────┴──────────┐ │ TasmotaLEDPusherRMT│ │TasmotaLED │ │ TasmotaLED │ │ │ │PusherSPI │ │PusherI2S │ ├────────────────────┤ ├──────────────┤ ├────────────────┤ │ - _pin: int8_t │ │- _pin: int8_t│ │(Future) │ │ - _channel: handle │ │- _spi_strip │ │ │ │ - _led_encoder │ │- _with_dma │ │ │ │ - _tx_config │ │ │ │ │ ├────────────────────┤ ├──────────────┤ ├────────────────┤ │ + Push(): bool │ │+ Push(): bool│ │ │ │ + CanShow(): bool │ │+ CanShow() │ │ │ └────────────────────┘ └──────────────┘ └────────────────┘ ``` --- ## LED Type Encoding System ### Type Encoding Structure The LED type is encoded in a 16-bit value with the following bit layout: ``` Bits 15-8: Timing Code (WS2812, SK6812, etc.) Bit 7: W Channel Position (0=after RGB, 1=before RGB) Bits 6-4: Pixel Order (GRB, RGB, RBG, BRG, BGR, GBR) Bits 3-0: Bytes per Pixel (3=RGB, 4=RGBW) ┌────────┬───┬───────┬──────────┐ │15 8 │ 7 │ 6 4 │ 3 0 │ ├────────┼───┼───────┼──────────┤ │ Timing │ W │ Order │ Size │ └────────┴───┴───────┴──────────┘ ``` ### Pixel Size Encoding (Bits 0-3) | Value | Enum | Description | Bytes per Pixel | |-------|------|-------------|-----------------| | `0x0` | `TasmotaLed_1_Def` | Default (same as RGB) | 3 | | `0x1` | `TasmotaLed_3_RGB` | RGB format | 3 | | `0x2` | `TasmotaLed_4_WRGB` | RGBW format | 4 | ### Pixel Order Encoding (Bits 4-6) | Value | Enum | Description | Channel Order | |-------|------|-------------|---------------| | `0b000` | `TasmotaLed_Def` | Default (GRB) | G, R, B | | `0b001` | `TasmotaLed_GRB` | Green-Red-Blue | G, R, B | | `0b010` | `TasmotaLed_RGB` | Red-Green-Blue | R, G, B | | `0b011` | `TasmotaLed_RBG` | Red-Blue-Green | R, B, G | | `0b100` | `TasmotaLed_BRG` | Blue-Red-Green | B, R, G | | `0b101` | `TasmotaLed_BGR` | Blue-Green-Red | B, G, R | | `0b110` | `TasmotaLed_GBR` | Green-Blue-Red | G, B, R | ### W Channel Position (Bit 7) | Value | Enum | Description | Format | |-------|------|-------------|--------| | `0` | `TasmotaLed_xxxW` | W after color | RGB + W | | `1` | `TasmotaLed_Wxxx` | W before color | W + RGB | ### Timing Code (Bits 8-15) | Value | Enum | Description | T0H | T0L | T1H | T1L | Reset | |-------|------|-------------|-----|-----|-----|-----|-------| | `0` | `TasmotaLed_WS2812` | WS2812/WS2812B | 400ns | 850ns | 800ns | 450ns | 80µs | | `1` | `TasmotaLed_SK6812` | SK6812 | 300ns | 900ns | 600ns | 600ns | 80µs | ### Predefined LED Types ```cpp enum TasmotaLEDTypes : uint16_t { // WS2812 with GRB ordering (most common) ws2812_grb = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_WS2812, // Value: 0x0011 (decimal 17) // SK6812 with GRBW ordering and W after RGB sk6812_grbw = TasmotaLed_4_WRGB | TasmotaLed_GRB | TasmotaLed_xxxW | TasmotaLed_SK6812, // Value: 0x0112 (decimal 274) // SK6812 with GRB ordering (no white channel) sk6812_grb = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_SK6812, // Value: 0x0111 (decimal 273) }; ``` ### Type Encoding Examples ```cpp // WS2812 RGB strip with GRB ordering uint16_t type1 = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_WS2812; // Binary: 0000 0000 0001 0001 = 0x0011 // SK6812 RGBW strip with RGB ordering and W first uint16_t type2 = TasmotaLed_4_WRGB | TasmotaLed_RGB | TasmotaLed_Wxxx | TasmotaLed_SK6812; // Binary: 0000 0001 1010 0010 = 0x01A2 // WS2812 RGB strip with BGR ordering uint16_t type3 = TasmotaLed_3_RGB | TasmotaLed_BGR | TasmotaLed_WS2812; // Binary: 0000 0000 0101 0001 = 0x0051 ``` --- ## Hardware Acceleration ### Hardware Support Matrix | Hardware | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | ESP32-C2 | ESP32-C6 | |----------|-------|----------|----------|----------|----------|----------| | **RMT** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | **SPI** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **I2S** | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ### Hardware Selection Hardware acceleration is selected using the `TasmotaLEDHardware` enum: ```cpp enum TasmotaLEDHardware : uint32_t { TasmotaLed_HW_Default = 0x000000, // Auto-select best available TasmotaLed_RMT = (1 << 0) << 16, // 0x010000 - RMT peripheral TasmotaLed_SPI = (1 << 1) << 16, // 0x020000 - SPI peripheral TasmotaLed_I2S = (1 << 2) << 16, // 0x040000 - I2S peripheral TasmotaLed_HW_None = 0xFF << 16, // 0xFF0000 - No hardware support }; ``` ### Hardware Resolution Priority When `TasmotaLed_HW_Default` is specified, the library selects hardware in this order: 1. **RMT** (if available) - Preferred for precision and low CPU overhead 2. **I2S** (if available) - Good for large strips 3. **SPI** (if available) - Fallback option, works on all ESP32 variants ### Compile-Time Configuration Hardware support can be enabled/disabled at compile time: ```cpp // In your build configuration or before including TasmotaLED #define TASMOTALED_HARDWARE_RMT 1 // Enable RMT (default: 1) #define TASMOTALED_HARDWARE_I2S 0 // Enable I2S (default: 0) #define TASMOTALED_HARDWARE_SPI 0 // Enable SPI (default: 0) ``` **Note:** If no hardware is explicitly enabled, SPI is automatically enabled as fallback. ### RMT Implementation **Advantages:** - Precise timing control (25ns resolution at 40MHz) - Low CPU overhead - Non-blocking operation with DMA - Supports up to 8 channels (ESP32) or 4 channels (ESP32-S2/S3/C3) **Technical Details:** - Clock resolution: 40 MHz (25ns per tick) - Memory block: 192 symbols (768 bytes) - Transaction queue depth: 4 - Encoder: Custom LED strip encoder with bit-level control **Timing Precision:** ``` WS2812: T0H=400ns (16 ticks), T0L=850ns (34 ticks) T1H=800ns (32 ticks), T1L=450ns (18 ticks) Reset=80µs (3200 ticks) SK6812: T0H=300ns (12 ticks), T0L=900ns (36 ticks) T1H=600ns (24 ticks), T1L=600ns (24 ticks) Reset=80µs (3200 ticks) ``` ### SPI Implementation **Advantages:** - Available on all ESP32 variants - Works on ESP32-C2 (no RMT support) - Reliable fallback option **Technical Details:** - Clock frequency: 2.5 MHz - Each LED bit encoded as 3 SPI bits - Bit encoding: `0` = `100`, `1` = `110` - DMA support: Optional (auto-selected) - Memory overhead: 3× pixel buffer size **Bit Encoding:** ``` LED bit 0: SPI bits 100 (high for 400ns, low for 800ns at 2.5MHz) LED bit 1: SPI bits 110 (high for 800ns, low for 400ns at 2.5MHz) ``` ### I2S Implementation **Status:** Planned but not yet implemented **Advantages:** - Very high throughput - Excellent for large LED installations - Parallel data transmission --- ## API Reference ### TasmotaLED Class #### Constructor ```cpp TasmotaLED(uint16_t type, uint16_t num_leds) ``` Creates a new TasmotaLED instance. **Parameters:** - `type`: LED type encoding (see LED Type Encoding section) - `num_leds`: Number of LEDs in the strip **Example:** ```cpp // Create a 60-LED WS2812 strip with GRB ordering TasmotaLED strip(ws2812_grb, 60); // Create a 100-LED SK6812 RGBW strip TasmotaLED strip(sk6812_grbw, 100); ``` #### Destructor ```cpp ~TasmotaLED() ``` Cleans up resources, frees buffers, and deletes the pusher instance. #### Initialization Methods ##### SetPusher() ```cpp void SetPusher(TasmotaLEDPusher *pusher) ``` Sets the hardware pusher implementation. Must be called before `Begin()`. **Parameters:** - `pusher`: Pointer to a TasmotaLEDPusher instance (RMT, SPI, or I2S) **Example:** ```cpp TasmotaLED strip(ws2812_grb, 60); TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create(TasmotaLed_RMT, 5); strip.SetPusher(pusher); ``` ##### Begin() ```cpp bool Begin(void) ``` Initializes the hardware pusher and prepares the strip for operation. **Returns:** `true` if initialization successful, `false` otherwise **Example:** ```cpp if (strip.Begin()) { // Strip is ready to use } else { // Initialization failed } ``` #### Pixel Manipulation Methods ##### SetPixelColor() ```cpp void SetPixelColor(int32_t index, uint32_t wrgb) ``` Sets the color of a single pixel. **Parameters:** - `index`: Pixel index (0-based, negative values count from end) - `wrgb`: Color value in 0xWWRRGGBB format (or 0xRRGGBB for RGB strips) **Example:** ```cpp strip.SetPixelColor(0, 0xFF0000); // Red strip.SetPixelColor(1, 0x00FF00); // Green strip.SetPixelColor(2, 0x0000FF); // Blue strip.SetPixelColor(-1, 0xFFFFFF); // Last pixel white strip.SetPixelColor(10, 0x80FF8000); // RGBW: W=0x80, R=0xFF, G=0x80, B=0x00 ``` ##### GetPixelColor() ```cpp uint32_t GetPixelColor(int32_t index) ``` Retrieves the color of a single pixel. **Parameters:** - `index`: Pixel index (0-based, negative values count from end) **Returns:** Color value in 0xWWRRGGBB or 0xRRGGBB format **Example:** ```cpp uint32_t color = strip.GetPixelColor(5); uint8_t red = (color >> 16) & 0xFF; uint8_t green = (color >> 8) & 0xFF; uint8_t blue = color & 0xFF; ``` ##### ClearTo() ```cpp void ClearTo(uint32_t rgbw, int32_t first = 0, int32_t last = -1) ``` Sets a range of pixels to the same color. **Parameters:** - `rgbw`: Color value in 0xWWRRGGBB or 0xRRGGBB format - `first`: First pixel index (default: 0) - `last`: Last pixel index (default: -1 = last pixel) **Example:** ```cpp strip.ClearTo(0x000000); // Clear entire strip to black strip.ClearTo(0xFF0000, 0, 9); // Set first 10 pixels to red strip.ClearTo(0x00FF00, 10, 19); // Set pixels 10-19 to green strip.ClearTo(0x0000FF, -10, -1); // Set last 10 pixels to blue ``` ##### Show() ```cpp void Show(void) ``` Pushes the pixel buffer to the LED strip. Converts from WRGB/RGB format to the strip's native format and transmits via hardware. **Example:** ```cpp strip.SetPixelColor(0, 0xFF0000); strip.SetPixelColor(1, 0x00FF00); strip.Show(); // Update the strip ``` ##### CanShow() ```cpp bool CanShow(void) const ``` Checks if the strip is ready to accept a new `Show()` command. **Returns:** `true` if ready, `false` if previous transmission still in progress **Example:** ```cpp if (strip.CanShow()) { strip.Show(); } ``` #### Configuration Methods ##### SetPixelCount() ```cpp void SetPixelCount(uint16_t num_leds) ``` Changes the number of LEDs in the strip. Reallocates buffers if necessary. **Parameters:** - `num_leds`: New number of LEDs **Example:** ```cpp strip.SetPixelCount(100); // Resize to 100 LEDs ``` ##### SetPixelSubType() ```cpp void SetPixelSubType(uint8_t type) ``` Changes only the pixel ordering and size (lower 8 bits of type), keeping timing unchanged. **Parameters:** - `type`: New subtype (bits 0-7 of type encoding) **Example:** ```cpp // Change from GRB to RGB ordering strip.SetPixelSubType(TasmotaLed_3_RGB | TasmotaLed_RGB); ``` ##### SetRawFormat() ```cpp void SetRawFormat(bool raw_format) ``` Enables/disables raw format mode. When enabled, buffer is copied directly without format conversion. **Parameters:** - `raw_format`: `true` for raw mode, `false` for automatic conversion **Example:** ```cpp strip.SetRawFormat(true); // Disable format conversion ``` #### Query Methods ##### PixelCount() ```cpp uint16_t PixelCount(void) const ``` Returns the number of LEDs in the strip. ##### PixelSize() ```cpp uint8_t PixelSize(void) const ``` Returns the number of bytes per pixel (3 for RGB, 4 for RGBW). ##### GetType() ```cpp uint8_t GetType(void) const ``` Returns the LED type encoding. ##### Pixels() ```cpp uint8_t * Pixels(void) const ``` Returns a pointer to the work buffer for direct manipulation. **Example:** ```cpp uint8_t *buf = strip.Pixels(); // Direct buffer access (advanced usage) buf[0] = 0xFF; // Set first byte ``` ##### IsDirty() ```cpp bool IsDirty(void) const ``` Returns the dirty flag (for NeoPixelBus compatibility, but not used internally). ##### Dirty() ```cpp void Dirty(void) ``` Sets the dirty flag to `true`. ### TasmotaLEDPusher Class (Abstract) #### Static Factory Methods ##### Create() ```cpp static TasmotaLEDPusher * Create(uint32_t hw, int8_t gpio) ``` Creates a pusher instance for the specified hardware type. **Parameters:** - `hw`: Hardware type (TasmotaLed_RMT, TasmotaLed_SPI, TasmotaLed_I2S, or TasmotaLed_HW_Default) - `gpio`: GPIO pin number for LED data output **Returns:** Pointer to pusher instance, or `nullptr` if creation failed **Example:** ```cpp TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create(TasmotaLed_RMT, 5); if (pusher) { strip.SetPusher(pusher); } ``` ##### ResolveHardware() ```cpp static uint32_t ResolveHardware(uint32_t hw) ``` Resolves hardware type based on SOC capabilities. **Parameters:** - `hw`: Requested hardware type **Returns:** Resolved hardware type (removes unsupported flags) #### Instance Methods ##### Begin() ```cpp virtual bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) ``` Initializes the hardware pusher. **Parameters:** - `pixel_count`: Number of LEDs - `pixel_size`: Bytes per pixel (3 or 4) - `led_timing`: Pointer to timing structure **Returns:** `true` if successful ##### Push() ```cpp virtual bool Push(uint8_t *buf) = 0 ``` Pushes pixel data to the LED strip (pure virtual). **Parameters:** - `buf`: Pointer to pixel buffer in strip format **Returns:** `true` if successful ##### CanShow() ```cpp virtual bool CanShow(void) = 0 ``` Checks if hardware is ready for next transmission (pure virtual). **Returns:** `true` if ready ##### SetPixelCount() ```cpp virtual bool SetPixelCount(uint16_t pixel_count) = 0 ``` Updates pixel count (pure virtual). **Parameters:** - `pixel_count`: New number of pixels **Returns:** `true` if successful ##### Initialized() ```cpp bool Initialized(void) const ``` Returns initialization status. ##### Error() ```cpp esp_err_t Error(void) const ``` Returns last error code. --- ## Configuration Guide ### Basic Setup #### Step 1: Include Headers ```cpp #include "TasmotaLED.h" #include "TasmotaLEDPusher.h" ``` #### Step 2: Create Strip Instance ```cpp // For WS2812 RGB strip with 60 LEDs TasmotaLED strip(ws2812_grb, 60); // For SK6812 RGBW strip with 100 LEDs TasmotaLED strip(sk6812_grbw, 100); // Custom configuration uint16_t custom_type = TasmotaLed_3_RGB | TasmotaLed_RGB | TasmotaLed_WS2812; TasmotaLED strip(custom_type, 144); ``` #### Step 3: Create and Assign Pusher ```cpp // Auto-select best hardware for GPIO 5 TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create(TasmotaLed_HW_Default, 5); if (pusher) { strip.SetPusher(pusher); } else { // Handle error } // Or explicitly request RMT TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create(TasmotaLed_RMT, 5); ``` #### Step 4: Initialize ```cpp if (!strip.Begin()) { // Handle initialization error Serial.println("Failed to initialize LED strip"); } ``` #### Step 5: Use the Strip ```cpp // Set colors strip.ClearTo(0x000000); // Clear to black strip.SetPixelColor(0, 0xFF0000); // First pixel red strip.SetPixelColor(1, 0x00FF00); // Second pixel green strip.Show(); // Update strip ``` ### Advanced Configuration #### Custom LED Type ```cpp // Create custom type: RGB, BGR ordering, WS2812 timing uint16_t custom_type = TasmotaLed_3_RGB | // 3 bytes per pixel TasmotaLed_BGR | // BGR ordering TasmotaLed_WS2812; // WS2812 timing TasmotaLED strip(custom_type, 60); ``` #### RGBW with W Channel First ```cpp // SK6812 RGBW with W channel before RGB uint16_t rgbw_type = TasmotaLed_4_WRGB | // 4 bytes per pixel TasmotaLed_RGB | // RGB ordering TasmotaLed_Wxxx | // W first TasmotaLed_SK6812; // SK6812 timing TasmotaLED strip(rgbw_type, 100); ``` #### Multiple Strips ```cpp // Strip 1 on GPIO 5 TasmotaLED strip1(ws2812_grb, 60); TasmotaLEDPusher *pusher1 = TasmotaLEDPusher::Create(TasmotaLed_RMT, 5); strip1.SetPusher(pusher1); strip1.Begin(); // Strip 2 on GPIO 18 TasmotaLED strip2(sk6812_grbw, 100); TasmotaLEDPusher *pusher2 = TasmotaLEDPusher::Create(TasmotaLed_RMT, 18); strip2.SetPusher(pusher2); strip2.Begin(); ``` #### Raw Format Mode ```cpp strip.SetRawFormat(true); // Disable automatic format conversion // Now buffer must be in strip's native format (e.g., GRB for WS2812) uint8_t *buf = strip.Pixels(); buf[0] = 0xFF; // Green buf[1] = 0x00; // Red buf[2] = 0x00; // Blue strip.Show(); ``` ### Compile-Time Configuration #### Enable/Disable Hardware Support In your `platformio.ini` or build flags: ```ini build_flags = -DTASMOTALED_HARDWARE_RMT=1 -DTASMOTALED_HARDWARE_SPI=1 -DTASMOTALED_HARDWARE_I2S=0 ``` Or in your code before including TasmotaLED: ```cpp #define TASMOTALED_HARDWARE_RMT 1 #define TASMOTALED_HARDWARE_SPI 0 #define TASMOTALED_HARDWARE_I2S 0 #include "TasmotaLED.h" ``` --- ## Performance Characteristics ### Memory Usage #### Per-Strip Overhead | Component | Size | Description | |-----------|------|-------------| | TasmotaLED object | ~60 bytes | Class instance | | Work buffer | `pixels × size` | Editable buffer (RGB or RGBW) | | Show buffer | `pixels × size` | Internal transmission buffer | | Pusher object | ~40-80 bytes | Hardware implementation | | **Total** | **~100 + (2 × pixels × size)** | | #### Examples | Configuration | Memory Usage | |---------------|--------------| | 60 RGB pixels | ~460 bytes | | 144 RGB pixels | ~964 bytes | | 300 RGB pixels | ~1,900 bytes | | 512 RGB pixels | ~3,172 bytes | | 60 RGBW pixels | ~580 bytes | | 144 RGBW pixels | ~1,252 bytes | #### SPI Additional Overhead SPI requires 3× buffer for encoding: | Configuration | Additional Memory | |---------------|-------------------| | 60 RGB pixels | +540 bytes | | 144 RGB pixels | +1,296 bytes | | 300 RGB pixels | +2,700 bytes | ### Timing Performance #### Buffer Operations (512 RGB pixels) | Operation | Time | Notes | |-----------|------|-------| | `ClearTo(0x000000)` | ~15 µs | Optimized memset | | `ClearTo(0xFF0000)` | ~124 µs | Per-pixel write | | `SetPixelColor()` | ~0.2 µs | Single pixel | | Format conversion | ~124 µs | RGB→GRB conversion | | `Show()` (RMT) | ~16.2 ms | Actual transmission | | `Show()` (SPI) | ~16.6 ms | Transmission + encoding | #### Transmission Times Transmission time depends on pixel count and timing: **WS2812 (30 µs per pixel):** - 60 pixels: ~1.8 ms - 144 pixels: ~4.3 ms - 300 pixels: ~9.0 ms - 512 pixels: ~15.4 ms **SK6812 (30 µs per pixel):** - Similar to WS2812 **Maximum Refresh Rate:** - 60 pixels: ~555 Hz - 144 pixels: ~232 Hz - 300 pixels: ~111 Hz - 512 pixels: ~65 Hz ### CPU Overhead #### RMT Implementation - **Setup:** ~100 µs (one-time) - **Per Show():** ~150 µs (format conversion + DMA setup) - **During transmission:** Near zero (DMA handles transfer) - **CPU usage:** <1% during transmission #### SPI Implementation - **Setup:** ~200 µs (one-time) - **Per Show():** ~550 µs (encoding + format conversion) - **During transmission:** Near zero (DMA handles transfer) - **CPU usage:** <2% during transmission ### Optimization Tips 1. **Minimize Show() Calls** ```cpp // Bad: Multiple Show() calls for (int i = 0; i < 60; i++) { strip.SetPixelColor(i, color); strip.Show(); // 60 transmissions! } // Good: Single Show() call for (int i = 0; i < 60; i++) { strip.SetPixelColor(i, color); } strip.Show(); // 1 transmission ``` 2. **Use ClearTo() for Bulk Operations** ```cpp // Faster than loop with SetPixelColor() strip.ClearTo(0xFF0000, 0, 29); // First 30 pixels red ``` 3. **Check CanShow() Before Show()** ```cpp if (strip.CanShow()) { strip.Show(); } ``` 4. **Direct Buffer Access for Complex Patterns** ```cpp uint8_t *buf = strip.Pixels(); // Direct manipulation (advanced) for (int i = 0; i < strip.PixelCount() * strip.PixelSize(); i++) { buf[i] = pattern[i]; } strip.Show(); ``` --- ## Usage Examples ### Basic Initialization ```cpp #include "TasmotaLED.h" #include "TasmotaLEDPusher.h" // Create strip instance TasmotaLED strip(ws2812_grb, 60); void setup() { // Create pusher with auto hardware selection TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create(TasmotaLed_HW_Default, 5); if (pusher) { strip.SetPusher(pusher); if (strip.Begin()) { // Strip ready strip.ClearTo(0x000000); // Clear to black strip.Show(); } } } ``` ### Setting Pixels ```cpp // Set individual pixels strip.SetPixelColor(0, 0xFF0000); // Red strip.SetPixelColor(1, 0x00FF00); // Green strip.SetPixelColor(2, 0x0000FF); // Blue // Set range of pixels strip.ClearTo(0xFF0000, 0, 9); // First 10 pixels red // Update strip strip.Show(); ``` ### RGBW Control ```cpp TasmotaLED strip(sk6812_grbw, 60); // Pure white using W channel strip.ClearTo(0xFF000000); // W=0xFF, RGB=0 // Warm white (W + Red) strip.ClearTo(0x80FF0000); // W=0x80, R=0xFF, G=0, B=0 // RGB color (no W) strip.ClearTo(0x00FF00FF); // W=0, R=0xFF, G=0, B=0xFF (Magenta) strip.Show(); ``` ### Non-Blocking Updates ```cpp void loop() { // Check if strip is ready before updating if (strip.CanShow()) { // Update pixels for (int i = 0; i < strip.PixelCount(); i++) { strip.SetPixelColor(i, colors[i]); } strip.Show(); } // Other code continues without blocking } ``` --- ## Integration with Tasmota ### Tasmota Light Driver Integration TasmotaLED is designed to integrate seamlessly with Tasmota's light driver system. Here's how it fits into the Tasmota architecture: #### Integration Points 1. **Driver Initialization (FUNC_INIT)** ```cpp void TasmotaLEDInit(void) { // Create strip based on Settings uint16_t led_type = Settings->light_type; uint16_t led_count = Settings->light_pixels; int8_t gpio = Pin(GPIO_LED); strip = new TasmotaLED(led_type, led_count); // Create pusher with hardware auto-selection TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create( TasmotaLed_HW_Default, gpio); if (pusher) { strip->SetPusher(pusher); if (strip->Begin()) { AddLog(LOG_LEVEL_INFO, "LED: Initialized %d pixels on GPIO %d", led_count, gpio); } } } ``` 2. **Pixel Updates (FUNC_SET_CHANNELS)** ```cpp void TasmotaLEDUpdate(void) { // Get color data from Tasmota light engine for (int i = 0; i < strip->PixelCount(); i++) { uint32_t color = GetPixelColorFromLightEngine(i); strip->SetPixelColor(i, color); } // Push to hardware if (strip->CanShow()) { strip->Show(); } } ``` 3. **Configuration Changes** ```cpp void TasmotaLEDReconfigure(void) { // Handle pixel count changes if (new_pixel_count != strip->PixelCount()) { strip->SetPixelCount(new_pixel_count); } // Handle type changes if (new_led_type != strip->GetType()) { strip->SetPixelSubType(new_led_type & 0xFF); } } ``` ### Tasmota Commands When integrated with Tasmota, these commands control the LED strip: | Command | Description | Example | |---------|-------------|---------| | `Pixels` | Set number of LEDs | `Pixels 60` | | `Scheme` | Set color scheme | `Scheme 2` | | `Speed` | Set animation speed | `Speed 10` | | `Width` | Set effect width | `Width 1` | | `Color` | Set RGB color | `Color #FF0000` | | `White` | Set white channel | `White 50` | | `Dimmer` | Set brightness | `Dimmer 75` | | `Fade` | Enable fade | `Fade 1` | ### Settings Storage Tasmota stores LED configuration in Settings structure: ```cpp struct SETTINGS { // ... uint16_t light_pixels; // Number of LEDs uint8_t light_type; // LED type encoding uint8_t light_scheme; // Animation scheme uint8_t light_speed; // Animation speed uint8_t light_width; // Effect width // ... }; ``` ### Hardware Selection in Tasmota Tasmota automatically selects the best hardware based on: 1. **SOC Capabilities**: Checks what hardware is available 2. **GPIO Availability**: Ensures GPIO is not in use 3. **Performance Requirements**: Selects RMT for best performance 4. **Fallback**: Uses SPI if RMT unavailable ```cpp uint32_t TasmotaSelectHardware(void) { uint32_t hw = TasmotaLed_HW_Default; // Force SPI on ESP32-C2 (no RMT) #ifdef CONFIG_IDF_TARGET_ESP32C2 hw = TasmotaLed_SPI; #endif // User override via SetOption if (Settings->flag5.led_use_spi) { hw = TasmotaLed_SPI; } return hw; } ``` --- ## Troubleshooting ### Common Issues #### 1. Strip Not Lighting Up **Symptoms:** No LEDs light up after calling `Show()` **Possible Causes:** - Pusher not initialized - Wrong GPIO pin - Incorrect LED type - Power supply issues **Solutions:** ```cpp // Check initialization if (!strip.Begin()) { Serial.println("Failed to initialize"); // Check pusher creation if (pusher == nullptr) { Serial.println("Pusher creation failed"); } } // Verify GPIO Serial.printf("Using GPIO %d\n", gpio_pin); // Test with simple pattern strip.ClearTo(0xFF0000); // All red strip.Show(); ``` #### 2. Wrong Colors **Symptoms:** Colors appear incorrect (e.g., red shows as green) **Possible Causes:** - Incorrect pixel ordering - Wrong LED type **Solutions:** ```cpp // Try different orderings strip.SetPixelSubType(TasmotaLed_3_RGB | TasmotaLed_RGB); // RGB strip.SetPixelSubType(TasmotaLed_3_RGB | TasmotaLed_GRB); // GRB strip.SetPixelSubType(TasmotaLed_3_RGB | TasmotaLed_BGR); // BGR // Test each ordering strip.ClearTo(0xFF0000); // Should be red strip.Show(); ``` #### 3. Flickering or Glitches **Symptoms:** Random flickering, incorrect colors, or partial updates **Possible Causes:** - Timing issues - Insufficient power - Long cable runs - Electromagnetic interference **Solutions:** ```cpp // Try different timing uint16_t type_ws2812 = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_WS2812; uint16_t type_sk6812 = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_SK6812; // Check power supply (5V, sufficient amperage) // Add capacitor (1000µF) across power supply // Keep data cable short (<1m) or use level shifter ``` #### 4. Memory Allocation Failures **Symptoms:** `Begin()` returns false, crashes, or resets **Possible Causes:** - Too many LEDs for available memory - Memory fragmentation **Solutions:** ```cpp // Check available heap Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap()); // Calculate required memory uint32_t required = strip.PixelCount() * strip.PixelSize() * 2; Serial.printf("Required: %d bytes\n", required); // Reduce pixel count if necessary if (required > ESP.getFreeHeap() / 2) { strip.SetPixelCount(smaller_count); } ``` #### 5. Performance Issues **Symptoms:** Slow updates, low frame rate **Possible Causes:** - Too many `Show()` calls - Inefficient pixel updates - Wrong hardware selection **Solutions:** ```cpp // Batch updates for (int i = 0; i < strip.PixelCount(); i++) { strip.SetPixelColor(i, colors[i]); } strip.Show(); // Single show call // Use ClearTo() for bulk operations strip.ClearTo(0xFF0000, 0, 29); // Faster than loop // Check hardware Serial.printf("Using hardware: 0x%08X\n", hw_type); // Verify CanShow() before Show() if (strip.CanShow()) { strip.Show(); } ``` ### Debug Logging Enable debug logging to diagnose issues: ```cpp // In Tasmota, set log level SerialLog 4 // Debug level // Look for these messages: // LED: RMT gpio 5 // LED: SPI gpio 5 // LED: Error create RMT bus failed 5 err=XXX ``` ### Hardware-Specific Issues #### ESP32-C2 - Only SPI supported (no RMT) - Ensure `TASMOTALED_HARDWARE_SPI` is enabled #### ESP32-S2/S3 - RMT channels limited to 4 - Check channel availability if using multiple strips #### ESP32 Classic - RMT channels limited to 8 - Most flexible hardware support ### Timing Verification Verify timing with oscilloscope or logic analyzer: **WS2812 Expected:** - T0H: 400ns ±150ns - T0L: 850ns ±150ns - T1H: 800ns ±150ns - T1L: 450ns ±150ns - Reset: >50µs **SK6812 Expected:** - T0H: 300ns ±150ns - T0L: 900ns ±150ns - T1H: 600ns ±150ns - T1L: 600ns ±150ns - Reset: >80µs --- ## Appendix ### Pixel Order Matrix The library uses a lookup table for pixel ordering: ```cpp static const uint8_t TASMOTALED_CHANNEL_ORDERS[8][3] = { {1, 0, 2}, // Def=GRB (0) {1, 0, 2}, // GRB (1) {0, 1, 2}, // RGB (2) {0, 2, 1}, // RBG (3) {2, 1, 0}, // BRG (4) {1, 2, 0}, // BGR (5) {2, 0, 1}, // GBR (6) {1, 0, 2} // GRB (7) - fallback }; ``` **Interpretation:** - Index 0: Position of Red channel - Index 1: Position of Green channel - Index 2: Position of Blue channel **Example:** GRB ordering `{1, 0, 2}` means: - Red goes to position 1 - Green goes to position 0 - Blue goes to position 2 - Result: G(0) R(1) B(2) ### Timing Structure ```cpp typedef struct TasmotaLED_Timing { uint16_t T0H; // Bit 0 high time (nanoseconds) uint16_t T0L; // Bit 0 low time (nanoseconds) uint16_t T1H; // Bit 1 high time (nanoseconds) uint16_t T1L; // Bit 1 low time (nanoseconds) uint32_t Reset; // Reset time (nanoseconds) } TasmotaLED_Timing; ``` ### Comparison with NeoPixelBus | Feature | TasmotaLED | NeoPixelBus | |---------|------------|-------------| | **Memory** | Lower (2 buffers) | Higher (3+ buffers) | | **Code Size** | ~15 KB | ~40 KB | | **Hardware** | RMT, SPI, I2S | RMT, I2S, UART, BitBang | | **ESP8266** | ❌ No | ✅ Yes | | **ESP32** | ✅ Yes | ✅ Yes | | **Buffer Swap** | No (copy on Show) | Yes (double buffering) | | **Dirty Flag** | Ignored | Used | | **API** | Similar | Full-featured | | **Performance** | Optimized | Feature-rich | ### License ``` TasmotaLED - Lightweight implementation for addressable LEDs Copyright (C) 2024 Stephan Hadinger This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ``` --- ## Revision History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2024 | Initial release with RMT and SPI support | --- **End of Documentation**