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: Packet header CRC8 check + Checksum data integrity verification
- Flexibility: Supports request/response mechanism (SACP_ATTR_REQ / SACP_ATTR_ACK)
- Modularity: Encapsulated packet structure, extensible to different instruction sets
- Efficiency: Fixed-length header + variable-length data part, saving communication overhead
The following is a summary of the entire protocol implementation sections:
| Module | Function |
|---|---|
| Protocol Header | Defines the packet format |
| CRC8 Header Check | Ensures header integrity |
| Checksum Data Verification | Ensures data integrity |
| Request-Response Mechanism | Communication between devices |
| Sequence Number Mechanism | Tracks request-response relationships |
| Packet Encapsulation and Parsing | Guarantees communication reliability |
We have previously discussed the implementation of the MVC framework, which can be easily extended into a typical MVC framework based on the SACP protocol:
The Beauty of Embedded Software Design – Applying MVC Framework and State Pattern in Practical Projects (Part 1)
The Beauty of Embedded Software Design – Applying MVC Framework and State Pattern in Practical Projects (Part 2)
The entire implementation code of the protocol can be found in the open-source repository:
https://github.com/Snapmaker/SnapmakerController-IDEX
1. Overall Architecture of the Protocol
1.1 Core Packet Structure
#pragma pack(1)
typedef struct {
uint8_t sof_h; // Sync header high byte 0xAA, one byte
uint8_t sof_l; // Sync 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 (for matching 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;
Members in the core packet structure are predefined with some macros in the header file:
// Descriptions of protocol, version number, and 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: Sync word, the starting flag of the packet (0xAA55)
- SACP_VERSION: Protocol version number, currently 0x01
- SACP_HEADER_LEN: Header length, indicating 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:
- Controller sends request
- Target device parses and returns request response
1.2 Core Header Structure
This header structure serves as the basic information for data encapsulation, used to generate a complete SACP_struct_t with package().
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.3 Data Parsing Cache Structure
typedef struct {
uint16_t lenght; // Records the current parsed data length
union {
uint8_t buff[PACK_PARSE_MAX_SIZE]; // Data buffer
SACP_struct_t sacp; // Direct mapping to SACP_struct_t
};
} SACP_param_t;
2. Protocol Implementation and 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, find the corresponding protocol fields 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}; // Example 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;
}
} 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 < 7) {
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] << 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;
}
} 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 < 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 the SACP Protocol
-
PC -> Controller
Send control commands
-
Controller -> HMI
Feedback and report information displayed through 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 extended into a typical MVC architecture.

END
Author:Yang Yuanxin
Source:Embedded Application Research InstituteCopyright belongs to the original author. If there is any infringement, please contact for deletion..▍Recommended ReadingOverview of Job Positions and Salary Status at ZhiHuiJun CompanyA Guide to Writing Embedded Driver Programs (Based on Timing Diagrams)Why is Hardware Harder than Software, but Hardware Engineers Earn Less than Software Engineers?→ Follow for more updates ←