收藏 分享(赏)

单片机串口通信的发送与接收.docx

上传人:ysd1539 文档编号:6357964 上传时间:2019-04-09 格式:DOCX 页数:10 大小:27.96KB
下载 相关 举报
单片机串口通信的发送与接收.docx_第1页
第1页 / 共10页
单片机串口通信的发送与接收.docx_第2页
第2页 / 共10页
单片机串口通信的发送与接收.docx_第3页
第3页 / 共10页
单片机串口通信的发送与接收.docx_第4页
第4页 / 共10页
单片机串口通信的发送与接收.docx_第5页
第5页 / 共10页
点击查看更多>>
资源描述

1、51 单片机的串口,是个全双工的串口,发送数据的同时,还可以接收数据。当串行发送完毕后,将在标志位 TI 置 1,同样,当收到了数据后,也会在 RI 置 1。无论 RI 或 TI 出现了 1,只要串口中断处于开放状态,单片机都会进入串口中断处理程序。在中断程序中,要区分出来究竟是发送引起的中断,还是接收引起的中断,然后分别进行处理。看到过一些书籍和文章,在串口收、发数据的处理方法上,很多人都有不妥之处。接收数据时,基本上都是使用“中断方式” ,这是正确合理的。即:每当收到一个新数据,就在中断函数中,把 RI 清零,并用一个变量,通知主函数,收到了新数据。发送数据时,很多的程序都是使用的“查询方

2、式” ,就是执行 while(TI =0); 这样的语句来等待发送完毕。这时,处理不好的话,就可能带来问题。看了一些网友编写的程序,发现有如下几条容易出错:有人在发送数据之前,先关闭了串口中断!等待发送完毕后,再打开串口中断。这样,在发送数据的等待期间内,如果收到了数据,将不能进入中断函数,也就不会保存的这个新收到的数据。这种处理方法,就会遗漏收到的数据。有人在发送数据之前,并没有关闭串口中断,当 TI = 1 时,是可以进入中断程序的。但是,却在中断函数中,将 TI 清零!这样,在主函数中的 while(TI =0);,将永远等不到发送结束的标志。还有人在中断程序中,并没有区分中断的来源,反

3、而让发送引起的中断,执行了接收中断的程序。对此,做而论道发表自己常用的方法:接收数据时,使用“中断方式 ”,清除 RI 后,用一个变量通知主函数,收到新数据。发送数据时,也用“中断方式 ”,清除 TI 后,用另一个变量通知主函数,数据发送完毕。这样一来,收、发两者基本一致,编写程序也很规范、易懂。更重要的是,主函数中,不用在那儿死等发送完毕,可以有更多的时间查看其它的标志。实例:求一个 PC 与单片机串口通信的程序,要求如下:1、如果在电脑上发送以$ 开始的字符串,则将整个字符串原样返回(字符串长度不是固定的)。2、如果接收到 1,则将 P10 置高电平,接收到 0,P10 置低电平。(用来控

4、制一个LED)单片机是 STC89C52RC/晶振 11.0592/波特率要求是 9600 或 4800。谢谢!问题补充:可能会将【$ABCD,123456,987654ccc,aasdasd,aaaa,sssd,4D】这样的字符串(字符串长度约为 50-150 个字符)传送给单片机,只能能原样返回。cpp view plaincopy1. 最佳答案: 2. 下列程序,已经调试成功。 3. #include 4. sbit LED = P10; 5. unsigned char UART_buff; 6. bit New_rec = 0, Send_ed = 1, Money = 0; 7.

5、/- 8. void main (void) 9. 10. SCON = 0x50; /串口方式 1, 8-n-1, 允许接收. 11. TMOD = 0x20; /T1 方式 2 12. TH1 = 0xFD; url=/9600bps11.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) /那就发送. 21. New_rec = 0; 22. Send_ed

6、 = 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.

7、 40. 41. /- http:/ 422 或者 485 通信中,还可能是一个主机(一般是计算机)带多个从机(相应的有单片机的板卡)。这就要求我们的单片机能够在连续接收到的串口数据序列中识别出符合自己板卡对应的通信协议,来进行控制操作,不符合则不进行任何操作。简而言之就是,单片机要在一串数据中找到符合一定规律的几个字节的数据。先来说下怎样定串口协议吧。这个协议指的不是串口底层的协议,而是前面提到的数据帧协议。一般都是有帧头(23 个字节吧),数据(长度根据需要),结束位(1 位,有时候设计成校验字节,最简单的校验也就是前面所有数据求和)。比如 0xaa 0x55 +(数据部分省略)+ 校验和

