当前位置:   article > 正文

STM32F4_FLASH模拟EEPROM_flash模拟eeprom介绍和代码实现

flash模拟eeprom介绍和代码实现

目录 

前言

1. 内部FLASH简介

2. 内部FLASH写入过程

3. 内部FLASH库函数

4. FLASH的读写保护及解除

5. FLASH相关寄存器

6. 实验程序

6.1 main.c

6.2 STMFlash.c

6.3 STMFlash.h


前言

STM32F4本身并没有自带EEPROM,但是STM32F4具有IAP功能,也就是在应用编程功能。本节将IAP在应用编程功能的FLASH当成EEPROM来使用

        STM32编程方式:

        ①:在线编程(ICP,In-Circuit Programming)

        通过JTAG/SWD协议或者系统加载程序(Bootloader)下载用户应用程序到微控制器中。

        ②:在程序中编程(IAP,In Application Programming)

        通过任何一种通信接口(如IO端口,USB,CAN,UART,I2C,SPI等)下载程序或者应用数据到存储器中。也就是说,STM32允许用户在应用程序中重新烧写闪存存储器中的内容。然而,IAP需要至少有一部分程序已经使用ICP方式烧到闪存存储器中(Bootloader)

1. 内部FLASH简介

        在STM32芯片内部有一个FLASH存储器,他主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行。

注:事实上,我们将代码下载到开发板的MCU中,实际上都是下载到芯片内部的FLASH存储器中。

        除了使用外部的工具(如下载器)读写内部FLASH外,STM32F4芯片在运行的时候,也能对自身的内部FLASH进行读写,因此,若内部FLASH存储了应用程序后还有剩余空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。

        由于访问内部FLASH比外部SPI-FLASH的速度快的多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算机机密信息并记录到某些区域,然后删除自身的部分加密代码,这些都涉及到内部FLASH的操作。

STM32内部FLASH包括主存储器系统存储器、OTP区域以及选项字节区域

        其中系统存储器是STM32开发板出厂之前就已经使用的一块区域,用户是无法访问系统存储区的,主要是做串口下载程序的支持,以及USB、CAN等ISP烧录功能。(系统存储器主要是用来存放STM32F4的bootloader代码,此代码是出厂的时候就固化在STM32F4里面的,专门来给主存储器下载代码的)

        OTP区域,即一次性可编程区域,共528字节,被分成两部分,前面512个字节(32字节为1块,分成16块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!!)后面16个字节,用于锁定对应块。

        选项字节区域是用来配置FLASH的读写保护、待机/停机、软件/硬件看门狗功能。可以通过修改FLASH的选项控制寄存器进行修改。

        主存储器:

                像我们在介绍一款芯片的时候,提到的256K FLASH或者512K FLASH,其中256K和512K指的都是这个主存储器的大小。主存储器用来存放代码和数据常量(如const类型的数据)

                主存储器分256页,每页大小2KB,共512KB。这个分页的概念,实质上就是FLASH存储器的扇区,与其他FLASH一样,在写入数据前,要先按照页,也就是扇区进行擦除。

                STM32F4的主存储器块分为 4个 16KB 扇区、1个 64KB 扇区和 7个 128KB 扇区

型号STM32F4ZG:

        其中型号中的字母G就表示FLASH的大小

        4表示16KB;       6表示32KB;      8表示64KB;      B表示128KB;

        C表示256KB;    E表示512KB;    F表示768KB;    G表示1024KB;

闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。

在执行闪存写操作时,任何对闪存的读操作都是锁住总线,在写操作完成后,读操作才能正确进行;也就是说在进行写或者擦除操作时,不能进行数据或者代码的读取操作

2. 内部FLASH写入过程

1. 解锁

        由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给 FLASH 上锁,这个时候不再允许设置 FLASH 的控制寄存器,同时也不能修改 FLASH 中的内容。

        所以对 FLASH 写入程序之前,需要先对其进行解锁操作

  •         往 FLASH 密钥寄存器 FLASH_KEYR 中写入 KEY1=0x45670123
  •         再往 FLASH 密钥寄存器 FLASH_KEYR 中写入 KEY2=0xCDEF89AB

2. 擦除扇区

        在写入新的数据前,需要先擦除存储区域,STM32提供了扇区擦除指令整个FLASH擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。

        扇区擦除的过程:

  •         检查FLASH_SR状态寄存器的 “忙碌寄存器BSY” ,以确认当前未执行任何FLASH操作
  •         在FLASH_CR寄存器中,将 “激活页擦除寄存器位PER” 置1
  •         用FLASH_AR寄存器选择要擦除的页
  •         将FLASH_CR控制寄存器中的 “开始擦除寄存器位STRT” 置1,开始擦除
  •         等待BSY位被清零,表示擦除完成

