目录
前言
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_ProgramWord和FLASH_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