素材來源 | GitHub,整理&排版?| 嵌入式應(yīng)用研究院
SACP(Snapmaker Advanced Communication Protocol)是Snapmaker設(shè)備的數(shù)據(jù)通信協(xié)議,用于 控制器(Controller)、PC 端(Host)、HMI(人機(jī)界面) 之間的數(shù)據(jù)傳輸,從該協(xié)議的設(shè)計(jì)思想上看,可以滿足以下幾個(gè)基本目標(biāo):
- 可靠性:數(shù)據(jù)包頭部 CRC8 校驗(yàn) + Checksum 數(shù)據(jù)完整性驗(yàn)證靈活性:支持 請(qǐng)求/應(yīng)答機(jī)制(SACP_ATTR_REQ / SACP_ATTR_ACK)模塊化:封裝 數(shù)據(jù)包結(jié)構(gòu),可擴(kuò)展 不同指令集高效性:固定長(zhǎng)度頭部 + 變長(zhǎng)數(shù)據(jù)部分,節(jié)省通信開銷
以下為整個(gè)協(xié)議的實(shí)現(xiàn)板塊總結(jié):
模塊 | 作用 |
---|---|
協(xié)議頭部 | 規(guī)定數(shù)據(jù)包的格式 |
CRC8頭部校驗(yàn) | 確保頭部完整性 |
Checksum數(shù)據(jù)校驗(yàn) | 確保數(shù)據(jù)完整性 |
請(qǐng)求-應(yīng)答機(jī)制 | 設(shè)備間通信 |
序列號(hào)機(jī)制 | 跟蹤請(qǐng)求-應(yīng)答關(guān)系 |
數(shù)據(jù)包封裝-解析 | 通信可靠性保證 |
之前我們也講解過關(guān)于MVC框架的實(shí)現(xiàn),基于SACP協(xié)議,很容易拓展為一個(gè)典型的MVC框架:
嵌入式軟件設(shè)計(jì)之美-以實(shí)際項(xiàng)目應(yīng)用MVC框架與狀態(tài)模式(上)
嵌入式軟件設(shè)計(jì)之美-以實(shí)際項(xiàng)目應(yīng)用MVC框架與狀態(tài)模式(下)
以下是整個(gè)協(xié)議的實(shí)現(xiàn)代碼的開源倉(cāng)庫(kù):https://github.com/Snapmaker/SnapmakerController-IDEX
1、協(xié)議整體架構(gòu)
unsetunset1.1、SACP協(xié)議數(shù)據(jù)包結(jié)構(gòu)unsetunset
unsetunset1.1.1、核心數(shù)據(jù)包結(jié)構(gòu)體unsetunset
#pragma?pack(1)
typedef?struct?{
??uint8_t?sof_h;???????????//同步頭高字節(jié)?0xAA,?一個(gè)字節(jié)
??uint8_t?sof_l;???????????//同步頭低字節(jié)?0x55,一個(gè)字節(jié)
??uint16_t?length;?????????//總長(zhǎng)度(頭部+數(shù)據(jù)+校驗(yàn)和),兩個(gè)字節(jié)
??uint8_t?version;?????????//協(xié)議版本(當(dāng)前為0x01),一個(gè)字節(jié)
??uint8_t?recever_id;????//目標(biāo)設(shè)備ID(PC/控制器/HMI),一個(gè)字節(jié)
??uint8_t?crc8;??????//頭部校驗(yàn)(CRC-8)
??uint8_t?sender_id;???????//發(fā)送設(shè)備ID,一個(gè)字節(jié)
??uint8_t?attr;????????????//數(shù)據(jù)包屬性(請(qǐng)求/應(yīng)答),一個(gè)字節(jié)
??uint16_t?sequence;???????//序列號(hào)(用于匹配請(qǐng)求-應(yīng)答包),兩個(gè)字節(jié)
??uint8_t?command_set;?????//命令集(功能分類),一個(gè)字節(jié)
??uint8_t?command_id;??????//具體命令,一個(gè)字節(jié)
??uint8_t?data[0];?????????//可變數(shù)據(jù)參數(shù),數(shù)據(jù)載荷(length-SACP_HEADER_LEN)
}?SACP_struct_t;
該核心數(shù)據(jù)包結(jié)構(gòu)體中的成員在頭文件中預(yù)定義的一些宏:
//關(guān)于協(xié)議、版本號(hào)、頭部長(zhǎng)度的描述
#define?SACP_PDU_SOF_H???0xAA
#define?SACP_PDU_SOF_L???0x55
#define?SACP_VERSION?????0x01
#define?SACP_HEADER_LEN??(15)
//設(shè)備ID標(biāo)識(shí)描述
#define?SACP_ID_PC?????????0
#define?SACP_ID_CONTROLLER?1
#define?SACP_ID_HMI????????2
//數(shù)據(jù)包大小描述
#define?PACK_PARSE_MAX_SIZE?512
#define?PACK_PACKET_MAX_SIZE?1024
//請(qǐng)求-應(yīng)答標(biāo)識(shí)
#define?SACP_ATTR_REQ?0
#define?SACP_ATTR_ACK?1
- SACP_PDU_SOF_H / SACP_PDU_SOF_L: 同步字,數(shù)據(jù)包的起始標(biāo)志 (0xAA55)SACP_VERSION: 協(xié)議版本號(hào),當(dāng)前為 0x01SACP_HEADER_LEN: 頭部長(zhǎng)度,表示 除去 Payload 的包頭部分SACP_ID_PC: PC 端SACP_ID_CONTROLLER: 控制器SACP_ID_HMI: 人機(jī)界面(HMI 屏幕)PACK_PARSE_MAX_SIZE: 單次解析最大數(shù)據(jù)包大小(512 字節(jié))PACK_PACKET_MAX_SIZE: 最大可打包數(shù)據(jù)長(zhǎng)度(1024 字節(jié))SACP_ATTR_REQ: 請(qǐng)求包SACP_ATTR_ACK: 應(yīng)答包
-
- 請(qǐng)求-響應(yīng)機(jī)制:控制器發(fā)送請(qǐng)求目標(biāo)設(shè)備解析并返回請(qǐng)求應(yīng)答
unsetunset1.1.2、核心頭部結(jié)構(gòu)unsetunset
該頭部結(jié)構(gòu)是作為數(shù)據(jù)封裝的基礎(chǔ)信息,即用于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、數(shù)據(jù)解析緩存結(jié)構(gòu)unsetunset
typedef?struct?{
??uint16_t?lenght;??//記錄當(dāng)前解析數(shù)據(jù)長(zhǎng)度
??union?{
????uint8_t?buff[PACK_PARSE_MAX_SIZE];?//數(shù)據(jù)緩沖區(qū)
????SACP_struct_t?sacp;?//sacp直接映射到SACP_struct_t
??};
}?SACP_param_t;
2、協(xié)議實(shí)現(xiàn)及其應(yīng)用
unsetunset2.1、數(shù)據(jù)包的解析unsetunset
解析SACP數(shù)據(jù)包,校驗(yàn)數(shù)據(jù)完整性:
ErrCode?ProtocolSACP::parse(uint8_t?*data,?uint16_t?len,?SACP_param_t?&out);
簡(jiǎn)單的應(yīng)用:
SACP_param_t?parsed_data;
ErrCode?result?=?protocol_sacp.parse(received_data,?data_length,?parsed_data);
if?(result?==?E_SUCCESS)?{
????//?解析成功,即通過parsed_data找到對(duì)應(yīng)協(xié)議的字段,分析對(duì)應(yīng)的數(shù)據(jù)來源并進(jìn)行進(jìn)一步的處理
}
unsetunset2.2、數(shù)據(jù)包的封裝unsetunset
封裝SACP請(qǐng)求/應(yīng)答數(shù)據(jù)包:
uint16_t?ProtocolSACP::package(SACP_head_base_t?head,?uint8_t?*in_data,?uint16_t?length,?uint8_t?*out_data);
簡(jiǎn)單的應(yīng)用:
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};?//?示例數(shù)據(jù)
uint16_t?packet_length?=?protocol_sacp.package(head,?data_payload,?sizeof(data_payload),?buffer);
send_packet(buffer,?packet_length);?//?發(fā)送數(shù)據(jù)包
完整實(shí)現(xiàn)邏輯(頭文件):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
完整實(shí)現(xiàn)邏輯(源文件):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協(xié)議的典型應(yīng)用場(chǎng)景
PC 端 -> 控制器
發(fā)送控制指令
控制器 -> HMI
反饋、上報(bào)一些信息通過GUI來進(jìn)行展示
模塊間通信
用戶操作、數(shù)據(jù)解析和發(fā)送、反饋等
基于該協(xié)議的設(shè)計(jì)和實(shí)現(xiàn),還可將其拓展為典型的MVC架構(gòu)。