5#if FL_PLATFORM_HAS_LARGE_MEMORY
8#define DEBUG_PRINT(x) do {} while(0)
9#define DEBUG_PRINTLN(x) do {} while(0)
37#include "platforms/arm/teensy/teensy4_common/drivers/objectfled/objectfled_diagnostics.h"
38#include "platforms/arm/rp/is_rp.h"
39#if defined(FL_IS_TEENSY_4X)
40#include "platforms/arm/teensy/teensy4_common/rx_flexpwm_channel.h"
41#include "platforms/arm/teensy/teensy4_common/drivers/flexio/flexio_driver.h"
45#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
46#include "platforms/arm/rp/rpcommon/rp_pio_tx_bus_traits.h"
47#include "platforms/arm/rp/rpcommon/rp_uart_bus_traits.h"
48#include "platforms/arm/rp/rpcommon/rx_pio_channel.h"
72class StandingRxChannelSwap {
74 explicit StandingRxChannelSwap(AutoResearchState*
state)
75 : mState(
state), mReleased(false) {}
80 void release(fl::shared_ptr<fl::RxChannel>* alias) {
81 if (mReleased || mState ==
nullptr) {
84 if (alias !=
nullptr) {
87 mState->rx_channel.reset();
91 ~StandingRxChannelSwap() {
92 if (!mReleased || mState ==
nullptr || mState->rx_factory ==
nullptr) {
98 mState->rx_channel = mState->rx_factory(mState->pin_rx);
101 StandingRxChannelSwap(
const StandingRxChannelSwap&) =
delete;
102 StandingRxChannelSwap& operator=(
const StandingRxChannelSwap&) =
delete;
105 AutoResearchState* mState;
109#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
149 if (ucs7604ModeForEncoder(encoder, &mode)) {
159fl::size rpPioMaxLedsForWireBytes(fl::size wire_bytes,
162 if (ucs7604ModeForEncoder(encoder, &mode)) {
163 constexpr fl::size kPreambleLen = 15;
164 if (wire_bytes <= kPreambleLen) {
169 return wire_bytes / (
rgbw ? 4u : 3u);
177 const uint64_t payload_ns =
178 static_cast<uint64_t
>(max_leds) * 24ULL * bit_period_ns;
179 return static_cast<uint32_t>((payload_ns + 999ULL) / 1000ULL) +
185 for (fl::size i = 0; i < tx_configs.
size(); i++) {
188 if (count > max_leds) {
195class ScopedFastLedBrightness {
210bool contaminateTxMuxWithPwm(
int pin) {
211#if defined(FL_IS_TEENSY_4X)
212 analogWriteFrequency(pin, 1000.0f);
227fl::json probeStandardGpioPad(
int tx_pin,
int rx_pin) {
229 probe.
set(
"supported",
false);
230 probe.
set(
"txPin",
static_cast<int64_t
>(tx_pin));
231 probe.
set(
"rxPin",
static_cast<int64_t
>(rx_pin));
232#if !defined(FL_IS_TEENSY_4X)
233 probe.
set(
"message",
"Teensy 4.x only");
236 probe.
set(
"supported",
true);
237 if (tx_pin < 0 || tx_pin >= NUM_DIGITAL_PINS ||
238 rx_pin < 0 || rx_pin >= NUM_DIGITAL_PINS) {
239 probe.
set(
"success",
false);
240 probe.
set(
"error",
"InvalidPin");
244 volatile uint32_t* fast_output = portOutputRegister(tx_pin);
246 reinterpret_cast<uintptr_t>(fast_output) - 0x01E48000u);
247 volatile uint32_t* fast_mode = portModeRegister(tx_pin);
249 reinterpret_cast<uintptr_t>(fast_mode) - 0x01E48000u);
250 volatile uint32_t* tx_mux = portConfigRegister(tx_pin);
251 volatile uint32_t* tx_pad = portControlRegister(tx_pin);
252 volatile uint32_t* rx_mux = portConfigRegister(rx_pin);
253 volatile uint32_t* rx_pad = portControlRegister(rx_pin);
260 static_cast<uint8_t>((output_addr - gpio6_base) >> 14);
261 const uint8_t bit = digitalPinToBit(tx_pin);
263 probe.
set(
"txBit",
static_cast<int64_t
>(bit));
264 probe.
set(
"txMask",
static_cast<int64_t
>(mask));
265 probe.
set(
"txOffset",
static_cast<int64_t
>(
offset));
267 probe.
set(
"success",
false);
268 probe.
set(
"error",
"UnsupportedGpioBank");
275 const uint32_t saved_tx_mux = *tx_mux;
276 const uint32_t saved_tx_pad = *tx_pad;
277 const uint32_t saved_rx_mux = *rx_mux;
278 const uint32_t saved_rx_pad = *rx_pad;
279 const uint32_t saved_fast_mode = *fast_mode;
280 const uint32_t saved_standard_mode = *standard_mode;
281 const uint32_t saved_fast_out = *fast_output;
282 const uint32_t saved_standard_out = *standard_output;
287 CCM_CCGR1 |= CCM_CCGR1_GPIO1(CCM_CCGR_ON);
288 CCM_CCGR0 |= CCM_CCGR0_GPIO2(CCM_CCGR_ON);
289 CCM_CCGR2 |= CCM_CCGR2_GPIO3(CCM_CCGR_ON);
290 CCM_CCGR3 |= CCM_CCGR3_GPIO4(CCM_CCGR_ON);
294 *rx_pad = (5u << 3) | (3u << 6);
299 *tx_pad = (3u << 3) | (3u << 6);
301 *standard_mode |= mask;
303 *
reinterpret_cast<volatile uint32_t*
>(
304 reinterpret_cast<uintptr_t>(standard_output) + 0x88u) = mask;
306 const int rx_initial = digitalReadFast(rx_pin);
308 int rx_high_count = 0;
309 int rx_low_count = 0;
310 for (
int i = 0; i < 5; ++i) {
311 *
reinterpret_cast<volatile uint32_t*
>(
312 reinterpret_cast<uintptr_t>(standard_output) + 0x84u) = mask;
314 if (digitalReadFast(rx_pin) == HIGH) {
317 *
reinterpret_cast<volatile uint32_t*
>(
318 reinterpret_cast<uintptr_t>(standard_output) + 0x88u) = mask;
320 if (digitalReadFast(rx_pin) == LOW) {
325 probe.
set(
"standardGpioRxInitial",
326 rx_initial == HIGH ?
"HIGH" :
"LOW");
327 probe.
set(
"standardGpioRxHighCount",
static_cast<int64_t
>(rx_high_count));
328 probe.
set(
"standardGpioRxLowCount",
static_cast<int64_t
>(rx_low_count));
329 probe.
set(
"standardGpioConnected",
330 (rx_high_count == 5) && (rx_low_count == 5));
336 int fast_high_count = 0;
337 int fast_low_count = 0;
338 int fast_high_zero_delay = 0;
339 int fast_low_zero_delay = 0;
340 constexpr int kFastIters = 50;
341 for (
int i = 0; i < kFastIters; ++i) {
342 *
reinterpret_cast<volatile uint32_t*
>(
343 reinterpret_cast<uintptr_t>(standard_output) + 0x84u) = mask;
344 if (digitalReadFast(rx_pin) == HIGH) {
345 ++fast_high_zero_delay;
348 if (digitalReadFast(rx_pin) == HIGH) {
351 *
reinterpret_cast<volatile uint32_t*
>(
352 reinterpret_cast<uintptr_t>(standard_output) + 0x88u) = mask;
353 if (digitalReadFast(rx_pin) == LOW) {
354 ++fast_low_zero_delay;
357 if (digitalReadFast(rx_pin) == LOW) {
361 probe.
set(
"fastToggleHighSettled",
static_cast<int64_t
>(fast_high_count));
362 probe.
set(
"fastToggleLowSettled",
static_cast<int64_t
>(fast_low_count));
363 probe.
set(
"fastToggleHighZeroDelay",
364 static_cast<int64_t
>(fast_high_zero_delay));
365 probe.
set(
"fastToggleLowZeroDelay",
366 static_cast<int64_t
>(fast_low_zero_delay));
367 probe.
set(
"fastToggleIterations",
static_cast<int64_t
>(kFastIters));
370 if ((saved_fast_out & mask) != 0) {
371 *
reinterpret_cast<volatile uint32_t*
>(
372 reinterpret_cast<uintptr_t>(fast_output) + 0x84u) = mask;
374 *
reinterpret_cast<volatile uint32_t*
>(
375 reinterpret_cast<uintptr_t>(fast_output) + 0x88u) = mask;
377 if ((saved_standard_out & mask) != 0) {
378 *
reinterpret_cast<volatile uint32_t*
>(
379 reinterpret_cast<uintptr_t>(standard_output) + 0x84u) = mask;
381 *
reinterpret_cast<volatile uint32_t*
>(
382 reinterpret_cast<uintptr_t>(standard_output) + 0x88u) = mask;
384 *fast_mode = saved_fast_mode;
385 *standard_mode = saved_standard_mode;
387 *tx_pad = saved_tx_pad;
388 *tx_mux = saved_tx_mux;
389 *rx_pad = saved_rx_pad;
390 *rx_mux = saved_rx_mux;
392 probe.
set(
"success",
true);
406 metric.
set(
"requested",
true);
407 metric.
set(
"supported",
false);
408 metric.
set(
"driver", driver_name.
c_str());
410 if (iterations < 1) {
411 metric.
set(
"message",
"iterations must be >= 1");
414 if (tx_configs.
empty()) {
415 metric.
set(
"message",
"no channel configs");
418 for (fl::size i = 0; i < tx_configs.
size(); i++) {
419 if (tx_configs[i].isSpi()) {
420 metric.
set(
"message",
"clocked SPI timing metric is not supported");
427 for (fl::size i = 0; i < sample_configs.
size(); i++) {
429 sample_configs[i].getDataPin(),
431 sample_configs[i].mLeds,
432 sample_configs[i].rgb_order);
436 metric.
set(
"message",
"failed to create channel");
442 ScopedFastLedBrightness scoped_brightness(255);
443 for (fl::size lane = 0; lane < sample_configs.
size(); lane++) {
445 sample_configs[lane].mLeds.size(),
451 metric.
set(
"message",
"warmup wait timeout");
457 expectedClocklessWireUs(timing, maxLaneLeds(sample_configs));
462 uint32_t min_total_us = 0xFFFFFFFFu;
464 uint32_t min_overhead_us = 0xFFFFFFFFu;
466 uint64_t sum_show_us = 0;
467 uint64_t sum_wait_us = 0;
468 uint64_t sum_total_us = 0;
469 uint64_t sum_overhead_us = 0;
474 for (fl::size lane = 0; lane < sample_configs.
size(); lane++) {
479 sample_configs[lane].mLeds.size(),
486 const bool ok =
FastLED.wait(1000);
497 total_us > expected_wire_us ? total_us - expected_wire_us : 0;
499 if (show_us < min_show_us) min_show_us = show_us;
500 if (show_us > max_show_us) max_show_us = show_us;
501 if (wait_us < min_wait_us) min_wait_us = wait_us;
502 if (wait_us > max_wait_us) max_wait_us = wait_us;
503 if (total_us < min_total_us) min_total_us = total_us;
504 if (total_us > max_total_us) max_total_us = total_us;
505 if (overhead_us < min_overhead_us) min_overhead_us = overhead_us;
506 if (overhead_us > max_overhead_us) max_overhead_us = overhead_us;
508 sum_show_us += show_us;
509 sum_wait_us += wait_us;
510 sum_total_us += total_us;
511 sum_overhead_us += overhead_us;
517 metric.
set(
"supported",
true);
518 metric.
set(
"samples",
static_cast<int64_t
>(samples));
519 metric.
set(
"iterations",
static_cast<int64_t
>(iterations));
520 metric.
set(
"expected_wire_us",
static_cast<int64_t
>(expected_wire_us));
521 metric.
set(
"max_allowed_overhead_us",
522 static_cast<int64_t
>(max_allowed_overhead_us));
525 metric.
set(
"passed",
false);
526 metric.
set(
"message", timed_out ?
"timing wait timeout" :
"no samples");
530 metric.
set(
"min_show_us",
static_cast<int64_t
>(min_show_us));
531 metric.
set(
"max_show_us",
static_cast<int64_t
>(max_show_us));
532 metric.
set(
"avg_show_us",
533 static_cast<int64_t
>(sum_show_us /
static_cast<uint64_t
>(samples)));
534 metric.
set(
"min_wait_us",
static_cast<int64_t
>(min_wait_us));
535 metric.
set(
"max_wait_us",
static_cast<int64_t
>(max_wait_us));
536 metric.
set(
"avg_wait_us",
537 static_cast<int64_t
>(sum_wait_us /
static_cast<uint64_t
>(samples)));
538 metric.
set(
"min_total_us",
static_cast<int64_t
>(min_total_us));
539 metric.
set(
"max_total_us",
static_cast<int64_t
>(max_total_us));
540 metric.
set(
"avg_total_us",
541 static_cast<int64_t
>(sum_total_us /
static_cast<uint64_t
>(samples)));
542 metric.
set(
"min_overhead_us",
static_cast<int64_t
>(min_overhead_us));
543 metric.
set(
"max_overhead_us",
static_cast<int64_t
>(max_overhead_us));
544 metric.
set(
"avg_overhead_us",
545 static_cast<int64_t
>(sum_overhead_us /
static_cast<uint64_t
>(samples)));
547 out_passed = !
timed_out && max_overhead_us <= max_allowed_overhead_us;
548 metric.
set(
"passed", out_passed);
550 metric.
set(
"message",
"timing wait timeout");
552 metric.
set(
"message", out_passed ?
"tight timing within budget"
553 :
"tight timing exceeded budget");
568 if (!
args.is_object()) {
570 response.set(
"error",
"InvalidArgs");
571 response.set(
"message",
"Expected {driver, laneSizes, pattern?, iterations?, pinTx?, pinRx?, timing?}");
575 fl::json config =
args;
580 if (!config.
contains(
"driver") || !config[
"driver"].is_string()) {
582 response.set(
"error",
"MissingDriver");
583 response.set(
"message",
"Required field 'driver' (string) missing");
586 fl::string driver_name = config[
"driver"].
as_string().value();
587 const bool is_object_fled_driver = (driver_name ==
"OBJECT_FLED");
590 bool driver_found =
false;
591 for (fl::size i = 0; i <
mState->drivers_available.size(); i++) {
592 if (
mState->drivers_available[i].name == driver_name) {
599 response.set(
"error",
"UnknownDriver");
601 msg <<
"Driver '" << driver_name.
c_str() <<
"' not available";
607 if (!config.
contains(
"laneSizes") || !config[
"laneSizes"].is_array()) {
609 response.set(
"error",
"MissingLaneSizes");
610 response.set(
"message",
"Required field 'laneSizes' (array) missing");
614 fl::json lane_sizes_json = config[
"laneSizes"];
615 if (lane_sizes_json.
size() == 0 || lane_sizes_json.
size() > 16) {
617 response.set(
"error",
"InvalidLaneCount");
618 response.set(
"message",
"laneSizes must have 1-16 elements");
622 fl::vector<int> lane_sizes;
628 const int max_leds_per_lane =
mState->rx_buffer.size() / 32;
629 for (fl::size i = 0; i < lane_sizes_json.
size(); i++) {
630 if (!lane_sizes_json[i].is_int()) {
632 response.set(
"error",
"InvalidLaneSizeType");
634 msg <<
"laneSizes[" << i <<
"] must be integer";
638 int size =
static_cast<int>(lane_sizes_json[i].
as_int().value());
641 response.set(
"error",
"InvalidLaneSize");
643 msg <<
"laneSizes[" << i <<
"] = " << size <<
" must be > 0";
647 if (size > max_leds_per_lane) {
649 response.set(
"error",
"LaneSizeTooLarge");
651 msg <<
"laneSizes[" << i <<
"] = " << size <<
" exceeds max " << max_leds_per_lane;
661 fl::string pattern =
"MSB_LSB_A";
662 if (config.
contains(
"pattern") && config[
"pattern"].is_string()) {
663 pattern = config[
"pattern"].
as_string().value();
668 if (config.
contains(
"iterations") && config[
"iterations"].is_int()) {
669 iterations =
static_cast<int>(config[
"iterations"].
as_int().value());
670 if (iterations < 1) {
672 response.set(
"error",
"InvalidIterations");
673 response.set(
"message",
"iterations must be >= 1");
684 if (config.
contains(
"frameCount") && config[
"frameCount"].is_int()) {
685 frame_count =
static_cast<int>(config[
"frameCount"].
as_int().value());
686 if (frame_count < 1 || frame_count > 16) {
688 response.set(
"error",
"InvalidFrameCount");
689 response.set(
"message",
"frameCount must be in [1, 16]");
695 int pin_tx =
mState->pin_tx;
696 if (config.
contains(
"pinTx") && config[
"pinTx"].is_int()) {
697 pin_tx =
static_cast<int>(config[
"pinTx"].
as_int().value());
698 if (pin_tx < 0 || pin_tx > 48) {
700 response.set(
"error",
"InvalidPinTx");
701 response.set(
"message",
"pinTx must be 0-48");
707 int pin_rx =
mState->pin_rx;
708 if (config.
contains(
"pinRx") && config[
"pinRx"].is_int()) {
709 pin_rx =
static_cast<int>(config[
"pinRx"].
as_int().value());
710 if (pin_rx < 0 || pin_rx > 48) {
712 response.set(
"error",
"InvalidPinRx");
713 response.set(
"message",
"pinRx must be 0-48");
719 fl::string timing_name =
"WS2812B-V5";
720 if (config.
contains(
"timing") && config[
"timing"].is_string()) {
721 timing_name = config[
"timing"].
as_string().value();
725 bool use_legacy_api =
false;
726 if (config.
contains(
"useLegacyApi") && config[
"useLegacyApi"].is_bool()) {
727 use_legacy_api = config[
"useLegacyApi"].
as_bool().value();
730 bool legacy_rgbw =
false;
731 if (config.
contains(
"legacyRgbw")) {
732 if (!use_legacy_api) {
734 response.set(
"error",
"LegacyRgbwRequiresLegacyApi");
735 response.set(
"message",
"legacyRgbw requires useLegacyApi=true");
738 if (!config[
"legacyRgbw"].is_bool()) {
740 response.set(
"error",
"InvalidLegacyRgbw");
741 response.set(
"message",
"legacyRgbw must be a boolean");
744 legacy_rgbw = config[
"legacyRgbw"].
as_bool().value();
747 fl::vector<LegacyClocklessChipset> legacy_chipsets;
748 if (config.
contains(
"legacyChipsets") && !use_legacy_api) {
750 response.set(
"error",
"LegacyChipsetsRequireLegacyApi");
751 response.set(
"message",
"legacyChipsets requires useLegacyApi=true");
754 if (use_legacy_api) {
755 for (fl::size i = 0; i < lane_sizes.
size(); i++) {
758 if (config.
contains(
"legacyChipsets")) {
759 if (!config[
"legacyChipsets"].is_array()) {
761 response.set(
"error",
"InvalidLegacyChipsets");
762 response.set(
"message",
"legacyChipsets must be an array");
765 fl::json legacy_chipsets_json = config[
"legacyChipsets"];
766 if (legacy_chipsets_json.
size() != lane_sizes.
size()) {
768 response.set(
"error",
"InvalidLegacyChipsetCount");
769 response.set(
"message",
"legacyChipsets length must match laneSizes");
772 for (fl::size i = 0; i < legacy_chipsets_json.
size(); i++) {
773 if (!legacy_chipsets_json[i].is_string()) {
775 response.set(
"error",
"InvalidLegacyChipsetType");
776 response.set(
"message",
"legacyChipsets entries must be strings");
779 fl::string chipset_name =
780 legacy_chipsets_json[i].
as_string().value();
784 response.set(
"error",
"UnsupportedLegacyChipset");
787 "supported legacyChipsets: WS2812B, WS2814, WS2818, SK6812");
790 legacy_chipsets[i] = chipset;
795 const char* legacy_driver_name =
nullptr;
796 if (use_legacy_api) {
798 if (legacy_driver_name !=
nullptr &&
799 driver_name != legacy_driver_name) {
801 response.set(
"error",
"LegacyDriverMismatch");
803 msg <<
"Legacy templates are compile-time bound to "
804 << legacy_driver_name <<
", but the request selected "
805 << driver_name.
c_str();
812 bool contaminate_tx_mux =
false;
813 if (config.
contains(
"contaminateTxMux") &&
814 config[
"contaminateTxMux"].is_bool()) {
815 contaminate_tx_mux = config[
"contaminateTxMux"].
as_bool().value();
819 bool measure_tight_timing =
false;
820 if (config.
contains(
"tightTiming") && config[
"tightTiming"].is_bool()) {
821 measure_tight_timing = config[
"tightTiming"].
as_bool().value();
824 int tight_timing_iterations = 8;
825 if (config.
contains(
"tightTimingIterations") &&
826 config[
"tightTimingIterations"].is_int()) {
827 tight_timing_iterations =
828 static_cast<int>(config[
"tightTimingIterations"].
as_int().value());
829 if (tight_timing_iterations < 1 || tight_timing_iterations > 64) {
831 response.set(
"error",
"InvalidTightTimingIterations");
832 response.set(
"message",
"tightTimingIterations must be in [1, 64]");
837 uint32_t tight_timing_max_overhead_us = 2000;
838 if (config.
contains(
"tightTimingMaxOverheadUs") &&
839 config[
"tightTimingMaxOverheadUs"].is_int()) {
840 const int max_overhead =
841 static_cast<int>(config[
"tightTimingMaxOverheadUs"].
as_int().value());
842 if (max_overhead < 1) {
844 response.set(
"error",
"InvalidTightTimingMaxOverheadUs");
845 response.set(
"message",
"tightTimingMaxOverheadUs must be >= 1");
848 tight_timing_max_overhead_us =
static_cast<uint32_t>(max_overhead);
855 if (use_legacy_api) {
856 int max_pin = pin_tx + (int)lane_sizes.
size() - 1;
857 const bool historical_pin_range = pin_tx >= 0 && max_pin <= 8;
858#if AUTORESEARCH_LEGACY_SUPPORTS_PIN_22
859 const bool current_loopback_pin =
860 lane_sizes.
size() == 1 && pin_tx == 22;
862 const bool current_loopback_pin =
false;
864 if (!historical_pin_range && !current_loopback_pin) {
866 response.set(
"error",
"LegacyApiPinRange");
868 msg <<
"Legacy template API supports pins 0-8";
869#if AUTORESEARCH_LEGACY_SUPPORTS_PIN_22
870 msg <<
", plus single-lane pin 22 for current ObjectFLED loopback";
872 msg <<
", got pins " << pin_tx <<
"-" << max_pin;
882 fl::json standard_gpio_pad_probe;
883 if (is_object_fled_driver) {
884 standard_gpio_pad_probe = probeStandardGpioPad(pin_tx, pin_rx);
887 if (contaminate_tx_mux) {
888 if (!is_object_fled_driver) {
890 response.set(
"error",
"MuxContaminationDriverUnsupported");
891 response.set(
"message",
"contaminateTxMux is only supported for OBJECT_FLED");
894 if (!contaminateTxMuxWithPwm(pin_tx)) {
896 response.set(
"error",
"MuxContaminationUnsupported");
897 response.set(
"message",
"contaminateTxMux requires Teensy 4.x analogWriteFrequency support");
905 response.set(
"error",
"DriverSetupFailed");
907 msg <<
"Failed to set " << driver_name.
c_str() <<
" as exclusive driver";
918 fl::ChipsetTimingConfig resolved_timing;
920 if (use_legacy_api) {
922 if (!legacy_chipsets.
empty()) {
923 first_chipset = legacy_chipsets[0];
939 }
else if (timing_name ==
"UCS7604-800KHZ") {
942 }
else if (timing_name ==
"WS2811-400KHZ") {
951 }
else if (timing_name ==
"SK6812") {
956 }
else if (timing_name ==
"WS2818") {
963 fl::NamedTimingConfig timing_config(resolved_timing, timing_name.
c_str(), resolved_encoder);
966 fl::vector<fl::unique_ptr<fl::vector<CRGB>>> led_arrays;
967 fl::vector<fl::ChannelConfig> tx_configs;
971 bool is_spi_chipset_driver = (driver_name ==
"LCD_SPI" ||
972 driver_name ==
"I2S_SPI" ||
973 driver_name ==
"SPI_UNIFIED");
975 for (fl::size i = 0; i < lane_sizes.
size(); i++) {
977 if (is_spi_chipset_driver) {
983 int data_pin = pin_tx;
984 int clock_pin = pin_rx + 1;
991 fl::span<CRGB>(
leds->data(),
leds->size()),
997 timing_config.timing,
998 fl::span<CRGB>(
leds->data(),
leds->size()),
1006 fl::shared_ptr<fl::RxChannel> rx_channel_to_use =
mState->rx_channel;
1012 bool have_backend_override =
false;
1013 if (config.
contains(
"rxBackend") && config[
"rxBackend"].is_string()) {
1014 fl::string rb = config[
"rxBackend"].
as_string().value();
1015 if (rb ==
"RMT") { rx_backend_override =
fl::RxBackend::RMT; have_backend_override =
true; }
1016 else if (rb ==
"ISR") { rx_backend_override =
fl::RxBackend::ISR; have_backend_override =
true; }
1017 else if (rb ==
"I2S_RX") { rx_backend_override =
fl::RxBackend::I2S_RX; have_backend_override =
true; }
1018 else if (rb !=
"PLATFORM_DEFAULT") {
1020 response.set(
"error",
"UnknownRxBackend");
1021 response.set(
"message",
"rxBackend must be PLATFORM_DEFAULT, RMT, ISR or I2S_RX");
1030#if defined(FL_IS_ESP_32C6)
1031 constexpr bool is_esp32_c6 =
true;
1033 constexpr bool is_esp32_c6 =
false;
1036 rx_backend_override, have_backend_override,
1037 driver_name ==
"PARLIO", is_esp32_c6);
1039 have_backend_override =
true;
1059 StandingRxChannelSwap standing_rx_swap(
mState.get());
1060 if (have_backend_override) {
1061 standing_rx_swap.release(&rx_channel_to_use);
1062 fl::RxChannelConfig rx_cfg(pin_rx, rx_backend_override);
1064 if (!rx_channel_to_use) {
1066 response.set(
"error",
"RxChannelCreationFailed");
1067 response.set(
"message",
"Failed to create RX channel for requested rxBackend");
1070 }
else if (pin_rx !=
mState->pin_rx &&
mState->rx_factory) {
1071 standing_rx_swap.release(&rx_channel_to_use);
1072 rx_channel_to_use =
mState->rx_factory(pin_rx);
1073 if (!rx_channel_to_use) {
1075 response.set(
"error",
"RxChannelCreationFailed");
1076 response.set(
"message",
"Failed to create RX channel on custom pin");
1081#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
1098 rx_channel_to_use ? rx_channel_to_use->backend()
1109 constexpr fl::u32 kArmingLeadInNs = 64000;
1111 const fl::u32 bit_period_ns =
1117 fl::RxChannelConfig(pin_rx).signal_range_max_ns,
1118 kArmingLeadInNs, bit_period_ns);
1120 for (fl::size i = 0; i < lane_sizes.
size(); i++) {
1121 const fl::size
leds =
static_cast<fl::size
>(lane_sizes[i]);
1125 const bool lane_rgbw =
1127 (i < legacy_chipsets.
size() &&
1129 const fl::size wire_bytes =
1130 rpPioWireFrameBytes(
leds, timing_config.encoder, lane_rgbw);
1131 if (wire_bytes > max_wire_bytes) {
1133 response.set(
"error",
"LaneSizeTooLarge");
1135 msg <<
"laneSizes[" << i <<
"] = " << lane_sizes[i] <<
" puts "
1136 <<
static_cast<int>(wire_bytes) <<
" bytes on the wire at "
1137 << timing_name.
c_str() <<
"; PIO RX capture holds "
1138 <<
static_cast<int>(max_wire_bytes) <<
" ("
1139 <<
static_cast<int>(
1140 rpPioMaxLedsForWireBytes(max_wire_bytes,
1141 timing_config.encoder,
1152 fl::AutoResearchConfig autoresearch_config(
1153 timing_config.timing,
1156 driver_name.
c_str(),
1161 timing_config.encoder,
1162 fl::span<const LegacyClocklessChipset>(legacy_chipsets),
1169 bool passed =
false;
1171 fl::vector<fl::RunResult> run_results;
1173 bool tight_timing_passed =
true;
1186 fl::ScopedLogDisable quiet_logs;
1188 if (is_object_fled_driver) {
1189 fl::objectFledDiagnosticsReset();
1190 fl::objectFledDiagnosticsSetRxPin(pin_rx);
1193 if (use_legacy_api) {
1195 for (
int iter = 0; iter < iterations; iter++) {
1196 int iter_total = 0, iter_passed = 0;
1203 for (
int iter = 0; iter < iterations; iter++) {
1204 int iter_total = 0, iter_passed = 0;
1214 if (measure_tight_timing) {
1215 if (use_legacy_api) {
1216 tight_timing_passed =
false;
1217 tight_timing_response.
set(
"requested",
true);
1218 tight_timing_response.
set(
"supported",
false);
1219 tight_timing_response.
set(
"passed",
false);
1220 tight_timing_response.
set(
"driver", driver_name.
c_str());
1221 tight_timing_response.
set(
"message",
"legacy API timing metric is not supported");
1223 tight_timing_response = measureTightTiming(
1225 timing_config.timing,
1227 tight_timing_iterations,
1228 tight_timing_max_overhead_us,
1229 tight_timing_passed);
1231 bool tight_timing_supported =
false;
1232 if (tight_timing_response.
contains(
"supported") &&
1233 tight_timing_response[
"supported"].is_bool()) {
1234 tight_timing_supported =
1235 tight_timing_response[
"supported"].
as_bool().value();
1237 if (tight_timing_supported) {
1238 passed = passed && tight_timing_passed;
1243 int capture_evidence_bytes = 0;
1244 int capture_evidence_raw_edges = 0;
1245 for (fl::size ri = 0; ri < run_results.
size(); ri++) {
1246 const auto& rr = run_results[ri];
1247 if (rr.capturedBytes > 0) {
1248 capture_evidence_bytes += rr.capturedBytes;
1250 if (rr.rawEdgesAfterWait > capture_evidence_raw_edges) {
1251 capture_evidence_raw_edges = rr.rawEdgesAfterWait;
1260 response.set(
"captureEvidenceExpected",
true);
1261 response.set(
"captureEvidenceBytes",
1262 static_cast<int64_t
>(capture_evidence_bytes));
1263 response.set(
"captureEvidenceRawEdges",
1264 static_cast<int64_t
>(capture_evidence_raw_edges));
1265 response.set(
"duration_ms",
static_cast<int64_t
>(duration_ms));
1266 response.set(
"show_duration_ms",
static_cast<int64_t
>(show_duration_ms));
1268 response.set(
"requestedTxPin",
static_cast<int64_t
>(pin_tx));
1269 response.set(
"requestedRxPin",
static_cast<int64_t
>(pin_rx));
1270 response.set(
"actualTxPin",
static_cast<int64_t
>(pin_tx));
1271 response.set(
"actualRxPin",
static_cast<int64_t
>(
1272 rx_channel_to_use ? rx_channel_to_use->getPin() : pin_rx));
1273 fl::string capture_backend = rx_channel_to_use
1274 ? rx_channel_to_use->getEngineName()
1275 : fl::string(
"none");
1277 response.set(
"laneCount",
static_cast<int64_t
>(lane_sizes.
size()));
1278#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
1279 if (driver_name ==
"UART0" || driver_name ==
"UART1") {
1280 auto& uart = driver_name ==
"UART0"
1281 ? fl::BusTraits<fl::Bus::UART, 0>::instance()
1282 : fl::BusTraits<fl::
Bus::
UART, 1>::instance();
1283 response.set(
"rpUartStartAttempted", uart.lastStartAttempted());
1284 response.set(
"rpUartStartSucceeded", uart.lastStartSucceeded());
1286 static_cast<int64_t
>(uart.lastEncodedSize()));
1288 static_cast<int64_t
>(uart.lastActualBaud()));
1289 response.set(
"rpUartLastError", uart.lastError().c_str());
1291 if (driver_name ==
"PIO0" || driver_name ==
"PIO1") {
1292 auto& rp_pio = driver_name ==
"PIO0"
1293 ? fl::BusTraits<fl::Bus::FLEX_IO, 0>::instance()
1294 : fl::BusTraits<fl::
Bus::
FLEX_IO, 1>::instance();
1295 response.set(
"rpPioActive", rp_pio.isActive());
1296 response.set(
"rpPioLastError", rp_pio.lastError().c_str());
1297 response.set(
"rpPioStartAttempted", rp_pio.lastStartAttempted());
1298 response.set(
"rpPioStartSucceeded", rp_pio.lastStartSucceeded());
1299 response.set(
"rpPioWordCount",
static_cast<int64_t
>(rp_pio.lastWordCount()));
1301 #if defined(FL_IS_RP2350)
1302 if (driver_name ==
"PIO2") {
1303 auto& rp_pio = fl::BusTraits<fl::Bus::FLEX_IO, 2>::instance();
1304 response.set(
"rpPioActive", rp_pio.isActive());
1305 response.set(
"rpPioLastError", rp_pio.lastError().c_str());
1306 response.set(
"rpPioStartAttempted", rp_pio.lastStartAttempted());
1307 response.set(
"rpPioStartSucceeded", rp_pio.lastStartSucceeded());
1308 response.set(
"rpPioWordCount",
static_cast<int64_t
>(rp_pio.lastWordCount()));
1314 for (
int size : lane_sizes) {
1315 sizes_response.
push_back(
static_cast<int64_t
>(size));
1317 response.set(
"laneSizes", sizes_response);
1319 response.set(
"useLegacyApi", use_legacy_api);
1320 if (use_legacy_api) {
1321 if (legacy_driver_name !=
nullptr) {
1322 response.set(
"legacyDriver", legacy_driver_name);
1324 response.set(
"legacyRgbw", legacy_rgbw);
1330 response.set(
"legacyChipsets", legacy_chipsets_response);
1332 response.set(
"frameCount",
static_cast<int64_t
>(frame_count));
1333 response.set(
"contaminateTxMux", contaminate_tx_mux);
1334 if (contaminate_tx_mux) {
1335 response.set(
"contaminateTxMuxPin",
static_cast<int64_t
>(pin_tx));
1337 if (measure_tight_timing) {
1338 response.set(
"tightTiming", tight_timing_response);
1340 if (is_object_fled_driver) {
1341 response.set(
"objectFledDiagnostics",
1342 fl::objectFledDiagnosticsToJson());
1343 response.set(
"standardGpioPadProbe", standard_gpio_pad_probe);
1345#if defined(FL_IS_TEENSY_4X)
1353 const bool emit_flex_diagnostics = (driver_name ==
"OBJECT_FLED");
1354 if (emit_flex_diagnostics) {
1355 response.set(
"flexPwmRxDiagnostics",
1356 fl::FlexPwmRxChannel::diagnosticsToJson(pin_rx));
1358 if (emit_flex_diagnostics) {
1364 fl::FlexIODiagnostics fd{};
1365 fl::flexio_read_diagnostics(&fd);
1367 fj.
set(
"ctrl",
static_cast<int64_t
>(fd.ctrl));
1368 fj.
set(
"shiftstat",
static_cast<int64_t
>(fd.shiftstat));
1369 fj.
set(
"shifterr",
static_cast<int64_t
>(fd.shifterr));
1370 fj.
set(
"timstat",
static_cast<int64_t
>(fd.timstat));
1371 fj.
set(
"shiftsden",
static_cast<int64_t
>(fd.shiftsden));
1372 fj.
set(
"shiftctl0",
static_cast<int64_t
>(fd.shiftctl0));
1373 fj.
set(
"shiftcfg0",
static_cast<int64_t
>(fd.shiftcfg0));
1374 fj.
set(
"timctl0",
static_cast<int64_t
>(fd.timctl0));
1375 fj.
set(
"timcfg0",
static_cast<int64_t
>(fd.timcfg0));
1376 fj.
set(
"timcmp0",
static_cast<int64_t
>(fd.timcmp0));
1377 fj.
set(
"ccmCcgr3",
static_cast<int64_t
>(fd.ccm_ccgr3));
1378 fj.
set(
"ccmCscmr2",
static_cast<int64_t
>(fd.ccm_cscmr2));
1379 fj.
set(
"ccmCs1cdr",
static_cast<int64_t
>(fd.ccm_cs1cdr));
1380 fj.
set(
"muxRegValue",
static_cast<int64_t
>(fd.muxRegValue));
1381 fj.
set(
"padRegValue",
static_cast<int64_t
>(fd.padRegValue));
1382 fj.
set(
"tcdSaddr",
static_cast<int64_t
>(fd.tcd_saddr));
1383 fj.
set(
"tcdDaddr",
static_cast<int64_t
>(fd.tcd_daddr));
1384 fj.
set(
"tcdCiter",
static_cast<int64_t
>(fd.tcd_citer));
1385 fj.
set(
"tcdBiter",
static_cast<int64_t
>(fd.tcd_biter));
1386 fj.
set(
"tcdCsr",
static_cast<int64_t
>(fd.tcd_csr));
1387 fj.
set(
"initialized", fd.initialized);
1388 fj.
set(
"dmaComplete", fd.dmaComplete);
1389 response.set(
"flexIoDiagnostics", fj);
1398 const bool ws2814_legacy = use_legacy_api && !legacy_chipsets.empty() &&
1400 if ((!passed || ws2814_legacy) && !run_results.
empty()) {
1402 for (fl::size ri = 0; ri < run_results.
size(); ri++) {
1403 const auto& rr = run_results[ri];
1404 if (rr.passed && !ws2814_legacy)
continue;
1406 pat.
set(
"runNumber",
static_cast<int64_t
>(rr.run_number));
1407 pat.
set(
"totalLeds",
static_cast<int64_t
>(rr.total_leds));
1408 pat.
set(
"totalBytes",
static_cast<int64_t
>(rr.totalBytes));
1409 pat.
set(
"passed", rr.passed);
1414 pat.
set(
"mismatchedLeds",
static_cast<int64_t
>(rr.mismatches));
1415 pat.
set(
"capturedBytes",
static_cast<int64_t
>(rr.capturedBytes));
1416 pat.
set(
"captureWaitResult",
static_cast<int64_t
>(rr.captureWaitResult));
1417 pat.
set(
"rawEdgesAfterWait",
static_cast<int64_t
>(rr.rawEdgesAfterWait));
1418 pat.
set(
"decodeOk",
static_cast<int64_t
>(rr.decodeOk));
1419 pat.
set(
"decodeError",
static_cast<int64_t
>(rr.decodeError));
1420 pat.
set(
"decodeBytes",
static_cast<int64_t
>(rr.decodeBytes));
1421 pat.
set(
"decodeOutputCapacity",
1422 static_cast<int64_t
>(rr.decodeOutputCapacity));
1423 if (rr.rpPioGpioTransitions >= 0) {
1424 pat.
set(
"rpPioGpioTransitions",
1425 static_cast<int64_t
>(rr.rpPioGpioTransitions));
1427 if (!rr.rxBeginError.empty()) {
1428 pat.
set(
"rxBeginError", rr.rxBeginError.c_str());
1430 if (!rr.rawEdgeSample.empty()) {
1431 pat.
set(
"rawEdgeSample", rr.rawEdgeSample.c_str());
1433 pat.
set(
"captureFailed", rr.captureFailed);
1434 pat.
set(
"mismatchedBytes",
static_cast<int64_t
>(rr.mismatchedBytes));
1435 pat.
set(
"lsbOnlyErrors",
static_cast<int64_t
>(rr.lsbOnlyErrors));
1438 constexpr fl::size kMaxSerializedErrors = 5;
1439 if (!rr.errors.empty()) {
1441 const fl::size errLimit = rr.errors.size() < kMaxSerializedErrors
1443 : kMaxSerializedErrors;
1444 for (fl::size ei = 0; ei < errLimit; ei++) {
1445 const auto& e = rr.errors[ei];
1447 err.
set(
"led",
static_cast<int64_t
>(e.led_index));
1449 expected.
push_back(
static_cast<int64_t
>(e.expected_r));
1450 expected.
push_back(
static_cast<int64_t
>(e.expected_g));
1451 expected.
push_back(
static_cast<int64_t
>(e.expected_b));
1454 static_cast<int64_t
>(e.expected_w));
1456 err.
set(
"expected", expected);
1458 actual.
push_back(
static_cast<int64_t
>(e.actual_r));
1459 actual.
push_back(
static_cast<int64_t
>(e.actual_g));
1460 actual.
push_back(
static_cast<int64_t
>(e.actual_b));
1462 actual.
push_back(
static_cast<int64_t
>(e.actual_w));
1464 err.
set(
"actual", actual);
1467 pat.
set(
"errors", errs);
1468 pat.
set(
"totalErrors",
static_cast<int64_t
>(rr.errors.size()));
1472 response.set(
"patterns", patterns);
1474 run_results.
clear();
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
bool autoResearchSetExclusiveDriverByName(const char *name)
AutoResearch-style helper: set an exclusive driver by name.
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
void autoResearchChipsetTimingLegacy(fl::AutoResearchConfig &config, int &total, int &passed, uint32_t &out_show_duration_ms, fl::vector< fl::RunResult > *out_results=nullptr, int num_runs_per_pattern=1)
void autoResearchChipsetTiming(fl::AutoResearchConfig &config, int &total, int &passed, uint32_t &out_show_duration_ms, fl::vector< fl::RunResult > *out_results=nullptr, int num_runs_per_pattern=1)
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.
bool legacyClocklessChipsetFromName(const fl::string &name, LegacyClocklessChipset *out)
const char * legacyClocklessChipsetName(LegacyClocklessChipset chipset)
const char * legacyClocklessBoundDriverName()
Name of the driver to which public legacy templates are bound.
bool legacyClocklessChipsetHasAutomaticRgbw(LegacyClocklessChipset chipset)
fl::net::ble — BLE GATT transport layer for JSON-RPC
SPI encoder configuration for clocked LED chipsets.
fl::json runSingleTestImpl(const fl::json &args)
fl::shared_ptr< AutoResearchState > mState
static fl::RxChannelPtr addRx(const fl::RxChannelConfig &config)
Add an RX channel with runtime configuration.
fl::u8 getBrightness()
Get the current global brightness setting.
void setBrightness(fl::u8 scale)
Set the global brightness scaling.
const char * c_str() const FL_NO_EXCEPT
size_t size() const FL_NO_EXCEPT
fl::optional< fl::string > as_string() const FL_NO_EXCEPT
fl::optional< i64 > as_int() const FL_NO_EXCEPT
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
fl::optional< bool > as_bool() const FL_NO_EXCEPT
void push_back(const json &value) FL_NO_EXCEPT
bool contains(size_t idx) const FL_NO_EXCEPT
static json object() FL_NO_EXCEPT
static json array() FL_NO_EXCEPT
void reset() FL_NO_EXCEPT
string str() const FL_NO_EXCEPT
fl::size size() const FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
void clear() FL_NO_EXCEPT
void push_back(const T &value) FL_NO_EXCEPT
void fill_solid(CRGB *targetArray, int numToFill, const CRGB &color) FL_NO_EXCEPT
Fill a range of LEDs with a solid color.
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
UCS7604 LED chipset encoder implementation.
Task executor — runs registered task runners and manages the run loop.
@ CHANNELS
Remove all channels from controller list.
Platform-abstracted heap memory query functions.
FastLED's Elegant JSON Library: fl::json
RxBackend resolveCaptureBackend(RxBackend requested_backend, bool has_explicit_override, bool is_parlio_driver, bool is_esp32_c6) FL_NO_EXCEPT
Select the independent capture backend used by AutoResearch.
size_t rpPioMaxWireBytes(size_t edge_capacity, size_t dma_tail_words, u32 samples_per_dma_word, u32 sample_period_ns, u32 idle_tail_ns, u32 arming_lead_in_ns, u32 bit_period_ns) FL_NO_EXCEPT
Largest frame, in wire bytes, an RP PIO RX capture can hold.
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
ClocklessEncoder
Identifies which encoder to use for clockless chipsets in the Channel API.
@ CLOCKLESS_ENCODER_UCS7604_8BIT
UCS7604 8-bit 800KHz.
@ CLOCKLESS_ENCODER_TM1812_RGBWW
TM1812: two 5-byte RGBWW pixels in each 12-byte IC frame.
@ CLOCKLESS_ENCODER_UCS7604_16BIT
UCS7604 16-bit 800KHz.
@ CLOCKLESS_ENCODER_UCS7604_16BIT_1600
UCS7604 16-bit 1600KHz.
@ CLOCKLESS_ENCODER_WS2812
Default, no preamble (WS2812 and compatible)
@ CLOCKLESS_ENCODER_TM1908
TM1908: command prefix followed by RGB pixels.
void delay(u32 ms, bool run_async=true) FL_NO_EXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
constexpr size_t kRpPioRxDmaTailWords
Reset-tail words appended to the DMA transfer.
constexpr u32 kRpPioRxSamplesPerDmaWord
Pin samples packed into each 32-bit DMA word.
constexpr ChipsetTimingConfig makeTimingConfig() FL_NO_EXCEPT
Convert compile-time CHIPSET type to runtime timing config.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
size_t ucs7604FrameBytes(size_t num_leds, UCS7604Mode mode, bool is_rgbw) FL_NO_EXCEPT
Total wire bytes of a complete UCS7604 frame.
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
fl::u32 micros()
Universal microsecond timer - returns microseconds since system startup.
@ I2S_RX
Classic-ESP32 I2S 1-bit oversampling backend — DMA sampling at 16 MHz, capture length limited by RAM ...
@ PIO
RP2040/RP2350 PIO receive backend.
@ PLATFORM_DEFAULT
Use the recommended backend for the active platform (RMT on ESP32; FlexPWM on Teensy 4....
@ RMT
ESP32-only RMT capture backend.
@ ISR
Platform-neutral interrupt-driven edge capture backend when available.
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
constexpr size_t kRpPioRxEdgeCapacity
Phase slots in the shared capture pool.
Bus
Portable public bus selector.
constexpr u32 kRpPioRxClockHz
After synchronizing to the first rising edge the PIO runs one IN PINS instruction per cycle,...
void pinMode(int pin, PinMode mode)
Set pin mode (input, output, pull-up, pull-down)
UCS7604Mode
UCS7604 protocol configuration modes.
@ UCS7604_MODE_8BIT_800KHZ
@ UCS7604_MODE_16BIT_1600KHZ
@ UCS7604_MODE_16BIT_800KHZ
void analogWrite(int pin, u16 val)
Write analog value to pin (PWM)
void delayNanoseconds(fl::u32 ns) FL_NO_EXCEPT
Delay for a runtime number of nanoseconds.
constexpr ClocklessEncoder encoder_for() FL_NO_EXCEPT
Extract the encoder selector from a compile-time TIMING type.
size_t ucs7604BytesPerLed(UCS7604Mode mode, bool is_rgbw) FL_NO_EXCEPT
Wire bytes each LED contributes to a UCS7604 frame.
@ RMT
RMT-based receiver (ESP32)
void delayMicroseconds(u32 us)
Delay for a given number of microseconds.
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Configuration for a single LED channel.
constexpr u32 total_period_ns() const FL_NO_EXCEPT
Get total bit period (T1 + T2 + T3)
u32 reset_us
Reset/latch time (microseconds)
Runtime bit-period timing for a clockless chipset.
static SpiEncoder apa102(u32 clock_hz=6000000) FL_NO_EXCEPT
Create APA102 encoder configuration.