A driver implementing UART (reception and transmission) using the ESP32 RMT peripheral. Reception is asynchronous with a ring buffer, transmission is synchronous. Allows you to implement a software UART on any pins with precise time synchronization thanks to hardware RMT capture.The driver is compatible with the main UART driver APIs, including the event queue for data reception.
- Full-duplex UART based on RMT.
- Hardware synchronization – RMT ensures precise timing intervals independent of CPU load.
- Ring buffer for reception – data is transferred from the receive task to the user application without being copied.
- Synchronous transmission – the
uart_rmt_write_bytescall blocks until the transmission is complete. - Support for standard UART parameters:
- Baud rate from 9600 to 115200 (can be extended by changing the divisors);
- 8 data bits;
- 1 or 2 stop bits;
- Optional parity bit (even/odd);
- RX/TX signal inversion.
- Event generation – Optional event queue for notifications of data arrival, frame/parity/overrun errors.
- RMT ping-pong mode – used for continuous reception of long packets without data loss (requires chip support, such as the ESP32-S3).
- Espressif microcontroller with RMT and ping-pong support (ESP32-S3, ESP32-P4, ESP32-C3, ESP32-H2, etc.).
- Two free GPIOs for RX and TX (optional third for RTS).
- No external components required.
- ESP-IDF version 5.0 or later (uses the new RMT driver).
- Standard FreeRTOS libraries (queues, ring buffers).
Copy the uart_rmt_rb.c and uart_rmt_rb.h files to the components/uart_rmt_rb folder of your ESP-IDF project. Add the following to the component's CMakeLists.txt:
idf_component_register(SRCS "uart_rmt_rb.c"
INCLUDE_DIRS ".")Or simply include the source files in your project. Or install it as a standard ESP-IDF component.
Types
typedef enum {
UART_RMT_PORT_0,
UART_RMT_PORT_1,
UART_RMT_PORT_2,
UART_RMT_PORT_3,
UART_RMT_PORT_MAX
} uart_rmt_port_number_t;
typedef enum {
UART_RMT_PARITY_DISABLE,
UART_RMT_PARITY_EVEN,
UART_RMT_PARITY_ODD,
UART_RMT_PARITY_MAX
} uart_rmt_parity_t;typedef struct {
int gpio_tx; // TX pin
int gpio_rx; // RX pin
int gpio_rts; // RTS pin (RS485), can be -1
uint32_t baud_rate; // speed (9600..115200)
uart_rmt_parity_t parity; // parity
uint32_t stop_bits; // 1 or 2
uint32_t rx_fifo_size; // Receive ring buffer size (bytes)
uint32_t rx_fifo_threshold; // UART_DATA event threshold (0 = use rx_fifo_size/2)
uint32_t rx_tout_threshold; // Packet timeout in characters (default 2)
bool inverse_rx; // Invert RX, false - start bit = 0
bool inverse_tx; // Invert TX, false - start bit = 0
} uart_rmt_cfg_t;/**
* @brief Initialization of the UART-over-RMT channel
* @param port port number (0..UART_RMT_PORT_MAX-1)
* @param config configuration pointer
* @param rx_rmt_event_queue if not NULL, the event queue will be returned here
* @return ESP_OK on success, otherwise an error code
*/
esp_err_t uart_rmt_init(uart_rmt_port_number_t port, const uart_rmt_cfg_t *config, QueueHandle_t *rx_rmt_event_queue);
/**
* @brief Channel deinitialization, freeing resources
* @param port port number (0..UART_RMT_PORT_MAX-1)
* @return ESP_OK on success, otherwise error code
*/
esp_err_t uart_rmt_deinit(uart_rmt_port_number_t port);
/**
* @brief Data writing (synchronous, blocking)
* @param port port number
* @param bytes data pointer
* @param size number of bytes
* @return number of bytes transferred or ESP_FAIL
*/
int uart_rmt_write_bytes(uart_rmt_port_number_t port, const uint8_t *bytes, size_t size);
/**
* @brief Read data from the receive ring buffer
* @param port port number
* @param data buffer to receive
* @param size buffer size
* @param tick_to_wait maximum time to wait for data (in FreeRTOS ticks)
* @return number of bytes read (may be less than size during a timeout)
*/
int uart_rmt_read_bytes(uart_rmt_port_number_t port, uint8_t *data, size_t size, TickType_t tick_to_wait);
/**
* @brief Flush (clear) the input buffer
* @param port port number (0..UART_RMT_PORT_MAX-1)
* @return ESP_OK on success, otherwise an error code
*/
esp_err_t uart_rmt_flush_input(uart_rmt_port_number_t port);If a non-zero rx_rmt_event_queue pointer is passed during initialization, the driver will send uart_event_t structures to this queue (defined in driver/uart.h for compatibility with the UART driver):
UART_DATA – Data is available (the size field contains the number of bytes in the FIFO, timeout_flag indicates the end of the packet).
UART_FRAME_ERR – Frame error (invalid start bit).
UART_PARITY_ERR – Parity error.
UART_BUFFER_FULL – Internal receiver buffer overflow.
UART_FIFO_OVF – Circular buffer overflow (data lost). ```
## Usage example
```c
#include "uart_rmt_rb.h"
void app_main(void)
{
uart_rmt_cfg_t cfg = {
.gpio_tx = GPIO_NUM_4,
.gpio_rx = GPIO_NUM_5,
.gpio_rts = -1,
.baud_rate = 115200,
.parity = UART_RMT_PARITY_DISABLE,
.stop_bits = 1,
.rx_fifo_size = 256,
.rx_fifo_threshold = 0, // will be set to 128
.rx_tout_threshold = 2, // timeout after 2 characters
.inverse_rx = false,
.inverse_tx = false
};
QueueHandle_t event_queue;
ESP_ERROR_CHECK(uart_rmt_init(UART_RMT_PORT_0, &cfg, &event_queue));
// Transmit (synchronous)
const char hello[] = "Hello RMT UART!\r\n";
uart_rmt_write_bytes(UART_RMT_PORT_0, (uint8_t*)hello, strlen(hello));
// Receive with a wait of up to 100 ms
uint8_t buf[64];
int len = uart_rmt_read_bytes(UART_RMT_PORT_0, buf, sizeof(buf), pdMS_TO_TICKS(100));
if (len > 0) {
printf("Received %d bytes\n", len);
}
// Freeing resources (usually not needed in app_main)
// uart_rmt_deinit(UART_RMT_PORT_0);
}
Other examples in the uart_rmt_rb_example folder
This operation requires a chip that supports RMT ping-pong mode (SOC_RMT_SUPPORT_RX_PINGPONG). If your chip doesn't support this, comment out the corresponding flag in the code – reception will still work, but the packet size is limited to half the RMT memory.
When selecting pins, keep in mind that RMT can operate on any GPIO.
The speed is calculated by dividing the RMT frequency (80 MHz / divisor 8 = 10 MHz). For speeds above 115200, you may need to change the divisors.
When using events, remember to handle the queue in a separate task to avoid blocking the driver.
Transmission is synchronous – the uart_rmt_write_bytes call will return only after all data has been sent. This eliminates the need for a separate ring buffer for TX.
This code is in the Public Domain or distributed under the CC0 license. You are free to use, modify, and distribute it without any restrictions.