STM32F103C8T6+STM32CubeMX串口通信入门教程:从配置到调试

发布时间:2026/10/4 3:06:59
STM32F103C8T6+STM32CubeMX串口通信入门教程:从配置到调试 1. 项目概述与硬件软件准备1.1 为什么选择STM32F103C8T6加STM32CubeMX这套组合如果你点进这篇笔记大概率已经在网上搜过“STM32F103C8T6最小系统板”或者“STM32CubeMX配置串口”这类关键词。先给结论用F103C8T6这颗Cortex-M3内核的片子配合STM32CubeMX图形化配置工具来做UART串口通信是整个STM32入门曲线里性价比最高、也最值得优先掌握的实操组合。STM32F103C8T6的核心优势在于“便宜大碗”。一块蓝色pill样式的系统板几十块钱就能买到板上自带USB转串口芯片、稳压电路、LED和按键面包板上插几根杜邦线就能开始干活。芯片本身拥有64KB Flash、20KB RAM主频最高72MHz工作电压2.0V到3.6V板载的LDO会把USB的5V降到3.3V。对于串口通信这种基础外设来说这个资源不仅绰绰有余还能同步支持ADC、I2C、SPI、PWM等后续实验作为学习板非常合适。选择STM32CubeMX的原因更直接很多人第一次接触STM32时被初始化代码劝退寄存器版的USART初始化光是查数据手册就要耗掉半天。CubeMX把时钟树、引脚复用、外设参数、中断优先级这些底层配置图形化直接生成可编译的初始化工程让我们把精力集中在业务逻辑上。这篇笔记不会通篇讲寄存器而是用“配置工具生成代码手写收发逻辑”的方式把串口通信的完整链路跑通。不管你是准备做毕业设计、公司项目预研还是单纯想搞懂UART原理这套打法都适用。1.2 硬件清单与软件版本选择做这个实验之前建议把下面这些东西备齐其中绝大多数都是入门玩家的标配STM32F103C8T6最小系统板一块注意确认板上有没有焊接USB转串口芯片有些精简版只引出了串口引脚USB转TTL模块一个常见方案有CH340G、CP2102、FT232RL。以CH340G最常见驱动安装最简单串口助手识别为COM口后直接使用杜邦线若干母对母最好用最小系统板引出的排针需要母头杜邦线对接电脑上安装好STM32CubeMX建议6.x版本、集成开发环境推荐KEIL MDK5或者IAR、一个趁手的串口调试助手如XCOM、SSCOM、友善串口助手需要提醒一句如果你买的是带CH340芯片的“蓝色Pill”板子板载USB口插上电脑后应该会识别出串口设备。如果没识别到大概率是驱动问题去设备管理器看一眼有没有带感叹号的设备装好CH340驱动基本就能解决。如果买的是不带USB转串口的最小系统板就需要外接USB转TTL模块来和电脑通信接线方法我放在第四章里详细讲。软件版本方面STM32CubeMX 6.x和KEIL MDK5推荐5.27以上是当前最主流的选择。CubeMX生成代码时选择MDK-ARMV5工具链生成的工程直接能被MDK打开。IAR用户也可以选IAR工具链但国内交流资料还是以MDK为主新手用MDK遇到问题更容易搜到解决方案。2. STM32CubeMX图形化配置UART的完整步骤2.1 新建工程与芯片选择打开STM32CubeMX后在主页选择“Access to MCU Selector”进入芯片选型界面。搜索框输入STM32F103C8T6注意在列表里找到标记为LQFP48封装、2048Bytes Flash字符串的那一项别选成CBT6或者RCT6引脚数量和Flash大小都不一样选错会导致后续引脚配置界面完全对不上。选中芯片后双击进入工程配置界面。这里要做三件基础设置在“System Core”里配置RCC和SYS在“Project Manager”里配置工程名称与工具链这两步虽然不起眼但做错会在后续踩坑。先看RCC配置在System Core选项卡下点RCC把High Speed ClockHSE设置为Crystal/Ceramic Resonator这是为了让系统能够正常启动外部8MHz晶振为后面的72MHz主频提供时钟源头。紧接着把SYS里的Debug选择为Serial Wire这一步极其关键如果保持默认的No Debug代码下载一次之后第二次就会因为SWD引脚被复用而无法连接调试器最终只能按住板载复位键抢时间下载得不偿失。Project Manager选项卡里工程名称建议命名为uart_demo之类不要带中文和空格。Toolchain选择MDK-ARMMinimum Heap Size和Minimum Stack Size保持默认即可对于串口这个级别的小程序完全够用。此外建议勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”这样每个外设都有独立的初始化和头文件工程结构清爽后续维护方便得多。2.2 配置USART1参数与中断回到Pinout Configuration界面左侧列表找到Connectivity展开后点击USART1。右边图形化界面上PA9和PA10两个引脚会自动被复用为USART1的TX和RX并变成绿色这说明引脚映射正确。Mode栏选择Asynchronous异步模式这就是我们通常说的UART通信方式不需要时钟线只要TX、RX两根数据线就能完成数据交换。然后在Configuration里的Parameter Settings中设置通信参数Baud Rate波特率设置为115200。这里可以顺手解释一下波特率的概念它表示每秒传输多少个二进制位1秒传115200个bit按一个字节10位8位数据起始位停止位粗算波特率115200大约每秒能传11520个字节。通信双方的波特率必须完全一致否则收到的就是乱码这就是很多新手串口调试时看到“”乱码的根本原因。Word Length设置为8 BitsParity设置为None无校验Stop Bits设置为1 BitFlow Control选择None。这套参数组合是串口通信事实上的“默认协议”缩写为115200-8-N-1绝大部分串口调试助手的默认设置就是这个组合不用特别改动。如果你做的是工业通信或与特殊设备对接再根据对方手册调整这些参数但学习阶段固定用115200-8-N-1就足够了。接下来打开NVIC Settings选项卡勾选USART1 global interrupt并把Preemption Priority设为一个合适的值。这里涉及一个容易忽略的点如果你在CubeMX里使能了中断但是没勾选NVIC代码生成后即使调用中断接收函数也不会进中断回调函数排查起来非常隐蔽。所以务必确认NVIC里的USART1 interrupt是Enable状态。2.3 时钟树配置与工程生成时钟树是这个工具新手比较容易懵的地方。切换到Clock Configuration选项卡这个界面由各种分频器、倍频器和选择器构成看起来像一张迷宫地图。我们需要把系统主频配置到72MHz过程是HSE外部8MHz通过PLL倍频到72MHz作为系统时钟SYSCLK再通过AHB分频得到HCLK之后分频到APB136MHz和APB272MHz。具体操作上在PLL Source下拉框选择HSE然后在PLLMul倍数里选择x98MHz乘以9等于72MHz。此时界面里的HCLK会变成72MHz如果显示红色或其他颜色说明有配置冲突。接着确认APB1 Prescaler为/2所以APB1是36MHzAPB2 Prescaler为/1APB2保持72MHz。USART1挂在APB2总线上所以USART1的时钟就是72MHz波特率的计算基于这个值。对于USART1来说APB272MHz是标准配置如果是USART2或USART3它们挂在APB1上对应的外设时钟是36MHz所以很多人在USART2上写波特率配置时会遇到一个小坑——CubeMX自动计算波特率时会根据实际时钟源自动调整但如果你手动改过时钟树一定要回头检查波特率是否仍是你期望的值。全部配置完成后点击右上角的GENERATE CODE按钮软件询问是否打开工程时选择打开。STM32CubeMX会自动生成一个完整的MDK工程里面已经包含系统初始化、GPIO初始化、USART初始化和中断服务函数框架我们接下来要做的就是在Main函数里补充自己的收发逻辑。3. KEIL下的数据收发实现与代码解析3.1 HAL库串口发送与接收的函数家族打开生成的KEIL工程编译一次确保没有报错然后开始写业务代码。在这里我们接触到HAL库的UART驱动层函数。先介绍最常用的四个HAL_UART_Transmit阻塞发送、HAL_UART_Receive阻塞接收、HAL_UART_Transmit_IT中断发送、HAL_UART_Receive_IT中断接收。阻塞式发送HAL_UART_Transmit的函数原型是HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);第一个参数是UART句柄在main.c里已经定义了UART_HandleTypeDef huart1第二个参数是要发送的数据缓冲区第三个参数是发送的字节数第四个参数是超时时间单位是毫秒。函数执行时会一个字节一个字节地把数据搬进发送数据寄存器DR全部发送完才返回期间MCU会卡在这里所以叫阻塞发送。对于调试打印这种低频操作这种方式完全够用。阻塞式接收HAL_UART_Receive同样需要传入句柄、缓冲区和长度它同样会阻塞等待直到收满指定字节数或者超时返回。这里要特别提醒新手不要在死循环主任务里用HAL_UART_Receive等待单字节因为一旦上位机没发数据程序就会一直卡在这一行看起来像死机了。后面我会给出更合理的中断接收方案。中断方式则完全不同。HAL_UART_Receive_IT被调用后会立刻返回数据到达时由硬件触发中断在中断服务函数里接收数据并存入指定缓冲区接收完成后再调用用户自定义的回调函数。这样主程序不会被阻塞适合处理实时性要求高的通信场景。3.2 最简单的回环收发实验一个字节收一个字节发为了快速验证串口链路是否通畅先用最直观的方式写一个回环实验电脑通过串口助手发送一个字节给STM32STM32收到后原样返回同时将收到字节的十六进制值通过串口发回电脑。在main函数的主循环里我们使用中断接收方式接收一个字节并在回调函数中把它发送出去。具体代码逻辑如下先看main.c里添加的内容uint8_t rx_buffer[1] {0}; // 接收缓冲区一个字节 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 启动第一次中断接收 HAL_UART_Receive_IT(huart1, rx_buffer, 1); while (1) { // 主循环不做任何事接收和回显都在中断中完成 } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { // 收到字节后把收到的字节通过串口发送回去 HAL_UART_Transmit(huart1, rx_buffer, 1, 1000); // 重新开启中断接收准备接收下一个字节 HAL_UART_Receive_IT(huart1, rx_buffer, 1); } }这段代码的要点在于HAL库的中断接收是一次性的收到指定数量的字节后中断接收就自动关闭所以在回调函数里必须再次调用HAL_UART_Receive_IT否则只能收到第一个字节后面的字节全部丢失。这是HAL库中断接收最容易踩的坑。通过这个实验你会直观地看到串口链路是否正常发送一个字符或者十六进制字节如果串口助手立刻收到回显说明从USB转串口到STM32、再从STM32返回电脑的整条链路都是通的。3.3 printf重定向调试输出的经典做法实际项目中最常用的调试手段不是回环而是printf打印。把printf的输出重定向到串口就能像调试上位机程序一样在串口助手里看到排行榜、变量值、状态信息等排查问题效率提升不止一个档次。在KEIL MDK环境下重定向printf有两种方式。最传统的是重写fputc函数前提是已经包含stdio.h然后把fputc的实现改成调用HAL_UART_Transmit发送一个字节#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }使用MicroLIB时也可以直接重写这个函数在MDK的Options for Target - Target选项卡中勾选Use MicroLIB可以显著减少printf等C库函数的体积效果更佳。另一种方式是重写_write函数这是ARM Compiler 5和6都支持的方式灵活性更高但新手阶段用fputc就足够了。重定向完成之后在main函数或任何位置直接写printf(Hello STM32 UART\r\n)串口助手里就会收到这行字符串。注意发送字符串时建议带上\r\nWindows下的串口助手需要回车换行符才能正确显示新的一行否则所有输出会连成一条长串。3.4 不定长数据接收的思路讨论单字节中断接收的回环实验做通之后你可能会面临一个更现实的问题怎么接收不定长的一整条数据比如上位机发送一段以特定字符结尾的命令字符串或者一个完整的数据帧。这里给出一个最简单、也最容易理解的做法每收到一个字节就把它存进一个缓冲区并通过校验帧起始标志或帧结束标志来判断一帧数据是否完整。比如约定上位机以0x0A换行符作为一帧数据的结尾那么在回调函数里判断如果当前字节不是0x0A就存入缓冲区如果是0x0A就把缓冲区里的数据交给业务逻辑处理然后清空缓冲区索引准备接收下一帧。uint8_t rx_buffer[256]; // 接收缓冲区 uint16_t rx_index 0; // 当前缓冲区写入位置 uint8_t frame_ready 0; // 帧完成标志 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { uint8_t ch rx_buffer[rx_index]; // 这里保存的是刚收到的字节 if (ch \n) { rx_buffer[rx_index] \0; frame_ready 1; rx_index 0; } else if (rx_index sizeof(rx_buffer) - 1) { rx_index; } HAL_UART_Receive_IT(huart1, rx_buffer[rx_index], 1); } }这段代码的关键在于HAL_UART_Receive_IT里传入的缓冲区地址是rx_buffer[rx_index]这样每次接收的新字节都会存到缓冲区当前游标位置而不会覆盖前面已经收到的数据。这种“一个字节中转”的接收方式虽然效率不高但对于115200波特率以及绝大多数非极端场景已经够用而且代码逻辑一眼就能看懂非常适合作为入门阶段的方案。4. 硬件接线、调试与实测过程4.1 USB转TTL模块与STM32的连接方式硬件接线这块看着简单但很多人接错线导致通信失败或者芯片发热损坏。先说清楚最核心的原则只接三根线TX接RX、RX接TX、GND接GND千万别搞反也别把电源线接错。以一块外接CH340G的USB转TTL模块和STM32F103C8T6最小系统板为例接线如下USB转TTL模块STM32F103C8T6最小系统板说明TXDPA10USART1_RX模块发送STM32接收RXDPA9USART1_TX模块接收STM32发送GNDGND必须共地否则数据参考电平不一致5V或3.3V可选板上已有供电可不接需要供电时才接注意电压必须匹配为什么要交叉接线因为串口通信里“发送端”和“接收端”是针锋相对的——A的TX要连到B的RXB的TX要连到A的RX这样才能正确收发。很多新手第一次接线把TX接TX、RX接RX结果数据根本传不过去这个坑踩过的人不在少数。如果用的是带板载USB转串口的蓝色Pill板接线就更省事直接拿一根USB线把板子连到电脑板载CH340会自动映射成一个COM口PA9和PA10此时已经通过跳帽接到了板载USB转串口芯片的对应引脚上所以不需要额外接杜邦线。前提是确认板子上两个跳帽是闭合的有些复古板子没有这两个跳帽就需要飞线到板载串口芯片的输入引脚少数板甚至需要你手动断开跳帽具体以你手里的板子原理图为准。4.2 串口调试助手设置与实测结果硬件接好线再把USB转TTL模块插到电脑USB口打开设备管理器在“端口COM和LPT”下面应该能看到一个新的COM号比如COM3或者COM5。看不到就先装驱动这是硬件识别层面的排查重点。打开串口调试助手配置项按下面的参数设置波特率115200数据位8停止位1校验位None流控None打开串口选择正确的COM号确认“打开”之后在发送区输入一个十六进制字节或者一段ASCII字符串点击发送。如果前面代码已经编译烧录进STM32串口助手的接收区会立刻显示STM32回传的内容。以回环实验为例发送十六进制0x55接收区应当收到0x55如果发送的是ASCII字符“A”接收区也应当收到“A”。实际调试中建议先用最基本的回环验证链路再逐步增加发送内容的复杂度。不要一开始就上printf和不定长接收链路不通时根本说不清是硬件问题、代码问题还是参数配置问题。这个“由简到繁”的调试思路在后续所有嵌入式调试中都适用。4.3 用逻辑分析仪进一步验证时序可选串口通信看不到波形对新手来说是比较抽象的一件事。如果你手边正好有逻辑分析仪可以把逻辑分析仪的通道0接到STM32的PA9引脚USART1_TXGND接板子的GND然后在分析软件里设置好波特率115200发送“Hello”几个字符就能看到完整的串口帧波形起先是高电平发送起始位时拉低一个位周期然后依次发送8个数据位和1个停止位。亲眼看到这个波形之后你对UART通信的理解会瞬间具象化。逻辑分析仪这一步不是必须的但如果你未来要接触更多通信协议I2C、SPI、CAN或者遇到疑难问题这会是排查的必要手段。串口协议的波形分析逻辑同样适用于其他协议先看空闲电平、再找起始位、然后逐位解码很多驱动问题一眼就能看出端倪。5. 高频踩坑问题与排查思路总结5.1 串口完全收不到任何数据这个现象排在所有串口问题里的第一名排查顺序基本固定硬件链路优先然后引脚配置接着软件逻辑目前我还没有见过跳过这一步直接找出根因的案例。先确认USB转TTL模块是否被电脑识别设备管理器里有没有正确的COM号驱动是否安装好。再看接线TX-RX交叉、RX-TX交叉、GND是否共地。很多新手只接TX和RX忘了接GND导致通信双方的电平参考点不同数据自然传不过去。接着检查STM32这边是否正常运行最简单的办法是看看最小系统板的电源指示灯是否亮起如果板子本身都没供电其他一切都无从谈起。硬件没问题的话回到CubeMX检查引脚分配确认PA9和PA10确实被复用成了USART1_TX和USART1_RX且没有在别的外设配置中被占用。工程代码烧录后如果串口助手依然收不到数据还可以在代码里用HAL_GPIO_TogglePin翻转板载LED确认程序是不是真的跑到了发送数据的那一行这比瞎猜快得多。5.2 串口收到乱码乱码问题基本都是参数不一致或者时钟配置错误导致的。先检查通信双方的波特率、数据位、停止位、校验位是否完全一致重点看波特率。串口助手设置115200但CubeMX里不小心把波特率选成9600收到的自然是乱码。另外通信双方最好都采用8位数据无校验、1位停止位这种最保守的组合不容易出错。时钟树配置也会导致乱码。如果HSE没有正确配置成8MHz或者PLL倍频系数不对系统时钟不是72MHzUSART1的外设时钟就是错的CubeMX计算出的波特率实际值也会偏离标称波特率。排查方式是重新打开CubeMX对照时钟树界面确认SYSCLK等于72MHzUSART1时钟源为APB2即72MHz。5.3 程序卡死在HAL_UART_Receive这个问题几乎每个用轮询接收的新手都会遇到。现象是程序运行到HAL_UART_Receive这一行之后就不动了不再执行后面的代码。原因我在前面提过轮询接收是阻塞式的在收到指定字节数或超时之前函数不会返回。如果上位机一直不发数据主循环就被卡死在这里。处理方法主要有两种。一种是设置一个合理的超时时间比如100毫秒这样即使收不到数据函数也会超时返回主循环还能继续跑不至于完全卡死但这种方式治标不治本超时时间内MCU仍然在空等。另一种是直接放弃轮询接收改用HAL_UART_Receive_IT中断接收主循环彻底解放出来。这也是实际项目中几乎都会采用的方案。5.4 中断接收只收到第一个字节后续就不进了中断接收只成功一次是HAL库经典的“一次性”行为导致的。HAL_UART_Receive_IT每调用一次只能接收指定长度的数据接收完成后中断配置会被自动清除不会自动重新开启。如果你只在main函数里调用了一次那收到第一个字节之后中断就关了后续数据自然进不来。解决办法很明确在HAL_UART_RxCpltCallback回调函数里再次调用HAL_UART_Receive_IT重新武装接收中断。另外还要确认CubeMX里NVIC的USART1全局中断已使能否则即使重新调用了Receive_IT中断向量不会触发回调函数也不会执行。这个坑排查起来比较隐蔽建议优先检查这两处。5.5 下载程序第二次开始失败这个坑与串口本身无关但第一次做串口实验时极大概率会遇到第一次下载程序成功后第二次编译下载就报错连接不上调试器。原因是CubeMX里SYS配置的Debug选项默认是No Debug烧录第一次后SWD引脚被强制复用为普通GPIO调试器无法再连接。解决办法在上文已经提到CubeMX的SYS选项卡里把Debug改为Serial Wire重新生成代码并烧录一次问题立刻消失。这是每一个用CubeMX开发STM32的人都应该牢牢记住的基础配置修改不然会反复吃这个亏。6. 从串口通信到更复杂应用的衔接建议串口通信学会之后你会发现自己已经不知不觉接触到了嵌入式开发的几条主线时钟树是处理器的“心跳”配置中断是外设事件处理的“神经系统”HAL库抽象层让你可以相对轻松地切换不同外设和芯片。顺着这条路径继续往下学习有几个典型的扩展方向可以无缝衔接。一个是基于串口实现上位机交互比如用Python的pyserial库或者C#的SerialPort类写一个简易上位机给STM32发送命令控制LED灯亮度、读取传感器数据等。这其实是“串口通信”最实际的应用场景也可以加深你对通信协议设计的理解数据帧如何组织、如何校验、如何处理粘包和半包。另一个是引入FreeRTOS实时操作系统。FreeRTOS在STM32F103C8T6上的移植已经非常成熟网上有一大堆移植教程。有了操作系统之后串口接收可以放在一个独立的任务里配合队列Queue把接收到的数据分发给其他任务。博文标题的热词里出现“freertos学习篇一: stm32f103c8t6下的移植”就是这个方向的最佳例证。F103C8T6只有20KB RAM跑一个轻量级的FreeRTOS加上几个任务绰绰有余。还有一个有趣的方向是“串口转GPIO扩展芯片”比如用UART协议控制1路串口转16路GPIO的芯片这在引脚不够用但想点亮大量LED或扫描键盘矩阵的场景下非常实用。学过UART协议之后再去阅读这种芯片的数据手册你会发现波特率、帧格式、寄存器配置这些概念已经全被拿下了。我在实际使用中的体会是串口始终是嵌入式开发中的“万金油”接口。它速度不快功能简单但几乎所有MCU都带UART几乎所有调试工具都支持串口几乎所有单板调试场景都缺不了它。把这一篇笔记里的实验完整跑通之后不妨自己改一改波特率、换一种接收方式、给它加一个简单的帧协议彻底吃透这个14400分之一秒传一个字节的接口你后续学任何协议都会快很多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询