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:

C
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:

  1. Install TX Channel: Allocate and configure an RMT TX channel using rmt_new_tx_channel().
  2. Create Encoder: Create an encoder, for instance, a copy encoder using rmt_new_copy_encoder().
  3. Enable Channel: Enable the RMT channel using rmt_enable().
  4. Transmit Data: Send the pulse sequence using rmt_transmit(). This function takes the channel handle, encoder handle, the data payload (array of rmt_symbol_word_t for a copy encoder), data length, and transmit configuration (rmt_transmit_config_t).
    • rmt_transmit_config_t: Can specify options like loop_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:

  1. Install RX Channel: Allocate and configure an RMT RX channel using rmt_new_rx_channel().
  2. Enable Channel: Enable the RMT channel using rmt_enable().
  3. 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 specify signal_range_min_ns and signal_range_max_ns to help the driver determine if a received pulse is valid.
  4. Process Data: Once rmt_receive() returns (e.g., due to buffer full or idle timeout), the provided buffer will contain the received rmt_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() or rmt_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 using rmt_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:

Plaintext
idf_component_register(...
                    REQUIRES esp_log rmt
                    ...)

2. Code (main.c):

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(&copy_encoder_config, &copy_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):

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 and rmt_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 specify mem_block_symbols in rmt_tx_channel_config_t and rmt_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 if CONFIG_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 the SOC_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 or soc/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:
  • Verify GPIO is RMT-capable using the datasheet for your ESP32 variant.
  • Ensure GPIO is not a critical strap pin or used by another peripheral (e.g., JTAG, SPI flash).
  • Check if external pull-ups/downs are needed (though RMT driver often handles basic setup).
  • Use gpio_reset_pin() before RMT initialization if reconfiguring a pin.
Misunderstanding RMT Resolution & Durations Pulses are too short/long, incorrect frequency, protocol timing violations. Solution:
  • Ensure resolution_hz is appropriate for desired precision. (e.g., 1MHz for 1µs ticks).
  • Remember durationX in rmt_symbol_word_t is in RMT ticks: duration_ticks = time_seconds * resolution_hz.
  • Verify calculations and output with an oscilloscope or logic analyzer.
  • Max duration for one segment is (215-1) ticks. For longer pulses, use multiple symbols or adjust resolution.
RMT Memory Limitations (mem_block_symbols) ESP_ERR_NO_MEM during channel creation, truncated signals, TX/RX failures for long sequences. Solution:
  • Ensure mem_block_symbols is within hardware limits (e.g., 64 for ESP32, 48 for ESP32-C3 per block).
  • For sequences longer than available memory:
    • TX: Use trans_queue_depth to queue multiple smaller transactions, or use TX DONE events/interrupts to feed next chunks.
    • RX: Use RX DONE/threshold events to process received data in chunks and re-start reception.
  • Consider CONFIG_RMT_MEM_SRC_SRAM for chips that support it, to use system SRAM for RMT symbols (check ESP-IDF Kconfig).
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:
  • Tune idle_threshold_us based on the maximum expected gap between valid pulses in your protocol.
  • Enable filter_en and adjust filter_threshold_us if noise is an issue. Start with filter disabled if unsure.
  • An oscilloscope is crucial for observing signal characteristics and noise to set these values correctly.
  • Ensure signal_range_min_ns and signal_range_max_ns in rmt_receive_config_t are appropriate for expected pulse widths.
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:
  • Ensure the correct encoder type is used (e.g., copy encoder for raw symbols, bytes encoder for byte data).
  • Verify the input data format matches what the encoder expects. For copy encoder, this is an array of rmt_symbol_word_t.
  • Check encoder configuration parameters.
  • Delete encoder with rmt_del_encoder() when no longer needed.
Resource Leaks ESP_ERR_NO_MEM on subsequent channel creations, eventual system instability. Solution:
  • Always call rmt_del_channel() to free an RMT channel when it’s no longer needed.
  • For TX, also call rmt_del_encoder() to free the encoder.
  • Ensure rmt_disable() is called before deleting an enabled channel.
Clock Source Configuration Incorrect pulse timings, RMT not working as expected, especially after deep sleep or frequency scaling. Solution:
  • Explicitly set clk_src in channel configuration if default is not suitable.
  • Be aware of clock source availability and frequency for your specific ESP32 variant (e.g., APB, XTAL, PLL).
  • If using a clock source affected by CPU frequency scaling or sleep modes (like APB), ensure it’s reconfigured or re-evaluated if issues arise post-sleep. RMT_CLK_SRC_XTAL is often more stable across sleep cycles if available and suitable.

Exercises

  1. 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.
  2. 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 if duration0 and duration1 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).
  3. 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 of rmt_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, and v5.1 with your IDF version if different).
  • 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top