3. 写入数据

        擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针指向地址赋值,赋值前还需要配置一系列的寄存器

  •         检查FLASH_SR状态寄存器中的BSY位,以确认当前未执行任何其他的内部FLASH操作
  •         将FLASH_CR控制寄存器中 “激活编程寄存器位PG” 置1
  •         向指定的FLASH存储器地址执行数据写入操作,每次只能以16位的方式写入
  •         等待BSY位被清零时,表示写入成功

查看工程的空间分布:

        由于内部FLASH本身存储有程序数据,若不是有意删除某段程序代码,一般不应该修改程序空间的内容,所以在使用内部FLASH存储其他数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应做任何的修改。通过查询应用程序编译时产生的 “*.map” 后缀文件,可以了解程序存储到了哪些区域。

3. 内部FLASH库函数

1. FLASH解锁、上锁函数

        解锁的时候,他对FLASH_KEYR寄存器写入两个解锁参数。上锁的时候,对FLASH_CR寄存器的FLASH_CR_LOCK位置1。

  1. #define FLASH KEY1 ((uint32_t)0x45670123)
  2. #define FLASH KEY2 ((uint32_t)0xCDEF89AB)
  3. void FLASH Unlock(void)
  4. {
  5. if((FLASH->CR & FLASH_CR_LOCK)!=RESET)
  6. {
  7. FLASH->KEYR = FLASH KEY1;
  8. FLASH->KEYR = FLASH KEY2;
  9. }
  10. }
  11. void FLASH_Lock(void)
  12. {
  13. FLASH->CR |= FLASH_CR_LOCK;
  14. }

2. 擦除函数

        解除后擦除扇区时可调用FLASH_EraseSector完成;

        该函数包含以Page_Address输入参数获得要擦除的地址。内部根据该参数配置FLASH_AR地址,然后擦除扇区,擦除扇区的时候需要等待一段时间,它使用FLASH_WaitForLastOperation等待,擦除完成的时候才会退出FLASH_EraseSector函数。

3. 写入数据

        对内部FLASH写入数据不像对SDRAM操作那样直接指针操作就可以完成了,还要设置一系列的寄存器,利用FLASH_ProgramWordFLASH_ProgramHalfWord函数可按字、半字节单位写入数据。

STM32F4内部FLASH库函数:

        1. 锁定解锁函数

        void FLASH_Unlock(void); //解锁函数     对FLASH操作前必须先进行解锁

        void FLASH_Lock(void);     //锁定FLASH

        2. 写操作函数

        FLASH_Status FLASH_ProgramDoubleWord(uint32_t Address,uint64_t Data);   //写入双字函数

        FLASH_Status FLASH_ProgramWord(uint32_t Address,uint32_t Data);   //写入字函数

        FLASH_Status FLASH_ProgramHalfWord(uint32_t Address,uint16_t Data);   //写入半字函数

        FLASH_Status FLASH_ProgramByte(uint32_t Address,uint8_t Data);   //写入字节函数

        3. 擦除函数

        FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector,uint8_t VoltageRange);   //擦除某个扇区函数

        FLASH_Status FLASH_EraseAllSectors(uint8_t VoltageRange);   //擦除整个扇区函数

        FLASH_Status FLASH_EraseAllBank1Sectors(uint8_t VoltageRange);   //STM32F4将所有的Sector分成两个Bank,所以定义两个函数来擦除两个Bank下的Sector

        FLASH_Status FLASH_EraseAllBank2Sector(uint8_t VoltageRange);   //擦除Bank下的Sector

函数第一个参数的取值范围为 FLASH_Sector_0~FLASH_Sector_11 (这些都是头文件中宏定义好的)

