Click on the above “Embedded Application Research Institute” and select “Pin/Star the Official Account“
Practical benefits delivered first-hand!
Source Material | GitHub
Compiled & Formatted | Embedded Application Research Institute
The SACP (Snapmaker Advanced Communication Protocol) is the data communication protocol for Snapmaker devices, used for data transmission between the Controller, PC (Host), and HMI (Human-Machine Interface). From the design philosophy of this protocol, it can meet the following basic goals:
-
Reliability: Data packet header CRC8 check + Checksum data integrity verification -
Flexibility: Supports request/response mechanism (SACP_ATTR_REQ / SACP_ATTR_ACK) -
Modularity: Encapsulates packet structure, extensible to different instruction sets -
Efficiency: Fixed-length header + variable-length data portion, saving communication overhead
Module | Function |
---|---|
Protocol Header | Defines the format of the data packet |
CRC8 Header Check | Ensures header integrity |
Checksum Data Check | Ensures data integrity |
Request-Response Mechanism | Communication between devices |
Sequence Number Mechanism | Tracks request-response relationships |
Packet Encapsulation-Parsing | Ensures communication reliability |
Previously, we also explained the implementation of the MVC framework, which can easily be extended into a typical MVC framework based on the SACP protocol:
The Beauty of Embedded Software Design – Application of MVC Framework and State Pattern in Real Projects (Part 1)
The Beauty of Embedded Software Design – Application of MVC Framework and State Pattern in Real Projects (Part 2)
Below is the open-source repository for the entire protocol implementation code:
https://github.com/Snapmaker/SnapmakerController-IDEX
1. Overall Architecture of the Protocol
1.1 SACP Protocol Packet Structure
1.1.1 Core Packet Structure
#pragma pack(1)
typedef struct {
uint8_t sof_h; // Synchronization header high byte 0xAA, one byte
uint8_t sof_l; // Synchronization header low byte 0x55, one byte
uint16_t length; // Total length (header + data + checksum), two bytes
uint8_t version; // Protocol version (currently 0x01), one byte
uint8_t recever_id; // Target device ID (PC/Controller/HMI), one byte
uint8_t crc8; // Header check (CRC-8)
uint8_t sender_id; // Sending device ID, one byte
uint8_t attr; // Packet attribute (request/response), one byte
uint16_t sequence; // Sequence number (used to match request-response packets), two bytes
uint8_t command_set; // Command set (function classification), one byte
uint8_t command_id; // Specific command, one byte
uint8_t data[0]; // Variable data parameters, data payload (length-SACP_HEADER_LEN)
} SACP_struct_t;
The members in this core packet structure are predefined in the header file with some macros:
// Descriptions about protocol, version number, header length
#define SACP_PDU_SOF_H 0xAA
#define SACP_PDU_SOF_L 0x55
#define SACP_VERSION 0x01
#define SACP_HEADER_LEN (15)
// Device ID identification descriptions
#define SACP_ID_PC 0
#define SACP_ID_CONTROLLER 1
#define SACP_ID_HMI 2
// Packet size descriptions
#define PACK_PARSE_MAX_SIZE 512
#define PACK_PACKET_MAX_SIZE 1024
// Request-response identifiers
#define SACP_ATTR_REQ 0
#define SACP_ATTR_ACK 1
-
SACP_PDU_SOF_H / SACP_PDU_SOF_L: Synchronization word, the starting flag of the packet (0xAA55) -
SACP_VERSION: Protocol version number, currently 0x01 -
SACP_HEADER_LEN: Header length, indicates the header part excluding the payload -
SACP_ID_PC: PC side -
SACP_ID_CONTROLLER: Controller -
SACP_ID_HMI: Human-Machine Interface (HMI screen) -
PACK_PARSE_MAX_SIZE: Maximum data packet size for single parsing (512 bytes) -
PACK_PACKET_MAX_SIZE: Maximum packable data length (1024 bytes) -
SACP_ATTR_REQ: Request packet -
SACP_ATTR_ACK: Response packet -
Request-Response Mechanism: -
The controller sends a request -
The target device parses and returns the request response
1.1.2 Core Header Structure
This header structure serves as the basic information for data encapsulation, which is used to package() to generate a complete 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;
1.1.3 Data Parsing Buffer Structure
typedef struct {
uint16_t lenght; // Record the current parsing data length
union {
uint8_t buff[PACK_PARSE_MAX_SIZE]; // Data buffer
SACP_struct_t sacp; // Directly map sacp to SACP_struct_t
};
} SACP_param_t;
2. Protocol Implementation and Its Applications
2.1 Packet Parsing
Parsing SACP data packets, verifying data integrity:
ErrCode ProtocolSACP::parse(uint8_t *data, uint16_t len, SACP_param_t &out);
Simple application:
SACP_param_t parsed_data;
ErrCode result = protocol_sacp.parse(received_data, data_length, parsed_data);
if (result == E_SUCCESS) {
// Parsing successful, i.e., find the corresponding fields of the protocol through parsed_data, analyze the corresponding data source and perform further processing
}
2.2 Packet Encapsulation
Encapsulating SACP request/response data packets:
uint16_t ProtocolSACP::package(SACP_head_base_t head, uint8_t *in_data, uint16_t length, uint8_t *out_data);
Simple application:
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}; // Sample data
uint16_t packet_length = protocol_sacp.package(head, data_payload, sizeof(data_payload), buffer);
send_packet(buffer, packet_length); // Send data packet
Complete implementation logic (header file): 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_payload
#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
Complete implementation logic (source file): 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. Typical Application Scenarios of SACP Protocol
-
PC Side -> Controller
Send control commands
-
Controller -> HMI
Feedback and report some information displayed via GUI
-
Inter-module communication
User operations, data parsing and sending, feedback, etc.
Based on the design and implementation of this protocol, it can also be expanded into a typical MVC architecture.
Previous Highlights
Open source project: Create a series of commonly used Linux application API wheels
Open source project – Based on the Bear Pi STM32 infrared thermal imager
Open source project – Hexadecimal protocol sensor adaptive scaling curve display terminal
An open-source efficient development solution to replace serial screens – Linglong GUI
The Beauty of Embedded Software Design – State Pattern in Embedded Software Architecture Design
From novice to expert: A complete analysis of timing diagrams in embedded software development, instantly understand the relationship between levels and time
If you find this article helpful, please click<span>[Looking]</span>
and share, it is also a support for me.