您的位置:首页 > 其它

关于STM32的FLASH操作【转载】

2017-11-29 13:34 134 查看
转载:http://www.cnblogs.com/zyqgold/p/3411028.html

说到STM32的FLSAH,我们的第一反应是用来装程序的,实际上,STM32的片内FLASH不仅用来装程序,还用来装芯片配置、芯片ID、自举程序等等。当然, FLASH还可以用来装数据。
 
FLASH分类
根据用途,STM32片内的FLASH分成两部分:主存储块、信息块。
主存储块用于存储程序,我们写的程序一般存储在这里。
信息块又分成两部分:系统存储器、选项字节。
系统存储器存储用于存放在系统存储器自举模式下的启动程序(BootLoader),当使用ISP方式加载程序时,就是由这个程序执行。这个区域由芯片厂写入BootLoader,然后锁死,用户是无法改变这个区域的。

选项字节存储芯片的配置信息及对主存储块的保护信息。

 

FLASH的页面

STM32的FLASH主存储块按页组织,有的产品每页1KB,有的产品每页2KB。页面典型的用途就是用于按页擦除FLASH。从这点来看,页面有点像通用FLASH的扇区。

 

STM32产品的分类
STM32根据FLASH主存储块容量、页面的不同,系统存储器的不同,分为小容量、中容量、大容量、互联型,共四类产品。
小容量产品主存储块1-32KB,每页1KB。系统存储器2KB。
中容量产品主存储块64-128KB,每页1KB。系统存储器2KB。
大容量产品主存储块256KB以上,每页2KB。系统存储器2KB。
互联型产品主存储块256KB以上,每页2KB。系统存储器18KB。
对于具体一个产品属于哪类,可以查数据手册,或根据以下简单的规则进行区分:
STM32F101xx、STM32F102xx 、STM32F103xx产品,根据其主存储块容量,一定是小容量、中容量、大容量产品中的一种,STM32F105xx、STM32F107xx是互联型产品。
互联型产品与其它三类的不同之处就是BootLoader的不同,小中大容量产品的BootLoader只有2KB,只能通过USART1进行ISP,而互联型产品的BootLoader有18KB,能通过USAT1、4、CAN等多种方式进行ISP。小空量产品、中容量产品的BootLoader与大容量产品相同。
 
关于ISP与IAP
ISP(In System Programming)在系统编程,是指直接在目标电路板上对芯片进行编程,一般需要一个自举程序(BootLoader)来执行。ISP也有叫ICP(In
Circuit Programming)、在电路编程、在线编程。
IAP(In Application Programming)在应用中编程,是指最终产品出厂后,由最终用户在使用中对用户程序部分进行编程,实现在线升级。IAP要求将程序分成两部分:引导程序、用户程序。引导程序总是不变的。IAP也有叫在程序中编程。
ISP与IAP的区别在于,ISP一般是对芯片整片重新编程,用的是芯片厂的自举程序。而IAP只是更新程序的一部分,用的是电器厂开发的IAP引导程序。综合来看,ISP受到的限制更多,而IAP由于是自己开发的程序,更换程序的时候更容易操作。
 
FPEC
FPEC(FLASH Program/Erase controller 闪存编程/擦除控制器),STM32通过FPEC来擦除和编程FLASH。FPEC使用7个寄存器来操作闪存:
FPEC键寄存器(FLASH_KEYR)                                   写入键值解锁。
选项字节键寄存器(FLASH_OPTKEYR)               写入键值解锁选项字节操作。
闪存控制寄存器(FLASH_CR)                                选择并启动闪存操作。
闪存状态寄存器(FLASH_SR)                                查询闪存操作状态。
闪存地址寄存器(FLASH_AR)                                存储闪存操作地址。
选项字节寄存器(FLASH_OBR)                             选项字节中主要数据的映象。
写保护寄存器(FLASH_WRPR)                              选项字节中写保护字节的映象。
 
键值
为了增强安全性,进行某项操作时,须要向某个位置写入特定的数值,来验证是否为安全的操作,这些数值称为键值。STM32的FLASH共有三个键值:
RDPRT键   = 0x000000A5         用于解除读保护
KEY1          = 0x45670123          用于解除闪存锁
KEY2          = 0xCDEF89AB             用于解除闪存锁
 
