FastLED 3.10.6
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"
39#include "AutoResearchTimingDrift.h" // timingDriftTest RPC (#2994 repro)
40#include "fl/chipsets/spi.h"
41#include "fl/channels/config.h"
42#include "fl/stl/span.h"
43#include "fl/stl/singleton.h"
44#include <Arduino.h>
45
46// #3428: FlexIO2 SPI mode bring-up smoke test (`flexioSpiSelfTest` RPC).
47// The header is Teensy 4.x-gated, but #include is safe on any platform
48// because all declarations sit inside `#if defined(FL_IS_TEENSY_4X)`.
49#include "platforms/arm/teensy/teensy4_common/drivers/flexio/flexio_spi_mode.h"
50
51// #3428: ObjectFLED DMA-bit-banged SPI mode bring-up smoke test
52// (`objectfledSpiSelfTest` RPC). Same Teensy 4.x-gating pattern.
53#include "platforms/arm/teensy/teensy4_common/drivers/objectfled/objectfled_spi_mode.h"
54
55#include "fl/net/ble.h"
56
57#include "fl/codec/h264.h"
58#include "fl/codec/mp4_parser.h"
60#include "fl/fx/frame.h"
61
62namespace {
63
64#if defined(FL_IS_TEENSY_4X)
65struct FlexIoObjectFledState {
66 CRGB leds[100];
67};
68
69FlexIoObjectFledState& flexIoObjectFledState() {
71}
72#endif
73
74} // namespace
75
77 // Register "flexioRxBenchmark" — square-wave validation for the new
78 // FlexIO RX backend (Phase 2 of FastLED#2764).
79 //
80 // Drives `tx_pin` with a `frequency_hz` square wave via the Teensy core's
81 // `analogWriteFrequency` PWM, configures an RxChannel on `rx_pin` using
82 // `RxBackend::FLEXIO`, captures for `duration_ms`, and reports per-period
83 // statistics: count, mean ns, σ ns, min ns, max ns.
84 //
85 // Args (positional, all optional with defaults):
86 // {
87 // "frequency_hz": int = 1000,
88 // "duration_ms": int = 100,
89 // "tx_pin": int = 3, // matches GPIO 3↔4 jumper
90 // "rx_pin": int = 4
91 // }
92 //
93 // Teensy-4-only because FLEXIO1 is iMXRT1062-specific. Other platforms
94 // get a clean "not supported" response so the RPC harness can still
95 // round-trip.
96 remote.bind("flexioRxBenchmark", [](const fl::json& args) -> fl::json {
97 fl::json response = fl::json::object();
98#if !defined(FL_IS_TEENSY_4X)
99 (void)args;
100 response.set("success", false);
101 response.set("error", "PlatformNotSupported");
102 response.set("message",
103 "flexioRxBenchmark is Teensy 4.x-only (FLEXIO1 capture).");
104 return response;
105#else
106 // Parse args
107 int frequency_hz = 1000;
108 int duration_ms = 100;
109 int tx_pin = 3;
110 int rx_pin = 4;
111 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
112 fl::json cfg = args[0];
113 if (cfg.contains("frequency_hz") && cfg["frequency_hz"].is_int()) {
114 frequency_hz = static_cast<int>(cfg["frequency_hz"].as_int().value());
115 }
116 if (cfg.contains("duration_ms") && cfg["duration_ms"].is_int()) {
117 duration_ms = static_cast<int>(cfg["duration_ms"].as_int().value());
118 }
119 if (cfg.contains("tx_pin") && cfg["tx_pin"].is_int()) {
120 tx_pin = static_cast<int>(cfg["tx_pin"].as_int().value());
121 }
122 if (cfg.contains("rx_pin") && cfg["rx_pin"].is_int()) {
123 rx_pin = static_cast<int>(cfg["rx_pin"].as_int().value());
124 }
125 }
126 if (frequency_hz < 1 || frequency_hz > 5000000 ||
127 duration_ms < 1 || duration_ms > 5000) {
128 response.set("success", false);
129 response.set("error", "InvalidArgs");
130 response.set("message",
131 "frequency_hz in [1, 5_000_000]; duration_ms in [1, 5000].");
132 return response;
133 }
134
135 // 1. Drive the TX pin with a square wave.
136 analogWriteFrequency(tx_pin, (float)frequency_hz);
137 analogWrite(tx_pin, 128); // 50% duty
138
139 // 2. Create the FlexIO RX channel.
140 // Width budget — size the edge buffer for the actual capture window:
141 // expected_edges = 2 * frequency_hz * duration_ms / 1000 (transitions)
142 // target = expected_edges * 1.5 (50% headroom for jitter/skew)
143 // Clamped to [1024, 16384] so the DMA buffer stays reasonable and
144 // matches the FlexIO RX driver's internal cap.
145 fl::RxChannelConfig rx_cfg(rx_pin, fl::RxBackend::FLEXIO);
146 const fl::u64 expected_edges =
147 (fl::u64)2 * (fl::u64)frequency_hz * (fl::u64)duration_ms / 1000ULL;
148 fl::u64 target = expected_edges + expected_edges / 2; // +50%
149 if (target < 1024ULL) target = 1024ULL;
150 if (target > 16384ULL) target = 16384ULL;
151 rx_cfg.edge_capacity = (size_t)target;
152 rx_cfg.start_low = false; // PWM output idles in either state, just track transitions
153 auto rx_channel = fl::RxChannel::create(rx_cfg);
154 if (!rx_channel) {
155 analogWrite(tx_pin, 0);
156 response.set("success", false);
157 response.set("error", "RxCreateFailed");
158 response.set("message",
159 "Failed to create FlexIO RX channel on pin (no FLEXIO1 mapping).");
160 return response;
161 }
162 if (!rx_channel->begin(rx_cfg)) {
163 analogWrite(tx_pin, 0);
164 response.set("success", false);
165 response.set("error", "RxBeginFailed");
166 response.set("message",
167 "RxChannel::begin() returned false (see device WARN log).");
168 return response;
169 }
170
171 // 3. Let it capture for the requested window.
172 rx_channel->wait((u32)duration_ms);
173
174 // 4. Stop the square wave so the line is quiet for stats computation.
175 analogWrite(tx_pin, 0);
176
177 // 5. Pull captured edges out.
178 fl::vector<fl::EdgeTime> edges;
179 edges.assign(rx_cfg.edge_capacity, fl::EdgeTime());
180 size_t edges_captured =
181 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(edges), 0);
182 edges.resize(edges_captured);
183
184 // 6. Compute period statistics. A "period" = duration_high + duration_low
185 // for adjacent edges, so we iterate in pairs.
186 fl::u64 sum_ns = 0;
187 fl::u64 sum_sq_ns = 0;
188 fl::u32 min_ns = 0xFFFFFFFFu;
189 fl::u32 max_ns = 0;
190 size_t periods = 0;
191 for (size_t i = 0; i + 1 < edges_captured; i += 2) {
192 const fl::u32 period_ns =
193 (fl::u32)edges[i].ns + (fl::u32)edges[i + 1].ns;
194 if (period_ns == 0) continue;
195 sum_ns += period_ns;
196 sum_sq_ns += (fl::u64)period_ns * (fl::u64)period_ns;
197 if (period_ns < min_ns) min_ns = period_ns;
198 if (period_ns > max_ns) max_ns = period_ns;
199 ++periods;
200 }
201 fl::u32 mean_ns = 0;
202 fl::u32 sigma_ns = 0;
203 if (periods > 0) {
204 mean_ns = (fl::u32)(sum_ns / (fl::u64)periods);
205 const fl::u64 mean64 = (fl::u64)mean_ns;
206 const fl::u64 var =
207 periods > 0 ? (sum_sq_ns / (fl::u64)periods) - (mean64 * mean64)
208 : 0;
209 // Integer sqrt for σ — adequate for the tolerance ranges we
210 // assert in Phase 2 (σ < 100 ns at 100 kHz).
211 fl::u32 s = 0;
212 while ((fl::u64)(s + 1) * (fl::u64)(s + 1) <= var) ++s;
213 sigma_ns = s;
214 }
215 if (min_ns == 0xFFFFFFFFu) min_ns = 0;
216
217 response.set("success", true);
218 response.set("frequency_hz", static_cast<int64_t>(frequency_hz));
219 response.set("duration_ms", static_cast<int64_t>(duration_ms));
220 response.set("tx_pin", static_cast<int64_t>(tx_pin));
221 response.set("rx_pin", static_cast<int64_t>(rx_pin));
222 response.set("edges_captured", static_cast<int64_t>(edges_captured));
223 response.set("periods", static_cast<int64_t>(periods));
224 response.set("period_mean_ns", static_cast<int64_t>(mean_ns));
225 response.set("period_sigma_ns", static_cast<int64_t>(sigma_ns));
226 response.set("period_min_ns", static_cast<int64_t>(min_ns));
227 response.set("period_max_ns", static_cast<int64_t>(max_ns));
228 return response;
229#endif
230 });
231
232 // Register "flexioObjectFledTest" — end-to-end ObjectFLED TX → FlexIO RX
233 // loopback verification (Phase 3 of FastLED#2764).
234 //
235 // Drives a small WS2812 pattern through `Bus::FLEX_IO` slot 0 on `tx_pin`,
236 // captures the wire signal back through the new `RxBackend::FLEXIO` on
237 // `rx_pin`, decodes the bit stream against WS2812B-V5 timing, and reports
238 // how many bytes matched the transmitted pattern.
239 //
240 // Test cases (parent issue Phase 3 table):
241 // 0 — Red single LED (1 LED, 0xFF0000 → wire-order GRB 00,FF,00)
242 // 1 — RGB three-LED chain (3 LEDs)
243 // 2 — All zeros (1 LED, 0x000000 — T0H/T0L fidelity)
244 // 3 — All ones (1 LED, 0xFFFFFF — T1H/T1L fidelity)
245 // 4 — 100-LED alternating R/G/B (long capture, watchdog-safety)
246 //
247 // Args (positional, all optional):
248 // { "test_case": int = 0,
249 // "tx_pin": int = 3, // matches GPIO 3↔4 jumper
250 // "rx_pin": int = 4,
251 // "capture_ms": int = 50 }
252 //
253 // Returns:
254 // { success, test_case, num_leds, expected_bytes, decoded_bytes,
255 // matched, mismatched, edges_captured }
256 //
257 // Teensy-4-only — FLEXIO1 is iMXRT1062-specific. ObjectFLED also relies
258 // on Teensy 4-core APIs, so non-Teensy builds return `PlatformNotSupported`.
259 remote.bind("flexioObjectFledTest", [](const fl::json& args) -> fl::json {
260 fl::json response = fl::json::object();
261#if !defined(FL_IS_TEENSY_4X)
262 (void)args;
263 response.set("success", false);
264 response.set("error", "PlatformNotSupported");
265 response.set("message",
266 "flexioObjectFledTest is Teensy 4.x-only (FLEXIO1 RX + "
267 "Teensy-core ObjectFLED driver).");
268 return response;
269#else
270 // Parse args
271 int test_case = 0;
272 int tx_pin = 3;
273 int rx_pin = 4;
274 int capture_ms = 50;
275 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
276 fl::json cfg = args[0];
277 if (cfg.contains("test_case") && cfg["test_case"].is_int()) {
278 test_case = static_cast<int>(cfg["test_case"].as_int().value());
279 }
280 if (cfg.contains("tx_pin") && cfg["tx_pin"].is_int()) {
281 tx_pin = static_cast<int>(cfg["tx_pin"].as_int().value());
282 }
283 if (cfg.contains("rx_pin") && cfg["rx_pin"].is_int()) {
284 rx_pin = static_cast<int>(cfg["rx_pin"].as_int().value());
285 }
286 if (cfg.contains("capture_ms") && cfg["capture_ms"].is_int()) {
287 capture_ms = static_cast<int>(cfg["capture_ms"].as_int().value());
288 }
289 }
290 if (test_case < 0 || test_case > 4 ||
291 capture_ms < 1 || capture_ms > 5000) {
292 response.set("success", false);
293 response.set("error", "InvalidArgs");
294 response.set("message",
295 "test_case in [0,4]; capture_ms in [1, 5000].");
296 return response;
297 }
298
299 // 1. Build the test pattern. Fixed upper bound (100 LEDs) covers
300 // case 4 — the larger cases reuse the same static buffer.
301 CRGB* leds_buf = flexIoObjectFledState().leds;
302 int num_leds = 0;
303 switch (test_case) {
304 case 0:
305 num_leds = 1;
306 leds_buf[0] = CRGB::Red;
307 break;
308 case 1:
309 num_leds = 3;
310 leds_buf[0] = CRGB::Red;
311 leds_buf[1] = CRGB::Green;
312 leds_buf[2] = CRGB::Blue;
313 break;
314 case 2:
315 num_leds = 1;
316 leds_buf[0] = CRGB::Black;
317 break;
318 case 3:
319 num_leds = 1;
320 leds_buf[0] = CRGB(0xFF, 0xFF, 0xFF);
321 break;
322 case 4:
323 num_leds = 100;
324 for (int i = 0; i < 100; ++i) {
325 leds_buf[i] = (i % 3 == 0) ? CRGB::Red
326 : (i % 3 == 1) ? CRGB::Green
327 : CRGB::Blue;
328 }
329 break;
330 }
331
332 // 2. Build the expected wire-order byte stream (WS2812 is GRB).
333 fl::vector<u8> expected;
334 expected.reserve((size_t)num_leds * 3);
335 for (int i = 0; i < num_leds; ++i) {
336 expected.push_back(leds_buf[i].g);
337 expected.push_back(leds_buf[i].r);
338 expected.push_back(leds_buf[i].b);
339 }
340
341 // 3. Set up FlexIO RX. Size the edge buffer for 24 bits per LED ×
342 // 2 transitions per bit, with 25% headroom.
343 const size_t expected_edges = (size_t)num_leds * 24u * 2u;
344 size_t edge_capacity = expected_edges + expected_edges / 4u + 64u;
345 if (edge_capacity > 16384u) edge_capacity = 16384u;
346
347 fl::RxChannelConfig rx_cfg(rx_pin, fl::RxBackend::FLEXIO);
348 rx_cfg.edge_capacity = edge_capacity;
349 rx_cfg.start_low = true;
350 auto rx_channel = fl::RxChannel::create(rx_cfg);
351 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
352 response.set("success", false);
353 response.set("error", "RxBeginFailed");
354 response.set("message",
355 "Failed to bring up FlexIO RX on the requested pin "
356 "(check kFlexIo1Pins[] mapping).");
357 return response;
358 }
359
360 // 4. Configure ObjectFLED TX via FastLED.add().
361 fl::ChannelOptions opts;
362 opts.mBus = fl::Bus::FLEX_IO;
363 opts.mBusWhich = 0;
364 auto resolved_timing = fl::makeTimingConfig<fl::TIMING_WS2812B_V5>();
365 auto resolved_encoder = fl::encoder_for<fl::TIMING_WS2812B_V5>();
366 fl::NamedTimingConfig timing_cfg(resolved_timing, "WS2812B-V5",
367 resolved_encoder);
368 fl::ChannelConfig tx_cfg(
369 tx_pin,
370 timing_cfg.timing,
371 fl::span<CRGB>(leds_buf, (size_t)num_leds),
372 RGB,
373 opts);
374 auto tx_channel = FastLED.add(tx_cfg);
375 if (!tx_channel) {
376 response.set("success", false);
377 response.set("error", "TxAddFailed");
378 response.set("message",
379 "FastLED.add() rejected the ObjectFLED ChannelConfig.");
380 return response;
381 }
382
383 // 5. Trigger TX. FastLED.show() schedules the DMA and returns once
384 // the frame has been queued. The RX wait must be long enough to
385 // cover BOTH the TX transmission time AND the capture-buffer
386 // fill — otherwise teardown happens mid-frame and the hardware
387 // can be left in a bad state. Compute a minimum from the actual
388 // WS2812 timing (1.25 µs per bit, 24 bits per LED, plus reset
389 // gap) and use the larger of the caller's `capture_ms` and that
390 // minimum.
391 FastLED.show();
392 const u32 ws2812_tx_us =
393 (u32)num_leds * 24u * 13u / 10u + 100u; // 1.25 µs/bit + reset
394 const u32 min_capture_ms = (ws2812_tx_us + 999u) / 1000u + 10u;
395 const u32 effective_capture_ms = ((u32)capture_ms > min_capture_ms)
396 ? (u32)capture_ms
397 : min_capture_ms;
398 rx_channel->wait(effective_capture_ms);
399
400 // 6. Decode the captured edge stream against WS2812 4-phase timing.
401 const fl::ChipsetTiming ws2812_timing =
403 fl::ChipsetTiming4Phase rx_timing =
404 fl::make4PhaseTiming(ws2812_timing);
405 fl::vector<u8> decoded;
406 decoded.assign(expected.size() + 16u, 0u);
407 auto decode_result =
408 rx_channel->decode(rx_timing, fl::span<u8>(decoded));
409
410 // 7. Compare decoded bytes against expected.
411 u32 decoded_bytes = 0u;
412 if (decode_result) {
413 decoded_bytes = decode_result.value();
414 }
415 const size_t cmp_n = (decoded_bytes < expected.size())
416 ? (size_t)decoded_bytes
417 : expected.size();
418 size_t matched = 0;
419 size_t mismatched = 0;
420 for (size_t i = 0; i < cmp_n; ++i) {
421 if (decoded[i] == expected[i]) ++matched; else ++mismatched;
422 }
423 if (decoded_bytes < expected.size()) {
424 mismatched += expected.size() - decoded_bytes;
425 }
426
427 // 8. Read raw edge count for diagnostics.
428 fl::vector<fl::EdgeTime> edges;
429 edges.assign(edge_capacity, fl::EdgeTime());
430 const size_t edges_captured =
431 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(edges), 0);
432
433 // 9. Tear the TX channel back down so subsequent tests can use the
434 // same pin (FastLED.clear(ClearFlags::CHANNELS) matches the
435 // pattern used by runSingleTestImpl in this file).
437
438 response.set("success", mismatched == 0 && decoded_bytes == expected.size());
439 response.set("test_case", static_cast<int64_t>(test_case));
440 response.set("tx_pin", static_cast<int64_t>(tx_pin));
441 response.set("rx_pin", static_cast<int64_t>(rx_pin));
442 response.set("num_leds", static_cast<int64_t>(num_leds));
443 response.set("expected_bytes", static_cast<int64_t>(expected.size()));
444 response.set("decoded_bytes", static_cast<int64_t>(decoded_bytes));
445 response.set("matched", static_cast<int64_t>(matched));
446 response.set("mismatched", static_cast<int64_t>(mismatched));
447 response.set("edges_captured", static_cast<int64_t>(edges_captured));
448 return response;
449#endif
450 });
451
452 // Register "flexioRxLoopbackPing" — FastLED#3066 phase 1.7 diagnostic.
453 //
454 // Bypasses every clockless driver entirely and tests whether the
455 // FlexIO RX backend can capture HAND-DRIVEN GPIO transitions. Drives
456 // `tx_pin` as a plain `digitalWrite` HIGH/LOW square wave while
457 // `RxBackend::FLEXIO` is armed on `rx_pin`. If the captured buffer
458 // shows any non-zero data, IOMUX ALT4 + PINSEL routing is correct
459 // and the WS2812-loopback failure mode is downstream (timer/shifter
460 // bandwidth, decoder thresholds, etc.). If the buffer stays all
461 // zero with a 4-edge ground-truth pin toggle, the routing itself is
462 // broken — debug that BEFORE more timer/shifter config experiments.
463 //
464 // Wiring: same TX↔RX jumper as flexioObjectFledTest (defaults 3↔4).
465 //
466 // Args:
467 // { tx_pin?: 3, // any digital pin
468 // rx_pin?: 4, // must be in kFlexIo1Pins[]
469 // toggle_us?: 50, // half-period of the manual square wave
470 // edges?: 8 } // number of digitalWrite transitions
471 // Returns:
472 // { success, tx_pin, rx_pin, toggle_us, edges, edges_captured,
473 // capture_buffer_first8_hex: [...], notes }
474 //
475 // Teensy-4-only.
476 remote.bind("flexioRxLoopbackPing", [](const fl::json& args) -> fl::json {
477 fl::json response = fl::json::object();
478#if !defined(FL_IS_TEENSY_4X)
479 (void)args;
480 response.set("success", false);
481 response.set("error", "PlatformNotSupported");
482 response.set("message",
483 "flexioRxLoopbackPing is Teensy 4.x-only (FLEXIO1 RX).");
484 return response;
485#else
486 int tx_pin = 3;
487 int rx_pin = 4;
488 int toggle_us = 50;
489 int edges = 8;
490 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
491 const fl::json &cfg = args[0];
492 if (cfg.contains("tx_pin") && cfg["tx_pin"].is_int()) {
493 tx_pin = static_cast<int>(cfg["tx_pin"].as_int().value());
494 }
495 if (cfg.contains("rx_pin") && cfg["rx_pin"].is_int()) {
496 rx_pin = static_cast<int>(cfg["rx_pin"].as_int().value());
497 }
498 if (cfg.contains("toggle_us") && cfg["toggle_us"].is_int()) {
499 toggle_us = static_cast<int>(cfg["toggle_us"].as_int().value());
500 }
501 if (cfg.contains("edges") && cfg["edges"].is_int()) {
502 edges = static_cast<int>(cfg["edges"].as_int().value());
503 }
504 }
505 if (edges < 2 || edges > 64) {
506 response.set("success", false);
507 response.set("error", "InvalidEdges");
508 response.set("message", "edges must be in [2, 64]");
509 return response;
510 }
511
512 // 1. Arm FlexIO RX on rx_pin.
513 fl::RxChannelConfig rx_cfg(rx_pin, fl::RxBackend::FLEXIO);
514 rx_cfg.edge_capacity = 1024; // plenty for ~10 edges
515 rx_cfg.start_low = true;
516 auto rx_channel = fl::RxChannel::create(rx_cfg);
517 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
518 response.set("success", false);
519 response.set("error", "RxBeginFailed");
520 response.set("message",
521 "FlexIO RX begin() failed (kFlexIo1Pins[] mapping?).");
522 return response;
523 }
524
525 // 2. Drive tx_pin as a plain GPIO output, toggle a known number of
526 // transitions. Start LOW so the first digitalWrite(HIGH) is a
527 // rising edge.
528 pinMode(tx_pin, OUTPUT);
529 digitalWrite(tx_pin, LOW);
530 delayMicroseconds(100); // let the line settle + RX arm fully
531
532 bool level = true;
533 for (int i = 0; i < edges; ++i) {
534 digitalWrite(tx_pin, level ? HIGH : LOW);
535 delayMicroseconds(toggle_us);
536 level = !level;
537 }
538
539 // FastLED#3066 iter 7 diagnostic: also probe SHIFTBUF[0] +
540 // SHIFTBUFBIS (bit-swapped) + SHIFTBUFBYS (byte-swapped) so we
541 // can see whether the captured bit lands in a bit position
542 // different from what the canonical SHIFTBUF[0] read produces.
543 {
544 constexpr uintptr_t kFLEXIO1_BASE_DIAG = 0x401AC000u;
545 volatile uint32_t *pin = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x00C);
546 volatile uint32_t *shftstat = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x010);
547 volatile uint32_t *timstat = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x018);
548 volatile uint32_t *shftbuf = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x200);
549 volatile uint32_t *shftbufbis = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x280);
550 volatile uint32_t *shftbufbys = (volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x300);
551 fl::json flexio_diag = fl::json::object();
552 flexio_diag.set("pin", static_cast<int64_t>(*pin));
553 flexio_diag.set("shiftstat", static_cast<int64_t>(*shftstat));
554 flexio_diag.set("timstat", static_cast<int64_t>(*timstat));
555 flexio_diag.set("shiftbuf0", static_cast<int64_t>(*shftbuf));
556 flexio_diag.set("shiftbufbis0", static_cast<int64_t>(*shftbufbis));
557 flexio_diag.set("shiftbufbys0", static_cast<int64_t>(*shftbufbys));
558 response.set("flexio1", flexio_diag);
559 }
560
561 // 3. Wait briefly for FlexIO RX to flush any pending DMA. The DMA
562 // is configured for completion-on-buffer-full; a partial fill
563 // won't trigger the ISR, so `wait()` will TIMEOUT — which is
564 // fine, we read the captured edges directly.
565 rx_channel->wait(50);
566
567 // 4. Read the captured edges via the public API.
568 fl::vector<fl::EdgeTime> captured_edges;
569 captured_edges.assign(64, fl::EdgeTime());
570 size_t edge_count =
571 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(captured_edges), 0);
572
573 // 5. Restore tx_pin so subsequent tests can use it.
574 pinMode(tx_pin, INPUT);
575
576 response.set("success", true);
577 response.set("tx_pin", static_cast<int64_t>(tx_pin));
578 response.set("rx_pin", static_cast<int64_t>(rx_pin));
579 response.set("toggle_us", static_cast<int64_t>(toggle_us));
580 response.set("edges", static_cast<int64_t>(edges));
581 response.set("edges_captured", static_cast<int64_t>(edge_count));
582
583 // Dump the first 8 captured edge durations (ns) so the host can
584 // see whether they roughly match the requested toggle_us *
585 // 1000 ns expectation.
586 fl::json edges_arr = fl::json::array();
587 const size_t to_dump = (edge_count < 8u) ? edge_count : 8u;
588 for (size_t i = 0; i < to_dump; ++i) {
589 fl::json e = fl::json::object();
590 e.set("high", captured_edges[i].high);
591 e.set("ns", static_cast<int64_t>(captured_edges[i].ns));
592 edges_arr.push_back(e);
593 }
594 response.set("first_edges", edges_arr);
595 return response;
596#endif
597 });
598
599 // Register "timingDriftTest" — issue #2994 reproducer for compounded
600 // per-sequence timing drift on master vs 3.10.3. Replays the reporter's
601 // minimal sketch (35-LED WS2812B ring, setMaxRefreshRate(800),
602 // millisDelay-gated 255-step fade, delay(1000) between sequences) and
603 // returns per-sequence wall time so the host can build a histogram.
604 // Args: [{pin, numLeds, iterations}] (all optional;
605 // default pin=4, numLeds=35, iterations=10; iterations clamped to
606 // autoresearch::timing_drift::kMaxIterations)
607 remote.bind("timingDriftTest", [](const fl::json& args) -> fl::json {
608 fl::json response = fl::json::object();
609
610 int pin = 4;
611 int num_leds = 35;
612 int iterations = 10;
613
614 fl::json config;
615 if (args.is_object()) {
616 config = args;
617 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
618 config = args[0];
619 }
620 if (!config.is_null()) {
621 if (config.contains("pin") && config["pin"].is_int()) {
622 pin = static_cast<int>(config["pin"].as_int().value());
623 }
624 if (config.contains("numLeds") && config["numLeds"].is_int()) {
625 num_leds = static_cast<int>(config["numLeds"].as_int().value());
626 }
627 if (config.contains("iterations") && config["iterations"].is_int()) {
628 iterations = static_cast<int>(config["iterations"].as_int().value());
629 }
630 }
631
632 auto r = autoresearch::timing_drift::run(pin, num_leds, iterations);
633
634 response.set("success", r.valid_pin);
635 if (!r.valid_pin) {
636 response.set("error", "InvalidPin");
637 response.set("message",
638 "pin is outside LegacyClocklessProxy range");
639 response.set("pin", r.pin);
640 return response;
641 }
642 response.set("pin", r.pin);
643 response.set("num_leds", r.num_leds);
644 response.set("iterations", r.iterations);
645 response.set("cpu_mhz", static_cast<int64_t>(r.cpu_mhz));
646 response.set("show_count", static_cast<int64_t>(r.show_count));
647 response.set("show_min_us", static_cast<int64_t>(r.show_min_us));
648 response.set("show_max_us", static_cast<int64_t>(r.show_max_us));
649 response.set("show_total_us", static_cast<int64_t>(r.show_total_us));
650
651 fl::json iter_ms_arr = fl::json::array();
652 for (int i = 0; i < r.iterations; ++i) {
653 iter_ms_arr.push_back(static_cast<int64_t>(r.iter_ms[i]));
654 }
655 response.set("iter_ms", iter_ms_arr);
656 return response;
657 });
658
659 // Register "flexioSpiSelfTest" — #3428 bring-up smoke test for the
660 // FlexIO2 SPI master mode. Exercises the full
661 // lookup → init → show → wait → deinit path on a user-specified
662 // (MOSI, SCLK) pin pair with a known byte pattern, then reports back
663 // success/failure of each stage plus the resolved FlexIO2 pin indices
664 // so a host-side test can diagnose routing and bring-up failures.
665 //
666 // This test does NOT need a logic analyzer or a connected APA102
667 // strip — it confirms the driver runs without crashing the firmware,
668 // pins route, init programs the shifter/timer, and DMA completes
669 // within the bounded timeout. Full byte-level verification needs a
670 // scope (see TODO markers in flexio_spi_mode.cpp.hpp).
671 //
672 // Args (positional, all optional):
673 // { "mosi_pin": int = 10, // FlexIO2 pin 0 (default APA102 MOSI)
674 // "sclk_pin": int = 12, // FlexIO2 pin 1 (default APA102 SCLK)
675 // "clock_hz": int = 1000000, // 1 MHz default for safety
676 // "num_bytes": int = 4 } // sent bytes 0xA5 0x5A 0xFF 0x00
677 //
678 // Returns:
679 // { success, mosi_pin, sclk_pin, clock_hz, num_bytes,
680 // lookup_ok, init_ok, show_ok, wait_ms,
681 // mosi_flexio_pin, sclk_flexio_pin }
682 //
683 // Teensy 4.x-only.
684 remote.bind("flexioSpiSelfTest", [](const fl::json& args) -> fl::json {
685 fl::json response = fl::json::object();
686#if !defined(FL_IS_TEENSY_4X)
687 (void)args;
688 response.set("success", false);
689 response.set("error", "PlatformNotSupported");
690 response.set("message",
691 "flexioSpiSelfTest is Teensy 4.x-only (FlexIO2 SPI master mode).");
692 return response;
693#else
694 // Parse args with defaults that work out-of-the-box on Teensy 4.x.
695 // Note: the RPC framework signature `fl::json(const fl::json&)` makes
696 // the framework strip the outer params array and pass `params[0]`
697 // directly as `args`. So `args` is an OBJECT here, not an array --
698 // some existing bindings in this file have the wrong `args.is_array()`
699 // check (pre-existing bug, fix tracked separately) and silently use
700 // their defaults always.
701 int mosi_pin = 10; // FlexIO2 pin 0 (teensy pin 10)
702 int sclk_pin = 12; // FlexIO2 pin 1 (teensy pin 12)
703 int clock_hz = 1000000; // 1 MHz -- safe for bring-up
704 int num_bytes = 4;
705 if (args.is_object()) {
706 const fl::json& cfg = args;
707 if (cfg.contains("mosi_pin") && cfg["mosi_pin"].is_int()) {
708 mosi_pin = static_cast<int>(cfg["mosi_pin"].as_int().value());
709 }
710 if (cfg.contains("sclk_pin") && cfg["sclk_pin"].is_int()) {
711 sclk_pin = static_cast<int>(cfg["sclk_pin"].as_int().value());
712 }
713 if (cfg.contains("clock_hz") && cfg["clock_hz"].is_int()) {
714 clock_hz = static_cast<int>(cfg["clock_hz"].as_int().value());
715 }
716 if (cfg.contains("num_bytes") && cfg["num_bytes"].is_int()) {
717 num_bytes = static_cast<int>(cfg["num_bytes"].as_int().value());
718 }
719 }
720
721 response.set("mosi_pin", mosi_pin);
722 response.set("sclk_pin", sclk_pin);
723 response.set("clock_hz", static_cast<int64_t>(clock_hz));
724 response.set("num_bytes", num_bytes);
725
726 // Validate input ranges BEFORE narrowing to u8/u32. Without these
727 // guards, mosi_pin=270 wraps to pin 14 in static_cast<u8>(), and
728 // a negative clock_hz wraps to a huge u32 that the SPI driver
729 // would then clamp to the 25 MHz ceiling -- both silently produce
730 // wrong-hardware behavior. Per coderabbitai review on PR #3431.
731 if (num_bytes <= 0 || num_bytes > 64) {
732 response.set("success", false);
733 response.set("error", "InvalidArgs");
734 response.set("message", "num_bytes must be in [1, 64].");
735 return response;
736 }
737 if (mosi_pin < 0 || mosi_pin > 255 ||
738 sclk_pin < 0 || sclk_pin > 255) {
739 response.set("success", false);
740 response.set("error", "InvalidArgs");
741 response.set("message", "mosi_pin / sclk_pin must be in [0, 255].");
742 return response;
743 }
744 if (clock_hz <= 0) {
745 response.set("success", false);
746 response.set("error", "InvalidArgs");
747 response.set("message", "clock_hz must be > 0.");
748 return response;
749 }
750
751 // 1. Pin-pair lookup.
752 fl::FlexIOSPIPinInfo pin_info{};
753 bool lookup_ok = fl::flexio_spi_lookup_pins(
754 static_cast<fl::u8>(mosi_pin),
755 static_cast<fl::u8>(sclk_pin),
756 &pin_info);
757 response.set("lookup_ok", lookup_ok);
758 if (!lookup_ok) {
759 response.set("success", false);
760 response.set("error", "PinNotRoutable");
761 response.set("message",
762 "(MOSI, SCLK) pair is not FlexIO2-routable; both pins "
763 "must be in {6, 7, 8, 9, 10, 11, 12, 13, 32}.");
764 return response;
765 }
766 response.set("mosi_flexio_pin",
767 static_cast<int64_t>(pin_info.mosi_flexio_pin));
768 response.set("sclk_flexio_pin",
769 static_cast<int64_t>(pin_info.sclk_flexio_pin));
770
771 // 2. Init.
772 bool init_ok = fl::flexio_spi_init(pin_info,
773 static_cast<fl::u32>(clock_hz));
774 response.set("init_ok", init_ok);
775 if (!init_ok) {
776 response.set("success", false);
777 response.set("error", "InitFailed");
778 response.set("message", "flexio_spi_init returned false.");
779 return response;
780 }
781
782 // 3. Send a known pattern. 0xA5 = 1010 0101 — easy to read on a
783 // scope to verify MSB-first ordering (high bit shifts out first).
784 // Fill the rest with alternating 0xFF / 0x00 so the wire toggles.
785 fl::u8 buffer[64] = {0};
786 for (int i = 0; i < num_bytes; ++i) {
787 switch (i % 4) {
788 case 0: buffer[i] = 0xA5; break;
789 case 1: buffer[i] = 0x5A; break;
790 case 2: buffer[i] = 0xFF; break;
791 case 3: buffer[i] = 0x00; break;
792 }
793 }
794
795 const fl::u32 show_start_ms = millis();
796 bool show_ok = fl::flexio_spi_show(buffer,
797 static_cast<fl::u32>(num_bytes));
798 response.set("show_ok", show_ok);
799 if (!show_ok) {
800 fl::flexio_spi_deinit();
801 response.set("success", false);
802 response.set("error", "ShowFailed");
803 response.set("message", "flexio_spi_show returned false.");
804 return response;
805 }
806
807 // 4. Wait for completion.
808 fl::flexio_spi_wait();
809 const fl::u32 wait_ms = millis() - show_start_ms;
810 response.set("wait_ms", static_cast<int64_t>(wait_ms));
811
812 // 5. Tear down.
813 fl::flexio_spi_deinit();
814
815 response.set("success", true);
816 return response;
817#endif
818 });
819
820 // Register "objectfledSpiSelfTest" — #3428 bring-up smoke test for the
821 // ObjectFLED DMA-bit-banged SPI master mode. Exercises the full
822 // lookup → init → show → wait → deinit path on a user-specified
823 // (MOSI, SCLK) pin pair (both must be on GPIO6) with a known byte
824 // pattern, reports back per-stage success + echoed args.
825 //
826 // Args (positional, all optional):
827 // { "mosi_pin": int = 14, // GPIO6 bit 18 on T4.1
828 // "sclk_pin": int = 15, // GPIO6 bit 19 on T4.1
829 // "clock_hz": int = 1000000,
830 // "num_bytes": int = 4 }
831 //
832 // Returns:
833 // { success, mosi_pin, sclk_pin, clock_hz, num_bytes,
834 // lookup_ok, init_ok, show_ok, wait_ms,
835 // mosi_bit, sclk_bit, dma_* (10), tmr3_* (5), xbar1_ctrl1 }
836 //
837 // Teensy 4.x-only.
838 remote.bind("objectfledSpiSelfTest", [](const fl::json& args) -> fl::json {
839 fl::json response = fl::json::object();
840#if !defined(FL_IS_TEENSY_4X)
841 (void)args;
842 response.set("success", false);
843 response.set("error", "PlatformNotSupported");
844 response.set("message",
845 "objectfledSpiSelfTest is Teensy 4.x-only (FlexPWM2 + eDMA + GPIO6).");
846 return response;
847#else
848 // Parse args. The RPC framework strips the outer params array
849 // and passes params[0] directly as `args`, so `args` is an OBJECT
850 // here (NOT an array).
851 int mosi_pin = 14; // GPIO6 bit 18 on T4.1 (and T4.0)
852 int sclk_pin = 15; // GPIO6 bit 19
853 int clock_hz = 1000000; // 1 MHz -- safe for bring-up
854 int num_bytes = 4;
855 if (args.is_object()) {
856 const fl::json& cfg = args;
857 if (cfg.contains("mosi_pin") && cfg["mosi_pin"].is_int()) {
858 mosi_pin = static_cast<int>(cfg["mosi_pin"].as_int().value());
859 }
860 if (cfg.contains("sclk_pin") && cfg["sclk_pin"].is_int()) {
861 sclk_pin = static_cast<int>(cfg["sclk_pin"].as_int().value());
862 }
863 if (cfg.contains("clock_hz") && cfg["clock_hz"].is_int()) {
864 clock_hz = static_cast<int>(cfg["clock_hz"].as_int().value());
865 }
866 if (cfg.contains("num_bytes") && cfg["num_bytes"].is_int()) {
867 num_bytes = static_cast<int>(cfg["num_bytes"].as_int().value());
868 }
869 }
870
871 response.set("mosi_pin", mosi_pin);
872 response.set("sclk_pin", sclk_pin);
873 response.set("clock_hz", static_cast<int64_t>(clock_hz));
874 response.set("num_bytes", num_bytes);
875
876 // Validate input ranges BEFORE narrowing to u8/u32.
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::ObjectFLEDSPIPinInfo pin_info{};
899 bool lookup_ok = fl::objectfled_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 on GPIO6 or otherwise invalid; "
909 "both pins must be GPIO6-resident (T4.x: 0, 1, 14-27, "
910 "T4.1 adds 38-41).");
911 return response;
912 }
913 response.set("mosi_bit", static_cast<int64_t>(pin_info.mosi_bit));
914 response.set("sclk_bit", static_cast<int64_t>(pin_info.sclk_bit));
915
916 // 2. Init.
917 bool init_ok = fl::objectfled_spi_init(pin_info,
918 static_cast<fl::u32>(clock_hz));
919 response.set("init_ok", init_ok);
920 if (!init_ok) {
921 response.set("success", false);
922 response.set("error", "InitFailed");
923 response.set("message", "objectfled_spi_init returned false.");
924 return response;
925 }
926
927 // 3. Send a known pattern.
928 fl::u8 buffer[64] = {0};
929 for (int i = 0; i < num_bytes; ++i) {
930 switch (i % 4) {
931 case 0: buffer[i] = 0xA5; break;
932 case 1: buffer[i] = 0x5A; break;
933 case 2: buffer[i] = 0xFF; break;
934 case 3: buffer[i] = 0x00; break;
935 }
936 }
937
938 const fl::u32 show_start_ms = millis();
939 bool show_ok = fl::objectfled_spi_show(
940 fl::span<const fl::u8>(buffer,
941 static_cast<fl::size>(num_bytes)));
942 response.set("show_ok", show_ok);
943 if (!show_ok) {
944 fl::objectfled_spi_deinit();
945 response.set("success", false);
946 response.set("error", "ShowFailed");
947 response.set("message", "objectfled_spi_show returned false.");
948 return response;
949 }
950
951 // 4. Wait for completion.
952 fl::objectfled_spi_wait();
953 const fl::u32 wait_ms = millis() - show_start_ms;
954 response.set("wait_ms", static_cast<int64_t>(wait_ms));
955
956 // 5. Snapshot DMA + QTimer3 + XBAR1 register state.
957 fl::ObjectFLEDSPIDiagnostics diag{};
958 fl::objectfled_spi_read_diagnostics(&diag);
959 response.set("dma_channel", static_cast<int64_t>(diag.dma_channel));
960 response.set("dma_erq", static_cast<int64_t>(diag.dma_erq));
961 response.set("dma_int", static_cast<int64_t>(diag.dma_int));
962 response.set("dma_err", static_cast<int64_t>(diag.dma_err));
963 response.set("dma_es", static_cast<int64_t>(diag.dma_es));
964 response.set("dma_citer", static_cast<int64_t>(diag.dma_citer));
965 response.set("dma_biter", static_cast<int64_t>(diag.dma_biter));
966 response.set("tmr3_cntr0", static_cast<int64_t>(diag.tmr3_cntr0));
967 response.set("tmr3_csctrl0", static_cast<int64_t>(diag.tmr3_csctrl0));
968 response.set("tmr3_sctrl0", static_cast<int64_t>(diag.tmr3_sctrl0));
969 response.set("tmr3_ctrl0", static_cast<int64_t>(diag.tmr3_ctrl0));
970 response.set("tmr3_enbl", static_cast<int64_t>(diag.tmr3_enbl));
971 response.set("xbar1_ctrl1", static_cast<int64_t>(diag.xbar1_ctrl1));
972
973 // 6. Tear down.
974 fl::objectfled_spi_deinit();
975
976 response.set("success", true);
977 return response;
978#endif
979 });
980}
981
982#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).
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.
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.
void bindDriverMethods(fl::Remote &remote)
void show(fl::u8 scale)
Update all our controllers with the current led colors, using the passed in brightness.
static void add(fl::ChannelPtr channel)
Add a Channel-based LED controller (from ChannelPtr)
void clear(bool writeData=false)
Clear the leds, wiping the local array of data.
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
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:866
bool is_null() const FL_NO_EXCEPT
Definition json.h:381
void push_back(const json &value) FL_NO_EXCEPT
Definition json.h:918
bool contains(size_t idx) const FL_NO_EXCEPT
Definition json.h:790
static json object() FL_NO_EXCEPT
Definition json.h:857
static json array() FL_NO_EXCEPT
Definition json.h:853
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
constexpr EOrder RGB
Definition eorder.h:17
Task executor — runs registered task runners and manages the run loop.
@ CHANNELS
Remove all channels from controller list.
Definition FastLED.h:668
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)
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 millis()
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:72
@ 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 pinMode(int pin, PinMode mode)
Set pin mode (input, output, pull-up, pull-down)
Definition pin.cpp.hpp:378
void analogWrite(int pin, u16 val)
Write analog value to pin (PWM)
Definition pin.cpp.hpp:227
void digitalWrite(int pin, PinValue val)
Write digital value to pin.
Definition pin.cpp.hpp:51
constexpr ClocklessEncoder encoder_for() FL_NO_EXCEPT
Extract the encoder selector from a compile-time TIMING type.
void delayMicroseconds(u32 us)
Delay for a given number of microseconds.
corkscrew_args args
Definition old.h:149
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
fl::uptr uintptr_t
Definition stdint.h:223
fl::size size_t
Definition stdint.h:222
fl::u32 uint32_t
Definition stdint.h:218
@ 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