请选择 进入手机版 | 继续访问电脑版

你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】用stm32的flash保存数据的优化方法

[复制链接]
STMCU小助手 发布时间:2022-1-25 22:40
最开始用stm32的flash保存数据的方法都是用原子的例程,STM32F1的话,原子的方法大概是创建一个1K或者2K的缓存,修改数据的时候,先把该扇区的所有数据写到该缓存,然后查看是否需要擦除整个扇区,一般在一个地方写的话,必须要擦除,要想不擦除,就需要一个变量记录下一次要写的地址,和数据一块保存。STM32F4的话,因为其最小扇区为16K,最大128K,写个稍大点的程序,就得用大扇区,原子的做法干脆不缓存了,直接擦了扇区,重写!(吐个槽,原子的一些程序可以再优化一下,感觉有些源码就是应付事儿,可以向更实用的更有效的方向发展发展嘛!!)回归正题,有一天,有一个项目用的屏幕不是静态显示的,需要不停的扫,每次保存数据的时候屏都会闪一次,原来是保存数据的时候,要擦除扇区,1K的扇区大概要15ms的时间才能擦除完成,而且这段时间单片机什么都不能干。为了解决这个问题,发现了网上有stm32flash模拟EEPROM的程序,学习后发现,比原子的例程更实用,更有效,既提高了存取速度,又能平均磨损flash,延长flash改写寿命。大家可百度stm32flash模拟EEPROM,还有人优化了官方给的demo。优化过后,加入了CurWrAddress,意在提高读写速度,但是正是这个CurWrAddress,引起了一些bug。
  1. 1 /* Global variable used to store variable value in read sequence */, J; H0 d0 _0 M/ l
  2. 2 uint16_t DataVar = 0;, K9 Y9 B, I8 j# H6 l- t( W
  3. 3 uint32_t CurWrAddress;. q, [: S6 w! @2 Q0 L2 z# o
  4. 4 /* Virtual address defined by the user: 0xFFFF value is prohibited */% m6 b) N0 `- r1 j
  5. 5 extern uint16_t VirtAddVarTab[NumbOfVar];
复制代码

( g$ k- u1 }6 L% P0 ^, b
第3行 把CurWrAddress初始化为0,就是一个bug。修改后的代码, 把InitCurrWrAddress()函数放在了__EE_Init()之后,也就是说只要__EE_Init()函数用到了CurWrAddress,那么CurWrAddress = 0,有某种情况下,这是个灾难。
  1. uint16_t EE_Init(void)
    " N' \" v/ O6 q  ]! D* ]3 e1 Y! Y- L* G
  2. {, q- q. _& v# g
  3. uint16_t FlashStatus;# @' x, T/ Z& i5 B1 u5 k
  4.    ' u& D+ N. }0 C1 C
  5.    FlashStatus=__EE_Init();5 }8 Q7 l5 ]3 _' }- ]
  6.    
    ( V8 I" ~% i) v9 j  o" Y8 n
  7.    InitCurrWrAddress();9 G% g% Z& L" [/ _; f8 j
  8.    
    % k5 ]" Q# f+ O$ d9 T0 r
  9.    return(FlashStatus);, K2 p  ]/ ?9 Z1 i& x8 ~
  10. }
复制代码
6 z- }) W% n" e& j, [& y: l1 r& u
接下来,看__EE_Init()函数
  1. 1   uint16_t __EE_Init(void)
    1 R& M+ }1 I; ]9 M( I' E
  2.   2    {      ... 13   
    , t) [+ X/ j  m7 F' W7 k
  3. 14    /* Check for invalid header states and repair if necessary */
    9 ?! I$ e1 B  {
  4. 15     switch (PageStatus0)
    . l3 H* w0 W. e& L" ^
  5. 16     {+ C9 x( N7 [& B, x' y+ L4 f0 K
  6. 17       case ERASED:             ...
    0 Z* D0 @% d5 a$ m/ V+ `
  7. 33       case RECEIVE_DATA:
    ) B) h- I5 ~- {' g2 q
  8. 34         if (PageStatus1 == VALID_PAGE) /* Page0 receive, Page1 valid */
    : m. Q0 h; P9 C7 w, |
  9. 35         {
    ( t5 s3 v8 s+ }& _; {1 ~& y
  10.           ... 43             if (VarIdx != x)
    7 s% q( ?3 [2 c
  11. 44             {
    ) G5 h: M- i; V# M  U
  12. 45               /* Read the last variables' updates */5 B) h" B( T3 s4 d5 c: G+ M
  13. 46               ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
    ; f7 y% D1 o. a
  14.                     .... 57               } 59           }) p" O; ~% d! d
  15.              ...    ) }+ I- L$ N8 Q0 G
  16. 85       case VALID_PAGE:6 A. `3 l& t" J# g2 C9 i
  17. 95         {103            if (VarIdx != x)* C) ?( M8 Q2 |6 h+ d
  18. 104            {6 {7 f5 s- F7 {* n
  19. 105              /* Read the last variables' updates */2 \& R' u0 f4 B* X" ]9 k
  20. 106              ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);7 b! m$ J# R" e2 G, Z  g
  21.           ...118            }
    1 d% b- Q: B' r. @
  22. 119          }+ H- C  g5 W4 b/ M
  23. 120         ....140    }9 [; Y+ g1 m0 k" O" V
  24. 141  5 d* }6 ]8 M" q# |, q7 R
  25. 142    return FLASH_COMPLETE;
    ! v& T8 [; f0 s; }# Y) d# k, H
  26. 143  }
复制代码
& s4 R. e! n0 g% d# m2 P. P
% u7 E% ]. s% _8 O" j0 M  d
我保留的出灾难的两种情况,第一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. 1 static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data)
    , }& O. C8 W, I4 K& H
  2. 2 {; Y( a" U, g$ p. j5 T
  3.         ...
    * ?3 R8 P' z' q
  4. 8   /* Get active Page for read operation */, u% [+ \$ L! \0 o7 o& Z) P
  5. 9   ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);
    4 U* z- m1 q7 L& M  [( t6 w$ J& S7 U
  6. 10 % c) t9 m+ n- z' a- [# ^' e  |+ k9 G
  7. 11      ...19   if (ValidPage == PAGE1)  /* Page0 valid */# E  X' j, h3 Y7 }: e
  8. 20   {
    4 N( d; A0 ~6 Z
  9. 21     /* New page address where variable will be moved to */6 Q$ b0 X7 p9 P7 S+ ]) Y- B9 F
  10. 22     NewPageAddress = PAGE0_BASE_ADDRESS;, ~# w' p0 B/ k6 l: Q: S
  11. 23
    8 z1 u) X- [: |0 o/ Q: Q
  12. 24     /* Old page address where variable will be taken from */
    7 P' J3 |: Y  y  v$ u
  13. 25     OldPageAddress = PAGE1_BASE_ADDRESS;+ s6 _: K/ u' n  i9 b: s$ |0 ?
  14. 26   }: _/ k6 q( X+ U4 s4 T
  15.     ...
    5 t1 Q$ x( X' i) U7 }+ m% V
  16. 32   /* Set the new Page status to RECEIVE_DATA status */
    , i. B1 F" A; T4 Q$ k8 a+ x
  17. 33   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, RECEIVE_DATA);3 Z$ t  k8 U+ x, ^0 V
  18.     ...* q# ?2 m! W1 F
  19. 40   InitCurrWrAddress();//aft 重新初始化写地址+ r' ?. p/ m8 V+ c; L+ R. P
  20. 41   /* Write the variable passed as parameter in the new active page */0 ?* s/ f8 e) P+ Y
  21. 42   EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
    * `" R$ C+ }! i' g" S
  22.     ....& i" V# c9 a* Q
  23. 49   /* Transfer process: transfer variables from old to the new active page */
    ( Z) }+ m9 h1 R
  24. 50   for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++)( u. K/ f$ n, P3 w
  25. 51   {
    3 y" l7 B3 P* f
  26. 52     if (VirtAddVarTab[VarIdx] != VirtAddress)  /* Check each variable except the one passed as parameter */7 h) h+ ?$ v4 N) L. D
  27. 53     {8 f, w  b1 z/ g* c; e( e7 n! j0 y
  28. 54       /* Read the other last variable updates */
    ( k" ]3 T7 h  ~0 K( ]5 T* E  _
  29. 55       ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);; C" a- i% A9 p, Y) H
  30.       ...67     }: Q+ ^  h3 P6 g4 ]" d
  31. 68   }
    5 v" w6 R3 V2 G' G' g. R  F
  32. 69 2 A; h/ g9 T; e: F( l
  33. 70   /* Erase the old Page: Set old Page status to ERASED status */
      S. L7 D* z  c( {9 D- j
  34. 71   FlashStatus = FLASH_ErasePage(OldPageAddress);  e2 V, v" Q9 v7 z
  35. 72   /* If erase operation was failed, a Flash error code is returned */
    ) V& Y- j7 C+ @1 z" w# a" ~
  36. 73   if (FlashStatus != FLASH_COMPLETE)
    ; w) L) `$ w5 V1 H+ I* P
  37. 74   {" \& h6 q: I) B: D
  38. 75     return FlashStatus;
    $ c* C5 `2 M% O8 z+ _
  39. 76   }
      O! ^. U$ m. N1 j& I
  40. 77
    - K% j& ?, p: Y& S" k5 f- g; w6 f
  41. 78   /* Set new Page status to VALID_PAGE status */9 B* l2 s, |* A0 C5 K
  42. 79   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, VALID_PAGE);4 ]: ?4 I2 ?% a: T2 C( ?0 J
  43. 80   /* If program operation was failed, a Flash error code is returned */
    7 q# C# ^3 @+ A) V
  44. 81   if (FlashStatus != FLASH_COMPLETE)# f6 i2 d, ]2 D
  45. 82   {7 N  X' h  W0 K/ R: e
  46. 83     return FlashStatus;
    / Z' a* d/ U5 s) U9 Z! T& Y3 D  ?
  47. 84   }" @1 E5 |* M% X, Y
  48. 85 4 q( g( U& n+ N8 W0 Y
  49. 86   /* Return last operation flash status */
    $ j7 ?5 L+ |2 w2 S8 ?( a
  50. 87   return FlashStatus;1 f4 U6 j# e1 c9 r7 r. f) j
  51. 88 }
复制代码
/ C& z, V5 ~( k3 q7 l* u3 V/ A
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));( P% I, B4 l# d7 {. Y  u
  2. ! a4 x6 @) r' U6 A$ e
  3.   /* Get the valid Page end Address */
    4 w- g$ e# c+ a# S
  4.   //Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));
    ! s7 n+ v) e% e2 F* h
  5.   Address=CurWrAddress-2;
    ; k% w  c8 l: Q7 k6 Y2 w/ A; U$ y
  6. ( \: |$ F$ t: _2 `; {# A
  7.   /* Check each active page address starting from end */
    1 S  a9 D, L2 i  ^7 [! F" J
  8.   while (Address > (PageStartAddress + 2))
    & _9 t! z) t* y7 C& F1 }
  9.   {% ]% V* p. U; G9 W- e& d" h  p# y
  10.     /* Get the current location content to be compared with virtual address */7 u, Z/ n3 K6 t- L2 u
  11.     AddressValue = (*(__IO uint16_t*)Address);8 k" H( u! n4 g3 c! r% Z8 m1 N3 Q
  12. # ~& l; B  n; l, B  B9 P7 u
  13.     /* Compare the read address with the virtual address */
    + f" `+ j7 m; c. j$ P
  14.     if (AddressValue == VirtAddress): _; F1 c1 N7 A2 e+ i* _
  15.     {: G" T8 I  B# F# P
  16.       /* Get content of Address-2 which is variable value */& |( L# W5 G, J# I+ u, j5 @/ @) ]
  17.       *Data = (*(__IO uint16_t*)(Address - 2));* ~+ k$ D- Y( I6 I9 E

  18. , d" ]1 g# A( G, ~7 e$ H
  19.       /* In case variable value is read, reset ReadStatus flag */
    $ n" {, y+ r/ f$ {
  20.       ReadStatus = 0;
    + D9 V/ \: [/ N" ~- b. Z

  21. . f6 V& M, U  \. ~3 j+ A  v
  22.       break;0 n- W- b% A5 Y: [$ R# u  x
  23.     }/ N- Z' ^$ b) u+ R: N. v
  24.     else
    4 `7 ]6 w4 p( |  |& L
  25.     {
    / E: p, E2 C; u/ h7 s
  26.       /* Next address location */9 c  D* i4 c$ M( p9 R
  27.       Address = Address - 4;
    # T8 {* O9 ?) B! q( V: ^2 H% V8 f6 B
  28.     }, w( G* Z. L$ R$ w2 B
  29.   }
复制代码
+ U/ F+ A$ }5 }. ^+ Z6 b: S

  X8 c, P6 B0 W! ^  B3 U) b
我的做法是,修改 EE_ReadVariable函数,在读取数据的时候,如果PAGE0或者PAGE1有一个状态为正在接受,那么Address就用官方Demo给的语句赋值。这时解决全部这篇所说bug的方法。
  1. //之前加上读取PageStatus0和1的语句
      U, c& o6 `+ S+ F+ j4 p$ R7 l8 R; ]
  2. 1     /* Get the valid Page end Address */
      e: {9 h) N. S: a( B9 C. F
  3. 2     if((PageStatus0 == RECEIVE_DATA)||(PageStatus1 == RECEIVE_DATA))//当在页传输时,地址放在有效页的末尾来搜索所有的存储信息
    8 [; {4 X3 ^1 {) A" L1 S! \
  4. 3     {/ c. K' }# M! x6 u
  5. 4         Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));
    ( ~4 l/ n$ n% Q$ R
  6. 5     }
    9 i  X+ S$ _3 T3 n
  7. 6     else
    / Z  t/ Q5 G) [9 W9 k
  8. 7     {
    * i) n5 ~4 [, q
  9. 8         Address=CurWrAddress-2;
    5 D# h% B  G: i2 N  O. F
  10. 9     }
复制代码
' Q3 J, W) o; K$ ?' m4 |8 M
- p9 R; I+ ~; o6 ^3 E
目前遇到的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。

. Z: u9 T! K& N+ j& Y. j, n
加上stm32的掉电检测保存数据,上电后,检测扇区剩余空间,不够下一次保存的话,提前换页,因为stm32f4擦除一个扇区要1s多,掉电那些时间根本不够,另外掉电后,阈值2.7v的话,测试发现,保守估计可保存1000个字,和外围器件,电容有关。
最后上张stm32flash保存数据的图,0x8040000是扇区6的首地址,用于保存这一页的状态00 00为有效页标志,0x8040004和05保存的是数据,06和07的01 A1保存的是变量虚拟地址,实际上是0XA101,因为STM32是小端模式,低位字节位排放在低地址端,高位字节排放在高位地址端。

, a$ q0 A( r" t" A2 }5 ]; P' T0 H) w 785853-20170318152906963-123214932.jpg
收藏 评论0 发布时间:2022-1-25 22:40

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版