src/platforms/shared/spi_bitbang/README.md
Software-based parallel SPI drivers for ESP32 platforms with two implementation strategies:
Both implementations use identical bit-banging logic - only the execution context differs.
FastLED provides two software SPI implementations using the same bit-banging logic:
ISR-based SPI (*_isr_*.hpp): Async execution via timer interrupts
Blocking SPI (*_blocking_*.hpp): Inline execution on main thread
Both implementations share the same bit-banging logic and 256-entry LUT design. Only the execution context differs (ISR vs main thread), making the implementation highly maintainable and consistent.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Shared Bit-Banging Logic (256-entry LUT) β
β - GPIO SET/CLEAR mask lookup β
β - Platform-agnostic (ESP32 & host) β
β - Zero volatile reads, write-only β
ββββββββββββββββββ¬ββββββββββββββββββββββββββββ¬βββββββββββββββββββββ
β β
βββββββββββββΌβββββββββββββ ββββββββββΌββββββββββββββ
β ISR Implementation β β Blocking Implementationβ
β - Timer-driven ISR β β - Main thread inline β
β - Async/non-blocking β β - Synchronous/blockingβ
β - ~1.6MHz ISR tick β β - No ISR overhead β
βββββββββββββ¬βββββββββββββ ββββββββββ¬ββββββββββββββββ
β β
βββββββββββββΌβββββββββββββββββββββββββ β
β ISR Width Wrappers β β
β - SpiIsr1 (1-way) β β
β - SpiIsr2 (2-way) β β
β - SpiIsr4 (4-way) β β
β - SpiIsr8 (8-way) β¨ β β
ββββββββββββββββββββββββββββββββββββββ β
β
βββββββββββββββββββββββββββββββΌβββββββββββββ
β Blocking Width Wrappers β
β - SpiBlock1 (1-way) β
β
β - SpiBlock2 (2-way) β
β
β - SpiBlock4 (4-way) β
β
β - SpiBlock8 (8-way) β¨ NEW! β
ββββββββββββββββββββββββββββββββββββββββββββ
| Width | Data Pins | Clock | Class Name | Header File |
|---|---|---|---|---|
| 1-way | 1 (D0) | 1 | SpiIsr1 | spi_isr_1.h |
| 2-way | 2 (D0-D1) | 1 | SpiIsr2 | spi_isr_2.h |
| 4-way | 4 (D0-D3) | 1 | SpiIsr4 | spi_isr_4.h |
| 8-way | 8 (D0-D7) | 1 | SpiIsr8 | spi_isr_8.h |
| Width | Data Pins | Clock | Class Name | Header File |
|---|---|---|---|---|
| 1-way | 1 (D0) | 1 | SpiBlock1 | spi_block_1.h |
| 2-way | 2 (D0-D1) | 1 | SpiBlock2 | spi_block_2.h |
| 4-way | 4 (D0-D3) | 1 | SpiBlock4 | spi_block_4.h |
| 8-way | 8 (D0-D7) | 1 | SpiBlock8 β¨ | spi_block_8.h β¨ |
Choose ISR-based SPI when:
Choose Blocking SPI when:
spi_isr_engine.h - C interface for ISR driver (formerly fl_parallel_spi_isr_rv.h)spi_isr_engine.cpp - RISC-V optimized ISR implementationspi_isr_1.h - 1-way ISR wrapper (formerly parallel_spi_isr_single_esp32c3.hpp)spi_isr_2.h - 2-way ISR wrapper (formerly parallel_spi_isr_dual_esp32c3.hpp)spi_isr_4.h - 4-way ISR wrapper (formerly parallel_spi_isr_quad_esp32c3.hpp)spi_isr_8.h - 8-way ISR wrapper β¨ (formerly fastled_parallel_spi_esp32c3.hpp)spi_block_1.h - 1-way blocking inline bit-banger (formerly parallel_spi_blocking_single.hpp)spi_block_2.h - 2-way blocking inline bit-banger (formerly parallel_spi_blocking_dual.hpp)spi_block_4.h - 4-way blocking inline bit-banger (formerly parallel_spi_blocking_quad.hpp)spi_block_8.h - 8-way blocking inline bit-banger β¨ (NEW!)spi_platform.h - Platform abstraction layer (ESP32 vs host)../esp/32/spi_platform_esp32.cpp - ESP32 platform-specific implementationhost_sim.h - Host simulation API (formerly fl_parallel_spi_host_sim.h)host_sim.cpp - Ring buffer capture for GPIO eventshost_timer.cpp - Timer simulation for testing#include "platforms/shared/spi_bitbang/spi_block_4.h"
using namespace fl;
void setup() {
// Create 4-way SPI blocking driver instance
SpiBlock4 spi;
// Configure pin mapping (4 data pins + 1 clock)
spi.setPinMapping(0, 1, 2, 3, 8); // D0-D3 = GPIO0-3, CLK = GPIO8
// Prepare data buffer
uint8_t data[4] = {0x0F, 0x0A, 0x05, 0x00};
spi.loadBuffer(data, 4);
// Transmit (blocks until complete - inline bit-banging)
spi.transmit();
// Done! No ISR setup/teardown needed.
}
#include "platforms/shared/spi_bitbang/spi_isr_4.h"
using namespace fl;
void setup() {
// Create 4-way SPI ISR driver instance
SpiIsr4 spi;
// Configure pin mapping (4 data pins + 1 clock)
spi.setPinMapping(0, 1, 2, 3, 8); // D0-D3 = GPIO0-3, CLK = GPIO8
// Prepare data buffer
uint8_t data[4] = {0x0F, 0x0A, 0x05, 0x00};
spi.loadBuffer(data, 4);
// Setup ISR (1.6MHz timer β 800kHz SPI bit rate)
spi.setupISR(1600000);
// Wait for memory visibility
SpiIsr4::visibilityDelayUs(10);
// Start transfer (non-blocking - ISR handles transmission)
spi.arm();
// Wait for completion (main thread can do other work here)
while (spi.isBusy()) {
delay(1);
}
// Acknowledge completion
spi.ackDone();
// Stop ISR
spi.stopISR();
}
// Pin configuration (varies by width)
void setPinMapping(...); // 1-way: (data, clk)
// 2-way: (d0, d1, clk)
// 4-way: (d0, d1, d2, d3, clk)
// Data transfer (simple blocking API)
void loadBuffer(const uint8_t* data, uint16_t n); // Load up to 256 bytes
void transmit(); // Block and transmit inline
Key Features:
// Pin configuration (varies by width)
void setPinMapping(...); // 1-way: (data, clk)
// 2-way: (d0, d1, clk)
// 4-way: (d0, d1, d2, d3, clk)
// 8-way: (d0-d7, clk)
// Data transfer (async API)
void loadBuffer(const uint8_t* data, uint16_t n); // Load up to 256 bytes
int setupISR(uint32_t timer_hz); // Setup timer ISR
void arm(); // Start async transfer
bool isBusy() const; // Check if transfer active
void ackDone(); // Acknowledge completion
void stopISR(); // Stop timer ISR
// Memory visibility (required before arm())
static void visibilityDelayUs(uint32_t us); // Wait for memory sync
// Status flags
uint32_t statusFlags() const; // Get status (BUSY | DONE)
static constexpr uint32_t STATUS_BUSY = 1u;
static constexpr uint32_t STATUS_DONE = 2u;
Key Features:
Both ISR and blocking implementations include host simulation for testing on development machines without ESP32 hardware:
# Blocking SPI tests (18 test cases total)
uv run test.py test_spi_blocking # All blocking variants (single/dual/quad)
# ISR-based SPI tests (26 test cases total)
uv run test.py test_parallel_spi_isr_single # 1-way ISR tests (10 test cases)
uv run test.py test_parallel_spi_isr_dual # 2-way ISR tests (9 test cases)
uv run test.py test_parallel_spi_isr_quad # 4-way ISR tests (7 test cases)
# Test coverage includes:
# - Basic transmission
# - Clock toggling verification
# - Data pattern verification (all fundamental patterns)
# - Multi-byte sequences
# - Edge cases (zero bytes, max bytes, etc.)
# - LUT initialization correctness
# - Multiple pin configurations
Blocking SPI Tests:
tests/test_spi_blocking.cpp - 18 test cases covering all blocking variants
ISR-Based SPI Tests:
tests/test_parallel_spi_isr_single.cpp - 10 test cases for 1-way ISRtests/test_parallel_spi_isr_dual.cpp - 9 test cases for 2-way ISRtests/test_parallel_spi_isr_quad.cpp - 7 test cases for 4-way ISRComplete Arduino examples are provided for both ISR and blocking implementations:
examples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_SingleSPI_Blocking/ - 1-way blockingexamples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_DualSPI_Blocking/ - 2-way blockingexamples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_QuadSPI_Blocking/ - 4-way blockingEach blocking example demonstrates:
transmit() callexamples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_SingleSPI_ISR/ - 1-way ISRexamples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_DualSPI_ISR/ - 2-way ISRexamples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_QuadSPI_ISR/ - 4-way ISRexamples/SpecialDrivers/ESP/ParallelSPI/Esp32C3_SPI_ISR/ - 8-way ISREach ISR example includes:
FL_SPI_ISR_VALIDATE)The ISR driver uses a 256-entry Look-Up Table (LUT) that maps each possible byte value to GPIO SET/CLEAR masks:
struct PinMaskEntry {
uint32_t set_mask; // GPIO pins to set high
uint32_t clear_mask; // GPIO pins to clear low
};
PinMaskEntry g_lut[256]; // LUT for all byte values (0x00-0xFF)
For each byte transmitted:
GPIO writes are abstracted through macros that select ESP32 hardware or host simulation:
#ifdef FASTLED_SPI_HOST_SIMULATION
// Host: Capture to ring buffer
#define FL_GPIO_WRITE_SET(mask) fl_gpio_sim_write_set(mask)
#define FL_GPIO_WRITE_CLEAR(mask) fl_gpio_sim_write_clear(mask)
#else
// ESP32: Direct MMIO write
#define FL_GPIO_WRITE_SET(mask) \
(*(volatile uint32_t*)(uintptr_t)FASTLED_GPIO_W1TS_ADDR = (mask))
#define FL_GPIO_WRITE_CLEAR(mask) \
(*(volatile uint32_t*)(uintptr_t)FASTLED_GPIO_W1TC_ADDR = (mask))
#endif
Typical Configuration:
FastLED's SPIBusManager automatically selects the best SPI implementation:
Hardware SPI Preferred (when available):
Software ISR Fallback (when needed):
The ESP32-P4 supports hardware octal-SPI for driving up to 8 parallel LED strips simultaneously via DMA. This is ~40Γ faster than software implementations with zero CPU usage during transmission.
Key Differences from Software Implementations:
src/platforms/esp/32/spi_hw_8_esp32.cpp and src/platforms/shared/spi_transposer.*For detailed hardware octal-SPI information, see:
src/platforms/esp/32/README.md - Hardware octal-SPI overviewsrc/platforms/shared/README.md - Transposer infrastructureTo test examples in QEMU:
# Install QEMU for ESP32 (one-time setup)
uv run ci/install-qemu.py
# Run example in QEMU
uv run test.py --qemu esp32c3 Esp32C3_SingleSPI_ISR
uv run test.py --qemu esp32c3 Esp32C3_DualSPI_ISR
uv run test.py --qemu esp32c3 Esp32C3_QuadSPI_ISR
Examples produce serial output that can be regex-matched for pass/fail validation.
LOOP.md (project root) - Project iteration loop and task trackingFEATURE_QUAD_SPI_EXTRA.md - Hardware Quad-SPI design documentationTASK.md - QEMU infrastructure and testingWhen adding new width variants or modifying implementations:
uv run test.py test_spi_blocking (blocking variants)uv run test.py test_parallel_spi_isr_* (ISR variants)parallel_spi_isr_quad_esp32c3.hpp as referenceparallel_spi_blocking_quad.hpp as reference| Characteristic | ISR-Based | Blocking (Inline) |
|---|---|---|
| Execution Context | Timer ISR (~1.6MHz) | Main thread inline |
| Overhead | ISR context switch | None (inline) |
| Latency | ISR entry delay | Zero (immediate) |
| Jitter | Interrupt scheduling | None (deterministic) |
| Main Thread | Non-blocking | Blocks during TX |
| API Complexity | Higher (7+ methods) | Lower (3 methods) |
| Use Case | Complex apps | Simple apps |
| Bit Rate | ~800kHz effective | Higher potential |
ISR Advantages:
Blocking Advantages:
Last Updated: 2025-10-13 Status: Production Ready β (Both ISR and Blocking Implementations)