函数第二个参数是电压范围,STM32F4的电压范围是3.3V,所以选择VoltageRange_3即可

        4. 获取FLASH状态

        FLASH_Status FLASH_GetStatus(void);   //获取FLASH状态函数

  1. FLASH_Status FLASH_GetStatus(void); //获取FLASH状态
  2. // 返回值通过枚举定义
  3. typedef enum
  4. {
  5. FLASH_BUSY=1, //操作忙
  6. FLASH_ERROR_RD, //读保护错误
  7. FLASH_ERROR_PGS, //编程顺序错误
  8. FLASH_ERROR_PGP, //编程并行位数错误
  9. FLASH_ERROR_PGA, //编程对齐错误
  10. FLASH_ERROR_WRP, //写保护错误
  11. FLASH_ERROR_PROGRAM, //编程错误
  12. FLASH_ERROR_OPERATION, //操作错误
  13. FLASH_COMPLETE //操作结束
  14. }FLASH_Status;

        5. 等待操作完成函数

        在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确的进行;因此在进行写操作或者擦除命令时,不能同时进行数据的读取

        FLASH_Status FLASH_WaitForLastOperation(void); //返回FLASH的状态

        在每次操作之前,都要等待上一次操作完成才能开始

        6. 读FLASH特定地址数据函数

        从指定地址读取一个字的函数

  1. u32 STMFLASH_ReadWord(u32 faddress) //参数输入地址
  2. {
  3. return *(vu32*)faddress; //定义一个vu32的指针指向该地址,解引用得到该地址上的值
  4. //返回解引用得到的该地址的值
  5. }

4. FLASH的读写保护及解除

选项字节和读写保护:

        在实际发布的产品中,STM32芯片的内部FLASH存储了控制程序,如果不做任何保护措施的话,可以使用下载器直接把内部FLASH的内容读取回来,得到bin或hex文件格式的代码拷贝,别有用心的厂家可能会利用该代码制造山寨产品、为此,STM32芯片提供了多种方式保护内部FLASH的程序不被非法读取,但是在默认状态下该保护功能是不开启的,若要开启该功能,需要改写内部FLASH选项字节(Option Bytes)中的配置。

修改选项字节的过程:

        修改选项字节的内容可修改各种配置,但是,当应用程序运行时,无法直接通过选项字节改写他们的内容。

        要改写其内容必须设置寄存器FLASH_OPTCR及FLASH_OPTCR1中对应数据位

        默认情况下,FLASH_OPTCR寄存器中的第0位OPTLOCK的值为1,它表示选项字节被上锁,需要解锁后才能进行修改,当寄存器的值设置完成后,对FLASH_OPTCR寄存器中的第1位OPTSTRT位设置为1,硬件就会擦除选项字节扇区的内容,并把FLASH_OPTCR/1寄存器中包含的值写入到选项字节。

        修改选项字节的配置步骤:        

  •                 解锁,在FLASH选项密钥寄存器FLASH_OPTKEYR中写入OPTKET1=0x0819 2A3B;接着在Flash选项密钥寄存器FLASH_OPTKEYR中写入OPTKEY2=0x4C5D 6E7F。
  •                 检查FLASH_SR状态寄存器中的BSY位,以确认当前未执行其他Flash操作。
  •                 在FLASH_OPTCR和/或FLASH_OPTCR1寄存器中写入选项字节值。
  •                 将FLASH_OPTCR寄存器中的选项启动位OPTSTRT置1。
  •                 等待BSY位清零,即写入完成。

闪存的读取:

        STM32F4可以通过内部的 I-Code指令总线D-Code数据总线 访问内置闪存模块;

        数据的读写可以通过 D-Code数据总线 来访问内部闪存模块。为了准确的读取 Flash 数据,必须根据CPU时钟(HCLK)频率和器件电源电压在 Flash存取控制寄存器FLASH_ACR 中正确的设置等待周期数LATENCY。当电源电压低于2.1V时,必须关闭预取缓冲器。

         等待周期WS通过FLASH存取控制寄存器FLASH_ACR寄存器LATENCY[2:0]三个位设置。系统复位后,CPU时钟频率为内部16M RC振荡器,LATENCY默认是0,即一个等待周期。供电电压一般是3.3V,所以设置168MHz频率作为CPU时钟之前,必须先设置LATENCY为5.(根据上表中对应的关系进行设置),否则FLASH读写可能出错,导致死机。

        根据上表中的对应周期,正常工作168MHz时,对应的是6个CPU周期,但是只需要对FLASH存取状态寄存器的低三位写入101,也就是5.

这是因为STM32F4具有自适应实时存储器加速器(ART Accelerator),通过指令缓存存储器,预取指令,实现相当于0 FLASH等待的运行速度。

        STM32F4的FLASH读取比较简单。例如,要从地址Address上读取一个字(字节为8位,半字为16位,字为32位),可以通过如下的语句读取:

Data=*(vu32*)Address

其中将地址Address强制转换成vu32的指针,然后解引用得到该指针指向地址的值。

