Flash# X& M8 @* r0 Y
闪存模块+ n7 U: y; `! p
& S4 I7 I7 T0 M' X& z2 u- x" f# x4 ~- R% Z% `, B
+ I' ]' ^4 g4 [4 Y, d
模块组织表如上,可见STM32F767IGT6由:主存储器、系统存储器、OPT区域、选项字节4部分组成。
; c( r; i$ s9 W, e& CSTM32F767的Flash访问路径有两条:AXIM和ICTM,一般使用AXIM接口访问Falsh,其其实地址为0X08000000/ C5 b. I* M W- x
9 y" ]$ j' R1 h1 S3 P5 ~主存储器) ~, g! @4 q( F9 o. P+ z7 n
存放代码和数据常量(const常量数据)。它可以分为1个Bank(默认)和2个Bank,可以通过选项字节nDBank位来设置。6 k8 A: k; }& N6 R* e) M% v
在单Bank模式下,STM32F767的主存储器被分为8个扇区,前4个扇区为32kb大小,第五个山扇区为128kb大小,剩下的3个扇区都是256kb大小,共1MB
1 |; W; w1 e6 z0 Q9 Q: j$ h8 j! X/ B
7 m" G+ }* \' \0 ]* l- {9 d4 ]系统存储器
: z: S5 o2 }: ]# \8 E存放Bootloader代码,此代码是出厂时就固化的,用来给主存储器下载代码,当B0接3.3v时默认从系统存储器启动
0 [: f3 A; Y' [% [
* b$ C5 l- f6 h& f u3 JOTP区域3 _1 s" R. V& ^0 g% B
一次性可编程区域,共1056byte,被划分为16个64字节的OTP数据块和1个16字节的OTP锁定块。锁定块决定了数据块是否可编程。
4 E% y3 c4 o0 W6 ]' {* q
* P) F; e9 o& u% I. \选项字节" j6 s) G- O& f6 y; I
- C% U( W2 `( e# t4 J闪存的读取; Z) d& D% B! G+ a; @7 e F( F
为了准确的读取Flash数据,需要根据CPU时钟(HCLK)的频率和器件电源电压设置FLSH的存取控制寄存器(FLASH_ACR)中的等待周期数(LATENCY),CPU频率与FLASH等待周期数的对应关系如图:+ ^/ m5 u2 \) o5 n
5 q3 j- `: _; T* s; e' H( }1 r' _: X1 z
" h9 }+ f6 i& R0 |8 S8 V' i等待周期通过FLASH_ACR寄存器的LATENCY[3:0]来设置,系统复位后,系统时钟为内部的16MRC振荡器,LATENCY默认是0(1个等待周期)。供电电压一般默认为3.3,所以在设置216Mhz频率作为CPU时钟之前,必须先设置LATENCY为7(8个CPU周期),否则FLASH读写可能会出错导致死机。( a+ I; b* l1 f% U
STM23F7的 FLASH 读取是很简单的。例如,我们要从地址 addr ,读取一个字(一个字为32 位),可以通过如下的语句读取:, _+ \# t2 {5 T) x/ R1 C7 x' a0 b
data=(vu32)addr;
- _$ O1 W( q5 e1 d$ f5 M将addr 强制转换为 vu32 指针,然后取该指针所指向的地址的值,即得到了 addr 地址的值。
7 O2 ^) A5 x: L$ v类似的,将上面的 vu32 改为 vu8 ,即可读取指定地址的一个字节。+ b( l; O+ I5 y; } G- j; [& g
; \" P$ A1 O( b9 K4 g闪存的编程和擦除
. f, v( U; _! Y3 K! W M U, G执行任何Flash的编程操作(擦除或编程)时,CPU时钟频率(HCLK)不能低于1Mhz。如果在Flash操作期间期间器件发生复位,无法保证Flash的内容。
+ y$ o B) i4 E6 t5 h在对Flash执行写入或擦除操作期间,任何读取Flash的尝试都会导致总线阻塞。只有在完成编程操作后才能正确处理读操作。
' m; V9 C4 n; c5 A/ e* n OSTM32F767的闪存编程由7个32位寄存器控制,如下
3 c5 N" R0 N1 L' g8 R
- i3 V+ g! H0 _& X0 K, O. u. s& ^3 @3 Y4 H9 o
9 o6 N* @6 L8 J9 q {
STM32F767复位后,Flash的编程操作是被保护的,不能写入FLASH_CR寄存器;通过写入特定的序列(0X45670123 和 0XCDEF89AB)到FLASH_KEYR寄存器才可解除写保护,只有在写保护被解除后,才能操作相关寄存器。
3 J- N$ g, B, X+ H. jSTM32F767的编程位数通过FLASH_CR的PSIZE字段配置,PSIZE的设置必须和电源电压匹配,见表,使用3.3v供电时,PSIZE必须设置为10,擦除护着编程都必须以32位为基础。
) p2 ^) s5 B, b$ i1 Y0 E# ^+ A6 ~5 a/ j" ~# U% w0 Y/ n
' X* `" v k) t" b( U3 x, c# {2 D$ K% E
FLASH在编程的时候,也必须要求其写入地址的FLASH是被擦除了的(值必须是0XFFFFFFFF),否则无法写入。9 G3 a! K2 @" M7 b+ |
8 j* d( ]8 g: d, qSTM32F767的标准编程步骤) o7 s/ B" H! s+ _, I4 l+ f \/ [
检查Flash_SR的BSY位,确定当前未执行任何Flash操作;+ `& f' W) _! D
将Flash_CR寄存器中的PG位置1,激活Flash编程;1 l3 F3 F3 j" L L: g
针对所需存储器地址(主存储器块或OTP区域内)执行数据写入操作(PSIZE需要已经设置)
# t& `/ f8 V" X: j4 O等待BSY位清,完成一次编程。# U% E& v7 O/ ]% ^' B& J9 O
以上四步操作就可以完成一次FLASH编程操作,需要注意几点:1 D' Q' O# J. K7 L% L
确保编程地址已擦除
% G( e$ e' b2 j/ R& V5 W* t需要先解锁FLASH_CR,操作完后要上锁' J8 } N, M8 i9 o0 e. A! P
变成操作对OPT区域也一样
$ [+ W; c0 l5 z% o+ j9 L
7 p0 `) c. D: z- n* Y' [- N! l# V) t: u
; x# t4 z8 k6 w$ r r/ S9 w' _STM32F767的擦除. f; B9 P# ^- f7 K
扇区擦除
8 f" ^: m% ~; t) n4 d' }检查FLASH_CR是否解锁,未解锁先解锁2 y5 O" n) x% N9 }3 Y% ]" L
检查FLASH_SR的BSY位,确保当前未执行任何FLASH操作: k1 w3 ?) n1 Y
将FLASH_CR的SER位置1,并从主存储块的12个扇区中选择要擦除的扇区(SNB)
! J) K) J8 L$ U v将FLASH_CR的STRT置1,触发擦除操作9 c! j% N" e W S& X3 d; _" Y
等待BSY位清零
# y, k! R5 ?% ~* f* t% `整片擦除: u4 G9 I+ \! V, A8 G3 r
附录
. ]) d7 m+ k' U$ W2 M# ~6 L4 W代码" j' j8 o- s# P. }
依赖于HAL库
; Z5 x# E2 K' W, z) b/ O3 z) x4 @0 L- u7 Z- e0 G& I) X/ s
- #include "stmflash.h"/ N' |' I# U! y4 b& }7 B5 K" C. q
- #include "delay.h"1 y- C `7 P# @8 O
- ; ~4 D* K* O( u9 w: B0 v
- //读取指定地址的字(32位数据)
a- o, Z3 Z6 Y - //faddr:读地址
+ d$ H0 l# [. ]7 ^8 `! x - //返回值:对应数据./ V* g6 k d$ c( L" Q
- u32 STMFLASH_ReadWord(u32 faddr)4 e+ j2 z% b5 A% m0 A. t
- {
9 R0 z0 T# u$ h$ @ h4 v' I t - return *(__IO uint32_t *)faddr;
3 Y) L# R3 |6 B6 @( L( v( h* q4 y l( z - }9 e# N. ^( g+ h2 @$ x0 G
- 1 S% M; g. o: a2 x9 a! F
- //获取某个地址所在的flash扇区7 F7 o/ m; W7 ~# l
- //addr:flash地址2 n* i* M. w9 [0 n9 f
- //返回值:0~11,即addr所在的扇区, ]. s( v z8 \" E/ t( O
- uint16_t STMFLASH_GetFlashSector(u32 addr)7 W0 B5 z* e+ n h2 U8 q
- {' n$ q, F- \- M3 n: e& k7 {
- if(addr<ADDR_FLASH_SECTOR_1)return FLASH_SECTOR_0;
/ L g0 J) a* A7 N. Q- G4 |" C* C - else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_SECTOR_1;
3 Y; ^% B$ f! C1 m7 Z - else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_SECTOR_2;
m% C2 U$ v0 d S W/ f - else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_SECTOR_3;7 Z$ @( `8 H+ u' t
- else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_SECTOR_4;
# E% F; ?* U/ m8 k1 A. Q, q% J - else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_SECTOR_5;( f* w* I. Z9 x3 h
- else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_SECTOR_6;
; Z( V7 ?- W) {% ]# h - return FLASH_SECTOR_7; - C7 A) U% m {" t" I
- }
& |" n! u6 R, N8 Y' l3 o2 W& q7 P - : U* Z8 W/ H: \( O$ ]
- //从指定地址开始写入指定长度的数据
* o* V6 l9 E5 O - //特别注意:因为STM32F7的扇区实在太大,没办法本地保存扇区数据,所以本函数4 M! Y( T( T$ d2 c! p* F# E
- // 写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以) j; [; A+ g8 P5 U
- // 写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
. C" X8 d9 [2 z5 F# }0 e - // 没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写. , ~) u/ n: c6 E5 p$ E6 ]
- //该函数对OTP区域也有效!可以用来写OTP区!
+ I- o4 @5 E8 L N. L1 w - //OTP区域地址范围:0X1FF0F000~0X1FF0F41F
; h3 k1 [" x, }+ _; z: G - //WriteAddr:起始地址(此地址必须为4的倍数!!)
+ X: p# z$ O3 u3 `' h6 _/ ` - //pBuffer:数据指针4 J2 T( D. v- H8 K
- //NumToWrite:字(32位)数(就是要写入的32位数据的个数.)
; Z, w0 \5 O, m# U - void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
9 E; ]: g/ B3 P8 v7 |; w; X; ?1 r9 D - { , X2 a3 A7 N$ d3 p
- FLASH_EraseInitTypeDef FlashEraseInit;
9 r: e5 {7 }+ t! i1 `, W; F - HAL_StatusTypeDef FlashStatus=HAL_OK;. ?' R/ ? G" x; X
- u32 SectorError=0;* l! m5 ]/ ?9 a- O- e
- u32 addrx=0;
6 G" N1 ^" ~4 R6 }; H3 z. R7 R* y - u32 endaddr=0; 8 Z, }! ]$ O# {' b
- if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址0 Z, G) j. |, G' G: P, M* W
- 0 I# x9 ^* w6 k0 _
- HAL_FLASH_Unlock(); //解锁, W" `4 M0 J1 G/ ^, n8 ?2 Z) A
- addrx=WriteAddr; //写入的起始地址# q5 G5 ?4 Z9 M8 N2 |) |/ g8 O
- endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
4 ^& l' k9 E, r0 p7 Q - & {9 Q1 E( k( z1 V. {
- if(addrx<0X1FF00000)
; ]* L. ?/ {; i# \8 b- D - {, {& E. U. M2 @2 _- z& n6 r
- while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)( P. h( f0 ^7 z# X# [( @
- {: c, s( V, x1 Q7 a" g
- if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
; Q5 d$ w: z* Q# b6 L. I6 K - { # p) v2 w. H3 d0 `4 ]
- FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS; //擦除类型,扇区擦除 % {8 k/ \! w/ w8 E, |& r
- FlashEraseInit.Sector=STMFLASH_GetFlashSector(addrx); //要擦除的扇区
& j9 \3 i# Z: l* \3 p" b - FlashEraseInit.NbSectors=1; //一次只擦除一个扇区
! r l+ _; }: t3 v: a& G6 q! n: t - FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3; //电压范围,VCC=2.7~3.6V之间!!
0 ]/ I/ Y& }( f. `, S1 ]- f - if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK)
+ S9 j. S6 l# L3 Y - {) l6 _1 n7 D: ]
- break;//发生错误了
$ |- t, }' P4 t, F; E, U) r - }/ S. l# V- x. Q6 \. v/ H
- SCB_CleanInvalidateDCache(); //清除无效的D-Cache
5 w u% b) t: [5 V - }else addrx+=4;
+ u6 X1 k, C/ v7 t, F4 A1 ] - FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成( x! m+ `2 F: g" l0 ~
- }
& s4 o7 L$ t6 }' V - }4 q* R/ O* @: U- m4 Q! j
- FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成( ~2 V7 Y: i& H6 h9 F
- if(FlashStatus==HAL_OK)1 N2 Y4 J3 M# Y4 ?, Q8 ^: o
- {
3 E' I- _/ i) J( q: i, U0 ~ - while(WriteAddr<endaddr)//写数据
1 j* z& N7 X" S, q* g0 I - {, k" o5 W& e: z
- if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,WriteAddr,*pBuffer)!=HAL_OK)//写入数据
" v; ]8 B8 x! J( y2 w0 k2 S/ H - {
. E* w* r$ j6 h- U% |. B; E4 W6 @: K - break; //写入异常
9 w! E3 U) h( [) [4 g - }
% X) R' u8 P+ U. ]+ j - WriteAddr+=4;
- J! m1 C3 q' j1 Q- r - pBuffer++;
2 D }. g" B3 _& }, \# H$ ] - } / @+ \$ Q6 V) H4 C: Q$ v; ]
- }! |0 S0 d9 r6 l) P5 ?
- HAL_FLASH_Lock(); //上锁
& v3 I( \: X: U9 I, k - }
! G1 {( N$ ~$ E+ H0 m; n) f% N - 8 U- o: i+ Y: x5 W2 x0 R @
- //从指定地址开始读出指定长度的数据
) G/ L2 Z( l% F5 ]4 X: h! f- ^ - //ReadAddr:起始地址0 l$ h, c5 ]2 z
- //pBuffer:数据指针
! A7 K9 L1 L( s - //NumToRead:字(32位)数
4 P2 l, p1 e& P1 H9 B+ t) y - void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)
# y7 Q7 U$ n0 Z f" {4 f! I - {
3 _! y8 l9 G& E5 M" r7 u - u32 i;6 ? ?$ K2 N H
- for(i=0;i<NumToRead;i++)2 `; T# e6 S3 R( v1 r
- {
3 q( b6 D4 j; j7 m5 E, E - pBuffer<i>=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
/ o% J$ `+ N* ^4 L - ReadAddr+=4;//偏移4个字节. & x4 |. e2 u8 y
- }
( W9 v% `& g) Y4 }+ T7 x. g - }, H2 G/ }0 K4 N0 Q) u8 C- D
- ; i" a4 u, e0 y# k% E3 L9 {0 ~
- </i>
复制代码
' @6 W. k) |. J# b3 e. @
, c" Z3 {$ J+ G9 W( c6 M7 n" U8 ] |