首页 / 资讯中心 / 文章详情

STM32F407标准库6路串口DMA中断配置实战

STM32F407标准库6路串口DMA中断配置实战 ★ FEATURED ARTICLE
简介这份资源面向使用STM32F407进行多串口通信开发的嵌入式工程师与学生基于标准外设库实现6路USART的完整配置并配套DMA发送与中断接收机制可解决多设备并发通信、模块化数据转发等场景下的串口资源调度问题。压缩包共115个文件以50个.c与50个.h源码文件为核心另含hex烧录文件、Keil工程文件uvprojx、uvoptx、批处理脚本及说明文档整体约552KB工程结构完整、可直接编译参考。目前已有3893人学习下载热度较高。代码覆盖串口初始化、GPIO与时钟配置、DMA通道映射、中断服务例程及多串口并行运行时的资源冲突处理读者可据此掌握DMA减轻CPU负载、中断回调区分数据来源的编程思路并借助工程文件快速搭建自己的多串口通信框架。1. 从一块吃灰的 F407 板子说起6 路串口到底难在哪手头这块 STM32F407ZGT6 最小系统板在抽屉里躺了大半年最近被一个多串口采集的活儿翻了出来——上位机要同时接 6 个传感器模块每个模块走一路 UART波特率还不一样有的 9600有的 115200。第一反应是“F407 有 6 个 USART/UART硬件够用”结果真动手才发现标准库下把 6 路全开起来、还要 DMA 发送加中断接收坑比想象中密得多。串口这东西看着简单配置错一个时钟位、DMA 通道选错一个映射表现就是“发出去没反应”或者“收几个字节就卡死”没有示波器根本不知道死在哪。这篇笔记就是记录我从零把 6 路串口跑通的全过程包括时钟怎么算、DMA 流怎么分、中断优先级怎么排以及装坏了怎么救回来。适合正在用标准库做多串口项目、被 DMA 和中断绕晕的开发者尤其是从 HAL 库转过来、对寄存器配置还不太熟的人。2. 环境与版本先列清标准库、器件手册、工具链一个都不能少2.1 硬件与固件库版本对照动手之前先把家底列清楚不然后面出问题都不知道该怀疑谁。我用的是 STM32F407ZGT6属于 F4 系列里外设比较全的型号6 个串口分别是 USART1、USART2、USART3、UART4、UART5、USART6。标准库用的是 ST 官方那套STM32F4xx_DSP_StdPeriph_Lib_V1.8.0这个版本对 F407 的支持最稳网上很多 V1.9.0 的包其实是给 F429 用的直接拿来编译 F407 的工程偶尔会报寄存器定义对不上。工具链我用的 Keil MDK 5.38 加 ARMCC V5 编译器如果你用 GCC 或者新版 AC6标准库里的core_cm4.h可能需要小改这个后面避坑章节细说。项目型号/版本说明MCUSTM32F407ZGT66 路串口全支持标准库STM32F4xx_DSP_StdPeriph_Lib_V1.8.0F407 适配最稳编译器ARMCC V5 / Keil MDK 5.38AC6 需改 core 头文件调试器ST-Link V2SWD 四线接法串口工具任意 USB-TTL建议 CH340 或 CP2102这里要特别提醒一句标准库和 HAL 库的工程结构完全不同标准库没有 CubeMX 自动生成的那套初始化代码所有外设时钟、GPIO 复用、NVIC 都得自己写。好处是可控坏处是漏一行就白屏。我一般会先建一个干净的模板工程把system_stm32f4xx.c、startup_stm32f40_41xxx.s、stm32f4xx_conf.h这几个关键文件确认到位再往里加串口代码。2.2 工程目录与关键文件确认标准库工程的目录结构建议这样组织后面加 6 路串口才不会乱Project/ ├── CMSIS/ # 内核相关 │ ├── core_cm4.h │ └── system_stm32f4xx.c ├── FWlib/ # 标准外设库 │ ├── inc/ │ └── src/ │ ├── stm32f4xx_usart.c │ ├── stm32f4xx_dma.c │ ├── stm32f4xx_gpio.c │ └── stm32f4xx_rcc.c ├── User/ │ ├── main.c │ ├── usart_config.c # 6 路串口配置集中放这 │ └── stm32f4xx_it.c # 中断服务函数 └── Output/stm32f4xx_conf.h里要把用到的外设头文件都放开否则编译时RCC_APB1PeriphClockCmd这类函数会报未定义。常见做法是把#include stm32f4xx_usart.h、stm32f4xx_dma.h、stm32f4xx_gpio.h、stm32f4xx_rcc.h这几行的注释去掉。这一步看着琐碎但漏一个后面就是几十行报错血泪经验。3. 六路串口初始化时钟、GPIO、DMA 流一次配到位3.1 先搞清楚 6 个串口挂在哪条总线上F407 的串口不是全挂在同一个时钟域这是第一个容易翻车的地方。USART1 和 USART6 挂在 APB2USART2、USART3、UART4、UART5 挂在 APB1。APB1 最高 42MHzAPB2 最高 84MHz算波特率的时候分频系数不一样如果统一按 84MHz 算APB1 上的串口波特率会差一倍表现就是乱码。我一般会在配置函数开头就把时钟使能写全// 使能 6 路串口时钟注意 APB1 和 APB2 分开 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_USART6, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB1Periph_USART3 | RCC_APB1Periph_UART4 | RCC_APB1Periph_UART5, ENABLE); // GPIO 时钟USART1/6 在 GPIOA/GPIOG其余分散在 GPIOA/GPIOB/GPIOC/GPIOD RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOG, ENABLE);逻辑说明RCC_APB2PeriphClockCmd管高速外设RCC_APB1PeriphClockCmd管低速外设两者不能混。参数里用|把多路串口拼起来一次使能比一行一行写清爽。GPIO 时钟走 AHB1这个别写成 APB否则引脚没输出。3.2 GPIO 复用配置AF7 是串口的统一答案6 路串口的引脚分布比较散我按板子实际走线选了一组USART1 用 PA9/PA10USART2 用 PA2/PA3USART3 用 PB10/PB11UART4 用 PC10/PC11UART5 用 PC12/PD2USART6 用 PG14/PG9。配置的时候统一用复用功能 AF7因为 F4 系列里 USART1/2/3/6 和 UART4/5 的复用编号都是 7这点比 F1 系列省心。GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; // 复用模式 GPIO_InitStructure.GPIO_OType GPIO_OType_PP; // 推挽输出 GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_UP; // 上拉防止悬空误触发 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; // 以 USART1 的 PA9/PA10 为例 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9 | GPIO_Pin_10; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);参数说明GPIO_PuPd_UP上拉在接收脚上很有用模块没上电时不会读到随机电平。GPIO_PinAFConfig这行最容易漏漏了引脚就是普通 IO串口一点反应没有。其余 5 路照这个模板改引脚和 AF 编号即可注意 UART5 的 RX 在 PD2PD 口也要使能时钟。3.3 串口参数与 DMA 发送通道分配波特率我按模块需求分别设成 9600、115200 等数据位 8、停止位 1、无校验。关键是 DMA 发送的通道分配F407 的 DMA1 和 DMA2 各有 8 个流每个流能映射不同请求串口发送请求的对应关系必须查手册不能随便挑串口DMA 控制器流通道方向USART1DMA2Stream7Channel4发送USART2DMA1Stream6Channel4发送USART3DMA1Stream3Channel4发送UART4DMA1Stream4Channel4发送UART5DMA1Stream7Channel4发送USART6DMA2Stream6Channel5发送这张表是我对着参考手册一页页翻出来的网上有些帖子把 USART6 写成 Channel4实际是 Channel5照抄就发不出去。配置 DMA 的时候还要开USART_DMAReq_Tx并且记得在发送函数里判断 DMA 传输完成标志否则连续发会丢数据。DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA2_Stream7); DMA_InitStructure.DMA_Channel DMA_Channel_4; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)tx_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_MemoryToPeripheral; DMA_InitStructure.DMA_BufferSize len; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_Medium; DMA_Init(DMA2_Stream7, DMA_InitStructure); DMA_Cmd(DMA2_Stream7, ENABLE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);逻辑说明DMA_DIR_MemoryToPeripheral表示内存到外设发送方向。DMA_Mode_Normal是单次传输发完就停适合不定长数据如果要循环发送才用Circular。DMA_PeripheralBaseAddr填串口数据寄存器地址这个别填错填成别的寄存器数据就发到奇怪的地方去了。3.4 中断接收配置与 NVIC 优先级接收我用中断方式每路串口开USART_IT_RXNE在中断里把数据存进各自的环形缓冲区。6 路串口的中断优先级要排一下避免高频率的那路把低频率的挤掉。我一般把波特率最高、数据量最大的 USART1 设成抢占优先级 1其余依次 2 到 6。NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE);参数说明NVIC_IRQChannelPreemptionPriority是抢占优先级数值越小越优先。SubPriority是子优先级同抢占级下才比较。中断服务函数里记得先判断USART_GetITStatus再读USART_ReceiveData读完自动清标志不用手动清。4. 避坑与排查6 路串口最容易翻车的 5 个地方4.1 现象某一路串口完全没输出其他路正常原因九成是 GPIO 复用没配或者时钟没使能。UART5 的 TX 在 PC12、RX 在 PD2PD 口时钟经常被忘掉USART6 的 TX 在 PG14GPIOG 时钟也容易漏。解决拿万用表量引脚电压空闲时 TX 应该是高电平如果是 0 或者浮空回去查RCC_AHB1PeriphClockCmd和GPIO_PinAFConfig这两行。4.2 现象DMA 发送只能发一次第二次没反应原因DMA_Mode_Normal模式下传输完成后流会自动关闭第二次发送前没有重新使能。解决在每次发送函数开头加DMA_Cmd(stream, DISABLE)再重新配置DMA_BufferSize和DMA_Cmd(stream, ENABLE)或者干脆用DMA_Mode_Circular配合双缓冲。我一般封装一个uart_dma_send()里面统一处理重装。4.3 现象接收数据偶尔丢字节尤其是高波特率原因中断里处理太慢或者没开USART_IT_ORE溢出中断数据寄存器被覆盖了都不知道。解决中断服务函数里只做“读数据进缓冲区”这一件事别在里面做解析和打印。另外把USART_IT_ORE也开上溢出时清标志能救回不少数据。4.4 现象编译报错core_cm4.h里__ASM未定义原因用 AC6 编译器编标准库 V1.8.0 时老的汇编语法不兼容。解决要么切回 ARMCC V5要么把core_cm4.h里__ASM改成__asm并加上-Wno-armcc-compat之类的编译选项。我一般直接锁 V5省事。4.5 现象6 路全开之后系统跑飞进 HardFault原因DMA 流冲突或者中断优先级配置超出范围。F407 的 NVIC 优先级分组默认是 4 位抢占 0 位子优先级如果NVIC_PriorityGroupConfig没设对写进去的优先级可能越界。解决在main开头加NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)然后检查 6 路 DMA 流有没有重复用同一个。5. 装完怎么验证用回环加示波器把 6 路一次跑通配置写完只是纸面功夫真正跑通得靠验证。我的习惯是分三步走别一上来就接 6 个模块那样出问题根本定位不到哪一路。第一步单路回环自测。把 USART1 的 TX 和 RX 用杜邦线短接发一串0x55看接收缓冲区是不是原样回来。这一步能验证 GPIO、波特率、中断接收是否正常。代码里加个简单的计数uint8_t test_buf[] {0x55, 0xAA, 0x01, 0x02}; uart_dma_send(USART1, test_buf, sizeof(test_buf)); HAL_Delay(10); // 标准库用自己写的延时 if (rx1_count sizeof(test_buf)) { // 回环成功 }第二步6 路同时发不同数据用逻辑分析仪或者示波器抓 TX 引脚。重点看两件事波形是不是干净的方波波特率对不对。9600 的位宽约 104us115200 约 8.7us拿示波器一量就知道分频算错没有。我一般会把 6 路 TX 分别接到逻辑分析仪的 6 个通道一次抓完比一路一路测快得多。第三步压力测试。让 6 路同时以最高波特率连续发 1KB 数据跑十分钟看有没有丢包或者死机。这一步最容易暴露 DMA 流冲突和中断优先级问题。如果某一路开始丢数据回去查它的 DMA 流是不是和别的外设共用了。验证步骤工具通过标准单路回环杜邦线收发一致6 路波形逻辑分析仪波特率误差 2%压力测试串口助手十分钟无丢包从那以后我每次配多路串口都强制先跑一遍单路回环再上整机这个习惯帮我省了至少三次通宵查线的时间。希望这套流程能帮到你少走点我踩过的弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站