UART 串口通信完全指南 2026:波特率、流控与多机通信

本文是嵌入式通信协议系列的第二篇。上一篇:I2C 总线完全指南

UART(Universal Asynchronous Receiver-Transmitter)大概是嵌入式世界里最”古老但至今仍在用”的协议了。

它诞生于 1960 年代,比 SPI 和 I2C 都早,但直到今天——你依然能在 ESP32 的串口调试输出、Arduino 的 Serial.println()、工业设备的 Modbus 通信里看到它的身影。

原因很简单:UART 不需要时钟线,不需要地址分配,接线最少,调试最方便。

但 UART 也有明显的短板:速度慢、距离受限、只能点对点通信(不像 I2C 和 SPI 能挂多个设备)。这篇文章把 UART 的协议细节、电平转换、工业协议实战全部讲清楚,让你知道什么时候 UART 是最佳选择,什么时候该换协议。

UART 协议原理

什么是”异步”?

UART 的”A”代表 Asynchronous——异步。与 SPI/I2C 的”同步”通信不同,UART 没有时钟线。发送方和接收方各自运行独立的时钟,通信的同步靠双方提前约定好的参数:

参数典型值说明波特率9600, 115200每秒传输的符号数(bits per second)数据位8 bits每帧有效数据的位数停止位1 bit帧结束的标记校验位None/Even/Odd奇偶校验(可选)

如果双方波特率不一致——比如发送方用 115200,接收方用 9600——收到的就是乱码。这是 UART 最常见的故障之一。

数据帧结构

UART 每次传输一个”帧”(frame),结构如下:

空闲(高) ┤

├─ 起始位(0) ┤ D0 ┤ D1 ┤ D2 ┤ D3 ┤ D4 ┤ D5 ┤ D6 ┤ D7 ┤ 校验(可选) ┤ 停止位(1) ┤─ 空闲(高)

└── 1 bit ───└────────── 8 data bits ──────────┘└─ 0/1 bit ─┘└── 1/2 bits ──┘

起始位:总是低电平,告诉接收方”数据来了”

数据位:低位先发(LSB first),通常 8 bit

校验位:可选,用于检测传输错误

停止位:总是高电平,标志一帧结束

波特率与误差容忍

UART 接收方在每一位的中间位置采样。如果双方波特率偏差超过一定范围,采样点就会漂移到错误位置。

误差公式:

最大允许误差 ≈ 1 / (2 × 数据位数) × 100%

以 8 数据位为例:最大允许误差约 6.25%。但实际工程中建议控制在 2% 以内,否则在高波特率下容易丢包。

常见波特率及对应位时间:

波特率位时间一帧时间(8N1)理论吞吐量9600104.17 μs1.04 ms~960 bytes/s3840026.04 μs260 μs~3.8 KB/s1152008.68 μs86.8 μs~11.5 KB/s9216001.09 μs10.9 μs~92 KB/s

115200 是最常用的”高速”波特率——足够快,又不至于太容易出错。

硬件流控 vs 软件流控

UART 传输没有时钟信号,也就没有”暂停”机制。当接收方处理不过来时,数据就会丢失。流控(Flow Control)就是为了解决这个问题。

RTS/CTS 硬件流控

RTS(Request to Send)和 CTS(Clear to Send)是两根额外的信号线:

发送方 ────TX────→ 接收方

发送方 ←───RX──── 接收方

发送方 ←───CTS─── 接收方 (接收方告诉发送方"我准备好了")

发送方 ────RTS───→ 接收方 (发送方告诉接收方"我要发了")

接收方处理缓冲区满时,拉低 CTS → 发送方暂停

缓冲区清空后,拉高 CTS → 发送方继续

优点:响应快,不占用数据带宽

缺点:多两根线,布线成本增加

XON/XOFF 软件流控

不需要额外引脚,通过在数据流中插入特殊字符来实现:

XON (0x11, Ctrl-Q):告诉对方”可以继续发送”

XOFF (0x13, Ctrl-S):告诉对方”暂停发送”

优点:只需 TX/RX 两根线

缺点:特殊字符可能与有效数据冲突,需要转义处理

ESP32 流控代码示例

// Arduino/ESP-IDF 风格

HardwareSerial Serial1(1); // 使用 UART1

void setup() {

// 115200 波特率,8 数据位,无校验,1 停止位

// 启用硬件流控(RTS=GPIO18, CTS=GPIO19)

Serial1.begin(115200, SERIAL_8N1,

/* RX */ GPIO_RX,

/* TX */ GPIO_TX,

/* RTS */ GPIO18,

/* CTS */ GPIO19);

}

