UART

 

 

 

 

 1. two clocks need not synchronized.

 

2.bit/s need sychronized.

 

3.Low Jump is suchrony signal;

 

4.Receive: 16* bps.  Record the value at every 8th.( which is middle he supposes)

 

5.two MCU communicates using UART,  wrong symbols happen.

 

6.9600bps setting. Do experiment.  104us for one bit is observed for both MCU, which means that setting of bps is very accurate. 

 

7. SBUF=49.    in HEX is 0x31 which is observed in Wave Display.

 

 

 

 

 

 

串口通信与编程01:串口基础知识

 

  串口是串行接口(serial port)的简称,也称为串行通信接口或COM接口。

  串口通信是指采用串行通信协议(serial communication)在一条信号线上将数据一个比特一个比特地逐位进行传输的通信模式。

  串口按电气标准及协议来划分,包括RS-232-C、RS-422、RS485等。

 

1.串行通信

  在串行通信中,数据在1位宽的单条线路上进行传输,一个字节的数据要分为8次,由低位到高位按顺序一位一位的进行传送。

  串行通信的数据是逐位传输的,发送方发送的每一位都具有固定的时间间隔,这就要求接收方也要按照发送方同样的时间间隔来接收每一位。不仅如此,接收方还必须能够确定一个信息组的开始和结束。

  常用的两种基本串行通信方式包括同步通信和异步通信。

1.1串行同步通信

  同步通信(SYNC:synchronous data communication)是指在约定的通信速率下,发送端和接收端的时钟信号频率和相位始终保持一致(同步),这样就保证了通信双方在发送和接收数据时具有完全一致的定时关系。

  同步通信把许多字符组成一个信息组(信息帧),每帧的开始用同步字符来指示,一次通信只传送一帧信息。在传输数据的同时还需要传输时钟信号,以便接收方可以用时针信号来确定每个信息位。

  同步通信的优点是传送信息的位数几乎不受限制,一次通信传输的数据有几十到几千个字节,通信效率较高。同步通信的缺点是要求在通信中始终保持精确的同步时钟,即发送时钟和接收时钟要严格的同步(常用的做法是两个设备使用同一个时钟源)。

  在后续的串口通信与编程中将只讨论异步通信方式,所以在这里就不对同步通信做过多的赘述了。

1.2串行异步通信

  异步通信(ASYNC:asynchronous data communication),又称为起止式异步通信,是以字符为单位进行传输的,字符之间没有固定的时间间隔要求,而每个字符中的各位则以固定的时间传送。

  在异步通信中,收发双方取得同步是通过在字符格式中设置起始位和停止位的方法来实现的。具体来说就是,在一个有效字符正式发送之前,发送器先发送一个起始位,然后发送有效字符位,在字符结束时再发送一个停止位,起始位至停止位构成一帧。停止位至下一个起始位之间是不定长的空闲位,并且规定起始位为低电平(逻辑值为0),停止位和空闲位都是高电平(逻辑值为1),这样就保证了起始位开始处一定会有一个下跳沿,由此就可以标志一个字符传输的起始。而根据起始位和停止位也就很容易的实现了字符的界定和同步。

  显然,采用异步通信时,发送端和接收端可以由各自的时钟来控制数据的发送和接收,这两个时钟源彼此独立,可以互不同步。

  下面简单的说说异步通信的数据发送和接收过程。

1.2.1异步通信的数据格式

  在介绍异步通信的数据发送和接收过程之前,有必要先弄清楚异步通信的数据格式。

  异步通信规定传输的数据格式由起始位(start bit)、数据位(data bit)、奇偶校验位(parity bit)和停止位(stop bit)组成,如图1所示(该图中未画出奇偶校验位,因为奇偶检验位不是必须有的,如果有奇偶检验位,则奇偶检验位应该在数据位之后,停止位之前)。

图1 异步通信数据格式

  (1)起始位:起始位必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。

  (2)数据位:数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定,一般可以是5位、7位或8位,标准的ASCII码是0~127(7位),扩展的ASCII码是0~255(8位)。传输数据时先传送字符的低位,后传送字符的高位。

  (3)奇偶校验位:奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位。

  举例来说,假设传输的数据位为01001100,如果是奇校验,则奇校验位为0(要确保总共有奇数个1),如果是偶校验,则偶校验位为1(要确保总共有偶数个1)。

  由此可见,奇偶校验位仅是对数据进行简单的置逻辑高位或逻辑低位,不会对数据进行实质的判断,这样做的好处是接收设备能够知道一个位的状态,有可能判断是否有噪声干扰了通信以及传输的数据是否同步。

  (4)停止位:停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是逻辑1电平,标志着传输一个字符的结束。

  (5)空闲位:空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。

