深入理解USART通信协议

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

什么是USART?

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

与UART的区别

特性UARTUSART
全称通用异步收发器通用同步异步收发器
通信模式仅支持异步支持同步和异步
时钟信号无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。选择合适的协议需要了解它们的特性:

特性USARTSPII2C
全称通用同步异步收发器串行外设接口集成电路间总线
线数2-3根(TX/RX/CLK)4根(MOSI/MISO/CLK/CS)2根(SDA/SCL)
通信拓扑点对点一主多从多主多从
最大速度4.5-10 Mbps数十 Mbps3.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%。

数据位长度

配置数据位校验位总有效数据
8N08位8位/帧
8E18位偶校验8位/帧
8O18位奇校验8位/帧
9N19位9位/帧
9N29位9位/帧

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

停止位长度

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

USART的高级特性

硬件流控制(RTS/CTS)

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

信号全称功能
RTSRequest To Send发送方输出,表示”我准备好接收数据了”
CTSClear 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 01839600
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 (发送)连接对方的 RXRX
RX (接收)连接对方的 TXTX
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 三种通信方式。在实际开发中,可以根据数据量和实时性要求选择合适的通信方式,同时注意电平匹配和波特率配置,避免常见的通信故障。