用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

快闪存储器(英语:flash memory),是一种电子式可清除程序化只读存储器的形式,允许在操作中被多次擦或写的存储器。这种科技主要用于一般性资料存储,以及在电脑与其他数字产品间交换传输资料,如储存卡与U盘。闪存是一种特殊的、以宏块抹写的EEPROM。早期的闪存进行一次抹除,就会清除掉整颗芯片上的资料。(摘自中文*

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

前言 | 遇到的问题

前段时间越野组的同学开始解决Flash存储问题,遇到一个困难是浮点型的数据如何存入Flash,并从Flash中读出。打开逐飞开源库,我们会发现Flash写入函数要求传入的是32位的无符号整型数据,这意味着浮点型的数据无法直接写入Flash中。那我们有什么办法能够将浮点型数据保存到Flash中,并从中读回吗?方法是有的,在本篇文章中我将介绍我的两种解决思路。

解决思路一 | 联合体

我们知道C语言中有强制类型转换的存在,如果我们直接把浮点型数据赋值给整型变量,那么就会发生强制类型转换,使得浮点数中的小数部分丢失,这显然不是我们想要的结果。那我们有什么办法可以绕开强制类型转换,把丢失的小数部分保存下来吗?这里我们可以尝试用联合体解决这个问题。

联合体

大家可能会对联合体很陌生,甚至没听说过有这个东西。这里是我写的一个函数,用来实现整型变量保存浮点型数据,先让我们通过这段代码认识结构体。

/*----------------------*/
/*	 浮点型转整型存储	*/
/*======================*/
unsigned long ex_float2int(float value){
//	定义联合体
	union{
		float float_value;
		unsigned long int_value;
	}c;
//	存储数据转换
	c.float_value = value;//以浮点型存入联合体
	return c.int_value;//以整型返回
}

在函数的开始定义了一个联合体,我们发现联合体的定义与结构体相似。这里我来讲讲我对于联合体的理解:我们知道,变量中的数据是存储在内存中的,每个变量就像是一个小房间,房间里住着变量存储的数据,而房门外有门牌号标识着房间的位置。联合体则类似于合租屋,联合体内的每个成员变量共享同一个房间,而房间的大小取决于占用内存空间最大的那个成员变量。所以我们在定义联合体时,编译器就会为联合体分配好内存空间,而联合体内的每一位成员变量共享这片内存空间。

回到代码中,这里我定义了一个联合体,名字叫做c。联合体中的成员变量分别是浮点型的float_value和无符号长整型的int_value。通过查阅资料我们了解到,计算机中长整型变量和浮点型变量所占的内存空间大小都是4字节,所以我们创建的这个联合体会占用四个字节大小的内存空间。

当我们将函数的传入参数value赋值给联合体中的成员变量float_value时,相当于是向联合体所占的内存空间中写入了value的二进制表达形式(同时因为两者同为浮点型,所以没有发生强制类型转换),比如3.30的二进制表达以十六进制表示就是0x40533333。这之后我们再用联合体中的无符号长整型成员变量将联合体中的数据读出,数值就会变成1079194419虽然以整型读出和以浮点型读出的数值不同,但二者在内存空间中的二进制组合形式是相同的。

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

我们发现,不管是以无符号长整型变量读出,还是以浮点型变量读出,联合体的内存空间中保存的二进制组合都是不变的。所以我们只需把浮点型数据存入联合体中,之后再以无符号长整型将数据读出内容并交给其他变量保存,就成功绕过强制类型转换了。

成功用整型变量保存好浮点型数据后,我们需要做的就是反向操作读回原数据了。这一步该怎么做相信你们已经有答案了,尝试下自己动手吧~

解决思路二 | 指针强制类型转换

指针可能是大多数同学比较讨厌的一个内容,它的用法有些让人摸不着头脑。但在理解了联合体的思路之后,指针强制类型转换思路应该不会很难理解。这里我复制了联合体的函数,让我们对它进行一点小修改:

unsigned long ex_float2int(float *value){
    return *((unsigned long*)value);//指针强制类型转换(注意:函数的传入参数value从变量变成了指针)
}

可以发现,修改后的代码变成了一行,让我们来看看它的执行结果:

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

可以发现,我们获得了和联合体同样的结果,这是如何实现的呢?我们把这行代码拆解出来分析看看:

首先我们会用到函数的传入参数:浮点型指针value。我们在调用函数时,把浮点型变量fvalue的地址传给了它,用框图的形式可以这样表达:

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

接着我们用(unsigned long*)把浮点型的指针value(读取4个字节)强制转换成了无符号长整型指针(读取4个字节),然后我们用这个无符号长整型指针去到fvalue的地址上将数据(二进制组合)以无符号长整型(读取4个字节)读出,然后通过return返回读出的数值:

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

我们发现这里指针对数据的访问和我们在联合体中所做的操作是类似的,都是读取同一块内存空间里面的二进制组合。只不过根据需求的不同,我们把这段二进制组合理解成了不同的类型:浮点型和无符号长整型。

关于指针强制类型转换的更多内容以及具体描述可以参看这篇博文:c语言指针强制类型转换的应用

在里面你肯定能领悟更多关于指针强制类型转换的应用!

至此我们同样实现了一开始的目标:把浮点型数据放入无符号长整型变量保存。我们发现以上两种方法的共同之处是二进制组合不变:只要最基本的内容没有改变,我们就可以以事先规定好的方式理解这段内容。

Flash操作

讲解完思路后你可能还想多了解些Flash相关的操作,那么可以接着往下看。

用一个不太恰当的比喻来形容Flash的话,它就有点儿类似黑板,黑板上空空如也的时候我们才能在上面书写新的内容。如果黑板已经写满了内容,我们则需要将原来的内容擦除后才能够书写新的内容上去。不过Flash是一块不那么“智能”的黑板,如果你想修改黑板中已有的一部分内容,它会要求你先把整块黑板擦除干净,接着才能把新的内容写到原来的位置上。(这意味着你不想修改的部分内容同样会被擦除掉)

所以我们每次在将数据写入Flash前,都需要先检查对应的扇区页上是否写进过数据。

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

如果对应的扇区页上写进过数据,我们则需要先将整个扇区擦除,然后才能往其中写入数据:

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

  • 扇区编号

我们可以把扇区理解成一个个黑板,假如教室里放着四块黑板,我们可以选择其中的任意一块来填写内容。(注意:低编号扇区(数字小)中可能存储着代码,我们尽量不要擦除低编号扇区)

  • 页编号

如果我们把扇区理解成黑板,那么页编号就可以理解成是黑板的一部分:有时候我们的内容填不满整块黑板。为了不每次添加新内容都将整块黑板擦除,我们可以把黑板分成几个区域,再把我们的内容填入到空白的区域当中去。

确保要写入的扇区页为空白后,我们才将数据写入到Flash当中:

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

  • 数据地址

数据地址指的是数组的首地址,我们先把要写入Flash的数据保存到一个数组中,然后再把数组的首地址传给函数,让函数可以根据首地址读取数组内容。

正如我们之前提到的,这里我们传入的数组类型是32位无符号整型。

  • 数据长度

要往Flash中写入数据的个数。从函数中我们知道,凑不满一页数据时,剩余的空间会被0xff填充。

用整型变量存储浮点数? | 智能车Flash存储思路分享 - 联合体和指针强制类型转换

上一篇:微软Office默认禁用Flash开启方法


下一篇:STM32F1C8T6音频数据的Flash读取与DAC播放