笔记来源于江科协议的视频
芯片采用与stm32F103C8T6
简介
•STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程,系统存储器里面存储的是BootLoader,是不允许修改的。
•读写FLASH的用途:
利用程序存储器的剩余空间来保存掉电不丢失的用户数据,一般是找最后一部分存储数据,以防覆盖程序本身代码。
通过在程序中编程(IAP)也叫OTA,实现程序的自我更新
•在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG(仿真器)、SWD协议或系统加载程序(Bootloader)下载程序
•在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序
FLASH
模块组织
这里分为三块,第一个是主存储器(程序存储器),用来存放程序代码的,第二个是信息块,里面分为启动程序代码和用户选择字节,启动程序代码就是刚才的系统存储器,存放的是原厂写入的BootLoader,用于串口下载,用户选择字节也就是选项字节,存放一些独立的参数。第三块是闪存存储器接口寄存器,这一块的存储器实际上并不是闪存,其实本质上是一个普通的外设,存储介质是SRAM,就是上面的闪存管理员,这些寄存器用来控制擦除和编程的过程,前两块是真正的闪存,第三块为闪存的管理员。
对于主存存储器,进行了分页,擦除和写保护都是以页为单位的,写入前必须擦除,擦除以最小单元进行。擦除后数据全为1,数据只能1写0,不能0写1,擦除和写入都需要等待忙,与W25Q64的特性是一致的,但是这里只有基本单位页,没有块和扇区,每页的大小都是1K,一共128页,容量为128K,对于C8T6来说,它只有64K,所以C8T6的页只有一半,0~63,总共64页,共64K,第一页的起始地址为0x0800 0000,之后就是一个字节一个地址,依次线性分配,规律:对低3位进行累加4,以400、800、C00结尾的就是起始地址,系统存储器的起始地址为0x1FFF F000,容量2K,选项字节起始地址为0x1FFF F800,容量16个字节,里面只有几个字节的配置参数。平时讲的32K、64K、128K,说的是主存储器容量,对信息块没有影响,都是固定的2K、16K。 最后就是一个闪存接口寄存器,里面包括键寄存器、SR状态寄存器、CR控制寄存器等待,外设的起始地址:0x4002 2000,每个寄存器4个字节,也就是32位。
逻辑框图
这里以C8T6为例,64K,一共64页,最后一页地址0x0800 FC00,左边是闪存存储器接口,也叫闪存编程和擦除控制器(FPEC),可以对程序存储器进行擦除和编程,也可以对选项字节进行擦除和编程,系统存储器只读,选项字节中很大一部分是配置主程序存储器的读写保护,右边黄色箭头的写入选项字节可以配置程序存储器的读写保护。
细节问题
Flash解锁
Flash操作之前需要解锁,目的是防止误操作,解锁方式是通过键寄存器写入指定的键值来实现,FPEC一共有三个键值,RDPRT解除读保护的密钥,值是0xA5,KEY1键是0x45670123,KEY2键是0xCDEF89AB。
解锁:复位后,FPEC被保护,不能写入FLASH_CR,复位后默认Flash是锁着的,在FLASH_KEYR先写入KEY1,再写入KEY2,解锁。错误的操作序列会在下次复位前锁死FPEC和FLASH_CR,一旦没有先写入KEY1再写入KEY2,整个模块就会完全锁死,除非复位。
•加锁:设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR
指针访问寄存器
*((IO uint16_t *)(0x08000000)):这个变量为地址,这里强制类型转换为uint16_t的指针类型,指针指向这个地址,最后一个✳号指针取内容,把这个指针指向的存储器取出来,值就是指定存储器的值,对于闪存是不需要解锁的,不对其进行更改。
*((IO uint16_t *)(0x08000000)) = 0x1234,指定地址写,先给定地址,再强转为指针,最后取内容,然后对其赋值,这里写数据时,要先对Flash进行解锁,还要套入写入的流程。如果这里写的是SRAM地址,如:0x2000 0000那么就不需要解锁,可以直接写入,因为SRAM在程序运行时是可读可写的,
这里——IO是一个宏定义,对应C语言的关键字volatile,意思是易变的数据,这里加上是一个保障措施,防止编译器优化,keil编译器默认优化是最低优化等级,如果要提高编译优先等级,这里就有问题了。编译器优化是为了去除无用的繁杂代码,降低代码空间,提升运行效率。这里就是告诉编译器无论怎么优化,这里不能动。
编译器还会利用缓存来加速代码,如:要频繁读写内存的某个变量,最常见的优化方式:先把变量转移到高速缓存里面,在stm32内核中,有一个类似的缓存的工作组寄存器,这类寄存器访问速度最快,把变量放缓存里,需要读写时,直接访问缓存就可以了,用完之后,再写回内存。但是程序有多线程,如:中断函数,在中断函数里改变了原始变量,但是缓存并不知道中断更改了原始变量,下次程序还看缓存的变量,就会造成数据更改不同步的问题,这时,可以在读取变量定义的前面加上volatile,告诉编译器这个变量是易变的,要直接从内存中找,不用缓存优化。
如果开启了编译器优化,在无意义加减变量,多线程更改变量,读写与硬件相关的存储器时,都需要加上volatile,防止编译器优化。
存储器操作
全擦除
第一步:读取LOCK位,看下芯片锁了没,如果LOCK=1,那就是锁住了,就执行解锁过程,解锁过程在KEYR寄存器中,先写KEY1,再写KEY2。没锁就不用解锁了,之后置控制寄存器CR的MER位为1,然后再置STRT位为1,STRT的1的触发条件,触发之后,芯片开始工作,芯片检测到MER位=1,芯片就全擦除。擦除过程开始后,程序要执行等待,判断状态寄存器SR中的BSY位,BSY芯片是否处于忙状态,等于1表示忙,等于0不忙,等于1的时候会跳回去继续判断,直到BSY等于0,跳出循环。
页擦除
这里第一步仍是解锁的流程,第二步,置控制寄存器的PER位为1,在AR地址寄存器中选择要擦除的页,最后置控制寄存器CR的STRT的位为1,最后等待BSY位,
闪存写入
STM32的闪存会在写入之前检查有没有擦除,如果没有擦除就写入,STM32则不执行写入操作,除非写入的数据全是0。 写入第一步:解锁, 第二步:需要置控制寄存器CR中的PG位为1,表示要写入数据,第三步:在指定的地址写入半字,写入操作只能以半字的形式写入, 第四步:等待忙
选项字节
这里n的意思 如:写入RDP数据时,要同时写入nRDP数据的反码,这样写入数据才是有效的,如果这两个寄存器不是互为反码,则写入的数据无效。这里硬件会自动写入。
•RDP:写入RDPRT键(0x000000A5)后解除读保护
•USER:配置硬件看门狗和进入停机/待机模式是否产生复位
•Data0/1:用户可自定义使用
•WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)
选项字节编程
•检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作
•解锁FLASH_CR的OPTWRE位
•设置FLASH_CR的OPTPG位为1
•写入要编程的半字到指定的地址
•等待BSY位变为0
•读出写入的地址并验证数据
选项字节擦除
•检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作(相当于事前等待)
•解锁FLASH_CR的OPTWRE位,相当于选项字节的解锁,也是先写入KEY1,再写入KEY2
•设置FLASH_CR的OPTER位为1
•设置FLASH_CR的STRT位为1
•等待BSY位变为0
•读出被擦除的选择字节并做验证
器件电子签名
•电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名
•
•闪存容量寄存器:
基地址:0x1FFF F7E0
大小:16位
•产品唯一身份标识寄存器:
基地址: 0x1FFF F7E8
大小:96位
代码
读写Flash
MyFlash.c
#include "stm32f10x.h" // Device header
//读一个32位的字
uint32_t MyFlash_ReadWord(uint32_t Addr)
{
return *((__IO uint32_t*)(Addr)); //使用指针访问指定地址下的数据并返回
}
//读一个32位的字
uint16_t MyFlash_ReadHalfWord(uint32_t Addr)
{
return *((__IO uint16_t*)(Addr)); //使用指针访问指定地址下的数据并返回
}
//读一个32位的字
uint8_t MyFlash_ReadByte(uint32_t Addr)
{
return *((__IO uint8_t*)(Addr)); //使用指针访问指定地址下的数据并返回
}
//全擦除
void MyFlash_EraserAllPage(void)
{
FLASH_Unlock(); //开锁
FLASH_EraseAllPages(); //全擦除
FLASH_Lock(); //关锁
}
void MyFlash_EraserPage(uint32_t Addr)
{
FLASH_Unlock(); //开锁
FLASH_ErasePage(Addr); //写入页擦除
FLASH_Lock(); //关锁
}
//写入字
void MyFlash_ProgramWord(uint32_t Addr, uint32_t data)
{
FLASH_Unlock(); //开锁
FLASH_ProgramWord(Addr,data); //写入字
FLASH_Lock(); //关锁
}
//写入半字
void MyFlash_ProgramHalfWord(uint32_t Addr, uint32_t data)
{
FLASH_Unlock(); //开锁
FLASH_ProgramHalfWord(Addr,data); //写入半字
FLASH_Lock(); //关锁
}
Store.c文章来源:https://www.toymoban.com/news/detail-800136.html
#include "stm32f10x.h" // Device header
#include "MyFlash.h"
#define STORE_START_ADDRESS 0x0800FC00
#define STORE_COUNT 512
uint16_t Store_Data[STORE_COUNT];
void Store_Init(void)
{
if(MyFlash_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) //如果是第一次读写
{
MyFlash_EraserPage(STORE_START_ADDRESS); //擦除
MyFlash_ProgramHalfWord(STORE_START_ADDRESS,0xA5A5); //写上自己的标志位
for(uint16_t i=1;i<STORE_COUNT;i++)
{
MyFlash_ProgramHalfWord(STORE_START_ADDRESS+i*2,0x0000); //除了标志位全部清0
}
}
for(uint16_t i=0;i<STORE_COUNT;i++)
{
Store_Data[i] = MyFlash_ReadHalfWord(STORE_START_ADDRESS+i*2);
}
}
//保存数据 存到闪存里面
void Store_Save(void)
{
MyFlash_EraserPage(STORE_START_ADDRESS); //擦除
for(uint16_t i=0;i<STORE_COUNT;i++)
{
MyFlash_ProgramHalfWord(STORE_START_ADDRESS+i*2,Store_Data[i]); //除了标志位全部清0
}
}
//将模块数据清零
void Store_Clear(void)
{
for(uint16_t i=1;i<STORE_COUNT;i++)
{
Store_Data[i] = 0x0000;
}
Store_Save();
}
main.c文章来源地址https://www.toymoban.com/news/detail-800136.html
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "Store.h"
uint8_t Key;
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init();
Store_Init();
OLED_ShowString(1,1,"Flag:");
OLED_ShowString(2,1,"Data:");
while (1)
{
Key = Key_GetNum();
if(Key == 1)
{
Store_Data[1]++;
Store_Data[2]++;
Store_Data[3]++;
Store_Data[4]++;
Store_Save();
}
if(Key == 2)
{
Store_Clear();
}
OLED_ShowHexNum(1,6,Store_Data[0],4);
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);
}
}
读ID
#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)
{
}
}
到了这里,关于STM32笔记 Flash的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!