最近一个物联网智能门禁项目,其中开锁的密码需要永久化存储,原来的方案是外挂一个EEPROM类型芯片,觉得这样操作有些繁琐,于是想把数据存放在STM32内部,想法成立,实践开始!
第一步:查找资料
本次使用的是STM32F407,从ST官网找到了相应的数据手册,该芯片的Flash内存是这样分布的
Flash结构:
- 主存储器块,共12个扇区,4个16KB,1个64KB,7个128KB的扇区,地址分布如下
- 系统存储器:
- 512OPT存储器
- 选项字节
本次会把数据存储在主存储器中的最后一个扇区中,选择这块扇区的原因是F407的程序代码区域大小有1MB,一般来说,写的代码是不会到达最后一个扇区. 主要对该区域进行读写,通过闪存存储器接口(外设),对程序存储器和选项字节进行擦除和编程.
补充知识:使用指针访问存储器
//使用指针读指定地址下的存储器
//0x080e0000 是扇区11的起始地址
uint32_t Data = *((__IO uint32_t* )(0x080e0000));
//对指定地址的存储器进行赋值
*((__IO uint32_t* )(0x080e0000)) = 0x12345678;
//其中,__IO 读取flash中的变量,一般要加volatile修饰
// volatile 表示易变的,用该关键字修饰的变量,CPU每次读取该变量的值时,必须到该变量存储的地址处读值,不能进行假设
#define __IO volatile
第二步: 代码编写
- 擦除指定存储器的内容
- 写指定存储器的内容
- 读指定存储器的内容
在实现代码之前首先了解一些Flash外设寄存器
stm32f407的flash共有6个寄存器,其中我们要了解的有2个
CR(控制寄存器),SR(状态寄存器)
位0:PG,开始编程,当开始写数据时需要将该位置1
位1:SER Sector Erase 扇区擦除,当要擦除扇区时,将该位置1,擦除结束后置0
位2:MER Mass Erase 擦除所有的扇区,谨慎使用,因为主储存器存放代码
位7:3 SNB sector number 扇区编号,可以指定要擦除的扇区号 0~11
位9:8 PSIZE 编程宽度,表示写入flash中的字节数,根据电压的不同请选择对应的位,否者写数据时无法写入
位16:STRT start 启动擦除操作,在BSY位置零后,自动置零,无需手动操作
…
位31: LOCK 表示当前Flash的状态为1时,表示已锁定,必须用解锁序列才能够对Flash进行读写操作
//解锁Flash主存储器需要连续对KEYR寄存器连续写入该序列,两者不可拆分,不可颠倒
void mFlash_Unlock(void)
{
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
//上锁
void mFlash_Lock(void)
{
FLASH->CR = 0x01;
}
//擦除Flash的一个扇区
void mFlash_EraseSector(uint8_t sector)
{
while((FLASH->SR & BIT(16)) == BIT(16)){}; //检测FLASH_SR 的BSY位
mFlash_UnLock();
FLASH->CR |= BIT(1); //块擦除
FLASH->CR |= (sector<<3); //指定擦除的扇区
FLASH->CR |= BIT(9); //这个是x32,看手册根据电压的不同,并行位也会不同
FLASH->CR |= (0x01 << 16); // STRT位置1
while((FLASH->SR & BIT(16)) == BIT(16)){}; //检测FLASH_SR 的BSY位
FLASH->CR &= (~BIT(1)); //对指定位进行擦除
mFlash_Lock();
}
//其中BIT
#define BIT(x) (0x01<<x)
//往Flash的指定扇区写数据
void mFlash_WriteData(__IO uint32_t* address,uint32_t data)
{
mFlash_UnLock();
FLASH->CR |= BIT(0); // FLASH_CR的第零位置1 PG = 1
*(address) = data; //由此可见,写入失败,为什么呢???? FLASH->CR BIT(9) 原因在这里
while((FLASH->SR & BIT(16)) == BIT(16)){}; //检测FLASH_SR 的BSY位
FLASH->CR &= (~BIT(0));
mFlash_Lock();
}
/*
* sector: 哪个区,F407,共有12个区,要往扇区0写数据,那么填写0x00
* address 该扇区的起始地址
* data: 要写入数据的数组首地址
* size: 写入多少数据
* @note: 在写入扇区之前,会把该扇区的内容全部清空,因此,如果想单独写入几字节数据,请注意数据的备份
**/
void mFlash_Write(uint8_t sector, __IO uint32_t* address,uint32_t *data,uint8_t size)
{
mFlash_Erase(sector);
//首地址写入是否写过数据标志 0XA5A5, 如果读取到该数据,那么表示该扇区存储过数据
address++;
for(int i = 1; i < size+1; i++)
{
mFlash_WriteData(address++, data[i-1]);
}
}
//读flash指定地址的数据
uint32_t mFlash_ReadWord(__IO uint32_t* address)
{
return *(address);
}
在编程中遇到的问题
- 下载程序时会报错,提示cannot …,手按住复位键,点击下载,松开复位键即可下载
- 程序写入失败,请检查自己的单片机电压,根据电压的不同,写入的位数也是不同的,一般3.3v电压,是要求写入32位数据
- 附上扇区首地址
#define Sector0 ((__IO uint32_t *)0x08000000)
#define Sector1 ((__IO uint32_t *)0x08004000)
#define Sector2 ((__IO uint32_t *)0x08008000)
#define Sector3 ((__IO uint32_t *)0x0800C000)
#define Sector4 ((__IO uint32_t *)0x08010000)
#define Sector5 ((__IO uint32_t *)0x08020000)
#define Sector6 ((__IO uint32_t *)0x08040000)
#define Sector7 ((__IO uint32_t *)0x08060000)
#define Sector8 ((__IO uint32_t *)0x08080000)
#define Sector9 ((__IO uint32_t *)0x080a0000)
#define Sector10 ((__IO uint32_t *)0x080c0000)
#define Sector11 ((__IO uint32_t *)0x080e0000) //将数据都存放在这里