系列文章目录

持续更新中…



前言

在嵌入式开发中,UART(通用异步收发传输器)是最常用的串行通信接口之一。ESP32作为一款高性能的微控制器,提供了丰富的UART接口和强大的功能支持。本篇文章将深入探讨ESP32的UART外设,帮助开发者掌握其高级应用技巧。

参考文档:ESP32-S3技术参考手册ESP32-S3编程指南


一、UART概述

1.主要功能

ESP32 系列内置 3 个 UART 控制器(UART0/1/2),每个控制器都能独立设置波特率、数据位(5/6/7/8 位)、停止位(1/1.5/2 位)与奇偶校验,并支持全双工收发、软/硬件流控、特殊字符/模式(AT 命令检测、IrDA、RS485 半双工)以及多种状态/错误中断(帧错、校验错、FIFO 满/溢出、超时、BREAK 等)。在驱动层,UART 提供缓冲区与事件队列;在硬件层,控制器还能与 GDMA配合,实现高吞吐、低占用的数据搬运。这样既能满足日志与终端等低速交互,也能覆盖到需要大块数据传输的应用。

2.UART 架构

ESP32 提供三个结构等价的 UART 控制器。每个控制器内部包含 发送/接收 FIFO、波特率发生器(分频器)、收发状态机(FSM)、软/硬件流控块、唤醒/回环/信号反相 等模块。UART 模块工作在两个时钟域:APB 时钟域负责寄存器访问与驱动交互,UART Core 时钟域驱动收发逻辑。
Core 时钟源可在 APB_CLK、RC_FAST、XTAL 之间选择,并经小数分频输出,使在保证波特率的前提下降低外设功耗。数据面向应用只需“写 TX FIFO / 读 RX FIFO”;如果需要更高吞吐,可把 FIFO 与 **GDMA(通过 UHCI)**连起来做零拷贝搬运。硬件流控通过 RTS/CTS 管脚,软件流控则由控制器在数据流中插入/识别 XON/XOFF 完成。模块还支持 信号反相、内部回环 以及 Light-sleep 唤醒(RX 线上计数边沿触发)。
UART 基本架构图
在这里插入图片描述

3.UART RAM

UART 的收发缓冲采用片上 共享 RAM 实现:三个控制器的 TX/RX FIFO 在一个 1024×8 bit 的 RAM 池中按 128×8 bit 为一个 block 进行切分与分配。默认每个 UART 都分到固定起始块;当你需要更深的 FIFO 时,可按 block 粒度扩大某个 UART 的 TX 或 RX 空间,但要注意前向扩展会侵占后一个 UART 的默认区域——例如扩展 UART0 的 TX 会压缩到 UART1 的 TX 可用空间,导致 UART1 发送不可用。FIFO 可单独复位,亦可配置空/满阈值与超限中断(如 TX 空阈触发 TXFIFO_EMPTY、RX 满触发 RXFIFO_FULL、RX 溢出触发 RXFIFO_OVF)。当三个 UART 都空闲时,还可以强制掉电 RAM以进一步省电。
UART 共享 RAM 图
在这里插入图片描述

4.UART 数据帧

UART 数据帧一帧数据以 START(低) 起始,随后是 5–8 位数据,可选 偶/奇校验位,以 STOP(高) 结束,停止位支持 1/1.5/2。发送侧在 FIFO 清空后可上报 TX_DONE;若配置 BREAK,控制器会在最后再发一段持续低电平的“NULL 帧”,结束后产生 TX_BRK_DONE。接收侧支持接收超时与总线 BREAK 检测:当空闲时间超过设定位时间阈值会触发 RXFIFO_TOUT 中断;若在一个 STOP 之后持续低电平达到 BREAK 判据,则上报 BRK_DET。此外还支持 AT 命令“连发同字符”模式(可用来做“+++”之类的包边界/模式切换检测)。
UART 数据帧结构
在这里插入图片描述

5.流控

硬件流控用 RTS/CTS:当接收 FIFO 逼近阈值时,硬件自动拉高 RTS 通知对端“先别发”;发送侧持续监测 CTS 以决定是否继续出字节。必要时还可以通过 DTR/DSR 指示设备就绪状态,并配合 RS485 做方向控制(DE 常接到 DTR/RTS,由硬件在帧前后自动翻转)。
软件流控则在数据流中插入/识别 XON/XOFF 字符来“软暂停/恢复”,两种方式可视需求任选或结合。除标准 UART 外,控制器还内建 RS485 半双工(可选在发送时允许侦听总线、冲突/帧错/校验错上报)与 IrDA(SIR 模式) 编解码,满足红外与总线多点场景。
硬件流控图
在这里插入图片描述

