Flash bucket内存技术
插图略掉了,博客上传插图真的很麻烦,你们觉得好,就让强子把文章发出来吧。
Flash写操作,必须先擦除后再写操作,而一般来说,擦除至少是一页。当使用Flash保存参数时,如果参数不是同时一起写入的,最简单的做法可以是一页保存一个参数,但是这种做法的Flash利用率很低下,当参数较多时浪费大量的资源。因此,设计一种基于内存桶的Flash内存控制技术bucket。本文以cotex M0 上Flash用作参数保存为例。
内存桶bucket
内存桶bucket的定义如下:
因此,有以下解析位:
此外,桶占用标志字节和桶校验初值定义如下:
因为Flash擦写最小单位是页,因此我们把一页分成一个个bucket桶单位,具体划分可以如下:
示意图如下:
bucket内存读写实现
1) 写操作
写操作先把要操作的参数页的有用数据备份到中转备份页上,然后先擦除目标参数页,再为目标参数寄存器构建参数桶并写到目标参数页上,最后把备份页上的除那个目标参数桶之外的有用的参数桶统统写回来。
这里先引入一个参数树的概念,其被我用来管理所有的参数页上的参数,用于联系参数寄存器与参数桶,定义如下:
typedef struct
{
U8 addr; // 寄存器桶地址,对应参数页上的参数桶索引
U8 len; // 寄存器数据长度,对应桶数据区长度
}S_PARA_TREE;
const S_PARA_TREE Tab_ParaTree[NUM_PARA_ITEM] =
{
…
};
通过参数树Tab_ParaTree就可以通过把参数寄存器排序,以其索引为寄存器地址,用户根据该寄存器地址就能找到对应的项,从而得到对应的以参数业中桶索引为地址的寄存器桶地址以及参数的数据长度。
参数寄存器写操作实现如下:
U8 memWPara(U8 reg, U8 page)
{
U8 i, j;
/* FLASH控制器初始化 */
R_FDL_Init();
/* 把写入数据备份到i2cmem.i2c_buf后半段 */
miscMemmove(i2cmem.i2c_buf+HALF_OF_I2CBUF,i2cmem.i2c_buf,HALF_OF_I2CBUF);
/* 开始bucket写操作,重试次数为3 */
for(i=0; i<3; i++)
{
/* 1) 备份目标参数页page中有效的参数桶到中转备份页 */
if(!memPageBackup(page))
continue;
/* 2) 擦除目标参数页page */
i2cmem.i2c_daddr = page;
if(PFDL_OK != R_FDL_Erase())
continue;
/* 3) 计算参数寄存器reg对应的地址、参数长度;同时构建 */
/* 目标参数的桶结构;最后写入目标参数的桶到FLASH。 /
i2cmem.i2c_raddr = Tab_ParaTree[reg].addrBYTES_OF_BUCKET;
memBucketFill(i2cmem.i2c_buf+FALF_OF_I2CBUF,Tab_ParaTree[reg].len);
i2cmem.i2c_num = BYTES_OF_BUCKET;
if(memE2ROM_W())
continue;
/* 4) 用中转备份页上回写除已经写的目标参数之外的参数桶。 /
for( j=0; j
if( j != reg )
{
if(!memRParaBucket(j,EEPAGE_BACK))
break;
i2cmem.i2c_daddr = page;
if(PFDL_OK != R_FDL_Write())
break;
if(PFDL_OK != R_FDL_Verify())
break;
}
}
/* 5) 检查是否成功回写,如不成功则重试 */
if( j >= NUM_PARA_ITEM )
break;
}
if( i > 3 )
return 1;
return 0;
}
/ 中转备份页操作 /
static U8 memPageBackup(U8 page)
{
U8 i;
/ 擦除中转备份页EEPAGE_BACK /
i2cmem.i2c_daddr = EEPAGE_BACK;
if(PFDL_OK == R_FDL_Erase())
{
i2cmem.i2c_num = BYTES_OK_BUCKET;
i2cmem.i2c_raddr = 0;
for( i=0; i
/* 遍历读取目标参数页参数桶 */
i2cmem.i2c_daddr = page;
i2cmem.i2c_raddr+=BYTES_OF_BUCKET;
R_FDL_read();
/* 把有效桶备份到中转备份页,记住备份的是有效桶 */
if(memBucketUsed(i2cmem.i2c_buf))
{
if(memBuckedCheck(i2cmem.i2c_buf))
return 0;
i2cmem.i2c_daddr = EEPAGE_BACK;
if(PFDL_OK != R_FDL_Write())
return 0;
if(PFDL_OK != R_FDL_Verify())
return 0;
}
}
return 1;
}
return 0;
}
/* 检查内存桶有没有被使用 */
static U8 memBucketUsed(U8 *bucket)
{
if(bucket[BIT_BUCKET_USED] == BYTE_BUCKET_USED)
return 1;
return 0;
}
/* 验证内存桶的校验是否准确 */
static U8 memBucketCheck(U8 *bucket)
{
U8 i, ret;
ret = (U8)BYTE_BUCKET_CHECK;
for(i=0; i
if( !ret )
return 0;
return 1;
}
/* 构建参数桶结构 */
static U8 memBucketFill(U8 *buf, U8 len)
{
U8 i, var;
/* 检查数据长度是否超过桶限制 */
if(len>=BYTES_OF_BUCKET-3)
return 0;
/ 构建参数桶结构 */
var = BYTE_BUCKET_CHECK;
i = BYTES_OF_BUCKET-1; // exccept check bit
while(i)
{
i--;
switch(i)
{
case 1:
i2cmem.i2c_buf[1] = len;
break;
case 0:
i2cmem.i2c_buf[0] = BYTE_BUCKET_USED;
break;
default:
i2cmem.i2c_buf = buf[i-2];
break;
}
var ^= i2cmem.i2c_buf;
}
i2cmem.i2c_buf[BIT_BUCKET_CHECK] = var;
return BYTES_OF_BUCKET;
}
2) 读操作
参数寄存器的读取,通过参数树查询得到地址和长度并读取,对结果解析得到最终的寄存器内容。实现如下:
U8 memRPara(U8 reg,U8 page)
{
i2cmem.i2c_raddr = Tab_ParaTree[reg].addrBYTES_OF_BUCKET;
i2cmem.i2c_daddr = page;
i2cmem.i2c_num = BYTES_OF_BUCKET;
R_FDL_Read();
/ 检查参数桶是否是有效,防止给予错误的参数 /
if(memBucketUsed(i2cmem.i2c_buf))
{
/ 验证校验字节 /
if(memBucketCheck(i2cmem.i2c_buf))
return 0;
/ 提取寄存器参数内容 /
i2cmem.i2c_num = i2cmem.i2c_buf[BIT_BUCKET_LEN]; miscMemmove( i2cmem.i2c_buf, i2cmem.i2c_buf+BIT_BUCKET_DATA,
i2cmem.i2c_num);
return 1;
}
return 0;
}
同时,提供了一个读取参数桶数据的方法,其用于备份数据还原,这个方法不排斥非有效的参数桶,即当读取的桶为unused时,返回1。
U8 memRParaBucket(U8 reg,U8 page)
{
/ 读取寄存器参数桶 /
i2cmem.i2c_raddr = Tab_ParaTree[reg].addrBYTES_OF_BUCKET;
i2cmem.i2c_daddr = page;
i2cmem.i2c_num = BYTES_OF_BUCKET;
R_FDL_Read();
/* 验证使用的参数桶的校验,保证读取无误 /
if(memBucketUsed(i2cmem.i2c_buf))
{
if(memBucketCheck(i2cmem.i2c_buf))
return 0; // read fail
}
/对于未占用的桶读取数据无意义,仍返回1*/
return 1;
}
文章评论(0条评论)
登录后参与讨论