src/platforms/esp/32/drivers/rmt_rx/README.md
This driver provides RMT (Remote Control) receive functionality for ESP32 platforms. It enables capturing and decoding timing signals from external sources, particularly useful for validating LED strip output by reading back transmitted signals.
Primary Use Case: Hardware-in-the-loop validation of SPI/RMT TX LED output
GPIO Pin → RMT RX Peripheral → Symbol Buffer → Decoder → Byte Array
The driver provides:
wait()decode() method#include "platforms/esp/32/drivers/rmt_rx/rmt_rx_channel.h"
// Create RX channel (GPIO 6, 40MHz resolution, 1024 symbol buffer)
auto rx = fl::esp32::RmtRxChannel::create(6, 40000000, 1024);
// Initialize hardware
if (!rx->begin()) {
FL_WARN("RX channel init failed");
return;
}
// Wait for data (50ms timeout)
if (rx->wait(50) == fl::esp32::RmtRxWaitResult::SUCCESS) {
// Access received symbols
auto symbols = rx->getReceivedSymbols();
FL_DBG("Received " << symbols.size() << " symbols");
// Decode to bytes
fl::vector<uint8_t> bytes;
auto result = rx->decode(fl::esp32::CHIPSET_TIMING_WS2812B_RX,
fl::back_inserter(bytes));
if (result.ok()) {
FL_DBG("Decoded " << result.value() << " bytes");
}
}
// TX setup (existing FastLED)
#define TX_PIN 5
CRGB leds[10];
FastLED.addLeds<WS2812B, TX_PIN, GRB>(leds, 10);
// RX setup (use same pin with io_loop_back)
auto rx = fl::esp32::RmtRxChannel::create(TX_PIN, 40000000, 1024);
rx->begin();
// Test pattern
fill_solid(leds, 10, CRGB::Red);
// Manual control API (for precise timing)
RmtSymbol rx_symbols[1024];
rx->clear();
rx->startReceive(rx_symbols, 1024);
// Transmit
FastLED.show();
// Wait for RX completion
while (!rx->finished()) {
delayMicroseconds(10);
}
// Decode and validate
fl::vector<uint8_t> bytes;
auto result = rx->decode(fl::esp32::CHIPSET_TIMING_WS2812B_RX,
fl::back_inserter(bytes));
// Compare bytes against expected values...
ESP32 DevKit
┌─────────────┐
│ GPIO 5 (TX) ├──┬── Jumper Wire ──┐
│ │ │ │
│ GPIO 6 (RX) ├──┘ │
│ │ │
│ GND ├──── LED Strip GND │
└─────────────┘ │
│
LED Strip (WS2812B) │
┌─────────────┐ │
│ DIN ├─────────────────────┘
│ GND ├──── ESP32 GND
│ +5V ├──── External Power
└─────────────┘
Minimal Setup (TX→RX only):
Full Setup (with LED strip):
Constraints:
Recommended Pins:
RmtRxChannel(gpio_num_t pin, uint32_t resolution_hz = 40000000)
bool begin()
true on success, false on failurestartReceive()bool startReceive(rmt_symbol_word_t* buffer, size_t buffer_size)
true if receive started, false on errorbool isReceiveDone() const
true when ISR callback fires (receive done)size_t getReceivedSymbols() const
isReceiveDone() returns truevoid reset()
receive_done_ and symbols_received_ flagstypedef struct {
uint32_t duration0 : 15; // Duration of level0 (in ticks)
uint32_t level0 : 1; // Level of first half (0 or 1)
uint32_t duration1 : 15; // Duration of level1 (in ticks)
uint32_t level1 : 1; // Level of second half (0 or 1)
} rmt_symbol_word_t;
Example (WS2812B bit 0 at 40MHz):
Bit 0: T0H=400ns, T0L=850ns
Symbol: {duration0=16, level0=1, duration1=34, level1=0}
16 ticks × 25ns = 400ns high
34 ticks × 25ns = 850ns low
For LED protocols transmitting 24 bits (3 bytes) per pixel:
Symbols per LED = 24 bits × 1 symbol/bit = 24 symbols
Symbols for N LEDs = N × 24 symbols
Add 20% margin for reset pulses and overhead
Example (10 LEDs):
Minimum: 10 × 24 = 240 symbols
Recommended: 240 × 1.2 = 288 symbols
Rounded up: 300 symbols
sizeof(rmt_symbol_word_t) = 4 bytes
Buffer memory = buffer_size × 4 bytes
Examples:
256 symbols = 1 KB
512 symbols = 2 KB
1024 symbols = 4 KB
40MHz (default) - 25ns per tick
10MHz - 100ns per tick
80MHz - 12.5ns per tick
rmt_receive_config_t rx_params = {};
rx_params.signal_range_min_ns = 100; // Min pulse width: 100ns
rx_params.signal_range_max_ns = 100000000; // Max pulse width: 100ms
| Platform | RMT RX | DMA Support | Tested |
|---|---|---|---|
| ESP32 (classic) | ✅ Yes | ❌ No | ⚠️ Pending |
| ESP32-S2 | ✅ Yes | ❌ No | ⚠️ Pending |
| ESP32-S3 | ✅ Yes | ✅ Yes* | ⚠️ Pending |
| ESP32-C3 | ✅ Yes | ❌ No | ⚠️ Pending |
| ESP32-C6 | ✅ Yes | ❌ No | ⚠️ Pending |
| ESP32-H2 | ✅ Yes | ❌ No | ⚠️ Pending |
| ESP32-P4 | ✅ Yes | ✅ Yes* | ⚠️ Pending |
*DMA support is available but not yet enabled in this driver (v1.0 uses non-DMA mode for universal compatibility)
Error: "RX channel not initialized"
startReceive() before begin()begin() first and check return valueError: "Failed to create RX channel"
Error: Receive never completes (isReceiveDone() stays false)
Error: Buffer overflow (symbols truncated)
Enable FastLED debug logging to troubleshoot RX issues:
// In your sketch before any FastLED includes
#define FASTLED_DEBUG 1
#include <FastLED.h>
Example debug output:
RmtRxChannel constructed: pin=6 resolution=40000000Hz
RX channel created successfully
RX callbacks registered successfully
RX channel enabled
RX receive started (buffer size: 1024 symbols)
The receive callback runs in ISR context:
IRAM_ATTR to ensure code is in IRAMThe RmtRxChannel class is not thread-safe:
The destructor automatically releases the RMT channel:
{
RmtRxChannel rx(GPIO_NUM_6);
rx.begin();
// Use channel...
} // Channel automatically released here
docs/RMT_RX_ARCHITECTURE.md - System architecture and designexamples/Validation/README.md - Loopback validation examplesrc/platforms/esp/32/drivers/rmt/rmt_5/README.md - RMT TX encoder documentationv1.0 (Agent 2) - Initial implementation
v2.0 (Agent 3) - Decoder implementation
FastLED library - MIT License