当前位置:网站首页>通讯协议设计与实现

通讯协议设计与实现

2022-07-07 10:31:00 咕咚.萌西

通讯协议分类

硬件协议

在物理层进行数据传输时,发送端和接收端之间需要遵循相应的硬件协议,例如我们通过IO采集一个电压信号,当输入电压高于2V时候,硬件会认为采集到一个高电平,低于2V则认为是一个低电平。常见的光纤,串口,CAN总线我们将其划分为硬件协议,他们是数据传输的载体。

软件协议

根据总线的物理特性,在此之上可以设计出各种各样的软件协议,此处涉及到的协议讲解部分为软件协议,后面简称协议。例如通过IO可以设计出SPI通讯协议和IIC协议,通过CAN总线可以设计出CANOpen协议,通过串口可以设计出Modbus协议。

帧格式

在硬件基础上进行数据传输时需要设计一个帧格式,例如串口传输的数据通常是以字节为单位,那么协议设计时传输的最小的单位也必须是字节,通讯帧通常分为两类,一类是定长帧,一类是不定长帧。

定长帧

所有帧长度都相同,接收者先接收完指定长度字符,然后根据报文类型做进一步处理,这种协议可拓展性较低,适合简单数据传输。

不定长帧

第一种:通讯双方根据功能划分,划分为不同的报文类型,每种类型的长度不尽相同,但是接收者和发送者对每种类型的数据长度都是提前约定好的,传输数据中不包含长度信息。

第二种:帧长度不固定,每帧可以分为三个部分,帧头,数据,校验,其中帧头和校验的长度是固定的,数据的长度是可变的,帧头中包含长度信息,当接收完一个完整的帧头后进行解析,通过读取其中的长度信息获取到剩余数据的长度,然后继续接收,直到接收到校验,一帧数据接收完成,然后进行数据的校验,校验成功进行下一步处理,这种情况下接收端在发送前并不清楚需要接收的数据长度,相比第一种协议可拓展性更强,可以用于传输文件等超长信息。

通讯帧设计

起始标识帧头数据校验结束标识
1 byte16 bytesn bytes1 byte1 byte

通讯报文主要分成两个部分:

  • 用户层包含帧头和数据,这部分定义了报文的类型和传输的内容
  • 传输层为了保证数据的准确性和完整性,加上了起始标识,校验和结束标识

起始标志和结束标志

为了避免在数据中出现起始标识和结束标识,因此对数据中出现该字符时需要进行转义。

起始标识结束标识0xdb
0x550x560xdb
0xdb 0xdc0xdb 0xdd0xdb 0xde

帧头

类型指令帧号总长度数据长度
2 byte2 byte2 bytes4 bytes4 bytes
  • 类型和指令用于告知接收者如何处理数据
  • 帧号和总长度用于传输大数据,例如文件
  • 数据长度则代表数据区的字节数

数据

Data
n bytes

数据长度不固定,是可变的,接收端会根据帧头中包含的长度信息接收数据,如果没有数据则直接接收帧尾

校验

CRC
1 bytes

帧尾部分包含1字节CRC,用于对整个数据进行校验,如果采用TCP这种可靠通讯,可以去掉CRC,CRC是为了防止传输过程中出错

帧数据结构

typedef struct
{
    
    uint16_t type;
    uint16_t cmd;
    uint16_t index;
    uint32_t total;
    uint32_t len;
} msg_header_t;

typedef struct
{
    
    msg_header_t header;
    uint8_t payload[];
} msg_t;
  • 按照C语言的默认边界对齐方式,帧头大小为16字节。
  • 数据长度不固定此处使用一个指针来表示。
  • msg_t大小为20字节。

通讯帧传输

发送报文

uint32_t msg_data_convert(uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_len)
{
    
    int i = 0;
    int j = 0;
    uint8_t *p = in;

    for (; (i < in_len) && (j < (out_len - 1)); i++, p++)
    {
    
        if (MSG_BEGIN_FLAG == *p)
        {
    
            out[j++] = 0xdb;
            out[j++] = 0xdc;
        }
        else if (MSG_END_FLAG == *p)
        {
    
            out[j++] = 0xdb;
            out[j++] = 0xdd;
        }
        else
        {
    
            out[j++] = *p;
            if (0xdb == *p)
            {
    
                out[j++] = 0xde;
            }
        }
    }

    DEBUG_ASSERT(j < (out_len - 1));

    return j;
}

