UART——STM32
1. 引言UARTUniversal Asynchronous Receiver/Transmitter通用异步收发器是嵌入式开发中最常用的通信接口之一。无论是调试信息输出、传感器数据采集还是与上位机、蓝牙模块、WiFi 模块通信UART 都扮演着不可或缺的角色。本文将基于 STM32 系列单片机从 UART 的基础原理出发逐步讲解 HAL 库与标准外设库两种开发方式下的 UART 配置方法并结合实际代码示例帮助读者快速掌握 STM32 UART 的使用技巧。2. UART 基础原理2.1 什么是 UARTUART 是一种异步串行通信协议通过两根数据线TX 发送、RX 接收实现全双工通信。与 SPI、I2C 等同步通信协议不同UART 不需要时钟线通信双方通过约定波特率Baud Rate来同步数据采样时刻。2.2 数据帧格式一个标准的 UART 数据帧由以下部分组成起始位1 位逻辑低电平标志数据传输开始数据位通常为 8 位也可配置为 7 位或 9 位校验位可选奇校验或偶校验停止位1 位或 2 位逻辑高电平标志数据传输结束起始位 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 校验位(可选) | 停止位2.3 波特率波特率表示每秒传输的比特数常用值有 9600、115200、460800 等。发送方与接收方必须使用相同的波特率才能正确通信。STM32 的波特率由系统时钟经过分频计算得到计算公式如下波特率 时钟频率 / (16 × USARTDIV)3. STM32 UART 硬件资源3.1 常用 UART 外设STM32 系列芯片通常集成多个 UART/USART 外设例如USART1挂载在 APB2 总线时钟频率较高USART2、USART3挂载在 APB1 总线UART4、UART5仅支持异步通信不支持同步模式不同型号的 STM32 芯片 UART 数量不同具体以芯片数据手册为准。3.2 引脚复用UART 的 TX、RX 引脚通常与 GPIO 复用需要通过 GPIO 配置将引脚映射到对应的 UART 外设。例如 STM32F103 的 USART1 默认引脚为TXPA9RXPA104. 基于 HAL 库的 UART 配置4.1 CubeMX 图形化配置使用 STM32CubeMX 可以快速完成 UART 的初始化配置步骤如下在 Pinout 视图中选择 USART1模式选择Asynchronous配置波特率、数据位、停止位、校验位在 GPIO Settings 中确认 TX、RX 引脚分配生成工程代码4.2 初始化代码解析CubeMX 生成的 UART 初始化代码位于usart.c文件中核心结构如下UART_HandleTypeDef huart1;staticvoidMX_USART1_UART_Init(void){huart1.InstanceUSART1;huart1.Init.BaudRate115200;huart1.Init.WordLengthUART_WORDLENGTH_8B;huart1.Init.StopBitsUART_STOPBITS_1;huart1.Init.ParityUART_PARITY_NONE;huart1.Init.ModeUART_MODE_TX_RX;huart1.Init.HwFlowCtlUART_HWCONTROL_NONE;huart1.Init.OverSamplingUART_OVERSAMPLING_16;if(HAL_UART_Init(huart1)!HAL_OK){Error_Handler();}}4.3 GPIO 复用配置staticvoidMX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct{0};__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitStruct.PinGPIO_PIN_9|GPIO_PIN_10;GPIO_InitStruct.ModeGPIO_MODE_AF_PP;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);}5. UART 数据收发5.1 阻塞式发送uint8_tdata[]Hello STM32!\r\n;HAL_UART_Transmit(huart1,data,sizeof(data)-1,1000);最后一个参数为超时时间单位为毫秒。若在指定时间内未完成发送函数返回HAL_TIMEOUT。5.2 阻塞式接收uint8_trx_data;HAL_UART_Receive(huart1,rx_data,1,1000);5.3 中断方式收发中断方式不占用 CPU 等待时间适合实时性要求较高的场景。// 启动中断接收HAL_UART_Receive_IT(huart1,rx_data,1);// 中断回调函数voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){// 处理接收到的数据HAL_UART_Transmit(huart1,rx_data,1,100);// 重新开启中断接收HAL_UART_Receive_IT(huart1,rx_data,1);}}5.3.1 中断 环形缓冲区完整接收示例上面的示例每次只接收 1 个字节数据稍多就容易丢失。实际工程中更推荐「中断接收 环形缓冲区」的组合中断只负责把字节快速写入缓冲区主循环再从缓冲区取走数据解析既不会丢数据也不会阻塞中断。#includestdint.h#includestring.h/* 环形缓冲区定义 */#defineRING_BUF_SIZE256// 缓冲区大小建议为 2 的幂便于取模typedefstruct{uint8_tbuffer[RING_BUF_SIZE];// 数据存储区volatileuint16_thead;// 写指针指向下一个待写入的位置volatileuint16_ttail;// 读指针指向下一个待读取的位置}RingBuffer_t;RingBuffer_t uart1_rx_ring;// USART1 接收环形缓冲区实例/* 缓冲区写入函数在中断回调中调用 */voidRingBuffer_Write(RingBuffer_t*ring,uint8_tdata){uint16_tnext(ring-head1)%RING_BUF_SIZE;// 计算下一个写位置// 若下一个写位置等于读指针说明缓冲区已满丢弃该字节可在此做溢出计数if(next!ring-tail){ring-buffer[ring-head]data;// 写入数据ring-headnext;// 更新写指针}}/* 缓冲区读取函数在主循环中调用 *//* 返回值1 表示成功读到 1 字节0 表示缓冲区为空 */uint8_tRingBuffer_Read(RingBuffer_t*ring,uint8_t*data){if(ring-headring-tail)// 读写指针相等说明缓冲区为空{return0;}*dataring-buffer[ring-tail];// 取出数据ring-tail(ring-tail1)%RING_BUF_SIZE;// 更新读指针return1;}/* 查询缓冲区中待处理的数据量 */uint16_tRingBuffer_Count(RingBuffer_t*ring){return(uint16_t)((ring-head-ring-tailRING_BUF_SIZE)%RING_BUF_SIZE);}/* 初始化清空缓冲区 */voidRingBuffer_Init(RingBuffer_t*ring){ring-head0;ring-tail0;memset(ring-buffer,0,RING_BUF_SIZE);}/* 中断回调每收到 1 字节就写入缓冲区 */voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){// 把刚收到的字节写入环形缓冲区RingBuffer_Write(uart1_rx_ring,rx_data);// 重新开启中断接收等待下一个字节HAL_UART_Receive_IT(huart1,rx_data,1);}}/* 主循环从缓冲区取数据并处理 */intmain(void){uint8_tch;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();RingBuffer_Init(uart1_rx_ring);// 初始化环形缓冲区// 启动中断接收每次接收 1 字节触发回调后自动重新开启HAL_UART_Receive_IT(huart1,rx_data,1);while(1){// 主循环中轮询读取缓冲区不阻塞中断while(RingBuffer_Read(uart1_rx_ring,ch)){// 在这里对 ch 做业务处理例如回显、协议解析、命令分发等HAL_UART_Transmit(huart1,ch,1,100);}// 可执行其他低优先级任务}}要点说明读写指针分离head只由中断回调写入tail只由主循环读取二者互不干扰天然避免了临界区竞争。满则丢弃缓冲区写满时新数据会被丢弃避免覆盖尚未读取的旧数据如需保留最新数据可改为覆盖tail的策略。容量取模(head 1) % RING_BUF_SIZE实现环形回绕缓冲区大小取 2 的幂时可用位运算 (RING_BUF_SIZE - 1)进一步提速。中断开销极小回调里只做「写一个字节 重新开启接收」耗时极短不会影响其他中断的实时性。5.4 DMA 方式收发DMA 方式适合大批量数据传输可显著降低 CPU 负载。uint8_ttx_buffer[64];HAL_UART_Transmit_DMA(huart1,tx_buffer,64);6. 基于标准外设库的 UART 配置对于使用标准外设库Standard Peripheral Library的开发者UART 配置方式如下GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);// 配置 GPIOGPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA,GPIO_InitStructure);// 配置 USARTUSART_InitStructure.USART_BaudRate115200;USART_InitStructure.USART_WordLengthUSART_WordLength_8b;USART_InitStructure.USART_StopBitsUSART_StopBits_1;USART_InitStructure.USART_ParityUSART_Parity_No;USART_InitStructure.USART_ModeUSART_Mode_RX|USART_Mode_TX;USART_Init(USART1,USART_InitStructure);// 使能 USARTUSART_Cmd(USART1,ENABLE);7. 串口重定向 printf在调试过程中将 printf 重定向到串口可以极大方便日志输出。以 HAL 库为例#includestdio.hintfputc(intch,FILE*f){HAL_UART_Transmit(huart1,(uint8_t*)ch,1,100);returnch;}intfgetc(FILE*f){uint8_tch;HAL_UART_Receive(huart1,ch,1,100);returnch;}配置完成后即可直接使用printf(value %d\r\n, value);输出调试信息。8. 常见问题与注意事项8.1 乱码问题检查波特率是否与上位机一致检查系统时钟配置是否正确检查数据位、停止位、校验位设置是否匹配8.2 接收不到数据确认 RX 引脚配置为浮空输入或上拉输入检查中断优先级配置确认共地连接8.3 发送数据不完整检查 DMA 配置是否正确确认发送缓冲区大小足够注意发送完成后是否需要等待9. 实战案例串口控制 LED下面通过一个完整示例演示如何通过串口接收指令控制 LED 开关。#includemain.hUART_HandleTypeDef huart1;uint8_trx_data;intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// 启动中断接收HAL_UART_Receive_IT(huart1,rx_data,1);while(1){// 主循环可执行其他任务}}voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){if(rx_data1){HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);HAL_UART_Transmit(huart1,(uint8_t*)LED ON\r\n,8,100);}elseif(rx_data0){HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);HAL_UART_Transmit(huart1,(uint8_t*)LED OFF\r\n,9,100);}// 重新开启中断接收HAL_UART_Receive_IT(huart1,rx_data,1);}}##10.参考资料与延伸阅读 以下资料可帮助读者进一步深入理解 STM32 UART 的底层原理与工程实践-**STM32F103 参考手册RM0008**ST 官方发布的芯片级参考手册详细描述了 USART 外设的寄存器定义、工作模式与中断/DMA 触发机制是查阅 UART 硬件细节的第一手资料。适合需要深入理解底层实现、排查硬件问题的开发者。-**STM32 HAL 库用户手册UM1025**官方 HAL 库的完整使用说明涵盖 HAL_UART_Transmit、HAL_UART_Receive_IT、HAL_UART_Transmit_DMA 等 API 的调用规范与回调机制。适合使用 HAL 库进行应用层开发的读者。-**环形缓冲区Ring Buffer设计与实现**介绍环形缓冲区的指针管理、满/空判断、并发读写等核心设计要点与本文5.3.1小节的示例直接对应。适合需要设计高可靠串口接收模块、避免数据丢失的开发者。-**STM32 中断优先级与 DMA 配置指南**讲解 NVIC 中断优先级分组、抢占优先级与子优先级的设置方法以及 DMA 通道与中断的配合策略。适合在中断接收、DMA 收发场景下需要合理配置优先级、避免数据竞争的项目。-**STM32CubeMX 用户指南UM1718**官方图形化配置工具的使用手册涵盖引脚分配、时钟树配置与代码生成流程。适合刚接触 CubeMX、希望快速搭建工程骨架的初学者。 本文从 UART 的基本原理出发详细介绍了 STM32 平台下 UART 的配置与使用方法 ##11.总结 本文从 UART 的基本原理出发详细介绍了 STM32 平台下 UART 的配置与使用方法涵盖 HAL 库与标准外设库两种开发方式并提供了中断、DMA 等多种收发模式的实际代码示例。**核心要点回顾**UART 通过 TX/RX 两根数据线实现异步全双工通信通信双方需约定一致的波特率、数据位、停止位与校验位STM32 的 UART 引脚与 GPIO 复用需先完成时钟使能与引脚映射配置。**两种开发方式对比**HAL 库配合 CubeMX 图形化配置代码可读性好、移植性强适合快速开发与维护标准外设库寄存器操作更直接、占用资源更少适合对底层有深入理解或资源受限的旧工程。**收发模式选择建议**少量数据或调试场景用阻塞式简单直接实时性要求高时用中断方式配合环形缓冲区可避免数据丢失大批量数据传输则优先 DMA显著降低 CPU 负载。**常见调试技巧**使用逻辑分析仪抓取 TX/RX 波形可直观核对波特率与帧格式是否正确串口助手调试时注意勾选与固件一致的波特率、数据位等参数并善用十六进制显示排查非 ASCII 数据。 希望本文能帮助读者快速上手 STM32 UART 开发在实际项目中灵活运用这一经典通信接口。 涵盖 HAL 库与标准外设库两种开发方式并提供了中断、DMA 等多种收发模式的实际代码示例。 在实际项目中建议根据通信数据量、实时性要求等因素选择合适的收发方式-**少量数据、调试场景**阻塞式或中断方式-**大批量数据**DMA 方式-**实时性要求高**中断方式配合环形缓冲区 希望本文能帮助读者快速上手 STM32 UART 开发在实际项目中灵活运用这一经典通信接口。