闪存锁
在FLASH_CR中,有一个LOCK位,该位为1时,不能写FLASH_CR寄存器,从而也就不能擦除和编程FLASH,这称为闪存锁。
当LOCK位为1时,闪存锁有效,只有向FLASH_KEYR依次写入KEY1、KEY2后,LOCK位才会被硬件清零,从而解除闪存锁。当LOCK位为1时,对FLASH_KEYR的任何错误写操作(第一次不是KEY1,或第二次不是KEY2),都将会导致闪存锁的彻底锁死,一旦闪存锁彻底锁死,在下一次复位前,都无法解锁,只有复位后,闪存锁才恢复为一般锁住状态。
复位后,LOCK位默认为1,闪存锁有效,此时,可以进行解锁。解锁后,可进行FLASH的擦除编程工作。任何时候,都可以通过对LOCK位置1来软件加锁,软件加锁与复位加锁是一样的,都可以解锁。
 
主存储块的擦除
主存储块可以按页擦除,也可以整片擦除。
页擦除
主存储块的任何一页都可以通过FPEC的页擦除功能擦除。
建议使用以下步骤进行页擦除:
1.检查FLASH_SR寄存器的BSY位。以确认没有其他正在进行的闪存操作。必须等待BSY位为0,才能继续操作。
              2.设置FLASH_CR寄存器的PER位为1。选择页擦除操作。
3.设置FLASH_AR寄存器为要擦除页所在地址,选择要擦除的页。FLASH_AR的值在哪一页范围内,就表示要擦除哪一页。
4.设置FLASH_CR寄存器的STRT位为1,启动擦除操作。
5.等待FLASH_SR寄存器的BSY位变为0,表示操作完成。
6.查询FLASH_SR寄存器的EOP位,EOP为1时,表示操作成功。
              7.读出被擦除的页并做验证。擦完后所有数据位都为1。
 
整片擦除
整片擦除功能擦除整个主存储块,信息块不受此操作影响。
建议使用以下步骤进行整片擦除:
       1.检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的闪存操作。
              2.设置FLASH_CR寄存器的MER位为1。选择整片擦除操作。
              3.设置FLASH_CR寄存器的STRT位为1。启动整片擦除操作。
              4.等待FLASH_SR寄存器的BSY位变为0,表示操作完成。
              5.查询FLASH_SR寄存器的EOP位,EOP为1时,表示操作成功。
              6.读出所有页并做验证。擦完后所有数据位都为1。
 
主存储块的编程
对主存储块编程每次可以写入16位。当FLASH_CR寄存器的PG位为1时,在一个闪存地址写入一个半字(16位)将启动一次编程;写入任何非半字的数据,FPEC都会产生总线错误。在编程过程中(BSY位为1时),任何读写闪存的操作都会使CPU暂停,直到此次闪存编程结束。
建议使用如下步骤对主存储块进行编:
       1.检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的编程操作。
       2.设置FLASH_CR寄存器的PG位为1。选择编程操作。
       3.在指定的地址写入要编程的半字。直接用指针写。
       4.等待FLASH_SR寄存器的BSY位变为0,表示操作完成。
       5.查询FLASH_SR寄存器的EOP位,EOP为1时,表示操作成功。
6.读出写入的地址并验证数据。
 
关于主存储块擦除编程操作的一些疑问
1. 为什么每次都要检查BSY位是否为0?
因为BSY位为1时,不能对任何FPEC寄存器执行写操作,所以必须要等BSY位为0时,才能执行闪存操作。
2. 如果没有擦除就进行编程,会出现什么结果?
STM32在执行编程操作前,会先检查要编程的地址是否被擦除,如果没有,则不进行编程,并置FLASH_SR寄存器的PGERR位为1。唯一例外的是,当要编程的数据为0X0000时,即使未擦除,也会进行编程,因为0X0000即使擦除也可以正确编程。
3. 为什么操作后要读出数据并验证?
STM32在某些特殊情况下(例如FPEC被锁住),可能根本就没有执行所要的操作,仅通过寄存器无法判断操作是否成功。所以,保险起见,操作后都要读出所有数据检查。
4. 等待BSY位为1的时间以多少为合适?
请参考STM32固件库中的数据。
5. FLASH编程手册上说进行闪存操作(擦除或编程)时,必须打开内部的RC振荡器(HSI),是不是一定要用HIS进行闪存的擦除及编程操作?
对于这点,我的理解是,进行闪存操作时,必须要保证HIS没有被关闭,但是操作时的系统仍然可以是HSE时钟。STM32复位后,HIS默认是开的,只要你不为了低功耗去主动关闭它,则用什么时钟都可以进行闪存操作的。我所编的程序也验证了这一点。
 
