一、介绍
电可擦和可编程只读存储器(EEPROM)可以对字节或字编程和擦除。EEPROM中的数据即使断电也能保持,但Z20K1xx芯片不含EEPROM。然而,闪存可以通过EEPROM仿真软件来模拟EEPROM。Z20K1xx包含两个flash阵列。编程和擦除操作可以在一个数组上进行,同时在另一个数组上执行代码。
二、存储原理
EEPROM仿真包含两个或多个扇区,每个扇区都包含一组闪存扇区。只有一个扇区被选为活动块。存储在活动扇区中的记录列表用于访问数据。每条记录都有一个ID,用来区别于其他记录。记录是一组变量。数据记录的长度是可变的。
记录按顺序编入活动扇区。为了更新记录,软件将新版本的记录写入活动扇区中下一个可用的位置。当读取记录时,软件检索具有匹配ID的最近写入的记录。当活动扇区没有足够的空间容纳新记录时,软件将活动扇区内的所有有效记录复制到其他EE扇区之一。这个新扇区成为活动扇区,之前的活动扇区失效。由于旧记录在交换期间被清除,新的活动扇区应该有空间用于记录更新。交换后,新记录将被写入新的活动扇区。 后续会详细描述,这一交换原理。
三、内存组成
下图为扇区的内存组成列表图包含扇区+记录+数据存储区。
1.扇区
每个扇区包含一个扇区报头,它包括以下部分
sectorlD:扇区标识。每当有一个新的扇区,这个数字就增加1变得活跃。它从1开始。sectorlD最大的部门是活跃的部门。
sectorstart address:扇区起始地址扇区大小:扇区的大小,以字节为单位。checksum:扇区id、扇区起始地址和扇区大小字段之和。
sector valid flag:扇区的有效标志,如果等于一个特殊的值,则表示该扇区有效
sector invalid flag:扇区的无效标志,如果等于特殊值,则表示该扇区为失效。
2.记录
每条记录包含一个记录头,它包括以下部分:
recordlD:记录的标识。
data address:记录的数据地址
record size:记录大小(以字节为单位)。
check sum:recordID、数据地址和记录大小字段之和。
record valid flag:记录的有效标志,如果它等于一个特殊值,则该记录是有效的。record invalid flag:记录的无效标志,如果它等于一个特殊值,则该记录无效。
四、扇区交换
前面提到过存储的原理实际就是活动扇区的交换,实际上扇区的交换就是在活动扇区满足以下这三点时:
①当活动扇区没有足够的空间来写入新记录时
②当在EE初始化期间检测到无效记录头时(可选)。
③当最后一个记录头无效时。
把所有的有效记录从一个活动扇区复制到另一个扇区,下图举例了三个扇区的交换:
初始时,Sector0是活动扇区,
第一次交换后,Sector0变为“invalid”,Sector1变为活动扇区。
第二次交换后,Sector1变为“invalid”,Sector2变为活动扇区。
再了解了存储结构和交换原理后我们就可以理解代码并写一段demo例子了。
五、代码编程
1.结构体初始化
static uint32_t cacheTable[EE_CACHE_RECORD_NUM];
static EE_cache_t cacheConf =
{
cacheTable, /* cache 起始地址 */
EE_CACHE_RECORD_NUM /* cache 缓存大小 */
};
/* 扇区0配置 */
static const EE_SectorConfig_t sectorConf0 =
{
EE_SECTOR_0_ADDR, /* 起始地址 */
EE_SECTOR_SIZE, /* 大小 */
};
/* 扇区1配置 */
static const EE_SectorConfig_t sectorConf1 =
{
EE_SECTOR_1_ADDR, /* 起始地址 */
EE_SECTOR_SIZE, /* 大小 */
};
/* 扇区2配置 */
static const EE_SectorConfig_t sectorConf2 =
{
EE_SECTOR_2_ADDR, /* 起始地址 */
EE_SECTOR_SIZE, /* 大小 */
};
/* 扇区配置数组 */
static const EE_SectorConfig_t* sectorConfig[EE_SECTOR_NUM] =
{
§orConf0,
§orConf1,
§orConf2,
};
/* EEPROM配置 */
EE_Config_t eeConf =
{
.sectorNum = EE_SECTOR_NUM, /* 扇区数量 */
.sectors = sectorConfig, /* 扇区配置 */
.cacheEn = ENABLE, /* cache 使能 */
.cTable = &cacheConf, /* cache 结构体 */
.busyFlag = RESET,
.maxRecordId = EE_MAX_RECORD_ID, /* 最大记录ID */
};
2.系统初始化
static void system_init(void)
{
WDOG_Config_t wdogCfg =
{
.winEnable = DISABLE,
.wait = DISABLE,
.stop = DISABLE,
.debug = DISABLE,
.windowValue = 0,
.timeoutValue = 9600,
.clkSource = WDOG_LPO_CLOCK,
.testMode = WDOG_TST_NORMAL,
};
CLK_SetClkDivider(CLK_CORE, CLK_DIV_1);
CLK_SetClkDivider(CLK_BUS, CLK_DIV_1);
CLK_SetClkDivider(CLK_SLOW, CLK_DIV_8);
if(ERR == WDOG_Init(&wdogCfg))
{
ErrorTrap();
}
if(ERR == WDOG_Enable())
{
ErrorTrap();
}
}
3.初始化EEPROM
EEPROM仿真初始化,最多尝试三次,错误处理判断
while ((i < 3U) && (ret != EE_OK))
{
ret = EE_Init(&eeConf, ENABLE, &CallBack);
i++;
}
if(ret != EE_OK)
{
ErrorTrap();
}
4.写入数据到EEPROM
写入数据到EEPROM,每个记录的ID从0到最大记录ID(EE_MAX_RECORD_ID
),数据大小从1字节到缓冲区大小(BUFFER_SIZE
),验证写入的数据,确保读取的数据与写入的数据一致。
for(id = 0U; id <= eeConf.maxRecordId; id++)
{
size = id + 1U;
if(size > BUFFER_SIZE)
{
size = BUFFER_SIZE;
}
/* Init buffer*/
for (i = 0; i < size; i++)
{
buffer[i] = (uint8_t)(i + id);
}
ret = EE_WriteRecord(&eeConf,id, size, buffer,0, CallBack);
if(ret != EE_OK)
{
/* if writing fails, re-initialize and try again */
ret = EE_Init(&eeConf, ENABLE, &CallBack);
if(ret != EE_OK)
{
ErrorTrap();
}
else
{
ret = EE_WriteRecord(&eeConf,id, size, buffer, 0, CallBack);
if(ret != EE_OK)
{
/* still error after reinitialization */
ErrorTrap();
}
}
}
for (i = 0; i < BUFFER_SIZE; i++)
{
buffer[i] = 0U;
}
ret = EE_ReadRecord(&eeConf,id,size,buffer,&readOutSize,CallBack);
if(ret != EE_OK)
{
ErrorTrap();
}
else
{
if(size != readOutSize)
{
ErrorTrap();
}
for(i = 0; i < readOutSize; i++)
{
if(buffer[i] != i + id)
{
ErrorTrap();
}
}
}
}
5.验证写入的数据
验证写入的数据,确保读取的数据与写入的数据一致。
ret = EE_DeleteRecord(&eeConf,EE_TEST_RECORD_ID,CallBack);
if(ret != EE_OK)
{
ErrorTrap();
}
for(i = 0; i < BUFFER_SIZE; i++)
{
buffer[i] = 0;
}
size = BUFFER_SIZE;
ret = EE_ReadRecord(&eeConf,EE_TEST_RECORD_ID,size,buffer,&readOutSize,
6.验证删除操作
if(ret != EE_ERROR_DATA_NOT_FOUND)
{
ErrorTrap();
}
while(true)
{
CallBack();
}
7.回调函数与错误处理函数
void CallBack(void);
static void ErrorTrap(void);
void CallBack(void)
{
WDOG_Refresh();
}
static void ErrorTrap(void)
{
while(true)
{
CallBack();
}
}