1、前一阵一直在做单片机的程序,由于串口不够,需要用 IO 口来模拟出一个串口。经过若干曲折并参考了一些现有的资料,基本上完成了。现在将完整的测试程序,以及其中一些需要总结的部分贴出来。程序硬件平台:11.0592M 晶振,STC 单片机(兼容51)/* 在单片机上模拟了一个串口,使用 P2.1作为发送端* 把单片机中存放的数据通过 P2.1作为串口 TXD 发送出去*/#include #include #include typedef unsigned char uchar;int i;uchar code info = 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0
2、x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55;sbit newTXD = P21;/模拟串口的发送端设为 P2.1void UartInit()SCON = 0x50; / SCON: serail mode 1, 8-bit UARTTMOD |= 0x21; / T0工作在方式1,十六位定时PCON |= 0x80; / SMOD=1;TH0 = 0xFE; / 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHzTL0 = 0x7F; / 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400
3、bps fosc=11.0592MHz/ TH0 = 0xFD; / 定时器0初始值,延时 417us,目的是令模拟串口的波特率为2400bps fosc=18.432MHz/ TL0 = 0x7F; / 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=18.432MHzvoid WaitTF0(void)while(!TF0);TF0=0;TH0=0xFE; / 定时器重装初值 fosc=11.0592MHzTL0=0x7F; / 定时器重装初值 fosc=11.0592MHz/ TH0 = 0xFD; / 定时器重装初值 fosc=18.432MHz/
4、TL0 = 0x7F; / 定时器重装初值 fosc=18.432MHzvoid WByte(uchar input)/发送启始位uchar j=8;TR0=1;newTXD=(bit)0;WaitTF0();/发送 8位数据位while(j-)newTXD=(bit)(input /先传低位WaitTF0();input=input1;/发送校验位(无)/发送结束位newTXD=(bit)1;WaitTF0();TR0=0; void Sendata()for(i=0;i#include#includetypedef unsigned char uchar ;/这里用来切换晶振频率,支持11
5、.0592MHz 和18.432MHz/#define F18_432#define F11_0592 uchar tmpbuf264=0;/用来作为模拟串口接收数据的缓存struct uchar recv :6 ;/tmpbuf2数组下标,用来将模拟串口接收到的数据存放到 tmpbuf2中uchar send :6 ;/tmpbuf2数组下标,用来将 tmpbuf2中的数据发送到串口tmpbuf2_point=0,0;sbit newRXD=P32 ;/模拟串口的接收端设为 P3.2void UartInit()SCON=0x50 ;/ SCON: serail mode 1, 8-bit
6、UARTTMOD|=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 fo
7、sc=11.0592MHzTL0=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.432MHzTL0=0x60 ;/ 定时器0初始值,
8、延时104us,目的是令模拟串口的波特率为9600bps fosc=18.432MHz#endif IE|=0x81 ;/ 中断允许总控制位 EA=1;使能外部中断0TF0=0 ;IT0=1 ;/ 设置外部中断0 为边沿触发方式TR1=1 ;/ 启动 TIMER1,用于产生波特率void WaitTF0(void)while(!TF0);TF0=0 ;#ifdef F11_0592 TH0=0xFF ;/ 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHzTL0=0xA0 ;/ 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz#en
9、dif #ifdef F18_432 TH0=0xFF ;/ 定时器重装初值 fosc=18.432MHzTL0=0x60 ;/ 定时器重装初值 fosc=18.432MHz#endif /接收一个字符uchar RByte()uchar Output=0 ;uchar i=8 ;TR0=1 ; /启动 Timer0#ifdef F11_0592 TH0=0xFF ;/ 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHzTL0=0xA0 ;/ 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz#endif #ifdef F18_432
10、 TH0=0xFF ;/ 定时器重装初值 fosc=18.432MHzTL0=0x60 ;/ 定时器重装初值 fosc=18.432MHz#endif TF0=0 ;WaitTF0();/等过起始位/接收 8位数据位while(i-)Output=1 ;if(newRXD)Output|=0x80 ;/先收低位WaitTF0();/位间延时TR0=0 ;/停止 Timer0return Output ;/向 COM1发送一个字符void SendChar(uchar byteToSend)SBUF=byteToSend ;while(!TI);TI=0 ;void main()UartInit
11、();while(1)if(tmpbuf2_point.recv!=tmpbuf2_point.send)/差值表示模拟串口接收数据缓存中还有多少个字节的数据未被处理(发送至串口)SendChar(tmpbuf2tmpbuf2_point.send+);/外部中断 0,说明模拟串口的起始位到来了void Simulated_Serial_Start()interrupt 0 EX0=0 ; /屏蔽外部中断0tmpbuf2tmpbuf2_point.recv+=RByte(); /从模拟串口读取数据,存放到 tmpbuf2数组中IE0=0 ; /防止外部中断响应2 次,防止外部中断函数执行2次E
12、X0=1 ; /打开外部中断0以上是两个独立的测试程序,分别是模拟串口发送的测试程序和接收的测试程序上面两个程序在编写过程中参考了这篇文章 51单片机模拟串口的三种方法 (在后文中简称51 ) ,但在它的基础上做了一些补充,下面是若干总结的内容:1、 51在接收数据的程序中,采用的是循环等待的方法来检测起始位(见51的“附:51 IO 口模拟串口通讯 C 源程序(定时器计数法) ” 部分) ,这种方法在较大程序中,可能会错过起始位(比如起始位到来的时候程序正好在干别的,而没有处于判断起始位到来的状态),或者一直在检测起始位,而没有办法完成其他工作。为了避免这个问题,在本接收程序中采用了外部中断
13、的方法,将外部中断0引脚作为模拟串口的接收端,设 IT0=1(将外部中断0设为边缘触发) 。这样当起始位(低电平)到来时,就会引发外部中断,然后在外部中断处理函数中接收余下的数据。这种方法可以保证没数据的时候程序该干什么干什么,一旦模拟串口接收端有数据,就可以立即接收到。2、加入了模拟串口接收缓冲区。在较大程序中,单片机要完成的工作很多,在模拟串口接收到了数据之后立即处理的话,有可能处理不过来造成丢失数据,或者影响程序其他部分执行。本程序中加入了64个字节的缓冲区,从模拟串口接收到的数据先存放在缓冲区中。这样就算程序一时没工夫处理这些数据,腾出手来之后也能在缓冲区中找到它们。3、 51文中的
14、WByte 函数和 RByte 函数中都先打开计数器后关闭计数器。如果使用本文的外部中断法来接收数据,并且外部中断处理函数里外都调用了 WByte 或 RByte 的话,需要将这两个函数中的 TR0=1, TR0=0操作的语句除去,并在 UartInit()中加入一句TR0=1;即让 TR0始终开着就可以。由于之前没有意识到这个问题,因此在具体应用时出现了奇怪的问题:表现为中断处理函数执行完毕之后,似乎回不到主程序,程序停在了一个不知道的地方。后来经过排查后找到了问题所在,那个程序的中断处理函数中用了 RByte,中断处理函数外用到了 WByte,而这两个函数的最后都有 TR0=0。这样当中断
15、处理函数执行完毕后,TR0 实际上是为0 的,返回主程序后(中断前的主程序可能正好处于其他的 WByte 或 RByte 执行中) ,原先以来定时器0溢出改变 TF0才能执行下去的 WByte 函数就无法进行下去,从而导致整个程序停下来不动。 (在本文的接收测试程序中不存在这个问题,因为中断处理程序中虽调用了 RByte,但中断处理程序外却没有调用 RByte 或 WByte)下面是修改后的 RByte、WByte 和 WaitTF0函数,仅供参考:/* 定时器0溢出后重装初值*/void WaitTF0(void)TF0=0 ;#ifdef F11_0592 TH0=0xFF ;/ 定时器重
16、装初值 fosc=11.0592MHzTL0=0xA0 ;/ 定时器重装初值 fosc=11.0592MHz#endif #ifdef F18_432 TH0=0xFF ;/ 定时器重装初值 fosc=18.432MHzTL0=0x60 ;/ 定时器重装初值 fosc=18.432MHz#endif while(!TF0);TF0=0 ;/* 从串口 B 接收一个字符*/uchar RByte()uchar Output=0 ;uchar i=8 ;/ TR0=1; /启动 Timer0/*#ifdef F11_0592TH0 = 0xFF; / 定时器重装初值 模拟串口的波特率为9600bp
17、s fosc=11.0592MHzTL0 = 0xA0; / 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz#endif#ifdef F18_432TH0 = 0xFF; / 定时器重装初值 fosc=18.432MHzTL0 = 0x60; / 定时器重装初值 fosc=18.432MHz#endif*/WaitTF0();/等过起始位/接收 8位数据位while(i-)Output=1 ;if(newRXD)Output|=0x80 ; /先收低位WaitTF0();/位间延时/ while(!TF0) if(newRXD) break; /此句和下一句不
18、能加,如果加上了将导致耗时过长,影响下一个字节的接收/ WaitTF0(); /等过结束位/ TR0=0; /停止 Timer0return Output ;/* 发送一个字节到串口 B*/void WByte(uchar input)/发送启始位uchar j=8 ;/TR0=1;newTXD=(bit)0 ;WaitTF0();/发送 8位数据位while(j-)newTXD=(bit)(input/先传低位WaitTF0();input=input1 ;/发送校验位(无)/发送结束位newTXD=(bit)1 ;WaitTF0();/TR0=0;4、在上面的新修改后的 RByte()函数
19、中,有被注释掉的如下两句:/ while(!TF0) if(newRXD) break; /此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收/ WaitTF0(); /等过结束位这两句在51文中的程序是存在的,但是使用中断接收法后,加上这两句后出现了问题。表现为接收到的下一个字节的数据不完整或直接接收不到,似乎这两句占用了过多的时间。看这两句的目的似乎是要延时以跳过结束位,但是我感觉这个结束位可以不用管它,反正结束位是个高电平,不会妨碍下一个字节是否到来的判断(下一个字节的起始位是低电平) 。那就由它去吧,没有必要为了它而占用 CPU 的时间。在本文的程序中,去掉这两句后程序执行正确,如果其他朋友在使用时真的出现问题,可以试着再把它们加上试一下。