用stm32的flash保存數據的優化方法


最開始用stm32的flash保存數據的方法都是用原子的例程,STM32F1的話,原子的方法大概是創建一個1K或者2K的緩存,修改數據的時候,先把該扇區的所有數據寫到該緩存,然后查看是否需要擦除整個扇區,一般在一個地方寫的話,必須要擦除,要想不擦除,就需要一個變量記錄下一次要寫的地址,和數據一塊保存。STM32F4的話,因為其最小扇區為16K,最大128K,寫個稍大點的程序,就得用大扇區,原子的做法干脆不緩存了,直接擦了扇區,重寫!(吐個槽,原子的一些程序可以再優化一下,感覺有些源碼就是應付事兒,可以向更實用的更有效的方向發展發展嘛!!)回歸正題,有一天,有一個項目用的屏幕不是靜態顯示的,需要不停的掃,每次保存數據的時候屏都會閃一次,原來是保存數據的時候,要擦除扇區,1K的扇區大概要15ms的時間才能擦除完成,而且這段時間單片機什么都不能干。為了解決這個問題,發現了網上有stm32flash模擬EEPROM的程序,學習后發現,比原子的例程更實用,更有效,既提高了存取速度,又能平均磨損flash,延長flash改寫壽命。大家可百度stm32flash模擬EEPROM,還有人優化了官方給的demo。優化過后,加入了CurWrAddress,意在提高讀寫速度,但是正是這個CurWrAddress,引起了一些bug。

1 /* Global variable used to store variable value in read sequence */
2 uint16_t DataVar = 0;
3 uint32_t CurWrAddress;
4 /* Virtual address defined by the user: 0xFFFF value is prohibited */
5 extern uint16_t VirtAddVarTab[NumbOfVar];

第3行 把CurWrAddress初始化為0,就是一個bug。修改后的代碼, 把InitCurrWrAddress()函數放在了__EE_Init()之后,也就是說只要__EE_Init()函數用到了CurWrAddress,那么CurWrAddress = 0,有某種情況下,這是個災難。

uint16_t EE_Init(void)
{
uint16_t FlashStatus;
   
   FlashStatus=__EE_Init();
   
   InitCurrWrAddress();
   
   return(FlashStatus);
}

 

接下來,看__EE_Init()函數

  1   uint16_t __EE_Init(void)
  2    {
      ...
13 14 /* Check for invalid header states and repair if necessary */ 15 switch (PageStatus0) 16 { 17 case ERASED:
...
33 case RECEIVE_DATA: 34 if (PageStatus1 == VALID_PAGE) /* Page0 receive, Page1 valid */ 35 {           ... 43 if (VarIdx != x) 44 { 45 /* Read the last variables' updates */ 46 ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar); .... 57 } 59 } ... 85 case VALID_PAGE: 95 {103 if (VarIdx != x) 104 { 105 /* Read the last variables' updates */ 106 ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);           ...118 } 119 } 120 ....140 } 141 142 return FLASH_COMPLETE; 143 }

我保留的出災難的兩種情況,第一PAGE0=VALID_PAGE,PAGE0 = RECEIVE_DATA,另一種反過來,其實就是在換頁的時候,沒有完成就掉電了,上電后初始化時,就會有bug了。為什么會有bug?因為這兩種情況都用到了EE_ReadVariable函數,而優化后的EE_ReadVariable函數在讀取保存變量的時候,是從CurWrAddress-2開始往后讀,直到這一頁的開始,問題來了,一開始CurWrAddress=0啊,uint32_t類型的0,減去2后等於多少?關鍵是還要用這個數,作為地址去讀flash。。。所以一旦出現這種情況,GG。解決辦法就是在碰到這兩種情況的時候在讀取變量之前,先調用InitCurrWrAddress(),並聲明CurWrAddress的時候初始化為第二頁的結尾(最起碼不會GG了),調用InitCurrWrAddress()之后,會把CurrWrAddress更改為有效頁或者RECEIVE頁(如果狀態為RECEIVE_DATA),然后再去讀取數據,進行換頁。這種辦法並不能解決全部已知bug。

到這里還沒完,還有一個bug,就是PAGE1要滿了,換到PAGE0的時候。

 1 static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data)
 2 {
        ...
 8   /* Get active Page for read operation */
 9   ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);
10 
11      ...19   if (ValidPage == PAGE1)  /* Page0 valid */
20   {
21     /* New page address where variable will be moved to */
22     NewPageAddress = PAGE0_BASE_ADDRESS;
23 
24     /* Old page address where variable will be taken from */
25     OldPageAddress = PAGE1_BASE_ADDRESS;
26   }
    ...
32   /* Set the new Page status to RECEIVE_DATA status */
33   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, RECEIVE_DATA);
    ...
