STM32 UART串口通信协议与3种底层驱动实现(寄存器/标准库/HAL库)
串口通信是STM32与上位机(电脑、手机、串口调试助手等)交互的核心方式,其稳定性依赖标准化的帧结构、校验规则与配置参数。本文将从串口通信核心规范入手,详细讲解STM32下UART串口的3种核心底层驱动实现方式(直接操作寄存器、标准外设库、HAL库),并对比不同方案的核心差异。
一、串口通信核心规范
串口数据收发的稳定性完全依赖统一的通信规范,包含帧结构、校验方式、配置参数等核心要素。
1.1 串口数据包结构
完整的数据帧需包含固定格式,保障传输完整性与可校验性:
包头 + 命令 + 数据 + 校验 + 包尾
1.2 数据校验方式
数据传输过程中易受干扰,需通过校验机制验证数据完整性,工程中常用以下3种方式:
- CR校验
- 异或校验
- 和校验(工程最常用)
1.3 串口配置核心参数
串口通信的基础参数需上下位机保持一致,核心配置如下:
- 波特率:默认115200(可根据需求灵活调整)
- 数据位:8位
- 停止位:1位
- 校验位:无校验
- 通信模式:异步通信
- 采样方式:16倍过采样
1.4 数据接收与校验逻辑
以接收缓冲区buf为例,典型的帧校验逻辑如下(工程常用):
- 帧头判定:
buf[0] == 0x55 - 帧尾判定:
buf[4] == 0xff - 校验逻辑:
buf[1] + buf[2] == buf[3] - 异常处理:校验失败时需返回错误信息,避免脏数据被处理
二、STM32 UART 3种核心底层驱动实现
以下3种方式均为直接操作UART外设的硬件底层驱动方案,相互独立、同级并列,适用于不同开发场景。
2.1 直接操作寄存器(最底层)
跳过所有库封装,直接读写UART硬件寄存器,适合深入理解串口底层工作原理。
核心寄存器说明
UART的核心操作依赖状态寄存器(SR)和数据寄存器(DR):
- SR(状态寄存器):位7(TXE)表示发送数据寄存器空;位5(RXNE)表示接收数据寄存器非空。
- DR(数据寄存器) :串口收发数据的载体,写DR完成数据发送,读DR获取接收数据。
(注:完整寄存器说明需参考对应STM32型号的数据手册)

代码实现(单字节收发+回显)
c
// 单字节发送函数
void SendBit(uint8_t Bit)
{
// 等待发送寄存器为空
while(!(USART1->SR & (1<<7))){}
// 写入数据到发送寄存器
USART1->DR = Bit;
}
// 单字节接收函数
uint8_t ReciveBit(void)
{
uint8_t Bit;
// 等待接收寄存器非空
while(!(USART1->SR & (1<<5))){}
// 读取接收寄存器数据
Bit = USART1->DR;
return Bit;
}
// 主循环实现数据回显
while (1)
{
uint8_t ch = ReciveBit();
SendBit(ch);
}
2.2 标准外设库实现(STM32F1示例)
ST早期官方推出的标准外设库,封装了寄存器操作,兼顾底层可读性与开发效率(注:该库已停止官方维护)。
关键实现步骤
- 开启UART和对应GPIO的时钟;
- 配置TX引脚为复用推挽输出,RX引脚为浮空输入;
- 初始化UART通信参数(波特率、数据位、停止位等);
- 使能UART外设。
代码实现
c
#include "stm32f10x.h"
// UART初始化函数
void UART_Init() {
// 开启USART1和GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
// 配置TX引脚(PA9)为复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置RX引脚(PA10)为浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 初始化UART参数
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStruct);
// 使能USART1
USART_Cmd(USART1, ENABLE);
}
// 单字符发送函数
void UART_SendChar(USART_TypeDef* USARTx, char ch) {
// 等待发送寄存器为空
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
// 发送字符
USART_SendData(USARTx, ch);
}
int main() {
UART_Init();
// 循环发送字符'H'
while (1) {
UART_SendChar(USART1, 'H');
}
}
2.3 HAL库实现(CubeMX生成)
ST当前主推的开发方案,高度封装,配合CubeMX图形化配置工具可快速完成开发,适配新芯片。
前期配置(CubeMX)
- 选择对应STM32型号,开启USART1;
- 配置UART模式为"Asynchronous"(异步);
- 设置波特率、数据位、停止位等核心参数;
- 生成工程代码(本文以MDK-ARM为例)。
(注:配置界面参考"串口cube配置"截图)
代码实现(循环发送字符串)
c
#include "main.h"
#include "usart.h"
#include "gpio.h"
int main(void) {
// 初始化HAL库
HAL_Init();
// 初始化GPIO
MX_GPIO_Init();
// 初始化USART1
MX_USART1_UART_Init();
uint8_t send_buf[] = "Hello, UART!\r\n";
while (1) {
// 发送字符串,超时时间100ms
HAL_UART_Transmit(&huart1, send_buf, sizeof(send_buf)-1, 100);
// 延时1秒
HAL_Delay(1000);
}
}
三、标准库与HAL库核心差异对比
| 对比维度 | 标准外设库 | HAL库 |
|---|---|---|
| 代码风格 | 接近寄存器操作,底层逻辑清晰 | 高度封装,代码简洁易移植 |
| 学习成本 | 适合学习底层原理 | 适合快速开发,上手快 |
| 官方支持 | 停止维护 | 主推方案,新芯片专属支持 |
| 开发效率 | 中等(需手动配置更多参数) | 高(CubeMX一键生成) |
四、总结
STM32 UART串口的3种底层驱动实现方式各有适用场景:
- 直接操作寄存器:适合深入理解串口硬件原理,或对代码体积、执行效率有极致要求的场景;
- 标准外设库:适合学习STM32外设驱动逻辑,兼容老项目维护;
- HAL库:适合新项目开发、快速迭代,尤其是使用STM32新款芯片的场景。
无论选择哪种方式,串口通信的核心始终是统一的帧结构、校验规则和配置参数,这是保障通信稳定性的基础。