目录
前言:
先说结论:
一、问题分析
二、异常排查
三、发现原因!
四、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~
前言:
长颈鹿最近在使用STM32G070单片机进行项目开发,过程中遇到了一个很棘手的Bug,设备在调试中突然死机,仿真得知,原来是写FLASH操作失败,导致设备进入FLASH错误处理流程,陷入无限循环。好奇怪的现象,FLASH读写属于底层驱动,一般是不会有改动的,笔者也并没有动FLASH的相关代码,但莫名其妙出现这样的情况。决定刨根问底,探究一下深层次的原因。
先说结论:
出现该异常是因为FLASH擦除失败,从而导致FLASH写入操作失败,而FLASH擦除失败的原因在于类的构造函数位置问题,出于项目需要,长颈鹿把一个类定义在了文件作用域内,在这个类中一个类成员对象的构造函数中调用了HAL_TIM_PWM_Start() 函数,虽加上了static前缀,但其仍相当于一个全局变量,而系统中定义的全局变量会在main函数调用之前先被创建,对于类而言,被创建时会自动调用其构造函数,而我又在这个构造函数中调用了HAL_TIM_PWM_Start()函数,在所有外设初始化之前就操作了RCC寄存器(HAL_TIM_PWM_Start()会修改RCC寄存器,如RCC->APB1ENR使能TIM时钟),导致RCC寄存器的状态出现了异常,而FLASH操作要求系统时钟处于稳定且已知的状态,由此就产生了冲突,导致FLASH擦除失败,进而系统卡死。
所以如果你也遇到了类似的问题而又无从下手,可以检查一下代码中类的构造函数或者类中包含的成员类的构造函数,类定义的作用域有没有问题,构造函数中是不是有在外设初始化之前操作RCC寄存器的嫌疑!
一、问题分析
擦除FLASH的操作调用了HAL库的HAL_FLASHEx_Erase(),在FLASH擦除函数中,调用了 FLASH_WaitForLastOperation() 函数来检测FLASH当前是否空闲,是否可对FLASH进行擦除和写入等操作,但是FLASH_WaitForLastOperation()函数在检测时,这个FLASH->SR中的FLASH_SR_CFGBSY位始终为1,导致函数超时退出。
FLASH->SR表示FLASH中的SR状态寄存器,FLASH_SR_CFGBSY 标志位是 SR 的状态位之一,该位表示FLASH当前是空闲,为 0 表示可执行擦除或写入操作。
通过Watch窗口查看FLASH-SR的值,以及HAL库中定义的FLASH_SR_CFGBSY的值,两者都为0x00040000,对应上了,表示当前FLASH正处于忙状态。
二、异常排查
那么问题来了,这步的FLASH读写操作是程序对FLASH进行的首次操作,FLASH的状态怎么会是BUSY呢?况且STM32G070只有一个内核,程序只能顺序执行,对FLASH的操作一般都会等待FLASH状态回归正常才会结束,这个现象真的是非常奇怪。
是什么导致的呢?
长颈鹿一步一步的在项目中寻找,终于发现了端倪,造成FLASH擦除出错的原因竟然是一个通过定时器实现的呼吸灯!这个呼吸灯函数放在代码的开始部分,检查有无设备是否处于充电状态,如果处于充电状态,就开启呼吸灯。如果没有,设备正常开机。笔者将呼吸灯的源文件注释掉,代码编译后可以正常运行,将呼吸灯的源文件添加进项目中编译,即使不调用该函数,设备在进行FLASH擦除操作时也会卡死在这个地方。
但是定时器和FLASH,两个八竿子打不着的外设,为什么会互相影响?
而且只要参与编译,即使没有在程序中调用该函数,也会对FLASH造成影响。因此推测,影响很可能在编译阶段就已经发生,需要重点关注编译阶段的有关代码,宏定义,全局变量,以及全局变量类的构造函数!
果不其然,在呼吸灯的构造函数中发现了异常点。
CBreathingLight::CBreathingLight() { bDirction = DOWN; u32CCRVal = LEDS_CCR_VAL; u8ChargeState = CHARGE_OFF; /* 充电状态 */ vON(); /* 启动PWM输出 */ }构造函数中的vON();函数会启动PWM输出,有操作定时器和调用系统时钟的行为,这个函数放在构造函数中,单独看没有问题,但是CBreathingLight类的对象定义在系统运行类CSystem中,而CSystem类的对象定义在文件作用域内!
#include "CSystem.h" static CSystem m_App; #if defined(__cplusplus) extern "C" { #endif void vInit(void) { m_App.bInit(); } void vRun(void* parameter) { m_App.vRun(0); } #if defined(__cplusplus) } #endif三、发现原因!
CSystem类是一个全局变量,编译器会在编译时创建全局变量的对象,而针对类的全局变量,会自动调用其构造函数初始化类内成员,也就是说,CSystem类的构造函数会在main函数之前调用,而CBreathingLight类作为CSystem类的类内成员,其构造函数会在CSystem类的构造函数之前调用。
最终,在还没有进入main函数进行外设(GPIO、TIM等)初始化操作时,系统就通过构造函数执行了CBreathingLight类内的vON();函数,启动PWM输出,操作了定时器和系统时钟,虽然语法上没有问题,但这种行为是非法的,推测对后续的系统时钟造成了影响,继而导致FLASH擦除出现错误。
果然,去掉构造函数内调用的vON函数之后,系统恢复正常。
STM32的所有外设(包括TIM和FLASH)都依赖系统时钟配置(RCC寄存器),HAL_TIM_PWM_Start()会修改RCC寄存器(如RCC->APB1ENR使能TIM3时钟),FLASH操作要求系统时钟处于稳定且已知的状态,(如FLASH->CR中的LOCK位需清空),vON()修改了TIM3时钟配置(RCC),导致FLASH控制器误判为"正在配置中",所以出现了FLASH擦除失败的现象