Flash(闪存)是一种可擦除的只读存储器,按照实现方式和运行特性Flash一般还会分为NOR
和NAND
两种。其中NOR Flash
支持随机地址的读取方式,在读取操作上类似于RAM
,比较适合程序的直接读取运行,而NAND Flash
读取是基于页的方式,一般无法随机读取。在微控制器中,Flash需要存储程序和数据,所以大多使用NOR Flash
。
YTM32系列MCU中通过Flash控制器EFM(Embedded Flash Module)控制管理Flash存储器,这里以ME0x系列MCU为例,EFM模块支持如下的一些功能:
这里梳理使用Flash过程中常用的一些约定术语:
以ME0x微控制器为例,芯片系统存储的映射地址空间,如表x所示。
Flash Device Name | Address Space | Size(KB) | Block | Protect |
---|---|---|---|---|
PFlash0 | 0x0000_0000 - 0x0007_FFFF | 512 KB | 0 | ADDR_PROT0 |
PFlash1 | 0x0008_0000 - 0x000F_FFFF | 512 KB | 1 | ADDR_PROT1 |
DFlash | 0x0010_0000 - 0x0013_FFFF | 256 KB | 2 | ADDR_PROT2 |
AES_NVR / HCU_NVR | 0x1000_0000 - 0x1000_03FF | 1 KB | 0 | No Read + Customer Key |
OTP_NVR | 0x1001_0000 - 0x1001_03FF | 1 KB | 0 | No Erase |
BOOT_NVR | 0x1002_0000 - 0x1002_03FF | 1 KB | 1 | SWAP CMD only |
CUS_NVR | 0x1003_0000 - 0x1003_03FF | 1 KB | 1 | Customer Key |
表x中PFlash0
和PFlash1
是两个物理存储设备,可用于存储用户程序。当使用OTA应用时,这两个Block可以通过物理地址的重映射互换(BOOT_SWAP
),方便实现应用升级。
AES_NVR
(也有的手册上使用HCU_NVR
的命名)主要用于保存HCU(硬件加密单元)中使用的密钥,该部分占用1KB空间,可以存放32 x 256 bit
的密钥数据。为了可以保证密钥的安全,该部分区域支持用户的擦除和写入,但是不支持读取操作。当HCU需要使用密钥的时候,软件可以直接调用Flash的命令(Load AES Key (0x20)
)将需要用到的Key直接载入到HCU中使用,整个过程中,软件只能选择Key(的索引)而不能读取或者修改Key(的内容)。注意,对AES Key区域的写入和擦除操作,都需要先用Customer Key
解锁(向EFM_CUS_KEY
寄存器中写一次0x4dff32
)。
OTP_NVR
可以用于保存一次性可编程数据,OTP(One Time Program)区域的特点是,只能一次写入,不支持擦写和二次写入。在具体应用中,可以在该区域保存产品ID信息或者永久固化的信息。注意,对该区域的读取并没有限制。
BOOT_NVR
可用来保存与OTA
升级相关的数据,例如,当前作为Block0的PFlash索引,软件应该避免对该区域进行操作。
CUS_NVR
包含Flash擦写保护配置和调试端口禁用的特定的数据标签(相当于是配置寄存器的值),用户可以向该区域的特定地址写入特定值,以启用对Flash的擦写保护和禁用调试端口。CUS_NVR
的剩余部分是用户可以自由使用的,但对CUS_NVR
的读写操作需要先通过写入Customer Key
来解锁。
读取(Read)、写入(Program)和擦除(Erase)是使用MCU内部集成Flash存储器的基本操作,Flash的特性是:擦除之后比特位的值变成1,写入操作是将相应的比特位的值改写成0。
由于YTM32的MCU的存储器都有ECC的机制,YTM32系列MCU都不支持二次写入操作(Re-Program),即不支持在Flash页的数据有非1的情况(已经被编程过)时再次对页编程。EFM外设在执行写入命令之前,会先从Flash中读取页数据,验证其数据为全1才能执行编程操作,否则若验证失败(无法通过ECC验证)则该页无法编程。
对Flash物理块的操作,在同一个时间,只能执行读取、写入或者擦除的任一操作,这就意味着用户在对一个物理块进行写入或者擦除操作时,不能读取Flash中的内容。也就是说,Flash在执行写入或者擦除的时候,处理器不能从Flash中继续读取程序执行代码,否则EFM模块会直接产生Bus Error(M0+中对应为hardfault)。这也是L系列MCU(仅有一块Flash物理存储块)在对Flash操作(包括DFlash)的时必须关闭中断的原因。
而M系列MCU内部集成了多块Flash物理存储块(MD集成了2个PFlash存储块,ME集成了2个PFlash存储块和1个DFlash存储块),在使用过程中,按照上述原则,虽然处理器在不能对同一个Block同时进行读写操作,但可以相互从操作,避开读写冲突,所以典型的使用方式有如下几种:
PFlash0
和PFlash1
用于保存程序和运行数据,DFlash
用于保存Bootloader
程序和模拟EEPROM
的代码(模拟EEPROM的存储器在PFlash上)。这样,在Boot模式下,软件可以直接对两块PFlash进行读写操作而不必禁用系统中断;而在应用软件运行过程中,软件可以直接操作模拟EEPROM
区域而不必禁用系统中断。PFlash0
和PFlash1
分别保存一套程序代码,通过BOOT_SWAP
指令,决定下次复位之后从哪个Block启动。当使用OTA功能的时候,当前Block运行的程序可以直接对另一个Block的Flash存储器进行擦写操作而不用关闭中断,因为这个时候软件是不会跳转到另外一个Block运行的,当然,如果软件在操作另外一个Block时,对这个Block的读操作也是不允许的。读取Flash的内容,只要按照常规访问存储空间的方式,使用地址寻址读数即可。无论是编译器,还是人为指定访问空间均可。
对于Flash的擦除和写入操作,则都是基于专门的命令来执行的,有命令清单如表x所示。
Code | Description | Need Address |
---|---|---|
0x02 | Program 64 bits | Y |
0x03 | Program 64bits and read back verify | Y |
0x10 | Sector erase | Y |
0x11 | Sector erase and verify | Y |
0x12 | Erase block (only main array) | Y |
0x13 | Erase block and then verify (only main array) | Y |
0x1E | Erase chip | N |
0x20 | Load AES Key | N (Y in MD1) |
0x30 | Boot Swap | N |
0x40 | Program NVR | Y |
0x41 | Erase NVR | Y |
0x42 | Read NVR | Y |
用户可以写这些命令到EFM_CMD[CMD]
寄存器字段中生效。如图x所示。
表x中,需要传入地址的命令(Need Address一栏中为Y),获取地址参数的方式可分为两类:
EFM_NVR_ADDR
寄存器写地址值,再向EFM_NVR_DATA
寄存器写将要写入的数值。之后再执行擦除或者写入NVR的命令。以写入Flash的操作为例,执行一次64 bits数据的写入操作,并验证的命令流程如下:
EFM_TIMING1[TPREPROG]
,EFM_TIMING1[TPROG]
,EFM_TIMING2[TERASE]
,EFM_TIMING2[TERASE_RETRY]
,EFM_CTRL[PRESCALER]
。(编程手册中并没有明确讲述确切的指标,但可参见SDK中的样例代码进行配置,大致是要确保操作Flash的时钟频率低于某个频率,24Mhz ???)。EFM_STS
寄存器,判断EFM在当前是否正在执行命令。如果正忙就原地等一等。0xfd9573f5
到EFM_CMD_UNLOCK
寄存器,解锁EFM的命令引擎。每次解锁后只能执行一次命令,然后自动上锁。0x03
到EFM_CMD
寄存器,开始执行写入并验证的命令。EFM_STS
寄存器,等待命令执行结束,并验证命令执行结果正常。注意,若是执行擦除操作,用户仍需要通过写数到Flash地址空间的方式传入将要擦除Sector的地址,其中写数操作的数值无所谓,例如在SDK的驱动中,就随便用了一个0x12345678
。
有些应用中对于Flash的写入和擦写的时间比较敏感,需要通过配合其他功能的时序完成功能,此处列写Flash基本操作的典型时间参数。如表x所示。
操作 | M系列 | L系列 |
---|---|---|
Sector Erase | 16ms | 4.5ms |
Block Erase | 16ms | 35ms |
Chip Erase | 16ms | 35ms |
Page Program | 45us | 50us |
操作Flash的时间同预分频和EFM_TIMING
寄存器配置有关,上述表格的数据是在Prescaler预分频正确配置和TIMING保持默认值下的数据(参见SDK代码中的配置)。
M系列MCU擦除的时间都是16ms,Flash array支持多扇区同时擦写,所以整个Block块甚至整个芯片的擦除时间都是16ms,当采用Erase Retry的擦除方式时,EFM会按照800us的周期对Flash进行擦除尝试,这种方式下擦写的时间是800us~16ms。Erase Retry仅支持单个Sector的擦写。
L系列MCU只有一个Block的缘故,Block Erase和Chip Erase并没有什么区别,也不支持erase retry功能,另外L系列的DFlash实际和PFlash属于同一个Block,所以对DFlash进行擦写操作时候,也不支持对PFlash进行读操作。因此用DFlash模拟eeprom的时,仍然需要禁用系统中断。
YTM32的两块PFlash(PFlash0
和PFlash1
)会映射到两个地址Block块(Block0
和Block1
),并且按照顺序映射地址空间。
在默认情况下,访问Block0
的地址区间,在物理上就是访问PFlash0
存储器。但若执行了BOOT_SWAP
命令后(向EFM_CMD[CMD]
寄存器字段中写命令码0x30
,如图x所示),再复位重新上电,则会交换地址空间向PFlash0
和PFlash1
的映射区间,Block0
将会映射到PFlash1
,Block1
将会映射到PFlash0
。
当执行了BOOT_SWAP
命令后,可通过查看标志位置位EFM_STS[BOOT_INFO]=1
,确认BOOT_SWAP
命令已生效。
当再次向EFM发送BOOT_SWAP
命令后,两个Flash的Block块地址映射会再次反转,恢复成默认的情况,同时标志位清零EFM_STS[BOOT_INFO]=0
。
需要特别注意的是,如果刚执行BOOT_SWAP
命令后,标志位被置位EFM_STS[BOOT_INFO]=1
,则CUS_NVR
存储区中的数据将被清除,这就意味着用户需要重新配置其中的选项,包括禁用SWD端口和地址保护等功能。
为了保护用户的软件代码不会被恶意地读取到芯片外面(被破解),ME系列支持通过EFM模块禁用芯片调试端口,这个过程是在芯片执行ROM中的初始化过程中通过硬件操作序列生效的。同样,为了避免程序在运行过程中对Flash进行意外操作,Flash同样支持基于地址的擦写保护,应用程序可以根据实际应用需求限制。
在YTM32B1ME0x中,CUS_NVR
的起始地址是0x1001_0000U
,这部分NVR区域中数据定义如下:
Region(32bits) | Address | Description |
---|---|---|
Debugger Disable Tag 0 | 0x10010000 | TAG=0x5a5a5a5a Disable debugger |
Debugger Disable Tag 1 | 0x10010004 | TAG=0x5a5a5a5a Disable debugger |
PROT0 Tag | 0x10010008 | TAG=0x5a5a5a5a |
PROT0 Value | 0x1001000C | ADDR_PROT0 Init value |
PROT1 Tag | 0x10010010 | TAG=0x5a5a5a5a |
PROT1 Value | 0x10010014 | ADDR_PROT1 Init value |
PROT2 Tag | 0x10010018 | TAG=0x5a5a5a5a |
PROT2 Value | 0x1001001C | ADDR_PROT2 Init value |
User NVR | User can use other left space |
因为NVR区域的页大小也是8 Bytes(64bits),实际单次至少写入两个32b字。例如,若需要在上电后禁用调试端口,需要向0x10010000U
地址写入0x5a5a5a5a
, 0x5a5a5a5a
,如此,下次芯片复位后,调试端口默认就不会开启了。
需要特别注意,使用禁用JTAG/SWD模块的功能要特别小心,一旦关停之后,用户将不能通过调试器连处理器内核。建议用户在使用关停JTAG/SWD模块之前,先预留好“后门”,可通过使用其他通信端口的Bootloader
更新Flash中的固件,或者能够通过预先准备好的程序擦除该区域以恢复使用JTAG/SWD模块。
表x中,ROT相应的tag
和value
也是一起编程的,当tag
匹配的时候(验证写数有效),下次上电对应的value
就会自动载入到相应的寄存器(EFM_ADDR_PROT
)实现对Flash的保护。
可以通过配置EFM_PROTn
寄存器(EFM_ADDR_PROTn
),为Flash的地址存储区设置写保护,防止Flash中的关键存储区被意外篡改。以YTM32B1ME05 MCU为例,其中集成了1MB Flash存储区,设计了EFM_PROT0
和EFM_PROT1
两个寄存器,分别对应两个地址块Block0和Block1,每个Block分为32个小块(对应EFM_PROTn
寄存器中的每个寄存器位),每个小块对应16KB的存储区。
在默认情况下,EFM_PROTn
寄存器中的所有寄存器位均为1,表示对应的存储区域可以正常擦写。一旦其中某个寄存器位被清零,表示对应的存储区域被保护起来,向这些区域的读写操作将被EFM忽视掉。
这组寄存器有Flash的特性,即仅能从1写0,不能从0写1。实际上,EFM_PROTn
寄存器的物理存储地址,就映射在CUR_NVM
区域中。因此,向EFM_PROTn
寄存器中写数的方式有两种:
EFM_PROTn
寄存器中写数。CUR_NVM
区域中偏移地址为0x10、0x18的区域中写入值,这些值将作为EFM_PROTn寄存器的初值(在上电复位后生效),每次写入的值都是64位,其中高32位必须为0x5A5A5A5A
,低32位则位数则为指定的设置值。