首先简单的介绍一下串行异步通讯的数据格式定义,发送或接收一个完整的字节信息,必须有“起始位”、“若干数据位”、“奇偶校验位”和“停止位”;定义每位信息的时间宽度——每秒发送的信息位个数,即为“波特率”。本文附带的模拟串口源程序采用数据帧的格式为 1位起始位(低电平),8位数据位(先低位在高位),1位停止位(高电平),且在线路空闲状态时总是保持为高电平。当在11M晶振时钟频率下,采用波特率为9600或19200时。该模拟串口都可以无误差的进行传输,具体分析后面进行。
参照源程序,我们知道,在这个模拟串口设计中,模拟的是单片机串行异步通讯方式1,由P1^0做接收端,P1^1做发送端,通过定时器1定时溢出中断来确定每位数据的时间,由StartBitOn()函数不断查询接收端的状态,当出现低电平(即起始位)的时候,调用接收程序,接收发送的数据。
针对以下附带的源程序分析知,PGetChar(),PSendChar()都是通过移位方式来接收数据,每接收一位数据,需要定时器溢出产生中断一次,故要得到一帧的数据,就必须经过10个数据位的时间才能完成,同样的,在模拟串口的发送端,要完整的发送一帧数据也要经过10个数据位的时间。因而在如下连接时,引出了以下的问题。
当RS232单次发送一个字符时,可以正常接收和发送回RS232。
当RS232连续发送一串字符时,通过模拟串口返回给RS232的字符只有原来的一半。如发送1234567890这样一个字符串时,接收的字符为13579。
那为什么在单独发送一个字符是不会丢失,而连续发送时就只有原来的一半了呢!
____________ ______________________________
| | | |
| | | |
| PC |---------->| PGetChar() |
| RS232| | | MCU |
| | | V |
| |<----------| PSendChar() |
------------- -------------------------------
源程序:
/**********************************************
IO 口模拟232串行异步通讯程序
**********************************************/
#include <reg51.h>
sbit BT_SND =P1^1;
sbit BT_REC =P1^0;
#define F_TM F0 //自定义标志位,作为中断标志位
#define TIMER0_ENABLE TL0=TH0; TR0=1;//TR0 = 1,启动T
#define TIMER0_DISABLE TR0=0;
// Acc 累加器做发送的移位寄存器
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;
//定时器计数器0的中断
void IntTimer0() interrupt 1
{
F_TM=1;
}
//发送一个字符
//数据格式一个启动位(0),8数据位,一个停止位(1)
void PSendChar(unsigned char Getch)
{
ACC=Getch;
F_TM=0;
BT_SND=0; //启动位
TIMER0_ENABLE; //记数器0启动
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
}
//接收一个字符
unsigned char PGetChar()
{
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;
}
//检查是不是有起始位
bit StartBitOn()
{
return (BT_REC==0);
}
void main()
{
unsigned char Getch;
TMOD=0x22; /*定时器1为工作模式2(8位自动重装),0为模式2(8位
自动重装) */
PCON=00;
TR0=0; //在发送或接收才开始使用
TF0=0;
TH0=(256-96); //9600bps 就是 1000000/9600=104.167微秒 执行的
//时间是104.167*11.0592/12= 96
TL0=TH0;
ET0=1;//定时器/记数器T0的溢出中断允许位,ET,允 许中断
EA=1;
while(1)
{
if(StartBitOn())
{
Getch=PGetChar();
PSendChar(Getch);
}
}
}
实验环境:
串口调试助手软件
AT89S51单片机及相应的硬件设备
win2000操作系统
原因分析如下:
PC的RS232像单片机发送1234567890字符串时,是连续一次发送的。当StartBitOn()检测到低电平起始位时,运行Getch=PGetChar()函数,显然上面说过要得到一帧的数据,就必须经过10个数据位的时间,同样运行PSendChar(Getch)函数时,也必须经过10个数据位的时间,且都是在忽略单片机本身执行指令的时间得到的。因而在当单片机接收了一个数据在接收下一位数据期间,必须至少消耗20个数据位的时间,等它在开始检测起始位时候,已经传输到第三个字符了,因而才会出现第上面的情况,当发送1234567890,而接收的字符是13579的原因。也就才会出现了很多工程师朋友们会对用软件实现的UART在可靠性和效率方面持怀疑态度的现象
if(StartBitOn())
{
Getch=PGetChar(); //采用移位方式要消耗10个数据位的时间
PSendChar(Getch); //采用移位方式要消耗10个数据位的时间
}
BT_REC 接收到的电平:
第1帧数据(10个数据位)第2帧(10个数据位)第3帧(10个起始位)
起始位1 起始位2 起始位3
参照上面的数据格式示意图知,当单片机StartBitOn()检测到起始位1并完成相应的接收转发Getch=PGetChar(); PSendChar(Getch),在进行下一次检测时,检测到的下一个起始位就是起始位3了。
综合上面分析可得出以下结论,在用软件模拟串行通讯时候,是以时间来模拟硬件设备的,用软件实现的UART的效率肯定没有办法和硬件UART比,在上叙连接时候,实际接收的数据确实只能有原来的一半,因为它是连续进行接收和发送两项工作的,但在实际应用中,可以通过一定的手段,比如先一次接收好PC所发送过来的所有数据,保存在预先设置的缓冲区里,稍后再去进行发送的工作,这样在接收的时候消耗的时间就只有10个数据位,在接收过程中不会丢失数据,能正确接收到所发送的所有字符。