FLASH模拟EEPROM
由于 AT32
单片机没有 EEPROM 功能,但是在一些应用中需要使用 EEPROM 存储数据。出于节省外置 EEPROM 芯片降低应用成本的考虑,使用 AT32 的片上 FLASH 模拟EEPROM 功能。
1 FLASH 与 EEPROM 简介
FLASH 和 EEPROM 都为非易失性存储器,在断电后数据仍然可以长期保存,这为 FLASH 模拟EEPROM 提供了条件,FLASH 与 EEPROM 特点对比如下表所示:

FLASH 模拟 EEPROM 优点:
― 低成本:可节约一颗 EEPROM 芯片;
― 存储、读取速度快:通讯速度快于使用 I2C 或者 SPI 通讯的 EEPROM
元件;
― 抗干扰能力强:由于 FLASH 在单片机内部,不会存在通讯总线被外部干扰的问题;
― 容量可调:可根据实际使用,灵活调整存储空间大小。
2 FLASH 模拟 EEPROM 原理
2.1 EERPOM 数据结构
由于 FLASH 在写入数据前,需要将 FLASH 数据先擦除为 0xFF,而 FLASH 擦除时通常为扇区擦除,例如 AT32F403A 的扇区大小为 2K 字节,这个特性决定了不能简单的将旧数据擦除然后写新数据,因为这样会导致存储在这个扇区内的其他数据也被擦除,并且也会导致 FLASH 频繁擦除而降低其使用寿命。所以 FLASH 模拟 EEPROM 的思路是:
― 新数据存储不影响旧数据;
― 尽量减少 FLASH 擦除次数,延长 FLASH 使用寿命。
基于以上的考虑,我们设计了以下存储结构:
EERPOM 结构
EEPROM 由两个页组成:页 0 和页 1,在使用的时候,1 个页处于有效状态,另外一个页处于擦除状态,读取或者写入数据都在有效状态的页进行。
数据格式
存储的数据格式为数据 + 数据地址,地址和数据都是 16 位方式存储,每一次存储占用 32 位也就是4 个字节。图中 data 列为数据,data address 列为数据地址,flash address 列为数据存储的实际flash 地址偏移量。例如上图中页 0 的 flash address=12 处,数据为 0x3003,数据地址为 0x0002。
页状态标志
在第一个数据存储区,存储页状态标志 status,页状态标志有 3 种:
― 有效状态:EE_PAGE_VALID,status = 0x0000,读取和写数据在此页进行;
― 数据转移状态:EE_PAGE_TRANSFER,status = 0xCCCC,另外一页满了,正在传输有效数据到本页;
― 擦除状态:EE_PAGE_ERASED,status = 0xFFFF。
数据写入
每一次写入数据前,都会从页起始地址开始寻找第一个未存储数据的区域(值为 0xFFFFFFFF),然后将待写入的数据和数据地址写到未存储数据的区域。例如上图中页 0 的 flash address = 20 处,值为 0xFFFFFFFF,就是第一个未存储数据的区域。
当知道了页的大小后,就可以算出最大的变量存储个数:页容量/4-1。例如当页大小为 1K 时,最大可存储的变量数量为 1024/4-1=255。需要注意的是,在实际使用中,应该尽量留出较多的空闲容量,这样可以减小 FLASH 擦除次数,提高 FLASH 寿命。
另外数据地址不可以超过最大能存储的变量数量,例如当页大小为 1K 时,最大可存储的变量数量为1024/4-1=255,那么数据地址 data address 不可以大于 255。
数据读取
每一次读取数据都会从页结束地址开始向前寻找最后一个存储的有效数据,例如现在要读取地址为0x0000 的数据。从上图中看到 flash address = 4 和 flash address = 16 都是地址为 0x0000 的数据,因为最后一次存储的数据为 flash address = 16 处的数据,所以此时读取地址 0x0000 的数据为0x1234。
数据转移
当一页数据存满了之后,会将数据传输到空闲页,将会执行以下操作(以页 0 满,页 1 空为例):
― 将页 1 状态标记为数据传输状态(EE_PAGE_TRANSFER);
― 将所有有效数据复制到页 1;
― 擦除页 0;
― 将页 1 状态标记为有效状态(EE_PAGE_VALID)。
2.2 EERPOM 物理结构
AT32 所实现的 EEPROM 结构如下图所示,一个页可以由 1 个或者多个扇区组成,可以根据实际应用灵活的选择扇区数量,扇区数量越多,可以存储的数据量就越多。通常 EEPROM 存储区定义在整个 FLASH 末尾,这样程序的烧录、执行和 EEPROM 区域互不影响。

