Back to Fastled

FastLED PARLIO Driver for ESP32-P4/C6/H2/C5

src/platforms/esp/32/drivers/parlio/README.md

3.10.324.6 KB
Original Source

FastLED PARLIO Driver for ESP32-P4/C6/H2/C5

Overview

The PARLIO (Parallel I/O) driver is a high-performance LED controller implementation for ESP32 chips with PARLIO hardware (ESP32-P4, ESP32-C6, ESP32-H2, ESP32-C5) that enables controlling up to 16 WS2812 LED strips simultaneously using hardware DMA. This driver achieves exceptional performance by offloading the precise timing requirements to dedicated hardware, freeing the CPU for other tasks.

Note: ESP32-S3 does NOT have PARLIO hardware (it uses the LCD peripheral instead).

Key Features

  • Parallel Output: Control 1, 2, 4, 8, or 16 LED strips simultaneously
  • Hardware DMA: Near-zero CPU usage during LED updates
  • WS2812 Timing: Precise waveform generation matching WS2812/WS2812B specifications
  • High Performance: 100+ FPS for 1000 LEDs across 8 strips
  • Large LED Counts: Automatic chunking handles thousands of LEDs per strip
  • Internal Clock: No external clock pin required - uses internal 8.0 MHz timing

Enhanced Features (New!)

  • RGBW Support: 4-component LEDs (SK6812, etc.) with dynamic buffer sizing
  • Runtime Reconfiguration: Change LED count, pins, or mode without full teardown
  • Double Buffering: Automatic ping-pong buffers for parallel processing (+15-30% FPS)
  • Dynamic Clock Adjustment: Auto-tune clock frequency based on LED count (up to +50% FPS for small counts)
  • GPIO Status Reporting: Detailed diagnostic output for debugging

How It Works

WS2812 Protocol Requirements

WS2812 LEDs require precise pulse-width modulation to encode data:

  • T0H: ~0.4μs high, ~0.85μs low (logical '0')
  • T1H: ~0.8μs high, ~0.45μs low (logical '1')
  • Total: ~1.25μs per bit (800kHz data rate)

Waveform Generation

Unlike simple GPIO bit-banging, PARLIO requires pre-encoded timing waveforms. The driver implements this through:

  1. Bitpattern Encoding: Each LED bit (0 or 1) expands to a 10-bit timing pattern:

    • Bit 0: 3 ticks high, 7 ticks low (375ns high, 875ns low)
    • Bit 1: 7 ticks high, 3 ticks low (875ns high, 375ns low)
  2. Waveform Cache: Each 8-bit color value maps to an 80-bit waveform:

    • 8 LED bits × 10 clock cycles = 80-bit pattern per color byte
    • Clocked at 8.0 MHz (125ns per tick) for precise timing
    • Example: Color value 0xFF → 10 bits of (7H+3L) pattern per bit
  3. Bit Transposition: For parallel output, bits are transposed across time-slices:

    • All strips' bit N collected into time-slice N
    • 32 time-slices packed into width-specific format (1/2/4/8/16 bits)
    • Result: Synchronized parallel waveform transmission

Buffer Management

Buffer Size Formula:

buffer_size = (num_leds × bits_per_led × data_width + 7) / 8 bytes

Where:

  • RGB mode: 240 bits per LED (3 color components × 80-bit waveform each)
  • RGBW mode: 320 bits per LED (4 color components × 80-bit waveform each)
  • data_width = number of parallel strips (1, 2, 4, 8, or 16)
  • Double buffering: 2× buffer size (automatic, transparent to user)

Examples (RGB mode):

  • 1 LED, 1 strip: 30 bytes (240 bits)
  • 1 LED, 8 strips: 240 bytes (1920 bits)
  • 100 LEDs, 8 strips: 24,000 bytes
  • 1000 LEDs, 8 strips: 240,000 bytes (chunked into ~4 transfers)

Examples (RGBW mode):

  • 1 LED, 1 strip: 40 bytes (320 bits)
  • 1 LED, 8 strips: 320 bytes (2560 bits)
  • 100 LEDs, 8 strips: 32,000 bytes
  • 1000 LEDs, 8 strips: 320,000 bytes (chunked into ~5 transfers)

Note: With double buffering (always enabled), actual memory usage is 2× these values

Chunked Transmission

PARLIO hardware limits single DMA transfers to 65,535 bytes. The driver automatically chunks larger transfers:

