FastLED 3.10.9
Loading...
Searching...
No Matches
AutoResearchRemoteDriverMethods.cpp
Go to the documentation of this file.
1// LED driver test/benchmark RPC bindings. Today: Teensy 4.x FlexIO RX
2// validation (flexioRxBenchmark, flexioObjectFledTest, flexioRxLoopbackPing).
3// Platform-specific driver tests for other MCUs land here in the future —
4// the RPC layer is platform-agnostic; #if guards inside each binding gate
5// the platform-specific bodies.
6// Extracted from AutoResearchRemote.cpp as part of #3132 / meta #3127.
7
9#if FL_PLATFORM_HAS_LARGE_MEMORY
10
11// Legacy debug macros (no-ops, kept for debugTest RPC function)
12#define DEBUG_PRINT(x) do {} while(0)
13#define DEBUG_PRINTLN(x) do {} while(0)
14
15#include "AutoResearchRemote.h"
16#include "AutoResearchBle.h"
17#include "AutoResearchNet.h"
18#include "AutoResearchOta.h"
20#include "fl/system/heap.h"
21#include "Common.h"
22#include "AutoResearchTest.h"
23#include "AutoResearchHelpers.h"
24#include "fl/stl/sstream.h"
25#include "fl/stl/unique_ptr.h"
26#include "fl/stl/optional.h"
27#include "fl/stl/json.h"
28#include "fl/task/task.h"
29#include "fl/task/executor.h"
31#include "fl/stl/atomic.h"
32#include "fl/task/promise.h"
33#include "fl/math/simd.h"
34#include "AutoResearchSimd.h"
41#include "AutoResearchTimingDrift.h" // timingDriftTest RPC (#2994 repro)
42#include "fl/chipsets/spi.h"
43#include "fl/channels/config.h"
44#include "fl/stl/span.h"
45#include "fl/stl/singleton.h"
46#include <Arduino.h>
47#if defined(FL_IS_ESP32) // ok platform specific - the explicit ESP32 SPI host probe needs this chip's peripheral count
48#include "platforms/esp/32/drivers/spi/spi_hw_base.h" // ok platform specific - FASTLED_ESP32_HAS_SPI3 rejects one-host chips
49#endif
50
51// #3428: FlexIO2 SPI mode bring-up smoke test (`flexioSpiSelfTest` RPC).
52// The header is Teensy 4.x-gated, but #include is safe on any platform
53// because all declarations sit inside `#if defined(FL_IS_TEENSY_4X)`.
54#include "platforms/arm/teensy/teensy4_common/drivers/flexio/flexio_spi_mode.h"
55
56// #3428: ObjectFLED DMA-bit-banged SPI mode bring-up smoke test
57// (`objectfledSpiSelfTest` RPC). Same Teensy 4.x-gating pattern.
58#include "platforms/arm/teensy/teensy4_common/drivers/objectfled/objectfled_spi_mode.h"
59
60#include "fl/net/ble.h"
61
62#include "fl/codec/h264.h"
63#include "fl/codec/mp4_parser.h"
65#include "fl/fx/frame.h"
66
67namespace {
68
69#if defined(FL_IS_TEENSY_4X)
70struct FlexIoObjectFledState {
71 CRGB leds[100];
72};
73
74FlexIoObjectFledState& flexIoObjectFledState() {
76}
77#endif
78
79} // namespace
80
82 // One physical data jumper checks either SPI host while a second APA102
83 // channel exercises the registration-order interaction from #4864.
84 remote.bind("dualSpiHostLoopback", [this](const fl::json& args) -> fl::json {
85 fl::json response = fl::json::object();
86#if !defined(FL_IS_ESP32) || !FASTLED_ESP32_HAS_SPI3 // ok platform specific - this probe requires two physical ESP32 SPI hosts
87 (void)this;
88 (void)args;
89 response.set("success", false);
90 response.set("error", "TwoSpiHostsUnavailable");
91 return response;
92#else
93 if (!args.is_object() || !args.contains("activeHost") ||
94 !args["activeHost"].is_int()) {
95 response.set("success", false);
96 response.set("error", "ExpectedActiveHost");
97 return response;
98 }
99 const int active_host = static_cast<int>(args["activeHost"].as_int().value());
100 if (active_host != 2 && active_host != 3) {
101 response.set("success", false);
102 response.set("error", "InvalidActiveHost");
103 return response;
104 }
105 const bool dummy_first = args.contains("dummyFirst") &&
106 args["dummyFirst"].is_bool() && args["dummyFirst"].as_bool().value();
107 constexpr int kLedCount = 100;
108 static bool already_run = false;
109 if (already_run) {
110 response.set("success", false);
111 response.set("error", "ResetBoardBeforeNextOrder");
112 return response;
113 }
114 already_run = true;
115 static CRGB active_leds[kLedCount];
116 static CRGB dummy_leds[kLedCount];
117 for (int i = 0; i < kLedCount; ++i) {
118 active_leds[i] = CRGB(static_cast<uint8_t>(i * 19 + 1),
119 static_cast<uint8_t>(i * 13 + 2),
120 static_cast<uint8_t>(i * 7 + 3));
121 dummy_leds[i] = CRGB::Black;
122 }
123 const auto register_active = [&]() {
124 FastLED.addLeds<APA102, 1, 3, RGB, DATA_RATE_KHZ(2400)>( // ok platform specific - test the public explicit ESP32 host API
125 active_leds, kLedCount, active_host == 2
126 ? fl::Esp32SpiBus::HOST2 : fl::Esp32SpiBus::HOST3); // ok platform specific - select the measured physical host
127 };
128 const auto register_dummy = [&]() {
129 FastLED.addLeds<APA102, 4, 5, RGB, DATA_RATE_KHZ(2400)>( // ok platform specific - register the other host as the dummy
130 dummy_leds, kLedCount, active_host == 2
131 ? fl::Esp32SpiBus::HOST3 : fl::Esp32SpiBus::HOST2); // ok platform specific - choose the host opposite the measured output
132 };
133 if (dummy_first) {
134 register_dummy();
135 register_active();
136 } else {
137 register_active();
138 register_dummy();
139 }
141 FastLED.show();
142 const bool primed = FastLED.wait(1000);
143 size_t captured = 0;
144 fl::RunResult diagnostics;
145 if (primed) {
146 captured = capture(mState->rx_channel, mState->rx_buffer,
147 kLedCount * 3, 1,
149 "SPI_UNIFIED", &diagnostics);
150 }
151 response.set("activeHost", active_host);
152 response.set("dummyFirst", dummy_first);
153 response.set("genericDecodedBytes", static_cast<int>(captured));
154 response.set("primed", primed);
155 response.set("rxWaitResult", diagnostics.captureWaitResult);
156 response.set("rxEdges", diagnostics.rawEdgesAfterWait);
157 response.set("rxBeginError", diagnostics.rxBeginError.c_str());
158 fl::EdgeTime edge_samples[256];
159 const size_t edge_count = mState->rx_channel->getRawEdgeTimes(
160 fl::span<fl::EdgeTime>(edge_samples, 256), 0);
161 response.set("rawEdges", static_cast<int>(edge_count));
162 constexpr size_t kVerifiedLeds = 8;
163 uint8_t first_bytes[kVerifiedLeds * 4] = {};
164 size_t bit_count = 0;
165 for (size_t i = 0; i < edge_count && bit_count < sizeof(first_bytes) * 8; ++i) {
166 const size_t bits = (edge_samples[i].ns + 208) / 416;
167 for (size_t j = 0; j < bits && bit_count < sizeof(first_bytes) * 8; ++j, ++bit_count) {
168 if (edge_samples[i].high) {
169 first_bytes[bit_count / 8] |=
170 static_cast<uint8_t>(1u << (7 - bit_count % 8));
171 }
172 }
173 }
174 int wire_mismatches = 0;
175 if (bit_count < sizeof(first_bytes) * 8) {
176 wire_mismatches = static_cast<int>(sizeof(first_bytes) * 8 - bit_count);
177 } else {
178 for (size_t i = 0; i < kVerifiedLeds; ++i) {
179 const size_t offset = i * 4;
180 wire_mismatches += first_bytes[offset] != 0xFF;
181 wire_mismatches += first_bytes[offset + 1] != active_leds[i].r;
182 wire_mismatches += first_bytes[offset + 2] != active_leds[i].g;
183 wire_mismatches += first_bytes[offset + 3] != active_leds[i].b;
184 }
185 }
186 response.set("verifiedLeds", static_cast<int>(kVerifiedLeds));
187 response.set("wireBits", static_cast<int>(bit_count));
188 response.set("wireMismatches", wire_mismatches);
189 response.set("first0", first_bytes[0]);
190 response.set("first1", first_bytes[1]);
191 response.set("first2", first_bytes[2]);
192 response.set("first3", first_bytes[3]);
193 response.set("first4", first_bytes[4]);
194 response.set("first5", first_bytes[5]);
195 response.set("first6", first_bytes[6]);
196 response.set("first7", first_bytes[7]);
197 if (edge_count >= 4) {
198 response.set("edge0Ns", static_cast<int>(edge_samples[0].ns));
199 response.set("edge1Ns", static_cast<int>(edge_samples[1].ns));
200 response.set("edge2Ns", static_cast<int>(edge_samples[2].ns));
201 response.set("edge3Ns", static_cast<int>(edge_samples[3].ns));
202 response.set("edge0High", edge_samples[0].high);
203 response.set("edge1High", edge_samples[1].high);
204 }
206 fl::string::from_literal("SPI_UNIFIED"));
207 const bool native_driver_found = static_cast<bool>(driver);
208 const bool native_driver_enabled = driver &&
210 response.set("nativeDriverFound", native_driver_found);
211 response.set("nativeDriverEnabled", native_driver_enabled);
212 response.set("success", primed && native_driver_enabled && wire_mismatches == 0);
213 return response;
214#endif
215 });
216 // Register "flexioRxBenchmark" — square-wave validation for the new
217 // FlexIO RX backend (Phase 2 of FastLED#2764).
218 //
219 // Drives `tx_pin` with a `frequency_hz` square wave via the Teensy core's
220 // `analogWriteFrequency` PWM, configures an RxChannel on `rx_pin` using
221 // `RxBackend::FLEXIO`, captures for `duration_ms`, and reports per-period
222 // statistics: count, mean ns, σ ns, min ns, max ns.
223 //
224 // Args (positional, all optional with defaults):
225 // {
226 // "frequency_hz": int = 1000,
227 // "duration_ms": int = 100,
228 // "tx_pin": int = 3, // matches GPIO 3↔4 jumper
229 // "rx_pin": int = 4
230 // }
231 //
232 // Teensy-4-only because FLEXIO1 is iMXRT1062-specific. Other platforms
233 // get a clean "not supported" response so the RPC harness can still
234 // round-trip.
235 remote.bind("flexioRxBenchmark", [](const fl::json& args) -> fl::json {
236 fl::json response = fl::json::object();
237#if !defined(FL_IS_TEENSY_4X)
238 (void)args;
239 response.set("success", false);
240 response.set("error", "PlatformNotSupported");
241 response.set("message",
242 "flexioRxBenchmark is Teensy 4.x-only (FLEXIO1 capture).");
243 return response;
244#else
245 // Parse args
246 int frequency_hz = 1000;
247 int duration_ms = 100;
248 int tx_pin = 3;
249 int rx_pin = 4;
250 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
251 fl::json cfg = args[0];
252 if (cfg.contains("frequency_hz") && cfg["frequency_hz"].is_int()) {
253 frequency_hz = static_cast<int>(cfg["frequency_hz"].as_int().value());
254 }
255 if (cfg.contains("duration_ms") && cfg["duration_ms"].is_int()) {
256 duration_ms = static_cast<int>(cfg["duration_ms"].as_int().value());
257 }
258 if (cfg.contains("tx_pin") && cfg["tx_pin"].is_int()) {
259 tx_pin = static_cast<int>(cfg["tx_pin"].as_int().value());
260 }
261 if (cfg.contains("rx_pin") && cfg["rx_pin"].is_int()) {
262 rx_pin = static_cast<int>(cfg["rx_pin"].as_int().value());
263 }
264 }
265 if (frequency_hz < 1 || frequency_hz > 5000000 ||
266 duration_ms < 1 || duration_ms > 5000) {
267 response.set("success", false);
268 response.set("error", "InvalidArgs");
269 response.set("message",
270 "frequency_hz in [1, 5_000_000]; duration_ms in [1, 5000].");
271 return response;
272 }
273
274 // 1. Drive the TX pin with a square wave.
275 analogWriteFrequency(tx_pin, (float)frequency_hz);
276 analogWrite(tx_pin, 128); // 50% duty
277
278 // 2. Create the FlexIO RX channel.
279 // Width budget — size the edge buffer for the actual capture window:
280 // expected_edges = 2 * frequency_hz * duration_ms / 1000 (transitions)
281 // target = expected_edges * 1.5 (50% headroom for jitter/skew)
282 // Clamped to [1024, 16384] so the DMA buffer stays reasonable and
283 // matches the FlexIO RX driver's internal cap.
284 fl::RxChannelConfig rx_cfg(rx_pin, fl::RxBackend::FLEXIO);
285 const fl::u64 expected_edges =
286 (fl::u64)2 * (fl::u64)frequency_hz * (fl::u64)duration_ms / 1000ULL;
287 fl::u64 target = expected_edges + expected_edges / 2; // +50%
288 if (target < 1024ULL) target = 1024ULL;
289 if (target > 16384ULL) target = 16384ULL;
290 rx_cfg.edge_capacity = (size_t)target;
291 rx_cfg.start_low = false; // PWM output idles in either state, just track transitions
292 auto rx_channel = fl::RxChannel::create(rx_cfg);
293 if (!rx_channel) {
294 analogWrite(tx_pin, 0);
295 response.set("success", false);
296 response.set("error", "RxCreateFailed");
297 response.set("message",
298 "Failed to create FlexIO RX channel on pin (no FLEXIO1 mapping).");
299 return response;
300 }
301 if (!rx_channel->begin(rx_cfg)) {
302 analogWrite(tx_pin, 0);
303 response.set("success", false);
304 response.set("error", "RxBeginFailed");
305 response.set("message",
306 "RxChannel::begin() returned false (see device WARN log).");
307 return response;
308 }
309
310 // 3. Let it capture for the requested window.
311 rx_channel->wait((u32)duration_ms);
312
313 // 4. Stop the square wave so the line is quiet for stats computation.
314 analogWrite(tx_pin, 0);
315
316 // 5. Pull captured edges out.
317 fl::vector<fl::EdgeTime> edges;
318 edges.assign(rx_cfg.edge_capacity, fl::EdgeTime());
319 size_t edges_captured =
320 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(edges), 0);
321 edges.resize(edges_captured);
322
323 // 6. Compute period statistics. A "period" = duration_high + duration_low
324 // for adjacent edges, so we iterate in pairs.
325 fl::u64 sum_ns = 0;
326 fl::u64 sum_sq_ns = 0;
327 fl::u32 min_ns = 0xFFFFFFFFu;
328 fl::u32 max_ns = 0;
329 size_t periods = 0;
330 for (size_t i = 0; i + 1 < edges_captured; i += 2) {
331 const fl::u32 period_ns =
332 (fl::u32)edges[i].ns + (fl::u32)edges[i + 1].ns;
333 if (period_ns == 0) continue;
334 sum_ns += period_ns;
335 sum_sq_ns += (fl::u64)period_ns * (fl::u64)period_ns;
336 if (period_ns < min_ns) min_ns = period_ns;
337 if (period_ns > max_ns) max_ns = period_ns;
338 ++periods;
339 }
340 fl::u32 mean_ns = 0;
341 fl::u32 sigma_ns = 0;
342 if (periods > 0) {
343 mean_ns = (fl::u32)(sum_ns / (fl::u64)periods);
344 const fl::u64 mean64 = (fl::u64)mean_ns;
345 const fl::u64 var =
346 periods > 0 ? (sum_sq_ns / (fl::u64)periods) - (mean64 * mean64)
347 : 0;
348 // Integer sqrt for σ — adequate for the tolerance ranges we
349 // assert in Phase 2 (σ < 100 ns at 100 kHz).
350 fl::u32 s = 0;
351 while ((fl::u64)(s + 1) * (fl::u64)(s + 1) <= var) ++s;
352 sigma_ns = s;
353 }
354 if (min_ns == 0xFFFFFFFFu) min_ns = 0;
355
356 response.set("success", true);
357 response.set("frequency_hz", static_cast<int64_t>(frequency_hz));
358 response.set("duration_ms", static_cast<int64_t>(duration_ms));
359 response.set("tx_pin", static_cast<int64_t>(tx_pin));
360 response.set("rx_pin", static_cast<int64_t>(rx_pin));
361 response.set("edges_captured", static_cast<int64_t>(edges_captured));
362 response.set("periods", static_cast<int64_t>(periods));
363 response.set("period_mean_ns", static_cast<int64_t>(mean_ns));
364 response.set("period_sigma_ns", static_cast<int64_t>(sigma_ns));
365 response.set("period_min_ns", static_cast<int64_t>(min_ns));
366 response.set("period_max_ns", static_cast<int64_t>(max_ns));
367 return response;
368#endif
369 });
370
371 // Register "flexioObjectFledTest" — end-to-end ObjectFLED TX → FlexIO RX
372 // loopback verification (Phase 3 of FastLED#2764).
373 //
374 // Drives a small WS2812 pattern through `Bus::FLEX_IO` slot 0 on `tx_pin`,
375 // captures the wire signal back through the new `RxBackend::FLEXIO` on
376 // `rx_pin`, decodes the bit stream against WS2812B-V5 timing, and reports
377 // how many bytes matched the transmitted pattern.
378 //
379 // Test cases (parent issue Phase 3 table):
380 // 0 — Red single LED (1 LED, 0xFF0000 → wire-order GRB 00,FF,00)
381 // 1 — RGB three-LED chain (3 LEDs)
382 // 2 — All zeros (1 LED, 0x000000 — T0H/T0L fidelity)
383 // 3 — All ones (1 LED, 0xFFFFFF — T1H/T1L fidelity)
384 // 4 — 100-LED alternating R/G/B (long capture, watchdog-safety)
385 //
386 // Args (positional, all optional):
387 // { "test_case": int = 0,
388 // "tx_pin": int = 3, // matches GPIO 3↔4 jumper
389 // "rx_pin": int = 4,
390 // "capture_ms": int = 50 }
391 //
392 // Returns:
393 // { success, test_case, num_leds, expected_bytes, decoded_bytes,
394 // matched, mismatched, edges_captured }
395 //
396 // Teensy-4-only — FLEXIO1 is iMXRT1062-specific. ObjectFLED also relies
397 // on Teensy 4-core APIs, so non-Teensy builds return `PlatformNotSupported`.
398 remote.bind("flexioObjectFledTest", [](const fl::json& args) -> fl::json {
399 fl::json response = fl::json::object();
400#if !defined(FL_IS_TEENSY_4X)
401 (void)args;
402 response.set("success", false);
403 response.set("error", "PlatformNotSupported");
404 response.set("message",
405 "flexioObjectFledTest is Teensy 4.x-only (FLEXIO1 RX + "
406 "Teensy-core ObjectFLED driver).");
407 return response;
408#else
409 // Parse args
410 int test_case = 0;
411 int tx_pin = 3;
412 int rx_pin = 4;
413 int capture_ms = 50;
414 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
415 fl::json cfg = args[0];
416 if (cfg.contains("test_case") && cfg["test_case"].is_int()) {
417 test_case = static_cast<int>(cfg["test_case"].as_int().value());
418 }
419 if (cfg.contains("tx_pin") && cfg["tx_pin"].is_int()) {
420 tx_pin = static_cast<int>(cfg["tx_pin"].as_int().value());
421 }
422 if (cfg.contains("rx_pin") && cfg["rx_pin"].is_int()) {
423 rx_pin = static_cast<int>(cfg["rx_pin"].as_int().value());
424 }
425 if (cfg.contains("capture_ms") && cfg["capture_ms"].is_int()) {
426 capture_ms = static_cast<int>(cfg["capture_ms"].as_int().value());
427 }
428 }
429 if (test_case < 0 || test_case > 4 ||
430 capture_ms < 1 || capture_ms > 5000) {
431 response.set("success", false);
432 response.set("error", "InvalidArgs");
433 response.set("message",
434 "test_case in [0,4]; capture_ms in [1, 5000].");
435 return response;
436 }
437
438 // 1. Build the test pattern. Fixed upper bound (100 LEDs) covers
439 // case 4 — the larger cases reuse the same static buffer.
440 CRGB* leds_buf = flexIoObjectFledState().leds;
441 int num_leds = 0;
442 switch (test_case) {
443 case 0:
444 num_leds = 1;
445 leds_buf[0] = CRGB::Red;
446 break;
447 case 1:
448 num_leds = 3;
449 leds_buf[0] = CRGB::Red;
450 leds_buf[1] = CRGB::Green;
451 leds_buf[2] = CRGB::Blue;
452 break;
453 case 2:
454 num_leds = 1;
455 leds_buf[0] = CRGB::Black;
456 break;
457 case 3:
458 num_leds = 1;
459 leds_buf[0] = CRGB(0xFF, 0xFF, 0xFF);
460 break;
461 case 4:
462 num_leds = 100;
463 for (int i = 0; i < 100; ++i) {
464 leds_buf[i] = (i % 3 == 0) ? CRGB::Red
465 : (i % 3 == 1) ? CRGB::Green
466 : CRGB::Blue;
467 }
468 break;
469 }
470
471 // 2. Build the expected wire-order byte stream (WS2812 is GRB).
472 fl::vector<u8> expected;
473 expected.reserve((size_t)num_leds * 3);
474 for (int i = 0; i < num_leds; ++i) {
475 expected.push_back(leds_buf[i].g);
476 expected.push_back(leds_buf[i].r);
477 expected.push_back(leds_buf[i].b);
478 }
479
480 // 3. Set up FlexIO RX. Size the edge buffer for 24 bits per LED ×
481 // 2 transitions per bit, with 25% headroom.
482 const size_t expected_edges = (size_t)num_leds * 24u * 2u;
483 size_t edge_capacity = expected_edges + expected_edges / 4u + 64u;
484 if (edge_capacity > 16384u) edge_capacity = 16384u;
485
486 fl::RxChannelConfig rx_cfg(rx_pin, fl::RxBackend::FLEXIO);
487 rx_cfg.edge_capacity = edge_capacity;
488 rx_cfg.start_low = true;
489 auto rx_channel = fl::RxChannel::create(rx_cfg);
490 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
491 response.set("success", false);
492 response.set("error", "RxBeginFailed");
493 response.set("message",
494 "Failed to bring up FlexIO RX on the requested pin "
495 "(check kFlexIo1Pins[] mapping).");
496 return response;
497 }
498
499 // 4. Configure ObjectFLED TX via FastLED.add().
500 fl::ChannelOptions opts;
501 opts.mBus = fl::Bus::FLEX_IO;
502 opts.mBusWhich = 0;
503 auto resolved_timing = fl::makeTimingConfig<fl::TIMING_WS2812B_V5>();
504 auto resolved_encoder = fl::encoder_for<fl::TIMING_WS2812B_V5>();
505 fl::NamedTimingConfig timing_cfg(resolved_timing, "WS2812B-V5",
506 resolved_encoder);
507 fl::ChannelConfig tx_cfg(
508 tx_pin,
509 timing_cfg.timing,
510 fl::span<CRGB>(leds_buf, (size_t)num_leds),
511 RGB,
512 opts);
513 auto tx_channel = FastLED.add(tx_cfg);
514 if (!tx_channel) {
515 response.set("success", false);
516 response.set("error", "TxAddFailed");
517 response.set("message",
518 "FastLED.add() rejected the ObjectFLED ChannelConfig.");
519 return response;
520 }
521
522 // 5. Trigger TX. FastLED.show() schedules the DMA and returns once
523 // the frame has been queued. The RX wait must be long enough to
524 // cover BOTH the TX transmission time AND the capture-buffer
525 // fill — otherwise teardown happens mid-frame and the hardware
526 // can be left in a bad state. Compute a minimum from the actual
527 // WS2812 timing (1.25 µs per bit, 24 bits per LED, plus reset
528 // gap) and use the larger of the caller's `capture_ms` and that
529 // minimum.
530 FastLED.show();
531 const u32 ws2812_tx_us =
532 (u32)num_leds * 24u * 13u / 10u + 100u; // 1.25 µs/bit + reset
533 const u32 min_capture_ms = (ws2812_tx_us + 999u) / 1000u + 10u;
534 const u32 effective_capture_ms = ((u32)capture_ms > min_capture_ms)
535 ? (u32)capture_ms
536 : min_capture_ms;
537 rx_channel->wait(effective_capture_ms);
538
539 // 6. Decode the captured edge stream against WS2812 4-phase timing.
540 const fl::ChipsetTiming ws2812_timing =
542 fl::ChipsetTiming4Phase rx_timing =
543 fl::make4PhaseTiming(ws2812_timing);
544 fl::vector<u8> decoded;
545 decoded.assign(expected.size() + 16u, 0u);
546 auto decode_result =
547 rx_channel->decode(rx_timing, fl::span<u8>(decoded));
548
549 // 7. Compare decoded bytes against expected.
550 u32 decoded_bytes = 0u;
551 if (decode_result) {
552 decoded_bytes = decode_result.value();
553 }
554 const size_t cmp_n = (decoded_bytes < expected.size())
555 ? (size_t)decoded_bytes
556 : expected.size();
557 size_t matched = 0;
558 size_t mismatched = 0;
559 for (size_t i = 0; i < cmp_n; ++i) {
560 if (decoded[i] == expected[i]) ++matched; else ++mismatched;
561 }
562 if (decoded_bytes < expected.size()) {
563 mismatched += expected.size() - decoded_bytes;
564 }
565
566 // 8. Read raw edge count for diagnostics.
567 fl::vector<fl::EdgeTime> edges;
568 edges.assign(edge_capacity, fl::EdgeTime());
569 const size_t edges_captured =
570 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(edges), 0);
571
572 // 9. Tear the TX channel back down so subsequent tests can use the
573 // same pin (FastLED.clear(ClearFlags::CHANNELS) matches the
574 // pattern used by runSingleTestImpl in this file).
576
577 response.set("success", mismatched == 0 && decoded_bytes == expected.size());
578 response.set("test_case", static_cast<int64_t>(test_case));
579 response.set("tx_pin", static_cast<int64_t>(tx_pin));
580 response.set("rx_pin", static_cast<int64_t>(rx_pin));
581 response.set("num_leds", static_cast<int64_t>(num_leds));
582 response.set("expected_bytes", static_cast<int64_t>(expected.size()));
583 response.set("decoded_bytes", static_cast<int64_t>(decoded_bytes));
584 response.set("matched", static_cast<int64_t>(matched));
585 response.set("mismatched", static_cast<int64_t>(mismatched));
586 response.set("edges_captured", static_cast<int64_t>(edges_captured));
587 return response;
588#endif
589 });
590
591 // Register "flexioRxLoopbackPing" — FastLED#3066 phase 1.7 diagnostic.
592 //
593 // Bypasses every clockless driver entirely and tests whether the
594 // FlexIO RX backend can capture HAND-DRIVEN GPIO transitions. Drives
595 // `tx_pin` as a plain `digitalWrite` HIGH/LOW square wave while
596 // `RxBackend::FLEXIO` is armed on `rx_pin`. If the captured buffer
597 // shows any non-zero data, IOMUX ALT4 + PINSEL routing is correct
598 // and the WS2812-loopback failure mode is downstream (timer/shifter
599 // bandwidth, decoder thresholds, etc.). If the buffer stays all
600 // zero with a 4-edge ground-truth pin toggle, the routing itself is
601 // broken — debug that BEFORE more timer/shifter config experiments.
602 //
603 // Wiring: same TX↔RX jumper as flexioObjectFledTest (defaults 3↔4).
604 //
605 // Args:
606 // { tx_pin?: 3, // any digital pin
607 // rx_pin?: 4, // must be in kFlexIo1Pins[]
608 // toggle_us?: 50, // half-period of the manual square wave
609 // edges?: 8 } // number of digitalWrite transitions
610 // Returns:
611 // { success, tx_pin, rx_pin, toggle_us, edges, edges_captured,
612 // capture_buffer_first8_hex: [...], notes }
613 //
614 // Teensy-4-only.
615 remote.bind("flexioRxLoopbackPing", [](const fl::json& args) -> fl::json {
616 fl::json response = fl::json::object();
617#if !defined(FL_IS_TEENSY_4X)
618 (void)args;
619 response.set("success", false);
620 response.set("error", "PlatformNotSupported");
621 response.set("message",
622 "flexioRxLoopbackPing is Teensy 4.x-only (FLEXIO1 RX).");
623 return response;
624#else
625 int tx_pin = 3;
626 int rx_pin = 4;
627 int toggle_us = 50;
628 int edges = 8;
629 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
630 const fl::json &cfg = args[0];
631 if (cfg.contains("tx_pin") && cfg["tx_pin"].is_int()) {
632 tx_pin = static_cast<int>(cfg["tx_pin"].as_int().value());
633 }
634 if (cfg.contains("rx_pin") && cfg["rx_pin"].is_int()) {
635 rx_pin = static_cast<int>(cfg["rx_pin"].as_int().value());
636 }
637 if (cfg.contains("toggle_us") && cfg["toggle_us"].is_int()) {
638 toggle_us = static_cast<int>(cfg["toggle_us"].as_int().value());
639 }
640 if (cfg.contains("edges") && cfg["edges"].is_int()) {
641 edges = static_cast<int>(cfg["edges"].as_int().value());
642 }
643 }
644 if (edges < 2 || edges > 64) {
645 response.set("success", false);
646 response.set("error", "InvalidEdges");
647 response.set("message", "edges must be in [2, 64]");
648 return response;
649 }
650
651 // 1. Arm FlexIO RX on rx_pin.
652 fl::RxChannelConfig rx_cfg(rx_pin, fl::RxBackend::FLEXIO);
653 rx_cfg.edge_capacity = 1024; // plenty for ~10 edges
654 rx_cfg.start_low = true;
655 auto rx_channel = fl::RxChannel::create(rx_cfg);
656 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
657 response.set("success", false);
658 response.set("error", "RxBeginFailed");
659 response.set("message",
660 "FlexIO RX begin() failed (kFlexIo1Pins[] mapping?).");
661 return response;
662 }
663
664 // 2. Drive tx_pin as a plain GPIO output, toggle a known number of
665 // transitions. Start LOW so the first digitalWrite(HIGH) is a
666 // rising edge.
667 pinMode(tx_pin, OUTPUT);
668 digitalWrite(tx_pin, LOW);
669 delayMicroseconds(100); // let the line settle + RX arm fully
670
671 bool level = true;
672 for (int i = 0; i < edges; ++i) {
673 digitalWrite(tx_pin, level ? HIGH : LOW);
674 delayMicroseconds(toggle_us);
675 level = !level;
676 }
677
678 // FastLED#3066 iter 7 diagnostic: also probe SHIFTBUF[0] +
679 // SHIFTBUFBIS (bit-swapped) + SHIFTBUFBYS (byte-swapped) so we
680 // can see whether the captured bit lands in a bit position
681 // different from what the canonical SHIFTBUF[0] read produces.
682 {
683 constexpr uintptr_t kFLEXIO1_BASE_DIAG = 0x401AC000u;
684 volatile uint32_t *pin = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x00C);
685 volatile uint32_t *shftstat = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x010);
686 volatile uint32_t *timstat = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x018);
687 volatile uint32_t *shftbuf = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x200);
688 volatile uint32_t *shftbufbis = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x280);
689 volatile uint32_t *shftbufbys = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x300);
690 fl::json flexio_diag = fl::json::object();
691 flexio_diag.set("pin", static_cast<int64_t>(*pin));
692 flexio_diag.set("shiftstat", static_cast<int64_t>(*shftstat));
693 flexio_diag.set("timstat", static_cast<int64_t>(*timstat));
694 flexio_diag.set("shiftbuf0", static_cast<int64_t>(*shftbuf));
695 flexio_diag.set("shiftbufbis0", static_cast<int64_t>(*shftbufbis));
696 flexio_diag.set("shiftbufbys0", static_cast<int64_t>(*shftbufbys));
697 response.set("flexio1", flexio_diag);
698 }
699
700 // 3. Wait briefly for FlexIO RX to flush any pending DMA. The DMA
701 // is configured for completion-on-buffer-full; a partial fill
702 // won't trigger the ISR, so `wait()` will TIMEOUT — which is
703 // fine, we read the captured edges directly.
704 rx_channel->wait(50);
705
706 // 4. Read the captured edges via the public API.
707 fl::vector<fl::EdgeTime> captured_edges;
708 captured_edges.assign(64, fl::EdgeTime());
709 size_t edge_count =
710 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(captured_edges), 0);
711
712 // 5. Restore tx_pin so subsequent tests can use it.
713 pinMode(tx_pin, INPUT);
714
715 response.set("success", true);
716 response.set("tx_pin", static_cast<int64_t>(tx_pin));
717 response.set("rx_pin", static_cast<int64_t>(rx_pin));
718 response.set("toggle_us", static_cast<int64_t>(toggle_us));
719 response.set("edges", static_cast<int64_t>(edges));
720 response.set("edges_captured", static_cast<int64_t>(edge_count));
721
722 // Dump the first 8 captured edge durations (ns) so the host can
723 // see whether they roughly match the requested toggle_us *
724 // 1000 ns expectation.
725 fl::json edges_arr = fl::json::array();
726 const size_t to_dump = (edge_count < 8u) ? edge_count : 8u;
727 for (size_t i = 0; i < to_dump; ++i) {
728 fl::json e = fl::json::object();
729 e.set("high", captured_edges[i].high);
730 e.set("ns", static_cast<int64_t>(captured_edges[i].ns));
731 edges_arr.push_back(e);
732 }
733 response.set("first_edges", edges_arr);
734 return response;
735#endif
736 });
737
738 remote.bind("legacyAsyncProbe", [](const fl::json& args) -> fl::json {
740 });
741 remote.bind("legacyAsyncErrorProbe", [](const fl::json& args) -> fl::json {
743 });
744
745 // Register "timingDriftTest" — issue #2994 reproducer for compounded
746 // per-sequence timing drift on master vs 3.10.3. Replays the reporter's
747 // minimal sketch (35-LED WS2812B ring, setMaxRefreshRate(800),
748 // millisDelay-gated 255-step fade, delay(1000) between sequences) and
749 // returns per-sequence wall time so the host can build a histogram.
750 // Args: [{pin, numLeds, iterations}] (all optional;
751 // default pin=4, numLeds=35, iterations=10; iterations clamped to
752 // autoresearch::timing_drift::kMaxIterations)
753 remote.bind("timingDriftTest", [](const fl::json& args) -> fl::json {
754 fl::json response = fl::json::object();
755
756 int pin = 4;
757 int num_leds = 35;
758 int iterations = 10;
759
760 fl::json config;
761 if (args.is_object()) {
762 config = args;
763 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
764 config = args[0];
765 }
766 if (!config.is_null()) {
767 if (config.contains("pin") && config["pin"].is_int()) {
768 pin = static_cast<int>(config["pin"].as_int().value());
769 }
770 if (config.contains("numLeds") && config["numLeds"].is_int()) {
771 num_leds = static_cast<int>(config["numLeds"].as_int().value());
772 }
773 if (config.contains("iterations") && config["iterations"].is_int()) {
774 iterations = static_cast<int>(config["iterations"].as_int().value());
775 }
776 }
777
778 auto r = autoresearch::timing_drift::run(pin, num_leds, iterations);
779
780 response.set("success", r.valid_pin);
781 if (!r.valid_pin) {
782 response.set("error", "InvalidPin");
783 response.set("message",
784 "pin is outside LegacyClocklessProxy range");
785 response.set("pin", r.pin);
786 return response;
787 }
788 response.set("pin", r.pin);
789 response.set("num_leds", r.num_leds);
790 response.set("iterations", r.iterations);
791 response.set("cpu_mhz", static_cast<int64_t>(r.cpu_mhz));
792 response.set("show_count", static_cast<int64_t>(r.show_count));
793 response.set("show_min_us", static_cast<int64_t>(r.show_min_us));
794 response.set("show_max_us", static_cast<int64_t>(r.show_max_us));
795 response.set("show_total_us", static_cast<int64_t>(r.show_total_us));
796
797 fl::json iter_ms_arr = fl::json::array();
798 for (int i = 0; i < r.iterations; ++i) {
799 iter_ms_arr.push_back(static_cast<int64_t>(r.iter_ms[i]));
800 }
801 response.set("iter_ms", iter_ms_arr);
802 return response;
803 });
804
805 // Register "flexioSpiSelfTest" — #3428 bring-up smoke test for the
806 // FlexIO2 SPI master mode. Exercises the full
807 // lookup → init → show → wait → deinit path on a user-specified
808 // (MOSI, SCLK) pin pair with a known byte pattern, then reports back
809 // success/failure of each stage plus the resolved FlexIO2 pin indices
810 // so a host-side test can diagnose routing and bring-up failures.
811 //
812 // This test does NOT need a logic analyzer or a connected APA102
813 // strip — it confirms the driver runs without crashing the firmware,
814 // pins route, init programs the shifter/timer, and DMA completes
815 // within the bounded timeout. Full byte-level verification needs a
816 // scope (see TODO markers in flexio_spi_mode.cpp.hpp).
817 //
818 // Args (positional, all optional):
819 // { "mosi_pin": int = 10, // FlexIO2 pin 0 (default APA102 MOSI)
820 // "sclk_pin": int = 12, // FlexIO2 pin 1 (default APA102 SCLK)
821 // "clock_hz": int = 1000000, // 1 MHz default for safety
822 // "num_bytes": int = 4 } // sent bytes 0xA5 0x5A 0xFF 0x00
823 //
824 // Returns:
825 // { success, mosi_pin, sclk_pin, clock_hz, num_bytes,
826 // lookup_ok, init_ok, show_ok, wait_ms,
827 // mosi_flexio_pin, sclk_flexio_pin }
828 //
829 // Teensy 4.x-only.
830 remote.bind("flexioSpiSelfTest", [](const fl::json& args) -> fl::json {
831 fl::json response = fl::json::object();
832#if !defined(FL_IS_TEENSY_4X)
833 (void)args;
834 response.set("success", false);
835 response.set("error", "PlatformNotSupported");
836 response.set("message",
837 "flexioSpiSelfTest is Teensy 4.x-only (FlexIO2 SPI master mode).");
838 return response;
839#else
840 // Parse args with defaults that work out-of-the-box on Teensy 4.x.
841 // Note: the RPC framework signature `fl::json(const fl::json&)` makes
842 // the framework strip the outer params array and pass `params[0]`
843 // directly as `args`. So `args` is an OBJECT here, not an array --
844 // some existing bindings in this file have the wrong `args.is_array()`
845 // check (pre-existing bug, fix tracked separately) and silently use
846 // their defaults always.
847 int mosi_pin = 10; // FlexIO2 pin 0 (teensy pin 10)
848 int sclk_pin = 12; // FlexIO2 pin 1 (teensy pin 12)
849 int clock_hz = 1000000; // 1 MHz -- safe for bring-up
850 int num_bytes = 4;
851 if (args.is_object()) {
852 const fl::json& cfg = args;
853 if (cfg.contains("mosi_pin") && cfg["mosi_pin"].is_int()) {
854 mosi_pin = static_cast<int>(cfg["mosi_pin"].as_int().value());
855 }
856 if (cfg.contains("sclk_pin") && cfg["sclk_pin"].is_int()) {
857 sclk_pin = static_cast<int>(cfg["sclk_pin"].as_int().value());
858 }
859 if (cfg.contains("clock_hz") && cfg["clock_hz"].is_int()) {
860 clock_hz = static_cast<int>(cfg["clock_hz"].as_int().value());
861 }
862 if (cfg.contains("num_bytes") && cfg["num_bytes"].is_int()) {
863 num_bytes = static_cast<int>(cfg["num_bytes"].as_int().value());
864 }
865 }
866
867 response.set("mosi_pin", mosi_pin);
868 response.set("sclk_pin", sclk_pin);
869 response.set("clock_hz", static_cast<int64_t>(clock_hz));
870 response.set("num_bytes", num_bytes);
871
872 // Validate input ranges BEFORE narrowing to u8/u32. Without these
873 // guards, mosi_pin=270 wraps to pin 14 in static_cast<u8>(), and
874 // a negative clock_hz wraps to a huge u32 that the SPI driver
875 // would then clamp to the 25 MHz ceiling -- both silently produce
876 // wrong-hardware behavior. Per coderabbitai review on PR #3431.
877 if (num_bytes <= 0 || num_bytes > 64) {
878 response.set("success", false);
879 response.set("error", "InvalidArgs");
880 response.set("message", "num_bytes must be in [1, 64].");
881 return response;
882 }
883 if (mosi_pin < 0 || mosi_pin > 255 ||
884 sclk_pin < 0 || sclk_pin > 255) {
885 response.set("success", false);
886 response.set("error", "InvalidArgs");
887 response.set("message", "mosi_pin / sclk_pin must be in [0, 255].");
888 return response;
889 }
890 if (clock_hz <= 0) {
891 response.set("success", false);
892 response.set("error", "InvalidArgs");
893 response.set("message", "clock_hz must be > 0.");
894 return response;
895 }
896
897 // 1. Pin-pair lookup.
898 fl::FlexIOSPIPinInfo pin_info{};
899 bool lookup_ok = fl::flexio_spi_lookup_pins(
900 static_cast<fl::u8>(mosi_pin),
901 static_cast<fl::u8>(sclk_pin),
902 &pin_info);
903 response.set("lookup_ok", lookup_ok);
904 if (!lookup_ok) {
905 response.set("success", false);
906 response.set("error", "PinNotRoutable");
907 response.set("message",
908 "(MOSI, SCLK) pair is not FlexIO2-routable; both pins "
909 "must be in {6, 7, 8, 9, 10, 11, 12, 13, 32}.");
910 return response;
911 }
912 response.set("mosi_flexio_pin",
913 static_cast<int64_t>(pin_info.mosi_flexio_pin));
914 response.set("sclk_flexio_pin",
915 static_cast<int64_t>(pin_info.sclk_flexio_pin));
916
917 // 2. Init.
918 bool init_ok = fl::flexio_spi_init(pin_info,
919 static_cast<fl::u32>(clock_hz));
920 response.set("init_ok", init_ok);
921 if (!init_ok) {
922 response.set("success", false);
923 response.set("error", "InitFailed");
924 response.set("message", "flexio_spi_init returned false.");
925 return response;
926 }
927
928 // 3. Send a known pattern. 0xA5 = 1010 0101 — easy to read on a
929 // scope to verify MSB-first ordering (high bit shifts out first).
930 // Fill the rest with alternating 0xFF / 0x00 so the wire toggles.
931 fl::u8 buffer[64] = {0};
932 for (int i = 0; i < num_bytes; ++i) {
933 switch (i % 4) {
934 case 0: buffer[i] = 0xA5; break;
935 case 1: buffer[i] = 0x5A; break;
936 case 2: buffer[i] = 0xFF; break;
937 case 3: buffer[i] = 0x00; break;
938 }
939 }
940
941 const fl::u32 show_start_ms = millis();
942 bool show_ok = fl::flexio_spi_show(buffer,
943 static_cast<fl::u32>(num_bytes));
944 response.set("show_ok", show_ok);
945 if (!show_ok) {
946 fl::flexio_spi_deinit();
947 response.set("success", false);
948 response.set("error", "ShowFailed");
949 response.set("message", "flexio_spi_show returned false.");
950 return response;
951 }
952
953 // 4. Wait for completion.
954 fl::flexio_spi_wait();
955 const fl::u32 wait_ms = millis() - show_start_ms;
956 response.set("wait_ms", static_cast<int64_t>(wait_ms));
957
958 // 5. Tear down.
959 fl::flexio_spi_deinit();
960
961 response.set("success", true);
962 return response;
963#endif
964 });
965
966 // Register "objectfledSpiSelfTest" — #3428 bring-up smoke test for the
967 // ObjectFLED DMA-bit-banged SPI master mode. Exercises the full
968 // lookup → init → show → wait → deinit path on a user-specified
969 // (MOSI, SCLK) pin pair (both must be on GPIO6) with a known byte
970 // pattern, reports back per-stage success + echoed args.
971 //
972 // Args (positional, all optional):
973 // { "mosi_pin": int = 14, // GPIO6 bit 18 on T4.1
974 // "sclk_pin": int = 15, // GPIO6 bit 19 on T4.1
975 // "clock_hz": int = 1000000,
976 // "num_bytes": int = 4 }
977 //
978 // Returns:
979 // { success, mosi_pin, sclk_pin, clock_hz, num_bytes,
980 // lookup_ok, init_ok, show_ok, wait_ms,
981 // mosi_bit, sclk_bit, dma_* (10), tmr3_* (5), xbar1_ctrl1 }
982 //
983 // Teensy 4.x-only.
984 remote.bind("objectfledSpiSelfTest", [](const fl::json& args) -> fl::json {
985 fl::json response = fl::json::object();
986#if !defined(FL_IS_TEENSY_4X)
987 (void)args;
988 response.set("success", false);
989 response.set("error", "PlatformNotSupported");
990 response.set("message",
991 "objectfledSpiSelfTest is Teensy 4.x-only (FlexPWM2 + eDMA + GPIO6).");
992 return response;
993#else
994 // Parse args. The RPC framework strips the outer params array
995 // and passes params[0] directly as `args`, so `args` is an OBJECT
996 // here (NOT an array).
997 int mosi_pin = 14; // GPIO6 bit 18 on T4.1 (and T4.0)
998 int sclk_pin = 15; // GPIO6 bit 19
999 int clock_hz = 1000000; // 1 MHz -- safe for bring-up
1000 int num_bytes = 4;
1001 if (args.is_object()) {
1002 const fl::json& cfg = args;
1003 if (cfg.contains("mosi_pin") && cfg["mosi_pin"].is_int()) {
1004 mosi_pin = static_cast<int>(cfg["mosi_pin"].as_int().value());
1005 }
1006 if (cfg.contains("sclk_pin") && cfg["sclk_pin"].is_int()) {
1007 sclk_pin = static_cast<int>(cfg["sclk_pin"].as_int().value());
1008 }
1009 if (cfg.contains("clock_hz") && cfg["clock_hz"].is_int()) {
1010 clock_hz = static_cast<int>(cfg["clock_hz"].as_int().value());
1011 }
1012 if (cfg.contains("num_bytes") && cfg["num_bytes"].is_int()) {
1013 num_bytes = static_cast<int>(cfg["num_bytes"].as_int().value());
1014 }
1015 }
1016
1017 response.set("mosi_pin", mosi_pin);
1018 response.set("sclk_pin", sclk_pin);
1019 response.set("clock_hz", static_cast<int64_t>(clock_hz));
1020 response.set("num_bytes", num_bytes);
1021
1022 // Validate input ranges BEFORE narrowing to u8/u32.
1023 if (num_bytes <= 0 || num_bytes > 64) {
1024 response.set("success", false);
1025 response.set("error", "InvalidArgs");
1026 response.set("message", "num_bytes must be in [1, 64].");
1027 return response;
1028 }
1029 if (mosi_pin < 0 || mosi_pin > 255 ||
1030 sclk_pin < 0 || sclk_pin > 255) {
1031 response.set("success", false);
1032 response.set("error", "InvalidArgs");
1033 response.set("message", "mosi_pin / sclk_pin must be in [0, 255].");
1034 return response;
1035 }
1036 if (clock_hz <= 0) {
1037 response.set("success", false);
1038 response.set("error", "InvalidArgs");
1039 response.set("message", "clock_hz must be > 0.");
1040 return response;
1041 }
1042
1043 // 1. Pin-pair lookup.
1044 fl::ObjectFLEDSPIPinInfo pin_info{};
1045 bool lookup_ok = fl::objectfled_spi_lookup_pins(
1046 static_cast<fl::u8>(mosi_pin),
1047 static_cast<fl::u8>(sclk_pin),
1048 &pin_info);
1049 response.set("lookup_ok", lookup_ok);
1050 if (!lookup_ok) {
1051 response.set("success", false);
1052 response.set("error", "PinNotRoutable");
1053 response.set("message",
1054 "(MOSI, SCLK) pair is not on GPIO6 or otherwise invalid; "
1055 "both pins must be GPIO6-resident (T4.x: 0, 1, 14-27, "
1056 "T4.1 adds 38-41).");
1057 return response;
1058 }
1059 response.set("mosi_bit", static_cast<int64_t>(pin_info.mosi_bit));
1060 response.set("sclk_bit", static_cast<int64_t>(pin_info.sclk_bit));
1061
1062 // 2. Init.
1063 bool init_ok = fl::objectfled_spi_init(pin_info,
1064 static_cast<fl::u32>(clock_hz));
1065 response.set("init_ok", init_ok);
1066 if (!init_ok) {
1067 response.set("success", false);
1068 response.set("error", "InitFailed");
1069 response.set("message", "objectfled_spi_init returned false.");
1070 return response;
1071 }
1072
1073 // 3. Send a known pattern.
1074 fl::u8 buffer[64] = {0};
1075 for (int i = 0; i < num_bytes; ++i) {
1076 switch (i % 4) {
1077 case 0: buffer[i] = 0xA5; break;
1078 case 1: buffer[i] = 0x5A; break;
1079 case 2: buffer[i] = 0xFF; break;
1080 case 3: buffer[i] = 0x00; break;
1081 }
1082 }
1083
1084 const fl::u32 show_start_ms = millis();
1085 bool show_ok = fl::objectfled_spi_show(
1086 fl::span<const fl::u8>(buffer,
1087 static_cast<fl::size>(num_bytes)));
1088 response.set("show_ok", show_ok);
1089 if (!show_ok) {
1090 fl::objectfled_spi_deinit();
1091 response.set("success", false);
1092 response.set("error", "ShowFailed");
1093 response.set("message", "objectfled_spi_show returned false.");
1094 return response;
1095 }
1096
1097 // 4. Wait for completion.
1098 fl::objectfled_spi_wait();
1099 const fl::u32 wait_ms = millis() - show_start_ms;
1100 response.set("wait_ms", static_cast<int64_t>(wait_ms));
1101
1102 // 5. Snapshot DMA + QTimer3 + XBAR1 register state.
1103 fl::ObjectFLEDSPIDiagnostics diag{};
1104 fl::objectfled_spi_read_diagnostics(&diag);
1105 response.set("dma_channel", static_cast<int64_t>(diag.dma_channel));
1106 response.set("dma_erq", static_cast<int64_t>(diag.dma_erq));
1107 response.set("dma_int", static_cast<int64_t>(diag.dma_int));
1108 response.set("dma_err", static_cast<int64_t>(diag.dma_err));
1109 response.set("dma_es", static_cast<int64_t>(diag.dma_es));
1110 response.set("dma_citer", static_cast<int64_t>(diag.dma_citer));
1111 response.set("dma_biter", static_cast<int64_t>(diag.dma_biter));
1112 response.set("tmr3_cntr0", static_cast<int64_t>(diag.tmr3_cntr0));
1113 response.set("tmr3_csctrl0", static_cast<int64_t>(diag.tmr3_csctrl0));
1114 response.set("tmr3_sctrl0", static_cast<int64_t>(diag.tmr3_sctrl0));
1115 response.set("tmr3_ctrl0", static_cast<int64_t>(diag.tmr3_ctrl0));
1116 response.set("tmr3_enbl", static_cast<int64_t>(diag.tmr3_enbl));
1117 response.set("xbar1_ctrl1", static_cast<int64_t>(diag.xbar1_ctrl1));
1118
1119 // 6. Tear down.
1120 fl::objectfled_spi_deinit();
1121
1122 response.set("success", true);
1123 return response;
1124#endif
1125 });
1126}
1127
1128#endif // FL_PLATFORM_HAS_LARGE_MEMORY
CRGB leds[1]
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
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)
Issue #2994 repro: compounded per-sequence timing drift vs 3.10.3.
Wave8 expansion micro-benchmark for #2526.
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ CHANNELS
Remove all channels from controller list.
Definition FastLED.h:663
@ APA102
APA102 LED chipset.
Definition FastLED.h:299
fl::unique_ptr< fl::Remote > remote
Definition RpcClient.ino:43
fl::net::ble — BLE GATT transport layer for JSON-RPC
SPI encoder configuration for clocked LED chipsets.
fl::shared_ptr< AutoResearchState > mState
void bindDriverMethods(fl::Remote &remote)
void clear(bool writeData=false) FL_NO_EXCEPT
Clear the leds, wiping the local array of data.
static void add(fl::ChannelPtr channel) FL_NO_EXCEPT
Add a Channel-based LED controller (from ChannelPtr)
void show(fl::u8 scale) FL_NO_EXCEPT
Update all our controllers with the current led colors, using the passed in brightness.
void setBrightness(fl::u8 scale) FL_NO_EXCEPT
Set the global brightness scaling.
Definition FastLED.h:1602
void wait() FL_NO_EXCEPT
Wait for all channel bus transmissions to complete.
::CLEDController & addLeds(::CLEDController *pLed, CRGB *data, int nLedsOrOffset, int nLedsIfOffset=0) FL_NO_EXCEPT
Add a CLEDController instance to the world.
static ChannelManager & registry() FL_NO_EXCEPT
Get the singleton WITHOUT registering platform default drivers.
fl::shared_ptr< IChannelDriver > findDriverByName(const fl::string &name) const FL_NO_EXCEPT
Silent counterpart to getDriverByName().
bool isDriverEnabled(const char *name) const FL_NO_EXCEPT
Check if a driver is enabled by name.
JSON-RPC server with scheduling support.
Definition remote.h:42
static RxChannelPtr create(const RxChannelConfig &config) FL_NO_EXCEPT
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
const char * c_str() const FL_NO_EXCEPT
fl::optional< i64 > as_int() const FL_NO_EXCEPT
Definition json.h:398
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
Definition json.h:873
bool is_null() const FL_NO_EXCEPT
Definition json.h:381
void push_back(const json &value) FL_NO_EXCEPT
Definition json.h:925
bool contains(size_t idx) const FL_NO_EXCEPT
Definition json.h:790
static json object() FL_NO_EXCEPT
Definition json.h:864
static json array() FL_NO_EXCEPT
Definition json.h:860
static string from_literal(const char *literal) FL_NO_EXCEPT
fl::size size() const FL_NO_EXCEPT
void reserve(fl::size n) FL_NO_EXCEPT
Definition vector.h:591
void resize(fl::size n) FL_NO_EXCEPT
Definition vector.h:593
void push_back(const T &value) FL_NO_EXCEPT
Definition vector.h:624
void assign(InputIt first, InputIt last) FL_NO_EXCEPT
Definition vector.h:638
fl::size_t size_t
Definition cstddef.h:61
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
constexpr EOrder RGB
Definition eorder.h:17
Task executor — runs registered task runners and manages the run loop.
#define DATA_RATE_KHZ
Definition fastspi.h:96
fl::CRGB CRGB
Definition crgb.h:25
Platform-abstracted heap memory query functions.
FastLED's Elegant JSON Library: fl::json
DriftResult run(int pin, int num_leds, int iterations)
fl::json legacyAsyncProbe(const fl::json &args)
Call after the regular driver test has registered the selected backend.
fl::json legacyAsyncErrorProbe(const fl::json &args)
unsigned char u8
Definition stdint.h:131
constexpr ChipsetTiming to_runtime_timing() FL_NO_EXCEPT
Convert enum-based timing type to runtime ChipsetTiming struct.
Definition led_timing.h:616
constexpr ChipsetTimingConfig makeTimingConfig() FL_NO_EXCEPT
Convert compile-time CHIPSET type to runtime timing config.
fl::u32 uint32_t
Definition s16x16x4.h:219
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
ChipsetTiming4Phase make4PhaseTiming(const ChipsetTiming &timing_3phase, u32 tolerance_ns) FL_NO_EXCEPT
Create 4-phase RX timing from 3-phase chipset timing with tolerance.
Definition rx.cpp.hpp:78
fl::uptr uintptr_t
Definition s16x16x4.h:224
@ FLEXIO
Teensy 4.x-only FlexIO capture backend (FLEXIO1 — FLEXIO2 is owned by the WS2812 TX driver)....
Definition types.h:14
@ FLEX_IO
Definition bus.h:36
void analogWrite(int pin, u16 val) FL_NO_EXCEPT
Write analog value to pin (PWM)
Definition pin.cpp.hpp:247
constexpr ClocklessEncoder encoder_for() FL_NO_EXCEPT
Extract the encoder selector from a compile-time TIMING type.
fl::u64 u64
Definition s16x16x4.h:221
void digitalWrite(int pin, PinValue val) FL_NO_EXCEPT
Write digital value to pin.
Definition pin.cpp.hpp:51
void pinMode(int pin, PinMode mode) FL_NO_EXCEPT
Set pin mode (input, output, pull-up, pull-down)
Definition pin.cpp.hpp:398
void delayMicroseconds(u32 us) FL_NO_EXCEPT
Delay for a given number of microseconds.
unsigned char uint8_t
Definition s16x16x4.h:209
corkscrew_args args
Definition old.h:149
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
@ Green
<div style='background:#008000;width:4em;height:4em;'></div>
Definition crgb.h:565
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
Definition crgb.h:629
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
Definition crgb.h:519
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517
u32 ns
Duration in nanoseconds (31 bits, max ~2.1s)
Definition rx.h:35
int captureWaitResult
RxWaitResult value, or -1 before wait.
int rawEdgesAfterWait
Raw edges visible after RX wait/decode.
fl::string rxBeginError
Machine-readable reason when RX begin() fails.