STM32 Flash详解教程文章

目录

Flash基本概念理解

Flash编程接口FPEC

Flash擦除/写入流程图

Flash选项字节基本概念理解

Flash电子签名

函数读取地址下存放的数据

Flash的数据处理限制部分


编写不易,请勿搬运,感谢理解!!!

Flash基本概念理解

STM32的Flash里面包含,程序存储器系统存储器选项字节,通过对Flash闪存的片上外设接口(接口地址在ram地址),可以对程序存储器跟选项字节进行擦除和编程。

**·程序存储器:**用来下载存放程序的位置

**·系统存储器:**用来存放BootoLoader芯片出厂自带程序

**·选项字节:**用来配置Flash读保护,写保护,等功能

其常见在程序中的用法是,通过程序软件编程利用内部Flash数据掉电不丢失的特性与内部Flash通常不用使用完,将重要的参数存储在Flash的最后几页内

和使用指针指向系统存储区域内,芯片的ID号地址,读取属于芯片自己的ID号,通过利用芯片不同的ID号,在程序执行时判断芯片ID号,如果不同则不执行程序,来实现程序只能在特定芯片上运行。

最后一种用法就是通过对Flash的选项字节配置,来完成程序的读保护跟写保护,在程序中来配置。

flash分配地址详解

在STM32F1系列芯片中,其中Flash每页的起始地址后三位均以 000 400 800 C00开头。

Flash编程接口FPEC

Flash编程控制器 FPEC 全称是 Flash Program/Erase Controller 用于对Flash存储器进行读写和擦除的硬件模块。该模块包含7个32位寄存器。

·FPEC键寄存器(FLASH_KEYR):用于解锁FPEC外设,允许进行Flash编程或擦除操作

·选择字节键寄存器(FLASH_OPTKEYR):用于解锁Flash选项字节的修改操作

·闪存控制寄存器(FLASH_CR):控制Flash编程和擦除操作的核心寄存器

·闪存状态寄存器(FLASH_SR):指示Flash当前状态和操作结果

·闪存地址寄存器(FLASH_AR):指定Flash操作的目标地址

·选择字节寄存器(FLASH_OBR):读取选项字节当前的配置状态

·写保护寄存器(FLASH_WRPR):指示和配置Flash的写保护状态

7个寄存器中需要注意的是,Flash_SR寄存器的BSY位,当该位为1的时候代表正在进行flash操作,当为0的时候代表操作结束或者发生错误,通常来读取该位的值来判断flash操作是否完成结束。

Flash_CR寄存器的PG位代表是否选择编程操作,PER位是否选择页擦除操作,MER位是否选择全擦除操作,OPTPG选择字节编程,OPTER擦除选择字节,STRT开始操作,该位为1的时候将触发擦除操作,只能由软件置1并在BSY为1的时候清除为0,LOCK锁为1代表FPEC和Flash_CR被锁住,为0代表解锁成功。

寄存器地址范围图

在芯片复位后,FPEC模块是被保护的,Flash_CR寄存器不能被读写操作,首先需要对,Flash_KEYR写入特定键值KEY1 KEY2 来完成解锁操作,如果写入错误会在下次复位之前锁死,FPEC跟Flash_CR寄存器。

·RDPRT键 = 0x000000A5 //解除读保护

·KEY1 = 0x45670123 //KEY1+KEY2解除FPEC锁

·KEY2 = 0xCDEF89AB

需要注意的是,在解锁FPEC对Flash_CR寄存器操作完成对Flash的读|写之后,需要设置Flash_CR中的LOCK位锁住FPEC跟Flash_CR位,来防止数据的误写入导致对Flash进行操作。

Flash擦除/写入流程图

Flash写入数据流程图

Flash页擦除流程图

Flash全擦除流程图

在三张流程图中只有擦除流程图需要置Flash_CR寄存器的STRT位为1,在上文有介绍过当为1的时候会执行擦除操作,然后当Flash_SR寄存器的BSY为0的是时候会将STRT位置0。

Flash选项字节基本概念理解

在芯片地址中给选项字节分配了16个字节的地址,其中8个字节的地址用来备份,当前选项字节的数据,只有8个字节用来配置Flash选项字节。其中四个字节用来写保护,1个字节用来读保护,1个字节配置选项,2个字节存储用户数据。

分配地址图

需要注意的是,在完成对FPEC的解锁之后,还需要再次对Flash_OPTKKEYR写入KEY1 KEY2来完成对选项字节的解锁,同事置Flash_CR的OPTWRE位为1才能对选项字节进行写入操作。

·RDP:写入RDPRT键(0x000000A5)后解除读保护

·USER:硬件看门狗配置 停机模式|待机模式 是否产生复位

·Data:用户自定义数据

·WRP:该位置用来配置写保护

Flash电子签名

电子签名是芯片出厂的时候由芯片制造商写入的信息,存放地址实在系统存储区域,用来表示芯片的唯一性跟芯片的flash容量大小。

·0x1FFFF7E0 ~ 0x1FFFF7E3 存放flash大小

·0x1FFFF7E8 ~ 0x1FFFF7EF 存储 UID。

其中UID Uniqune Device ID 唯一设备标识,共96位12字节改信息用户不可更改,可以利用指针进行访问。