40   InitCurrWrAddress();//aft 重新初始化寫地址
41   /* Write the variable passed as parameter in the new active page */
42   EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
    ....
49   /* Transfer process: transfer variables from old to the new active page */
50   for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++)
51   {
52     if (VirtAddVarTab[VarIdx] != VirtAddress)  /* Check each variable except the one passed as parameter */
53     {
54       /* Read the other last variable updates */
55       ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
      ...67     }
68   }
69 
70   /* Erase the old Page: Set old Page status to ERASED status */
71   FlashStatus = FLASH_ErasePage(OldPageAddress);
72   /* If erase operation was failed, a Flash error code is returned */
73   if (FlashStatus != FLASH_COMPLETE)
74   {
75     return FlashStatus;
76   }
77 
78   /* Set new Page status to VALID_PAGE status */
79   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, VALID_PAGE);
80   /* If program operation was failed, a Flash error code is returned */
81   if (FlashStatus != FLASH_COMPLETE)
82   {
83     return FlashStatus;
84   }
85 
86   /* Return last operation flash status */
87   return FlashStatus;
88 }

33行執行完后,要更改CurrAddress了,這時如果是PAGE0要接收,那么CurrAddress = PAGE0_StarAdress+4了,那么在后邊的讀取數據,用於轉換的時候,有個判斷

Address=CurWrAddress-2;

while (Address > (PageStartAddress + 2)) 這個地方PageStartAddress = Page1_StarAddress ,而PAGE0_StarAdress+2肯定小於Page1_StarAddress啊,直接跳過了,導致不能轉存其他數據

 1  EE_ReadVariable函數
    /* Get active Page for read operation */
    ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);//而READ的規則是誰有效 就是誰 不關系是否RECIVE 和寫有效區分
  PageStartAddress = (uint32_t)(EEPROM_START_ADDRESS + (uint32_t)(ValidPage * PAGE_SIZE)); 2 3 /* Get the valid Page end Address */ 4 //Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE)); 5 Address=CurWrAddress-2; 6 7 /* Check each active page address starting from end */ 8 while (Address > (PageStartAddress + 2)) 9 { 10 /* Get the current location content to be compared with virtual address */ 11 AddressValue = (*(__IO uint16_t*)Address); 12 13 /* Compare the read address with the virtual address */ 14 if (AddressValue == VirtAddress) 15 { 16 /* Get content of Address-2 which is variable value */ 17 *Data = (*(__IO uint16_t*)(Address - 2)); 18 19 /* In case variable value is read, reset ReadStatus flag */ 20 ReadStatus = 0; 21 22 break; 23 } 24 else 25 { 26 /* Next address location */ 27 Address = Address - 4; 28 } 29 }

我的做法是,修改 EE_ReadVariable函數,在讀取數據的時候,如果PAGE0或者PAGE1有一個狀態為正在接受,那么Address就用官方Demo給的語句賦值。這時解決全部這篇所說bug的方法。

//之前加上讀取PageStatus0和1的語句
1     /* Get the valid Page end Address */
2     if((PageStatus0 == RECEIVE_DATA)||(PageStatus1 == RECEIVE_DATA))//當在頁傳輸時,地址放在有效頁的末尾來搜索所有的存儲信息
3     {
4         Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));
5     }
6     else
7     {
8         Address=CurWrAddress-2;
9     }

目前遇到的bug就這些。

綜上所述,都是CurWrAddress鬧得,

第一種:因為初始化CurWrAddress為0,在剛上電時候,恰巧碰到有一頁狀態為RECEIVE_DATA(概率不大),則在執行EE_ReadVariable函數的時候,Address=CurWrAddress-2;導致得到一個非法地址。

第二種:PAGE1滿 要轉給PAGE0,還是出在EE_ReadVariable函數里,Address=CurWrAddress-2;因為EE_PageTransfer中在轉移數據之前,重新調整了CurWrAddress寫地址到PAGE0了,而在讀取未轉移的地址需要從PAGE1底部開始查詢,這就導致轉移不全。

解決辦法就是修改EE_ReadVariable函數,加入讀取PAGE0和PAGE1的狀態語句,並判斷,如果有一個狀態為RECEIVE_DATA,則按官方的調整Address。

 

加上stm32的掉電檢測保存數據,上電后,檢測扇區剩余空間,不夠下一次保存的話,提前換頁,因為stm32f4擦除一個扇區要1s多,掉電那些時間根本不夠,另外掉電后,閾值2.7v的話,測試發現,保守估計可保存1000個字,和外圍器件,電容有關。

最后上張stm32flash保存數據的圖,0x8040000是扇區6的首地址,用於保存這一頁的狀態00 00為有效頁標志,0x8040004和05保存的是數據,06和07的01 A1保存的是變量虛擬地址,實際上是0XA101,因為STM32是小端模式,低位字節位排放在低地址端,高位字節排放在高位地址端。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM