用普通 I/O 口也可以模拟标准 UART 串行口,进行串行通信。
帧
UART 通信规范是以 8 位二进制数为一帧,低位在前,逐位的传输。
为了区分各个帧,在每一帧之前,要有一个 0 作为起始标记,之后,有一个 1,作为结束符。
在结束符之前,还可选发一个“校验位”,但是,目前多数的应用都不选择这个位。
那么,每次的串行通信,就是传送一个字节,加上前后的标记,共 10 位二进制数。
空闲时,发送的都是 1;一旦出现了 0,就说明开始传输数据了。
波特率
串行通信的一个重要指标就是传输速度,就是每秒传送了多少位二进制数。
这个速度称为波特率,单位是 bps,中文就是“位/秒”。
时间设定
当以 9600bps 来传送数据时,每一位数的持续时间是 (1/9600)s,这也就是间隔时间。
如果选用晶振频率是 11059200Hz,一个机器周期T的时间就是 (12/11059200)s
那么,一位数的持续时间 (1/9600)s,是多少个机器周期T呢 ?
这是很容易算的,就是下面的这个算式:
X = (1/9600) / (12/11059200) = 11059200 / 12 / 9600 = 96T
为了精确定时,可以利用定时器来定时,每当 96T 时间到了,就发送出去一位二进制数,这就行了。
实验程序
用 IO 口模拟串口输出的程序如下:
#include
sbit TXD1 = P2^0; //用IO口模拟串口发送端
sbit RXD1 = P2^1; //用IO口模拟串口接收端
bit T96; //位变量
//----------------------------------------
void Wait96(void) //延时,控制波特率
{
while(T96); //等待出现0
T96 = 1; //清标志
}
//----------------------------------------
void WByte(char x) //发送一帧数据
{
char i;
TL0 = 160; //初值=256-96=160
TXD1 = 0; //发送起始位0
TR0 = 1; //启动定时器
Wait96(); //等待96T
for (i = 0; i < 8; i++) { //8位数
TXD1 = x & 1; //先传低位
x >>= 1;
Wait96(); //等待96T
}
TXD1 = 1; //发送结束位1
Wait96(); //等待96T
TR0 = 0; //关闭定时器
}
//----------------------------------------
void main()
{
char i;
TMOD = 0x02; //T0定时方式2
TH0 = 160; //初值=256-96=160
IE = 0x82;
T96 = 1; //清标志
while(1) {
for (i = 0x41; i < 0x5b; i++) //A~Z
WByte(i);
WByte(0x0D);
WByte(0x0A);
}
}
//----------------------------------------
void inttime0() interrupt 1 //T0中断
{
T96 = 0; //设置标志
}
//----------------------------------------
仿真截图
见下图:
图片链接:
http://xiangce.baidu.com/picture/detail/dabb5025872a8ae3030f64fd6dfc9c9a7d2bd791
由图可见,并没有使用单片机本身的串行口,而是把虚拟终端接到了 P2 口。
此时,虚拟终端也收到了字符 A~Z,以及回车换行。
后记
用 IO 口模拟串口输入也是可行的。
但是,这种模拟方法,编程的工作量,可以说相当的大;CPU 的工作时间,也被大量占用;
而且,还占用了定时器,那么在硬件资源方面,也浪费很多。
如果讨论模拟串口的接收问题,那还得搭上一个外部中断。
这种笨方法,当初,不知道是被谁想出来的 !真的远远比不上做而论道在前一篇博文中所介绍的用三态门扩充串口的方法。
费了这么大的劲,仅仅换来一个串口,可谓得不偿失。
做而论道是不会使用这样的方法的,写这篇博文,只不过是回答网上提出的问题而已。