关于串口接收的处理策略

  • Post author:
  • Post category:其他


关于串口接收的处理策略

一 需求:

1.每帧的数据量:20~1045字节之间

2.数据频率:10ms级别

3.能够识别错误帧和正确帧,并丢弃错误帧,留下正确帧

4.数据采用两个字节的头码(7E 7E),中间有数据长度,最后一个字节为CRC8校验码

二 问题

由于发送端的数据通过串口到达接收端时,每帧数据不是完整一次性发过来的,可能会分成2包、3包

或者4包数据发过来(也就是说一帧数据分成多少包发过来,我们是不知道的),那么如何接收到一帧

完整的数据,则是我们必须要解决的了。

三 解决思路

一开始遇到这个问题时就想,既然一帧数据分成几包发过来,那么也就是说其实想要收到一帧完整的数据,

是需要一定的时间的,那么只要给它设置个定时不就行了吗?思路如下:

在串口接收到数据时,就启动一个定时器,开始计时,待定时时间一到,就从接收缓冲区中取出这帧数据,

同时将定时器清0,以便下一帧数据到来时启用。

有了思路,代码就好办了。敲好代码,编译,上电,测试。发现还是有效果的,但是尴尬的事情来了。

当通讯的每帧数据的长度相差不大时,这个方法还是挺奏效的,能够正确分出每帧数据。但当通讯的每帧

数据长度相差悬殊时,如20个字节和1045字节,结果就比较尴尬了。这个时候,定时的时间长度就不好定了。

因为接收1045和20个字节的时间势必不一样,这时如果为了接收1045字节的数据而把定时时间定长了,则会出现

粘包的情况(指接收时,原本发送是两帧数据的,接收成一帧数据了);而出现粘包的情况,则是因为定时的时间

定长了,于是将定时时间改短点,则接收较长的数据时,则出现了断帧的现象。总之,一句话,定时时间不好把握。

这个方法夭折了。下面看看另外一种解决方法。

观察每帧数据,其实都是由头码、数据长度、数据内容、CRC校验码组成,那么我们是否可以从这些特征中着手呢?

当然可以。只要每次去判断缓冲区中出现7E 7E(头码)的地方,然后从这个地方开始读,先获取到这一帧的数据长

读(存放数据长度的位置是固定的),再根据这个长度去读取这一帧数据,这样就拿到一帧完整数据了。当然是否可行

还得测试之后才知道。敲好代码,编译,上电,测试。效果不错,长数据和短数据都能够顺利接收。但这样的接收处理

逻辑还不足以胜任,因为一旦数据内容中也含有头码的内容(数据内容也出现7E 7E,暂称伪头码),而当我们搜索7E 7E

是通过遍历的方式的,也就是说,数据内容中的7E 7E 有可能会被我们误认为是头码,这时我们会将伪头码之后的数据当

成一帧正确的数据(其实是错误的数据)交给程序进行解析处理。那么在这里加上CRC校验码进行检测该帧数据是否为正确

数据则是非常必要的了。下面再完善下这个接收处理逻辑:

有一个缓冲区(uartbuf)用来存放串口每次接收到的数据,串口收到数据后,我们就直接判断缓冲区的头两个字节是否为

头码内容,如果符合要求,则根据数据长度接收完这帧数据,之后进行CRC校验判断,若能满足,则表示这帧数据是对的。

若这帧数据不能满足CRC校验,则说明这是一帧错误是数据,有可能我们拿到的头码是伪头码,此时我们需要重新处理这帧

数据,将伪头码遗弃,再在缓冲区中遍历出现头码的地方,然后将其以及之后的数据复制到缓冲区的头部(主要是为了方便处理了)。

如果我们一开始判断缓冲区的头两个字节不是头码内容时,则我们判断第三、第四个字节,以此类推,直至找到头码内容。有个原则,

我们要保证缓冲区的头两个字节为头码内容,这样做只是为了方便处理。

下面贴出代码:

dataIndex += uart_length;  //dataindex为缓冲区索引,而uart_length则表示此次串口接收到的数据长度

if(dataIndex >= UART_BUF_SIZE){  //做防边界溢出处理
    dataIndex = 0;
    continue;
}
if(dataIndex >= 18 && uartbuff[0] == 0x7E && uartbuff[1] == 0x7E){   //判断当前缓冲区中的数据长度是否满足18,主要是为拿到该帧的数据长度 &&判断是否满足头码内容  
    rxlenth = ((uint16_t)uartbuff[16] << 8)+(uint16_t)uartbuff[17]+19;  //拿到数据长度
    if(rxlenth == dataIndex){
        if(uartbuff[rxlenth-1] == CalcCheckSum((uint8_t *)&uartbuff[2] , rxlenth-3)){  //CRC校验检测
            memcpy(uartbuf , uartbuff , rxlenth);
            recvFlag = 0x8000;
            dataIndex = 0;
        }else{                                                             //若不能满足CRC校验,则丢弃伪头码
            if(uartbuff[2] == 0x7E){                                         //需要注意此时有可能是3个头码内容连在一起,即7E 7E 7E,有可能后两个头码内容才是正确的
                memcpy(uartbuff , &uartbuff[1] , dataIndex-1);
                dataIndex = dataIndex -1;
            }else{
                memcpy(uartbuff , &uartbuff[2] , dataIndex-2);
                dataIndex = dataIndex -2;
            }
        }
    }
    else if(rxlenth < dataIndex){                    //有可能缓冲区中数据长度大于当前帧的数据长度,即含有下一帧数据的部分内容
        if(uartbuff[rxlenth-1] == CalcCheckSum((uint8_t *)&uartbuff[2] , rxlenth-3)){
            memcpy(uartbuf , uartbuff , rxlenth);
            recvFlag = 0x8000;
            memcpy(uartbuff , &uartbuff[rxlenth] , dataIndex-rxlenth);  //需要复制到缓冲区前部,与下一次串口接收到的内容进行组帧
            dataIndex = dataIndex-rxlenth;
        }else{
            if(uartbuff[2] == 0x7E){
                memcpy(uartbuff , &uartbuff[1] , dataIndex-1);
                dataIndex = dataIndex -1;
            }else{
                memcpy(uartbuff , &uartbuff[2] , dataIndex-2);
                dataIndex = dataIndex -2;
            }
        }
    }
}else{                                        //用来处理当缓冲区的头两个字节不是头码内容的情况,这时需要遍历缓冲区,并将头码内容以及之后的数据复制到缓冲区前部
    if(dataIndex >= 18){
        for(i = 0;i<dataIndex-1;i++){
            if(uartbuff[i] == 0x7E && uartbuff[i+1] == 0x7E){
                memcpy(uartbuff , &uartbuff[i] , dataIndex-i);
                dataIndex = dataIndex-i;
                i = 0;
                break;
            }
        }
        if(i == dataIndex-1){            //用来处理当遍历完缓冲区,找不到头码内容时,需要将这些数据都丢弃
            dataIndex = 0;
        }
    }



版权声明:本文为u010456460原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。