STM32F4_FLASH模拟EEPROM

news2024/11/24 10:49:38

目录 

前言

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初阶数据结构——排序

目录 排序的概念常见排序算法插入排序希尔排序选择排序堆排序冒泡排序快速排序hoare挖坑法前后指针法快排的时间复杂度三路划分三数取中和随机数选中快排的非递归版本快速排序的总结 归并排序归并的递归版本归并的非递归版本 内排序和外排序非比较排序稳定性排序算法复杂度和稳…

测试基础 Android 应用测试总结

目录 启动&#xff1a; 功能介绍&#xff0c;引导图&#xff0c;流量提示等&#xff1a; 权限&#xff1a; 文件错误 屏幕旋转&#xff1a; 流量&#xff1a; 缓存&#xff08;/sdcard/data/com.your.package/cache/&#xff09;&#xff1a; 正常中断&#xff1a; 异…

Android 自定义CheckBox样式,设置切换背景图,类似于RadioButton

文章目录 概要自定义CheckBok资源文件如下使用方法实现效果 概要 目前要实现类似于Radiobutton选择按钮&#xff0c;如果只有一个RadioButton&#xff0c;就不能和radio Group连用&#xff0c;导致选择没办法取消&#xff0c;如果要实现只能代码中进行操作&#xff0c;过于繁琐…

项目管理软件选择指南:最佳实践与避坑指南

当今企业中&#xff0c;协作工具是必不可少的&#xff0c;每个企业都会寻找最适合自己的协作工具来提高工作效率。在这些协作工具中&#xff0c;Zoho Projects项目协作工具是最常用的一种&#xff0c;因为它能够为团队提供一个集任务、项目、文档、IM、目标、日历、甘特图、工时…

MIT 6.829 -- L2 The Internetworking Problem

MIT 6.829 -- L2 The Internetworking Problem 前言The Internetworking Problem: Many Different NetworksGateWays互联网设计原则通用性原则健壮性原则互联网缺点互联网协议标准流程 最早的TCP/IP今天的TCP/IP: IPv4地址分片和重组Time-to-live&#xff08;TTL&#xff09;Ty…

2023 7.17~7.23 周报 (最近读的论文方法论分析)

0 上周回顾 上周完成了RTM的研究学习, 完成了进一步阅读论文所需的知识储备. 同时从代码层面深度解析了正演和RTM存在的关系, 发掘了很多富有参考意义的信息. 1 本周计划 深度剖析论文《Deep-Learning Full-Waveform Inversion Using Seismic Migration Images》的方法体系,…

计算机网络——VLan介绍

学习视频&#xff1a; 网工必会&#xff0c;十分钟搞明白&#xff0c;最常用的VLAN技术_哔哩哔哩_bilibili 技术总结&#xff1a;VLAN&#xff0c;网络中最常用的技术&#xff0c;没有之一_哔哩哔哩_bilibili 全国也没几个比我讲得好的&#xff1a;VLAN虚拟局域网 本来补充了…

微服务day1——微服务入门

