本文开发环境:( N% \( M+ V0 z& D& x: U
3 C% u0 r6 U& e9 l2 r7 yMCU型号:STM32F051R8T6
' r( y' J, `0 h/ AIDE环境: MDK 5.254 h0 r, b4 {% S1 P. j
代码生成工具:STM32CubeMx 5.0.1, n! u7 [7 W7 C
HAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series)
# c3 ]+ e" g: J/ a$ `0 l/ R' Y& D' j. b- ^: q. P5 \
本文内容:
" u( O) W$ J( l! u: w! [6 N P8 u4 V! b) h9 [& U L
MCU片内Flash(闪存)的擦除与读写. P! M+ y9 m0 l1 o+ ~+ }
一个Flash读写例子9 Z, E* b2 X# G8 \, q
0 ?! }7 w. y7 }4 Z8 b; x$ ]Example8 V, y7 x# D" Q0 [+ Y
首先给出一个Flash程序一个完整的片段:/ x0 Y) F1 [+ p: w
- ... ...
- C% Q1 u/ c& C4 \ Z7 E1 n9 T& I# v - #include "stm32f0xx_hal.h"
& D7 }/ ]% q" p( r - ... ...2 ]4 ]4 H' c5 ]; G8 m
- static FLASH_EraseInitTypeDef EraseInitStruct = {
% F( v( E! N9 L+ K9 d5 P+ ? - .TypeErase = FLASH_TYPEERASE_PAGES, //页擦除
) N+ {/ B6 l* j0 v0 A4 n0 L - .PageAddress = 0x08008000, //擦除地址
1 D' Y( h. J# i$ P8 W" r - .NbPages = 1 //擦除页数
* ]" T7 M$ j/ h" n6 Y% T - };# o9 r# H2 c1 Z1 F, m- T0 K
- ... ...
4 \- w! ?3 [# ] - while (1)
* b5 b% b p' v4 k3 d - {6 i4 z$ |- T) u3 f% s% n0 K
- HAL_FLASH_Unlock();
+ l, z7 U' t) O4 k - uint32_t PageError = 0;8 q" `6 L0 b [3 I) D
- __disable_irq(); //擦除前关闭中断
( B+ Y3 R0 e5 [" i# k5 T - if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)
3 b! s$ a. R4 ?$ \& p% \ - {* O' C( I7 R2 O S
- printf("擦除 成功\r\n");" ?( ^+ _! \5 a5 a! a* h
- }9 T1 M- b* m& @/ H& E. q8 q
- __enable_irq(); //擦除后打开中断. Y" r4 J/ m: H, s. v( @
- uint32_t writeFlashData = 0x55555555; //待写入的值
# D, v* H* P: Z* P( Z! B T: C - uint32_t addr = 0x08008000; //写入的地址; i1 l8 _. H0 X( I: O5 |1 o8 C
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);. `2 X% t5 x* P C& A2 V
- printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);
+ J8 V! M3 ]; Z2 c3 A6 @ - HAL_FLASH_Lock();
% y( O V8 Z" z6 ? l - while(1); R( f$ J# `$ l4 g: E( ?
-
7 T7 D$ a% D# |! W; `# T - /* USER CODE END WHILE */
/ r% z/ U8 i. B) e$ h - /* USER CODE BEGIN 3 */
0 ?& G1 b" a/ O" ~' o, ?- X - }
复制代码 ' L( X9 Y5 t% x/ p. ?7 y
本地代码段写在main.c中,STMCubeMx生成工程时已经在main.c文件中包含了各种头文件,所以这段代码可以直接使用的,如果我们想在其它文件中使用它,请记得包含HAL库头文件:
. ?+ N5 N' r5 r- #include "stm32f0xx_hal.h"
复制代码
: k8 N* q1 [* t4 m' E! d7 K! p8 N特别的,擦除flash的时候不能执行其他程序,所以擦除前要关闭中断,擦除后记得恢复中断状态。这段代码一旦运行,就会在特定地址写入特定的值,并可以通过串口助手查看数据:
4 d% K% O8 ?7 D7 K
- |; f! Q, `! S/ K W
. e+ |4 N( Z# l/ O+ f8 X
0 C0 U" s; u# i+ ?" T# O# M4 rFLASH 简介. o* Q* _, u) Y. B, v% ?* J
Reference manual (RM0091)手册给出了该系列的用户Flash的起始地址,Page,Sector的划分,这个是一个重要的参考信息,因为在Flash操作中,我们要写入数据需要先擦除数据,而擦除的最小单位是PAGE。单片机有几种启动模式,但大多数是从0x0800 0000这段代码开始运行的,我们的代码也是从这个地址开始烧录。这类似于我们计算机,常见开机就是从C盘(如果C是系统盘)启动,但是也可以设置从U盘启动。由于Flash的介绍,手册和网上已经有非常多的资料。+ w! s3 Y8 E' n- k1 l5 ]( T6 p6 Z
3 K! ~% k2 p0 U( E1 p3 D+ W. i7 [
0 B$ T" o( m+ W, P6 @
# r, q' V( A! U8 A3 L, x# sFlash 的上锁与解锁
+ p- o1 u1 \7 B! i4 w2 A# r8 j在操作Flash之前,我们都需要对Flash进行解锁,对应的,操作完Flash之后,则需要对Flash进行上锁。这里的操作包括擦除,读和写等。HAL库提供了2个API,用户可以直接调用:! K# j9 K d' u# S% u
: E; h( Y/ i* I% J& `- HAL_StatusTypeDef HAL_FLASH_Unlock(void);
$ t+ r3 ~% ~+ o - HAL_StatusTypeDef HAL_FLASH_Lock(void);
复制代码 0 H; x$ P# U- y e2 I: C
Flash 的擦除5 ?" f7 U" y' a1 j5 E% D5 Q) _ C
在擦除Flash之前,我们需要确定一些参数,擦除的地址,擦除的页数等,HAL库提供了一个与之相关的结构体:- \: O& k+ C8 Z
0 [* W4 ]7 o7 @" d: M& ?' ^/ r
- typedef struct7 r/ Y4 F2 ^1 C J5 }2 V/ s
- {/ k1 Z0 z9 T/ X3 T) D5 {
- uint32_t TypeErase; /*!< TypeErase: Mass erase or page erase.
( d( \. T2 K& X& k3 L& ~ - This parameter can be a value of @ref FLASHEx_Type_Erase */3 B! w! M: j' v. H7 e
! l4 a! N' z+ E% t3 _2 R: R- uint32_t PageAddress; /*!< PageAdress: Initial FLASH page address to erase when mass erase is disabled. a5 i, g4 [' O3 V# t( k; k8 y2 ?
- This parameter must be a number between Min_Data = FLASH_BASE and Max_Data = FLASH_BANK1_END */8 K& U D8 A# ~- C1 }$ d* N; f
4 Z+ h+ L. O: z9 }+ w% t- uint32_t NbPages; /*!< NbPages: Number of pagess to be erased.
5 @' @9 s+ T5 R' R& B6 u - This parameter must be a value between Min_Data = 1 and Max_Data = (max number of pages - value of initial page)*/3 U. W! i0 y9 Z' C1 B4 s* }
& w3 E8 U1 \! m: n. N- } FLASH_EraseInitTypeDef;
& X) L' w3 T5 F4 Y" d/ v0 J2 L
复制代码
. h" c4 k4 [/ Z8 U5 P所以在使用擦除函数之前,我们先定义一个结构体,并初始化它:
7 I ^! Q9 y5 d6 H% A8 L* O9 J- static FLASH_EraseInitTypeDef EraseInitStruct = {) t$ ?3 S5 C- U+ p! g0 M. y* y
- .TypeErase = FLASH_TYPEERASE_PAGES, //擦除类型:page擦除,即擦除整页。也可以选择擦除整片
" j) G! V4 ?* ^4 h5 ]/ d5 T - .PageAddress = 0x08008000, //擦除起始地址
( N, V4 N* `( z7 Z3 T8 m+ {6 j, E - .NbPages = 1 //擦除页数1 a" ?/ o% Z1 f. h3 R' }
- };
复制代码 . ?) E5 [0 t5 Q- s4 F+ x6 o
初始化好结构体之后,就可以使用HAL官方提供的API进行擦除:
. H; B/ R: w' G* X7 V& y* F6 A, Y- F4 c. M- t
- HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
复制代码 : Q* \( @; l! o8 ]( t8 @# R% |) p
其中第一个参数为指针,我们传入我们上文初始化好的结构体,第二个参数用来存放错误信息,我们新建一个值来存储,以下是示例代码:9 D2 y6 I& K6 ]8 J' n! q
- ... ...# G1 T' u& o: T, f4 U$ U, Z
- uint32_t PageError = 0;
$ h# U `- |/ ]6 P. O - HAL_FLASH_Unlock();
, Y9 k9 O- G; R' `$ Q* k - if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)
' b# C4 I1 K+ k: a+ T& e7 I - {' X! P' v! p2 O
- printf("Erase Succeed\r\n");: K# d2 K E! d8 S
- }3 S. n/ E( O0 i* S- `# \6 S1 |
- HAL_FLASH_Lock();4 [( w {6 I/ F
- ... ...
复制代码 7 f* F( k0 S$ D! T' u& F" h
如果你有ST-LINK或者J-LINK此类的调试器,可以使用硬件调试,然后查看0x08000000对应扇区的值,你会发现擦除后的数据全是0xFF。2 s2 [- B) v4 K) p, J Q
* S/ _0 \, l5 Z% N# B H+ S$ F) aFlash 的读写操作- m" o& ]* ?: w7 o% w
Flash的写操作
' V, e6 C0 h+ L" X* Z+ q当我们擦除了一个扇区,该扇区就属于可以写状态,我们可以通过读Flash来确认我们写入的值,HAL库提供的写操作函数如下:" ]3 v& i4 _1 p& j, x
- HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
复制代码 ( ]; t# p8 G: Y5 ~- m7 L3 v
其中:) A' G5 H7 s4 d: r% z& W! C. z
- - TypeProgram :写类型,只可以是半字(2个字节)或字(4个字节)
4 \1 y* {/ l2 @: F e! l* h% A4 f0 ~ - - Address :地址7 R5 [( ? e* ^2 b
- - Data :写入的数据的值
复制代码 3 h5 H6 K7 C% M. v4 O9 U) m2 Y/ P
Flash的读操作. }! F& v( ?% `
Flash 只需要所在地址,就可以读取对应的值:; f+ F, X3 `" @: H$ c1 g- b0 o+ \& }
- uint32_t pValue= *(__IO uint32_t*)(addr);
复制代码
- z. S; K5 p5 d5 J4 m以下程序写入一个值,并将整个值读取出来:/ `$ _: Q6 Z: a5 P; V5 @
- ... ...0 [" M5 {$ N9 b& B
- uint32_t writeFlashData = 0x55555555; V6 I! \) y% _) _2 C" m# q$ K
- uint32_t addr = 0x08008000;, g: C6 Z5 E% h& a
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);6 R" f' V. V" s3 G4 p" r
- printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);& m# C1 I# a8 p: J* z) y& l( h
- HAL_FLASH_Lock(); s: q' q3 ?% }# w, A0 F$ c
- ... ...
复制代码 8 k+ h# u* w7 \- f
注意
8 L7 O9 T8 s8 j' K( d由于我们经常使用Flash来存放用户程序,所以要特别注意我们读写的时候不破坏到原程序,本文因为程序很小,不可能写到0x08008000,所以直接用这个地址作为例子,在实际运行中,我们需要设计要那些区域是可以用来读写的,避免出现程序错误。( {# X) s w5 Z7 i: y
i; Y- ?. e5 C0 Y' \
5 ~, G$ W" o9 T4 ~% q- Z& ^
3 d, R5 @3 m" l J" W1 e8 Y3 N; G |