STM32F4_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。

#define FLASH KEY1  ((uint32_t)0x45670123)
#define FLASH KEY2  ((uint32_t)0xCDEF89AB)

void FLASH Unlock(void)
{
    if((FLASH->CR & FLASH_CR_LOCK)!=RESET)
    {
        FLASH->KEYR = FLASH KEY1;
        FLASH->KEYR = FLASH KEY2;    
    }
}

void FLASH_Lock(void)
{
    FLASH->CR |= FLASH_CR_LOCK;
}

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状态函数

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

// 返回值通过枚举定义

typedef enum
{
    FLASH_BUSY=1, //操作忙
    FLASH_ERROR_RD, //读保护错误
    FLASH_ERROR_PGS, //编程顺序错误
    FLASH_ERROR_PGP, //编程并行位数错误
    FLASH_ERROR_PGA, //编程对齐错误
    FLASH_ERROR_WRP, //写保护错误
    FLASH_ERROR_PROGRAM, //编程错误
    FLASH_ERROR_OPERATION, //操作错误
    FLASH_COMPLETE  //操作结束
}FLASH_Status;

        5. 等待操作完成函数

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

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

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

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

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

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

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

#include "stm32f4xx.h"                 
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "MyI2C.h"
#include "AT24C02.h"
#include "STMFlash.h"

//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
	LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
	led_set(sta);
}

//要写入STM32F4 FLASH的字符串数组
const u8 TEXT_Buffer[]={"STM32 FLASH TEST"};  
#define TEXT_LENTH sizeof(TEXT_Buffer)   //数组长度
#define SIZE  TEXT_LENTH/4+((TEXT_LENTH%4)?1:0)   //判断字节长是不是4的倍数,也可以说是判断地址是否有效
//如果字能被4整除,那么TEXT_LENTH/4一定是一个正数,TEXT_LENTH%4一定是0,那么整体一定是真,返回SIZE等于1;
//如果不是4的倍数,那么返回SIZE是0;

#define FLASH_SAVE_ADDRESS 0x0800C004   //设置FLASH 保存地址(必须为偶数,且所在的扇区要大于本代码所用到的扇区)
										//否则,写操作的时候,可能会导致擦除整个扇区,从而引起部分程序丢失,引起死机

int main(void)
{
	u8 key=0;
	u16 i=0;
	u8 datatemp[SIZE];
	delay_init(168);
	uart_init(115200);
	
	LED_Init();
	LCD_Init();
	Key_Init();
	POINT_COLOR=RED;
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
	LCD_ShowString(30,70,200,16,16,"FLASH EEPROM TEST");
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2023/07/18");
	LCD_ShowString(30,130,200,16,16,"KEY1:Write  KEY0:Read");
	
	while(1)
	{
		key=KEY_Scan(0); 
		if(key==2)  //KEY1按下  写STM32 FLASH
		{
			LCD_Fill(0,170,239,319,WHITE);  //清除半屏 x范围是0-239,y范围是170-319
			LCD_ShowString(30,170,200,16,16,"Start Write FLASH……");
			STMFLASH_Write(FLASH_SAVE_ADDRESS,(u32*)TEXT_Buffer,SIZE); //在保存地址上写入字节长为SIZE的字,要写入的字来自于TEXT_Buffer
			LCD_ShowString(30,170,200,16,16,"FLASH Write Finished!"); //提示传送完成
		}
		if(key==1)  //KEY0按下  读取字符串并显示
		{
			LCD_ShowString(30,170,200,16,16,"Start Read FLASH……");
			STMFLASH_Read(FLASH_SAVE_ADDRESS,(u32*)datatemp,SIZE);   //从写入的地址上读出字节SIZE长的字,读到的字存储到datatemp数组中
			LCD_ShowString(30,170,200,16,16,"The Data Readed Is:  "); 
			LCD_ShowString(30,190,200,16,16,datatemp);  //显示读到的字符串
		}
		i++;
		delay_ms(10);
		if(i==20)
		{
			LED0=!LED0; //提示系统正在运行
			i=0;
		}
	}
}



6.2 STMFlash.c

#include "stm32f4xx.h"                
#include "STMFlash.h"
#include "delay.h"
#include "usart.h"


