#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "driver/rmt.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char *TAG = "soft_uart_rmt";
// ---------------------- 配置项 ----------------------
#define SOFT_UART_TX_GPIO 17 // 软串口 TX 引脚
#define SOFT_UART_RX_GPIO 16 // 软串口 RX 引脚
#define RMT_TX_CHANNEL RMT_CHANNEL_0
#define RMT_RX_CHANNEL RMT_CHANNEL_1
#define BAUD_RATE 9600 // 软串口波特率
#define RMT_CLK_DIV 80 // RMT 时钟分频:80MHz/80 = 1MHz (1 tick = 1µs)
// ------------------------------------------------------
static uint32_t bit_ticks; // 每比特持续时间 (µs)
static RingbufHandle_t rb = NULL; // RX 环形缓冲区句柄
// ------------ RMT TX 初始化 ------------
void soft_uart_rmt_tx_init(void)
{
bit_ticks = 1000000 / BAUD_RATE;
rmt_config_t tx_cfg = {
.rmt_mode = RMT_MODE_TX,
.channel = RMT_TX_CHANNEL,
.gpio_num = SOFT_UART_TX_GPIO,
.clk_div = RMT_CLK_DIV,
.mem_block_num = 1,
};
ESP_ERROR_CHECK(rmt_config(&tx_cfg));
ESP_ERROR_CHECK(rmt_driver_install(tx_cfg.channel, 0, 0));
// 置闲置高电平
gpio_set_direction(SOFT_UART_TX_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(SOFT_UART_TX_GPIO, 1);
ESP_LOGI(TAG, "Soft UART TX ready on GPIO%d @%dbps", SOFT_UART_TX_GPIO, BAUD_RATE);
}
// 发送单字节
void soft_uart_rmt_send_byte(uint8_t data)
{
rmt_item32_t items[10];
int idx = 0;
// 起始位 (低电平)
items[idx].level0 = 0;
items[idx].duration0 = bit_ticks;
items[idx].level1 = 1;
items[idx].duration1 = 0;
idx++;
// 8 数据位,LSB 先发
for (int b = 0; b < 8; b++) {
items[idx].level0 = (data >> b) & 0x01;
items[idx].duration0 = bit_ticks;
items[idx].level1 = 1;
items[idx].duration1 = 0;
idx++;
}
// 停止位 (高电平)
items[idx].level0 = 1;
items[idx].duration0 = bit_ticks;
items[idx].level1 = 1;
items[idx].duration1 = 0;
idx++;
ESP_ERROR_CHECK(rmt_write_items(RMT_TX_CHANNEL, items, idx, true));
}
// 发送缓冲区
void soft_uart_rmt_send(const uint8_t *buf, size_t len)
{
for (size_t i = 0; i < len; i++) {
soft_uart_rmt_send_byte(buf[i]);
}
ESP_LOGI(TAG, "Soft UART sent %u bytes", len);
}
// ------------ RMT RX 初始化 ------------
void soft_uart_rmt_rx_init(void)
{
rmt_config_t rx_cfg = {
.rmt_mode = RMT_MODE_RX,
.channel = RMT_RX_CHANNEL,
.gpio_num = SOFT_UART_RX_GPIO,
.clk_div = RMT_CLK_DIV,
.mem_block_num = 1,
.rx_config = {
.filter_en = true,
.filter_ticks_thresh = 100,
// idle_threshold 设置为 10 bit 时长以上,用以分帧
.idle_threshold = bit_ticks * 11,
},
};
ESP_ERROR_CHECK(rmt_config(&rx_cfg));
// 第二个参数为 RX 环形缓冲区大小
ESP_ERROR_CHECK(rmt_driver_install(rx_cfg.channel, 1000, 0));
// 获取环形缓冲区句柄
ESP_ERROR_CHECK(rmt_get_ringbuf_handle(rx_cfg.channel, &rb));
// 启动接收
ESP_ERROR_CHECK(rmt_rx_start(rx_cfg.channel, true));
ESP_LOGI(TAG, "Soft UART RX ready on GPIO%d @%dbps", SOFT_UART_RX_GPIO, BAUD_RATE);
}
// 将一帧 10 个 item 解码成一个字节
static uint8_t soft_uart_parse_frame(const rmt_item32_t *items)
{
uint8_t byte = 0;
// items[0] 是起始位, items[1]~items[8] 是数据位, items[9] 是停止位
for (int b = 0; b < 8; b++) {
if (items[1 + b].level0) {
byte |= (1 << b);
}
}
return byte;
}
// RX 任务:不断从环形缓冲区读 RMT items 并解析
void soft_uart_rmt_rx_task(void *arg)
{
size_t rx_size;
while (1) {
// 等待接收数据
rmt_item32_t *items = (rmt_item32_t *) xRingbufferReceive(rb, &rx_size, portMAX_DELAY);
if (items) {
int item_count = rx_size / sizeof(rmt_item32_t);
// 每帧固定 10 items
for (int i = 0; i + 10 <= item_count; i += 10) {
uint8_t c = soft_uart_parse_frame(&items[i]);
ESP_LOGI(TAG, "Soft UART RX: 0x%02X '%c'", c, (c >= 32 && c < 127) ? c : '.');
}
// 返还 buffer
vRingbufferReturnItem(rb, (void *)items);
}
}
}
void app_main(void)
{
// 初始化 TX/RX
soft_uart_rmt_tx_init();
soft_uart_rmt_rx_init();
// 启动 RX 任务
xTaskCreate(soft_uart_rmt_rx_task, "soft_uart_rx", 2048, NULL, 10, NULL);
// 主循环:每秒发送一次示例字符串
const char *demo = "Hello from soft UART!\r\n";
while (1) {
soft_uart_rmt_send((const uint8_t *)demo, strlen(demo));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

要点说明:
-
RMT_TX_CHANNEL 和 RMT_RX_CHANNEL 分别用于模拟 TX、RX;
-
bit_ticks 通过波特率计算,1 tick = 1 µs;
-
RX 采用 RMT 打点 + 环形缓冲区读取,再按 10 个 item(一帧)解码为一个字节;
-
整体无需占用任何硬件 UART,即可新增一条全双工软串口。
如需多路或更高性能方案,可引入 esp32-rmt-uart 社区库,它封装了更完善的 RX/TX API。