AN0002 入门指南 |
FLASH模拟EEPROM入门指南 |
前言
由于AT32单片机没有EEPROM功能,但是在一些应用中需要使用EEPROM存储数据。出于节省外置EEPROM芯片降低应用成本的考虑,本入门指南详细阐述了如何使用AT32的片上FLASH模拟EEPROM功能。
注:本应用笔记对应的代码是基于雅特力提供的V2.x.x 板级支持包(BSP)而开发,对于其他版本BSP,需要注意使用上的区别。
支持型号列表:
支持型号 | AT32全系列 |
目录
表目录
图目录
- FLASH与EEPROM简介
FLASH和EEPROM都为非易失性存储器,在断电后数据仍然可以长期保存,这为FLASH模拟EEPROM提供了条件,FLASH与EEPROM特点对比如下表所示:
表1. FLASH与EEPROM特点对比
异同 | FLASH | EEPROM |
相同点 | 非易失性存储,断电后数据仍可保存 | 非易失性存储,断电后数据仍可保存 |
不同点 | 写入数据前需先擦除(只能将位由1写0) | 写入数据前无需擦除(能将位由1写0,和由0写1) |
最小擦除单元为扇区 | 无扇区结构 | |
容量大,价格便宜 | 容量小,价格昂贵 | |
擦写寿命大于10万次 | 擦写寿命大于100万次 | |
数据保存时间大于20年 | 数据保存时间大于100年 |
FLASH模拟EEPROM优点:
- 低成本:可节约一颗EEPROM芯片;
- 存储、读取速度快:通讯速度快于使用I2C或者SPI通讯的EEPROM元件;
- 抗干扰能力强:由于FLASH在单片机内部,不会存在通讯总线被外部干扰的问题;
- 容量可调:可根据实际使用,灵活调整存储空间大小。
由于FLASH在写入数据前,需要将FLASH数据先擦除为0xFF,而FLASH擦除时通常为扇区擦除,例如AT32F403A的扇区大小为2K字节,这个特性决定了不能简单的将旧数据擦除然后写新数据,因为这样会导致存储在这个扇区内的其他数据也被擦除,并且也会导致FLASH频繁擦除而降低其使用寿命。
所以FLASH模拟EEPROM的思路是:
- 新数据存储不影响旧数据;
- 尽量减少FLASH擦除次数,延长FLASH使用寿命。
基于以上的考虑,我们设计了以下存储结构:
图1. EEPROM数据存储结构
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. EEPROM写入流程图
-
- EERPOM物理结构
AT32所实现的EEPROM结构如下图所示,一个页可以由1个或者多个扇区组成,可以根据实际应用灵活的选择扇区数量,扇区数量越多,可以存储的数据量就越多。通常EEPROM存储区定义在整个FLASH末尾,这样程序的烧录、执行和EEPROM区域互不影响。
图3. EEPROM物理结构
扇区配置可通过project\at_start_f403a\eeprom\inc\eeprom.h里面的宏配置
- EE_SECTOR_NUM:定义单个页的扇区个数;
- EE_SECTOR_SIZE:定义扇区大小,单位是字节,不同型号的扇区大小不一样,详情见下表;
- EE_BASE_ADDRESS:定义EEPROM扇区起始地址,通常将EEPROM放在最末尾(已经自动计算,放在末尾,无需用户配置)。
表2. 不同型号扇区大小
型号 | 扇区大小 |
AT32F403 | 2K |
AT32F403A | 2K |
AT32F413 | 2K |
AT32F415 | 2K |
AT32F435 | 4K |
AT32F421 | 1K |
AT32F425 | 1K |
AT32L021 | 1K |
以AT32F403A 1024K Flash容量为例,EEPROM定义如下图所示:
图4. 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寿命。
由于EEPROM是通过页0和页1的status标志管理的,当一页写满了之后会进行复制有效数据到新页,有可能在复制数据过程中发生断电、MCU复位等情况,此时当MCU重新启动时,需要继续完成之前的操作才能继续使用。所以在使用EEPROM前,需要根据status标志值,来执行相关的初始化操作。初始化状态机已经被封装进了函数flash_ee_init(),用户可以直接调用。
表3. EEPROM初始化状态机
页1 | ||||
有效 VALID | 数据转移 TRANSFER | 擦除 ERASE | ||
页0 | 有效 VALID | 擦除页0 标记页0为VALID 擦除页1 | 擦除页1 数据从页0复制到页1 擦除页0 标记页1为VALID | 擦除页1 |
数据转移 TRANSFER | 擦除页0 数据从页1复制到页0 擦除页1 标记页0为VALID | 擦除页0 标记页0为VALID 擦除页1 | 擦除页1 标记页0为VALID | |
擦除 ERASE | 擦除页0 | 擦除页0 标记页1为VALID | 擦除页0 标记页0为VALID 擦除页1 |
FLASH模拟EERPOM功能提供了3个函数以供用户使用,分别是初始化、写数据、读数据。
- EEPROM初始化,每一次MCU复位了之后,都必须调用此函数进行初始化。
flash_status_type flash_ee_init(void); |
- 返回值:Flash操作状态。
- 从EEPROM读取数据
uint16_t flash_ee_data_read(uint16_t address, uint16_t* pdata); |
- address:为变量的地址;
- pdata:为读出的数据;
- 返回值:数据读取状态0:成功读取数据,1:未找到数据。
- 写数据到EEPROM
flash_status_type flash_ee_data_write(uint16_t address, uint16_t data); |
- address:为变量的地址;
- data:为写入的数据;
- 返回值:Flash操作状态。
需要注意的是address值不可以大于变量个数,例如当页大小为2K时,最大能存储2048/4-1=511个变量,那么address的范围就是0~511。
前几章节所叙述的FLASH模拟EEPROM机制,在存储少量数据时具有使用便捷、存储可靠的优点。但是在存储大量数据或者想实现对FLASH任意地址实现存储访问时,这种机制便不太适合。
所以我们也提供了另外一种思路,用户直接访问FLASH实现数据的存储。如下图所示:分配FLASH后面一段空间来存储数据,用户可以对这段空间的任意地址进行存储或者读取数据,由于FLASH擦除是按扇区擦除,而扇区大小通常为1K、2K、4K(见表2),所以在对扇区写数据时不能直接往里面写数据。在写数据时,应该先开辟出一个和扇区大小相同的缓存区,先将扇区数据读回来,然后在缓存中更改数据,然后擦除扇区,再将数据写进扇区。
这种数据存储方式,只适用于存储非关键数据,例如一些运行日志之类的信息,因为在将扇区数据读取到缓存,然后擦除扇区时,如果此时发生了掉电或者MCU复位的异常情况,将会导致这个扇区数据丢失。关键数据的存储还是要选择FLASH模拟EEPROM这种存储模式。
图5. FLASH存储结构
扇区配置可通过project\at_start_f403a\flash_write_read\inc\flash.h里面的宏配置
- FLASH_SECTOR_SIZE:定义扇区大小,单位是字节,不同型号的扇区大小不一样,详情见表2;
- FLASH_CODE_SIZE:定义程序存储区域大小,单位是字节。
以AT32F403A 1024K Flash容量为例定义存储区:
图6. FLASH存储定义示例
如上图所示,FLASH总计扇区个数为512个,总容量为1024K。分配扇区0~99,共计200K字节用于程序存储;分配扇区100~511共计824K用于数据存储。
所以定义扇区大小FLASH_SECTOR_SIZE为2048,定义程序存储区大小FLASH_CODE_SIZE为为1024*200。
FLASH数据直接访问模式提供了2个函数以供用户使用,分别是写数据、读数据。
- 从FLASH读取数据
void flash_read(uint32_t address, uint16_t *pdata, uint32_t number); |
- address:为FLASH的地址,需要在数据存储区域里面,并且2字节对齐,例如地址可以配置为0x8032002、0x8032004,但是不可以为0x8032003;
- pdata:为读出的数据;
- number:数据读取个数。
- 写数据到FLASH
void flash_write(uint32_t address, uint16_t *pdata, uint32_t number); |
- address:为FLASH的地址,需要在数据存储区域里面,并且2字节对齐,例如地址可以配置为0x8032002、0x8032004,但是不可以为0x8032003;
- data:为写入的数据;
- number:数据写入个数。
上述两种存储模式可以混合使用,以达到既能实现关键数据的可靠存储,也能实现大量数据的存储。以AT32F403A 1024K Flash容量为例定义存储区:
图7. 混合存储定义示例
如上图所示,FLASH总计扇区个数为512个,总容量为1024K。分配扇区0~99,共计200K字节用于程序存储;分配扇区100~507共计816K用于数据存储;分配扇区508~511共计8K用于EEPROM。
演示对EEPROM进行写数据和读数据。
本示例程序在AT32所有系列单片机上通用,不同型号间移植时只需要更改扇区个数EE_SECTOR_NUM、扇区大小EE_SECTOR_SIZE即可,详细介绍见2.2章节。
- 硬件环境:
AT32F403A AT-START BOARD
- 软件环境
EEPROM_Emulation_V2.0.0\project\at_start_f403a\eeprom
注:所有project都是基于keil 5而建立,若用户需要在其他编译环境上使用,请参考
AT32xxx_Firmware_Library_V2.x.x\project\at_start_xxx\templates中各种编译环境(例如IAR6/7,keil 4/5)进行简单修改即可。
- 配置流程
- 初始化EEPROM
- 向EEPROM写数据
- 从EEPROM读数据
- 比较读取的数据和写入的数据是否相等
- 代码介绍
- main函数代码描述
int main(void) { uint16_t i, address;
/* 初始化系统时钟 */ system_clock_config(); /* 初始START板子资源 */ at32_board_init();
/* 初始化flash eeprom */ flash_ee_init();
/* 写数据到 eeprom */ for(i = 0; i < BUF_SIZE; i++) { address = i;
flash_ee_data_write(address, buf_write[i]); }
/* 从eeprom读取数据 */ for(i = 0; i < BUF_SIZE; i++) { address = i;
flash_ee_data_read(address, &buf_read[i]); }
/* 比较读取的数据和写入的数据是否相等 */ if(buffer_compare(buf_write, buf_read, BUF_SIZE) == 0) { at32_led_on(LED3); } else { at32_led_off(LED3); }
while(1) { } } |
- 如果读取的数据和写入的数据相等,则LED3点亮,否则LED3不亮。
演示对FLASH进行直接写数据和读数据。
本示例程序在AT32所有系列单片机上通用,不同型号间移植时只需要更改程序存储区域大小FLASH_CODE_SIZE、扇区大小FLASH_SECTOR_SIZE即可,详细介绍见4.1章节。
- 硬件环境:
AT32F403A AT-START BOARD
- 软件环境
EEPROM_Emulation_V2.0.0\project\at_start_f403a\flash_write_read
注:所有project都是基于keil 5而建立,若用户需要在其他编译环境上使用,请参考
AT32xxx_Firmware_Library_V2.x.x\project\at_start_xxx\templates中各种编译环境(例如IAR6/7,keil 4/5)进行简单修改即可。
- 配置流程
- 向FLASH写数据
- 从FLASH读数据
- 比较读取的数据和写入的数据是否相等
- 代码介绍
- main函数代码描述
int main(void) { /* 初始化系统时钟 */ system_clock_config(); /* 初始START板子资源 */ at32_board_init();
/* 写数据到FLASH */ flash_write(0x8000000 + 1024 * 256, buf_write, BUF_SIZE);
/* 从FLASH读取数据 */ flash_read(0x8000000 + 1024 * 256, buf_read, BUF_SIZE);
/* 比较读取的数据和写入的数据是否相等 */ if(buffer_compare(buf_write, buf_read, BUF_SIZE) == 0) { at32_led_on(LED3); } else { at32_led_off(LED3); }
while(1) { } } |
- 如果读取的数据和写入的数据相等,则LED3点亮,否则LED3不亮。
表4. 文档版本历史
日期 | 版本 | 变更 |
2021.12.15 | 2.0.0 | 最初版本 |
2022.09.07 | 2.0.1 | 修改表1 EEPROM描述错误“由1写0”更改为“由0写1” |