src/platforms/arm/rp/rpcommon/PARALLEL.md
The automatic parallel output driver enables seamless parallel LED control on RP2040/RP2350 platforms using the standard FastLED API. Unlike the manual ParallelClocklessController, this driver:
FastLED.addLeds() callsDefine FASTLED_RP2040_CLOCKLESS_PIO_AUTO before including FastLED.h:
#define FASTLED_RP2040_CLOCKLESS_PIO_AUTO 1
#include <FastLED.h>
#define NUM_LEDS 100
// Standard FastLED arrays
CRGB leds1[NUM_LEDS];
CRGB leds2[NUM_LEDS];
CRGB leds3[NUM_LEDS];
CRGB leds4[NUM_LEDS];
void setup() {
// Just use standard addLeds() - automatic parallel grouping!
FastLED.addLeds<WS2812, 2, GRB>(leds1, NUM_LEDS); // GPIO 2
FastLED.addLeds<WS2812, 3, GRB>(leds2, NUM_LEDS); // GPIO 3 (grouped with 2)
FastLED.addLeds<WS2812, 4, GRB>(leds3, NUM_LEDS); // GPIO 4 (grouped with 2-3)
FastLED.addLeds<WS2812, 5, GRB>(leds4, NUM_LEDS); // GPIO 5 (grouped with 2-4)
}
void loop() {
// Update LEDs
fill_rainbow(leds1, NUM_LEDS, millis() / 10);
// Standard FastLED.show() - all 4 strips output in parallel!
FastLED.show();
}
When FastLED.show() is called, the driver:
addLeds() calls| Consecutive Pins | Group Size | PIO Output Mode |
|---|---|---|
| 2 pins | 2-lane parallel | Bit-transposed |
| 3 pins | 2-lane + 1 sequential | Mixed |
| 4 pins | 4-lane parallel | Bit-transposed |
| 5-7 pins | 4-lane + fallback | Mixed |
| 8+ pins | 8-lane parallel | Bit-transposed |
Non-consecutive pins fall back to sequential (non-parallel) output.
// Pins: 2, 3, 4, 5, 10, 11, 15
FastLED.addLeds<WS2812, 2>(...); // ┐
FastLED.addLeds<WS2812, 3>(...); // ├─ Group 1: 4-lane parallel (GPIO 2-5)
FastLED.addLeds<WS2812, 4>(...); // │
FastLED.addLeds<WS2812, 5>(...); // ┘
FastLED.addLeds<WS2812, 10>(...); // ┬─ Group 2: 2-lane parallel (GPIO 10-11)
FastLED.addLeds<WS2812, 11>(...); // ┘
FastLED.addLeds<WS2812, 15>(...); // ── Group 3: Sequential (GPIO 15 alone)
Resources Used:
RP2040/RP2350 PIO hardware requires consecutive GPIO pins for parallel output.
This is a hardware limitation of the PIO out pins, N instruction.
✅ Valid Configurations:
GPIO 2-3 (2 pins)
GPIO 2-5 (4 pins)
GPIO 10-17 (8 pins)
GPIO 0-7 (8 pins)
❌ Invalid Configurations:
GPIO 2, 4, 6, 8 (non-consecutive - will use sequential fallback)
GPIO 1, 3, 5 (non-consecutive - will use sequential fallback)
Avoid using:
Recommended consecutive ranges:
The driver transposes LED data from standard RGB format to bit-parallel format:
| Group Size | Transpose Time (100 LEDs) | CPU Overhead |
|---|---|---|
| 2 strips | ~20 µs @ 133 MHz | <1% |
| 4 strips | ~35 µs @ 133 MHz | ~2% |
| 8 strips | ~60 µs @ 133 MHz | ~3% |
Transpose algorithms:
WS2812B timing limits:
Practical frame rates:
Buffer allocation (RGB mode):
(max_leds × 3 bytes) × num_strips
max_leds × 24 bytes per group
Buffer allocation (RGBW mode):
(max_leds × 4 bytes) × num_strips
max_leds × 32 bytes per group
Memory location:
Per parallel group:
Example: 4-pin + 2-pin + 1-pin groups:
#include <platforms/arm/rp/rp2040/clockless_arm_rp2040.h>
CRGB leds[4][100];
fl::ParallelClocklessController<
2, 4, // Base pin, 4 lanes
400, 850, 50000, // WS2812B timing
GRB
> controller;
void setup() {
for (int i = 0; i < 4; i++) {
controller.addStrip(i, leds[i], 100);
}
controller.init();
}
void loop() {
controller.showLeds(0xFF); // NOT FastLED.show()!
}
#define FASTLED_RP2040_CLOCKLESS_PIO_AUTO 1
#include <FastLED.h>
CRGB leds1[100], leds2[100], leds3[100], leds4[100];
void setup() {
FastLED.addLeds<WS2812, 2, GRB>(leds1, 100);
FastLED.addLeds<WS2812, 3, GRB>(leds2, 100);
FastLED.addLeds<WS2812, 4, GRB>(leds3, 100);
FastLED.addLeds<WS2812, 5, GRB>(leds4, 100);
}
void loop() {
FastLED.show(); // Standard API!
}
Benefits:
Consecutive pins required for parallel output
out pins, N instructionMaximum 12 DMA channels (shared with other peripherals)
Maximum 8 PIO state machines (2 PIOs × 4 SMs)
Variable strip lengths supported but padded to maximum
RGBW mode fully supported ✅
Sequential fallback TODO for single pins
Cause: All 8 PIO state machines are in use by other peripherals.
Solutions:
Cause: All 12 DMA channels are in use.
Solutions:
Cause: Sequential fallback not fully implemented yet.
Solutions:
Possible causes:
Power supply issues (most common)
GPIO pin conflict
Timing issues
Strips can be added at runtime:
void setup() {
// Initial strips
FastLED.addLeds<WS2812, 2, GRB>(leds1, 100);
FastLED.addLeds<WS2812, 3, GRB>(leds2, 100);
}
void addMoreStrips() {
// Add more strips later (auto-regroups on next show())
FastLED.addLeds<WS2812, 4, GRB>(leds3, 100);
FastLED.addLeds<WS2812, 5, GRB>(leds4, 100);
// Next FastLED.show() will detect 4-pin group
}
Note: Pin grouping is re-evaluated when the strip configuration changes.
The RP2040/RP2350 automatic parallel driver fully supports RGBW (4-channel) LED strips like SK6812.
#define FASTLED_RP2040_CLOCKLESS_PIO_AUTO 1
#include <FastLED.h>
#define NUM_LEDS 100
CRGB leds[NUM_LEDS];
void setup() {
// Add RGBW strip (SK6812 or similar)
FastLED.addLeds<WS2812, 2, GRB>(leds, NUM_LEDS).setRgbw(RgbwDefault());
}
void loop() {
// Set colors normally - white channel calculated automatically
fill_solid(leds, NUM_LEDS, CRGB::White);
FastLED.show();
}
Multiple RGBW strips work with automatic parallel grouping:
CRGB leds1[100], leds2[100], leds3[100], leds4[100];
void setup() {
// All 4 strips will output in parallel (GPIO 2-5)
FastLED.addLeds<WS2812, 2, GRB>(leds1, 100).setRgbw(RgbwDefault());
FastLED.addLeds<WS2812, 3, GRB>(leds2, 100).setRgbw(RgbwDefault());
FastLED.addLeds<WS2812, 4, GRB>(leds3, 100).setRgbw(RgbwDefault());
FastLED.addLeds<WS2812, 5, GRB>(leds4, 100).setRgbw(RgbwDefault());
}
You can mix RGB and RGBW strips in the same parallel group:
CRGB rgb_leds[100];
CRGB rgbw_leds[100];
void setup() {
// GPIO 2: RGB strip (WS2812)
FastLED.addLeds<WS2812, 2, GRB>(rgb_leds, 100);
// GPIO 3: RGBW strip (SK6812)
FastLED.addLeds<WS2812, 3, GRB>(rgbw_leds, 100).setRgbw(RgbwDefault());
// These will be grouped for parallel output
}
Important: When mixing RGB and RGBW strips in a parallel group:
| Strip Type | Bytes per LED | Transpose Buffer (100 LEDs) |
|---|---|---|
| RGB (3 channels) | 3 | 2,400 bytes (24 bytes/LED) |
| RGBW (4 channels) | 4 | 3,200 bytes (32 bytes/LED) |
Frame time impact:
The transpose functions support both RGB and RGBW via a bytes_per_led parameter:
transpose_8strips(input, output, num_leds, bytes_per_led): 8 parallel stripstranspose_4strips(input, output, num_leds, bytes_per_led): 4 parallel stripstranspose_2strips(input, output, num_leds, bytes_per_led): 2 parallel stripsThe bytes_per_led parameter defaults to 3 (RGB) but can be set to 4 for RGBW.
The correct function is automatically selected based on:
bytes_per_led = 4)No performance regression: RGB-only groups use bytes_per_led = 3 (default).
Enable FastLED debug output to see grouping decisions:
#define FASTLED_DEBUG_LEVEL 1
#define FASTLED_RP2040_CLOCKLESS_PIO_AUTO 1
#include <FastLED.h>
Example output:
Detecting pin groups from 4 pins
Created 4-pin parallel group at GPIO 2
Allocated resources for 4-pin parallel group at GPIO 2 (PIO0, SM0, DMA0)
Transposed 4-pin group at GPIO 2 (100 LEDs, 2400 bytes)
Parallel output for 4 pins starting at GPIO 2 (2400 bytes)
Input (Standard RGB):
Strip 0: [R0][G0][B0][R1][G1][B1]...
Strip 1: [R0][G0][B0][R1][G1][B1]...
Strip 2: [R0][G0][B0][R1][G1][B1]...
Strip 3: [R0][G0][B0][R1][G1][B1]...
Output (Bit-Transposed for 4-pin PIO):
Byte 0: [0][0][0][0][S3_R0_b7][S2_R0_b7][S1_R0_b7][S0_R0_b7] // MSB of R0
Byte 1: [0][0][0][0][S3_R0_b6][S2_R0_b6][S1_R0_b6][S0_R0_b6]
...
Byte 7: [0][0][0][0][S3_R0_b0][S2_R0_b0][S1_R0_b0][S0_R0_b0] // LSB of R0
Byte 8: [0][0][0][0][S3_G0_b7][S2_G0_b7][S1_G0_b7][S0_G0_b7] // MSB of G0
...
Byte 23: [0][0][0][0][S3_B0_b0][S2_B0_b0][S1_B0_b0][S0_B0_b0] // LSB of B0
Total: 24 bytes per LED (8 bytes per channel × 3 channels)
Input (Standard RGBW):
Strip 0: [R0][G0][B0][W0][R1][G1][B1][W1]...
Strip 1: [R0][G0][B0][W0][R1][G1][B1][W1]...
Strip 2: [R0][G0][B0][W0][R1][G1][B1][W1]...
Strip 3: [R0][G0][B0][W0][R1][G1][B1][W1]...
Output (Bit-Transposed for 4-pin PIO):
Byte 0: [0][0][0][0][S3_R0_b7][S2_R0_b7][S1_R0_b7][S0_R0_b7] // MSB of R0
...
Byte 7: [0][0][0][0][S3_R0_b0][S2_R0_b0][S1_R0_b0][S0_R0_b0] // LSB of R0
Byte 8: [0][0][0][0][S3_G0_b7][S2_G0_b7][S1_G0_b7][S0_G0_b7] // MSB of G0
...
Byte 15: [0][0][0][0][S3_G0_b0][S2_G0_b0][S1_G0_b0][S0_G0_b0] // LSB of G0
Byte 16: [0][0][0][0][S3_B0_b7][S2_B0_b7][S1_B0_b7][S0_B0_b7] // MSB of B0
...
Byte 23: [0][0][0][0][S3_B0_b0][S2_B0_b0][S1_B0_b0][S0_B0_b0] // LSB of B0
Byte 24: [0][0][0][0][S3_W0_b7][S2_W0_b7][S1_W0_b7][S0_W0_b7] // MSB of W0
...
Byte 31: [0][0][0][0][S3_W0_b0][S2_W0_b0][S1_W0_b0][S0_W0_b0] // LSB of W0
Total: 32 bytes per LED (8 bytes per channel × 4 channels)
PIO out pins, 4 instruction: Outputs lower 4 bits to GPIO 2-5 simultaneously.
┌──────────────────────────────────────────────────────────────────┐
│ User Code: FastLED.addLeds<WS2812, PIN>() │
└──────────────────┬───────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ ClocklessController_RP2040_PIO_WS2812<PIN> │
│ - One instance per addLeds() call │
│ - Stores: mPin (GPIO number) │
└──────────────────┬───────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ RP2040ParallelGroup (Singleton) │
│ - RectangularDrawBuffer: Collects all LED data │
│ - Pin grouping detection: Sorts & detects consecutive runs │
│ - PinGroup array: Stores groups with allocated PIO/DMA │
└──────────────────┬───────────────────────────────────────────────┘
│
┌──────────┴──────────┬──────────┬──────────┐
▼ ▼ ▼ ▼
┌─────────────────┐ ┌─────────────┐ ... ┌─────────────┐
│ PinGroup 1 │ │ PinGroup 2 │ │ PinGroup N │
│ - base_pin: 2 │ │ - base_pin │ │ - base_pin │
│ - num_pins: 4 │ │ - num_pins │ │ - num_pins │
│ - PIO0, SM0 │ │ - PIO/SM │ │ - PIO/SM │
│ - DMA0 │ │ - DMA │ │ - DMA │
│ - Transpose buf │ │ - Buffer │ │ - Buffer │
└────────┬────────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Hardware: PIO State Machines + DMA │
│ - PIO: Precise WS2812 timing via custom program │
│ - DMA: Non-blocking data transfer │
│ - GPIO: Parallel output to consecutive pins │
└─────────────────────────────────────────────────────────┘
See examples/SpecialDrivers/RP/Parallel_IO.ino for a complete working example.
This driver is part of FastLED and is licensed under the MIT License.
Found a bug? Have a suggestion? Please open an issue or pull request on GitHub: https://github.com/FastLED/FastLED