深入理解USART通信协议

在嵌入式系统开发中,通信是至关重要的一部分。在电赛中,我们往往会用到STM32开发板、CanMV开发板、树莓派等板载计算机,他们之间要进行数据的交换就必须进行通信。而USART(通用同步 / 异步收发传输器)作为一种常见的串行通信接口,广泛应用于各种设备之间的数据传输。本文将详细介绍USART的工作原理、特点,并给出基于STM32单片机的代码实现示例。

什么是USART?

USARTUniversal Synchronous Asynchronous Receiver Transmitter,即通用同步异步收发器。USART最初是由Motorola在1970年代设计的,用于解决早期微处理器与外部设备之间的串行通信问题。随着技术发展,USART逐渐成为嵌入式系统中不可或缺的标准接口。

与UART的区别

特性 UART USART
全称 通用异步收发器 通用同步异步收发器
通信模式 仅支持异步 支持同步和异步
时钟信号 无CLK引脚 有CLK引脚(可选)
硬件复杂度 相对简单 相对复杂
应用场景 简单的点对点通信 需要时钟同步的场合

USART 比 UART 多一个 同步时钟输出功能(对应引脚 CLK),可在通信中提供时钟信号。实际应用中,由于串口通信极少使用同步模式,USART与UART在异步模式下可视为等价的,硬件驱动和配置流程基本一致。

数据帧格式详解

串口通信的核心是数据帧的概念。一个完整的数据帧就像一封包含多个部分的信件,有开头、内容和结尾。

标准数据帧结构

1
2
3
4
5
6
7
8
┌─────────┬───────────────────┬─────────┬─────────┬─────────┐
│ 起始位 │ 数据位 │ 校验位 │ 停止位 │ 停止位 │
│ (1 bit) │ (8 或 9 bit) │ (可选) │ (可选) │ (可选) │
└─────────┴───────────────────┴─────────┴─────────┴─────────┘
0 7 6 5 4 3 2 1 0 P 1 1
┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ D7│ D6│ D5│ D4│ D3│ D2│ D1│ D0│ P │ ← 发送顺序(LSB优先)
└───┴───┴───┴───┴───┴───┴───┴───┴───┘

各部分详解

起始位(Start Bit):每个数据帧以逻辑”0”开始,持续1位时间。空闲时线路保持逻辑”1”。接收端通过检测从”1”到”0”的跳变来识别新帧的开始。

数据位(Data Bits):紧跟起始位之后,通常为8位(最常用)或9位。数据以低位优先(LSB First)的方式发送,即 bit0 最先到达线路。

校验位(Parity Bit,可选):用于简单的错误检测。校验位的值由数据位中”1”的个数决定。

停止位(Stop Bits):表示帧的结束,通常为1位、1.5位或2位。停止位为逻辑”1”,用于给接收端提供恢复时间。

校验位的工作原理

校验位是一种简单的检错机制:

  • 无校验(None):不发送校验位,数据帧只有起始位+数据位+停止位
  • 奇校验(Odd):确保数据位和校验位中”1”的总数为奇数
  • 偶校验(Even):确保数据位和校验位中”1”的总数为偶数

例如,发送数据 0x55(二进制 01010101),其中有4个”1”: - 偶校验:校验位 = 0(保持总数为偶数4) - 奇校验:校验位 = 1(使总数变为奇数5)

注意:校验位只能检测奇数个比特错误,如果同时有2个比特出错,校验位可能检测不到。

信号时序与波形

理解串口通信的时序对于调试和问题排查至关重要。

TTL电平信号

在单片机级别(如STM32),USART使用TTL(晶体管-晶体管逻辑)电平:

电平状态 电压范围 逻辑含义
低电平 0V ~ 0.4V 逻辑 “0”(SPACE)
高电平 2.4V ~ 3.3V/5V 逻辑 “1”(MARK)

发送一个字节的时序示例

以9600波特率、8N1配置(8位数据、无校验、1位停止位)为例,发送数据 0x55

1
2
3
4
5
6
7
8
9
10
空闲状态: ──────────────────────────────────────────────
↓ 起始位 数据 '0x55' 停止位
┌───┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ ┌───┐
线路电平: ────────┘ │0│1│0│1│0│1│0│1│1│ │ 1 │
└───┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ └───┘
起始位 bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7 停止
(0) (1) (2) (3) (4) (5) (6) (7) 位