1.2.2异步通信的数据发送过程

  清楚了异步通信的数据格式之后,就可以按照指定的数据格式发送数据了,发送数据的具体步骤如下:

  (1)初始化后或者没有数据需要发送时,发送端输出逻辑1,可以有任意数量的空闲位。

  (2)当需要发送数据时,发送端首先输出逻辑0,作为起始位。

  (3)接着就可以开始输出数据位了,发送端首先输出数据的最低位D0,然后是D1,最后是数据的最高位。

  (4)如果设有奇偶检验位,发送端输出检验位。

  (5)最后,发送端输出停止位(逻辑1)。

  (6)如果没有信息需要发送,发送端输出逻辑1(空闲位),如果有信息需要发送,则转入步骤(2)。

1.2.3异步通信的数据接收过程

  在异步通信中,接收端以接收时钟和波特率因子决定每一位的时间长度。下面以波特率因子等于16(接收时钟每16个时钟周期使接收移位寄存器移位一次)为例来说明。

  (1)开始通信,信号线为空闲(逻辑1),当检测到由1到0的跳变时,开始对接收时钟计数。

  (2)当计到8个时钟的时候,对输入信号进行检测,若仍然为低电平,则确认这是起始位,而不是干扰信号。

  (3)接收端检测到起始位后,隔16个接收时钟对输入信号检测一次,把对应的值作为D0位数据。

  (4)再隔16个接收时钟,对输入信号检测一次,把对应的值作为D1位数据,直到全部数据位都输入。

  (5)检验奇偶检验位。

  (6)接收到规定的数据位个数和校验位之后,通信接口电路希望收到停止位(逻辑1),若此时未收到逻辑1,说明出现了错误,在状态寄存器中置“帧错误”标志;若没有错误,对全部数据位进行奇偶校验,无校验错时,把数据位从移位寄存器中取出送至数据输入寄存器,若校验错,在状态寄存器中置“奇偶错”标志。

  (7)本帧信息全部接收完,把线路上出现的高电平作为空闲位。

  (8)当信号再次变为低时,开始进入下一帧的检测。

  以上就是异步通信中数据发送和接收的全过程了。

1.3几个概念

  为了更好的理解串口通信,我们还需要了解几个串口通信当中的基本概念。

  (1)发送时钟:发送数据时,首先将要发送的数据送入移位寄存器,然后在发送时钟的控制下,将该并行数据逐位移位输出。

  (2)接收时钟:在接收串行数据时,接收时钟的上升沿对接收数据采样,进行数据位检测,并将其移入接收器的移位寄存器中,最后组成并行数据输出。

  (3)波特率因子:波特率因子是指发送或接收1个数据位所需要的时钟脉冲个数。

 

2.串口接头

  常用的串口接头有两种,一种是9针串口(简称DB-9),一种是25针串口(简称DB-25)。每种接头都有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。9针串口的外观如图2所示。

图2 DB-9外观图

  由图2可以看出,在9针串口接头中,公头和母头的管脚定义顺序是不一样,这一点需要特别注意。那么,这些管脚都有什么作用呢?9针串口和25针串口常用管脚的功能说明如图3所示。

图3 9针串口和25针串口常用管脚功能说明

3.RS-232C标准

  常用的串行通信接口标准有RS-232C、RS-422、RS-423和RS-485。其中,RS-232C作为串行通信接口的电气标准定义了数据终端设备(DTE:data terminal equipment)和数据通信设备(DCE:data communication equipment)间按位串行传输的接口信息,合理安排了接口的电气信号和机械要求,在世界范围内得到了广泛的应用。

3.1电气特性

  RS-232C对电器特性、逻辑电平和各种信号功能都做了规定,如下:

  在TXD和RXD数据线上:

  (1)逻辑1为-3~-15V的电压

  (2)逻辑0为3~15V的电压

  在RTS、CTS、DSR、DTR和DCD等控制线上:

  (1)信号有效(ON状态)为3~15V的电压

  (2)信号无效(OFF状态)为-3~-15V的电压

  由此可见,RS-232C是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。

