内部flash闪存
1、内部flash的简介
STM32F1系列的flash闪存(掉电不丢失)包含:
①程序存储器flash(存储编译好的程序),地址是以0x0800 0000开始。
②系统存储器(存储BootLoader程序,不允许修改),地址以0x1FFF F000开始。
③选项字节(存储配置参数)三个部分,地址以0x1FFF F800开始
通过闪存存储器接口(外设)可以对程序存储器flash和选项字节进行擦除和编程。
读写FLASH的用途:
利用程序存储器的剩余空间来保存掉电不丢失的用户数据
通过在程序中编程(IAP),实现程序的自我更新
2、闪存模块组织
如上图所示:①stm32的中容量产品中的程序存储器flash的最小存储单元页(Page),每一页的存储空间大小是1KB。而程序存储器flash占有了128页(f103c8t6只有64页,即64KB),系统存储器占用了2KB的存储空间,选项字节占用了16B。②而下面的寄存器(FPEC)就是上面存储器的管理员,通过对这些寄存器的配置,就能够对上面的存储空间进行擦除,写入等操作。而FLASH以000 ,400,800,c00结尾的地址就是每一页的首地址。
【注】擦除写入等操作的注意事项参考W25Q64的那一节。
3、对程序存储器flash的操作
在操作前必须要对FPEC进行解锁(防止误操作),解锁的一共3个密码,按照将值写入FLASH_KEYR寄存器即可。其中解锁的密码如下:
RDPRT键 = 0x000000A5 //解除对程序存储器的读保护
KEY1 = 0x45670123
KEY2 = 0xCDEF89AB
解锁:
在FLASH_KEYR先写入KEY1,再写入KEY2,解除不能写入FLASH_CR
加锁:
设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR
3.1、闪存全擦除
如上图所示:在执行擦除之前先读取LOCK位,判断FLASH_CR是否锁住,如果锁住了就解锁,没有锁住就执行下面的操作。解锁完成后将FLASH_CR寄存器中的MER位置1(全擦除),然后将START位置1,开始执行。擦除的执行过程中BSY位会置1代表忙状态,置0代表不忙,擦除完成。
3.1、闪存页擦除
3.2、闪存写入过程
在写入之前会检测是否擦除,入门没有擦除则不会执行写入
【注】对闪存的读写操作过程中,CPU会暂停,直到读写接收后CPU又开始执行,这是一个很不好的弊端。
4、对选项字节的操作
USER:配置硬件看门狗和进入停机/待机模式是否产生复位
RDP:写入RDPRT键(0x000000A5)后解除程序存储器的读保护
Data0/1:用户可自定义使用
WRP0/1/2/3:配置写保护,每1个位对应保护4个存储页(中容量),即1个字节对应保护32页,4个字节对应保护128页(正好是中容量的全部容量)。
【注】nxxx:代表写入与之对应的字节的反码,由硬件执行。
4.1、选项字节擦除
【注】解除了FLASH_CR寄存器的锁后还要解除选项字节的锁,即1、在FLASH_KEYR先写入KEY1,再写入KEY2,解除不能写入FLASH_CR。2、FLASH_OPTKEYR先写入KEY1,再写入KEY2,解除选项字节的锁。
4.2、选项字节编程
5、读内部闪存FLASH进行读写
5.1、读取FLASH里面指定地址的数据
①MyFlash.c的代码程序如下:
#include "stm32f10x.h" // Device header
/*
* 读取一个字的数据(32位/4个字节的数据)
*/
uint32_t MyFlash_ReadWord(uint32_t Address)
{
return *((__IO uint32_t *)(Address));
}
/*
* 读取半个字的数据(16位/2个字节的数据)
*/
uint16_t MyFlash_ReadHalfWord(uint32_t Address)
{
return *((__IO uint16_t *)(Address));
}
/*
* 读取一个字节的数据(8位)
*/
uint8_t MyFlash_ReadByte(uint32_t Address)
{
return *((__IO uint8_t *)(Address));
}
②主函数文件的代码程序
/*
内部闪存flash的使用
*/
#include "stm32f10x.h"
#include "OLED.h"
#include "MyFlash.h"
int main(void)
{
OLED_Init();
OLED_Clear();
OLED_ShowHexNum(1,1,MyFlash_ReadWord(0x08000000),8);//以十六进制显示一个字(32位)的数据
OLED_ShowHexNum(2,1,MyFlash_ReadHalfWord(0x08000000),4);
OLED_ShowHexNum(3,1,MyFlash_ReadByte(0x08000000),2);
while(1)
{
}
}
OLED的显示结果:
20000660
0660
60
读取一个字的数据如下:
读取半字的数据如下:
读取一个字节的数据如下:
综上:一个地址单元存储空间的大小是一个字节。得出如下图的结论:传输的地址是0x80000000,若显示一个字(4个字节)则数据以传输的地址为开始,向后揽括4个字节的数据,结果是20 00 06 60。如显示半字(2个字节),结果是06 60。所以显示是以后位地址的数据在前。
5.2、写入FLASH里面指定地址的数据
记住在指定地址写入之前要进行擦除,而擦除的区域一定是存储代码后剩余的区域,如果将代码也擦除了,那么FLASH里面什么都没有了,单片机也不再执行程序。
①MyFlash.c的代码程序如下:
#include "stm32f10x.h" // Device header
/*
* 读取一个字的数据(32位/4个字节的数据)
*/
uint32_t MyFlash_ReadWord(uint32_t Address)
{
return *((__IO uint32_t *)(Address));
}
/*
* 读取半个字的数据(16位/2个字节的数据)
*/
uint16_t MyFlash_ReadHalfWord(uint32_t Address)
{
return *((__IO uint16_t *)(Address));
}
/*
* 读取一个字节的数据(8位)
*/
uint8_t MyFlash_ReadByte(uint32_t Address)
{
return *((__IO uint8_t *)(Address));
}
/*
* Flash全部擦除
*/
void MyFlash_EraseAllPages(void)
{
FLASH_Unlock();//解锁
FLASH_EraseAllPages();//全部擦除
FLASH_Lock();//上锁
}
/*
* Flash页擦除(地址为页的首地址)
*/
void MyFlash_ErasePage(uint32_t PageAddress)
{
FLASH_Unlock();//解锁
FLASH_ErasePage(PageAddress);//擦除指定地址的数据
FLASH_Lock();//上锁
}
/*
* 指定地址写入一个字(4个字节)的数据
*/
void MyFlash_ProgramWord(uint32_t Address,uint32_t Data)
{
FLASH_Unlock();//解锁
FLASH_ProgramWord(Address,Data);//指定地址写入一个字的数据
FLASH_Lock();//上锁
}
/*
* 指定地址写入半个字(2个字节)的数据
*/
void MyFlash_ProgramHalfWord(uint32_t Address,uint16_t Data)
{
FLASH_Unlock();//解锁
FLASH_ProgramHalfWord(Address,Data);//指定地址写入半字的数据
FLASH_Lock();//上锁
}
②主函数文件的代码程序
/*
内部闪存flash的使用
*/
#include "stm32f10x.h"
#include "OLED.h"
#include "MyFlash.h"
int main(void)
{
OLED_Init();
OLED_Clear();
OLED_ShowHexNum(1,1,MyFlash_ReadWord(0x08000000),8);
OLED_ShowHexNum(2,1,MyFlash_ReadHalfWord(0x08000000),4);
OLED_ShowHexNum(3,1,MyFlash_ReadByte(0x08000000),2);
MyFlash_ErasePage(0x0800FC00);//指定最后1页的地址擦除
MyFlash_ProgramWord(0x0800FC00,0x12345678);//在最后一页的起始地址写入数据
MyFlash_ProgramHalfWord(0x0800FC04,0x1234);
while(1)
{
}
}
由于每次操作读写闪存Flash时,都要直接指定地址地址操作,这样十分的不方便。所以我们可以在SRAM(存储临时变量空间)里面开发一个数组变量,空间的大小正好为1KB,即uin16_t Store_Data[512];每次改变数组里面的数据后,就将数据写入到FLASH闪存里面的最后一页的空间(1KB)里面。这样断电了,Flash闪存是掉电不丢失的,就相当于备份了数组的数据。而断电后上电,在将FLASH里面存储的数据读回到数组里面。
①Store.c文件的代码如下:
#include "stm32f10x.h" // Device header
#include "MyFLASH.h"
#define STORE_START_ADDRESS 0x0800FC00 //存储的起始地址
#define STORE_COUNT 512 //存储数据的个数
uint16_t Store_Data[STORE_COUNT]; //定义SRAM数组
/*
* 函 数:参数存储模块初始化
*/
void Store_Init(void)
{
/*判断是不是第一次使用*/
if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) //读取第一个半字的标志位,if成立,则执行第一次使用的初始化
{
MyFLASH_ErasePage(STORE_START_ADDRESS); //擦除指定页
MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5); //在第一个半字写入自己规定的标志位,用于判断是不是第一次使用
for (uint16_t i = 1; i < STORE_COUNT; i ++) //循环STORE_COUNT次,除了第一个标志位
{
MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000); //除了标志位的有效数据全部清0
}
}
/*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/
for (uint16_t i = 0; i < STORE_COUNT; i ++) //循环STORE_COUNT次,包括第一个标志位
{
Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2); //将闪存的数据加载回SRAM数组
}
}
/*
* 将数据备份到Flash闪存里面
*/
void Store_Save(void)
{
MyFLASH_ErasePage(STORE_START_ADDRESS); //擦除指定页
for (uint16_t i = 0; i < STORE_COUNT; i ++) //循环STORE_COUNT次,包括第一个标志位
{
MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]); //将SRAM数组的数据备份保存到闪存
}
}
/**
* 函 数:参数存储模块将所有有效数据清0
* 参 数:无
* 返 回 值:无
*/
void Store_Clear(void)
{
for (uint16_t i = 1; i < STORE_COUNT; i ++) //循环STORE_COUNT次,除了第一个标志位
{
Store_Data[i] = 0x0000; //SRAM数组有效数据清0
}
Store_Save(); //保存数据到闪存
}
【一些小知识点】
1、下图为程序所保存的空间范围,起始空间地址为0x0800 0000,范围大小为0xFC(252B)。当然如果程序大小>252B,也可以将程序的保存空间增大。
2、程序下载时对空间进行擦除,第一个是将全部的空间(64KB)擦除,第二个是将下载程序所占用的空间擦除
3、计算程序所占用FLASH空间的大小以及变量所占用SRAM空间的大小。程序所占用空间的大小 = 前面3个数相加(2784+1788+4 = 4576B)得出,程序所占用的内存为4576字节。变量所占SRAM的空间大小 = 后面2数相加(4+2660 = 2664B)
当然我们也可以直接双击Target1,进行查看即可。