8、(除了 aa 55 之外数据的和),如果要是多板卡的话有时候还要在帧头后面加一个板选字节(相当于 3 字节帧头了)。第一次写串口接收程序的时候,我首先想到的就是定义一个全局变量(实际上最好是定义局部静态变量),初始值设置为 0,然后每进一次中断+1,然后加到串口通信协议的长度的时候再清零。然后判断帧头、校验。写完了之后我自己都觉得不对,一旦数据错开了一位,后面就永远都接收不到数了。无奈看了一下前辈们的代码,跟我的思路差不多,只不过那个计数值跟接收到的数据时同时判断的,而且每次中断都要判断,一旦不对计数的那个变量就清零。废话少说,直接上一段代码让大家看看就明白了。(通信协议姑且按照简单的 aa

9、55 一个字节数据 一个字节校验,代码是基于 51 单片机的)。接收成功则在中断程序中把串口接收成功标志位置 1。cpp view plaincopy1. 然后串口中断部分 2. void ser()interrupt 4 3. 4. static unsigned char count;/串口接收计数的变量 5. RI=0;/手动清某个寄存器,大家都懂的 6. receivecount=SBUF; 7. if(count=0 10. 11. else if(count=1 14. 15. else if(count=2) 16. 17. count+; 18. 19. else if(cou

10、nt=3 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,就不受控制

11、了。后来我也想到一个 bug,如果在多设备通信中,属于其他设备的的帧数据最后一位是aa(或者最后两位为 aa 55 ,或者最后 3 位为 aa 55 板选),下一次通信的数据就接收不到了。当时对于数据突然中断的 bug,没有想到很好的解决办法,不过这种情况几率极小,所以一直用这个方法写也没有问题。多设备通信最后一位恰好是 aa 的几率也很小,出问题的可能也很小。当时项目里面的控制数据跟校验恰好不可能出现 aa,于是我把if(count=0/板选地址,通过检测几个 io 引脚,具体怎么得到的就不写了,很简单的 3. unsigned char g_DatRev 10=0;/接收缓存 4. bit

12、 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,计

13、算得 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_DatRevcount = SBUF; 32. if(g_DatRevc

14、ount=0xaa 35. 36. else if(count=1 39. 40. 41. else if (count=2 44. count=3; 45. 46. 47. 48. else if(count=3 4. RI=0; 5. 6. for(i=0;i3;i+) 7. 8. receivei=receivei+1; 9. 10. receive3=SBUF; 11. if(reveive0=0xaa 14. ES = 0; 15. 16. 17. 这段代码看上去可是简单明了,这样判断可是不错啊,同时判断帧头跟校验不会产生前面提到的 bug。说实话当时我刚想出这种方法并写出来的时候,

15、马上就被我给否了。那个 for循环可真是很占时间的啊,延时函数都是这样写的。每次都循环一下,这延时太长,通信速度太快的话就不能接收到下一字节数据了。最要命的是这个时间的长度是随着通信协议帧的字节数增加而增加的,如果一次要接收几十个字节,肯定就玩完了。这种方法我一次都没用过。不过我居然又想出来了这种方法的改良措施,是前两天刚想出来的,呵呵,还没有实践过呢。下面代码的协议就按第二段程序(定时器清零的那个协议,一共 10 字节)全局变量cpp view plaincopy1. bit ret_flag; 2. unsigned char receive256=0; 3. unsigned char

16、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. receivei=SBUF; 15. total=total-receivei-7+receivei-1; 16. 17. if(receivei-9=0xaa 22. ES = 0; 23. 24. i+; 25. 26. 之所以要定义 256 个长度的数组,就是为了能够让数组“首尾相接”。因为 0 -1 = 25

17、5 , 255+1 = 0。而且我在计算校验的时候也改进了算法,不会因为数据长度的增加而增加计算校验值的时间。这种方法也是我不久前才想出来的,所以还没有经过实际的验证。上面的代码可能会有逻辑上的错误,如果真有错误,有网友看出来的话,请在下面留言告诉我。这个方法也是我原创的哦,别人也肯能会想到,不过我这个绝对不是抄袭别人的。上面的代码最大的缺点就是变量定义的太多了,太占 ram 资源了,编译的时候可能会出现错误,毕竟 51 单片机才 128 字节的 ram(有的资源也很丰富的,比如 c8051 系列的),这一下子就是 256 字节的变量。不过对于资源多一些的单片机,这样写还是可以的。要是能有 4

18、bit 在一起的数据类型就好了,呵呵,verilog 代码里面是可以的,C 语言里貌似不行啊。要想能在例如 51 单片机上运行,只能按照下面的折中方式了,也就是把 i 相关的量都与一个 0x0fcpp view plaincopy1. 全局变量 2. 3. bit ret_flag; 4. unsigned char receive16=0;/ 可以考虑在定义时加上 idata,毕竟还可能是 32 5. /或者 64 长度的数组呢 unsigned char idata receive16=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. receivei 19. total=total-receive(i-7) 20. 21. if(receive(i-9) 26. ES = 0; 27. 28. i+; 29. 30.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 网络科技 > 网络与通信

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报