3.2信号线分配

  RS-232C标准接口有25条线,其中,4条数据线、11条控制线、3条定时线以及7条备用和未定义线。那么,这些信号线在9针串口和25针串口的管脚上是如何分配的呢?9针串口和25针串口信号线分配如图4所示。

图4 9针串口和25针串口信号线分配示意图

  下面对这些信号线做个简单的介绍。

  (1)数据装置准备好(DSR),有效状态(ON)表示数据通信设备处于可以使用状态。

  (2)数据终端准备好(DTR),有效状态(ON)表示数据终端设备处于可以使用状态。

  这两个设备状态信号有效,只表示设备本身可用,并不说明通信链路可以开始进行通信了,能否开始进行通信要由下面的一些控制信号决定。

  (3)请求发送(RTS),用来表示数据终端设备(DTE)请求数据通信设备(DCE)发送数据。

  (4)允许发送(CTS),用来表示数据通信设备(DCE)已经准备好了数据,可以向数据终端设备(DTE)发送数据,是对请求发送信号RTS的响应。

  请求发送(RTS)和允许发送(CTS)用于半双工的通信系统中,在全双工的系统中,不需要使用请求发送(RTS)和允许发送(CTS)信号,直接将其置为ON即可。

  (5)数据载波检出(DCD),用于表示数据通信设备(DCE)已接通通信链路,告知数据终端设备(DTE)准备接收数据。

  (6)振铃指示(RI),当数据通信设备收到交换台送来的振铃呼叫信号时,使该信号有效(ON),通知终端,已被呼叫。

  (7)发送数据(TXD),数据终端设备(DTE)通过该信号线将串行数据发送到数据通信设备(DCE)。

  (8)接收信号(RXD),数据终端设备(DTE)通过该信号线接收从数据通信设备(DCE)发来的串行数据。

  (9)地线(SG、PG),分别表示信号地和保护地信号线。

 

 

作者: 依旧淡然
邮箱:menlsh@163.com
本文版权归作者所有,未经作者同意,严禁转载及用作商业传播,否则将追究法律责任。
 
 
标签:  单片机相关
好文要顶  关注我  收藏该文   
6
0
 
 
 
« 上一篇: Android学习笔记20:Http协议及Java Web编程
» 下一篇: 朋友,一路走好
posted @  2013-01-28 21:48 依旧淡然 阅读(32500) 评论(2) 编辑 收藏
 

 
   回复引用
#1楼   2015-06-30 14:47 | makoto  
Friend,对门外汉来说太好懂了。。可是编程呢,,有后续的博文吗,谢啦!
   回复引用
#2楼   2015-12-01 11:24 | 一寸雪蓝  
 
 
 
 
 
 
 
 
 
 

51单片机串口通信的发送与接收(转)

 分类:
嵌入式C开发(7) 

http://club.topsage.com/thread-2765635-1-1.html

 

51单片机的串口,是个全双工的串口,发送数据的同时,还可以接收数据。
当串行发送完毕后,将在标志位 TI 置 1,同样,当收到了数据后,也会在 RI 置 1。
无论 RI 或 TI 出现了 1,只要串口中断处于开放状态,单片机都会进入串口中断处理程序。
在中断程序中,要区分出来究竟是发送引起的中断,还是接收引起的中断,然后分别进行处理。
看到过一些书籍和文章,在串口收、发数据的处理方法上,很多人都有不妥之处。
接收数据时,基本上都是使用“中断方式”,这是正确合理的。
即:每当收到一个新数据,就在中断函数中,把 RI 清零,并用一个变量,通知主函数,收到了新数据。
发送数据时,很多的程序都是使用的“查询方式”,就是执行 while(TI ==0); 这样的语句来等待发送完毕。
这时,处理不好的话,就可能带来问题。
看了一些网友编写的程序,发现有如下几条容易出错:
1.有人在发送数据之前,先关闭了串口中断!等待发送完毕后,再打开串口中断。
这样,在发送数据的等待期间内,如果收到了数据,将不能进入中断函数,也就不会保存的这个新收到的数据。
这种处理方法,就会遗漏收到的数据。
2.有人在发送数据之前,并没有关闭串口中断,当 TI = 1 时,是可以进入中断程序的。
但是,却在中断函数中,将 TI 清零!
这样,在主函数中的while(TI ==0);,将永远等不到发送结束的标志。
3.还有人在中断程序中,并没有区分中断的来源,反而让发送引起的中断,执行了接收中断的程序。
对此,做而论道发表自己常用的方法:
接收数据时,使用“中断方式”,清除 RI 后,用一个变量通知主函数,收到新数据。
发送数据时,也用“中断方式”,清除 TI 后,用另一个变量通知主函数,数据发送完毕。
这样一来,收、发两者基本一致,编写程序也很规范、易懂。
更重要的是,主函数中,不用在那儿死等发送完毕,可以有更多的时间查看其它的标志。


