传统的8051 系列单片机一般都配备一个串口,尤其是PIC系列的低端单片机根本就没有串口。这样就出问题了,假如当前单片机系统要求二个串口或多个串口进行同时通信,单片机只有一个串口可供通信就显得十分尴尬,但是在实际的应用中,有两种方法可以选择。
方法1:使用能够支持多串口通信的单片机,不过通过更换其他单片机多串口单片机会接导致成本的增加。优点就是编程简单,而且通信稳定可靠。
方法2:在IO 资源比较充足的情况下,可以通过IO 来模拟串口的通信,虽然这样会增加编程的难度,模拟串口的波特率会比真正的串口通信低一个层次,但是唯一优点就是成本上得到控制,而且通过不同的IO 组合可以实现更加之多的模拟串口,在实际应用中往往会采用模拟串口的方法来实现多串口通信。
普遍使用串口通信的数据流都是1 位起始位、8 位数据位、1 位停止位的格式的。
要注意的是,起始位作为识别是否有数据到来,停止位标志数据已经发送完毕。起始位固定值为0,停止位固定值为1,那么为什么起始位要是0,停止位要是1 呢?这个很好理解,假设停止位固定值为1,为了更加易识别数据的到来,电平的跳变最为简单也最容易识别,那么当有数据来的时候,只要在规定的时间内检测到发送过来的第一位的电平是否0 值,就可以确定是否有数据到来;另外停止位为1 的作用就是当没有收发数据之后引脚置为高电平起到抗干扰的作用。
在平时使用红外无线收发数据时,一般都采用模拟串口来实现的,但是有个问题要注意,波特率越高,传输距离越近;波特率越低,传输距离越远。对于这些通过模拟串口进行数据传输,波特率适宜为1200b/s来进行数据传输。
下面,就调试一个模拟串口通讯的例程,模拟串口接收引脚为P3.0,发送引脚为P3.1。
说明:
为了达到精确的定时,减少模拟串口时收发数据的累积误差,有必要通过对T/C 进行频繁的使能和禁止等操作。例如宏TIMER_ENABLE 为使能T/C,宏TIMER_DISABLE 禁止T/C,宏TIMER_WAIT 等待T/C 超时。
模拟串口的工作波特率为9600b/s,在串口收发的数据流当中,每一位的时间为1/9600≈104us,若单片机工作在12MHz 频率下,使用T/C0 工作在方式2,那么为了达到104us 的定时时间,TH0、TL0的初值为256-104=152,在实际的模拟串口中,往往出现收发数据不正确的现象。原因就在于TH0、TL0的初值,或许很多人会疑惑,按道理来说,计算T/C0 的初值是没有错的。对,是没有错,但是在SendByte和Recv 的函数当中,执行每一行代码都要消耗一定的时间,这就是所谓的“累积误差”导致收发数据出现问题,因此我们必须通过实际测试得到TH0、TL0 的初值,最佳值256-99=157。那么在T/C 初始化TimerInit 函数中,TH0、TL0 的初值不能够按照常规来计算得到,实际初值在正常初值附近,可以通过实际测试得到。
模拟串口主要复杂在模拟串口发送与接收,具体实现函数在SendByte 和RecvByte 函数,这两个函数必须要遵循“1 位起始位、8 位数据位、1 位停止位”的数据流。
SendByte 函数用于模拟串口发送数据,以起始位“0”作为移位传输的起始标志,然后将要发送的自己从低字节到高字节移位传输,最后以停止位“1”作为移位传输的结束标志。
RecvByte 函数用于模拟串口接收数据,一旦检测到起始位“0”,就立刻将接收到的每一位移位存储,最后以判断停止位“1”结束当前数据的接收。
main 函数完成T/C 的初始化,在while(1)死循环以检测起始位“0”为目的,当接收到的数据达到宏RECEIVE_MAX_BYTES 的个数时,将接收到的数据返发到外设。
STC12C5A32S2调试通过,临时没有发现错误。其他类型的单片机可根据此算法移植。
UART.C:
#include "stc.h"
#include "S_UART.h"
unsigned char fTimeouts=0;//定时器超时溢出标志位
unsigned char RecvBuf[16];
unsigned char RecvCount=0;
void Delay100ms() //@12.000MHz
{
unsigned char i, j, k;
i = 5;
j = 144;
k = 71;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
/****************************************
*函数名称:UARTSendByte
*输 入:byte 要发送的字节
*输 出:无
*功 能:串口发送单个字节
******************************************/
void SendByte(unsigned char b)
{
unsigned char i=8;
TXD=0;
TIMER_ENABLE();
TIMER_WAIT();
while(i--)
{
if(b&1)TXD=1;
else TXD=0;
TIMER_WAIT();
b>>=1;
}
TXD=1;
TIMER_WAIT();
TIMER_DISABLE();
}
/****************************************
*函数名称:RecvByte
*输 入:无
*输 出:单个字节
*功 能:串口 接收单个字节
******************************************/
unsigned char RecvByte(void)
{
unsigned char i;
unsigned char b=0;
TIMER_ENABLE();
TIMER_WAIT();
for(i=0;i<8;i++)
{
if(RXD)b|=(1<<i);
TIMER_WAIT();
}
TIMER_WAIT(); //等待结束位
TIMER_DISABLE();
return b;
}
/****************************************
*函数名称:PrintfStr
*输 入:pstr 字符串
*输 出:无
*功 能:串口 打印字符串
******************************************/
void PrintfStr(char * pstr)
{
while(pstr && *pstr)
{
SendByte(*pstr++);
}
}
/****************************************
*函数名称:TimerInit
*输 入:无
*输 出:无
*功 能:定时器初始化
******************************************/
void TimerInit(void)
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x02; //设置定时器模式
TL0 = 0x98; //设置定时初值
TH0 = 0x98; //设置定时重载值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
}
/****************************************
*函数名称:StartBitCome
*输 入:无
*输 出:0/1
*功 能:是否有起始位到达
******************************************/
unsigned char StartBitCome(void)
{
return (RXD==0);
}
/****************************************
*函数名称:main
*输 入:无
*输 出:无
*功 能:函数主体
******************************************/
void main(void)
{
unsigned char i;
TimerInit();
PrintfStr("Hello 8051\r\n");
while(1)
{
if(StartBitCome())
{
RecvBuf[RecvCount++]=RecvByte();
if(RecvCount>=RECEIVE_MAX_BYTES)
{
RecvCount=0;
for(i=0;i<RECEIVE_MAX_BYTES;i++)
{
SendByte(RecvBuf[i]);
}
}
}
PrintfStr("Hello 8051\r\n");
Delay100ms();
}
}
/****************************************
*函数名称:TimerIRQ
*输 入:无
*输 出:无
*功 能:定时器中断服务程序
******************************************/
void TimerIRQ(void) interrupt 1 using 0
{
fTimeouts=1;
}
UART.h
#ifndef __S_UART_H__
#define __S_UART_H__
#define RXD P3_0 //宏定义:接收数据的引脚
#define TXD P3_1 //宏定义:发送数据的引脚
#define RECEIVE_MAX_BYTES 16//宏定义:最大接收字节数
#define TIMER_ENABLE() {TL0=TH0;TR0=1;fTimeouts=0;}//使能定时器
#define TIMER_DISABLE() {TR0=0;fTimeouts=0;}//禁止定时器
#define TIMER_WAIT() {while(!fTimeouts);fTimeouts=0;}//等待定时器超时
extern unsigned char fTimeouts;//定时器超时溢出标志位
extern unsigned char RecvBuf[16];
extern unsigned char RecvCount;
#endif