整体无需占用任何硬件 UART,即可新增一条全双工软串口

复制代码
#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));
    }
}

要点说明:

  1. RMT_TX_CHANNELRMT_RX_CHANNEL 分别用于模拟 TX、RX;

  2. bit_ticks 通过波特率计算,1 tick = 1 µs;

  3. RX 采用 RMT 打点 + 环形缓冲区读取,再按 10 个 item(一帧)解码为一个字节;

  4. 整体无需占用任何硬件 UART,即可新增一条全双工软串口。

如需多路或更高性能方案,可引入 esp32-rmt-uart 社区库,它封装了更完善的 RX/TX API。

相关推荐
wuyk5558 小时前
98.C语言易混难点:字符数组与字符串指针的底层差异
c语言·开发语言·c++·stm32·嵌入式硬件·算法
恒锐丰-罗生9 小时前
国产 H 桥驱动 SS6289|18V/4A 有刷电机驱动芯片性与应用分析 内置采样 + 多保护,门锁 / 机器人优选器件
驱动开发·嵌入式硬件·硬件工程
zcmodeltech12 小时前
工程机械与矿山机械沙盘模型多系统协同控制系统设计:基于STM32与Modbus RTU的露天开采-井下掘进-智慧矿山全场景联动方案
数据库·stm32·单片机·嵌入式硬件·制造·多分类
云泽80813 小时前
深入浅出 STM32(十五):多通道GPIO驱动的软件抽象与实现
stm32·单片机·嵌入式硬件
嵌入式阿蔡14 小时前
OTA 实战五(收官):量产落地 Checklist —— 版本号 / 防回滚 / 灰度 / 断点续传
网络·stm32·单片机·嵌入式硬件·嵌入式实时数据库
碧海银沙音频科技研究院15 小时前
基于杰理AC7016C的GTCRN门控卷积神经网络语音增强方法
人工智能·嵌入式硬件·语音识别
ACP广源盛1392462567316 小时前
Qwen3.8‑2.4T 开源落地@ACP#国产 MoE 大模型私有化部署中 DP 重定时器 GSV6155 的硬件链路价值与应用场景
大数据·数据库·人工智能·嵌入式硬件·矩阵·开源
遇雪长安17 小时前
STM32_BOOT未引出_串口升级系统Bootloader方案
stm32·单片机·嵌入式硬件·bootloader
dcdannycheung17 小时前
【无标题】
嵌入式硬件
自小吃多17 小时前
Capture软件原理图添加元器件笔记
笔记·嵌入式硬件