利用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填充。

posted @ 2023-10-24 15:58  okyihu  阅读(967)  评论(0编辑  收藏  举报