1、什么是Flash y2 J/ C0 t% ^
FLASH闪存是属于内存器件的一种,其存储特性相当于硬盘,下图就能看出Flash扇区就等于电脑硬盘分区,但是对Flash进行写数据时必须先进行扇区擦除,然后才能再写入,否则会写入失败,MCU的Flash大小可参考数据手册
3 @# W, Y+ _& E: Y7 X2 q! N4 T! U" W' Y3 D
( s4 ~; \ \3 }6 l0 U3 v" U6 ^1 z
, T+ e6 s, N/ S, h2 Z
2、闪存模块存储器组织
$ t% [! b. B' Q7 ^此为STM32F407ZGT6的FLASH大小为1024K0 \4 s; z3 V) n6 J. ?
6 P; F( ~4 X3 z' G7 A
. I3 M- k4 a( t: |/ z5 A3 I# t
' Y; i, [) {4 N8 t不同容量的STM32F4,拥有的扇区数不一样,比如我们的STM32F407ZGT6,则拥有全部12个扇区。从上图可以看出主存储器的起始地址就是0X08000000, B0、B1都接GND的时候,就是从0X08000000开始运行代码的。
! L; ?* N+ X1 A6 i8 {; m @8 W+ {0 T* C$ o% ^7 a" v% X" l7 l
STM32F40x的闪存模块由:主存储器、系统存储器、OPT区域和选项字节等4部分组成:
6 j' u! [, K0 I% Q
# W' h$ g( ~" w+ l+ w3 ^# k2.1、主存储器* v6 B1 E: T+ o4 }
该部分用来存放代码和数据常数(如const类型的数据)。分为12个扇区,前4个扇区为16KB大小,然后扇区4是64KB大小,扇区5~11是128K大小。6 d( z. i2 V y6 m/ H- k8 F
$ }9 X3 T" B+ X: g! ^4 D
2.2、系统存储器' P. [( H( Y' W) ^$ g2 h
主要用来存放STM32F4的bootloader代码,此代码是出厂的时候就固化在STM32F4里面了,专门来给主存储器下载代码的。当B0接V3.3,B1接GND的时候,从该存储器启动(即进入串口下载模式)。1 P6 L3 h/ ?9 c
! N0 ]7 |* z3 D0 x6 P
2.3、OTP区域( U. O; F: ~% `% U& {
即一次性可编程区域,共528字节,被分成两个部分,前面512字节(32字节为1块,分成16块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面16字节,用于锁定对应块。
! y. {" x4 Q3 V. r& P
/ _1 L% y7 G( D' @5 g6 j2.4、选项字节
S' P& D$ \: ]; q8 \用于配置读保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。
) {% F& q$ h) E9 C$ l, p: J7 x7 D k# _7 n/ y1 ]4 Y$ K
3、STM32F4的闪存6个32位寄存器控制
7 }- H5 }6 z- `' N0 Y y) R①访问控制寄存器(FLASH_ACR)* n: j" J5 q' K5 ~# j# X6 h
$ K7 @ u0 d# u6 B②密钥寄存器(FLASH_KEYR)/ C9 ~" l2 k1 B% D% Y
: K7 }* k1 i: o& [ 其中FPEC 密钥总共有2个键值:9 [6 R" ?/ ]7 z9 T% {1 @; N- k
$ h) z, o2 X5 O1 O* G
. G5 a& x' |- B9 W; W' m9 y0 _
9 V1 @) B6 i& X- c& k③选项秘钥寄存器(FLASH_OPTKEYR)
, D% k5 {6 d s8 V
# ^/ x; e9 x% a M9 p0 l④状态寄存器(FLASH_SR)
: R8 e7 O" g* [# \" ^ H. d3 i3 V: g5 I: K# _# a. R: }
⑤控制寄存器(FLASH_CR)
. X( { R& Q* C$ j( P) ^
2 y6 X8 }" v* x- a+ V+ {1 r) z⑥选项控制寄存器(FLASH_OPTCR)8 q! ?; J: Z) q, f# c7 o
9 p: K) B/ y! [0 K q! q: I相关寄存器的操作可参考《STM32F4xx中文参考手册》的第3.8小节
! l, r" x) C0 W$ z& E* E
. z0 t/ ^0 U5 f0 X% [8 y" U7 W! l8 V4、Flash等待周期与CPU时钟频率间的关系5 w5 o& U# I9 A( a1 h
为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地设置等待周期数 (LATENCY)。当电源电压低于2.1V 时,必须关闭预取缓冲器。1 u. F3 b' T; l/ P9 z3 D% E
) h: R2 b5 n- a
6 W0 F: g% C& ]8 P
' }2 W! I' U% q4 B" D! J
我们供电电压一般是3.3V,所以,在我们设置168Mhz频率作为CPU时钟之前,必须先设置LATENCY为5,否则FLASH读写可能出错,导致死机。
% V: ~" z, h, [' Y# W' Z5 N" }7 c2 s
" ]( `: f/ L$ f0 w5、FLASH的操作介绍
( v' t; D& q& h4 J5.1、读, j4 g' ~' U3 E: ~/ J Q
STM23F4的FLASH读取是很简单的。例如,我们要从地址addr,读取一个字(字节为8位,半字为16位,字为32位),可以通过如下的语句读取:
* E {0 G u) R
) P8 S) _0 A5 h9 Y ?- O data=*(vu32*)addr;
' B- E. y: ^ t$ q, l6 |
. ?( M. \% S7 A# g3 I5 q将addr强制转换为vu32指针,然后取该指针所指向的地址的值,即得到了addr地址的值。类似的,将上面的vu32改vu16,即可读取指定地址的一个半字。
9 c+ j H8 ?4 u7 [
; f. `+ v3 U, O, m: v" x5.2、写
$ ~7 h# w" W: Q* k在对 STM32F4的Flash执行写入或擦除操作期间,任何读取Flash的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从Flash中执行代码或数据获取操作。
9 d' p' @: Z$ `6 H
4 B5 i. L5 [7 Z0 U) U注意:
3 T3 _4 m2 z" g$ M8 h+ t; M4 ?- j: T) ~* B# N0 Q
编程前,要确保要写如地址的FLASH已经擦除。0 [4 _1 Z4 f* |4 K1 | x
要先解锁(否则不能操作FLASH_CR)。. l: o8 l6 ?; z# X: i# S
编程操作对OPT区域也有效,方法一模一样。3 L7 O0 [ e# U3 d: r, o3 l
5.3、闪存擦除0 l' w& D% S/ G$ T
我们在STM32F4的FLASH编程的时候,先判断首地址是否被擦除了,STM32F4的闪存擦除分为两种:扇区擦除整片擦除。
2 r* ]" }; p, e+ K% ^2 K1 \; F. a+ k# q' Z+ K \% J
5.3.1、扇区擦除步骤8 T0 d' m/ B, a6 `, {; U
①检查FLASH_CR的LOCK是否解锁,如果没有则先解锁) P" y/ q* \ H# E
9 H! |3 p+ B k S j+ O' D( l
②检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作
5 g" Q& {' ~$ S9 N9 A2 p. T2 r1 ^ [5 w6 Z# _, h
③将FLASH_CR控制寄存器的SER位置1,并从主存储块的12个扇区中选择要擦除的扇区 (SNB)8 K" s2 {3 ?2 w7 R
' J) A* C3 o0 p7 r5 Q
④将FLASH_CR寄存器中的STRT位置1,触发擦除操作
2 h3 E: }, K2 T
. }' ]- L% ? G. j⑤等待BSY位清零
- `& S2 {! [* H+ o, ]# C* b6 a* K0 e7 ^1 l8 s( E* i9 J( P
经过以上五步,就可以擦除某个扇区。
7 w$ z* U# }, z! j6 x0 z# ^) [) b5 f0 F
5.3.2、批量/整片擦除步骤
7 Y/ u2 x1 P/ D: P9 F5 E①检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作8 ]6 L4 ~( C! D1 U) p
9 }" J& A6 F* A- Q# C3 m. d+ W. V②在FLASH_CR寄存器中,将MER位置1 (STM32F407xx/405xx/43xxx)
/ o( q7 [9 R2 K: A2 t; d9 X [$ ]5 u9 @. J+ h. b
③将FLASH_CR寄存器中的STRT位置1,触发擦除操作' F f. l5 u5 Y- U9 J
( L ~1 H: {# r/ z; j6 N
④等待BSY位清零( O1 Y) D# r( ~- S" e$ F1 [' G1 K
' [/ \! z1 s& W6 `- f8 [
经过以上四步,就可以批量擦除扇区。扇区擦除功能可以看《STM32F4xx中文参考手册》第3.5小节。
6 H: E; O; B+ K* \) M2 J
7 ^, z& _2 q/ k9 H% F6、STM32F4的标准编程步骤1 i, Q( o, ~- d# x& ~6 K8 H
①检查FLASH_SR中的BSY位,确保当前未执行任何FLASH操作。
1 Z" A# p- n; C
6 T/ J% a1 j% \6 e3 ^" [②将FLASH_CR寄存器中的PG位置1,激活FLASH编程。/ ~$ o: ~6 i( P* ?) M3 F/ s* ]
5 D# @+ j& e+ B1 y" Q③针对所需存储器地址(主存储器块或OTP区域内)执行数据写入操作:
" g& N. G( i5 [. `5 Q2 b, \$ y" \. B
—并行位数为x8时按字节写入(PSIZE=00)
" `7 K7 `& `# r0 m) b+ C( T# k7 k2 V d [ N! u' r
—并行位数为x16时按半字写入(PSIZE=01)
% S9 }' |- p5 f8 d1 n, d/ |7 e; t$ M" ?; Z9 Q0 n) J8 Y
—并行位数为x32时按字写入(PSIZE=02)7 t$ B" M8 a, }2 F' o5 L
9 o: d6 @9 ~4 G" d
—并行位数为x64时按双字写入(PSIZE=03)
' l$ j. i, c0 ~
! s* }" O( w2 W! K④等待BSY位清零,完成一次编程。
" M1 n: c0 z6 ]" Y# _, h3 f/ A3 r( {) ]5 ~( v0 ^
按以上四步操作,就可以完成一次FLASH编程。不过有几点要注意:+ M& o `* ^1 o+ Q
4 e0 O& b3 Z) g5 k1、编程前,要确保要写如地址的FLASH已经擦除) o- a/ I7 v& J: S- X$ N: S8 d
* A6 m2 W5 i$ h: a/ ~4 o4 l2、要先解锁(否则不能操作FLASH_CR)( N Z: }- Y' }. |- S7 l
8 Z! F" p6 o; L
3、编程操作对OPT区域也有效,方法一模一样' K4 _7 J; ~# c% F" M1 g5 I
! b+ x- Y6 u& O; d3 w4、Flash写数据时会把整个扇区擦除掉,所以原来的数据得不到保存,如果想要保存数据,需要分配一个扇区大小的内存去存储原来的数据,占用内存太大,不像SPI的W25Qxx那么方便" A# v& w7 }8 A& Z' H6 l/ F+ H' u5 L
% \+ s" W, ?8 `* W: ]7、代码的编写实现& A2 E- |* M/ g: y% a% b0 D
flash.c:
* K% k" J6 K+ \7 ?! S
, I, h5 B# k; U6 k- #include "stmflash.h"
; u% d/ v8 s e0 q% @ - #include "delay.h"
0 i7 {" {% `' y2 `3 s- B% D! K - 7 @6 }+ y+ d* K( r& \ R, ~
) h9 k0 J" t; L0 @# R- //读取指定地址的半字(16位数据) 6 D- I( s; M3 h; E
- //faddr:读地址 / e6 s! b$ e& T1 j
- //返回值:对应数据.
6 Z8 ?1 g, G/ a - u32 STMFLASH_ReadWord(u32 faddr)/ u8 E2 N4 M# ?& B5 ?2 |- Q
- {
, N; ~$ e" C; ? w% F8 { - return *(vu32*)faddr;
. f; E" p1 M6 f' B# H5 t - }
: T. M2 C1 n* ~) ~0 N% Q2 E" q - ) a: Y: f0 M9 F
- //获取某个地址所在的flash扇区
, _- s* s9 s- o% A9 t; _5 N2 o - //addr:flash地址
+ T) b, K, F B% {2 A$ ? - //返回值:0~11,即addr所在的扇区% }; t4 ?. ?! r' P1 j, s7 z6 p- Z k
- uint16_t STMFLASH_GetFlashSector(u32 addr): i7 @& P+ L) e( |' q
- {
& p8 Q* K; R' ^( U) p4 ~ - if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0; W7 r# Z* U6 D6 G2 r; _$ N; d
- else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;# L* W2 Z4 ]. r! t$ B! X7 W
- else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
3 B; M F |% t; A# ~5 X# z - else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;4 L n: H4 M) c" V+ b: v. V- A
- else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
+ a- H2 H2 U9 W! r" R8 |" p - else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;9 f i. i z! }! i
- else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
2 }/ U# s% n4 H4 v) u0 h - else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
. w0 m1 P3 J3 ^$ v. L( l1 R - else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;
% t6 m/ j) k! I5 o7 Q5 ] - else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;$ `" s Q' N' N; ~) \4 f
- else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10;
$ ?& N# z1 y7 R9 b# ?0 R, e9 a - return FLASH_Sector_11; $ V6 v2 d3 W. j8 C
- }
4 e( \) J/ v0 @. h
6 j& Z6 M9 ]3 P- Q) K- //从指定地址开始写入指定长度的数据
0 X( c5 ~& p. [# V - //特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数6 w* ^: _& v: N0 f1 y; G
- // 写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
, v5 b: v# a, h) Y8 r: W ` - // 写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
) _+ o0 R4 o; o+ X3 X0 P - // 没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写.
- e7 e4 f& E7 @5 O! Q, B - //该函数对OTP区域也有效!可以用来写OTP区!2 T ~3 g# [3 d/ i* C
- //OTP区域地址范围:0X1FFF7800~0X1FFF7A0F
- T2 _) o$ L$ _6 U7 _ - //WriteAddr:起始地址(此地址必须为4的倍数!!)
% Z1 V5 w( c& F$ M1 M2 b, b - //pBuffer:数据指针: T b0 D- `0 n. e) R A2 r8 [1 e/ m
- //NumToWrite:字(32位)数(就是要写入的32位数据的个数.) 3 X) a/ p, _6 \ ?
- void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
' q7 n* F* E& q f - {
5 h1 c$ \- B9 U+ I$ @ - FLASH_Status status = FLASH_COMPLETE;/ ], c5 M7 [" N, C) @
- u32 addrx=0;
, m0 S7 f' ?9 d2 Z - u32 endaddr=0; t8 _1 C/ A* S# b3 L6 F
- if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址
9 S# t" j% i4 u" m: t - FLASH_Unlock(); //解锁 ①2 S) L! x8 d% m
- FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存$ E6 S U5 s# o- V! S! N3 ]
-
% b. ?" p2 t; _8 l0 i) H* [ - addrx=WriteAddr; //写入的起始地址: b: C+ P) G5 @) i5 ^
- endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
# X0 C) j' G/ w; s0 c1 |" a! z - if(addrx<0X1FFF0000) //只有主存储区,才需要执行擦除操作!!" R! C+ E, Z; [0 A' n2 m
- {
+ A7 `/ f; ~( z) }" C - while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)# i/ t/ H# F) E; t
- {
9 c7 n/ R% U1 R! \ ?+ \' \ - if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区 ③ ]3 k5 ^) N# [ g3 ]% B7 ~# G
- { $ m: p! L1 `. e, ~' d
- status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间!! H- p9 ~, R5 m5 a" J; h3 [
- if(status!=FLASH_COMPLETE)break; //发生错误了
( P9 V H; }8 v& S) L - }else addrx+=4;* R) u) ]. ~4 g
- }
' G0 ], E/ D8 ~1 m - }/ I3 ~: E; R Z& a
- if(status==FLASH_COMPLETE)9 |1 o ] a8 d$ A' A' p. J& z+ s
- {- {/ O' q* o/ u+ r! N
- while(WriteAddr<endaddr)//写数据
2 Z) _* o" E9 ]- O - {
) \- ^/ t9 d. S# W; o' R4 ^2 u - if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据④+ f4 N; h! t3 D! d7 ]
- { + G7 P' V( z( X7 B3 m/ G
- break; //写入异常
( s+ z' O- u; _ ~4 d j - }+ h" E6 E1 y. |3 _, G
- WriteAddr+=4;' u X" E3 \" A& Q
- pBuffer++;& p- ~( u; V: n- Q1 Q* i' c/ Y
- } . H; _' a6 ]& T
- }
. I' U6 A) z L) U- |" D - FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存! x! t0 i. Q' w' ^+ J/ W' _$ _
- FLASH_Lock();//上锁 ⑤/ K* A3 B Z+ G" ?
- }
( Q9 [5 f. a/ i$ V! v& i% c
8 i% ]" S' T+ U3 V) s* K# T- //从指定地址开始读出指定长度的数据
0 l2 Q6 M% R$ i - //ReadAddr:起始地址& b q0 b" c8 ^1 z
- //pBuffer:数据指针. k& Y4 L. i2 G# I$ w- t9 I
- //NumToRead:字(4位)数: f, o# b/ y' {# s" H
- void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)
i- \" H! `9 B8 V - {
1 s5 \- Z6 F/ [7 m5 X - u32 i;5 U3 S9 }, f+ N) G+ X2 V
- for(i=0;i<NumToRead;i++)
9 d" _1 w5 Q/ |/ ~! L, n- I) ~ - {' o( F6 ~0 e. v" q2 j. s! ^8 j
- pBuffer<i>=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
' I2 ~8 u/ y. O* @ - ReadAddr+=4;//偏移4个字节.
9 O2 k2 M- z1 e1 z& y6 L2 Z0 P5 ? - }$ i& r) a2 l0 K$ v$ @0 @
- }</i>
复制代码 8 p& c x# {* g, R' |
flash.h
/ S2 M; F" @ y( S K% [
+ z. j, j9 S: j4 L) G5 m/ Z7 H- <i>#ifndef __STMFLASH_H__</i>
, e( L! L O7 E9 @# L' R5 u8 m - <i>#define __STMFLASH_H__</i>
2 c. e0 w& Y: X' y - <i>#include "sys.h" </i>: n' j2 E# W; V1 ]1 }0 i6 i
- 1 F! X; y& b5 X* C- |- E# v1 d: q
- <i>//FLASH起始地址</i>/ o$ n% [1 v G& b" A- B
- <i>#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址</i>
1 y1 c# `& g+ E - + A5 ^; ]3 m8 [; W4 Y+ t- ^1 z: s
- <i>//FLASH 扇区的起始地址</i>! m6 D V! M. e* t
- <i>#define ADDR_FLASH_SECTOR_0 ((u32)0x08000000) //扇区0起始地址, 16 Kbytes </i>- w% ?3 P, K" W4 b& W+ O3 L
- <i>#define ADDR_FLASH_SECTOR_1 ((u32)0x08004000) //扇区1起始地址, 16 Kbytes </i>
6 }; x4 g c! e% M) V: }* W - <i>#define ADDR_FLASH_SECTOR_2 ((u32)0x08008000) //扇区2起始地址, 16 Kbytes </i>
7 i: P6 b0 _: @% i1 k: W/ w) e - <i>#define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000) //扇区3起始地址, 16 Kbytes </i>
4 q, L, s" K# }4 e+ ~ - <i>#define ADDR_FLASH_SECTOR_4 ((u32)0x08010000) //扇区4起始地址, 64 Kbytes </i>
9 S/ v4 M6 P; Z- C+ X9 e8 w: t* \' m - <i>#define ADDR_FLASH_SECTOR_5 ((u32)0x08020000) //扇区5起始地址, 128 Kbytes </i>( p D" n' P) I
- <i>#define ADDR_FLASH_SECTOR_6 ((u32)0x08040000) //扇区6起始地址, 128 Kbytes </i>8 B' z) B5 d5 N! E& R
- <i>#define ADDR_FLASH_SECTOR_7 ((u32)0x08060000) //扇区7起始地址, 128 Kbytes </i>5 Q4 c& i1 o+ u$ L8 [8 T$ {( y
- <i>#define ADDR_FLASH_SECTOR_8 ((u32)0x08080000) //扇区8起始地址, 128 Kbytes </i>7 d; o$ Z% A: d! G1 F
- <i>#define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000) //扇区9起始地址, 128 Kbytes </i>
. U3 T7 \3 ]4 I% {0 ]( q - <i>#define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000) //扇区10起始地址,128 Kbytes </i>
9 w; j9 M6 J1 S9 z! t - <i>#define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000) //扇区11起始地址,128 Kbytes </i>4 {) u( {9 ^% ]6 M5 L4 r& [* c$ `
, `3 i, u- H' \$ G4 H. u- <i>u32 STMFLASH_ReadWord(u32 faddr); //读出字 </i>" _" p' Y0 s! X1 V
- <i>void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite); //从指定地址开始写入指定长度的数据</i>
+ S' c8 j* Q& d# p - <i>void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead); //从指定地址开始读出指定长度的数据</i>
8 h" F+ V7 ]7 K" L- c f: U - <i> </i>
K0 e% [& k2 ]" E* Z) p - <i>#endif</i>7 b! I5 a# s, k
# [' w) q G+ [5 t3 m- g7 q% F
, J7 v% ?' i, k
复制代码 : e7 V7 ?1 V7 T* ?" o! O2 @
5 C+ @% p2 k3 t$ ] |