找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 551|回复: 6
打印 上一主题 下一主题
收起左侧

串行通信数据包解析不正常

[复制链接]
跳转到指定楼层
楼主
ID:705846 发表于 2026-6-6 17:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式


通过PC机串口发送hex数据 5A 02 01 02 5F。返回给PC的数据不稳定,有时是5A 02, 有时是5A 01, 并且每次都返回四次。请大家帮忙分析一下原因。抛砖引玉。谢谢。

unsigned char tempsbuf;

typedef enum{
        WAIT_HEADER,
        WAIT_LEN,
        WAIT_DATA,
        WAIT_CHECKSUM
} ParserState;

typedef struct Parser{
        ParserState state; //当前状态
        unsigned char len;        //数据长度
        unsigned char buf_data[3]; //数据缓冲区
        unsigned char index;        //数据接收计数
        unsigned char checksum;        //检验和
} Parser;

void parser_init(Parser *p){
        p->state=WAIT_HEADER;
        p->len=0;
        p->index=0;
        p->checksum=0;
}

int parser_input_byte(Parser *p,unsigned char byte){
        switch(p->state){
                //1、等待帧头0x5a
                case WAIT_HEADER:
                        if(byte==0x5A){
                                p->checksum=byte; //累加校验
                                p->state=WAIT_LEN;
                        }
                break;
                //2、等待长度
                case WAIT_LEN:
                        p->len=byte;
                        p->checksum+=byte;
                        p->index=0;
                        p->state=WAIT_DATA;
                break;
                //3、等待数据
                case WAIT_DATA:
                        p->buf_data[p->index++]=byte;
                        //p->index++;
                        p->checksum+=byte;
                        if(p->index>=p->len){p->state=WAIT_CHECKSUM;}
                break;
                //4、等待校验
                case WAIT_CHECKSUM:
                        p->state=WAIT_HEADER; //解析完成回到初始状态
                                if(byte==p->checksum){
                                        return 1;
                                }
                                break;
        }       
        return 0;
}


