【ESP32-IDF】高级外设开发1:UART
ESP32 UART开发指南摘要 本文详细介绍了ESP32微控制器的UART外设功能与应用。ESP32提供3个独立的UART控制器,支持5-8位数据位、1-2个停止位、奇偶校验和全双工通信。关键特性包括: 灵活的FIFO配置(共享1024×8 RAM) 硬件/软件流控(RTS/CTS/XON/XOFF) 多种工作模式(RS485半双工、IrDA红外) 丰富的错误检测和中断机制 GDMA高速数据传输
系列文章目录
持续更新中…
文章目录
前言
在嵌入式开发中,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 的串口通信。
「智能机器人开发者大赛」官方平台,致力于为开发者和参赛选手提供赛事技术指导、行业标准解读及团队实战案例解析;聚焦智能机器人开发全栈技术闭环,助力开发者攻克技术瓶颈,促进软硬件集成、场景应用及商业化落地的深度研讨。 加入智能机器人开发者社区iRobot Developer,与全球极客并肩突破技术边界,定义机器人开发的未来范式!
更多推荐
所有评论(0)