电子产业一站式赋能平台

PCB联盟网

搜索
查看: 48|回复: 0
收起左侧

一个可用于多设备间的嵌入式通信协议!

[复制链接]

575

主题

575

帖子

4422

积分

四级会员

Rank: 4

积分
4422
发表于 3 天前 | 显示全部楼层 |阅读模式
SACP(Snapmaker Advanced Communication Protocol)是Snapmaker设备的数据通信协议,用于 控制器(Controller)、PC 端(Host)、HMI(人机界面) 之间的数据传输,从该协议的设计思想上看,可以满足以下几个基本目标:
  • 可靠性:数据包头部 CRC8 校验 + Checksum 数据完整性验证
  • 灵活性:支持 请求/应答机制(SACP_ATTR_REQ / SACP_ATTR_ACK)
  • 模块化:封装 数据包结构,可扩展 不同指令集
  • 高效性:固定长度头部 + 变长数据部分,节省通信开销以下为整个协议的实现板块总结:模块作用协议头部规定数据包的格式CRC8头部校验确保头部完整性Checksum数据校验确保数据完整性请求-应答机制设备间通信序列号机制跟踪请求-应答关系数据包封装-解析通信可靠性保证之前我们也讲解过关于MVC框架的实现,基于SACP协议,很容易拓展为一个典型的MVC框架:
    嵌入式软件设计之美-以实际项目应用MVC框架与状态模式(上)
    嵌入式软件设计之美-以实际项目应用MVC框架与状态模式(下)
    以下是整个协议的实现代码的开源仓库:
    https://github.com/Snapmaker/SnapmakerController-IDEX
    1、协议整体架构unsetunset1.1、SACP协议数据包结构unsetunsetunsetunset1.1.1、核心数据包结构体unsetunset#pragma pack(1)
    typedef struct {
      uint8_t sof_h;           //同步头高字节 0xAA, 一个字节
      uint8_t sof_l;           //同步头低字节 0x55,一个字节
      uint16_t length;         //总长度(头部+数据+校验和),两个字节
      uint8_t version;         //协议版本(当前为0x01),一个字节
      uint8_t recever_id;    //目标设备ID(PC/控制器/HMI),一个字节
      uint8_t crc8;      //头部校验(CRC-8)
      uint8_t sender_id;       //发送设备ID,一个字节
      uint8_t attr;            //数据包属性(请求/应答),一个字节
      uint16_t sequence;       //序列号(用于匹配请求-应答包),两个字节
      uint8_t command_set;     //命令集(功能分类),一个字节
      uint8_t command_id;      //具体命令,一个字节
      uint8_t data[0];         //可变数据参数,数据载荷(length-SACP_HEADER_LEN)
    } SACP_struct_t;
    该核心数据包结构体中的成员在头文件中预定义的一些宏:
    //关于协议、版本号、头部长度的描述
    #define SACP_PDU_SOF_H   0xAA
    #define SACP_PDU_SOF_L   0x55
    #define SACP_VERSION     0x01
    #define SACP_HEADER_LEN  (15)
    //设备ID标识描述
    #define SACP_ID_PC         0
    #define SACP_ID_CONTROLLER 1
    #define SACP_ID_HMI        2
    //数据包大小描述
    #define PACK_PARSE_MAX_SIZE 512
    #define PACK_PACKET_MAX_SIZE 1024
    //请求-应答标识
    #define SACP_ATTR_REQ 0
    #define SACP_ATTR_ACK 1
  • SACP_PDU_SOF_H / SACP_PDU_SOF_L: 同步字,数据包的起始标志 (0xAA55)
  • SACP_VERSION: 协议版本号,当前为 0x01
  • SACP_HEADER_LEN: 头部长度,表示 除去 Payload 的包头部分
  • SACP_ID_PC: PC 端
  • SACP_ID_CONTROLLER: 控制器
  • SACP_ID_HMI: 人机界面(HMI 屏幕)
  • PACK_PARSE_MAX_SIZE: 单次解析最大数据包大小(512 字节)
  • PACK_PACKET_MAX_SIZE: 最大可打包数据长度(1024 字节)
  • SACP_ATTR_REQ: 请求包
  • SACP_ATTR_ACK: 应答包
  • 请求-响应机制:
  • 控制器发送请求
  • 目标设备解析并返回请求应答unsetunset1.1.2、核心头部结构unsetunset该头部结构是作为数据封装的基础信息,即用于package()生成完整的SACP_struct_t
    typedef struct {
      uint8_t recever_id;
      uint8_t attribute;
      uint16_t sequence;
      uint8_t command_set;
      uint8_t command_id;
    } SACP_head_base_t;
    unsetunset1.1.3、数据解析缓存结构unsetunsettypedef struct {
      uint16_t lenght;  //记录当前解析数据长度
      union {
        uint8_t buff[PACK_PARSE_MAX_SIZE]; //数据缓冲区
        SACP_struct_t sacp; //sacp直接映射到SACP_struct_t
      };
    } SACP_param_t;
    2、协议实现及其应用unsetunset2.1、数据包的解析unsetunset解析SACP数据包,校验数据完整性:
    ErrCode ProtocolSACP::parse(uint8_t *data, uint16_t len, SACP_param_t &out);
    简单的应用:
    SACP_param_t parsed_data;
    ErrCode result = protocol_sacp.parse(received_data, data_length, parsed_data);
    if (result == E_SUCCESS) {
        // 解析成功,即通过parsed_data找到对应协议的字段,分析对应的数据来源并进行进一步的处理
    }
    unsetunset2.2、数据包的封装unsetunset封装SACP请求/应答数据包:
    uint16_t ProtocolSACP::package(SACP_head_base_t head, uint8_t *in_data, uint16_t length, uint8_t *out_data);
    简单的应用:
    uint8_t buffer[PACK_PACKET_MAX_SIZE];
    SACP_head_base_t head = {SACP_ID_HMI, SACP_ATTR_REQ, protocol_sacp.sequence_pop(), 0x10, 0x02};
    uint8_t data_payload[] = {0x01, 0x02, 0x03}; // 示例数据
    uint16_t packet_length = protocol_sacp.package(head, data_payload, sizeof(data_payload), buffer);
    send_packet(buffer, packet_length); // 发送数据包
    完整实现逻辑(头文件):protocol_sacp.h
    #ifndef PROTOCOL_ASCP_H
    #define PROTOCOL_ASCP_H
    #include "../J1/common_type.h"
    #include
    // protocol relative macros
    #define SACP_PDU_SOF_H   0xAA
    #define SACP_PDU_SOF_L   0x55
    #define SACP_VERSION     0x01
    #define SACP_HEADER_LEN  (15)   // frame_length - length_paylod
    #define SACP_ID_PC         0
    #define SACP_ID_CONTROLLER 1
    #define SACP_ID_HMI        2
    #define PACK_PARSE_MAX_SIZE 512
    #define PACK_PACKET_MAX_SIZE 1024
    #define SACP_ATTR_REQ 0
    #define SACP_ATTR_ACK 1
    #pragma pack(1)
    typedef struct {
      uint8_t sof_h;
      uint8_t sof_l;
      uint16_t length;
      uint8_t version;  // 0x01
      uint8_t recever_id;
      uint8_t crc8;
      uint8_t sender_id;
      uint8_t attr;
      uint16_t sequence;
      uint8_t command_set;
      uint8_t command_id;
      uint8_t data[0];
    } SACP_struct_t;
    typedef struct {
      uint8_t recever_id;
      uint8_t attribute;
      uint16_t sequence;
      uint8_t command_set;
      uint8_t command_id;
    } SACP_head_base_t;
    typedef struct {
      uint16_t lenght;  // The total length of data
      union {
        uint8_t buff[PACK_PARSE_MAX_SIZE];
        SACP_struct_t sacp;
      };
    } SACP_param_t;
    #pragma pack()
    class ProtocolSACP {
      public:
        ErrCode parse(uint8_t *data, uint16_t len, SACP_param_t &out);
        // Package the incoming data
        uint16_t package(SACP_head_base_t head, uint8_t *in_data, uint16_t length, uint8_t *out_data);
        uint16_t sequence_pop() {return sequence++;}
      private:
        uint32_t sequence = 0;
    };
    extern ProtocolSACP protocol_sacp;
    #endif
    完整实现逻辑(源文件):protocol_sacp.cpp
    #include "protocol_sacp.h"
    #include
    #include "HAL.h"
    #include "../../Marlin/src/core/serial.h"
    ProtocolSACP protocol_sacp;
    static uint8_t sacp_calc_crc8(uint8_t *buffer, uint16_t len) {
      int crc = 0x00;
      int poly = 0x07;
    for (int i = 0; i
        for (int j = 0; j
          bool bit = ((buffer >> (7 - j) & 1) == 1);
          bool c07 = ((crc >> 7 & 1) == 1);
          crc
          if (c07 ^ bit) {
            crc ^= poly;
          }
        }
      }
      crc &= 0xff;
    return crc;
    }
    uint16_t calc_checksum(uint8_t *buffer, uint16_t length) {
      uint32_t volatile checksum = 0;
    if (!length || !buffer)
        return 0;
    for (int j = 0; j
        checksum += (uint32_t)(buffer[j]
    if (length % 2)
        checksum += buffer[length - 1];
    while (checksum > 0xffff)
        checksum = ((checksum >> 16) & 0xffff) + (checksum & 0xffff);
      checksum = ~checksum;
    return (uint16_t)checksum;
    }
    ErrCode ProtocolSACP::parse(uint8_t *data, uint16_t len, SACP_param_t &out) {
      uint8_t *parse_buff = out.buff;
    if (parse_buff[0] != SACP_PDU_SOF_H) {
        out.lenght = 0;
      }
    for (uint16_t i = 0; i
        uint8_t ch = data;
        if (out.lenght == 0) {
          if (ch == SACP_PDU_SOF_H) {
            parse_buff[out.lenght++] = ch;
          }
        } elseif (out.lenght == 1) {
          if (ch == SACP_PDU_SOF_L) {
            parse_buff[out.lenght++] = ch;
          } else {
            out.lenght = 0;
          }
        } else {
          parse_buff[out.lenght++] = ch;
        }
        if (out.lenght
          break;
        }
        elseif (out.lenght == 7) {
          if (sacp_calc_crc8(parse_buff, 6) != parse_buff[6]) {
            out.lenght = 0;
          }
        }
        else {
          uint16_t data_len = (parse_buff[3]
          uint16_t total_len = data_len + 7;
          if (out.lenght == total_len) {
            uint16_t checksum = calc_checksum(&parse_buff[7], data_len - 2);
            uint16_t checksum1 = (parse_buff[total_len - 1]
            if (checksum == checksum1) {
              out.lenght = 0;
              return E_SUCCESS;
            } else {
              out.lenght = 0;
              return E_PARAM;
            }
          } elseif (out.lenght > total_len) {
            out.lenght = 0;
            return E_PARAM;
          }
        }
      }
    return E_IN_PROGRESS;
    }

    uint16_t ProtocolSACP::package(SACP_head_base_t head, uint8_t *in_data, uint16_t length, uint8_t *out_data) {
      uint16_t data_len = (length + 8); // header 6 byte, checknum 2byte
      SACP_struct_t *out =  (SACP_struct_t *)out_data;
      out->sof_h = SACP_PDU_SOF_H;
      out->sof_l = SACP_PDU_SOF_L;
      out->length = data_len;
      out->version = SACP_VERSION;
      out->recever_id = head.recever_id;
      out->crc8 = sacp_calc_crc8(out_data, 6);
      out->sender_id = SACP_ID_CONTROLLER;
      out->attr = head.attribute;
      out->sequence = head.sequence;
      out->command_set = head.command_set;
      out->command_id = head.command_id;
    for (uint16_t i = 0; i
        out->data = in_data;
      }
      uint16_t checksum = calc_checksum(&out_data[7], data_len - 2);  // - checknum 2 byte
      length = sizeof(SACP_struct_t) + length;
      out_data[length++] = (uint8_t)(checksum & 0x00FF);
      out_data[length++] = (uint8_t)(checksum>>8);
    return length;
    }
    3、SACP协议的典型应用场景
  • PC 端 -> 控制器
    发送控制指令
  • 控制器 -> HMI
    反馈、上报一些信息通过GUI来进行展示
  • 模块间通信
    用户操作、数据解析和发送、反馈等
    基于该协议的设计和实现,还可将其拓展为典型的MVC架构。
    猜你喜欢:
    嵌入式软件:函数式 VS 非函数式编程
    嵌入式领域:Linux 与 RTOS 的巅峰对决!
    嵌入式性能指标竟藏着这些秘密,你了解几个?
    嵌入式软件进阶指南,一起来进阶!
    嵌入式编程模型 | MVC模型
    嵌入式编程模型 | 观察者模式
    手把手教你搭建嵌入式容器化开发环境!一款优雅的嵌入式多功能调试器!
    一个非常轻量的嵌入式日志库!
    一个非常轻量的嵌入式线程池库!
    Github上热门 C 语言项目汇总!
    实用 | 10分钟教你通过网页点灯
    嵌入式开发必备技能 | Git子模块
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表