调试数据存储到nandflash的那些事儿(二): 存入nandflash的方式比较

书接上回我们讨论了sizeof的坑 

调试数据存储到nandflash的那些事儿(一):关于sizeof()的一些坑

其实用到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));
}

在读取数据时,我们要先创建一个结构体,然后赋给其指针,让他读到你建立的结构体中。然后你就可以使用结构体中的数据了。如果命名成员名称比较好的话,可读性也非常好。

缺点是有的时候我们只需要一个4字节的数,但我们需要开一个结构体大小的堆栈,这就造成了资源的浪费。

发布了26 篇原创文章 · 获赞 0 · 访问量 3008

猜你喜欢

转载自blog.csdn.net/nianzhu2937/article/details/99334165