一、共用体
共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
定义格式为:
union 共用体名
{
共用体成员 1,
共用体成员 2,
...
};
结构体和共用体的区别:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(对齐造成的),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
使用场景:
1.通信中的数据包会用到共用体,因为不知道对方会发送什么样的数据包过来,定义几种格式的包,收到包之后就可以根据包的格式取出数据。
2.节约内存。如果有2个大内存的数据结构,但不会同时使用,可以考虑用共用体来设计。
3.某些应用需要大量的临时变量,变量类型不同,而且会随时更换。而堆栈空间有限,不能同时分配大量临时变量。这时可以使用共用体让这些变量共享同一个内存空间,这些临时变量不用长期保存,用完即丢,和寄存器差不多,不用维护。
二、数据存储
1.浮点数
定义共用体
union eeprom_float
{
float a;
uint8_t b[4];
}float_write,float_read;
// 声明
float_write.a = 3.1415926;
浮点数是4个字节,声明float_write.a=3.1415926,由于共用体共占一段内存,把一个float拆分为4个字节,然后逐字节的写入EEPROM,来达到保存float数据的目的。所以,通过float_read.b即可拆分flaot数据。
读写数据:
for(i=0;i<sizeof(float);i++)
{
Write_AT24c02(0+i,float_write.b[i]);
HAL_Delay(5);
}
for(i=0;i<sizeof(float);i++)
{
float_read.b[i]=Read_AT24c02(0+i);
}
iic读写函数如下:
void Write_AT24c02(uint8_t addr,uint8_t data)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CSendByte(data);
I2CWaitAck();
I2CStop();
}
uint8_t Read_AT24c02(uint8_t addr)
{
uint8_t val;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
val=I2CReceiveByte();
I2CWaitAck();
I2CStop();
return val;
}
2.负数
定义共用体
union eeprom_s16
{
int16_t a;
uint8_t b[2];
}s16_write,s16_read;
//声明
s16_write.a = -777;
思路和浮点数一样。负数在内存中的存储和正数一样,只是最高位用于表示正负。通过拆分有符号数据,然后逐字节的存储。
for(i=0;i<sizeof(uint16_t);i++)
{
Write_AT24c02(33+i,s16_write.b[i]);
HAL_Delay(5);
}
for(i=0;i<sizeof(uint16_t);i++)
{
s16_read.b[i]=Read_AT24c02(33+i);
}
3.多种数据类型
定义共用体
union eeprom_dat
{
uint8_t t1;
uint16_t t2;
uint32_t t3;
int16_t t4;
float f1;
double f2;
uint8_t str[20];
}eeprom_dat_write,eeprom_dat_read;
//声明
eeprom_dat_write.f2 = 3.1415926535;
要确保uint8_t足够大,为了各种类型都能得到安全的存储。
for(i = 0; i < sizeof(eeprom_dat_write.f2); i++)
{
Write_AT24c02(i,eeprom_dat_write.str[i]);
HAL_Delay(5);
}
for(i = 0; i < sizeof(eeprom_dat_write.f2); i++)
{
eeprom_dat_read.str[i] = Read_AT24c02(i);
}
4. 16位/32位数据
union eeprom_u16
{
uint16_t a;
uint8_t b[2];
}u16_write;
union eeprom_u32
{
uint32_t a;
uint8_t b[4];
}u32_write;
// 16数据
u16_write.a = 11521;
for(i=0;i < sizeof(u16);i++)
{
write_24c02(&u16_write.b[i],i,1);
HAL_Delay(5);
}
for(i=0;i<sizeof(u16);i++)
{
read_24c02(&u16_write.b[i],i,1);
}
// 32数据
u32_write.a = 999999;
for(i=0;i < sizeof(u32);i++)
{
write_24c02(&u32_write.b[i],i,1);
HAL_Delay(5);
}
for(i=0;i<sizeof(u32);i++)
{
read_24c02(&u32_write.b[i],i,1);
}
注意:上电判断问题:
如何在设备第一次上电时从EEPROM读取我们设定好的值呢?第七届省赛中题目要求如下:
由于在初始化时EEPROM中存储的数值是不确定的,所以需要向EEPROM先写入我们的值,并且只要在设备第一次运行此程序时才写入,其余时刻直接从EEPROM读取数值。第一次读取的是设定的初值,之后读取到的是修改值。需要加一个判断语句,随便选取读取一个或者两个EEPROM的地址判断是否等于一个随机数。如果判断为真,就将初始值写入EEPROM;判断为假,就不写入初始值,并且判断语句只需要执行一次。
// 判断第一次写标志
uint8_t flag1_24c02,flag2_24c02;
uint8_t val_24c02=10;
// 读取eeprom地址33和34的值作为判断
read_24c02(&flag1_24c02,33,1);
read_24c02(&flag2_24c02,34,1);
if(flag1_24c02!= 10&&flag2_24c02!=10) //第一次上电不满足
{
write_24c02(num,0,3);//写入设置的值
HAL_Delay(10);
write_24c02(&val_24c02,33,2); //保证程序第一次上电只执行一次
HAL_Delay(10);
}
read_24c02(read,0,3);HAL_Delay(10);
地址33和34值都为10的可能性是非常小的,成功的概论还是比较高的。
连续读写
小数:
//共用体:几个不同的变量共同占用一段内存的结构;
union eeprom_float
{
float a;
uint8_t b[4];
}float_write,float_read;
// 显示小数
float_write.a = 3.1415926;
for(i=0;i<sizeof(float);i++)
{
write_24c02(&float_write.b[i],i,1);
HAL_Delay(5);
}
for(i=0;i<sizeof(float);i++)
{
read_24c02(&float_read.b[i],i,1);
}
负数
union eeprom_s16
{
int16_t a;
uint8_t b[2];
}s16_write,s16_read;
// 显示负数
s16_write.a = -777;
for(i=0;i < sizeof(s16);i++)
{
write_24c02(&s16_write.b[i],i,1);
HAL_Delay(5);
}
for(i=0;i<sizeof(s16);i++)
{
read_24c02(&s16_read.b[i],i,1);
}