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

【经验分享】用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 */. S- z& o" J( u1 r5 K6 _
  2. 2 uint16_t DataVar = 0;) h, x6 e1 c& w4 r' g8 W
  3. 3 uint32_t CurWrAddress;
    6 k& @8 Y: Y7 M2 }. Y" R* t
  4. 4 /* Virtual address defined by the user: 0xFFFF value is prohibited */. s& x' i4 y8 x& C% V
  5. 5 extern uint16_t VirtAddVarTab[NumbOfVar];
复制代码

) B7 h/ g* ]5 B5 S9 V- h( I
第3行 把CurWrAddress初始化为0,就是一个bug。修改后的代码, 把InitCurrWrAddress()函数放在了__EE_Init()之后,也就是说只要__EE_Init()函数用到了CurWrAddress,那么CurWrAddress = 0,有某种情况下,这是个灾难。
  1. uint16_t EE_Init(void)
    5 U3 p1 J, C3 |6 a) D
  2. {5 w' m+ Y! j2 t) D% F
  3. uint16_t FlashStatus;0 o) G  U0 J. U2 B
  4.    
    % P0 ]" b  u. @3 ^
  5.    FlashStatus=__EE_Init();
    , U3 I% B- ^- J& T( v: q1 z
  6.    * D1 O0 x" A% G4 s0 j4 L0 U4 g
  7.    InitCurrWrAddress();/ R  J$ z% ]9 j9 U* [7 M$ Q* m
  8.    / A" ~2 W  x- C  A. T! D4 X4 q
  9.    return(FlashStatus);. @0 C9 L. H) x$ v9 a7 z
  10. }
复制代码