闪存的编程和擦除:

        执行任何Flash编程操作(擦除或编程)时,CPU时钟频率HCLK不能低于1MHz。如果在FLASH操作期间发生器件复位,无法保证Flash中的内容。

        在对STM32F4的Flash执行写入或擦除操作期间,任何读取Flash的尝试都会导致总线阻塞。也就是说,STM32F4内部的FLASH进行写入或者擦除操作时,是不能进行数据的读取的。

        FLASH_CR的解锁序列:

        1. 写0x45670123到FLASH_KEYR密钥寄存器

        2. 写0xCDEF89AB到FLASH_KEYR密钥寄存器

        通过这两个步骤,即可解锁FLASH_CR,如果写入错误,那么FLASH_CR将被锁定,直到下次复位后才可以再次解锁。

        STM32F4闪存编程位数:

        闪存编程位数通过FLASH_CR的PSIZE字段配置,PSIZE的设置必须和电源电压匹配。

STM32F4开发板使用的是3.3V电压,所以PSIZE必须设置为10,也就是并行位数x32位。擦除或者编程都必须以32位为基础进行。

注:STM32F4的FLASH在编程的时候,必须要求其写入地址的FLASH是被擦除了的(STM32F4内部的FLASH只能写入1或者0,擦除以后的32位是0xFFFF FFFF,也就是说只能在擦除以后的1的基础之上改为0,否则就要重新进入擦除操作)

        STM32F4的标准编程步骤:

        1. 检查FLASH_SR中的BSY位,确保当前未执行任何FLASH操作

        2. 将FLASH_CR寄存器的PG位置1,激活FLASH编程

        3. 针对所需存储器地址(主存储器或OTP区域内)执行数据写入操作   (通过设置FLASH状态寄存器的 PSIZE 位,设置并行位数位x32时按字写入)

        4. 等待BSY位清零,完成一次编程

注意:1. 编程前要确保要写入地址的FLASH已经擦除;2. 要先写入FLASH密钥寄存器解锁,否则是不能操作FLASH状态寄存器的;3. 编程操作对OTP区域同样有效。

STM32F4的FLASH编程的时候,要先判断所写地址是否被擦除了;STM32F4的闪存擦除分为两种:扇区擦除整片擦除

         扇区擦除步骤:

        1. 检查FLASH_CR的LOCK是否解锁,如果没有则先解锁

        2. 检查FLASH_SR寄存器的BSY位,确保当前未执行任何FLASH操作

        3. 在FLASH_CR寄存器中,将SER位置1,并从主存储块的12个扇区中选择要擦除的扇区SNB

        4. 将FLASH_CR寄存器中的 STRT位 置1,触发擦除操作

        5. 等待BSY位清零

        批量擦除步骤:

        1. 检查FLASH_SR寄存器中的BSY位,确保当前未执行任何FLASH操作

        2. 在FLASH_CR寄存器中,将MER位置1(批量擦除)

        3. 将FLASH_CR寄存器中的STRT位置1,触发擦除操作

        4. 等待BSY位清零

5. FLASH相关寄存器

FLASH访问控制寄存器:FLASH_ACR

Flash Access Control Register:Flash访问控制寄存器用于使能/关闭加速功能,并且可根据CPU频率控制Flash访问时间

位10 DCEN:数据缓存使能(Data cache enable)

  •         0:关闭数据缓存
  •         1:使能数据缓存

位9 ICEN:指令缓存(Instruction cache enable)

  •         0:关闭指令缓存
  •         1:使能指令缓存

位8 PRFTEN:预取使能(Prefetch enable)

  •         0:关闭预取
  •         1:使能预取

DCEN、ICEN 和 PRFTEN 这三个位也非常重要,为了达到最佳的性能,这三个位一般都设置为 1 即可

位2:0 LATENCY:延迟(Latency) 这些位表示CPU时钟周期与Flash访问时间之比

        这三个位必须通过MCU的工作电压和频率来进行正确的设置,否则可能会死机。

  •         000:零等待周期
  •         001:一个等待周期
  •         010:两个等待周期
  •         011:三个等待周期
  •         100:四个等待周期
  •         101:五个等待周期
  •         110:六个等待周期
  •         111:七个等待周期

FLASH密钥寄存器:FLASH_KEYR

Flash Key Register:借助Flash密钥寄存器,可允许Flash控制寄存器的访问,进而允许进行编程或擦除操作。

位31:0 FKEYR:FPEC密钥寄存器(FPEC key)

        将FLASH_CR寄存器解锁并允许对其执行编程/擦除操作,必须顺序编程以下值:

  •         a:KEY1=0x45670123
  •         b:KEY2=0xCDEF89AB