相关新闻

RC滤波设计详解从截止频率到工程计算

RC滤波设计详解从截止频率到工程计算

在电压采样、电流检测和传感器接口里,一个电阻、一个电容就能组成滤波器。但选好截止频率,并不意味着设计已经完成。滤掉多少干扰、损失多少有效信号、多久才能稳定,以及后级是否能正确采样,都要一起计算。 本文围绕单端、一阶无…

2026/9/30 5:06:17 阅读更多 →
工业缺陷检测小样本训练与漏检控制:YOLO微调与异常检测级联实战

工业缺陷检测小样本训练与漏检控制:YOLO微调与异常检测级联实战

1. 工业缺陷检测的现状与核心挑战拆解工业缺陷检测这个方向,我在产线上摸爬滚打了好几年,从最早的模板匹配、传统图像处理,到后来全面转向深度学习,踩过的坑比走过的路还多。这个领域的核心矛盾其实一直没变:产线对漏检…

2026/9/30 5:05:17 阅读更多 →
PyTorch训练中tqdm进度条的工程化实践与性能优化

PyTorch训练中tqdm进度条的工程化实践与性能优化

1. 为什么训练时盯着终端“刷屏”是种低效的自我折磨?你有没有过这样的经历:启动一个PyTorch训练脚本,然后端着咖啡杯坐在电脑前,眼睛死死盯住终端里那一行行飞速滚动的loss: 2.4567,acc: 0.6321,epoch: 1/50——不是因为你在实时…

