
简介一份基于STM32F103C8T6(M3内核)的UART串口通信完整项目资料结合HAL库与STM32CubeMX面向嵌入式初学者与STM32开发者解决从CubeMX图形化配置到USART收发、中断处理及DMA优化等问题可直接导入开发环境进行编译下载。压缩包共146个文件涵盖C源码、H头文件、Hex固件、IOC工程配置、Keil工程文件及编译中间文件整体约4.31MB目录清晰适合对照源码与配置逐行学习。已有5985人学习/下载具有较高参考热度。资料包含CubeMX时钟树与串口参数的配置思路、HAL_UART_Transmit/Receive等API调用示例并涉及NV_USART模块的非易失性存储扩展同时介绍了串口调试终端连接、常见错误排查和DMA大量数据传输的优化方法能够帮助读者从零搭建稳定串口通信功能为后续RS485、蓝牙等外设通信打下基础。 前几天整理资料翻出一个老项目包名字就叫“基于STM32F103C8T6(HAL库)和CubeMX实现UART串口通信数据收发.rar”。这其实是我给实验室新生准备的第一份入门例程别看串口通信听起来基础很多新手第一次接触STM32卡就卡在环境、配置、收发这三关上。今天干脆把这个项目拆开从CubeMX配置到HAL库代码再到实际接线和踩坑过程完整聊一遍。选择STM32F103C8T6最小系统板原因很直接便宜、资料多、引脚够用。C8T6是64引脚LQFP封装Flash 64KB、RAM 20KB主频最高72MHz跑串口、I2C、SPI这类常规外设绰绰有余。板子本身可以通过USB转串口模块连接电脑也能用ST-Link下载调试。CubeMX负责图形化配置外设HAL库把寄存器操作封装成结构清晰、可读性强的函数接口两者结合能把开发周期压缩得很短。有人可能问直接用标准外设库或者寄存器操作不是更底层吗不是不好而是对大多数应用场景来说HAL库更符合“先跑通再优化”的思路。对于学生作业、竞赛原型、快速验证方案这套组合就是最优解。1. 项目拆解这个UART例程到底解决了什么问题1.1 为什么选STM32F103C8T6HAL库CubeMX先说芯片。STM32F103C8T6属于F1主流系列虽然推出时间比较早但市场验证非常充分最小系统板的资源也极其丰富。核心板一般只需要一个8MHz晶振、复位电路、BOOT配置和3.3V供电就能稳定跑起来。这个项目用UART作为第一个外设是因为串口几乎是所有嵌入式开发的“调试窗口”。程序跑到哪里、传感器数据是多少、协议交互是否正常都可以通过串口打印出来比单纯用Keil仿真观察变量直观得多。CubeMX在这个项目里的作用是把最繁琐的初始化阶段自动化。以前用标准库写UART要手动计算波特率寄存器、配置GPIO复用模式、打开USART时钟、使能中断任何一个寄存器写错现象就是乱码或者完全没反应。CubeMX生成工程之后这些底层配置全部由工具完成剩下的精力可以放在业务逻辑上。HAL库则提供了一套统一的API初始化结构体、发送接收函数、中断回调机制结构清晰。遇到问题去社区搜几乎都能找到现成答案。对于刚接触单片机的新手来说这套组合的学习成本远低于从寄存器裸写开始啃。1.2 UART通信的核心知识点有哪些UART全称是Universal Asynchronous Receiver/Transmitter通用异步收发器。它只需要TX和RX两根数据线外加共地就能实现全双工通信。为什么叫异步因为收发双方没有共享时钟而是靠约定波特率来采样。这就像两个人约好语速一个人说另一个人按同样的节奏听如果语速对不上内容必然听岔。一个完整的数据帧包括起始位、数据位通常8位、可选的奇偶校验位、停止位。我们常用的115200 8 N 1意思就是波特率115200bps8个数据位无校验1个停止位。波特率决定每秒钟传输多少个bit两个设备不一致收到的数据必然乱码。STM32的波特率由时钟分频得到比如USART1挂载在APB2总线时钟72MHz想要得到115200bps分频系数大约是39.06。CubeMX会根据你填的波特率自动计算寄存器值配置起来很快但前提是时钟树必须正确否则算出来的波特率同样会偏。理解这几个基本概念后续对接蓝牙模块、WiFi模块、GPS模块时就不会被各种参数搞晕。2. CubeMX配置细节从新建工程到生成代码2.1 芯片选型、调试接口与时钟树打开CubeMX新建工程在MCU选择器里搜索STM32F103C8Tx。双击选中后第一步先把SYS里的Debug改成Serial Wire。这一步不做ST-Link可能没法通过SWD接口连接板子尤其是板载SWD引脚被复用的情况。很多最小系统板都引出了SWCLK和SWDIO但CubeMX默认调试接口是No Debug生成代码后调试器会识别不到芯片。第二步配置RCCHSE选择Crystal/Ceramic Resonator。绝大多数最小系统板都焊了8MHz晶振用外部晶振才能稳定跑到72MHz主频。如果偷懒选择HSI内部时钟实际主频会变成8MHzCubeMX里虽然填了72MHz但串口波特率会整体偏移现象就是乱码这点后面会重点说。接着进入Clock Configuration页面把PLL Source选为HSE输入8MHz倍频到72MHz。F103的时钟树有限制APB1总线最高36MHzAPB2最高72MHz。USART1挂在APB2上所以它能跑满72MHz。正因如此例程里优先使用USART1配置简单通信稳定。2.2 USART1参数配置这些选项不能乱填在左侧Categories里展开Connectivity选中USART1Mode改为Asynchronous。下方Parameter Settings会列出波特率、数据长度、校验、停止位等参数。我习惯设115200 8 N 1因为和绝大多数串口助手、蓝牙模块、GPS模块默认参数一致。实际项目如果走长线或者电磁环境差可以降到9600误码概率更低。波特率越高对时钟精度和线路质量的要求就越高。引脚方面USART1默认映射到PA9TX和PA10RX这也是最小系统板引出的排针直接能用杜邦线连接USB转TTL模块。有人问为什么不用USART2USART2也可以但它挂在APB1上时钟上限只有36MHz而且PA2/PA3有时会被其他外设占用。C8T6上USART1最方便兼容性最好。另外要注意STM32的串口引脚输出的是TTL电平不是RS232电平所以不能直接接电脑的DB9串口必须通过USB转TTL模块转换。2.3 三个容易踩的配置项工程生成后如果发现不能下载、不能调试先回头检查SYS里的Debug是否是Serial Wire。这是新手最常见的第一个坑。第二个坑在Project Manager。Toolchain / IDE要选对用Keil就选MDK-ARM用STM32CubeIDE就选STM32CubeIDE否则生成的工程根本打不开。很多资料默认用的是MDK但高版本CubeMX生成的工程直接用旧版Keil打开也会报错建议升级到较新的Keil版本。第三个坑是Timebase Source。如果后续要用FreeRTOS或者复杂中断建议把时基从SysTick改成一个定时器比如TIM1。SysTick本身被HAL_Delay占用FreeRTOS也依赖它配置冲突时程序会莫名其妙卡死。CubeMX里这个选项在SYS页面默认是SysTick。没有特殊需求可以保持默认但作为稳定习惯我通常在初期就把它改成TIM1给系统多留一条后路。3. HAL库代码实现三种收发方式实测3.1 轮询方式适合打印日志和固定帧交互CubeMX生成代码后串口初始化已经完成用户代码主要加在main函数的while(1)里。最简单的发送是调用HAL_UART_Transmituint8_t tx_buf[] Hello STM32 UART\r\n; while (1) { HAL_UART_Transmit(huart1, tx_buf, sizeof(tx_buf) - 1, 1000); HAL_Delay(500); }这个函数有四个参数串口句柄、待发送数据指针、发送长度、超时时间。超时时间单位是毫秒如果发送在超时时间内没完成函数会返回超时错误码。轮询发送的特点是简单直接适合打印日志、调试信息或者发送固定长度的协议帧但要避免在中断回调里调用否则可能阻塞中断处理。接收对应HAL_UART_Receive但它有个特点必须收满指定字节数才返回。比如设len5如果只来了3个字节程序会一直阻塞在那里直到超时或者凑够5个字节。所以轮询接收适合长度固定的协议帧不适合不定长字符串。第一次上手时我先用发送函数打印测试信息再接USB转串口模块看返回确认链路通。3.2 中断方式真正解决“只收一次”的坑串口收发最常用的是中断方式但网上“串口中断接收只收一次”的求助特别多。先看正确写法uint8_t rx_byte 0; int main(void) { // 初始化代码... HAL_UART_Receive_IT(huart1, rx_byte, 1); while (1) { // 主循环不处理串口接收 } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { HAL_UART_Transmit(huart1, rx_byte, 1, 100); HAL_UART_Receive_IT(huart1, rx_byte, 1); } }关键在于HAL_UART_Receive_IT每调用一次只能完成一次接收。进入中断回调后HAL库会把串口状态从READY置为BUSY如果不重新调用Receive_IT串口就再也不收新数据了。很多教程没强调这点代码跑一遍只收一次就以为板子坏了其实只是缺少最后一行重新使能。另外注意回调函数名不能改而且要判断huart实例防止多个串口回调互相干扰。中断回调里尽量别做耗时操作比如HAL_Delay或协议解析否则实时性会变差。更好的做法是把收到的字节丢进环形缓冲区在主循环里统一处理。3.3 DMA方式大批量数据收发的进阶路径当数据量很大比如每秒几KB的传感器数据中断方式频繁进中断会占用CPU资源。DMA可以让外设直接把数据搬进内存搬完再通知CPU。HAL库提供了HAL_UART_Transmit_DMA和HAL_UART_Receive_DMA。配置DMA时发送一般用Normal模式接收做不定长数据时用Circular模式配合USART的IDLE空闲中断能实现“收一批数据再统一处理”的效果。不过C8T6的DMA资源有限只有一个DMA1控制器共7个通道串口收发分别占用通道使用前要查数据手册确认别跟ADC、SPI抢通道。DMA适合做传输层优化基础项目可以先不碰但值得列入下一个学习目标。等把轮询、中断、DMA三种方式都跑一遍对HAL库的串口模型就会有比较完整的认识。4. 实操接线、驱动与回环验证4.1 最小系统板接线与USB转串口选型STM32F103C8T6最小系统板的串口引脚通常印在板上PA9是USART1_TXPA10是USART1_RX。与USB转TTL模块连接时一定要交叉MCU的TX接模块RXMCU的RX接模块TXGND必须共地。如果不交叉发出去的信号跑到了自己的接收脚什么都收不到。STM32F103C8T6USB转TTL模块PA9 (TX)RXPA10 (RX)TXGNDGND市面上USB转串口模块主控常见的有CH340、FT232R、FT231X。CH340便宜多数Windows系统免驱FT232R和FT231X贵一些但兼容性和抗干扰更好。用FT232R或FT231X时部分新装系统或者精简系统会识别成未知设备需要去官网下载对应驱动安装后设备管理器里才能看到COM口。驱动装不上时试着禁用Windows驱动强制签名或者换个USB口很多玄学问题其实是接触不良。4.2 串口助手与回环测试先排除链路问题把USB转串口模块插到电脑设备管理器里出现COM口然后打开任意串口助手波特率设成和代码一致。最稳妥的验证方式是“回环测试”先把模块TX和RX短接在串口助手里发送数据如果自己能收到说明USB串口链路是通的。再把模块和STM32接好发送数据给MCUMCU用中断回显如果串口助手能收到同样的数据说明整个收发链路没问题。我在调试时还会开一个逻辑分析仪抓PA9和PA10的电平波形确认有数据帧在跑。没有逻辑分析仪也不要紧回环测试加串口助手已经能覆盖大部分情况。如果状态指示灯不亮或者电压不对优先用万用表量3.3V供电。很多最小系统板供电能力一般外接多个设备时容易掉电压导致串口异常。5. 常见问题排查与避坑实录5.1 串口中断接收只收一次的完整复盘这是最典型的问题值得专门展开。现象很好复现上电后第一次发数据MCU能收到并回显再发就没反应了。原因就是3.2节说的HAL_UART_Receive_IT只配置一次中断完成后没有重新使能。但还有两个隐藏原因一是回调函数中执行了HAL_UART_Transmit发送期间会产生发送相关中断如果NVIC里收发中断优先级配置不当接收中断可能一直被别的中断抢占二是回调里处理了复杂逻辑导致再一次调用Receive_IT时串口状态还处于BUSY。解决办法很简单回调里只做数据搬运不要在里面做协议解析和耗时操作。优先级分组在CubeMX的NVIC配置里统一设置中断优先级不要全都一样两个不同外设的中断如果抢占优先级相同调度时会出问题。5.2 串口乱码八成是时钟或波特率问题串口助手显示乱码第一反应不要怀疑代码先查CubeMX时钟树。F103默认HSI内部时钟是8MHz如果你没有配置外部晶振却在时钟树里把SYSCLK设成72MHz实际主频就错了波特率也跟着错乱码就这么来的。另一个容易忽略的点是串口助手、USB转串口模块、STM32三者的波特率必须完全一致。传输距离长时建议降低波特率并在模块端接好地线。短接回环测试时正常但一接MCU就乱码优先检查共地和电源纹波。5.3 串口工具收不到数据或设备无法识别如果串口助手完全收不到数据按这个顺序排查先看设备管理器里COM口是否存在没有就装驱动FT232R和FT231X用户重点查驱动版本再看接线是否交叉、是否共地然后做回环测试区分模块和MCU的故障最后检查代码里USART1是否真的使能了接收中断。硬件问题用万用表量电压软件问题用调试器打断点。把排查步骤固定下来比漫无目的地猜快得多。现象常见原因处理方式中断只收一次接收中断未重新使能在RxCpltCallback里再次调用HAL_UART_Receive_IT串口乱码时钟树未配置外部晶振配置HSE为8MHzPLL倍频到72MHz设备管理器无COM口USB转串口驱动缺失安装CH340/FT232R/FT231X对应驱动完全收不到数据TX/RX没有交叉或未共地按接线表重新连线回环正常但MCU不返回中断优先级或代码问题检查NVIC配置确认回调里重新调用Receive_IT最后再说说我在实际项目里的一个习惯。串口这个外设看起来简单但坑都藏在配置细节里。每次新建工程我都会先用回环测试确认硬件链路再用轮询发送打印一个版本号最后才写中断接收。这样每一步出问题都能快速定位是硬件、配置还是代码的问题。如果你第一次跑通这套UART例程建议下一步试着加一个环形缓冲区把接收到的字节先存起来再在主循环里解析再往后可以研究DMA加IDLE中断的接收方式基本能覆盖绝大多数串口应用场景。串口玩明白了再学I2C、SPI、CAN这些外设思路都是相通的。本文还有配套的精品资源点击获取