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-319LCD_ShowString(30,170,200,16,16,"Start Write FLASH……");STMFLASH_Write(FLASH_SAVE_ADDRESS,(u32*)TEXT_Buffer,SIZE); //在保存地址上写入字节长为SIZE的字,要写入的字来自于TEXT_BufferLCD_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;elsereturn 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 Kbytesu32 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/27806.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

操作系统进行设备控制的方式

一.I/O控制方式 上一篇的博客介绍了设备管理的一些概念基础知识点&#xff0c;其中I/O控制方式这一块没有详细说明。设备管理的主要任务之一是控制设备和内存或CPU之间的数据传送。外围设备和内存之间的输入/输出控制方式有4种&#xff0c;下面分别加以介绍。 二.程序直接控制…

Kotlin基础(五):类和接口

前言 本文主要讲解类和接口&#xff0c;主要包括类的声明、构造器、类成员、修饰符、类的继承、接口、抽象类。 Kotlin文章列表 Kotlin文章列表: 点击此处跳转查看 目录 1.1 类的声明 在 Kotlin 中&#xff0c;类的声明使用关键字 class。下面是一个简单的类声明的示例&…

高时空分辨率、高精度一体化预测技术的风、光、水自动化预测技术的应用

第一章 预测平台讲解及安装 一、高精度气象预测基础理论介绍 综合气象观测数值模拟模式&#xff1b; 全球预测模式、中尺度数值模式&#xff1b; 二、自动化预测平台介绍 Linux系统 Crontab定时任务执行机制 Bash脚本自动化编程 硬件需求简介 软件系统安装 …

【论文阅读】一些多轮对话文章的体会 ACL 2023

前言 本文是对昨天看到的ACL 2023三篇多轮对话文章的分享这三个工作都是根据一些额外属性控制输出的工作&#xff0c;且评估的方面比较相似&#xff0c;可以借鉴 方法 这几篇文章都不是做general任务的&#xff0c;倾向于通过一些额外信息&#xff0c;来做specific任务 【1】…

Linux的基本使用和web程序部署

注意&#xff1a;本文章不适合C学习者&#xff08;知识点远远不够&#xff09;&#xff0c;只适合Java学习者&#xff0c;学习简单的Linux命令 1.Linux的背景知识 1.1Linux是什么 Linux是一个操作系统&#xff0c;和Windows是“并列”的关系。经过多年的发展&#xff0c;Lin…

Three.js——十三、自定义大小画布、UI交互按钮以及3D场景交互、渲染画布为文件(图片)

画布全屏以及自定义大小画布 <!-- canvas元素默认是行内块元素 --> <divclass"model"style"background-color: #ff0000;"width"300"height"180" ></div>画布随窗口变化 // 画布跟随窗口变化 window.onresize fun…

Qt 项目架构之----MVC架构

MVC 模式代表 Model-View-Controller&#xff08;模型-视图-控制器&#xff09; 模式。这种模式用于应用程序的分层开发。 Model&#xff08;模型&#xff09;-是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View&#xff08;视图&#x…

2023 Testing Expo倒计时-聚焦风丘9003展位

请点击此处&#xff0c;即可进行在线登记报名并了解更多信息&#xff01;

25个高级SQL查询-列出结果集的前5行

本专栏中的许多示例将基于以下员工表(employee)。只有少数例子将以其他表格为基础;在这些情况下,表格将与示例一起进行说明。 一、WITH WITH AS 短语,也叫做子查询部分,定义一个SQL片断后,该SQL片断可以被整个SQL语句所用到。有的时候,with as 是为了提高SQL语句的可读…

2023 Testing Expo倒计时-聚焦Softing 9003展位

请点击此处&#xff0c;即可进行在线登记报名并了解更多信息&#xff01;

计算机二级c语言考试复习大纲(一战到底)

1.C语言关键字 1.数据类型关键字&#xff08;12个&#xff09; char(字符型) short&#xff08;短整型&#xff09; int&#xff08;整型&#xff09; long&#xff08;长整型&#xff09; float&#xff08;单精度浮点型&#xff09; double&#xff08;双精度浮点…

设计模式——单例模式

单例模式 定义 确保某一个类只有一个实例&#xff0c;而且自行实例化并向整个系统提供这个实例。 即保证一个类只有一个实例&#xff0c;并且提供一个全局访问点 优缺点、应用场景 优点 单例对象在内存中只有一个实例&#xff0c;减少了内存的开支。尤其对于一个频繁创建…