嵌入式系统中结构体数据类型的存储和读取
在嵌入式单片机系统开发中,系统配置参数通常需要永久存储在外部或者内部的flash / eeprom中。对于系统参数较多时,我们常常使用结构体来进行管理。
如下面的实例,比如这是一个基于单片机开发的某个产品的系统参数。这些参数被定义为系统配置结构体的成员变量。这些成员变量的类型都是不同的,所占的存储空间也不同。通过C语言中的sizeof(SysData)函数可以获得这个结构体类型的存储空间为N个字节。
注意:关于结构体整体占用存储空间的大小,并不是其中每个成员变量的长度简单的相加那么简单,牵扯到另外的知识。
typedef struct
{
//关于系统时钟的变量
uint8_t second;
uint8_t minute;
uint8_t hour;
//阀门开关标志
BOOL IsValveOpen;
ValveStatus ValveNowSts;
//设置温度记录
uint16_t TempSet;
BOOL IsTempMode;
//当前室内温度记录
uint16_t TempNow;
//热量及使用时间记录
uint32_t HeatUsed;
uint32_t TimeUsed;
//自动开关模式及时间设置记录
uint8_t OpenMin;
uint8_t CloseMin;
uint8_t OpenHour;
uint8_t CloseHour;
BOOL IsAutoMode;
//地址
uint8_t MeterAddr[7];
//信道
uint8_t Channel;
}SysData;
这样我们在应用中就能够比较明了、明确的使用这个结构体,比如
SysData data;
data.second = 45;
data.HeatUsed = 13543;
...
那么如何实现对以上的结构体数据在flash或者eepron中进行存储和读取呢?即把完整的N字个节的结构体变量存储在flash或eeprom中连续的N个字节的存储空间中,读取时实现完整的变量读取,并且结构体中的成员变量也被正确赋值。
当然,你可以这么做:
依次取出结构体中每个成员,再依次存入flash或eeprom。读取方法相同。但这样做不免费时费力。
如果我们利用结构体指针,只要几行代码就能实现了。
#define SYS_DATA_ADDR 0 //flash中保存结构体数据的首地址
//单字节逐个保存的子函数,一般不用,但是可以用来说明和解释一些问题
void flashWriteSysData(SysData *data)
{
uint16_t i = 0;
uint8_t temp = 0;
for(i=0;i<sizeof(*data);i++)
{
/*结构体中的成员类型不是一定的,因此,通过内存访问的形式,将结构体中数据按照
字节依次取出,再写入flash。注意,使用结构体指针时,如果语句为:结构体首地址+1
则地址会增加整个结构体的长度。理论上不能使用地址增加的形式访问结构体成员。
如果需要特殊应用,则如下程序所示,先将结构体指针转换为uint8型指针,再通过
地址增加的形式,就可以每次增加一个字节的地址了。*/
memcpy(&temp,((uint8_t *)(data))+i,1); //从结构体所在内存中拷贝一个byte
FLASH_WiteByte((SYS_DATA_ADDR+i),temp); //写入这个byte
}
}
//当然,实际应用中我们写了flash连续读写的子函数,以上函数则可以简化为
void flashWriteSysData(SysData *data)
{
uint16_t len = sizeof(*data); //获取结构体总长度字节数
FLASH_WiteNByte(SYS_DATA_ADDR,(uint8_t *)data, len); //一次性写入全部,由于是按字节连续写入,则必须首先强制把data指针转换成uint8_t类型
}
//读取,一次性把所有数据存储在flash中的数据读入到data指针指向的结构体
void flashGetSysData(SysData *data)
{
uint16_t len = sizeof(*data); //获取结构体总长度字节数
FLASH_ReadNByte(SYS_DATA_ADDR,(uint8_t *)data, len); //一次性全部读出,由于是按字节连续读出,则必须首先强制把data指针转换成uint8_t类型
}
以上的实现方法利用了结构体指针,写入和读出都是一次操作就完成了,读出数据到data指针指向的结构体后,我们就可以像线面一样方便的直接引用了。
uint8_t a = data.second;
uint32_t b = data.HeatUsed;
...
下面进行简单的分析:
入口参数“SysData *data”为结构体类型的指针,data指向该结构体的首地址。
“data+1”则会使该指针地址向后跳过42字节。因为data是结构体类型的指针,而该结构体在内存中占用连续的42字节空间,并被认为是一个变量。那么如果指针+1,其指向的地址将会向后跳过一个完整的结构体存储空间,即42字节。
如果需要使用这个指针来依次访问该结构体的每一个成员变量,即每次指针+1,地址跳过1byte,则需要将该结构体类型的指针转换为unsigned char类型的指针:“(uint8_t *)(data)”,这时候再+1,则地址仅会增加一个字节。