上位机与单片机通过modbus通讯时,上位机发送的float类型数据会以二进制的形式传下来,而单片机编译器中变量以十进制方式显示。此时需要进行二进制和float类型数据的转换。在转换开始前,需要了解float在计算机中是怎样存储的。

一、float数据在计算机中存储方式

举个例子:分析float类型数据12.345在计算机中的存储方式。

①首先将12.345转换为2进制,结果为:

1100.0101100001010001111010111000010100011110101110001

小数转换为二进制方法可参考其他博主的解释,都很详尽。

②对二进制数进行移位,变成1.1000101100……。目的为将整数部分只保留一位。对于这个数来说,在float类型数据中,指数位就是3-1+128=130,也就是10000010,其中3是移位的位数。

至于-1+128的原因,是要保证整数位为1(移位为0)以及移位位数为负数(因为小数点后面是0,所以向右移)等情况下依然可以正常储存数据,具体情况可分别编程验证。


float类型数字在计算机中用4个字节(32位)存储,遵循IEEE-754格式标准。32位数据分为三部分:符号位s(1位)和、指数e(8位)、底数m(23位)。双精度存储方式类似,这里仅以单精度32位为例。

  • 符号位(Sign) :0代表正,1代表为负

  • 指数位(Exponent):存储数据的移位位数,存储上述②中10000010

  • 尾数部分(Mantissa):储存转换并移位后小数点后面的部分。如例子中,就存储

10001011000010100011110(23位)。

至于小数点前的那一位1,因为所有的数在移位之后整数部分都只有一位1,可省略。即使二进制转换后小数点左边为0(如0.001),在移位后小数点左边也是1,只不过小数点是向右移(其他的向左移)。

经以上操作,便可知float类型十进制数12.345在计算机中储存方式为:

0_10000010_10001011000010100011111

二、对二进制数进行float类型逆向解析

我们知道modbus数据是8位一组传输,如果传下来的是float类型数据,需要先把要转换的数据合并成32位。modbus数据的具体传输不在本文讨论范围之内。

那么我们的目标就很明确了:将符号位、指数位和尾数位分别取出来,按照float类型数据的储存规则进行反向操作,就可以解析出原来的float类型数据。


这个过程有几个要点:

①将32位数据中的每一位分别取出。我们可以构建一个32位数据,初始值为1。然后将这个变量与目标数据进行按位与再除以这个变量,便可以得到这一位是1还是0。将变量循环左移,便可将数据中每一位分别取出。比如:要取出data = 10011中的数据

double loop = 1;
int data = 0B10011;
int val0, val1, val2;

val0 = (data & loop) / loop;
loop=loop<<1;

val1 = (data & loop) / loop;
loop=loop<<1;

val2 = (data & loop) / loop;

②根据移位位数计算位数部分对应数据。整数部分很容易,按照正常二进制进行操作即可。比如10011.111,其整数部分就是19。主要是小数部分,十分位对应2的指数为-1,百分位对应2的指数为-2,以此类推。比如10011.111,将其转换为十进制时小数部分为:

2^(-1)+2^(-2)+2^(-3)

③转换完成后加上省略的一位1对应的十进制数据,省略的一位在上一章中有提及。

④尾数部分处理。在函数构造初期,我跟随最直觉的方式——将整数部分和小数部分分开处理再相加。这是不错的,根据移位数对23位尾数部分分别计算可以解析出原始数据。但是当处理很小的数(0.0001)时,方法失效了。因为和大数进行左移不同,很小的数移位位数是负的,也就是向右移。这导致原来针对小数点左移并且对整数和小数分别处理的算法失效了。后来发现,将小数部分和整数部分割裂开是不对的。以10011.111为例,我最初的数据转换构思如下,这样将小数整数割裂开了。

int data_b=0B10011.111
int data_d=1*2^(0)+1*2^(1)+0*2^(2)+0*2^(3)+1*2^(4)+2^(-1)+2^(-2)+2^(-3)

实质上只需要简单的调序,就可以得到2的指数连续变化的统一转换模式:

int data_b=0B10011.111
int data_d=1*2^(4)+0*2^(3)+0*2^(2)+1*2^(1)+1*2^(0)+2^(-1)+2^(-2)+2^(-3)

三、代码实现

float Data_cov(unsigned long data){//转换为float数据
    int i ;
    float Data=0;
    Uint32 loo = 1;//用于取出某一位
    int edot;//2的指数
    unsigned long temp;
    int dotch = 0;//移位数
    loo=(unsigned long)1<< 23;//强制类型转换

    for (i = 0; i < 8; i++) {//算移位位数
        temp = (data & loo) / loo;
        dotch += temp * pow(2, i);
        loo *= 2;
        if (i == 7) {
            loo = 1;
            dotch = dotch-127;//移位位数,整数位数需+1
            edot = dotch - 23;//float最低位对应的指数
        }
    }

    for (i = 0; i < 23; i++) {//取尾数部分数据

        temp = (data & loo) / loo;
        Data += temp * pow(2, edot);
        edot++;
        loo *= 2;
    }
    Data += pow(2, dotch);//取计算机省去一位数据
    return Data;
}

附 接收上位机数据程序备份

//接收上位机发送的float数据
        data = 0;
        data |= ReceValue[0];
        data = data << 16;
        data |= ReceValue[1];
        if(Data_cov(data)) Kp = Data_cov(data);

        data = 0;
        data |= ReceValue[2];
        data = data << 16;
        data |= ReceValue[3];
        if(Data_cov(data)) Ki = Data_cov(data);

        data = 0;
        data |= ReceValue[4];
        data = data << 16;
        data |= ReceValue[5];
        if(Data_cov(data)) V_ref = Data_cov(data);

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