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

相关推荐
iCxhust32 分钟前
8255 PORTC 按键输入测试
单片机·嵌入式硬件·微机原理
d111111111d4 小时前
在STM32函数指针是什么,怎么使用还有典型应用场景。
笔记·stm32·单片机·嵌入式硬件·学习·算法
♛识尔如昼♛5 小时前
计算机组成原理(21) 第五章 - 总线性能指标
单片机·嵌入式硬件
快乐的划水a5 小时前
windows用户态到内核态
stm32·单片机·嵌入式硬件
Hy行者勇哥6 小时前
从零搭建小智 AI 音箱 MCP 开发环境:自定义智能家居控制技能实战指南
人工智能·嵌入式硬件·硬件工程·智能家居
richxu202510018 小时前
嵌入式学习之路>单片机核心原理篇>(11) 存储器(Flash & SRam)
单片机·嵌入式硬件·学习
@good_good_study8 小时前
STM32 TIM编码器模式配置函数及实验
stm32·单片机
云山工作室9 小时前
基于单片机的客车载客状况自动检测系统设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计
Rorsion9 小时前
第二章(2.5):微控制器8051的硬件结构---时钟、复位和MCU工作方式
单片机·嵌入式硬件·备考ing
Qingniu0110 小时前
SP40P65NJ:一款高性能40V P沟道MOSFET深度解析
单片机·嵌入式硬件·电机驱动·dc-dc降压/升压转换器·电源管理模块