6.UART 中断/事件

UART 的事件非常丰富:数据面向应用常用的有 RX 满/超时、RX 溢出、TX 空/完成、BREAK、帧错/校验错、CTS/DSR 变沿 等;高级场景有 AT 模式检测、RS485 冲突/帧错/奇偶错 等。驱动层把这些打包成事件队列,应用只需从队列取事件即可(如 UART_DATA、UART_PATTERN_DET、UART_FIFO_OVF 等)。
ESP32-S3 的 UART 支持 GDMA 搬运(IDF 5.x),用于高吞吐、低占用的数据传输:数据经 UHCI 做分隔符封装/转义(类似 SLIP:默认分隔符 0xC0,可配置转义字节),然后直接搬入/搬出内存;发送完成与接收完成分别有 GDMA EOF 类中断 上报,软件几乎不需参与字节级搬运,CPU 占用显著降低。
GDMA 模式数据传输
在这里插入图片描述

7 FIFO 阈值与中断触发逻辑

ESP32 UART 的接收中断由两类条件触发,驱动再把硬件 RX FIFO 的数据批量搬到软件环形缓冲区,从而降低 ISR 频率、提高吞吐:
1.RX FIFO 满阈值:当硬件 FIFO 中累计字节数达到 rxfifo_full_thresh 时触发一次中断。阈值应不大于硬件 FIFO 深度(可用 UART_FIFO_LEN 宏查询,ESP32 系列为 128 字节)。
2.RX 空闲超时:当串口线空闲时间超过 rx_timeout_thresh 时触发一次中断。单位是“发送 1 个字符的时间”。因此在 115200 bps、8N1 时:
rx_timeout_thresh = 1 约等于 ~86.8 µs;
rx_timeout_thresh = 2 约等于 ~173.6 µs;以此类推。

这两个机制保证了按“批次”触发中断。以 115200 bps(约 11.5 kB/s)估算,若每次批量搬 64~120B,一秒也就几十到一两百次中断;即便 921600 bps,用较高门限或超时,CPU 也能从容应对。这些行为由驱动/硬件共同完成(RX FIFO 满/超时中断)。
参考实现(放在初始化处)

// 低延迟回显的一套配置:更快“出数”,避免长时间阻塞等待
uart_intr_config_t intr_cfg = {
    .intr_enable_mask = UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT, // 开 RX 满/空闲超时两类中断
    .rxfifo_full_thresh = 64,   // 硬件 FIFO >=64 字节即触发一次中断(UART_FIFO_LEN 为 128)
    .rx_timeout_thresh  = 1,    // 空闲时间 >= 1 个“字符时间”即触发(≈86.8 µs @ 115200, 8N1)
    .txfifo_empty_intr_thresh = 0, // 发送侧可按需设置,0 表示保持默认
};
ESP_ERROR_CHECK(uart_intr_config(UARTx, &intr_cfg));
// 可选:确保 RX 相关中断已打开(若之前手动关过)
ESP_ERROR_CHECK(uart_enable_rx_intr(UARTx));

二、UART类型定义及相关API

需包含的公共头文件:#include “driver/uart.h”
UART类型定义(IDF v5.5)

// ==========================================================
// UART 基本类型(端口号)
// ==========================================================
typedef int uart_port_t;                 // 端口号标识
#define UART_NUM_0  (0)
#define UART_NUM_1  (1)
#define UART_NUM_2  (2)
#define UART_NUM_MAX (3)                 // ESP32-S3 拥有 3 个 UART 控制器

// ==========================================================
// 1) 工作模式(常规 / RS485 / IrDA 等)
// ==========================================================
typedef enum {
    UART_MODE_UART                  = 0x00, // 常规 UART
    UART_MODE_RS485_HALF_DUPLEX     = 0x01, // 通过 RTS 控制的半双工 RS485
    UART_MODE_IRDA                  = 0x02, // IrDA 模式
    UART_MODE_RS485_COLLISION_DETECT= 0x03, // RS485 碰撞检测(测试/诊断)
    UART_MODE_RS485_APP_CTRL        = 0x04, // 应用自行控制 RTS 的 RS485(测试/诊断)
    UART_MODE_MAX
} uart_mode_t;