FLASH选项密钥寄存器:FLASH_OPTKEYR

Flash option key register:借助Flash选项密钥寄存器,可允许在用户配置扇区中执行编程和擦除操作

位31:0 OPTKEYR:选项字节密钥(Option byte key) 将FLASH_OPTCR寄存器解锁并允许对其编程,必须顺序编程以下值:

  •         a:OPTKEY1=0x08192A3B
  •         b:OPTKEY2=0x4C5D6E7F

FLASH状态寄存器:FLASH_SR

Flash Status Register:Flash状态寄存器提供正在执行的编程和擦除操作的相关信息

位16 BSY:繁忙(Busy)

        该位指示Flash操作正在进行。该位在Flash操作开始时置1,在操作结束或出现错误时清零。

  •         0:当前未执行任何Flash操作
  •         1:正在执行Flash操作

FLASH控制寄存器:FLASH_CR

Flash Control Register:Flash 控制寄存器用于配置和启动Flash 操作

位31 LOCK:锁定Lock

        该位只能写入1。该位置1时,表示FLASH_CR寄存器已锁定。当检测到解锁序列时,由硬件将该位清0。如果解锁操作失败,该位仍保持置1,直到下一次复位。

位16 STRT:启动Start

        该位置1后可触发擦除操作。该位只能通过软件置1,并在BSY位清零后随之清零。

位9:8 PSIZE:编程大小(Program size) 这些位用于选择编程并行位数

  •         00:x8编程
  •         01:x16编程
  •         10:x32编程
  •         11:x64 编程 

位7:3 SNB:扇区编号(Sector number) 这些位用于选择要擦除的扇区

  •         0000:扇区0
  •         0001:扇区1
  •         ……
  •         01011:扇区11
  •         01100:不允许
  •         01101:不允许
  •         01111:不允许
  •         10000:扇区12

位2 MER:批量擦除(Mass Erase)

        针对所有用户扇区激活擦除操作

位1 SER:扇区擦除(Sector Erase)

        激活扇区擦除

位0 PG:编程(Programming)

        激活Flash编程

6. 实验程序

实验现象:

        开机时在LCD上显示一些提示信息,然后在主循环里面检测两个按键,其中按键KEY1用来执行写入FLASH的操作,按键KEY0用来执行读出FLASH中的数据的操作。

6.1 main.c

  1. #include "stm32f4xx.h"
  2. #include "delay.h"
  3. #include "usart.h"
  4. #include "LED.h"
  5. #include "lcd.h"
  6. #include "Key.h"
  7. #include "usmart.h"
  8. #include "MyI2C.h"
  9. #include "AT24C02.h"
  10. #include "STMFlash.h"
  11. //LCD状态设置函数
  12. void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
  13. {
  14. LED1=sta;
  15. }
  16. //函数参数调用测试函数
  17. void test_fun(void(*ledset)(u8),u8 sta)
  18. {
  19. led_set(sta);
  20. }
  21. //要写入STM32F4 FLASH的字符串数组
  22. const u8 TEXT_Buffer[]={"STM32 FLASH TEST"};
  23. #define TEXT_LENTH sizeof(TEXT_Buffer) //数组长度
  24. #define SIZE TEXT_LENTH/4+((TEXT_LENTH%4)?1:0) //判断字节长是不是4的倍数,也可以说是判断地址是否有效
  25. //如果字能被4整除,那么TEXT_LENTH/4一定是一个正数,TEXT_LENTH%4一定是0,那么整体一定是真,返回SIZE等于1
  26. //如果不是4的倍数,那么返回SIZE0
  27. #define FLASH_SAVE_ADDRESS 0x0800C004 //设置FLASH 保存地址(必须为偶数,且所在的扇区要大于本代码所用到的扇区)
  28. //否则,写操作的时候,可能会导致擦除整个扇区,从而引起部分程序丢失,引起死机
  29. int main(void)
  30. {
  31. u8 key=0;
  32. u16 i=0;
  33. u8 datatemp[SIZE];
  34. delay_init(168);
  35. uart_init(115200);
  36. LED_Init();
  37. LCD_Init();
  38. Key_Init();
  39. POINT_COLOR=RED;
  40. LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
  41. LCD_ShowString(30,70,200,16,16,"FLASH EEPROM TEST");
  42. LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
  43. LCD_ShowString(30,110,200,16,16,"2023/07/18");
  44. LCD_ShowString(30,130,200,16,16,"KEY1:Write KEY0:Read");
  45. while(1)
  46. {
  47. key=KEY_Scan(0);
  48. if(key==2) //KEY1按下 写STM32 FLASH
  49. {
  50. LCD_Fill(0,170,239,319,WHITE); //清除半屏 x范围是0-239,y范围是170-319
  51. LCD_ShowString(30,170,200,16,16,"Start Write FLASH……");
  52. STMFLASH_Write(FLASH_SAVE_ADDRESS,(u32*)TEXT_Buffer,SIZE); //在保存地址上写入字节长为SIZE的字,要写入的字来自于TEXT_Buffer
  53. LCD_ShowString(30,170,200,16,16,"FLASH Write Finished!"); //提示传送完成
  54. }
  55. if(key==1) //KEY0按下 读取字符串并显示
  56. {
  57. LCD_ShowString(30,170,200,16,16,"Start Read FLASH……");
  58. STMFLASH_Read(FLASH_SAVE_ADDRESS,(u32*)datatemp,SIZE); //从写入的地址上读出字节SIZE长的字,读到的字存储到datatemp数组中
  59. LCD_ShowString(30,170,200,16,16,"The Data Readed Is: ");
  60. LCD_ShowString(30,190,200,16,16,datatemp); //显示读到的字符串
  61. }
  62. i++;
  63. delay_ms(10);
  64. if(i==20)
  65. {
  66. LED0=!LED0; //提示系统正在运行
  67. i=0;
  68. }
  69. }
  70. }