扇区配置可通过 projectat_start_f403aeeprominceeprom.h 里面的宏配置
― EE_SECTOR_NUM:定义单个页的扇区个数;
― EE_SECTOR_SIZE:定义扇区大小,单位是字节,不同型号的扇区大小不一样,详情见下表;
― EE_BASE_ADDRESS:定义 EEPROM 扇区起始地址,通常将 EEPROM 放在最末尾(已经自动计算,放在末尾,无需用户配置)。

以 AT32F403A 1024K Flash 容量为例,EEPROM 定义如下图所示:

如上图所示,FLASH 总计扇区个数为 512 个,总容量为 1024K。分配扇区 0~507,共计 1016K 字节用于程序存储;分配扇区 508~511 共计 8K 用于 EEPROM。EEPROM 存储在最后 4 个扇区,所以定义 EEPROM 扇区起始地址 EE_BASE_ADDRESS 为 0x80FE000(EE_BASE_ADDRESS 已经自动计算放在 FLASH 末尾,用户无需关心)。
每一个扇区大小为 2K,所以定义扇区大小 EE_SECTOR_SIZE 为 2048。每一个页包含了 2 个扇区,所以定义扇区数量 EE_SECTOR_NUM 为 2。此时单个页的容量为 4K,所以最大能存储的变量为 4096/4-1=1023 个,需要注意的是 1023 是理论上最大能存储的变量个数,在实际使用中,应该尽量留出更多的空闲容量,这样可以减小 FLASH 擦除次数,提高 FLASH 寿命。
3 EEPROM 使用
3.1 初始化状态机
由于 EEPROM 是通过页 0 和页 1 的 status 标志管理的,当一页写满了之后会进行复制有效数据到新页,有可能在复制数据过程中发生断电、MCU 复位等情况,此时当 MCU 重新启动时,需要继续完成之前的操作才能继续使用。所以在使用 EEPROM 前,需要根据 status 标志值,来执行相关的初始化操作。初始化状态机已经被封装进了函数 flash_ee_init(),用户可以直接调用。
3.2 函数接口
FLASH 模拟 EERPOM 功能提供了 3 个函数以供用户使用,分别是初始化、写数据、读数据。
4 数据直接存储模式
4.1 存储原理介绍
前几章节所叙述的 FLASH 模拟 EEPROM 机制,在存储少量数据时具有使用便捷、存储可靠的优点。但是在存储大量数据或者想实现对 FLASH 任意地址实现存储访问时,这种机制便不太适合。所以我们也提供了另外一种思路,用户直接访问 FLASH 实现数据的存储。如下图所示:分配FLASH 后面一段空间来存储数据,用户可以对这段空间的任意地址进行存储或者读取数据,由于FLASH 擦除是按扇区擦除,而扇区大小通常为 1K、2K、4K(见表 2),所以在对扇区写数据时不能直接往里面写数据。在写数据时,应该先开辟出一个和扇区大小相同的缓存区,先将扇区数据读回来,然后在缓存中更改数据,然后擦除扇区,再将数据写进扇区。这种数据存储方式,只适用于存储非关键数据,例如一些运行日志之类的信息,因为在将扇区数据读取到缓存,然后擦除扇区时,如果此时发生了掉电或者 MCU 复位的异常情况,将会导致这个扇区数据丢失。关键数据的存储还是要选择 FLASH 模拟 EEPROM 这种存储模式。

扇区配置可通过 projectat_start_f403aflash_write_readincflash.h 里面的宏配置
― FLASH_SECTOR_SIZE:定义扇区大小,单位是字节,不同型号的扇区大小不一样,详情见表2;
― FLASH_CODE_SIZE:定义程序存储区域大小,单位是字节。以 AT32F403A 1024K Flash 容量为例定义存储区:

如上图所示,FLASH 总计扇区个数为 512 个,总容量为 1024K。分配扇区 0~99,共计 200K 字节用于程序存储;分配扇区 100~511 共计 824K 用于数据存储。
所以定义扇区大小 FLASH_SECTOR_SIZE 为 2048,定义程序存储区大小 FLASH_CODE_SIZE 为为 1024*200。
4.2 函数接口
FLASH 数据直接访问模式提供了 2 个函数以供用户使用,分别是写数据、读数据。
5 两种存储模式混合使用
上述两种存储模式可以混合使用,以达到既能实现关键数据的可靠存储,也能实现大量数据的存储。以 AT32F403A 1024K Flash 容量为例定义存储区:

如上图所示,FLASH 总计扇区个数为 512 个,总容量为 1024K。分配扇区 0~99,共计 200K 字节用于程序存储;分配扇区 100~507 共计 816K 用于数据存储;分配扇区 508~511 共计 8K 用于EEPROM。