; U* t: }5 F" k
接下来,看__EE_Init()函数
  1. 1   uint16_t __EE_Init(void)' D+ M& d( R" ?5 X1 g) s8 C
  2.   2    {      ... 13   5 ]8 T) c8 f# |
  3. 14    /* Check for invalid header states and repair if necessary */
    ! N, _) f- {: `6 D9 Y: F
  4. 15     switch (PageStatus0)2 V& G. T& L3 ?! l
  5. 16     {5 ?8 W  A! }. N7 W
  6. 17       case ERASED:             ..." _; ~. J' Y6 V2 _# m
  7. 33       case RECEIVE_DATA:8 S& a$ \' C' o' e
  8. 34         if (PageStatus1 == VALID_PAGE) /* Page0 receive, Page1 valid */3 p7 y- {) M) M+ y' f
  9. 35         {7 R; @4 e) k& F* `* v8 k
  10.           ... 43             if (VarIdx != x)
    + p4 u3 m7 z  C3 ~" a8 u
  11. 44             {
    - m% N+ W3 B8 X1 k% e
  12. 45               /* Read the last variables' updates */! \" f: P% w; T5 x/ G
  13. 46               ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
    ' k8 H! M; S- O) O! ^5 F: e- ^
  14.                     .... 57               } 59           }; d, l6 V& h7 ^% p3 u: M2 j) [7 f
  15.              ...   
    0 K/ m3 K4 g/ Y4 Y5 {" v3 A
  16. 85       case VALID_PAGE:
    - ~# B, o1 v2 S: ^/ _
  17. 95         {103            if (VarIdx != x)  @9 d/ b" w( H2 A5 U3 _
  18. 104            {9 I0 U  U" v0 y8 G! b
  19. 105              /* Read the last variables' updates */
    6 C2 p0 x) _: [; H) g9 ~! X
  20. 106              ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
    4 {- c( r6 G' x0 m0 T& |
  21.           ...118            }8 r+ d8 S$ C. O3 ~7 h  B# \4 @
  22. 119          }. m* I6 r9 ^, n
  23. 120         ....140    }1 W7 K# k( o' d% ]
  24. 141  
      s9 q. M# M  T) v7 `
  25. 142    return FLASH_COMPLETE;
    " R# M( b( [7 u- D/ x, U+ U
  26. 143  }
复制代码
8 Q: q% e# O4 a8 w4 z3 K: ~

" |  X) o- p% t6 I
我保留的出灾难的两种情况,第一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)
    & s0 Q2 \3 e- j6 g
  2. 2 {
    0 a" S, m% u4 Z2 z! i: E
  3.         ...' i2 s, B9 ?4 ^% @4 V, D
  4. 8   /* Get active Page for read operation */
    & t+ \( W) K4 X) F  V! ^
  5. 9   ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);
    ( S6 |1 K& J$ ?, u' o5 i
  6. 10 3 M" E( `8 q: c% \
  7. 11      ...19   if (ValidPage == PAGE1)  /* Page0 valid */: T# g  t) d$ V) I% C  h
  8. 20   {
    6 E1 G5 B& {9 u6 N; O% h
  9. 21     /* New page address where variable will be moved to */- D4 W  t2 ~: F# e% \* W
  10. 22     NewPageAddress = PAGE0_BASE_ADDRESS;& p8 r  G/ c/ D) n* V
  11. 23 7 c, M% i/ o2 s, |0 L1 e1 U, s
  12. 24     /* Old page address where variable will be taken from */1 w, C& Z, W2 p4 B6 d# G; {: {7 n$ G
  13. 25     OldPageAddress = PAGE1_BASE_ADDRESS;
    , t2 r8 W1 X- Z1 z
  14. 26   }8 v, b( m* T' o
  15.     ...$ ^. _. o0 }4 R1 V( j3 R/ L3 }9 K
  16. 32   /* Set the new Page status to RECEIVE_DATA status */# G" r4 p6 A4 G6 D
  17. 33   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, RECEIVE_DATA);
    6 C6 V% V* o0 W0 v
  18.     ...
    & k! d; f5 M6 M0 d* K, I
  19. 40   InitCurrWrAddress();//aft 重新初始化写地址
    ) e8 [: H* S( q0 P
  20. 41   /* Write the variable passed as parameter in the new active page */3 h4 z0 u9 C/ U& g3 I
  21. 42   EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);2 S7 J$ O) J8 _
  22.     ....
    0 R% m1 Y+ ]+ s
  23. 49   /* Transfer process: transfer variables from old to the new active page */
    % v/ X8 E6 c* h6 z
  24. 50   for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++)8 q  A' B; i  p% M" {
  25. 51   {1 V/ Z3 Z+ W6 d. W) M( t
  26. 52     if (VirtAddVarTab[VarIdx] != VirtAddress)  /* Check each variable except the one passed as parameter */8 n2 u6 O' _1 l0 r
  27. 53     {
    6 E6 _) j3 @$ T9 f; k: h; @
  28. 54       /* Read the other last variable updates */
    ) v( M' ^+ G. ]
  29. 55       ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);4 ^/ }8 I3 r4 r5 U3 ~9 Y5 `
  30.       ...67     }
    " M7 ]8 g$ e0 i" M& F4 W  ~
  31. 68   }
    1 q0 P% c+ R/ U$ z/ g
  32. 69
    ! J) G0 Q  j$ w2 p$ x$ x
  33. 70   /* Erase the old Page: Set old Page status to ERASED status */$ r! @' ]" v. G0 x0 {. @# l' \
  34. 71   FlashStatus = FLASH_ErasePage(OldPageAddress);( Q% ?2 }3 }, G: I8 K) M/ c; _
  35. 72   /* If erase operation was failed, a Flash error code is returned */
    & W5 f9 n: \; Q
  36. 73   if (FlashStatus != FLASH_COMPLETE)
      A  N/ p& W  Y1 k
  37. 74   {
    , v0 }; ~' u& |' A2 R1 \
  38. 75     return FlashStatus;
    : f# Z6 F$ s3 y; n3 }8 }3 Y
  39. 76   }
    . Y: c0 k+ }+ d( r+ q; ?6 Q
  40. 77   ~/ I! A0 h) N' |
  41. 78   /* Set new Page status to VALID_PAGE status */
    2 H0 V% A- O# ^' F. z  S
  42. 79   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, VALID_PAGE);$ x; X, ~$ [$ c$ E; N& I3 m. i
  43. 80   /* If program operation was failed, a Flash error code is returned */
    # m1 s6 s, f- E( C
  44. 81   if (FlashStatus != FLASH_COMPLETE)  S" ]0 H& V! S* `; x' o/ B
  45. 82   {
    ' o  d5 ^( H8 Y& l
  46. 83     return FlashStatus;
    7 Y9 T% M% u8 }( w0 c% G7 _
  47. 84   }
    & I6 A: C+ U" {+ P! D
  48. 85
    8 y5 m, F& x4 q' a
  49. 86   /* Return last operation flash status */+ u  S6 z/ G  {( h
  50. 87   return FlashStatus;1 W3 ?$ R, P2 {$ w
  51. 88 }
复制代码
6 R" U5 k/ }! X+ g5 s
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));7 ]: H; M; J" B

  2. 8 N" n8 R; p5 b( c" G2 K
  3.   /* Get the valid Page end Address */4 w/ @# E1 q0 p, {6 o: r8 D
  4.   //Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));
    4 A3 W9 X: S2 _( D, J+ c! d+ s5 s8 j
  5.   Address=CurWrAddress-2;3 q) R- e3 `! X, V" g* `
  6. 9 h& I4 Z( @- V" e# u. d
  7.   /* Check each active page address starting from end */
    , d3 O) a, c4 W. f5 f, p
  8.   while (Address > (PageStartAddress + 2))+ j. e6 b: M, G: s: n) r3 k
  9.   {- N4 h5 h  D2 q
  10.     /* Get the current location content to be compared with virtual address */* x, w0 ]; ~- i0 P+ S4 z1 r( f/ \6 c
  11.     AddressValue = (*(__IO uint16_t*)Address);: h- Y! L9 V3 |, `8 ?
  12. ! B8 o3 e+ _2 V9 _
  13.     /* Compare the read address with the virtual address */8 C  w3 I. L- O( x- P, p
  14.     if (AddressValue == VirtAddress)3 C# L* N4 S- O" V/ i+ q( Y
  15.     {
    7 O9 c: s7 [$ `$ K6 T1 v
  16.       /* Get content of Address-2 which is variable value */* \) ]3 l' F9 `
  17.       *Data = (*(__IO uint16_t*)(Address - 2));. M4 u, L  B. f+ A" ]/ u! M. W

  18. 5 Y  f$ U/ x0 \* ?4 V5 R
  19.       /* In case variable value is read, reset ReadStatus flag */
    2 s$ `' J, G# |: ]3 c5 ~; [
  20.       ReadStatus = 0;
    + J1 m+ a. [$ C. X/ S6 f* z' n% {
  21.   s1 _8 [% {2 ~* R8 d' v" G; C
  22.       break;
    ( {( q6 @* D+ O0 `% f/ n
  23.     }
    # \5 o& p( ~, L% R; K
  24.     else
    2 n+ @: {+ Q/ y+ L. |* P5 y2 K
  25.     {
    , N7 z* ^. M% E- v! D: s
  26.       /* Next address location */
    ) D, O9 v6 y/ q0 n/ n4 y1 u: a
  27.       Address = Address - 4;6 ?3 f1 ~% Z# u2 X7 _+ Q, q; J8 C8 m
  28.     }; w1 R. K, h+ Z3 e4 D: E' _
  29.   }