6.2 STMFlash.c

  1. #include "stm32f4xx.h"
  2. #include "STMFlash.h"
  3. #include "delay.h"
  4. #include "usart.h"
  5. //读取指定地址的字(32位数据)
  6. //fAddress:读地址
  7. //返回值:对应数据
  8. u32 STMFLASH_ReadWord(u32 fAddress) //字节8位,半字16位,字为32
  9. {
  10. return *(vu32*)fAddress; //将地址强制类型转换为vu32的指针,然后解引用得到该指针指向位置的数据
  11. }
  12. //获取某个地址所在的FLASH扇区
  13. //Address:Flash地址
  14. //返回值:0~11,即Address所在的扇区
  15. uint16_t STMFLASH_GetFlashSector(u32 Address)
  16. {
  17. //该函数的架构思路是:首先我在头文件中宏定义了每个扇区的起始地址,只要给定地址Address小于某一扇区的起始地址,就认为该地址处于上一个扇区中
  18. if(Address<ADDRESS_FLASH_SECTOR_1) //所给地址Address小于扇区1的起始地址
  19. return FLASH_Sector_0; //认为所给地址处于扇区0中 以下每一个判断语句都是如此
  20. else if(Address<ADDRESS_FLASH_SECTOR_2)
  21. return FLASH_Sector_1;
  22. else if(Address<ADDRESS_FLASH_SECTOR_3)
  23. return FLASH_Sector_2;
  24. else if(Address<ADDRESS_FLASH_SECTOR_4)
  25. return FLASH_Sector_3;
  26. else if(Address<ADDRESS_FLASH_SECTOR_5)
  27. return FLASH_Sector_4;
  28. else if(Address<ADDRESS_FLASH_SECTOR_6)
  29. return FLASH_Sector_5;
  30. else if(Address<ADDRESS_FLASH_SECTOR_7)
  31. return FLASH_Sector_6;
  32. else if(Address<ADDRESS_FLASH_SECTOR_8)
  33. return FLASH_Sector_7;
  34. else if(Address<ADDRESS_FLASH_SECTOR_9)
  35. return FLASH_Sector_8;
  36. else if(Address<ADDRESS_FLASH_SECTOR_10)
  37. return FLASH_Sector_9;
  38. else if(Address<ADDRESS_FLASH_SECTOR_11)
  39. return FLASH_Sector_10;
  40. else
  41. return FLASH_Sector_11;
  42. }
  43. //从指定地址开始写入指定长度的数据,数据存储在pBuffer缓冲区中
  44. //特别注意:往内部FLASH中写程序时,本函数写地址如果非0xFF,那么会先擦除整个扇区并且不保存扇区数据;(意味着所要写的地址上有数据,但是要写入新的数据,必须擦除原来的数据并且不会保存)
  45. // 在SPI写W25Q128的时候,假设所要写的地址上有数据,我们是定义一个新的缓存区,把原本的数据存在这个缓存区之后,再进行擦除操作,这样操作完成后,再把缓存区的数据放到原本的地址上
  46. // 这样可以有效的防止扇区数据丢失,确保所要写的缓存区内没有重要数据
  47. //该函数对OTP区域也有效!可以用来写OTP区
  48. //OTP区域地址范围:0x1FFF7800~0x1FFF7A0F
  49. //WriteAddress:起始地址(一个字节是4位,所以此地址必须是4的倍数!!!)
  50. //pBuffer:数据指针,指向所要存储区域的地址
  51. //NumToWrite:字(32位)数 (要写入32位数据的个数,一个数据占4位,FLASH中存储的是一位一位的,所以FLASH中存储一个字要占4位)
  52. void STMFLASH_Write(u32 WriteAddress,u32 *pBuffer,u32 NumToWrite) //往起始地址WriteAddress写入NumToWrite个字,写入的这些字存储到pBuffer缓存区中
  53. {
  54. //****************************************************************************
  55. // FLASH_Status FLASH_GetStatus(void); //获取FLASH状态
  56. // // 返回值通过枚举定义
  57. // typedef enum
  58. // {
  59. // FLASH_BUSY=1, //操作忙
  60. // FLASH_ERROR_RD, //读保护错误
  61. // FLASH_ERROR_PGS, //编程顺序错误
  62. // FLASH_ERROR_PGP, //编程并行位数错误
  63. // FLASH_ERROR_PGA, //编程对齐错误
  64. // FLASH_ERROR_WRP, //写保护错误
  65. // FLASH_ERROR_PROGRAM, //编程错误
  66. // FLASH_ERROR_OPERATION, //操作错误
  67. // FLASH_COMPLETE //操作结束
  68. // }FLASH_Status;
  69. //****************************************************************************
  70. FLASH_Status status=FLASH_COMPLETE; //获取FLASH操作状态函数得到的返回值,该返回值通过枚举结构体定义 设置结构体变量status,初始化为操作结束FLASH_COMPLETE
  71. //FLASH_COMPLETE表示获取FLASH的状态为操作结束
  72. //FLASH在写操作和擦除操作时,是不可以进行数据读取的,否则会导致数据堵塞
  73. //所以获得FLASH状态为操作结束时,才可以从指定地址开始写入指定长度的数据
  74. u32 Address=0; //定义起始地址
  75. u32 EndAddress=0; //定义结束地址
  76. if(WriteAddress<STM32_FLASH_START_ADDRESS_BASE||WriteAddress%4) //要写的地址必须在FLASH的起始地址之后,否则写入的区域不是FLASH的主存储区,也就是非法地址
  77. //WriteAddress%4的意思是:因为写入时是一个字一个字写入的,一个字占FLASH存储区的4位,所以每一次写入的起始地址都必须是4的倍数
  78. {
  79. return; //非法地址
  80. }
  81. FLASH_Unlock(); //确认所写入的地址是合法的情况下,进行解锁操作,确保可以操作FLASH_CR控制寄存器
  82. FLASH_DataCacheCmd(DISABLE); //FLASH擦除期间,必须禁止数据缓存,否则会造成数据阻塞,因为擦除和写入操作时是不可以进行数据读取的
  83. Address=WriteAddress; //得到写入的起始地址
  84. EndAddress=WriteAddress+NumToWrite*4; //得到写入的结束地址,结束地址等于起始地址加上所要写入字节个数*4;之所以乘4是因为FLASH中1个字占4
  85. if(Address<0x1FFF0000) //0x1FFF0000是系统存储区的首地址,而我们写入的程序一般存储在主存储区中,所以起始地址小于系统存储区的起始地址,就默认是在主存储区中
  86. //只有在主存储中,才能执行擦除操作!!!
  87. {
  88. while(Address<EndAddress) //起始地址一定要小于结束地址才有效
  89. {
  90. if(STMFLASH_ReadWord(Address)!=0xFFFFFFFF) //调用读取指定地址字的函数,如果这个地址上读取的字不是0XFFFFFFFFF,就要擦除这个扇区
  91. {
  92. status=FLASH_EraseSector(STMFLASH_GetFlashSector(Address),VoltageRange_3);//STMFLASH_GetFlashSector获取地址所在的扇区,开发板选择的电压范围是3.3V,所以选择VoltageRange_3
  93. //FLASH_EraseSector该函数为擦除某个扇区函数,第一个参数是某个扇区,第二个参数是电压范围
  94. if(status!=FLASH_COMPLETE) //擦除某个扇区的返回值给到status,如果擦除完某个扇区得到的返回值不是操作结束FLASH_COMPLETE,那么报错
  95. {
  96. break;
  97. }
  98. }
  99. else //否则表示该扇区的字为0xFFFFFFFF,无需擦除
  100. Address=Address+4; //指向下一个地址,再次判断是否是0xFFFFFFFF,需不需要擦除
  101. }
  102. }
  103. if(status==FLASH_COMPLETE) //如果擦除这个扇区得到的返回值是FLASH_COMPLETE,那么意味着擦除操作已经结束,可以进行写数据操作了
  104. {
  105. while(WriteAddress<EndAddress) //写数据 要写入的地址一定要大于扇区的起始地址,保证写入FLASH的主存储区域中
  106. {
  107. if(FLASH_ProgramWord(WriteAddress,*pBuffer)!=FLASH_COMPLETE) //写入数据
  108. //FLASH_ProgramWord写入字函数,写入的字存储到pBuffer缓存区中,如果得到的返回值不是操作结束,那么报错
  109. //只有得到返回结束,才意味着该字节写入成功
  110. {
  111. break; //写入异常
  112. }
  113. WriteAddress=WriteAddress+4; //while循环中,每写一个字的数据,地址加4,指向下一个字
  114. pBuffer++; //指针指向下一个字
  115. }
  116. }
  117. FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
  118. FLASH_Lock(); //上锁,进行写保护,保护重要信息
  119. }
  120. //从指定地址开始读出指定长度的数据
  121. //ReadAddress:起始地址
  122. //pBuffer:数据指针
  123. //NumToRead:字数
  124. void STMFLASH_Read(u32 ReadAddress,u32 *pBuffer,u32 NumToRead)
  125. {
  126. u32 i;
  127. for(i=0;i<NumToRead;i++)
  128. {
  129. pBuffer[i]=STMFLASH_ReadWord(ReadAddress); //读取该地址上的字,通过循环依次存储到pBuffer中
  130. ReadAddress=ReadAddress+4; // 每读一次,地址+4,读下一个字
  131. }
  132. }

