调试数据存储到nandflash的那些事儿(一):关于sizeof()的一些坑
调试数据存储到nandflash的那些事儿(二): 存入nandflash的方式比较
调试数据存储到nandflash的那些事儿(三): 关于移位存入,强制类型转换读出的讨论
调试数据存储到nandflash的那些事儿(四):nandflash底层的简介
调试数据存储到nandflash的那些事儿(五):对nandflash的寻址说明
其实用到sizeof是因为我要把一些数据组成结构体存入进nandflash中,类似这样的方式
nand_address = PCBA_CalculateAddr(PCBA_SysData.u16PcbaSavedCount);//判断是否需要清除一下块
Air_Clear_Block(nand_address);
// apTrace("Note: IN NAND_Write()\r\n");
StorageMng(CMD_WRITE_NANDFLASH, nand_address, (uint8_t *)&_SaveData, sizeof(_SaveData));
// apTrace("Note: OUT NAND_Write()\r\n");
所以接下来我大概概述一下有关nandflash的存储方式(只涉及到应用层,不到底层驱动)。
核心存入nandflash就是这几个步骤:
1.先对要存入的地址按照某种方式计算出来
2.判断要存入的这块地址是否需要清除。因为在nandflash中如果一个地方在写入前有数据存在,那么是写不进去的。必须要把这块地方擦干净才能存储,我们知道nandflash的擦出只能以块为单位进行擦除,擦除后都为高电平1。
所以如果遇到这样一个问题:有一个块前半部分没有数据,那么存入nandflash的时候是不需要擦除的,但是该块的后半段有数值的话,你再擦除整个块,不就把前半段的数值也擦除掉了吗?
所以,在向一个新块写数据时最好要把这个块检查一下,如果有数值,我提前擦掉。避免之后的存储干扰之前的存储。
这样的话,就对你给数据规划nandflash地址的规则有一定要求了,最好是按照地址的由小到大按顺序存储,并且尽量要让数据包从每一块的第一个位置开始进行存储。
比如我们是按照系统时间进行计算nandflash的逻辑地址。后面我会提到如何用时间规划nandflash的存储。
其实这个判断是否清除这个步骤现在也不太需要了,因为我在nandflash的写入驱动中加了一些操作,从结果上看,擦除了该块后,我保留了该块的前半段数据。这样就不用担心了。这个操作耗费了很长时间去调试,详情请看下面链接,内有驱动代码
调试数据存储到nandflash的那些事儿(四):nandflash底层的简介
3.将结构体的指针和结构体长度作为入口参数,让nandflash存储下来。
接下来我把两种存储和写入代码完整的贴一下:
第一种存储方式:
//tTime 是要存入时的时间,因为寻址的方式是通过时间来计算的
//datatype 是数据的类型,支持存储实时数据、分钟数据、小时数据、日数据。寻址方式也跟数据类型有关。
void AirStation_SaveData2Nandflash(HexTime_Typedef* tTime, uint8_t datatype)
{
uint32_t offset = 4; //存储数据时的偏移量,在组完包之后会知道一共有多少数据需要存入。
uint32_t _iMinutes = Compute_CurrentMinutes(tTime); //把时间计算成分钟,跟寻址方式有关。
uint8_t Save_Buff[310]; //这个数组用来把分散的数据组成包
uint32_t nand_address; //计算出的nand的地址
//把分钟数存入数组的前四个位置,其实这里我有一个困惑,为什么需要这种移位,强制类型转换或者memset的方式不行吗。
//后面我们再讨论吧
Save_Buff[0] = (uint8_t) _iMinutes;
Save_Buff[1] = (uint8_t) (_iMinutes >> 8);
Save_Buff[2] = (uint8_t) (_iMinutes >> 16);
Save_Buff[3] = (uint8_t) (_iMinutes >> 24);
//这个Split_Data是干什么的呢,他将第一个入口参数(float)先强制转换为uint32_t
//然后再经过上面的移位赋值给第二个入口参数(uint8_t*)
//因为offset此时等于4,所以把数值从Save_Buff的第五个位置开始移入4个字节。
Split_Data(tTime->year, Save_Buff + offset);
offset += 4; //别忘了让移位增加4个字节
switch (datatype)
{
case RT_DATA:
if (gSystemPara.PM10 == 1)
{
Split_Data(PM_MinData[0].PM10.Avg, Save_Buff + offset);
offset += 4;
Split_Data(PM_MinData[0].PM10.Flag, Save_Buff + offset);
offset += 4;
}
if (gSystemPara.PM25 == 1)
{
Split_Data(PM_MinData[0].PM2_5.Avg, Save_Buff + offset);
offset += 4;
Split_Data(PM_MinData[0].PM2_5.Flag, Save_Buff + offset);
offset += 4;
}
if (gSystemPara.TSP == 1)
{
Split_Data(PM_MinData[0].TSP.Avg, Save_Buff + offset);
offset += 4;
Split_Data(PM_MinData[0].TSP.Flag, Save_Buff + offset);
offset += 4;
}
if (gSystemPara.SO2 == 1)
{
Split_Data(SO2_MinData[0].SO2.Avg, Save_Buff + offset);
offset += 4;
Split_Data(SO2_MinData[0].SO2.Flag, Save_Buff + offset);
offset += 4;
}
if (gSystemPara.NO2 == 1)
{
Split_Data(NO2_MinData[0].NO2.Avg, Save_Buff + offset);
offset += 4;
Split_Data(NO2_MinData[0].NO2.Flag, Save_Buff + offset);
offset += 4;
}
if (gSystemPara.CO == 1)
{
Split_Data(CO_MinData[0].CO.Avg, Save_Buff + offset);
offset += 4;
Split_Data(CO_MinData[0].CO.Flag, Save_Buff + offset);
offset += 4;
}
if (gSystemPara.O3 == 1)
{
Split_Data(O3_MinData[0].O3.Avg, Save_Buff + offset);
offset += 4;
offset += 4;
}
if (gSystemPara.TVOC == 1)
{
Split_Data(TVOC_MinData[0].TVOC.Avg, Save_Buff + offset);
offset += 4;
Split_Data(TVOC_MinData[0].TVOC.Flag, Save_Buff + offset);
offset += 4;
}
break;
case MINUTE_DATA:
break;
case HOUR_DATA:
break;
case DAY_DATA:
break;
default:
break;
}
nand_address = Air_CalculateAddr(tTime, datatype);
//判断是否需要清除一下块
Air_Clear_Block(nand_address);
//apTrace("Note: IN NAND_Write()\r\n");
StorageMng(CMD_WRITE_NANDFLASH, nand_address, Save_Buff, offset);
//apTrace("Note: OUT NAND_Write()\r\n");
}
可以看出在存储时把分散的数据全部组到一个足够大的uint8_t数组中,因为存入nand时入口参数就是uint8_t。组完整个包后,把数组存入nand中。
优点:到后面介绍到存入结构体的方式就会知道,这样更节省nand的空间
缺点:比较繁琐,尤其是我很不喜欢那种移位的操作。所以我才会想到第二种方式。
第一种读取方式
/**************************************
显示列表中的数据listview
***************************************/
void Dialog_RefreshList(void)
{
GUI_HWIN hListView;
uint8_t i, j;
uint32_t baseaddr;
uint8_t Valid_Para;
uint8_t Para_Value[4];
uint8_t k;
float value;
char valuestr[10];
char timestr[18];
LV_FirstTime.minute = 0;
LV_FirstTime.second = 59;
Valid_Para = 1;
hListView = WM_GetDialogItem(guiSysData.hPageWin[PAGE_LISTVIEW-1], GUI_ID_LISTVIEW_DList);
//列数
for(j=0; j<RealDataRow; j++)
{
//行数
for(i=0; i<23; i++)
{
LV_FirstTime.minute = (List_CurrentPage * 23) + i;
if(Bcd2Int(&gTime) <= Hex2Int(&LV_FirstTime) || LV_FirstTime.minute > 59)
{
LISTVIEW_SetItemText(hListView, 0, i, "");
LISTVIEW_SetItemText(hListView, Valid_Para, i, "");
}
else
{
//列表第一列的时间
baseaddr = Air_CalculateAddr(&LV_FirstTime,RT_DATA);
//时间字符串
sprintf(timestr, "20%d-%02d-%02d %02d:%02d", LV_FirstTime.year, LV_FirstTime.month, LV_FirstTime.day, LV_FirstTime.hour, LV_FirstTime.minute);
//Minute_To_T(Compute_CurrentMinutes(&LV_FirstTime) , LV_FirstTime.year , timestr);
//显示时间字符串
LISTVIEW_SetItemText(hListView, 0, i, timestr);
//从nandflash中读取数据
StorageMng(CMD_READ_NANDFLASH, baseaddr + (j*8) + AVG_OFFSET,Para_Value, 4);
if((Para_Value[0] == 0xFF)&&(Para_Value[1] == 0xFF)&&(Para_Value[2] == 0xFF)&&(Para_Value[3] == 0xFF))
{
value = 0.0f;
sprintf(valuestr, "- -"); //转为字符串显示
}
else
{
value = *(float *)&Para_Value;
if(GAS_Sign[j] == 0x01)
{
if(GAS_NUM[j] == 0x01)
{
gSysSetting.Device_uint_Sign = gSysSetting.SO2_Device_uint_Sign;
k=SO2_Mr;
}
else if(GAS_NUM[j] == 0x02)
{
gSysSetting.Device_uint_Sign = gSysSetting.NO2_Device_uint_Sign;
k=NO2_Mr;
}
else if(GAS_NUM[j] == 0x03)
{
gSysSetting.Device_uint_Sign = gSysSetting.CO_Device_uint_Sign;
k=CO_Mr;
}
else if(GAS_NUM[j] == 0x04)
{
gSysSetting.Device_uint_Sign = gSysSetting.O3_Device_uint_Sign;
k=O3_Mr;
}
else
gSysSetting.Device_uint_Sign = 0x00;
//PPM
if(gSysSetting.Device_uint_Sign == 0x00)
{
sprintf(valuestr, "%.3f", value);
}
//PPb
else if(gSysSetting.Device_uint_Sign == 0x01)
{
value = value * 1000;
sprintf(valuestr, "%.1f", value);
}
//ug/m3
else if(gSysSetting.Device_uint_Sign == 0x02)
{
value = (value * k * 1000)/22.4;
sprintf(valuestr, "%.1f", value);
}
//mg/m3
else if(gSysSetting.Device_uint_Sign == 0x03)
{
value = (value * k)/22.4;
sprintf(valuestr, "%.3f", value);
}
else
{
sprintf(valuestr, "%.3f", value);
}
}
//TVOC气体
else if(GAS_Sign[j] == 0x02)
{
gSysSetting.Device_uint_Sign = 0x00;
sprintf(valuestr, "%.3f", value);
}
else
{
//转为字符串显示
sprintf(valuestr, "%.2f", value);
}
}
LISTVIEW_SetItemText(hListView, Valid_Para, i, valuestr);
}
}
if(Valid_Para > RealDataRow)
{
break;
}
Valid_Para++;
}
}
上面的函数是把历史数据显示在emwin的listview上,同存储一样,也是先按照时间和数据类型计算出地址,再从这个地址按照存储时的顺序把数据取出。
优点是每次只取出我想知道的某个数据,而无需把整个包都取出,节省堆栈,代码效率更高。
缺点是读数存在一个前提,代码的耦合高,因为我得知道存储时是按照什么顺序存储的,我才能计算出某个数据的偏移。从这一点上看,用结构体效率不高,因为即便你只想知道一个数我也会把整个包读出来。但是耦合更低,你只需要使用读出来的结构体即可,无需知道顺序,也无需计算位置偏移。
第二种存储方式:
extern Typedef_Float_U32 T_ADC[T_SUM][T_ADC_CHANNEL_SUM];
//u16SavedCount是我的寻址方式用到的,这个时候就不是按照时间来寻址了
//这个函数之前我在调试的时候返回值设为了PCBA_ReadDataFromNand_Typedef这个结构体,我想知道我存入的结构体的具体内容,
//但是每次存储完nand后总会宕机,把返回值去掉后就正常了,后面我们继续讨论
void PCBA_SaveData2Nandflash(uint16_t u16SavedCount)
{
uint8_t i = 0;
uint8_t k = 0;
uint32_t nand_address;
PCBA_ReadDataFromNand_Typedef _SaveData;
_SaveData.u32SaveTime = Bcd2Int(&gTime);
_SaveData.u32PcbaCode = PCBA_SysData.u32PcbaCode;
//GasBD_SysData.u32DeviceCode[cId] = *(uint32_t *)Save_Buff;
for(i=0; i<T_SUM; i++)
{
for(k=0; k<T_ADC_CHANNEL_SUM; k++)
{
_SaveData.T_Part[i][k].u8TestCode = \
PCBA_SysData.u8TestCode[i][k];
_SaveData.T_Part[i][k].T_ADC_RtData.Float_U32.u32Value = \
T_ADC[i][k].Float_U32.u32Value;
_SaveData.T_Part[i][k].T_ADC_Min.Float_U32.u32Value = \
PCBA_SysData.T_ADC_Min[i][k].Float_U32.u32Value;
_SaveData.T_Part[i][k].T_ADC_Max.Float_U32.u32Value = \
PCBA_SysData.T_ADC_Max[i][k].Float_U32.u32Value;
}
}
for(i=0; i<PCBA_TEM_HUM_PA_SUM; i++)
{
_SaveData.TemHumPa_Part[i].TemHumPa_RtData.Float_U32.u32Value = \
PCBA_TemHumPa_RtData[i].Float_U32.u32Value;
_SaveData.TemHumPa_Part[i].TemHumPa_Min.Float_U32.u32Value = \
PCBA_SysData.TemHumPa_Min[i].Float_U32.u32Value;
_SaveData.TemHumPa_Part[i].TemHumPa_Max.Float_U32.u32Value = \
PCBA_SysData.TemHumPa_Max[i].Float_U32.u32Value;
}
for(i=0; i<PCBA_CHANNEL_VOLTAGE_SUM; i++)
{
_SaveData.ChannelVoltage_Part[i].ChannelVoltage_RtData.Float_U32.u32Value = \
PCBA_ChannelVoltage_RtData[i].Float_U32.u32Value;
_SaveData.ChannelVoltage_Part[i].ChannelVoltage_Min.Float_U32.u32Value = \
PCBA_SysData.ChannelVoltage_Min[i].Float_U32.u32Value;
_SaveData.ChannelVoltage_Part[i].ChannelVoltage_Max.Float_U32.u32Value = \
PCBA_SysData.ChannelVoltage_Max[i].Float_U32.u32Value;
}
_SaveData.u16FramStatus = *(uint16_t*)PCBA_FramStatus;
nand_address = PCBA_CalculateAddr(u16SavedCount);
//判断是否需要清除一下块
Air_Clear_Block(nand_address);
//apTrace("Note: IN NAND_Write()\r\n");
StorageMng(CMD_WRITE_NANDFLASH, nand_address, (uint8_t *)&_SaveData, sizeof(_SaveData));
//apTrace("Note: OUT NAND_Write()\r\n");
}
其实核心思想就是什么呢,因为需要存入nandflash的数据都分散在程序的各个模块之中,为了更简便和结构清晰,我把这些分散的数据组成一个结构体,然后把这个结构体整个存入nandflash之中,
优点是结构清晰,避免了很多移位操作,只要我在存储前把这个结构体完善了,那么存入的时候相当方便。
缺点我们在前一节也提到了,那就是会占用更多的空间,在nand空间本来就匮乏的时候这样浪费空间。
从nandflash中读数据:
//u16Num是我寻址的方式用到的,pReadData是我要把数据读到的结构体指针
void PCBA_ReadDataFromNandflash(uint16_t u16Num, PCBA_ReadDataFromNand_Typedef* pReadData)
{
uint32_t nand_address; //nandflash地址
nand_address = PCBA_CalculateAddr(u16Num);
//把结构体pReadData存入到nandflash的nand_address地址处,长度为sizeof(PCBA_ReadDataFromNand_Typedef))
StorageMng(CMD_READ_NANDFLASH, nand_address, (uint8_t *)pReadData, sizeof(PCBA_ReadDataFromNand_Typedef));
}
在读取数据时,我们要先创建一个结构体,然后赋给其指针,让他读到你建立的结构体中。然后你就可以使用结构体中的数据了。如果命名成员名称比较好的话,可读性也非常好。
缺点是在使用清晰的情况下,代码量也增加了。