bool msg_create(msg_parser_t *obj, msg_type_def type, msg_cmd_def cmd, uint32_t index, uint32_t total, uint8_t *payload, uint32_t len)
{
    
    uint8_t crc = 0;
    bool ret = false;
    msg_t msg = {
    0};
    uint32_t offset = 0;

    if ((payload && (len > 0)) || (!payload && (len == 0)))
    {
    
        ret = true;
    }

    msg.header.type = (uint16_t)type;
    msg.header.cmd = (uint16_t)cmd;
    msg.header.index = index;
    msg.header.total = total;
    msg.header.len = len;
    msg.payload = payload;
    crc = msg_parser_calculate_crc(0, (uint8_t *)&msg, sizeof(msg_header_t));
    crc = msg_parser_calculate_crc(crc, msg.payload, msg.header.len);

    obj->msg_send_buffer[offset++] = MSG_BEGIN_FLAG;
    /* 对数据中出现帧头和帧尾的情况进行转义 */
    offset += msg_data_convert((uint8_t *)&msg, sizeof(msg_header_t), &obj->msg_send_buffer[offset], sizeof(obj->msg_send_buffer) - offset);
    offset += msg_data_convert(msg.payload, msg.header.len, &obj->msg_send_buffer[offset], sizeof(obj->msg_send_buffer) - offset);
    offset += msg_data_convert(&crc, sizeof(crc), &obj->msg_send_buffer[offset], sizeof(obj->msg_send_buffer) - offset);
    obj->msg_send_buffer[offset++] = MSG_END_FLAG;
    /* 通过串口发送报文 */
    com_write(obj->com_drv, obj->msg_send_buffer, offset);

    return ret;
}

接收报文

msg_t *msg_parser_recv(msg_parser_t *obj)
{
    
    uint8_t temp = 0;
    uint32_t crc = 0;
    uint8_t recv_crc = 0;
    uint32_t recv_cnt = 0;
    msg_t *ret = &obj->recv_msg;

    /* 接收帧头 */
    do
    {
    
        com_read_byte(obj->com_drv, &temp, osWaitForever);
    } while (MSG_BEGIN_FLAG != temp);

    /* 接收剩余数据 */
    do
    {
    
        com_read_byte(obj->com_drv, &temp, osWaitForever);

        if (temp == 0xdb)
        {
    
            com_read_byte(obj->com_drv, &temp, osWaitForever);
            if (0xdc == temp)
                obj->msg_recv_buffer[recv_cnt++] = MSG_BEGIN_FLAG;
            else if (0xdd == temp)
                obj->msg_recv_buffer[recv_cnt++] = MSG_END_FLAG;
            else if (0xde == temp)
                obj->msg_recv_buffer[recv_cnt++] = 0xdb;
        }
        else
        {
    
            obj->msg_recv_buffer[recv_cnt++] = temp;
        }
    } while ((MSG_END_FLAG != temp) && (recv_cnt < sizeof(obj->msg_recv_buffer)));

    if (recv_cnt < sizeof(obj->msg_recv_buffer))
    {
    
        /* 获取帧头 */
        memcpy(&ret->header, obj->msg_recv_buffer, sizeof(msg_header_t));

        if (recv_cnt == (sizeof(msg_header_t) + 2 + ret->header.len))
        {
    
            /* 获取数据 */
            ret->payload = obj->msg_recv_buffer + sizeof(msg_header_t);
            /* 获取校验 */
            recv_crc = obj->msg_recv_buffer[recv_cnt - 2];
            /* 计算校验 */
            crc = msg_parser_calculate_crc(0, (uint8_t *)ret, sizeof(msg_header_t));
            crc = msg_parser_calculate_crc(crc, ret->payload, ret->header.len);

            if (recv_crc != crc)
            {
    
                ret = NULL;
            }
        }
        else
        {
    
            ret = NULL;
        }
    }
    else
    {
    
        ret = NULL;
    }

    return ret;
}
原网站

版权声明
本文为[咕咚.萌西]所创,转载请带上原文链接,感谢
https://hongquan.blog.csdn.net/article/details/125382101

随机推荐