FLASH、EEPROM和FLASH模拟EEPROM

FLASH和EEPROM

FLASH存储器EEPROM(电可擦可编程只读存储器)都属于非易失性存储器,但它们在工作原理、使用方式、应用场景等方面有一些区别。

FLASH存储器

FLASH(闪存)是一种非易失性存储器,能够在没有电源的情况下保存数据。它的工作原理类似于EEPROM,但在数据擦除和写入的方式上有所不同。

特点:

  1. 大容量:FLASH存储器一般提供比EEPROM更大的存储容量,通常从几兆字节(MB)到几百兆字节(甚至几个GB)。
  2. 块擦除:FLASH存储器以(通常为几KB到几MB)为单位进行擦除,擦除操作需要清空整个块,而不能像EEPROM那样按字节擦除。这使得FLASH的擦写次数比EEPROM更低,一般在10,000到100,000次之间。
  3. 写入速度较快:相比EEPROM,FLASH的写入速度更快,适用于需要快速写入的应用场景。
  4. 应用广泛:FLASH主要用于存储大容量数据,广泛应用于固态硬盘(SSD)、U盘、存储卡、智能手机、嵌入式设备等。
  5. 编程和擦除:FLASH可以通过电气方式编程和擦除,但擦除和编程过程比EEPROM要复杂得多,且无法像EEPROM那样轻松按字节更新数据。

类型:

  • NAND Flash:主要用于大容量存储,速度快,成本低,广泛用于移动存储设备(如U盘、SD卡、SSD等)。
  • NOR Flash:速度较慢但可按字节进行读写,主要用于代码存储,适合嵌入式系统。

EEPROM vs FLASH

特性 EEPROM FLASH
存储容量 较小,一般为几KB到几MB 较大,可以达到几GB
数据擦除方式 按字节擦除 按块擦除(大块范围,一般几KB至几MB)
写入速度 较慢,尤其在单个字节写入时 较快,适合大块数据写入
擦写次数 较多,通常达到100,000次以上 较少,通常为10,000到100,000次
应用场景 存储配置参数、序列号、校准数据等 存储操作系统、应用程序、数据文件等
价格 较贵,尤其在小容量时 相对便宜,适用于大容量存储
  • EEPROM通常用于存储少量的数据,比如设备配置、用户设置等,需要频繁的、字节级的读写操作。它的主要优点是可以单独字节擦写,但容量较小,写入速度较慢。
  • FLASH则适用于大容量的存储需求,广泛用于存储操作系统、应用程序、媒体文件等。由于其按块擦除的特性,写入速度较快,但擦写次数较少,适合大数据量的存储。

对于嵌入式系统来说,EEPROM适用于需要频繁更新的小量数据,而FLASH则适合存储较大的程序或数据集。

STM32F429中的FLASH

组织架构

MCU自带FLASH组织
STM32F429 本身没有自带 EEPROM,但是 STM32F429 具有 IAP(在应用编程)功能,所以我们可以把它的 FLASH 当成 EEPROM 来使用。对于F429IGT6来说,其FLASH容量为1024KB。FLASH模块分为:

  • 主存储器:该部分用来存放代码和数据常数(如 const 类型的数据)。分为两个 Bank,每个 Bank 分为 12 个扇区,前 4 个扇区为 16KB 大小,第五个扇区是 64KB 大小,剩下的 7 个扇区都是 128K大小,总共 1M 字节。两个 Bank 就是 2M 字节。不同容量的 STM32F429,拥有的扇区数不一样。以STM32F429IGT6为例,只有 Bank1,拥有 12 个扇区,1M 字节容量。主存储器的起始地址一般为 0X08000000,B0、B1 都接 GND 的时候,就是从0X08000000 开始运行代码的。
  • 系统存储器:主要用来存放 STM32F429的 bootloader代码,此代码在出厂时已经固化在 STM32F429 内,专门用来给主存储器下载代码。当 B0 接 V3.3,B1 接 GND 的时候,从该存储器启动(即进入串口下载模式)。
  • OTP区域(一次性可编程区域):共 528 字节,被分成两个部分,前面 512字节(32 字节为1块,分成16块),可以用来存储一些用户数据(一次性,写完一次,永远不可以擦除),后面 16 字节,用于锁定对应块。
  • 选项字节:用于配置读保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。

在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正
确地进行;即在进行写或擦除操作时,不能进行代码或数据的读取操作。

读取

为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地设置等待周期数 (LATENCY)。当电源电压低于 2.1V 时,必须关闭预取缓冲器。

ACLK时钟频率和对应的FLASH等待周期

等待周期通过FLASH_ACR寄存器的LATENCY[2:0]三个位设置。

STM32读取FLASH很简单,例如要从地址addr读取一个字(32位),只需要一句话:

1
Data = *(volatile uint32_t *)faddr;

