|
最开始用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 */
5 V1 K D7 a! \; K - 2 uint16_t DataVar = 0;
8 D7 w; x; G7 B8 |; t3 G8 c0 F - 3 uint32_t CurWrAddress;
7 U. X, ^! o9 Z2 o8 r - 4 /* Virtual address defined by the user: 0xFFFF value is prohibited */1 } X) h0 N4 f: S
- 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,有某种情况下,这是个灾难。 - uint16_t EE_Init(void)( t" f- i* \0 N) Z
- {
7 r: \* k; s8 n$ B6 G+ u - uint16_t FlashStatus;8 o6 W$ W4 I9 b K) n3 P, U
-
$ ~$ l5 U5 h5 s+ W - FlashStatus=__EE_Init();! Q+ i# b5 K5 @. X
-
" e5 ?( M7 y5 U$ E4 n: q - InitCurrWrAddress();# S% s0 k5 ?2 \: E: ~. K5 q- ?
-
( s% ?9 K+ X, u+ s5 F( b9 a6 m - return(FlashStatus);0 C. W6 e( V& e2 O; y3 z; e& A
- }
复制代码
9 c& M& T$ `: o3 H- H# P- V接下来,看__EE_Init()函数 - 1 uint16_t __EE_Init(void)
( B1 @$ R- p$ g: K5 p$ s1 F - 2 { ... 13 . c% Y t! X' r+ s) \' }# i
- 14 /* Check for invalid header states and repair if necessary */3 I9 s( r) b+ g0 Q" j: d* |
- 15 switch (PageStatus0)
7 j5 P4 E1 V7 j( D6 E7 E& }4 t; y - 16 {; [) w/ j* u/ r
- 17 case ERASED: ...
. N: Z. \" q/ p - 33 case RECEIVE_DATA:. V% |- n* C Z: g0 y+ j2 n& K& |
- 34 if (PageStatus1 == VALID_PAGE) /* Page0 receive, Page1 valid */
* [5 f& R t% B - 35 {
9 F. o) q2 Y X- I* b1 Z8 M+ A- s - ... 43 if (VarIdx != x)
- V5 B+ e* y+ O9 N6 h - 44 {) o8 h! D6 q. p; f) a/ \/ F! N
- 45 /* Read the last variables' updates */
0 I) i- g6 h2 t8 O, ~9 h( [' N: u - 46 ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
2 [7 ~: ?0 a& d! k! o# e - .... 57 } 59 }4 Q n8 ?& s; z! p( u# w1 V
- ...
+ J' Y9 t' M$ I6 l" s3 ?) s, [ - 85 case VALID_PAGE:$ k. C9 ` W0 U+ ^$ Y( c& W. V
- 95 {103 if (VarIdx != x)
+ N! s' }& E5 @7 b4 j; ]4 o - 104 {" g4 o& A% H9 p. }
- 105 /* Read the last variables' updates */$ k1 s9 X6 _+ ^( ^
- 106 ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);
# c L% `6 t* P( e/ j) n8 W - ...118 }1 V; j3 @( h% \4 W$ p- I; R/ w4 X
- 119 }
3 I# p2 \1 I+ \4 h) L$ z* u% \ - 120 ....140 }
* W) [- f0 S2 ~- O" z - 141
0 {( L. t: Y" h+ J - 142 return FLASH_COMPLETE;' J/ j% t( v9 K# H
- 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 static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data)
: j/ f% d3 w2 z5 l( r, X; @ - 2 { c* h; c: ?4 u) Q* H
- ...7 P9 U3 A( V& x4 Q1 ^/ D( R" Q
- 8 /* Get active Page for read operation */5 `! ^3 s- `. t0 ^" i" z) ?
- 9 ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);
% u. z5 h1 n6 h( z& n4 q- C& o - 10 ) ]5 j( }7 v. _* r8 Z
- 11 ...19 if (ValidPage == PAGE1) /* Page0 valid */
1 W" c* }$ [' Q$ S - 20 {; o' u1 C' h1 O9 a0 S- F! j! r
- 21 /* New page address where variable will be moved to */
1 E! C3 l. y) r+ e- ]4 V - 22 NewPageAddress = PAGE0_BASE_ADDRESS;
2 w( r* n& h# h! D! B- B - 23
" _: U+ m3 a3 ~. U% k0 r) l$ |3 F: ? - 24 /* Old page address where variable will be taken from */
6 G( {( g& i9 s+ T4 v - 25 OldPageAddress = PAGE1_BASE_ADDRESS;
- s& Z! y+ K; k - 26 }& |) f) t+ T$ M6 Y0 K
- ...
# H7 q l& z# I5 X$ S8 ^ - 32 /* Set the new Page status to RECEIVE_DATA status */
7 Y9 v. p% i, k0 f - 33 FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, RECEIVE_DATA);% C; V4 A* e. {/ m$ q
- ...) a' x& K7 _# l2 s* E" j& k( q6 j4 U
- 40 InitCurrWrAddress();//aft 重新初始化写地址
* M% b1 i& }- _- G, h5 m: z - 41 /* Write the variable passed as parameter in the new active page */) o b% g% d+ H. ^, z
- 42 EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
$ h- q: I8 Y; }3 B - ....5 t/ V& [. F' p# U8 f5 H8 j
- 49 /* Transfer process: transfer variables from old to the new active page */; L& B: L; l) r5 u
- 50 for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++)- X, {. n8 ]/ D9 y8 { Y
- 51 {
( @5 H0 z8 T) x5 u5 d: B - 52 if (VirtAddVarTab[VarIdx] != VirtAddress) /* Check each variable except the one passed as parameter */
+ ^1 T) |8 ~6 V% v: \ - 53 {1 V+ } K0 G) x [2 }8 n
- 54 /* Read the other last variable updates */
( u ?8 }+ N/ P( H9 A - 55 ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar);9 W D& P# {: [, M
- ...67 }
) X7 p$ {( s. q - 68 }+ p% g% N0 d9 s4 o
- 69
! i3 R0 `* M8 J; v. l. v+ U - 70 /* Erase the old Page: Set old Page status to ERASED status */! R0 E1 C( T E3 E8 T
- 71 FlashStatus = FLASH_ErasePage(OldPageAddress);9 s4 d# l9 P2 c) r
- 72 /* If erase operation was failed, a Flash error code is returned */- p5 Q' t3 s- r! C2 n9 i- K
- 73 if (FlashStatus != FLASH_COMPLETE)
+ y0 J- l" M7 B o - 74 {
+ w$ H3 S6 `* w r6 f2 P5 f - 75 return FlashStatus;) g- b9 {6 x" i+ G z
- 76 }8 K2 G U- i- u; O/ P4 \8 Y
- 77 - e' @5 W! e3 c
- 78 /* Set new Page status to VALID_PAGE status */
" d4 t d( `7 [9 `! c) j - 79 FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, VALID_PAGE);
1 r# c3 c* |0 Z; u/ }# E - 80 /* If program operation was failed, a Flash error code is returned */
9 J: m6 j( U3 R9 J; e" D" m - 81 if (FlashStatus != FLASH_COMPLETE); S Q' V! I4 O( u$ e
- 82 {1 O Q' F4 B7 c4 c+ g
- 83 return FlashStatus;
& e8 D+ f* O6 K% |, x' A, F. ^ - 84 }% H! M2 `: y- E0 P( v* G
- 85
" _4 o, x) h5 l4 |; C: b - 86 /* Return last operation flash status */
7 e9 }! H' R$ u( C - 87 return FlashStatus;5 [% u7 J1 m/ z1 X, V% U
- 88 }
复制代码
% `) I1 w; t* F# q. T33行执行完后,要更改CurrAddress了,这时如果是PAGE0要接收,那么CurrAddress = PAGE0_StarAdress+4了,那么在后边的读取数据,用于转换的时候,有个判断 Address=CurWrAddress-2; while (Address > (PageStartAddress + 2)) 这个地方PageStartAddress = Page1_StarAddress ,而PAGE0_StarAdress+2肯定小于Page1_StarAddress啊,直接跳过了,导致不能转存其他数据 - 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+ \
# M/ T. b0 C: J- /* Get the valid Page end Address */1 o( Z+ g6 L8 g* x3 T, Z6 O" I r
- //Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));
# F+ L1 w1 I. `" Y( _/ v$ L - Address=CurWrAddress-2;
8 o- J3 m. V5 q3 ? - ' l# q/ j& W/ \/ S6 {/ n0 z' Y
- /* Check each active page address starting from end */1 C5 V# Q/ c8 G" T1 L
- while (Address > (PageStartAddress + 2))
) U u) Q+ i* b% l# s - {
( J( D% j3 r j( Q" c - /* Get the current location content to be compared with virtual address */: d h' }$ y: i( l( }" M- R. c3 d
- AddressValue = (*(__IO uint16_t*)Address);+ X4 u1 f# e; x
- 5 j% U6 z: @ A7 T% q
- /* Compare the read address with the virtual address */, T7 }/ ^, t) w" D7 I
- if (AddressValue == VirtAddress)* L$ q: ^9 n n' E2 i+ ], z7 q
- {
1 ?, Y2 G/ b$ V4 t/ t- ~ - /* Get content of Address-2 which is variable value */
! l1 j# R; E/ Q - *Data = (*(__IO uint16_t*)(Address - 2));
# B. X a9 W7 q1 w - + a3 n4 m5 ?, e8 F' l) A* W$ t
- /* In case variable value is read, reset ReadStatus flag */2 O" x% l* i+ L: F
- ReadStatus = 0; {$ t. \0 |3 B1 o3 y
# I& `# W5 U L: ^6 z; e- break;
3 g% a% r* u. ~3 u - }4 W' r+ C9 S0 a) c- n' c
- else$ r/ @+ J: J8 {8 h
- {
0 G4 U8 d4 Z( U; ?$ w" Q - /* Next address location */
/ ^& w0 h; y+ D* [! F0 M - Address = Address - 4;6 u, c& z6 J9 j3 Z- b: m" i
- }
# b% r+ D, G3 B6 V, p x; Q. {7 t - }
复制代码 . R' b b. n( @9 B( P0 N
7 a" r) a" g) z# I; \
我的做法是,修改 EE_ReadVariable函数,在读取数据的时候,如果PAGE0或者PAGE1有一个状态为正在接受,那么Address就用官方Demo给的语句赋值。这时解决全部这篇所说bug的方法。 - //之前加上读取PageStatus0和1的语句. ?( L/ l- u, x9 I2 p
- 1 /* Get the valid Page end Address */: ~/ s0 b u) p: T4 C+ S
- 2 if((PageStatus0 == RECEIVE_DATA)||(PageStatus1 == RECEIVE_DATA))//当在页传输时,地址放在有效页的末尾来搜索所有的存储信息
; h' x/ S2 _6 r/ O- c4 f6 c - 3 {
1 T5 [- `: U( _0 I' | - 4 Address = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));6 B7 @2 i K. |& }
- 5 }
2 K7 F6 d( G: I$ ~: m9 _( d - 6 else2 P: J# I) \. D* \/ V) o8 T( a
- 7 {
d/ n$ y- u6 W& e$ [; l0 l& P( Q - 8 Address=CurWrAddress-2;
9 K) I) W% }" z! d - 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
|