实例:
求一个PC与单片机串口通信的程序,要求如下:
1、如果在电脑上发送以$开始的字符串,则将整个字符串原样返回(字符串长度不是固定的)。
2、如果接收到1,则将P10置高电平,接收到0,P10置低电平。(用来控制一个LED)
单片机是STC89C52RC/晶振11.0592/波特率要求是9600或4800。谢谢!
问题补充:可能会将【$ABCD,123456,987654ccc,aasdasd,aaaa,sssd,4D】这样的字符串(字符串长度约为50-150个字符)传送给单片机,只能能原样返回。

[cpp]  view plain  copy
 
  1. 最佳答案:  
  2. 下列程序,已经调试成功。  
  3. #include <REG52.H>  
  4. sbit LED = P1^0;  
  5. unsigned char UART_buff;  
  6. bit New_rec = 0, Send_ed = 1, Money = 0;  
  7. //----------------------------------------------  
  8. void main (void)  
  9. {  
  10.     SCON = 0x50;   //串口方式1, 8-n-1, 允许接收.  
  11.     TMOD = 0x20;   //T1方式2  
  12.     TH1 = 0xFD;    [url=]//9600bps@11.0592MHz[/url]  
  13.     TL1 = 0xFD;  
  14.     TR1 = 1;                          
  15.     ES  = 1;       //开中断.  
  16.     EA  = 1;  
  17.     while(Money == 0);    //等着交费,呵呵,等着接收$.  
  18.     while(1)  {   
  19.       if ((New_rec == 1) && (Send_ed == 1))  {  //如果收到新数据及发送完毕  
  20.         SBUF = UART_buff; //那就发送.  
  21.         New_rec = 0;  
  22.         Send_ed = 0;  
  23.     } }  
  24. }  
  25. //----------------------------------------------  
  26. void ser_int (void) interrupt 4   
  27. {  
  28.     if(RI == 1) {  //如果收到.  
  29.       RI = 0;      //清除标志.  
  30.       New_rec = 1;  
  31.       UART_buff = SBUF;  //接收.  
  32.       if(UART_buff == '1')  LED = 1;  
  33.       if(UART_buff == '0')  LED = 0;  
  34.       if(UART_buff == '$')  Money = 1;  
  35.     }  
  36.     else  {        //如果送毕.  
  37.       TI = 0;      //清除标志.  
  38.       Send_ed = 1;  
  39.     }  
  40. }   
  41. //----------------------------------------------  

http://bbs.ednchina.com/BLOG_ARTICLE_3007162.HTM

 

串口接收程序是基于串口中断的,单片机的串口每次接收到一字节数据产生一次中断,然后再读取某个寄存器就可以得到串口接收的数据了。然而在实际应用当中,基本上不会有单字节接收的情况。一般都是基于一定串口通信协议的多字节通信。在422或者485通信中,还可能是一个主机(一般是计算机)带多个从机(相应的有单片机的板卡)。这就要求我们的单片机能够在连续接收到的串口数据序列中识别出符合自己板卡对应的通信协议,来进行控制操作,不符合则不进行任何操作。简而言之就是,单片机要在一串数据中找到符合一定规律的几个字节的数据。

 

        先来说下怎样定串口协议吧。这个协议指的不是串口底层的协议,而是前面提到的数据帧协议。一般都是有帧头(2~3个字节吧),数据(长度根据需要),结束位(1位,有时候设计成校验字节,最简单的校验也就是前面所有数据求和)。

        比如0xaa 0x55 +(数据部分省略)+校验和(除了aa 55 之外数据的和),如果要是多板卡的话有时候还要在帧头后面加一个板选字节(相当于3字节帧头了)。

 

       第一次写串口接收程序的时候,我首先想到的就是定义一个全局变量(实际上最好是定义局部静态变量),初始值设置为0,然后每进一次中断+1,然后加到串口通信协议的长度的时候再清零。然后判断帧头、校验。写完了之后我自己都觉得不对,一旦数据错开了一位,后面就永远都接收不到数了。无奈看了一下前辈们的代码,跟我的思路差不多,只不过那个计数值跟接收到的数据时同时判断的,而且每次中断都要判断,一旦不对计数的那个变量就清零。

 

       废话少说,直接上一段代码让大家看看就明白了。(通信协议姑且按照简单的aa 55 一个字节数据 一个字节校验,代码是基于51单片机的)。接收成功则在中断程序中把串口接收成功标志位置1。

