ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103+FreeRTOS程序跑飞?排查发现芯片是假的

STM32F103+FreeRTOS程序跑飞?排查发现芯片是假的 刷完代码上电灯不亮串口没输出Debug模式下程序直接卡在HardFault_Handler里。这是我做STM32F103 FreeRTOS项目时遇到的状况排查了整整两天最后终于意识到问题不在代码而在芯片本身——我可能买到“假芯片”了。这个标题估计不少人都觉得眼熟。尤其是刚从标准库裸机开发切换FreeRTOS的开发者遇到芯片没反应时第一反应都是怀疑任务堆栈配小了、时钟初始化有问题、FreeRTOSConfig.h配置错了很少有人会想到芯片根子上就有问题。这篇博文我想完整复盘一遍当时的排查思路从软件配置一路查到硬件电路再反推到芯片真伪顺便把FreeRTOS移植过程中几个非常隐蔽的坑也一并整理出来。如果你也在用STM32F103 FreeRTOS做项目或者正准备把手头工程从裸机迁移到RTOS这篇文章应该能帮你少走不少弯路。1. 现象复盘代码看起来啥都没问题芯片就是不理你1.1 项目背景和故障现场先说项目背景。当时我在做一个小型数据采集设备主控选的是STM32F103RCT6开发环境是Keil MDK固件库用的是标准库Std v3.5下位机通信用RS232基于FreeModbus v1.6移植了Modbus RTU从站协议同时跑FreeRTOS管理三个任务一个负责LED心跳闪烁一个负责串口打印调试信息一个负责周期采集ADC数据。这种组合在F103上算是非常经典的配置了资源完全够用。故障现象也很典型第一次烧录程序后偶尔能跑起来LED正常闪但只要按一下复位键或者重新上电十有八九就死在那里。串口打印的初始化信息只出过一次后面再也没有输出过。用Keil进入调试模式单步执行到创建任务的地方下一秒就跳进了HardFault_Handler。有时候更夸张连下载器都连接费力Keil报错说“Cannot access target”得多试几次才能连上。这种“时好时坏”的故障是最折磨人的。我先怀疑是不是自己FreeRTOS移植的锅毕竟裸机程序跑得好好的加上RTOS就出问题直觉上就是操作系统配置有问题。于是我开始逐个排查FreeRTOS相关的配置项这也是绝大多数人第一反应。1.2 第一反应FreeRTOS配置到底有没有问题FreeRTOS移植到STM32F103最常见的问题确实集中在FreeRTOSConfig.h。我当时重点检查了几个关键参数#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 10 * 1024 ) ) #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCHECK_FOR_STACK_OVERFLOW 2 #define configMAX_PRIORITIES 5 #define configKERNEL_INTERRUPT_PRIORITY 15 #define configMAX_SYSCALL_INTERRUPT_PRIORITY 5当时我任务栈的配置是这样的LED任务给了128字512字节串口打印任务给了256字1KBADC采集任务同样256字。每个任务还有对应的TaskHandle创建时也都检查了返回值没有出现pdPASS之外的结果。按理说10KB的堆内存跑三个小任务绰绰有余。但问题依旧。后来我甚至开启了FreeRTOS的堆栈溢出检测功能在FreeRTOSConfig.h里把configCHECK_FOR_STACK_OVERFLOW设为2然后补上钩子函数void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName ) { ( void ) xTask; ( void ) pcTaskName; for( ;; ); }钩子函数确实触发了但触发的位置不固定有时候是LED任务有时候是ADC任务。这就很奇怪了任务栈明明都够用怎么会说溢出就溢出后来我才想明白并不是任务栈真正的使用量超标了而是程序跑到某个故障点后栈指针已经完全错乱检测机制自然认为栈溢出了。也就是说堆栈溢出检测到的是“结果”不是“原因”。1.3 再查启动文件和链接脚本排除了任务栈和堆配置之后我把怀疑重点转向启动文件和链接脚本。STM32F103系列有一个非常经典的坑启动文件必须和芯片型号匹配。我用的RCT6属于高密度Flash型号应该选startup_stm32f10x_hd.s。如果选成md.s或者ld.s虽然编译能通过但程序启动后中断向量表位置可能不对同样会跑飞。我核对了三遍启动文件没有选错。链接脚本倒是确实出过一个低级问题。我用的是标准库startup文件里默认把栈顶设置在了RAM末尾也就是0x2000C000RCT6有48KB SRAM。但后来我核对芯片手册发现我的工程里keil的Target页SRAM起始地址填错了写成了0x20000000大小却填了64KB超出了芯片实际的SRAM大小。这会导致编译器认为RAM比实际大某些大数组和任务栈分配的位置可能在芯片不存在的地址上一运行就踩空。这个问题修正之后依然跑不起来但方向已经逐渐从软件走向硬件了。2. 从软件到硬件我的排查顺序可能和你想的不一样2.1 先查最小系统电路我把FreeRTOS的配置翻了个底朝天都没有结论决定先回到最基本的硬件最小系统去验证。STM32F103的最小系统就四样东西电源、复位电路、晶振、BOOT引脚。任何一个有问题芯片都不可能正常工作。先量电源3.3V输出稳定纹波在20mV以内看起来正常。再查复位脚NRST上拉电阻10k到3.3V按键复位电路也正常。用示波器测8MHz晶振奇怪的事情出现了——晶振引脚上几乎看不到波形。我当时第一反应是晶振没起振换了个新的晶振负载电容也重新按手册配了一遍依然是同样的现象。后来我才反应过来F103的晶振电路只有在程序里使能了外部高速振荡器HSE之后晶振才会稳定起振。单片机刚上电的时候晶振引脚上测不到正确波形其实是正常的。所以这个现象并不能说明硬件有问题只能说在跑FreeRTOS的程序里HSE被初始化了但初始化之后是否稳定还需要进一步验证。再查BOOT0和BOOT1引脚BOOT0接10k下拉电阻到地BOOT1悬空或下拉状态没问题。到这里最小系统电路看起来都是健康的但芯片就是跑不起来。2.2 软件侧的一个关键动作PA8引时钟出来看看排查遇到瓶颈后我想到一个非常古老的验证手段用PA8的MCO功能把系统时钟引出来用示波器量频率确认芯片内部时钟到底跑在多少。MCO是STM32的时钟输出引脚可以把SYSCLK、PLLCLK、HSE、HSI等时钟信号输出到PA8上。标准库的配置代码很简单void MCO_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); RCC_MCOConfig(RCC_MCO_PLLCLK_Div2); }这里我选择的是把PLLCLK二分频后输出。如果系统时钟正常工作在72MHzPLL输出也是72MHz二分频后PA8应该量到36MHz的方波。但实际测出来的结果非常离谱PA8输出频率只有7.2MHz左右偏差极大。如果PLL锁定不稳定或者分频系数计算有偏差根本不可能精确输出这个频率。这是一个非常关键的异常信号。频率不对意味着芯片内部的PLL可能没有正常工作或者这个芯片内部的核心逻辑和正版STM32F103并不完全一致。也正是从这一刻起我开始怀疑芯片本身了。2.3 串口通信异常带来的另一个线索除了PA8输出频率不对串口也暴露了问题。我用的是RS232通过SP3232做电平转换FreeModbus模块跑Modbus RTU从站波特率配置成9600。正常情况下主机发一帧请求从机应当回复。但实测下来主机这边收到的数据全是乱码个别时候完全没有响应。乱码说明波特率对不上。我一开始以为是SP3232电路问题后来把示波器探到串口TX引脚上看波形发现单帧数据的位宽明显不对。波特率9600时一个bit的宽度理论上应该是104us但实际量出来差不多130us误差接近25%。这种误差绝对不是晶振电容配错能解释的更像是芯片内部串口外设的时钟源本身就不准。结合PA8输出频率异常和串口波特率偏差我基本确定问题出在芯片内部时钟系统的根基上。也就是说这个芯片内部可能根本没在用8MHz外部晶振跑或者在PLL环节就已经出问题了。软件再怎么调都治不了硬件层面的硬伤。3. FreeRTOS为什么会“掀开”假芯片的老底3.1 裸机能跑RTOS却跑了就挂的本质原因很多人会有个疑惑同一个芯片裸机程序能跑为什么上了FreeRTOS就各种死机这其实非常好解释。裸机程序是单线程的轮询逻辑对RAM的占用比较小主要代码都放在Flash里线性执行。如果芯片本身有部分Flash坏块或者RAM比标称的小只要不踩到那几个坏的区域程序勉强能转起来。FreeRTOS的情况完全不一样。它要管理任务控制块TCB、内核堆栈、任务堆栈、队列、信号量等这些都需要比较大的RAM做动态分配。更关键的是任务调度器会把上下文保存在对应任务的内核栈里如果RAM实际可用空间比芯片标称小任务一切换就可能写进了非法地址。加上FreeRTOS默认用PendSV和Systick中断做任务切换这两个中断的优先级配置也有讲究一旦配置越界系统直接进HardFault。所以FreeRTOS对芯片资源的要求比裸机高得多芯片任何一项“缩水”都会被RTOS放大并快速暴露。3.2 市面上的“假芯片”常见的几种情况结合这次经历和我过去几年的采购经验市面上所谓“STM32F103假芯片”或者说“问题芯片”大概可以分成三类。第一类是打磨重标片。把原厂芯片表面的丝印磨掉重新打上更高型号的标记这就叫打磨片。这种片子的来源很复杂有可能是回收料也有可能是不同型号的库存散料。打磨片最大的问题不是型号不对而是引脚、内部Die可能已经受过损伤稳定性完全没有保证。偶尔遇到能用的算是运气好像晶振不起振、复位异常、PLL锁不住这些问题都可能发生。第二类是低配冒充高配。用C8T6冒充RCT6用CBT6冒充RCT6这是非常常见的操作。C8T6只有64KB Flash和20KB SRAMRCT6是256KB Flash和48KB SRAM。如果工程是按RCT6的容量链接的程序烧进去之后Flash后半段的数据根本没有物理存储空间要么烧录时校验失败要么运行时取指令取到无效数据程序自然跑飞。我在博客开头说的“烧录后第一次能跑复位就不行”就非常符合这种低配冒充高配的特征。第三类是兼容芯片冒充原厂。有些国产兼容型号引脚定义和ST基本一致但内部外设寄存器细节并不完全相同。裸机点个灯还行一旦跑FreeRTOS任务切换依赖Systick和PendSV如果芯片的中断控制器行为跟ARM标准不完全一致就会出现调度不稳定的问题。此外部分兼容芯片的Flash和RAM实际容量比STM32对应型号小运行RTOS时很容易超限。3.3 哪些现象属于“假芯片特有”并不是所有跑不起来的芯片都是假芯片但下面这些现象组合出现时就需要高度警惕了同样的程序在别的板子上正常在这块板上时好时坏尤其是复位后大概率死机PA8的MCO输出频率严重偏离预期值不是简单的误差而是百分之几十的偏差串口波特率怎么配都不对波形位宽明显异常用示波器测外部晶振波形幅度偏低或起振困难下载程序时经常出现Flash校验失败但降低速度后又偶尔能成功芯片工作温度异常摸上去烫手电流明显偏大。如果出现两条以上这类现象基本上可以判定芯片来源有问题。这时候再往下调试FreeRTOS的堆栈、优先级意义已经不大了应该先解决芯片本身的问题。4. 验明正身我自己在用的几种检测方法4.1 读芯片ID和Flash容量寄存器最直接的验证方式是让芯片自己“报上名来”。STM32F103有对应的DBGMCU_IDCODE寄存器地址在0xE0042000低16位是Device ID。不同型号的F103这个值也不一样。比如0x410对应的是中容量产品如C8T60x414对应高容量产品如RCT6、ZET6。用代码读一下就知道它到底是哪一类。uint32_t dbgmcu_idcode; uint16_t device_id; dbgmcu_idcode *(__IO uint32_t *)0xE0042000; device_id (uint16_t)(dbgmcu_idcode 0xFFFF);还有一个更直观的信息芯片内部有一个Flash容量寄存器地址在0x1FFFF7E016位宽单位是KB。比如我的RCT6读出来应该是256。如果买的是RCT6却读出来64或者128那基本可以确定是低容量芯片冒充的。uint16_t flash_size_kb; flash_size_kb *(__IO uint16_t *)0x1FFFF7E0;这两个寄存器读起来非常简单但效果极好。我把这个检测代码放到板子最开头执行通过串口把读到的值打印出来几秒钟就能判断芯片型号是否对得上。4.2 写满Flash做全片校验读容量只是第一步Flash内部的存储介质是否可靠还得实际写一遍才知道。我给这块问题板子写了一个全片校验的小程序从0x08000000开始以4KB为块单位依次写入0xAA、0x55、0x00、0xFF等固定模式然后读出来逐字节比对。写Flash的时候要注意千万不要覆盖到程序自身所在的区域。我的策略是先把校验代码放到SRAM里执行通过Keil的分散加载文件把校验函数放到RAM或者把程序放在Flash起始地址校验范围从Flash末尾往低地址方向走避开前64KB程序区。实际操作中用J-Flash或STM32CubeProgrammer也能直接做整片擦除和写入校验但有时候低端下载器对假芯片的兼容性反而掩盖了问题还是用芯片自身的操作更可靠。这块问题板子的校验结果非常难看写到中后段地址时芯片报编程错误读出来的数据和写入的完全对不上。这些区域看起来能写但内部存储单元实际上已经损坏或不存在了。这进一步印证了Flash容量不足的猜测。4.3 串口波特率验证法和内部RC校准前面提到我量到串口波特率偏差很大其实这也可以做成一个自动检测脚本。方法很简单让芯片初始化串口并发送连续的0x55这个字节的bit序列是01010101示波器上看非常工整用示波器测相邻两个下降沿之间的时间计算实际波特率。用逻辑分析仪的话更简单直接解析帧格式看设备上报的波特率是否和配置值吻合。如果偏差在2%以内基本正常偏差超过5%就要考虑外部晶振没工作、芯片内部PLL配置不对或者芯片本身时钟系统异常。这块问题板子的偏差高达25%已经不能用“测量误差”来解释了。4.4 正规采购渠道和到货抽检建议检测方法再多也不如从源头上控制。芯片采购尽量走原厂代理商、正规授权分销商或者信誉较好的大型电子元件平台。散新片、拆机片不是不能用但要做坏品率评估千万别拿量产项目的需求去买那种价格低得离谱的货。到货之后我现在的习惯是先抽检3-5片跑一遍包含“读ID 读容量 全片Flash读写校验 定时器72MHz校准 串口回环测试”的自检固件全部通过再入库。这套流程看起来费时间但能避免在调试阶段浪费大量精力。5. 后续建议除了假芯片FreeRTOS移植还有这几个坑5.1 堆栈溢出检测一定要开经历了这次假芯片时间之后我又对FreeRTOS本身的常见问题做了几轮加固。第一个就是堆栈溢出检测。很多人用FreeRTOS都不开configCHECK_FOR_STACK_OVERFLOW直到程序跑飞了才后悔。我建议开发阶段一定要开启而且要选方法2。方法1只检测任务栈指针是否越界方法2会在任务切换时检查栈是否有被覆盖的痕迹检测更全面。代价是多一点CPU开销但这在调试阶段完全值得。一旦检测到溢出钩子函数里不要只是死循环至少要把出问题的任务名打出来或者点一个独立的错误指示灯。否则钩子触发了你都不知道还以为是系统调度卡死了。5.2 中断优先级不要乱设Systick和PendSV必须最低FreeRTOS移植到Cortex-M3上一个很重要的规则是中断优先级数值越大优先级越低而FreeRTOS要求PendSV和Systick中断的优先级必须设为最低也就是数值最大。同时所有调用FreeRTOS API的中断其优先级数值必须大于configMAX_SYSCALL_INTERRUPT_PRIORITY。如果某个中断的优先级数值小于这个阈值也就是逻辑优先级更高那它就能打断FreeRTOS的临界区可能造成调度器状态错乱。我在之前的裸机工程里把串口中断优先级设成了0也就是最高优先级。移植到FreeRTOS之后忘了改结果就是程序在串口中断里调用FreeRTOS队列发送函数直接把系统干挂。这个坑和假芯片现象有一定相似性排查时容易混淆。5.3 拿到新板子先验证芯片再铺RTOS最后分享一个我个人的操作习惯拿到任何一块新板子我不会直接往下铺FreeRTOS工程而是先烧一个最小的验证固件包含“读ID、读Flash容量、点亮板载LED、串口回环”四个功能。固件代码不超过100行完全不用RTOS裸机就能跑。确认芯片身份和基本外设正常之后再把这个裸机工程作为底座往上叠FreeRTOS。这一步看起来多余但在踩过打磨片的坑之后我深刻意识到在不可靠的硬件基础上调试软件等于拿着一个坏温度计去修空调永远找不到真实原因。先花十分钟确认芯片没问题后面省下的是几十个小时的排查时间。这次假芯片事件之后我团队内部也定了一个不成文的规定所有STM32项目的硬件方案评审第一页必须放“芯片验证方案”包括型号读取、容量校验和最小外设测试这三个环节。这不是流程繁琐而是吃过亏之后总结出来的保命手段。如果你正准备用STM32F103 FreeRTOS做项目尤其是从非正规渠道采购芯片强烈建议你先把这块板子的芯片底细查清楚再让FreeRTOS接管任务调度。芯片是地基地基本身不稳上面盖再漂亮的楼早晚都会塌。
返回列表