学习STM32微控制器中FLASH闪存操作
摘要:FLASH闪存的存储结构;如何向FLASH中读取数据、写入数据、擦除数据;
FLASH闪存是一种非易失性存储,掉电后数据不丢失。
FLASH闪存包括程序存储器、系统存储器、选项字节三部分,其中程序存储器为存储程序的地方,系统存储器中的参数不可修改。
闪存读写会使程序暂停。
分区 | 作用 | 起始地址 |
---|---|---|
程序存储器 | 存储C语言编译后的程序代码 | 0x 0800 0000 |
系统存储器 | 存储BootLoader(启动参数) | 0x 1FFF F000 |
选项字节 | 存储一些独立于程序代码的配置参数 | 0x 1FFF F800 |
1.程序存储器
程序存储器,存储程序的部分,采用分页存储的方式。
(1)可以在程序存储器未占用的部分,存储一些重要参数。
(2)IAP:通过程序修改FLASH中的程序文件,如果是远程的话就是OTA
1. 程序存储器全擦除
将程序存储器中的全部数据清除
2.程序存储器页擦除
指定特定页进行页擦除
3.程序存储器写入
在特定的地址写入数据,注意只能以半字(16bits)和全字(32bits)的格式写入
2.闪存存储器接口(FPEC)
通过闪存存储器接口可以对程序存储器和选项字节进行擦除和编程
1.加锁和解锁
FLASH自身加锁对闪存数据进行保护,在对闪存内的数据修改前需要对FLASH解锁,防止误操作。
键值:相当于用于解锁的密码
RDPRT键=0xA5:解除读保护
KEY1=0x45670123:解除写入保护的一级密码
KEY2=0xCDEF89AB:解除写入保护的二级密码
解锁
在FLASH_KEYR(钥匙寄存器)先写入KEY1,再写入KEY2后才能解锁。
(1)复位后,FPEC被保护,不能写入FLASH_CR(控制寄存器)
(2)错误的操作序列会在下次复位前锁死FPEC,只能复位才能再次尝试解锁。
加锁
设置FLASH_CR中的LOCK位锁住FPEC
2.读写FLASH数据
使用指针访问存储器
#define __IO volatile
//使用指针读对应地址的数据
uint16_t Data=*((__IO uint16_t *)(0x8000000));
//使用指针写对应地址的数据
*((__IO uint16_t *)(0x8000000))=0x1234;
volatile的作用
防止数据被优化以及变量赋值冲突问题
3.选项字节
[31:24]和[15:8]分别为[23:16]和[7:0]的反码,为系统自动生成,不需要用户配置
RDP:写入RDPRT键后解除读保护
USER:配置硬件看门狗和进入停机/待机模式是否产生复位
Data0/1:用户可自定义使用
WRP0/1/2/3:配置写入保护,每一个位对应保护4个存储页(中容量) 64K,每个寄存器对应4位
1.选项字节擦除
2.选项字节写入
4.器件电子签名
每一个芯片都有其独特的电子签名储存在FLASH空间内。
电子签名地址
闪存容量寄存器:基地址(0x 1FFF F7E0),16位
产品唯一身份标识寄存器:基地址(0x 1FFF F7E8) 96位
5.开发工具
1.函数库
1.标准库
//FLASH解锁
void FLASH_Unlock(void);
//FLASH上锁
void FLASH_Lock(void);
//页擦除,输入:页起始地址,返回:擦除状态
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
//全擦除
FLASH_Status FLASH_EraseAllPages(void);
//在指定地址,写入一个字的数据(4byte)
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
//在指定地址,写入半字的数据(2byte)
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
2.FLASH库(进一步封装)
//读取指定地址的数据,数据大小为4byte
uint32_t MyFLASH_ReadWord(uint32_t Address);
//读取指定地址的数据,数据大小为2byte
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
//读取指定地址的数据,数据大小为1byte
uint8_t MyFLASH_ReadByte(uint32_t Address);
//擦除所有数据
void MyFLASH_EraseAllPages(void);
//擦除指定页,需要输入该页的基地址
void MyFLASH_ErasePage(uint32_t PageAddress);
//指定地址写入4bite
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
//指定地址写入2bite
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
3.Store库
由于实际上使用标准库向FLASH中读写数据还是比较不方便的。
因此我们采用的思路是先将FLASH中的数据读取出来,在闪存外对数据进行修改,然后再写入FLASH中。
//SRAM数组
uint16_t Store_Data[STORE_COUNT];
//初始化,如未写入数据,则将FLASH中第一页清空(除标志位),若已写入数据,则将数据读取到SRAM数组
void Store_Init(void);
//擦除第一页,并将SRAM数组中的数据写入到FLASH
void Store_Save(void);
//SRAM数组清零后写入到FLASH
void Store_Clear(void);
2.STLINK Utility
用STLINK Utility来读取和修改FLASH闪存中的数据
Connect to the target:连接到设备
Disconnect from the target:断开与设备的连接,软件与设备连接时会与下载程序冲突
Address:软件显示的基地址
Size:数据显示长度
Data Width:数据显示格式
6.实验
0.硬件接线
1.读写FLASH内部数据
1.主函数
在OLED显示SRAM数组的前4个数据,按下按键1,数据自加,并写入到FLASH中,按下按键2,清除数据。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //按键初始化
Store_Init(); //参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失
/*显示静态字符串*/
OLED_ShowString(1, 1, "Flag:");
OLED_ShowString(2, 1, "Data:");
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
Store_Data[1] ++; //变换测试数据
Store_Data[2] += 2;
Store_Data[3] += 3;
Store_Data[4] += 4;
Store_Save(); //将Store_Data的数据备份保存到闪存,实现掉电不丢失
}
if (KeyNum == 2) //按键2按下
{
Store_Clear(); //将Store_Data的数据全部清0
}
OLED_ShowHexNum(1, 6, Store_Data[0], 4); //显示Store_Data的第一位标志位
OLED_ShowHexNum(3, 1, Store_Data[1], 4); //显示Store_Data的有效存储数据
OLED_ShowHexNum(3, 6, Store_Data[2], 4);
OLED_ShowHexNum(4, 1, Store_Data[3], 4);
OLED_ShowHexNum(4, 6, Store_Data[4], 4);
}
}
2.实验现象
2.读取芯片ID
1.主函数
去对应的地址,读取数据
#include "stm32f10x.h" // Device header
#include "Delay.h"
#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)
{
}
}
2.实验现象
作者:疯狂生长的陈大花