目录
一、FLASH简介
STM32F1系列的FLASH由程序存储器(存储代码)、系统存储器(原厂写入不可修改的Bootloader)、选项字节(独立配置参数)三部分组成,其中程序存储器和选项字节可通过闪存存储器接口来操作(实际就是一个外设,相当于“管理员”,可进行擦除和编程)
关于bootloader,也叫自举程序,用途是程序自我更新,串口下载。在程序更新过程中,bootloader接收usart1数据,刷新到程序存储器,这时主程序还处于瘫痪状态。刷新完成,再启动主程序,就可以执行新程序,相当于“刷机”
1.1读写FLASH的用途:
- 利用程序存储器中的剩余空间来保存需要掉电不丢失的用户数据
- 程序中编程(IAP),实现程序的自我更新
IAP:In Application Programming,程序中编程,可支持任一种通信接口下载更新程序
ICP:In Circuit Programming,在线/电路中编程,用于更新程序存储器中的全部内容,它通过JTAG、SWD协议(ST-Link)或Bootloader(串口)进行下载程序
1.2闪存模块组织
以中容量为例:
1.3FLASH基本结构
二、FLASH相关操作
2.1解锁
对于FLASH的解锁(读不用解锁!)相当于W25Q64中的写使能,实际是对FLASH_KEYR寄存器进行操作
- FPEC共有3个键值:PDPRT键、KEY1、KEY2(都可自定义)
- 解锁操作通过先写KEY1、再写KEY2
- 上锁通过FLASH_CR的LOCK位锁住FPEC和FLASH_CR
- 复位后FPEC被保护不可写,误操作顺序会锁死直到复位
2.2指针访问寄存器
因为对于闪存的编程是以页为单位,为避免覆盖和有序使用,往往根据地址来操作,与地址相关当然用指针比较高效
- 读寄存器:uint16_t Data = *((__IO uint16_t *)(0x8000 0400));
- 写寄存器: *((__IO uint16_t *)(0x8000 0400)) = 0x1234;
其中:#define __IO volatile 表示易变数据,防止编译器优化。keil编译器默认是最低优化等级,对于编译器的优化可以用来去除无用的代码、降低代码空间,提高执行效率。但优化之后,可能会在某些地方弄巧成拙,比如利用变量计数空循环实现延时就可能被优化掉。另外,编译器还会利用缓存来加速代码,比如要频繁读写内存的某个数据,最常见的优化方式就是先把数据转移到高速缓存,在这里面进行读写速度会更高效,用完写回内存即可,但如果在读写过程中,同时程序有多个线程,比如中断函数中改变了原始数据,但高速缓存并不知情,就会导致更改数据不同步,这时候加上volatile就能避免这类问题
2.3擦除和编程
1.全擦除流程
判断是否要解锁->FLASH_CR中的MER=1(表示全擦)、START=1(启动擦除)->循环擦除同时判断忙状态
void MYFLASH_EraseAllPages(void)//全擦除
{
FLASH_Unlock();
FLASH_EraseAllPages();
FLASH_Lock();
}
2.页擦除流程
判断是否要解锁->FLASH_CR中的PR=1(表示页擦)、写页起始地址选择要擦的页、START=1(启动擦除)->循环擦除同时判断忙状态
void MYFLASH_ErasePage(uint32_t Page_Address)//页擦除
{
FLASH_Unlock();
FLASH_ErasePage(Page_Address);
FLASH_Lock();
}
3.编程/写入
写前会自动检查有没有擦除(尤其是写1,因为FLASH里的数据只能由1变0而不能由0变1)
判断是否要解锁->FLASH_CR中的PG=1(表示编程)->在指定地址写入半字(启动编程)
void MYFLASH_WriteWord(uint32_t Address,uint32_t Data) //以字大小写数据
{
FLASH_Unlock();
FLASH_ProgramWord(Address,Data);
FLASH_Lock();
}
void MYFLASH_WriteHalfWord(uint32_t Address,uint16_t Data) //以半字大小写数据
{
FLASH_Unlock();
FLASH_ProgramHalfWord(Address,Data);
FLASH_Lock();
}
//写入字节较为麻烦,最好用缓存RAM来复制修改再写回flash
注意:任何对于闪存的读写操作都会导致CPU暂停,其他代码会得不到执行,对中断的影响尤为严重!
三、选项字节
3.1选项字节擦除
3.2选项字节编程
3.3 软件设置
ST-Link Utility中对于选项字节读保护、写保护可以便捷设置
四、器件电子签名
其实就是STM32的ID号,电子签名存放在闪存的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,可通过指针读指定地址下的存储器获取电子签名
- 闪存容量寄存器, 基地址:0x1FFF F7E0,大小16位
- 产品唯一身份标识寄存器,基地址:0x1FFF F7E8,大小96位
五、接口实现
5.1 底层
#include "stm32f10x.h"
#include "myflash.h"
//flash不需要初始化,直接实现接口即可
uint32_t MYFLASH_ReadWord(uint32_t address) //读取字
{
return *((__IO uint32_t*)(address));
}
uint16_t MYFLASH_ReadHalfWord(uint32_t address) //读取半字
{
return *((__IO uint16_t*)(address));
}
uint8_t MYFLASH_ReadByte(uint32_t address) //读取字节
{
return *((__IO uint8_t*)(address));
}
void MYFLASH_EraseAllPages(void)//全擦除
{
FLASH_Unlock();
FLASH_EraseAllPages();
FLASH_Lock();
}
void MYFLASH_ErasePage(uint32_t Page_Address)//页擦除
{
FLASH_Unlock();
FLASH_ErasePage(Page_Address);
FLASH_Lock();
}
void MYFLASH_WriteWord(uint32_t Address,uint32_t Data) //以字大小写数据
{
FLASH_Unlock();
FLASH_ProgramWord(Address,Data);
FLASH_Lock();
}
void MYFLASH_WriteHalfWord(uint32_t Address,uint16_t Data) //以半字大小写数据
{
FLASH_Unlock();
FLASH_ProgramHalfWord(Address,Data);
FLASH_Lock();
}
//写入字节较为麻烦,最好用SRAM来复制修改再写回flash
5.2 应用层
#include "stm32f10x.h"
#include "store.h"
#include "myflash.h"
#define Store_StartPage (0x0800FC00)
#define Store_Num (512)
//Store_Init读取闪存数据进SRAM数组,后续读取、修改都基于SRAM
//需要掉电不丢失,调用Store_Save把数组所有内容写进闪存
//需要清空闪存,调用Store_Clear清除除标记外的所有数据
uint16_t Store_Data[Store_Num]; //对应该页数据,变量类型对应半字
void Store_Init(void) //确认闪存标记+复制数据到SRAM
{
if(MYFLASH_ReadHalfWord(Store_StartPage) != 0x0327)//第一个半字当作标志位
{
MYFLASH_ErasePage(Store_StartPage);
MYFLASH_WriteHalfWord(Store_StartPage,0x0327); //第一次使用,标志位进行标记
for(uint16_t i = 1; i < Store_Num; i++)
{
MYFLASH_WriteHalfWord(Store_StartPage+i*2,0x0000); //清零该页所有剩余数据
}
}
for(uint16_t i = 0; i < Store_Num; i++) //该页整体备份到SRAM数组里
{
Store_Data[i] = MYFLASH_ReadHalfWord(Store_StartPage+i*2);
}
}
void Store_Save(void)//把SRAM数组内容备份到闪存里
{
MYFLASH_ErasePage(Store_StartPage);
for(uint16_t i = 0; i < Store_Num; i++)
{
MYFLASH_WriteHalfWord(Store_StartPage+i*2,Store_Data[i]);
}
}
void Store_Clear(void)//清零数据,同时更新到闪存
{
for(uint16_t i = 1; i < 512; i++) //不清标记
{
Store_Data[i] = 0x0000;
}
Store_Save();
}
5.3 测试程序
#include "stm32f10x.h" // Device header
#include "swi2c.h"
#include "systick.h"
#include "store.h"
#include "key.h"
int main(void)
{
uint8_t KeyNum;
Store_Init();
Key_Init();
SW_OLED_Init();
OLED_CLEAR();
OLED_ShowString(0,0,"Flag:",2);
OLED_ShowString(0,2,"Data:",2);
while (1)
{
KeyNum = Scan_KeyNum();
if(KeyNum == 1)
{
Store_Data[1] ++;
Store_Data[2] += 2;
Store_Data[3] += 3;
Store_Data[4] += 4;
Store_Save();
}
if(KeyNum == 2)
{
Store_Clear();
}
OLED_ShowHexNum(40,0,Store_Data[0],4,2);
OLED_ShowHexNum(0,4,Store_Data[1],4,2);
OLED_ShowHexNum(40,4,Store_Data[2],4,2);
OLED_ShowHexNum(0,6,Store_Data[3],4,2);
OLED_ShowHexNum(40,6,Store_Data[4],4,2);
}
}