Chapter 141: RMT (Remote Control) Module of ESP32
Chapter Objectives
After completing this chapter, you will be able to:
- Understand the architecture and purpose of the RMT peripheral.
- Configure RMT channels for transmitting pulse-based signals.
- Configure RMT channels for receiving pulse-based signals.
- Understand and use RMT symbols to define pulse sequences.
- Implement basic RMT TX and RX examples using ESP-IDF v5.x.
- Be aware of differences in RMT capabilities across various ESP32 variants.
- Troubleshoot common issues related to RMT usage.
Introduction
Many communication protocols, especially in remote controls (like infrared) or custom device signaling (like controlling addressable LEDs), rely on precisely timed sequences of high and low pulses. Standard serial interfaces like UART, I2C, or SPI are often not flexible enough to generate or capture these arbitrary pulse patterns. This is where the ESP32‘s Remote Control (RMT) peripheral shines.
The RMT module is a highly versatile peripheral designed for generating and receiving infrared remote control signals, but its capabilities extend far beyond that. It can be programmed to create virtually any sequence of digital pulses, making it invaluable for implementing custom communication protocols, controlling devices like WS2812 (NeoPixel) LEDs, or interfacing with sensors that use proprietary pulse-based communication.
This chapter will explore the fundamentals of the RMT peripheral, focusing on its use for generic pulse-based protocol handling. We will cover how to configure RMT channels for both transmission (TX) and reception (RX) of pulse sequences using the ESP-IDF v5.x drivers. Subsequent chapters will build upon this knowledge to implement specific protocols like Infrared and WS2812 control.
Theory
The RMT peripheral on ESP32 microcontrollers is essentially a pulse generator and receiver. It can be thought of as a small, programmable state machine that can switch a GPIO pin high or low for precise durations.
RMT Architecture Overview
The RMT peripheral consists of a configurable number of channels. Each channel can be independently configured as either a transmitter (TX) or a receiver (RX).
- Channels: These are the core operational units of the RMT peripheral. Each channel is associated with a GPIO pin.
- RMT Memory: Each channel has a dedicated block of RAM (RMTMEM) to store pulse definitions. This memory is limited, which dictates the maximum length of a pulse sequence that can be handled in a single operation.
- Clock Source: The RMT peripheral uses a clock source (e.g., APB_CLK, XTAL_CLK, PLL_F80M_CLK depending on the ESP32 variant and configuration) which is then divided to achieve a specific resolution for pulse durations. The
resolution_hz
parameter in the configuration determines the tick rate of the RMT channel. - RMT Symbols (Items): The pulse sequences are defined by RMT symbols. In ESP-IDF v5.x, the fundamental unit is
rmt_symbol_word_t
. Each such symbol can define up to two pulses (duration and level for the first pulse, and duration and level for the second pulse).
RMT Symbol Format
The core of RMT operation lies in how pulses are defined. An rmt_symbol_word_t
is a 32-bit structure:
typedef struct {
uint32_t duration0 : 15; // Duration of the first pulse in RMT ticks
uint32_t level0 : 1; // Logic level of the first pulse (0 or 1)
uint32_t duration1 : 15; // Duration of the second pulse in RMT ticks
uint32_t level1 : 1; // Logic level of the second pulse (0 or 1)
} rmt_symbol_word_t;
Field | Bits | Description |
---|---|---|
duration0 |
15 | Duration of the first pulse in RMT channel ticks. Max value: 215-1. |
level0 |
1 | Logic level of the first pulse (0 for low, 1 for high). |
duration1 |
15 | Duration of the second pulse in RMT channel ticks. Max value: 215-1. (Set to 0 if only one pulse is defined by this symbol). |
level1 |
1 | Logic level of the second pulse (0 for low, 1 for high). (Ignored if duration1 is 0). |
If a symbol only needs to define one pulse, the second part (duration1
, level1
) can be set to zero duration. A sequence of these symbols forms the complete waveform.
RMT Transmitter (TX)
When configured as a transmitter, an RMT channel takes a series of rmt_symbol_word_t
items and generates corresponding pulses on its assigned GPIO pin.
Key TX Configuration Parameters (rmt_tx_channel_config_t
):
Parameter | Type | Description |
---|---|---|
gpio_num |
gpio_num_t |
The GPIO pin number to use for RMT TX output. |
clk_src |
rmt_clock_source_t |
Clock source for the RMT channel (e.g., RMT_CLK_SRC_DEFAULT , RMT_CLK_SRC_APB , RMT_CLK_SRC_XTAL ). Default is usually APB clock. |
resolution_hz |
uint32_t |
Desired resolution of the RMT channel ticks in Hz. This determines the duration of one RMT tick (tick_duration = 1 / resolution_hz). Example: 1,000,000 Hz for 1µs resolution. |
mem_block_symbols |
size_t |
Number of rmt_symbol_word_t items that can be stored in the channel’s dedicated RAM. Typically a multiple of hardware block size (e.g., 64 for ESP32, 48 for ESP32-C3). If set to 0, driver usually allocates one block. |
trans_queue_depth |
size_t |
Size of the transaction queue. Allows multiple RMT transactions to be queued up and processed by the hardware/driver in the background. |
carrier_en |
bool |
Enable or disable carrier modulation for the output signal. Set to true to enable. |
carrier_freq_hz |
uint32_t |
Carrier frequency in Hz if carrier modulation is enabled. (e.g., 38000 for 38kHz IR). |
carrier_duty_percent |
uint8_t |
Duty cycle of the carrier wave in percent (0-100) if carrier modulation is enabled. |
carrier_level |
rmt_carrier_level_t |
Logic level on which the carrier is applied (RMT_CARRIER_LEVEL_LOW or RMT_CARRIER_LEVEL_HIGH ). |
flags.with_dma |
uint32_t (bit field) |
(ESP32 specific, may vary on other chips) Set to use DMA for RMT data transfer. Usually handled by driver. Note: ESP-IDF v5.x abstracts DMA usage. This flag might be less relevant or managed internally. Check specific IDF version docs. |
flags.invert_out |
uint32_t (bit field) |
Set to 1 to invert the RMT TX output signal. Default is 0 (not inverted). |
Encoders:
To make it easier to convert application data (like a byte array) into RMT symbols, the ESP-IDF RMT driver introduces the concept of encoders (rmt_encoder_t). For basic pulse generation where you define the symbols directly, a “copy encoder” is often used. This encoder simply copies the provided rmt_symbol_word_t items to the RMT hardware. Other encoders, like byte encoders or NEC protocol encoders, are available for more specific tasks (covered in later chapters).
Transmission Process:
- Install TX Channel: Allocate and configure an RMT TX channel using
rmt_new_tx_channel()
. - Create Encoder: Create an encoder, for instance, a copy encoder using
rmt_new_copy_encoder()
. - Enable Channel: Enable the RMT channel using
rmt_enable()
. - Transmit Data: Send the pulse sequence using
rmt_transmit()
. This function takes the channel handle, encoder handle, the data payload (array ofrmt_symbol_word_t
for a copy encoder), data length, and transmit configuration (rmt_transmit_config_t
).rmt_transmit_config_t
: Can specify options likeloop_count
for repeating the transmission.
graph TB subgraph "RMT TX Process" direction TB A["Application Data <br> (e.g., byte array, raw pulses)"] --> B{Encoder}; style A fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF B --> C["RMT Symbols <br> (rmt_symbol_word_t array)"]; style B fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E style C fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF C --> D["RMT Hardware TX Engine <br> (Channel Memory, Logic)"]; style D fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 subgraph "Optional Carrier Modulation" D --> D_CM{Carrier Enabled?}; style D_CM fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E D_CM -- Yes --> CM["Modulate with Carrier <br> (Freq, Duty, Level)"]; style CM fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF CM --> E[GPIO Output Pin]; D_CM -- No --> E; end E --> F((Pulse Waveform )); style E fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF % Process Node style F fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 % End/Success Node %% Styling classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; end
RMT Receiver (RX)
When configured as a receiver, an RMT channel listens for pulses on its assigned GPIO pin, measures their durations and logic levels, and stores this information as rmt_symbol_word_t
items in its memory.
Key RX Configuration Parameters (rmt_rx_channel_config_t
):
Parameter | Type | Description |
---|---|---|
gpio_num |
gpio_num_t |
The GPIO pin number to use for RMT RX input. |
clk_src |
rmt_clock_source_t |
Clock source for the RMT channel (e.g., RMT_CLK_SRC_DEFAULT ). Affects timing resolution. |
resolution_hz |
uint32_t |
Desired resolution for measuring incoming pulse durations in Hz. Determines the smallest time unit the RMT receiver can distinguish. |
mem_block_symbols |
size_t |
Number of rmt_symbol_word_t items that can be stored in the channel’s dedicated RAM. Determines how many pulses can be captured in one go. |
filter_en |
bool |
Enable or disable the input glitch filter. Set to true to enable. |
filter_threshold_us |
uint32_t |
If the filter is enabled, glitches shorter than this duration (in microseconds) will be ignored. The actual hardware threshold is in RMT source clock cycles; the driver converts this value. |
idle_threshold_us |
uint32_t |
Defines the maximum duration of an idle period (no signal changes on the input GPIO) in microseconds. If the line remains idle for longer than this, the RMT receiver considers the current reception complete. |
flags.invert_in |
uint32_t (bit field) |
Set to 1 to invert the RMT RX input signal before processing. Default is 0 (not inverted). |
flags.with_dma |
uint32_t (bit field) |
(ESP32 specific, may vary) Set to use DMA. Usually handled by driver in v5.x. |
Reception Process:
- Install RX Channel: Allocate and configure an RMT RX channel using
rmt_new_rx_channel()
. - Enable Channel: Enable the RMT channel using
rmt_enable()
. - Start Reception: Prepare the channel to receive data using
rmt_receive()
. This function takes the channel handle, a buffer to store received symbols, the buffer length, and receive configuration (rmt_receive_config_t
).rmt_receive_config_t
: Can specifysignal_range_min_ns
andsignal_range_max_ns
to help the driver determine if a received pulse is valid.
- Process Data: Once
rmt_receive()
returns (e.g., due to buffer full or idle timeout), the provided buffer will contain the receivedrmt_symbol_word_t
items. The application then needs to parse these symbols to interpret the received signal.
graph TD subgraph "RMT RX Process" direction TB A[Incoming Pulses on GPIO Pin] --> B{RMT Hardware RX Engine}; style A fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 style B fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 subgraph "Hardware Processing Stages" B --> B1{Optional Glitch Filter}; style B1 fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E B1 -- Filtered Signal --> B2[Edge Detection]; style B2 fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF B2 --> B3["Pulse Duration & Level Measurement <br> (using RMT clock & resolution)"]; style B3 fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF end B3 --> C["RMT Symbols <br> (rmt_symbol_word_t)"]; style C fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF C --> D{Store in RMT Channel Memory}; style D fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 D --> E{"Reception Complete? <br> (Buffer Full / Idle Timeout / Signal Range)"}; style E fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E E -- Yes --> F[Transfer Symbols to Application Buffer]; style F fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF F --> G(( Application Processes Data )); style G fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 E -- No --> B; %% Styling classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; end
RMT Channel Management
- Installation:
rmt_new_tx_channel()
orrmt_new_rx_channel()
initializes a channel. - Enabling/Disabling:
rmt_enable()
activates the channel for operations.rmt_disable()
deactivates it. - Deletion:
rmt_del_channel()
frees the resources associated with a channel. Encoders also need to be deleted usingrmt_del_encoder()
.
sequenceDiagram participant App as Application participant RMT as RMT Driver participant HW as Hardware Note over App,HW: TX Channel Lifecycle App->>RMT: rmt_new_tx_channel(config) RMT->>RMT: Allocate resources RMT-->>App: tx_channel_handle or ERROR opt Create Encoder (typical) App->>RMT: rmt_new_tx_encoder(config) RMT-->>App: encoder_handle or ERROR end App->>RMT: rmt_enable(tx_channel_handle) RMT->>HW: Configure and enable TX channel HW-->>RMT: Channel ready RMT-->>App: ESP_OK or ERROR loop Transmission Operations App->>RMT: rmt_transmit(tx_channel, encoder, data) RMT->>HW: Start transmission HW->>HW: Transmit data HW-->>RMT: TX complete event RMT-->>App: TX done callback/event end App->>RMT: rmt_disable(tx_channel_handle) RMT->>HW: Disable TX channel HW-->>RMT: Channel disabled RMT-->>App: ESP_OK or ERROR opt Delete Encoder App->>RMT: rmt_del_encoder(encoder_handle) RMT->>RMT: Free encoder resources RMT-->>App: ESP_OK end App->>RMT: rmt_del_channel(tx_channel_handle) RMT->>RMT: Free channel resources RMT-->>App: ESP_OK or ERROR Note over App,HW: RX Channel Lifecycle App->>RMT: rmt_new_rx_channel(config) RMT->>RMT: Allocate resources RMT-->>App: rx_channel_handle or ERROR App->>RMT: rmt_enable(rx_channel_handle) RMT->>HW: Configure and enable RX channel HW-->>RMT: Channel ready RMT-->>App: ESP_OK or ERROR loop Reception Operations App->>RMT: rmt_receive(rx_channel, buffer, size) RMT->>HW: Start reception HW->>HW: Receive data alt Data received HW-->>RMT: RX complete event RMT-->>App: RX done callback with data else Timeout HW-->>RMT: Timeout event RMT-->>App: Timeout callback end end App->>RMT: rmt_disable(rx_channel_handle) RMT->>HW: Disable RX channel HW-->>RMT: Channel disabled RMT-->>App: ESP_OK or ERROR App->>RMT: rmt_del_channel(rx_channel_handle) RMT->>RMT: Free channel resources RMT-->>App: ESP_OK or ERROR
Practical Examples
Let’s explore how to use the RMT peripheral with practical code examples. Ensure your ESP-IDF project is configured, and you have a way to observe the output (like a logic analyzer or oscilloscope) or provide input signals.
Example 1: Transmitting a Simple Pulse Sequence
This example will configure an RMT TX channel to generate a repetitive square wave.
1. Project Setup:
Create a new ESP-IDF project or use an existing one. Add the following to your main/CMakeLists.txt if not already present:
idf_component_register(...
REQUIRES esp_log rmt
...)
2. Code (main.c
):
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h"
#include "driver/gpio.h"
#define RMT_TX_GPIO_NUM GPIO_NUM_18 // Choose any suitable GPIO
#define RMT_RESOLUTION_HZ 1000000 // 1MHz resolution, 1 tick = 1us
#define RMT_MEM_BLOCK_SYMBOLS 64 // ESP32/S3/S2 typically have 64 symbols per block. C3/C6/H2 might have 48.
static const char *TAG = "RMT_TX_EXAMPLE";
void app_main(void)
{
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_tx_channel_config_t tx_chan_config = {
.gpio_num = RMT_TX_GPIO_NUM,
.clk_src = RMT_CLK_SRC_DEFAULT, // Use default clock source (usually APB CLK)
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS,
.trans_queue_depth = 4, // Number of transactions that can be pending in the background
};
rmt_channel_handle_t tx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_channel));
ESP_LOGI(TAG, "Install RMT copy encoder");
rmt_copy_encoder_config_t copy_encoder_config = {}; // Default config
rmt_encoder_handle_t copy_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_copy_encoder(©_encoder_config, ©_encoder));
ESP_LOGI(TAG, "Enable RMT TX channel");
ESP_ERROR_CHECK(rmt_enable(tx_channel));
// Define the pulse sequence (e.g., a 500us high, 500us low square wave)
rmt_symbol_word_t raw_symbols[] = {
{.duration0 = 500, .level0 = 1, .duration1 = 500, .level1 = 0}, // 500us high, 500us low
};
rmt_transmit_config_t transmit_config = {
.loop_count = 0, // 0 means no loop (transmit once). -1 means infinite loop.
// For older IDF versions, 0 might mean infinite loop. Check docs for your IDF version.
// For ESP-IDF v5.x, 0 means transmit once. Use -1 for infinite.
};
ESP_LOGI(TAG, "Start transmitting a single pattern");
ESP_ERROR_CHECK(rmt_transmit(tx_channel, copy_encoder, raw_symbols, sizeof(raw_symbols), &transmit_config));
ESP_LOGI(TAG, "Transmission of single pattern initiated.");
// Wait for the transmission to complete (optional, for finite transmissions)
// rmt_tx_wait_all_done(tx_channel, portMAX_DELAY); // Uncomment if loop_count is 0 and you want to wait
// Example: Transmit with infinite loop
transmit_config.loop_count = -1; // Infinite loop
ESP_LOGI(TAG, "Start transmitting with infinite loop");
ESP_ERROR_CHECK(rmt_transmit(tx_channel, copy_encoder, raw_symbols, sizeof(raw_symbols), &transmit_config));
ESP_LOGI(TAG, "Infinite transmission initiated. Observe GPIO %d", RMT_TX_GPIO_NUM);
// The task can do other things or sleep here.
// For this example, we'll just let it run.
// To stop: rmt_disable(tx_channel); rmt_del_encoder(copy_encoder); rmt_del_channel(tx_channel);
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
3. Build and Flash:
Use the VS Code ESP-IDF extension to build and flash the project onto your ESP32 board.
4. Observe:
Connect a logic analyzer or oscilloscope to the GPIO pin defined by RMT_TX_GPIO_NUM (GPIO18 in this example). You should see a square wave with a period of 1ms (500µs high, 500µs low), repeating if loop_count is set to -1.
Tip: The
rmt_tx_wait_all_done()
function can be used to block until all queued RMT transmissions are complete. This is useful when you are not using looping and need to know when the signal has been fully sent.
Example 2: Receiving a Pulse Sequence
This example will configure an RMT RX channel to receive pulses and print their durations and levels. You’ll need to provide a signal to the RX GPIO pin (e.g., by connecting the TX pin from Example 1 to the RX pin of this example, or using a function generator).
1. Project Setup:
Similar to Example 1. Ensure rmt component is linked.
2. Code (main.c
):
#include <stdio.h>
#include <string.h> // For memset
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h" // For rmt_rx_done_event_data_t if using event queue
#include "esp_log.h"
#include "driver/rmt_rx.h"
#include "driver/gpio.h"
#define RMT_RX_GPIO_NUM GPIO_NUM_19 // Choose any suitable GPIO
#define RMT_RESOLUTION_HZ 1000000 // 1MHz resolution, 1 tick = 1us
#define RMT_MEM_BLOCK_SYMBOLS 64
#define RMT_RX_BUFFER_SIZE (RMT_MEM_BLOCK_SYMBOLS * 4) // Size of buffer for received RMT symbols (in bytes)
// Each symbol is 4 bytes.
#define RMT_RX_TIMEOUT_US 1000000 // 1 second RX timeout
static const char *TAG = "RMT_RX_EXAMPLE";
// Buffer to store received RMT symbols
static rmt_symbol_word_t rx_symbols_buffer[RMT_MEM_BLOCK_SYMBOLS]; // Max symbols channel can hold at once
void app_main(void)
{
ESP_LOGI(TAG, "Create RMT RX channel");
rmt_rx_channel_config_t rx_chan_config = {
.gpio_num = RMT_RX_GPIO_NUM,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS,
// .flags.invert_in = true, // Uncomment to invert input signal if needed
.filter_en = false, // Disable filter for this basic example
// .filter_threshold_us = 10, // 10us filter, glitches shorter than this are ignored (if filter_en=true)
.idle_threshold_us = RMT_RX_TIMEOUT_US, // Max idle time before RX finishes
};
rmt_channel_handle_t rx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_channel));
ESP_LOGI(TAG, "Enable RMT RX channel");
ESP_ERROR_CHECK(rmt_enable(rx_channel));
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1250, // Shortest pulse we expect (e.g., 1.25us). Depends on protocol.
// Adjust based on your signal characteristics.
.signal_range_max_ns = 120000000, // Longest pulse we expect (e.g., 120ms).
// Adjust based on your signal characteristics.
};
size_t received_size = 0;
ESP_LOGI(TAG, "Starting RMT reception. Apply signal to GPIO %d", RMT_RX_GPIO_NUM);
while (1) {
// Clear buffer before next reception
memset(rx_symbols_buffer, 0, sizeof(rx_symbols_buffer));
// Start reception
// This call is blocking until data is received or timeout occurs.
// For non-blocking, you would typically use the event queue or run this in a separate task.
esp_err_t ret = rmt_receive(rx_channel, rx_symbols_buffer, sizeof(rx_symbols_buffer), &receive_config, &received_size);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Received %d bytes (%d symbols):", received_size, received_size / sizeof(rmt_symbol_word_t));
for (size_t i = 0; i < received_size / sizeof(rmt_symbol_word_t); i++) {
ESP_LOGI(TAG, "Symbol %2d: L0: %d, D0: %5d us | L1: %d, D1: %5d us", i,
rx_symbols_buffer[i].level0, rx_symbols_buffer[i].duration0,
rx_symbols_buffer[i].level1, rx_symbols_buffer[i].duration1);
}
} else if (ret == ESP_ERR_TIMEOUT) {
ESP_LOGW(TAG, "RMT RX Timeout");
} else {
ESP_LOGE(TAG, "RMT RX Error: %s", esp_err_to_name(ret));
// Potentially re-enable or re-initialize channel if error is persistent
// For simplicity, we break here on other errors.
// In a real app, you might need rmt_disable() and rmt_enable() or full reinit.
break;
}
// Add a small delay to allow logging and prevent tight loop on continuous signal
vTaskDelay(pdMS_TO_TICKS(100));
}
// Cleanup
ESP_LOGI(TAG, "Disabling and deleting RMT RX channel and encoder");
ESP_ERROR_CHECK(rmt_disable(rx_channel));
ESP_ERROR_CHECK(rmt_del_channel(rx_channel));
}
3. Build and Flash:
Build and flash this code to another ESP32 board, or modify your setup to provide a signal to RMT_RX_GPIO_NUM.
4. Test:
Apply a pulse sequence to the RX GPIO pin. For instance, connect the TX pin (GPIO18) from Example 1 (running on one board) to the RX pin (GPIO19) of Example 2 (running on another board, or the same board if you manage GPIOs carefully). You should see the received RMT symbols printed in the serial monitor, matching the transmitted pattern.
Warning: When connecting TX of one ESP32 to RX of another, ensure they share a common ground (GND).
Variant Notes
The RMT peripheral is available on most ESP32 variants, but there are differences in the number of channels, memory per channel, and available clock sources.
Feature | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | ESP32-C6 | ESP32-H2 |
---|---|---|---|---|---|---|
Total RMT Channels | 8 | 4 | 8 (4 TX, 4 RX) | 4 (2 TX, 2 RX) | 4 (2 TX, 2 RX) | 4 (2 TX, 2 RX) |
Channel Type | TX/RX Configurable | TX/RX Configurable | Dedicated TX/RX | Dedicated TX/RX | Dedicated TX/RX | Dedicated TX/RX |
Memory per Channel (Symbols) | 64 | 64 | 64 | 48 (Typical) | 48 (Typical) | 48 (Typical) |
Typical Default Clock Source | APB (80MHz) | APB (80MHz) | APB (80MHz) | APB (80MHz) / XTAL | PLL_F80M / XTAL | PLL_F64M / XTAL |
mem_block_symbols Note |
Actual number of symbols per hardware block. Driver may allow requesting more if SRAM backing (CONFIG_RMT_MEM_SRC_SRAM) is supported and enabled on certain chips. | |||||
Clock Sources | Refer to SOC_RMT_SUPPORTED_CLK_SRC_MASK and datasheet for specific chip. Options like APB, XTAL, PLL-derived clocks vary. |
Key Implications of Variants:
- Channel Availability: The number of available RMT channels might limit the number of simultaneous pulse-based protocols you can handle.
- Dedicated vs. Shared Channels: Newer variants (S3, C3, C6, H2) have dedicated TX and RX channels. This simplifies configuration as you don’t choose the mode for a generic channel; you pick a specific TX or RX channel. The ESP-IDF driver handles this abstraction through
rmt_new_tx_channel
andrmt_new_rx_channel
. - Memory per Channel (
mem_block_symbols
): This affects the maximum length of a continuous pulse sequence that can be transmitted or received in one go without CPU intervention (e.g., using ping-pong buffering or interrupts for longer streams). The ESP-IDF driver allows you to specifymem_block_symbols
inrmt_tx_channel_config_t
andrmt_rx_channel_config_t
. If set to 0 or a value smaller than one hardware block, the driver usually allocates one hardware memory block (e.g., 64 symbols for ESP32, 48 for ESP32-C6). You can request more symbols if the hardware supports multiple blocks per channel or ifCONFIG_RMT_MEM_SRC_SRAM
is enabled (for some chips, allowing RMT data to be in general SRAM). - Clock Sources and Resolution: The available clock sources and their frequencies impact the achievable
resolution_hz
and the maximum pulse duration. Always verify theSOC_RMT_SUPPORTED_CLK_SRC_MASK
for your target chip.
Tip: Always refer to the ESP-IDF documentation and the specific datasheet for your ESP32 variant for the most accurate information on RMT capabilities. The
SOC_CAPS
(SoC capabilities) header files in ESP-IDF (e.g.,esp_rom/efuse/esp32s3/esp_efuse_table.csv
orsoc/esp32/include/soc/rmt_caps.h
) also provide hardware-specific details.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Incorrect GPIO Configuration | No output/input signal, erratic behavior, boot issues (if strap pin used incorrectly). |
Solution:
|
Misunderstanding RMT Resolution & Durations | Pulses are too short/long, incorrect frequency, protocol timing violations. |
Solution:
|
RMT Memory Limitations (mem_block_symbols ) |
ESP_ERR_NO_MEM during channel creation, truncated signals, TX/RX failures for long sequences. |
Solution:
|
Incorrect RX Idle Threshold or Filter Settings | RX: Premature reception end (idle_threshold_us too short), delays in reception (idle_threshold_us too long), valid short pulses missed or noise captured (filter_threshold_us ). |
Solution:
|
Forgetting to Enable Channel | No TX output or RX input, ESP_ERR_INVALID_STATE from rmt_transmit() or rmt_receive() . |
Solution: Always call rmt_enable(channel_handle) after successful channel and encoder (for TX) creation, and before any TX/RX operations.
|
Encoder Issues (TX) | Incorrect waveform generated, data not matching input, ESP_ERR_INVALID_ARG from rmt_transmit() . |
Solution:
|
Resource Leaks | ESP_ERR_NO_MEM on subsequent channel creations, eventual system instability. |
Solution:
|
Clock Source Configuration | Incorrect pulse timings, RMT not working as expected, especially after deep sleep or frequency scaling. |
Solution:
|
Exercises
- Modified Pulse Transmitter:
- Modify Example 1 to transmit a specific pattern: a 100µs high pulse, followed by a 200µs low pulse, then a 300µs high pulse, and finally a 400µs low pulse. Transmit this pattern 5 times, then stop.
- Verify the output using a logic analyzer.
- Simple Pulse Decoder:
- Using Example 2 as a base, write code that receives RMT symbols.
- If it detects a sequence of:
- Pulse 1: High, duration between 400µs and 600µs
- Pulse 2: Low, duration between 400µs and 600µs
- (These two pulses would come from one
rmt_symbol_word_t
ifduration0
andduration1
are used)
- Then, log “Specific pattern detected!” to the console.
- Test this by sending the appropriate signal (e.g., from a modified Example 1 or a function generator).
- RMT Loopback Test:
- Combine Example 1 (TX) and Example 2 (RX) into a single application running on one ESP32 board.
- Configure one RMT TX channel on a GPIO (e.g., GPIO18) and one RMT RX channel on another GPIO (e.g., GPIO19).
- Connect GPIO18 to GPIO19 with a jumper wire.
- Transmit a known sequence of pulses from the TX channel.
- Receive the pulses on the RX channel and verify that the received symbols match the transmitted ones. Log the comparison result.
Summary
- The RMT peripheral is designed for generating and receiving precise pulse-based digital signals.
- It operates using channels, each configurable as TX or RX, and associated with a GPIO.
- Pulse sequences are defined by
rmt_symbol_word_t
items, each specifying up to two pulse durations and levels. - The ESP-IDF v5.x RMT driver uses handles for channels (
rmt_channel_handle_t
) and encoders (rmt_encoder_handle_t
). - TX Process: Involves configuring a TX channel, creating an encoder (e.g., copy encoder), enabling the channel, and calling
rmt_transmit()
. - RX Process: Involves configuring an RX channel, enabling it, and calling
rmt_receive()
to capture incoming pulses into a buffer ofrmt_symbol_word_t
. - Key configuration parameters include GPIO number, clock source, resolution (
resolution_hz
), memory allocation (mem_block_symbols
), and protocol-specific timings (idle threshold, filters for RX; carrier for TX). - Different ESP32 variants have varying numbers of RMT channels, memory capacities, and clocking options. Always consult the documentation for your specific chip.
- Proper error checking and resource management (deleting channels and encoders when done) are crucial.
Further Reading
- ESP-IDF RMT Driver Documentation:
- ESP-IDF RMT API Reference (Replace
esp32
with your target chip, e.g.,esp32s3
,esp32c3
, andv5.1
with your IDF version if different).
- ESP-IDF RMT API Reference (Replace
- ESP32 Technical Reference Manual: (Search for your specific ESP32 variant’s TRM)
- The TRM provides in-depth hardware details about the RMT peripheral. This can be found on the Espressif website.