// ==========================================================
// 2) 字长(数据位)
// ==========================================================
typedef enum {
    UART_DATA_5_BITS = 0x0,  // 5 位
    UART_DATA_6_BITS = 0x1,  // 6 位
    UART_DATA_7_BITS = 0x2,  // 7 位
    UART_DATA_8_BITS = 0x3,  // 8 位
    UART_DATA_BITS_MAX = 0x4
} uart_word_length_t;

// ==========================================================
// 3) 停止位
// ==========================================================
typedef enum {
    UART_STOP_BITS_1   = 0x1,  // 1 位
    UART_STOP_BITS_1_5 = 0x2,  // 1.5 位
    UART_STOP_BITS_2   = 0x3,  // 2 位
    UART_STOP_BITS_MAX = 0x4
} uart_stop_bits_t;

// ==========================================================
// 4) 奇偶校验
// ==========================================================
typedef enum {
    UART_PARITY_DISABLE = 0x0, // 无校验
    UART_PARITY_EVEN    = 0x2, // 偶校验
    UART_PARITY_ODD     = 0x3  // 奇校验
} uart_parity_t;

// ==========================================================
// 5) 硬件流控(RTS/CTS)
// ==========================================================
typedef enum {
    UART_HW_FLOWCTRL_DISABLE = 0x0, // 关闭
    UART_HW_FLOWCTRL_RTS     = 0x1, // 开启 RX 侧 RTS(接收侧握手)
    UART_HW_FLOWCTRL_CTS     = 0x2, // 开启 TX 侧 CTS(发送侧握手)
    UART_HW_FLOWCTRL_CTS_RTS = 0x3, // 同时开启 CTS/RTS
    UART_HW_FLOWCTRL_MAX     = 0x4
} uart_hw_flowcontrol_t;

// ==========================================================
// 6) 信号反相(按位掩码,供 uart_set_line_inverse 使用)
//    注:不同芯片可用信号略有差异
// ==========================================================
typedef enum {
    UART_SIGNAL_INV_DISABLE = 0,
    UART_SIGNAL_IRDA_TX_INV = (0x1 << 0),
    UART_SIGNAL_IRDA_RX_INV = (0x1 << 1),
    UART_SIGNAL_RXD_INV     = (0x1 << 2),
    UART_SIGNAL_CTS_INV     = (0x1 << 3),
    UART_SIGNAL_DSR_INV     = (0x1 << 4),
    UART_SIGNAL_TXD_INV     = (0x1 << 5),
    UART_SIGNAL_RTS_INV     = (0x1 << 6),
    UART_SIGNAL_DTR_INV     = (0x1 << 7),
} uart_signal_inv_t;

// ==========================================================
// 7) UART 时钟源(v5.5)
//    实际可用项依芯片而定,ESP32-S3 文档列出 APB/XTAL/RC_FAST。
// ==========================================================
typedef enum {
    UART_SCLK_APB     = 0,    // APB 时钟
    UART_SCLK_RTC     = 1,    // 兼容旧名,部分目标用作占位
    UART_SCLK_XTAL    = 2,    // 外部晶振
    UART_SCLK_RC_FAST = 3,    // RC_FAST
} uart_sclk_t;

// ==========================================================
// 8) AT 命令/模式检测参数(新版用“模式检测”API,更通用)
//    兼容你书里的结构体写法;在 v5.5 中推荐使用
//    uart_enable_pattern_det_baud_intr() 进行模式检测配置。
// ==========================================================
typedef struct {
    uint8_t  cmd_char;   // 要检测的字符
    uint8_t  char_num;   // 连续出现的个数
    uint32_t gap_tout;   // 字符间超时(按 baud 的位周期计)
    uint32_t pre_idle;   // 前导空闲期(位周期)
    uint32_t post_idle;  // 末尾空闲期(位周期)
} uart_at_cmd_t;         // 仅作兼容参考

// ==========================================================
// 9) 软件流控参数(XON/XOFF)——旧式结构体写法,保留作兼容
//    v5.5 常用 API 是 uart_set_sw_flow_ctrl()(由驱动内部维护)。
// ==========================================================
typedef struct {
    uint8_t  xon_char;       // XON
    uint8_t  xoff_char;      // XOFF
    uint8_t  xon_thrd;       // 触发 XON 的 RX 阈值
    uint8_t  xoff_thrd;      // 触发 XOFF 的 RX 阈值
} uart_sw_flowctrl_t;        // 兼容/示意

