本文开发环境:0 V3 i5 z% K5 ~+ d
. B: {* ~) f X& c+ t6 e- B! R) ~
MCU型号:STM32F051R8T6$ v/ R; E7 J& g$ B( o9 D: y
IDE环境: MDK 5.25
1 `; X! a( B; J, d, c" H代码生成工具:STM32CubeMx 5.0.1
* M+ }+ x5 t$ d7 XHAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series)/ {* x/ z0 G1 r3 u X
$ t& t6 a5 r- p" a* l* g本文内容:; `* E' w6 P/ T8 I8 [. ^
8 c x2 G* }( p8 J. e# y; d4 G
MCU片内Flash(闪存)的擦除与读写5 W! c$ s4 O, V
一个Flash读写例子
6 {! @ x, j: |
! x) @( j' t3 @2 SExample
3 T0 k! \3 N# @- g* W9 o首先给出一个Flash程序一个完整的片段:: M3 S; \! T3 ] E3 K' \3 \
- ... ...0 T; t# }( H% G8 f6 q$ q3 i
- #include "stm32f0xx_hal.h"
8 Q0 ]; ]6 D5 f; V2 C - ... ...
7 @$ k* V3 V% T, d$ E - static FLASH_EraseInitTypeDef EraseInitStruct = {
9 f! |/ D( U2 v( s* S8 u( t4 P - .TypeErase = FLASH_TYPEERASE_PAGES, //页擦除; p" S1 v/ W8 z0 U& j. I5 w9 A$ m& E
- .PageAddress = 0x08008000, //擦除地址
$ E* P4 Q# p; y9 C - .NbPages = 1 //擦除页数: c, v3 Q& Y- Z7 _ S1 }- y
- };) x5 d0 r( K1 u& v
- ... ...
$ v9 e! G( y3 N$ |4 K5 N - while (1)
# j' j: t+ T8 o, \0 U - {
; y0 y2 `% q1 P3 ^ - HAL_FLASH_Unlock();' |* N& l( m% D7 C+ L7 g
- uint32_t PageError = 0;
4 O/ U/ C p8 G4 O3 Z9 c% T$ W - __disable_irq(); //擦除前关闭中断' T/ E" w4 Y; v/ }
- if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)4 Y I# k! B/ U; {% n
- {+ |3 q5 O4 @2 W: K" x9 g! Z
- printf("擦除 成功\r\n");7 e9 X) Q3 o- D* A7 t! V! B' Q
- }0 O( B3 q& ^9 Z. {0 Z
- __enable_irq(); //擦除后打开中断
& C1 S* W8 M, m - uint32_t writeFlashData = 0x55555555; //待写入的值
. f: z! h- {" k5 i/ Q* @ - uint32_t addr = 0x08008000; //写入的地址
' J6 e2 Y; @: } - HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);
& w' A' ]5 x/ \ i( K, V3 x/ A - printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);& J# a% Y2 \0 e1 g
- HAL_FLASH_Lock();
( Q6 O1 N x, n6 l$ X% X/ _# Z4 i - while(1);
9 d, I9 H! t4 j - # U# S) N1 u+ [0 x2 c
- /* USER CODE END WHILE */
8 ^5 g7 j# ~3 q! \5 @$ _% x - /* USER CODE BEGIN 3 */
8 {( c" A! x: N# y - }
复制代码 ; L+ p# B q" \( g c
本地代码段写在main.c中,STMCubeMx生成工程时已经在main.c文件中包含了各种头文件,所以这段代码可以直接使用的,如果我们想在其它文件中使用它,请记得包含HAL库头文件:, }0 H4 G7 E0 Z8 u; B" _4 u# t
- #include "stm32f0xx_hal.h"
复制代码 1 h d- s; s) j( z
特别的,擦除flash的时候不能执行其他程序,所以擦除前要关闭中断,擦除后记得恢复中断状态。这段代码一旦运行,就会在特定地址写入特定的值,并可以通过串口助手查看数据:
8 E/ B# B; L' w" n
8 Q4 O# D/ {% B9 s( ^- W! { ]8 r: ~+ z; E& l* H4 O( z
! X- D# _( ~& |1 W! Y
FLASH 简介' y. l# m6 j+ Y d# E8 {- ^
Reference manual (RM0091)手册给出了该系列的用户Flash的起始地址,Page,Sector的划分,这个是一个重要的参考信息,因为在Flash操作中,我们要写入数据需要先擦除数据,而擦除的最小单位是PAGE。单片机有几种启动模式,但大多数是从0x0800 0000这段代码开始运行的,我们的代码也是从这个地址开始烧录。这类似于我们计算机,常见开机就是从C盘(如果C是系统盘)启动,但是也可以设置从U盘启动。由于Flash的介绍,手册和网上已经有非常多的资料。
5 N. k$ @% k: s+ C, c1 R
9 O# U+ `6 m0 i" R) T% ^% i$ E
( k s0 x8 r! W
$ G# A& ~; v6 r- e/ B* Q' dFlash 的上锁与解锁7 j1 s5 [5 {$ R8 t/ \+ a9 r6 F* p
在操作Flash之前,我们都需要对Flash进行解锁,对应的,操作完Flash之后,则需要对Flash进行上锁。这里的操作包括擦除,读和写等。HAL库提供了2个API,用户可以直接调用:
" [9 e4 g G9 T: \' R
% B; z* u) W# l& k x6 H. B- HAL_StatusTypeDef HAL_FLASH_Unlock(void); w- G/ T- U i
- HAL_StatusTypeDef HAL_FLASH_Lock(void);
复制代码
L, [1 [- ^ ^8 ^4 H' ?Flash 的擦除! `& f0 G( w# C* B4 ]/ {" E; m* ?
在擦除Flash之前,我们需要确定一些参数,擦除的地址,擦除的页数等,HAL库提供了一个与之相关的结构体:* n$ s5 ^; y) ~% X. S3 h* ?
* y; J# `/ r- j1 @% p8 X. N
- typedef struct
& a" l" K9 B6 w9 z - {
3 y, \8 ]$ x7 Z - uint32_t TypeErase; /*!< TypeErase: Mass erase or page erase.
8 `) f) `# p" ?! p) ]/ J - This parameter can be a value of @ref FLASHEx_Type_Erase */# s+ Z6 I1 l* b# _' k# n* _* R2 ?
- 4 T* Z5 W( j0 C) {8 e0 C0 V
- uint32_t PageAddress; /*!< PageAdress: Initial FLASH page address to erase when mass erase is disabled
! h @$ N2 x; N0 v% z8 v - This parameter must be a number between Min_Data = FLASH_BASE and Max_Data = FLASH_BANK1_END */, h9 Z/ m8 Z' Q( ]' ?: [
' I5 I: x2 y1 L8 r1 s7 m4 a- uint32_t NbPages; /*!< NbPages: Number of pagess to be erased.
9 l. ?5 ^- p: w2 i; @- r' v& u - This parameter must be a value between Min_Data = 1 and Max_Data = (max number of pages - value of initial page)*/- c9 ]9 w1 ^- C+ P4 W e
- 0 x( \; N; ?' ^5 d# Q
- } FLASH_EraseInitTypeDef;, {2 w7 m9 F4 S( u
复制代码
1 _6 O+ H1 S8 R4 x' [所以在使用擦除函数之前,我们先定义一个结构体,并初始化它:' u0 l. Y1 K( \' o0 e l
- static FLASH_EraseInitTypeDef EraseInitStruct = {
$ @8 O/ [$ U5 V - .TypeErase = FLASH_TYPEERASE_PAGES, //擦除类型:page擦除,即擦除整页。也可以选择擦除整片
+ R" l" x- d1 ?) E - .PageAddress = 0x08008000, //擦除起始地址4 p% _' i w2 x3 q5 Z: ]6 o* l
- .NbPages = 1 //擦除页数5 w' ?1 l u. c' s; M; a3 q
- };
复制代码
% v. Q3 D( f8 [; R- e& l. \9 d1 G初始化好结构体之后,就可以使用HAL官方提供的API进行擦除:
) b' N) \0 R$ P5 I% U
$ x4 B& o7 a0 b4 l- HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
复制代码 ( P' L/ ]8 B8 T( `8 Z3 e& u5 p. E+ z
其中第一个参数为指针,我们传入我们上文初始化好的结构体,第二个参数用来存放错误信息,我们新建一个值来存储,以下是示例代码:
# ?* O3 A" P) v- ... ...( r2 H8 z- a7 J) i: @& x: P' ^! d' h
- uint32_t PageError = 0;6 b! a! l3 @4 V( Q& o
- HAL_FLASH_Unlock();
0 m0 ?, r0 j# n - if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)% h! Q2 L; y. ?. }+ N! W
- {/ g' |8 ?9 V" s; S
- printf("Erase Succeed\r\n");$ J* S1 y' m+ q4 S$ J; d( [% G
- }& j8 C9 v, ]+ ? g# m& g
- HAL_FLASH_Lock();
: |8 K* M* U% {" o - ... ...
复制代码
: O. }3 K. @+ F如果你有ST-LINK或者J-LINK此类的调试器,可以使用硬件调试,然后查看0x08000000对应扇区的值,你会发现擦除后的数据全是0xFF。
! c+ Y* h9 K* _$ [
$ g1 [5 `: R3 g3 A% W. BFlash 的读写操作
4 V8 V) f. y) }7 ]/ sFlash的写操作- z' M8 r/ s2 M6 d8 Q& A4 P
当我们擦除了一个扇区,该扇区就属于可以写状态,我们可以通过读Flash来确认我们写入的值,HAL库提供的写操作函数如下:
+ [# E' w; V7 m! B( r- HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
复制代码
0 N' F$ [/ F. Z$ n9 R其中:
& Q$ z. F' g/ J k8 c& s: }- - TypeProgram :写类型,只可以是半字(2个字节)或字(4个字节)6 g+ P# ^) m+ v% D" U
- - Address :地址
; |/ \5 L p2 R% R5 d - - Data :写入的数据的值
复制代码 ' K5 ]2 o" D3 {) {# q' R
Flash的读操作+ p# W. P% B8 t& s$ n; r9 D1 _
Flash 只需要所在地址,就可以读取对应的值:
, ?2 N/ [. Y4 ^) Z0 |: l5 I! P7 e- uint32_t pValue= *(__IO uint32_t*)(addr);
复制代码 * T; {1 @( Y" h! h$ ^: J
以下程序写入一个值,并将整个值读取出来:
& Y+ G- j9 N" w( g- ... ..., z* N; o' A9 Z. p) {( f; \1 {. T
- uint32_t writeFlashData = 0x55555555;+ ?# R6 ^! H4 l0 L! }7 V4 L8 d
- uint32_t addr = 0x08008000;: K; P3 S9 E8 O; m
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);
: f( K" p' ]7 ~0 r( e - printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);/ N& f1 }% F) y0 B# D
- HAL_FLASH_Lock();
+ P! W8 b. [+ T; y' c - ... ...
复制代码 ! \& v, y% |1 g6 \" g; W9 }# }
注意
0 f5 v; t7 f* q由于我们经常使用Flash来存放用户程序,所以要特别注意我们读写的时候不破坏到原程序,本文因为程序很小,不可能写到0x08008000,所以直接用这个地址作为例子,在实际运行中,我们需要设计要那些区域是可以用来读写的,避免出现程序错误。
$ o7 Q. H/ B* U: W, o, V0 V- {2 `
; e/ j6 t! G1 v0 u% ?# n
' ?1 _- J1 d% h |