每个位持续时间 = 1/9600 ≈ 104.17 μs
总传输时间 = 10位 × 104.17 μs ≈ 1.04 ms

波特率与传输时间

波特率(Baud Rate)表示每秒传输的符号数,在串口中也就是每秒传输的位数。常见的标准波特率包括:

波特率 适用场景 每位时间
300 远距离、低速设备 3.33 ms
1200 老式调制解调器 833 μs
2400 低速串口设备 417 μs
9600 通用串口(最常用) 104 μs
19200 较快的数据传输 52 μs
115200 高速通信(USB转串口常用) 8.68 μs
921600 高速模块通信 1.08 μs
4608000 特殊高速需求 0.22 μs

传输一个字节所需时间 = (数据位 + 起始位 + 停止位 + 校验位) / 波特率

例如 115200 波特率、8N1 格式:传输时间 = 10位 / 115200 ≈ 86.8 μs

USART的工作原理

硬件结构

USART的内部结构可以分为以下几个主要部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
                ┌─────────────────────────────────────┐
│ USART 控制器 │
│ ┌─────────────────────────────────┐│
数据总线 ◄───────│──│ 发送数据寄存器 (TDR) ││
│ └───────────┬─────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐│
│ │ 发送移位寄存器 (8/9位) ││
│ └───────────┬─────────────────────┘│
│ │ │
│ ▼ ▼
│ ┌─────────────────────────────────┐│
│ │ TX 引脚 (串行输出) ││
│ └─────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────┐│
│ │ RX 引脚 (串行输入) ││
│ └───────────┬─────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐│
│ │ 接收移位寄存器 (8/9位) ││
│ └───────────┬─────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐│
数据总线 ◄───────│──│ 接收数据寄存器 (RDR) ││
│ └─────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────┐│
│ │ 波特率发生器 ││
│ └─────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────┐│
│ │ 状态/控制寄存器 ││
│ └─────────────────────────────────┘│
└─────────────────────────────────────┘

发送路径详解

流程:CPU(并行数据) → TDR → 发送移位寄存器 → TX引脚(串行位流)

  1. 写入数据寄存器 (TDR) CPU或DMA控制器通过内部数据总线,将8位或9位的并行数据写入发送数据寄存器(TDR)。此时数据暂存在TDR中等待发送。

  2. 传输至移位寄存器发送移位寄存器为空(上一个数据发送完毕)时,TDR中的数据会自动被硬件转移到发送移位寄存器中。一旦数据转移完成,状态寄存器中的 TXE (发送数据寄存器空) 标志置1,提示CPU可以立即写入下一个数据。这种双缓冲机制允许CPU在数据发送的同时准备下一个数据,大大提高了传输效率。

  3. 串行移位输出 发送控制器根据配置的波特率产生时钟。在时钟驱动下,发送移位寄存器将数据逐位向TX引脚移动。同时,硬件会自动在数据位前面插入起始位(逻辑0),在数据位后面插入校验位(如果启用)和停止位(逻辑1)

  4. 输出引脚 (TX) 最终,处理好的完整帧(起始位+数据+校验位+停止位)以串行位流的形式,通过TX引脚输出到外部设备。

接收路径详解

流程:RX引脚(串行位流) → 接收移位寄存器 → RDR → CPU(并行数据)

  1. 空闲检测与起始位采样 平时,USART的RX引脚保持在高电平状态(空闲状态)。当接收控制器检测到RX从高电平变为低电平时,说明一个新的数据帧即将开始。控制器会立即启动波特率发生器来同步采样时钟。

  2. 过采样与起始位验证 为了准确检测起始位并避免噪声干扰,USART通常采用16倍过采样(STM32默认配置)。即在每个比特时间采集16个样本,取中间几个样本进行多数表决。例如,采样序号8、9、10的三个值,取多数作为该位的实际值。

    过采样不仅提高了抗干扰能力,还能检测通信错误:

    • 如果连续3个采样周期内电平不一致,检测到噪声错误
    • 如果在停止位位置采样值为0,检测到帧错误
    • 如果奇偶校验不通过,检测到校验错误
  3. 数据位接收 完成起始位验证后,接收移位寄存器在同步时钟下,逐位采集RX引脚的电平状态。数据以低位优先的方式接收,即先接收bit0,最后接收bit7。

  4. 校验与停止位处理 接收完数据位后,硬件自动进行校验位检查(如果启用了校验)。然后等待停止位的到来,验证停止位是否为预期的逻辑1。

  5. 传输至数据寄存器 (RDR) 当一个完整的数据帧(8或9位数据)全部移入接收移位寄存器后,该数据会被并行转移到接收数据寄存器(RDR)中。此时,状态寄存器中的 RXNE (接收数据寄存器非空) 标志置1,同时可能会触发中断或DMA请求通知CPU读取数据。

  6. CPU读取 CPU或DMA控制器通过内部总线读取RDR中的数据。读取完成后,RXNE标志自动清零,USART准备接收下一个字节。

