UART/USART硬件接口详解¶
概述¶
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是嵌入式系统中最基础、最常用的串行通信接口。无论是调试输出、传感器通信还是模块间数据传输,UART都扮演着重要角色。
完成本文学习后,你将能够:
- 理解UART/USART的工作原理和硬件特性
- 掌握波特率、数据位、停止位、校验位的配置
- 了解硬件流控制(RTS/CTS)的作用
- 学会RS232和RS485电平转换电路设计
- 掌握UART接口的硬件设计要点
背景知识¶
UART vs USART¶
UART(Universal Asynchronous Receiver/Transmitter): - 异步通信,不需要时钟信号 - 只有TX(发送)和RX(接收)两根数据线 - 通信双方需要约定相同的波特率
USART(Universal Synchronous/Asynchronous Receiver/Transmitter): - 支持同步和异步两种模式 - 同步模式下有额外的时钟线(CLK) - 更灵活,但大多数应用只使用异步模式
实际应用中: - STM32等MCU的"USART"外设通常工作在异步模式(UART模式) - 术语"UART"和"USART"在异步通信场景下可以互换使用 - 本文主要讨论异步模式(UART)
串行通信基础¶
串行 vs 并行:
| 特性 | 串行通信 | 并行通信 |
|---|---|---|
| 数据线 | 1根 | 多根(8/16/32根) |
| 传输速度 | 较慢 | 较快 |
| 传输距离 | 远(可达数十米) | 近(通常<1米) |
| 成本 | 低 | 高 |
| 抗干扰 | 好 | 差 |
为什么选择UART: - 硬件简单,只需2根线(TX、RX) - 成本低,几乎所有MCU都内置UART - 传输距离远,适合板间通信 - 调试方便,可直接连接PC串口
UART工作原理¶
数据帧格式¶
UART以"帧"为单位传输数据,一个完整的数据帧包含:
标准数据帧示例(8N1配置):
时间 →
___ ___ ___ ___ ___ ___ ___ ___ ___ _______
| | | | | | | | | | |
|___| | | | | | | | |___|
起始 D0 D1 D2 D3 D4 D5 D6 D7 停止
(0) (1)
发送字节:0x55 (01010101)
实际传输:0 1 0 1 0 1 0 1 0 1(起始+数据+停止)
帧格式参数详解¶
1. 起始位(Start Bit)¶
- 固定为1位
- 电平为低(0)
- 作用:通知接收方数据传输开始
- 检测:接收方检测到下降沿后开始采样
2. 数据位(Data Bits)¶
- 可选位数:5、6、7、8、9位
- 最常用:8位(1字节)
- 传输顺序:LSB(最低位)先发送
- 示例:发送0xA5(10100101)
- 实际传输顺序:1-0-1-0-0-1-0-1
3. 校验位(Parity Bit)¶
- 可选类型:
- 无校验(None):最常用,不添加校验位
- 奇校验(Odd):数据位+校验位中1的个数为奇数
- 偶校验(Even):数据位+校验位中1的个数为偶数
- 标记校验(Mark):校验位固定为1
- 空格校验(Space):校验位固定为0
奇校验示例:
偶校验示例:
4. 停止位(Stop Bits)¶
- 可选位数:1、1.5、2位
- 电平为高(1)
- 作用:标志数据帧结束,为下一帧做准备
- 最常用:1位
常见配置格式¶
8N1(最常用): - 8个数据位 - 无校验(None) - 1个停止位 - 总共10位/帧
8E1: - 8个数据位 - 偶校验(Even) - 1个停止位 - 总共11位/帧
9N1: - 9个数据位(用于多机通信) - 无校验 - 1个停止位 - 总共11位/帧
波特率详解¶
什么是波特率¶
波特率(Baud Rate):每秒传输的符号数(位数),单位为bps(bits per second)。
常用波特率: - 9600 bps(最常用,兼容性好) - 19200 bps - 38400 bps - 57600 bps - 115200 bps(调试常用) - 230400 bps - 460800 bps - 921600 bps
波特率计算¶
位时间(Bit Time):
帧传输时间:
帧时间 = 位时间 × 帧长度
示例(8N1配置):
- 9600 bps → 10位/帧 × 104.17μs ≈ 1.04 ms/字节
- 115200 bps → 10位/帧 × 8.68μs ≈ 86.8 μs/字节
有效数据速率:
有效速率 = 波特率 × (数据位 / 总位数)
示例(8N1):
- 9600 bps → 9600 × (8/10) = 7680 bps = 960 字节/秒
- 115200 bps → 115200 × (8/10) = 92160 bps = 11520 字节/秒
波特率误差¶
误差来源: - MCU时钟频率不精确 - 波特率分频器的整数限制 - 晶振精度
误差容限: - 推荐:误差 < 2% - 可接受:误差 < 5% - 危险:误差 > 5%(可能导致通信失败)
误差计算示例(STM32):
假设:
- 系统时钟:72 MHz
- 目标波特率:115200 bps
- USART分频器:USARTDIV = 72000000 / (16 × 115200) ≈ 39.0625
实际分频值:39(整数部分)
实际波特率:72000000 / (16 × 39) = 115384.6 bps
误差:(115384.6 - 115200) / 115200 × 100% ≈ 0.16% ✓ 可接受
波特率选择建议¶
| 应用场景 | 推荐波特率 | 原因 |
|---|---|---|
| 调试输出 | 115200 | 速度快,实时性好 |
| GPS模块 | 9600 | 标准配置,兼容性好 |
| 蓝牙模块 | 9600/115200 | 根据模块规格 |
| 传感器 | 9600/19200 | 稳定可靠 |
| 长距离通信 | 9600 | 抗干扰能力强 |
| 高速数据传输 | 460800/921600 | 需要短距离、良好屏蔽 |
硬件流控制¶
什么是硬件流控¶
硬件流控(Hardware Flow Control):通过额外的控制信号(RTS/CTS)来协调数据传输,防止数据丢失。
标准UART信号: - TX(Transmit):发送数据 - RX(Receive):接收数据 - GND:公共地
带硬件流控的UART信号: - TX:发送数据 - RX:接收数据 - RTS(Request To Send):请求发送 - CTS(Clear To Send):清除发送 - GND:公共地
RTS/CTS工作原理¶
设备A 设备B
(DTE) (DCE)
TX ────────────────────────→ RX
RX ←──────────────────────── TX
RTS ────────────────────────→ CTS
CTS ←──────────────────────── RTS
GND ←───────────────────────→ GND
工作流程:
- 设备A准备接收数据:
- 设备A拉低RTS信号(表示"我准备好接收了")
-
设备B检测到CTS为低
-
设备B开始发送:
- 设备B通过TX发送数据
-
设备A通过RX接收数据
-
设备A缓冲区快满:
- 设备A拉高RTS信号(表示"暂停发送")
-
设备B检测到CTS为高,停止发送
-
设备A处理完数据:
- 设备A再次拉低RTS
- 设备B继续发送
何时需要硬件流控¶
需要硬件流控的场景: - 高速数据传输(>115200 bps) - 接收方处理速度慢于发送速度 - 接收缓冲区较小 - 数据不能丢失的关键应用
不需要硬件流控的场景: - 低速通信(≤9600 bps) - 简单的命令响应通信 - 接收方有足够大的缓冲区 - 可以容忍偶尔丢失数据
软件流控 vs 硬件流控¶
软件流控(XON/XOFF): - 使用特殊字符控制(XON=0x11, XOFF=0x13) - 不需要额外硬件线 - 占用数据带宽 - 不适合二进制数据传输
硬件流控(RTS/CTS): - 使用专用控制线 - 响应速度快 - 不占用数据带宽 - 需要额外的引脚
电平标准与转换¶
TTL电平(MCU标准)¶
电平定义: - 逻辑高(1):2.4V ~ VCC(通常3.3V或5V) - 逻辑低(0):0V ~ 0.4V - 空闲状态:高电平
特点: - 大多数MCU的UART直接输出TTL电平 - 传输距离短(<1米) - 抗干扰能力弱 - 不能直接连接PC串口
RS232电平标准¶
电平定义: - 逻辑高(1):-3V ~ -15V(通常-12V) - 逻辑低(0):+3V ~ +15V(通常+12V) - 注意:RS232的逻辑是反向的!
特点: - PC串口(DB9接口)使用RS232电平 - 传输距离较远(15米) - 抗干扰能力强 - 需要电平转换芯片
TTL与RS232对比:
| 信号 | TTL电平 | RS232电平 |
|---|---|---|
| 逻辑1 | +3.3V/+5V | -12V |
| 逻辑0 | 0V | +12V |
| 空闲 | 高电平 | 负电压 |
RS232电平转换电路¶
使用MAX232芯片:
MCU (TTL) MAX232 PC (RS232)
TX (3.3V) ──→ T1IN T1OUT ──→ TX (-12V/+12V)
RX (3.3V) ←── R1OUT R1IN ←── RX (-12V/+12V)
电源:
VCC (+3.3V/+5V)
GND
C1+ ─┬─ 0.1μF ─┬─ C1-
└─ 0.1μF ─┘
C2+ ─┬─ 0.1μF ─┬─ C2-
└─ 0.1μF ─┘
MAX232特点: - 内置电荷泵,产生±10V电压 - 只需4个0.1μF电容 - 支持双通道收发 - 工作电压:3.3V或5V
引脚连接示例:
| MAX232引脚 | 功能 | 连接 |
|---|---|---|
| 1 (C1+) | 电容正极 | 0.1μF电容 |
| 2 (V+) | 正电压输出 | 0.1μF电容到GND |
| 3 (C1-) | 电容负极 | 0.1μF电容 |
| 4 (C2+) | 电容正极 | 0.1μF电容 |
| 5 (C2-) | 电容负极 | 0.1μF电容到GND |
| 6 (V-) | 负电压输出 | 0.1μF电容到GND |
| 7 (T2OUT) | RS232输出2 | DB9-7 (RTS) |
| 8 (R2IN) | RS232输入2 | DB9-8 (CTS) |
| 9 (R2OUT) | TTL输出2 | MCU CTS |
| 10 (T2IN) | TTL输入2 | MCU RTS |
| 11 (T1IN) | TTL输入1 | MCU TX |
| 12 (R1OUT) | TTL输出1 | MCU RX |
| 13 (R1IN) | RS232输入1 | DB9-2 (RX) |
| 14 (T1OUT) | RS232输出1 | DB9-3 (TX) |
| 15 (GND) | 地 | GND |
| 16 (VCC) | 电源 | +5V |
RS485电平标准¶
电平定义: - 差分信号:A线和B线的电压差 - 逻辑高(1):VA - VB > +200mV - 逻辑低(0):VA - VB < -200mV
特点: - 差分传输,抗干扰能力极强 - 传输距离远(1200米) - 支持多设备总线(最多128个节点) - 半双工通信(需要方向控制)
RS485转换电路:
MCU MAX485 RS485总线
TX ──→ DI (数据输入)
RX ←── RO (数据输出)
DE ──→ DE (发送使能) A ←─────→ A线
RE ──→ RE (接收使能) B ←─────→ B线
终端电阻:
A ─┬─ 120Ω ─┬─ B
└─────────┘
MAX485特点: - 半双工通信 - 需要DE/RE控制发送/接收模式 - 总线两端需要120Ω终端电阻 - 支持最多32个节点(标准负载)
DE/RE控制逻辑:
| DE | RE | 模式 |
|---|---|---|
| 0 | 0 | 接收模式 |
| 1 | 1 | 发送模式 |
注意:DE和RE通常连接在一起,由一个GPIO控制。
UART硬件设计要点¶
基本连接¶
最简单的UART连接:
关键要点: 1. 交叉连接:TX连接到对方的RX 2. 共地:两个设备必须共地 3. 电平匹配:确保电平兼容(3.3V ↔ 3.3V 或 5V ↔ 5V)
电平转换¶
3.3V ↔ 5V电平转换:
方案1:使用电平转换芯片
方案2:使用分压电阻(单向,5V→3.3V)
5V设备 3.3V MCU
TX ──┬─ 2kΩ ─┬─────────→ RX
└─ 1kΩ ─┴─ GND
输出电压 = 5V × 1kΩ/(2kΩ+1kΩ) ≈ 1.67V ✗ 太低!
正确分压:
TX ──┬─ 1kΩ ─┬─────────→ RX
└─ 2kΩ ─┴─ GND
输出电压 = 5V × 2kΩ/(1kΩ+2kΩ) ≈ 3.33V ✓
方案3:使用MOSFET(双向)
上拉电阻¶
何时需要上拉电阻: - 开漏输出(Open-Drain) - 长距离传输 - 多设备共享总线
典型值: - 短距离(<10cm):10kΩ - 中距离(10cm-1m):4.7kΩ - 长距离(>1m):1kΩ - 2.2kΩ
ESD保护¶
静电保护电路:
TX ──┬─────────→ 外部接口
│
├─ TVS二极管 ─┬─ GND
│ │
└─ 100Ω ──────┘
推荐TVS型号:
- PESD1CAN(单通道)
- PESD2CAN(双通道)
PCB布线建议¶
布线规则: 1. 短而直:UART走线尽量短而直 2. 远离干扰源:远离高速信号、电源线 3. 差分走线:RS485的A/B线应等长、平行走线 4. 地平面:在信号线下方保留完整地平面 5. 串联电阻:长距离传输时在TX端串联22Ω-100Ω电阻
推荐布局:
实践示例¶
示例1:STM32 UART硬件配置¶
硬件连接:
STM32F103C8T6 USB转TTL模块
PA9 (USART1_TX) ────→ RX
PA10 (USART1_RX) ───→ TX
GND ─────────────────→ GND
寄存器配置要点:
// 1. 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // USART1时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // GPIOA时钟
// 2. 配置GPIO
// PA9 (TX) - 复用推挽输出
GPIOA->CRH &= ~(0xF << 4);
GPIOA->CRH |= (0xB << 4); // 50MHz, 复用推挽
// PA10 (RX) - 浮空输入
GPIOA->CRH &= ~(0xF << 8);
GPIOA->CRH |= (0x4 << 8); // 浮空输入
// 3. 配置波特率(115200 bps,72MHz系统时钟)
// USARTDIV = 72000000 / (16 × 115200) = 39.0625
// 整数部分:39 (0x27)
// 小数部分:0.0625 × 16 = 1 (0x1)
USART1->BRR = (39 << 4) | 1; // 0x271
// 4. 配置帧格式(8N1)
USART1->CR1 &= ~USART_CR1_M; // 8位数据
USART1->CR1 &= ~USART_CR1_PCE; // 无校验
USART1->CR2 &= ~USART_CR2_STOP; // 1个停止位
// 5. 使能UART
USART1->CR1 |= USART_CR1_TE; // 使能发送
USART1->CR1 |= USART_CR1_RE; // 使能接收
USART1->CR1 |= USART_CR1_UE; // 使能USART
发送函数:
void UART_SendByte(uint8_t data) {
// 等待发送数据寄存器空
while (!(USART1->SR & USART_SR_TXE));
// 写入数据
USART1->DR = data;
}
void UART_SendString(const char *str) {
while (*str) {
UART_SendByte(*str++);
}
}
接收函数:
uint8_t UART_ReceiveByte(void) {
// 等待接收数据寄存器非空
while (!(USART1->SR & USART_SR_RXNE));
// 读取数据
return USART1->DR;
}
示例2:Arduino UART配置¶
硬件连接:
软件配置:
void setup() {
// 配置UART(8N1,115200 bps)
Serial.begin(115200);
// 等待串口就绪
while (!Serial);
Serial.println("UART Ready!");
}
void loop() {
// 发送数据
Serial.write(0x55);
Serial.print("Hello");
Serial.println(" World!");
// 接收数据
if (Serial.available() > 0) {
char c = Serial.read();
Serial.print("Received: ");
Serial.println(c);
}
delay(1000);
}
使用软件串口(额外的UART):
#include <SoftwareSerial.h>
// 定义软件串口(RX=D2, TX=D3)
SoftwareSerial mySerial(2, 3);
void setup() {
Serial.begin(115200); // 硬件串口(调试)
mySerial.begin(9600); // 软件串口(通信)
}
void loop() {
// 从软件串口接收,转发到硬件串口
if (mySerial.available()) {
char c = mySerial.read();
Serial.write(c);
}
// 从硬件串口接收,转发到软件串口
if (Serial.available()) {
char c = Serial.read();
mySerial.write(c);
}
}
示例3:RS485通信电路¶
完整电路设计:
STM32 MAX485 RS485总线
PA2 (TX) ──→ DI
PA3 (RX) ←── RO
PA4 (DE) ──┬─→ DE A ──┬─────────→ A线
└─→ RE B ──┴─────────→ B线
│
GND
终端电阻(总线两端):
A ─┬─ 120Ω ─┬─ B
└─────────┘
偏置电阻(可选,提高空闲稳定性):
VCC ─┬─ 680Ω ─┬─ A
└─ 680Ω ─┴─ B ─ GND
控制代码:
#define RS485_DE_PIN GPIO_PIN_4
#define RS485_DE_PORT GPIOA
// 切换到发送模式
void RS485_TxMode(void) {
HAL_GPIO_WritePin(RS485_DE_PORT, RS485_DE_PIN, GPIO_PIN_SET);
HAL_Delay(1); // 等待切换完成
}
// 切换到接收模式
void RS485_RxMode(void) {
HAL_Delay(1); // 等待最后一个字节发送完成
HAL_GPIO_WritePin(RS485_DE_PORT, RS485_DE_PIN, GPIO_PIN_RESET);
}
// 发送数据
void RS485_SendData(uint8_t *data, uint16_t len) {
RS485_TxMode();
HAL_UART_Transmit(&huart2, data, len, 1000);
RS485_RxMode();
}
// 接收数据
HAL_StatusTypeDef RS485_ReceiveData(uint8_t *data, uint16_t len, uint32_t timeout) {
RS485_RxMode();
return HAL_UART_Receive(&huart2, data, len, timeout);
}
常见问题与调试¶
问题1:无法通信¶
可能原因:
- 波特率不匹配
- 检查:双方波特率设置是否一致
-
解决:统一波特率配置
-
TX/RX接反
- 检查:TX应连接到对方RX
-
解决:交换TX和RX连接
-
未共地
- 检查:GND是否连接
-
解决:连接两个设备的GND
-
电平不兼容
- 检查:3.3V设备连接5V设备
- 解决:添加电平转换电路
调试步骤:
1. 用万用表测量TX引脚电压
- 空闲时应为高电平(3.3V或5V)
- 发送时应有电平变化
2. 用示波器观察波形
- 检查波特率是否正确
- 检查数据帧格式
3. 回环测试
- 将TX和RX短接
- 发送数据应能接收到
问题2:数据乱码¶
可能原因:
- 波特率误差过大
- 检查:计算实际波特率误差
-
解决:调整系统时钟或选择其他波特率
-
帧格式不匹配
- 检查:数据位、校验位、停止位配置
-
解决:统一帧格式(通常8N1)
-
电磁干扰
- 检查:走线是否靠近干扰源
- 解决:重新布线,添加滤波电容
示例:波特率误差检查
// 计算实际波特率
uint32_t SystemClock = 72000000; // 72MHz
uint32_t TargetBaud = 115200;
uint32_t Divider = SystemClock / (16 * TargetBaud);
uint32_t ActualBaud = SystemClock / (16 * Divider);
float Error = (float)(ActualBaud - TargetBaud) / TargetBaud * 100;
printf("Target: %d bps\n", TargetBaud);
printf("Actual: %d bps\n", ActualBaud);
printf("Error: %.2f%%\n", Error);
// 如果误差>2%,考虑更换波特率
问题3:数据丢失¶
可能原因:
- 接收缓冲区溢出
- 检查:接收速度是否跟上发送速度
-
解决:增大缓冲区或使用硬件流控
-
中断优先级过低
- 检查:UART中断是否被其他中断抢占
-
解决:提高UART中断优先级
-
处理时间过长
- 检查:中断服务函数是否耗时过长
- 解决:使用DMA或优化中断处理
使用DMA避免数据丢失:
// STM32 HAL库DMA接收示例
uint8_t rxBuffer[256];
void UART_DMA_Init(void) {
// 启动DMA循环接收
HAL_UART_Receive_DMA(&huart1, rxBuffer, sizeof(rxBuffer));
}
// DMA接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart == &huart1) {
// 处理接收到的数据
ProcessData(rxBuffer, sizeof(rxBuffer));
// 重新启动DMA接收
HAL_UART_Receive_DMA(&huart1, rxBuffer, sizeof(rxBuffer));
}
}
问题4:RS485通信异常¶
可能原因:
- 缺少终端电阻
- 检查:总线两端是否有120Ω电阻
-
解决:在总线两端添加终端电阻
-
DE/RE控制时序错误
- 检查:切换时序是否正确
-
解决:添加适当延时
-
总线负载过多
- 检查:节点数量是否超过32个
- 解决:使用中继器或减少节点
正确的DE/RE控制时序:
void RS485_SendByte(uint8_t data) {
// 1. 切换到发送模式
HAL_GPIO_WritePin(DE_PORT, DE_PIN, GPIO_PIN_SET);
// 2. 等待切换完成(MAX485典型值:30ns)
// 在低速MCU上可能不需要延时
__NOP(); __NOP(); __NOP();
// 3. 发送数据
HAL_UART_Transmit(&huart, &data, 1, 100);
// 4. 等待发送完成
while (__HAL_UART_GET_FLAG(&huart, UART_FLAG_TC) == RESET);
// 5. 切换回接收模式
HAL_GPIO_WritePin(DE_PORT, DE_PIN, GPIO_PIN_RESET);
}
性能优化¶
提高传输速度¶
方法1:使用更高波特率
注意事项: - 确保时钟精度足够 - 缩短传输距离 - 使用屏蔽线缆 - 添加终端电阻
方法2:使用DMA传输
// 配置DMA发送
void UART_DMA_Send(uint8_t *data, uint16_t len) {
HAL_UART_Transmit_DMA(&huart1, data, len);
}
// DMA发送完成回调
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
// 发送完成,可以发送下一批数据
}
方法3:减少帧开销 - 使用9N1代替8E1(减少1位校验位) - 批量发送数据,减少帧间隔
降低功耗¶
方法1:动态波特率调整
void UART_SetLowPowerMode(void) {
// 降低波特率到9600
USART1->BRR = (468 << 4) | 12;
}
void UART_SetNormalMode(void) {
// 恢复到115200
USART1->BRR = (39 << 4) | 1;
}
方法2:空闲时关闭UART
void UART_Sleep(void) {
// 等待发送完成
while (!(USART1->SR & USART_SR_TC));
// 关闭UART
USART1->CR1 &= ~USART_CR1_UE;
// 关闭时钟
RCC->APB2ENR &= ~RCC_APB2ENR_USART1EN;
}
void UART_Wakeup(void) {
// 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 使能UART
USART1->CR1 |= USART_CR1_UE;
}
方法3:使用空闲中断
// 使能空闲中断
USART1->CR1 |= USART_CR1_IDLEIE;
// 空闲中断处理
void USART1_IRQHandler(void) {
if (USART1->SR & USART_SR_IDLE) {
// 清除空闲标志
uint32_t temp = USART1->SR;
temp = USART1->DR;
(void)temp;
// 处理接收到的数据
ProcessReceivedData();
}
}
提高可靠性¶
方法1:添加CRC校验
uint16_t CRC16(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
void UART_SendWithCRC(uint8_t *data, uint16_t len) {
// 发送数据
HAL_UART_Transmit(&huart1, data, len, 1000);
// 计算并发送CRC
uint16_t crc = CRC16(data, len);
HAL_UART_Transmit(&huart1, (uint8_t*)&crc, 2, 1000);
}
方法2:实现重传机制
#define MAX_RETRY 3
bool UART_SendWithRetry(uint8_t *data, uint16_t len) {
for (int retry = 0; retry < MAX_RETRY; retry++) {
// 发送数据
UART_SendWithCRC(data, len);
// 等待ACK
uint8_t ack;
if (UART_ReceiveTimeout(&ack, 1, 1000) == HAL_OK) {
if (ack == 0x06) { // ACK
return true;
}
}
// 重试
HAL_Delay(100);
}
return false; // 发送失败
}
方法3:使用环形缓冲区
#define BUFFER_SIZE 256
typedef struct {
uint8_t buffer[BUFFER_SIZE];
uint16_t head;
uint16_t tail;
} RingBuffer_t;
void RingBuffer_Init(RingBuffer_t *rb) {
rb->head = 0;
rb->tail = 0;
}
bool RingBuffer_Put(RingBuffer_t *rb, uint8_t data) {
uint16_t next = (rb->head + 1) % BUFFER_SIZE;
if (next == rb->tail) {
return false; // 缓冲区满
}
rb->buffer[rb->head] = data;
rb->head = next;
return true;
}
bool RingBuffer_Get(RingBuffer_t *rb, uint8_t *data) {
if (rb->head == rb->tail) {
return false; // 缓冲区空
}
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % BUFFER_SIZE;
return true;
}
总结¶
通过本文学习,你已经掌握了:
- ✅ UART基础:工作原理、数据帧格式、异步通信特点
- ✅ 波特率配置:计算方法、误差分析、选择建议
- ✅ 硬件流控:RTS/CTS原理、应用场景
- ✅ 电平转换:TTL、RS232、RS485的区别和转换方法
- ✅ 硬件设计:连接方式、PCB布线、ESD保护
- ✅ 实践应用:STM32/Arduino配置、RS485通信
- ✅ 故障排查:常见问题诊断和解决方法
- ✅ 性能优化:提速、降耗、可靠性提升
关键要点¶
- UART通信三要素:
- 波特率必须匹配
- 帧格式必须一致(通常8N1)
-
必须共地连接
-
电平转换:
- TTL:MCU标准,短距离
- RS232:PC串口,中距离,需MAX232
-
RS485:工业标准,长距离,差分信号
-
硬件设计:
- TX/RX交叉连接
- 添加ESD保护
-
长距离传输使用终端电阻
-
可靠性保证:
- 使用DMA避免数据丢失
- 添加CRC校验
- 实现重传机制
实践建议¶
初学者练习¶
- 基础通信:
- 实现MCU与PC的串口通信
- 发送"Hello World"
-
实现回显功能
-
数据传输:
- 发送传感器数据到PC
- 实现简单的命令解析
-
添加数据校验
-
RS485实验:
- 搭建RS485通信电路
- 实现两个MCU之间的通信
- 测试传输距离
进阶项目¶
- Modbus协议:
- 实现Modbus RTU从站
- 支持标准功能码
-
添加CRC校验
-
无线透传:
- 使用蓝牙/WiFi模块
- 实现UART数据透传
-
添加AT命令配置
-
多机通信:
- RS485总线多机通信
- 实现主从协议
- 地址分配和冲突检测
延伸阅读¶
相关文章¶
- SPI硬件接口与应用 - 学习高速同步通信接口
- I2C硬件接口实战 - 学习两线总线通信
- CAN总线硬件设计 - 学习汽车和工业通信
协议标准¶
- Modbus协议实现
- AT命令集应用
硬件设计¶
参考资料¶
数据手册¶
- MAX232 Datasheet - RS232电平转换芯片
- MAX485 Datasheet - RS485收发器
- STM32F1 Reference Manual - USART章节
技术标准¶
- RS232标准:EIA-232-F(原RS-232C)
- RS485标准:TIA/EIA-485-A
- UART规范:各MCU厂商参考手册
在线工具¶
开源项目¶
- TinyUSB - USB CDC虚拟串口
- libmodbus - Modbus协议库
- SerialPort - 跨平台串口库
常见应用场景¶
1. 调试输出¶
应用: - printf重定向到UART - 日志输出 - 错误信息显示
配置:115200 bps, 8N1
2. GPS模块¶
应用: - 接收NMEA数据 - 位置信息解析
配置:9600 bps, 8N1
3. 蓝牙/WiFi模块¶
应用: - AT命令配置 - 数据透传
配置:9600/115200 bps, 8N1
4. 工业通信¶
应用: - Modbus RTU - 传感器网络 - PLC通信
配置:9600/19200 bps, 8E1, RS485
5. 多机通信¶
应用: - 主从架构 - 分布式系统 - 数据采集网络
配置:RS485总线,自定义协议
附录¶
A. 常用波特率对应的位时间¶
| 波特率 | 位时间 | 字节时间(8N1) | 1KB传输时间 |
|---|---|---|---|
| 9600 | 104.17 μs | 1.04 ms | 1.07 s |
| 19200 | 52.08 μs | 520.8 μs | 533 ms |
| 38400 | 26.04 μs | 260.4 μs | 267 ms |
| 57600 | 17.36 μs | 173.6 μs | 178 ms |
| 115200 | 8.68 μs | 86.8 μs | 89 ms |
| 230400 | 4.34 μs | 43.4 μs | 44 ms |
| 460800 | 2.17 μs | 21.7 μs | 22 ms |
| 921600 | 1.09 μs | 10.9 μs | 11 ms |
B. RS232 DB9接口引脚定义¶
| 引脚 | 信号 | 方向 | 功能 |
|---|---|---|---|
| 1 | DCD | ← | 载波检测 |
| 2 | RXD | ← | 接收数据 |
| 3 | TXD | → | 发送数据 |
| 4 | DTR | → | 数据终端就绪 |
| 5 | GND | - | 信号地 |
| 6 | DSR | ← | 数据设备就绪 |
| 7 | RTS | → | 请求发送 |
| 8 | CTS | ← | 清除发送 |
| 9 | RI | ← | 振铃指示 |
最简连接(三线): - 引脚2(RXD) - 引脚3(TXD) - 引脚5(GND)
C. 故障排查检查清单¶
硬件检查: - [ ] 电源电压正常 - [ ] TX/RX正确交叉连接 - [ ] GND已连接 - [ ] 电平匹配(3.3V/5V) - [ ] 线缆长度合理 - [ ] 终端电阻已安装(RS485)
软件检查: - [ ] 波特率配置一致 - [ ] 帧格式配置一致(8N1) - [ ] GPIO复用功能正确 - [ ] 时钟已使能 - [ ] UART已使能 - [ ] 中断优先级合理
测试方法: - [ ] 回环测试(TX接RX) - [ ] 示波器观察波形 - [ ] 万用表测量电压 - [ ] 逻辑分析仪抓包
反馈与支持: - 如果你在学习过程中遇到问题,欢迎在评论区留言 - 分享你的项目经验,与其他学习者交流 - 发现文档错误?请提交Issue帮助我们改进
版权声明:本文采用 CC BY-SA 4.0 协议,欢迎分享和改编,但请注明出处。