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

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

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

帧头1 帧头2 字段1 字段2 校验
固定值:0x55 固定值:0xAA 设备ID 电压值 前面所有数据异或值
char char short float char
1字节 1字节 2字节 4字节 1字节

数据在发送时涉及到一个大小端的概念,大小端是针对多字节数据的传输,比如上述协议中字段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;}

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

后来有人告诉我可以定义个结构体,然后使用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

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

2021年9月27-29日,ELEXCON深圳国际电子展暨嵌入式系统展即将在深圳国际会展中心(宝安)盛大开幕!届时展会以“嵌入式智能系统,加速中国AIoT技术商用落地”为主题,云集数百家嵌入式系统厂商、AIoT技术与解决方案厂商、MCU/SOC厂商、RISC-V厂商、存储厂商、嵌入式工控板厂商、工业显示/电源厂商、AI芯片与FPGA厂商展示前沿技术、新品及方案。

(0)

相关推荐

  • 嵌入式编程中的高低位交换如何实现?

    最近的协议中遇到字节高低位转换的问题,于是偷懒上网查看,遇到类似的问题,也认识一个新的名字,叫做蝶式交换 问题是这样子的 协议要求字节低位在左,高位在右,对每个字节做转换处理,逐个交换其高低位,例如1 ...

  • 嵌入式开发中的两点编程思想

    嵌入式开发,除了掌握基本的编程语法之外,编程的思想也是很关键,下面说两点编程思想. 1分层思想 嵌入式分层思想,可能你不懂,但你肯定见到过很多类似下面这样的软件结构图. 比如,RT-Thread Na ...

  • 嵌入式开发中为什么很少用设计模式?

    刚开始工作的两年,我做项目写代码不会考虑代码扩展.移植.模块化等,导致项目有新增功能.或修改的时候不知如何下手.今天分享一篇肖遥整理的关于设计模式的文章,希望对大家有帮助. 工作有些年了,每每看到一些 ...

  • 嵌入式开发中需要用到设计模式吗?

    工作有些年了,每每看到一些朋友会问,设计模式需要学吗?好像做嵌入式的从没遇到过需要用设计模式的,所以一直没系统学习,但是我也知道这个很重要,久而久之,到头来还是没学. 这里我说一下自己的看法和思考,来 ...

  • 详解嵌入式开发中的三种程序架构

    前言 在嵌入式软件开发,包括单片机开发中,软件架构对于开发人员是一个必须认真考虑的问题.软件架构对于系统整体的稳定性和可靠性是非常重要的,一个合适的软件架构不仅结构清晰,并且便于开发.我相信在嵌入式或 ...

  • 总结嵌入式开发中的C语言知识点

    有人说C语言是非常简单的,也有人说学了十年还是没有学明白.事实上,编写优质嵌入式C程序并非易事,需要了解相关硬件特性和缺陷,还需要了解相应地编译原理. 关键字 几乎每一门语言中都有关键字,具有特殊功能 ...

  • 嵌入式开发中的三种程序构架

    关注.星标公众号,直达精彩内容 0.前言 在嵌入式软件开发,包括单片机开发中,软件架构对于开发人员是一个必须认真考虑的问题.软件架构对于系统整体的稳定性和可靠性是非常重要的,一个合适的软件架构不仅结构 ...

  • 嵌入式开发中常用的几种通信接口总结

    在嵌入式系统中,板上通信接口是指用于将各种集成电路与其他外围设备交互连接的通信通路或总线. 以下内容为常用板上通信接口:包括I2C.SPI.UART.1-Wire: I2C总线 I2C总线是一种同步. ...

  • 嵌入式开发中的滤波器设计

    什么是滤波器?     各种传感器信号多多少少会携带一些噪声信号,那么通过滤波器就能够更好的降低和去除噪声,还原真实有用信号.     滤波器是一个电路,其去除或"过滤掉"频率分量 ...

  • 嵌入式开发中静态代码分析工具的几种功能和用途

    当前标准的C语言编译器存在普遍只能找出代码中潜在的缺陷,而对程序方案设计并没有效. 使用静态代码分析器有助于提升固件和捕获编译器难以察觉的问题. 用于嵌入式的常见代码静态分析工具 代码静态分析工具,顾 ...