傳統(tǒng)的8051 系列單片機(jī)一般都配備一個(gè)串口,尤其是PIC系列的低端單片機(jī)根本就沒有串口。這樣就出問題了,假如當(dāng)前單片機(jī)系統(tǒng)要求二個(gè)串口或多個(gè)串口進(jìn)行同時(shí)通信,單片機(jī)只有一個(gè)串口可供通信就顯得十分尷尬,但是在實(shí)際的應(yīng)用中,有兩種方法可以選擇。
方法1:使用能夠支持多串口通信的單片機(jī),不過通過更換其他單片機(jī)多串口單片機(jī)會(huì)接導(dǎo)致成本的增加。優(yōu)點(diǎn)就是編程簡單,而且通信穩(wěn)定可靠。
方法2:在IO 資源比較充足的情況下,可以通過IO 來模擬串口的通信,雖然這樣會(huì)增加編程的難度,模擬串口的波特率會(huì)比真正的串口通信低一個(gè)層次,但是唯一優(yōu)點(diǎn)就是成本上得到控制,而且通過不同的IO 組合可以實(shí)現(xiàn)更加之多的模擬串口,在實(shí)際應(yīng)用中往往會(huì)采用模擬串口的方法來實(shí)現(xiàn)多串口通信。
普遍使用串口通信的數(shù)據(jù)流都是1 位起始位、8 位數(shù)據(jù)位、1 位停止位的格式的。
要注意的是,起始位作為識(shí)別是否有數(shù)據(jù)到來,停止位標(biāo)志數(shù)據(jù)已經(jīng)發(fā)送完畢。起始位固定值為0,停止位固定值為1,那么為什么起始位要是0,停止位要是1 呢?這個(gè)很好理解,假設(shè)停止位固定值為1,為了更加易識(shí)別數(shù)據(jù)的到來,電平的跳變最為簡單也最容易識(shí)別,那么當(dāng)有數(shù)據(jù)來的時(shí)候,只要在規(guī)定的時(shí)間內(nèi)檢測到發(fā)送過來的第一位的電平是否0 值,就可以確定是否有數(shù)據(jù)到來;另外停止位為1 的作用就是當(dāng)沒有收發(fā)數(shù)據(jù)之后引腳置為高電平起到抗干擾的作用。
在平時(shí)使用紅外無線收發(fā)數(shù)據(jù)時(shí),一般都采用模擬串口來實(shí)現(xiàn)的,但是有個(gè)問題要注意,波特率越高,傳輸距離越近;波特率越低,傳輸距離越遠(yuǎn)。對(duì)于這些通過模擬串口進(jìn)行數(shù)據(jù)傳輸,波特率適宜為1200b/s來進(jìn)行數(shù)據(jù)傳輸。
下面,就調(diào)試一個(gè)模擬串口通訊的例程,模擬串口接收引腳為P3.0,發(fā)送引腳為P3.1。
說明:
為了達(dá)到精確的定時(shí),減少模擬串口時(shí)收發(fā)數(shù)據(jù)的累積誤差,有必要通過對(duì)T/C 進(jìn)行頻繁的使能和禁止等操作。例如宏TIMER_ENABLE 為使能T/C,宏TIMER_DISABLE 禁止T/C,宏TIMER_WAIT 等待T/C 超時(shí)。
模擬串口的工作波特率為9600b/s,在串口收發(fā)的數(shù)據(jù)流當(dāng)中,每一位的時(shí)間為1/9600≈104us,若單片機(jī)工作在12MHz 頻率下,使用T/C0 工作在方式2,那么為了達(dá)到104us 的定時(shí)時(shí)間,TH0、TL0的初值為256-104=152,在實(shí)際的模擬串口中,往往出現(xiàn)收發(fā)數(shù)據(jù)不正確的現(xiàn)象。原因就在于TH0、TL0的初值,或許很多人會(huì)疑惑,按道理來說,計(jì)算T/C0 的初值是沒有錯(cuò)的。對(duì),是沒有錯(cuò),但是在SendByte和Recv 的函數(shù)當(dāng)中,執(zhí)行每一行代碼都要消耗一定的時(shí)間,這就是所謂的“累積誤差”導(dǎo)致收發(fā)數(shù)據(jù)出現(xiàn)問題,因此我們必須通過實(shí)際測試得到TH0、TL0 的初值,最佳值256-99=157。那么在T/C 初始化TimerInit 函數(shù)中,TH0、TL0 的初值不能夠按照常規(guī)來計(jì)算得到,實(shí)際初值在正常初值附近,可以通過實(shí)際測試得到。
模擬串口主要復(fù)雜在模擬串口發(fā)送與接收,具體實(shí)現(xiàn)函數(shù)在SendByte 和RecvByte 函數(shù),這兩個(gè)函數(shù)必須要遵循“1 位起始位、8 位數(shù)據(jù)位、1 位停止位”的數(shù)據(jù)流。
SendByte 函數(shù)用于模擬串口發(fā)送數(shù)據(jù),以起始位“0”作為移位傳輸?shù)钠鹗紭?biāo)志,然后將要發(fā)送的自己從低字節(jié)到高字節(jié)移位傳輸,最后以停止位“1”作為移位傳輸?shù)慕Y(jié)束標(biāo)志。
RecvByte 函數(shù)用于模擬串口接收數(shù)據(jù),一旦檢測到起始位“0”,就立刻將接收到的每一位移位存儲(chǔ),最后以判斷停止位“1”結(jié)束當(dāng)前數(shù)據(jù)的接收。
main 函數(shù)完成T/C 的初始化,在while(1)死循環(huán)以檢測起始位“0”為目的,當(dāng)接收到的數(shù)據(jù)達(dá)到宏RECEIVE_MAX_BYTES 的個(gè)數(shù)時(shí),將接收到的數(shù)據(jù)返發(fā)到外設(shè)。
STC12C5A32S2調(diào)試通過,臨時(shí)沒有發(fā)現(xiàn)錯(cuò)誤。其他類型的單片機(jī)可根據(jù)此算法移植。
UART.C:
#include "stc.h"
#include "S_UART.h"
unsigned char fTimeouts=0;//定時(shí)器超時(shí)溢出標(biāo)志位
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);
}
/****************************************
*函數(shù)名稱:UARTSendByte
*輸 入:byte 要發(fā)送的字節(jié)
*輸 出:無
*功 能:串口發(fā)送單個(gè)字節(jié)
******************************************/
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();
}
/****************************************
*函數(shù)名稱:RecvByte
*輸 入:無
*輸 出:單個(gè)字節(jié)
*功 能:串口 接收單個(gè)字節(jié)
******************************************/
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(); //等待結(jié)束位
TIMER_DISABLE();
return b;
}
/****************************************
*函數(shù)名稱:PrintfStr
*輸 入:pstr 字符串
*輸 出:無
*功 能:串口 打印字符串
******************************************/
void PrintfStr(char * pstr)
{
while(pstr && *pstr)
{
SendByte(*pstr++);
}
}
/****************************************
*函數(shù)名稱:TimerInit
*輸 入:無
*輸 出:無
*功 能:定時(shí)器初始化
******************************************/
void TimerInit(void)
{
AUXR &= 0x7F; //定時(shí)器時(shí)鐘12T模式
TMOD &= 0xF0; //設(shè)置定時(shí)器模式
TMOD |= 0x02; //設(shè)置定時(shí)器模式
TL0 = 0x98; //設(shè)置定時(shí)初值
TH0 = 0x98; //設(shè)置定時(shí)重載值
TF0 = 0; //清除TF0標(biāo)志
TR0 = 1; //定時(shí)器0開始計(jì)時(shí)
ET0=1;
EA=1;
}
/****************************************
*函數(shù)名稱:StartBitCome
*輸 入:無
*輸 出:0/1
*功 能:是否有起始位到達(dá)
******************************************/
unsigned char StartBitCome(void)
{
return (RXD==0);
}
/****************************************
*函數(shù)名稱:main
*輸 入:無
*輸 出:無
*功 能:函數(shù)主體
******************************************/
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();
}
}
/****************************************
*函數(shù)名稱:TimerIRQ
*輸 入:無
*輸 出:無
*功 能:定時(shí)器中斷服務(wù)程序
******************************************/
void TimerIRQ(void) interrupt 1 using 0
{
fTimeouts=1;
}
UART.h
#ifndef __S_UART_H__
#define __S_UART_H__
#define RXD P3_0 //宏定義:接收數(shù)據(jù)的引腳
#define TXD P3_1 //宏定義:發(fā)送數(shù)據(jù)的引腳
#define RECEIVE_MAX_BYTES 16//宏定義:最大接收字節(jié)數(shù)
#define TIMER_ENABLE() {TL0=TH0;TR0=1;fTimeouts=0;}//使能定時(shí)器
#define TIMER_DISABLE() {TR0=0;fTimeouts=0;}//禁止定時(shí)器
#define TIMER_WAIT() {while(!fTimeouts);fTimeouts=0;}//等待定時(shí)器超時(shí)
extern unsigned char fTimeouts;//定時(shí)器超時(shí)溢出標(biāo)志位
extern unsigned char RecvBuf[16];
extern unsigned char RecvCount;
#endif