news 2026/8/9 18:44:35

STM32出现FLASH擦除失败异常现象分析及解决方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32出现FLASH擦除失败异常现象分析及解决方法

目录

前言:

先说结论:

一、问题分析

二、异常排查

三、发现原因!

四、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~


前言:

长颈鹿最近在使用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寄存器的嫌疑!

这里的HAL_FLASH_Program()函数写入失败
导致程序进入Error_Handler()错误处理函数,陷入无限循环

一、问题分析

仿真发现,写FLASH失败是因为上一步擦除FLASH扇区出现了异常

擦除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 表示可执行擦除或写入操作。

FLASH->SR寄存器位

通过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擦除失败的现象

四、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 22:55:52

CMAKE指令集

目录 1、指定CMAKE最低的版本号 2、设置工程名 3、添加可执行文件 4、简化项目名的表示 5、添加多个可执行文件 6、添加多个可执行文件的简洁方法 7、添加版本号和配置头文件 8、指定C版本 9、添加库 10、使用库 11、将库设置为可选项 如果这篇文章能帮助到你&…

作者头像 李华
网站建设 2026/8/3 21:22:06

AI写作助手测评大会

当前主流AI写作工具对比ChatGPT (OpenAI) 支持长文本生成和多轮对话,擅长创意写作和内容改写。提供网页版和API接口,订阅版可访问GPT-4模型。在学术写作和商业文案场景表现突出。Claude (Anthropic) 注重安全性和事实准确性,内置宪法AI约束机…

作者头像 李华
网站建设 2026/8/7 12:52:05

【必收藏】突破传统RAG瓶颈:Deep Thinking RAG架构详解与实战指南

Deep Thinking RAG是一种将RAG与Agent技术融合的新型架构,突破了传统RAG的局限性。它通过四个智能模块(规划代理、检索监督者、多阶段检索漏斗、策略代理)实现从线性链到循环图的跃迁,支持多跳推理、动态知识边界和自适应检索策略…

作者头像 李华
网站建设 2026/8/2 3:39:14

查询指定任务的办理时间轴

一、接口核心需求分析 你提供的这段代码是Activiti工作流中查询指定任务的办理时间轴接口,核心业务需求如下: 关联查询:根据传入的任务ID(taskId),先获取对应的流程实例ID(processInstanceId&am…

作者头像 李华
网站建设 2026/8/2 22:36:59

ubuntu系统_每天定时23:00 定时关机_怎么实现

在Ubuntu系统中设置每天23:00定时关机,最可靠的方法是使用系统内置的 cron 服务。下面的表格汇总了实现步骤和关键要点,你可以快速了解整个过程。步骤核心操作说明/命令示例1. 编辑计划任务终端中输入 sudo crontab -e使用 sudo 获取root权限&#xff0c…

作者头像 李华
网站建设 2026/7/26 10:45:55

mysql一条sql语句是如何运行的

MySQL SQL 语句执行流程MySQL 执行一条 SQL 语句的过程可以分为多个阶段,包括连接管理、解析与优化、执行引擎处理以及结果返回。以下是详细流程:连接管理客户端通过 TCP/IP 或 Unix Socket 连接到 MySQL 服务器。连接建立后,服务器进行身份验…

作者头像 李华