// ==========================================================
// 10) UART 参数总配置(一次性配置)
// ==========================================================
typedef struct {
    int                   baud_rate;        // 波特率
    uart_word_length_t    data_bits;        // 字长
    uart_parity_t         parity;           // 奇偶
    uart_stop_bits_t      stop_bits;        // 停止位
    uart_hw_flowcontrol_t flow_ctrl;        // 硬件流控模式
    uint8_t               rx_flow_ctrl_thresh; // RTS 触发阈值(HW 流控时有效)
    union {
        uart_sclk_t       source_clk;       // v5.5:推荐使用
        bool              use_ref_tick;     // 旧字段(已弃用),保持 ABI 兼容
    };
} uart_config_t;

// ==========================================================
// 11) 中断配置结构(阈值/超时)
// ==========================================================
typedef struct {
    uint32_t intr_enable_mask;   // 启用的中断掩码(如 UART_INTR_RXFIFO_FULL 等)
    uint8_t  rx_timeout_thresh;  // 接收超时阈值(单位:符号位数)
    uint8_t  txfifo_empty_intr_thresh; // TX FIFO 空阈值
    uint8_t  rxfifo_full_thresh;      // RX FIFO 满阈值
} uart_intr_config_t;

// ==========================================================
// 12) 事件类型与事件结构(配合 FreeRTOS 队列使用)
// ==========================================================
typedef enum {
    UART_DATA,             // 收到数据
    UART_BREAK,            // BREAK 检测
    UART_BUFFER_FULL,      // 驱动缓冲满
    UART_FIFO_OVF,         // 硬件 FIFO 溢出
    UART_FRAME_ERR,        // 帧错误
    UART_PARITY_ERR,       // 奇偶校验错
    UART_DATA_BREAK,       // 数据中断
    UART_PATTERN_DET,      // 模式检测命中
    UART_EVENT_MAX
} uart_event_type_t;

typedef struct {
    uart_event_type_t type;  // 事件类型
    size_t            size;  // 对于 UART_DATA:可读字节数
    bool              timeout_flag; // 对于超时相关中断:是否超时
} uart_event_t;

UART 相关 API

// ======================= 安装/卸载驱动 =======================

/**
 * @brief  安装 UART 驱动并创建环形缓冲区、事件队列与中断
 *
 * @param uart_num        UART 端口号(UART_NUM_0 / 1 / 2)
 * @param rx_buffer_size  RX 环形缓冲区字节数(>0 才会启用驱动的缓冲式接收)
 * @param tx_buffer_size  TX 环形缓冲区字节数(>0 则启用缓冲式发送;为 0 时按硬件 FIFO 可用空间直写)
 * @param queue_size      事件队列长度(>0 才会创建事件队列并投递 uart_event_t)
 * @param uart_queue      若不关心事件可传 NULL;否则填入指针以返回创建好的队列句柄
 * @param intr_alloc_flags 中断分配标志(见 esp_intr_alloc.h,例如 ESP_INTR_FLAG_IRAM 等)
 *
 * @return ESP_OK 安装成功;其它错误码表示失败
 *
 * @note 驱动安装后,可用 uart_write_bytes()/uart_read_bytes() 在“驱动缓冲区”层面读写;
 *       事件队列开启时会收到 UART_DATA / FIFO_OVF / PARITY_ERR 等事件。
 */
esp_err_t uart_driver_install(uart_port_t uart_num,
                              int rx_buffer_size,
                              int tx_buffer_size,
                              int queue_size,
                              QueueHandle_t *uart_queue,
                              int intr_alloc_flags);

/**
 * @brief  卸载 UART 驱动,释放环形缓冲区、事件队列与中断
 *
 * @param uart_num  UART 端口号
 */
esp_err_t uart_driver_delete(uart_port_t uart_num);


// ======================= 参数配置(一次性 or 分步) =======================

/**
 * @brief  一次性设置串口参数
 *
 * @param uart_num     UART 端口号
 * @param uart_config  串口配置结构体(波特率、数据位、校验、停止位、硬件流控、RX 阈值、时钟源等)
 *
 * @note  等价于分别调用 set_baudrate/word_length/parity/stop_bits/hw_flow_ctrl 等函数。
 */
esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config);


// -------- 分步配置 --------

/** @brief 设置/读取波特率(单位:bit/s) */
esp_err_t uart_set_baudrate(uart_port_t uart_num, uint32_t baudrate);
esp_err_t uart_get_baudrate(uart_port_t uart_num, uint32_t *baudrate);

/** @brief 设置/读取数据位宽(UART_DATA_5_BITS/6/7/8_BITS) */
esp_err_t uart_set_word_length(uart_port_t uart_num, uart_word_length_t data_bit);
esp_err_t uart_get_word_length(uart_port_t uart_num, uart_word_length_t *data_bit);

/** @brief 设置/读取校验位(UART_PARITY_DISABLE/EVEN/ODD) */
esp_err_t uart_set_parity(uart_port_t uart_num, uart_parity_t parity_mode);
esp_err_t uart_get_parity(uart_port_t uart_num, uart_parity_t *parity_mode);

/** @brief 设置/读取停止位(UART_STOP_BITS_1 / 1_5 / 2) */
esp_err_t uart_set_stop_bits(uart_port_t uart_num, uart_stop_bits_t stop_bits);
esp_err_t uart_get_stop_bits(uart_port_t uart_num, uart_stop_bits_t *stop_bits);

/**
 * @brief  设置硬件流控 RTS/CTS
 *
 * @param uart_num   端口号
 * @param flow_ctrl  UART_HW_FLOWCTRL_DISABLE/RTS/CTS/CTS_RTS
 * @param rx_thresh  触发 RTS 的 RX FIFO 阈值(字节数,当接收缓冲到达该阈值时拉高 RTS)
 */
esp_err_t uart_set_hw_flow_ctrl(uart_port_t uart_num, uart_hw_flowcontrol_t flow_ctrl, uint8_t rx_thresh);

/** @brief 读取当前硬件流控模式 */
esp_err_t uart_get_hw_flow_ctrl(uart_port_t uart_num, uart_hw_flowcontrol_t *flow_ctrl);

/**
 * @brief  设置软件流控(XON/XOFF)
 *
 * @param uart_num       端口号
 * @param enable         true 启用 / false 关闭
 * @param rx_xon_thresh  当 RX FIFO 中数据量低于该阈值时发送 XON
 * @param rx_xoff_thresh 当 RX FIFO 中数据量高于该阈值时发送 XOFF
 */
esp_err_t uart_set_sw_flow_ctrl(uart_port_t uart_num, bool enable, uint8_t rx_xon_thresh, uint8_t rx_xoff_thresh);

/**
 * @brief  设置 UART 工作模式
 *
 * @param uart_num  端口号
 * @param mode      模式:UART_MODE_UART(普通串口)、UART_MODE_RS485_HALF_DUPLEX、
 *                  UART_MODE_IRDA 等(不同芯片/版本可用项略有差异)
 */
esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode);

/**
 * @brief  信号线极性反转
 *
 * @param uart_num  端口号
 * @param inv_mask  由 UART_SIGNAL_INV_* 掩码按位或组成(RXD/CTS/DSR/TXD/RTS/DTR/IrDA_RX/TX)
 *                  例如:UART_SIGNAL_RXD_INV | UART_SIGNAL_TXD_INV
 */
esp_err_t uart_set_line_inverse(uart_port_t uart_num, uint32_t inv_mask);


// ======================= 引脚映射 =======================

/**
 * @brief  将 UART 信号映射到具体 GPIO
 *
 * @param uart_num  端口号
 * @param tx_io_num TX 引脚;若保持不变,传 UART_PIN_NO_CHANGE
 * @param rx_io_num RX 引脚;若保持不变,传 UART_PIN_NO_CHANGE
 * @param rts_io_num RTS 引脚;未用可传 UART_PIN_NO_CHANGE
 * @param cts_io_num CTS 引脚;未用可传 UART_PIN_NO_CHANGE
 *
 * @note  ESP32 系列通过 GPIO Matrix 进行映射;确保所选 GPIO 支持该功能并未与其它外设冲突。
 */
esp_err_t uart_set_pin(uart_port_t uart_num,
                       int tx_io_num, int rx_io_num,
                       int rts_io_num, int cts_io_num);


// ======================= 发送/接收(缓冲式) =======================