2026/9/30 5:05:17 阅读更多 →

最新新闻

欧拉法求常微分方程近似解:原理、Python实现与步长稳定性指南

欧拉法求常微分方程近似解:原理、Python实现与步长稳定性指南

欧拉法(Eulers method)求常微分方程近似解,是我见过最容易被轻视、也最容易被误用的数值方法。几乎每个人的第一门数值分析课都会讲它,公式只有一行,代码不到十行,于是很多人写完就丢在一边,转头…

2026/9/30 5:54:40 阅读更多 →
基于双隐含层Elman神经网络的松散回潮出口含水率预测与加水比例反推

基于双隐含层Elman神经网络的松散回潮出口含水率预测与加水比例反推

简介:这份PDF面向卷烟制丝工程技术人员与工业过程控制方向的研究者,聚焦松散回潮出口含水率难以精确控制这一实际难题。资源以Elman神经网络为核心,结合历史生产数据建立加水比例预测模型,将加水比例与环境温湿度作为输入&#xf…

2026/9/30 5:54:40 阅读更多 →
DeepSeek+GPU集群:基层CT影像辅助诊断模型训练实战

DeepSeek+GPU集群:基层CT影像辅助诊断模型训练实战

简介:这份PDF文档面向基层医院影像科医生、医疗AI方向的研究者与工程技术人员,围绕DeepSeek模型与GPU集群部署,讲解如何构建CT影像辅助诊断模型的完整训练流程。内容从医疗影像分析现状与基层医院痛点切入,依次覆盖DeepSeek技术原…

