本文开发环境:
$ x2 p+ I7 J; E3 s) O2 t2 S3 o- t- r0 u
MCU型号:STM32F051R8T6
; g* y, p) ?8 M9 PIDE环境: MDK 5.25- f4 `2 @6 b/ F- L" h3 Z) L
代码生成工具:STM32CubeMx 5.0.1! U. W' A% j6 s1 |6 C
HAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series)0 B$ d' I( Z* Z7 ?
U% C0 p5 _- S* `7 A本文内容:3 J4 W& s( Y% v' z: ?
( R, e0 @' `! F
MCU片内Flash(闪存)的擦除与读写
0 q. C4 Z4 a! O$ }1 ]一个Flash读写例子
0 z! `& V. r) Z7 U( O: u/ f# j7 z9 u2 L4 E8 ~+ W0 R
Example$ k; \. ?& @+ Y4 X
首先给出一个Flash程序一个完整的片段:
5 L, }8 \3 C. d6 w! C( x- ... ...% W) T/ c) O/ t, L% X. F
- #include "stm32f0xx_hal.h"$ k/ l6 b4 [0 X& p& W" m
- ... ...
6 A* G- i( {; m. H/ b" W" \ - static FLASH_EraseInitTypeDef EraseInitStruct = {
: J0 p" y) O8 z( X - .TypeErase = FLASH_TYPEERASE_PAGES, //页擦除
2 H9 ~! v7 K1 Y - .PageAddress = 0x08008000, //擦除地址0 Z* ~2 F/ E+ ~7 ]- Z8 ~3 X5 W
- .NbPages = 1 //擦除页数
7 m# X6 d* I! ? - };% d7 T2 i7 S+ `1 L' G& @# n3 }
- ... ...# p' Y3 _' u9 q: z' o$ c
- while (1)
8 `# i2 t- u' M; ]* X2 Z8 y - {
) R3 R# `3 @/ z" h; H2 ]1 b/ g. u - HAL_FLASH_Unlock();" k0 D0 |0 I$ r+ [9 C$ d0 Y, V
- uint32_t PageError = 0;
" l9 d* a# I r5 s' d2 h/ u - __disable_irq(); //擦除前关闭中断7 F- a, n" X: w; q! h/ Y4 ]* g
- if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)8 M2 [2 }4 V- D% F
- {8 t# A0 c0 h$ [( c, Q6 Y. {3 m
- printf("擦除 成功\r\n");, q7 m, F7 \! C+ d( Y
- }
& T8 `0 x* ~: x$ Z - __enable_irq(); //擦除后打开中断
2 P0 L( z9 s7 d - uint32_t writeFlashData = 0x55555555; //待写入的值9 \ N2 Z" Z" D" T, t* M" d
- uint32_t addr = 0x08008000; //写入的地址
3 u. C* y9 _( F+ J5 f$ N - HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);
) L6 S+ l; Y; f# c - printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);4 t( ^2 V. x& K( B& o
- HAL_FLASH_Lock();
- P' i; }! L3 k9 Y# E1 S/ L - while(1);
7 X, ?) ^# D8 v -
; p8 U0 j8 i. I: R' Q - /* USER CODE END WHILE */: b' ^, p. m/ g8 W% x( W1 H
- /* USER CODE BEGIN 3 */
7 U. t* v% k$ C8 N - }
复制代码
* U4 {' t5 l$ z" _# m本地代码段写在main.c中,STMCubeMx生成工程时已经在main.c文件中包含了各种头文件,所以这段代码可以直接使用的,如果我们想在其它文件中使用它,请记得包含HAL库头文件:' _) a% u& z( V
- #include "stm32f0xx_hal.h"
复制代码 ) H4 H2 G: h( e8 i/ q9 V, R
特别的,擦除flash的时候不能执行其他程序,所以擦除前要关闭中断,擦除后记得恢复中断状态。这段代码一旦运行,就会在特定地址写入特定的值,并可以通过串口助手查看数据:6 x1 ~7 O- i& F8 W( c V8 _) C
' D$ m$ r* d: j( D" ~( S: A* u9 i- ~& P9 d% [5 U
1 D, B1 G4 i8 n1 N# ]# l2 ~5 UFLASH 简介
7 r* o( y2 d1 F, u+ `) o; RReference manual (RM0091)手册给出了该系列的用户Flash的起始地址,Page,Sector的划分,这个是一个重要的参考信息,因为在Flash操作中,我们要写入数据需要先擦除数据,而擦除的最小单位是PAGE。单片机有几种启动模式,但大多数是从0x0800 0000这段代码开始运行的,我们的代码也是从这个地址开始烧录。这类似于我们计算机,常见开机就是从C盘(如果C是系统盘)启动,但是也可以设置从U盘启动。由于Flash的介绍,手册和网上已经有非常多的资料。- t% u4 D- N' t; E- }) _
' ]% g+ \* k% r, }, q9 R( X% i) o! ]% p6 b6 n: `9 l6 T
7 y1 U7 W4 z3 B+ ~+ W
Flash 的上锁与解锁
Y0 m. c9 v' p: B" I0 n在操作Flash之前,我们都需要对Flash进行解锁,对应的,操作完Flash之后,则需要对Flash进行上锁。这里的操作包括擦除,读和写等。HAL库提供了2个API,用户可以直接调用:) u8 N# r' T9 Y- o1 a4 R% A
- C. P+ A7 P! u9 f1 ^1 p- y- `- HAL_StatusTypeDef HAL_FLASH_Unlock(void);
, H) J) Q4 a0 u9 e - HAL_StatusTypeDef HAL_FLASH_Lock(void);
复制代码
+ {* }+ {9 N* R, V' ]7 kFlash 的擦除7 W+ O% D! R; M# b, N2 Z$ V+ g, e9 t
在擦除Flash之前,我们需要确定一些参数,擦除的地址,擦除的页数等,HAL库提供了一个与之相关的结构体:
( M/ i' ^% Y- Y/ Z9 s) l+ p7 C- F, o0 u& b3 Q, H3 e) B4 ^
- typedef struct
7 M% ^ H( w2 g h - {# I, w* l9 n; r2 X; p& b
- uint32_t TypeErase; /*!< TypeErase: Mass erase or page erase.
" |6 r. f7 d1 y, M( n - This parameter can be a value of @ref FLASHEx_Type_Erase */
* e8 A$ |3 g9 J5 s2 o ^, W2 M
2 [1 T: {, r' ]. ?8 J. Y- uint32_t PageAddress; /*!< PageAdress: Initial FLASH page address to erase when mass erase is disabled. p) L2 C J% Z7 S
- This parameter must be a number between Min_Data = FLASH_BASE and Max_Data = FLASH_BANK1_END */3 S8 @/ D8 s& ^9 }) L# l7 @
6 t4 N" Q# q- i8 E- uint32_t NbPages; /*!< NbPages: Number of pagess to be erased.1 _( t9 A9 c" g5 k" F" ^) e* ?
- This parameter must be a value between Min_Data = 1 and Max_Data = (max number of pages - value of initial page)*/
[' q9 e# e: @/ t9 |2 U: \+ G
! d6 T0 p5 R( `/ l) M- } FLASH_EraseInitTypeDef;. g5 D" T0 u0 Q
复制代码
' w! H) H, p/ w. e& o V" Y所以在使用擦除函数之前,我们先定义一个结构体,并初始化它:
8 a& Z2 t1 u# L! d0 k p+ }3 K- static FLASH_EraseInitTypeDef EraseInitStruct = {' z2 e3 `5 t! K$ F5 ?2 N
- .TypeErase = FLASH_TYPEERASE_PAGES, //擦除类型:page擦除,即擦除整页。也可以选择擦除整片
) e* m. ]5 Q0 f - .PageAddress = 0x08008000, //擦除起始地址) \! E& {( {" \" \8 {0 j) q
- .NbPages = 1 //擦除页数- C, p, }1 U, X$ u, c
- };
复制代码 ( F% g# e# C- T9 v$ F
初始化好结构体之后,就可以使用HAL官方提供的API进行擦除:1 O& D4 j) z# ?( H. h
2 a0 v# M7 }) U3 K6 H! X
- HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
复制代码
{) A7 @/ D. D- l* u8 e其中第一个参数为指针,我们传入我们上文初始化好的结构体,第二个参数用来存放错误信息,我们新建一个值来存储,以下是示例代码:+ G8 [ z% l: c; Z6 v4 r
- ... ... S/ [$ L2 B1 L" e7 T
- uint32_t PageError = 0;1 u9 @+ a2 |6 S& D
- HAL_FLASH_Unlock();
' X& n; y0 A' N* [- p; F4 y - if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)& f2 i& S C, v# ^8 ?/ ?! L
- {3 M$ p' g6 E0 X! g" b! R
- printf("Erase Succeed\r\n");
$ O( T( Z- q0 b6 D - }5 n0 d: L. a4 F' ^
- HAL_FLASH_Lock();% B0 {0 Z+ C: f1 C D; h- s3 @( O
- ... ...
复制代码 / L. D" u4 {7 q) |) k9 ^6 l
如果你有ST-LINK或者J-LINK此类的调试器,可以使用硬件调试,然后查看0x08000000对应扇区的值,你会发现擦除后的数据全是0xFF。# E% }3 X* `, j
2 K0 a3 A" w& P7 m/ W
Flash 的读写操作
' L& v9 F P# m. f2 ~& `% r$ Y/ ?Flash的写操作2 s* J+ _$ E9 D; [
当我们擦除了一个扇区,该扇区就属于可以写状态,我们可以通过读Flash来确认我们写入的值,HAL库提供的写操作函数如下:
9 ?1 b6 n7 B8 I1 q# x- HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
复制代码
4 m9 r3 I: m% i$ H/ e% d0 }; ^其中:# R* x) w; g. _: D
- - TypeProgram :写类型,只可以是半字(2个字节)或字(4个字节)
% i- ^- I6 V4 R; b6 C - - Address :地址
5 `7 i- z& Z, @4 E - - Data :写入的数据的值
复制代码
! P2 a! ~ C3 u' P7 Y( O; A; OFlash的读操作
+ k& L: D; l) j; Q n: J# jFlash 只需要所在地址,就可以读取对应的值:
! a/ m: e* p* E6 Z- uint32_t pValue= *(__IO uint32_t*)(addr);
复制代码
& k5 U4 S" i9 @. U0 D以下程序写入一个值,并将整个值读取出来:
% n8 t- u" d. g4 v- ... ...
7 p1 M% f: m4 e - uint32_t writeFlashData = 0x55555555;
: `7 \. D4 k( d - uint32_t addr = 0x08008000;
. m' _6 g; I' v, S1 R* ?" d' O - HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);
$ g3 x8 s) [; y - printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);
3 v' @- w, u# f; L8 O& `, r - HAL_FLASH_Lock();
- }/ d1 p8 H, f% w/ L& |9 M! ]4 { - ... ...
复制代码
" q. h! N# Z a6 h- i注意
7 t$ ^! k5 h5 P5 M由于我们经常使用Flash来存放用户程序,所以要特别注意我们读写的时候不破坏到原程序,本文因为程序很小,不可能写到0x08008000,所以直接用这个地址作为例子,在实际运行中,我们需要设计要那些区域是可以用来读写的,避免出现程序错误。7 C3 o1 S& @( R, [/ F. @( }& h6 [- q
" U7 d- R; X% r0 g
! L& ]: k$ Q7 Y* |; q
5 D8 T+ t7 y7 K1 H) z |