Max LEDs per chunk (width=8) = 65,535 / 240 = 273 LEDs

For 1000 LEDs:

  • Chunk 0: 273 LEDs (65,520 bytes)
  • Chunk 1: 273 LEDs (65,520 bytes)
  • Chunk 2: 273 LEDs (65,520 bytes)
  • Chunk 3: 181 LEDs (43,440 bytes)

Chunks are transmitted sequentially with seamless timing continuity.

Hardware Support

Compatible Chips

  • ESP32-P4: Full PARLIO support (up to 16 parallel outputs)
  • ESP32-C6: Full PARLIO support (up to 16 parallel outputs)
  • ESP32-H2: Full PARLIO support (up to 16 parallel outputs)
  • ESP32-C5: Full PARLIO support (up to 16 parallel outputs)

NOT Compatible:

  • ESP32-S3: Does NOT have PARLIO hardware (uses LCD peripheral instead)

Supported LED Types

  • WS2812 / WS2812B (RGB, tested)
  • WS2813 (RGB, tested)
  • SK6812 (RGBW, 4-component support)
  • SK6812W (RGBW variant)
  • Other 800kHz RGB/RGBW LEDs with similar timing requirements

Usage

Basic Single Strip

cpp
#include "FastLED.h"

#define NUM_LEDS 100
#define DATA_PIN 1

CRGB leds[NUM_LEDS];

void setup() {
    FastLED.addLeds<WS2812, DATA_PIN>(leds, NUM_LEDS);
}

void loop() {
    fill_rainbow(leds, NUM_LEDS, 0, 7);
    FastLED.show();
}

Multiple Parallel Strips

cpp
#include "FastLED.h"

#define NUM_STRIPS 8
#define NUM_LEDS_PER_STRIP 256
#define NUM_LEDS (NUM_LEDS_PER_STRIP * NUM_STRIPS)

// Pin definitions for 8 parallel strips
#define PIN0 1
#define PIN1 2
#define PIN2 3
#define PIN3 4
#define PIN4 5
#define PIN5 6
#define PIN6 7
#define PIN7 8

CRGB leds[NUM_LEDS];

