Flash闪存(掉电不丢失的存储器)
- STM32F1系列的FLASH包含程序存储器(起始地址:0x0800 0000)、系统存储器(起始地址0x1FFFF000)和选项字节(起始地址:0x1FFFF800)三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程
- 读写FLASH的用途:
- 1. 利用程序存储器的剩余空间来保存掉电不丢失的用户数据
- 2. 通过在程序中编程(IAP),实现程序的自我更新
- 在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序
- 在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序
- 闪存模块组织:
- 闪存的部分:
- 主存储器:程序存储器,用来存放数据代码的,这个C8T6芯片自带的flash(只有64K(页),中容量的一半)只有一个单位就是页(1KB),不像W25Q64那样有块、扇区、页那么复杂并且分页是为了更好的管理闪存,同时擦除和写保护都是以页为单位的。
注:地址只要以000、400、800、C00结尾的都是页的起始地址
- 信息块:
- 启动程序代码(程序存储器):存放原厂写入的bootloader,用于串口下载,
- 用户选择字节(选项字节):存放一些独立的参数
注:平时说的,芯片闪存容量是64K,128K是主存储器容量,信息块里的东西虽然也是闪存,但是并不统计在这个容量里面。
- 闪存的管理员部分:
- 闪存存储器接口寄存器:这些存储器就是用来控制擦除和编程这个过程的,实际并不算flash,因为他们地址首位都是4开头,基本和GPIO、定时器等等是一个性质的东西。并且这些存储器它们的存储介质,也都是SRAM。
注:闪存存储器接口寄存器里包括了KEYR键寄存器,SR,CR等等寄存器,外设起始地址为0x4002 2000,每个寄存器都是4个字节(32位)
- Flash的基本结构图:
整个闪存分为程序存储器、系统存储器、选项字节三部分。
闪存存储器接口(也称为闪存编程和擦除控制器(FPEC)):
它可以对程序存储器进行编程和擦除,同时也可以对选项字节进行擦除和编程
选项字节:
选项字节里有很大一部分配置位,主要是用来配置主程序存储器的读写保护
- Flash的解锁操作(配置FPEC(闪存编程和擦除控制器))
- FPEC共有三个键值:
- RDPRT键 = 0x000000A5
- KEY1 = 0x45670123
- KEY2 = 0xCDEF89A
- 解锁操作:
- 1. 复位后,FPEC被保护,不能写入FLASH_CR
(就是复位后flash默认是锁着的)
2. 在FLASH_KEYR先写入KEY1,再写入KEY2,解锁
3. 错误的操作序列会在下次复位前锁死FPEC和FLASH_CR
(就是发现有程序在尝试撬锁,没有按照先key1再key2的操作开锁,整个模块就会锁死,除非复位)
- 加锁操作:
- 设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR
- 如何使用指针访问存储器:
因为stm32的内部存储器都是挂在总线上的,所以想读写某个寄存器就很简单直接使用c中的指针访问即可。
volatile是c语言中的关键字,直译是易变的数据
volatile的作用是:
添加一个安全保障措施,防止编译器优化;
编译器优化等级:用途是可以去除无用的繁杂代码,降低代码空间,提升运行效率。
Keil编译器默认情况下是最低优化等级,如果要提高编译器优化等级编译器在有些地方就会弄巧成。总之,如果想运用一些方法完成或实现某种功能就在变量定义前面加上volatile告诉编译器不要将其优化掉了,原封不动的去执行即可。
- 其中:
#define __IO volatile
- 使用指针读指定地址下的存储器(不用解锁):
- uint16_t Data = *((__IO uint16_t *)(0x08000000));
将0x08000000强制类型转换成uint16_t * 类型且是防止编译器优化的类型,然后用*取出指针内容。
- 使用指针写指定地址下的存储器:
- *((__IO uint16_t *)(0x08000000)) = 0x1234;
先给定地址,再强转为指针,最*号取内容
同时,闪存在程序运行时,是只读的,不能轻易更改,而现在需要对闪存进行修改,所以需要权限较高,需要提前解锁,并且还要套一个下面的程序存储器编程过程的流程。
- 程序存储器编程图(写入)
Stm32的闪存会在写入之前检查指定地址有没有擦除,如果没有擦除就写入,stm32就不执行写入操作。除非写入的都是0,这个除外因为不擦除写入会导致写入错误。
流程:
- 读取lock位,查看锁没锁,锁住执行解锁操作
- 置CR寄存器的PG(Programming)为1,表示即将写入数据。
- 在指定的地址写入半字(16位)(同时也是触发),就是用上面的使用指针在指定地址写入数据
注:写入操作,只能以半字(16位的形式)的形式写入
- 因为擦除是要时间的,所以程序要执行等待,也就是判断BSY(busy)位是否为1,busy位表示芯片是否处于忙状态,为1表示芯片在忙,所以这里要一直等到BSY位0,跳出循环
- 最后,读出并验证所有页的流程,这个是验证程序干的,我们不需要执行这个操作。
- 程序存储器页擦除
Stm32的闪存也是写入前必须擦除,擦除后,所有数据位变为1
擦除的最小位就是1页(1K(1024字节))
流程:
- 读取lock位,查看锁没锁,锁住执行解锁操作
2. 置CR寄存器里的PER(Page Erase)位为1,然后在AR地址寄存器中选择要擦除的页,最后置CR寄存器中STRT(Start)位(触发位)为1,当触发位为1时,芯片开始运行
然后芯片看到PER位置1,就会执行页擦除流程,同时查看Address Register中的数据指定地址。
- 因为擦除是要时间的,所以程序要执行等待,也就是判断BSY(busy)位是否为1,busy位表示芯片是否处于忙状态,为1表示芯片在忙,所以这里要一直等到BSY位0,跳出循环
- 最后,读出并验证所有页的流程,这个是验证程序干的,我们不需要执行这个操作
- 程序存储器全擦除:
作用:将所有页都擦除掉
流程:
- 读取lock位,查看锁没锁,锁住执行解锁操作
- 置CR寄存器里的MER(Mass Erase)位为1,然后再置STRT(Start)位(触发位)为1,当触发位为1时,芯片开始运行
然后芯片看到MER位置1,就会执行全擦除流程
- 因为擦除是要时间的,所以程序要执行等待,也就是判断BSY(busy)位是否为1,busy位表示芯片是否处于忙状态,为1表示芯片在忙,所以这里要一直等到BSY位0,跳出循环
- 最后,读出并验证所有页的流程,这个是验证程序干的,我们不需要执行这个操作。
注:这三个操作库函数已经封装好了,直接调用整体函数即可。
- 选项字节:
这个图中是选项字节(16个字节)的展开
同时有些位前有个n,意思是例如RDP和nRDP,就是在写入RDP的数据时,同时也要在nRDP中写入与之对应的反码,其他的也是一样,写入的同时要在带n的对应的寄存器中写入反码,这样写入才有效。这是个安全措施,同时硬件会自动帮我们检查,不需要调试。
RDP:Read Protect(读保护配置位)若写入RDPRT键(0x000000A5)后解除读保护
USER:配置硬件看门狗和进入停机/待机模式是否产生复位
Data0/1:用户可自定义使用
WRP0/1/2/3:配置写保护,一共32位,每一个位对应保护4个存储页(中容量产品)
- 选项字节的编程:
- 检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作
- 解锁FLASH_CR的OPTWRE位(选项字节的解锁)
- 设置FLASH_CR的OPTPG位为1(即将进入写入选项字节)
- 写入要编程的半字到指定的地址
- 等待BSY位变为0
- 读出写入的地址并验证数据
- 选项字节的擦除:
- 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作
- 解锁FLASH_CR的OPTWRE位(选项字节的解锁)
- 设置FLASH_CR的OPTER位为1(选择字节开始擦除)
- 设置FLASH_CR的STRT位为1
- 等待BSY位变为0
- 读出被擦除的选择字节并做验证
- 器件的电子签名
- 电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名
- 闪存容量寄存器:
- 基地址:0x1FFF F7E0
- 大小:16位
- 产品唯一身份标识寄存器:
- 基地址: 0x1FFF F7E8
- 大小:96位