STM32与上位机实现浮点数传输

目录

  • 单片机(STM32)与上位机传输数据的方法
  • 1. 传输整形数据
  • 2. 传输浮点数据
  • 3. 如何打包与解包
  • 单片机(STM32)与上位机传输数据的方法

    在进行单片机程序的开发时,常常需要与其他设备进行通信。一种情况是与其他电路板通信,比如STM32主机与STM32从机通信,STM32从机与树莓派主机通信。一种情况是与上位机通信,上位机软件进行人机交互。这个时候需要进行数据传输,传输数据有两种方式,传输整形数据与直接传输浮点数据。

    1. 传输整形数据

    一种方法是传输整形数据,工业中常用的Modbus就是这种方式。这里以传输16位整形数据为例,一个数据就占用两个字节,可以是正数和负数。

    测试代码:

    int main()
    {
        uint16_t me, you;
        uint8_t data[100];
        me = 120;
        data[0] = me >> 8;
        data[1] = me;
        
        you = (uint16_t)data[0] << 8 | (uint16_t)data[1];
    
        printf("you = %d", you);
    
        return 0;
    }
    

    出来的结果是一样的120

    那么负数怎么办呢?其实是一样的。不管是uint16_t还是int16_t,在内存中的存储都是一样的,区别不在于写,而在于怎么读

    int main()
    {
        uint16_t me, you;
        uint8_t data[100];
        int16_t para;
        me = -120;
        data[0] = me >> 8;
        data[1] = me;
        
        you = ((uint16_t)data[0] << 8 | (uint16_t)data[1]);
        para = (int16_t)((uint16_t)data[0] << 8 | (uint16_t)data[1]);
        printf("me = %d\n", me);
        printf("me = %d\n", (int16_t)me);
        printf("you = %d\n", (int16_t)you);
        printf("para = %d", para);
        
        return 0;
    }
    

    结果:

    上面me是一个uint16_t类型,怎么能直接让它等于-120呢?当然是可以的,只不过调用me的时候,是按照uint16_t类型读取的,结果就是65416,如果按照int16_t类型读取,结果就是-120。

    同理,you也是一个uint16_t类型,you = ((uint16_t)data[0] << 8 | (uint16_t)data[1])是按照移位拷贝的方式将me的值赋给了you,只要按照int16_t类型读取出来,结果就是正确的负数。

    理解了这种思想,在进行单片机与其他设备通信的时候,就可以定义一个数组,uint16_t register1[1000],数组的索引就是数据地址,一个萝卜一个坑。第二个设备(其他单片机或电脑)同样定义一个数组,uint16_t registe2[1000],按照上面的方法一个数据一个数据传输就行了。

    再次注意:直接定义无符号数组即可,传输负数时直接赋值,只要另一端收到数据后按照int16_t类型读取,结果就是正确的负数。

    2. 传输浮点数据

    传输整形方法的缺点是:(1)不能直接传输浮点数,传输浮点数时需要进行倍数处理。例如0.12,将其乘100变成整形的12,上位机收到后除100变成浮点型的0.12。这种方法较麻烦,哪些地址的数据需要进行倍数,需要下位机和上位机同时定义清楚。(2)有符号和无符号类型数据区分。uint16类型数据较简单,直接传输,直接解析,没问题。int16上位机解析时,就需要进行类型转换了。哪些地址的数据要进行(int16_t)类型转换,也要定义清楚。(3)表示的数据范围有限,16位整形无符号数只能到65535,有符号数除2减半。如果是浮点数,乘掉了倍数,表示范围直接缩水。如果是翻100倍,只能表示到655。

    所以,最方便的就是直接传输浮点数,省去很多麻烦。当然浮点数的缺点就是,一个数据要占用4个字节。因此效率是传输整形数据的一半。

    传输浮点数,需要定义一个联合体:

    union float_data
    {
        float f_data;
        uint8_t byte[4];
    };
    

    f_databyte[4]共用4个字节的内存单元,成员f_data是实际使用的数据,成员byte[4]是通信时用的数据,各司其职。

    使用方法:

    #include <stdio.h>
    #include <stdint.h>
    
    union float_data
    {
        float f_data;
        uint8_t byte[4];
    };
    
    int main()
    {
        union float_data me, you;
        me.f_data = 0.12;
        you.byte[3] = me.byte[3];
        you.byte[2] = me.byte[2];
        you.byte[1] = me.byte[1];
        you.byte[0] = me.byte[0];
        printf("you = %f", you.f_data);
    
        return 0;
    }
    

    出来的结果是一样的,0.12。聪明的读者可以发现,meyou对应两个设备。只要按照这种方式进行传输,就可以传输浮点数。传输多个浮点数,meyou就可以定义为一个数组,例如me[100], you[100]

    3. 如何打包与解包

    知道了数据如何传输,第二步就是思考如何进行数据打包和解包了,因为一个数据帧当然是要传输多个数据的。需要两个设备定义好通信协议,才能正确的解析数据。

    数据打包也有两种方式,一种按照功能字,一种按照地址——数据对。

    (1)按照功能字

    这种方法用一个数据位表示功能字,对方设备收到这一帧数据,根据这个功能字就能判断你这一帧数据是什么,然后进行解析。例如一款陀螺仪的数据上传协议为:



    它用第一个字节表示帧头,0x55,第二个字节表示功能字,0x52是角速度输出,0x53是角度输出,单片机读陀螺仪的数据时,按照它给定的这个协议,依次把数据读出来就可以了。

    如果是自己定义通信协议,也可以模仿,这种方式每一帧数据都要进行定义,优点是物理意义明确,缺点是一旦确定了,如果想要修改,两端的设备要同时修改。

    (2)按照地址数据对

    这种方法模拟计算计的存储方式,为每一个数据安排一个地址,请注意这个地址并需要是真正的内存地址,它的核心是“索引”。例如一个数据就可以实现这种功能。uint16_t data[100],数组的索引就是地址。例如我用data[0]表示姓名,data[1]表示年龄。那么姓名的地址就是0,年龄的地址就是1。

    这种方法的优点和缺点与第一种方法相反,物理意义不明确,但移植性强、维护性好。

    下面是我自创的一种通信协议,传输浮点数。前两个字节为帧头,不同帧头分别代表从机主动上传、主机下发修改数据、主机下发查询数据。(这种通信协议为一对一,不支持总线通信)

    (1)单片机主动上传数据:


    发送N个数据(32 bits)一共4N+6个帧字节。

    (2)上位机下发更改数据:

    发送N个数据(32bits)也是一共2N+6个帧字节。

    (3)上位机下发查询数据:

    查询从起始地址开始的N个数据,查询帧是6个字节。下位机收到数据按照上传数据格式上传。

    作者:何为其然

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32与上位机实现浮点数传输

    发表回复