FastLED 3.9.15
Loading...
Searching...
No Matches

◆ capture()

size_t capture ( fl::shared_ptr< fl::RxChannel > rx_channel,
fl::span< uint8_t > rx_buffer,
const fl::ChipsetTimingConfig & timing,
const char * driver_name )

Definition at line 317 of file AutoResearchTest.cpp.

317 {
318 if (!rx_channel) {
319 FL_ERROR("RX channel is null");
320 return 0;
321 }
322
323 // Clear RX buffer
324 fl::memset(rx_buffer.data(), 0, rx_buffer.size());
325
326 // Prepare RX config (but don't arm yet to avoid locking TX resources)
327 fl::RxChannelConfig rx_config(rx_channel->getPin());
328 rx_config.hz = 40000000; // 40MHz for high-precision LED timing capture
329
330 // Buffer size: 1 LED byte = 8 bits = 8 RMT symbols
331 // UART with TX inversion produces standard WS2812 waveform, same symbol count
332 bool is_uart_driver = (fl::strcmp(driver_name, "UART") == 0);
333 rx_config.edge_capacity = rx_buffer.size() * 8;
334
335 // Internal loopback configuration: Enable ONLY for RMT TX -> RMT RX scenarios
336 // When driver_name == "RMT", enable io_loop_back to route RMT TX output to RMT RX internally
337 // This is REQUIRED for ESP32-S3 because TX GPIO output stops when RX is active on different GPIO
338 // For other drivers (PARLIO, SPI, UART, I2S), disable io_loop_back (use external GPIO wire)
339 bool is_rmt_driver = (fl::strcmp(driver_name, "RMT") == 0);
340 rx_config.io_loop_back = is_rmt_driver;
341 // RX DMA streaming: extends capture past the non-DMA cap by sizing
342 // mem_block_symbols = 14336 so ESP-IDF allocates ~14 DMA descriptor
343 // nodes (each 4092 bytes), yielding a 57 KB user-buffer cap — enough to
344 // capture ~583 WS2812B LEDs in a single rmt_receive() call. Safe for
345 // non-RMT TX paths (SPI, PARLIO, I2S, UART, LCD_*) that don't contend
346 // for the shared ESP32-S3 DMA slot. RMT loopback stays non-DMA.
347 // See issue #2254.
348 rx_config.use_dma = !is_rmt_driver;
349 if (is_rmt_driver) {
350 FL_WARN("[CAPTURE] RMT TX -> RMT RX: Internal loopback enabled (io_loop_back=true)");
351 } else {
352 // The RX peripheral is platform-dependent (RMT on ESP32, FlexPWM on
353 // Teensy 4, LPC_SCT on LPC845, …). Don't claim "RMT RX" on platforms
354 // that have no RMT — that label burned an hour of Teensy-4 debug
355 // (FastLED#3059). Just say "external RX" so the label stays correct
356 // regardless of backend.
357 FL_WARN("[CAPTURE] " << driver_name << " TX -> external RX: External GPIO wire (io_loop_back=false, use_dma=true)");
358 }
359
360 // Driver-aware capture strategy:
361 // - RMT: Two-TX approach (ESP32-S3 workaround - TX GPIO blocked when RX active)
362 // - PARLIO/SPI/other: Single-TX approach (arm RX first, then TX)
363
364 // TX wait timeout: 1 second max per frame - prevents infinite hang if driver stalls
365 // Even a 10000-LED strip at 800kbps takes only ~300ms, so 1s is very safe
366 const uint32_t TX_WAIT_TIMEOUT_MS = 1000;
367
368 if (is_rmt_driver) {
369 // RMT: Two-TX approach for ESP32-S3 compatibility
370 // First TX without RX armed (diagnostics), then arm RX, then second TX
371 FL_WARN("[CAPTURE] RMT: Two-TX approach (ESP32-S3 workaround)");
372 FastLED.show();
373 if (!FastLED.wait(TX_WAIT_TIMEOUT_MS)) {
374 FL_ERROR("[CAPTURE] TX wait timeout (pre-arm) - driver may be stalled");
375 return 0;
376 }
377
378 // Arm RX for capture
379 if (!rx_channel->begin(rx_config)) {
380 FL_ERROR("Failed to arm RX receiver");
381 return 0;
382 }
383 // Second TX with RX armed
384 FastLED.show();
385 if (!FastLED.wait(TX_WAIT_TIMEOUT_MS)) {
386 FL_ERROR("[CAPTURE] TX wait timeout (capture) - driver may be stalled");
387 return 0;
388 }
389 } else {
390 // Non-RMT (PARLIO, SPI, etc.): Single-TX approach
391 if (!rx_channel->begin(rx_config)) {
392 FL_WARN("[CAPTURE] RX begin() failed for pin " << rx_channel->getPin());
393 return 0;
394 }
395 FL_WARN("[CAPTURE] RX armed, calling FastLED.show()...");
396
397 FastLED.show();
398 FL_WARN("[CAPTURE] FastLED.show() returned, calling wait...");
399 if (!FastLED.wait(TX_WAIT_TIMEOUT_MS)) {
400 FL_WARN("[CAPTURE] FastLED.wait() timed out");
401 return 0;
402 }
403 FL_WARN("[CAPTURE] FastLED.wait() done");
404 }
405
406
407
408
409 // Wait for RX completion
410 // WS2812B: ~30μs per LED → 3000 LEDs = 90ms, use 150ms
411 // UART with inverted TX produces same waveform timing as WS2812
412 const uint32_t rx_wait_ms = is_uart_driver ? 500 : 150;
413 FL_WARN("[CAPTURE] Waiting for RX completion (" << rx_wait_ms << "ms timeout)...");
414 auto wait_result = rx_channel->wait(rx_wait_ms);
415 FL_WARN("[CAPTURE] RX wait returned: " << static_cast<int>(wait_result));
416
417 if (wait_result != fl::RxWaitResult::SUCCESS) {
418 FL_WARN("RX wait failed (timeout or no data received)");
419 fl::sstream ss;
420 ss << "\n⚠️ TROUBLESHOOTING:\n";
421 ss << " 1. Connect physical jumper wire from TX GPIO to RX GPIO " << rx_channel->getPin() << "\n";
422 ss << " 2. Check that both TX and RX pins are correctly configured\n";
423 ss << " 3. Verify the GPIO connection is working (GPIO baseline test should pass)\n";
424 ss << " 4. For RMT TX → RMT RX: Ensure io_loop_back=true in RxConfig";
425 FL_WARN(ss.str());
426 if (!is_uart_driver) {
427 return 0;
428 }
429 // UART: still attempt decode with captured edges (timeout may be expected)
430 FL_WARN("[CAPTURE] UART: attempting decode with captured edges despite timeout");
431 }
432
433 // UART with TX inversion at 4 Mbps produces a standard WS2812-compatible
434 // waveform on the wire. Each 10-bit UART frame (2500ns) encodes exactly 2
435 // LED bits with correct WS2812 timing:
436 // T0H=250ns, T0L=1000ns (LED bit "0")
437 // T1H=750ns, T1L=500ns (LED bit "1")
438 // Use the standard WS2812 decoder with UART-specific timing thresholds.
439 if (is_uart_driver) {
440 FL_WARN("[CAPTURE] UART (inverted TX): using standard WS2812 decoder with UART timing...");
441 // UART timing at 4 Mbps: T0H=250ns, T1H-T0H=500ns, T0L=1000ns
442 fl::ChipsetTiming uart_timing{
443 250, // T1 = T0H (1 UART bit = 250ns)
444 500, // T2 = T1H - T0H (3 bits - 1 bit = 2 bits = 500ns)
445 500, // T3 = T1L (2 UART bits = 500ns, stop + next start)
446 50, // reset_us (WS2812 minimum)
447 "UART_4Mbps"
448 };
449 // Use wider tolerance (250ns) for UART because the UART clock and RMT
450 // sample clock are asynchronous, and GPIO matrix adds ~10-20ns jitter
451 auto rx_timing = fl::make4PhaseTiming(uart_timing, 250);
452 rx_timing.gap_tolerance_ns = 100000; // 100µs for UART inter-frame gaps
453
454 FL_WARN("[CAPTURE] UART RX timing: T0H=" << uart_timing.T1
455 << " T1H=" << (uart_timing.T1 + uart_timing.T2)
456 << " T0L=" << (uart_timing.T2 + uart_timing.T3)
457 << " T1L=" << uart_timing.T3);
458
459 auto decode_result = rx_channel->decode(rx_timing, rx_buffer);
460 if (!decode_result.ok()) {
461 FL_WARN("[CAPTURE] UART decode failed (error: " << static_cast<int>(decode_result.error()) << ")");
462 dumpRawEdgeTiming(rx_channel, timing, fl::EdgeRange(0, 256));
463 return 0;
464 }
465 FL_WARN("[CAPTURE] UART decoded " << decode_result.value() << " LED bytes");
466 return decode_result.value();
467 }
468
469 // SPI chipset drivers (LCD_SPI, I2S_SPI): decode raw SPI bit stream
470 // These drivers use LCD_CAM I80 bus or I2S to output APA102 data.
471 // RMT RX captures edges on the data pin; clock pin is ignored.
472 bool is_lcd_spi_driver = (fl::strcmp(driver_name, "LCD_SPI") == 0);
473 bool is_i2s_spi_driver = (fl::strcmp(driver_name, "I2S_SPI") == 0);
474 if (is_lcd_spi_driver || is_i2s_spi_driver) {
475 // SPI clock used for validation: 2.4MHz (matches ValidationRemote.cpp)
476 const uint32_t spi_clock_hz = 2400000;
477 FL_WARN("[CAPTURE] SPI chipset decode: clock=" << spi_clock_hz << " Hz");
478 size_t decoded = decodeSpiEdges(rx_channel, rx_buffer, spi_clock_hz);
479 if (decoded == 0) {
480 FL_WARN("[CAPTURE] SPI decode failed");
481 dumpRawEdgeTiming(rx_channel, timing, fl::EdgeRange(0, 256));
482 }
483 return decoded;
484 }
485
486 // Decode received data directly into rx_buffer
487 // Create 4-phase RX timing from the TX timing
488 //
489 // Wave8 drivers (SPI, PARLIO, I2S) use 8-bit expansion encoding:
490 // Bit 0: round(T1/(T1+T2+T3)*8) HIGH pulses, rest LOW
491 // Bit 1: round((T1+T2)/(T1+T2+T3)*8) HIGH pulses, rest LOW
492 // The actual pulse widths are quantized to tick boundaries, so we must
493 // compute the exact wave8 timing for RX decode thresholds.
494 // Without this correction, the RX decoder may reject valid waveforms when
495 // the nominal timing period differs from the quantized 8-tick period.
496 // Example: WS2812_800KHZ has T0L=1000ns nominal but PARLIO wave8 produces 750ns.
497 //
498 // Clock sources differ by driver:
499 // SPI/I2S: clock = 8/(T1+T2+T3) Hz, tick = (T1+T2+T3)/8 ns (variable)
500 // PARLIO: clock = 8 MHz fixed, tick = 125 ns (fixed)
501 bool is_spi_driver = (fl::strcmp(driver_name, "SPI") == 0);
502 bool is_parlio_driver = (fl::strcmp(driver_name, "PARLIO") == 0);
503 bool is_i2s_driver = (fl::strcmp(driver_name, "I2S") == 0);
504 // LCD_CLOCKLESS auto-selects wave3 vs wave8 based on canUseWave3() of the
505 // chipset timing. Compute the same eligibility check on the host side so
506 // the RX decoder reconstructs the correct quantized waveform.
507 bool is_lcd_clockless_driver = (fl::strcmp(driver_name, "LCD_CLOCKLESS") == 0);
508 bool lcd_clockless_uses_wave3 = false;
509 if (is_lcd_clockless_driver) {
510 fl::ChipsetTiming probe{timing.t1_ns, timing.t2_ns, timing.t3_ns, timing.reset_us, timing.name};
511 lcd_clockless_uses_wave3 = fl::canUseWave3(probe);
512 }
513 bool uses_wave8 = is_spi_driver || is_parlio_driver || is_i2s_driver
514 || (is_lcd_clockless_driver && !lcd_clockless_uses_wave3);
515 bool uses_wave3 = is_lcd_clockless_driver && lcd_clockless_uses_wave3;
516 fl::ChipsetTiming tx_timing;
517 if (uses_wave8) {
518 // Compute actual wave8 timing from chipset timing
519 const uint32_t period = timing.t1_ns + timing.t2_ns + timing.t3_ns;
520 // PARLIO uses fixed 8MHz clock (125ns/tick), not derived from period
521 // SPI/I2S/LCD_CLOCKLESS derive clock from period: tick = period/8
522 const uint32_t tick_ns = is_parlio_driver ? 125 : (period / 8);
523 // Wave8 LUT computes: pulses = round(fraction * 8)
524 const uint32_t pulses_bit0 = static_cast<uint32_t>(
525 static_cast<float>(timing.t1_ns) / period * 8.0f + 0.5f);
526 const uint32_t pulses_bit1 = static_cast<uint32_t>(
527 static_cast<float>(timing.t1_ns + timing.t2_ns) / period * 8.0f + 0.5f);
528 // Convert back to 3-phase timing (T1=T0H, T2=T1H-T0H, T3=actual_period-T1H)
529 const uint32_t actual_t0h = pulses_bit0 * tick_ns;
530 const uint32_t actual_t1h = pulses_bit1 * tick_ns;
531 const uint32_t actual_period = 8 * tick_ns;
532 const char* wave8_name = is_spi_driver ? "SPI_wave8" :
533 is_parlio_driver ? "PARLIO_wave8" :
534 is_lcd_clockless_driver ? "LCD_CLOCKLESS_wave8" : "I2S_wave8";
535 tx_timing = fl::ChipsetTiming{
536 actual_t0h, // T1 = T0H
537 actual_t1h - actual_t0h, // T2 = T1H - T0H
538 actual_period - actual_t1h, // T3 = actual_period - T1H
539 timing.reset_us,
540 wave8_name
541 };
542 FL_WARN("[RX TIMING] " << wave8_name << ": pulses_bit0=" << pulses_bit0
543 << " pulses_bit1=" << pulses_bit1
544 << " tick_ns=" << tick_ns
545 << " -> T1=" << tx_timing.T1 << " T2=" << tx_timing.T2
546 << " T3=" << tx_timing.T3);
547 } else if (uses_wave3) {
548 // Wave3 encoding: 3 ticks per LED bit, clock = 3/(T1+T2+T3) Hz
549 const uint32_t period = timing.t1_ns + timing.t2_ns + timing.t3_ns;
550 const uint32_t tick_ns = period / 3;
551 const uint32_t ticks_bit0 = static_cast<uint32_t>(
552 static_cast<float>(timing.t1_ns) / period * 3.0f + 0.5f);
553 const uint32_t ticks_bit1 = static_cast<uint32_t>(
554 static_cast<float>(timing.t1_ns + timing.t2_ns) / period * 3.0f + 0.5f);
555 const uint32_t actual_t0h = ticks_bit0 * tick_ns;
556 const uint32_t actual_t1h = ticks_bit1 * tick_ns;
557 const uint32_t actual_period = 3 * tick_ns;
558 tx_timing = fl::ChipsetTiming{
559 actual_t0h,
560 actual_t1h - actual_t0h,
561 actual_period - actual_t1h,
562 timing.reset_us,
563 "LCD_CLOCKLESS_wave3"
564 };
565 FL_WARN("[RX TIMING] LCD_CLOCKLESS_wave3: ticks_bit0=" << ticks_bit0
566 << " ticks_bit1=" << ticks_bit1
567 << " tick_ns=" << tick_ns
568 << " -> T1=" << tx_timing.T1 << " T2=" << tx_timing.T2
569 << " T3=" << tx_timing.T3);
570 } else {
571 tx_timing = fl::ChipsetTiming{timing.t1_ns, timing.t2_ns, timing.t3_ns, timing.reset_us, timing.name};
572 }
573 // Wave8/wave3 encoding has timing jitter due to clock quantization and GPIO matrix latency
574 // Use wider tolerance (200ns) to accommodate clock rounding
575 const uint32_t tolerance = (uses_wave8 || uses_wave3) ? 200 : 170;
576 auto rx_timing = fl::make4PhaseTiming(tx_timing, tolerance);
577
578 // Enable gap tolerance for PARLIO/SPI DMA gaps
579 // PARLIO: ~20µs typical gaps during buffer transitions
580 // SPI: Can have longer inter-frame gaps due to software encoding timing
581 // Increased to 100µs to accommodate SPI driver timing variations
582 rx_timing.gap_tolerance_ns = 100000; // 100µs (was 30µs)
583
584 FL_WARN("[CAPTURE] Decoding...");
585 auto decode_result = rx_channel->decode(rx_timing, rx_buffer);
586
587 if (!decode_result.ok()) {
588 // Use FL_WARN instead of FL_ERROR to avoid triggering bash autoresearch exit
589 // This can happen during warmup/setup and is not fatal
590 FL_WARN("Decode failed (error code: " << static_cast<int>(decode_result.error()) << ")");
591 // Print raw edge timing on decode failure to diagnose the issue
592 dumpRawEdgeTiming(rx_channel, timing, fl::EdgeRange(0, 256));
593 return 0;
594 }
595
596 FL_WARN("[CAPTURE] Decoded " << decode_result.value() << " bytes");
597 return decode_result.value();
598}
void dumpRawEdgeTiming(fl::shared_ptr< fl::RxChannel > rx_channel, const fl::ChipsetTimingConfig &timing, fl::EdgeRange range)
Dump raw edge timing data to console for debugging.
static size_t decodeSpiEdges(fl::shared_ptr< fl::RxChannel > rx_channel, fl::span< uint8_t > rx_buffer, uint32_t clock_hz)
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
fl::result< u32, DecodeError > decode(const ChipsetTiming4Phase &timing, fl::span< u8 > out) FL_NOEXCEPT
bool begin(const RxChannelConfig &config) FL_NOEXCEPT
RxWaitResult wait(u32 timeout_ms) FL_NOEXCEPT
int getPin() const FL_NOEXCEPT
const T * data() const FL_NOEXCEPT
Definition span.h:461
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
string str() const FL_NOEXCEPT
Definition strstream.h:43
#define FL_WARN(X)
Definition log.h:276
#define FL_ERROR(X)
Definition log.h:219
fl::u32 uint32_t
Definition s16x16x4.h:219
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
ChipsetTiming4Phase make4PhaseTiming(const ChipsetTiming &timing_3phase, u32 tolerance_ns) FL_NOEXCEPT
Create 4-phase RX timing from 3-phase chipset timing with tolerance.
Definition rx.cpp.hpp:51
@ SUCCESS
Operation completed successfully.
Definition rx.h:152
int strcmp(const char *s1, const char *s2) FL_NOEXCEPT
FL_OPTIMIZE_FUNCTION bool canUseWave3(const ChipsetTiming &timing)
Check if a chipset timing is eligible for wave3 encoding.
Definition wave3.cpp.hpp:25
u32 T2
Additional high time for bit 1 (nanoseconds)
Definition led_timing.h:88
u32 T3
Low tail duration (nanoseconds)
Definition led_timing.h:89
u32 T1
High time for bit 0 (nanoseconds)
Definition led_timing.h:87
Generic chipset timing entry Provides T1, T2, T3 timing parameters in nanoseconds for any LED protoco...
Definition led_timing.h:86
u32 t1_ns
T0H: High time for bit 0 (nanoseconds)
u32 t2_ns
T1H-T0H: Additional high time for bit 1 (nanoseconds)
u32 reset_us
Reset/latch time (microseconds)
const char * name
Human-readable chipset name.
u32 t3_ns
T0L: Low tail duration (nanoseconds)
Edge range specification for getRawEdgeTimes() debugging.
Definition rx.h:56

References fl::canUseWave3(), fl::span< T, Extent >::data(), decodeSpiEdges(), dumpRawEdgeTiming(), fl::RxChannelConfig::edge_capacity, FastLED, FL_ERROR, FL_WARN, fl::RxChannelConfig::hz, fl::RxChannelConfig::io_loop_back, fl::make4PhaseTiming(), fl::memset(), fl::ChipsetTimingConfig::name, fl::ChipsetTimingConfig::reset_us, fl::span< T, Extent >::size(), fl::sstream::str(), fl::strcmp(), fl::SUCCESS, fl::ChipsetTiming::T1, fl::ChipsetTimingConfig::t1_ns, fl::ChipsetTiming::T2, fl::ChipsetTimingConfig::t2_ns, fl::ChipsetTiming::T3, fl::ChipsetTimingConfig::t3_ns, and fl::RxChannelConfig::use_dma.

Referenced by runMultiTest(), and runTest().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: