最近有个项目,需要频繁的操作移远M26的内部flash。众所周知,flash的擦写次数为10W次,超过这个次数后flash就没有用了。这个项目需要频繁的对其进行写操作,要想设备工作3~5年,10W明显不够用呀。
通过百度和谷歌查找解决办法,找到一个“平衡磨损算法”,大体思想就是将读写操作平均在每一块FLASH上。参见Leeee的博客:Flash存储器磨损均衡原理及实现。当然了只是理论知识,没有具体的实现。而且这个有一个局限性,它是建立在flash可以按字节擦写的。一般芯片得内部flash都不是按字节擦写的,为了读写速度,大多是按页或者块擦写。查看了一下M26 OpenCPU的指导手册flash相关章节“File System API”,它是以文件系统形式进行操作的,指导手册参见:Quectel_M26-OpenCPU_User_Guide_V1.0
这个应该是不好实现平衡磨损算法了,而且手册中2.2.2章节有一句话,很刺眼,如下:
不支持用户访问,我只能呵呵了,那么大的区域没法访问,浪费呀。那是不是这个项目就要黄了呢?还是要增加外部flash芯片呢?通过翻看移远提供的opencpu开发包里的文档,里面有一个篇文章是介绍安全数据区的,文档如下:Quectel_OpenCPU_Security_Data_Application_Note_V1.0
通过阅读该文档,发现这个其实就是操作内部flash,可以实现断电保持功能。介绍如下:
可惜的是,他也不是按字节访问的,而是按块,他被分成13块,每个块字节数不定,具体介绍如下:
所有介绍详情参见手册,手册不长,也就11页,而且有用部分也就5、6页.
好在每个块最少也有50字节的存储量,对于这个应用完全够了。既然不能按照常规的平衡磨损算法来实现增加FLASH擦写次数,那就退而求其次,使用我的粗暴处理方式,一个块可以擦写10W次,13块不就是130W次了,为了保险起见,每个块擦写99900次。
呵呵,这样处理方法的思路就出来了,先对第一个块进行访问,当其擦写次数达到99900次时,开始对第二个块访问,不再对第一个块进行写操作,以次类推,不就有99900*13=1287000次。每次要将当前次数写入数据区。每十秒操作一次,也就是3757h,每天工作2~3小时,差不多3~5年,完全可以满足要求了。
废话不多说,直接上代码:
// 保存参数,工作时间及已经工作时间
if(++timerCnt[2] >= 10)
{
for(i = 0; i < 13; i++)
{
Ql_memset(pchData, 0x0, sizeof(pchData));
dataLen = Ql_sprintf(pchData, "<-- i:%d -->\r\n",i);
Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
ret = Ql_SecureData_Read(i+1, &g_WorkTimeRd, 32);
if(32 == ret)
{
// 读出擦写次数
ReadTimes[i] = (u32)g_WorkTimeRd[28]|(u32)(g_WorkTimeRd[29] << 8)|(u32)(g_WorkTimeRd[30] << 16)|(u32)(g_WorkTimeRd[31] << 24);
// 小于最大次数,继续使用当前块
if(ReadTimes[i] < WRITE_FLASH_TIMES_MAX)
{
Ql_memset(pchData, 0x0, sizeof(pchData));
dataLen = Ql_sprintf(pchData, "<-- Read back data from security data region. Security data region index:%d WriteFlashCnt:%d-->\r\n",i+1,ReadTimes[i]);
Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
WriteFlashIndex = i+1;
Ql_memset(pchData, 0x0, sizeof(pchData));
dataLen = Ql_sprintf(pchData, "<-- Security data region index:%d -->\r\n",WriteFlashIndex);
Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
break;
}
}
// 读事变,表示当前块还没有操作过,后面使用当前块
else
{
WriteFlashIndex = i+1;
Ql_memset(pchData, 0x0, sizeof(pchData));
dataLen = Ql_sprintf(pchData, "<-- Fail to read back data from security data region. %d -->\r\n",ret);
Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
break;
}
}
// 全部操作完,重启系统
// if(i >= 13)
// {
// Ql_memset(pchData, 0x0, sizeof(pchData));
// dataLen = Ql_sprintf(pchData, "<-- FLASH 使用期限已到,将关闭系统 -->\r\n");
// Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
//
// Ql_Reset(0); // 关闭连接,重新启动系统
// }
// 擦写次数累加
WriteFlashCnt++;
// 超过最大次数重新计数
if(WriteFlashCnt > WRITE_FLASH_TIMES_MAX)
{
Ql_memset(pchData, 0x0, sizeof(pchData));
dataLen = Ql_sprintf(pchData, "<-- Security data region index:%d 缓存已满 -->\r\n",WriteFlashIndex-1);
Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
WriteFlashCnt = 1;
}
// 处理要保存的数据
g_WorkTimeWr[0] = (u8)g_WorkTimeCnt;
g_WorkTimeWr[1] = (u8)(g_WorkTimeCnt>>8);
g_WorkTimeWr[2] = (u8)(g_WorkTimeCnt>>16);
g_WorkTimeWr[3] = (u8)(g_WorkTimeCnt>>24);
g_WorkTimeWr[4] = (u8)g_WorkTimeSet;
g_WorkTimeWr[5] = (u8)(g_WorkTimeSet>>8);
g_WorkTimeWr[6] = (u8)(g_WorkTimeSet>>16);
g_WorkTimeWr[7] = (u8)(g_WorkTimeSet>>24);
g_WorkTimeWr[8] = (u8)Receive_sjon.nPowerSwitch;
g_WorkTimeWr[9] = (u8)Receive_sjon.nMassageType;
g_WorkTimeWr[10] = (u8)Receive_sjon.nIntensityType;
g_WorkTimeWr[11] = (u8)Receive_sjon.nHeatType;
g_WorkTimeWr[12] = (u8)Receive_sjon.nPressureSwitch;
g_WorkTimeWr[28] = (u8)WriteFlashCnt;
g_WorkTimeWr[29] = (u8)(WriteFlashCnt>>8);
g_WorkTimeWr[30] = (u8)(WriteFlashCnt>>16);
g_WorkTimeWr[31] = (u8)(WriteFlashCnt>>24);
// 保持数据
ret = Ql_SecureData_Store(WriteFlashIndex, &g_WorkTimeWr, 32);
if (ret !=QL_RET_OK)
{
Ql_memset(pchData, 0x0, sizeof(pchData));
dataLen = Ql_sprintf(pchData, "<-- Fail to store critical data! Cause:%d -->\r\n", ret);
Ql_UART_Write(UART_PORT1,(u8*)pchData,dataLen);
}
timerCnt[2] = 0;
}
这段代码只需要放在定时器1S中断函数里即可。当然了,这个并没有经过长时间的验证,我只是验证过50,100,120次的,每一秒读写一次。验证结果如下:m26 flash平衡磨损算法测试日志
对于以上代码不保证稳定性和安全性,后续会用在产品上进行测试,随时更新测试状态。小弟c语言功底浅薄、读者如有发现不足之处,烦请指出,谢谢。
欢迎读者提出疑问,可以加群讨论:838839442 微信公众号:物联网技术交流与学习