行业资讯
📅 2026/8/1 4:43:30
STM32串口初始化实战:标准库配置USART1/2/3与避坑指南
1. 项目概述为什么串口初始化是STM32开发的基石在嵌入式开发领域尤其是基于STM32的项目中串口通信USART/UART几乎是每个项目都绕不开的基础功能。无论是用于打印调试信息、与上位机通信还是连接GPS、蓝牙、Wi-Fi等模块串口都扮演着“系统嘴巴和耳朵”的关键角色。很多新手在拿到开发板后跑通LED闪烁就卡在了串口这里看着教程里一堆寄存器配置头大如斗。而标准库Standard Peripheral Library的出现正是为了降低这道门槛它用一套结构清晰、函数封装的API把底层硬件的复杂操作包装起来让我们能更专注于业务逻辑。今天我就以最常用的STM32F1系列为例手把手带你写出USART1、USART2、USART3这三个串口的初始化函数代码。别小看这个“初始化”它就像盖房子打地基配置不对后续的收发数据全是空中楼阁。我会把标准库的配置逻辑掰开揉碎了讲不仅给你能直接“抄作业”的代码更要把每个参数为什么这么设置、背后对应的硬件原理是什么、以及我踩过的那些坑都分享出来。无论你是刚接触STM32还是想巩固一下通信基础这篇内容都能让你对串口有一个透彻的理解。2. 核心思路与硬件原理拆解2.1 串口通信的本质与STM32的USART外设串口通信简单说就是两根线RX接收TX发送按约定好的节奏一位一位地传输数据。这个“节奏”就是波特率比如9600bps表示每秒传输9600个二进制位。STM32中的USART通用同步异步收发器外设就是负责实现这个过程的硬件模块。它比基础的UART多了同步时钟支持但在异步模式下两者对我们开发者来说用法基本一致。初始化串口本质上就是通过配置一系列寄存器告诉USART这个硬件模块几件事工作模式是只用异步模式我们最常用的还是要用同步模式是全双工同时收/发还是半双工通信参数波特率是多少数据位是8位还是9位停止位是1位、1.5位还是2位要不要奇偶校验位硬件流控制是否需要使用RTS请求发送和CTS清除发送这两根线来控制数据流防止缓冲区溢出这在高速或不稳定通信中很重要。中断与DMA数据收到或发送完成时是让CPU轮询去查状态还是触发一个中断让CPU来处理或者为了极致效率直接配置DMA直接存储器访问来搬运数据彻底解放CPU标准库的伟大之处在于它把上面这些需要直接操作寄存器的配置项全部封装成了几个结构体比如USART_InitTypeDef和对应的初始化函数USART_Init。我们只需要给结构体成员赋值然后调用函数库就会帮我们完成所有寄存器的正确写入。2.2 标准库与HAL库的选择考量现在ST主推HAL库和LL库为什么我们还要讲标准库这背后有实际的工程考量。标准库虽然已经停止更新主要支持F1, F2, F4, F0等系列但它代码结构直观封装层薄执行效率高对硬件资源的占用和控制粒度更细。很多存量项目、经典教程以及追求极致性能和可控性的工程师依然在使用它。学习标准库能让你更清晰地理解外设的配置流程和硬件工作原理这份理解是过渡到HAL库或直接操作寄存器的坚实基础。对于学习而言从标准库入手再去看HAL库那些抽象化的HAL_UART_Transmit()函数你会恍然大悟明白底层到底发生了什么。3. 工程准备与代码结构规划3.1 开发环境与工程建立工欲善其事必先利其器。我们需要准备以下环境IDEKeil MDK-ARMuVision5或 IAR Embedded Workbench。这里以Keil为例。标准库文件从ST官网或可靠资源获取对应你芯片型号的标准库包。例如STM32F10x的STM32F10x_StdPeriph_Lib。目标芯片以STM32F103C8T6蓝色药丸核心板常用款为例它拥有USART1, USART2, USART3三个串口。在Keil中新建工程时关键步骤是正确添加库文件路径和源文件。通常你需要将库包中的Libraries\STM32F10x_StdPeriph_Driver\inc和src目录分别添加到工程的头文件路径和源文件组中。同时还需要添加库包Project\Template下的main.c,stm32f10x_conf.h,stm32f10x_it.c/h等文件作为工程模板。stm32f10x_conf.h这个文件至关重要它用来使能或失能你用到的外设库比如我们需要用到USART和GPIO就要确保里面有#define USE_STDPERIPH_DRIVER和#include “stm32f10x_usart.h”、#include “stm32f10x_gpio.h”等。3.2 初始化函数的模块化设计思路一个好的代码结构能提升可读性和可维护性。我们不建议把所有串口的初始化代码都堆在main.c里。更清晰的做法是在main.c中调用初始化函数。将三个串口的初始化函数实现单独放在一个源文件里例如bsp_usart.c并对应创建头文件bsp_usart.h进行声明。在bsp_usart.h中还可以定义一些常用的宏如串口发送单个字符的宏、重定向printf的底层函数声明等。这样当你的项目需要增加USART4时只需要在bsp_usart.c/h中依葫芦画瓢添加即可main.c几乎不用动符合高内聚、低耦合的软件设计原则。4. USART1初始化代码详解与实操4.1 USART1的硬件特性与引脚映射USART1是STM32F103系列中功能最全的串口它挂载在APB2高速总线上因此可以支持更高的波特率。它的引脚是固定的TX: PA9 (主功能) 或 PB6 (重映射)RX: PA10 (主功能) 或 PB7 (重映射)默认情况下我们使用主功能引脚PA9和PA10。如果你因为PCB布线或引脚冲突需要使用重映射功能则需要额外开启AFIO时钟并调用GPIO_PinRemapConfig函数。这里我们先从默认引脚开始。4.2 分步代码实现与逐行解析下面是在bsp_usart.c中实现USART1初始化的完整代码。我会在每一段关键代码后加上详细注释。/** * brief 初始化USART1 * param baudrate: 波特率例如9600, 115200 * retval 无 */ void USART1_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* 第一步开启时钟 —— 所有外设操作的前提 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 注意USART1在APB2总线GPIOA也在APB2总线。AFIO时钟用于中断引脚重映射这里先开启备用。 /* 第二步配置USART1的TX(PA9)为复用推挽输出RX(PA10)为浮空输入 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; // TX引脚 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; // 输出速度高速有利于方波边沿陡峭 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; // RX引脚 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入模式 // 对于输入引脚GPIO_Speed设置无效 GPIO_Init(GPIOA, GPIO_InitStructure); /* 第三步配置USART1的工作参数 */ USART_InitStructure.USART_BaudRate baudrate; // 设置波特率 USART_InitStructure.USART_WordLength USART_WordLength_8b; // 8位数据位 USART_InitStructure.USART_StopBits USART_StopBits_1; // 1位停止位 USART_InitStructure.USART_Parity USART_Parity_No; // 无奇偶校验 USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; // 使能接收和发送模式 USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; // 无硬件流控制 USART_Init(USART1, USART_InitStructure); // 将配置写入寄存器 /* 第四步配置USART1接收中断 */ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能“接收缓冲区非空”中断 /* 第五步配置NVIC嵌套向量中断控制器 */ NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; // 中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; // 使能该中断通道 NVIC_Init(NVIC_InitStructure); /* 第六步使能USART1 */ USART_Cmd(USART1, ENABLE); }关键点解析与避坑指南时钟使能顺序一定要先开时钟再配置外设这是STM32编程的铁律。没开时钟就去操作寄存器是无效的程序会卡死。USART1在APB2GPIOA也在APB2所以可以一起开启。GPIO模式选择这是最容易出错的地方之一。TX引脚要设置为复用推挽输出GPIO_Mode_AF_PP。因为USART外设要控制这个引脚输出高低电平所以是“复用”功能并由外设驱动推挽输出能力强。RX引脚要设置为浮空输入GPIO_Mode_IN_FLOATING或上拉输入。浮空输入意味着引脚电平完全由外部信号决定适用于有确定外部驱动的情况。如果线路干扰大可以改用上拉输入GPIO_Mode_IPU给一个默认的高电平增强抗干扰能力。波特率计算标准库的USART_Init函数内部会根据你传入的baudrate、系统时钟SystemCoreClock和USART所在的总线频率APB1或APB2自动计算并设置波特率寄存器USART_BRR。对于72MHz系统时钟的F103设置115200波特率是精确的但有些波特率如9600可能会存在微小误差标准库会选择一个最接近的值。中断配置代码中我们使能了USART_IT_RXNE接收缓冲区非空中断。这意味着每当USART的接收数据寄存器RDR里有一个新字节就会触发中断。你必须在stm32f10x_it.c文件中实现USART1_IRQHandler函数来处理接收到的数据。如果不需要中断接收可以省略第四、五步。4.3 配套的中断服务函数与数据收发基础初始化完成后串口还不能工作我们需要实现中断服务函数和基本的发送函数。在stm32f10x_it.c中添加extern uint8_t USART1_Rx_Buffer[256]; // 假设在别处定义了一个接收缓冲区 extern uint16_t USART1_Rx_Index; // 接收缓冲区索引 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) // 判断是否是RXNE中断 { // 读取接收到的数据这一操作会自动清除RXNE标志位 uint8_t receivedData USART_ReceiveData(USART1); // 简单的例子将数据存入缓冲区 USART1_Rx_Buffer[USART1_Rx_Index] receivedData; // 这里可以添加协议解析例如判断是否收到回车符‘\n’表示一帧结束 // if(receivedData ‘\n’) { ... 处理一帧数据 ... } } // 还可以处理其他中断如发送完成中断(TC)、溢出错误(OE)等 // if(USART_GetITStatus(USART1, USART_IT_TC) ! RESET) { ... } }一个简单的阻塞式发送函数非中断可以这样写void USART1_SendByte(uint8_t data) { USART_SendData(USART1, data); // 将数据写入发送数据寄存器(TDR) while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); // 等待发送寄存器空 // 注意TXE标志表示TDR已空可以写入新数据。TC标志表示整个字节包括停止位已发送完毕。 } void USART1_SendString(char *str) { while(*str ! ‘\0’) { USART1_SendByte(*str); } }注意while(USART_GetFlagStatus(...))这种是阻塞式等待在发送期间CPU什么都干不了。在实际项目中更优的做法是使用发送完成中断TC或DMA进行非阻塞发送或者至少使用发送数据寄存器空中断TXE来提升效率。5. USART2与USART3初始化代码实现5.1 USART2/3与USART1的核心差异USART2和USART3在代码逻辑上与USART1完全一致主要区别在于三点总线时钟不同USART2和USART3挂载在APB1总线上。APB1的时钟最高为36MHz在系统时钟72MHz下而APB2时钟为72MHz。这意味着在计算相同波特率时写入USART_BRR寄存器的值会不同但标准库的USART_Init函数已经帮我们处理好了我们只需传入正确的波特率值。引脚不同需要查阅芯片数据手册Datasheet或参考手册Reference Manual来确定引脚。USART2: TX - PA2, RX - PA3 (主功能)。也可重映射到PD5/PD6部分型号。USART3: TX - PB10, RX - PB11 (主功能)。这是一个部分重映射默认就是PB10/PB11。完全重映射可以到PC10/PC11或PD8/PD9。中断向量不同在NVIC配置中中断通道要改为USART2_IRQn和USART3_IRQn。5.2 USART2初始化函数代码理解了差异代码就信手拈来了。以下是USART2的初始化函数void USART2_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* 第一步开启时钟 —— USART2在APB1GPIOA在APB2 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // USART2默认引脚PA2,PA3属于GPIOA /* 第二步配置USART2的TX(PA2)和RX(PA3) */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); /* 第三步配置USART2工作参数 */ USART_InitStructure.USART_BaudRate baudrate; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_Init(USART2, USART_InitStructure); /* 第四、五步配置中断可选 */ USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; // 优先级可与USART1不同 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); /* 第六步使能USART2 */ USART_Cmd(USART2, ENABLE); }5.3 USART3初始化函数代码USART3的初始化函数如下注意其默认引脚是PB10和PB11void USART3_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* 第一步开启时钟 —— USART3在APB1GPIOB在APB2 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // USART3默认引脚PB10,PB11属于GPIOB /* 第二步配置USART3的TX(PB10)和RX(PB11) */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure); /* 第三步配置USART3工作参数 */ USART_InitStructure.USART_BaudRate baudrate; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_Init(USART3, USART_InitStructure); /* 第四、五步配置中断可选 */ USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 3; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); /* 第六步使能USART3 */ USART_Cmd(USART3, ENABLE); }代码复用与优化细心的你可能发现了这三个函数除了时钟、引脚、外设名称和中断通道其他部分几乎一模一样。在实际项目中我们可以写一个更通用的初始化函数通过传入参数如USART编号、GPIO组、引脚号、波特率等来减少重复代码。但对于初学者分开写三个函数更直观便于理解和调试。6. 高级配置与性能优化技巧6.1 重映射功能的使用场景与配置引脚重映射Remap是为了解决PCB布线困难或引脚冲突。例如你的PA9、PA10被用作其他用途了但还想用USART1就可以将其重映射到PB6、PB7。配置重映射需要以下额外步骤开启AFIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);调用重映射函数GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);// 使能USART1完全重映射将GPIO初始化改为对重映射后的引脚PB6、PB7进行配置。注意重映射有“部分重映射”和“完全重映射”之分具体支持哪些引脚需要查阅芯片数据手册的“Alternate function remapping”表格。配置错误会导致串口无法工作。6.2 中断优先级管理与DMA应用入门当多个串口同时使用中断或者串口中断与其他中断如定时器、外部中断共存时合理设置NVIC的抢占优先级PreemptionPriority和子优先级SubPriority就至关重要。优先级数字越小优先级越高。在上面的例子中我给三个串口设置了不同的抢占优先级1,2,3这意味着USART1中断可以打断USART2和USART3的中断服务程序。对于大量、高速的数据传输例如图像传输、文件上传使用中断接收每个字节仍然会消耗大量CPU资源。此时DMA直接存储器访问是终极解决方案。你可以配置DMA通道让硬件自动将USART接收数据寄存器RDR里的数据搬运到你指定的内存数组中搬完一整个数组或达到设定长度后才产生一次中断通知CPU处理。这极大降低了CPU开销。配置USART DMA接收的大致步骤配置DMA通道例如USART1_RX对应DMA1_Channel5。设置源地址外设地址USART1-DR、目标地址内存数组地址、传输方向外设到内存、数据宽度、传输数量等。使能DMA通道。配置USART使其在接收时请求DMAUSART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE)。在DMA传输完成中断中处理接收到的整块数据。6.3 波特率误差计算与时钟精度影响标准库的USART_Init函数内部使用以下公式计算波特率分频系数USARTDIV fck / (16 * baudrate)其中fck是给USART的时钟频率APB1或APB2。然后将USARTDIV的整数部分和小数部分分别写入BRR寄存器。在72MHz系统时钟下APB2为72MHz计算115200波特率USARTDIV 72000000 / (16 * 115200) 39.0625整数部分39写入BRR[15:4]小数部分0.0625 * 16 1写入BRR[3:0]。所以BRR寄存器值应为0x0271。这是一个精确值没有误差。但对于APB136MHz下的USART2/3计算9600波特率USARTDIV 36000000 / (16 * 9600) 234.375整数部分2340xEA小数部分0.375*1660x6。BRR寄存器值应为0xEA6。实际波特率 36000000 / (16 * 234.375) 9600也是精确的。误差主要来源于两方面一是系统时钟源HSE晶振本身的精度二是计算出的USARTDIV小数部分不能被16整除时的舍入误差。对于大多数应用如调试、传感器读数这点误差在可接受范围内。但对高速或长距离通信建议使用高精度晶振并选择能产生精确分频的波特率如115200在72MHz下。7. 调试实战与常见问题排查7.1 硬件连接检查与软件配置验证串口调不通第一步永远是检查硬件。线序确保MCU的TX连接USB转串口模块的RXMCU的RX连接USB转串口模块的TX。这是最常见的错误接反了数据无法传输。共地务必确保开发板和USB转串口模块的GND连接在一起否则电平参考点不同通信必然失败。电源检查开发板供电是否稳定。有些USB转串口模块如CH340的3.3V输出能力不足不建议用来给核心板供电最好单独供电。驱动在电脑设备管理器中确认USB转串口模块的驱动已正确安装并记下生成的COM口号。软件配置验证清单[ ] 系统时钟初始化SystemInit()是否已调用标准库的启动文件通常会调用但自己移植代码时容易遗漏。[ ] 在stm32f10x_conf.h中是否包含了#include “stm32f10x_usart.h”和#include “stm32f10x_gpio.h”[ ] 是否开启了正确的GPIO和USART时钟用if(RCC_APB2Periph_GetClockCmd(RCC_APB2Periph_USART1))类似的函数可以检查。[ ] GPIO模式是否配置正确TX: AF_PP, RX: IN_FLOATING/IPU[] 波特率、数据位、停止位、校验位是否与串口调试助手如XCOM, SSCOM的设置完全一致这是第二大常见错误源。[ ] 如果使用了中断NVIC优先级配置是否正确中断服务函数名是否写对了USART1_IRQHandler7.2 典型故障现象与解决方案速查表故障现象可能原因排查步骤与解决方案发送数据调试助手收不到任何东西1. TX/RX线接反2. 串口助手波特率设置错误3. USART未使能(USART_Cmd)4. TX引脚模式错误不是AF_PP1. 交换TX/RX线序测试。2. 核对并尝试常用波特率9600, 115200。3. 单步调试确认执行了USART_Cmd(USARTx, ENABLE)。4. 检查GPIO初始化代码。能收到数据但全是乱码1. 波特率不匹配最常见2. 时钟配置错误如HSI精度差3. 数据位、停止位、校验位不匹配1. 精确计算并核对波特率尝试微调。2. 检查SystemCoreClock值是否正确改用外部晶振(HSE)。3. 严格保证双方通信参数一致。只能发送不能接收1. RX引脚模式错误不是输入模式2. 未使能接收模式(USART_Mode_Rx)3. 中断接收未正确配置如未清标志1. 检查GPIO初始化RX应为IN_FLOATING或IPU。2. 检查USART_InitStructure.USART_Mode是否包含Rx。3. 在中断服务函数中读取USART_ReceiveData会自动清RXNE标志但其他错误标志如ORE需要手动清除。通信一段时间后死机或数据错乱1. 接收缓冲区溢出ORE错误2. 中断服务函数处理时间过长3. 电源不稳定或受到干扰1. 在中断服务函数中检查并清除ORE标志(USART_ClearFlag)。2. 优化中断服务函数只做最必要的操作如存数据将协议解析放到主循环。3. 检查电源为RX线加上拉电阻或使用屏蔽线。使用printf重定向后无法打印1. 未重写fputc或_write函数2. 在fputc函数中使用了错误的USART发送函数3. 使用了MicroLIB但未在Keil中勾选1. 确保正确实现了int fputc(int ch, FILE *f)函数并调用串口发送。2. 在Keil的“Target”选项中勾选“Use MicroLIB”可以简化重定向。7.3 利用调试器进行在线诊断如果以上步骤都检查无误问题依旧可以祭出硬件调试器ST-Link, J-Link。查看寄存器在调试模式下暂停程序查看外设寄存器窗口。重点检查USART_SR状态寄存器TXE,TC,RXNE,ORE等标志位状态。USART_BRR波特率寄存器计算出的值是否与预期相符。USART_CR1控制寄存器1UEUSART使能、TE发送使能、RE接收使能、RXNEIE接收中断使能等位是否置1。单步调试在初始化函数和发送函数设置断点单步执行观察程序流是否按预期进行。逻辑分析仪/示波器这是终极武器。用示波器探头直接测量TX引脚可以看到实际发出的波形。测量一个字节的时长可以反推出实际波特率与设定值对比一目了然。8. 工程整合与进阶应用示例8.1 在main函数中的调用与测试将我们写好的bsp_usart.c/h文件添加到工程后就可以在main.c中轻松调用了。#include “stm32f10x.h” #include “bsp_usart.h” #include “bsp_led.h” // 假设有个LED用来指示 int main(void) { /* 系统时钟初始化通常由启动文件完成这里确保已调用 */ // SystemInit(); // 标准库启动文件通常已调用 /* 初始化硬件 */ LED_GPIO_Config(); // 初始化一个LED USART1_Init(115200); // 初始化USART1波特率115200 USART2_Init(9600); // 初始化USART2波特率9600 // USART3_Init(115200); // 按需初始化USART3 USART1_SendString(“\r\n System Boot Success! \r\n”); // 发送启动信息 while(1) { // 示例1回环测试将USART1收到的数据原样发回 // 这部分代码通常放在USART1的中断服务函数中更合适 // 这里仅作演示 // if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) SET) { // uint8_t data USART_ReceiveData(USART1); // USART_SendData(USART1, data); // } // 示例2通过USART2控制LED // if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) SET) { // uint8_t cmd USART_ReceiveData(USART2); // if(cmd ‘1’) GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN); // else if(cmd ‘0’) GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN); // } // 主循环可以处理其他任务如协议解析、状态机等 // 串口接收通过中断处理不阻塞主循环 } }8.2 多串口协同工作与数据转发案例一个常见的应用场景是“数据中转站”STM32通过USART1从GPS模块接收NMEA数据解析出需要的经纬度信息后通过USART2以自定义协议发送给另一个主控设备同时通过USART3将原始日志打印到电脑串口助手。这种架构的关键在于中断隔离每个串口的接收中断服务函数独立互不干扰。在各自的中断里只做最快速的数据搬运存入对应的环形缓冲区。缓冲区设计为每个串口的接收和发送设计环形缓冲区FIFO中断向缓冲区写入/读取数据主循环从缓冲区取出数据进行解析和转发。这能有效应对数据突发避免丢失。协议分层主循环中的逻辑负责应用层协议。例如在USART1的接收缓冲区中寻找“$GPRMC”帧头解析出经纬度然后封装成“{lat:xx.xxxx, lon:yy.yyyy}”的JSON格式放入USART2的发送缓冲区。优先级管理如果三个串口都非常繁忙需要根据业务重要性设置NVIC中断优先级。例如GPS数据实时性要求高USART1中断优先级可设为最高调试日志不重要USART3中断优先级设为最低。通过这样的设计你的STM32就能稳健地扮演一个多路串口数据路由和处理核心的角色代码结构清晰且能充分利用硬件资源。