src/platforms/arm/rp/rpcommon/README.md
This directory contains shared code for all Raspberry Pi RP2xxx platforms (RP2040, RP2350, and future variants). All code in this directory is platform-independent and works across the entire RP2xxx family.
clockless_rp_pio.hPurpose: Core PIO-based implementation for clockless LED protocols (WS2812, WS2811, SK6812, etc.)
Key Features:
Platform Adaptation:
// Automatically adapts to platform PIO count
#if defined(PICO_RP2040)
const PIO pios[NUM_PIOS] = { pio0, pio1 }; // 2 PIOs
#elif defined(PICO_RP2350)
const PIO pios[NUM_PIOS] = { pio0, pio1, pio2 }; // 3 PIOs
#endif
Usage: Platform-specific wrappers include this file:
rp2040/clockless_arm_rp2040.h includes ../rpcommon/clockless_rp_pio.hrp2350/clockless_arm_rp2350.h includes ../rpcommon/clockless_rp_pio.hpio_asm.hPurpose: Macros for generating PIO assembly instructions programmatically
Key Macros:
pio_out(pins, bit_count) // Output to pins
pio_set(destination, value) // Set scratch/pins/pindirs
pio_nop() // No operation (used for timing)
pio_jmp(condition, label) // Conditional jump
pio_wait(polarity, source) // Wait for condition
pio_in(source, bit_count) // Input from source
pio_push(if_full, block) // Push to RX FIFO
pio_pull(if_empty, block) // Pull from TX FIFO
pio_mov(destination, source) // Move data
pio_irq(mode, num) // Trigger/clear/wait IRQ
Example Usage:
// Build PIO program for WS2812 LEDs
uint16_t program[] = {
pio_out(pio_pins, 1), // Output 1 bit to pins
pio_jmp(x_dec, loop_start), // Decrement X, jump if not zero
pio_set(pins, 0), // Set pins low
// ...
};
Platform Independence: PIO instruction encoding is identical across all RP2xxx platforms.
pio_gen.hPurpose: Generates complete PIO programs for clockless LED protocols at runtime
Key Function:
void generate_pio_program(
uint16_t* program, // Output: Generated PIO instructions
int T1, int T2, int T3, // Timing: T1=high, T2=low(1), T3=low(0) in cycles
int* wrap_target, // Output: Program loop start
int* wrap // Output: Program loop end
);
How It Works:
Example Timing:
// WS2812 timing at 125 MHz (RP2040)
T1 = 100 cycles // 800ns high pulse (for '1' bit)
T2 = 56 cycles // 450ns high pulse (for '0' bit)
T3 = 106 cycles // 850ns low pulse
// Same timing at 150 MHz (RP2350) automatically scales
T1 = 120 cycles // Still 800ns
T2 = 68 cycles // Still 450ns
T3 = 128 cycles // Still 850ns
Platform Independence: Timing calculations automatically adapt to F_CPU.
These drivers use PIO and DMA to control multiple SPI-based LED strips (APA102, SK9822, etc.) simultaneously.
spi_hw_2_rp.cppPurpose: Dual-lane parallel SPI driver - control 2 strips simultaneously
Pin Configuration:
// Strip 0: Default SPI pins
Clock: GPIO 18
Data: GPIO 19
// Strip 1: Secondary pins
Clock: GPIO 20
Data: GPIO 21
Features:
spi_hw_4_rp.cppPurpose: Quad-lane parallel SPI driver - control 4 strips simultaneously
Pin Configuration:
// Strips 0-3 use sequential pins starting from base
Clock: GPIO 2, 4, 6, 8
Data: GPIO 3, 5, 7, 9
Resource Usage:
spi_hw_8_rp.cppPurpose: Octal-lane parallel SPI driver - control 8 strips simultaneously
Pin Configuration:
// Strips 0-7 use sequential pins
Clock: GPIO 2, 4, 6, 8, 10, 12, 14, 16
Data: GPIO 3, 5, 7, 9, 11, 13, 15, 17
Resource Usage:
Platform Considerations:
led_sysdefs_rp_common.hPurpose: Common system definitions shared across all RP2xxx platforms
Defines:
// Interrupt control
namespace fl {
inline void interrupts() { /* enable interrupts */ }
inline void noInterrupts() { /* disable interrupts */ }
} // namespace fl
// Delay functions (if not provided by framework)
#ifndef delay
#define delay(ms) /* platform delay */
#endif
// Common RP-specific macros
#define FL_CLOCKLESS_CONTROLLER_DEFINED 1 // PIO clockless support available
Usage: Included by platform-specific led_sysdefs_arm_rpXXXX.h files:
// In rp2040/led_sysdefs_arm_rp2040.h
#include "../rpcommon/led_sysdefs_rp_common.h"
#ifndef F_CPU
#define F_CPU 125000000 // RP2040: 125 MHz default
#endif
// In rp2350/led_sysdefs_arm_rp2350.h
#include "../rpcommon/led_sysdefs_rp_common.h"
#ifndef F_CPU
#define F_CPU 150000000 // RP2350: 150 MHz default
#endif
The common code uses these macros to adapt to platform differences:
// PIO instance count (defined by pico-sdk)
NUM_PIOS // 2 on RP2040, 3 on RP2350
// Platform detection
PICO_RP2040 // Defined for RP2040
PICO_RP2350 // Defined for RP2350
// CPU frequency (defined in platform-specific headers)
F_CPU // 125000000 (RP2040) or 150000000 (RP2350)
Each RP2xxx chip has multiple PIO instances, each containing 4 state machines:
RP2040 (2 PIOs × 4 state machines = 8 total):
pio0: [sm0] [sm1] [sm2] [sm3]
pio1: [sm0] [sm1] [sm2] [sm3]
RP2350 (3 PIOs × 4 state machines = 12 total):
pio0: [sm0] [sm1] [sm2] [sm3]
pio1: [sm0] [sm1] [sm2] [sm3]
pio2: [sm0] [sm1] [sm2] [sm3] ← Additional PIO instance
Clockless LEDs (1 strip):
Parallel SPI (2 strips):
Parallel SPI (4 strips):
Parallel SPI (8 strips):
// 1. Generate program based on LED timing
uint16_t program[32];
int wrap_target, wrap;
generate_pio_program(program, T1, T2, T3, &wrap_target, &wrap);
// 2. Load program into PIO instance
PIO pio = pio0; // or pio1, pio2
uint offset = pio_add_program(pio, &program_config);
// 3. Configure state machine
uint sm = pio_claim_unused_sm(pio, true);
pio_sm_config config = pio_get_default_sm_config();
sm_config_set_wrap(&config, offset + wrap_target, offset + wrap);
// 4. Start state machine
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
// Configure DMA channel for LED data transfer
int dma_chan = dma_claim_unused_channel(true);
dma_channel_config config = dma_channel_get_default_config(dma_chan);
channel_config_set_transfer_data_size(&config, DMA_SIZE_32); // 32-bit transfers
channel_config_set_dreq(&config, pio_get_dreq(pio, sm, true)); // PIO TX FIFO
channel_config_set_read_increment(&config, true); // Increment source
channel_config_set_write_increment(&config, false); // Fixed destination (PIO FIFO)
dma_channel_configure(
dma_chan, &config,
&pio->txf[sm], // Destination: PIO TX FIFO
led_buffer, // Source: LED data in RAM
num_leds * 3, // Transfer count (RGB bytes)
true // Start immediately
);
| LEDs | Data Size | Transfer Time | CPU Usage |
|---|---|---|---|
| 100 | 300 bytes | ~3.0 ms | < 1% CPU |
| 500 | 1500 bytes | ~15 ms | < 1% CPU |
| 1000 | 3000 bytes | ~30 ms | < 1% CPU |
Notes:
| Strips | LEDs/Strip | Clock Speed | Total Time | CPU Usage |
|---|---|---|---|---|
| 2 | 100 | 10 MHz | ~2.4 ms | < 1% CPU |
| 4 | 100 | 10 MHz | ~2.4 ms | < 1% CPU |
| 8 | 100 | 10 MHz | ~2.4 ms | < 1% CPU |
Notes:
If LEDs aren't working correctly:
pio_sm_get_pc() to check if program is runningIf data isn't transferring:
dma_channel_is_busy() to verify transfer in progress// ❌ WRONG: Direct PIO register access without claiming SM
pio_sm_set_enabled(pio0, 0, true); // May conflict with other code
// ✅ CORRECT: Claim state machine first
uint sm = pio_claim_unused_sm(pio0, true);
pio_sm_set_enabled(pio0, sm, true);
// ❌ WRONG: Incorrect F_CPU
#define F_CPU 125000000 // But actually running at 133 MHz = timing errors!
// ✅ CORRECT: Match F_CPU to actual clock speed
#define F_CPU 133000000 // Correct for 133 MHz overclock
To add support for a new clockless LED protocol (e.g., WS2813, SK6805):
Determine timing requirements from LED datasheet:
T1 = High time for '1' bit (e.g., 580ns)
T2 = High time for '0' bit (e.g., 220ns)
T3 = Low time for both bits (e.g., 580ns)
Calculate PIO cycles based on F_CPU:
// At 125 MHz (8ns per cycle)
T1_cycles = (580ns / 8ns) = 73 cycles
T2_cycles = (220ns / 8ns) = 28 cycles
T3_cycles = (580ns / 8ns) = 73 cycles
Generate PIO program:
uint16_t program[32];
int wrap_target, wrap;
generate_pio_program(program, 73, 28, 73, &wrap_target, &wrap);
No changes to common code needed - timing values are the only difference!
When modifying common code:
NUM_PIOS and F_CPU for adaptationLast Updated: October 2024 Maintainer: FastLED Project Status: Production Ready