//读取指定地址的字(32位数据)
//fAddress:读地址
//返回值:对应数据
u32 STMFLASH_ReadWord(u32 fAddress) //字节8位,半字16位,字为32位
{
	return *(vu32*)fAddress; //将地址强制类型转换为vu32的指针,然后解引用得到该指针指向位置的数据
}
//获取某个地址所在的FLASH扇区
//Address:Flash地址
//返回值:0~11,即Address所在的扇区
uint16_t STMFLASH_GetFlashSector(u32 Address)
{
	//该函数的架构思路是:首先我在头文件中宏定义了每个扇区的起始地址,只要给定地址Address小于某一扇区的起始地址,就认为该地址处于上一个扇区中
	if(Address<ADDRESS_FLASH_SECTOR_1) //所给地址Address小于扇区1的起始地址
		return FLASH_Sector_0;   //认为所给地址处于扇区0中    以下每一个判断语句都是如此
	else if(Address<ADDRESS_FLASH_SECTOR_2)
		return FLASH_Sector_1;
	else if(Address<ADDRESS_FLASH_SECTOR_3)
		return FLASH_Sector_2;
	else if(Address<ADDRESS_FLASH_SECTOR_4)
		return FLASH_Sector_3;
	else if(Address<ADDRESS_FLASH_SECTOR_5)
		return FLASH_Sector_4;
	else if(Address<ADDRESS_FLASH_SECTOR_6)
		return FLASH_Sector_5;
	else if(Address<ADDRESS_FLASH_SECTOR_7)
		return FLASH_Sector_6;
	else if(Address<ADDRESS_FLASH_SECTOR_8)
		return FLASH_Sector_7;
	else if(Address<ADDRESS_FLASH_SECTOR_9)
		return FLASH_Sector_8;
	else if(Address<ADDRESS_FLASH_SECTOR_10)
		return FLASH_Sector_9;
	else if(Address<ADDRESS_FLASH_SECTOR_11)
		return FLASH_Sector_10;
	else
		return FLASH_Sector_11;
}
//从指定地址开始写入指定长度的数据,数据存储在pBuffer缓冲区中
//特别注意:往内部FLASH中写程序时,本函数写地址如果非0xFF,那么会先擦除整个扇区并且不保存扇区数据;(意味着所要写的地址上有数据,但是要写入新的数据,必须擦除原来的数据并且不会保存)
//			在SPI写W25Q128的时候,假设所要写的地址上有数据,我们是定义一个新的缓存区,把原本的数据存在这个缓存区之后,再进行擦除操作,这样操作完成后,再把缓存区的数据放到原本的地址上
//			这样可以有效的防止扇区数据丢失,确保所要写的缓存区内没有重要数据
//该函数对OTP区域也有效!可以用来写OTP区
//OTP区域地址范围:0x1FFF7800~0x1FFF7A0F
//WriteAddress:起始地址(一个字节是4位,所以此地址必须是4的倍数!!!)
//pBuffer:数据指针,指向所要存储区域的地址
//NumToWrite:字(32位)数  (要写入32位数据的个数,一个数据占4位,FLASH中存储的是一位一位的,所以FLASH中存储一个字要占4位)
void STMFLASH_Write(u32 WriteAddress,u32 *pBuffer,u32 NumToWrite) //往起始地址WriteAddress写入NumToWrite个字,写入的这些字存储到pBuffer缓存区中
{
//****************************************************************************
//	FLASH_Status FLASH_GetStatus(void);  //获取FLASH状态
	
//	// 返回值通过枚举定义
	
//	typedef enum
//	{
//		FLASH_BUSY=1, //操作忙
//		FLASH_ERROR_RD, //读保护错误
//		FLASH_ERROR_PGS, //编程顺序错误
//		FLASH_ERROR_PGP, //编程并行位数错误
//		FLASH_ERROR_PGA, //编程对齐错误
//		FLASH_ERROR_WRP, //写保护错误
//		FLASH_ERROR_PROGRAM, //编程错误
//		FLASH_ERROR_OPERATION, //操作错误
//		FLASH_COMPLETE  //操作结束
//	}FLASH_Status;
//****************************************************************************
	FLASH_Status status=FLASH_COMPLETE; //获取FLASH操作状态函数得到的返回值,该返回值通过枚举结构体定义  设置结构体变量status,初始化为操作结束FLASH_COMPLETE
	//FLASH_COMPLETE表示获取FLASH的状态为操作结束
	//FLASH在写操作和擦除操作时,是不可以进行数据读取的,否则会导致数据堵塞
	//所以获得FLASH状态为操作结束时,才可以从指定地址开始写入指定长度的数据
	u32 Address=0;  //定义起始地址
	u32 EndAddress=0;  //定义结束地址
	if(WriteAddress<STM32_FLASH_START_ADDRESS_BASE||WriteAddress%4)  //要写的地址必须在FLASH的起始地址之后,否则写入的区域不是FLASH的主存储区,也就是非法地址
																	//WriteAddress%4的意思是:因为写入时是一个字一个字写入的,一个字占FLASH存储区的4位,所以每一次写入的起始地址都必须是4的倍数
	{
		return; //非法地址
	}
	FLASH_Unlock();  //确认所写入的地址是合法的情况下,进行解锁操作,确保可以操作FLASH_CR控制寄存器
	FLASH_DataCacheCmd(DISABLE);  //FLASH擦除期间,必须禁止数据缓存,否则会造成数据阻塞,因为擦除和写入操作时是不可以进行数据读取的
	
	Address=WriteAddress;   //得到写入的起始地址
	EndAddress=WriteAddress+NumToWrite*4;   //得到写入的结束地址,结束地址等于起始地址加上所要写入字节个数*4;之所以乘4是因为FLASH中1个字占4位
	if(Address<0x1FFF0000)      //0x1FFF0000是系统存储区的首地址,而我们写入的程序一般存储在主存储区中,所以起始地址小于系统存储区的起始地址,就默认是在主存储区中
		//只有在主存储中,才能执行擦除操作!!!
	{
		while(Address<EndAddress)  //起始地址一定要小于结束地址才有效
		{
			if(STMFLASH_ReadWord(Address)!=0xFFFFFFFF) //调用读取指定地址字的函数,如果这个地址上读取的字不是0XFFFFFFFFF,就要擦除这个扇区
			{
				status=FLASH_EraseSector(STMFLASH_GetFlashSector(Address),VoltageRange_3);//STMFLASH_GetFlashSector获取地址所在的扇区,开发板选择的电压范围是3.3V,所以选择VoltageRange_3
				//FLASH_EraseSector该函数为擦除某个扇区函数,第一个参数是某个扇区,第二个参数是电压范围
				if(status!=FLASH_COMPLETE) //擦除某个扇区的返回值给到status,如果擦除完某个扇区得到的返回值不是操作结束FLASH_COMPLETE,那么报错
				{
					break;
				}
			}
			else	//否则表示该扇区的字为0xFFFFFFFF,无需擦除
				Address=Address+4; //指向下一个地址,再次判断是否是0xFFFFFFFF,需不需要擦除
		}
	}
	if(status==FLASH_COMPLETE) //如果擦除这个扇区得到的返回值是FLASH_COMPLETE,那么意味着擦除操作已经结束,可以进行写数据操作了
	{
		while(WriteAddress<EndAddress)  //写数据  要写入的地址一定要大于扇区的起始地址,保证写入FLASH的主存储区域中
		{
			if(FLASH_ProgramWord(WriteAddress,*pBuffer)!=FLASH_COMPLETE)  //写入数据
				//FLASH_ProgramWord写入字函数,写入的字存储到pBuffer缓存区中,如果得到的返回值不是操作结束,那么报错
				//只有得到返回结束,才意味着该字节写入成功
			{
				break;  //写入异常
			}
			WriteAddress=WriteAddress+4; //while循环中,每写一个字的数据,地址加4,指向下一个字
			pBuffer++;	//指针指向下一个字
		}
	}
	FLASH_DataCacheCmd(ENABLE);  //FLASH擦除结束,开启数据缓存
	FLASH_Lock(); //上锁,进行写保护,保护重要信息
}
//从指定地址开始读出指定长度的数据
//ReadAddress:起始地址
//pBuffer:数据指针
//NumToRead:字数
void STMFLASH_Read(u32 ReadAddress,u32 *pBuffer,u32 NumToRead)
{
	u32 i;
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=STMFLASH_ReadWord(ReadAddress); //读取该地址上的字,通过循环依次存储到pBuffer中
		ReadAddress=ReadAddress+4; // 每读一次,地址+4,读下一个字
	}
}