函数读取地址下存放的数据

上文中提到了在大多数单片机程序中因为自身Flash没有被使用完,因此可以对剩余Flash空间进行读写数据操作函数如下。

cpp 复制代码
uint32_t FlashR_Word(uint32_t address)
{
    return *((__IO uint32_t *)(address))
}

函数中__IO来自官方定义库文件定义如下

cpp 复制代码
#define __IO  voliate

将宏定义带入到函数中就能得到下面代码

cpp 复制代码
(__IO uint32_t *)(address) = (volitate uint32_t *)(address)

改代码作用是将函数局部变量参数转化为指向该参数的uint32_t,指针类型,同时括号外面的*就是指针解引用的意思,就是返回指针指向该地址下的数值,这里因为声明的指针类型为uint32_t类型所以就能返回一个字的地址。

也就是32位数据,4个字节的数据,同理可以得到读取16位数据的函数,还有读取一个字节的函数,只要把函数内声明的指针类型给改了就行了

cpp 复制代码
uint16_t FlashR_HWord(uint32_t address)
{
    return *((__IO uint16_t *)(address))
}

uint8_t FlashR_Byte(uint32_t address)
{
    return *((__IO uint8_t *)(address))
}
//这里因为改变了 内部声明的指针类型 跟返回值类型所以能获取的数据大小也被限制了

Flash的数据处理限制部分

在对Flash进行数据存储的通常情况下需要再程序中定义一个 uint16_t data[512] 数组,大小是1024也就是一个Flash页面大小,先将数据写入ram的数组里面然后再写入Flash里面进行存储,通常这种情况是因为Flash本身的写入限制问题。

首先Flash擦除的最小单位是页 ,同时在Flash进行写入的时候不存在数据覆盖这种写法 ,也就是将数据0x12345对改地址重新写入数据0x56787是写不进去的是因为Flash中的数据只能从1->0,而不能从0->1,而在擦除完单页Flash之后,该页存储的数据全部为0xFFFF FFFF 也就是全部是1。

这就导致了如果直接对Flash进行数据写入,会发现在数据写入到最后的时候如果想要更改前面的数据大概率是需要擦除才能进行写入的,但是如果进行擦除其他位的数据又丢失,所以通常常用的数据数据手段也就是。

将Flash单页数据读出保存在一个Flash页大小的数组里面进行更改数据,然后将整页数据重新写入到Flash数组里面来完成操作,跟将需要写入的数据先在数组里面进行覆盖,最后统一写入Flash里面。

Step 1 :先从 Flash 读取数据到 RAM

Step 2 :在 RAM 修改数据

Step 3 :擦除 整个 Flash Page (Flash 只能整页擦除)。

Step 4 :将 RAM 数据重新写入 Flash

cpp 复制代码
#include "stm32f10x.h"
#include "Store.h"
#include "MyFlash.h"
//数组内512个数据 每个数据类型是 uint16_t 也就是2个字节 数组能存放1024字节数据 也就是对应Flash一页的容量
uint16_t Store_Data[512];
//在ram 里面定义一个数组 需要备份的时候统一转到闪存里面
void Flash_Init(void)
{

	if(Flash_WHWord(0x08000000)!=0xA5A5)
	{
		Earse_FlashPage(0x08000000);
		Flash_WHWord(0x08000000 ,0xA5A5 );
		for(uint16_t i = 1;i<512;i++)
		{
			Flash_WHWord(0x08000000 +i*2 ,0x0000 );
		}
	}
	//将Flash里面数据保存读到ram里面
	for(uint16_t i = 0; i<512;i++)
	{
		Store_Data[i] = FlashRH_Word(0x08000000 + i*2);
	
	}
}
void Flash_Save(void)
{
	//先擦除Flash在进行写入,不然会发现写不进去
	Earse_FlashPage(0x08000000);
	for(uint16_t i = 0;i<512;i++)
	{
		Flash_WHWord(0x08000000 + i*2,Store_Data[i] );
	}
}
//函数用来清除数组 Flash里面的数据
void Flah_Clear(void)
{
	for(uint16_t i = 0;i<512;i++)
	{
		Store_Data[i] = 0x0000;
	}
	Flash_Save();
}

代码部分

这里需要注意的是i*2问题,因为在Flash里面最小写入单元跟最小存储单元室一个半字,也就是16位,4个字节,举个例子就是0x0800 0000是单元起始地址,到0x0800 0001 地址该单元结束,该地址指向存储单元,每个地址指向的存储单元能够存储 2个字节。

所以在数据数据进行写入的时候,地址的起始地址都是用2的倍数开头的,因为像0x0800 0001这种是不合法地址,是没有办法进行写入半字数据。

这种用法通常是用在单片机程序很小,自身Flash占用不完的情况下使用,而有没有办法知道自己的程序占用多少字节,同时改变程序烧录的位置,无论程序大小都不能烧录到自己保存数据的Flash数据部分这种方法也是有的。

查看程序自身大小方法图

魔术棒里面有程序的起始地址还有结束地址,只要把在Keil5里面可以更改这两个地址从而达到更改程序的烧录地址的效果。

欢迎指正,希望对你,有所帮助!!!