UART串口通信:从协议到实战
UART,说白了就是串口。这东西在嵌入式开发里太常见了——调试打印、传感器通信、GPS模块、蓝牙模块,几乎每个项目都离不开它。我最早接触ESP32时,第一个跑通的例程就是UART回环测试。嗯,今天我们就把它彻底搞明白。
UART协议基础
UART是异步串行通信,没有时钟线。收发双方靠约定的波特率来同步。数据格式很简单:
- 起始位:1位,逻辑低电平
- 数据位:5~9位,常用8位
- 校验位:可选,奇校验/偶校验/无校验
- 停止位:1位或2位,逻辑高电平
举个例子,8N1表示8数据位、无校验、1停止位。这是最常用的配置。我在项目中遇到过一个问题:两个设备波特率都设成了115200,但通信就是乱码。查了半天,发现一个设备用的是16MHz晶振,另一个是12MHz,实际波特率误差超过了2%。UART对时钟精度有要求,误差超过3%就容易丢数据。
ESP-IDF UART驱动API
ESP-IDF的UART驱动封装得不错。核心API就几个,我列个表方便你对照:
| 函数 | 作用 | 我常用的参数 |
|---|---|---|
uart_driver_install() |
安装驱动,分配缓冲区 | 队列大小、环形缓冲区大小 |
uart_param_config() |
配置波特率、数据位等 | UART_NUM_1, 115200, 8N1 |
uart_set_pin() |
映射GPIO引脚 | TX、RX、RTS、CTS |
uart_write_bytes() |
发送数据 | 超时时间设为0 |
uart_read_bytes() |
读取数据 | 配合环形缓冲区使用 |
安装驱动的顺序很重要。我习惯这样写:
// 1. 配置参数
uart_config_t uart_config = {
.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
};
uart_param_config(UART_NUM_1, &uart_config);
// 2. 设置引脚
uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
// 3. 安装驱动,分配环形缓冲区
uart_driver_install(UART_NUM_1, RX_BUF_SIZE, TX_BUF_SIZE, 20, &uart_queue, 0);
uart_driver_install()的第三个参数是TX缓冲区大小。如果你只发不收,可以设成0。但RX缓冲区建议至少256字节,否则高频数据容易溢出。
串口收发数据实战
收发数据其实就两个函数:uart_write_bytes()和uart_read_bytes()。但实际项目中,收发逻辑要复杂得多。
发送很简单:
const char *data = "Hello ESP32\r\n";
uart_write_bytes(UART_NUM_1, data, strlen(data));
接收就有讲究了。你不能一直轮询,那样CPU就废了。我推荐用事件驱动方式:
uart_event_t event;
while (1) {
if (xQueueReceive(uart_queue, (void *)&event, portMAX_DELAY)) {
switch (event.type) {
case UART_DATA:
// 有数据来了
uint8_t *buf = malloc(event.size);
uart_read_bytes(UART_NUM_1, buf, event.size, 0);
// 处理数据...
free(buf);
break;
case UART_FIFO_OVF:
// FIFO溢出,清空缓冲区
uart_flush_input(UART_NUM_1);
break;
default:
break;
}
}
}
我曾经在一个项目中用轮询方式接收GPS数据,结果CPU占用率飙到40%。改成事件驱动后,降到5%以下。你想想看,这差距多大。
环形缓冲区应用
环形缓冲区是UART驱动的核心。ESP-IDF内部已经帮你实现了,但理解它的原理很重要。
说白了,环形缓冲区就是一个固定大小的数组,加上读指针和写指针。写指针追着读指针跑,读指针追着写指针跑。当写指针追上读指针时,就说明缓冲区满了——要么覆盖旧数据,要么丢弃新数据。
我画个图帮你理解:
实际使用中,你不需要自己实现环形缓冲区。ESP-IDF的uart_driver_install()已经帮你分配好了。但有几个坑要注意:
- 缓冲区大小要匹配数据量:如果一帧数据1024字节,缓冲区至少2048,留余量
- 读取要及时:否则写指针追上读指针,数据就被覆盖了
- 中断服务程序要快:不要在ISR里做复杂处理,只把数据丢进队列
核心要点:UART通信的可靠性,一半靠硬件配置,一半靠软件缓冲区管理。环形缓冲区用好了,数据流再大也不怕。
我记得有一次调试一个4G模块,数据量特别大,每秒好几KB。一开始缓冲区设了512字节,结果一直丢数据。后来改成2048字节,配合事件驱动读取,问题就解决了。说白了,缓冲区大小和读取策略要匹配数据速率。
嗯,UART这块内容就这些。从协议到API,再到实战和环形缓冲区,都是我在项目中反复踩过的坑。你把这些搞明白,串口通信这块基本就稳了。