双缓冲机制详解

USART采用双缓冲设计,极大地提升了通信效率:

环节 缓冲区A 缓冲区B 工作方式
发送 TDR(发送数据寄存器) 发送移位寄存器 CPU写入TDR,同时移位寄存器发送TDR内容
接收 接收移位寄存器 RDR(接收数据寄存器) 移位寄存器接收数据,同时CPU读取RDR

双缓冲的意义:如果没有双缓冲,CPU必须在等待一个字节发送完成后才能写入下一个字节,这会导致CPU时间浪费。使用双缓冲后,CPU可以连续快速地向TDR写入数据,硬件会自动处理移位发送,CPU只需关注TXE标志即可。

USART的通信模式

异步通信模式(最常用)

异步模式下,发送端和接收端各自使用独立的时钟,通过波特率来约定数据传输的速率。这种模式布线简单,只需TX/RX两根线即可通信。

优点:接线简单,成本低,适合大多数点对点通信场景。

缺点:双方时钟必须足够接近(误差<5%),否则会累积误差导致数据错位。

同步通信模式

同步模式下,USART会通过CLK引脚输出时钟信号,与数据信号同步传输。时钟频率与波特率一致。

同步模式的典型应用: - 连接外部AD/DA转换器 - 与LCD显示屏通信 - 模拟SPI协议(虽然有局限)

重要限制:大多数USART硬件仅支持时钟输出,不支持时钟输入,因此无法实现两个USART设备之间的同步通信。如果需要真正的同步通信,通常使用专用的SPI或I2C接口。

单线半双工模式

某些USART支持单线模式,仅使用一根数据线(TX/RX复用),通过切换方向实现半双工通信。这种模式可以节省IO引脚。

USART与其他通信协议的对比

在嵌入式系统中,常见的通信协议还有SPI和I2C。选择合适的协议需要了解它们的特性:

特性 USART SPI I2C
全称 通用同步异步收发器 串行外设接口 集成电路间总线
线数 2-3根(TX/RX/CLK) 4根(MOSI/MISO/CLK/CS) 2根(SDA/SCL)
通信拓扑 点对点 一主多从 多主多从
最大速度 4.5-10 Mbps 数十 Mbps 3.4 Mbps(高速模式)
硬件流控 支持(RTS/CTS)
地址识别 通过CS引脚选择 7/10位地址
从设备数量 1个 多个(每个需独立CS) 多个(有限,总线电容限制)
协议复杂度 简单 中等 较复杂
功耗 中等 较高(持续时钟) 较低

何时选择USART: - 需要与电脑通信(USB转串口) - 两个设备之间的简单通信 - 需要硬件流控(RTS/CTS) - 传输距离相对较长(可达15米,RS232模式下)

何时选择SPI: - 高速数据传输(显示屏、存储芯片) - 一个主设备连接多个从设备 - 全双工通信需求

何时选择I2C: - 连接多个传感器 - 节省引脚(仅需2根线) - 需要多主机共存

USART的电气特性与电平标准

TTL电平(单片机级别)

大多数单片机(如STM32、51单片机)使用TTL电平:

项目 说明
逻辑”0” 0V ~ 0.4V
逻辑”1” 2.4V ~ 3.3V(或5V)
驱动能力 有限,短距离通信

RS232电平(传统串口)

老式电脑的串口使用RS232标准:

