本文开发环境:
; f1 {) W" l2 [0 f( _3 a, E- M r8 x; R0 I3 X6 b
MCU型号:STM32F051R8T6
8 b: o; t9 v8 h& |4 j @IDE环境: MDK 5.25
9 y. r' o9 ~8 t7 t代码生成工具:STM32CubeMx 5.0.1
9 i) l! h% B" Y9 QHAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series): P6 O& H7 \7 k g6 z; E
* M5 z, }. W, \/ z! A3 `" B, Y( G$ i本文内容:
, E: a" T; c- X7 n9 ~% f, _+ V6 X, R$ r- s- n
MCU片内Flash(闪存)的擦除与读写
" C4 J# x0 E/ l6 }# c一个Flash读写例子
u" G+ [) b& X8 x+ ^2 K; |$ o# t! C' A$ C# c8 C
Example
6 Q% e1 L$ j7 f8 I; F q首先给出一个Flash程序一个完整的片段:. U/ L- F* `. \ B# {0 @
- ... ...
* I+ X0 J# @4 z3 Y - #include "stm32f0xx_hal.h"' g6 k3 _, u5 l9 l# O/ ]) D
- ... ...8 s3 m# b. @" F$ @/ w5 ?
- static FLASH_EraseInitTypeDef EraseInitStruct = {; ^0 N1 N" l, o
- .TypeErase = FLASH_TYPEERASE_PAGES, //页擦除 F2 m! k: F% Q! J! N% x
- .PageAddress = 0x08008000, //擦除地址% H: a4 V0 q0 { J; z/ Z- N+ n" l& q; A
- .NbPages = 1 //擦除页数4 [6 A. ?9 K: {5 Q" U, [
- };
4 T" }3 J- M' A/ o2 I/ J6 ^ - ... ...
$ f, F' n( V" l1 ` - while (1)
3 d& e/ C( c D+ j - {
: b* D$ l/ [6 u4 }' x! T) d0 {( T - HAL_FLASH_Unlock();' ?; L' `% |3 }0 `2 c8 i
- uint32_t PageError = 0;: S, z1 S i& O6 u$ X% W3 U k+ ~
- __disable_irq(); //擦除前关闭中断* `: v7 e- k* c% G$ l
- if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)" H, }8 R) H* K3 ?0 |
- {. ?9 }, Z! e/ E9 m: r+ _
- printf("擦除 成功\r\n");9 F7 J0 N) v8 S" Q" z2 w
- }
. i, P/ l2 c2 W& r - __enable_irq(); //擦除后打开中断
6 \ E3 M7 ?' H# ^6 \* f - uint32_t writeFlashData = 0x55555555; //待写入的值% c7 q' s$ a7 {3 X7 g" I- H5 R
- uint32_t addr = 0x08008000; //写入的地址. C; {" F, d: \* }4 k8 ~ U9 x
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);7 Z O$ E( X1 x* Y! O
- printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);
4 J6 d5 {$ e1 v+ g) U - HAL_FLASH_Lock();
4 L6 `3 a2 d7 S/ J# @. q% z7 V - while(1);& \! x& ] M8 z: ?' f
- l4 G# U* ?, a3 `& R
- /* USER CODE END WHILE */+ s( D9 [3 D1 H- |7 O1 E' {' M
- /* USER CODE BEGIN 3 */
* N9 j1 b7 Y, u. }6 n - }
复制代码 ' X, U" T# d: w- ]4 n
本地代码段写在main.c中,STMCubeMx生成工程时已经在main.c文件中包含了各种头文件,所以这段代码可以直接使用的,如果我们想在其它文件中使用它,请记得包含HAL库头文件:/ w ?- |9 e& L7 M' V
- #include "stm32f0xx_hal.h"
复制代码 2 ^. z8 Y- _9 X( \0 \3 j: l
特别的,擦除flash的时候不能执行其他程序,所以擦除前要关闭中断,擦除后记得恢复中断状态。这段代码一旦运行,就会在特定地址写入特定的值,并可以通过串口助手查看数据:9 ?0 R4 K! O! d9 ^
( j% m# @- C0 k7 J% F* z; r: f' f! Q
2 v; C4 T- T2 b: n" y, {. \9 ]7 l6 _
FLASH 简介
) r- o+ f* x6 J3 EReference manual (RM0091)手册给出了该系列的用户Flash的起始地址,Page,Sector的划分,这个是一个重要的参考信息,因为在Flash操作中,我们要写入数据需要先擦除数据,而擦除的最小单位是PAGE。单片机有几种启动模式,但大多数是从0x0800 0000这段代码开始运行的,我们的代码也是从这个地址开始烧录。这类似于我们计算机,常见开机就是从C盘(如果C是系统盘)启动,但是也可以设置从U盘启动。由于Flash的介绍,手册和网上已经有非常多的资料。
9 ~4 ~) b4 Y" K: `: V4 \- W% i$ ?' N9 E; m3 T1 h4 q2 n
+ _$ H* S) f) L+ o
$ e0 }( \2 e- h
Flash 的上锁与解锁( m$ B- h1 Y6 ~! A$ F+ }4 J
在操作Flash之前,我们都需要对Flash进行解锁,对应的,操作完Flash之后,则需要对Flash进行上锁。这里的操作包括擦除,读和写等。HAL库提供了2个API,用户可以直接调用:% z3 q, u: t+ E X) t( V$ [2 [
8 g! X! g) m6 d+ h2 A- HAL_StatusTypeDef HAL_FLASH_Unlock(void);0 k/ j1 G! m. X: w* r2 d% @( R
- HAL_StatusTypeDef HAL_FLASH_Lock(void);
复制代码
& ~+ Z8 {. Q* V4 X1 [Flash 的擦除/ I+ g/ L4 |+ n
在擦除Flash之前,我们需要确定一些参数,擦除的地址,擦除的页数等,HAL库提供了一个与之相关的结构体:/ z* C: \* J6 p' o: i0 @
5 b5 V$ e& g* i- ^
- typedef struct
6 q# H+ i+ m+ V0 F - {1 k4 W5 \5 l( N2 t0 d
- uint32_t TypeErase; /*!< TypeErase: Mass erase or page erase.- E9 `3 ~: d5 S
- This parameter can be a value of @ref FLASHEx_Type_Erase */
* q1 b. n2 j$ K% r
. R3 F% m. v+ ] z5 u/ t- uint32_t PageAddress; /*!< PageAdress: Initial FLASH page address to erase when mass erase is disabled! D# {: D- {$ K! u
- This parameter must be a number between Min_Data = FLASH_BASE and Max_Data = FLASH_BANK1_END */2 x( N ?% N8 y2 r) }) {9 Z3 ^
- & M$ B% y% ~. i; L! J" _
- uint32_t NbPages; /*!< NbPages: Number of pagess to be erased.
0 q) \+ E5 N( e8 \ - This parameter must be a value between Min_Data = 1 and Max_Data = (max number of pages - value of initial page)*/( f; ]1 F: R) S. Y6 o
- , x1 {& ~7 o- X" Q6 C6 ]- s
- } FLASH_EraseInitTypeDef;
& w* q$ T9 D! O! s
复制代码 : q4 b& R+ T" a7 P0 H
所以在使用擦除函数之前,我们先定义一个结构体,并初始化它:1 \# b& ^2 N; b& z
- static FLASH_EraseInitTypeDef EraseInitStruct = {& M" X1 ]( {( z4 T3 Z0 t* a: ]
- .TypeErase = FLASH_TYPEERASE_PAGES, //擦除类型:page擦除,即擦除整页。也可以选择擦除整片
6 W6 _+ n' N3 T7 S2 [ - .PageAddress = 0x08008000, //擦除起始地址
6 Q. S* k) C1 M& | - .NbPages = 1 //擦除页数- T; ?3 A6 B2 x8 ^1 D4 h m9 }/ b+ a
- };
复制代码 , v9 a+ I, j( ]. m
初始化好结构体之后,就可以使用HAL官方提供的API进行擦除:( V- `. e! w6 R- B8 b& W! P
- j; \8 o C" T7 W* {, N" M3 [5 l# P& o* j- HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
复制代码
8 w% L) K! `' X1 q/ W其中第一个参数为指针,我们传入我们上文初始化好的结构体,第二个参数用来存放错误信息,我们新建一个值来存储,以下是示例代码:
/ _* M% J3 j$ @0 Q- ... ...
S1 V$ q& [- s/ I% v* v( D% k - uint32_t PageError = 0;
. n" {" g! h4 W o$ z+ i - HAL_FLASH_Unlock();& N9 W8 O! `: d- n
- if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)
* q( e3 V1 n2 A$ b/ D) w - {+ ~( q& N1 n0 H/ {' b. Y/ i
- printf("Erase Succeed\r\n");) a1 o4 {& r( d: ]: A @
- }
2 z$ s/ x; ?' A: o - HAL_FLASH_Lock();0 K+ g8 r* f" C" D" I: e2 q+ }
- ... ...
复制代码
) W2 N4 o) g! i, V" P$ ?- e4 X如果你有ST-LINK或者J-LINK此类的调试器,可以使用硬件调试,然后查看0x08000000对应扇区的值,你会发现擦除后的数据全是0xFF。, t# T( h% I8 |' ~& p& l2 X
8 E4 ^* ]4 I C- s
Flash 的读写操作
" R2 l/ K$ w" ~3 X# rFlash的写操作$ B4 g' ]5 f- C/ `
当我们擦除了一个扇区,该扇区就属于可以写状态,我们可以通过读Flash来确认我们写入的值,HAL库提供的写操作函数如下:! D/ J! v) _3 d( |
- HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
复制代码 : O" s7 B% |9 }) G( a
其中:- o0 R2 l# n$ {2 V `# {
- - TypeProgram :写类型,只可以是半字(2个字节)或字(4个字节)
; r: F4 V! `6 Q6 J0 B$ I6 ?* m) g2 B - - Address :地址; d1 [3 P; t' w1 E
- - Data :写入的数据的值
复制代码
3 \# H1 a* |+ Y7 jFlash的读操作) G1 l7 p) a& M( p
Flash 只需要所在地址,就可以读取对应的值:
4 _* Y8 @* c `( [9 i' m- uint32_t pValue= *(__IO uint32_t*)(addr);
复制代码
4 }# ^8 \$ c4 f5 y以下程序写入一个值,并将整个值读取出来:
$ a8 X. |, G" d- ... ...
7 L l( e+ T2 o- I: X! y. [ - uint32_t writeFlashData = 0x55555555;$ g9 f, H/ u2 }6 T
- uint32_t addr = 0x08008000;+ b% p. q& P' f
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr, writeFlashData);5 @7 v# Z* {0 |/ D
- printf("at address:0x%x, read value:0x%x\r\n", addr, *(__IO uint32_t*)addr);
1 r* p( k9 d/ r& d. B - HAL_FLASH_Lock();6 @3 C+ }% T% x+ T/ ~: N
- ... ...
复制代码
" Z+ _5 P! F1 a. z( {: R6 V8 z* V注意
% {1 N0 M8 [6 m; i1 A* a由于我们经常使用Flash来存放用户程序,所以要特别注意我们读写的时候不破坏到原程序,本文因为程序很小,不可能写到0x08008000,所以直接用这个地址作为例子,在实际运行中,我们需要设计要那些区域是可以用来读写的,避免出现程序错误。6 V* G8 P7 t* L' |$ w) P8 p6 w
: G' x2 t( m$ g! B
5 k4 Y1 r$ w6 e; { T$ ]* V5 \6 ?9 O% v) M5 a/ t* c0 k
|