
# SLIP 协议封装与解析详解原理、C 代码与实战案例 适配嵌入式 IAR / ZYNQ 裸机项目适用于 ICD 协议、PS 与 PL 之间 SLIP 报文通信场景。---## 目录1. 什么是 SLIP 协议2. 帧格式与转义规则3. 完整 C 语言实现编码 / 解码4. 与 CRC-16-IBM 结合完整流程5. ZYNQ 实战PS 接收 PL 的 SLIP 数据包6. 高频踩坑点与边界处理7. 测试用例参考表---## 1. 什么是 SLIP 协议**SLIPSerial Line Internet Protocol串行线路因特网协议** 是一种非常简单的串行线路数据封装协议最早用于在串口上传输 IP 数据包。在嵌入式领域军工 ICD、ZYNQ PS/PL 通信、串口链路SLIP 被广泛用作**面向字节流的帧封装协议**解决两个核心问题1. **帧边界识别**串口是连续字节流没有帧概念SLIP 用特殊字节标记帧的起始和结束。2. **透明传输**当数据内部出现特殊字节时通过转义机制保证不干扰帧边界。 SLIP 的最大优点**实现极其简单几乎零协议开销**。缺点是无差错校验需配合 CRC、无压缩、无重传机制——所以工程上通常 **SLIP 负责组帧 CRC 负责校验** 组合使用。---## 2. 帧格式与转义规则### 2.1 特殊字节定义| 字节 | 值 | 含义 || ---- | ---- | ---- || END | 0xC0 | 帧结束标记也是帧起始标记 || ESC | 0xDB | 转义标记 || ESC_END | 0xDC | 转义后的 END0xC0 || ESC_ESC | 0xDD | 转义后的 ESC0xDB |### 2.2 标准 SLIP 帧结构------------------------------| 0xC0 | 数据(已转义) | 0xC0 |------------------------------- 帧首尾各一个 0xC0。- 中间的数据经过转义处理保证内部不会出现裸的 0xC0。### 2.3 转义规则核心务必记牢**编码发送侧**——数据中遇到特殊字节时替换| 原始数据字节 | 替换为 || ---- | ---- || 0xC0 | 0xDB 0xDC || 0xDB | 0xDB 0xDD || 其他 | 原样保留 |**解码接收侧**——遇到 0xDB 时看下一个字节| 收到的序列 | 还原为 || ---- | ---- || 0xDB 0xDC | 0xC0 || 0xDB 0xDD | 0xDB | ⚠️ **重点**转义对象是 **整帧数据含 CRC 字节**而不是只对业务数据转义。CRC 本身可能算出 0xC0 或 0xDB必须一并转义否则帧边界会被破坏。---## 3. 完整 C 语言实现IAR 裸机### 3.1 SLIP 编码封装c#include stdint.h#define SLIP_END 0xC0#define SLIP_ESC 0xDB#define SLIP_ESC_END 0xDC#define SLIP_ESC_ESC 0xDD/*** brief SLIP 编码把原始报文封装成 SLIP 帧含首尾 0xC0* param src 原始报文业务数据 CRC即完整待发数据* param src_len 原始报文长度* param dst 输出缓冲区需足够大最坏为 src_len*2 2* return 封装后的 SLIP 帧总长度*/uint16_t slip_encode(const uint8_t *src, uint16_t src_len, uint8_t *dst){uint16_t out 0;dst[out] SLIP_END; /* 帧首 0xC0 */for (uint16_t i 0; i src_len; i){if (src[i] SLIP_END){dst[out] SLIP_ESC;dst[out] SLIP_ESC_END;}else if (src[i] SLIP_ESC){dst[out] SLIP_ESC;dst[out] SLIP_ESC_ESC;}else{dst[out] src[i];}}dst[out] SLIP_END; /* 帧尾 0xC0 */return out;}### 3.2 SLIP 解码解析解码采用**状态机 逐字节输入**方式适合串口中断 / DMA 逐字节收包的场景。ctypedef enum{SLIP_STATE_WAIT_END, /* 等待帧首 0xC0 */SLIP_STATE_DATA, /* 接收数据 */SLIP_STATE_ESC /* 遇到转义符 0xDB */} slip_state_t;typedef struct{slip_state_t state;uint8_t rx_buf[1024]; /* 接收缓冲区按需调整 */uint16_t rx_len; /* 已接收数据长度 */} slip_rx_t;/*** brief SLIP 逐字节解码* param rx SLIP 接收句柄* param byte 收到的原始字节* return 1 收到完整一帧rx_buf 内为解码后数据0 继续接收* -1 缓冲区溢出错误*/int slip_decode_byte(slip_rx_t *rx, uint8_t byte){switch (rx-state){case SLIP_STATE_WAIT_END:if (byte SLIP_END){rx-state SLIP_STATE_DATA;}/* 帧首之前的多余字节直接丢弃 */break;case SLIP_STATE_DATA:if (byte SLIP_END){/* 收到帧尾完整一帧 */rx-state SLIP_STATE_WAIT_END;return 1;}else if (byte SLIP_ESC){rx-state SLIP_STATE_ESC;}else{if (rx-rx_len sizeof(rx-rx_buf)){rx-rx_len 0;rx-state SLIP_STATE_WAIT_END;return -1; /* 溢出 */}rx-rx_buf[rx-rx_len] byte;}break;case SLIP_STATE_ESC:rx-state SLIP_STATE_DATA;if (byte SLIP_ESC_END){byte SLIP_END;}else if (byte SLIP_ESC_ESC){byte SLIP_ESC;}else{/* 非法转义序列按错误处理丢弃该帧 */rx-rx_len 0;rx-state SLIP_STATE_WAIT_END;return -1;}if (rx-rx_len sizeof(rx-rx_buf)){rx-rx_len 0;rx-state SLIP_STATE_WAIT_END;return -1;}rx-rx_buf[rx-rx_len] byte;break;}return 0;}---## 4. 与 CRC-16-IBM 结合完整流程 工程上 SLIP 不提供校验必须搭配 CRC。推荐组合**CRC-16-IBM 校验 SLIP 组帧**。 CRC-16-IBM 实现见另一篇博客参数 poly0x8005 init0xFFFF refintrue refouttrue### 4.1 发包完整流程发送侧业务数据 → CRC16 计算 → 数据CRC 追加 → SLIP 转义封装 → 加 0xC0 帧首尾 → 串口/DMA 发送1. 组装 ICD 业务报文原始业务数据。2. 用 crc16_ibm() 计算业务数据 CRC。3. 将 CRC 小端追加到业务数据末尾得到**原始完整报文**。4. 用 slip_encode() 对原始完整报文含 CRC整体转义加帧首尾 0xC0。5. 通过串口 / DMA 发送。c/* 发包示例 */void slip_send_packet(const uint8_t *business_data, uint16_t data_len){static uint8_t raw_pkt[1024]; /* 业务数据 CRC */static uint8_t slip_frame[2048]; /* SLIP 封装后 *//* 1. 拷贝业务数据 */memcpy(raw_pkt, business_data, data_len);/* 2. 计算 CRC 并小端追加 */uint16_t crc crc16_ibm(business_data, data_len);raw_pkt[data_len 0] (uint8_t)(crc 0xFF);raw_pkt[data_len 1] (uint8_t)((crc 8) 0xFF);/* 3. SLIP 封装含 CRC 一起转义 */uint16_t frame_len slip_encode(raw_pkt, data_len 2, slip_frame);/* 4. 发送串口 DMA 或轮询 */uart_dma_send(slip_frame, frame_len);}### 4.2 收包完整流程接收侧串口字节流 → 逐字节 SLIP 解码 → 还原原始报文 → 末尾2字节CRC → 校验 → 通过则解析业务失败丢弃1. 串口中断 / DMA 每次收到 1 字节调用 slip_decode_byte()。2. 返回 1 表示收到完整一帧rx_buf 内为**解码后的原始报文**。3. 原始报文末尾 2 字节是 CRC前面是业务数据。4. 用 crc16_check_packet() 校验通过才解析业务失败丢弃该帧。c/* 收包示例 */slip_rx_t g_rx {0};/* 串口中断里调用 */void uart_isr_byte_received(uint8_t byte){int ret slip_decode_byte(g_rx, byte);if (ret 1){/* 收到完整一帧g_rx.rx_buf 为解码后数据g_rx.rx_len 为长度 */if (g_rx.rx_len 2){uint16_t data_len g_rx.rx_len - 2;uint16_t calc_crc crc16_ibm(g_rx.rx_buf, data_len);uint16_t recv_crc ((uint16_t)g_rx.rx_buf[data_len 1] 8)| g_rx.rx_buf[data_len];if (calc_crc recv_crc){/* CRC 校验通过处理业务数据 g_rx.rx_buf[0..data_len-1] */handle_business_frame(g_rx.rx_buf, data_len);}/* 校验失败静默丢弃 */}g_rx.rx_len 0; /* 复位准备下一帧 */}else if (ret 0){g_rx.rx_len 0; /* 溢出或非法转义丢弃并复位 */}}---## 5. ZYNQ 实战PS 接收 PL 的 SLIP 数据包 你的项目场景PL 通过串口/内部互联把 SLIP 数据包发给 PSPS 侧负责解码和校验。### 5.1 关键设计要点1. **DMA 还是轮询**如果 PL 持续发送建议用 **UART DMA 空闲中断**减少 CPU 占用数据量小也可用轮询。2. **逐字节喂给状态机**无论 DMA 还是中断最终都逐字节调用 slip_decode_byte()。3. **接收缓冲够大**SLIP 转义会使数据膨胀但**解码后的原始报文长度 ≤ 帧数据长度**所以接收缓冲按原始报文最大长度设置即可。4. **复位时机**每收完一帧或出错立即清零 rx_len防止脏数据污染下一帧。### 5.2 PS 端收包伪代码cvoid ps_slip_rx_task(void){while (1){uint8_t byte;if (uart_receive_byte(byte) OK){int ret slip_decode_byte(g_rx, byte);if (ret 1){process_slip_frame(); /* 校验 业务解析 */}else if (ret 0){g_rx.rx_len 0; /* 异常复位 */}}}}---## 6. 高频踩坑点与边界处理| 场景 | 问题 | 正确做法 || ---- | ---- | ---- || 转义范围 | 只对业务数据转义漏掉 CRC 字节 | CRC 会算出 0xC0/0xDB必须 **连同 CRC 一起转义** || 计算 CRC 时机 | 直接对 SLIP 转义后的字节流算 CRC | 必须先解码还原原始报文再用原始数据算 CRC || 解码复位 | 一帧结束后 rx_len 未清零 | 收到帧尾立即清零防止残留脏数据 || 非法转义 | 收到 0xDB 后跟了非 0xDC/0xDD | 视为帧错误丢弃整帧并复位 || 帧首前垃圾 | 帧首 0xC0 之前有多余字节 | 状态机自动丢弃不进入数据区 || 缓冲区溢出 | 单帧超过缓冲 | 溢出即报错丢弃该帧并复位 || 连续帧 | 两帧紧邻前一帧尾即后一帧首 | 状态机天然支持收到帧尾后自动进入等待帧首下一字节 0xC0 即新帧首 |### 6.1 边界技巧用帧首尾同字节简化SLIP 的 END0xC0既是帧首又是帧尾因此**两个连续帧可以直接拼接**帧A 0xC0 ...data... 0xC0 帧B 0xC0 ...data... 0xC0状态机收到 0xC0 时如果当前在 DATA 态则是帧尾完成一帧如果在 WAIT_END 态则是新帧首。上述实现天然兼容无需额外处理。---## 7. 测试用例参考表### 7.1 转义测试| 原始数据(HEX) | SLIP 编码后(HEX) || ---- | ---- || C0 | C0 DB DC C0 || DB | C0 DB DD C0 || 01 02 03 | C0 01 02 03 C0 || C0 DB 01 | C0 DB DC DB DD 01 C0 |### 7.2 完整报文测试含 CRC以业务数据 02 63 06 00 07 02 01CRC0x3C8D小端 8D 3C为例原始完整报文02 63 06 00 07 02 01 8D 3CSLIP 帧 C0 02 63 06 00 07 02 01 8D 3C C0 本例无 0xC0/0xDB未触发转义 若业务数据或 CRC 中出现 0xC0 / 0xDB则会触发转义帧长度相应膨胀。