项目 说明
逻辑”0” +3V ~ +15V
逻辑”1” -15V ~ -3V
传输距离 最远15米
抗干扰 较强(使用差分信号概念)

注意:STM32的TTL电平与电脑的RS232电平不兼容,直接连接会损坏单片机!必须使用电平转换芯片(如MAX232)。

RS485电平(工业现场总线)

RS485用于工业环境的长距离通信:

项目 说明
信号类型 差分信号(A/B两线)
传输距离 最远1200米
传输速率 与距离相关(1200m时约100kbps)
拓扑结构 总线型,多设备共线
需要终端电阻 通常需要120Ω终端电阻

RS485通常需要外接收发器芯片(如MAX485、SP3485),并通过USART发送数据到收发器,再由收发器转换为差分信号。

USART的关键参数配置

波特率设置

波特率的精度取决于系统时钟和分频系数:

1
2
波特率 = f_ck / (16 × USARTDIV)    // 16倍过采样模式
波特率 = f_ck / (8 × USARTDIV) // 8倍过采样模式

其中USARTDIV是一个12位的分数寄存器。例如,系统时钟为72MHz,要设置115200波特率:

1
2
3
4
USARTDIV = 72000000 / (16 × 115200) ≈ 39.0625
整数部分 = 39 = 0x27
小数部分 = 0.0625 × 16 = 1
所以BRR寄存器值 = 0x271

波特率误差:如果计算出的波特率与目标值有偏差,当偏差过大时会导致通信错误。STM32的技术手册建议误差不超过±2.5%。

数据位长度

配置 数据位 校验位 总有效数据
8N0 8位 8位/帧
8E1 8位 偶校验 8位/帧
8O1 8位 奇校验 8位/帧
9N1 9位 9位/帧
9N2 9位 9位/帧

9位数据模式常用于实现多机通信:主机发送地址(第九位=1)和数据(第九位=0),从机通过检测第九位来识别地址帧。

停止位长度

停止位长度 适用场景
0.5位 低速通信,节省时间
1位 最常用
1.5位 仅在数据位=8时有效
2位 高速通信,增强可靠性

USART的高级特性

硬件流控制(RTS/CTS)

当发送方和接收方速度不匹配时,可能导致数据丢失。硬件流控制通过额外的两根信号线来解决:

信号 全称 功能
RTS Request To Send 发送方输出,表示”我准备好接收数据了”
CTS Clear To Send 接收方输出,表示”你可以发送数据了”

工作流程: 1. 接收方准备好接收时,将CTS置低 2. 发送方检测到CTS为低,开始发送数据 3. 接收方缓冲区快满时,将CTS置高 4. 发送方检测到CTS为高,暂停发送

多处理器通信

USART支持多处理器模式,通过第9位来区分地址帧和数据帧:

  1. 所有从机配置为接收模式,检测第9位
  2. 主机发送地址帧(第9位=1),所有从机接收
  3. 从机将自己的地址与接收到的地址比较
  4. 匹配的从机切换到正常接收模式(第9位=0)
  5. 其他从机保持忽略状态

LIN协议支持(汽车网络)

USART硬件支持LIN(Local Interconnect Network)协议的简化版本,常用于汽车电子中的低速网络。

USART的典型应用场景

调试与日志输出

通过USART连接电脑,使用串口调试助手实时查看程序运行状态,是嵌入式开发最常用的调试手段。

模块通信

模块类型 通信协议 波特率
GPS模块 NMEA 0183 9600
ESP8266 WiFi模块 AT指令 115200
蓝牙模块 透传模式 9600/115200
指纹模块 自定义协议 57600
OLED显示屏 I2C/SPI -

数据采集传输

传感器数据通过USART发送到上位机或数据采集卡,实现环境监测、工业控制等功能。

双机通信

两片STM32之间通过USART实现数据交换,可用于分布式控制系统。

USART的常见问题与解决方案

问题现象 可能原因 解决方案
收到乱码 波特率不匹配 确认双方波特率一致
过采样模式不同 统一使用16倍或8倍过采样
时钟偏差过大 检查晶振或PLL配置
无数据接收 TX/RX接反 交换TX和RX线
未使能接收中断 检查NVIC和中断回调函数
硬件连接问题 检查杜邦线连接和电平匹配
数据丢包 接收处理太慢 使用DMA或增加缓冲区
波特率过高 降低波特率或优化处理逻辑
通信时好时坏 线缆质量差 更换屏蔽线或缩短距离
地线未连接 确保共地
电磁干扰 使用差分信号或增加滤波

