资讯动态

STM32F405RGT6五串口通信实战:引脚分配与代码详解

发布时间:2026/9/9 18:56:43 来源:尧图企业网站定制
简介这套代码基于STM32F405RGT6单片机面向需要同时管理串口1至串口5通信的嵌入式开发者解决多路UART数据接收、独立缓冲与状态标志管理的工程问题。压缩包共145个文件涵盖42个.h头文件、36个.c源文件以及编译生成的.o、.crf、.axf、.hex等中间/烧录文件另有Keil MDK工程配置与调试辅助文件整体大小仅2.56MB目录结构清晰便于按类型查找。代码为每个串口定义了独立的接收缓冲区、接收计数与接收完成标志演示了多串口并发收发的变量组织方式工程内还包含标准外设库中的定时器、RTC、RCC、ADC、CAN、USART等模块源文件方便开发者对照学习外设初始化及寄存器级操作也可以直接在此基础上扩展其他外设驱动。已有1214人学习/下载适合正在调试多串口通信、或希望基于STM32F4标准外设库快速搭建串口框架的嵌入式工程师与相关专业学生参考。 STM32F405RGT6这颗料我在项目里用了不少LQFP64封装64个引脚却给足了5路串口——USART1、USART2、USART3、UART4、UART5。很多设备做到后面就会发现一路串口给调试打印一路接RS485一路接4G模组一路接GPS一路留给升级协议接口数不够的时候要么换芯片要么加扩展F405RGT6刚好卡在这个资源点上512KB Flash、192KB RAM、带FPU的Cortex-M4F内核做多串口网关或者工业采集板都非常合适。这篇文章我就把自己实际在F405RGT6上跑通的五串口通信代码完整拆开讲内容包括引脚分配、时钟配置、发送接收的几种写法和区别以及调试串口时最容易踩的那些坑。如果你想在这颗芯片上同时使用多路串口、又不想把参考手册来回翻三遍才敢写代码可以直接照着这套框架来做。1. 先摸清F405RGT6上这5个串口的底细1.1 5个串口的硬件资源与引脚分配F405RGT6的5个串口并不是完全一样的。USART1、USART2、USART3是通用同步异步收发器支持同步时钟输出、LIN、IrDA、智能卡模式UART4和UART5是纯异步串口没有同步时钟功能。对于绝大多数通信场景比如接GPS、接RS485、接TTL模组两者用起来没有区别。引脚分配是第一个值得注意的地方同一个串口往往有多组引脚映射但引脚位置不同实际布线难度完全不同串口主用TX/RX备用TX/RX复用AF编号USART1PA9 / PA10PB6 / PB7AF7USART2PA2 / PA3PD5 / PD6AF7USART3PB10 / PB11PC10 / PC11 或 PD8 / PD9AF7UART4PA0 / PA1PC10 / PC11AF8UART5PC12 / PD2部分封装无备用AF8这张表看着简单里面藏着两个大坑UART4和USART3都复用PC10/PC11这两个串口不能同时使用同一组引脚UART5的RX脚PD2在很多板上同时被SDIO的CMD线占用如果你一边用SD卡一边用UART5极容易出现“莫名初始化失败”的诡异问题。所以拿到一块F405RGT6板子第一件事就是打开数据手册的引脚定义页把5个串口的引脚选择先定下来别急着写代码。我自己的习惯是USART1固定PA9/PA10跑调试日志USART2用PA2/PA3接4G模组USART3用PB10/PB11走RS485UART4用PA0/PA1接GPSUART5用PC12/PD2做设备间通信前提是不和SD卡同时使用。如果要用SDIO就把UART5挪到别的位置或者干脆换一个串口。1.2 时钟源配置为什么影响所有串口串口通信的底层是波特率发生器波特率来源于串口外设时钟而F405的串口时钟分挂两条总线USART1挂在APB2上默认84MHzUSART2、USART3、UART4、UART5都挂在APB1上默认42MHz。系统时钟初始化一旦配错5个串口会同时全乱。波特率寄存器的计算方式不算复杂只要记住分子是外设时钟频率就行。以APB1的42MHz为例配置115200波特率、16倍过采样分频系数就是42MHz除以(16×115200)约等于22.786。BRR寄存器写入这个值的小数定点形式实际波特率误差可以控制在0.1%以内。但如果APB1实际频率是36MHz按42MHz去计算并写入波特率偏差就会直接超出串口容错范围表现就是接收方全是乱码。因此用CubeMX生成工程时一定要先检查时钟树。外部晶振是8MHz还是25MHz在RCC配置里必须填对否则HSE_VALUE宏与实际晶振不匹配PLL计算出的SYSCLK就是错的最后会连锁导致APB1/APB2分频结果全部不对。遇到“代码看着完全正确但串口就是乱码”的情况九成以上是这里出了问题。2. 五串口通信的整体方案设计2.1 每个串口干什么活先想清楚再写代码5个串口同时启用最大的挑战不是初始化而是后期调试时怎么区分数据到底从哪个口来、应该往哪个口去。如果所有串口都干同一件事日志和数据混在一起等设备跑起来你根本分不清哪条打印是哪个外设输出的。我建议在写代码之前先做一张串口功能分配表。比如设备是一个数据采集网关USART1调试日志输出连USB转TTL到PC全程打印关键节点信息USART2连接4G通信模组处理TCP链路、云平台数据上报USART3RS485总线走Modbus RTU协议挂载多台仪表UART4GPS/北斗模块解析NMEA语句获取定位信息UART5上位机本地升级串口传输固件或配置文件这样每个串口的职责边界清晰数据流方向明确遇到问题也能快速定位。调试口必须独立出来千万不要把日志打印和业务通信混在同一个串口里。我在早期项目里图省事让业务数据口顺带打印调试信息结果通信一频繁打印和业务数据互相穿插定位问题花的时间远超预期。2.2 收发方式选型轮询、中断还是DMA5个串口的收发方式如果不提前规划代码写到最后一定是各种嵌套和阻塞。区别可以简单列一下方式优点缺点适用场景轮询逻辑简单代码直观阻塞CPU收发期间无法做别的事调试打印、短数据、低速中断不阻塞主流程响应及时频繁中断会占用CPU时间不定长数据、中等速率DMA空闲中断几乎不占CPU高速可靠配置复杂内存占用大大数据量、高速连续传输对于F405RGT6这种多串口芯片我的建议是全部串口接收都走中断。中断接收意味着每收到一个字节都会触发一次中断在回调里把字节存入缓冲、重新开启下一次接收即可。发送则根据场景来调试串口用阻塞发送业务串口如果数据量大可以考虑DMA发送。如果某个串口需要持续接收上百字节的数据帧比如升级固件或高速传感器数据单字节中断模式会频繁打断主循环这时应该用DMA空闲中断方案。F405系列较新的HAL库提供了HAL_UARTEx_ReceiveToIdle_IT这样的函数实现了空闲中断DMA接收在接收完成或总线空闲时一次性给出整包数据。不过这个函数在不同HAL库版本里支持程度不同用之前要确认当前库版本是否包含对应接口。2.3 中断优先级分配思路5个串口同时开启中断后优先级分配直接影响系统的稳定性。STM32的NVIC中断优先级数字越小越优先F405通常可配置4位抢占优先级。如果全部串口使用相同优先级恰好有两个串口同时触发中断CPU只能按中断号顺序执行高频率的通信口就可能被低频率口拖累出现延迟接收甚至溢出丢数据。优先级分配的原则是数据实时性要求越高的串口优先级越要抬高日志打印这种掉了也不影响业务的串口优先级放最低。比如我常用的配置USART3的RS485优先级抢占2USART2的4G模组优先级抢占3UART4的GPS优先级抢占4UART5的升级口抢占5USART1的调试日志抢占6。同时打开NVIC_PRIORITYGROUP_4分组保证抢占优先级逻辑清晰。除了优先级数值中断回调函数的写法同样关键。回调函数里绝对不能做耗时操作比如调用printf、HAL_Delay、解析协议等。收到的数据先在回调里放进缓冲区并置一个标志位回到主循环后再慢慢处理。只要守住这条规则5个串口同时跑也稳得住。3. 从零配置和初始化五个串口3.1 CubeMX配置五路串口的细节用CubeMX生成基础工程是目前效率最高的方式。选好STM32F405RGT6后在Pinout视图中直接用鼠标点选对应的TX/RX引脚每个引脚会弹出可选功能列表选择对应的USARTx_TX或USARTx_RX即可。软件会自动把引脚复用模式设置成AF7或AF8。配置串口参数时Mode选Asynchronous波特率根据外设需求设置大多数设备115200就够用。数据位8、停止位1、无校验、无硬件流控这是最通用的配置。然后到NVIC Settings里勾选全局中断使能这一步别忘了否则HAL_UART_Receive_IT不会触发回调。时钟树部分先确认HSE外部晶振频率再把PLL倍频系数设置为168MHz系统时钟APB1预分频系数设置为4得到42MHzAPB2预分频系数设置为2得到84MHz。这样串口时钟就位了。生成代码后main函数里会自动出现MX_USART1_UART_Init到MX_UART5_UART_Init的初始化调用每个串口对应一个独立的初始化函数。3.2 初始化代码结构怎么做到串口多了不乱5个串口如果每个都单独写一套接收缓冲和标志变量代码会有大量重复后期维护也容易出错。我习惯把每个串口的句柄、收发缓冲区和状态都封装成一个结构体typedef struct { UART_HandleTypeDef *huart; uint8_t rx_buf[128]; volatile uint8_t rx_len; volatile uint8_t rx_flag; } UART_Port; UART_Port g_uart1 { .huart huart1 }; UART_Port g_uart2 { .huart huart2 }; UART_Port g_uart3 { .huart huart3 }; UART_Port g_uart4 { .huart huart4 }; UART_Port g_uart5 { .huart huart5 };初始化时统一遍历结构体对每个串口调用HAL_UART_Receive_IT使能接收。中断回调里根据huart指针判断是哪个串口把字节存入对应结构体的缓冲区同时重新使能下一次接收。这样做的好处非常明显新增一个串口只需在结构体数组里增加一项不用复制一大段重复代码。5个串口的数据接收逻辑只写一份改一处就能同步更新所有串口的行为。3.3 标准库或寄存器方式的关键计算如果你不用CubeMX和HAL库而是使用标准库或者寄存器操作核心步骤是手动配置RCC时钟、GPIO复用、串口控制寄存器。BRR波特率寄存器的计算是绕不开的环节。波特率计算公式为USARTDIV fck / (16 × BaudRate)。以APB1上的USART2为例fck为42MHz波特率115200时USARTDIV 42000000 / (16 × 115200) 22.786。BRR寄存器的写入值是将USARTDIV的小数部分转换为4位或3位小数的定点格式。在实际工程中如果计算出的分频系数不是整数BRR最终也只能写入近似值因此波特率必然存在微小误差只要误差在约±2%以内通信就不会出错。寄存器方式初始化USART1的核心代码大致如下RCC-APB2ENR | RCC_APB2ENR_USART1EN; RCC-AHB1ENR | RCC_AHB1ENR_GPIOAEN; GPIOA-MODER | (GPIO_MODE_AF_PP GPIO_MODER_MODER9_Pos) | (GPIO_MODE_AF_PP GPIO_MODER_MODER10_Pos); GPIOA-AFR[1] | (7 GPIO_AFRH_AFSEL9_Pos) | (7 GPIO_AFRH_AFSEL10_Pos); USART1-BRR 0x16C; // 84MHz时钟下约115200波特率 USART1-CR1 | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;这种方式配置起来比HAL库繁琐但好处是代码体积小、执行快而且你对每个寄存器的用途会记得更牢。对于刚接触F405的开发者我建议先从CubeMXHAL库入手跑通之后再回头研究寄存器两条路都能走通才算真正理解串口。4. 发送接收代码怎么写才不容易翻车4.1 阻塞发送与中断接收的组合在一个多串口系统里我推荐的组合是“阻塞发送 中断接收”。发送数据时调用HAL_UART_Transmit阻塞等待发送完成代码逻辑最简单直接接收数据时用HAL_UART_Receive_IT使能逐字节中断接收每一字节到达后触发中断拷贝到缓冲区后自动开启下一字节接收。中断接收的HAL库回调函数是全局统一的HAL_UART_RxCpltCallback所有串口共用这一个函数所以需要通过huart指针区分来源void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart huart1) { g_uart1.rx_buf[g_uart1.rx_len] g_uart1.tmp; g_uart1.rx_len % 128; HAL_UART_Receive_IT(huart1, g_uart1.tmp, 1); } else if (huart huart2) { // 同样处理 } }这种逐字节中断接收方式每一字节都会触发一次中断数据量大的时候中断频率很高。如果不用HAL库直接在串口中断服务函数里读DR寄存器并存入缓冲区效率会更高。但HAL库的方式胜在代码可读性好回调机制清晰5个串口的管理并不复杂。4.2 五串口数据缓冲与协议解析框架逐字节中断接收解决了“收到数据”的问题但真正让多个串口稳定运行的关键是数据缓冲和协议解析的解耦。中断回调只负责往缓冲区写数据主循环负责从缓冲区读数据并解析两者通过缓冲区衔接互不阻塞。最简单可靠的缓冲区是环形FIFO。写一个环形缓冲区大概需要read_index、write_index、buffer三个要素写入时自增write_index读取时自增read_index两者相遇表示缓冲已空或已满。串口中断回调里调用写入函数主循环的while里定时调用读取函数处理数据。有了缓冲区后协议解析就有了解耦的空间。以RS485口的Modbus RTU为例主循环每轮检查环形缓冲区是否有完整帧有就解析执行没有就跳出继续下一轮。另外几个串口的数据处理类似只是帧格式不同。这样5个串口共用一套“中断写入主循环解析”的架构比每个串口各写一套处理逻辑要清晰得多。对于不定长数据如果HAL库版本支持UARTEx还可以用空闲中断判断一帧数据的结束。空闲中断指的是总线上超过一个字节时间没有新数据则视为当前帧接收完成。这个机制配合DMA可以在几乎不占用CPU的前提下完成整帧接收是我在处理升级文件传输、传感器高速数据时的首选方案。5. 调试中的常见问题和我的排查经验5.1 串口乱码的三种常见原因串口乱码是新手遇到最多的现象但原因往往就那么几类。第一是晶振频率配置错误。CubeMX生成的工程默认HSE_VALUE可能是25MHz如果你的板子实际用的是8MHz晶振不修改这个宏系统时钟就会偏得离谱串口全乱。检查SystemClock_Config函数中RCC_OscInitStruct的配置务必确保PLL输入频率和实际硬件一致。第二是波特率不匹配。代码里配置115200串口调试助手里却选的9600这属于低级错误但出现频率极高排查时先核对两侧参数。第三是USB转串口工具没有正常工作。CH340、CP2102、FT232这类芯片都需要安装对应驱动驱动异常时设备管理器根本看不到COM口自然无法通信。另外USB转串口模块如果供电不足也会出现偶发乱码和断连。调试5个串口时不要一次性全开。先把USART1调通能稳定收发字节后再依次打开其他串口每打开一个就用调试助手验证一遍这样出问题能快速缩小范围。5.2 多串口同时收发丢数据如果单独测试每个串口都正常5个串口全部启用后偶发丢数据绝大多数原因是中断处理耗时过长。HAL库的UART接收中断里如果直接调用printf、sprintf或者耗时很长的协议解析函数中断会长时间占用CPU期间的串口数据无法被及时处理硬件接收寄存器溢出后数据自然就丢了。解决办法有两个方向。一个是在中断回调里只做“拷贝字节到缓冲”这一件事所有解析和处理全部放到主循环另一个是对数据率较高的串口单独提升中断优先级确保它不会被其他串口中断长时间打断。两个方向要配合使用优先级只是降低被打断的概率真正解决问题还是要让中断函数足够短。如果是通过杜邦线连接外部设备线长超过20cm且波特率高于115200时信号质量也可能导致丢数据。此时把波特率降下来或者换成屏蔽线、双绞线问题往往立刻缓解。5.3 引脚复用冲突多串口系统最容易忽略的是引脚复用冲突因为CubeMX有时并不会给出直观警告。前面提到的USART3和UART4同时使用PC10/PC11就是典型例子两个串口都想占用同一个引脚配置了不一定会报错但实际运行只会有一个工作正常。另一个我踩过的坑是UART5和SDIO共用PD2。一次设计中我同时启用了UART5和SD卡SD卡初始化就出现偶发失败。排查很久才发现PD2被两个外设同时占用中段信号互相干扰。最后把UART5的引脚从PD2挪开才彻底解决。在F405上PB3、PB4、PA15这些引脚默认是JTAG调试口如果要当作串口引脚使用必须先关闭对应JTAG功能否则引脚电平被调试电路锁定串口怎么配都不通。配置GPIO时还要确认复用功能编号USART用AF7UART4和UART5用AF8写错AF号同样会导致串口无信号。最后再分享一个我个人的习惯不管需求里最终要不要用DMA第一版调试代码一律全部配置成中断接收、阻塞发送先把5路串口的数据通路全部跑通再根据实际负载逐个优化成DMA。这个节奏看起来保守但实际省下的调试时间远比我最初预想的多。多串口系统的复杂度不在单个串口而在串口之间的资源协调先把基础框架打稳后面加功能才不至于推翻重来。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价