【IoT】51 单片机 IO 口模拟串口数据收发

举报
产品人卫朋 发表于 2021/10/30 01:24:34 2021/10/30
【摘要】 程序硬件平台:11.0592M晶振,STC单片机(兼容51)。 1、发送数据 /****************************************************************    在单片机上模拟了一个串口,使用P2.1作为发送端*    把单片机中存放的数据...

程序硬件平台:11.0592M晶振,STC单片机(兼容51)。

1、发送数据


  
  1. /***************************************************************
  2. *    在单片机上模拟了一个串口,使用P2.1作为发送端
  3. *    把单片机中存放的数据通过P2.1作为串口TXD发送出去
  4. ***************************************************************/
  5. #include <reg51.h>
  6. #include <stdio.h>
  7. #include <string.h>
  8. typedef unsigned char uchar;
  9. int i;
  10. uchar code info[] = 
  11. {
  12. 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
  13. };
  14. sbit newTXD = P2^1;//模拟串口的发送端设为P2.1
  15. void UartInit()
  16. {
  17.     SCON  = 0x50;     // SCON: serail mode 1, 8-bit UART
  18.     TMOD |= 0x21;     // T0工作在方式1,十六位定时
  19.     PCON |= 0x80;     // SMOD=1;
  20.     TH0   = 0xFE;     // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz
  21.     TL0   = 0x7F;     // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz
  22. }
  23. void WaitTF0(void)
  24. {
  25.     while(!TF0);
  26.     TF0=0;
  27.     TH0=0xFE;    // 定时器重装初值 fosc=11.0592MHz
  28.     TL0=0x7F;    // 定时器重装初值 fosc=11.0592MHz
  29. }
  30. void WByte(uchar input)
  31. {
  32.     //发送启始位
  33.     uchar j=8;
  34.     TR0=1;
  35.     newTXD=(bit)0;
  36.     WaitTF0();
  37.     
  38.     //发送8位数据位
  39.     while(j--)
  40.     {
  41.        newTXD=(bit)(input&0x01);      //先传低位
  42.        WaitTF0();
  43.        input=input>>1;
  44.     }
  45.     //发送校验位(无)
  46.     //发送结束位
  47.     newTXD=(bit)1;
  48.     WaitTF0();
  49.     TR0=0;
  50. }    
  51. void Sendata()
  52. {
  53.     for(i=0;i<sizeof(info);i++)//外层循环,遍历数组
  54.     {
  55.         WByte(info[i]);
  56.     }
  57. }
  58. void main()
  59. {
  60.     UartInit();
  61.     while(1)
  62.     {
  63.         Sendata();
  64.     }
  65. }

2、接收数据


  
  1. /***************************************************************
  2. *    模拟接收程序,这个程序的作用从模拟串口接收数据,然后将这些数据发送到实际串口
  3. *    在单片机上模拟了一个串口,使用P3.2作为发送和接收端
  4. *    以P3.2模拟串口接收端,从模拟串口接收数据发至串口
  5. ***************************************************************/
  6. #include<reg51.h>
  7. #include<stdio.h>
  8. #include<string.h>
  9. typedef unsigned char uchar;
  10. //这里用来切换晶振频率,支持11.0592MHz
  11. #define F11_0592 
  12. uchar tmpbuf2[64]={0};
  13. //用来作为模拟串口接收数据的缓存
  14. struct 
  15. {
  16.     uchar recv :6 ;//tmpbuf2数组下标,用来将模拟串口接收到的数据存放到tmpbuf2中
  17.     uchar send :6 ;//tmpbuf2数组下标,用来将tmpbuf2中的数据发送到串口
  18. }tmpbuf2_point={0,0};
  19. sbit newRXD=P3^2 ;//模拟串口的接收端设为P3.2
  20. void UartInit()
  21. {
  22.     SCON=0x50 ;// SCON: serail mode 1, 8-bit UART
  23.     TMOD|=0x21 ;// TMOD: timer 1, mode 2, 8-bit reload,自动装载预置数(自动将TH1送到TL1);T0工作在方式1,十六位定时
  24.     PCON|=0x80 ;// SMOD=1;
  25.     
  26.     #ifdef F11_0592 
  27.     TH1=0xE8 ;// Baud:2400  fosc=11.0592MHz 2400bps为从串口接收数据的速率
  28.     TL1=0xE8 ;// 计数器初始值,fosc=11.0592MHz 因为TH1一直往TL1送,所以这个初值的意义不大
  29.     TH0=0xFF ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz
  30.     TL0=0xA0 ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz
  31.     #endif 
  32.     IE|=0x81 ;// 中断允许总控制位EA=1;使能外部中断0
  33.     TF0=0 ;
  34.     IT0=1 ;// 设置外部中断0为边沿触发方式
  35.     TR1=1 ;// 启动TIMER1,用于产生波特率
  36. }
  37. void WaitTF0(void)
  38. {
  39.     while(!TF0);
  40.     TF0=0 ;
  41.     #ifdef F11_0592 
  42.     TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
  43.     TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
  44.     #endif 
  45. }
  46. //接收一个字符
  47. uchar RByte()
  48. {
  49.     uchar Output=0 ;
  50.     uchar i=8 ;
  51.     TR0=1 ;    //启动Timer0
  52.     
  53.     #ifdef F11_0592 
  54.     TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
  55.     TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
  56.     #endif 
  57.     TF0=0 ;
  58.     WaitTF0();//等过起始位
  59.     
  60.     //接收8位数据位
  61.     while(i--)
  62.     {
  63.         Output>>=1 ;
  64.         if(newRXD)Output|=0x80 ;//先收低位
  65.             WaitTF0();//位间延时
  66.     }
  67.     TR0=0 ;//停止Timer0
  68.     return Output ;
  69. }
  70. //向COM1发送一个字符
  71. void SendChar(uchar byteToSend)
  72. {
  73.     SBUF=byteToSend ;
  74.     while(!TI);
  75.     TI=0 ;
  76. }
  77. void main()
  78. {
  79.     UartInit();
  80.     while(1)
  81.     {
  82.         if(tmpbuf2_point.recv!=tmpbuf2_point.send)//差值表示模拟串口接收数据缓存中还有多少个字节的数据未被处理(发送至串口)
  83.         {
  84.             SendChar(tmpbuf2[tmpbuf2_point.send++]);
  85.         }
  86.     }
  87. }
  88. //外部中断0,说明模拟串口的起始位到来了
  89. void Simulated_Serial_Start()interrupt 0 
  90. {
  91.     EX0=0 ;    //屏蔽外部中断0
  92.     tmpbuf2[tmpbuf2_point.recv++]=RByte();    //从模拟串口读取数据,存放到tmpbuf2数组中
  93.     IE0=0 ;    //防止外部中断响应2次,防止外部中断函数执行2次
  94.     EX0=1 ;    //打开外部中断0
  95. }

以上是两个独立的测试程序,分别是模拟串口发送的测试程序和接收的测试程序。

3、基于 实现模拟串口的三种方法 的优化

上面两个程序在编写过程中参考了文章《51单片机模拟串口的三种方法》(在后文中简称《51》),但在它的基础上做了一些补充,下面是若干总结的内容:

a、《51》在接收数据的程序中,采用的是循环等待的方法来检测起始位(见《51》的“附:51 IO口模拟串口通讯C源程序(定时器计数法)” 部分),这种方法在较大程序中,可能会错过起始位(比如起始位到来的时候程序正好在干别的,而没有处于判断起始位到来的状态),或者一直在检测起始位,而没有办法完成其他工作。

为了避免这个问题,在本接收程序中采用了外部中断的方法,将外部中断0引脚作为模拟串口的接收端,设IT0=1(将外部中断0设为边缘触发)。这样当起始位(低电平)到来时,就会引发外部中断,然后在外部中断处理函数中接收余下的数据。

这种方法可以保证没数据的时候程序该干什么干什么,一旦模拟串口接收端有数据,就可以立即接收到。

b、加入了模拟串口接收缓冲区。在较大程序中,单片机要完成的工作很多,在模拟串口接收到了数据之后立即处理的话,有可能处理不过来造成丢失数据,或者影响程序其他部分执行。本程序中加入了64个字节的缓冲区,从模拟串口接收到的数据先存放在缓冲区中。这样就算程序一时没工夫处理这些数据,腾出手来之后也能在缓冲区中找到它们。

c、《51》文中的WByte函数和RByte函数中都先打开计数器后关闭计数器。如果使用本文的外部中断法来接收数据,并且外部中断处理函数里外都调用了WByte或RByte的话,需要将这两个函数中的TR0=1,TR0=0操作的语句除去,并在UartInit()中加入一句TR0=1;即让TR0始终开着就可以。

由于之前没有意识到这个问题,因此在具体应用时出现了奇怪的问题:

表现为中断处理函数执行完毕之后,似乎回不到主程序,程序停在了一个不知道的地方。后来经过排查后找到了问题所在,那个程序的中断处理函数中用了RByte,中断处理函数外用到了WByte,而这两个函数的最后都有TR0=0。

这样当中断处理函数执行完毕后,TR0实际上是为0的,返回主程序后(中断前的主程序可能正好处于其他的WByte或RByte执行中),原先以来定时器0溢出改变TF0才能执行下去的WByte函数就无法进行下去,从而导致整个程序停下来不动。(在本文的接收测试程序中不存在这个问题,因为中断处理程序中虽调用了RByte,但中断处理程序外却没有调用RByte或WByte)。

下面是修改后的RByte、WByte和WaitTF0函数,仅供参考:


  
  1. /**********************************************
  2. *            定时器0溢出后重装初值
  3. **********************************************/
  4. void WaitTF0(void)
  5. {
  6.     TF0=0 ;
  7.     
  8.     #ifdef F11_0592 
  9.     TH0=0xFF ;// 定时器重装初值 fosc=11.0592MHz
  10.     TL0=0xA0 ;// 定时器重装初值 fosc=11.0592MHz
  11.     #endif 
  12.     while(!TF0);
  13.     TF0=0 ;
  14. }
  15. /**********************************************
  16. *            从串口B接收一个字符
  17. **********************************************/
  18. uchar RByte()
  19. {
  20.     uchar Output=0 ;
  21.     uchar i=8 ;
  22.     //    TR0=1;                             //启动Timer0
  23. /*
  24.     #ifdef F11_0592
  25.     TH0      = 0xFF;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
  26.     TL0   = 0xA0;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
  27.     #endif
  28.     #ifdef F18_432
  29.     TH0      = 0xFF;    // 定时器重装初值 fosc=18.432MHz
  30.     TL0   = 0x60;    // 定时器重装初值 fosc=18.432MHz
  31.     #endif
  32. */
  33.     WaitTF0();//等过起始位
  34.     
  35.     //接收8位数据位
  36.     while(i--)
  37.     {
  38.         Output>>=1 ;
  39.         if(newRXD)Output|=0x80 ;        //先收低位
  40.             WaitTF0();//位间延时
  41.     }
  42.     //  while(!TF0) if(newRXD) break;    //此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收
  43.     //    WaitTF0();                        //等过结束位
  44.     //    TR0=0;                             //停止Timer0
  45.     
  46.     return Output ;
  47. }
  48. /**********************************************
  49. *            发送一个字节到串口B
  50. **********************************************/
  51. void WByte(uchar input)
  52. {
  53.     //发送启始位
  54.     uchar j=8 ;
  55.     //TR0=1;
  56.     newTXD=(bit)0 ;
  57.     WaitTF0();
  58.     //发送8位数据位
  59.     while(j--)
  60.     {
  61.         newTXD=(bit)(input&0x01);//先传低位
  62.             WaitTF0();
  63.         input=input>>1 ;
  64.     }
  65.     //发送校验位(无)
  66.     //发送结束位
  67.     newTXD=(bit)1 ;
  68.     WaitTF0();
  69.     //TR0=0;
  70. }

在上面的新修改后的RByte()函数中,有被注释掉的如下两句:

//while(!TF0) if(newRXD) break;    //此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收
//WaitTF0();                       //等过结束位

这两句在《51》文中的程序是存在的,但是使用中断接收法后,加上这两句后出现了问题。表现为接收到的下一个字节的数据不完整或直接接收不到,似乎这两句占用了过多的时间。

看这两句的目的似乎是要延时以跳过结束位,但是我感觉这个结束位可以不用管它,反正结束位是个高电平,不会妨碍下一个字节是否到来的判断(下一个字节的起始位是低电平)。那就由它去吧,没有必要为了它而占用 CPU 的时间。


 

文章来源: blog.csdn.net,作者:简一商业,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/liwei16611/article/details/93503468

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。