跟着单片机的运用日益频繁,用其作前置机进行并重和通讯也常见于各种运用,一般是运用前置
机并重各种终端数据后进行处理、存储,再主动或被迫上报给管理站。这种状况下下,并重会需
要一个串口,上报又需求另一个串口,这就要求单片机具有双串口的功用,但咱们知道一般的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则为
96×2=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);
}
}
}