6.3 STMFlash.h

  1. #ifndef _STMFLASH__H_
  2. #define _STMFLASH__H_
  3. #include "sys.h"
  4. //FLASH起始地址
  5. #define STM32_FLASH_START_ADDRESS_BASE 0x08000000 //STM32 FLASH起始地址
  6. //FLASH扇区起始地址 定义扇区的地址是定义FLASH主存储区的地址,本次使用的是STM32F4系列的芯片,对应FLASH主存储区大小1024K
  7. #define ADDRESS_FLASH_SECTOR_0 ((u32)0x08000000) //扇区0起始地址,16 Kbytes
  8. #define ADDRESS_FLASH_SECTOR_1 ((u32)0x08004000) //扇区1起始地址,16 Kbytes
  9. #define ADDRESS_FLASH_SECTOR_2 ((u32)0x08008000) //扇区2起始地址,16 Kbytes
  10. #define ADDRESS_FLASH_SECTOR_3 ((u32)0x0800C000) //扇区3起始地址,16 Kbytes
  11. #define ADDRESS_FLASH_SECTOR_4 ((u32)0x08010000) //扇区4起始地址,64 Kbytes
  12. #define ADDRESS_FLASH_SECTOR_5 ((u32)0x08020000) //扇区5起始地址,128 Kbytes
  13. #define ADDRESS_FLASH_SECTOR_6 ((u32)0x08040000) //扇区6起始地址,128 Kbytes
  14. #define ADDRESS_FLASH_SECTOR_7 ((u32)0x08060000) //扇区7起始地址,128 Kbytes
  15. #define ADDRESS_FLASH_SECTOR_8 ((u32)0x08080000) //扇区8起始地址,128 Kbytes
  16. #define ADDRESS_FLASH_SECTOR_9 ((u32)0x080A0000) //扇区9起始地址,128 Kbytes
  17. #define ADDRESS_FLASH_SECTOR_10 ((u32)0x080C0000) //扇区10起始地址,128 Kbytes
  18. #define ADDRESS_FLASH_SECTOR_11 ((u32)0x080E0000) //扇区11起始地址,128 Kbytes
  19. u32 STMFLASH_ReadWord(u32 fAddress);
  20. uint16_t STMFLASH_GetFlashSector(u32 Address);
  21. void STMFLASH_Write(u32 WriteAddress,u32 *pBuffer,u32 NumToWrite);
  22. void STMFLASH_Read(u32 ReadAddress,u32 *pBuffer,u32 NumToRead);
  23. #endif
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/481363
推荐阅读
相关标签
  

闽ICP备14008679号