/**
 * @brief  发送数据到 UART(写入驱动 TX 缓冲;必要时阻塞直至写入)
 *
 * @param uart_num  端口号
 * @param src       待发送数据首址
 * @param size      待发送字节数
 * @return 实际写入驱动缓冲区的字节数
 */
int  uart_write_bytes(uart_port_t uart_num, const char *src, size_t size);

/**
 * @brief  发送数据并在末尾追加一个 BREAK(持续低电平)
 *
 * @param uart_num  端口号
 * @param src       待发送数据
 * @param size      数据长度
 * @param brk_len   BREAK 时长(按比特时间计算;由驱动换算为硬件寄存器配置)
 */
int  uart_write_bytes_with_break(uart_port_t uart_num, const char *src, size_t size, int brk_len);

/**
 * @brief  直接写入尽可能多的字节到硬件 TX FIFO(不会等待缓冲区可用)
 *
 * @param uart_num  端口号
 * @param buffer    待写入数据
 * @param len       最大写入长度
 * @return 本次实际写入到硬件 FIFO 的字节数(可能小于 len)
 *
 * @note  适合在中断中或对时延要求高的场景快速“塞 FIFO”。
 */
int  uart_tx_chars(uart_port_t uart_num, const char *buffer, uint32_t len);

/**
 * @brief  等待 TX 发送完成(直到硬件 FIFO 为空/移交完毕或超时)
 *
 * @param uart_num      端口号
 * @param ticks_to_wait 等待超时(FreeRTOS ticks;portMAX_DELAY 表示永久等待)
 */
esp_err_t uart_wait_tx_done(uart_port_t uart_num, TickType_t ticks_to_wait);

/**
 * @brief  从驱动 RX 缓冲区读取数据
 *
 * @param uart_num      端口号
 * @param buf           目标缓冲区
 * @param length        期望读取字节数
 * @param ticks_to_wait 最长等待时间(FreeRTOS ticks)
 * @return 实际读取的字节数(可能小于 length)
 */
int  uart_read_bytes(uart_port_t uart_num, void *buf, uint32_t length, TickType_t ticks_to_wait);

/**
 * @brief  查询 RX 缓冲区当前可读的数据量(字节)
 *
 * @param uart_num  端口号
 * @param size      输出:可读字节数
 */
esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t *size);

/**
 * @brief  清空 UART RX 缓冲区(丢弃尚未读取的数据)
 *
 * @param uart_num  端口号
 */
esp_err_t uart_flush(uart_port_t uart_num);


// -------- 手动控制 RTS/DTR(未启用 HW 流控时可用) --------

/** @brief 设置 RTS 引脚电平(0/1),用于手动流控或外设握手 */
esp_err_t uart_set_rts(uart_port_t uart_num, int level);

/** @brief 设置 DTR 引脚电平(0/1),用于手动流控或外设握手 */
esp_err_t uart_set_dtr(uart_port_t uart_num, int level);


// ======================= 中断/事件(可选) =======================

/**
 * @brief  配置并使能 UART 中断
 *
 * @param uart_num    端口号
 * @param intr_conf   中断配置结构体:
 *                    - intr_enable_mask:要使能的中断位集合(UART_INTR_* 掩码按位或)
 *                    - rx_timeout_thresh:RX 超时阈值
 *                    - txfifo_empty_intr_thresh:TX FIFO 空阈值
 *                    - rxfifo_full_thresh:RX FIFO 满阈值
 */
esp_err_t uart_intr_config(uart_port_t uart_num, const uart_intr_config_t *intr_conf);

/** @brief 批量使能/关闭指定中断掩码 */
esp_err_t uart_enable_intr_mask(uart_port_t uart_num, uint32_t intr_mask);
esp_err_t uart_disable_intr_mask(uart_port_t uart_num, uint32_t intr_mask);

/** @brief 快捷开启/关闭 RX 相关中断(如 FIFO 满/超时) */
esp_err_t uart_enable_rx_intr(uart_port_t uart_num);
esp_err_t uart_disable_rx_intr(uart_port_t uart_num);


// -------- 模式检测(PATTERN DETECT,可用于“AT 结尾”等场景) --------

