利用STM32 PVD掉电中断功能保存少量数据到FLASH里的方案
1 目标功能及方案
1.1 FLASH的1页的擦除时间约为10-20ms,写一个字节的时间约几十us, 当应用程序是每1ms都要执行一个循环周期时,如果在保存数据换页时要擦除FLASH,显然会阻塞程序的执行,所以在应用程序有严格的实时性要求时,eeprom_emulate保存数据的方案显然不合适。
1.2 下图是检测到掉电后,进入掉电中断,置位某个管脚的波形图,从中可以看出掉电持续时间长度以及开始进入掉电保护时的电压,其中绿色曲线是电源电压,蓝色是被置位的管脚上的信号。根据波形可以看出掉电持续时间约50ms,利用这个时间来保存数据是足够的。
1.3 保存方案:
A 应用层软件模块增加一个变量storage, 有参数调节过,则storage置位,掉电中断保存数据时先检查sotrage==1? 如果等于1,则开始保存数据,如果不等于1则不需要保存数据。
B 用户数据UserData[]格式如下:data1,data2,data3,......datan,版本号低八位,版本号高八位,CRC校验和。
C UserData[]数据保存方案:将FLASH最后两页安排为数据保存区域,这里取倒数第二页为A区,倒数第一页为B区;
上电运行时,先读取A、B两个区域的数据(根据UserData[]的大小来读),并分别得到A、B两区的版本号Version及校验和是否正确的标志位。
紧接着,判断如果A,B两区的校验和都正确,并且A的版本号高于B的版本号(A的数据较新),则加载A区的数据作为运行数据,并擦除B区,并设置好接下来这次掉电保存数据的起始地址(B区的首地址),以及接下来这批掉电保存数据的版本号(A版本号自加1)。
如果A,B两区的校验和都正确,并且B的版本号高于A的版本号(B的数据较新),则加载B区的数据作为运行数据,并擦除A区,并设置好接下来这次掉电保存数据的起始地址(A区首地址),以及接下来这批掉电保存数据的版本号(B版本号自加1)。
如果A区校验和正确,B区校验和不正确,则加载A区的数据作为运行数据,并擦除B区,并设置好接下来这次掉电保存数据的起始地址(B区的首地址),以及接下来这批掉电保存数据的版本号(A版本号自加1)。
如果A区校验和不正确,B去校验和正确,则加载B区的数据作为运行数据,并擦除A区,并设置好接下来这次掉电保存数据的起始地址(A区首地址),以及接下来这批掉电保存数据的版本号(B版本号自加1)。
如果A区校验和和B区校验和都不正确,则擦除两个区域,把用户数据初始化为默认值,版本号也从1开始,并把A区做为本次数据保存的区域。
掉电时,检查storage是否要保存数据(是否有数据更新),有直接将UserData[]的数据编程到FLASH里,没有则直接退出即可。
以上有一个问题没考虑到,就是如果A区被擦掉并等待本次掉电写入数据,如果storge==0,本次掉电过程不需要写入数据,那么下次开机时,A区数据的校验就会出错,又会把A擦掉一次。所以每次检查完校验和和版本号,并且要开始擦除的时候,先判断一下该区域(A区)是否被编程过Flash_E2PROMChkBlank(),如果没有被编程过,则不需要执行擦除函数。
2 底层设置代码
2.1 STM32CUBE里配置使能PVD中断 PVD interrupt EXTI line 16
2.2 HAL_Init()->HAL_MspInit()里修改PVD配置(CUBE里没找到相关配置PWR的地方),修改后的代码
PWR_PVDTypeDef sConfigPVD; /*##-1- Enable Power Clock #################################################*/ __HAL_RCC_PWR_CLK_ENABLE(); /*##-2- Configure the NVIC for PVD #########################################*/ HAL_NVIC_SetPriority(PVD_IRQn, 0, 0); HAL_NVIC_EnableIRQ(PVD_IRQn); /*##the step -2 has been done by STCubeMx###################################*/ /* Configure the PVD Level to 3 and generate an interrupt on rising and falling edges(PVD detection level set to 2.5V, refer to the electrical characteristics of you device datasheet for more details) */ sConfigPVD.PVDLevel = PWR_PVDLEVEL_FALLING_5;//PWR_PVDLEVEL_6;//PWR_PVDLEVEL_RISING_6; sConfigPVD.Mode = PWR_PVD_MODE_IT_RISING; HAL_PWR_ConfigPVD(&sConfigPVD); /* Enable the PVD Output */ HAL_PWR_EnablePVD();
3 应用层代码
app_flash.h
/* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __app_flash_H #define __app_flash_H #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include "main.h" extern uint8_t E2PROMStorage; /*==1:need to store parameters ; ==0:no need to sotre parameters*/ void App_Flash_E2PROMInit(void) ; void App_Flash_DownloadEEParam(void) ; void App_Flash_RunParamInit(void) ; /* USER CODE BEGIN Includes */ #define ADDR_FLASH_PAGE_0 ((uint32_t)0x08000000) /* Base @ of Page 0, 2 Kbytes */ #define ADDR_FLASH_PAGE_1 ((uint32_t)0x08000800) /* Base @ of Page 1, 2 Kbytes */ #define ADDR_FLASH_PAGE_2 ((uint32_t)0x08001000) /* Base @ of Page 2, 2 Kbytes */ #define ADDR_FLASH_PAGE_3 ((uint32_t)0x08001800) /* Base @ of Page 3, 2 Kbytes */ #define ADDR_FLASH_PAGE_4 ((uint32_t)0x08002000) /* Base @ of Page 4, 2 Kbytes */ #define ADDR_FLASH_PAGE_5 ((uint32_t)0x08002800) /* Base @ of Page 5, 2 Kbytes */ #define ADDR_FLASH_PAGE_6 ((uint32_t)0x08003000) /* Base @ of Page 6, 2 Kbytes */ #define ADDR_FLASH_PAGE_7 ((uint32_t)0x08003800) /* Base @ of Page 7, 2 Kbytes */ #define ADDR_FLASH_PAGE_8 ((uint32_t)0x08004000) /* Base @ of Page 8, 2 Kbytes */ #define ADDR_FLASH_PAGE_9 ((uint32_t)0x08004800) /* Base @ of Page 9, 2 Kbytes */ #define ADDR_FLASH_PAGE_10 ((uint32_t)0x08005000) /* Base @ of Page 10, 2 Kbytes */ #define ADDR_FLASH_PAGE_11 ((uint32_t)0x08005800) /* Base @ of Page 11, 2 Kbytes */ #define ADDR_FLASH_PAGE_12 ((uint32_t)0x08006000) /* Base @ of Page 12, 2 Kbytes */ #define ADDR_FLASH_PAGE_13 ((uint32_t)0x08006800) /* Base @ of Page 13, 2 Kbytes */ #define ADDR_FLASH_PAGE_14 ((uint32_t)0x08007000) /* Base @ of Page 14, 2 Kbytes */ #define ADDR_FLASH_PAGE_15 ((uint32_t)0x08007800) /* Base @ of Page 15, 2 Kbytes */ #define ADDR_FLASH_PAGE_16 ((uint32_t)0x08008000) /* Base @ of Page 16, 2 Kbytes */ #define ADDR_FLASH_PAGE_17 ((uint32_t)0x08008800) /* Base @ of Page 17, 2 Kbytes */ #define ADDR_FLASH_PAGE_18 ((uint32_t)0x08009000) /* Base @ of Page 18, 2 Kbytes */ #define ADDR_FLASH_PAGE_19 ((uint32_t)0x08009800) /* Base @ of Page 19, 2 Kbytes */ #define ADDR_FLASH_PAGE_20 ((uint32_t)0x0800A000) /* Base @ of Page 20, 2 Kbytes */ #define ADDR_FLASH_PAGE_21 ((uint32_t)0x0800A800) /* Base @ of Page 21, 2 Kbytes */ #define ADDR_FLASH_PAGE_22 ((uint32_t)0x0800B000) /* Base @ of Page 22, 2 Kbytes */ #define ADDR_FLASH_PAGE_23 ((uint32_t)0x0800B800) /* Base @ of Page 23, 2 Kbytes */ #define ADDR_FLASH_PAGE_24 ((uint32_t)0x0800C000) /* Base @ of Page 24, 2 Kbytes */ #define ADDR_FLASH_PAGE_25 ((uint32_t)0x0800C800) /* Base @ of Page 25, 2 Kbytes */ #define ADDR_FLASH_PAGE_26 ((uint32_t)0x0800D000) /* Base @ of Page 26, 2 Kbytes */ #define ADDR_FLASH_PAGE_27 ((uint32_t)0x0800D800) /* Base @ of Page 27, 2 Kbytes */ #define ADDR_FLASH_PAGE_28 ((uint32_t)0x0800E000) /* Base @ of Page 28, 2 Kbytes */ #define ADDR_FLASH_PAGE_29 ((uint32_t)0x0800E800) /* Base @ of Page 29, 2 Kbytes */ #define ADDR_FLASH_PAGE_30 ((uint32_t)0x0800F000) /* Base @ of Page 30, 2 Kbytes */ #define ADDR_FLASH_PAGE_31 ((uint32_t)0x0800F800) /* Base @ of Page 31, 2 Kbytes */ /* USER CODE END Includes */ #ifdef __cplusplus } #endif #endif /*__ pinoutConfig_H */
app_flash.c
/* Includes ------------------------------------------------------------------*/ #include "tim.h" #include "ultrasonic.h" #include "app_flash.h" #include "bsp_flash.h" #include "app_encoder.h" /* USER CODE BEGIN 0 */ #define E2PROM_SIZE 24 /*Will program 8 bytes once a time*/ /*Flash_E2PROMChkBlank();PROGRAM_END_ADDRESS*/ #define PARAM_SIZE 15 /*The padding bytes are not counted*/ /*Flash_UploadEEParam();CalcChksum();App_Flash_DownloadEEParam()->CalcChksum();*/ #define PAGE_SIZE 0x800 #define EEPROM_START_ADDRESS ADDR_FLASH_PAGE_30 #define PAGEA_BASE_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + 0x000)) #define PAGEA_END_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + (PAGE_SIZE - 1))) #define PAGEB_BASE_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + PAGE_SIZE)) #define PAGEB_END_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + (2 * PAGE_SIZE - 1))) #define UNPROGRAMED 1 uint8_t E2PROMStorage; /*==1:need to store parameters ; ==0:no need to sotre parameters*/ uint8_t AMatched, BMatched; /*==1:chksum in the flash page is matched ; ==0:unmatched*/ uint16_t AVersion, BVersion; /*version of parameters in this flash pages*/ uint32_t Address, PROGRAM_END_ADDRESS;
void Bsp_Flash_InitEEParam(void)
{
//根据不同产品初始化参数,当两页都没数据或者两页CRC都错误时,会调用该函数对参数进行初始化
}
uint8_t CalcChksum(uint8_t *data, uint8_t size) { uint8_t chksum = 0, i; for( i =0; i< size; i++) { chksum += data[i]; } return chksum; } uint8_t Flash_E2PROMChkBlank(uint32_t addr,uint8_t size ) { uint8_t *p = (uint8_t *)addr; while(size){ if(*p != 0xFF){ return(0); } size--; p++; } return (1); } void Flash_ErasePageA(void) { uint32_t PAGEError; FLASH_EraseInitTypeDef EraseInitStruct; HAL_FLASH_Unlock(); /* Unlock the Flash to enable the flash control register access *************/ EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; /* Fill EraseInit structure*/ EraseInitStruct.Page = 30; EraseInitStruct.NbPages = 1; __disable_irq(); if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) { /* Infinite loop */ while (1) ; } __enable_irq(); HAL_FLASH_Lock(); } void Flash_ErasePageB(void) { uint32_t PAGEError; FLASH_EraseInitTypeDef EraseInitStruct; HAL_FLASH_Unlock(); /* Unlock the Flash to enable the flash control register access *************/ EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; /* Fill EraseInit structure*/ EraseInitStruct.Page = 31; EraseInitStruct.NbPages = 1; __disable_irq(); if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) { /* Infinite loop */ while (1) ; } __enable_irq(); HAL_FLASH_Lock(); } void Flash_UploadEEParam(uint32_t addr,uint8_t *p) { uint32_t address=addr; /*base address*/ while (address < (addr + PARAM_SIZE)) /*28 Datas ,address :0~27*/ { *p= *(uint8_t*)(address); address++; p++; } Param.EEParam.reserved0 =0xFF; /*The padding bytes*/ Param.EEParam.reserved1 =0xFF; Param.EEParam.reserved2 =0xFF; Param.EEParam.reserved3 =0xFF; Param.EEParam.reserved4 =0xFF; Param.EEParam.reserved5 =0xFF; Param.EEParam.reserved6 =0xFF; Param.EEParam.reserved7 =0xFF; Param.EEParam.reserved8 =0xFF; } void Flash_GetE2PROMVersionAndChksum(void) { Flash_UploadEEParam(PAGEA_BASE_ADDRESS, (unsigned char *)&Param.EEParam.CentralFreq); if(CalcChksum((unsigned char *)&Param.EEParam.CentralFreq,PARAM_SIZE-1) ==Param.EEParam.chksum){ AMatched =1; } else{ AMatched =0; } AVersion =Param.EEParam.version; Flash_UploadEEParam(PAGEB_BASE_ADDRESS, (unsigned char *)&Param.EEParam.CentralFreq); if(CalcChksum((unsigned char *)&Param.EEParam.CentralFreq,PARAM_SIZE-1) ==Param.EEParam.chksum){ BMatched =1; } else{ BMatched =0; } BVersion =Param.EEParam.version; } void App_Flash_E2PROMInit(void) { Flash_GetE2PROMVersionAndChksum(); if(AMatched) /*#The chksum of Page A is matched*/ { if(BMatched)/*#The chksum of Page B is matched also*/ { if(AVersion > BVersion) /*#The parameters in Page A are latest*/ { AVersion++; /*#Upload parameters in Page A*/ Flash_UploadEEParam(PAGEA_BASE_ADDRESS,(unsigned char *)&Param.EEParam.CentralFreq); /*#Prepare for data storage in Power-Down stage*/ Param.EEParam.version =AVersion; if(Flash_E2PROMChkBlank(PAGEB_BASE_ADDRESS,E2PROM_SIZE) != UNPROGRAMED){ Flash_ErasePageB(); } Address = PAGEB_BASE_ADDRESS; PROGRAM_END_ADDRESS =PAGEB_BASE_ADDRESS + E2PROM_SIZE ; } else /*#The parameters in Page B are latest*/ { BVersion ++; Flash_UploadEEParam(PAGEB_BASE_ADDRESS,(unsigned char *)&Param.EEParam.CentralFreq); /*UPDATE UserData[]*/ Param.EEParam.version =BVersion; if(Flash_E2PROMChkBlank(PAGEA_BASE_ADDRESS,E2PROM_SIZE) != UNPROGRAMED){ Flash_ErasePageA(); } Address = PAGEA_BASE_ADDRESS; PROGRAM_END_ADDRESS =PAGEA_BASE_ADDRESS + E2PROM_SIZE ; } } else /*#The chksum of Page B is unmatched */ { AVersion++; Flash_UploadEEParam(PAGEA_BASE_ADDRESS,(unsigned char *)&Param.EEParam.CentralFreq); /*UPDATE UserData[]*/ Param.EEParam.version =AVersion; if(Flash_E2PROMChkBlank(PAGEB_BASE_ADDRESS,E2PROM_SIZE) != UNPROGRAMED){ Flash_ErasePageB(); } Address = PAGEB_BASE_ADDRESS; PROGRAM_END_ADDRESS =PAGEB_BASE_ADDRESS + E2PROM_SIZE ; } } else /*#The chksum of Page A is unmatched*/ { if(BMatched) /*#The chksum of Page B is matched*/ { BVersion ++; Flash_UploadEEParam(PAGEB_BASE_ADDRESS,(unsigned char *)&Param.EEParam.CentralFreq); /*UPDATE UserData[]*/ Param.EEParam.version =BVersion; if(Flash_E2PROMChkBlank(PAGEA_BASE_ADDRESS,E2PROM_SIZE) != UNPROGRAMED){ Flash_ErasePageA(); } Address = PAGEA_BASE_ADDRESS; PROGRAM_END_ADDRESS =PAGEA_BASE_ADDRESS + E2PROM_SIZE ; } else /*#Both The chksum of Page A and Page B is unmatched*/ { Flash_ErasePageA(); Flash_ErasePageB(); Bsp_Flash_InitEEParam(); E2PROMStorage =1; Address = PAGEA_BASE_ADDRESS; PROGRAM_END_ADDRESS =PAGEA_BASE_ADDRESS +E2PROM_SIZE; } } } void App_Flash_DownloadEEParam(void) { uint8_t n =0; HAL_FLASH_Unlock(); __disable_irq(); Param.EEParam.chksum = CalcChksum((unsigned char *)&Param.EEParam.CentralFreq,PARAM_SIZE-1); while (Address < PROGRAM_END_ADDRESS) /*the Address was assigned in power-on stage*/ { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, Param.eeparam[n]) == HAL_OK){ n++; Address = Address+8; } else{ while (1) ; } } __enable_irq(); HAL_FLASH_Lock(); } void HAL_PWR_PVD_Rising_Callback(void) { // HAL_GPIO_WritePin(LED_RUN_GPIO_Port,LED_RUN_Pin,GPIO_PIN_SET); if(E2PROMStorage){ App_Flash_DownloadEEParam(); } } void App_Flash_RunParamInit(void) { //获取FLASH里的数据后,将数据格式调整成显示、运行时的格式,根据不同产品添加参数初始化代码 }
4 使用示例
4.1主程序初始化部分调用:
App_Flash_E2PROMInit();
App_Flash_RunParamInit()
4.2 PVD中断回调函数里调用:
void HAL_PWR_PVD_Rising_Callback(void) { if(E2PROMStorage){ App_Flash_DownloadEEParam();//直接保存数据 } }
5 注意事项
5.1 有的系列的芯片没有PVD掉电进入中断的功能,如F030系列的,所以就无法采用此方案保存数据。
5.2 有的芯片FLASH擦除和写入数据的函数、数据格式不同,需要对这些地方进行修改。例如:
stm32g071系列HAL层提供的FLASH编程数据函数,只有两种类型可选,一种是FLASH_TYPEPROGRAM_DOUBLEWORD,另一种是FLASH_TYPEPROGRAM_FAST,通过User Manual了解到前一种是一次编程双字 (8字节64-bit)数据,后一种是一次编程 32 个双字(8字节64-bit)数据;通过datasheet了解到编程一个双字(8字节64-bit)数据,需要85-125us, 编程一次32个双字需要2.7-4.6ms
5.3
5.3.1 代码编程写入0x1234567812345678到FLASH里,用STLINK-V2和ST-UTINITY观察到FLASH里写入后的结果从低地址到高地址为7856341278563412。
5.3.2 发现编程STM32G071XXX的FLASH调用
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, DATA_64) == HAL_OK)
时,如果第三个参数是变量或者宏定义,每次编程都能通过,如果是指针变量转换为64bit的变量
(*(uint64_t *)&EEParam.CentralFreq)不管怎么搞都编程不了FLASH ,直接进入HardFault.
后面被搞的没办法,把参数定义成联合体,利用联合体的性质来传参数给该函数。
typedef union __Us_ParamTypeDef
{
Us_EEParamTypeDef EEParam;
uint64_t eeparam[3];
}Us_ParamTypeDef;
5.3.3 把保存的参数定义成联合体的形式后,数据保存初步成功。
5.3.4 由于STM32G071的HAL层提供的FLASH编程函数每次是编程8个字节,所以程序里定义EEPARAM的时候,也是按8个字节为基本单位来定义的,不足8字节的用Reserve byte来填充,考虑到后续参数个数有增加,所以多预留了一组存储空间。这样设计时为了便于FLASH编程8字节的时候,产生不必要的麻烦,比如你吧6字节的参数按8字节来保存,最后两字节是RAM里面的随机数,可能会出错。所以干脆把参数定义成8字节一组,用不到的就用0xFF填充。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具