void loop() {

if (Serial1.available()) {

String data = Serial1.readStringUntil('\n');

Serial1.println("Received: " + data);

}

}

ESP32 的三个 UART 口(UART0/1/2)都支持硬件流控,但默认只启用 TX/RX。需要流控时,手动指定 RTS/CTS 引脚即可。

ESP32 多 UART 口使用

ESP32 芯片内置 3 个 UART 控制器,这是它相比其他 MCU 的一大优势:

UART默认引脚典型用途UART0TX=GPIO1, RX=GPIO3USB 串口调试(Serial)UART1TX=GPIO17, RX=GPIO16Flash 缓存(系统保留,可复用)UART2TX=GPIO17, RX=GPIO16自由使用

⚠️ 注意:ESP32 的 UART1 默认被 SPI Flash 缓存占用,但可以通过配置释放给普通串口使用。ESP32-S3/C3 的引脚分配略有不同。

实战:同时使用三个 UART 口

// 场景:UART0 调试输出,UART1 接 GPS 模块,UART2 接 Modbus 传感器

HardwareSerial gpsSerial(1); // UART1 → GPS

HardwareSerial modbusSerial(2); // UART2 → Modbus 传感器

void setup() {

Serial.begin(115200); // UART0:调试输出

gpsSerial.begin(9600, SERIAL_8N1, 16, 17); // GPS 通常 9600

modbusSerial.begin(9600, SERIAL_8N1, 22, 23); // Modbus 传感器

}

void loop() {

// 读取 GPS NMEA 数据

if (gpsSerial.available()) {

String nmea = gpsSerial.readStringUntil('\n');

Serial.println("GPS: " + nmea);

}

// 读取 Modbus 传感器数据

if (modbusSerial.available()) {

// Modbus 帧处理

}

}

这在工业场景中非常实用——一个 ESP32 同时连接 GPS、传感器和调试终端,各司其职。

RS232 / RS485 / RS422 电平转换

UART 是协议层概念,而 RS232/RS485/RS422 是物理层电气标准。MCU 的 UART 输出是 3.3V/5V TTL 电平,但长距离通信需要转换为更抗干扰的电平标准。

三种电平对比

标准电压范围传输模式最大距离节点数典型场景TTL0-3.3V/5V单端< 1m2板级通信、调试RS232±3V ~ ±15V单端~15m2PC 串口、老设备RS422±1.5V ~ ±6V差分~1200m10工业控制RS485±1.5V ~ ±6V差分,半双工~1200m32/128工业总线

RS485 半双工通信

RS485 是最常用的工业串行标准:

差分信号:用 A/B 两根线的电压差表示数据,抗共模干扰能力强

半双工:同一时刻只能发送或接收(需要 DE/RE 引脚控制方向)

多点总线:一条总线上可以挂 32 个(甚至 128 个)节点

典型接线:

ESP32 ──[MAX485]── A ───┬──[MAX485]── 节点1

├──[MAX485]── 节点2

└──[MAX485]── 节点3

B ───┘

ESP32 RS485 发送控制代码:

// MAX485 的 DE(发送使能) 和 RE(接收使能) 接在同一 GPIO

#define RS485_DE_RE 4

void setup() {

pinMode(RS485_DE_RE, OUTPUT);

digitalWrite(RS485_DE_RE, LOW); // 默认接收模式

Serial2.begin(9600, SERIAL_8N1, 16, 17);

}

void rs485_send(const char* data) {

digitalWrite(RS485_DE_RE, HIGH); // 切换到发送模式

delay(1); // 等待 MAX485 稳定

Serial2.print(data);

Serial2.flush(); // 等待发送完成

delay(1); // 确保最后一位发完

digitalWrite(RS485_DE_RE, LOW); // 切回接收模式

}

⚠️ 关键细节:DE/RE 切换必须有延迟。MAX485 的使能引脚响应时间约 50ns,但 MCU 的 GPIO 切换和 UART FIFO 排空需要额外缓冲,一般 1ms 足够。

Modbus RTU 协议实战

Modbus RTU 是运行在 UART/RS485 上的应用层协议,广泛用于工业传感器、PLC、变频器。

Modbus 帧结构

| 地址(1B) | 功能码(1B) | 数据(NB) | CRC(2B) |

字段说明地址从站地址(0=广播,1-247=从站)功能码03=读保持寄存器,06=写单寄存器,10=写多寄存器数据寄存器地址、数量、写入值等CRC16 位循环冗余校验(低位在前)

Modbus RTU 读取温度传感器

#include

HardwareSerial modbus(2);