void setup() {
    // Add each strip - driver automatically uses PARLIO
    FastLED.addLeds<WS2812, PIN0, GRB>(leds + (0 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN1, GRB>(leds + (1 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN2, GRB>(leds + (2 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN3, GRB>(leds + (3 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN4, GRB>(leds + (4 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN5, GRB>(leds + (5 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN6, GRB>(leds + (6 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);
    FastLED.addLeds<WS2812, PIN7, GRB>(leds + (7 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP);

    FastLED.setBrightness(64);
}

void loop() {
    // Fill all strips with rainbow
    for (int strip = 0; strip < NUM_STRIPS; strip++) {
        fill_rainbow(leds + (strip * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP, strip * 32, 7);
    }
    FastLED.show();
}

RGBW LED Support (SK6812)

cpp
#include "FastLED.h"

#define NUM_LEDS 100
#define DATA_PIN 1

// Note: FastLED automatically detects RGBW chipsets
CRGB leds[NUM_LEDS];

void setup() {
    // Use SK6812 chipset - driver automatically enables RGBW mode
    FastLED.addLeds<SK6812, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void loop() {
    // Set RGBW color (white component handled automatically)
    fill_solid(leds, NUM_LEDS, CRGB::White);
    FastLED.show();
}

Runtime Reconfiguration

cpp
#include "FastLED.h"

#define MAX_LEDS 1024
#define DATA_PIN 1

CRGB leds[MAX_LEDS];
uint16_t current_led_count = 256;  // Start with 256 LEDs

void setup() {
    FastLED.addLeds<WS2812, DATA_PIN>(leds, current_led_count);
}

void loop() {
    // Your animation code here
    fill_rainbow(leds, current_led_count, 0, 7);
    FastLED.show();

    // Dynamically increase LED count
    if (Serial.available()) {
        current_led_count = 512;  // Change to 512 LEDs

        // Just call begin() again - driver auto-detects changes
        FastLED.clear();
        FastLED.addLeds<WS2812, DATA_PIN>(leds, current_led_count);
    }
}

Dynamic Clock Adjustment

cpp
#include "FastLED.h"

#define NUM_LEDS 256
#define DATA_PIN 1

CRGB leds[NUM_LEDS];

void setup() {
    // For small LED counts (≤256), enable auto-clock for +50% FPS boost
    // Driver automatically adjusts clock frequency based on LED count:
    // - ≤256 LEDs: 150% speed (8.0 MHz → 12.0 MHz)
    // - ≤512 LEDs: 137.5% speed (8.0 MHz → 11.0 MHz)
    // - >512 LEDs: 100% speed (8.0 MHz)

    FastLED.addLeds<WS2812, DATA_PIN>(leds, NUM_LEDS);
    // Auto-clock adjustment is enabled by default for optimal performance
}

GPIO Status Diagnostics

cpp
#include "FastLED.h"

#define NUM_LEDS 100
#define DATA_PIN 1

CRGB leds[NUM_LEDS];

void setup() {
    Serial.begin(115200);

    FastLED.addLeds<WS2812, DATA_PIN>(leds, NUM_LEDS);

    // GPIO configuration and status automatically logged during initialization
    // Look for detailed output like:
    // ╔═══════════════════════════════════════════════╗
    // ║   PARLIO Configuration Summary                ║
    // ╠═══════════════════════════════════════════════╣
    //   Data width:        8 bits
    //   Active lanes:      8
    //   LED mode:          RGB (3-component)
    //   Clock frequency:   3200000 Hz (3200 kHz)
    //   ...
}

Configuration Options

The driver uses sensible defaults, but you can customize if needed:

cpp
// Configuration is typically not needed - defaults work well
// The following fields exist for compatibility but are unused:

ParlioDriverConfig config;
config.clk_gpio = -1;              // [UNUSED] Internal clock used
config.clock_freq_hz = 0;          // 0 = use default 8.0 MHz for WS2812
config.num_strips = 8;             // Number of parallel strips
config.is_rgbw = false;            // Auto-detected from chipset type
config.auto_clock_adjustment = true; // Enable dynamic clock tuning

Build-Time Configuration

Advanced users can override defaults via build flags:

ISR Priority Level

The FL_ESP_PARLIO_ISR_PRIORITY macro defines the desired interrupt priority level for the PARLIO peripheral's ISR callbacks:

ini
# platformio.ini
[env:esp32c6]
platform = espressif32
board = esp32-c6-devkitc-1
build_flags =
    -DFL_ESP_PARLIO_ISR_PRIORITY=3  # Set ISR priority (default: 3)

Priority Levels:

  • Level 1: Lowest priority (can be preempted by all higher interrupts)
  • Level 2: Medium priority
  • Level 3: Highest priority for C handlers (default, recommended)
  • Level 4+: Requires assembly handlers (not supported by ESP-IDF PARLIO driver)

⚠️ Current Status: This configuration is NOT currently functional due to ESP-IDF PARLIO driver limitations:

  • ESP-IDF's parlio_tx_unit_config_t does NOT expose an intr_priority field
  • ESP-IDF's parlio_tx_unit_register_event_callbacks() does NOT accept priority configuration
  • The PARLIO peripheral uses default interrupt allocation internally

The macro is defined for:

  • Documentation purposes: Clearly indicates intended priority level
  • Future compatibility: Will be used if ESP-IDF adds priority configuration support

One-Shot ISR Worker Pattern: The PARLIO driver uses a one-shot ISR pattern where:

  • txDoneCallback: Fired on transmission completion (ISR context)
  • workerIsrCallback: Called directly from txDoneCallback to populate next DMA buffer
  • No periodic timer overhead (refactored from 50µs periodic ISR to on-demand invocation)

Performance Characteristics

Expected Frame Rates

With Enhanced Features (Double Buffering + Auto-Clock):

LED CountStripsBase FPSEnhanced FPSImprovementCPU Usage
2568280420++50%<5%
5128140175++25%<5%
102487085++15%<5%
204883540++15%<10%

Performance Breakdown:

  • Double Buffering: +15-30% FPS (parallel pack + transmit)
  • Auto-Clock (≤256 LEDs): +50% FPS (1.5× clock speed)
  • Auto-Clock (≤512 LEDs): +37.5% FPS (1.375× clock speed)
  • Auto-Clock (>512 LEDs): No change (maintains stable timing)

Timing Calculations

Clock frequency: 8.0 MHz = 125ns per tick
LED bit time: 10 ticks = 1.25μs
Color byte time: 80 ticks = 10μs
Full RGB LED: 240 ticks = 30μs

Frame time (1000 LEDs, 1 strip):
1000 LEDs × 30μs = 30ms = ~33 FPS theoretical
Actual: ~25-30 FPS (including overhead)

Frame time (256 LEDs, 8 strips):
256 LEDs × 30μs = 7.68ms = ~130 FPS theoretical
Actual: ~100-120 FPS (including overhead)

Memory Usage

RGB Mode (with double buffering):

Single LED (width=8): 240 bytes × 2 = 480 bytes
100 LEDs (width=8): 24 KB × 2 = 48 KB
1000 LEDs (width=8): 234 KB × 2 = 468 KB

RGBW Mode (with double buffering):

Single LED (width=8): 320 bytes × 2 = 640 bytes
100 LEDs (width=8): 32 KB × 2 = 64 KB
1000 LEDs (width=8): 312 KB × 2 = 624 KB

Note: Double buffering is always enabled for optimal performance. Memory usage is 2× compared to single-buffer implementations, but the performance benefit (+15-30% FPS) justifies the cost.

Technical Details

Clock Configuration

The driver uses an internal clock source at 8.0 MHz:

  • No external clock pin required
  • Precise timing for WS2812 protocol
  • Automatically configured by the driver

Previous versions required an external clock pin (GPIO 9), but this is no longer necessary.

Color Component Order

WS2812 LEDs expect GRB order (Green, Red, Blue). The driver handles this automatically:

  • CRGB buffer order: [R=0, G=1, B=2]
  • Transmission order: G, R, B
  • You don't need to worry about this - use CRGB normally

Parallel Width Selection

The driver automatically selects the optimal bit width based on the number of strips:

  • 1 strip → 1-bit width
  • 2 strips → 2-bit width
  • 3-4 strips → 4-bit width
  • 5-8 strips → 8-bit width
  • 9-16 strips → 16-bit width

Enhanced Features Details

1. RGBW Support

The driver automatically detects RGBW chipsets (like SK6812) and adjusts buffer sizing accordingly:

  • RGB: 3 components × 80 bits = 240 bits per LED
  • RGBW: 4 components × 80 bits = 320 bits per LED
  • Component order: G, R, B, W (W always last for RGBW)
  • No API changes needed - just use addLeds<SK6812, ...>()

2. Runtime Reconfiguration

The driver can detect configuration changes and reinitialize efficiently:

  • Change detection for: LED count, RGBW mode, lane count, GPIO pins, clock frequency
  • Just call begin() again with new parameters
  • Driver skips reinitialization if nothing changed
  • Proper cleanup ensures no memory leaks
  • Logs what changed for debugging

3. Double Buffering

Always-on ping-pong buffering for parallel processing:

  • Two DMA buffers allocated automatically
  • CPU packs next frame while DMA transmits current frame
  • Buffer swapping happens transparently
  • Performance gain: +15-30% FPS depending on LED count
  • Memory cost: 2× buffer size (worth it for performance)

4. Dynamic Clock Adjustment

Automatic clock frequency tuning based on LED count:

  • ≤256 LEDs: 150% base frequency (e.g., 8.0 MHz → 12.0 MHz)
  • ≤512 LEDs: 137.5% base frequency (e.g., 8.0 MHz → 11.0 MHz)
  • >512 LEDs: 100% base frequency (maintains stable timing)
  • Enabled by default via config.auto_clock_adjustment = true
  • Safety limits: 1-40 MHz, max 2× base frequency
  • Performance gain: Up to +50% FPS for small LED counts

5. GPIO Status Reporting

Comprehensive diagnostic output during initialization:

  • Box-drawing table format for visual clarity
  • Shows: Data width, active lanes, LED mode (RGB/RGBW), clock frequency
  • GPIO pin mapping with status indicators: [active], [unused], [INVALID]
  • Runtime status: DMA busy state, current buffer, buffer addresses
  • Call print_status() method for on-demand diagnostics

Known Limitations

Buffer Management for Small LED Counts (RESOLVED)

✅ RESOLVED: Previous versions of the PARLIO driver experienced deterministic single-bit corruption with small LED counts (1-10 LEDs). This issue has been fully resolved through dynamic ring buffer management.

Root Cause (Identified and Fixed):

  • Original Problem: DMA ring buffer partitioning created tiny buffers (< 15 bytes) for small LED counts
  • Mechanism: Small buffers didn't give the DMA controller enough transition time between buffer segments
  • Manifestation: 44.2μs timing glitch at buffer boundaries caused MSB corruption
  • Threshold: Buffers < 15 bytes → 100% corruption; Buffers ≥ 15 bytes → 0% corruption

Solution (Implemented in v3.10.0+): The driver now dynamically adjusts the ring buffer count based on LED count to ensure all buffers meet the minimum size requirement:

cpp
// Enforced minimum: 15 bytes per buffer (5 LEDs × 3 bytes/LED)
const size_t MIN_BYTES_PER_BUFFER = 15;

// Dynamic ring count adjustment
size_t effective_ring_count = ParlioRingBuffer3::RING_BUFFER_COUNT;
while (effective_ring_count > 1) {
    size_t test_bytes_per_buffer = (totalBytes + effective_ring_count - 1) / effective_ring_count;
    if (test_bytes_per_buffer >= MIN_BYTES_PER_BUFFER) {
        break;  // Found acceptable ring count
    }
    effective_ring_count--;  // Reduce ring count to increase buffer size
}

Test Results:

  • 1 LED: 0% corruption (100% pass rate, 10/10 runs)
  • 5 LEDs: 0% corruption (100% pass rate, 10/10 runs)
  • 10 LEDs: 0% corruption (100% pass rate, 10/10 runs) - previously 100% failure
  • 50+ LEDs: 0% corruption (100% pass rate, 10/10 runs) - no regression

Impact:

  • ✅ All LED counts now work reliably on ESP32-C6
  • ✅ No performance penalty - calculation happens once during setup
  • ✅ Backward compatible - no API changes required
  • ✅ Applies to all PARLIO-capable chips (ESP32-P4, C6, H2, C5)

Historical Note: Earlier documentation incorrectly attributed this issue to "undocumented ESP32-C6 PARLIO hardware timing limitations." Comprehensive testing revealed it was a software buffer management issue, not a hardware defect. The fix is applicable to all PARLIO implementations regardless of chip variant.

Platform Compatibility

The PARLIO peripheral is only available on:

  • ESP32-P4
  • ESP32-C6
  • ESP32-H2
  • ESP32-C5

Other ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3) do not have PARLIO hardware.

Compilation on Windows MSys

Currently, ESP32-P4 targets cannot be compiled in Windows MSys/Git Bash environments due to ESP-IDF toolchain limitations. Workarounds:

  • Use native Windows Command Prompt
  • Use WSL2 (Windows Subsystem for Linux)
  • Use Linux natively
  • Use Docker with ESP-IDF image
  • Use PlatformIO with proper environment setup

This is an ESP-IDF limitation, not a FastLED issue.

Maximum LED Count

Theoretical maximum is limited by available memory:

  • ESP32-P4: ~8 MB PSRAM (can handle 10,000+ LEDs)
  • ESP32-C6: ~512 KB SRAM (can handle 2,000+ LEDs)
  • ESP32-H2: ~256 KB SRAM (can handle 1,000+ LEDs)
  • ESP32-C5: ~512 KB SRAM (can handle 2,000+ LEDs)

Practical limits depend on your application's other memory needs.

Troubleshooting

LEDs Don't Light Up

  1. Check connections: Verify data pins are correctly wired
  2. Power supply: Ensure adequate power for LED count (5V, ~60mA per LED at full white)
  3. Pin assignments: Some pins may not support PARLIO output
  4. Enable PARLIO: Ensure FASTLED_USES_ESP32P4_PARLIO is defined (automatic with platform detection)

Wrong Colors

  1. Color order: Try different color orders (GRB, RGB, etc.) in addLeds<>()
  2. Check LED type: Verify your LEDs are WS2812/compatible
  3. Voltage levels: Ensure 5V data signal (use level shifter if needed)

Flickering or Glitches

  1. Power supply: Check for voltage drops under load
  2. Data line quality: Keep data lines short, use proper wiring
  3. Interference: Separate data lines from power lines
  4. Frame rate: Reduce update rate if pushing limits

Performance Lower Than Expected

  1. CPU overhead: Check what else is running on the chip
  2. Memory constraints: Large LED counts may cause PSRAM access overhead
  3. Power throttling: Thermal or power limits may reduce clock speeds

Testing and Mock Support

Mock Peripheral Architecture

The PARLIO driver supports comprehensive unit testing through a peripheral abstraction layer:

ParlioEngine (High-level logic)
      │
      └──► IParlioPeripheral (Virtual interface)
                  │
          ┌───────┴────────┐
          │                │
   ParlioPeripheralESP  ParlioPeripheralMock
   (Real hardware)      (Unit testing)

Key Interfaces

IParlioPeripheral - Virtual interface defining all hardware operations:

  • initialize() - Configure peripheral with timing and GPIO pins
  • enable()/disable() - Control peripheral power state
  • transmit() - Queue DMA transmission
  • waitAllDone() - Block until transmission complete
  • registerTxDoneCallback() - Install ISR handler
  • allocateDmaBuffer()/freeDmaBuffer() - DMA-safe memory management

ParlioPeripheralESP - Real hardware implementation (thin wrapper):

  • Delegates directly to ESP-IDF PARLIO APIs
  • Zero business logic - pure hardware abstraction
  • Used on ESP32-C6/P4/H2/C5 with PARLIO hardware

ParlioPeripheralMock - Mock implementation for unit testing:

  • Simulates hardware behavior on host platforms (x86/ARM)
  • Captures transmitted waveform data for validation
  • Provides ISR simulation and error injection
  • Enables hardware-independent testing of driver logic

Writing Unit Tests

Use the mock peripheral to test PARLIO driver behavior without real hardware:

cpp
#include "test.h"
#include "FastLED.h"
#include "platforms/shared/mock/esp/32/drivers/parlio_peripheral_mock.h"
#include "platforms/esp/32/drivers/parlio/parlio_engine.h"

using namespace fl::detail;

TEST_CASE("ParlioEngine transmission test") {
    // Get driver instance
    auto& driver = ParlioEngine::getInstance();

    // Initialize with test configuration
    fl::vector<int> pins = {1, 2, 4, 8};
    ChipsetTimingConfig timing = {350, 800, 450, 50};  // WS2812 timing
    driver.initialize(4, pins, timing, 100);

    // Prepare test data
    uint8_t scratch[300];  // 100 LEDs × 3 bytes
    // ... fill with test pattern ...

    // Transmit
    bool success = driver.beginTransmission(scratch, 300, 4, 300);
    CHECK(success);

    // Access mock for validation
    auto* mock = getParlioMockInstance();
    REQUIRE(mock != nullptr);

    // Verify transmission occurred
    CHECK(mock->getTransmitCount() > 0);
    CHECK(mock->isEnabled());

    // Inspect captured waveform data
    const auto& history = mock->getTransmissionHistory();
    CHECK(history.size() > 0);
    CHECK(history[0].bit_count > 0);
}

Mock Features

State Inspection:

  • isInitialized() - Check if peripheral configured
  • isEnabled() - Check if transmission active
  • isTransmitting() - Check if DMA transfer in progress
  • getTransmitCount() - Count total transmit() calls
  • getConfig() - Inspect peripheral configuration

Waveform Capture:

  • getTransmissionHistory() - Access all transmitted buffers
  • Each record includes: buffer copy, bit count, idle value, timestamp
  • Enables detailed validation of waveform encoding correctness

Error Injection:

  • setTransmitFailure(bool) - Force transmit() to fail
  • setTransmitDelay(uint32_t) - Simulate transmission timing
  • simulateTransmitComplete() - Manually trigger ISR callback

Example Tests: See tests/fl/channels/parlio_mock.cpp for comprehensive test examples covering:

  • Single/multi-lane initialization
  • Transmission lifecycle
  • ISR callback simulation
  • Ring buffer streaming
  • Error handling
  • Edge cases (zero LEDs, maximum width)

Performance Implications

The virtual dispatch pattern has minimal performance impact:

  • Virtual call overhead: ~2-3 CPU cycles per call
  • ISR context: Modern compilers can devirtualize calls when type is known
  • Memory overhead: +8 bytes for vtable pointer (negligible)
  • Measured impact: <1μs difference in ISR execution time

This architecture enables comprehensive testing while maintaining production performance.

Implementation Reference

This implementation is based on TroyHacks' proven WLED PARLIO driver, adapted to FastLED's architecture. Key differences:

  • FastLED handles brightness/gamma upstream
  • FastLED's color order abstraction simplifies usage
  • Integrated with FastLED's controller infrastructure
  • Added peripheral abstraction layer for unit testing (new!)

Further Reading

Credits

  • Original Algorithm: TroyHacks (WLED PARLIO implementation)
  • Enhanced Features: Inspired by WLED Moon Modules (troyhacks/WLED P4_experimental branch)
  • FastLED Integration: FastLED contributors
  • WS2812 Protocol: WorldSemi

License

This code is part of the FastLED library and is licensed under the MIT License.