STM8S003单片机串口通信通信协议分析

最近在用STM8S003这个片子做项目,在做串口通信的时候,发现以前写的协议太简单了,项目中用不适合。

//协议 : 0XDD xx xx  xx xx xx xx  0XAA 

@far @interrupt void UART1_Receive(void)

{
unsigned char res;
res=UART1_DR;
if(res==0xDD)       //头
{
Rec_statu=1;     //标志开始接收
Rec_Cnt=0;
Rec_End=0;
return;
}
if(res==0xAA)       //尾
{
Rec_statu=0;
Rec_End=1;        //标志接收完成
SendData();       //接收完一组数据后 在发送一组数据出去
return;
}
if(Rec_statu==1)           //如果处于数据接收中
{
Rec_Buf[Rec_Cnt++]=res;
}
return;
}

这是测试用的一个简单协议,用0XDD作为数据头,0XAA作为数据尾。不限制数据长度。能进行简单的通信功能,但是实际项目中应用时,如果传输的数据中有0XDD或者0XAA,这个协议在解析是就可能出错。

于是决定修改协议,将协议的头改为两位数据。

// A5 5A(头) 45(数据类型) 04(数量) 0x00 0x00 0x00 0x00 0xFF(校验)
// A5 5A 45 04 08 A0 09 B0 5E
@far @interrupt void UART1_Receive(void)
{
unsigned char res;
unsigned int check=0x00;   //  校验
res=UART1_DR;
UART1_SR&=~(1<<5);      //RXNE 清零
if((res==0xA5)&&(Rec_head==0))               //头
{
Rec_head=1;             //标志开始接收
Rec_statu=0;
Rec_Cnt=0;
Rec_End=0;
return;
}
if((res==0x5A)&&(Rec_head==1))
{
Rec_head=1;             //成功接收到了数据头 正式开始接收数据
Rec_statu=1;
Rec_Cnt=0;
Rec_End=0;
return;
}
if(Rec_Cnt==6)              //尾
{
check=(0x5a+0x5a+Rec_Buf[0]+Rec_Buf[1]+Rec_Buf[2]+Rec_Buf[3]+Rec_Buf[4]+Rec_Buf[5]);
if(res==(check&0xff))          //数据正确 存储接收到的温度值
{
//在这块处理数据时 串口中断会进不来
//env_tem=Rec_Buf[4]*256+Rec_Buf[5];    //环境温度值 2480
//obj_tem=Rec_Buf[2]*256+Rec_Buf[3];    //目标温度值 2208
Rec_End=1;            //标志接收完成
}
else
Rec_End=0;            //接收失败
Rec_head=0;
Rec_statu=0;
Rec_Cnt=0;
return;
}
if(Rec_statu==1)           //如果处于数据接收中
{
Rec_Buf[Rec_Cnt]=res;
Rec_Cnt++;
return;
}
return;

}

      数据头变成了两位,数据长度为固定值,增加了校验位。看起来貌似没有什么问题,但是仔细分析,发现还是有很多漏洞。

       当接收到0XA5时,准备开始接收数据,当收到0X5A时,正式开始接收数据,然后统计数据的个数,当收到规定个数数据时,结束接收数据。 如果发送的数据是 A5 5A 45 04 08 A0 09 B0 5E时,可以正常接收到数据。但是如果发送的数据是 A5 00 01 02 03 5A 45 04 08 A0 09 B0 5E 时,遇见0XA5标记数据准备接收,然后又接着收到了00 01 02 03这几个数据,此时由于接收数据没有正式开始,所以程序不会有任何动作,当接收到下一个数据0x5A时,程序开始正式接收数据。但是这笔数据是非法数据,被程序误判为正常数据。从而接收到了一组非法数据。

      那么能不能用下标来控制数据头的识别,比如如果遇到了0xA5,数据准备接收,同时开始计数,如果下一个数据是0x5A,那么标记开始正式接收数据。这样看起来好像能够避免上面发送 这笔数据 A5 00 01 02 03 5A 45 04 08 A0 09 B0 5E 的情况,但是如果发送的数据是 A5 A5 5A 45 04 08 A0 09 B0 5E 呢?我们来分析一下,如果遇到A5程序准备接收数据,同时开始计数,第二个数据是A5而不是设定的5A,那么判定为非法数据,计数器清零,准备重新接收,当第三个数据5A进来时,发现不是数据头A5,抛弃本次数据,重新等待数据头A5。显而易见这笔数据里面包含有正常数据,不过第一个数据是A5刚好和数据头重复了,导致本次数据里面的正常数据仍然被丢弃了。

        那么如果避免这种情况,可以考虑在接收的数据判断收到的头A5,并且后面紧跟的数据是5A,那么就说明接收到了正确的数据头。但是有个问题,本次接收的数据如果是A5,下次的数据还不知道是不是5A,那么本次的数据是要丢弃还是要存储起来,如果要存储起来,那么后面连续好多个5A的话,如果区分哪个5A是哪一次接收的。这样判断起来程序就会显得极为复杂,那么有没有简单有效的方法呢?可以换个思维,能不能判断如果这次接收到了数据尾5A,判断上次接收的数据是A5,那么说明接收到了数据头。这样的话如果有连续多个A5的话,只要后面不是5A,那么就不会开始接收数据。新修改的协议如下:

//接收数据标志位 各个位含义
//bit7:接收到尾    0xA5 0x5A  0 未收到  1 收到
//bit6:接收到头    0x55 0xAA  0 未收到  1 收到
//bit0-5:接收到数据个数      最多64个数据
unsigned  char rec_flag = 0;            //接收数据标志位
unsigned  char rec_buf[20] = {0}; //接收数据存储
//串口接收数据格式为:  头1(0xA5) 头2(0x5A)  数据N位  尾1(0x55) 尾2(0xAA)
// 0xA5 0x5A xx xx xx xx xx xx xx xx xx xx xx  0x55 0xAA

//接收中断函数 中断号18
#pragma vector  =  20                   // IAR中的中断号,要在STVD中的中断号上加2
__interrupt void UART1_Handle( void )
{
    unsigned char res = 0;
    if( rec_flag & 0x80 )                               //如果接收数据结束返回 数据被读取后才清0 标志位
    {
        return;
    }
    res = UART1_DR;
    UART1_SR &= ~( 1 << 5 );                                //RXNE 清零
    rec_flag++;                                         //接收数据计数
    //这种方式判断,可以有效识别类似 A5 A5 A5 5A XX XX 这样的数据开头
    //如果只判断第一位是A5,第二位是5A的话,上面的这种数据就判定为非法数据,不能有效识别
    if( ( res == 0xA5 ) && ( ( rec_flag & 0x40 ) == 0 ) )       //如果没有开始接收数据,并且当前的数据是0xA5 将计数清零
    {
        rec_flag = 0;
        rec_buf[0] = res;
        return;
    }
    if( ( rec_flag & 0x3F ) == 0x01 )                   //第二位数据
    {
        if( res == 0x5A )                               //如果第二位数据是0x5A 说明已经正确收到了数据头
        {
            rec_buf[rec_flag & 0x3F] = res;
            rec_flag |= ( 1 << 6 );                         // 接收到头第2位   0x5A     置标志位
        }
        else
        {
            rec_flag = 0;                                   //接收错误,重新开始
        }
        return;
    }
    if( ( ( rec_flag & 0x40 ) == 0x40 ) && ( ( rec_flag & 0x80 ) == 0 ) ) //已经成功接收到了头并且没有收到尾
    {
        //本次接收的是0xAA 上一笔数据是0x55 说明数据传输结束
        //这种方式可以有效识别 数据和结束符一样的情况如 XX XX XX 55 55 55 AA
        //如果接收到55就开始判断的话,上述的情况就会识别为非法数据,不能有效判断
        if( res == 0xAA )                               //接收到尾 第2位
        {
            rec_buf[rec_flag & 0x3F] = res;
            if( rec_buf[( rec_flag & 0x3F ) - 1] == 0x55 ) //如果上次接收的数据是 尾第1位
            {
                rec_flag |= ( 1 << 7 );                     //接收数据结束
            }
        }
        else
        {
            rec_buf[rec_flag & 0x3F] = res;                 //存储接收到的数据
        }
    }
}


    这次协议改为了,数据头为两位 A5 5A ,数据尾也为两位 55 AA。数据长度可以为任意值,校验位暂时没加进去。

这次判断的流程为,如果接收到的数据是A5那么就把计数值清0,如果不是A5,接收数据,并把计数值加1,然后在判断第一位数据是不是5A,如果不是5A,那么重新清0,重新接收。 这样的话 如果发送的数据是 A5 00 00 5A xx xx 时,第一位数据5A,计数器清0,然后接收的第一位数据是00,不等于5A,那么计时器清0,重新开始接收。如果发送的数据是 A5 A5 A5 5A xx xx时,接收的第一个数据是5A,计时器清0,接收的第二个数据还是5A,那么计时器的值还是0,当第三个5A来时,计数器的值还为0,当第四个数据来时,不为A5,那么接收数据,计数器加1,此时计数器的值为1。然后判断计数器的值为1时,收到的数据是5A,开始正式接收数据。这样就可以有效的判断到数据头,不会误判和漏判。同样数据结束时也用两位判断 55 AA,当本次接收的数据是AA,同时判断上一次接收的数据是55时,说明数据接收结束。由于判断数据尾时,前面接收的数据都在数组中存储着,所以判断本次接收的数据和前一次接收的数据可以同时进行。比判断数据头简单。

      修改后的协议可以接收 A5 5A xx xx xx xx 55 AA这样的标准数据,同时也能有效的判断到 类似 A5 A5 A5 A5 5A xx xx xx xx 55 55 55 55 AA这种带有干扰数据,但是又是合法数据的数据。如果需要数据校验,可以在结束符之前添加校验位。这次修改后的协议目前看来还是比较理想的,可以考虑在实际项目中应用。

 

 

你可能感兴趣的:(STM8S003单片机串口通信通信协议分析)