在嵌入式产品开发中,经常需要处理设备之间的通信、设备与服务器的通信、设备和上位机的通信等。很多情况下,通信协议都是自定义的,这就涉及到了自定义协议的解析和组包问题。
比如对于下面这样一个协议:
帧头1 | 帧头2 | 字段1 | 字段2 | 校验 |
---|---|---|---|---|
固定值:0x55 | 固定值:0xAA | 设备ID | 电压值 | 前面所有数据异或值 |
char | char | short | float | char |
1字节 | 1字节 | 2字节 | 4字节 | 1字节 |
在发送数据时,涉及到了大小端的概念。大小端是用于多字节数据传输的方式。以协议中的字段1为例,假设字段1的值为0x0001,在小端模式下先发送0x01,再发送0x00;在大端模式下先发送0x00,再发送0x01。
假设字段1的值为0x001,字段2的值为0x40533333(对应3.3)。
如果按照小端方式发送,帧数据如下所示:
55 AA 01 00 33 33 53 40 ED
现在我们来看看如何解析这个数据。
很多年前,当我第一次面对这种问题时,我使用了下面这种简单直接的代码方式来实现解析:
#include
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;iif(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && Rxbuf[8]==check )
{
DeviceId=(Rxbuf[3]float *)&Rxbuf[4]);
printf("DeviceId:%d\n",DeviceId);
printf("Voltage:%f\n",Voltage);
}
return 0;
}
简单来说就是硬来,按照数组的先后顺序逐个重组解析,如果协议比较长,代码里会充斥着很多的数组下标,一不小心就数错了。而且如果更改协议的话,代码要改动很多地方。
后来有人告诉我可以定义个结构体,然后使用memcpy函数直接复制过去就完事了,
#include
#include
#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;iif(Rxbuf[0]==0x55 && Rxbuf[1]==0xAA && RxData.check==check )
{
printf("DeviceId:%d\n",RxData.deviceId);
printf("Voltage:%f\n",RxData.voltage);
}
return 0;
}
嗯,的确是方便了很多。不过该方式仅适合小端传输方式。
再后来,又见到有人用如下代码实现,
#include
#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;iif(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
#include
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 source + *index + len - i - 1);
}
else
{
for( i = 0; 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 source + *index + len - i - 1);
}
else
{
for( i = 0; 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厂商展示前沿技术、新品及方案。
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !