资讯动态

STM32F103上电没反应?从假芯片鉴别到FreeRTOS调试的完整排查指南

发布时间:2026/9/5 1:28:15 来源:尧图企业网站定制
STM32F103 FreeRTOS芯片上电后竟然一点反应都没有调试器连不上LED不闪代码烧进去石沉大海。这种问题在嵌入式群里几乎天天有人问而我的第一反应从来不是查代码而是先怀疑一件事你手里这块芯片到底是不是真的。先交代一下背景。我做嵌入式开发这些年STM32F103C8T6这块芯片我前前后后买过不下几百片有从嘉立创、得捷这类正规渠道拿的也有从淘宝某些“拆机原装”店铺贪便宜买的散新片。踩的坑多了之后基本上拿到手用指甲刮一下丝印、上电摸一下温度就能判断个七八成。这篇文章我就把“芯片没反应”这件事从假芯片识别、硬件最小系统、焊接细节到FreeRTOS软件配置完整地梳理一遍。尤其是如果你正准备在STM32F103上跑FreeRTOS却发现连点灯都点不亮那我强烈建议你先看完第二小节——很多时候不是你的代码不行是芯片本身就“不行”。1. 芯片没反应先从“假芯片”说起很多人遇到芯片不工作第一反应是怀疑自己不熟悉FreeRTOS怀疑移植步骤出了问题于是反复检查代码甚至把官方例程整个搬过来结果还是不亮。这时候我一般会问一句你的芯片是从哪买的很多人的回答是淘宝或者某些价格低到离谱的电子元件店。1.1 STM32F103“假芯片”到底是怎么来的市面上所谓“假芯片”其实分好几种情况我实际遇到过的有以下几类。第一类是翻新片。这种芯片是从废旧PCB板上拆下来的机器吹下来之后重新处理引脚、清洗痕迹然后打上新的丝印当成新片卖。这种芯片最大的问题有两个一是引脚会有不同程度的氧化或损伤焊接后容易虚焊二是芯片内部Flash的擦写寿命已经被消耗过一部分你烧录几次之后可能就写不进去了或者出现烧录成功但运行随机死机的情况。第二类是Remark片也就是被打磨后重新印字的芯片。比如用STM32F103CBT6128KB Flash打磨后印成C8T664KB Flash或者更恶劣的拿GD32F103C8T6、APM32F103C8T6这类国产替代芯片打磨后冒充ST原厂。这种芯片从外观上很难看出来但有一个致命的区别GD32的USB、ADC、Flash操作时序和原厂是有差别的你按ST的库函数去驱动很可能出现外设行为异常但你查代码查不出任何问题。第三类是检验不合格的散片。工厂流出的测试不通过的芯片或者封装环节有瑕疵的芯片通过某些渠道流入市场。这类芯片运气好能正常工作运气不好就表现出各种诡异的问题某个引脚电平不对、内部Flash某个地址读出来是乱的、芯片发热严重、上电后电流异常偏大等等。1.2 真假芯片的几个实用鉴别方法我说几个我自己实测过、不需要专业设备的方法适合没有X-ray、没有开盖工具的普通开发者。首先是丝印观察法。拿一个放大镜或者手机微距镜头对准芯片表面的丝印ST原厂的丝印激光打标边缘清晰锐利字迹有立体感笔画内部是均匀的浅灰色。翻新片和打磨片重新印字后字迹通常会比原厂略模糊笔画边缘有轻微毛刺丝印位置偶尔还能看到原来被磨掉的痕迹尤其是芯片表面的底色不够均匀一片亮一片暗。其次是引脚光泽观察法。新的原厂芯片引脚是亮银色的反光均匀表面没有划痕。翻新片的引脚经过拆焊、清洗、重新镀锡颜色会偏暗或者偏黄引脚根部偶尔能看到助焊剂的残留痕迹。然后是上电温度法。STM32F103正常工作时不跑大负载程序的情况下芯片表面温度接近室温你用手指贴着基本感觉不到明显的热。如果你上电之后什么都不烧录芯片表面就开始发热那大概率是内部已经损坏或者存在短路。这个方法非常实用我每次拿到一批新芯片都会先焊一片空板子只焊芯片不上电外设通电后用手摸一下超过两秒钟觉得烫手直接退货。最后是唯一ID读取法。STM32F103的Flash容量寄存器位于0x1FFFF7E0地址唯一ID寄存器位于0x1FFFF7E8。你写一段小代码读出来打印到串口上原厂ST芯片的ID会有一个固定的厂商标识段其实具体值不太好直接肉眼判断真假但ID和Flash容量、封装型号对应关系可以用来交叉验证。比如C8T6读出来Flash容量应该是64KB如果你读出来是128KB说明这是CBT6打标成C8T6的Remark片。我举个例子下面是一段读芯片ID和Flash容量的代码用标准库写的逻辑很简单。#include stm32f10x.h typedef struct { uint16_t flash_size_kb; uint32_t uid[3]; } ChipInfo; void get_chip_info(ChipInfo *info) { // FLASH容量寄存器地址0x1FFFF7E0单位是KB info-flash_size_kb *(volatile uint16_t *)0x1FFFF7E0; // 唯一ID寄存器96位从0x1FFFF7E8开始 info-uid[0] *(volatile uint32_t *)0x1FFFF7E8; info-uid[1] *(volatile uint32_t *)0x1FFFF7EC; info-uid[2] *(volatile uint32_t *)0x1FFFF7F0; } int main(void) { ChipInfo chip; get_chip_info(chip); // 通过串口打印或者调试器查看 while (1) { } }如果你买的是C8T6打印出来的flash_size_kb却是128那你就要警惕了。另外GD32F103的UID读取方式和ST不完全一致如果你按ST的地址去读GD32读出来的数据往往比较奇怪这个也可以作为辅助判断手段。1.3 正规渠道和风险渠道的差价逻辑很多人问我为什么淘宝上STM32F103C8T6只要两三块钱嘉立创上要七八块原因是多方面的。正规渠道的芯片是从ST官方代理商拿货经过层层质检、包装、运输带完整的批次号和溯源记录这个成本摆在那里。淘宝上的低价芯片要么是翻新片要么是国产替代片要么是渠道不明的散片成本天然低很多。我不是说淘宝上买不到好东西实际上我早期也经常在淘宝买芯片做原型验证但我的做法是原型阶段用便宜的散片没问题但在产品化、量产、或者调试疑难杂症的时候务必换正规渠道的芯片交叉验证。而且一定要养成一个习惯——拿到芯片先做一次最基本的点灯测试再跑RTOS不要一上来就烧复杂程序否则出了问题你根本不知道是软件问题还是硬件问题。2. 排除“假芯片”之后再看最小系统电路和焊接即使芯片是真的芯片没反应很多时候也是因为最小系统电路没设计对或者焊接有问题。STM32F103C8T6最小系统虽然简单但我见过太多人在这里翻车。2.1 一个标准的最小系统长什么样以STM32F103C8T6为例一个能正常跑起来的最小系统需要包含以下部分模块具体要求常见错误供电VDD接3.3VVSSA、VDDA接3.3VVSS接地只接了VDD没接VDDA去耦电容每个VDD引脚旁放一个100nF陶瓷电容VDDA旁再放一个1uF省掉电容或电容离引脚太远BOOT0接10k下拉电阻到地悬空导致上电模式不稳定NRST接10k上拉电阻到3.3V并联100nF电容到地悬空或直接接地晶振8MHz晶振 两个20pF负载电容用错负载电容、晶振走线过长下载接口SWD四线SWDIO、SWCLK、GND、3.3V只接了SWDIO和SWCLK没共地很多人觉得去耦电容是可省可不省的实际上这是一个很大的误区。STM32F103内部逻辑翻转瞬间会产生高频电流需求如果电源引脚旁边没有低阻抗的100nF电容电压会瞬间跌落导致芯片内部逻辑状态错乱表现出来就是“代码烧进去了但行为诡异”或者干脆“芯片不启动”。我的经验是100nF电容尽量靠近芯片电源引脚距离不要超过3mm。然后是BOOT0引脚的坑。STM32F103的BOOT0决定了上电后从哪启动。BOOT0接高电平是进入系统存储器Bootloader接低电平才是从主Flash启动。如果你把BOOT0悬空它内部虽然有下拉电阻但这个下拉电阻的驱动力有限在电磁干扰稍大的环境下可能被拉高芯片就会进入Bootloader而不是用户程序表现就是“程序烧进去了但没有跑”。我处理过一个客户的问题就是BOOT0悬空引起的重新画板加10k下拉电阻后问题消失。2.2 焊接层面容易忽略的细节我遇到过一个典型情况朋友拿来一块板子说芯片怎么都不工作NRST引脚一直为低电平拔掉复位电容可以识别芯片按复位键之后SWD能连上但程序跑不起来。后来我用放大镜检查发现NRST引脚旁边有一小块锡渣搭桥导致NRST被持续拉低芯片一直处于复位状态。这种问题排查起来非常费事因为原理图、代码看着都是对的。所以焊接LQFP48这类封装的芯片我建议焊接完之后做三件事第一用放大镜或微距镜头逐个引脚检查是否有多锡、少锡、引脚间搭桥。第二用万用表通断档检查相邻引脚之间是否短路重点检查VDD和GND之间是否短路这个最简单也最致命。第三上电后用万用表测芯片各电源引脚的电压确认VDD、VDDA都是3.3VVSS、VSSA都是0VANRST引脚应该被上拉到3.3V。焊接优先用热风枪配合钢网温度控制在300到320摄氏度因为STM32F103是LQFP封装引脚间距只有0.5mm手工烙铁焊不小心就容易连锡。如果只能用烙铁建议刀头烙铁配合助焊剂拖焊拖完之后一定要用洗板水和无尘布把助焊剂残留清理干净否则残留的助焊剂在潮湿环境下会形成漏电通路让某些引脚电平被拉低。2.3 用最小测试工程区分硬件还是软件问题当芯片没反应的时候我强烈建议先不要跑FreeRTOS也不要跑复杂的外设初始化先写一个最小的裸机点灯程序。具体来说就是系统主频用内部HSI 8MHz不初始化外部晶振直接操作GPIO寄存器翻转LED。如果这个程序烧录后LED能亮起来说明芯片、最小系统电路、下载链路都正常。这时候再逐步加上外部晶振初始化、串口初始化最后再去跑FreeRTOS。用8MHz内部时钟先测试是很有讲究的。外部晶振有没有起振、起振频率准确不准确这个因素会直接影响系统能不能正常工作。如果你一上来就初始化外部8MHz晶振但晶振没起振代码就会卡死在等待HSE Ready的超时循环里。系统的表现就是程序烧进去了但没有任何反应。很多人这时候会以为是FreeRTOS移植问题其实根本不是。反过来如果连内部时钟点灯都点不亮那问题大概率在芯片、电源、焊接、下载配置这几个层面。这时候你需要停下来检查硬件而不是继续改代码。3. 确认硬件正常后FreeRTOS“假死”的软件排查清单硬件没问题、芯片也是真的但移植FreeRTOS之后系统就是不工作或者创建的任务不执行、调度器跑不起来。这种情况我遇到的也不少问题往往出现在几个非常隐蔽的地方。3.1 SysTick被FreeRTOS接管了你怎么还用HAL_Delay这是新手最容易忽略的一个坑。FreeRTOS的调度器启动后会占用SysTick作为系统时基即vPortSetupTimerInterrupt会重写SysTick的中断处理函数。如果你在启动调度器之后还调用HAL_Delay标准库是delay_ms这类依赖SysTick的延时函数会产生两个结果要么延时时间完全不准要么直接卡死。我推荐的做法是在启动FreeRTOS调度器之前初始化Led、串口等简单外设启动后不要调用任何阻塞式延时。任务内部需要延时的时候用vTaskDelay或vTaskDelayUntil代替裸机的delay函数。如果需要毫秒级精确延时可以再用一个硬件定时器做us级延时不要和SysTick混用。我见过一个案例用户在某个任务里用了裸机延时函数延时10毫秒然后控制LED翻转结果调度器启动后LED闪都不闪。查了很久才发现是HAL_Delay和FreeRTOS里的SysTick中断优先级冲突导致中断进不去系统卡死。3.2 中断优先级分组这个坑能让你系统直接崩溃FreeRTOS移植到Cortex-M3内核时对中断优先级分组有一个硬性要求必须使用4位全部用于抢占优先级即NVIC_PriorityGroup_4。这是因为FreeRTOS的临界区保护实现依赖BASEPRI寄存器它只能屏蔽优先级高于某一阈值的中断如果系统中存在子优先级BASEPRI无法正确屏蔽子优先级中断导致临界区被意外打断。在STM32F103上标准库默认的优先级分组是NVIC_PriorityGroup_22位抢占优先级2位子优先级如果你不手动改成Group_4直接跑FreeRTOS系统在运行过程中很可能会在某个时刻因为中断嵌套导致临界区破坏现象就是系统随机死机或者任务调度异常。正确做法是在main函数开头就设置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这句话必须在创建任务和启动调度器之前调用。养成这个习惯后FreeRTOS导致的随机死机问题至少减少一半。3.3 堆栈溢出排查建议打开这个钩子FreeRTOS任务栈溢出是另一个高频问题。在STM32F103上默认每个任务的栈大小是128或256个Word注意是Word不是字节如果你在任务里定义了一个大的局部数组比如uint8_t buf[512]栈瞬间就爆了。栈溢出之后系统的行为是不可预测的可能任务不运行可能系统跑着跑着卡死也可能数据莫名其妙被改写。排查栈溢出最直接的方法是开启FreeRTOS的栈溢出检测在FreeRTOSConfig.h里配置两个宏#define configCHECK_FOR_STACK_OVERFLOW 2然后在main.c里实现钩子函数void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName) { // 任务名存储在pcTaskName中 // 可以在这里置一个标志位点一个错误LED // 或者直接进断言 configASSERT(0); }configCHECK_FOR_STACK_OVERFLOW设置为2表示使用第二种检测方式这种方式会在任务切换时检测栈指针是否越界比方式1更可靠一些但会消耗额外的性能。在开发调试阶段建议开产品阶段可以关掉。另外还有一个经验每个任务初始的栈大小先给宽裕的数值比如512 Word跑通功能之后再逐步调小。不要一上来就精打细算给128 Word除非你对任务的栈使用量非常有把握。3.4 启动文件选错代码烧进去也白搭这个坑非常隐蔽尤其是用标准库的时候。STM32F103C8T6这颗芯片属于**高密度High-density**产品线Flash容量是64KB。对应的启动文件应该是startup_stm32f10x_hd.s。如果你用的是startup_stm32f10x_md.s中等密度适用于STM32F103C6、C8等中容量芯片C8T6可以正常工作。但如果你用的是startup_stm32f10x_cl.s互联型或者其他不对应的启动文件系统初始化阶段中断向量表就会错乱芯片根本跑不到main函数。说实话我自己也在这个坑里栽过。有一次从旧项目复制代码启动文件用的是startup_stm32f10x_xl.s超大容量烧到C8T6上之后怎么都不工作后来换成startup_stm32f10x_hd.s立马就好了。C8T6虽然是64KB Flash但它属于高密度系列不是说Flash小就一定要用md启动文件这个逻辑需要用参考手册里的“产品线密度分类”去对应。4. 从串口打印 调试器角度定位“没反应”的根源当芯片“没反应”的时候你手上最有力的两个工具就是串口和调试器。很多人只会用调试器烧录却不懂得怎么利用调试器读取运行状态下面这几个技巧很实用。4.1 先让芯片“开口说话”——串口打印定位法很多嵌入式开发者有个不好的习惯程序写完就直接烧录不看输出全靠肉眼观察LED亮灭。对于排查问题来说这种方式效率太低。我建议在出现问题的时候第一时间在代码里加串口打印让芯片“说话”。在STM32F103上用串口1打印最简单的初始化代码标准库写法如下#include stm32f10x.h #include stdio.h void uart1_init(uint32_t baudrate) { GPIO_InitTypeDef gpio; USART_InitTypeDef usart; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); gpio.GPIO_Pin GPIO_Pin_9; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_10; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); usart.USART_BaudRate baudrate; usart.USART_WordLength USART_WordLength_8b; usart.USART_StopBits USART_StopBits_1; usart.USART_Parity USART_Parity_No; usart.USART_HardwareFlowControl USART_HardwareFlowControl_None; usart.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART1, usart); USART_Cmd(USART1, ENABLE); } int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; } int main(void) { uart1_init(115200); // 点亮启动前的第一个LED表示代码走到了这里 printf(System boot...\r\n); while (1) { } }使用串口打印配合点灯可以在系统启动的每个关键节点打印一条日志比如进入main之前打印一行。外设初始化完成后打印一行。创建FreeRTOS任务前打印一行。启动调度器前打印一行。这样一旦系统出现问题你能非常精确地知道是死在哪一步。如果没有逻辑分析仪和示波器串口打印是最廉价有效的调试手段。4.2 用调试器读寄存器判断程序死在哪个位置如果你的板子上有SWD接口使用STM32CubeProgrammer或者Keil内置的调试器连接失败时可以读取芯片的IDCODE确认芯片是否被识别。如果IDCODE能读到0x410Cortex-M3核心对应的IDCODE说明芯片和调试链路基本正常。程序烧录完成后你在调试模式下启动程序运行到某一步卡住了这时候可以在调试器里暂停查看当前的PC指针指向哪个地址再用反汇编或者map文件对照定位程序死在了哪个函数里。如果PC停在某个硬件延时循环里基本就是外设在等待某个标志位比如等待HSE就绪、等待Flash操作完成、等待某个外设初始化完成这些通常就是硬件配置没生效的表现。4.3 在FreeRTOS里利用uxTaskGetStackHighWaterMark检查栈余量前面提到栈溢出检测这里再补充一个开发阶段很好用的函数UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);这个函数返回的是目标任务从创建以来栈空间剩余的最小值以Word为单位。你在任务循环里周期调用一次把结果打印出来就能知道当前任务实际用掉了多少栈空间。如果一个任务的HighWaterMark已经接近0说明这个任务的栈随时可能爆掉需要调大栈尺寸。我在实际项目里有一个习惯每个任务的局部变量尽量控制在几百字节以内动态分配用FreeRTOS的pvPortMalloc代替C库的malloc因为pvPortMalloc是线程安全的并且能由FreeRTOS的堆管理策略统一管理。C库的malloc在嵌入式系统中不仅可能碎片化而且在多任务环境下需要额外加锁得不偿失。5. 从“点灯”到“跑FreeRTOS”几个常见问题的速查表最后整理一个我在实际项目里反复用到的排查速查表遇到问题可以先按图索骥。5.1 问题现象与解决对照表现象可能原因解决方向上电后芯片完全没反应调试器无法识别供电异常、焊接不良、芯片损坏、BOOT0悬空检查电源电压、用万用表测引脚短路、更换正规渠道芯片调试器能识别但程序烧不进去Flash保护、芯片Flash寿命耗尽、供电不足检查RDP级别、换一块芯片验证、检查ST-Link供电能力程序烧进去了但LED不亮启动文件选错、外部晶振没起振、时钟配置卡死换成内部时钟点灯、检查启动文件是否正确、查晶振电路裸机点灯正常跑FreeRTOS就不行SysTick被抢占、任务栈溢出、优先级分组不对检查HAL_Delay使用、开栈溢出钩子、设置Group_4FreeRTOS运行一段时间后随机死机中断优先级配置错误、临界区受干扰、堆栈溢出检查NVIC优先级分组、检查中断回调是否调用了非线程安全函数任务创建成功但从未执行定时器任务或空闲任务栈太短、调度器启动失败调大定时器任务栈、检查vTaskStartScheduler返回值5.2 一个我强烈建议养成的好习惯产品验证两套芯片并行测试最后分享一个我个人的习惯。凡是产品化项目我在做硬件验证的时候手边始终会同时备着两个渠道的芯片。一片是嘉立创或得捷的正品芯片一片是淘宝上买来的低价散片。先用散片做原理验证因为坏了不心疼再用正规渠道芯片做整机验证因为这个结果才是可信的。两边结果一致说明代码和硬件设计没问题两边结果不一致那就要高度怀疑芯片本身的可靠性。这个习惯帮我排掉过不少“看似无解”的bug。印象最深的一次是某项目在客户现场出现随机重启代码逻辑排查了好几轮都没发现问题后来把现场的设备寄回来拆开一看芯片丝印的字迹比原厂略浅读取Flash容量发现是128KB而不是64KB——是Remark片。换回正规渠道芯片后问题再没出现过。做嵌入式开发芯片是所有代码的物理载体。芯片如果不可靠你写再好的代码都是白搭。所以我一直觉得当系统“没反应”的时候与其反复烧录调试不如先把芯片的来源确认了把最小系统验证了然后再去追软件的问题。这个顺序反了很容易被人带进牛角尖里出不来。

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

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

免费获取报价