选项字节
选项字节用于存储芯片使用者对芯片的配置信息。
目前,所有的STM32101xx、STM32102xx、STM32103xx、STM32105xx、STM32107xx产品,选项字节都是16字节。但是这16字节,每两个字节组成一个正反对,即,字节1是字节0的反码,字节3是字节2的反码,...,字节15是字节14的反码,所以,芯片使用者只要设置8个字节就行了,另外8个字节系统自动填充为反码。因此,有时候,也说STM32的选项字节是8个字节,但是占了16字节的空间。
选项字节的8字节正码概述如下:
RDP                    字节0。读保护字节,存储对主存储块的读保护设置。
USER           字节2。用户字节,配置看门狗、停机、待机。
Data0           字节4。数据字节0,由芯片使用者自由使用。
Data1           字节6。数据字节1,由芯片使用者自由使用。
WRP0          字节8。写保护字节0,存储对主存储块的写保护设置。
WRP1          字节10。写保护字节1,存储对主存储块的写保护设置。
WRP2          字节12。写保护字节2,存储对主存储块的写保护设置。
WRP3          字节14。写保护字节3,存储对主存储块的写保护设置。3
 
选项字节写使能
在FLASH­_CR中,有一个OPTWRE位,该位为0时,不允许进行选项字节操作(擦除、编程)。这称为选项字节写使能。只有该位为1时,才能进行选项字节操作。
该位不能软件置1,但可以软件清零。只有向FLASH_OPTKEYR依次写入KEY1和KEY2后,硬件会自动对该位置1,此时,才允许选项字节操作。这称为解锁(打开)选项字节写使能。
该位为1后,可以由软件清零,关闭写使能。
复位后,该位为0。错误操作不会永远关闭写使能,只要写入正确的键序列,则又可以打开写使能。写使能已打开时,再次打开,不会出错,并且依然是打开的。
很显然,进行选项字节操作前,先要解开闪存锁,然后打开选项字节写使能,之后,才能进行选项字节操作。
 
选项字节擦除
建议使用如下步骤对选项字节进行擦除:
       1.检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的闪存操作。
       2.解锁FLASH_CR寄存器的OPTWRE位。即,打开写使能。
       3.设置FLASH_CR寄存器的OPTER位为1。选择选项字节擦除操作。
       4.设置FLASH_CR寄存器的STRT位为1。
       5.等待FLASH_SR寄存器的BSY位变为0,表示操作完成。
       6.查询FLASH_SR寄存器的EOP位,EOP为1时,表示操作成功。
7.读出选项字节并验证数据。
由于选项字节只有16字节,因此,擦除时是整个选项字节都被擦除了。
 
选项字节编程
建议使用如下步骤对选项字节进行编程:
       1.检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的编程操作。
       2.解锁FLASH_CR寄存器的OPTWRE位。即,打开写使能。
       3.设置FLASH_CR寄存器的OPTPG位为1。选择编程操作。
       4.写入要编程的半字到指定的地址。启动编程操作。
       5.等待FLASH_SR寄存器的BSY位变为0,表示操作完成。
       6.查询FLASH_SR寄存器的EOP位,EOP为1时,表示操作成功。
7.读出写入的选项字节并验证数据。
对选项字节编程时,FPEC使用半字中的低字节并自动地计算出高字节(高字节为低字节的反码),并开始编程操作,这将保证选项字节和它的反码始终是正确的。
 
主存储块的保护
可以对主存储块中的数据进行读保护、写保护。
读保护用于保护数据不被非法读出。防止程序泄密。
写保护用于保护数据不被非法改写,增强程序的健壮性。
读保护
主存储块启动读保护后,简单的说具有以下特性:
1.从主存储块启动的程序,可以对整个主存储块执行读操作,不允许对主存储块的前4KB进行擦除编程操作,可以对4KB之后的区域进行擦除编程操作。
2.从SRAM启动的程序,不能对主存储块进行读、页擦除、编程操作,但可以进行主存储块整片擦除操作。
       3.使用调试接口不能访问主存储块。
这些特性足以阻止主存储器数据的非法读出,又能保证程序的正常运行。
只有当RDP选项字节的值为RDPRT键值时,读保护才被关闭,否则,读保护就是启动的。因此,擦除选项字节的操作,将启动主存储块的读保护。如果要关闭读保护,必须将RDP选项字节编程为RDPRT键值。并且,如果编程选项字节,使RDP由非键值变为键值(即由保护变为非保护)时,STM32将会先擦除整个主存储块,再编程RDP。
芯片出厂时,RDP会事先写入RDPRT键值,关闭写保护功能。
 