/**
 * @brief  基于“波特时序”的模式检测:当接收到连续的指定字符并满足时序条件时触发中断
 *
 * @param uart_num    端口号
 * @param pattern_chr 需要检测的字符(例如 '\n')
 * @param chr_num     连续出现的个数(≥1)
 * @param chr_tout    模式字符之间允许的最大间隔(基于波特时序的“字符间隙”阈值)
 * @param post_idle   最后一个模式字符之后的最小空闲期阈值(基于波特时序)
 * @param pre_idle    第一个模式字符之前的最小空闲期阈值(基于波特时序)
 *
 * @note  命中后可通过 uart_pattern_pop_pos() 从队列里取出命中的位置索引;
 *        也可用 uart_pattern_queue_reset() 设定用于缓存命中位置的队列长度。
 */
esp_err_t uart_enable_pattern_det_baud_intr(uart_port_t uart_num,
                                            char pattern_chr, uint8_t chr_num,
                                            int chr_tout, int post_idle, int pre_idle);

/** @brief 关闭模式检测中断 */
esp_err_t uart_disable_pattern_det_intr(uart_port_t uart_num);

/**
 * @brief  从“模式位置队列”弹出一次命中的位置
 *
 * @param uart_num  端口号
 * @param pos       输出:命中时刻在 RX 缓冲中的位置(字节索引)
 * @return 若取到位置则返回 UART_PATTERN_DET/非负值;队列为空返回 -1
 */
int uart_pattern_pop_pos(uart_port_t uart_num, int *pos);

/**
 * @brief  重新设置“模式位置队列”容量(用于缓存多次命中位置)
 *
 * @param uart_num      端口号
 * @param queue_length  队列容量(命中位置的保存个数)
 * @return 0 表示成功;其它负值表示失败
 */
int uart_pattern_queue_reset(uart_port_t uart_num, int queue_length);

三、UART示例程序

发送数据的过程分为以下步骤:
1.将数据写入 Tx FIFO 缓冲区
2.FSM 序列化数据
3.FSM 发送数据

接收数据的过程类似,只是步骤相反:
1.FSM 处理且并行化传入的串行流
2.FSM 将数据写入 Rx FIFO 缓冲区
3.从 Rx FIFO 缓冲区读取数据

示例 1:阻塞式串口回显(UART1)

#include "driver/uart.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "hal/uart_hal.h"

#define UARTx UART_NUM_1
#define TX1_PIN GPIO_NUM_10
#define RX1_PIN GPIO_NUM_11

void app_main(void)
{
    const uart_config_t cfg = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };

    // 1) 安装驱动(仅用 RX 缓冲,TX 直写硬件 FIFO)
    ESP_ERROR_CHECK(uart_driver_install(UARTx, 2048, 0, 0, NULL, 0));
    // 2) 参数 & 引脚
    ESP_ERROR_CHECK(uart_param_config(UARTx, &cfg));
    ESP_ERROR_CHECK(uart_set_pin(UARTx, TX1_PIN, RX1_PIN,
                                 UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));

    // 3) 配置接收阈值与超时 —— 关键:让“零散数据”也能及时搬到驱动缓冲
    uart_intr_config_t intr_cfg = {
        .intr_enable_mask = UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT,
        .rxfifo_full_thresh = 64, // 达 64B 触发
        .rx_timeout_thresh = 1,   // 很短空闲即触发
    };
    ESP_ERROR_CHECK(uart_intr_config(UARTx, &intr_cfg));
    ESP_ERROR_CHECK(uart_enable_rx_intr(UARTx));

    // 4) 清理启动噪声并开始阻塞式回显
    uart_flush_input(UARTx);

    uint8_t buf[256];
    while (1)
    {
        int n = uart_read_bytes(UARTx, buf, sizeof(buf), pdMS_TO_TICKS(50)); // 50ms 超时
        if (n > 0)
        {
            uart_write_bytes(UARTx, (const char *)buf, n);
        }
    }
}

ESP-IDF 的 UART 驱动把硬件 RX FIFO 里的字节凑成一包再塞进驱动内部的 ring buffer,而“凑包”的两个典型触发条件是:
1.FIFO 达到“满阈值”(RXFIFO full threshold);
2.RX 线空闲达到“超时阈值”(RX timeout,按“比特时间”计)。

如果这两个阈值(尤其是 RX 超时)没有合理配置,你用 uart_read_bytes(…, portMAX_DELAY) 就会一直等“那一包”,哪怕已经有零星字节到了硬件 FIFO,也不会立刻唤醒;等到满阈值或超时发生才会返回。你把等待改成 50 ms 超时后,哪怕还没“凑成包”,函数也会按时醒来,所以看起来就“好了”。官方文档也说明 UART 接收是靠 FIFO 阈值或超时中断把数据搬到软件缓冲区、uart_read_bytes 再从缓冲区取数据;这些阈值是可配置的。

