最近在用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这种带有干扰数据,但是又是合法数据的数据。如果需要数据校验,可以在结束符之前添加校验位。这次修改后的协议目前看来还是比较理想的,可以考虑在实际项目中应用。