你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32的Flash擦除方式

[复制链接]
STMCU小助手 发布时间:2022-11-29 16:00
前言

本文主要介绍STM32的内部Flash擦除方式和擦除长文件的功能函数怎样编写。并且介绍一些注意事项,如只想擦除当前地址,却发现上下地址都出现了擦除等问题。阅读完本文可以使你能够正常的完成Flash擦除。并对擦除时会影响的地址大小有一个深入的认识,并在对页擦除时,页的起始地址和大小有所了解。

6 v, N) [! R. |- h! m6 ~7 ]. ~
. b5 \6 T$ L' c
介绍
STM32 FLASH

不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了1024K 字节。本次实验选用的STM32 开发板是F103ZET6,其 FLASH 容量为 512K 字节,属于大容量产品(另外还有中容量和小容量产品),大容量产品的闪存模块组织如图 所示:

0 L% D3 ?6 Z3 \4 ]
640 (4).png

7 R  N- }2 C" y: E. Z
STM32 的闪存模块由:主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。# ^& V; l, k  k) G  V* I6 I
主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。注意,小容量和中容量产品则每页只有 1K 字节。从上图可以看出主存储器的起始地址就是0X08000000, B0、B1 都接 GND 的时候,就是从 0X08000000开始运行代码的。7 T9 H$ f/ `9 d+ T7 q, Z
信息块,该部分分为 2 个小部分,其中启动程序代码,是用来存储 ST 自带的启动程序,用于串口下载代码,当 B0 接 V3.3,B1 接 GND 的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能。
7 ^  M( Z( ~" w" i4 @/ D闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。
. d5 j2 |' E* |在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。


" I3 ]* r0 a5 h( a闪存的编程和擦除

STM32 的闪存编程是由 FPEC(闪存编程和擦除控制器)模块处理的,这个模块包含 7 个4 F$ N+ {/ H& }" h4 N* C* j7 a
32 位寄存器,他们分别是:

  • FPEC 键寄存器(FLASH_KEYR)
  • 选择字节键寄存器(FLASH_OPTKEYR)
  • 闪存控制寄存器(FLASH_CR)
  • 闪存状态寄存器(FLASH_SR)
  • 闪存地址寄存器(FLASH_AR)
  • 选择字节寄存器(FLASH_OBR)
  • 写保护寄存器(FLASH_WRPR)
    6 k* o, k3 k, ]( D! jSTM32 复位后,FPEC 模块是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列到 FLASH_KEYR 寄存器可以打开 FPEC 模块,只有在写保护被解除后,我们才能操作相关寄存器。+ h# l; I, Z/ Q. r3 q) M
    STM32 闪存的编程每次必须写入 16 位(不能单纯的写入 8 位数据哦!),当 FLASH_CR 寄存器的 PG 位为’1’时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据,FPEC 都会产生总线错误。在编程过程中(BSY 位为’1’),任何读写闪存的操作都会使 CPU暂停,直到此次闪存编程结束。! Y* S* S8 ]1 O" M
    同样,STM32 的 FLASH 在编程的时候,也必须要求其写入地址的 FLASH 是被擦除了的(也就是其值必须是 0XFFFF),否则无法写入,在FLASH_SR 寄存器的 PGERR 位将得到一个警告。
    % W. {7 O, X# h. p0 g

在我们日常的开发中STM32的Flash擦除最常用的就是页擦除,所以我们在这里着重介绍一下页擦除。
+ o5 q2 C7 i6 p5 U4 wSTM32 的页擦除顺序为:

  • 检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁
  • 检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的闪存操作
  • 设置 FLASH_CR 寄存器的 PER 位为’1’
  • 用 FLASH_AR 寄存器选择要擦除的页
    : z# l. n6 E: V  t# x-设置 FLASH_CR 寄存器的 STRT 位为’1’
  • 等待 BSY 位变为’0’
  • 读出被擦除的页并做验证0 p8 i5 c2 a" o0 E3 p9 S5 ]
    该寄存器我们本章只用到了它的 LOCK、STRT、PER 和 PG 等 4 个位。
    . _3 s! {$ t( d* rLOCK 位,该位用于指示 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。
    % q0 g2 [5 G0 M) x8 rSTRT 位,该位用于开始一次擦除操作。在该位写入 1 ,将执行一次擦除操作。% B7 j# ]  F3 W2 W
    PER 位,该位用于选择页擦除操作,在页擦除的时候,需要将该位置1。
    ; q- }& \$ d9 v" L9 JPG 位,该位用于选择编程操作,在往 FLASH 写数据的时候,该位需要置1。
    2 b' O8 [# ]8 t2 K2 |
Flash擦除的标准库函数
  • 解锁函数:void FLASH_Unlock(void);4 b1 \  W7 x: _+ a
    对 FLASH 进行写操作前必须先解锁,解锁操作也就是必须在 FLASH_KEYR 寄存器写入特定的序列,固件库函数实现很简单:只需要直接调用 FLASH_Unlock();即可。
  • 锁定函数:void FLASH_Lock(void);7 E7 U$ ~& }$ f0 f6 l4 m  L# G
    有解锁当然就有上锁,为了保护Flash,读写和擦除全部需要的Flash后需要上锁,只需要调用:FLASH_Lock();
  • 擦除函数- j, ~! s* e+ {$ ~4 z
    固件库我们主要使用两个 FLASH 擦除函数:
    3 [( h. K% c$ z4 yFLASH_Status FLASH_ErasePage(uint32_t Page_Address);
    * F1 c' T4 h1 y& y8 [- E0 U% }FLASH_Status FLASH_EraseAllPages(void);' l; {) n2 U0 u
    顾名思义,第一个函数是页擦除函数,根据页地址擦除特定的页数据。
    ( W, E0 q0 y. a' ^& Q8 |/ j& V第二个函数是擦除所有的页数据。
  • 获取 FLASH 状态8 ?0 e: e; V9 t& u* o& U% ~
    主要是用的函数是:FLASH_Status FLASH_GetStatus(void);/ t: L: R. A' K$ ^) v, C
    返回值是通过枚举类型定义的,分别为:' y6 q* @7 }+ v' f- r/ h
    FLASH_BUSY = 1,//忙
    $ u1 T  }' T6 v# _% S9 xFLASH_ERROR_PG,//编程错误; i0 V/ I' x; ^3 i1 D# Z' J! ?
    FLASH_ERROR_WRP,//写保护错误4 T" Q9 X* @! I* D+ j
    FLASH_COMPLETE,//操作完成
    " Z% f% [- |+ tFLASH_TIMEOUT//操作超时
  • 等待操作完成函数
    % s4 d" R- ~! _在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。
    : A+ o$ o, b" S所以在每次操作之前,我们都要等待上一次操作完成这次操作才能开始。使用的函数是:FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout)入口参数为等待时间,返回值是 FLASH 的状态,这个很容易理解,这个函数本身我们在固件库中使用得不多,但是在固件库函数体中间可以多次看到。

    0 X, d+ h) u  b0 F) t
软件设计2 n' j: u4 ]. b+ j9 v5 l0 u
直接使用固件库函数擦除当前地址所在的内容

直接使用固件库擦除选定的地址的内容,每次会擦除选定地址的当前页。
$ q6 K5 J1 z* w* [) u, r注意:这里有一个很容易混淆的点,擦除当前页,并不是擦除从这个地址之后的一页,而是STM32规定的该地址所在的页。不知道的可以看Flash的图,也可以自己计算,其实就是0x0800 0000 每次加2k(中小容量加1K),比如0x0800 0810,此时就会擦除0x0800 0800-0x0800 0FFF的所有内容,而不是0x0800 0810到0x0800 100F的内容。

8 E1 R8 U. Z( p* v( V; Y# X

640 (3).png

9 \, Q2 A+ [' [. c0 B
  1. FLASH_Unlock();  //解锁
    4 E! x* B9 M. D, z; s/ i
  2. FLASH_ErasePage(0X08003A98);( w' k" Z5 ?4 P. N4 b' V; S
  3. FLASH_Lock();//上锁
复制代码

此语句就可擦除0x0800 3A98所在页的全部内容。
1 V5 p! O0 [  V, O8 D0 _$ E/ s可能有人疑惑,难道不能擦除单个字节的内容嘛,很遗憾,确实不能,并非是程序的问题,而是硬件设计,就是按页擦除。

+ @$ P# N7 u6 Z5 k* w* b) W2 v) p5 D7 t

擦除对应地址和大小的Flash

我们在开发中,不可能每次都计算用擦除多少页的地址,或者要擦除的范围是多少,这里我们就可以编写一个函数来帮我们实现。

  f0 s' s$ Y. x; z( z2 m8 v, W

  1. void STMFLASH_Erase(u32 EraseAddr,u16 NumToErase)  ! n9 j9 d* ~# S
  2. {; @0 m0 x' p9 l0 K; m' X
  3.   u32 secpos;     //扇区地址3 U) _+ P7 X8 [. \) ]
  4.   u16 secoff;     //扇区内偏移地址(16位字计算)3 n& V# l. q9 E1 m/ P5 p# S
  5.   u16 secremain; //扇区内剩余地址(16位字计算)     * `" V( ^0 e6 c1 ~% J# u  q: [
  6.    u16 i;    * M0 M  a' ]: B3 [6 L
  7.   u32 offaddr;   //去掉0X08000000后的地址
    ( T; y6 e, k4 C. h
  8.   if(EraseAddr<STM32_FLASH_BASE||(EraseAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址  G; _- x9 w) b! \
  9.   FLASH_Unlock();            //解锁# l. \- s/ ?5 d7 _9 L- D  C
  10.   offaddr=EraseAddr-STM32_FLASH_BASE;    //实际偏移地址.
    9 v1 r1 ]8 r9 o
  11.   secpos=offaddr/STM_SECTOR_SIZE;      //扇区地址  0~127 for STM32F103RBT69 C5 z7 c/ U: j2 O$ y7 M; q  E- [
  12.   secoff=(offaddr%STM_SECTOR_SIZE)/2;    //在扇区内的偏移(2个字节为基本单位.)
    ' @& J- s5 ^: E- [; d
  13.   secremain=STM_SECTOR_SIZE/2-secoff;    //扇区剩余空间大小   
    # Z! A# z( r1 j% x
  14.   if(NumToErase<=secremain)
    3 j. u; S5 `7 u! Y/ Q$ _7 @
  15.     secremain=NumToErase;//不大于该扇区范围4 }4 H2 `0 u5 D4 L* S
  16.   while(1)
    : T. r6 c# _! I0 o" |
  17.   {  FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区   
    5 v" V$ Z8 Y1 E9 `, g
  18.     if(NumToErase==secremain)$ l4 A# Y/ C7 T7 f9 w
  19.       break; //擦除结束了1 ~0 m# |* t0 U7 x$ J7 B
  20.     else     //擦除未结束6 A+ f8 ~, Y1 f8 e
  21.     {: ^# ^3 a. t( i! `" A' Q7 l
  22.       secpos++;        //扇区地址增1
    5 M2 }( g/ l" f! W# Z$ D4 h' ]
  23.       secoff=0;        //偏移位置为0   
    + h: }+ ]$ t2 f8 e' t8 C- P: e& R
  24.       EraseAddr+=secremain;  //地址偏移     
    % b6 d* u" {" o) m
  25.       NumToErase-=secremain;  //字节(16位)数递减2 N2 V9 N( |! J! j
  26.       if(NumToErase>(STM_SECTOR_SIZE/2))
    $ o1 U. ^) r. h1 u
  27.         secremain=STM_SECTOR_SIZE/2;//下一个扇区还是擦除不完
    5 N1 J9 ~+ w) e4 b% _
  28.       else secremain=NumToErase;//下一个扇区可以擦除完了( f6 {! m) }7 o' l2 z
  29.     }   
    8 [- L& R2 |: M+ Q: D. \
  30.   };  
      q8 w3 d. N# Z0 ]6 S: D! p* A
  31.   FLASH_Lock();//上锁
    # @2 i7 H/ d% c, {8 i) d: Q8 j9 X
  32. }
