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

【经验分享】用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 */
    5 V1 K  D7 a! \; K
  2. 2 uint16_t DataVar = 0;
    8 D7 w; x; G7 B8 |; t3 G8 c0 F
  3. 3 uint32_t CurWrAddress;
    7 U. X, ^! o9 Z2 o8 r
  4. 4 /* Virtual address defined by the user: 0xFFFF value is prohibited */1 }  X) h0 N4 f: S
  5. 5 extern uint16_t VirtAddVarTab[NumbOfVar];
复制代码
9 q9 \) @2 P. ?( }& L9 B$ C0 T: u
第3行 把CurWrAddress初始化为0,就是一个bug。修改后的代码, 把InitCurrWrAddress()函数放在了__EE_Init()之后,也就是说只要__EE_Init()函数用到了CurWrAddress,那么CurWrAddress = 0,有某种情况下,这是个灾难。
  1. uint16_t EE_Init(void)( t" f- i* \0 N) Z
  2. {
    7 r: \* k; s8 n$ B6 G+ u
  3. uint16_t FlashStatus;8 o6 W$ W4 I9 b  K) n3 P, U
  4.    
    $ ~$ l5 U5 h5 s+ W
  5.    FlashStatus=__EE_Init();! Q+ i# b5 K5 @. X
  6.    
    " e5 ?( M7 y5 U$ E4 n: q
  7.    InitCurrWrAddress();# S% s0 k5 ?2 \: E: ~. K5 q- ?
  8.    
    ( s% ?9 K+ X, u+ s5 F( b9 a6 m
  9.    return(FlashStatus);0 C. W6 e( V& e2 O; y3 z; e& A
  10. }
复制代码

9 c& M& T$ `: o3 H- H# P- V
接下来,看__EE_Init()函数
  1. 1   uint16_t __EE_Init(void)
    ( B1 @$ R- p$ g: K5 p$ s1 F
  2.   2    {      ... 13   . c% Y  t! X' r+ s) \' }# i
  3. 14    /* Check for invalid header states and repair if necessary */3 I9 s( r) b+ g0 Q" j: d* |
  4. 15     switch (PageStatus0)
    7 j5 P4 E1 V7 j( D6 E7 E& }4 t; y
  5. 16     {; [) w/ j* u/ r
  6. 17       case ERASED:             ...
    . N: Z. \" q/ p
  7. 33       case RECEIVE_DATA:. V% |- n* C  Z: g0 y+ j2 n& K& |
  8. 34         if (PageStatus1 == VALID_PAGE) /* Page0 receive, Page1 valid */
    * [5 f& R  t% B
  9. 35         {
    9 F. o) q2 Y  X- I* b1 Z8 M+ A- s
  10.           ... 43             if (VarIdx != x)
    - V5 B+ e* y+ O9 N6 h
  11. 44             {) o8 h! D6 q. p; f) a/ \/ F! N
  12. 45               /* Read the last variables' updates */
    0 I) i- g6 h2 t8 O, ~9 h( [' N: u
  13. 46               ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
    2 [7 ~: ?0 a& d! k! o# e
  14.                     .... 57               } 59           }4 Q  n8 ?& s; z! p( u# w1 V
  15.              ...   
    + J' Y9 t' M$ I6 l" s3 ?) s, [
  16. 85       case VALID_PAGE:$ k. C9 `  W0 U+ ^$ Y( c& W. V
  17. 95         {103            if (VarIdx != x)
    + N! s' }& E5 @7 b4 j; ]4 o
  18. 104            {" g4 o& A% H9 p. }
  19. 105              /* Read the last variables' updates */$ k1 s9 X6 _+ ^( ^
  20. 106              ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
    # c  L% `6 t* P( e/ j) n8 W
  21.           ...118            }1 V; j3 @( h% \4 W$ p- I; R/ w4 X
  22. 119          }
    3 I# p2 \1 I+ \4 h) L$ z* u% \
  23. 120         ....140    }
    * W) [- f0 S2 ~- O" z
  24. 141  
    0 {( L. t: Y" h+ J
  25. 142    return FLASH_COMPLETE;' J/ j% t( v9 K# H
  26. 143  }
复制代码
/ N% S. V* K3 l8 g

* z* L9 I  Z0 v# o
我保留的出灾难的两种情况,第一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)
    : j/ f% d3 w2 z5 l( r, X; @
  2. 2 {  c* h; c: ?4 u) Q* H
  3.         ...7 P9 U3 A( V& x4 Q1 ^/ D( R" Q
  4. 8   /* Get active Page for read operation */5 `! ^3 s- `. t0 ^" i" z) ?
  5. 9   ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);
    % u. z5 h1 n6 h( z& n4 q- C& o
  6. 10 ) ]5 j( }7 v. _* r8 Z
  7. 11      ...19   if (ValidPage == PAGE1)  /* Page0 valid */
    1 W" c* }$ [' Q$ S
  8. 20   {; o' u1 C' h1 O9 a0 S- F! j! r
  9. 21     /* New page address where variable will be moved to */
    1 E! C3 l. y) r+ e- ]4 V
  10. 22     NewPageAddress = PAGE0_BASE_ADDRESS;
    2 w( r* n& h# h! D! B- B
  11. 23
    " _: U+ m3 a3 ~. U% k0 r) l$ |3 F: ?
  12. 24     /* Old page address where variable will be taken from */
    6 G( {( g& i9 s+ T4 v
  13. 25     OldPageAddress = PAGE1_BASE_ADDRESS;
    - s& Z! y+ K; k
  14. 26   }& |) f) t+ T$ M6 Y0 K
  15.     ...
    # H7 q  l& z# I5 X$ S8 ^
  16. 32   /* Set the new Page status to RECEIVE_DATA status */
    7 Y9 v. p% i, k0 f
  17. 33   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, RECEIVE_DATA);% C; V4 A* e. {/ m$ q
  18.     ...) a' x& K7 _# l2 s* E" j& k( q6 j4 U
  19. 40   InitCurrWrAddress();//aft 重新初始化写地址
    * M% b1 i& }- _- G, h5 m: z
  20. 41   /* Write the variable passed as parameter in the new active page */) o  b% g% d+ H. ^, z
  21. 42   EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
    $ h- q: I8 Y; }3 B
  22.     ....5 t/ V& [. F' p# U8 f5 H8 j
  23. 49   /* Transfer process: transfer variables from old to the new active page */; L& B: L; l) r5 u
  24. 50   for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++)- X, {. n8 ]/ D9 y8 {  Y
  25. 51   {
    ( @5 H0 z8 T) x5 u5 d: B
  26. 52     if (VirtAddVarTab[VarIdx] != VirtAddress)  /* Check each variable except the one passed as parameter */
    + ^1 T) |8 ~6 V% v: \
  27. 53     {1 V+ }  K0 G) x  [2 }8 n
  28. 54       /* Read the other last variable updates */
    ( u  ?8 }+ N/ P( H9 A
  29. 55       ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);9 W  D& P# {: [, M
  30.       ...67     }
    ) X7 p$ {( s. q
  31. 68   }+ p% g% N0 d9 s4 o
  32. 69
    ! i3 R0 `* M8 J; v. l. v+ U
  33. 70   /* Erase the old Page: Set old Page status to ERASED status */! R0 E1 C( T  E3 E8 T
  34. 71   FlashStatus = FLASH_ErasePage(OldPageAddress);9 s4 d# l9 P2 c) r
  35. 72   /* If erase operation was failed, a Flash error code is returned */- p5 Q' t3 s- r! C2 n9 i- K
  36. 73   if (FlashStatus != FLASH_COMPLETE)
    + y0 J- l" M7 B  o
  37. 74   {
    + w$ H3 S6 `* w  r6 f2 P5 f
  38. 75     return FlashStatus;) g- b9 {6 x" i+ G  z
  39. 76   }8 K2 G  U- i- u; O/ P4 \8 Y
  40. 77 - e' @5 W! e3 c
  41. 78   /* Set new Page status to VALID_PAGE status */
    " d4 t  d( `7 [9 `! c) j
  42. 79   FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, VALID_PAGE);
    1 r# c3 c* |0 Z; u/ }# E
  43. 80   /* If program operation was failed, a Flash error code is returned */
    9 J: m6 j( U3 R9 J; e" D" m
  44. 81   if (FlashStatus != FLASH_COMPLETE); S  Q' V! I4 O( u$ e
  45. 82   {1 O  Q' F4 B7 c4 c+ g
  46. 83     return FlashStatus;
    & e8 D+ f* O6 K% |, x' A, F. ^
  47. 84   }% H! M2 `: y- E0 P( v* G
  48. 85
    " _4 o, x) h5 l4 |; C: b
  49. 86   /* Return last operation flash status */
    7 e9 }! H' R$ u( C
  50. 87   return FlashStatus;5 [% u7 J1 m/ z1 X, V% U
  51. 88 }
复制代码

% `) I1 w; t* F# q. T
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));
    ; t6 M; o; l5 f8 T3 c+ \

  2. # M/ T. b0 C: J
  3.   /* Get the valid Page end Address */1 o( Z+ g6 L8 g* x3 T, Z6 O" I  r
  4.   //Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));
    # F+ L1 w1 I. `" Y( _/ v$ L
  5.   Address=CurWrAddress-2;
    8 o- J3 m. V5 q3 ?
  6. ' l# q/ j& W/ \/ S6 {/ n0 z' Y
  7.   /* Check each active page address starting from end */1 C5 V# Q/ c8 G" T1 L
  8.   while (Address > (PageStartAddress + 2))
    ) U  u) Q+ i* b% l# s
  9.   {
    ( J( D% j3 r  j( Q" c
  10.     /* Get the current location content to be compared with virtual address */: d  h' }$ y: i( l( }" M- R. c3 d
  11.     AddressValue = (*(__IO uint16_t*)Address);+ X4 u1 f# e; x
  12. 5 j% U6 z: @  A7 T% q
  13.     /* Compare the read address with the virtual address */, T7 }/ ^, t) w" D7 I
  14.     if (AddressValue == VirtAddress)* L$ q: ^9 n  n' E2 i+ ], z7 q
  15.     {
    1 ?, Y2 G/ b$ V4 t/ t- ~
  16.       /* Get content of Address-2 which is variable value */
    ! l1 j# R; E/ Q
  17.       *Data = (*(__IO uint16_t*)(Address - 2));
    # B. X  a9 W7 q1 w
  18. + a3 n4 m5 ?, e8 F' l) A* W$ t
  19.       /* In case variable value is read, reset ReadStatus flag */2 O" x% l* i+ L: F
  20.       ReadStatus = 0;  {$ t. \0 |3 B1 o3 y

  21. # I& `# W5 U  L: ^6 z; e
  22.       break;
    3 g% a% r* u. ~3 u
  23.     }4 W' r+ C9 S0 a) c- n' c
  24.     else$ r/ @+ J: J8 {8 h
  25.     {
    0 G4 U8 d4 Z( U; ?$ w" Q
  26.       /* Next address location */
    / ^& w0 h; y+ D* [! F0 M
  27.       Address = Address - 4;6 u, c& z6 J9 j3 Z- b: m" i
  28.     }
    # b% r+ D, G3 B6 V, p  x; Q. {7 t
  29.   }
复制代码
. R' b  b. n( @9 B( P0 N
7 a" r) a" g) z# I; \
我的做法是,修改 EE_ReadVariable函数,在读取数据的时候,如果PAGE0或者PAGE1有一个状态为正在接受,那么Address就用官方Demo给的语句赋值。这时解决全部这篇所说bug的方法。
  1. //之前加上读取PageStatus0和1的语句. ?( L/ l- u, x9 I2 p
  2. 1     /* Get the valid Page end Address */: ~/ s0 b  u) p: T4 C+ S
  3. 2     if((PageStatus0 == RECEIVE_DATA)||(PageStatus1 == RECEIVE_DATA))//当在页传输时,地址放在有效页的末尾来搜索所有的存储信息
    ; h' x/ S2 _6 r/ O- c4 f6 c
  4. 3     {
    1 T5 [- `: U( _0 I' |
  5. 4         Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));6 B7 @2 i  K. |& }
  6. 5     }
    2 K7 F6 d( G: I$ ~: m9 _( d
  7. 6     else2 P: J# I) \. D* \/ V) o8 T( a
  8. 7     {
      d/ n$ y- u6 W& e$ [; l0 l& P( Q
  9. 8         Address=CurWrAddress-2;
    9 K) I) W% }" z! d
  10. 9     }
复制代码

2 a2 B1 v& j5 ]1 h
; n, d3 F, s* M+ ]) B/ b
目前遇到的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。

6 W- g  M; A5 O  F2 l+ U8 [% E
加上stm32的掉电检测保存数据,上电后,检测扇区剩余空间,不够下一次保存的话,提前换页,因为stm32f4擦除一个扇区要1s多,掉电那些时间根本不够,另外掉电后,阈值2.7v的话,测试发现,保守估计可保存1000个字,和外围器件,电容有关。
最后上张stm32flash保存数据的图,0x8040000是扇区6的首地址,用于保存这一页的状态00 00为有效页标志,0x8040004和05保存的是数据,06和07的01 A1保存的是变量虚拟地址,实际上是0XA101,因为STM32是小端模式,低位字节位排放在低地址端,高位字节排放在高位地址端。
! Q# T. M( f1 q9 N% k
785853-20170318152906963-123214932.jpg
赞 收藏 评论0 发布时间:2022-1-25 22:40

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版