[cpp]  view plain  copy
 
  1. 然后串口中断部分  
  2. void ser()interrupt 4  
  3. {  
  4. static unsigned char count;//串口接收计数的变量  
  5.   RI=0;//手动清某个寄存器,大家都懂的  
  6.   receive[count]=SBUF;  
  7.   if(count==0&&receive[count]==0xaa)//同时判断count跟收到的数据  
  8.   {  
  9.        count=1;  
  10.   }  
  11.   else if(count==1&&receive[count]==0x55)  
  12.   {  
  13.      count=2;  
  14.   }  
  15.   else if(count==2)  
  16.   {  
  17.        count++;  
  18.   }  
  19.   else if(count==3&&receive[count]== receive [2])//判断校验和,数据多的话是求//和,或者其他的校验方法,也可能是固定的帧尾  
  20.   {  
  21.     count=0;  
  22.      uart_flag =1;//串口接收成功标志,为1时在主程序中回复,然后清零  
  23.    ES=0;      //关中断,回复完了再ES=1;  
  24.   }  
  25.   else  
  26.   {  
  27.      count=0;//判断不满足条件就将计数值清零  
  28.   }  
  29. }  

 

第一次做的串口大概就按照这个方法写完了(我后来看过其他的代码,有人用switch语句写的,逻辑跟这个也差不多,不过我还是感觉用if else来写清晰一些),

        不过在测试的时候发现了bug,如果数据帧发送一半,然后突然停止,再来重新发,就会丢失一帧的数据。比如先接受到aa 55,然后断了,再进来aa 55 01 01,就不受控制了。后来我也想到一个bug,如果在多设备通信中,属于其他设备的的帧数据最后一位是aa(或者最后两位为aa 55 ,或者最后3位为aa 55 板选),下一次通信的数据就接收不到了。

 

        当时对于数据突然中断的bug,没有想到很好的解决办法,不过这种情况几率极小,所以一直用这个方法写也没有问题。多设备通信最后一位恰好是aa的几率也很小,出问题的可能也很小。当时项目里面的控制数据跟校验恰好不可能出现aa,于是我把if(count==0&&receive[count]==0xaa)改成了if(receive[count]==0xaa)其他都没变,解决了,没有bug了。

 

        后来我又写了几次单片机程序,才想到了一些解决问题的方法——不过改天再接着写吧,太累了,明天还要上班呢。

 

        在后来的项目中,真的遇到了数据位跟校验位都可能出现aa的情况。我考虑到每次数据都是连续发送的(至少我们用labwindows做的上位机程序是这样的),成功接收到了一帧数据是要有一定时间回复的,也就是说如果接收到一半,但是很长时间没接收到数据,把计数值count清零就ok啦。涉及时间的问题自然要用定时器来实现啦。

这次的通信协议如下,串口波特率19200,2个帧头aa 55 ,一个板选,6字节数据,一个校验字节(除帧头外其他数据的和)。