复制代码
4 m. X/ b8 H9 U* R3 Y- ~

调用本函数就可以擦除EraseAddr地址开始,NumToErase大小的Flash了。注意NumToErase是16位,也就是半字(两个字节),如果你打算擦除1980字节的程序,NumToErase就应该是990。& @" i. ]6 j8 [0 n. o
因为函数中已经编写了解锁和上锁,所以就不用在使用时再加了,直接调用STMFLASH_Erase(0X0x0800 0810,2049);
5 i' Z% I5 W1 k4 T6 J这一句程序可以实现擦除0x0800 0810开始4098字节所在页的内容。
9 W9 o/ N6 F7 h/ l! X注意,这里的擦除也是所在页,并不是正好擦除该地址后面4098字节的内容。如果我们填入的起始地址不是STM32设定的某页的起始地址,那么擦除的时候,就会也把前面的一部分Flash内容进行擦除,比如STMFLASH_Erase(0X0x0800 0810,2049); 就是擦除了0x0800 0800-0x0800 0FFF(第二页)和0x0800 1000-0x0801 17FF(第三页)的所有内容。. i9 U8 V( D  J! S2 |! U2 S
可以看出来,虽然我们只想擦除0X0x0800 0810后4098个字节的全部内容,但我们不可避免的擦除了4K的全部内容,编写程序时,需要注意。

) J) P0 s: I1 t8 F  D% l( V

如何查看Flash的内容

Keil的软件调试中,有专门可以查看所连接的板子的Flash的内容。

首先我们点击调试按钮,如下图


- t+ C5 S% H4 M. E/ {( b6 y$ m) Y

。 640 (2).png


# m: w3 {. ^* U! p7 \5 ?( l" |


/ U$ m6 f9 U/ `) ^% p  H! Q之后我们在View中找到memory Windows,选择memory1。之后我们就可以看到右下角出现Flash的内容,第一次打开是空白,因为还没有选定地址,会出现下图内容。


1 B8 }! M" w/ ]6 N) C% [
640 (1).png

' m( D  D/ Z8 T& B9 c5 z3 b( ]
我们在地址处填写想查看的地址,比如查看0x0800 3A98,输入完成后点击回车,就会出现Flash地址对应的内容。

) r. N8 p8 c5 ^6 Z/ \0 d2 R
640.png

0 v7 x% k  K) z7 B$ T

转载自:跋扈洋


0 t) R5 R5 K8 f
赞 收藏 评论0 发布时间:2022-11-29 16:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版