简介:针对STM32串口通信学习与Proteus虚拟仿真验证的嵌入式入门资源包,适合正在学习STM32标准库/HAL库开发及硬件仿真调试的开发者,帮助解决串口初始化、数据收发逻辑与电路虚拟联调中的常见问题。压缩包共136个文件,约19.9MB,以STM32固件库C源文件、头文件为主,并包含Keil工程文件(uvprojx)、Proteus仿真工程(pdsprj)以及编译生成的hex、axf等输出文件,便于直接打开工程对照学习。内容涵盖USART配置、波特率设定、中断收发等重点,同时集成定时器、ADC、I2C等外设驱动模块,可在Proteus中直观观察数据收发过程,快速验证程序正确性,并可参考工程结构学习串口与外部设备交互的设计思路。已有1744人学习下载,适合作为串口实验入门、课程设计以及仿真调试的实用参考资料。1. 项目定位:一个STM32串口实验+Proteus仿真资源能带来什么当手头没有任何一块STM32开发板,又想先把USART串口收发跑通,代码和仿真一起给的资源就是最友好的入口。这个压缩包里的东西很直接:一个基于标准外设库的STM32串口工程,外加一份配好的Proteus仿真图。你不需要自己画电路、不需要焊线,只要打开仿真,就能在虚拟终端上看到单片机发来的字符串;想体验中断接收,也可以直接把代码烧进仿真芯片里逐步调试。它适合刚接触嵌入式、手里只有一块学习板甚至没有板子的同学。比起照着网文从零配寄存器,这套实验用“能跑的结果”倒推原理,学习路径会短很多。我自己拆过不少人发的串口实验包,这份的目录是最典型的一种:源码、仿真、说明缺一不可。2. 技术栈与工程结构:从Keil到Proteus的完整链路2.1 资源包内的目录层次拆开压缩包,我建议先别急着打开工程,先看目录。常见的打包形式是这样:stm32串口实验+protues仿真/ ├─ USART_Demo/ │ ├─ User/ │ │ ├─ main.c │ │ ├─ stm32f10x_it.c │ │ └─ stm32f10x_conf.h │ ├─ Core/ │ │ ├─ core_cm3.c │ │ └─ startup_stm32f10x_hd.s │ ├─ Drivers/ │ │ └─ stm32f10x_stdperiph_lib │ ├─ Objects/ │ │ └─ USART_Demo.hex │ └─ USART_Demo.uvprojx ├─ Simulation/ │ └─ usart_sim.pdsprj └─ 使用说明.txt这个结构在往届同学分享的工程里最常见,逻辑也很清晰:User/main.c:全部业务逻辑都在这里,包括GPIO初始化、USART配置、主循环。串口实验的“实验”二字,最终就体现在这个文件里。User/stm32f10x_it.c:中断服务函数的集合。如果你要用串口中断接收或发送,USART1_IRQHandler会在这个文件里实现。Core目录:启动文件和内核抽象层,startup_stm32f10x_hd.s是芯片复位后执行的第一段汇编代码,它负责初始化堆栈、跳转到main。这个文件一般不用动,但如果芯片型号换到低密度产品,启动文件也要跟着换。Drivers:标准外设库模块。里面有stm32f10x_usart.c、stm32f10x_gpio.c等源文件,你调用的USART_SendData就定义在 usart 模块里。Objects:编译输出目录。USART_Demo.hex是最终要烧进仿真芯片的文件。很多同学只改源码不编译,或者编译后找不到hex,都是因为对这一个目录不熟。Simulation/usart_sim.pdsprj:Proteus工程,双击就能打开。后缀.pdsprj是Proteus 8的工程格式,如果安装的是Proteus 7,可能打不开,用Proteus 8 Professional系列的常见版本就行。使用说明.txt:绝大多数作者会把引脚定义、仿真版本、操作步骤写在里面。我第一次拿到这类资源时习惯性跳过,后来发现里面有波特率说明,省去很多猜测。2.2 技术选型:为什么不是HAL库,偏偏用标准库这个工程用的是STM32F103系列,外设库是标准外设库(SPL),而不是后来主流的HAL库。原因很现实:串口实验的教学场景里,标准库的函数名和寄存器操作一一对应,比如USART_SendData、USART_GetFlagStatus,你能直观看到发送数据时硬件状态怎么变化。HAL库抽象层太厚,对入门者来说黑匣子更多。我用一个简单对比来说明:能力标准外设库HAL库函数复杂度底层,直接映射寄存器高层,封装状态机时钟配置手动配置RCC手动+自动配置中断处理直接写IRQHandler回调函数HAL_UART_RxCpltCallback入门学习寄存器友好不友好这里不是说HAL库不好,而是这个实验包选标准库有助于学习者把波特率寄存器配置、TX/RX标志位这类基础概念弄清。等你在标准库上把一个串口功能调通,再看HAL库几乎不会迷路。Proteus这边的选型也很关键。仿真图里用的是STM32F103R6这一类芯片模型,配合虚拟终端(Virtual Terminal)来模拟PC串口调试助手。虚拟终端本质是一个串口外设,你把单片机TX/RX对应接上去,设置好相同的波特率,就能看到字符流。相比真实硬件还要USB转TTL、驱动、接线的麻烦,仿真环境几乎零成本复现串口实验。另外要注意,Proteus里仿真STM32,芯片型号里如果带R6或C8与真正的C8T6在引脚数量上有差异。大多数实验使用PA9、PA10这两个USART1引脚,R6和C8都有,所以不影响跑通;但如果换成串口2或串口3,就可能遇到引脚不在芯片封装上的坑。这一点在后面的避坑章节会细说。2.3 main函数里的执行顺序串口实验的main.c虽然短,但执行顺序藏着嵌入式开发的通用套路。我可以把核心逻辑抽象出来:int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); USART1_Config(); NVIC_Config(); printf("STM32 USART Demo\r\n"); while (1) { // 主循环业务 } }第一步设置优先级分组,第二步配置串口GPIO和通信参数,第三步打开中断,最后再进入业务代码。这里有一个容易被忽略的点:如果在配置串口前就调用发送函数,此时USART还没使能,数据发不出去;所以初始化永远在最前面。很多跑不起来的仿真,问题不在代码本身,而在初始化顺序。通过这个代码块可以看到,模拟的工程规模很小,但麻雀虽小五脏俱全。你拿到手之后,按这个结构认识项目,比从头到尾读一遍源码高效得多。3. 核心模块拆解:USART初始化的数据流与中断逻辑3.1 USART1初始化函数逐行讲这段代码基本是所有串口实验的“标准动作”,先贴出来,再讲每一行的作用。void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 打开GPIOA和USART1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 2. PA9作为USART1_TX,推挽复用输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. PA10作为USART1_RX,浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 4. 配置串口参数 USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 5. 使能串口 USART_Cmd(USART1, ENABLE); }第1步时钟:STM32外设使用前必须先打开对应时钟。GPIOA挂在APB2总线上,USART1也挂在APB2,所以用RCC_APB2PeriphClockCmd同时打开。这个函数接收两个参数:外设位掩码和ENABLE/DISABLE。这里可能踩坑的是有人分不清APB1和APB2,USART2到USART5在APB1上,USART1在APB2上,写错根本不能工作。第2步、第3步引脚配置:PA9、PA10是USART1的默认引脚。注意复用功能,GPIO_Mode_AF_PP表示复用推挽输出,用于TX;RX引脚必须设置为浮空输入,因为接收数据时引脚处于高阻状态,由外部信号驱动。如果你把RX也设成AF_PP,数据收不进来。第4步参数结构体:USART_BaudRate设置波特率,这里9600是虚拟终端匹配的默认值;字长8位,1个停止位,无校验,无硬件流控,收发都开。如果你要改通信协议,比如9位数据或偶校验,改这里的USART_WordLength和USART_Parity,同时虚拟终端那边也要同步设置。第5步使能串口:配置结构体只是写寄存器,最终通过USART_Cmd(USART1, ENABLE)让串口真正工作起来。很多人漏了这一步,结果发送函数写了半天,数据根本没发出。在标准库内部,波特率其实是通过写BRR寄存器实现的,计算公式是BRR = PCLK2 / (16 * 波特率)。对于USART1,PCLK2通常是72MHz。库函数帮你算了,但如果你改了系统时钟,就要留意波特率是否偏移,这也是后面避坑章节说乱码的主要原因。3.2 数据发送和接收:轮询与中断两条路发送数据最常用的是查询方式,核心代码很短:void USART1_SendString(char* str) { while (*str != '\0') { // 等待发送数据寄存器为空 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 把当前字符放入数据寄存器 USART_SendData(USART1, (uint16_t)(*str)); // 指针指向下一个字符 str++; } }这个函数的逻辑是:先检查TXE标志,发送数据寄存器空了才能写新数据,否则会覆盖还没移位完成的字节。USART_GetFlagStatus返回SET或RESET,当TXE为1时说明可以发送。USART_SendData把无符号16位数据写进DR寄存器,虽然参数是16位,但实际只有低8位被移位发送。对每个字符重复直到字符串结束。初学者经常忘掉while等待,导致发送结果是在循环里快速连续写DR寄存器,中间字节丢失。接收数据建议用中断,初始化里需要加上NVIC的配置:void NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 使能接收缓冲区非空中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); }然后在中断服务函数里读数据:void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t rx_data = USART_ReceiveData(USART1); // 在这里做你的业务处理,比如回显 USART1_SendByte(rx_data); USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }USART_ReceiveData返回的16位数据中低8位是有效数据,直接把返回值赋给uint8_t即可。中断方式的好处是不占用主循环轮询时间,但要注意:每收到一帧数据都会进一次中断,如果主循环里有长时间阻塞,别在中断里做复杂计算。常规做法是,中断里把数据丢进环形缓冲区,主循环去解析,这样接收和处理解耦。如果你不喜欢中断,也可以在主循环轮询RXNE标志来做接收,逻辑类似发送。但轮询的坏处是在接收之前主循环不能做其他事,对串口实验来说够用,扩展项目就会发现效率不够。3.3 Proteus仿真电路中的串口连接仿真文件里,电路连接其实比想象中简单。芯片用STM32F103R6,电源和晶振电路Proteus会自动给默认配置。你只需要把虚拟终端放到图纸上,再做三根连线:STM32 PA9 -> Virtual Terminal RXD STM32 PA10 -> Virtual Terminal TXD GND -> Virtual Terminal GND特别留意交叉接线。单片机的TX对应终端的RXD,RX对应终端的TXD。终端在收到数据时,RXD引脚是输入方向;反过来单片机接收时,终端TXD引脚是输出方向。很多人第一次画图会直接TX对TX,结果两边都等在一条线上,什么都收不到。虚拟终端的属性面板里有一个Baud Rate选项,这里必须和代码里的USART_BaudRate一致。如果代码是9600,虚拟终端也选9600;好多实验喜欢用115200,那你两边都要改。数据位、校验位同样要对应。在Proteus中虚拟终端默认8N1,恰好和标准库配置相同,所以实验中基本不需要额外调整。另一个常见做法是加一个COMPIM组件模拟物理串口,配合串口调试助手查看数据。但这个实验包里只要虚拟终端就够了——虚拟终端自带显示窗口,省掉宿主机串口映射的复杂度。如果你打开仿真图发现里面是COMPIM而不是虚拟终端,说明作者想让你用PC上的串口工具对接,原理相同,但需要把调试助手波特率调到一致。4. 运行实战:从Keil编译到Proteus看到串口输出4.1 Keil工程编译配置在Keil中打开USART_Demo.uvprojx,确认左侧Project栏里的目标芯片是什么。如果芯片型号和源码中对不上,比如源码按F103C8写而工程里选了F103RB,直接编译可能会报库不匹配。检查方式是右键 -> Options for Target -> Device,选择你手上实际型号。本实验用标准外设库,选STM32F103C8或STM32F103R6都可以,Proteus仿真用R6,Keil工程选C8,生成的HEX不影响仿真,因为内核和外设寄存器映射一致。接下来要确保生成HEX文件。打开Options for Target,在Output标签页里勾选Create HEX File。很多嵌入式初学者第一次跑仿真,发现Proteus加载不了工程,原因就是Keil默认不输出HEX。勾选后点编译按钮,等进度条走完,到Objects目录看有没有.hex后缀文件。在C/C++标签页里还要确认宏定义是否完整。标准外设库工程通常需要定义:USE_STDPERIPH_DRIVER STM32F10X_HD如果缺少USE_STDPERIPH_DRIVER,库里的断言函数会失效,很多外设函数无法编译;如果缺少STM32F10X_HD,启动文件和你选的高密度芯片不匹配,串口外设的寄存器结构体可能不对。这两个宏在作者原本的工程里一般已经填好,但如果编译报错,优先检查这里。如果编译报错,大概率是库文件路径问题。标准外设库的工程经常因为压缩包解压路径带中文或空格,导致Keil找不到include目录。解决方法是把项目整体放到纯英文无空格路径下,比如D:\STM32_USART。这也是拆别人项目时最常遇到的状况,整理一下就能解决。4.2 把HEX加载进Proteus并启动打开Simulation目录下的usart_sim.pdsprj。在Proteus中双击STM32芯片,弹出属性对话框,在Program File一项点击文件夹图标,选择刚才生成的USART_Demo.hex。然后检查两处时钟设置:芯片属性中的Crystal Frequency,常见默认值是8MHz。标准库的串口波特率计算依赖SystemCoreClock,默认系统时钟为72MHz。Proteus里晶振频率设置为8MHz时,它内部会模拟RCC倍频到72MHz。如果改了晶振频率,可能波特率偏移。虚拟终端的波特率与代码中的9600一致。设置完成,先放虚拟终端的位置,它会在仿真运行时弹出串口监视窗口。点击Proteus右下角的“开始仿真”(播放按钮)。程序加载后,main函数里调用发送字符串的代码会立刻执行,虚拟终端窗口会显示Hello, STM32 USART!之类的内容。如果你看到终端里出现的是乱码,不要慌,先检查波特率设置。如果完全没输出,按暂停,检查中断服务函数里是否有死循环、芯片有没有复位,重跑一次仿真。为了验证中断接收,可以在虚拟终端窗口里输入一个字符,如果代码里写了回显逻辑,窗口会把这个字符再打印一次。这一步是检验USART1_IRQHandler是否触发的直接方法。输入abc,终端立刻回显abc,说明串口双向通信都通了。5. 避坑指南:串口仿真最容易翻车的四个点这一章专门记录我在拆这个实验包和帮人调试时遇到的高频问题。不要小看这几个,它们占了串口仿真失败的八成原因。5.1 现象:HEX加载了却什么都没发生情况是仿真运行正常,虚拟终端窗口弹出,但屏幕上什么字符都不显示,按复位键也没用。原因:最常见是没有加载HEX,或者加载的是旧的、未重新编译的HEX。很多同学改完代码后懒得在Keil里重新编译,直接拿压缩包里自带的旧hex去加载,自然看不到新效果。还有一种情况是芯片属性的Program File路径里填了.uvprojx工程文件,Proteus根本解析不了。解决:在Keil里重新编译,到Objects目录确认生成时间最新的.hex。打开Proteus芯片属性,重新选择这个文件,确认后缀是.hex。如果还是不行,把芯片删掉再重新从库里拖一个出来,重新加载,这能排除模型本身的残留问题。5.2 现象:虚拟终端显示出一堆乱码情况是能看到字符,但全是“□”或不能读的符号,也有的是字符数量对但内容完全不对。原因:波特率不一致是最常见的。虚拟终端属性里的Baud Rate默认可能是2400、4800,代码里是9600,两边不在一个速率上,当然乱码。其次是虚拟终端的数据位、校验位和代码不匹配。最后,如果芯片的Crystal Frequency被改成非8MHz,HSE_VALUE变化导致USART分频计算偏移,同样乱码。解决:先停止仿真,双击虚拟终端,把Baud Rate改成和代码完全相同。再看属性里的Data Bits、Parity、Stop Bits是否8、无、1。然后把芯片属性里的Crystal Frequency恢复为8MHz。记住:代码、虚拟终端、芯片时钟三个地方必须形成一条正确的计算链。5.3 现象:输出完全没有,但终端窗口一直在闪情况是虚拟终端窗口有光标闪烁,但怎么也不显示字符,点键盘输入也没有回显。原因:TX/RX接反了。PA9必须接虚拟终端的RXD,PA10接TXD。接反时,单片机的TX输出接到终端的TX输出,两个都是输出脚,总线冲突,数据根本传不到终端。另一个高频原因是忘了接GND,虚拟终端和单片机之间没有参考地,信号电平悬空,收不到数据。解决:检查连线,交错连接:PA9 -> RXD,PA10 -> TXD,GND -> GND。在Proteus里可以直接点中导线高亮,沿着网络名排查。画图时养成命名网标的习惯,比如TX、RX、GND,就不会出现这种问题。5.4 现象:轮询发送正常,中断接收一个字节就卡死情况是主循环里发送字符串没问题,但从虚拟终端输入一个字符后,板卡似乎进入死循环,后续发送也停止。原因:USART_ReceiveData本身会清除RXNE标志,但如果你的中断服务函数里写死了USART_ClearITPendingBit,并且放在不符合规范的流程里,可能引发重复中断。另一个玄学点是NVIC优先级分组没有设置,标准库默认是分组0,如果你的工程其他地方改了分组,USART1优先级可能失效。解决:中断服务函数里不要画蛇添足。标准的做法是先读DR寄存器,再处理数据,最后用USART_ClearITPendingBit清除挂起位。同时建议在主函数初始化前调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2),再配置串口中断。这个分组会影响所有中断优先级,放在最前面统一设置。6. 二次开发:把串口实验变成你的调试利器6.1 改造一:实现不定长数据包的接收原实验可能只是固定字符串发送加回显,实际项目中更多是接收完整数据包。你可以在中断里加上简单的状态机:uint8_t rx_buffer[64]; uint8_t rx_index = 0; void USART1_IRQHandler(void) { uint8_t data = USART_ReceiveData(USART1); if (data == '\n') { rx_buffer[rx_index] = '\0'; // 一包数据接收完毕,置标志位给主循环 rx_index = 0; } else { if (rx_index < 63) rx_buffer[rx_index++] = data; } }把结束符从\n改成你协议里的帧尾,比如0xAA 0x55,就构成了最简分包逻辑。再进一步可以加CRC校验、DMA搬运,这些都可以在这个工程上迭代。6.2 改造二:从标准库移植到HAL库或其他STM32型号如果后续项目要求用HAL库开发,标准库的初始化逻辑对照一下其实不复杂。HAL里用HAL_UART_Init代替USART_Init,用HAL_UART_Transmit代替轮询发送,用HAL_UART_Receive_IT开启中断接收。引脚复用和GPIO配置记得同步修改。换芯片时最需要注意的是时钟。标准库工程默认针对F103,如果换到F407,RCC配置和GPIO复用功能定义会完全不同。我的做法是:先固定外设库版本,再根据目标芯片的参考手册重新对一遍RCC_APB2PeriphClockCmd和GPIO_PinAFConfig,这也是当初我自己踩得最狠的坑。6.3 验证思路:仿真和实物双确认在我自己的习惯里,Proteus只是第一道验证,它验证的是逻辑正确性,不等于真实引脚电平、上升沿、噪声。跑通仿真后,我会拿最小系统板配合USB转TTL再跑一遍。把代码烧进去后,先用串口调试助手看接收,再用逻辑分析仪量PA9波形,确认比特率准确。从那以后,每次做串口实验我都先让仿真跑一遍,再上手板子,省下的排查时间远大于那几分钟仿真开销,希望帮到你。本文还有配套的精品资源,点击获取
简介:针对STM32串口通信学习与Proteus虚拟仿真验证的嵌入式入门资源包,适合正在学习STM32标准库/HAL库开发及硬件仿真调试的开发者,帮助解决串口初始化、数据收发逻辑与电路虚拟联调中的常见问题。压缩包共136个文件,约19.9MB,以STM32固件库C源文件、头文件为主,并包含Keil工程文件(uvprojx)、Proteus仿真工程(pdsprj)以及编译生成的hex、axf等输出文件,便于直接打开工程对照学习。内容涵盖USART配置、波特率设定、中断收发等重点,同时集成定时器、ADC、I2C等外设驱动模块,可在Proteus中直观观察数据收发过程,快速验证程序正确性,并可参考工程结构学习串口与外部设备交互的设计思路。已有1744人学习下载,适合作为串口实验入门、课程设计以及仿真调试的实用参考资料。1. 项目定位:一个STM32串口实验+Proteus仿真资源能带来什么当手头没有任何一块STM32开发板,又想先把USART串口收发跑通,代码和仿真一起给的资源就是最友好的入口。这个压缩包里的东西很直接:一个基于标准外设库的STM32串口工程,外加一份配好的Proteus仿真图。你不需要自己画电路、不需要焊线,只要打开仿真,就能在虚拟终端上看到单片机发来的字符串;想体验中断接收,也可以直接把代码烧进仿真芯片里逐步调试。它适合刚接触嵌入式、手里只有一块学习板甚至没有板子的同学。比起照着网文从零配寄存器,这套实验用“能跑的结果”倒推原理,学习路径会短很多。我自己拆过不少人发的串口实验包,这份的目录是最典型的一种:源码、仿真、说明缺一不可。2. 技术栈与工程结构:从Keil到Proteus的完整链路2.1 资源包内的目录层次拆开压缩包,我建议先别急着打开工程,先看目录。常见的打包形式是这样:stm32串口实验+protues仿真/ ├─ USART_Demo/ │ ├─ User/ │ │ ├─ main.c │ │ ├─ stm32f10x_it.c │ │ └─ stm32f10x_conf.h │ ├─ Core/ │ │ ├─ core_cm3.c │ │ └─ startup_stm32f10x_hd.s │ ├─ Drivers/ │ │ └─ stm32f10x_stdperiph_lib │ ├─ Objects/ │ │ └─ USART_Demo.hex │ └─ USART_Demo.uvprojx ├─ Simulation/ │ └─ usart_sim.pdsprj └─ 使用说明.txt这个结构在往届同学分享的工程里最常见,逻辑也很清晰:User/main.c:全部业务逻辑都在这里,包括GPIO初始化、USART配置、主循环。串口实验的“实验”二字,最终就体现在这个文件里。User/stm32f10x_it.c:中断服务函数的集合。如果你要用串口中断接收或发送,USART1_IRQHandler会在这个文件里实现。Core目录:启动文件和内核抽象层,startup_stm32f10x_hd.s是芯片复位后执行的第一段汇编代码,它负责初始化堆栈、跳转到main。这个文件一般不用动,但如果芯片型号换到低密度产品,启动文件也要跟着换。Drivers:标准外设库模块。里面有stm32f10x_usart.c、stm32f10x_gpio.c等源文件,你调用的USART_SendData就定义在 usart 模块里。Objects:编译输出目录。USART_Demo.hex是最终要烧进仿真芯片的文件。很多同学只改源码不编译,或者编译后找不到hex,都是因为对这一个目录不熟。Simulation/usart_sim.pdsprj:Proteus工程,双击就能打开。后缀.pdsprj是Proteus 8的工程格式,如果安装的是Proteus 7,可能打不开,用Proteus 8 Professional系列的常见版本就行。使用说明.txt:绝大多数作者会把引脚定义、仿真版本、操作步骤写在里面。我第一次拿到这类资源时习惯性跳过,后来发现里面有波特率说明,省去很多猜测。2.2 技术选型:为什么不是HAL库,偏偏用标准库这个工程用的是STM32F103系列,外设库是标准外设库(SPL),而不是后来主流的HAL库。原因很现实:串口实验的教学场景里,标准库的函数名和寄存器操作一一对应,比如USART_SendData、USART_GetFlagStatus,你能直观看到发送数据时硬件状态怎么变化。HAL库抽象层太厚,对入门者来说黑匣子更多。我用一个简单对比来说明:能力标准外设库HAL库函数复杂度底层,直接映射寄存器高层,封装状态机时钟配置手动配置RCC手动+自动配置中断处理直接写IRQHandler回调函数HAL_UART_RxCpltCallback入门学习寄存器友好不友好这里不是说HAL库不好,而是这个实验包选标准库有助于学习者把波特率寄存器配置、TX/RX标志位这类基础概念弄清。等你在标准库上把一个串口功能调通,再看HAL库几乎不会迷路。Proteus这边的选型也很关键。仿真图里用的是STM32F103R6这一类芯片模型,配合虚拟终端(Virtual Terminal)来模拟PC串口调试助手。虚拟终端本质是一个串口外设,你把单片机TX/RX对应接上去,设置好相同的波特率,就能看到字符流。相比真实硬件还要USB转TTL、驱动、接线的麻烦,仿真环境几乎零成本复现串口实验。另外要注意,Proteus里仿真STM32,芯片型号里如果带R6或C8与真正的C8T6在引脚数量上有差异。大多数实验使用PA9、PA10这两个USART1引脚,R6和C8都有,所以不影响跑通;但如果换成串口2或串口3,就可能遇到引脚不在芯片封装上的坑。这一点在后面的避坑章节会细说。2.3 main函数里的执行顺序串口实验的main.c虽然短,但执行顺序藏着嵌入式开发的通用套路。我可以把核心逻辑抽象出来:int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); USART1_Config(); NVIC_Config(); printf("STM32 USART Demo\r\n"); while (1) { // 主循环业务 } }第一步设置优先级分组,第二步配置串口GPIO和通信参数,第三步打开中断,最后再进入业务代码。这里有一个容易被忽略的点:如果在配置串口前就调用发送函数,此时USART还没使能,数据发不出去;所以初始化永远在最前面。很多跑不起来的仿真,问题不在代码本身,而在初始化顺序。通过这个代码块可以看到,模拟的工程规模很小,但麻雀虽小五脏俱全。你拿到手之后,按这个结构认识项目,比从头到尾读一遍源码高效得多。3. 核心模块拆解:USART初始化的数据流与中断逻辑3.1 USART1初始化函数逐行讲这段代码基本是所有串口实验的“标准动作”,先贴出来,再讲每一行的作用。void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 打开GPIOA和USART1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 2. PA9作为USART1_TX,推挽复用输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. PA10作为USART1_RX,浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 4. 配置串口参数 USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 5. 使能串口 USART_Cmd(USART1, ENABLE); }第1步时钟:STM32外设使用前必须先打开对应时钟。GPIOA挂在APB2总线上,USART1也挂在APB2,所以用RCC_APB2PeriphClockCmd同时打开。这个函数接收两个参数:外设位掩码和ENABLE/DISABLE。这里可能踩坑的是有人分不清APB1和APB2,USART2到USART5在APB1上,USART1在APB2上,写错根本不能工作。第2步、第3步引脚配置:PA9、PA10是USART1的默认引脚。注意复用功能,GPIO_Mode_AF_PP表示复用推挽输出,用于TX;RX引脚必须设置为浮空输入,因为接收数据时引脚处于高阻状态,由外部信号驱动。如果你把RX也设成AF_PP,数据收不进来。第4步参数结构体:USART_BaudRate设置波特率,这里9600是虚拟终端匹配的默认值;字长8位,1个停止位,无校验,无硬件流控,收发都开。如果你要改通信协议,比如9位数据或偶校验,改这里的USART_WordLength和USART_Parity,同时虚拟终端那边也要同步设置。第5步使能串口:配置结构体只是写寄存器,最终通过USART_Cmd(USART1, ENABLE)让串口真正工作起来。很多人漏了这一步,结果发送函数写了半天,数据根本没发出。在标准库内部,波特率其实是通过写BRR寄存器实现的,计算公式是BRR = PCLK2 / (16 * 波特率)。对于USART1,PCLK2通常是72MHz。库函数帮你算了,但如果你改了系统时钟,就要留意波特率是否偏移,这也是后面避坑章节说乱码的主要原因。3.2 数据发送和接收:轮询与中断两条路发送数据最常用的是查询方式,核心代码很短:void USART1_SendString(char* str) { while (*str != '\0') { // 等待发送数据寄存器为空 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 把当前字符放入数据寄存器 USART_SendData(USART1, (uint16_t)(*str)); // 指针指向下一个字符 str++; } }这个函数的逻辑是:先检查TXE标志,发送数据寄存器空了才能写新数据,否则会覆盖还没移位完成的字节。USART_GetFlagStatus返回SET或RESET,当TXE为1时说明可以发送。USART_SendData把无符号16位数据写进DR寄存器,虽然参数是16位,但实际只有低8位被移位发送。对每个字符重复直到字符串结束。初学者经常忘掉while等待,导致发送结果是在循环里快速连续写DR寄存器,中间字节丢失。接收数据建议用中断,初始化里需要加上NVIC的配置:void NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 使能接收缓冲区非空中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); }然后在中断服务函数里读数据:void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t rx_data = USART_ReceiveData(USART1); // 在这里做你的业务处理,比如回显 USART1_SendByte(rx_data); USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }USART_ReceiveData返回的16位数据中低8位是有效数据,直接把返回值赋给uint8_t即可。中断方式的好处是不占用主循环轮询时间,但要注意:每收到一帧数据都会进一次中断,如果主循环里有长时间阻塞,别在中断里做复杂计算。常规做法是,中断里把数据丢进环形缓冲区,主循环去解析,这样接收和处理解耦。如果你不喜欢中断,也可以在主循环轮询RXNE标志来做接收,逻辑类似发送。但轮询的坏处是在接收之前主循环不能做其他事,对串口实验来说够用,扩展项目就会发现效率不够。3.3 Proteus仿真电路中的串口连接仿真文件里,电路连接其实比想象中简单。芯片用STM32F103R6,电源和晶振电路Proteus会自动给默认配置。你只需要把虚拟终端放到图纸上,再做三根连线:STM32 PA9 -> Virtual Terminal RXD STM32 PA10 -> Virtual Terminal TXD GND -> Virtual Terminal GND特别留意交叉接线。单片机的TX对应终端的RXD,RX对应终端的TXD。终端在收到数据时,RXD引脚是输入方向;反过来单片机接收时,终端TXD引脚是输出方向。很多人第一次画图会直接TX对TX,结果两边都等在一条线上,什么都收不到。虚拟终端的属性面板里有一个Baud Rate选项,这里必须和代码里的USART_BaudRate一致。如果代码是9600,虚拟终端也选9600;好多实验喜欢用115200,那你两边都要改。数据位、校验位同样要对应。在Proteus中虚拟终端默认8N1,恰好和标准库配置相同,所以实验中基本不需要额外调整。另一个常见做法是加一个COMPIM组件模拟物理串口,配合串口调试助手查看数据。但这个实验包里只要虚拟终端就够了——虚拟终端自带显示窗口,省掉宿主机串口映射的复杂度。如果你打开仿真图发现里面是COMPIM而不是虚拟终端,说明作者想让你用PC上的串口工具对接,原理相同,但需要把调试助手波特率调到一致。4. 运行实战:从Keil编译到Proteus看到串口输出4.1 Keil工程编译配置在Keil中打开USART_Demo.uvprojx,确认左侧Project栏里的目标芯片是什么。如果芯片型号和源码中对不上,比如源码按F103C8写而工程里选了F103RB,直接编译可能会报库不匹配。检查方式是右键 -> Options for Target -> Device,选择你手上实际型号。本实验用标准外设库,选STM32F103C8或STM32F103R6都可以,Proteus仿真用R6,Keil工程选C8,生成的HEX不影响仿真,因为内核和外设寄存器映射一致。接下来要确保生成HEX文件。打开Options for Target,在Output标签页里勾选Create HEX File。很多嵌入式初学者第一次跑仿真,发现Proteus加载不了工程,原因就是Keil默认不输出HEX。勾选后点编译按钮,等进度条走完,到Objects目录看有没有.hex后缀文件。在C/C++标签页里还要确认宏定义是否完整。标准外设库工程通常需要定义:USE_STDPERIPH_DRIVER STM32F10X_HD如果缺少USE_STDPERIPH_DRIVER,库里的断言函数会失效,很多外设函数无法编译;如果缺少STM32F10X_HD,启动文件和你选的高密度芯片不匹配,串口外设的寄存器结构体可能不对。这两个宏在作者原本的工程里一般已经填好,但如果编译报错,优先检查这里。如果编译报错,大概率是库文件路径问题。标准外设库的工程经常因为压缩包解压路径带中文或空格,导致Keil找不到include目录。解决方法是把项目整体放到纯英文无空格路径下,比如D:\STM32_USART。这也是拆别人项目时最常遇到的状况,整理一下就能解决。4.2 把HEX加载进Proteus并启动打开Simulation目录下的usart_sim.pdsprj。在Proteus中双击STM32芯片,弹出属性对话框,在Program File一项点击文件夹图标,选择刚才生成的USART_Demo.hex。然后检查两处时钟设置:芯片属性中的Crystal Frequency,常见默认值是8MHz。标准库的串口波特率计算依赖SystemCoreClock,默认系统时钟为72MHz。Proteus里晶振频率设置为8MHz时,它内部会模拟RCC倍频到72MHz。如果改了晶振频率,可能波特率偏移。虚拟终端的波特率与代码中的9600一致。设置完成,先放虚拟终端的位置,它会在仿真运行时弹出串口监视窗口。点击Proteus右下角的“开始仿真”(播放按钮)。程序加载后,main函数里调用发送字符串的代码会立刻执行,虚拟终端窗口会显示Hello, STM32 USART!之类的内容。如果你看到终端里出现的是乱码,不要慌,先检查波特率设置。如果完全没输出,按暂停,检查中断服务函数里是否有死循环、芯片有没有复位,重跑一次仿真。为了验证中断接收,可以在虚拟终端窗口里输入一个字符,如果代码里写了回显逻辑,窗口会把这个字符再打印一次。这一步是检验USART1_IRQHandler是否触发的直接方法。输入abc,终端立刻回显abc,说明串口双向通信都通了。5. 避坑指南:串口仿真最容易翻车的四个点这一章专门记录我在拆这个实验包和帮人调试时遇到的高频问题。不要小看这几个,它们占了串口仿真失败的八成原因。5.1 现象:HEX加载了却什么都没发生情况是仿真运行正常,虚拟终端窗口弹出,但屏幕上什么字符都不显示,按复位键也没用。原因:最常见是没有加载HEX,或者加载的是旧的、未重新编译的HEX。很多同学改完代码后懒得在Keil里重新编译,直接拿压缩包里自带的旧hex去加载,自然看不到新效果。还有一种情况是芯片属性的Program File路径里填了.uvprojx工程文件,Proteus根本解析不了。解决:在Keil里重新编译,到Objects目录确认生成时间最新的.hex。打开Proteus芯片属性,重新选择这个文件,确认后缀是.hex。如果还是不行,把芯片删掉再重新从库里拖一个出来,重新加载,这能排除模型本身的残留问题。5.2 现象:虚拟终端显示出一堆乱码情况是能看到字符,但全是“□”或不能读的符号,也有的是字符数量对但内容完全不对。原因:波特率不一致是最常见的。虚拟终端属性里的Baud Rate默认可能是2400、4800,代码里是9600,两边不在一个速率上,当然乱码。其次是虚拟终端的数据位、校验位和代码不匹配。最后,如果芯片的Crystal Frequency被改成非8MHz,HSE_VALUE变化导致USART分频计算偏移,同样乱码。解决:先停止仿真,双击虚拟终端,把Baud Rate改成和代码完全相同。再看属性里的Data Bits、Parity、Stop Bits是否8、无、1。然后把芯片属性里的Crystal Frequency恢复为8MHz。记住:代码、虚拟终端、芯片时钟三个地方必须形成一条正确的计算链。5.3 现象:输出完全没有,但终端窗口一直在闪情况是虚拟终端窗口有光标闪烁,但怎么也不显示字符,点键盘输入也没有回显。原因:TX/RX接反了。PA9必须接虚拟终端的RXD,PA10接TXD。接反时,单片机的TX输出接到终端的TX输出,两个都是输出脚,总线冲突,数据根本传不到终端。另一个高频原因是忘了接GND,虚拟终端和单片机之间没有参考地,信号电平悬空,收不到数据。解决:检查连线,交错连接:PA9 -> RXD,PA10 -> TXD,GND -> GND。在Proteus里可以直接点中导线高亮,沿着网络名排查。画图时养成命名网标的习惯,比如TX、RX、GND,就不会出现这种问题。5.4 现象:轮询发送正常,中断接收一个字节就卡死情况是主循环里发送字符串没问题,但从虚拟终端输入一个字符后,板卡似乎进入死循环,后续发送也停止。原因:USART_ReceiveData本身会清除RXNE标志,但如果你的中断服务函数里写死了USART_ClearITPendingBit,并且放在不符合规范的流程里,可能引发重复中断。另一个玄学点是NVIC优先级分组没有设置,标准库默认是分组0,如果你的工程其他地方改了分组,USART1优先级可能失效。解决:中断服务函数里不要画蛇添足。标准的做法是先读DR寄存器,再处理数据,最后用USART_ClearITPendingBit清除挂起位。同时建议在主函数初始化前调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2),再配置串口中断。这个分组会影响所有中断优先级,放在最前面统一设置。6. 二次开发:把串口实验变成你的调试利器6.1 改造一:实现不定长数据包的接收原实验可能只是固定字符串发送加回显,实际项目中更多是接收完整数据包。你可以在中断里加上简单的状态机:uint8_t rx_buffer[64]; uint8_t rx_index = 0; void USART1_IRQHandler(void) { uint8_t data = USART_ReceiveData(USART1); if (data == '\n') { rx_buffer[rx_index] = '\0'; // 一包数据接收完毕,置标志位给主循环 rx_index = 0; } else { if (rx_index < 63) rx_buffer[rx_index++] = data; } }把结束符从\n改成你协议里的帧尾,比如0xAA 0x55,就构成了最简分包逻辑。再进一步可以加CRC校验、DMA搬运,这些都可以在这个工程上迭代。6.2 改造二:从标准库移植到HAL库或其他STM32型号如果后续项目要求用HAL库开发,标准库的初始化逻辑对照一下其实不复杂。HAL里用HAL_UART_Init代替USART_Init,用HAL_UART_Transmit代替轮询发送,用HAL_UART_Receive_IT开启中断接收。引脚复用和GPIO配置记得同步修改。换芯片时最需要注意的是时钟。标准库工程默认针对F103,如果换到F407,RCC配置和GPIO复用功能定义会完全不同。我的做法是:先固定外设库版本,再根据目标芯片的参考手册重新对一遍RCC_APB2PeriphClockCmd和GPIO_PinAFConfig,这也是当初我自己踩得最狠的坑。6.3 验证思路:仿真和实物双确认在我自己的习惯里,Proteus只是第一道验证,它验证的是逻辑正确性,不等于真实引脚电平、上升沿、噪声。跑通仿真后,我会拿最小系统板配合USB转TTL再跑一遍。把代码烧进去后,先用串口调试助手看接收,再用逻辑分析仪量PA9波形,确认比特率准确。从那以后,每次做串口实验我都先让仿真跑一遍,再上手板子,省下的排查时间远大于那几分钟仿真开销,希望帮到你。本文还有配套的精品资源,点击获取