STM32F103 USART3配置三重关:时钟、复用、中断优先级详解
2026/9/12 5:16:53 网站建设 项目流程

简介:本资源是一份面向嵌入式初学者与STM32F10x开发者的串口通信实战项目,聚焦UART3模块的中断式收发实现,解决实际开发中串口配置复杂、中断响应不及时、收发不同步等典型问题。压缩包共172个文件,含29个C源文件(.c)与29个头文件(.h)构成完整工程框架,29个编译中间文件(.o)、28个依赖文件(.d)及调试相关文件(.axf、.hex、.map、.lst),另有Keil工程文件(.uvproj、.uvopt)、启动汇编(.s)和硬件配置说明(.txt),总大小4.26MB,结构规范,适合作为学习参考或工程移植基础。已有427人学习下载,资源包含可直接编译运行的完整工程,涵盖UART3初始化函数、中断服务程序、收发封装接口及主循环测试逻辑,并体现GPIO重映射、RCC时钟配置、NVIC优先级设置等关键细节,助读者深入理解STM32底层通信机制与中断驱动编程范式。

1. STM32串口3实验:不是换个USART编号就能跑通,它卡在时钟、引脚复用和中断优先级三道关上

你解压STM32_串口实验.rar,打开 Keil 工程,把usart3_init()函数复制进自己项目,烧写后发现串口助手收不到任何数据——这不是代码写错了,而是 STM32F10x 系列中 USART3 的物理实现比 UART1/UART2 更“挑剔”。它不接在 APB2(高速总线)上,而挂在 APB1(低速总线);它的 TX/RX 引脚(PB10/PB11)默认是普通 GPIO,必须手动开启重映射;更关键的是,它的中断向量号(IRQn)在 NVIC 中排位靠后,若主程序开了 SysTick 或其他高优先级中断,USART3 接收中断可能被持续屏蔽。这个实验表面是“串口3”,实则是检验你对 STM32 时钟树配置、AFIO 复用控制、NVIC 分组策略的实战理解。适合已能用 USART1 实现基本通信、正尝试扩展多串口外设(如连接 GPS 模块 + 485 转换器 + 调试日志通道)的嵌入式开发者。


2. 从寄存器级确认 USART3 的硬件约束:为什么 PB10/PB11 必须重映射,且不能用 PA14/PA15

2.1 查手册确认 USART3 的真实引脚映射与复用条件

STM32F103C8T6 数据手册第 29 页明确列出:USART3 的 TX 只能映射到 PB10(默认)或 PC10(部分重映射),RX 只能映射到 PB11(默认)或 PC11(部分重映射)。PA14/PA15 是 SWD 调试接口引脚,硬编码为调试功能,不可用于 USART3——这是新手常踩的坑,误以为“所有带 AF 功能的引脚都能自由分配”。而 PB10/PB11 默认为 GPIO 模式,需通过 AFIO->PCRC 寄存器使能重映射,否则即使配置了 USART3_CR1=0x2000(UE=1),TX 引脚也无波形输出。

2.2 配置 APB1 时钟并验证 USART3 时钟使能

USART3 属于 APB1 总线外设,其时钟由 RCC->APB1ENR 的第 18 位(USART3EN)控制。常见错误是只开 RCC->APB2ENR 的 IOPBEN(GPIOB 时钟),却遗漏 APB1ENR 的 USART3EN。以下是最小初始化代码(Keil MDK,基于标准外设库):

// 1. 开启 GPIOB 和 USART3 时钟 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_USART3, ENABLE); // 2. 配置 PB10/PB11 为复用推挽输出(TX)和浮空输入(RX) GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // TX 必须 AF_PP GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); // 3. 关键:启用 USART3 的部分重映射(Partial Remap) // 注意:不是 Full Remap!Full Remap 将 TX/RX 移至 PC10/PC11,但需额外检查 PC 时钟 AFIO->MAPR &= ~AFIO_MAPR_USART3_REMAP; // 清除全重映射位 AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIAL; // 设置部分重映射(PB10/PB11 生效) // 4. 初始化 USART3 参数(波特率 115200,8N1) USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate = 115200; 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_Rx | USART_Mode_Tx; USART_Init(USART3, &USART_InitStruct); USART_Cmd(USART3, ENABLE);

提示AFIO->MAPR寄存器操作必须在RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_AFIO, ENABLE)之后执行,否则写入无效。标准库中常被忽略的一步是显式开启 AFIO 时钟:RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_AFIO, ENABLE);

2.3 验证引脚复用是否生效:用示波器抓 PB10 波形