void Uart() interrupt 4 //串行中断函数
{
        if(TI)
                {                       
                        TI=0; //清楚TI传送完成标记
                }
               
        if(RI){       
                RI=0;
                tempsbuf=SBUF;
               
        }
       



Parser p;
parser_init(&p);

void main(){
    while(1)
        {
               
                if(parser_input_byte(&p,tempsbuf)){
                SendByte(p.buf_data[0]);
                SendByte(p.buf_data[1]);
                }
        }

}
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:161164 发表于 2026-6-8 13:16 | 只看该作者
啥芯片?晶振多少?串口初始化代码呢?
回复

使用道具 举报

板凳
ID:384109 发表于 2026-6-8 16:02 | 只看该作者
接收数据的判断应该在串口接收中断里判断
回复

使用道具 举报

地板
ID:1133081 发表于 2026-6-8 20:00 | 只看该作者
楼主说明下通讯协议
回复

使用道具 举报

5#
ID:1064915 发表于 2026-6-9 08:38 | 只看该作者
串口初始化,误差大
回复

使用道具 举报

6#
ID:1064915 发表于 2026-6-9 08:47 | 只看该作者
这段代码实现了一个基于状态机的串口数据包解析器,结构基本正确,但存在几个严重且典型的问题,在实际项目中可能导致数据错乱、死循环甚至程序崩溃。下面逐一分析。

1. 代码功能简述
  • 协议格式:帧头 0x5A + 长度(len) + 数据(len字节) + 校验和(累加和)
  • 校验方式:累加和(包含帧头、长度、全部数据字节)
  • 状态机:WAIT_HEADER → WAIT_LEN → WAIT_DATA → WAIT_CHECKSUM → 回到 WAIT_HEADER
  • 成功解析后:发送 buf_data[0] 和 buf_data[1](假定长度至少为2)


2. 存在的关键问题① 主循环与中断完全无同步 —— 数据竞争 / 重复解析 / 丢字节if(RI){    RI=0;    tempsbuf=SBUF;}// main loopif(parser_input_byte(&p,tempsbuf)){    ...}
  • tempsbuf 在中断中被写入,主循环随时读取,没有任何互斥或标志位。
  • 主循环运行极快,会在同一个字节上反复调用 parser_input_byte,导致状态机被多次喂入相同数据,完全打乱解析逻辑。
  • 同时,当新字节到来时,tempsbuf 可能正在被主循环读取,造成数据竞争。

正确做法:使用 volatile 标志位,中断置位,主循环检测后清除并处理。
volatile bit hasNewByte = 0;// 中断中:tempsbuf = SBUF;hasNewByte = 1;// 主循环:if(hasNewByte) {    hasNewByte = 0;    if(parser_input_byte(&p, tempsbuf)) { ... }}
② 数据缓冲区 buf_data[3] 没有长度保护,极易溢出unsigned char buf_data[3;...p->buf_data[p->index++ = byte;if(p->index >= p->len) { p->state = WAIT_CHECKSUM; }
  • 如果通信对方发来的 len 大于 3,index 会超过数组边界,直接破坏内存(溢出到后续成员或栈)。
  • 必须在 WAIT_LEN 收到长度后检查合法性,否则应当视为无效帧并丢弃。

case WAIT_LEN:    p->len = byte;    if(p->len > sizeof(p->buf_data)) {        p->state = WAIT_HEADER;  // 长度非法,复位        break;    }    ...
③ 同步恢复能力极差,错误字节极易造成永久失步
  • 当前状态机在 WAIT_HEADER 下只认 0x5A,任何非帧头字节都被直接丢弃。
  • 一旦因干扰或丢字节导致状态机偏离,数据区出现的 0x5A 会被误认为帧头,从而产生一整帧错误解析,且无校验失败后的显式复位(虽然校验失败后会回到 WAIT_HEADER,但已错误地消费了若干字节,后续帧头完全错位)。
  • 更健壮的做法:增加帧尾、转义机制,或超时复位。

④ parser_init(&p) 位置错误 —— C 语言不允许在文件作用域执行函数Parser p;parser_init(&p);   // ← 这行不能在全局作用域调用void main() { ... }
这种写法在某些编译器下可能被当作声明甚至报错。必须移到 main 内部:
void main() {    parser_init(&p);    while(1) { ... }}
⑤ 校验和在失败后未清除,且校验包含帧头本身
  • p->checksum 初始为 0,在收到 0x5A 时置为 0x5A,然后累加长度和数据。最后与收到的校验字节比较。
  • 这要求发送方的校验和也必须包含帧头,否则永远校验失败。这一点不常见(通常校验只覆盖长度+数据),需确认协议双方一致。
  • 校验失败后,p->checksum 未清零,下次 WAIT_HEADER 会重新赋值为 0x5A,所以无累积问题,但逻辑上最好在校验分支内做明确清理以增强可读性。

⑥ main 函数缺少串口初始化
  • 寄存器未配置(波特率、工作方式等),中断使能未开启,实际完全无法接收数据。真实代码需要加入串口初始化。

⑦ 发送函数 SendByte 未定义,且 while(1) 内无其他保护
  • 若 SendByte 是阻塞查询方式发送,且没有流控,高速连续解析可能丢失发送数据。不过这不属于当前代码片段的核心问题。


3. 改进后的代码框架(关键部分)volatile bit hasNewByte = 0;unsigned char tempsbuf;// ... Parser 定义、parser_init、parser_input_byte(加入长度检查) ...void Uart() interrupt 4 {    if(TI) {        TI = 0;    }    if(RI) {        RI = 0;        tempsbuf = SBUF;        hasNewByte = 1;    }}void main() {    Parser p;    parser_init(&p);    Uart_Init();  // 假设已实现    while(1) {        if(hasNewByte) {            hasNewByte = 0;            if(parser_input_byte(&p, tempsbuf)) {                SendByte(p.buf_data[0);                SendByte(p.buf_data[1);            }        }    }}
并且在 WAIT_LEN 中添加:
if(byte > sizeof(p->buf_data)) {    p->state = WAIT_HEADER;    break;}
总结
代码展现了状态机解析思想,但缺少实时系统中的同步机制、无边界检查、初始化位置错误,是典型的“能跑但一有干扰就崩”的嵌入式代码。上述几点修正后,才能在实际串口通信中稳定工作。

回复

使用道具 举报

7#
ID:705846 发表于 2026-6-9 09:46 | 只看该作者
人中狼 发表于 2026-6-8 16:02
接收数据的判断应该在串口接收中断里判断

对的。在接收中断内判定是好很多,但还是有误码问题。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表