[cpp]  view plain  copy
 
  1. 全局变量定义  
  2. unsigned char boardAddr;//板选地址,通过检测几个io引脚,具体怎么得到的就不写了,很简单的  
  3. unsigned char g_DatRev [10]={0};//接收缓存  
  4. bit retFlag=0;//为1代表串口接收到了一帧数据  
  5.    
  6.    
  7. 串口初始化函数,晶振22.1184  
  8.    
  9. void init_uart()  
  10. {  
  11.        SCON = 0x50;                 //串口方式1允许接收  
  12.        TMOD = 0x21;                //定时器1,方式2,8位自动重载,同时配置定时器0,工作方式1  
  13.        PCON = 0x80;                // 波特率加倍  
  14.        TH1 = 0xfa;  
  15.        TL1 = 0xfa;               //写入串口定时器初值  
  16.        TH0=(65536-2000)/256;    //写入定时器0初值,串口传输一个字节时间为(1/19200)*10,计算得0.52ms  
  17.        TL0=(65536-2000)%256;   //定时器0定时大约1ms多  
  18.     EA=1;  
  19.     ET0=1;                  //波特率:19200    22.1184M  初值:250(0xfa)  
  20.        IE |= 0x90;             
  21.     TR1 = 1;                     
  22. }  
  23.    
  24. 串口中断函数  
  25.    
  26. void UART_INT(void) interrupt 4  
  27. {   
  28.        static unsigned char count;//串口接收计数的变量  
  29.    
  30.               RI = 0;  
  31.               g_DatRev[count] = SBUF;  
  32.               if(g_DatRev[count]==0xaa&&count==0)             //帧头  
  33.            {  
  34.                    count=1;                                                   
  35.             }  
  36.                else if(count==1&&g_DatRev[count]==0x55)   
  37.             {    
  38.                          count=2;            
  39.             }  
  40.    
  41.                 else if (count==2&&g_DatRev[2] == boardAddr)  
  42.                {   
  43.                   CK = g_DatRev[count];  
  44.                    count=3;  
  45.                    
  46.                }  
  47.        
  48.                else if(count>=3&&count<9)  
  49.               {       
  50.                   
  51.                      CK += g_DatRev[count];  
  52.                     count ++;  
  53.             }  
  54.                
  55.            else if(count == 9&&CK==g_DatRev[9])  
  56.                      {       
  57.                          ES = 0;   
  58.                         retFlag = 1;  
  59.                          count=0;              
  60.                      }              
  61.               else  
  62.                {  
  63.                     count=0;  
  64.                }   
  65.              resettimer();  
  66.    
  67. }  
  68.    
  69. //判断count不为0的话就启动定时器  
  70. void resettimer()  
  71. {  
  72.        TR0=0;  
  73.        TH0=(65536-2000)/256;  
  74.        TL0=(65536-2000)%256;  
  75.        if(count!=0)  
  76.        {  
  77.               TR0=1;  
  78.        }  
  79. }  
  80.    
  81. 定时器中断函数  
  82. void T0_time()interrupt 1  
  83. {       
  84.     TR0=0;  
  85.        TH0=(65536-2000)/256;  
  86.        TL0=(65536-2000)%256;  
  87.        count=0;  
  88.    
  89. }  

这种方法的确是本人自己想出来的,别人可能也这样做过,但我这个绝对不是抄袭或者模仿来的。这样写的确可以避免前面提到过的bug,不过代价是多用了一个定时器的资源,而且中断函数里的内容更多了,占用了更多的时间。

 

        要是能把第一种方法改进一下就好了,主要是那个校验不能为aa的那个bug,因为毕竟传输到一半突然断了的可能性是非常小的。后来我想第一个判断if(count==0&&receive[count]==0xaa)好像有点太严格了,考虑到第二字节的帧头,跟板选地址不可能为aa,于是把这个改写为if(count>=0&&count<=2&& receive[count]==0xaa),这样就把bug出现的几率降到了非常小,也只是在前一帧结尾数据恰好为 aa 55 板选 的时候才出现,几率是多少大家自己算一下吧,呵呵。这样我自己觉得,昨天写的那种方法改进到这个程度,应该算可以啦,反正我是很满意了。

 

        实际上我还想过其他的方法,比如缓存的数组采用移位寄存的方式。拿前面的4个字节的协议为例。

[cpp]  view plain  copy
 
  1. void ser()interrupt 4  
  2. {  
  3.  unsigned char i;  
  4.   RI=0;  
  5.    
  6.   for(i=0;i<3;i++)  
  7.   {  
  8.      receive[i]=receive[i+1];  
  9.   }  
  10.   receive[3]=SBUF;  
  11.   if(reveive[0]==0xaa&&receive[1]==0x55&&receive[2]==receive[3])  
  12.   {  
  13.      ret_flag=1;  
  14.        ES = 0;    
  15.   }  
  16.    
  17. }  

这段代码看上去可是简单明了,这样判断可是不错啊,同时判断帧头跟校验不会产生前面提到的bug。说实话当时我刚想出这种方法并写出来的时候,马上就被我给否了。那个for循环可真是很占时间的啊,延时函数都是这样写的。每次都循环一下,这延时太长,通信速度太快的话就不能接收到下一字节数据了。最要命的是这个时间的长度是随着通信协议帧的字节数增加而增加的,如果一次要接收几十个字节,肯定就玩完了。这种方法我一次都没用过。

 

        不过我居然又想出来了这种方法的改良措施,是前两天刚想出来的,呵呵,还没有实践过呢。

