1、什么是Flash/ R* \# r3 h9 \6 P( G
FLASH闪存是属于内存器件的一种,其存储特性相当于硬盘,下图就能看出Flash扇区就等于电脑硬盘分区,但是对Flash进行写数据时必须先进行扇区擦除,然后才能再写入,否则会写入失败,MCU的Flash大小可参考数据手册, U! Z" ~; R# d
0 ?) x2 y; l5 T" y
- c$ b# q- Q2 e& R/ D
' Y; T4 H, o" z6 }3 [5 n2、闪存模块存储器组织
7 a- ?. B% C9 b6 Z" A- J4 \, j此为STM32F407ZGT6的FLASH大小为1024K5 n6 |8 R% M) O' p
3 c+ N6 w- X- `
- ?0 l! N% c4 @
( v& d, `- {4 y9 [4 F: l5 d
不同容量的STM32F4,拥有的扇区数不一样,比如我们的STM32F407ZGT6,则拥有全部12个扇区。从上图可以看出主存储器的起始地址就是0X08000000, B0、B1都接GND的时候,就是从0X08000000开始运行代码的。- r) z8 y- u9 q ?) b5 ?; Y. [
( D+ [5 A- b t) J, i1 X& ^1 l1 Y# t9 J
STM32F40x的闪存模块由:主存储器、系统存储器、OPT区域和选项字节等4部分组成:/ J9 f* S+ f6 p: ?5 a; P
; `% L l2 g# V2.1、主存储器
( ^2 q0 P. X) C) j# y; z该部分用来存放代码和数据常数(如const类型的数据)。分为12个扇区,前4个扇区为16KB大小,然后扇区4是64KB大小,扇区5~11是128K大小。* P. U3 f' \ o% Y, p
$ f+ v6 M* f' k2 e$ p$ n2.2、系统存储器
( t R: k; v+ H! F% b主要用来存放STM32F4的bootloader代码,此代码是出厂的时候就固化在STM32F4里面了,专门来给主存储器下载代码的。当B0接V3.3,B1接GND的时候,从该存储器启动(即进入串口下载模式)。
7 h! {1 O: V0 R/ h. u/ f! W
. `, X! _: d1 c2.3、OTP区域
5 o, B/ ^3 i$ x* ~5 k9 q. Z1 @+ \即一次性可编程区域,共528字节,被分成两个部分,前面512字节(32字节为1块,分成16块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面16字节,用于锁定对应块。
% s2 o% p+ h- S$ j- u6 T
' Y- w1 b- `+ X% y; S# f# }2.4、选项字节2 S L8 ~1 s3 s! \6 k
用于配置读保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。9 F( A4 Y2 Z [5 X! i% G) J
) J) x/ p: \5 Z M: U
3、STM32F4的闪存6个32位寄存器控制
& B% d- `1 |9 V6 z①访问控制寄存器(FLASH_ACR), K) P1 Z/ O2 b8 C2 l" E
* P; z, S" e* e, ?1 n' s
②密钥寄存器(FLASH_KEYR)
# X& O& C5 }! ~) ]2 ]
+ a6 O' d2 e/ k/ V% p 其中FPEC 密钥总共有2个键值:$ z, X" O) u' G3 T% j$ N9 z: S Q
: w* v5 ^2 ~9 m# e! P2 _8 N; g
# \+ l" w8 ^& i
) M9 f/ u. @6 ]; `" g* j1 l* ]. w③选项秘钥寄存器(FLASH_OPTKEYR)
, h3 f$ F9 ?3 n% k) p4 N1 \$ k1 H
④状态寄存器(FLASH_SR)
: @4 H% Z% G4 r! S
1 E/ V6 L" p( i! ~) h7 O- K⑤控制寄存器(FLASH_CR)( }& b7 X7 h" M6 l# {* F
% W8 l7 |/ c5 { q+ ?* C8 C0 I⑥选项控制寄存器(FLASH_OPTCR)
1 g! Z$ w9 }2 _* l- O& I3 j @" }' o+ f
相关寄存器的操作可参考《STM32F4xx中文参考手册》的第3.8小节9 q& p! X1 ~2 z& C! n7 V
$ z4 @- B. j$ ?8 n, q0 s
4、Flash等待周期与CPU时钟频率间的关系2 K9 K G: U; Z; R4 r
为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地设置等待周期数 (LATENCY)。当电源电压低于2.1V 时,必须关闭预取缓冲器。4 m1 d8 p) J. ]8 F4 ]6 B
0 c. U5 u/ A1 s* N
/ o7 h w, C* ]" I* h
8 c' K" }: D0 s我们供电电压一般是3.3V,所以,在我们设置168Mhz频率作为CPU时钟之前,必须先设置LATENCY为5,否则FLASH读写可能出错,导致死机。# b5 T0 b2 M# R" b
+ p1 g2 P- S# |4 N5、FLASH的操作介绍
1 X9 q4 D) j% u1 z6 e; ]( z4 a1 Y5.1、读
& k8 x4 n% S6 \" xSTM23F4的FLASH读取是很简单的。例如,我们要从地址addr,读取一个字(字节为8位,半字为16位,字为32位),可以通过如下的语句读取:) n0 B/ W) z5 l9 ~
0 x/ ~. Z: N+ _' @. b" N
data=*(vu32*)addr;5 O% @/ c* r& N) k2 L: V' H
2 k r& ?% n* A8 |7 j
将addr强制转换为vu32指针,然后取该指针所指向的地址的值,即得到了addr地址的值。类似的,将上面的vu32改vu16,即可读取指定地址的一个半字。
- `5 w" z' ?. L. W' i: ?5 G
+ ~9 ?1 L p+ e. T+ T o5.2、写6 n) s2 c' I- G7 ^
在对 STM32F4的Flash执行写入或擦除操作期间,任何读取Flash的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从Flash中执行代码或数据获取操作。7 q. h! b( }$ N
+ D& `3 x* m2 _, ~3 M注意:/ W% U* ^$ D: W+ j6 G+ `2 g9 J% H
$ c% O8 W' I! d. ]9 Q' q: D3 R
编程前,要确保要写如地址的FLASH已经擦除。
; q4 ^+ X2 R: Z/ ?$ V/ K2 H要先解锁(否则不能操作FLASH_CR)。
! A7 y9 H- n0 f Z 编程操作对OPT区域也有效,方法一模一样。' V8 n3 ~* `* H; Y0 A
5.3、闪存擦除+ T( H. b5 {/ _) K& k/ r
我们在STM32F4的FLASH编程的时候,先判断首地址是否被擦除了,STM32F4的闪存擦除分为两种:扇区擦除整片擦除。
4 n; o; S5 n; [+ @# q1 l- I
4 W# i; T7 r. l- ^! n- U5.3.1、扇区擦除步骤
% `1 H& P- k, L0 L①检查FLASH_CR的LOCK是否解锁,如果没有则先解锁
# J5 |7 J+ \6 X# y
1 W) C* \5 ^' O$ Y/ h②检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作
[: \. Z. I5 V
# X2 [8 ?: s+ r. k W6 x5 m③将FLASH_CR控制寄存器的SER位置1,并从主存储块的12个扇区中选择要擦除的扇区 (SNB)7 L6 h% s3 D1 m- w7 E6 F0 T
. R2 ~/ ]- u4 B2 q: U! t④将FLASH_CR寄存器中的STRT位置1,触发擦除操作! ^; @- v4 t1 G( f. | ^
! ^) [, E' A+ y/ y) L/ z3 ~
⑤等待BSY位清零
8 o( a& v( q0 c g7 Z0 [ k5 L
- d! I9 y( M' t+ W" V经过以上五步,就可以擦除某个扇区。
2 t9 X6 v# \8 w2 {. d
" s) J9 |/ e. y) h4 S. b/ ~5.3.2、批量/整片擦除步骤+ ?/ o- T' X! G4 p2 J
①检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作
1 b0 I( C" }: s7 j5 }
7 R, G& G5 y) u5 a+ B②在FLASH_CR寄存器中,将MER位置1 (STM32F407xx/405xx/43xxx)' R6 x. s- T |/ ^ |; L2 f
; U1 ]+ J: Y- U3 G& Y) Q③将FLASH_CR寄存器中的STRT位置1,触发擦除操作# p3 Q3 u" E- h! ]4 T. K6 S
4 f8 t! u! R) L2 i5 M4 n④等待BSY位清零
3 j+ t, _4 P8 { n( f/ n8 C! r- Y: S# G6 G: W# V
经过以上四步,就可以批量擦除扇区。扇区擦除功能可以看《STM32F4xx中文参考手册》第3.5小节。% O# g+ I. s) D9 q u5 B1 n/ Y- V
( @* E8 G+ {8 n4 m
6、STM32F4的标准编程步骤 ]5 d5 J0 t8 T5 c: i
①检查FLASH_SR中的BSY位,确保当前未执行任何FLASH操作。/ L6 f& i0 s" h
' h, C+ m) c" o& v( ^5 J2 O
②将FLASH_CR寄存器中的PG位置1,激活FLASH编程。$ Y1 H# P2 q$ U) l
7 _+ Y0 L: X; [4 `1 O' n; t③针对所需存储器地址(主存储器块或OTP区域内)执行数据写入操作:, ?5 O$ \" |9 Z
- t9 G9 w" d* p
—并行位数为x8时按字节写入(PSIZE=00)4 O" k+ N) H- c# z# X, T: ?+ @
6 w6 x3 X1 H2 _4 ?7 L9 Z
—并行位数为x16时按半字写入(PSIZE=01)
5 g; G1 [0 w4 k4 A$ V1 b4 @% s6 O' V r5 t6 L; [& c7 m
—并行位数为x32时按字写入(PSIZE=02)
- z: F9 ], W7 G8 u0 N
) [, a9 T0 ]( J- ]: ^8 E' y% X —并行位数为x64时按双字写入(PSIZE=03)
# V7 G- M/ [, E2 O
# j! `* z d$ |# D) c; c④等待BSY位清零,完成一次编程。
( l$ i/ c( u- y' C/ w2 w% E6 x) I' j, {% G/ O7 F
按以上四步操作,就可以完成一次FLASH编程。不过有几点要注意:6 V, X, f* A3 A9 x7 P
3 e" ?$ o" |. C1 r. W1、编程前,要确保要写如地址的FLASH已经擦除: J3 b9 E7 E) \8 @+ ?
S0 c$ V- n4 [$ J
2、要先解锁(否则不能操作FLASH_CR)! F6 X) O$ R8 J
! L% Y4 P: @) s% F; J0 {- c& R' l
3、编程操作对OPT区域也有效,方法一模一样
& |# x: W* g( j# R: S- I0 s9 F/ ~$ h6 H
4、Flash写数据时会把整个扇区擦除掉,所以原来的数据得不到保存,如果想要保存数据,需要分配一个扇区大小的内存去存储原来的数据,占用内存太大,不像SPI的W25Qxx那么方便
/ E! ]1 L3 E4 n# F2 Q
' |( {+ c; _* X N+ G9 T7 C0 ^1 h7、代码的编写实现
2 \: b, @) R& D8 V' L- Xflash.c:
2 q" X( A( s, Y6 e/ p
. F- F$ [0 r P1 O& t* H K$ t8 ]- #include "stmflash.h"* i( A: K" L# e1 V1 @
- #include "delay.h"2 d7 K) E( f0 X5 k1 W
, u/ R% k" f2 A! V
) @* A8 j( W5 r' s. ]# `! `- //读取指定地址的半字(16位数据) 4 v7 P/ [2 w3 W/ \- d- G f( g
- //faddr:读地址 5 E$ @0 j' p' m% ^4 w" n
- //返回值:对应数据.: x) y6 Q3 ^% e0 H5 V" p
- u32 STMFLASH_ReadWord(u32 faddr), S' T% C* f) i; V" h
- {
, }- @. I# J: I4 V# G! P - return *(vu32*)faddr; ' E' a7 U) [+ G, Y' l9 p- @' S
- }
1 k) P* O, Q4 x. V4 y' h - , F& K2 t2 S- E; m, o% d; E
- //获取某个地址所在的flash扇区9 q* D9 \# T, U. V8 ^$ m- `' C
- //addr:flash地址' j Q4 M& X* d
- //返回值:0~11,即addr所在的扇区+ K. D" R5 V9 x( x
- uint16_t STMFLASH_GetFlashSector(u32 addr)
9 B9 Z8 [2 u0 B' F - {
+ Y. s8 C4 E, Z$ o1 c - if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;
+ ~0 y9 \' V; g8 D* r - else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;
, x+ x1 |6 y# W. m5 k$ n$ L$ }9 ` - else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
( H, }0 r- A6 y$ @' J- ]5 Z - else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;
: n. [( R* ]1 y5 P" u8 w - else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
0 X$ Y3 x1 j+ P5 h - else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;
9 j! t+ w% z3 t! @+ Z r& { - else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
& f( b7 O/ h9 s5 B/ O - else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
2 q& a) j: i/ H( y$ r - else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;' A/ {; S* L+ D* C9 ]
- else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;
5 Q1 H9 ?0 u ^+ N5 V9 v- z - else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10;
- \1 W1 ^2 d, P+ E& { - return FLASH_Sector_11; 5 P: B9 a5 e9 h% S5 e
- }: ?& Z! o) Q5 Q- F/ l. D
- 4 I P: R, N. L) _) H; s
- //从指定地址开始写入指定长度的数据, Q% \4 D" H2 B& Y: `
- //特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数
$ K+ _5 y D3 |' E# k& b - // 写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以" T' f! {% N% s8 H7 @
- // 写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
; l3 U6 F& i6 u" \" F9 g' h0 t - // 没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写.
0 l% m3 l* z8 a7 c' p$ O - //该函数对OTP区域也有效!可以用来写OTP区!7 o. ?9 s( P/ r
- //OTP区域地址范围:0X1FFF7800~0X1FFF7A0F& e5 Y: p, O5 a) D. p( ~! R- u: r
- //WriteAddr:起始地址(此地址必须为4的倍数!!)
8 }! }6 j& e( U$ _! k7 w - //pBuffer:数据指针9 ~$ x( U5 T7 A* I9 n8 ?
- //NumToWrite:字(32位)数(就是要写入的32位数据的个数.) ! q( y0 d- ^5 ^- r4 x
- void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite) * b0 @/ y9 H- B
- {
) G# ^$ q* Y$ y- t5 q - FLASH_Status status = FLASH_COMPLETE;: V# `) Y4 d1 o( W0 f
- u32 addrx=0;
1 s& k* }$ [+ h - u32 endaddr=0; g: ]9 ~5 D$ }$ x$ o) F. [5 r5 b
- if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址: d* ?: }# G3 { E, [. y( a
- FLASH_Unlock(); //解锁 ①' z. b: c( \4 N; b9 Z5 M% r
- FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存, P' t/ i: x6 y, x
- & a- g8 N3 J. T( C! t
- addrx=WriteAddr; //写入的起始地址# ?! H* `6 u, |0 G
- endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
( j s( ] K( N1 B - if(addrx<0X1FFF0000) //只有主存储区,才需要执行擦除操作!!
, G1 r( S* f, b% R5 U/ U - {. k% g( d0 r. O1 y* G/ l
- while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)( }+ D0 j* z5 l0 E( {
- {/ o. g8 T( @5 c. w; Q6 D
- if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区 ③7 [ o$ X: ?" q8 b/ q3 ~
- {
. @3 }9 [; e" }0 l& v - status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间!! t2 p G' k( M* s5 [; f0 j
- if(status!=FLASH_COMPLETE)break; //发生错误了6 K& d" b& }4 S# f( i
- }else addrx+=4;+ S+ m# V, G2 H) U1 j( E$ J
- } & o( A7 u+ q' m% e+ S* G
- } a4 ^) e( C! i7 P$ t$ Q
- if(status==FLASH_COMPLETE)0 I, N4 r: E% D
- {% r' ^7 F& V5 k+ w( y
- while(WriteAddr<endaddr)//写数据
1 V+ u: D Q& D/ x - {, K1 |. e. L5 \" s4 w
- if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据④
8 t, V0 k$ `; k - {
+ t* _9 [' X$ u# X5 ~8 d - break; //写入异常2 Z, D* {+ X' M
- }3 I$ z( n: a/ i& }) X h
- WriteAddr+=4;
4 j% T; r7 W3 H+ E - pBuffer++;
0 l" ^; _% s0 F' B: P/ H: a - }
6 ^7 g0 m; a4 p( f - }+ l2 ~" D! [6 W, t
- FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存; B+ L: K# v+ G+ {! i0 ^$ E! y- \
- FLASH_Lock();//上锁 ⑤
* I$ W" b- u/ b( V/ j - }
/ g! k3 W7 I1 |6 W2 s% o
2 T" g8 T/ n& I( _. `- //从指定地址开始读出指定长度的数据1 O! U/ U( \& p; W
- //ReadAddr:起始地址
3 g% m! @% P% Y6 U" Z4 q - //pBuffer:数据指针
' h' _6 l f% f- Y5 |0 G - //NumToRead:字(4位)数
' G7 x$ z- T) b4 X/ D3 p! {% W - void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead) + ~! }2 E A- X: v
- {
; A( I, }+ i% I3 s3 N: M* d - u32 i;
/ w3 w6 k! j3 H9 h9 t* Q - for(i=0;i<NumToRead;i++)0 O9 p. _! [. g3 E
- {
" L8 v% J4 Q; D7 J; H$ ?& k. j" d - pBuffer<i>=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
3 v. t; I( F% z% a0 Z$ K - ReadAddr+=4;//偏移4个字节. & Z4 A L* W3 |: P% g6 ~
- }
; R* N1 D) z; h+ R; ^ - }</i>
复制代码 7 d* z4 P5 L+ O, J
flash.h
5 B5 r( b. x4 B6 d1 ~" d& i$ \; }
4 z, j) T0 T0 \, t# [) s% H) g6 [9 t- <i>#ifndef __STMFLASH_H__</i>
) T; |7 X* {9 [9 E8 Y - <i>#define __STMFLASH_H__</i>, [; n: ~4 t" H" h; k
- <i>#include "sys.h" </i>
( P4 Q3 {3 a' \- m& U# W2 N: ]) w
, \' H( V) _$ k% e9 J- <i>//FLASH起始地址</i>
5 N+ `; {8 p5 X$ Z1 f - <i>#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址</i>, r0 @) {& l8 v$ c
- 6 V# l O. ^- Z H! d
- <i>//FLASH 扇区的起始地址</i>0 t' b0 ~" L) ]7 O4 y1 x4 a
- <i>#define ADDR_FLASH_SECTOR_0 ((u32)0x08000000) //扇区0起始地址, 16 Kbytes </i>
4 q& U6 x+ x2 |$ K" U+ U - <i>#define ADDR_FLASH_SECTOR_1 ((u32)0x08004000) //扇区1起始地址, 16 Kbytes </i>( S Z0 E# y; o/ m! d
- <i>#define ADDR_FLASH_SECTOR_2 ((u32)0x08008000) //扇区2起始地址, 16 Kbytes </i># ~7 z9 L2 F1 v6 Z; l4 ?) \$ z
- <i>#define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000) //扇区3起始地址, 16 Kbytes </i>
+ z! p1 @0 d e+ Q2 U, f& [ I, ~ - <i>#define ADDR_FLASH_SECTOR_4 ((u32)0x08010000) //扇区4起始地址, 64 Kbytes </i>
7 u/ |7 v* a1 z/ F; C - <i>#define ADDR_FLASH_SECTOR_5 ((u32)0x08020000) //扇区5起始地址, 128 Kbytes </i>
7 q. B: a2 T: g; V* p - <i>#define ADDR_FLASH_SECTOR_6 ((u32)0x08040000) //扇区6起始地址, 128 Kbytes </i>7 Q2 B" o) ~* z
- <i>#define ADDR_FLASH_SECTOR_7 ((u32)0x08060000) //扇区7起始地址, 128 Kbytes </i># v: z' V) r4 }. i1 M( k; h3 w
- <i>#define ADDR_FLASH_SECTOR_8 ((u32)0x08080000) //扇区8起始地址, 128 Kbytes </i>
- v* g$ \5 S7 j( d K - <i>#define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000) //扇区9起始地址, 128 Kbytes </i>
9 b/ D- M0 u c/ K - <i>#define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000) //扇区10起始地址,128 Kbytes </i>
n4 [; @% k' G q z - <i>#define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000) //扇区11起始地址,128 Kbytes </i>% N! w" Q' {6 a* g9 K
- F0 y# p( i& L% w+ G- <i>u32 STMFLASH_ReadWord(u32 faddr); //读出字 </i>3 y* `* C( v6 F5 G! G1 y* x8 n/ v
- <i>void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite); //从指定地址开始写入指定长度的数据</i>9 e4 R- s) h; w
- <i>void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead); //从指定地址开始读出指定长度的数据</i>$ A. A8 D1 e8 j) p
- <i> </i>3 w7 Z$ ~1 q7 K) \0 Y+ D
- <i>#endif</i>8 l+ x6 O ^5 _ y3 @& r* m
4 F, q& { w. e$ `! L1 L- 1 \: K$ P5 t% i! c5 E/ U$ z( q
复制代码
8 W& @2 `9 `# f2 T: l
& O' [/ v* A5 K' k' ^ |