本文开发环境:
& o& Z6 J4 e+ O# @% i3 i: q9 c/ d% F+ w# s! c- U6 M8 ^# K+ y& z
MCU型号:STM32F051R8T6
e" n: G' |7 L0 N; H' J1 ~# PIDE环境: MDK 5.25
5 |3 v+ k/ f7 ~! }6 D; c1 z代码生成工具:STM32CubeMx 5.0.1( g; g+ R+ U2 L; d0 D( @4 d
HAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series)
& Y! G& x0 D0 ` ]
/ C, Z$ D2 }! r/ K) [本文内容:
- b; ~) G* f8 k4 m+ |3 A; P* i2 x" R0 h9 a6 H1 I* Z( c- s- r
MCU片内Flash(闪存)的擦除与读写
+ Q6 b9 }7 s' y8 P( a一个Flash读写例子
- M7 E o9 m8 T7 q; x( V9 X5 ^3 Y" b ]) b% \8 h, ]
Example/ o) F, Q( S* H- A$ |5 v
首先给出一个Flash程序一个完整的片段:+ W; Y7 z) y( x9 y+ @. O
- ... ...: n+ c, B1 g! \4 ?) c8 W6 Y
- #include "stm32f0xx_hal.h"
7 ~" L2 A& {) ^# v - ... ...8 k& i+ k& i. t5 {
- static FLASH_EraseInitTypeDef EraseInitStruct = {
5 b W) c- M6 W - .TypeErase = FLASH_TYPEERASE_PAGES, //页擦除4 X- W+ V" _0 o( E5 c8 [
- .PageAddress = 0x08008000, //擦除地址
3 }" b: J4 d# C) M$ a$ a - .NbPages = 1 //擦除页数
) h4 F9 g7 L7 m2 u# H3 y - };
' O* \; G: h' p2 w: i9 a! E - ... ...
, `$ I4 c a1 `' f' P8 K - while (1)
& C. G( \! a6 K2 V$ ? - {
. H1 a& t# u0 P+ p0 G3 i4 J - HAL_FLASH_Unlock();8 P" d1 J+ U0 b. H4 j
- uint32_t PageError = 0;
' p q) v+ z, u0 t0 R5 F# S' M - __disable_irq(); //擦除前关闭中断* n3 Y0 o1 j! \9 i9 \
- if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)" O% \9 s, ^4 o. G" z9 c( j# |- u
- {
& e" u# @6 z% [4 o - printf("擦除 成功\r\n");
$ ^7 z. i1 I3 w - }, d+ b! W2 V& O, L( U
- __enable_irq(); //擦除后打开中断
3 d/ q4 D5 _- }/ M# d0 g - uint32_t writeFlashData = 0x55555555; //待写入的值* d; j H1 d1 L, q$ W5 I
- uint32_t addr = 0x08008000; //写入的地址
( P1 l8 M$ L m( N - HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);
/ x' ?, M( @7 L8 T, k' w' } - printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);( t. o3 D, ?. ]$ D
- HAL_FLASH_Lock();
1 I, C7 V+ T3 s0 T - while(1);6 N% I5 C8 D/ C& H! m; I
- ( f1 C7 Q: N% z
- /* USER CODE END WHILE */# ` I2 n3 D, l( P8 ~
- /* USER CODE BEGIN 3 */
/ G5 V+ j5 C' I0 O0 R0 x - }
复制代码 8 y' x. i1 g8 O0 p5 c
本地代码段写在main.c中,STMCubeMx生成工程时已经在main.c文件中包含了各种头文件,所以这段代码可以直接使用的,如果我们想在其它文件中使用它,请记得包含HAL库头文件:
9 c+ K- n$ N; z# k- #include "stm32f0xx_hal.h"
复制代码
2 @$ `* z2 o; F6 {" t* \6 K5 _特别的,擦除flash的时候不能执行其他程序,所以擦除前要关闭中断,擦除后记得恢复中断状态。这段代码一旦运行,就会在特定地址写入特定的值,并可以通过串口助手查看数据:
8 Z+ W |4 I1 A6 y8 A% L: c
3 d1 G6 f a5 R) H
. l+ @( N% d$ ?4 D; C
$ `- e4 |# c: |7 H3 e6 ?& M! jFLASH 简介8 t- X" N2 l6 e: \$ j# u6 {- _
Reference manual (RM0091)手册给出了该系列的用户Flash的起始地址,Page,Sector的划分,这个是一个重要的参考信息,因为在Flash操作中,我们要写入数据需要先擦除数据,而擦除的最小单位是PAGE。单片机有几种启动模式,但大多数是从0x0800 0000这段代码开始运行的,我们的代码也是从这个地址开始烧录。这类似于我们计算机,常见开机就是从C盘(如果C是系统盘)启动,但是也可以设置从U盘启动。由于Flash的介绍,手册和网上已经有非常多的资料。
' V; n' B3 r- V/ }! y6 v8 T" T, n+ m/ e% z
# L7 @& l. K, F5 g, [- v) p
# t( c$ t, b d% `Flash 的上锁与解锁
K' N7 C4 T/ o在操作Flash之前,我们都需要对Flash进行解锁,对应的,操作完Flash之后,则需要对Flash进行上锁。这里的操作包括擦除,读和写等。HAL库提供了2个API,用户可以直接调用:
- _$ N2 ]9 _) A: J; Z* E% Q$ M q# `! p
- HAL_StatusTypeDef HAL_FLASH_Unlock(void);) h0 h5 w+ }# g
- HAL_StatusTypeDef HAL_FLASH_Lock(void);
复制代码 & j4 B+ X* m) P/ q( B
Flash 的擦除2 R3 \# e, F- t" c7 o
在擦除Flash之前,我们需要确定一些参数,擦除的地址,擦除的页数等,HAL库提供了一个与之相关的结构体:
# ~! x; g: g& i4 y7 B( S- @4 L; m4 t% r+ u0 U+ W# S
- typedef struct
L( ^# U2 C8 y l \# R! z - {0 H- q# U! H* z+ _6 N3 d+ A! q6 e
- uint32_t TypeErase; /*!< TypeErase: Mass erase or page erase.1 l% V* Z! `; z* r& q
- This parameter can be a value of @ref FLASHEx_Type_Erase */$ {; A# z" ?7 X1 B" g: m) W0 [. V
" ?, V5 |# |/ W T. j C- uint32_t PageAddress; /*!< PageAdress: Initial FLASH page address to erase when mass erase is disabled. Y+ c" {; s |# k) X
- This parameter must be a number between Min_Data = FLASH_BASE and Max_Data = FLASH_BANK1_END */
1 ^2 @0 _% c: }0 m
$ ]1 n1 L' ~" _) z9 S- uint32_t NbPages; /*!< NbPages: Number of pagess to be erased.9 B3 W! d% Q/ F4 m4 v
- This parameter must be a value between Min_Data = 1 and Max_Data = (max number of pages - value of initial page)*/
9 G; m( b, Q) z" H. E! i - % o( L x1 I" L- d- x/ O# B
- } FLASH_EraseInitTypeDef;) p3 h/ r6 V3 ~
复制代码 ; b: A! G& p) C! d' }0 z, m( z Z" u0 o
所以在使用擦除函数之前,我们先定义一个结构体,并初始化它:) V$ L+ S9 U3 @
- static FLASH_EraseInitTypeDef EraseInitStruct = {# n$ M- j, C1 o/ c
- .TypeErase = FLASH_TYPEERASE_PAGES, //擦除类型:page擦除,即擦除整页。也可以选择擦除整片
+ E: N' c9 T. F: [ - .PageAddress = 0x08008000, //擦除起始地址+ H- A. i5 R% R) T/ x! v8 @( Q
- .NbPages = 1 //擦除页数
- H! K9 @% c" ]: w0 y. a# `, P- s - };
复制代码 ; Y7 U7 u+ r+ [6 R
初始化好结构体之后,就可以使用HAL官方提供的API进行擦除:
3 |& ~* C2 n1 c0 C- w$ \, G1 \! s e( A/ a5 V, S N( l$ a
- HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
复制代码
0 n: J# t8 z; L+ F其中第一个参数为指针,我们传入我们上文初始化好的结构体,第二个参数用来存放错误信息,我们新建一个值来存储,以下是示例代码:$ M5 p1 I8 h/ w# _! ^' E
- ... ...0 \5 V: \# {" o; k' x' Q5 I5 G* L: F7 ^
- uint32_t PageError = 0;* ]# E) b( F/ e9 X
- HAL_FLASH_Unlock();
; n. ]$ ~3 Z7 J) D) Q - if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)( C/ V, S. _8 C% Y! j" ]$ n+ V- X
- {5 g: g5 P) t# K) c. p# G
- printf("Erase Succeed\r\n");
A6 T- C% u! n3 W/ u5 D1 n5 q - }
/ X, q' d0 C% _# _0 b - HAL_FLASH_Lock(); j- ]" t" h9 v# l, R/ S% F
- ... ...
复制代码
! R: ^% t- w* F; ?* _如果你有ST-LINK或者J-LINK此类的调试器,可以使用硬件调试,然后查看0x08000000对应扇区的值,你会发现擦除后的数据全是0xFF。, e" P$ d) t1 b) A1 {" h$ @4 Z
8 l3 @& i, p$ R& e- pFlash 的读写操作
! E. E" _- t9 \( yFlash的写操作
& d& [, }+ y- X2 J6 f1 y2 O当我们擦除了一个扇区,该扇区就属于可以写状态,我们可以通过读Flash来确认我们写入的值,HAL库提供的写操作函数如下:
/ d+ x5 T/ R3 _# o3 ~- HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
复制代码 5 ?! h1 ?. m1 z D& ^
其中:
5 L% E) H# y9 O _- - TypeProgram :写类型,只可以是半字(2个字节)或字(4个字节): M- F$ b2 j9 s/ m) A
- - Address :地址
1 Q# i2 }. H% N9 q) M - - Data :写入的数据的值
复制代码
* r5 m& S; M. r# PFlash的读操作" i( @8 s: G* ^0 r) G2 B
Flash 只需要所在地址,就可以读取对应的值:: n/ E& \6 K( }" H: B$ \ Z
- uint32_t pValue= *(__IO uint32_t*)(addr);
复制代码 , t3 w8 L7 x8 N7 ?
以下程序写入一个值,并将整个值读取出来:
# m0 J" x8 M6 ~+ w- ... ..., w6 q& }' B* G7 q/ k- u
- uint32_t writeFlashData = 0x55555555;, g1 X( S/ v0 R
- uint32_t addr = 0x08008000;
2 t; o! u& I# k c( P6 i - HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);
# z7 `+ } ?" q6 L( V, u/ D; z - printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);. u2 W% `& V% d# y8 @! _
- HAL_FLASH_Lock();! W: w& [$ L' {# Q
- ... ...
复制代码 4 c+ N1 x y$ @7 F9 f% Z& ^
注意
) K8 K. r, M y9 f由于我们经常使用Flash来存放用户程序,所以要特别注意我们读写的时候不破坏到原程序,本文因为程序很小,不可能写到0x08008000,所以直接用这个地址作为例子,在实际运行中,我们需要设计要那些区域是可以用来读写的,避免出现程序错误。; n) @& f* P3 m3 T e
; {8 |$ U* u; S0 X) u
( S: m P+ V9 z. x. ^) q0 P
# l2 l4 d+ T5 t! I0 K! E* E. K8 o
|