下面代码的协议就按第二段程序(定时器清零的那个协议,一共10字节)

全局变量

 

 

[cpp]  view plain  copy
 
  1. bit ret_flag;  
  2. unsigned char receive[256]={0};  
  3. unsigned char boardaddress;  
  4.    
  5. 中断函数  
  6.    
  7. void ser()interrupt 4  
  8. {  
  9.    
  10.    
  11.   static unsigned char i=0;  
  12.   static unsigned char total=0;  
  13.   RI=0;  
  14.   receive[i]=SBUF;  
  15.   total=total-receive[i-7]+receive[i-1];  
  16.    
  17.   if(receive[i-9]==0xaa&&receive[i-8]==0x55  
  18.   &&receive[i-7]==boardaddress&&receive[i]==total  
  19.   )  
  20.   {  
  21.      ret_flag=1;  
  22.        ES = 0;    
  23.   }  
  24.   i++;  
  25.    
  26. }  


 

        之所以要定义256个长度的数组,就是为了能够让数组“首尾相接”。因为0 -1 = 255 , 255+1 = 0。而且我在计算校验的时候也改进了算法,不会因为数据长度的增加而增加计算校验值的时间。这种方法也是我不久前才想出来的,所以还没有经过实际的验证。上面的代码可能会有逻辑上的错误,如果真有错误,有网友看出来的话,请在下面留言告诉我。这个方法也是我原创的哦,别人也肯能会想到,不过我这个绝对不是抄袭别人的。

 

        上面的代码最大的缺点就是变量定义的太多了,太占ram资源了,编译的时候可能会出现错误,毕竟51单片机才128字节的ram(有的资源也很丰富的,比如c8051系列的),这一下子就是256字节的变量。不过对于资源多一些的单片机,这样写还是可以的。要是能有4bit在一起的数据类型就好了,呵呵,verilog代码里面是可以的,C语言里貌似不行啊。

 

        要想能在例如51单片机上运行,只能按照下面的折中方式了,也就是把i相关的量都与一个0x0f

 

 

[cpp]  view plain  copy
 
  1. 全局变量  
  2.    
  3. bit ret_flag;  
  4. unsigned char receive[16]={0};// 可以考虑在定义时加上idata,毕竟还可能是32  
  5. //或者64长度的数组呢unsigned char idata receive[16]={0};  
  6.    
  7. unsigned char boardaddress;  
  8.    
  9. 中断函数  
  10.    
  11. void ser()interrupt 4  
  12. {  
  13.    
  14.    
  15.   static unsigned char i=0;  
  16.   static unsigned char total=0;  
  17.   RI=0;  
  18.   receive[i&0x0f]=SBUF;  
  19.   total=total-receive[(i-7)&0x0f]+receive[(i-1)&0x0f];  
  20.    
  21.   if(receive[(i-9)&0x0f]==0xaa&&receive[(i-8)&0x0f]==0x55  
  22.   &&receive[(i-7)&0x0f]==boardaddress&&receive[i&0x0f]==total  
  23.   )  
  24.   {  
  25.      ret_flag=1;  
  26.        ES = 0;    
  27.   }  
  28.   i++;  
  29.    
  30. }  


 


 

 
5
1
 
 
 

我的同类文章

嵌入式C开发(7)
 
猜你在找
查看评论
4楼  qq_25262147 2016-03-17 10:39发表 [回复]
用的是什么软件,怎么看
3楼  limilie 2015-06-04 22:20发表 [回复]
在接收中断中使用状态机的方式进行串口帧接收,主函数中进行帧处理,可以减少中断的影响
2楼  fanrusen 2015-04-02 10:15发表 [回复]
定时器那种方法改进探讨:count定义为全局变量,在主函数中先判断在开定时中断,就可以减少中断程序压力
1楼  annekqiu 2014-11-02 22:48发表 [回复]
让我基本了解了通信方面的内容!
 
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
 
 
 
 
  • 个人资料
 
 
    • 访问:174331次
    • 积分:1643
    • 等级: 
    • 排名:第17655名
    • 原创:9篇
    • 转载:111篇
    • 译文:3篇
    • 评论:19条
  • 最新评论
 
 

 

 
 

转载于:https://www.cnblogs.com/stevenxiu/p/5849337.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值