35#include "platforms/arm/rp/is_rp.h"
37#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
48#include "hardware/dma.h"
49#include "hardware/pio.h"
54constexpr int kDefaultPinA = 2;
55constexpr int kDefaultPinB = 4;
56constexpr int kDefaultLeds = 256;
57constexpr int kDefaultIterations = 8;
65constexpr uint32_t kShowTimeoutMs = 2000;
67PIO pioForIndex(
int index) {
68 if (index == 0)
return pio0;
69 if (index == 1)
return pio1;
70#if defined(FL_IS_RP2350)
71 if (index == 2)
return pio2;
77int freeSmsInBlock(
int pio_index) {
78 PIO pio = pioForIndex(pio_index);
79 if (pio ==
nullptr)
return 0;
81 for (uint sm = 0; sm < 4; ++sm) {
82 if (!pio_sm_is_claimed(pio, sm)) ++free_count;
87int freeDmaChannels() {
89 for (uint ch = 0; ch < NUM_DMA_CHANNELS; ++ch) {
90 if (!dma_channel_is_claimed(ch)) ++free_count;
96struct ResourceSnapshot {
97 int free_sms[NUM_PIOS] = {};
101 for (
int i = 0; i < NUM_PIOS; ++i) free_sms[i] = freeSmsInBlock(i);
102 free_dma = freeDmaChannels();
105 int totalFreeSms()
const {
107 for (
int i = 0; i < NUM_PIOS; ++i) total += free_sms[i];
111 bool operator==(
const ResourceSnapshot& other)
const {
112 if (free_dma != other.free_dma)
return false;
113 for (
int i = 0; i < NUM_PIOS; ++i) {
114 if (free_sms[i] != other.free_sms[i])
return false;
119 fl::json toJson()
const {
122 for (
int i = 0; i < NUM_PIOS; ++i) {
123 blocks.
push_back(
static_cast<int64_t
>(free_sms[i]));
125 out.
set(
"freeSmsPerBlock", blocks);
126 out.
set(
"freeDma",
static_cast<int64_t
>(free_dma));
148 int pin_a = kDefaultPinA;
149 int pin_b = kDefaultPinB;
150 int num_leds = kDefaultLeds;
151 int iterations = kDefaultIterations;
152 if (
args.is_object()) {
153 auto read_int = [&
args](
const char* key,
int fallback) ->
int {
154 if (
args.contains(key) &&
args[key].is_int()) {
155 return static_cast<int>(
args[key].as_int().value());
159 pin_a = read_int(
"pinA", pin_a);
160 pin_b = read_int(
"pinB", pin_b);
161 num_leds = read_int(
"numLeds", num_leds);
162 iterations = read_int(
"iterations", iterations);
169 if (num_leds < 1 || num_leds > kMaxLeds || iterations < 1 ||
170 iterations > kMaxIterations) {
172 response.set(
"error",
"InvalidArgs");
173 response.set(
"maxNumLeds",
static_cast<int64_t
>(kMaxLeds));
174 response.set(
"maxIterations",
static_cast<int64_t
>(kMaxIterations));
178 response.set(
"pinA",
static_cast<int64_t
>(pin_a));
179 response.set(
"pinB",
static_cast<int64_t
>(pin_b));
180 response.set(
"numLeds",
static_cast<int64_t
>(num_leds));
181 response.set(
"iterations",
static_cast<int64_t
>(iterations));
183 ResourceSnapshot baseline;
185 response.set(
"baseline", baseline.toJson());
192 fl::vector<CRGB> leds_a(num_leds);
193 fl::vector<CRGB> leds_b(num_leds);
195 bool collision_free =
false;
196 bool no_residue =
true;
197 bool shows_completed =
false;
199 fl::ChannelPtr channel_a = addPioChannel(0, pin_a, leds_a);
200 fl::ChannelPtr channel_b = addPioChannel(1, pin_b, leds_b);
201 response.set(
"channelACreated",
static_cast<bool>(channel_a));
202 response.set(
"channelBCreated",
static_cast<bool>(channel_b));
204 if (!channel_a || !channel_b) {
207 response.set(
"error",
"ChannelCreationFailed");
211 for (
int i = 0; i < num_leds; ++i) {
212 leds_a[i] =
CRGB(0xFF, 0x00, 0x00);
213 leds_b[i] =
CRGB(0x00, 0xFF, 0x00);
218 ResourceSnapshot busiest;
220 int fewest_free = busiest.totalFreeSms() + busiest.free_dma;
221 int frames_sampled = 0;
226 int samples_taken = 0;
227 shows_completed =
true;
228 for (
int iter = 0; iter < iterations && shows_completed; ++iter) {
231 const u32 deadline =
millis() + kShowTimeoutMs;
233 while (!
done &&
static_cast<i32
>(
millis() - deadline) < 0) {
234 ResourceSnapshot in_flight;
236 const int held = in_flight.totalFreeSms() + in_flight.free_dma;
237 if (held < fewest_free) {
245 shows_completed =
done;
249 ResourceSnapshot between;
251 if (!(between == baseline)) no_residue =
false;
253 response.set(
"samplesTaken",
static_cast<int64_t
>(samples_taken));
254 response.set(
"showsCompleted", shows_completed);
255 response.set(
"framesSampled",
static_cast<int64_t
>(frames_sampled));
256 response.set(
"busiestInFlight", busiest.toJson());
257 response.set(
"noResidueBetweenFrames", no_residue);
260 const bool pio0_held_one = busiest.free_sms[0] == baseline.free_sms[0] - 1;
261 const bool pio1_held_one = busiest.free_sms[1] == baseline.free_sms[1] - 1;
262 response.set(
"pio0HeldOne", pio0_held_one);
263 response.set(
"pio1HeldOne", pio1_held_one);
264 response.set(
"bothHeldAtOnce", pio0_held_one && pio1_held_one);
269 bool no_over_claim = busiest.free_dma >= baseline.free_dma - 2;
270 for (
int i = 0; i < NUM_PIOS; ++i) {
271 if (busiest.free_sms[i] < baseline.free_sms[i] - 1) no_over_claim =
false;
273 const bool observed_in_flight =
274 busiest.totalFreeSms() + busiest.free_dma <
275 baseline.totalFreeSms() + baseline.free_dma;
276 collision_free = no_over_claim && observed_in_flight;
277 response.set(
"noOverClaim", no_over_claim);
278 response.set(
"observedInFlight", observed_in_flight);
279 response.set(
"collisionFree", collision_free);
284 ResourceSnapshot released;
286 response.set(
"released", released.toJson());
287 const bool no_leak = released == baseline;
290 response.set(
"success", collision_free && shows_completed && no_residue &&
304 response.set(
"success",
false);
305 response.set(
"error",
"RP PIO parallel resource test requires an RP2040/RP2350");
size_t capture(fl::shared_ptr< fl::RxChannel > rx_channel, fl::span< uint8_t > rx_buffer, size_t expected_data_bytes, int tx_pin, const fl::ChipsetTimingConfig &timing, const char *driver_name, fl::RunResult *diagnostics=nullptr)
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
ESP32-P4 Parallel IO (PARLIO) LED channel.
void setDriverEnabled(const char *name, bool enabled)
Enable or disable a channel driver by name at runtime.
void show(fl::u8 scale)
Update all our controllers with the current led colors, using the passed in brightness.
void wait()
Wait for all channel bus transmissions to complete.
void clear(bool writeData=false)
Clear the leds, wiping the local array of data.
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
void push_back(const json &value) FL_NO_EXCEPT
static json object() FL_NO_EXCEPT
static json array() FL_NO_EXCEPT
@ CHANNELS
Remove all channels from controller list.
constexpr int kMaxIterations
fl::json runRpPioParallelResourceTest(const fl::json &args)
Prove PIO0 + PIO1 simultaneous operation at the resource level (#3899).
constexpr ChipsetTimingConfig makeTimingConfig() FL_NO_EXCEPT
Convert compile-time CHIPSET type to runtime timing config.
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
FASTLED_FORCE_INLINE bool operator==(const CRGB &lhs, const CRGB &rhs) FL_NO_EXCEPT
Check if two CRGB objects have the same color data.
@ PIO
PIO receiver (RP2040/RP2350)
Configuration for a single LED channel.
Optional channel configuration parameters All fields have sensible defaults and can be overridden as ...
Compile-time timing extraction from ChipsetTiming structs.