
做STM32开发这些年我见过太多人卡在同一个地方程序写好了编译也过了一点下载——No target connected。然后开始怀疑人生板子坏了芯片烧了线没接对其实大概率都不是。这种经历我太熟了所以这篇文章不打算讲STM32基础也不写高深算法就专门聊聊STM32开发调试过程中那些真正浪费过时间的坑以及我是怎么一步步排查出来的。不管你是正在做毕业设计的学生、刚转嵌入式的新人还是写了几年程序的工程师在调试这条路上总有那么几个坑能让你对号入座。我尽量把每个坑的根因、排查链路和解决办法都写得具体一点方便你直接对照自己的项目。毕竟调试这种事最怕的不是问题难而是连从哪儿下手都不知道。1. 环境搭建与工程样板先解决“能不能编译、能不能下载”1.1 Keil5装完却找不到STM32芯片包很多新手第一次装Keil5装完兴冲冲打开结果在Device选择列表里翻半天都找不到STM32。不是软件有问题而是Keil MDK从V5版本开始把芯片支持从安装包里拆出来了变成了独立的Pack包。MDK安装包本身默认只带ARM通用编译器具体芯片的器件支持需要额外安装。解决办法是打开Pack Installer找到STM32F1系列的DFP包如Keil.STM32F1xx_DFP安装。但这里有个很现实的坑Pack Installer需要联网下载国内网络状况下经常卡在下载进度条不动或者下载一半失败。我遇到过好几次Pack下载失败后还残留了部分文件导致重新安装时报错。如果你也遇到这种情况直接去Keil官网下载对应DFP安装包手动双击安装比Pack Installer里干等靠谱得多。还有一个小细节安装DFP时最好先关掉Keil工程否则可能出现安装成功但工程里仍然显示不了芯片的情况需要重启工程。另一个很常见的坑是C51和STM32共存的问题。Keil的C51版本和MDK版本其实是两个独立产品一个管8051一个管ARM。如果你电脑上先装了C51的uVision4后来又装了MDK的uVision5或者反过来很容易出现我装了Keil但打开工程提示Device不存在的情况。因为两个IDE虽然看起来差不多但支持的芯片列表完全不同。我的做法是C51和MDK都装安装路径分开图标虽然很像但打开前看清楚是哪个版本。如果你手头有51的工程和STM32的工程要轮流折腾千万别图省事只装一个那才是真正的折磨。1.2 STM32标准库新建工程的三个隐藏雷虽然现在新项目很多人直接用STM32CubeMX加HAL库但标准库依然有大量存量代码和教材尤其是做毕业设计和老项目维护的场景。新建标准库工程时我踩过的雷可以列出三个第一个是启动文件选错。STM32F1系列启动文件分了好几种startup_stm32f10x_ld.s、md.s、hd.s分别对应低容量、中容量、高容量。很多教程里默认用hd.s如果你手里的芯片是F103C8T6这种中容量的用hd.s也不是不能启动但如果你选的是F103RCT6高容量芯片却用了md.s程序能烧进去运行却会莫名其妙出问题。因为启动文件里定义了堆栈大小和中断向量表的位置容量不匹配会导致内存映射错位。第二个是宏定义没加。标准库工程必须在C/C的Define里加上USE_STDPERIPH_DEVICE以及STM32F10X_HD或者STM32F10X_MD取决于你的芯片容量。很多教程建工程时根本不提这个导致编译报一堆未定义错误。我当年第一次建工程编译报错几十条还以为是库文件没拷全其实是宏定义漏了。第三个是头文件路径没有包含全。标准库的源码按文件夹组织你需要把Misc、GPIO、RCC、USART等用到的外设头文件路径都加进Include Paths。漏一个就编译报无法打开文件。建议直接把整个Libraries文件夹路径加进去省得来回改。这其实是Keil工程配置里的基本功但新人在这一步卡一天很正常。我带的几个实习生几乎每一个都在新建工程上耗过半天一旦跑通一次后面就顺了。1.3 标准库和HAL库到底怎么选关于选库这件事属于每次群里都有人问的问题。很多人纠结标准库字符级操作直接、好多教程都用标准库HAL库配合CubeMX生成代码效率高但代码量太大。我的看法是标准库封装层次低离寄存器近适合学习原理、调试底层问题、做资源紧张的简单项目。ST官方已经停止维护标准库新出的芯片也没标准库可用但F1、F4系列老片子用户量太大网上资料几乎取之不尽。HAL库抽象层更高配合STM32CubeMX可以快速生成初始化代码适合快速原型、复杂外设协同、多项目复用。但Debug时函数调用层级深出问题了追踪起来费劲。HAL库本身也有一些黑魔法比如状态机管理、超时机制你要是没吃透它的设计思路初次上手容易觉得怎么这么绕。LL库介于两者之间更接近寄存器操作但用的人相对少。如果你的目标是搞懂原理或者项目要求极致的执行效率走标准库没问题如果目标是快速出活、接新项目、用新芯片直接学HALCubeMX更划算。最怕的是学到一半想换代码写到一半从标准库迁HAL库那种痛我真的不建议你体验。顺带提一下VSCode配置STM32的问题。最近问的人确实多用EIDE插件或者CMake加ARM GCC工具链确实能摆脱Keil的界面。但我的建议是如果你还在调试阶段频繁踩坑先把Keil用好再考虑VSCode。VSCode编辑代码舒服但调试器配置、烧录器配置、cmsis-dap或OpenOCD的target配置任何一个没配对都能让你折腾一晚上。工具是为主业服务的不是用来折腾的。2. 下载器连接失败从No target connected说起2.1 插上ST-Link电脑却提示无法识别USB设备先说一个最算不上故障的故障USB设备无法识别。排查顺序很重要我一般是这样的换一根USB线。现在很多充电线只有电源线没有数据线插上去能供电但设备管理器毫无反应。这种伪故障占比非常高尤其是你随手从抽屉里摸出来一根线的时候。换一个USB口。前置面板的USB口供电和信号质量经常不如主板后置口尤其是接ST-Link这种需要稳定供电的设备。检查设备管理器。如果看到未知设备或者带感叹号的设备尝试右键更新驱动。ST-Link的驱动有专门的安装包老版本Windows下经常需要手动装一次。确认ST-Link固件版本。太旧的ST-Link固件在某些机器上会识别异常用STM32CubeProgrammer或者官方ST-Link Upgrade工具升级一下固件。如果设备管理器里能看到ST-Link设备但连接目标板时报错那问题就在目标板这边了。常见原因包括目标板没有独立供电ST-Link的3.3V输出能力有限、SWD接线过长信号不稳定、目标板复位电路异常。这个时候先把SWD线尽量缩短把下载速率调低往往就能解决问题。下载速率太高的坑特别容易忽略ST-Link默认可能跑1.8MHz飞线搭的板子信号质量差降到500kHz甚至100kHz就稳定了。2.2 第一次下载成功第二次就No target connected这个坑我印象太深了因为它发生的时间点永远是你觉得稳了的时候。说的就是程序第一次下载成功板子也跑起来了然后你改了几行代码想重新下载点击下载却直接报No target connected。根因大概率不是你板子坏了而是你的程序里把SWD引脚给禁用了。STM32的SWD和JTAG共用一组引脚如果你在初始化代码里调用了类似GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE)这样的函数调试口会被释放成普通GPIO。程序一旦跑起来SWDIO和SWCLK两个引脚就不听调试器指挥了下一次下载当然连不上。这种坑最恶心的地方在于它很隐蔽。你只是按照某个例程加了一句禁用JTAG防止干扰的代码结果把下载口也禁了。排查的方法是按住目标板的复位键不松手然后点下载在程序执行到初始化之前把连接抢下来或者把BOOT0拉高进ISP模式用串口全片擦除芯片内的程序。如果是已经禁用了调试口的固件最稳妥的办法就是进ISP擦除。后来我给自己定了一条规矩开发调试阶段绝不轻易在代码里禁用SWD/JTAG功能。即使量产阶段为了省电和IO资源需要禁用也一定保留一段可以通过跳线或者特定操作恢复的方案否则后续升级固件就成了噩梦。2.3 ST-Link Utility和STM32CubeProgrammer的救援价值很多人不知道下载器除了烧录程序还能用来救命。ST官方早年的ST-Link Utility现在已经升级为STM32CubeProgrammer可以绕过正常下载流程直接操作芯片内部Flash。最常见的救援场景是芯片读写保护打开后无法连接。有些芯片在量产阶段被设置了读保护RDP等级1此时调试器默认无法读取Flash。你用ST-Link连接时会报错但用STM32CubeProgrammer执行一次Full chip erase全片擦除会把保护位一起清掉芯片就能重新下载了。注意全片擦除会清掉所有代码和配置操作前确认数据已备份。STM32CubeProgrammer比ST-Link Utility更值得常用它支持多种连接方式ST-Link、UART串口ISP、USB DFU。当SWD口被意外禁用时你还有一条逃生通道——UART ISP。只要BOOT0拉高、BOOT1拉低芯片上电进入系统存储器里的Bootloader用串口就能擦除和重刷Flash。这个方法我在折腾自制最小系统板时救过好几次命。还有一个小技巧STM32CubeProgrammer可以读取和修改Option Bytes。如果你需要调整读保护等级、看门狗模式或者BOOT配置不用额外写代码直接在这个软件里操作就行。调试阶段建议大家把读保护关了否则每次下载前都要先擦除麻烦得很。3. 时钟、延时与启动程序跑飞前最隐蔽的三个坑3.1 时钟树配置不对串口乱码和延时失准的共同源头STM32的时钟系统比51单片机复杂得多这也是很多人从51转过来时最不适应的点。51单片机接个晶振就能跑STM32内部有HSI内部高速时钟、HSE外部高速晶振、PLL倍频、总线分频这一整套时钟树。调试经验里凡是程序能跑但串口乱码延时时间完全不对USB枚举失败这些症状十有八九和时钟配置有关。最经典的场景你的开发板上有一个8MHz外部晶振代码里SystemInit默认使用HSE作为系统时钟源。但如果你的板子没有焊外部晶振或者晶振虚焊HSE启动会失败。STM32F1的启动逻辑在HSE失败后会自动退回HSI运行程序不会死但系统时钟从72MHz掉到了内部8MHz所有依赖时间的计算全崩了——串口波特率不对、定时器溢出时间变了、delay函数的时间尺度也变了。表现出来就是板子好像所有功能都不太对劲。排查手段其实很直接。先用万用表量晶振两脚波形或者用示波器看有没有起振再进调试器看RCC-CR寄存器的HSERDY位是否为1如果HSEON为1但HSERDY一直为0就是外部晶振电路没起来。很多人会忽略电路本身的问题晶振需要两个负载电容如果板子上没焊或者尽最大努力只是看起来焊了实际上虚焊也会导致不起振。还有一种情况是代码里明明写了外部8M晶振但实际板子上装的是12M晶振——频率不对串口乱码、定时器时间整体偏差。有一个经验如果你用的是内部HSI时源USB外设大概率无法正常工作因为USB要求48MHz的精确时钟内部RC振荡器精度不够。很多基于F103的USB虚拟串口项目调试不通过最后都栽在时钟源上。所以不是能用内部时钟就省事得先看清外设的时钟需求。3.2 延时函数卡死SysTick被谁抢了延时卡死可以说是嵌入式开发里的固有节目。明明代码里写了一个Delay(1000)结果程序执行到这就再也不走了。这类问题我从标准库转到HAL库之后又遇到过一次根因还不完全一样。标准库下常见的Delay实现是基于SysTick定时器中断实现。卡死的原因有几个某段代码把全局中断关了SysTick中断一直在Pending状态无法执行计数器永远不会更新Delay就永远退不出来。你的工程里其他地方重写了SysTick_Handler标准库的延时计数逻辑被覆盖了。比如有些人从网上拷贝一段代码里面自己写了一个SysTick_Handler结果把系统那个顶替了。优化等级太高编译优化把循环判断条件里的变量给优化掉了导致for循环延时永久执行。排查方法也不复杂在Delay调用前点亮一个LED调用后再熄灭看LED行为进调试器查看SysTick的CTRL寄存器和Current Value寄存器确认SysTick是否在跑如果怀疑优化等级把优化等级临时改成-O0试一下。如果问题消失就是优化引起的你可以用volatile修饰循环变量或者改用官方的SysTick延时逻辑。HAL库的HAL_Delay卡死也有一个隐蔽原因HAL_Delay依赖一个全局变量uwTick而这个变量是在SysTick_Handler里调用HAL_IncTick递增的。如果你用标准库的习惯写代码或者移植项目时忘了调用HAL_InitSysTick中断没有初始化HAL_Delay就会永远等下去。还有人在自己的中断服务程序里长时间阻塞SysTick中断一直没有机会执行HAL_Delay也会表现异常。3.3 HardFault_Handler数组越界与栈溢出的表象程序跑着跑着突然进入HardFault_Handler死循环这是嵌入式开发的经典梦魇。很多人一进HardFault就慌其实它本质上是CPU遇到不可恢复的错误后跳转到异常向量大多数情况下不是芯片坏了而是你的代码触发了非法操作。最常见的几个触发原因数组越界写入尤其是DMA数据传输缓冲区定义小了数据写入越界。栈溢出在中断里定义了超大局部数组或者递归调用过深把栈撑爆了。操作了未使能时钟的外设寄存器。比如你忘了开启GPIOA的时钟却直接往GPIOA的ODR寄存器写数据。在F1系列上这会导致总线错误最终进HardFault。函数指针为空、未初始化就被调用。排查HardFault的关键是定位触发点。在Keil里当程序停在HardFault_Handler时打开Call Stack窗口看调用栈通常能看到触发异常前的最后几层调用。再用Peripherals窗口查看SCB-CFSR寄存器的Fault状态位能分辨是总线错误还是用法错误还是断言失败。最实用的办法是记录下R14(LR)和R15(PC)的值在反汇编窗口输入地址看当前执行到了哪条指令再对应到源码里的具体行。我调过最长的一个HardFault案例是串口空闲中断加DMA接收不定长数据时接收长度判断逻辑出了错DMA把数据写到了数组末尾之外。症状是程序运行一段时间后随机死机一度怀疑是硬件问题最后在Fault Reports里发现是总线错误再查DMA缓冲区地址才锁定了越界。从那以后凡是用DMA的地方我都强制在缓冲数组尾部多留几个字节的冗余空间不为别的就是为了排查时少掉几根头发。3.4 启动文件与宏定义选错的连锁反应之前讲了建工程时的启动文件选择但启动文件的问题不止是编译期影响。有几次程序编译下载都成功了上电就是跑不起来最后发现是启动文件里的堆栈尺寸设置太小。启动文件顶部有一段Stack和Heap的配置比如Stack_Size EQU 0x400如果你在代码里大量使用了局部数组、嵌入式实时操作系统、或者malloc动态内存默认的栈空间可能不够。栈溢出不一定会立刻死机而是在某个深层调用或者中断嵌套时才崩。这种问题极其难查因为规律不明显可能运行几十秒才出一次问题。我的习惯是调试阶段就把栈改成0x800或者更大省得被栈溢出干扰判断。另外还有一个容易被忽略的坑启动文件里定义了中断向量表如果你在工程里另外写了一个包含相同中断向量的文件可能编译报重复定义——这是好情况。坏情况是链接器选择了你后加的那个向量表导致定时器或其他外设的中断根本进不去程序看起来一切正常但功能不响应。所以新建工程时不要贪多启动文件、系统时钟文件、标准库外设文件凡是从网上拷来的大杂烩工程模板都值得你花时间拆开看一遍确认每一部分的作用再拼装。用别人整合好的一键工程出了问题你真的不知道怎么排查因为你不清楚里面有哪些隐藏配置。4. 串口通信与外设调试从乱码到数据丢失4.1 串口乱码的排查链路串口通信是STM32项目里最常用的外设基本每个项目都用但乱码问题也最多。我总结了一条排查链路你按顺序走一遍绝大多数乱码都能解决第一步排除接线问题。TX接RX、RX接TX这两个脚位接错是最常见的。如果你用的是USB转串口模块还要确认模块电平是否兼容——3.3V和5V混接可能导致通信不稳定甚至烧坏引脚。第二步确认上位机波特率和代码一致。看起来是废话但真的很多人把代码里的9600和工具里的115200搞混或者代码里改了波特率工具没改。最简单的方式是用串口助手自发自收也就是把USB转串口的TX和RX短接在电脑上发什么收什么。自发自收正常说明USB转串口没问题问题在STM32端。第三步用示波器量STM32的TX引脚。波特率是否正确可以直接测量一个字节的位宽来判断。比如9600波特率一位的时间是104微秒左右示波器上能看到脉冲宽度明显不对就是波特率的计算结果错了。第四步回到时钟树问题。之前说过外部晶振没起振或频率不一致会导致PCLK计算错误波特率必然不对。F1系列的USART波特率发生器是从PCLK分频来的系统时钟是72M还是8M同一个波特率寄存器数值对应的实际波特率差了9倍。如果你的串口在低速率下看着正常高速率全乱大概率还是时钟问题不是串口代码本身的问题。通信波特率不对还有一个非常容易忽略的情形外部晶振虚焊导致HSE启动失败但芯片没死退回HSI后程序继续跑串口打印出来的内容偶尔正常偶尔乱码会误导你以为是串口代码有BUG。这时候不要反复改串口参数回头检查外部晶振。4.2 HAL库串口中断发送与接收的坑HAL库是很多人从标准库转过来的第一站但它的串口接口和标准库用法差异很大踩坑点也完全不同。先说HAL_UART_Transmit_IT。这个函数是异步发送调用后立即返回实际数据通过中断逐字节发出去。问题在于如果你上一次发送还没结束就再次调用函数会返回HAL_BUSY数据直接丢掉。很多人写一个while循环里调用发送结果数据丢得一塌糊涂因为发送速度跟不上调用速度。解决办法是维护一个发送完成标志位在HAL_UART_TxCpltCallback回调里置0调用前先判断标志位。或者直接用阻塞式的HAL_UART_Transmit省心但会占用CPU。HAL_UART_Receive_IT也有类似问题它是一次性接收——接收完指定字节数后触发回调之后就不会再接收了需要在回调里重新调用一次才能继续收。很多人不知道这个机制以为调用一次就能永远收结果发现只收到一次数据就再也不进了。不定长串口数据接收我推荐的做法是空闲中断加DMA。配置串口的DMA接收为循环模式数据到了自动进缓冲区然后在串口空闲中断里读取DMA剩余计数计算出本次收到的数据长度。这个方法能处理不定长数据包不丢字节CPU占用也低。但有几个坑要避开串口空闲中断和DMA中断的优先级要合理安排而且要在中断里快速读出长度再去处理数据不能让中断处理太久。DMA循环模式的数据是不断覆盖写的你必须在空闲中断里拷贝出当前这批数据到应用缓冲区否则下一次数据来了会覆盖。回调函数里访问缓冲区时务必注意边界别把固定数组写爆了。4.3 USB虚拟串口设备管理器里那个感叹号STM32F103的USB虚拟串口项目很多人按照例程做完插上USB线电脑却提示未知设备或者设备管理器里一个黄色感叹号。这类问题有固定的排查方向第一个方向是时钟。F103的USB外设需要48MHz时钟如果系统时钟不是由8MHz晶振经过PLL倍频到72MHz再二分频得到48MHzUSB的枚举过程就会失败。如果你用的是内部HSI或者外部晶振不是8MHzUSB极大概率无法工作。第二个方向是硬件上拉。USB设备通过DP引脚的1.5k上拉电阻告知主机这是一个全速设备。很多STM32开发板把DP上拉做到芯片内部由固件控制PA12实现也有的板子需要外接上拉电阻。如果你用的板子需要外接但没焊这个电阻自然枚举不了。看原理图时专门找一下DP引脚附近有没有上拉电阻。第三个方向是堆栈和中断配置。USB库占用的RAM不少如果你的启动文件堆栈设得太小枚举过程可能跑飞。USB相关中断优先级也要正确配置不能把其他中断长时间阻塞USB中断。还有一个常被忽略的细节USB虚拟串口发送数据不能在USB的中断回调里直接调用发送接口否则可能造成死锁或者丢数据。正确做法是把要发的数据存到循环缓冲区在应用层空闲时批量发送。4.4 定时器捕获测频与编码器模式的小细节定时器和编码器是电机控制、测速项目里的常规外设但很多人第一次用输入捕获测频率时得到的第一个值总是错的。原因是输入捕获的第一次捕获往往发生在定时器计数器的任意位置两次捕获之间的差值并不能正确反映一个完整周期。正确的做法是第一次捕获值先丢弃从第二次捕获开始计算周期或者连续捕获多次求平均。另外如果信号频率很低捕获周期大于定时器的溢出周期你必须处理更新中断把溢出次数也一起计进去否则测出来的频率会突然跳变。编码器模式也一样配置成编码器接口后定时器计数值随编码器旋转方向自动增减。常见的坑是有人把编码器A相接到定时器通道1、B相接通道2但代码里配置成了只计数单通道结果方向无法判断。还有Z相清零的问题如果你需要绝对位置参考得把Z相信号接到另一个引脚并在捕获到Z相时清零计数否则位置数据会一直累加漂移。超声波测距模块也是热门毕设题材。用定时器输入捕获来测量回波脉宽比用GPIO轮询要靠谱得多。GPIO轮询在近距离时容易因为代码执行时延误差导致测量结果不准而输入捕获是硬件记录边沿时刻精度明显更高。不过要注意把GPIO配置成正确的输入模式数字输入和模拟输入的引脚行为差异很大配置错了回波信号根本读不到。5. 疑难杂症的排查思路把经验变成方法论5.1 三板斧先复位、再点灯、后打量调试经验积累到一定程度你会发现大多数问题都可以归纳为三板斧排查法。这个方法土但靠谱。第一板斧是硬件确认。用万用表量电压——3.3V是否到位、GND是否连通、复位按键是否为高电平。很多宣称程序有问题的情况最后发现是板子供电接触不良或者杜邦线松了一根。不要小看这类低级问题他们占的比例一点都不低。第二板斧是软件最小化。把主循环里所有业务代码注释掉只留一个LED翻转或者GPIO翻转看系统还能不能跑。如果带动业务代码就死机去掉业务代码就正常说明问题出在业务逻辑里跟你怀疑的编译器、链接器、芯片都没关系。如果连最小系统都不跑那才轮得到怀疑初始化配置或者硬件。第三板斧是调试器读寄存器。进调试器看Peripherals里的GPIO ODR、RCC CR、USART SR这些寄存器能直接判断外设是否初始化成功。这个方法比printf高效得多比如一个GPIO输出没电平先看GPIO时钟有没有开再看引脚配置对不对两步就能定位。我印象很深的一次排查是超声波测距模块始终返回0。主循环点LED没问题按理说初始化也没问题但就是测不到。最后用调试器看GPIO配置寄存器发现把ECHO引脚配成了模拟输入模拟输入状态下数字电平读取永远返回0。改成浮空输入后立刻恢复正常。如果当时不打开寄存器窗口光靠猜代码可能半天都定位不到。5.2 用好调试器别全靠printf很多初学者调试单片机时习惯用串口printf这也是江科大这类视频教程带出来的惯性。printf没有原罪但它有两个明显的副作用一是在中断里调用printf会严重干扰时序。中断服务程序里花几百微秒做字符串格式化输出对其他中断响应的影响是灾难性的尤其是有定时器捕获、USB通信这类对时序敏感的功能时。二是printf本身也可能改变程序行为。有个经典现象加上printf就正常去掉printf就死机这种墨菲定律式问题通常不是printf解决了什么逻辑错误而是printf消耗的时间恰好掩盖了某个时序竞争。遇到这种情况不要觉得是不加上就不行的魔法应该警惕背后真实的竞态条件。我建议的方式是用调试器打断点、看变量值、看寄存器这些是第一手段。printf作为第二手段尽量只在非中断上下文调用而且打上明显的调试开关产品发布前统一关掉。真要在HardFault里打印诊断信息直接把PC和LR寄存器值格式化后发出来比盲猜强太多。5.3 我的三个真理时刻与给新手的成长路线做嵌入式这些年我印象最深的三个时刻都跟解决问题的方式有关第一次是当我终于静下心来读RM0008参考手册而不是到处搜代码片段很多之前搞不动的疑惑突然就通了。网上代码质量参差不齐二手信息满天飞真正一手的正确答案永远在芯片手册里。第二次是发现自己从开发板上拿来的出厂例程有问题。例程为了展示方便很多配置并不严谨有的是时钟配置过度依赖默认值有的是外设初始化顺序不对。从那时起我每拿到一个开发板第一件事是自己重新写一份最小系统初始化而不是直接依赖例程。第三次是开始用示波器和逻辑分析仪辅助调试。以前遇到奇怪的问题总是反复看代码其实很多答案在信号层面早就摆在眼前了——晶振起振没有、串口波形对不对、PWM频率是不是设置的值示波器扫一眼全知道。如果你现在刚入坑STM32我建议的成长路线很朴素准备一块F103C8T6最小系统板加ST-Link有条件再配一个几十块钱的逻辑分析仪。从闪灯开始依次走通串口、定时器、中断、DMA、USB这几个关键外设的调试流程。这个过程中你会把下载器、工程配置、时钟系统、调试技巧全都踩一遍等这些基本功扎实了再去做环境监测、智能台灯、双轮小车这些毕业设计或项目就是水到渠成的事。其实踩坑从来都不是坏事真正浪费时间的是重复踩同一个坑还不总结。希望这篇总结能帮你少走几段弯路把省下来的时间留给真正值得研究的功能和代码。