一、认识微服务 1、单体架构 将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署。 优点 架构简单部署成本低 缺点 耦合度高 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPfsQXAn-1689593800699)(https://picture.wangkay.tec…

探索OLED拼接屏的特点及在莱山的场景化应用

涞山oled拼接屏是一种高清晰度的显示屏&#xff0c;由多个oled屏幕拼接而成。它可以用于各种场合&#xff0c;如商业展示、广告宣传、会议演示等。涞山oled拼接屏具有以下特点&#xff1a; 1. 高清晰度&#xff1a;oled屏幕具有高对比度、高亮度、高色彩饱和度等特点&#xff0…

jeecg-boot sql注入漏洞解决

输入下面的链接地址&#xff0c;就会出现下面的获取数据&#xff0c;这个漏洞还是比较严重的啊 http://localhost:8080/nbcio-boot/sys/ng-alain/getDictItemsByTable/%20from%20sys_user/*,%20/x.js 通过上面方式可以获取用户信息了 如下&#xff1a; RequestMapping(valu…

层次分析模型

层次分析法是对一些较为复杂、模糊的问题做出决策的简易方法 这里涉及一个决策概念的理解 初步理解应该是一种评价类的模型 层次分析法的典型应用&#xff1a; 1、用于最佳方案的选取 2、用于评价类分析 3、用于指标体系的优选 层次分析法的名字中层次的原因 层次分析法的步骤…

Skywalking使用说明

需求背景 随着分布式的盛行&#xff0c;系统的复杂度也逐步增加&#xff0c;不同服务间的交互对性能的定位提出了更高的要求。任意一个节点的异常&#xff0c;都可能对业务系统造成损失。对于链路追踪&#xff0c;迫切需要一个优秀的监测工具。 需求如下 功能性需求 请求链…

怎么把word文档转换成pdf文件?这三个方法超级实用!

word文档编辑完成后&#xff0c;通常会将其转换为PDF格式&#xff0c;以使文档内容更加简洁。那么&#xff0c;如何将Word文档转换为PDF呢&#xff1f;下面将介绍三种方法&#xff0c;相信对你会有所帮助。 一、记灵在线工具 首先&#xff0c;在浏览器中打开记灵在线工具的网…

Spring+SpringMVC+JdbcTemplate小Demo

项目目录结构 创建mavenWeb项目 pom文件 spring依赖、spring-web依赖、spring-webmvc依赖、spring-test、junit依赖 servlet依赖、jsp依赖、jstl、standard依赖 jackson的core和databind和annotations依赖、fastjson依赖、 文件上传的commons-fileupload和commons-io依赖 日志c…

图片修补 EdgeConnect 论文的阅读与翻译:生成边缘轮廓先验,再填补缺失内容

本文将要介绍的论文就是&#xff1a;EdgeConnect: Generative Image Inpainting with Adversarial Edge Learning&#xff0c;因为知乎在&#xff08;2019-02-02&#xff09;前&#xff0c;缺少详细介绍这篇论文的文章&#xff0c;而我最近需要复现它&#xff0c;所以顺便在这里…

消息重试框架 Spring-Retry 和 Guava-Retry

一 重试框架之Spring-Retry 1.Spring-Retry的普通使用方式 2.Spring-Retry的注解使用方式 二 重试框架之Guava-Retry 总结 图片 一 重试框架之Spring-Retry Spring Retry 为 Spring 应用程序提供了声明性重试支持。它用于Spring批处理、Spring集成、Apache Hadoop(等等)。…

智能应急疏散系统在公共建筑中的的应用

安科瑞 华楠 摘 要&#xff1a;随着大型公共建筑物的不断增多&#xff0c;其所产生的各种建筑安全隐患问题也在逐渐加剧&#xff0c;一旦出现火灾险情&#xff0c;要想从公共建筑中安全的脱离出来&#xff0c;其难度也是可想而知。因此&#xff0c;这就需要在进行公共建筑设计时…

Java打怪升级路线的相关知识

第一关:JavaSE阶段 1、计算机基础 2、java入门学习 3、java基础语法 4、流程控制和方法 5、数组 6、面向对象编程 7、异常 8、常用类 9、集合框架 10、IO 11、多线程 12、GUI编程 13、网络编程 14、注解与反射 15、JUC编程 16、JVM探究 17、23种设计模式 18、数据结构与算法 1…

mysql数字开头字符串排序

表结构 CREATE TABLE building (id bigint NOT NULL,name varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT 名称,full_name varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT 全称,PRIMARY KEY (id) USIN…

Qt|读写ini文件使用QSettings 节键值 设置相对路径

#include <QtWidgets/QApplication> #include <QWidget> #include <QSettings> #include <QString>int main(int argc, char *argv[]) {QApplication a(argc, argv);// Qt中使用QSettings类读写ini文件// QSettings构造函数的第一个参数是ini文件的路径…