整体无需占用任何硬件 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。

相关推荐
编程墨客11 小时前
STM32F103C8T6单片机内部执行原理及启动流程详解
stm32·单片机·嵌入式硬件
XINVRY-FPGA15 小时前
XCZU47DR-2FFVG1517I Xilinx FPGA AMD ZynqUltraScale+ RFSoC
人工智能·嵌入式硬件·fpga开发·信息与通信·信号处理·射频工程·fpga
Cyrus_柯15 小时前
单片机基础(STM32-DAY2(GPIO))
单片机·嵌入式硬件
努力的小帅16 小时前
STM32单片机_3
stm32·单片机·嵌入式硬件·学习·stm32c8t6
逼子格16 小时前
开关电源和线性电源Multisim电路仿真实验汇总——硬件工程师笔记
嵌入式硬件·硬件工程·硬件工程师·开关电源·multisim电路仿真·稳压电源·线性电源
SKYDROID云卓小助手17 小时前
无人设备遥控器之无线电频率篇
服务器·网络·单片机·嵌入式硬件·算法
逼子格18 小时前
振荡电路Multisim电路仿真实验汇总——硬件工程师笔记
笔记·嵌入式硬件·硬件工程·硬件工程师·硬件工程师真题·multisim电路仿真·震荡电流
云卓SKYDROID18 小时前
无人机三叶螺旋桨概述
单片机·无人机·遥控器·云卓科技·精准降落·螺旋桨
Do vis82418 小时前
STM32第十九天 ESP8266-01S和电脑实现串口通信(2)
stm32·单片机·嵌入式硬件