Table of Contents
- What lives here
- Popular boards → where to look
- Platform header organization pattern
- Controller types in FastLED
- Picking the right path
- Backend selection cheat sheet
- Troubleshooting and tips
- Optional feature defines by platform
- How to implement a clockless controller
- How to implement a SPI controller
- PROGMEM
The src/platforms/ directory contains platform backends and integrations that FastLED targets. Each subfolder provides pin helpers, timing implementations, and controllers tailored to specific microcontrollers or host environments.
- adafruit – Adapter that lets you use Adafruit_NeoPixel via the FastLED API
- apollo3 – Ambiq Apollo3 (SparkFun Artemis family)
- arm – ARM families (Teensy, SAMD21/SAMD51, RP2040, STM32/GIGA, nRF5x, Renesas UNO R4)
- avr – AVR (Arduino UNO, Nano Every, ATtiny/ATmega variants)
- esp – ESP8266 and ESP32 families
- neopixelbus – Adapter to drive NeoPixelBus through the FastLED API
- posix – POSIX networking helpers for host builds
- shared – Platform‑agnostic components (ActiveStripData, JSON UI)
- stub – Native, no‑hardware target used by unit tests/host builds
- wasm – WebAssembly/browser target and JS bridges
- win – Windows‑specific networking helpers
- Arduino UNO (ATmega328P) → avr
- Arduino Nano Every (ATmega4809) → avr
- ATtiny85/other ATtiny (supported subsets) → avr
- Teensy 3.x (MK20) → arm/k20
- Teensy 3.6 (K66) → arm/k66
- Teensy LC (KL26) → arm/kl26
- Teensy 4.x (i.MX RT1062) → arm/mxrt1062
- Arduino Zero / SAMD21 → arm/d21
- Feather/Itsy M4, Wio Terminal / SAMD51 → arm/d51
- Arduino GIGA R1 (STM32H747) → arm/giga
- STM32 (e.g., F1 series) → arm/stm32
- Raspberry Pi Pico (RP2040) → arm/rp2040
- Nordic nRF51/nRF52 → arm/nrf51, arm/nrf52
- Arduino UNO R4 (Renesas RA4M1) → arm/renesas
- ESP8266 → esp/8266
- ESP32 family (ESP32, S2, S3, C3, C6) → esp/32
- Ambiq Apollo3 (Artemis) → apollo3
If you are targeting a desktop/browser host for demos or tests:
FastLED has evolved its platform directory to contain dispatch headers that follow a coarse-to-fine delegation pattern. These dispatch headers live at the root of src/platforms/ (e.g., int.h, io_arduino.h, quad_spi_platform.h) and route the compiler to the appropriate platform-specific implementations. This keeps platform routing clean and maintainable.
Architecture: Headers at platforms/header.h perform coarse platform detection (ESP32, AVR, ARM families) and delegate to platform-specific subdirectories for fine-grained detection (ESP32-S3, ATmega328P, SAMD51, etc.).
// platforms/header.h - Coarse detection
#if defined(FASTLED_TESTING)
#include "platforms/stub/platform_stub.h"
#elif defined(ESP32)
// Delegate to ESP-specific header for fine-grained variant detection
#include "platforms/esp/platform_esp.h"
#elif defined(__AVR__)
#include "platforms/avr/platform_avr.h"
#else
// Fallback
#endif// platforms/esp/platform_esp.h - Fine-grained detection
#if defined(ESP32) || defined(CONFIG_IDF_TARGET_ESP32)
// ESP32 classic
#elif defined(ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S2)
// ESP32-S2
#elif defined(ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S3)
// ESP32-S3
#elif defined(ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C3)
// ESP32-C3
#elif defined(ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32P4)
// ESP32-P4
// ... etc
#endif- Coarse headers stay simple: Only check for broad platform families (
ESP32,__AVR__,FL_IS_ARM) - Fine-grained logic isolated: Variant-specific detection (
ESP32S3,CONFIG_IDF_TARGET_ESP32C6) lives in platform subdirectories - Easy to maintain: Adding new variants only requires changes in platform-specific headers
- Follows existing patterns: See
platforms/int.h,platforms/audio.hfor reference implementations
FastLED uses a unified hardware manager pattern for initializing SPI controllers across all platforms. This pattern centralizes hardware initialization, uses feature flags for conditional compilation, and implements priority-based registration for multi-lane controllers.
Single Entry Point: Each platform has one initSpiHardware() function in a platform-specific manager file:
- ESP32:
platforms/esp/32/drivers/spi_hw_manager_esp32.cpp.hpp - STM32:
platforms/arm/stm32/spi_hw_manager_stm32.cpp.hpp - Teensy 4.x:
platforms/arm/teensy/teensy4_common/spi_hw_manager_mxrt1062.cpp.hpp - RP2040/RP2350:
platforms/arm/rp/rpcommon/spi_hw_manager_rp.cpp.hpp - SAMD21:
platforms/arm/d21/spi_hw_manager_samd21.cpp.hpp - SAMD51:
platforms/arm/d51/spi_hw_manager_samd51.cpp.hpp - nRF52:
platforms/arm/nrf52/spi_hw_manager_nrf52.cpp.hpp - Stub:
platforms/stub/spi_hw_manager_stub.cpp.hpp
Dispatch Headers: Coarse-to-fine platform routing via platforms/init_spi_hw.h:
// Top-level: platforms/init_spi_hw.h
#if defined(FASTLED_TESTING)
#include "platforms/stub/init_spi_hw.h"
#elif defined(FL_IS_ESP)
#include "platforms/esp/init_spi_hw.h"
#elif defined(FL_IS_ARM)
#include "platforms/arm/init_spi_hw.h"
#endif
// Platform-level: platforms/esp/init_spi_hw.h
namespace fl {
namespace platform {
void initSpiHardware();
}
}Each platform manager follows this structure (example from ESP32):
namespace fl {
namespace detail {
/// Priority constants (higher = preferred for routing)
constexpr int PRIORITY_HW_16 = 9; // Highest (16-lane I2S)
constexpr int PRIORITY_HW_8 = 8;
constexpr int PRIORITY_HW_4 = 7;
constexpr int PRIORITY_HW_2 = 6;
constexpr int PRIORITY_HW_1 = 5; // Lowest (single-lane)
/// Helper function pattern with feature flags
static void addSpiHw16IfPossible() {
#if FASTLED_ESP32_HAS_I2S
// Include concrete implementation
#include "platforms/esp/32/drivers/i2s/spi_hw_i2s_esp32.cpp.hpp"
// Create and register instances
static auto i2s0 = fl::make_shared<SpiHwI2SESP32>(0);
SpiHw16::registerInstance(i2s0, PRIORITY_HW_16);
FL_DBG("ESP32: Added I2S SpiHw16 controller");
#else
// No-op if feature not available
#endif
}
} // namespace detail
namespace platform {
/// Unified initialization entry point
void initSpiHardware() {
FL_DBG("ESP32: Initializing SPI hardware");
// Register in priority order (highest to lowest)
detail::addSpiHw16IfPossible(); // Priority 9
detail::addSpiHw8IfPossible(); // Priority 8
detail::addSpiHw4IfPossible(); // Priority 7
detail::addSpiHw2IfPossible(); // Priority 6
detail::addSpiHw1IfPossible(); // Priority 5
FL_DBG("ESP32: SPI hardware initialized");
}
} // namespace platform
} // namespace fl- Lazy initialization: Hardware is only initialized on first access to
SpiHwN::getAll() - No static constructors: Avoids initialization order issues and startup overhead
- Feature flag driven: Conditional compilation uses clear
PLATFORM_HAS_*macros - Priority-based routing: Higher lane counts get priority when multiple controllers are available
- Platform dispatch: Clean separation between detection (dispatch headers) and implementation (manager files)
- Meyer's Singleton: Thread-safe lazy initialization using static local variables
- Type-safe shared pointers: Automatic memory management with
fl::shared_ptr
| Platform | SpiHw1 | SpiHw2 | SpiHw4 | SpiHw8 | SpiHw16 | Hardware |
|---|---|---|---|---|---|---|
| ESP32 classic | ✅ | ✅ | ✅ | ✅ | ✅ | I2S LCD_CAM (16-lane) |
| ESP32-S3 | ✅ | ✅ | ✅ | ✅ | ❌ | SPI peripheral |
| STM32 F2/F4/F7/H7/L4 | ✅ | ✅ | ✅ | ✅ | ❌ | Timer+DMA (stream-based) |
| Teensy 4.x | ✅ | ✅ | ✅ | ❌ | ❌ | LPSPI WIDTH field |
| RP2040/RP2350 | ✅ | ✅ | ✅ | ✅ | ❌ | PIO state machines |
| SAMD21 | ✅ | ✅ | ❌ | ❌ | ❌ | SERCOM + DMA |
| SAMD51 | ✅ | ✅ | ✅ | ❌ | ❌ | SERCOM + DMA |
| nRF52 | ✅ | ✅ | ✅ | ❌ | ❌ | Timer/PPI |
See shared/SPI_MANAGER_PATTERN.md for a detailed implementation guide and template code for adding SPI hardware support to a new platform
FastLED provides two primary controller categories. Choose based on your LED chipset and platform capabilities.
- For WS2811/WS2812/WS2812B/SK6812 and similar “NeoPixel”‑class LEDs
- Encodes bits as precisely timed high/low pulses (T0H/T1H/T0L/T1L)
- Implemented with tight CPU timing loops, SysTick, or hardware assist (e.g., ESP32 RMT or I2S‑parallel)
- Available as single‑lane and, on some platforms, multi‑lane “block clockless” drivers
- Sensitive to long interrupts; keep ISRs short during
show()
Special cases:
- ESP32 offers multiple backends for clockless output (see esp/32):
- RMT (recommended, per‑channel timing)
- I2S‑parallel (many strips with identical timing)
- Clockless over SPI (WS2812‑class only)
- For chipsets with data+clock lines (APA102/DotStar, SK9822, LPD8806, WS2801)
- Uses hardware SPI peripherals to shift pixels with explicit clocking
- Generally tolerant of interrupts and scales to higher data rates more easily
- Requires an SPI‑capable LED chipset (not for WS2812‑class, except the ESP32 “clockless over SPI” path above)
- WS2812/SK6812 → Clockless controllers (ESP32: RMT or I2S‑parallel for many identical strips)
- APA102/SK9822/DotStar → SPI controllers for stability and speed
- Prefer neopixelbus or adafruit adapters if you rely on those ecosystems but want to keep FastLED’s API RP2040 (PIO clockless) needs no special defines; use standard WS2812 addLeds call.
- ESP32 clockless: RMT (flexible), I2S‑parallel (many identical strips), or SPI‑WS2812 path (WS2812‑only)
- See ESP32 section for detailed I2S support (ESP32Dev, ESP32‑S3) and RMT v4 vs v5 selection, including define examples and guidance on avoiding mixed RMT4/RMT5 usage.
- Teensy 3.x/4.x: clockless (single or block multi‑lane), plus OctoWS2811/SmartMatrix integrations (see arm/k20, arm/mxrt1062)
- AVR: clockless WS2812 and SPI chipsets; small devices benefit from fewer interrupts during
show()
- First‑pixel flicker or color glitches often indicate interrupt interference. Reduce background ISRs (Wi‑Fi/BLE/logging) during
FastLED.show()or select a hardware‑assisted backend. - Power and level shifting matter: WS281x typically need 5V data at sufficient current; ensure a common ground between controller and LEDs.
- On ARM families, PROGMEM is typically disabled (
FASTLED_USE_PROGMEM=0); on AVR it is enabled. - For very large installations, consider parallel outputs (OctoWS2811 on Teensy, I2S‑parallel on ESP32) and DMA‑friendly patterns.
Clockless controllers generate the one‑wire NRZ waveforms used by WS281x‑class LEDs by toggling a GPIO with precise timings. FastLED’s clockless base controllers accept three timing parameters per bit: T1, T2, T3.
The timing model is:
- At T=0: line goes high to start a bit
- At T=T1: drop low to transmit a 0‑bit (T0H just ended)
- At T=T1+T2: drop low to transmit a 1‑bit (T1H just ended)
- At T=T1+T2+T3: bit cycle ends; next bit can start
Mapping to datasheet values:
- T0H = T1
- T1H = T2
- T0L = duration − T0H
- T1L = duration − T1H
Where duration is the max of the two bit periods: max(T0H + T0L, T1H + T1L).
Embedded helper script (from src/chipsets.h) to derive T1/T2/T3 from datasheet T0H/T0L/T1H/T1L:
print("Enter the values of T0H, T0L, T1H, T1L, in nanoseconds: ")
T0H = int(input(" T0H: "))
T0L = int(input(" T0L: "))
T1H = int(input(" T1H: "))
T1L = int(input(" T1L: "))
duration = max(T0H + T0L, T1H + T1L)
print("The max duration of the signal is: ", duration)
T1 = T0H
T2 = T1H
T3 = duration - T0H - T0L
print("T1: ", T1)
print("T2: ", T2)
print("T3: ", T3)Notes:
- Some platforms express timings in CPU cycles rather than nanoseconds; FastLED provides converters/macros to derive cycle counts.
- See the platform‑specific
ClocklessControllerimplementation for the required units and conversion helpers.
“Multilane” or “block clockless” controllers send several strips in parallel by transposing pixel data into per‑bit planes and toggling multiple pins simultaneously.
- Benefits: High aggregate throughput; useful for matrices and large installations.
- Constraints: All lanes must share the same timing and frame cadence; ISR windows apply across all lanes during output.
Supported in this codebase:
- Teensy 3.x (K20): clockless_block_arm_k20.h
- Teensy 3.6 (K66): clockless_block_arm_k66.h
- Teensy 4.x (i.MX RT1062): block_clockless_arm_mxrt1062.h
- Arduino Due (SAM3X): clockless_block_arm_sam.h
- ESP8266: clockless_block_esp8266.h
- ESP32: I2S‑parallel backend (see esp/32) provides many‑lane output with identical timing
Typically single‑lane only in this codebase:
- AVR family
- RP2040 (PIO driver here is single‑lane)
- nRF51/nRF52
- STM32/GIGA (clockless single‑lane in this tree)
For Teensy, also consider OctoWS2811 for DMA‑driven parallel output.
SPI‑driven LEDs use a clocked data line plus a clock line. Implementation is generally simpler and more ISR‑tolerant than clockless.
- Hardware SPI: Uses the MCU’s SPI peripheral; best performance and CPU efficiency; works with DMA on some platforms.
- Software SPI (bit‑bang): Uses GPIO toggling when a hardware SPI peripheral or pins aren’t available; slower and more CPU‑intensive but universally portable.
FastLED’s SPIOutput abstracts both; platform headers select efficient implementations where available.
- APA102 / “DotStar” (and HD variants): Data + clock + per‑pixel global brightness; tolerant of interrupts. See APA102 controller definitions.
- SK9822: Protocol compatible with APA102 with minor differences; supported.
- WS2801, LPD8806, LPD6803, P9813, SM16716: Older or niche SPI‑style protocols; supported via specific controllers.
When choosing SPI speed, consult chipset and wiring limits (signal integrity on long runs). Long APA102 runs may require reduced clock rates.
Some platforms store constant tables and palettes in flash (program) memory rather than RAM. FastLED abstracts PROGMEM usage via macros; support varies by platform.
- AVR: Full PROGMEM support; default
FASTLED_USE_PROGMEM=1. Flash is separate from RAM; usepgm_read_accessors for reads. - ESP8266: PROGMEM supported; see progmem_esp8266.h. Provides
FL_PROGMEM,FL_PGM_READ_*, andFL_ALIGN_PROGMEM(4‑byte alignment) helpers. - ESP32, ARM (Teensy, SAMD21/51, RP2040, STM32/GIGA, nRF): Typically treat constants as directly addressable; FastLED defaults to
FASTLED_USE_PROGMEM=0on these families to avoid unnecessary indirection.
Aligned reads for PROGMEM:
- On platforms that require alignment (e.g., ESP8266), use 4‑byte alignment for multibyte table entries to avoid unaligned access traps. FastLED exposes
FL_ALIGN_PROGMEMfor this purpose.
Platforms without PROGMEM (or where it’s a no‑op in this codebase):
- Most ARM families (SAMD, STM32, nRF, RP2040) and ESP32 use memory‑mapped flash; PROGMEM macros are disabled here via
FASTLED_USE_PROGMEM=0.
Check each platform’s led_sysdefs_* header for the recommended PROGMEM and interrupt policy.
These are commonly available across multiple platforms. Pass them as build defines (e.g., build_flags in PlatformIO), and define them prior to including FastLED.h.
FASTLED_USE_PROGMEM— Control PROGMEM usage (enabled on AVR, typically disabled elsewhere)FASTLED_ALLOW_INTERRUPTS— Allow interrupts duringshow()(platform defaults vary)FASTLED_INTERRUPT_RETRY_COUNT— Global retry count when timing is disruptedFASTLED_DEBUG_COUNT_FRAME_RETRIES— Enable counters/logging of frame retries due to timing issues
For platform‑specific feature defines (e.g., ESP32 RMT/I2S knobs, RP2040 PIO selection, Teensy/STM32 options), see the README in that platform’s directory:
- ESP32
- AVR
- ARM families (SAMD, RP2040, STM32/GIGA, nRF, Teensy, Renesas)
- ESP8266
- WASM
- Adapters: Adafruit, NeoPixelBus