# protocol_lib **Repository Path**: zhao-zhangbo/protocol_lib ## Basic Information - **Project Name**: protocol_lib - **Description**: 一种嵌入式轻量级通用hex协议解析器 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 5 - **Created**: 2024-03-21 - **Last Updated**: 2025-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # b_protocol_core **通用协议解析器(Protocol Core)** — 一个用于嵌入式系统的轻量、可扩展帧解析库,基于高效的环形缓冲区(RingBuffer)。 适用于UART/SPI等任意数据输入流接口,能方便灵活地支持定长/变长/带帧头帧尾/不带帧头帧尾等多种协议格式(只需要对协议按`b_frame_type`进行具体的抽象即可)。 --- ## 主要特点 - 多协议并存:定义多个 `b_frame_type` 实例。 - 高效环形缓冲:`ringbuffer` 模块是我工作中非常喜欢的一个 FIFO。 - 可配置:帧头/帧尾、长度解析回调、校验回调、输入/输出缓冲区大小均可自定义。 - 防挂机制:利用 `b_frame_idie_timer` 和 `IDIE_FACTOR` 消除通信错误导致帧长错误匹配或卡死情况。(可选项) - 调试:支持按等级打印原始数据与解析日志(通过 `EN_FRAME_DEBUG_LV` 开关)。 --- ## 仓库结构 ``` . ├── b_protocol_core.c # 核心实现 ├── b_protocol_core.h # 公共 API 定义 ├── ringbuffer.c # 环形缓冲区实现 ├── ringbuffer.h # 环形缓冲区头文件 └── README.md ``` --- ## 使用说明(快速上手) ### 1. 基本概念 - **输入缓冲区(in_frame_buffer)**:用于接收原始字节流(必须指定且大小建议为 2 的幂)。 - **输出缓冲区(out_frame_buffer)**:用于临时组帧和校验,并返回地址给用户使用,大小应能容纳最大一帧。 - **get_frame_len_cb**:返回“完整帧长度(含头/尾)”,如果当前条件不足以判断帧长应返回 `0`。 - **check_cb**:对完整合法帧进行校验(例如 CRC/XOR),返回 `B_SUCCESS` 或 `B_ERROR`。 > 注意:若使用 `head_len == 0`(不强制匹配帧头),可以在 `get_frame_len_cb` 内部自行判定帧头(例如兼容多种不同帧头协议时可以这样应用)。 --- ## 协议举例 ### 1) 变长协议(带头尾) | 内容 | 字节长度 | 说明 | |------|----------|------| | 帧头 | 2 | `A5 5A` | | 命令 | 1 | 命令字 | | 长度 | 1 | 数据区字节数 | | 数据 | N | 负载 | | 校验 | 1 | XOR/Checksum/CRC | | 帧尾 | 2 | `0D 0A` | --- ### 2) 定长协议(8 字节,无尾) | 内容 | 字节长度 | |------|----------| | 起始 | 1 (`0x68`) | | 地址 | 1 | | 功能 | 1 | | 数据 | 4 | | 校验 | 1 | --- ## 示例代码 ### 协议 A — 变长(A5 5A头、XOR 校验、尾 0D 0A) ```c /* example_a5.c */ #include "b_protocol_core.h" #include #include /* get_frame_len_cb * buffer[2] == cmd, buffer[3] == len */ uint16_t a5_get_frame_len(uint8_t *buffer, uint16_t len) { if (len < 4) return 0; // head(2) + cmd(1) + len(1) uint8_t payload_len = (uint8_t)buffer[3]; uint16_t total = 2 + 1 + 1 + payload_len + 1 + 2; // head + cmd + len + payload + xor + tail return total; } // check_cb uint8_t a5_check_cb(uint8_t *buffer, uint16_t len) { if (len < 7) return B_ERROR; // minimal frame length uint8_t calc = 0; for (uint16_t i = 2; i < len - 3; ++i) { // cmd .. last data byte calc ^= (uint8_t)buffer[i]; } uint8_t cs = (uint8_t)buffer[len - 3]; return (calc == cs) ? B_SUCCESS : B_ERROR; } static uint8_t in_buf[256]; //或者堆分配,需要满足2的幂 static uint8_t out_buf[256];//或者堆分配,需要满足最大帧 void init_a5_protocol(b_frame_type *frame) { b_frame_init_type cfg = { .pname = "A5_Protocol", .head = "\xA5\x5A", .head_len = 2, .end = "\x0D\x0A", .end_len = 2, .get_frame_len_cb = a5_get_frame_len, .check_cb = a5_check_cb, .in_frame_buffer = in_buf, .out_frame_buffer = out_buf, .in_buffer_len = sizeof(in_buf), .out_buffer_len = sizeof(out_buf), }; b_frame_init(frame, &cfg); } /* 测试用例(伪代码): uint8_t sample[] = {0xA5,0x5A,0x01,0x03,0x11,0x22,0x33,0x02,0x0D,0x0A}; b_frame_put(&frame, sample, sizeof(sample)); uint16_t flen; const uint8_t *p = b_frame_check_get(&frame, &flen); if (p) { // 成功 } */ ``` --- ### 协议 B — 定长 8 字节(0x68 起始,SUM 校验) ```c /* example_fixed8.c */ #include "b_protocol_core.h" #include #include /* get_frame_len_cb: head_len == 1, head byte 0x68 */ uint16_t fixed8_get_frame_len(uint8_t *buffer, uint16_t len) { return 8; // 固定 8 字节 } /* check_cb: sum of first 7 bytes mod 256 equals last byte */ uint8_t fixed8_check_cb(uint8_t *buffer, uint16_t len) { if (len != 8) return B_ERROR; uint8_t sum = 0; for (uint16_t i = 0; i < 7; ++i) sum += (uint8_t)buffer[i]; uint8_t cs = (uint8_t)buffer[7]; return (cs == sum) ? B_SUCCESS : B_ERROR; } static uint8_t in_buf[128];//或者堆分配,需要满足2的幂 static uint8_t out_buf[8];//或者堆分配,需要满足最大帧 void init_fixed8_protocol(b_frame_type *frame) { b_frame_init_type cfg = { .pname = "Fixed8_Protocol", .head = "\x68", .head_len = 1, .end = NULL, .end_len = 0, .get_frame_len_cb = fixed8_get_frame_len, .check_cb = fixed8_check_cb, .in_frame_buffer = in_buf, .out_frame_buffer = out_buf, .in_buffer_len = sizeof(in_buf), .out_buffer_len = sizeof(out_buf), }; b_frame_init(frame, &cfg); } /* 测试用例(伪代码): uint8_t sample[] = {0x68,0x01,0x03,0x11,0x22,0x33,0x44,0x16}; b_frame_put(&frame, sample, sizeof(sample)); uint16_t flen; const uint8_t *p = b_frame_check_get(&frame, &flen); if (p) { // 成功 } */ ``` ## API 参考 ```c uint8_t b_frame_init(b_frame_type *pframe, b_frame_init_type *pframeinit);//初始化协议抽象 void b_frame_idie_timer(b_frame_type *pframe);//(可选项) uint8_t b_frame_put(b_frame_type *pframe, uint8_t *dat, uint32_t len);//将获取的数据put到协议解析器 void b_frame_fifo_clear(b_frame_type *pframe);//辅助函数,清空fifo,特殊情况使用,几乎很少使用,与协议解析无关 uint32_t b_frame_fifo_get(b_frame_type *pframe, uint8_t *dat, uint32_t len, uint32_t timeout);//辅助函数,用于从fifo中堵塞获取数据,特殊情况使用,与协议解析无关 const uint8_t *b_frame_check_get(b_frame_type *pframe, uint16_t *len);//协议解析器,轮询调用解析fifo中的数据,如发现合法帧会返回帧缓存首地址,否则返回空。 ``` --- ## 实现细节与设计考量(参考,大概写了下~) - **ringbuffer 的大小必须为 2 的 N 次幂**,`ring_buf_init` 会将用户提供的长度向下调整到最近的 2 的幂,但仍建议直接定义2的n次幂大小。 - 当接收到的数据大于定义的帧头时 - 若存在帧头,内部会尝试从 ringbuffer 中逐字节匹配 head; - 若不存在帧头,则默认帧头匹配成功; - 匹配成功后,使用 `get_frame_len_cb` 获取总帧字节 - 若存在帧尾,会等待fifo中数据达到预期,匹配帧尾,若不存在帧尾,则默认帧尾匹配成功; - 若帧尾匹配成功,则调用`check_cb`。 - 若帧尾匹配不成功,则丢弃帧头长度的字节 - 若check_cb不成功,则丢弃帧头长度字节,若帧头不存在,则仅丢弃1字节。 - 若check_cb成功,则copy完整帧数据到缓冲区,并通过b_frame_check_get返回地址。 - 若长时间(受 IDIE_TIMER_US 与 IDIE_FACTOR 控制)没有收到get_frame_len_cb的数据,则丢弃当前 head以恢复同步。 - 若长时间没有完整帧(受 IDIE_TIMER_US 与 IDIE_FACTOR 控制),会放弃当前 head 并可能丢弃部分数据以恢复同步。(需要调用b_frame_idie_timer以提供心跳) --- ## 调试与常见问题 - **问题:经常报 ringbuffer 满** - 检查是否在 ISR 中频繁但未及时消费数据。适当增大 `in_buffer_len` 或优化上层处理逻辑。 - **建议**:调试时可以开启 `EN_FRAME_DEBUG_LV` 能帮助定位字节流问题。 --- --- ## 致谢 感谢你使用并贡献此项目。欢迎提交 Issue / PR / 测试用例。 ---