示例 2:事件队列式回显(UART1)(推荐)

#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "driver/gpio.h"

#define UARTx UART_NUM_1
#define TX1_PIN GPIO_NUM_10
#define RX1_PIN GPIO_NUM_11

static QueueHandle_t uart_q;

static void uart_event_task(void *arg)
{
    uart_event_t e;   // UART事件结构体
    uint8_t buf[256]; // 数据缓冲区,一次最多处理256字节

    for (;;)
    {
        // 阻塞等待UART事件到来
        if (xQueueReceive(uart_q, &e, portMAX_DELAY))
        {
            if (e.type == UART_DATA)
            {
                // 数据接收事件:有新数据到达
                // 计算本次读取字节数,防止超过缓冲区大小
                int to_read = e.size > sizeof(buf) ? sizeof(buf) : e.size;

                // 立即读取数据(超时时间为0,非阻塞)
                int n = uart_read_bytes(UARTx, buf, to_read, 0);

                // 如果成功读到数据,原样回显
                if (n > 0)
                    uart_write_bytes(UARTx, (const char *)buf, n);
            }
            else if (e.type == UART_FIFO_OVF || e.type == UART_BUFFER_FULL)
            {
                // 缓冲区溢出事件:数据接收过快,缓冲区满
                uart_flush_input(UARTx); // 清空接收缓冲区
                xQueueReset(uart_q);     // 重置事件队列
            }
        }
    }
}

void app_main(void)
{
    // UART配置结构体
    const uart_config_t cfg = {
        .baud_rate = 115200,                   // 波特率115200
        .data_bits = UART_DATA_8_BITS,         // 8位数据位
        .parity = UART_PARITY_DISABLE,         // 无校验位
        .stop_bits = UART_STOP_BITS_1,         // 1位停止位
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, // 禁用硬件流控
        .source_clk = UART_SCLK_DEFAULT,       // 使用默认时钟源
    };

    // 安装UART驱动
    // 参数:串口号, RX缓冲大小, TX缓冲大小, 事件队列大小, 队列句柄, 中断标志
    uart_driver_install(UARTx, 4096, 4096, 10, &uart_q, 0);

    // 配置UART参数(波特率、数据格式等)
    uart_param_config(UARTx, &cfg);

    // 设置UART引脚映射
    // 参数:串口号, TX引脚, RX引脚, RTS引脚(不使用), CTS引脚(不使用)
    uart_set_pin(UARTx, TX1_PIN, RX1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    // 创建UART事件处理任务
    // 参数:任务函数, 任务名称, 栈大小, 参数, 优先级, 任务句柄
    xTaskCreate(uart_event_task, "uart_evt", 4096, NULL, 10, NULL);
}

不阻塞主循环,由驱动的 ISR 把事件送进队列,在任务里按事件处理。
UART_DATA(有数据)和 UART_FIFO_OVF/UART_BUFFER_FULL(溢出)是最常用处理分支


总结

本文围绕 ESP32 的 UART 外设进行了系统梳理:从硬件层面的 3 路控制器、可选时钟与小数分频、共享 1024×8 FIFO/RAM、AT/RS485/IrDA 与唤醒/回环等能力,到关键机制如波特率产生与自检、帧格式与 BREAK、软/硬件流控、FIFO 阈值与超时中断,再到驱动层的类型定义与常用 API 以及阻塞/事件队列/GDMA 三类收发示例。按需选择“驱动缓冲 + 事件队列”或 GDMA,可在保证可靠性的同时降低 CPU 占用,覆盖从调试日志到高速数据流的应用场景。希望本文能帮助你快速落地并优化基于 ESP32 的串口通信。

Logo

「智能机器人开发者大赛」官方平台,致力于为开发者和参赛选手提供赛事技术指导、行业标准解读及团队实战案例解析;聚焦智能机器人开发全栈技术闭环,助力开发者攻克技术瓶颈,促进软硬件集成、场景应用及商业化落地的深度研讨。 加入智能机器人开发者社区iRobot Developer,与全球极客并肩突破技术边界,定义机器人开发的未来范式!

更多推荐