将 faddr 强制转换为 volatile uint32_t 指针,然后取该指针所指向的地址的值,即得到了 faddr 地址的值。类似的,将上面的 volatile uint32_t 改为 volatile uint8_t,即可读取指定地址的一个字节。此处的 volatile 关键字告诉编译器该地址的内容可能随时改变,因此禁止编译器对该地址做任何优化。通常用于表示硬件寄存器或外设的内存地址,因为这些内容的值可能由外部事件(如外部硬件)改变。

编程和擦除

执行任何 Flash编程操作(擦除或编程)时,CPU时钟频率(HCLK)不能低于 1MHz。在对 STM32F429 的 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从 Flash 中执行代码或数据获取操作。

STM32F429 用户闪存的编程一般由 6 个 32 位寄存器控制,他们分别是:

  • FLASH 访问控制寄存器(FLASH_ACR)
  • FLASH 秘钥寄存器(FLASH_KEYR)
  • FLASH 选项秘钥寄存器(FLASH_OPTKEYR)
  • FLASH 状态寄存器(FLASH_SR)
  • FLASH 控制寄存器(FLASH_CR)
  • FLASH 选项控制寄存器(FLASH_OPTCR)

STM32F429复位后,FLASH 编程操作是被保护的,不能写入 FLASH_CR寄存器;通过写入特定的序列(0X456701230XCDEF89AB)到 FLASH_KEYR 寄存器才可解除写保护,只有在写保护被解除后,我们才能操作相关寄存器。

FLASH_CR 的解锁序列为:

  1. 0X45670123 到 FLASH_KEYR
  2. 0XCDEF89AB 到 FLASH_KEYR

通过这两个步骤,即可解锁 FLASH_CR,如果写入错误,那么 FLASH_CR 将被锁定,直到下次复位后才可以再次解锁。

STM32F429 闪存的编程位数可以通过 FLASH_CR 的 PSIZE 字段配置,PSIZE 的设置必须
和电源电压匹配:
编程/擦除并行位数与电压关系

当开发板电压为3.3V时,PSIZE10,并行位数32位。也就是擦除或编程都需要以32位为基础进行。

FLASH在编程时必须要求目标写入地址的FLASH已经被擦除(值为0xFFFFFFFF),否则将无法写入。

F429的FLASH标准编程步骤如下:

  1. 检查FLASH_CR中的BSY位,确保总线空闲
  2. 将FLASH_CR寄存器的PG位置1,激活FLASH编程
  3. 针对所需存储地址(主存储器块或 OTP 区域内)执行数据写入操作:
    • 并行位数为x32时按字写入(PSIZE=02)
  4. 等待BSY位清零

注意
- 编程前确保写入地址已经擦除
- 先解锁FLASH_CR后编程

扇区擦除步骤如下:

  1. 检查FLASH_CR的LOOK是否解锁,如果没有则先解锁
  2. 检查FLASH_CR中的BSY位,确保总线空闲
  3. FLASH_CR的SER位置1,并从主存储块的12个扇区中选择要擦除的扇区(SNB)
  4. FLASH_CR寄存器中STRT位置1,触发擦除
  5. 等待BSY位清零

寄存器

FLASH_ACR - Flash访问控制寄存器

FLASH_ACR
重点为LATENCY[2:0]位,应根据MCU电压和频率设置,否则死机。

FLASH_KEYR - Flash密钥寄存器

FLASH_KEYR
用于解锁FLASH_CR,必须按顺序写入KEY1和KEY2

FLASH_CR - Flash控制寄存器

FLASH_CR

  • LOCK位:用于指示 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。
  • PG位:用于选择编程操作,在往 FLASH 写数据的时候,该位需要置 1。
  • SER位:位用于选择扇区擦除操作,在扇区擦除的时候,需要将该位置 1。
  • PSIZE[1:0]位:设置编程宽度,一般设置 PSIZE =2 即可(32 位)。
  • STRT位:该位用于开始一次擦除操作。在该位写入 1 ,将执行一次擦除操作。
  • SNB[3:0]位:用于选择要擦除的扇区编号,取值范围为 0~15。

FLASH_SR - Flash状态寄存器

FLASH_SR

  • BSY位:确认BANK是否正在执行编程操作

代码

读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 /**
* @brief 从指定地址读取一个字(32位)
* @param faddr : 读取地址
* @retval 读取到的数据(32位)
*/
uint32_t stmflash_read_word(uint32_t faddr)
{
return *(volatile uint32_t *)faddr;
}

/**
* @brief 从指定地址开始读出指定长度的数据
* @param raddr : 起始地址
* @param pbuf : 数据指针
* @param length: 要读取的字(32)数,即 4 个字节的整数倍
* @retval
*/
void stmflash_read(uint32_t raddr, uint32_t *pbuf, uint32_t length)
{
uint32_t i;

for (i = 0; i < length; i++)
{
pbuf[i] = stmflash_read_word(raddr);
raddr += 4;
}
}

写入

