Flash
' s/ r1 B) R% x# I. {$ X0 i闪存模块
! z8 K3 u0 H! J9 M
! q0 Q: r' E; c" y5 h- u" ^* |% d7 W, g u2 J, D) K
% {9 ~4 r/ [; u, Y0 e% g' A模块组织表如上,可见STM32F767IGT6由:主存储器、系统存储器、OPT区域、选项字节4部分组成。* C' H6 ^' o6 _' N7 r$ ]/ e
STM32F767的Flash访问路径有两条:AXIM和ICTM,一般使用AXIM接口访问Falsh,其其实地址为0X08000000
" S) ~9 S% ]5 b5 s3 y# N
" H' r4 H- p, Z1 c" q' Y+ ^主存储器
@8 i! g' o( H, Q6 M3 ] z6 o存放代码和数据常量(const常量数据)。它可以分为1个Bank(默认)和2个Bank,可以通过选项字节nDBank位来设置。4 P3 I9 I9 E! d
在单Bank模式下,STM32F767的主存储器被分为8个扇区,前4个扇区为32kb大小,第五个山扇区为128kb大小,剩下的3个扇区都是256kb大小,共1MB
' ?' M$ q# a. O, ?5 E( e' g
* M$ s& E; J, H O系统存储器) }- H. V4 ]9 ]# h
存放Bootloader代码,此代码是出厂时就固化的,用来给主存储器下载代码,当B0接3.3v时默认从系统存储器启动
2 M: ~4 D) C% z" U
2 \1 w) i0 y7 p. _ POTP区域. ~' ~4 z. `9 O" X; M- q& S0 n
一次性可编程区域,共1056byte,被划分为16个64字节的OTP数据块和1个16字节的OTP锁定块。锁定块决定了数据块是否可编程。
. z: T/ I* }' y9 F. o
" V0 P- J6 h! ?选项字节4 a" a2 y/ v0 ]6 I G) ~& v
8 v+ d4 I- q, j7 V8 E3 C, Q" R9 e
闪存的读取6 r' A$ V4 E M! F s3 l
为了准确的读取Flash数据,需要根据CPU时钟(HCLK)的频率和器件电源电压设置FLSH的存取控制寄存器(FLASH_ACR)中的等待周期数(LATENCY),CPU频率与FLASH等待周期数的对应关系如图:, W* g7 N+ k; S# o3 F2 e$ f" S1 E4 ~
# R8 ^0 j. S% L1 Z: R7 f
1 t% G& r# m5 e$ @# d, W' D. {
) o8 @7 X5 {" U+ z$ X* `0 @5 F0 j
等待周期通过FLASH_ACR寄存器的LATENCY[3:0]来设置,系统复位后,系统时钟为内部的16MRC振荡器,LATENCY默认是0(1个等待周期)。供电电压一般默认为3.3,所以在设置216Mhz频率作为CPU时钟之前,必须先设置LATENCY为7(8个CPU周期),否则FLASH读写可能会出错导致死机。
! S1 H i% ^, K4 G" ~8 ZSTM23F7的 FLASH 读取是很简单的。例如,我们要从地址 addr ,读取一个字(一个字为32 位),可以通过如下的语句读取:
9 _0 x0 D( s3 Q& ?/ ?7 N data=(vu32)addr;; c8 Q n, R3 P) k- e& O
将addr 强制转换为 vu32 指针,然后取该指针所指向的地址的值,即得到了 addr 地址的值。
( Q# ?1 s/ s2 K6 o类似的,将上面的 vu32 改为 vu8 ,即可读取指定地址的一个字节。 }, K/ X2 f% r3 B- |
! o$ x9 P. e! R" \' B* e闪存的编程和擦除& C8 _; _; m ]1 ^# e
执行任何Flash的编程操作(擦除或编程)时,CPU时钟频率(HCLK)不能低于1Mhz。如果在Flash操作期间期间器件发生复位,无法保证Flash的内容。) ^; @ A& Y" N5 ?2 |# O
在对Flash执行写入或擦除操作期间,任何读取Flash的尝试都会导致总线阻塞。只有在完成编程操作后才能正确处理读操作。; `# f! g) Q( j
STM32F767的闪存编程由7个32位寄存器控制,如下: \# g; R0 i2 P7 ]+ |8 ]
+ N. U' \+ H- p
& A6 d; v b, w- o9 q
1 i$ J; N% s5 l6 R8 k. O* ^2 Y% uSTM32F767复位后,Flash的编程操作是被保护的,不能写入FLASH_CR寄存器;通过写入特定的序列(0X45670123 和 0XCDEF89AB)到FLASH_KEYR寄存器才可解除写保护,只有在写保护被解除后,才能操作相关寄存器。7 V- v/ r+ k: [9 x5 z5 b& o6 T
STM32F767的编程位数通过FLASH_CR的PSIZE字段配置,PSIZE的设置必须和电源电压匹配,见表,使用3.3v供电时,PSIZE必须设置为10,擦除护着编程都必须以32位为基础。
1 k: Q. _0 D; V& a$ y2 }
+ ^- [0 H8 Y: r" {4 k8 w$ m5 ?$ B# n( n8 _7 ]6 M8 O) j; {3 s; _( ?0 i
1 K. M2 h" x+ ]8 d. v; ^FLASH在编程的时候,也必须要求其写入地址的FLASH是被擦除了的(值必须是0XFFFFFFFF),否则无法写入。' s) e) p; B; a
( u8 R8 u1 N3 \7 m+ k8 r4 hSTM32F767的标准编程步骤6 v; P* Q) Y& f* i" a
检查Flash_SR的BSY位,确定当前未执行任何Flash操作;
: w, X, {5 C: C( X0 \将Flash_CR寄存器中的PG位置1,激活Flash编程;
8 t8 E- s1 T" B" D2 i# h针对所需存储器地址(主存储器块或OTP区域内)执行数据写入操作(PSIZE需要已经设置)
* X1 q: R4 U2 F: T6 N; F9 f等待BSY位清,完成一次编程。
7 }- g; Z( _1 T5 e, o, o: o$ C# G: U以上四步操作就可以完成一次FLASH编程操作,需要注意几点:
h8 g& S) i$ ]9 F5 o) v; s/ b确保编程地址已擦除
* `. x& Z- Y, y5 }需要先解锁FLASH_CR,操作完后要上锁
8 R; l% w/ B, C& Y2 U变成操作对OPT区域也一样
7 n" r! C, n% S4 B2 ~1 b0 c% I, l+ ]: x) r/ h
( F& p4 m5 @% W. g. TSTM32F767的擦除0 q8 \* O0 ^/ M) ?
扇区擦除
6 K' ~+ P2 Y5 J$ I1 l3 y检查FLASH_CR是否解锁,未解锁先解锁
2 ?2 p" f5 {# x2 |检查FLASH_SR的BSY位,确保当前未执行任何FLASH操作
. F o, Y( e4 R. P3 W将FLASH_CR的SER位置1,并从主存储块的12个扇区中选择要擦除的扇区(SNB)# L6 A* I& T5 ?! L+ \5 X
将FLASH_CR的STRT置1,触发擦除操作
' r2 D5 b; K* K& X5 O u% R等待BSY位清零5 c7 g( ^) Q( _5 H
整片擦除, X" n0 p- Q: O2 p6 m+ Z
附录
- |& r6 e4 E9 a* |代码8 S$ C- E7 T1 c1 Z. @9 L/ p9 G" ?
依赖于HAL库* i' e( X0 ^% ]3 j1 j' d2 x
6 z/ ~6 K0 A# {' b# B, r% `
- #include "stmflash.h"
w: `7 _& K/ k: h: z- Y+ E* m8 w - #include "delay.h"( U6 P& X- E2 Q
- , O: J: c3 f# E8 C# K$ K- @
- //读取指定地址的字(32位数据)
0 V- l' q# A( x - //faddr:读地址 . k+ M0 ?8 f8 e% N0 Y
- //返回值:对应数据.
1 X& `. W( U8 a/ t7 ] - u32 STMFLASH_ReadWord(u32 faddr)
! B0 U( `7 o& I1 Z - {
, h0 c% u8 @6 c - return *(__IO uint32_t *)faddr; 4 p9 w$ u: X1 w$ Z- H
- }
) w: y4 x3 ^" |1 p! ^9 s. ` - 6 Y" d/ p9 o! z/ M2 p$ ]4 t
- //获取某个地址所在的flash扇区- S- O5 D% ^3 R( g9 m8 s
- //addr:flash地址0 [ N3 x ~' T2 Y& ~+ X. ~
- //返回值:0~11,即addr所在的扇区
. Q& I2 e! a5 c, M - uint16_t STMFLASH_GetFlashSector(u32 addr)
( Y$ S& D H2 L. s - {
# }9 T# s, B* c- H - if(addr<ADDR_FLASH_SECTOR_1)return FLASH_SECTOR_0;; x5 ^' Y( Q q0 r9 q0 ?5 {
- else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_SECTOR_1;5 H2 H; ?' U1 l: J
- else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_SECTOR_2;
( J0 q% l$ x+ ~; Q; E - else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_SECTOR_3;
# x3 [7 N1 V# m5 l; D0 l* }: j - else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_SECTOR_4;3 E( j+ ^* E# `" G* ]$ C5 G
- else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_SECTOR_5;# ~. Q/ f* G9 F% o* I. Y8 j
- else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_SECTOR_6;. r& N* ~4 I; I. E3 l6 S, I( R. M
- return FLASH_SECTOR_7;
$ l; `7 ~/ d! b5 V8 k - }9 }" E1 t: B& h3 S$ @
- $ D. O- G& P0 n Y2 O4 U/ X
- //从指定地址开始写入指定长度的数据3 \8 I' o* `, _% y$ `/ q
- //特别注意:因为STM32F7的扇区实在太大,没办法本地保存扇区数据,所以本函数$ k N# X! \; a" w% Y4 \) K
- // 写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
7 Q8 H8 N0 f; Q0 ]* M) g- h0 I( D8 K - // 写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
+ Z7 ?. O" o. l3 o$ b+ R - // 没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写. ! T- b4 B4 S9 g: f. l# u$ R
- //该函数对OTP区域也有效!可以用来写OTP区!0 S7 C# x* c( }( |
- //OTP区域地址范围:0X1FF0F000~0X1FF0F41F
8 ]' W4 N$ z' I5 o - //WriteAddr:起始地址(此地址必须为4的倍数!!)
5 y( O/ A ] t. J6 d - //pBuffer:数据指针
1 `9 Z7 p, z( K! d+ W2 i; l( B - //NumToWrite:字(32位)数(就是要写入的32位数据的个数.)
4 s, |! O m% e+ \4 s - void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite) ' H# ~* |5 o" w7 X K1 r
- {
6 `8 \; L. v4 h4 R5 ~% O: F - FLASH_EraseInitTypeDef FlashEraseInit;. J- k. N" [" F# D9 d* `
- HAL_StatusTypeDef FlashStatus=HAL_OK;4 @4 H! ~+ n2 K/ B
- u32 SectorError=0;7 u9 f: ~* N. ~' D
- u32 addrx=0;
/ p( Z I8 Z6 d) E: a; B1 a - u32 endaddr=0;
9 t" h( f, |0 g7 H - if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址( O$ @) Y E" A1 P; z' N
# o, B ? `* l* m( I6 q2 K. r- HAL_FLASH_Unlock(); //解锁3 k, E; x1 |# {- |7 z
- addrx=WriteAddr; //写入的起始地址% s7 w0 Y; l/ s
- endaddr=WriteAddr+NumToWrite*4; //写入的结束地址* W/ j- |& v( q* h
- ( v" [) b* O A1 ]1 `9 S
- if(addrx<0X1FF00000)3 ]) F- [" ? D& K, h$ p
- {+ N _* K) V! \, B6 u6 m
- while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
5 Y- z( q8 y, Q3 z) u. O - {
8 ^# ^9 h5 N) A' d, |+ h+ L. Z - if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区" F# F% ?. q& z. M
- {
/ q/ B+ c E* G/ p% B {0 ^4 d - FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS; //擦除类型,扇区擦除 . v% ?: N! m) @7 ^! A
- FlashEraseInit.Sector=STMFLASH_GetFlashSector(addrx); //要擦除的扇区
4 ~) U/ m, S5 L5 H - FlashEraseInit.NbSectors=1; //一次只擦除一个扇区
4 k2 U2 S f- n, v- s6 q - FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3; //电压范围,VCC=2.7~3.6V之间!!
* P. [( m9 l* } m { - if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK)
/ g/ |3 F, t: E& D* S - {5 C& N2 Z- c# C& h: x7 a1 @+ l
- break;//发生错误了 ; a* r( r4 ?9 I4 w. u8 J
- }
; ?& Z, w3 N0 O& ~3 @6 t1 c - SCB_CleanInvalidateDCache(); //清除无效的D-Cache
! ^$ s2 Y, M7 p$ O0 Q; Q6 s- }# ? - }else addrx+=4;. W4 |- b3 M" C; f- F
- FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
* c" X/ |0 v, s0 b9 w - }3 }/ e- ?! p# B0 y; | f( [, Z
- }
; @: K0 U- H% w - FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
9 X! O6 v& B* |. O" m( ]. u% q - if(FlashStatus==HAL_OK)+ I0 Q/ S% I8 I+ {( E
- {$ f- R T9 U' D6 j
- while(WriteAddr<endaddr)//写数据1 C4 T* K. L4 h
- {8 P: a7 L T3 P! z0 i7 E) H
- if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,WriteAddr,*pBuffer)!=HAL_OK)//写入数据
; L% F3 j5 ~- e$ E! v$ P, k - {
/ H; F% u" d" @$ {% a1 \$ E# r - break; //写入异常
- X6 C" A3 D( b/ k7 s4 k# e - }
" [5 |. j$ _5 I6 t: j }1 Q ? - WriteAddr+=4;
$ q2 [4 S% A/ |. ^ d - pBuffer++;( `+ A/ g+ _, `" R8 h5 C: a3 O
- } 8 y4 R! p6 s- C4 c
- }7 @" f- I% O! z9 k' E5 o
- HAL_FLASH_Lock(); //上锁
% H: l" `) s6 o - }
6 s1 ^7 \% x' h0 q8 K2 z7 b" J5 R; R
2 l {3 I0 Y% v. B4 o- //从指定地址开始读出指定长度的数据
# a0 `" |/ z* D0 _3 F6 M - //ReadAddr:起始地址: f2 C9 D" T4 |& W* n* m6 Y
- //pBuffer:数据指针
Q0 v/ @( p! A1 q/ A$ { k - //NumToRead:字(32位)数% y" {) V( j2 ?! {
- void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)
. e, X9 a2 z' c - {$ l; a8 W+ |+ Z2 @1 i
- u32 i;
1 O6 h1 R" p( ]* l' e2 R - for(i=0;i<NumToRead;i++): u0 F4 E4 p9 h: s( w
- {
6 Z e1 ^7 i& a& ^ - pBuffer<i>=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
1 M; e7 V0 [. F - ReadAddr+=4;//偏移4个字节.
# S0 Y1 n" N, A* w2 U y - }' [ z; O8 X9 r5 ^
- }
* N' C+ [# j" Y
D6 O2 m( B6 L' U- </i>
复制代码
1 a# w1 g6 c; S! V/ @- |; ]6 o2 O2 L8 [7 }
|