分享

15W4K58S4 实验15:串行通信中的错误校验(校验和)

 360tsgyd 2018-07-08

实验笔记:校验和与串口通信实例

数据通信中的错误校验
​  
数据通信难免发生错误,为了让接收端判断数据传输过程是否发生错误,需要在发送的数据中传送额外的附加数据,附加数据常用的是校验和与CRCCRC的计算很占用时间,适用于对数据准确性要求很苛刻的场合;校验和对数据准确性的判断能力低于CRC,但占用时间极少,为了缩短程序执行时间和简化程序编写,建议优先选用校验和作为数据校验方式。

 校验和( CheckSum)
​   
校验和的方法就是把需要发送或接收的一组数据(或字符的 ASCII)进行相加计算,计算其总和后将此数据与某一数字(通常是256)相除,取其余数,将此余数组合成发送数据的一部分发送出去。
​    
同样,接收数据的一方也以相同的方式将所发送过来的数据进行相加计算,并与发送方所发过来的计算值比较,若其值相同,则代表所发送的数据是正确的,反之则是错误的,检查错误时,接收方可能要求发送方重新发送,以确保数据的正确性。

例如,被发送的数值为0xAB  0xCD  0xEF  0x01  0x020  x03,将它们的数值相加结果是0x026D,以十进制表示为621,256相除后取余数,其值为109,再转换成十六进制为0x6D

发送数据时在数据的尾端再加上一个字节0x6D,因此实际发送出去的数据成为:

0xAB  0xCD  0xEF  0x01  0x02  0x03  0x6D,

对方收到所发送的数据后会根据以上方式再进行一次计算,如果计算出来的结果是0x6D,表示此次发送数据是正确的。
​    
在单片机中,一般是定义一个无符号整型变量,用这个无符号整型变量存储多个二进制数值相加的结果,然后将这个整型变量对256求余数(C语言中的运算符为%),求余后的数值不会大于255,是整数,所以还需要强制转换成无符号char型才能发送出去,这里的强制转换实际上就是把整数的低字节保留,高字节舍弃。

校验和一般用于环境干扰不大或对数据准确性要求不算太高的场合,最大的优点是计算过程简单,占用单片机时间极少,缺点是有少数错误检查不出来,比如发送2个数据,100200,校验和(100+200)%256=44,若数据发送过程中受到干扰100变成了110,200变成了190,接收方对数据校验(110+190)%256=44,与发送方计算的校验和相同,这时就把错误的数据当作正确的数据处理了。

例:串口通信的完整格式与校验和实例

通信协议:每次计算机串口助手向单片机发送5个字节的数据,

第一个字节为0x7E,数据开始标志(即帧头),后面3个字节为任意数据,最后一个字节为前4个数据和的低字节(高字节忽略),即校验和。

单片机接收到5个字节后,如果校验正确,发回第1字节0x7E作为帧头,234字节为接收的234字节加1后的数据,第5字节为前4个字节的校验和。

使用11.0592MHz内部R/C时钟,波特率9600,最为常见的N.8.1帧格式(无奇偶校验、8位数据位、1位停止位)

测试方法:在STC串口助手发送区输入数据:7E1234561A,选择HEX发送,HEX显示,单击发送后接收窗口立即显示7E1335571D,则测试成功。如果串口助手向单片机发送数据后接收不到单片机返回数据,重点检查波特率设置是否正确。

51单片机串口接收和发送校验和测试程序:接收采用中断方式,发送采用查询方式,选择T2作串口波特率发生器,这样可以使T0T1空出来做别的事。

关于格式化输出:

%0x%x都是以十六进制格式右对齐输出,输出的是无符号数。

在不指定占宽情况下以数据的实际宽度输出,而系统又自动消除左端的无效0,所以%0x%x在显示效果上没有什么不同。

在指定占宽的情况下,在指定的输出占宽范围内,实际数据宽度不足时用%0x作控制的前面用0补齐,而用%x作控制的前面用空格补齐。如:

printf(“%04X\n”);

表示十六进制显示,占4个字符的位置,不足的补0

​源代码:
​#include "STC15W4K.H"
#define uchar unsigned char
#define uint  unsigned int


#define FMBEGIN 0x7e      //帧头标志
uchar idata RecCount;     //串口接收计数器,全局变量没有赋值以前,系统默认为0
uchar idata RecBuf[5];    //接收缓冲区 数据格式为:帧头+3字节数据+校验和
uchar idata SendBuf[5];   //发送缓冲区 数据格式为:帧头+3字节数据+校验和

​//串行口1初始化
void UART1_init(void)
{
 SCON = 0x50;  //8位数据,可变波特率
 AUXR |= 0x01;  //串口1选择定时器2为波特率发生器
 AUXR |= 0x04;  //定时器2时钟为Fosc,即1T
 T2L = 0xE0;      //设定定时初值
 T2H = 0xFE;      //设定定时初值


​ AUXR |= 0x10;  //启动定时器2

 ES=1;   //开启UART1中断
 EA=1;  //开启总中断
}


void sendcombytes(uchar *ptr, uchar len)
{
 uchar idata i;
 for(i=0; i<len; i++)
 {
  SBUF=*(ptr+i);
  while(TI==0);
  TI=0;
 
 }
}


//串口1中断服务程序
void UART1(void) interrupt 4
{
 if(RI)  //如果串口1发生接收中断
 {
  RI=0;  //清接收中断标志
 
  if(RecCount==5) RecCount=0; 

  RecBuf[RecCount]=SBUF;

  if(RecCount==0) //判断帧头是否正确
  {
   if(RecBuf[RecCount]==FMBEGIN) //帧头正确
   {
    RecCount++;
   }
   else
   {        //等待接收帧头
    RecCount=0;
   }
  }
  else
  {
   RecCount++;
  }
 }

}


uchar CheckSum(uchar *ptr, uchar len)
{
 uchar idata i;
 uchar idata a;
 uint  idata Value=0;

 for(i=0; i<len; i++)
 {
  Value=Value+ptr[i];
 }
 a=Value;

 return(a);
}
 

//主函数入口
void main()
{
  uchar data i;
  uchar idata CheckValue;

 UART1_init();  //串行口初始化
    
 while(1)
 {
  if(RecCount==5)
  {
   RecCount=0;
   CheckValue=CheckSum(RecBuf,4);

   if(CheckValue==RecBuf[4])
   {
    SendBuf[0]=FMBEGIN;
    for(i=1; i<4; i++)
    {
     SendBuf[i]=RecBuf[i]+1;
    }
    CheckValue=CheckSum(SendBuf,4);
    SendBuf[4]=CheckValue;
    sendcombytes(SendBuf,5);
   }
   else
   {
    SendBuf[0]=FMBEGIN;
    for(i=1; i<5; i++)
    {
     SendBuf[i]=0xaa;
    }
    sendcombytes(SendBuf,5);
   }
  }

 }//while(1)
}
​​实验效果:

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多