素材来源 | GitHub,整理&排版?| 嵌入式应用研究院
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协议数据包结构unsetunset
unsetunset1.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: 协议版本号,当前为 0x01SACP_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、数据解析缓存结构unsetunset
typedef?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?<functional>
//?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?<functional>
#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?<?len;?i++)?{
????for?(int?j?=?0;?j?<?8;?j++)?{
??????bool?bit?=?((buffer[i]?>>?(7?-?j)?&?1)?==?1);
??????bool?c07?=?((crc?>>?7?&?1)?==?1);
??????crc?<<=?1;
??????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?<?(length?-?1);?j?=?j?+?2)
????checksum?+=?(uint32_t)(buffer[j]?<<?8?|?buffer[j?+?1]);
??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?<?len;?i++)?{
????uint8_t?ch?=?data[i];
????if?(out.lenght?==?0)?{
??????if?(ch?==?SACP_PDU_SOF_H)?{
????????parse_buff[out.lenght++]?=?ch;
??????}
????}?else?if?(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?<?7)?{
??????break;
????}
????else?if?(out.lenght?==?7)?{
??????if?(sacp_calc_crc8(parse_buff,?6)?!=?parse_buff[6])?{
????????out.lenght?=?0;
??????}
????}
????else?{
??????uint16_t?data_len?=?(parse_buff[3]?<<?8?|?parse_buff[2]);
??????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]?<<?8)?|?parse_buff[total_len?-?2];
????????if?(checksum?==?checksum1)?{
??????????out.lenght?=?0;
??????????return?E_SUCCESS;
????????}?else?{
??????????out.lenght?=?0;
??????????return?E_PARAM;
????????}
??????}?else?if?(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?<?length;?i++)?{
????out->data[i]?=?in_data[i];
??}
??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架构。
1713