【STM32 学习笔记】FLASH闪存
FLASH闪存
介绍
读写FLASH的用途:
- 存储用户数据:
在C8T6芯片中,可以利用程序存储器的剩余空间来保存掉电不丢失的用户数据。选择存储区域时,应避免覆盖原有程序代码。
对于我们这个C8T6芯片来说,它的程序存储器容量是64K,一般我们写个简单的程序,可能就只占前面的很小一部分空间,剩下的大片空余空间我们就可以加以利用,比如存储一些我们自定义的数据,这样就非常方便,而且可以充分利用资源,不过这里要注意我们在选取存储区域时,一定不要覆盖了原有的程序,要不然程序自己把自己给破坏了,
一般存储少量的参数,我们就选最后几页存储就行了
- 程序自我更新(IAP):
通过在应用程序中编程,可以实现程序的自我更新。这涉及到编写一个BOOTLOADER程序,并将其存放在程序更新时不会覆盖的地方。
闪存模块组织
对于小容量产品和大容量产品,闪存的分配方式有些区别,这个可以参考一下手册,那首先提醒一下闪存这一章的内容在手册里是单独列出来的,并不在之前的参考手册里,我们需要打开这个闪存编程参考手册,这里以中容量产品为例来讲解。
在闪存编程参考手册中,我们可以看到C8T6的闪存分为三个主要部分:主存储器、信息块和闪存存储器接口寄存器。
- 主存储器:这是闪存中容量最大的部分,用于存放程序代码。主存储器被划分为多个页,每页大小为1K。C8T6共有64页。
- 信息块:这部分包含系统存储器和用户选择字节(也称为选项字节)。系统存储器的起始地址是0x1FFFF000,容量为2K,用于存放原厂写入的BOOTLOADER,以便通过串口下载。用户选择字节的起始地址是0x1FFFF800,容量为16字节,用于存储配置参数。需要注意的是,虽然系统存储器和用户选择字节属于闪存的一部分,但它们通常不计入我们常说的64K或128K闪存容量中。
- 闪存存储器接口寄存器:这些寄存器不属于闪存本身,而是作为外设存在,其地址以0x4002开头,表明它们是普通的外设寄存器,类似于GPIO、定时器、串口等。这些寄存器包括KEYR、SR、CR等,用于控制闪存的擦除和编程过程。
对于主存储器,这里对它进行了分页,分页是为了更好的管理闪存,擦除和写保护都是以页为单位的,这点和之前W25Q64芯片的闪存一样,同为闪存它们的特性基本一样,写入前必须擦除,擦除必须以最小单位进行,擦除后数据位全变为1,数据只能1写0,不能0写1,擦除和写入之后都需要等待忙,这些都是一样的,学习这节之前,大家可以再复习一下W25Q64,再学这一节就会非常轻松了,那W25Q64的分配方式是先分为块block,再分为扇区sector比较复杂,这里就比较简单了,它只有一个基本单位就是页,每一页的大小都是1K,0到127总共128页,总量就是128K,对于C8T6来说,它只有64K,所以C8T6的页只有一半0~63总共64页共64K,
FLASH基本结构
接下来理一下这个基本结构图,整个闪存分为程序存储器
、系统存储器
和选项字节
三部分,这里程序存储器为以C8T6为例,它是64K的,所以总共只有64页,最后一页的起始地址是0800FC00;左边这里是闪存存储器接口(闪存编程和擦除控制器LPEC),然后这个控制器就是闪存的管理员,他可以对程序存储器进行擦除和编程,也可以对选项字节进行擦除和编程,系统存储器是不能擦除和编程的,这个选项字节里面有很大一部分配置位,其实是配置主程序存储器的读写保护的,所以右边画的写入选项字节,可以配置程序存储器的读写保护,当然选项字节还有几个别的配置参数,这个待会再讲,那这就是整个闪存的基本结构。
FLASH解锁
解锁过程:
- 复位后保护状态:在微控制器复位后,FPEC(Flash Programming and Erase Controller)默认是被保护的,此时不能写入FLASH_CR(Flash Control Register)。
- 写入解锁键值:要解锁FPEC,需要按照正确的顺序在FLASH_KEYR(Flash Key Register)中写入特定的键值。
- KEY1:首先写入0x45670123。
- KEY2:然后写入0xCDEF89AB。
- 安全性设计:这种两步解锁过程提高了安全性,因为需要连续写入两个正确的键值才能解锁。这减少了因程序异常而意外解锁的风险。
- 错误操作保护:如果解锁序列错误,比如没有按照先KEY1后KEY2的顺序写入,FPEC和FLASH_CR将会在下次复位前被锁死,防止了进一步的误操作。
加锁过程:
- 操作完成后加锁:在完成所有必要的闪存操作后,为了防止意外写入,需要重新锁定FPEC。
- 设置LOCK位:通过在FLASH_CR寄存器中设置LOCK位来重新锁定FPEC。通常,向LOCK位写入1即可完成加锁操作。
注意事项:
解锁和加锁操作都需要谨慎进行,以确保系统的稳定性和安全性。
在进行任何解锁操作之前,确保理解了相关寄存器的功能和操作步骤,以避免不必要的风险。
接着看下一个知识点,这个地方我们要学习的是,如何使用指针访问存储器,因为STM32内部的存储器是直接挂在总线上的,所以这时在读写某个存储器就非常简单了,直接使用C语言的指针来访问即可。
使用指针访问存储器
讲解为什么会用到volatile
如果你这个地址写的是SRAM的地址,比如0X20000000,那可以直接写入了,因为SRAM在程序运行时是可读可写的,这是使用指针访问存储器的C语言代码,0X08000000,其中读取可以直接读,写入需要解锁,并且执行后面的流程。
程序存储器全擦除
下面我们来详细审视以下三个流程图的内容。首先是编程流程,亦即数据写入过程。其次是页擦除流程,值得注意的是,在STM32的闪存操作中,写入数据前需进行擦除操作。完成擦除后,该页的所有数据位将统一变为1,页擦除的操作单元是1K,即1024字节。最后是全擦除流程,这一过程涉及对所有页面的擦除。关于这些流程的细节,库函数已经为我们封装好了相应的操作,我们只需调用一个总函数即可,操作便捷
- 检查锁状态:首先,读取芯片的LOCK位,以确定芯片是否处于锁定状态。若LOCK位为1,表明芯片已被锁定,此时需要执行解锁操作。
- 解锁操作(如果需要):
如果芯片锁定(LOCK位等于1),则需在KEYR寄存器中依次写入KEY1和KEY2以执行解锁。这一步骤在流程图中有所体现,即锁定了才需要解锁。
若芯片未锁定,则无需执行解锁操作。然而,库函数的设计是直接执行解锁过程,不考虑芯片是否实际锁定。这种方法虽然简单直接,但最终效果是相同的。 - 启动全擦除:
将控制寄存器中的MER(Mass Erase)位置1,以指示全擦除操作。
接着,将STRT(Start)位置1。STRT位为1时,将触发芯片开始执行操作。当芯片检测到MER位为1时,它会识别接下来的操作是全擦除,并自动执行全擦除流程。 - 等待擦除完成:
全擦除操作需要一定时间,因此程序需等待擦除过程结束。这通过检查状态寄存器的BSY(Busy)位来实现。
如果BSY位为1,表示芯片正忙于擦除操作,程序将继续循环检查,直到BSY位变为0,表明擦除操作完成。 - 验证擦除结果(可选):
流程的最后一步是读取并验证所有页的数据。这一步骤通常用于测试程序,以确保擦除操作的成功。
在正常操作中,全擦除完成后,我们可以默认操作成功。由于全读出并验证所有页的数据工作量巨大,因此在实际应用中,这一步骤通常可以省略。
程序存储器页擦除
接下来,我们来看看页擦除的过程,这一过程与全擦除类似,包含以下步骤:
- 解锁操作:首先执行与全擦除相同的解锁流程。如果芯片处于锁定状态,需要在KEYR寄存器中依次写入KEY1和KEY2来解锁。
- 设置页擦除模式:
将控制寄存器中的PER(Page Erase)位置1,指示接下来要执行的是页擦除操作。
在AR(Address Register)地址寄存器中写入要擦除的页的起始地址。这一步是必要的,因为闪存包含多个页,而页擦除操作需要明确指出具体要擦除哪一页。 - 启动擦除操作:
将控制寄存器的STRT(Start)位置1,这是触发条件,告诉芯片开始执行擦除操作。
当芯片检测到PER位为1时,它会识别接下来的操作是页擦除,并且会参考AR寄存器中的地址来确定要擦除的具体页。 - 等待擦除完成:
擦除操作开始后,程序需要等待操作完成。这同样是通过检查状态寄存器的BSY(Busy)位来实现的。
如果BSY位为1,表示芯片正在执行擦除操作,程序将持续检查,直到BSY位变为0,表明擦除操作已经完成。 - 验证擦除结果(可选):
最后一步是读取并验证擦除页的数据。这一步骤在测试程序中可能需要执行,以确保擦除操作的成功。
在实际应用中,由于验证所有数据的工作量较大,通常可以省略这一步骤,假设擦除操作已经成功完成。
总结来说,页擦除过程包括解锁、设置擦除模式、启动擦除、等待操作完成,以及可选的数据验证步骤。通过这些步骤,我们可以确保指定的页被正确擦除。
程序存储器编程
最后,我们来探讨闪存的写入流程。在擦除操作之后,我们就可以进行数据写入。以下是写入流程的详细步骤:
- 解锁操作:与擦除操作类似,写入流程的第一步是对闪存进行解锁,确保可以执行写入操作。
- 设置编程模式:
将控制寄存器中的PG(Programming)位置1,这表示即将进行数据写入操作。 - 写入数据:
在指定的地址写入半字(16位数据)。这一步骤通过指针操作实现,可以直接在指定的内存地址写入想要的数据。
需要注意的是,STM32的闪存写入操作仅支持半字写入。在STM32中,数据单位有字(32位)、半字(16位)和字节(8位)。因此,写入时必须以半字为单位,即每次写入16位数据。如果需要写入32位数据,则需要分两次写入;而写入8位数据时,则需要额外的处理。
处理字节写入:
如果需要单独写入一个字节且保留另一个字节的原始数据,必须将整页数据读取到SRAM中,修改SRAM中的数据,然后擦除整页闪存,并将修改后的整页数据写回。这种方法虽然繁琐,但能实现类似SRAM的灵活读写。
- 触发写入操作:
写入数据后,芯片将自动进入忙状态,开始执行写入操作。与擦除操作不同,写入操作不需要显式设置STRT位,写入半字即可触发。
等待写入完成:
写入过程中,程序需要等待状态寄存器的BSY(Busy)位清0,这表示写入操作已经完成。 - 重复写入流程:
每次执行上述流程,只能写入一个半字。若需要写入大量数据,则需要循环调用写入流程,直到所有数据写入完成。
总结来说,闪存的写入流程包括解锁、设置编程模式、写入数据、等待写入完成,并根据需要重复写入流程以写入多个数据。STM32的闪存写入有一定的限制,需要按照半字单位进行,并且在特定情况下需要采取额外的步骤来保证数据的正确写入。
选项字节
现在,让我们进一步了解选项字节的相关内容。对此有一个基本的认识就足够了。首先,我们要关注的是选项字节的结构和它们的作用。
在图表中,可以看到选项字节的起始地址
,即我们之前提到的0x1FFF8000。这一区域包含的数据,正如表格所示,总共只有16个字节。这些字节在图中被详细展示,它们中的每一个都有一个对应的名称。值得注意的是,其中一半的名称带有“N”前缀,例如RDP和nRDP,USER和nUSER。这表示在写入数据到RDP等存储器时,必须同时在对应的nRDP存储器中写入数据的反码。这样的操作确保了写入的有效性。如果芯片检测到这些存储器中的数据不是反码关系,那么数据将被视为无效,相关的功能也不会被执行。这是一种安全特性,旨在防止错误操作。幸运的是,硬件会自动处理反码的写入过程,因此在使用库函数时,我们只需直接调用相应的函数即可,无需手动干预。
接下来看看这些存储器的具体功能
。排除带有“N”前缀的字节后,我们剩下八个字节存储器。首先是RDP(读保护配置位
),通过向RDP存储器写入特定的RDPRT键(例如0xA5),可以解除读保护。如果RDP不包含0xA5,则闪存将处于读保护状态,防止调试器读取程序代码,从而保护代码不被未授权访问。第二个字节是USER
,它包含了一些零碎的配置位,可以用来配置硬件看门狗以及停机待机模式是否产生复位等。
接着是Data0/1
这两个字节,它们在芯片中没有预设功能,用户可以根据自己的需求进行自定义。最后四个字节,WRP0/1/2/3
,用于配置写保护。在中容量产品中,每个位对应保护四个存储页,总共32位,可以保护128页,这与中容量产品的最大页数相匹配。
对于小容量和大容量产品,写保护配置有所不同。根据手册中的2.5节,小容量产品每个位同样对应保护四个存储页,但由于其最大容量只有32K,因此只需使用一个字节WRP0,即8位,足以保护32页。其他三个字节WRP1、WRP2、WRP3在此不被使用。而对于大容量产品,每个位仅能保护两个存储页,因此四个字节不足以覆盖所有页。为此,规定WRP3的最高位用于保护剩余的所有页,从而确保了写保护功能的完整性。
然后看一下如何去写入这些位呢,这里两页PPT展示的就是选项字节的擦除和编程,因为选项字节本身也是闪存,所以它也得擦除,这里参考手册并没有给流程图,我们看一下这个文字流程,这个文字流程和流程图细节上有些出入,我们知道关键部分就行。
首先,我们来探讨选项字节的擦除流程。虽然第一步在文字描述中未明确提及,但实际上,它同样是解锁闪存。接着,我们看到文字版流程中包含了额外的步骤,即检查状态寄存器(SR)的BSY位,以确保没有其他闪存操作正在进行。这一步骤实际上是一个预先等待的过程:如果检测到BSY位为忙状态,我们需要等待直到操作完成。这一步骤在先前的流程图中并未展示。
下一步是解锁控制寄存器(CR)的OPTWRE(Option Write Enable)位,这是专门针对选项字节的解锁操作。在解锁整个闪存之后,我们还需要单独解锁选项字节,才能对其进行操作。关于解锁选项字节,我们可以参考之前的寄存器组织图。整个闪存的解锁是通过KEYR寄存器完成的,而选项字节的小锁则是通过OPTKEYR(Option Key Register)寄存器来解锁。解锁这个小锁的流程如下:首先在OPTKEYR中写入KEY1,然后写入KEY2,这样就可以成功解锁选项字节。
解锁选项字节的小锁之后,接下来的步骤与之前的擦除操作类似。首先,我们需要将CR的OPTER(Option Erase)位置1,这表示我们准备擦除选项字节。然后,设置CR的STRT位为1,这一操作将触发芯片开始擦除选项字节的过程。在设置STRT位后,我们等待BUSY位变为0,这表明擦除选项字节的过程已经完成。一旦擦除操作完成,我们就可以进行后续的写入操作了。
和普通的闪存写入也差不多,先检测BSY,然后解除小锁,之后设置CR的OPTPG(Option Programming)位为1,表示即将写入选项字节,再之后写入要编程的半字到指定的地址,这个是指针写入操作,最后等待忙,这样写入选项字节就完成了。
最后我们花几分钟学一下器件电子签名,这个非常简单,既然讲到闪存了,就顺便学习一下吧
看一下电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写不可更改,使用指针读指定地址下的存储器,可获取电子签名,电子签名其实就是STM32的id号,它的存放区域是系统存储器,它不仅有BOOTLOADER程序,还有几个字节的id号,系统存储器起始地址是1FFFF000,看下这里,这里有两段数据,第一个是闪存容量存储器,基地址是1FFF F7E0,通过地址也可以确定它的位置,就是系统存储器,这个存储器的大小是16位,它的值就是闪存的容量单位是KB,然后第二个是产品唯一身份标识寄存器,就是每个芯片的身份证号,这个数据存放的基地址是1FFFF7E8,大小是96位,每一个芯片的这96位数据都是不一样的,使用这个唯一id号可以做一些加密的操作,比如你想写入一段程序,只能在指定设备运行,那也可以在程序的多处加入id号判断,如果不是指定设备的id号,就不执行程序功能,这样即使你的程序被盗,在别的设备上也难以运行,这是STM32的电子签名。
代码实战:读写内部FLASH&读取芯片 ID
建议观看视频:15-2 读写内部FLASH&读取芯片 ID
-
读写内部FLASH
-
读取芯片 ID