2026/9/30 5:54:40 阅读更多 →
生产级 WebSocket 中继:面向工业边缘的帧级流控与上下文桥接

生产级 WebSocket 中继:面向工业边缘的帧级流控与上下文桥接

1. 项目概述:为什么一个 WebSocket 中继需要“生产级”这个前缀?我第一次在 GitHub 上看到 Orca Cloud Relay 这个项目时,心里其实是有点疑惑的——不就是个 WebSocket 转发器吗?用 Node.js 的ws库写个on(message) → send()就能跑…

2026/9/30 5:54:40 阅读更多 →
GTK入门实战:从零打造Linux原生图形界面

GTK入门实战:从零打造Linux原生图形界面

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/30 5:54:40 阅读更多 →
Windows 与 Linux 命令对照及终端选型避坑

Windows 与 Linux 命令对照及终端选型避坑

在 Windows 和 Linux 之间来回切换的人,多少都经历过这种尴尬:在 Linux 上肌肉记忆敲出ls,换到 cmd 里一回车,屏幕冷冷回一句"不是内部或外部命令";反过来在 PowerShell 里习惯了Get-ChildItem,登…

2026/9/30 5:53:39 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述:为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多,后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表,动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介:本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档,聚焦城市公共广告资源信息化管理痛点,提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构,含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求,背景很直接:公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关,开放给几个业务团队用。结果第一个月账单出来,额度直接超了 4 倍。仔细查日志,发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 8:16:59 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/29 8:24:48 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/29 3:55:56 阅读更多 →