复制代码

5 G+ d& n' w& {8 D% ?& m. ]
) o9 d7 o  O6 {( x/ c0 L- e! q
我的做法是,修改 EE_ReadVariable函数,在读取数据的时候,如果PAGE0或者PAGE1有一个状态为正在接受,那么Address就用官方Demo给的语句赋值。这时解决全部这篇所说bug的方法。
  1. //之前加上读取PageStatus0和1的语句/ V; G7 {, ?8 x/ x
  2. 1     /* Get the valid Page end Address */+ R! X* X$ M/ k) [9 s# |
  3. 2     if((PageStatus0 == RECEIVE_DATA)||(PageStatus1 == RECEIVE_DATA))//当在页传输时,地址放在有效页的末尾来搜索所有的存储信息/ s; u8 y: t; P9 F
  4. 3     {
    - {& e2 {4 o! Q( @( j7 q8 t1 h
  5. 4         Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));0 c" B) `" V8 b) {9 s, U% ]
  6. 5     }
    & L$ J1 t4 l$ R8 n* H
  7. 6     else
    8 d5 I3 F% ?- r. e6 _  n
  8. 7     {
    3 n! o9 N3 n( }/ d( |' Z1 \
  9. 8         Address=CurWrAddress-2;
    ! N% z, r2 y$ Q
  10. 9     }
复制代码
5 O4 d* p% J2 B1 v. C6 m) k

0 f+ q6 t/ N1 _# K+ P) ]
目前遇到的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。
  l( c( C) v$ q# }0 A( Z" @
加上stm32的掉电检测保存数据,上电后,检测扇区剩余空间,不够下一次保存的话,提前换页,因为stm32f4擦除一个扇区要1s多,掉电那些时间根本不够,另外掉电后,阈值2.7v的话,测试发现,保守估计可保存1000个字,和外围器件,电容有关。
最后上张stm32flash保存数据的图,0x8040000是扇区6的首地址,用于保存这一页的状态00 00为有效页标志,0x8040004和05保存的是数据,06和07的01 A1保存的是变量虚拟地址,实际上是0XA101,因为STM32是小端模式,低位字节位排放在低地址端,高位字节排放在高位地址端。
, l9 g- A+ @* _! }! ?3 T
785853-20170318152906963-123214932.jpg
收藏 评论0 发布时间:2022-1-25 22:40

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版