写保护
STM32主存储块可以分域进行写保护。
如果试图对写保护的域进行擦除或编程操作,在闪存状态寄存器(FLASH_SR)中会返回一个写保护错误标志。
STM32主存储块每个域4KB,WRP0-WRP3选项字节中的每一位对应一个域,位为0时,写保护有效。对于超过128KB的产品,WRP3.15保护了域31及之后的所有域。
显然,擦除选项字节将导致解除主存储块的写保护。
 
选项字节与它的寄存器映象
我们知道,FPEC有两个寄存器存储了选项字节的映象。那么,选项字节本体(在FLASH中)与映象(在寄存器中)究竟有什么区别呢?
选项字节的本体只是个FLASH,它的作用只是掉电存储选项字节内容而以,真正起作用的是寄存器中的映象。即,一个配置是否有效,不是看本体,而是看映象。而映象是在复位后,用本体的值加载的,此后,除非复位,映象将不再改变。所以,更改本体的数据后,不会立即生效,只有复位加载到映象中后,才会生效。
有一点要注意的是,当更改本体的值,使主存储块读保护变为不保护时,会先擦除整片主存储块,然后再改变本体。这是唯一一个改变本体会引发的动作。但即使这样,读保护依然要等到复位后,加载到映象后,才会解除。
 
 
关于FLASH编程手册中文版的几处错误(不一定是,但是与我的理解不符)
1.选项字节编程一节中:
对FPEC解锁后,必须分别写入KEY1和KEY2(见2.3.1节)到FLASH_OPTKEYR寄存器,再设置FLASH_CR寄存器的OPTWRE位为’1’,此时可以对选项字节进行编程
实际上,对FLASH_OPTKEYR写入KEY1和KEY2后,OPTWRE位会被硬件置1,而不是用软件写1。这一点在后面的寄存器描述中也可以得到验证。
2.对读保护的描述中:
对读保护的数值对无法理解。正确的应该是,RDP为RDPRT键值时,解除读保护,为其它值时,读保护生效。

STM32F4Discovery开发帮使用的STM32F407VGT6芯片,内部FLASH有1M之多。平时写的代码,烧写完之后还有大量的剩余。有效利用这剩余的FLASH能存储不少数据。因此研究了一下STM32F4读写内部FLASH的一些操作。

【STM32F4 内部Flash的一些信息】

STM32F407VG的内部FLASH的地址是:0x08000000,大小是0x00100000。

写FLASH的时候,如果发现写入地址的FLASH没有被擦出,数据将不会写入。FLASH的擦除操作,只能按Sector进行。不能单独擦除一个地址上的数据。因此在写数据之前需要将地址所在Sector的所有数据擦除。

在STM32F4的编程手册上可找到FLASH的Sector划分,我们现在只操作Main memory:





参考Demo中的例子,将FLASH的页的其实地址(基地址)可定义如下:

/* Base address of the Flash sectors */

#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */

#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */

#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */

#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */

#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */

#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */

#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */

#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */

#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */

#define ADDR_FLASH_SECTOR_9     ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */

#define ADDR_FLASH_SECTOR_10    ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */

#define ADDR_FLASH_SECTOR_11    ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */

在库里边,FLASH的Sector编号定义如下,这是供库里边的几个函数使用的。需要将地址转换成Sector编号:

#define FLASH_Sector_0     ((uint16_t)0x0000) /*!< Sector Number 0 */

#define FLASH_Sector_1     ((uint16_t)0x0008) /*!< Sector Number 1 */

#define FLASH_Sector_2     ((uint16_t)0x0010) /*!< Sector Number 2 */

#define FLASH_Sector_3     ((uint16_t)0x0018) /*!< Sector Number 3 */

#define FLASH_Sector_4     ((uint16_t)0x0020) /*!< Sector Number 4 */

#define FLASH_Sector_5     ((uint16_t)0x0028) /*!< Sector Number 5 */

#define FLASH_Sector_6     ((uint16_t)0x0030) /*!< Sector Number 6 */

#define FLASH_Sector_7     ((uint16_t)0x0038) /*!< Sector Number 7 */

#define FLASH_Sector_8     ((uint16_t)0x0040) /*!< Sector Number 8 */

