• 正文
  • 相关推荐
申请入驻 产业图谱

【教程】CW32L012滑动窗口解析库

15小时前
163
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

往期的文章中,我们分享了简单无锁队列(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

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

以开放、共享、互助为理念,致力于构建武汉芯源半导体CW32系列MCU生态社区。无论是嵌入式MCU小自还是想要攻破技术难题的工程师,亦或是需求解决方案的产品经理都可在CW32生态社区汲取营养、共同成长。