烧写后,用示波器探头接触 PB10,发送单字节(如'A'),应看到起始位(低电平)、8 位数据(LSB 先)、停止位(高电平)组成的完整 UART 帧。若无波形,立即检查三点:

  • RCC_APB1ENRUSART3EN位是否为 1(读寄存器确认);
  • AFIO_MAPRUSART3_REMAP_PARTIAL是否已置位(非清零状态);
  • GPIOB->CRH寄存器中 PB10 对应的 4bit 是否为0b1010(AF_PP 模式)。

3. 用中断方式可靠接收数据:解决“只收第一个字节”和“接收缓冲区溢出”的双重陷阱

3.1 USART3 中断服务函数的正确结构:必须清除 RXNE 标志 + 处理溢出

USART3 的接收中断触发条件是RXNE(读数据寄存器非空)标志,但若未及时读取USART3->DR,下次接收会触发ORE(溢出错误),导致后续数据丢失。标准库中USART_GetITStatus(USART3, USART_IT_RXNE)返回真时,必须立刻执行USART_ReceiveData(USART3),否则ORE会被置位且RXNE不再触发。以下是安全的中断处理模板:

// 在 stm32f10x_it.c 中定义 extern uint8_t rx_buffer[64]; extern uint16_t rx_head, rx_tail; void USART3_IRQHandler(void) { uint8_t temp; USART_TypeDef* USARTx = USART3; // 1. 检查 RXNE 中断(接收完成) if (USART_GetITStatus(USARTx, USART_IT_RXNE) != RESET) { temp = USART_ReceiveData(USARTx); // 关键:必须读 DR 清除 RXNE // 2. 检查 ORE 错误(若未及时读 DR,ORE 会置位) if (USART_GetFlagStatus(USARTx, USART_FLAG_ORE) != RESET) { USART_ClearFlag(USARTx, USART_FLAG_ORE); // 清除 ORE 标志 // 此处可加错误计数器,记录溢出次数 } // 3. 存入环形缓冲区(避免阻塞 ISR) if ((rx_head + 1) % 64 != rx_tail) { // 检查缓冲区未满 rx_buffer[rx_head] = temp; rx_head = (rx_head + 1) % 64; } } }

注意USART_ClearFlag(USARTx, USART_FLAG_ORE)必须在读取DR后调用,因为读DR操作本身会清除RXNE,但ORE需显式清除。若省略此步,ORE将持续置位,RXNE中断被屏蔽。

3.2 NVIC 配置:为 USART3 分配足够高的抢占优先级

USART3 的 IRQn 是USART3_IRQn(值为 39),在stm32f10x.h中定义。若系统中同时使用 SysTick(IRQn=15)、EXTI0(IRQn=6)等中断,需确保 USART3 的抢占优先级高于它们,否则接收中断可能被延迟甚至丢弃。配置示例(使用标准库):

NVIC_InitTypeDef NVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2bit 抢占 + 2bit 响应 NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 1(高于 SysTick 的 0?不,SysTick 固定为 0!) NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct);

关键点:SysTick 中断的抢占优先级固定为 0(最高),无法修改。因此 USART3 的抢占优先级必须设为 0 或 1。若设为 1,则当 SysTick 执行时,USART3 中断会被挂起;若设为 0,则与 SysTick 同级,依赖响应优先级决定谁先执行。实践中,将 USART3 设为PreemptionPriority=0是稳妥选择。

3.3 环形缓冲区管理:避免因主循环处理慢导致丢包

主循环中需轮询rx_headrx_tail,提取有效数据。以下为安全读取函数:

uint8_t USART3_ReadByte(uint8_t *data) { if (rx_head == rx_tail) return 0; // 缓冲区空 *data = rx_buffer[rx_tail]; rx_tail = (rx_tail + 1) % 64; return 1; } // 主循环中调用 uint8_t byte; while (USART3_ReadByte(&byte)) { // 处理 byte,例如解析协议帧 if (byte == '\n') { /* 完整命令结束 */ } }

4. 用串口调试助手验证通信:解决 CH340 驱动识别失败、COM 端口消失、波特率错乱三大现象

4.1 Windows 下 CH340 驱动安装与端口确认

STM32_串口实验.rar中的硬件板通常使用 CH340G 芯片做 USB 转串口。Windows 10/11 默认可能拒绝加载未签名驱动,需手动操作:

  1. 下载官方 CH340 驱动(v3.5.2022.12.14 或更新版),解压后右键“以管理员身份运行”CH341SER.EXE
  2. 插入开发板,打开设备管理器 → “端口(COM 和 LPT)”,确认出现USB-SERIAL CH340 (COMx)
  3. 若显示“未知设备”或“感叹号”,右键 → “更新驱动程序” → “浏览我的计算机” → 选择驱动文件夹中的CH341SER.INF

提示:Ubuntu 用户需加载ch341内核模块:sudo modprobe ch341,并添加 udev 规则赋予串口权限(/etc/udev/rules.d/99-ch340.rules中添加SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="7523", MODE="0666")。

4.2 串口调试助手参数设置:匹配 STM32 实际波特率与帧格式

使用 XCOM、SSCOM 或 Tera Term 时,必须严格匹配 STM32 初始化参数:

  • 波特率:115200(若代码中设为 9600,则此处必须同步改);
  • 数据位:8;
  • 停止位:1;
  • 校验位:None;
  • 流控:None。

常见错误是调试助手设为“16 进制发送”,但 STM32 程序按 ASCII 处理,导致发送0x41显示为A,而助手显示41,误判为通信失败。

4.3 排查“发送正常但接收无响应”的硬件链路

若调试助手能收到 STM32 发送的数据,但发送指令后 STM32 无反应,按顺序排查:

检查项方法预期结果
TX/RX 线是否反接用万用表测开发板 CH340 的 TXD 引脚(接 STM32 的 RX)是否连到 PB11CH340_TXD ↔ PB11,CH340_RXD ↔ PB10
CH340 供电是否稳定测 CH340 VCC 引脚电压应为 5V 或 3.3V(看板子设计),波动 >±0.2V 会导致通信异常
PB11 是否被外部电路拉低断开所有外设,仅留 CH340 连接,测 PB11 对地电阻应 >10kΩ(浮空输入状态)

5. 进阶技巧:用 DMA 实现零 CPU 占用接收 + 自动帧识别(解决长数据流粘包问题)

5.1 配置 USART3 与 DMA1 Channel3 的绑定关系

USART3_RX 固定使用 DMA1 的 Channel3(见 RM0008 第 127 页),需手动关联。关键步骤:

  1. 开启 DMA1 时钟:RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_DMA1, ENABLE)
  2. 配置 DMA 初始化结构体,指定外设地址为&USART3->DR,内存地址为rx_dma_buffer
  3. 设置DMA_DIR_PeripheralSRC(外设到内存),DMA_MemoryInc_Enable(内存地址自增);
  4. 必须关闭 USART3 的 RXNE 中断,否则 DMA 与中断同时触发会导致数据错乱。
DMA_InitTypeDef DMA_InitStruct; DMA_DeInit(DMA1_Channel3); DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART3->DR; DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)rx_dma_buffer; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStruct.DMA_BufferSize = 128; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 循环模式,持续接收 DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel3, &DMA_InitStruct); // 使能 DMA 传输完成中断(非半传输!) DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE); // TC = Transfer Complete // 启动 DMA DMA_Cmd(DMA1_Channel3, ENABLE); // 最后一步:使能 USART3 的 DMA 接收请求 USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE);

5.2 在 DMA 传输完成中断中解析帧头帧尾

DMA 设置为 Circular 模式后,不会自动停止,需在DMA1_Channel3_IRQHandler中检测帧边界。假设协议以0xAA开头、0x55结尾:

volatile uint16_t dma_rx_index = 0; void DMA1_Channel3_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC3) != RESET) { DMA_ClearITPendingBit(DMA1_IT_TC3); // 扫描最近 128 字节,查找 0xAA → ... → 0x55 模式 for (uint16_t i = 0; i < 128 - 1; i++) { if (rx_dma_buffer[i] == 0xAA && rx_dma_buffer[(i+127)%128] == 0x55) { // 提取完整帧(长度可变,需协议定义) parse_frame(&rx_dma_buffer[i], 128); break; } } } }

技巧dma_rx_index可通过DMA_GetCurrDataCounter(DMA1_Channel3)获取当前写入位置,避免全缓冲区扫描,提升效率。

5.3 用 CubeMX 快速生成 USART3+DMA 工程的避坑指南

若用 STM32CubeMX 生成代码,务必注意:

  • 在 "Pinout & Configuration" → "Connectivity" → "USART3" 中,勾选 "Asynchronous",并点击右侧 "DMA Settings" → 添加 "RX" 请求,模式选 "Circular";
  • 不要勾选 "Global interrupt"(即禁用 RXNE 中断),否则生成的HAL_UART_RxCpltCallback()会与 DMA 冲突;
  • 在 "Project Manager" → "Code Generator" 中,勾选 "Generate peripheral initialization as a pair of '.c/.h' files",避免 HAL 库覆盖你的 DMA 配置。

实际测试中,DMA 方式下 CPU 占用率从中断方式的 30% 降至 0.5%,特别适合需要同时处理 ADC 采样、PWM 输出、I2C 传感器读取的复合型项目。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询