USART特性总结

灵活的通信模式:支持同步和异步通信,可根据应用需求切换。异步模式最为常用,仅需两根线即可实现全双工通信。

硬件自动化处理:数据帧的组包、拆包、起始位和停止位的添加/移除、校验位的生成与检查,都由硬件自动完成,CPU只需读写数据寄存器。

高效的双缓冲机制:发送和接收各有两个缓冲区(数据寄存器+移位寄存器),允许CPU和硬件并行工作,提高通信效率。

丰富的可配置参数:波特率、数据位长度、停止位数量、校验方式都可以根据需要调整,适应不同的通信需求。

多种传输方式:支持阻塞轮询、中断和DMA三种传输方式,可根据数据量和实时性要求选择合适的方案。

STM32 代码实现

本节以 STM32F4 系列为例,演示使用 HAL 库进行 USART 通信的完整流程。

1. 初始化配置

首先需要在 STM32CubeMX 中配置 USART 参数,或者手动初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* USART 初始化配置示例(USART1, GPIOA: TX=PA9, RX=PA10) */
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200; // 波特率 115200
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 8 位数据位
huart1.Init.StopBits = UART_STOPBITS_1; // 1 位停止位
huart1.Init.Parity = UART_PARITY_NONE; // 无校验
huart1.Init.Mode = UART_MODE_TX_RX; // 收发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控
huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 16 倍过采样
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}

2. 阻塞式发送与接收

阻塞模式是最简单的通信方式,函数会等待数据发送或接收完成才返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 阻塞式发送字符串 */
void UART_Send_String(UART_HandleTypeDef *huart, char *str)
{
HAL_UART_Transmit(huart, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
}

/* 阻塞式接收一个字节 */
uint8_t UART_Receive_Byte(UART_HandleTypeDef *huart)
{
uint8_t data;
HAL_UART_Receive(huart, &data, 1, HAL_MAX_DELAY);
return data;
}

/* 阻塞式发送指定长度数据 */
void UART_Send_Data(UART_HandleTypeDef *huart, uint8_t *data, uint16_t len)
{
HAL_UART_Transmit(huart, data, len, HAL_MAX_DELAY);
}

3. 中断式发送与接收

中断方式可以让 CPU 在数据发送或接收期间处理其他任务,提高系统效率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 定义全局缓冲区 */
uint8_t rx_buffer[256];
uint8_t tx_buffer[256];
volatile uint8_t rx_index = 0;
volatile uint8_t rx_complete = 0;

/* 开启中断接收 */
void UART_Start_Receive_IT(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &rx_buffer[rx_index], 1);
}

/* USART 中断回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
rx_index++;
if (rx_index >= 256) rx_index = 0;

// 收到换行符表示一帧数据结束
if (rx_buffer[rx_index - 1] == '\n')
{
rx_complete = 1;
}
else
{
// 继续接收下一个字节
HAL_UART_Receive_IT(huart, &rx_buffer[rx_index], 1);
}
}
}

/* 中断发送函数 */
void UART_Send_IT(UART_HandleTypeDef *huart, uint8_t *data, uint16_t len)
{
HAL_UART_Transmit_IT(huart, data, len);
}

/* 发送完成回调 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
// 可以在这里添加发送完成后的处理逻辑
}

4. DMA 传输

DMA(直接内存访问)可以让 USART 在不占用 CPU 的情况下传输数据,非常适合大数据量传输:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* DMA 配置(需要在 CubeMX 中使能对应 DMA) */
#define TX_BUFFER_SIZE 256
#define RX_BUFFER_SIZE 256

uint8_t dma_tx_buffer[TX_BUFFER_SIZE];
uint8_t dma_rx_buffer[RX_BUFFER_SIZE];
volatile uint8_t dma_tx_complete = 0;
volatile uint8_t dma_rx_complete = 0;

/* 开启 DMA 接收 */
void UART_Start_Receive_DMA(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_DMA(huart, dma_rx_buffer, RX_BUFFER_SIZE);
}