6.3 STMFlash.h

#ifndef _STMFLASH__H_
#define _STMFLASH__H_
#include "sys.h"


//FLASH起始地址
#define STM32_FLASH_START_ADDRESS_BASE 0x08000000  //STM32 FLASH起始地址

//FLASH扇区起始地址   定义扇区的地址是定义FLASH主存储区的地址,本次使用的是STM32F4系列的芯片,对应FLASH主存储区大小1024K
#define ADDRESS_FLASH_SECTOR_0  ((u32)0x08000000)  //扇区0起始地址,16 Kbytes
#define ADDRESS_FLASH_SECTOR_1  ((u32)0x08004000)  //扇区1起始地址,16 Kbytes
#define ADDRESS_FLASH_SECTOR_2  ((u32)0x08008000)  //扇区2起始地址,16 Kbytes
#define ADDRESS_FLASH_SECTOR_3  ((u32)0x0800C000)  //扇区3起始地址,16 Kbytes
#define ADDRESS_FLASH_SECTOR_4  ((u32)0x08010000)  //扇区4起始地址,64 Kbytes
#define ADDRESS_FLASH_SECTOR_5  ((u32)0x08020000)  //扇区5起始地址,128 Kbytes
#define ADDRESS_FLASH_SECTOR_6  ((u32)0x08040000)  //扇区6起始地址,128 Kbytes
#define ADDRESS_FLASH_SECTOR_7  ((u32)0x08060000)  //扇区7起始地址,128 Kbytes
#define ADDRESS_FLASH_SECTOR_8  ((u32)0x08080000)  //扇区8起始地址,128 Kbytes
#define ADDRESS_FLASH_SECTOR_9  ((u32)0x080A0000)  //扇区9起始地址,128 Kbytes
#define ADDRESS_FLASH_SECTOR_10 ((u32)0x080C0000)  //扇区10起始地址,128 Kbytes
#define ADDRESS_FLASH_SECTOR_11 ((u32)0x080E0000)  //扇区11起始地址,128 Kbytes
	

u32 STMFLASH_ReadWord(u32 fAddress);
uint16_t STMFLASH_GetFlashSector(u32 Address);
void STMFLASH_Write(u32 WriteAddress,u32 *pBuffer,u32 NumToWrite);
void STMFLASH_Read(u32 ReadAddress,u32 *pBuffer,u32 NumToRead);



#endif

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值