TopSemic · 2021年09月22日

嵌入式开发中自定义协议的解析与组包

在嵌入式产品开发中,经常会遇到两个设备之间的通信、设备与服务器的通信、设备和上位机的通信等,很多时候通信协议都是自定义的,所以就涉及到自定义协议的解析和组包问题。

比如针对下面的这样一个协议:

aijishu_iot.png

数据在发送时涉及到一个大小端的概念,大小端是针对多字节数据的传输,比如上述协议中字段1,假设两字节内容为0x0001,先发送0x01后发送0x00,称为小端模式;先发送0x00后发送0x01,称为大端模式。

假设字段1内容为0x001,字段2内容为0x40533333 (对应为3.3)

假设按照小端方式发送,下面是帧数据:

55 AA 01 00 33 33 53 40 ED

下面来看看如何解析,

若干年前,在第一次面对这种问题时,用的如下傻瓜式的代码方式实现:

#include <stdio.h>  
  
int main()  
{  
    unsigned char Rxbuf[9] = {0x55,0xAA,0x01,0x00,0x33,0x33,0x53,0x40,0xED};  
  
    short DeviceId;  
    float Voltage;  
  
    unsigned char check = 0;  
    int i;  
  
    for(i=0;i<8;i++)  
    {  
        check ^= Rxbuf[i];  
    }  
  
    if(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && Rxbuf[8]==check )  
    {  
        DeviceId=(Rxbuf[3]<<8)|Rxbuf[2];  
        Voltage= *((float *)&Rxbuf[4]);  
  
        printf("DeviceId:%d\n",DeviceId);  
        printf("Voltage:%f\n",Voltage);  
    }  
  
    return 0;  
}  

aijshu_iot1.png

简单来说就是硬来,按照数组的先后顺序逐个重组解析,如果协议比较长,代码里会充斥着很多的数组下标,一不小心就数错了。而且如果更改协议的话,代码要改动很多地方。

后来有人告诉我可以定义个结构体,然后使用memcpy函数直接复制过去就完事了,

#include <stdio.h>  
#include <string.h>  
#pragma pack(1)  
struct RxFrame  
{  
    unsigned char header1;     
    unsigned char header2;     
    short deviceId;    
    float voltage;      
    unsigned char check;  
};  
  
  
int main()  
{  
    unsigned char Rxbuf[9] = {0x55,0xAA,0x01,0x00,0x33,0x33,0x53,0x40,0xED};  
  
    struct RxFrame RxData;  
  
    unsigned char check = 0;  
    int i;  
  
    for(i=0;i<8;i++)  
    {  
        check ^= Rxbuf[i];  
    }  
  
    memcpy(&RxData,Rxbuf,sizeof(Rxbuf));  
  
    if(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && RxData.check==check )  
    {  
        printf("DeviceId:%d\n",RxData.deviceId);  
        printf("Voltage:%f\n",RxData.voltage);  
    }  
  
    return 0;  
}  

嗯,的确是方便了很多。不过该方式仅适合小端传输方式。

再后来,又见到有人用如下代码实现,

#include <stdio.h>
#include "convert.h"

int main()
{
    unsigned char Rxbuf[9] = {0x55,0xAA,0x01,0x00,0x33,0x33,0x53,0x40,0xED};

    short DeviceId;
    float Voltage;

    unsigned char check = 0;
    int i;
    int index = 0;

    for(i=0;i<8;i++)
    {
        check ^= Rxbuf[i];
    }

    if(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && Rxbuf[8]==check )
    {
        index += 2;
        ByteToShort(Rxbuf, &index, &DeviceId);
        ByteToFloat(Rxbuf, &index, &Voltage);

        printf("DeviceId:%d\n",DeviceId);
        printf("Voltage:%f\n",Voltage);
    }

    return 0;
}

其中convert.h如下:

#ifndef CONVERT_H  
#define CONVERT_H  
  
void  ShortToByte(unsigned char* dest, int* index, short value);  
void  FloatToByte(char* dest, int* index, float value);  
  
#endif // CONVERT_H  

convert.c如下:

#include "convert.h"  
#include <string.h>  
#include <stdbool.h>  
  
static bool Endianflag = 0;  
  
void ByteToShort(const unsigned char* source, int* index, short* result)  
{  
    int i, len = sizeof(short);  
    char p[len];  
    memset(p, 0, len);  
  
    if(Endianflag == 1 )  
    {  
        for( i = 0; i < len; i++ )  
            *(p+i) = *(source + *index + len - i - 1);  
    }  
    else  
    {  
        for( i = 0; i < len; i++ )  
            *(p+i) = *(source + *index + i);  
    }  
  
  
    *result = *((short*)p);  
  
    *index += len;  
}  
  
void ByteToFloat(unsigned char* source, int* index, float* result)  
{  
    int i, len = sizeof(float);  
    char p[len];  
    memset(p, 0, len);  
  
  
    if(Endianflag == 1 )  
    {  
        for( i = 0; i < len; i++ )  
            *(p+i) = *(source + *index + len - i - 1);  
    }  
    else  
    {  
        for( i = 0; i < len; i++ )  
            *(p+i) = *(source + *index + i);  
    }  
  
  
    *result = *((float*)p);  
  
    *index += len;  
}  

该方法既可以支持小端模式,也可以支持大端模式,使用起来也是比较方便。

除了上述2个函数,完整的转换包含以下函数,就是将Bytes转换为不同的数据类型,以及将不同的数据类型转换为Bytes。

#ifndef CONVERT_H  
#define CONVERT_H  
  
void  ByteToShort(const unsigned char* source, int* index, short* result);  
void  ByteToInt(unsigned char* source, int* index, int* result);  
void  ByteToLong(char* source, int* index, long long* result);  
void  ByteToFloat(unsigned char* source, int* index, float* result);  
void  ByteToDouble(unsigned char* source, int* index, double* result);  
void  ByteToString(unsigned char* source, int* index, char* result, int length);  
  
  
void  ShortToByte(unsigned char* dest, int* index, short value);  
void  IntToByte(char* dest, int* index, int value);  
void  LongToByte(char* dest, int* index, long long value);  
void  FloatToByte(char* dest, int* index, float value);  
void  DoubleToByte(unsigned char* dest, int* index, double value);  
void  StringToByte(char* dest, int* index, int length, char* value);  
  
#endif // CONVERT_H  

组包的过程和解析的过程正好相反,这里不再赘述。你在开发中遇到这种问题,是如何处理的呢?欢迎留言讨论

作者:wuyage
来源:https://mp.weixin.qq.com/s/hd10wFuiDSZbd_6chWr6qA
aijishu_TopSemic_1.jpg

推荐阅读

更多芯片嵌入式电子技术分享请关注Topsemic嵌入式极术专栏
推荐阅读
关注数
3263
内容数
45
让芯片使用更简单。 专注分享:嵌入式,单片机,STM32,ARM,RTOS,Linux, 软硬件,半导体,电子技术等相关内容。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息