往期的文章中,我们分享了简单无锁队列(simple_queue)【用户投稿】CW32L012移植Ultra-Lightweight Queue:轻量级队列的极致实践的实现,它的运作非常高效,内存占用非常低,可谓是轻量级MCU在单生产者单消费者模式下最适合的队列。本次我们给大家带来队列比较实用的应用场景--“滑动窗口解析”。本滑动窗口解析算法和著名的TCP滑动窗口流量控制算法是两回事情,我们的“滑动”是指在待解析的数据区滑动式解析,不遗漏任何数据,同时本算法是注重于轻量级,而不是做一个高吞吐量的系统。
例程下载:
通过网盘分享的文件:cw32l012_sliding_window.rar
链接: https://pan.baidu.com/s/1MrI64eHvaTnWUikTdJR9PA?pwd=L012 提取码: L012
一、为什么要写滑动窗口解析库?
在嵌入式开发中,串口协议解析几乎是每个项目的"必修课"。最常见的做法是串口空闲超时法:
串口总线空闲超过 N ms,就认为一包数据接收完毕,然后对整包数据进行解析。
这种方式简单直观,但有几个明显的痛点:
必须等超时才能开始解析,引入了额外的延迟(超时时间越长,延迟越大)
超时时间难以精确设定:太短容易把一帧数据截断,太长又影响响应速度
总线干扰/丢字节时容易出错:一旦中间丢失或多了几个字节,整包数据解析失败,只能丢弃
多帧连续发送时无法区分帧边界:两帧之间的间隔如果小于超时时间,就会被当成一帧处理
有没有一种方式,不依赖超时判断,数据到了就能实时解析,还能自动跳过错误数据、持续推进?
有的兄弟!有的!
二、滑动窗口解析简介
2.1 核心思想
滑动窗口是一种区间遍历算法——在数据流中逐字节探测,寻找符合协议帧格式的有效数据:
数据流: xx xx [AA 55 ... 完整帧 ... 55 AA] xx xx ...^ ^窗口起点 窗口终点
与超时法相比:
2.2 协议帧格式
本库默认支持的协议帧格式如下(可在头文件中自定义修改):
| 帧头(2B) | 包序号(1B) | 目标地址(1B) | 指令(2B) | 数据长度(2B) | 数据(NB) | CRC16(2B) | 帧尾(2B) || 0xAA55 | 0~255 | 0~255 | 0~FFFF | 0~128 | .... | CCITT | 0x55AA |
帧头/帧尾:0xAA55 / 0x55AA,用于帧边界识别
数据长度:最大 128 字节(可配置 PROTO_DATA_LEN_MAX)
CRC16-CCITT:校验范围覆盖帧头到数据段(不含 CRC 本身和帧尾)
字节序:高字节在前(大端)
你可以轻松修改帧头帧尾值、字段顺序来适配自己的协议。
三、算法详解
3.1 状态机设计
解析器内部维护一个 13 状态的状态机,每个状态对应协议帧的一个字段:
HEADER_H → HEADER_L → PACKED_ID → ADDR → CMD_H → CMD_L→ LEN_H → LEN_L → DATA(循环) → CRC_H → CRC_L → TAIL_H → TAIL_L
状态机通过函数指针表实现分发,代码结构清晰:
// 状态机函数指针表 —— 每个状态对应一个处理函数const func_proto_parser_state_t proto_parser_state_list[PROTO_STATE_MAX_NUM] ={[PROTO_STATE_HEADER_H] = proto_parser_state_header_h,[PROTO_STATE_HEADER_L] = proto_parser_state_header_l,[PROTO_STATE_PACKED_ID] = proto_parser_state_packed_id,[PROTO_STATE_ADDR] = proto_parser_state_addr,[PROTO_STATE_CMD_H] = proto_parser_state_cmd_h,[PROTO_STATE_CMD_L] = proto_parser_state_cmd_l,[PROTO_STATE_LEN_H] = proto_parser_state_len_h,[PROTO_STATE_LEN_L] = proto_parser_state_len_l,[PROTO_STATE_DATA] = proto_parser_state_data,[PROTO_STATE_CRC_H] = proto_parser_state_crc16_h,[PROTO_STATE_CRC_L] = proto_parser_state_crc16_l,[PROTO_STATE_TAIL_H] = proto_parser_state_tail_h,[PROTO_STATE_TAIL_L] = proto_parser_state_tail_l,};
通过宏 proto_parser_by_byte(parser, byte) 一行代码即可完成状态分发,扩展新字段只需增加状态函数和表项。
3.2 滑动窗口工作流程
以一帧数据 FF AA 55 01 01 00 01 00 02 11 22 CRC_H CRC_L 55 AA 为例:
第1轮: peek [FF] → 状态机 HEADER_H 不匹配 → skip(1) → 窗口滑动1字节第2轮: peek [AA] → HEADER_H ✓ → peek [55] → HEADER_L ✓ → peek [01] → PACKED_ID ✓→ peek [01] → ADDR ✓ → peek [00 01] → CMD ✓ → peek [00 02] → LEN=2 ✓→ peek [11 22] → DATA ✓ → peek [CRC] → CRC ✓ → peek [55 AA] → TAIL ✓→ 完整帧! → 计算CRC16校验 → 校验通过 → frame_dispose() → skip(13) → 完成!
如果中间某个字节出错:
peek [AA] → HEADER_H ✓ → peek [xx] → HEADER_L ✗ → 返回 ERR→ skip(1) → 窗口只滑动1字节 → 重新从 HEADER_H 开始匹配
这就是"滑动窗口"的精髓:不浪费任何有效数据,也不放过任何错误。
四、核心 API
4.1 文件结构
| 文件 | 说明 |
proto_parser.h |
解析器头文件,定义所有对外接口和数据结构 |
proto_parser.c |
解析器实现,包含 13 个状态处理函数和滑动窗口主循环 |
4.2 关键数据结构
// 操作接口 —— 用户实现这些函数,解析器通过它们操作队列typedef struct {func_proto_parser_queue_get_used_t queue_get_used; // 获取队列已有数据量func_proto_parser_queue_peek_t queue_peek; // 窥读队列数据(不移除)func_proto_parser_queue_skip_t queue_skip; // 跳过(移除)队列数据func_proto_parser_frame_dispose_t frame_dispose; // 完整帧回调处理} parser_ops_t;// 解析器实例typedef struct {// ...} proto_parser_t;// 解析器初始化参数typedef struct{// ...}proto_parser_init_t;
4.3 对外 API(仅 3 个)
| 函数 | 说明 |
proto_parser_init() |
初始化解析器,传入 ops 和帧缓存 |
proto_parser_handler() |
主处理函数,放在主循环或轮询中调用 |
proto_parser_crc16_ccitt() |
CRC16-CCITT 计算(可选用查表/逐位/硬件) |
五、快速开始
5.1 配置项(在 proto_parser.h 中修改)
#define PROTO_DATA_LEN_MAX 128 // 最大数据长度#define USE_PROTO_PARSER_ASSERT 0 // 是否开启断言#define USE_PROTO_PARSER_PRINTF 1 // 是否开启日志输出#define USE_PROTO_PARSER_CRC16_CCITT_TAB 1 // CRC查表法(速度快,占ROM)#define USE_PROTO_PARSER_CRC16_CCITT_HW 0 // CRC硬件加速(需自行实现)
5.2 实现 ops 接口
你只需要实现 4 个函数,将你的队列操作"桥接"给解析器,本库不关心你用什么队列。你可以使用:
simple_queue(作者自用)
lwrb
ringbuffer
FreeRTOS Queue
CherryRB
或任何你喜欢的队列
// 队列需要自行选择一种并初始化// simple_queue队列queue_t uart3_rx_queue = {0};uint8_t uart3_rx_queue_buf[128] = {0};void service_uart3_rx_queue_init(void){if (!simple_queue_init(&uart3_rx_queue, uart3_rx_queue_buf, sizeof(uart3_rx_queue_buf), sizeof(uint8_t))) {printf("[cw32 log]: uart3 rx queue init failed!n");return;}printf("[cw32 log]: uart3 rx queue init okn");uart3_rx_it_enable();printf("[cw32 log]: uart3 rx interrupt enablen");}// 1. 获取队列中已有数据的数量uint16_t parser_queue_get_used_port(void){return simple_queue_get_used(&uart3_rx_queue);}// 2. 窥读队列数据(只读不移除)// peek_buf: 数据缓存指针// skip_num: 跳过前多少个字节// peek_num: 要读取多少个字节// 返回值:实际读取的字节数uint16_t parser_queue_peek_port(uint8_t* peek_buf, uint16_t skip_num, uint16_t peek_num){return simple_queue_peek(&uart3_rx_queue, peek_buf, skip_num, peek_num);}// 3. 跳过(移除)队列头部的数据// skip_num: 要跳过的多少个字节// 返回值:实际跳过的字节数uint16_t parser_queue_skip_port(uint16_t skip_num){return simple_queue_skip(&uart3_rx_queue, skip_num);}// 4. 完整帧处理回调 —— 在这里写你的业务逻辑void parser_frame_dispose(frame_msg_t *msg, uint8_t* frame_buf, uint16_t len){// 打印完整帧数据,模拟处理数据printf("cmd_val = 0x%04Xn", msg->cmd_val);printf("data_len = 0x%04Xn", msg->data_len);printf("packed_id = 0x%02Xn", msg->packed_id);printf("dev_addr = 0x%02Xn", msg->dev_addr);for (int i = 0; i < len; i++) {printf("0x%02X ", frame_buf[i]);}printf("n");}
5.3 实例化parser
// 组装操作接口const parser_ops_t parser_ops = {.queue_get_used = parser_queue_get_used_port,.queue_peek = parser_queue_peek_port,.queue_skip = parser_queue_skip_port,.frame_dispose = parser_frame_dispose,};// 帧缓存(大小至少 PROTO_FRAME_BUFFER_SIZE_MIN)uint8_t frame_buffer[PROTO_FRAME_BUFFER_SIZE_MIN] = {0};proto_parser_t parser = {0};// 初始化void service_proto_parser_init(void){proto_parser_init_t parser_init = {.ops = &parser_ops,.frame_buf = frame_buffer,.frame_buf_size = sizeof(frame_buffer),};if(! proto_parser_init(&parser,&parser_init)){printf(PROTO_PARSER_PRINTF_TAG"err: proto parser init failed !!!n");}printf(PROTO_PARSER_PRINTF_TAG"msg: proto parser init okn");}
5.4 初始化并调用 handler
int main(void){// 其他业务初始化...// 队列初始化// todo 队列需要自行初始化service_uart3_rx_queue_init(); // 队列初始化// 初始化解析器service_proto_parser_init();// 主循环 / 轮询任务while (1) {// ... 其他业务 ...// 调用解析器处理函数(内部会自动检测新数据并解析)proto_parser_handler(&parser);// ... 其他业务 ...}}
5.5 初始化日志
如果运行demo,会输出以下信息
[cw32 log]: MCU: cw32L012 , RAM: 8k FLASH 64k.[cw32 log]: uart init ok.[cw32 log]: system clk 96Mhz.[cw32 log]: uart baud 115200.[cw32 log]: uart3 rx queue init ok[cw32 log]: uart3 rx interrupt enable[cw32 log]: msg: init success[cw32 log]: msg: proto parser init ok
使用就这么简单。你不需要关心数据什么时候到、到多少、是否完整,解析器会自动处理。
六、滑动窗口解析测试
6.1 发送垃圾数据测试
发送(hex): 11 22 33 44 55 66 77 88 99 00
测试通过:parse failed !!! 系统拒绝匹配了所有数据
» 11 22 33 44 55 66 77 88 99 00« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0x11[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 9[cw32 log]: msg: peek = 0x22[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 8[cw32 log]: msg: peek = 0x33[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 7[cw32 log]: msg: peek = 0x44[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 6[cw32 log]: msg: peek = 0x55[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 5[cw32 log]: msg: peek = 0x66[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 4[cw32 log]: msg: peek = 0x77[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 3[cw32 log]: msg: peek = 0x88[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 2[cw32 log]: msg: peek = 0x99[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.
6.2 发送正确的完整帧
发送(hex):AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA
测试通过:get frame输出,正确解析出一帧数据
» AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: increase = 15[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x02[cw32 log]: msg: peek = 0x03[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x12[cw32 log]: msg: peek = 0x1B[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: cmd_val = 0x0001[cw32 log]: msg: data_len = 0x0004[cw32 log]: msg: packed_id = 0x00[cw32 log]: msg: dev_addr = 0x01[cw32 log]: msg: get frame: 0xAA 0x55 0x00 0x01 0x00 0x01 0x00 0x04 0x01 0x02 0x03 0x04 0x12 0x1B 0x55 0xAA
6.3 发送粘包数据测试
发送(hex):AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA
测试通过:get frame输出,正确解析出2帧数据
» AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: increase = 31[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x02[cw32 log]: msg: peek = 0x03[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x12[cw32 log]: msg: peek = 0x1B[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: cmd_val = 0x0001[cw32 log]: msg: data_len = 0x0004[cw32 log]: msg: packed_id = 0x00[cw32 log]: msg: dev_addr = 0x01[cw32 log]: msg: get frame: 0xAA 0x55 0x00 0x01 0x00 0x01 0x00 0x04 0x01 0x02 0x03 0x04 0x12 0x1B 0x55 0xAA[cw32 log]: msg: queue not empty. trigger parse again.[cw32 log]: msg: increase = 16[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x02[cw32 log]: msg: peek = 0x03[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x12[cw32 log]: msg: peek = 0x1B[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: cmd_val = 0x0001[cw32 log]: msg: data_len = 0x0004[cw32 log]: msg: packed_id = 0x00[cw32 log]: msg: dev_addr = 0x01[cw32 log]: msg: get frame: 0xAA 0x55 0x00 0x01 0x00 0x01 0x00 0x04 0x01 0x02 0x03 0x04 0x12 0x1B 0x55 0xAA
6.4 断包数据测试
第一次发送 AA 55 00 01 00 01 00
隔一段时间再发送 04 01 02 03 04 12 1B 55 AA
测试通过:可以观察到日志分为两个部分,启动了两次解析,数据正常拼接
» AA 55 00 01 00 01 00« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: increase = 6[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00» 04 01 02 03 04 12 1B 55 AA« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0x04[cw32 log]: msg: increase = 8[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x02[cw32 log]: msg: peek = 0x03[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x12[cw32 log]: msg: peek = 0x1B[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: cmd_val = 0x0001[cw32 log]: msg: data_len = 0x0004[cw32 log]: msg: packed_id = 0x00[cw32 log]: msg: dev_addr = 0x01[cw32 log]: msg: get frame: 0xAA 0x55 0x00 0x01 0x00 0x01 0x00 0x04 0x01 0x02 0x03 0x04 0x12 0x1B 0x55 0xAA
6.5 测试数据首尾有噪声
发送(hex):FF FF 00 00 AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA FF FF 00 00
测试通过:解析系统过滤了所有噪声,成功提取了正确的数据帧
» FF FF 00 00 AA 55 00 01 00 01 00 04 01 02 03 04 12 1B 55 AA FF FF 00 00« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0xFF[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 23[cw32 log]: msg: peek = 0xFF[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 22[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 21[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 20[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x02[cw32 log]: msg: peek = 0x03[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x12[cw32 log]: msg: peek = 0x1B[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: cmd_val = 0x0001[cw32 log]: msg: data_len = 0x0004[cw32 log]: msg: packed_id = 0x00[cw32 log]: msg: dev_addr = 0x01[cw32 log]: msg: get frame: 0xAA 0x55 0x00 0x01 0x00 0x01 0x00 0x04 0x01 0x02 0x03 0x04 0x12 0x1B 0x55 0xAA[cw32 log]: msg: queue not empty. trigger parse again.[cw32 log]: msg: increase = 4[cw32 log]: msg: peek = 0xFF[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 3[cw32 log]: msg: peek = 0xFF[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 2[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.
6.6 测试数据帧数据内容错误
发送(hex):A 55 00 01 00 01 00 04 00 02 03 04 12 1B 55 AA
测试通过:crc16 failed !!! 解析以后发现CRC16校验不通过,重新寻找命令帧
» AA 55 00 01 00 01 00 04 00 02 03 04 12 1B 55 AA« [cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0xAA[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 16[cw32 log]: msg: peek = 0xAA[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x01[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x00[cw32 log]: msg: peek = 0x02[cw32 log]: msg: peek = 0x03[cw32 log]: msg: peek = 0x04[cw32 log]: msg: peek = 0x12[cw32 log]: msg: peek = 0x1B[cw32 log]: msg: peek = 0x55[cw32 log]: msg: peek = 0xAA[cw32 log]: err: crc16 failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 15[cw32 log]: msg: peek = 0x55[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 14[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 13[cw32 log]: msg: peek = 0x01[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 12[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 11[cw32 log]: msg: peek = 0x01[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 10[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 9[cw32 log]: msg: peek = 0x04[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 8[cw32 log]: msg: peek = 0x00[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 7[cw32 log]: msg: peek = 0x02[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 6[cw32 log]: msg: peek = 0x03[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 5[cw32 log]: msg: peek = 0x04[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 4[cw32 log]: msg: peek = 0x12[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 3[cw32 log]: msg: peek = 0x1B[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 2[cw32 log]: msg: peek = 0x55[cw32 log]: err: parse failed !!! move the window and trigger parse again.[cw32 log]: msg: increase = 1[cw32 log]: msg: peek = 0xAA
6.7 篇幅有限,其余测试命令帧自行测试
七、设计亮点
零耦合:通过 parser_ops_t 函数指针表与队列完全解耦,换队列实现不改解析器一行代码
状态持久化:状态机状态保存在 proto_parser_t 结构体中,天然支持数据分批次到达
CRC延迟计算:只有帧头、帧尾、长度全部校验通过后才计算 CRC,避免无效运算
自动连续处理:一帧处理完毕后,如果队列中还有数据,自动递归触发下一帧解析
错误自恢复:任何状态出错,窗口滑动 1 字节重新开始,不会卡死
可配置性强:最大数据长度、CRC 算法选择、日志开关等均可通过宏配置
面向对象设计:C 语言实现面向对象思想,多实例互不干扰
八、总结
这个滑动窗口协议解析库的核心价值在于:
把"数据接收"和"协议解析"彻底分离——你只管往队列里扔数据,解析器自己会滑动窗口去找帧、校验帧、处理帧。
代码量小(核心不到 400 行)、可移植性强(纯 C,无平台依赖)、设计清晰(状态机 + 函数指针表),适合直接嵌入到你的嵌入式项目中。
九、题外话
在测试demo中,serv_parser.h 中定义了如下信息:
#define DEMO_UART_ROUND 0 // 测试串口回环(将收到的数据发出)#define DEMO_PROTO_PARSER 1 // 测试解析器#define TEST_DEMO_SELECT DEMO_PROTO_PARSER
默认是测试解析器,用户还可以选择测试串口回环
#define TEST_DEMO_SELECT DEMO_UART_ROUND
感兴趣的小伙伴可以用来测试CW32串口结合simple queue队列回环透传的性能,用串口助手给单片机发送数据,单片机会将该数据返回,博主测试115200波特率下,发送561100字节,接收561100字节,收发数量一致,没有丢包,性能不错。
扫码加入QQ群,3群| 610403240
163