/* DMA 发送 */
void UART_Send_DMA(UART_HandleTypeDef *huart, uint8_t *data, uint16_t len)
{
HAL_UART_Transmit_DMA(huart, data, len);
}

/* DMA 接收完成回调 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
dma_rx_complete = 1;
}

/* DMA 发送完成回调 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
dma_tx_complete = 1;
}

/* 使用示例:发送大量数据 */
void Example_Send_Large_Data(void)
{
// 准备数据
for (int i = 0; i < 100; i++)
{
dma_tx_buffer[i] = i;
}

// DMA 方式发送(不阻塞 CPU)
UART_Send_DMA(&huart1, dma_tx_buffer, 100);

// 此时 CPU 可以处理其他任务
// ...

// 等待发送完成
while(!dma_tx_complete);
dma_tx_complete = 0;
}

5. printf 重定向

printf 函数重定向到 USART,方便调试输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 重定向 printf 到 USART1 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}

/* 现在可以直接使用 printf */
void Example_Printf(void)
{
printf("USART Initialized\r\n");
printf("Baudrate: %d\r\n", 115200);
printf("Data: %02X\r\n", 0xFF);
}

实用技巧与注意事项

1. 硬件接线

串口通信只需要三根线(如果是TTL电平):

STM32 引脚 功能 连接设备
TX (发送) 连接对方的 RX RX
RX (接收) 连接对方的 TX TX
GND 共地 GND

注意:不同设备间通信时电平必须匹配。STM32 通常使用 TTL 电平(0~3.3V),而电脑使用 RS232(±12V)USB 转串口。不匹配的电平可能会损坏设备!

2. 常见问题排查

问题现象 可能原因 解决方案
接收到乱码 波特率不匹配 确认双方波特率一致
无数据接收 TX/RX 接反 交换 TX 和 RX 线
接线松动 检查杜邦线连接
未使能接收中断 检查中断使能和回调函数
数据丢包 数据处理太慢 使用 DMA 或增加缓冲区
波特率过低 提高波特率

3. 调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* 方法1:使用 LED 指示状态 */
void UART_Debug_With_LED(void)
{
// 收到数据时翻转 LED
if (rx_complete)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
rx_complete = 0;
}
}

/* 方法2:添加超时机制防止阻塞 */
HAL_StatusTypeDef UART_Receive_With_Timeout(UART_HandleTypeDef *huart,
uint8_t *data, uint32_t timeout)
{
return HAL_UART_Receive(huart, data, 1, timeout);
}

/* 方法3:环形缓冲区实现 */
#define RB_SIZE 256
typedef struct {
uint8_t buffer[RB_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
} RingBuffer;

void RB_Put(RingBuffer *rb, uint8_t data)
{
uint16_t next = (rb->head + 1) % RB_SIZE;
if (next != rb->tail) {
rb->buffer[rb->head] = data;
rb->head = next;
}
}

uint8_t RB_Get(RingBuffer *rb, uint8_t *data)
{
if (rb->tail == rb->head) return 0; // 空
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % RB_SIZE;
return 1;
}

4. 通信协议设计建议

在实际项目中,为了保证通信可靠性,建议设计一个简单的通信协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 简单帧格式示例 */
typedef struct {
uint8_t header[2]; // 帧头 0xAA 0x55
uint8_t length; // 数据长度
uint8_t type; // 数据类型
uint8_t data[32]; // 数据域
uint8_t checksum; // 校验和
uint8_t footer; // 帧尾 0x0D 0x0A
} UART_Frame;

/* 校验和计算 */
uint8_t Calc_Checksum(uint8_t *data, uint8_t len)
{
uint8_t sum = 0;
for (int i = 0; i < len; i++) {
sum += data[i];
}
return sum;
}

总结

USART 作为嵌入式开发中最常用的通信接口之一,掌握其原理和编程方法是每个嵌入式工程师的必备技能。本文详细介绍了 USART 的工作原理、特点,并提供了基于 STM32 HAL 库的完整代码示例,包括阻塞、中断和 DMA 三种通信方式。在实际开发中,可以根据数据量和实时性要求选择合适的通信方式,同时注意电平匹配和波特率配置,避免常见的通信故障。