写入相对来说复杂一点,首先要判断写入地址是否正确(不能超出FLASH地址范围,不能不是4的倍数),判断通过后,解锁FLASH,禁用数据缓存,然后判断待写入地址范围是否为0xFFFFFFFF。如果是,表明该地址已经被擦除;如果不是,表明该地址需要先擦除,再写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void stmflash_write(uint32_t waddr, uint32_t *pbuf, uint32_t length)
{
FLASH_EraseInitTypeDef flash_erase_init;
HAL_StatusTypeDef flashstatus = HAL_OK;
uint32_t sector_error = 0;
uint32_t addrx = 0;
uint32_t endaddr = 0;

if (waddr < STM32_FLASH_BASE || waddr % 4 || waddr > (STM32_FLASH_BASE + STM32_FLASH_SIZE)) return; /* 地址非法 */

HAL_FLASH_Unlock();

FLASH->ACR &= ~(1 << 10); /* 禁用数据缓存 */
addrx = waddr; /* 写入起始地址*/
endaddr = waddr + length * 4; /* 写入结束地址 */

if (addrx < 0x1FFF0000) /* 主存储区*/
while (addrx < endaddr)
{
if (stmflash_read_word(addrx) != 0xFFFFFFFF) /* 当前地址不为0xFFFFFFFF则表示需要擦除此地址 */
{
flash_erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
flash_erase_init.Sector = stmflash_get_flash_sector(addrx);
flash_erase_init.NbSectors = 1;
flash_erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;

if (HAL_FLASHEx_Erase(&flash_erase_init, &sector_error) != HAL_OK) break;
}
else
{
addrx += 4;
}
FLASH_WaitForLastOperation(FLASH_WAITETIME);
}
flashstatus = FLASH_WaitForLastOperation(FLASH_WAITETIME);
if (flashstatus == HAL_OK)
{
while (waddr < endaddr)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, waddr, *pbuf) != HAL_OK) break;
waddr += 4;
pbuf++;
}
}
FLASH->ACR |= 1 << 10; /* 启用数据缓存 */

HAL_FLASH_Lock();
}

这里有几个需要注意的点:

  1. 擦除和写入期间必须禁用数据缓存,否则缓存器中的缓存内容和新的内容可以不一致导致bug。禁用数据缓存通过位操作FLASH->ACR &= ~(1 << 10)实现,1 << 10表示1左移10位,~表示取反。
  2. 擦除操作通过HAL_FLASHEx_Erase()实现,HAL库对于该函数的定义为:
1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief Perform a mass erase or erase the specified FLASH memory sectors
* @param[in] pEraseInit pointer to an FLASH_EraseInitTypeDef structure that
* contains the configuration information for the erasing.
*
* @param[out] SectorError pointer to variable that
* contains the configuration information on faulty sector in case of error
* (0xFFFFFFFFU means that all the sectors have been correctly erased)
*
* @retval HAL Status
*/
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError)

形参1为存储擦除配置的结构体,形参2为存储报错信息的变量。
结构体格式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief FLASH Erase structure definition
*/
typedef struct
{
uint32_t TypeErase; /*!< Mass erase or sector Erase.
This parameter can be a value of @ref FLASHEx_Type_Erase */

uint32_t Banks; /*!< Select banks to erase when Mass erase is enabled.
This parameter must be a value of @ref FLASHEx_Banks */

uint32_t Sector; /*!< Initial FLASH sector to erase when Mass erase is disabled
This parameter must be a value of @ref FLASHEx_Sectors */

uint32_t NbSectors; /*!< Number of sectors to be erased.
This parameter must be a value between 1 and (max number of sectors - value of Initial sector)*/

uint32_t VoltageRange;/*!< The device voltage range which defines the erase parallelism
This parameter must be a value of @ref FLASHEx_Voltage_Range */

} FLASH_EraseInitTypeDef;
  1. 全部擦除完成并等待FLASH_WaitForLastOperation(FLASH_WAITETIME)传回HAL_OK后,才可以开始进行。写入函数为HAL_FLASH_Program
1
2
3
4
5
6
7
8
9
10
/**
* @brief Program byte, halfword, word or double word at a specified address
* @param TypeProgram Indicate the way to program at a specified address.
* This parameter can be a value of @ref FLASH_Type_Program
* @param Address specifies the address to be programmed.
* @param Data specifies the data to be programmed
*
* @retval HAL_StatusTypeDef HAL Status
*/
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)

形参1用于指定写入的是字节(8位)、半个字(16位)、字(32位)或双字(64位);形参2为写入地址,形参3为待写入数据。连续写入时,形参3一般设置为指针,每次循环对相应地址+1处理。
4. 写入完成后,启用数据缓存,FLASH上锁。


FLASH、EEPROM和FLASH模拟EEPROM
http://akichen891.github.io/2025/01/02/FLASH、EEPROM和FLASH模拟EEPROM/
作者
Aki
发布于
2025年1月2日
更新于
2025年1月6日
许可协议