#define FLASH_Sector_9     ((uint16_t)0x0048) /*!< Sector Number 9 */

#define FLASH_Sector_10    ((uint16_t)0x0050) /*!< Sector Number 10 */

#define FLASH_Sector_11    ((uint16_t)0x0058) /*!< Sector Number 11 */

Demo中有将地址转换成Sector的代码:

uint32_t GetSector(uint32_t Address)

{

  uint32_t sector = 0;

  

  if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))

  {

    sector = FLASH_Sector_0;  

  }

  else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))

  {

    sector = FLASH_Sector_1;  

  }

  else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))

  {

    sector = FLASH_Sector_2;  

  }

  else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))

  {

    sector = FLASH_Sector_3;  

  }

  else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))

  {

    sector = FLASH_Sector_4;  

  }

  else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))

  {

    sector = FLASH_Sector_5;  

  }

  else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))

  {

    sector = FLASH_Sector_6;  

  }

  else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7))

  {

    sector = FLASH_Sector_7;  

  }

  else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8))

  {

    sector = FLASH_Sector_8;  

  }

  else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9))

  {

    sector = FLASH_Sector_9;  

  }

  else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10))

  {

    sector = FLASH_Sector_10;  

  }

  else/*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11))*/

  {

    sector = FLASH_Sector_11;  

  }

  return sector;

}

 

有了这些定义之后,我们就可以开始正式操作FLASH了

首先,要向FLASH写入数据需要先将FLASH解锁。根据手册定义,解锁FLASH需要先向寄存器FLASH_KEYR写入0x45670123之后再向这个寄存器写入0xCDEF89AB。这两个数据在库中已经定义成了:FLASH_KEY1和FLASH_KEY2.

使用库函数不用这么麻烦,函数FLASH_Unlock()即可完成对FLASH的解锁。

解锁FLASH之后,使用函数FLASH_ClearFlag清除FLASH的状态寄存器。然后就可以对FLASH进行写操作了。我按照示例工程,擦除两块FLASH。

下边是操作FLASH的代码,首先擦除两块FLASH,然后向这两块FLASH中写入数据。最后进行校验:

要写入的数据定义:

#define DATA_32                 ((uint32_t)0x12345678)

开始FLASH操作:

  FLASH_Unlock(); //解锁FLASH后才能向FLASH中写数据。

  FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | 

                  FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);

  /* Get the number of the start and end sectors */

  StartSector = GetSector(FLASH_USER_START_ADDR);  //获取FLASH的Sector编号

  EndSector = GetSector(FLASH_USER_END_ADDR);

  

  //擦除FLASH

  for (i = StartSector; i < EndSector; i += 8)  //每次FLASH编号增加8,可参考上边FLASH Sector的定义。

  {

    /* Device voltage range supposed to be [2.7V to 3.6V], the operation will

       be done by word */ 

    if (FLASH_EraseSector(i, VoltageRange_3) != FLASH_COMPLETE)

    { 

      while (1)

      {

      }

    }

  }

  

  /*擦除完毕*/

  /*开始写入*/

  Address = FLASH_USER_START_ADDR;

  

    while (Address < FLASH_USER_END_ADDR)

  {

    if (FLASH_ProgramWord(Address, DATA_32) == FLASH_COMPLETE)   //将DATA_32写入相应的地址。

    {

      Address = Address + 4;

    }

    else

    { 

      /* Error occurred while writing data in Flash memory. 

         User can add here some code to deal with this error */

      while (1)

      {

      }

    }

  }

  

  FLASH_Lock();  //读FLASH不需要FLASH处于解锁状态。

  

//读出数据 检查写入值是否正确

  Address = FLASH_USER_START_ADDR;

  MemoryProgramStatus = 0x0;

   while (Address < FLASH_USER_END_ADDR)

  {

    data32 = *(__IO uint32_t*)Address;   //读FLASH中的数据,直接给出地址就行了。跟从内存中读数据一样。

    if (data32 != DATA_32)

    {

      MemoryProgramStatus++;  

    }

    Address = Address + 4;

  }  

 

下边是使用STLink Utility读出的数据,检查一下,确实写进去数据了:





参考文档是ST的 STM32F40xxx and STM32F41xxx Flash programming manual。可在ST网站下载。文档编号:PM0081。FLASH的有不少寄存器,各个含义手册上有详细介绍。我只是简单地看了下。使用库函数操作,好像不大需要详细理解这些寄存器了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: