单片机模拟串口_c#的虚拟串口如何与单片机实际串口连接 - CSDN
精华内容
参与话题
  • 51单片机GPIO口模拟串口通信

    万次阅读 2020-08-20 18:37:54
    随着单片机的使用日益频繁,用其作前置机进行采集和通信也常见于各种应用...这种情况下下,采集会需要一个串口,上报又需要另一个串口,这就要求单片机具有双串口的功能,但我们知道一般的51系列只提供一个串口,那么另

            随着单片机的使用日益频繁,用其作前置机进行采集和通信也常见于各种应用,一般是利用前置机采集各种终端数据后进行处理、存储,再主动或被动上报给管理站。这种情况下下,采集会需要一个串口,上报又需要另一个串口,这就要求单片机具有双串口的功能,但我们知道一般的51系列只提供一个串口,那么另一个串口只能靠程序模拟。

            本文所说的模拟串口, 就是利用51的两个输入输出引脚P1.0和P1.1,置1或0分别代表高低电平,也就是串口通信中所说的位,如起始位用低电平,则将其置0,停止位为高电平,则将其置1,各种数据位和校验位则根据情况置1或置0。

            以11.0592MHz的晶振为例,通过定时计数器0产生中断信号来模拟串口电平,下面附上具体源代码。

    /*
    ************************************************************************************
    Fuction:使用51单片机GPIO口模拟串口通信,通过定时计数器0来产生中断信号
    Software Designer:Jason
    ************************************************************************************
    */
    #include <reg52.h>
    sbit P1_0 = P1^0;
    sbit P1_1 = P1^1;
    #define RXD P1_0
    #define TXD P1_1
    unsigned char flag;
    void init();
    void send_byte(unsigned char);
    unsigned char rec_byte();
    void wait_int();
    //将从PC机串口接收到的数据原封不动回传给PC机
    void main()
    {
    	unsigned char temp;
    	init();
    	while(1)
    	{
    		if(RI == 1)
    		{
    			RI = 0;
    			temp = rec_byte();
    			send_byte(temp);
    			while(!TI);
    			TI = 0;
    		}
    	}
    }
    //端口及中断初始化
    void init()
    {
    	TMOD = 0x02;	//定时器0,方式2
    	TH0 = 0xfd;		//波特率9600
    	TL0 = TH0;
    	TR0 = 0;		//在发送或接收时打开
    	ET0 = 1; 		//允许定时器0中断
    	EA =1;		 	//允许所有中断
    }
    //通过串口发送一个字节数据
    void send_byte(unsigned char dat)
    {
    	unsigned char i=8;
    	TR0 = 1;		//开启T0中断
    	TXD = 0;		//发送起始位0
    	wait_int();
    	while(i--)		//发送8位数据
    	{
    		TXD = (bit)(dat & 0x01);
    		wait_int();
    		dat = dat>>1;
    	}
    	TXD = 1;	//发送停止位1
    	wait_int();
    	TR0 = 0;		//关闭T0中断
    }
    //通过串口接收一个字节数据
    unsigned char rec_byte()
    {
    	unsigned char dat=0;
    	unsigned char i=8;
    	TR0 = 1;		//开启T0中断
    	wait_int();		//等过起始位电平
    	while(i--)		//接收8位数据
    	{
    		dat = dat<<1;
    		if(RXD)
    			dat |= 0x80;
    		wait_int();
    	}
    	wait_int();		//等过停止位电平
    	TR0 = 0;		//关闭T0中断
    	return dat;
    }
    //等待中断到来
    void wait_int()
    {
    	while(!flag);
    	flag = 0;
    }
    //中断服务程序
    void timer0() interrupt 1
    {
    	flag = 1;
    }


     

    展开全文
  • 前一阵一直在做单片机的程序,由于串口不够,需要用IO口来模拟出一个串口。经过若干曲折并参考了一些现有的资料,基本上完成了。现在将完整的测试程序,以及其中一些需要总结的部分贴出来。  程序硬件平台:11....

    前一阵一直在做单片机的程序,由于串口不够,需要用IO口来模拟出一个串口。经过若干曲折并参考了一些现有的资料,基本上完成了。现在将完整的测试程序,以及其中一些需要总结的部分贴出来。

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

      /***************************************************************

      *    在单片机上模拟了一个串口,使用P2.1作为发送端

      *    把单片机中存放的数据通过P2.1作为串口TXD发送出去

      ***************************************************************/

      #i nclude <reg51.h>

      #i nclude <stdio.h>

      #i nclude <string.h>

      typedef unsigned char uchar;

      int i;

      uchar code info[] =

      {

      0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55

      };

      sbit newTXD = P2^1;//模拟串口的发送端设为P2.1

      void UartInit()

      {

      SCON  = 0x50;   // SCON: serail mode 1, 8-bit UART

      TMOD |= 0x21;   // T0工作在方式1,十六位定时

      PCON |= 0x80;   // SMOD=1;

      TH0      = 0xFE;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz

      TL0   = 0x7F;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz

      //    TH0      = 0xFD;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=18.432MHz

      //    TL0   = 0x7F;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=18.432MHz

      }

      void WaitTF0(void)

      {

      while(!TF0);

      TF0=0;

      TH0=0xFE;    // 定时器重装初值 fosc=11.0592MHz

      TL0=0x7F;    // 定时器重装初值 fosc=11.0592MHz

      //    TH0      = 0xFD;    // 定时器重装初值 fosc=18.432MHz

      //    TL0   = 0x7F;    // 定时器重装初值 fosc=18.432MHz

      }

      void WByte(uchar input)

      {

      //发送启始位

      uchar j=8;

      TR0=1;

      newTXD=(bit)0;

      WaitTF0();

      //发送8位数据位

      while(j--)

      {

      newTXD=(bit)(input&0x01);      //先传低位

      WaitTF0();

      input=input>>1;

      }

      //发送校验位(无)

      //发送结束位

      newTXD=(bit)1;

      WaitTF0();

      TR0=0;

      }

      void Sendata()

      {

      for(i=0;i<sizeof(info);i++)//外层循环,遍历数组

      {

      WByte(info[i]);

      }

      }

      void main()

      {

      UartInit();

      while(1)

      {

      Sendata();

      }

      }

      ##############################################################################

      /***************************************************************

      *       模拟接收程序,这个程序的作用从模拟串口接收数据,然后将这些数据发送到实际串口

      *    在单片机上模拟了一个串口,使用P3.2作为发送和接收端

      *    以P3.2模拟串口接收端,从模拟串口接收数据发至串口

      ***************************************************************/

      #i nclude<reg51.h>

      #i nclude<stdio.h>

      #i nclude<string.h>

      typedef unsigned char uchar ;

      //这里用来切换晶振频率,支持11.0592MHz和18.432MHz

      //#define F18_432

      #define F11_0592

      uchar tmpbuf2[64]={0};

      //用来作为模拟串口接收数据的缓存

      struct

      {

      uchar recv :6 ;//tmpbuf2数组下标,用来将模拟串口接收到的数据存放到tmpbuf2中

      uchar send :6 ;//tmpbuf2数组下标,用来将tmpbuf2中的数据发送到串口

      }tmpbuf2_point={0,0};

      sbit newRXD=P3^2 ;//模拟串口的接收端设为P3.2

      void UartInit()

      {

      SCON=0x50 ;// SCON: serail mode 1, 8-bit UART

      TMOD|=0x21 ;// TMOD: timer 1, mode 2, 8-bit reload,自动装载预置数(自动将TH1送到TL1);T0工作在方式1,十六位定时

      PCON|=0x80 ;// SMOD=1;

      #ifdef F11_0592

      TH1=0xE8 ;// Baud:2400  fosc=11.0592MHz 2400bps为从串口接收数据的速率

      TL1=0xE8 ;// 计数器初始值,fosc=11.0592MHz 因为TH1一直往TL1送,所以这个初值的意义不大

      TH0=0xFF ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz

      TL0=0xA0 ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz

      #endif

      #ifdef F18_432

      TH1=0xD8 ;     // Baud:2400  fosc=18.432MHz 2400bps为从串口接收数据的速率

      TL1=0xD8 ;     // 计数器初始值,fosc=18.432MHz 因为TH1一直往TL1送,所以这个初值的意义不大

      TH0=0xFF ;// 定时器0初始值,延时104us,目的是令模拟串口的波特率为9600bps fosc=18.432MHz

      TL0=0x60 ;// 定时器0初始值,延时104us,目的是令模拟串口的波特率为9600bps fosc=18.432MHz

      #endif

      IE|=0x81 ;// 中断允许总控制位EA=1;使能外部中断0

      TF0=0 ;

      IT0=1 ;// 设置外部中断0为边沿触发方式

      TR1=1 ;// 启动TIMER1,用于产生波特率

      }

      void WaitTF0(void)

      {

      while(!TF0);

      TF0=0 ;

      #ifdef F11_0592

      TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

      TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

      #endif

      #ifdef F18_432

      TH0=0xFF ;

      // 定时器重装初值 fosc=18.432MHz

      TL0=0x60 ;

      // 定时器重装初值 fosc=18.432MHz

      #endif

      }

      //接收一个字符

      uchar RByte()

      {

      uchar Output=0 ;

      uchar i=8 ;

      TR0=1 ;     //启动Timer0

      #ifdef F11_0592

      TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

      TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

      #endif

      #ifdef F18_432

      TH0=0xFF ;// 定时器重装初值 fosc=18.432MHz

      TL0=0x60 ;// 定时器重装初值 fosc=18.432MHz

      #endif

      TF0=0 ;

      WaitTF0();//等过起始位

      //接收8位数据位

      while(i--)

      {

      Output>>=1 ;

      if(newRXD)Output|=0x80 ;//先收低位

      WaitTF0();//位间延时

      }

      TR0=0 ;//停止Timer0

      return Output ;

      }

      //向COM1发送一个字符

      void SendChar(uchar byteToSend)

      {

      SBUF=byteToSend ;

      while(!TI);

      TI=0 ;

      }

      void main()

      {

      UartInit();

      while(1)

      {

      if(tmpbuf2_point.recv!=tmpbuf2_point.send)//差值表示模拟串口接收数据缓存中还有多少个字节的数据未被处理(发送至串口)

      {

      SendChar(tmpbuf2[tmpbuf2_point.send++]);

      }

      }

      }

      //外部中断0,说明模拟串口的起始位到来了

      void Simulated_Serial_Start()interrupt 0

      {

      EX0=0 ;     //屏蔽外部中断0

      tmpbuf2[tmpbuf2_point.recv++]=RByte();     //从模拟串口读取数据,存放到tmpbuf2数组中

      IE0=0 ;     //防止外部中断响应2次,防止外部中断函数执行2次

      EX0=1 ;     //打开外部中断0

      }

      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    [NextPage]

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

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

      1、《51》在接收数据的程序中,采用的是循环等待的方法来检测起始位(见《51》的“附:51 IO口模拟串口通讯C源程序(定时器计数法)” 部分),这种方法在较大程序中,可能会错过起始位(比如起始位到来的时候程序正好在干别的,而没有处于判断起始位到来的状态),或者一直在检测起始位,而没有办法完成其他工作。为了避免这个问题,在本接收程序中采用了外部中断的方法,将外部中断0引脚作为模拟串口的接收端,设IT0=1(将外部中断0设为边缘触发)。这样当起始位(低电平)到来时,就会引发外部中断,然后在外部中断处理函数中接收余下的数据。这种方法可以保证没数据的时候程序该干什么干什么,一旦模拟串口接收端有数据,就可以立即接收到。

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

      3、《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函数,仅供参考:

      /**********************************************

      *            定时器0溢出后重装初值

      **********************************************/

      void WaitTF0(void)

      {

      TF0=0 ;

      #ifdef F11_0592

      TH0=0xFF ;// 定时器重装初值 fosc=11.0592MHz

      TL0=0xA0 ;// 定时器重装初值 fosc=11.0592MHz

      #endif

      #ifdef F18_432

      TH0=0xFF ;// 定时器重装初值 fosc=18.432MHz

      TL0=0x60 ;// 定时器重装初值 fosc=18.432MHz

      #endif

      while(!TF0);

      TF0=0 ;

      }

      /**********************************************

      *            从串口B接收一个字符

      **********************************************/

      uchar RByte()

      {

      uchar Output=0 ;

      uchar i=8 ;

      //    TR0=1;                             //启动Timer0

      /*

      #ifdef F11_0592

      TH0      = 0xFF;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

      TL0   = 0xA0;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

      #endif

      #ifdef F18_432

      TH0      = 0xFF;    // 定时器重装初值 fosc=18.432MHz

      TL0   = 0x60;    // 定时器重装初值 fosc=18.432MHz

      #endif

      */

      WaitTF0();//等过起始位

      //接收8位数据位

      while(i--)

      {

      Output>>=1 ;

      if(newRXD)Output|=0x80 ;          //先收低位

      WaitTF0();//位间延时

      }

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

      //    WaitTF0();                        //等过结束位

      //    TR0=0;                             //停止Timer0

      return Output ;

      }

      /**********************************************

      *            发送一个字节到串口B

      **********************************************/

      void WByte(uchar input)

      {

      //发送启始位

      uchar j=8 ;

      //TR0=1;

      newTXD=(bit)0 ;

      WaitTF0();

      //发送8位数据位

      while(j--)

      {

      newTXD=(bit)(input&0x01);//先传低位

      WaitTF0();

      input=input>>1 ;

      }

      //发送校验位(无)

      //发送结束位

      newTXD=(bit)1 ;

      WaitTF0();

      //TR0=0;

      }

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

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

      //    WaitTF0();                        //等过结束位

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

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

      在本文的程序中,去掉这两句后程序执行正确,如果其他朋友在使用时真的出现问题,可以试着再把它们加上试一下

    展开全文
  • 一般的 51 系列单片机只提供一个串口,需要更多串口就只能靠程序模拟。 1、什么是模拟串口 模拟串口就是利用51的两个输入输出引脚如P1.0和P1.1,置1或0分别代表高低电平,也就是串口通信中所说的位,如起始位用低...

    一般的 51 系列单片机只提供一个串口,需要更多串口就只能靠程序模拟。

    1、什么是模拟串口

    模拟串口就是利用51的两个输入输出引脚如P1.0和P1.1,置1或0分别代表高低电平,也就是串口通信中所说的位,如起始位用低电平,则将其置0,停止位为高电平,则将其置1,各种数据位和校验位则根据情况置1或置0。

    2、什么是波特率

    串口通信的波特率,说到底只是每位电平持续的时间,波特率越高,持续的时间越短。如波特率为9600bps,即每一位传送时间为1000ms/9600=0.104ms,即位与位之间的延时为0.104毫秒。

    单片机的延时是通过执行若干条指令来达到目的的,因为每条指令为1-3个指令周期,可通过若干个指令周期来进行延时,

    3、指令周期

    单片机常用11.0592M的的晶振,用此频率则每个指令周期的时间为(12/11.0592)us,那么波特率为9600BPS每位要间融多少个指令周期呢?

    指令周期s=(1000000/9600)/(12/11.0592)=96,刚好为一整数,如果为4800BPS则为96x2=192,如为19200BPS则为48,刚好都为整数个指令周期。

    4、模拟串口实现

    以11.0592M的晶振为例,实现三种模拟串口的方法:

    方法一: 延时法

    分析: 

    此种方法在接收上存在一定的难度,主要是采样定位需较准确,另外还必须知道每条语句的指令周期数。

    此法可能模拟若干个串口,实际中采用它的人也很多,但如果你用Keil C,本人不建议使用此种方法,上述程序在P89C52、AT89C52、W78E52三种单片机上实验通过。

    通过上述计算大家知道,串口的每位需延时0.104秒,中间可执行96个指令周期。

    #define uchar unsigned char
    
    sbit P1_0 = 0x90;
    
    sbit P1_1 = 0x91;
    
    sbit P1_2 = 0x92;
    
    #define RXD P1_0 //接收脚
    
    #define TXD P1_1 //发送脚
    
    #define WRDYN 44 //写延时
    
    #define RDDYN 43 //读延时
    
    //延时程序
    void Delay2cp(unsigned char i)
    
    {
      while(--i); //刚好两个指令周期。
    }
    
    //往串口发送一个字节
    void WByte(uchar input)
    {
    uchar i=8;
    
    TXD=(bit)0; //发送启始位
    
    Delay2cp(39);
    
    //发送8位数据位
    while(i--)
    {
    TXD=(bit)(input&0x01); //先传低位
    Delay2cp(36);
    input=input>>1;
    }
    
    //发送校验位(无)
    TXD=(bit)1; //发送结束位
    Delay2cp(46);
    }
    
    //从串口接收一个字节
    uchar RByte(void)
    {
    uchar Output=0;
    
    uchar i=8;
    
    uchar temp=RDDYN;
    
    //接收8位数据位
    
    Delay2cp(RDDYN*1.5); //此处注意,等过起始位
    while(i--)
    {
    Output >>=1;
    
    if(RXD) Output |=0x80; //先收低位
    
    Delay2cp(35); //(96-26)/2,循环共占用26个指令周期
    }
    
    while(--temp) //在指定的时间内搜寻结束位。
    {
    Delay2cp(1);
    
    if(RXD)break; //收到结束位便退出
    }
    
    return Output;
    }
    
    

    方法二: 计数法

    分析: 

    51的计数器在每指令周期加1,直到溢出,同时硬件置溢出标志位。这样我们就可以通过预置初值的方法让机器每96个指令周期产生一次溢出,程序不断的查询溢出标志来决定是否发送或接收下一位。接收和发送都很准确,另外不需要计算每条语句的指令周期数。

    //计数器初始化
    void S2INI(void)
    
    {
    TMOD |=0x02; //计数器0,方式2
    
    TH0=0xA0;    //预值为256-96=140,十六进制A0
    
    TL0=TH0;
    
    TR0=1;       //开始计数
    
    TF0=0;
    }
    
    //查询计数器溢出标志位
    void WaitTF0( void )
    {
    while(!TF0);
    TF0=0;
    }
    
    //向串口发送一个字节数据
    void WByte(uchar input)
    {
    //发送启始位
    uchar i=8;
    
    TR0=1;
    
    TXD=(bit)0;
    
    WaitTF0();
    
    //发送8位数据位
    while(i--)
    {
    TXD=(bit)(input&0x01);//先传低位
    
    WaitTF0();
    
    input=input>>1;
    }
    
    //发送校验位(无)
    //发送结束位
    TXD=(bit)1;
    WaitTF0();
    
    TR0=0;
    }

    方法三:中断法

    分析:

    中断的方法和计数器的方法差不多,只是当计算器溢出时便产生一次中断,用户可以在中断程序中置标志,程序不断的查询该标志来决定是否发送或接收下一位,当然程序中需对中断进行初始化,同时编写中断程序。本程序使用Timer0中断。

    另外还需注明的是本文所说的串口就是通常的三线制异步通信串口(UART),只用 RXD、TXD、GND。

    #define TM0_FLAG P1_2 //设传输标志位
    
    //计数器及中断初始化
    void S2INI(void)
    {
    TMOD |=0x02; //计数器0,方式2
    
    TH0=0xA0;    //预值为256-96=140,十六进制A0
    
    TL0=TH0;
    
    TR0=0;       //在发送或接收才开始使用
    
    TF0=0;
    
    ET0=1;       //允许定时器0中断
    
    EA=1;        //中断允许总开关
    }
    
    //接收一个字符
    uchar RByte()
    {
    uchar Output=0;
    
    uchar i=8;
    
    TR0=1; //启动Timer0
    
    TL0=TH0;
    
    WaitTF0(); //等过起始位
    
    //接收8位数据位
    
    while(i--)
    {
    Output >>=1;
    
    if(RXD) Output |=0x80; //先收低位
    
    WaitTF0(); //位间延时
    }
    
    while(!TM0_FLAG) if(RXD) break;
    
    TR0=0; //停止Timer0
    
    return Output;
    }
    
    //中断1处理程序
    void IntTimer0() interrupt 1
    
    {
    TM0_FLAG=1; //设置标志位。
    }
    
    //查询传输标志位
    void WaitTF0( void )
    {
    while(!TM0_FLAG) ;
    
    TM0_FLAG=0; //清标志位
    }

     

     


    refer:

    http://www.elecfans.com/d/650267.html

     

     

    展开全文
  • 要一个串口,上报又需要另一个串口,这就要求单片机具有双串口的功能,但我们知道一般的51 系列只提供一个串口,那么另一个串口只能靠程序模拟。 本文所说的模拟串口, 就是利用51的两个输入输出引脚如P1.0和

    随着单片机的使用日益频繁,用其作前置机进行采集和通信也常见于各种应用,一般是利用前置

    机采集各种终端数据后进行处理、存储,再主动或被动上报给管理站。这种情况下下,采集会需

    要一个串口,上报又需要另一个串口,这就要求单片机具有双串口的功能,但我们知道一般的51

    系列只提供一个串口,那么另一个串口只能靠程序模拟。

    本文所说的模拟串口, 就是利用51的两个输入输出引脚如P1.0和P1.1,置1或0分别代表高低电

    平,也就是串口通信中所说的位,如起始位用低电平,则将其置0,停止位为高电平,则将其置

    1,各种数据位和校验位则根据情况置1或置0。至于串口通信的波特率,说到底只是每位电平持续

    的时间,波特率越高,持续的时间越短。如波特率为9600BPS,即每一位传送时间为

    1000ms/9600=0.104ms,即位与位之间的延时为为0.104毫秒。单片机的延时是通过执行若干条

    指令来达到目的的,因为每条指令为1-3个指令周期,可即是通过若干个指令周期来进行延时的,

    单片机常用11.0592M的的晶振,现在我要告诉你这个奇怪数字的来历。用此频率则每个指令周期

    的时间为(12/11.0592)us,那么波特率为9600BPS每位要间融多少个指令周期呢?

    指令周期s=(1000000/9600)/(12/11.0592)=96,刚好为一整数,如果为4800BPS则为

    96x2=192,如为19200BPS则为48,别的波特率就不算了,都刚好为整数个指令周期,妙吧。至于

    别的晶振频率大家自已去算吧。

    现在就以11.0592M的晶振为例,谈谈三种模拟串口的方法。

    方法一:延时法

         通过上述计算大家知道,串口的每位需延时0.104秒,中间可执行96个指令周期。

    #define uchar unsigned char

    sbit P1_0 = 0x90;

    sbit P1_1 = 0x91;

    sbit P1_2 = 0x92;

    #define RXD P1_0

    #define TXD P1_1

    #define WRDYN 44 //写延时

    #define RDDYN 43 //读延时

    //往串口写一个字节

    void WByte(uchar input)

    {

         uchar i=8;

         TXD=(bit)0;                      //发送启始

         Delay2cp(39);

         //发送8位数据位

         while(i--)

         {

             TXD=(bit)(input&0x01);      //先传低位

             Delay2cp(36);

             input=input>>1;

         }

         //发送校验位(无)

         TXD=(bit)1;                      //发送结束

         Delay2cp(46);

    }

    //从串口读一个字节

    uchar RByte(void)

    {

         uchar Output=0;

         uchar i=8;

         uchar temp=RDDYN;

         //发送8位数据位

    Delay2cp(RDDYN*1.5);          //此处注意,等过起始位

         while(i--)

         {

             Output >>=1;

             if(RXD) Output   |=0x80;      //先收低位

             Delay2cp(35);              //(96-26)/2,循环共

    占用26个指令周期

         }

         while(--temp)                     //在指定的

    时间内搜寻结束位。

         {

             Delay2cp(1);

             if(RXD)break;              //收到结束位便退出

         }

         return Output;

    }

    //延时程序*

    void Delay2cp(unsigned char i)

    {

         while(--i);                      //刚好两个

    指令周期。

    }

         此种方法在接收上存在一定的难度,主要是采样定位存在需较准确,另外还必须知道

    每条语句的指令周期数。此法可能模拟若干个串口,实际中采用它的人也很多,但如果你用Keil

    C,本人不建议使用此种方法,上述程序在P89C52、AT89C52、W78E52三种单片机上实验通过。

    方法二:计数法

         51的计数器在每指令周期加1,直到溢出,同时硬件置溢出标志位。这样我们就可以

    通过预置初值的方法让机器每96个指令周期产生一次溢出,程序不断的查询溢出标志来决定是否

    发送或接收下一位。

        

    //计数器初始化

    void S2INI(void)

    {

         TMOD |=0x02;                 //计数器0,方式2

    TH0=0xA0;                     //预值为256-96=140,十六进制A0

         TL0=TH0;        

         TR0=1;                         //开始计数

         TF0=0;

    }

    void WByte(uchar input)

    {

         //发送启始位

         uchar i=8;

         TR0=1;

         TXD=(bit)0;

         WaitTF0();

         //发送8位数据位

         while(i--)

         {

             TXD=(bit)(input&0x01);      //先传低位

             WaitTF0();

             input=input>>1;

         }

         //发送校验位(无)

         //发送结束位

         TXD=(bit)1;

         WaitTF0();

         TR0=0;

    }    

    //查询计数器溢出标志位

    void WaitTF0( void )

    {

         while(!TF0);

         TF0=0;

    }

         接收的程序,可以参考下一种方法,不再写出。这种办法个人感觉不错,接收和发送

    都很准确,另外不需要计算每条语句的指令周期数。

    方法三:中断法

         中断的方法和计数器的方法差不多,只是当计算器溢出时便产生一次中断,用户可以

    在中断程序中置标志,程序不断的查询该标志来决定是否发送或接收下一位,当然程序中需对中

    断进行初始化,同时编写中断程序。本程序使用Timer0中断。

    #define TM0_FLAG P1_2 //设传输标志位

    //计数器及中断初始化

    void S2INI(void)

    {

         TMOD |=0x02;                 //计数器0,方式2

    TH0=0xA0;                     //预值为256-96=140,十六进制A0

         TL0=TH0;        

         TR0=0;                          //在发送或

    接收才开始使用

         TF0=0;

         ET0=1;                          //允许定时

    器0中断

         EA=1;                          //中断允许

    总开关

    }

    //接收一个字符

    uchar RByte()

    {

         uchar Output=0;

         uchar i=8;

    TR0=1;                          //启动Timer0

    TL0=TH0;

         WaitTF0();                     //等过起始

         //发送8位数据位

         while(i--)

         {

             Output >>=1;

             if(RXD) Output   |=0x80;      //先收低位

    WaitTF0();                 //位间延时

         }

         while(!TM0_FLAG) if(RXD) break;

         TR0=0;                          //停止

    Timer0

         return Output;

    }

    //中断1处理程序

    void IntTimer0() interrupt 1

    {

         TM0_FLAG=1;                 //设置标志位。

    }

    //查询传输标志位

    void WaitTF0( void )

    {

    while(!TM0_FLAG);

    TM0_FLAG=0;                  //清标志位

    }

         中断法也是我推荐的方法,和计数法大同小异。发送程序参考计数法,相信是件很容

    易的事。

    另外还需注明的是本文所说的串口就是通常的三线制异步通信串口(UART),只用RXD、TXD、

    GND。

    附:51 IO口模拟串口通讯C源程序(定时器计数法)

    #include

    sbit BT_SND =P1^0;

    sbit BT_REC =P1^1;

    /**********************************************

    IO 口模拟232通讯程序

    使用两种方式的C程序 占用定时器0

    **********************************************/

    #define MODE_QUICK

    #define F_TM F0

    #define TIMER0_ENABLE   TL0=TH0; TR0=1;

    #define TIMER0_DISABLE TR0=0;

    sbit ACC0=    ACC^0;

    sbit ACC1=    ACC^1;

    sbit ACC2=    ACC^2;

    sbit ACC3=    ACC^3;

    sbit ACC4=    ACC^4;

    sbit ACC5=    ACC^5;

    sbit ACC6=    ACC^6;

    sbit ACC7=    ACC^7;

    void IntTimer0() interrupt 1

    {

    F_TM=1;

    }

    //发送一个字符

    void PSendChar(unsigned char inch)

    {

    #ifdef MODE_QUICK

    ACC=inch;

    F_TM=0;

    BT_SND=0; //start bit

    TIMER0_ENABLE; //启动

    while(!F_TM);

    BT_SND=ACC0; //先送出低位

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC1;

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC2;

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC3;

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC4;

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC5;

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC6;

    F_TM=0;

    while(!F_TM);

    BT_SND=ACC7;

    F_TM=0;

    while(!F_TM);

    BT_SND=1;

    F_TM=0;

    while(!F_TM);

    TIMER0_DISABLE; //停止timer

    #else

    unsigned char ii;

    ii=0;

    F_TM=0;

    BT_SND=0; //start bit

    TIMER0_ENABLE; //启动

    while(!F_TM);

    while(ii<8)

    {

    if(inch&1)

    {

    BT_SND=1;

    }

    else

    {

    BT_SND=0;

    }

    F_TM=0;

    while(!F_TM);

    ii++;

    inch>>=1;

    }

    BT_SND=1;

    F_TM=0;

    while(!F_TM);

    #endif

    TIMER0_DISABLE; //停止timer

    }

    //接收一个字符

    unsigned char PGetChar()

    {

    #ifdef MODE_QUICK

    TIMER0_ENABLE;

    F_TM=0;

    while(!F_TM); //等过起始位

    ACC0=BT_REC;

    TL0=TH0;

    F_TM=0;

    while(!F_TM);

    ACC1=BT_REC;

    F_TM=0;

    while(!F_TM);

    ACC2=BT_REC;

    F_TM=0;

    while(!F_TM);

    ACC3=BT_REC;

    F_TM=0;

    while(!F_TM);

    ACC4=BT_REC;

    F_TM=0;

    while(!F_TM);

    ACC5=BT_REC;

    F_TM=0;

    while(!F_TM);

    ACC6=BT_REC;

    F_TM=0;

    while(!F_TM);

    ACC7=BT_REC;

    F_TM=0;

    while(!F_TM)

    {

    if(BT_REC)

    {

    break;

    }

    }

    TIMER0_DISABLE; //停止timer

    return ACC;

    #else

    unsigned char rch,ii;

    TIMER0_ENABLE;

    F_TM=0;

    ii=0;

    rch=0;

    while(!F_TM); //等过起始位

    while(ii<8)

    {

    rch>>=1;

    if(BT_REC)

    {

    rch|=0x80;

    }

    ii++;

    F_TM=0;

    while(!F_TM);

    }

    F_TM=0;

    while(!F_TM)

    {

    if(BT_REC)

    {

    break;

    }

    }

    TIMER0_DISABLE; //停止timer

    return rch;

    #endif

    }

    //检查是不是有起始位

    bit StartBitOn()

    {

    return   (BT_REC==0);

    }

    void main()

    {

    unsigned char gch;

    TMOD=0x22; /*定时器1为工作模式2(8位自动重装),0为模式2(8位

    自动重装) */

    PCON=00;

    TR0=0; //在发送或接收才开始使用

    TF0=0;

    TH0=(256-96); //9600bps 就是 1000000/9600=104.167微秒 执行的

    timer是

    //             

    104.167*11.0592/12= 96

    TL0=TH0;

    ET0=1;

    EA=1;

    PSendChar(0x55);

    PSendChar(0xaa);

    PSendChar(0x00);

    PSendChar(0xff);

    while(1)

    {

    if(StartBitOn())

    {

    gch=PGetChar();

    PSendChar(gch);

    }

    }

    }

    展开全文
  • 单片机I/O口模拟串口

    2019-04-26 15:42:56
    要用到2个串口,我使用了AMTEGA8A单片机,所以用普通IO模拟做了一个串口。花了我两个晚上的时间,才调试好模拟串口程序,也遇到不少的问题,今天终于搞定了。但是还只是波特率1200,校验位N 数据8 停止1 ,以后再...
  • 发一个51单片机模拟串口代码

    千次阅读 2018-12-03 15:11:43
    由于8位单片机一般只有一个串行接口,往往不够用,只能自己写一个模拟串口的程序,下面这个程序是自己早先写的,放上来,对别人也许有点用,注意接收端同时接到P3.2(INT0) #include &lt;at89X52.h&gt; #...
  • 教你如何在STC51单片机模拟串口

    千次阅读 2019-07-05 16:36:37
    我们可以不使用单片机本身带有的串口,而自己用程序去模拟一个串口并达到和本身的串口具有同样的功能, 首先,我们需要用到CH340串口模块,大家可以上某宝自行购买。 正面: 反面: 然后我们需要了解一下...
  • 51单片机模拟串口的三种方法

    千次阅读 2010-08-24 11:53:00
    ...要一个串口,上报又需要另一个串口,这就要求单片机具有双串口的功能,但我们知道一般的51 系列只提供一个串口,那么另一个串
  • Keil串口仿真调试

    万次阅读 2015-08-29 10:08:48
    ★用到的软件 ●Keil开发ruan
  • 效果图 虚拟串口 串口调试 Proteus电路设计图 51单片机程序代码 运行
  • keil MDK 中使用虚拟串口调试串口

    万次阅读 2016-08-19 15:13:37
    题目有点拗口,想利用串口调试Modbus啊,GPS啊什么的,可是手头没有硬件,怎么办?其实字节KEIL MDK和VSPD(这里两个软件网上都很容易就可以下载到...利用VSPD将PC上的两个虚拟串口连接起来。如图我将COM4 和COM5连接
  • windows下虚拟串口软件VSPD

    千次阅读 2011-01-20 15:29:00
    但是本本上往往没有“串口”这个玩意儿,在发现Proteus这个好玩的电路(单片机)仿真软件后,不可避免的接触到了虚拟串口软件Virtual Serial Ports Driver(VSPD)。Proteus+VSPD+UV 已经成为单片机开发的一套利器。...
  • 单片机一般只有一个串口,有时需要两个串口,本例采用模拟GPIO方法,使用115200波特率,测试通讯正常,时序精准。 ; /*UART1 DRIVERS ; – 115200Boundrate, 1 start bit, 8 data bit, 1 stop bit, no verify ;...
  • C51单片机 串口通信RX&TX---适合初学

    千次阅读 2019-03-01 13:14:43
    原料:keil、仿真软件、虚拟串口工具(群文件里的tools)vspdctl.dll记得覆盖 说明:串口中断允许位ES=1时,单片机一接收到数据或者发送数据就会进入串口中断函数(我认为是因为SBUF一有动静,便会触发到中断),...
  • 今天调试Proteus连接VB做的串口程序时遇到...工具:虚拟串口,串口调试助手,Virtual Terminal,COMPIM,AT89C52 问题是串口调试时不同接线法,收发有的无效,如下图,测试后的接法,有其它解决方案可评论交流! ...
  • Keil软件仿真的串口调试技巧

    千次阅读 2011-05-19 15:24:00
    Keil软件仿真的串口调试技巧引言 在单片机系统中,串口(UART,通用异步收发接口)是一个非常重要的组成部分。通常使用单片机串口通过RS232/RS485电平转换芯片与上位机连接,以进行上位机与下位机的数据交换、参数...
  • Proteus的51单片机串口通信

    千次阅读 2020-02-19 12:48:19
    首先需要在Proteus中将环境建立起来。我这里使用的版本是8.8. 利用两个元器件就可以建立VIRTUAL TERMINAL和COMPIM的连接。如上图所示,两个VIRTUAL... VIRTUAL TERMINAL是串口监视仪器,可以通过它将数据线上的符...
  • 虚拟串口 VSPD 的使用

    千次阅读 多人点赞 2019-04-27 21:54:02
    虚拟串口一般用来调试PC上位机软件和串口的通讯,假设我们需要开发一个串口助手的小工具,如果使用使用单片机的串口来实际调试,那么我们至少还需要一个USB转串口,这样才能让单片机和电脑串口通讯,接着我们还需要...
  • 本系列文章讲述了基于proteus仿真的51单片机学习,内容全面,不仅讲解电路原理,还讲解了单片机c语言,实例丰富,内容全面。
  • 两个单片机串口收发数据

    千次阅读 2019-07-11 08:57:33
    使用两个单片机开发版,串口3.0与3.1交叉互接,两个开发版分别烧写串口接收,串口发送的单片机程序观察P0端口输出值的变化 ============接收串口单片机程序=========== #include <reg52.h> #define uchar...
1 2 3 4 5 ... 20
收藏数 8,279
精华内容 3,311
关键字:

单片机模拟串口