// 功能码 03:读取保持寄存器

// 从站地址 1,起始寄存器 0x0000,读取 2 个寄存器(温度+湿度)

uint8_t readHoldingRegs[] = {

0x01, // 从站地址

0x03, // 功能码:读保持寄存器

0x00, 0x00, // 起始地址 0x0000

0x00, 0x02, // 读取 2 个寄存器

// CRC 稍后计算

};

uint16_t calculateCRC(uint8_t* buf, uint8_t len) {

uint16_t crc = 0xFFFF;

for (uint8_t i = 0; i < len; i++) {

crc ^= buf[i];

for (uint8_t j = 0; j < 8; j++) {

if (crc & 0x0001) {

crc >>= 1;

crc ^= 0xA001;

} else {

crc >>= 1;

}

}

}

return crc;

}

void sendModbusRequest() {

digitalWrite(RS485_DE_RE, HIGH);

delay(1);

// 计算 CRC 并追加

uint16_t crc = calculateCRC(readHoldingRegs, 6);

modbus.write(readHoldingRegs, 6);

modbus.write(crc & 0xFF); // CRC 低位

modbus.write((crc >> 8) & 0xFF); // CRC 高位

modbus.flush();

delay(1);

digitalWrite(RS485_DE_RE, LOW);

// 读取响应(等待从站回复)

delay(50);

if (modbus.available() >= 7) { // 地址(1) + 功能(1) + 字节数(1) + 数据(4)

uint8_t response[9];

modbus.readBytes(response, 9);

// 解析温度值(假设温度在寄存器 0,16 bit)

uint16_t temperature = (response[3] << 8) | response[4];

float tempC = temperature / 10.0; // 假设精度 0.1°C

Serial.printf("温度: %.1f°C\n", tempC);

}

}

💡 实用建议:实际项目中建议使用成熟的 Modbus 库(如 ModbusMaster 或 ArduinoModbus),而不是手写帧构造和 CRC 计算。上面的代码主要用于理解协议底层逻辑。

故障排查:UART 常见问题

1. 乱码

症状:收到奇怪的字符,如 ÿÿÿ 或随机符号。

可能原因及排查:

原因排查方法解决方案波特率不匹配确认两端波特率一致改为相同值,误差 < 2%地线未连接用万用表测 GND 连通性确保共地TX/RX 接反检查接线交叉连接(TX→RX, RX→TX)电平不匹配测量信号电压加电平转换芯片(如 TXB0108)

2. 数据丢失

症状:发送 10 字节,只收到 8 字节。

排查:

检查是否启用流控(RTS/CTS 或 XON/XOFF)

检查接收方缓冲区是否溢出

降低波特率测试

确认没有中断延迟影响 UART ISR

3. 通信距离受限

症状:TTL 电平超过 1 米就开始出错。

方案:

短距离(< 1m):TTL 直连即可

中距离(1-15m):RS232(MAX232 芯片)

长距离(15-1200m):RS485(MAX485 芯片)

4. RS485 总线冲突

症状:多个节点同时发送,数据混乱。

解决方案:

使用主从架构:只有一个主站负责发起请求

设置发送超时:从站回复超时后主站重试

总线两端加 120Ω 终端电阻(消除信号反射)

UART vs SPI vs I2C 快速对比

特性UARTSPII2C线数2(TX/RX)+ 可选流控4(MOSI/MISO/SCLK/CS)2(SDA/SCL)速度最高 ~4 Mbps最高 ~80 MHz最高 3.4 MHz (Hs)拓扑点对点一主多从多主多从地址无(点对点)片选线选择7/10 位地址全双工✅✅❌适合场景调试、长距离通信、工业总线高速外设(屏幕、Flash)低速传感器调试方便度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

简单选型:

需要调试输出或接传感器读文本数据 → UART

需要高速数据传输(屏幕、SD 卡) → SPI

需要挂多个低速传感器且省引脚 → I2C

总结

UART 可能是嵌入式世界最”朴素”的协议——没有时钟线、没有地址、没有复杂的仲裁机制。但正是这种简单,让它在调试、工业通信、设备对接场景中不可替代。

记住几个关键点:

波特率必须一致,误差控制在 2% 以内

TTL 电平只适合板级短距离通信,长距离用 RS485

RS485 半双工需要手动控制 DE/RE 引脚方向

Modbus RTU 是 UART 上最成熟的工业协议

ESP32 有 3 个 UART 口,可以同时接多个设备

本文是嵌入式通信协议系列第三篇。如果你对传感器连接、工业现场总线感兴趣,这个系列值得持续关注。