跳转至

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以"帧"为单位传输数据,一个完整的数据帧包含:

空闲状态(高电平)
起始位(1位,低电平)
数据位(5-9位,LSB先发送)
校验位(0或1位,可选)
停止位(1、1.5或2位,高电平)
空闲状态(高电平)

标准数据帧示例(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

奇校验示例

数据:0x55 (01010101) → 4个1(偶数)
校验位:1(使总数为奇数)

偶校验示例

数据:0x55 (01010101) → 4个1(偶数)
校验位: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)

位时间 = 1 / 波特率

示例:
- 9600 bps → 位时间 = 1/9600 ≈ 104.17 μs
- 115200 bps → 位时间 = 1/115200 ≈ 8.68 μs

帧传输时间

帧时间 = 位时间 × 帧长度

示例(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

工作流程

  1. 设备A准备接收数据
  2. 设备A拉低RTS信号(表示"我准备好接收了")
  3. 设备B检测到CTS为低

  4. 设备B开始发送

  5. 设备B通过TX发送数据
  6. 设备A通过RX接收数据

  7. 设备A缓冲区快满

  8. 设备A拉高RTS信号(表示"暂停发送")
  9. 设备B检测到CTS为高,停止发送

  10. 设备A处理完数据

  11. 设备A再次拉低RTS
  12. 设备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连接

MCU1              MCU2
TX  ────────────→ RX
RX  ←──────────── TX
GND ←───────────→ GND

关键要点: 1. 交叉连接:TX连接到对方的RX 2. 共地:两个设备必须共地 3. 电平匹配:确保电平兼容(3.3V ↔ 3.3V 或 5V ↔ 5V)

电平转换

3.3V ↔ 5V电平转换

方案1:使用电平转换芯片

3.3V MCU          TXS0108E          5V设备
TX ──→ A1                B1 ──→ RX
RX ←── A2                B2 ←── TX

方案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(双向)

3.3V MCU              5V设备
TX ──┬────────────┬── RX
     │   BSS138   │
     ├─ G    D ───┤
     │   │    │   │
    3.3V  S    S  5V
     │         │   │
    GND       GND GND

上拉电阻

何时需要上拉电阻: - 开漏输出(Open-Drain) - 长距离传输 - 多设备共享总线

典型值: - 短距离(<10cm):10kΩ - 中距离(10cm-1m):4.7kΩ - 长距离(>1m):1kΩ - 2.2kΩ

VCC
 ├─ 4.7kΩ ─┬─ TX
 │         │
 └─ 4.7kΩ ─┴─ RX

ESD保护

静电保护电路

TX ──┬─────────→ 外部接口
     ├─ TVS二极管 ─┬─ GND
     │             │
     └─ 100Ω ──────┘

推荐TVS型号:
- PESD1CAN(单通道)
- PESD2CAN(双通道)

PCB布线建议

布线规则: 1. 短而直:UART走线尽量短而直 2. 远离干扰源:远离高速信号、电源线 3. 差分走线:RS485的A/B线应等长、平行走线 4. 地平面:在信号线下方保留完整地平面 5. 串联电阻:长距离传输时在TX端串联22Ω-100Ω电阻

推荐布局

[MCU] ─ 短走线 ─ [ESD保护] ─ [电平转换] ─ [接口]
                     │            │
                    GND          GND

实践示例

示例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配置

硬件连接

Arduino Uno           外部设备
D0 (RX) ────────────→ TX
D1 (TX) ────────────→ RX
GND ────────────────→ GND

软件配置

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:无法通信

可能原因

  1. 波特率不匹配
  2. 检查:双方波特率设置是否一致
  3. 解决:统一波特率配置

  4. TX/RX接反

  5. 检查:TX应连接到对方RX
  6. 解决:交换TX和RX连接

  7. 未共地

  8. 检查:GND是否连接
  9. 解决:连接两个设备的GND

  10. 电平不兼容

  11. 检查:3.3V设备连接5V设备
  12. 解决:添加电平转换电路

调试步骤

1. 用万用表测量TX引脚电压
   - 空闲时应为高电平(3.3V或5V)
   - 发送时应有电平变化

2. 用示波器观察波形
   - 检查波特率是否正确
   - 检查数据帧格式

3. 回环测试
   - 将TX和RX短接
   - 发送数据应能接收到

问题2:数据乱码

可能原因

  1. 波特率误差过大
  2. 检查:计算实际波特率误差
  3. 解决:调整系统时钟或选择其他波特率

  4. 帧格式不匹配

  5. 检查:数据位、校验位、停止位配置
  6. 解决:统一帧格式(通常8N1)

  7. 电磁干扰

  8. 检查:走线是否靠近干扰源
  9. 解决:重新布线,添加滤波电容

示例:波特率误差检查

// 计算实际波特率
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:数据丢失

可能原因

  1. 接收缓冲区溢出
  2. 检查:接收速度是否跟上发送速度
  3. 解决:增大缓冲区或使用硬件流控

  4. 中断优先级过低

  5. 检查:UART中断是否被其他中断抢占
  6. 解决:提高UART中断优先级

  7. 处理时间过长

  8. 检查:中断服务函数是否耗时过长
  9. 解决:使用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通信异常

可能原因

  1. 缺少终端电阻
  2. 检查:总线两端是否有120Ω电阻
  3. 解决:在总线两端添加终端电阻

  4. DE/RE控制时序错误

  5. 检查:切换时序是否正确
  6. 解决:添加适当延时

  7. 总线负载过多

  8. 检查:节点数量是否超过32个
  9. 解决:使用中继器或减少节点

正确的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:使用更高波特率

// 从115200提升到460800
USART1->BRR = (9 << 4) | 12;  // 72MHz / (16 × 460800)

注意事项: - 确保时钟精度足够 - 缩短传输距离 - 使用屏蔽线缆 - 添加终端电阻

方法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通信
  • 故障排查:常见问题诊断和解决方法
  • 性能优化:提速、降耗、可靠性提升

关键要点

  1. UART通信三要素
  2. 波特率必须匹配
  3. 帧格式必须一致(通常8N1)
  4. 必须共地连接

  5. 电平转换

  6. TTL:MCU标准,短距离
  7. RS232:PC串口,中距离,需MAX232
  8. RS485:工业标准,长距离,差分信号

  9. 硬件设计

  10. TX/RX交叉连接
  11. 添加ESD保护
  12. 长距离传输使用终端电阻

  13. 可靠性保证

  14. 使用DMA避免数据丢失
  15. 添加CRC校验
  16. 实现重传机制

实践建议

初学者练习

  1. 基础通信
  2. 实现MCU与PC的串口通信
  3. 发送"Hello World"
  4. 实现回显功能

  5. 数据传输

  6. 发送传感器数据到PC
  7. 实现简单的命令解析
  8. 添加数据校验

  9. RS485实验

  10. 搭建RS485通信电路
  11. 实现两个MCU之间的通信
  12. 测试传输距离

进阶项目

  1. Modbus协议
  2. 实现Modbus RTU从站
  3. 支持标准功能码
  4. 添加CRC校验

  5. 无线透传

  6. 使用蓝牙/WiFi模块
  7. 实现UART数据透传
  8. 添加AT命令配置

  9. 多机通信

  10. RS485总线多机通信
  11. 实现主从协议
  12. 地址分配和冲突检测

延伸阅读

相关文章

协议标准

  • Modbus协议实现
  • AT命令集应用

硬件设计

参考资料

数据手册

  1. MAX232 Datasheet - RS232电平转换芯片
  2. MAX485 Datasheet - RS485收发器
  3. STM32F1 Reference Manual - USART章节

技术标准

  1. RS232标准:EIA-232-F(原RS-232C)
  2. RS485标准:TIA/EIA-485-A
  3. UART规范:各MCU厂商参考手册

在线工具

  1. 波特率计算器 - ST官方工具
  2. CRC计算器 - 在线CRC计算
  3. 串口调试助手 - 串口测试工具

开源项目

  1. TinyUSB - USB CDC虚拟串口
  2. libmodbus - Modbus协议库
  3. 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 协议,欢迎分享和改编,但请注明出处。