注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

singleboy的博客

愿工作和生活中的点点滴滴与你分享。。。感谢各位同仁,让我跟着大家一起进步。

 
 
 

日志

 
 

(转帖)用IO口模拟出一个串口(51C程序)  

2009-09-06 21:44:11|  分类: 设计参考资料 |  标签: |举报 |字号 订阅

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

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

/***************************************************************
*    在单片机上模拟了一个串口,使用P2.1作为发送端
*    把单片机中存放的数据通过P2.1作为串口TXD发送出去
***************************************************************/

#include <reg51.h>
#include <stdio.h>
#include <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模拟串口接收端,从模拟串口接收数据发至串口
***************************************************************/

#include<reg51.h>
#include<stdio.h>
#include<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
}

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


以上是两个独立的测试程序,分别是模拟串口发送的测试程序和接收的测试程序
上面两个程序在编写过程中参考了这篇文章《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的时间。
在本文的程序中,去掉这两句后程序执行正确,如果其他朋友在使用时真的出现问题,可以试着再把它们加上试一下。
  评论这张
 
阅读(1150)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017