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。谢谢!
问题弥补:或许会将这样的字符串(字符串长度约为50-150个字符)传送给单片机,只能能原样回来。
最佳答案:下列程序,现已调试成功。#includesbit LED = P1^0;unsigned char UART_buff;bit New_rec = 0, Send_ed = 1, Money = 0;//----------------------------------------------void main (void){SCON = 0x50; //串口办法1, 8-n-1, 答应接纳.TMOD = 0x20; //T1办法2TH1 = 0xFD; [url=]//9600bps@11.0592MHz[/url]TL1 = 0xFD;TR1 = 1; ES = 1; //开中止.EA = 1;while(Money == 0); //等着交费,,等着接纳$.while(1) { if ((New_rec == 1) && (Send_ed == 1)) { //假如收到新数据及发送结束SBUF = UART_buff; //那就发送.New_rec = 0;Send_ed = 0;} }}//----------------------------------------------void ser_int (void) interrupt 4 {if(RI == 1) { //假如收到.RI = 0; //铲除标志.New_rec = 1;UART_buff = SBUF; //接纳.if(UART_buff == 1) LED = 1;if(UART_buff == 0) LED = 0;if(UART_buff == $) Money = 1;}else { //假如送毕.TI = 0; //铲除标志.Send_ed = 1;}} //----------------------------------------------
http://bbs.ednchina.com/BLOG_ARTICLE_3007162.HTM
串口接纳程序是根据串口中止的,单片机的串口每次接纳到一字节数据发生一次中止,然后再读取某个存放器就能够得到串口接纳的数据了。然而在实践使用傍边,根本上不会有单字节接纳的状况。一般都是根据必定串口通讯协议的多字节通讯。在422或许485通讯中,还或许是一个主机(一般是核算机)带多个从机(相应的有单片机的板卡)。这就要求咱们的单片机能够在接连接纳到的串口数据序列中识别出契合自己板卡对应的通讯协议,来进行操控操作,不契合则不进行任何操作。简而言之便是,单片机要在一串数据中找到契合必定规则的几个字节的数据。
先来说下怎样定串口协议吧。这个协议指的不是串口底层的协议,而是前面说到的数据帧协议。一般都是有帧头(2~3个字节吧),数据(长度根据需要),结束位(1位,有时分规划成校验字节,最简略的校验也便是前面一切数据求和)。
比方0xaa 0x55 +(数据部分省掉)+校验和(除了aa 55 之外数据的和),假如要是多板卡的话有时分还要在帧头后边加一个板选字节(相当于3字节帧头了)。
榜首次写串口接纳程序的时分,我首要想到的便是界说一个大局变量(实践上最好是界说部分静态变量),初始值设置为0,然后每进一次中止+1,然后加到串口通讯协议的长度的时分再清零。然后判别帧头、校验。写完了之后我自己都觉得不对,一旦数据错开了一位,后边就永久都接纳不到数了。无法看了一下长辈们的代码,跟我的思路差不多,只不过那个计数值跟接纳到的数据时一起判别的,并且每次中止都要判别,一旦不对计数的那个变量就清零。
废话少说,直接上一段代码让咱们看看就理解了。(通讯协议权且依照简略的aa 55 一个字节数据 一个字节校验,代码是根据51单片机的)。接纳成功则在中止程序中把串口接纳成功标志方位1。
然后串口中止部分void ser()interrupt 4{static unsigned char count;//串口接纳计数的变量RI=0;//手动清某个存放器,咱们都懂的receive[count]=SBUF;if(count==0&&receive[count]==0xaa)//一起判别count跟收到的数据{count=1;}else if(count==1&&receive[count]==0x55){count=2;}else if(count==2){count++;}else if(count==3&&receive[count]== receive [2])//判别校验和,数据多的话是求//和,或许其他的校验办法,也或许是固定的帧尾{count=0;uart_flag =1;//串口接纳成功标志,为1时在主程序中,然后清零ES=0; //关中止,完了再ES=1;}else{count=0;//判别不满足条件就将计数值清零}}
榜首次做的串口大约就依照这个办法写完了(我后来看过其他的代码,有人用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字节数据,一个校验字节(除帧头外其他数据的和)。
大局变量界说unsigned char boardAddr;//板选地址,通过检测几个io引脚,详细怎样得到的就不写了,很简略的unsigned char g_DatRev [10]={0};//接纳缓存bit retFlag=0;//为1代表串口接纳到了一帧数据串口初始化函数,晶振22.1184void init_uart(){SCON = 0x50; //串口办法1答应接纳TMOD = 0x21; //守时器1,办法2,8位主动重载,一起装备守时器0,工作办法1PCON = 0x80; // 波特率加倍TH1 = 0xfa;TL1 = 0xfa; //写入串口守时器初值TH0=(65536-2000)/256; //写入守时器0初值,串口传输一个字节时刻为(1/19200)*10,核算得0.52msTL0=(65536-2000)%256; //守时器0守时大约1ms多EA=1;ET0=1; //波特率:19200 22.1184M 初值:250(0xfa)IE |= 0x90; TR1 = 1; }串口中止函数void UART_INT(void) interrupt 4{ static unsigned char count;//串口接纳计数的变量RI = 0;g_DatRev[count] = SBUF;if(g_DatRev[count]==0xaa&&count==0) //帧头{count=1; }else if(count==1&&g_DatRev[count]==0x55) { count=2; }else if (count==2&&g_DatRev[2] == boardAddr){ CK = g_DatRev[count];count=3;}else if(count>=3&&count<9){ CK += g_DatRev[count];count ++;}else if(count == 9&&CK==g_DatRev[9]){ ES = 0; retFlag = 1;count=0; } else{count=0;} resettimer();}//判别count不为0的话就发动守时器void resettimer(){TR0=0;TH0=(65536-2000)/256;TL0=(65536-2000)%256;if(count!=0){TR0=1;}}守时器中止函数void T0_time()interrupt 1{ TR0=0;TH0=(65536-2000)/256;TL0=(65536-2000)%256;count=0;}
这种办法确实是自己自己想出来的,他人或许也这样做过,但我这个必定不是抄袭或许仿照来的。这样写确实能够防止前面说到过的bug,不过价值是多用了一个守时器的资源,并且中止函数里的内容更多了,占用了更多的时刻。
要是能把榜首种办法改善一下就好了,主要是那个校验不能为aa的那个bug,由于究竟传输到一半忽然断了的或许性是十分小的。后来我想榜首个判别if(count==0&&receive[count]==0xaa)如同有点太严厉了,考虑到第二字节的帧头,跟板选地址不或许为aa,所以把这个改写为if(count>=0&&count<=2&& receive[count]==0xaa),这样就把bug呈现的几率降到了十分小,也仅仅在前一帧结束数据刚好为 aa 55 板选 的时分才呈现,几率是多少咱们自己算一下吧,。这样我自己觉得,昨日写的那种办法改善到这个程度,应该算能够啦,横竖我是很满足了。
实践上我还想过其他的办法,比方缓存的数组选用移位存放的办法。拿前面的4个字节的协议为例。
void ser()interrupt 4{unsigned char i;RI=0;for(i=0;i<3;i++){receive[i]=receive[i+1];}receive[3]=SBUF;if(reveive[0]==0xaa&&receive[1]==0x55&&receive[2]==receive[3]){ret_flag=1;ES = 0; }}
这段代码看上去但是简略明了,这样判别但是不错啊,一起判别帧头跟校验不会发生前面说到的bug。说实话其时我刚想出这种办法并写出来的时分,立刻就被我给否了。那个for循环可真是很占时刻的啊,延时函数都是这样写的。每次都循环一下,这延时太长,通讯速度太快的话就不能接纳到下一字节数据了。最要命的是这个时刻的长度是跟着通讯协议帧的字节数添加而添加的,假如一非必须接纳几十个字节,必定就玩完了。这种办法我一次都没用过。
不过我竟然又想出来了这种办法的改进办法,是前两天刚想出来的,,还没有实践过呢。
下面代码的协议就按第二段程序(守时器清零的那个协议,总共10字节)
大局变量
bit ret_flag;unsigned char receive[256]={0};unsigned char boardaddress;中止函数void ser()interrupt 4{static unsigned char i=0;static unsigned char total=0;RI=0;receive[i]=SBUF;total=total-receive[i-7]+receive[i-1];if(receive[i-9]==0xaa&&receive[i-8]==0x55&&receive[i-7]==boardaddress&&receive[i]==total){ret_flag=1;ES = 0; }i++;}
之所以要界说256个长度的数组,便是为了能够让数组“首尾相接”。由于0 -1 = 255 , 255+1 = 0。并且我在核算校验的时分也改善了算法,不会由于数据长度的添加而添加核算校验值的时刻。这种办法也是我不久前才想出来的,所以还没有通过实践的验证。上面的代码或许会有逻辑上的过错,假如真有过错,有网友看出来的话,请在下面留言告诉我。这个办法也是我原创的哦,他人也肯能会想到,不过我这个必定不是抄袭他人的。
上面的代码最大的缺陷便是变量界说的太多了,太占ram资源了,编译的时分或许会呈现过错,究竟51单片机才128字节的ram(有的资源也很丰厚的,比方c8051系列的),这一下子便是256字节的变量。不过关于资源多一些的单片机,这样写仍是能够的。要是能有4bit在一起的数据类型就好了,,verilog代码晒干是能够的,C言语里圆满不可啊。
要想能在例如51单片机上运转,只能依照下面的折中办法了,也便是把i相关的量都与一个0x0f
大局变量bit ret_flag;unsigned char receive[16]={0};// 能够考虑在界说时加上idata,究竟还或许是32//或许64长度的数组呢unsigned char idata receive[16]={0};unsigned char boardaddress;中止函数void ser()interrupt 4{static unsigned char i=0;static unsigned char total=0;RI=0;receive[i&0x0f]=SBUF;total=total-receive[(i-7)&0x0f]+receive[(i-1)&0x0f];if(receive[(i-9)&0x0f]==0xaa&&receive[(i-8)&0x0f]==0x55&&receive[(i-7)&0x0f]==boardaddress&&receive[i&0x0f]==total){ret_flag=1;ES = 0; }i++;}