【嵌入式外设精学】Day11粘包与半包状态机解帧今天解决中断一次来半个包、两个包解析器怎么写才不乱目录问题 2. 原理 3. 代码 4. 误区 5. 自测 6. 答案1. 问题把“收到多少字节”当成“一条消息”数据就会错位。2. 原理2.1 字节流 vs 报文串口给你的是一串字节不是一条条消息。解析层要从流里“抠”出报文现象含义半包一次只来帧的前半粘包一次来两帧甚至多帧错峰上次没解析完污染下次2.2 状态机四步找头 → 收长度 → 收负载 → 收校验 → 成功/丢弃 → 回到找头成功或失败后都要回到“找头”否则永久错位。2.3 不变量好解析器的标准一次只吃 1 个字节不假设“一次一帧”长度有上限防溢出校验失败立刻复位3. 代码示例#includestdint.h#includestdbool.h#includestring.h#defineHDR0xAAu#defineMAX_DATA8utypedefstruct{uint8_tcmd;uint8_tdata[MAX_DATA];}msg_t;typedefenum{ST_HEAD,ST_LEN,ST_CMD,ST_DATA,ST_SUM}st_t;typedefstruct{st_tst;uint8_tidx;uint8_tlen;uint8_tcmd;uint8_tbuf[MAX_DATA];}parser_t;voidparser_init(parser_t*p){memset(p,0,sizeof(*p));p-stST_HEAD;}staticuint8_tadd_sum(constuint8_t*d,uint32_tn){uint8_ts0;while(n--){s(uint8_t)(s*d);}returns;}/* 每收 1 字节调用返回 true 表示 out 里有一条完整消息 */boolparser_feed(parser_t*p,uint8_tb,msg_t*out){switch(p-st){caseST_HEAD:if(bHDR){p-stST_LEN;}break;caseST_LEN:if(bMAX_DATA){parser_init(p);break;}p-lenb;p-st(b0u)?ST_SUM:ST_CMD;break;caseST_CMD:p-cmdb;p-idx0;p-st(p-len0u)?ST_SUM:ST_DATA;break;caseST_DATA:p-buf[p-idx]b;if(p-idxp-len){p-stST_SUM;}break;caseST_SUM:{uint8_ts(uint8_t)(HDRp-lenp-cmd);s(uint8_t)(sadd_sum(p-buf,p-len));if(sbout!0){out-cmdp-cmd;memcpy(out-data,p-buf,p-len);parser_init(p);returntrue;}parser_init(p);break;}default:parser_init(p);break;}returnfalse;}4. 常见误区误区正解解析完不复位下一条永久错位在 ISR 里做复杂业务只入队主循环解析len 不检查缓冲区溢出用while一次性读完假设流是分片到的5. 自测连续两帧同时到状态机要怎样校验错了怎么做6. 答案处理完第一帧立即复位继续吃后面的字节。丢弃并回ST_HEAD不交给业务。作者shuifanyu标签状态机串口协议粘包