参考教程:[15-1] FLASH闪存_哔哩哔哩_bilibili
1、STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程。
(1)读写FLASH的用途:
①利用程序存储器的剩余空间来保存掉电不丢失的用户数据。
②通过在程序中编程(IAP),实现程序的自我更新。
(2)在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序。
(3)在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序。
2、闪存模块组织:
3、FLASH基本结构:
4、FLASH解锁:
(1)FPEC共有三个键值:RDPRT键 = 0x000000A5;KEY1 = 0x45670123;KEY2 = 0xCDEF89AB
(2)解锁:
①复位后,FPEC被保护,默认不能写入FLASH_CR(控制寄存器)。
②在FLASH_KEYR先写入KEY1,再写入KEY2,即可解锁。
③错误的操作序列会在下次复位前锁死FPEC和FLASH_CR。
(3)加锁:
设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR。
5、使用指针访问存储器:
(1)使用指针读指定地址下的存储器:
uint16_t Data = *((__IO uint16_t *)(0x08000000)); //读地址0x08000000下的2个字节(16位,半字)
(2)使用指针写指定地址下的存储器:
*((__IO uint16_t *)(0x08000000)) = 0x1234; //写2个字节(16位,半字)的数据0x1234到地址0x08000000
其中:
#define __IO volatile //volatile在逻辑上没有什么作用,只是为了防止编译器优化
6、程序存储器编程:
(1)首先读取控制寄存器的LOCK位,如果LOCK=1,那就需要解锁(在KEYR寄存器先写入KEY1再写入KEY2)才能继续操作,如果LOCK=0,可以往下进行编程(写入)操作。
(2)置CR寄存器的PG位为1,开始编程操作,不过STM32的闪存会在写入数据之前会检查指定地址有没有被擦除,如果未被擦除,STM32不执行写入操作。(另外每次写入都必须一次性写半字,也就是16位)
(3)编程过程需要一定时间,在这段时间里程序等待CR寄存器的BSY位被置为0,也就是等待编程操作完成。
(4)“读出并验证所有页的数据”这一步是测试程序的步骤,一般程序不执行这一步。
7、程序存储器页擦除:(被擦除部分全部置为1)
(1)首先读取控制寄存器的LOCK位,如果LOCK=1,那就需要解锁(在KEYR寄存器先写入KEY1再写入KEY2)才能继续操作,如果LOCK=0,可以往下进行擦除操作。
(2)首先置CR寄存器的PER位为1,在AR寄存器中写入要擦除的页,再置STRT位为1,其中置STRT为1是触发条件,触发芯片开始工作,而PER=1则决定芯片需要进行页擦除操作。
(3)擦除过程需要一定时间,在这段时间里程序等待CR寄存器的BSY位被置为0,也就是等待擦除操作完成。
(4)“读出并验证所有页的数据”这一步是测试程序的步骤,一般程序不执行这一步。
8、程序存储器全擦除:
(1)首先读取控制寄存器的LOCK位,如果LOCK=1,那就需要解锁(在KEYR寄存器先写入KEY1再写入KEY2)才能继续操作,如果LOCK=0,可以往下进行擦除操作。
(2)首先置CR寄存器的MER位为1,置STRT位为1,其中置STRT为1是触发条件,触发芯片开始工作,而MER=1则决定芯片需要进行全擦除操作。
(3)擦除过程需要一定时间,在这段时间里程序等待CR寄存器的BSY位被置为0,也就是等待擦除操作完成。
(4)“读出并验证所有页的数据”这一步是测试程序的步骤,一般程序不执行这一步。
9、选项字节:(nXXX代表XXX的反码)
(1)信息块的组织结构:
①RDP:写入RDPRT键(0x000000A5)后解除读保护。
②USER:配置硬件看门狗和进入停机/待机模式是否产生复位。
③Data0/1:用户可自定义使用。
④WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)。
(2)选项字节编程:
①解锁闪存(解锁LOCK位),检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作。
②解锁FLASH_CR的OPTWRE位。
③设置FLASH_CR的OPTPG位为1。
④写入要编程的半字到指定的地址。
⑤等待BSY位变为0。
⑥读出写入的地址并验证数据。
(3)选项字节擦除:
①解锁闪存(解锁LOCK位),检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作。
②解锁FLASH_CR的OPTWRE位。
③设置FLASH_CR的OPTER位为1。
④设置FLASH_CR的STRT位为1。
⑤等待BSY位变为0。
⑥读出被擦除的选择字节并做验证。
10、器件电子签名:电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名。
(1)闪存容量寄存器:基地址为0x1FFF F7E0,大小为16位。
(2)产品唯一身份标识寄存器:基地址为0x1FFF F7E8,大小为96位。
11、读写内部FLASH:
(1)按照下图所示接好电路,并将OLED显示屏的项目文件夹复制一份作为模板使用。
(2)在stm32f10x_flash.h文件中有闪存模块相关的函数。
[1]FLASH_Unlock函数:解锁LOCK位。
[2]FLASH_Lock函数:加锁LOCK位。
[3]FLASH_ErasePage函数:闪存擦除某一页。
[4]FLASH_EraseAllPages函数:闪存擦除全部页。
[5]FLASH_EraseOptionBytes函数:擦除选项字节。
[6]FLASH_ProgramWord函数:往闪存指定地址写一个字(32位)。
[7]FLASH_ProgramHalfWord函数:往闪存指定地址写半个字(16位)。
[8]FLASH_ITConfig函数:开启中断。
[9]FLASH_GetFlagStatus函数:获取状态标志位。
[10]FLASH_ClearFlag函数:清除状态标志位。
[11]FLASH_GetStatus函数:获取当前状态。
[12]FLASH_WaitForLastOperation函数:等待上一次操作完成(等待BSY=0)。
(3)在项目的System组中添加MyFLASH.h文件和MyFLASH.c文件用于封装闪存模块的代码。
①MyFLASH.h文件:
#ifndef __MyFLASH_H
#define __MyFLASH_H
uint32_t MyFLASH_ReadWord(uint32_t Address);
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
uint8_t MyFLASH_ReadByte(uint32_t Address);
void MyFLASH_EraseAllPages(void);
void MyFLASH_ErasePages(uint32_t PageAddress);
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
#endif
②MyFLASH.c文件:
#include "stm32f10x.h" // Device header
uint32_t MyFLASH_ReadWord(uint32_t Address) //读取Address地址下的一个字
{
return *((__IO uint32_t *)(Address));
}
uint16_t MyFLASH_ReadHalfWord(uint32_t Address) //读取Address地址下的半个字
{
return *((__IO uint16_t *)(Address));
}
uint8_t MyFLASH_ReadByte(uint32_t Address) //读取Address地址下的一个字节
{
return *((__IO uint8_t *)(Address));
}
void MyFLASH_EraseAllPages(void) //擦除全部页
{
FLASH_Unlock(); //解锁
FLASH_EraseAllPages(); //擦除全部页
FLASH_Lock(); //加锁
}
void MyFLASH_ErasePages(uint32_t PageAddress) //擦除页起始地址PageAddress下的页
{
FLASH_Unlock(); //解锁
FLASH_ErasePage(PageAddress); //擦除指定页
FLASH_Lock(); //加锁
}
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data) //往Address地址写一个字Data
{
FLASH_Unlock(); //解锁
FLASH_ProgramWord(Address, Data); //往指定地址写一个字
FLASH_Lock(); //加锁
}
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data) //往Address地址写半个字Data
{
FLASH_Unlock(); //解锁
FLASH_ProgramHalfWord(Address, Data); //往指定地址写半个字
FLASH_Lock(); //加锁
}
(4)在项目的System组中添加Store.h文件和Store.c文件用于封装业务层的代码。
①Store.h文件:
#ifndef __Store_H
#define __Store_H
extern uint16_t Store_Data[];
void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);
#endif
②Store.c文件:
#include "stm32f10x.h" // Device header
#include "MyFLASH.h"
#define STORE_START_ADDRESS 0x800FC00 //使用闪存0x800FC00的一页进行调试(只要程序占用空间不大,该页一般空闲)
#define STORE_COUNT 512 //一页有512个半字
uint16_t Store_Data[STORE_COUNT]; //存储在SRAM中的数组,用于暂存FLASH中的数据
void Store_Init(void)
{
if(MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) //闪存的0x800FC00页只初始化一次即可
{
MyFLASH_ErasePages(STORE_START_ADDRESS); //擦除0x800FC00页
MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);
//0x800FC00页的第一个半字用来做标志,如果初始化过一次,数据掉电不丢失,重新上电后该半字仍为0xA5A5
for(uint16_t i = 1; i < STORE_COUNT; i++) //该页剩下的空间全部初始化为0
{
MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);
}
}
for(uint16_t i = 0; i < STORE_COUNT; i++) //将0x800FC00页的数据拷贝到SARM的数组中,便于程序修改
{
Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);
}
}
void Store_Save(void) //将SARM中的数据保存到闪存的0x800FC00页
{
MyFLASH_ErasePages(STORE_START_ADDRESS); //先擦除原来保存的数据
for(uint16_t i = 0; i < STORE_COUNT; i++) //把SRAM中数组的数据逐半字写入FLASH
{
MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);
}
}
void Store_Clear(void) //清空0x800FC00页保存的数据(不是擦除)
{
for(uint16_t i = 1; i < STORE_COUNT; i++) //先把SARM中暂存的数据请空
{
Store_Data[i] = 0;
}
Store_Save(); //将SARM中的数据保存到闪存的0x800FC00页
}
(5)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中进行调试。
#include "stm32f10x.h" // Device headerCmd
#include "OLED.h"
#include "Key.h"
#include "Store.h"
uint8_t KeyNum;
int main()
{
OLED_Init();
Key_Init();
Store_Init();
OLED_ShowString(1,1,"Flag:");
OLED_ShowString(2,1,"Data:");
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1) //按下按键1,修改闪存数据
{
Store_Data[1] += 1;
Store_Data[2] += 2;
Store_Data[3] += 3;
Store_Data[4] += 4;
Store_Save(); //保存数据
}
if(KeyNum == 2) //按下按键2,清空指定页STORE_START_ADDRESS的数据
{
Store_Clear(); //清空数据
}
OLED_ShowHexNum(1,6,Store_Data[0],4); //0xA5A5(标志)
OLED_ShowHexNum(3,1,Store_Data[1],4);
OLED_ShowHexNum(3,6,Store_Data[2],4);
OLED_ShowHexNum(4,1,Store_Data[3],4);
OLED_ShowHexNum(4,6,Store_Data[4],4);
}
}
(6)使用STM32 ST-LINK Utility可以查看闪存中的数据存储情况,在程序下载到开发板中后,点击红框所在的按钮,然后把Address修改为0x0800FC00,就可以查看0x0800FC00这一页的存储内容。(如果想下载新程序,需要点击蓝框所在按钮,否则ST-LINK会被占用)
(7)程序最大占用空间可以设置,当程序所占空间超过设定值时程序将无法编译。
12、读取芯片ID:
(1)按照下图所示接好电路,并将OLED显示屏的项目文件夹复制一份作为模板使用。
(2)产品唯一身份标识寄存器的地址:
(3)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中进行调试。
#include "stm32f10x.h" // Device header
#include "OLED.h"
int main(void)
{
OLED_Init(); //OLED初始化
OLED_ShowString(1, 1, "F_SIZE:"); //显示静态字符串
OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4); //使用指针读取指定地址下的闪存容量寄存器
OLED_ShowString(2, 1, "U_ID:"); //显示静态字符串
OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4); //使用指针读取指定地址下的产品唯一身份标识寄存器
OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
while (1)
{
}
}