通信按照基本類型可以分為并行通信和串行通信。并行通信時(shí)數(shù)據(jù)的各個位同時(shí)傳送,可以實(shí)現(xiàn)字節(jié)為單位通信,但是因?yàn)橥ㄐ啪€多占用資源多,成本高。比如我們前邊用到的P0 = 0xfe;一次給P0的8個IO口分別賦值,同時(shí)進(jìn)行信號輸出,類似于有8個車道同時(shí)可以過去8輛車一樣,這種形式就是并行的,我們習(xí)慣上還稱P0、P1、P2和P3為51單片機(jī)的4組并行總線。
約定好速度后,我們還要考慮第二個問題,數(shù)據(jù)什么時(shí)候是起始,什么時(shí)候是結(jié)束呢?不管是提前接收還是延遲接收,數(shù)據(jù)都會接收錯誤。在UART串行通信的時(shí)候,一個字節(jié)是8位,規(guī)定當(dāng)沒有通信信號發(fā)生時(shí),通信線路保持高電平,當(dāng)要發(fā)送數(shù)據(jù)之前,先發(fā)一位0表示起始位,然后發(fā)送8位數(shù)據(jù)位,數(shù)據(jù)位是先低后高的順序,數(shù)據(jù)位發(fā)完后再發(fā)一位1表示停止位。這樣本來要發(fā)送一個字節(jié)8位數(shù)據(jù),而實(shí)際上我們一共發(fā)送了10位,多出來的兩位其中一位起始位,一位停止位。而接收方呢,原本一直保持的高電平,一旦檢測到來了一位低電平,那就知道了要開始準(zhǔn)備接收數(shù)據(jù)了,接收到8位數(shù)據(jù)位后,然后檢測到停止位,再準(zhǔn)備下一個數(shù)據(jù)的接收了。我們圖示看一下,如圖11-2所示。
雖然這三個腳的名字和我們單片機(jī)上的串口名字一樣,但是卻不能直接和單片機(jī)對連直接通信,這是為什么呢?隨著我們了解的內(nèi)容越來越多,我們得慢慢知道,不是所有的電路都是5V代表高電平而0V代表低電平的。對于RS232標(biāo)準(zhǔn)來說,它是個反邏輯,也叫做負(fù)邏輯。為何叫負(fù)邏輯?它的TXD和RXD的電壓,-3V到-15V代表是1,3-15V之間的電壓代表是0。低電平代表的是1,而高電平代表的是0,所以稱之為負(fù)邏輯。因此電腦的9針232串口是不能和單片機(jī)直接連接的,需要用一個轉(zhuǎn)換芯片MAX232來完成,如圖11-4所示。
11.4 IO口模擬UART串口通信為了讓大家充分理解UART串口通信的原理,我們先用P3.0和P3.1這兩個當(dāng)做IO口來進(jìn)行模擬實(shí)際串口通信的過程,原理搞懂后,我們再使用寄存器配置實(shí)現(xiàn)串口通信過程。
對于
UART串口波特率,常用的值是300、600、1200、2400、4800、9600、14400、19200、28800、38400、57600、115200、128000、256000等速率。IO口模擬UART串行通信程序是一個簡單的演示程序,我們使用串口調(diào)試助手下發(fā)一個數(shù)據(jù),數(shù)據(jù)加1后,再自動返回。串口調(diào)試助手,在我們進(jìn)行全板子測試視頻的時(shí)候,大家已經(jīng)見過,這里我們直接使用STC-ISP軟件自帶的串口調(diào)試助手,可到www.torrancerestoration.com去下載此程序,先把串口調(diào)試助手使用給大家說一下,如圖11-6所示。第一步要選擇串口助手菜單,第二步選擇十六進(jìn)制顯示,第三步選擇十六進(jìn)制發(fā)送,第四步選擇COM口,這個COM口要和自己電腦設(shè)備管理器里的那個COM口一致,波特率是我們程序設(shè)定好的選擇,我們程序中讓一個數(shù)據(jù)位持續(xù)時(shí)間是1/9600秒,那這個地方選擇波特率就是選9600,校驗(yàn)位選N,數(shù)據(jù)位8,停止位1。
7.JPG (178.6 KB, 下載次數(shù): 316)
下載附件
2013-9-28 14:31 上傳
圖11-6 串口調(diào)試助手示意圖
串口調(diào)試助手的實(shí)質(zhì)就是我們利用電腦上的UART通信接口,通過這個UART接口發(fā)送數(shù)據(jù)給我們的單片機(jī),也可以把我們的單片機(jī)發(fā)送的數(shù)據(jù)接收到這個調(diào)試助手界面上。
因?yàn)槌醮谓佑|通信方面的技術(shù),所以我對這個程序進(jìn)行一下解釋,大家可以邊看我的解釋邊看程序,把底層原理先徹底弄懂。
變量定義部分就不用說了,直接看main主函數(shù)。首先是對通信的波特率的設(shè)定,在這里我們配置的波特率是9600,那么串口調(diào)試助手也得是9600。配置波特率的時(shí)候,我們用的是定時(shí)器0的模式2。模式2中,不再是TH0代表高8位,TL0代表低8位了,而只有TL0在進(jìn)行計(jì)數(shù)了。當(dāng)TL0溢出后,不僅僅會讓TF0變1,而且還會將TH0中的內(nèi)容重新自動裝到TL0中。這樣有一個好處,我們可以把我們想要的定時(shí)器初值提前存在TH0中,當(dāng)TL0溢出后,TH0自動把初值就重新送入TL0了,全自動的,不需要程序上再給TL0重新賦值了,配置方式很簡單,大家可以自己看下程序并且計(jì)算一下初值。
波特率設(shè)置好以后,打開中斷,然后等待接收串口調(diào)試助手下發(fā)的數(shù)據(jù)。接收數(shù)據(jù)的時(shí)候,首先要進(jìn)行低電平檢測 while (PIN_RXD),若沒有低電平則說明沒有數(shù)據(jù),一旦檢測到低電平,就進(jìn)入啟動接收函數(shù)StartRXD()。接收函數(shù)最開始啟動半個波特率周期,初學(xué)可能這里不是很明白。大家回頭看一下我們的圖11-2里邊的串口數(shù)據(jù)示意圖,信號在數(shù)據(jù)位電平變化的時(shí)候去讀,因?yàn)闀r(shí)序上的誤差以及信號穩(wěn)定性的問題很容易讀錯數(shù)據(jù),所以我們希望在信號最穩(wěn)定的時(shí)候去讀數(shù)據(jù)。除了信號變化的那個沿的位置外,其他位置都很穩(wěn)定,那么我們現(xiàn)在就約定在信號中間位置去讀取電平狀態(tài),這樣能夠保證我們信號讀的是對的。
一旦讀到了起始信號,我們就把當(dāng)前狀態(tài)設(shè)定成接受狀態(tài),并且打開定時(shí)器中斷,第一次是半個周期進(jìn)入中斷后,對起始位進(jìn)行二次判斷一下,確認(rèn)一下起始位是低電平,而不是一個干擾信號。以后每經(jīng)過9600分之一秒進(jìn)入一次中斷,并且把這個引腳的狀態(tài)讀到RxdBuf里邊。等待接收完畢之后,我們再把這個RxdBuf加1,再通過TXD引腳發(fā)送出去,同樣需要先發(fā)一位起始位,然后發(fā)8個數(shù)據(jù)位,再發(fā)結(jié)束位,發(fā)送完畢后,程序運(yùn)行到while (PIN_RXD),等待第二輪信號接收的開始。
#include <reg52.h>
sbit PIN_RXD = P3^0; //接收引腳定義
sbit PIN_TXD = P3^1; //發(fā)送引腳定義
bit RxdOrTxd = 0; //指示當(dāng)前狀態(tài)為接收還是發(fā)送
bit RxdEnd = 0; //接收結(jié)束標(biāo)志
bit TxdEnd = 0; //發(fā)送結(jié)束標(biāo)志
unsigned char RxdBuf = 0; //接收緩沖器
unsigned char TxdBuf = 0; //發(fā)送緩沖器
void ConfigUART(unsigned int baud);
void StartTXD(unsigned char dat);
void StartRXD();
void main ()
{
ConfigUART(9600); //配置波特率為9600
EA = 1; //開總中斷
while(1)
{
while (PIN_RXD); //等待接收引腳出現(xiàn)低電平,即起始位
StartRXD(); //啟動接收
while (!RxdEnd); //等待接收完成
StartTXD(RxdBuf+1); //接收到的數(shù)據(jù)+1后,發(fā)送回去
while (!TxdEnd); //等待發(fā)送完成
}
}
void ConfigUART(unsigned int baud) //串口配置函數(shù),baud為波特率
{
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x02; //配置T0為模式2
TH0 = 256 - (11059200/12) / baud; //計(jì)算T0重載值
}
void StartRXD() //啟動串行接收
{
TL0 = 256 - ((256-TH0) >> 1); //接收啟動時(shí)的T0定時(shí)為半個波特率周期
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動T0
RxdEnd = 0; //清零接收結(jié)束標(biāo)志
RxdOrTxd = 0; //設(shè)置當(dāng)前狀態(tài)為接收
}
void StartTXD(unsigned char dat) //啟動串行發(fā)送,dat為待發(fā)送字節(jié)數(shù)據(jù)
{
TxdBuf = dat; //待發(fā)送數(shù)據(jù)保存到發(fā)送緩沖器
TL0 = TH0; //T0計(jì)數(shù)初值為重載值
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動T0
PIN_TXD = 0; //發(fā)送起始位
TxdEnd = 0; //清零發(fā)送結(jié)束標(biāo)志
RxdOrTxd = 1; //設(shè)置當(dāng)前狀態(tài)為發(fā)送
}
void InterruptTimer0() interrupt 1 //T0中斷服務(wù)函數(shù),處理串行發(fā)送和接收
{
static unsigned char cnt = 0; //bit計(jì)數(shù)器,記錄當(dāng)前正在處理的位
if (RxdOrTxd) //串行發(fā)送處理
{
cnt++;
if (cnt <= 8) //低位在先依次發(fā)送8bit數(shù)據(jù)位
{
PIN_TXD = TxdBuf & 0x01;
TxdBuf >>= 1;
}
else if (cnt == 9) //發(fā)送停止位
{
PIN_TXD = 1;
}
else //發(fā)送結(jié)束
{
cnt = 0; //復(fù)位bit計(jì)數(shù)器
TR0 = 0; //關(guān)閉T0
TxdEnd = 1; //置發(fā)送結(jié)束標(biāo)志
}
}
else //串行接收處理
{
if (cnt == 0) //處理起始位
{
if (!PIN_RXD) //起始位為0時(shí),清零接收緩沖器,準(zhǔn)備接收數(shù)據(jù)位
{
RxdBuf = 0;
cnt++;
}
else //起始位不為0時(shí),中止接收
{
TR0 = 0; //關(guān)閉T0
}
}
else if (cnt <= 8) //處理8位數(shù)據(jù)位
{
RxdBuf >>= 1; //低位在先,所以將之前接收的位向右移
if (PIN_RXD) //接收腳為1時(shí),緩沖器最高位置1;為0時(shí)不處理即仍保持移位后的0
{
RxdBuf |= 0x80;
}
cnt++;
}
else //停止位處理
{
cnt = 0; //復(fù)位bit計(jì)數(shù)器
TR0 = 0; //關(guān)閉T0
if (PIN_RXD) //停止位為1時(shí),方能認(rèn)為數(shù)據(jù)有效
{
RxdEnd = 1; //置接收結(jié)束標(biāo)志
}
}
}
}
同學(xué)們通過學(xué)習(xí)我們的程序,也慢慢感受到了,程序的延時(shí)部分已經(jīng)不再使用簡單的delay來完成了,我們要通過我們的程序編寫積累,慢慢提高自己靈活運(yùn)用定時(shí)器的能力。一個小小的定時(shí)器,可以幫我們完成很多很多工作。
11.5 UART串口通信的基本應(yīng)用11.5.1 通信的三種基本類型我們常用的通信通�?梢苑譃閱喂ぁ腚p工、全雙工通信。
單工就是指只允許一方向另外一方傳送信息,而另一方不能回傳信息。比如我們的電視遙控器,我們的收音機(jī)廣播等,都是單工通信技術(shù)。
半雙工是指數(shù)據(jù)可以在雙方之間相互傳播,但是同一時(shí)刻只能其中一方發(fā)給另外一方,比如我們的對講機(jī)就是典型的半雙工。
全雙工通信就發(fā)送數(shù)據(jù)的同時(shí)也能夠接受數(shù)據(jù),兩者同步進(jìn)行,就如同我們的電話一樣,我們說話的同時(shí)也可以聽到對方的聲音。
11.5.2 UART模塊介紹IO口模擬串口通信,大家了解了串口通信的實(shí)質(zhì),但是我們的單片機(jī)程序卻需要不停的檢測掃描單片機(jī)IO口收到的數(shù)據(jù),大量占用了CPU資源。這時(shí)候就會有聰明人想了,其實(shí)我們不是很關(guān)心通信的過程,我們只需要一個通信的結(jié)果,最終得到接收到的數(shù)據(jù)就行了。這樣我們可以在單片機(jī)內(nèi)部做一個硬件模塊,讓他自動接收數(shù)據(jù),接收完了,通知我們一下就可以了,我們的51單片機(jī)內(nèi)部就存在這樣一個UART模塊,要正確使用它,當(dāng)然還得先把對應(yīng)的特殊功能寄存器配置好。
51單片機(jī)的UART串行口的結(jié)構(gòu)由串行口控制寄存器SCON、發(fā)送和接收電路三部分構(gòu)成,先來了解一下串口控制寄存器SCON。
表11-1 SCON--串行控制寄存器的位分配(地址:98H)
可位尋址;復(fù)位值:0x00;復(fù)位源:任何復(fù)位
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
符號 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
表11-2 SCON--串行控制寄存器的位描述
| | |
| | 這兩位共同決定了串口通信的模式0到模式3共4種模式。我們最常用的就是模式1,也就是SM0=0,SM1=1,下邊我們重點(diǎn)就講模式1,其他模式從略。 |
| |
| | 多機(jī)通信控制位(很少用),模式1直接清零。 |
| | 使能串行接收。由軟件置位使能接收,軟件清零則禁止接收 |
| | 模式2和3中將要發(fā)送的第9位數(shù)據(jù)(很少用) |
| | 模式2和3中接收第9位數(shù)據(jù)(很少用),模式1用來接收停止位 |
| | 發(fā)送中斷標(biāo)志位,模式1下,在數(shù)據(jù)位最后一位發(fā)送結(jié)束,開始發(fā)送停止位時(shí)由硬件自動置1,必須通過軟件清零。也就是說,再發(fā)送前我們清零TI,發(fā)送數(shù)據(jù),數(shù)據(jù)發(fā)送到停止位時(shí),TI硬件置1,方便我們CPU查詢發(fā)送完畢狀態(tài)。 |
| | 接收中斷標(biāo)志位,當(dāng)接收電路接收到停止位的中間位置時(shí),RI由硬件置1。也就是說,接收數(shù)據(jù)之前我們必須清零RI,接受數(shù)據(jù)到停止位的中間位置時(shí),RI硬件置1,方便我們CPU查詢到接收狀態(tài)。 |
前邊學(xué)了那么多寄存器的配置,相信SCON這個地方,對于大多數(shù)同學(xué)來說已經(jīng)不是難點(diǎn)了,應(yīng)該能看懂并且可以自己配置了。對于串口的四種模式,模式1是最常用的,就是我們前邊提到的1位起始位,8位數(shù)據(jù)位和1位結(jié)束位。因?yàn)槲覀兊慕坛滩煌诮炭茣灰械墓δ芏家灰唤榻B,我們只介紹實(shí)用的技術(shù),所以其他3種模式,真正遇到需要使用的時(shí)候大家再去查資料就行。
在我們使用IO口模擬串口通信的時(shí)候,我們串口的波特率是使用定時(shí)器0的中斷體現(xiàn)出來的。在實(shí)際串口模塊中,有一個專門的波特率發(fā)生器用來控制發(fā)送數(shù)據(jù)的速度和讀取接收數(shù)據(jù)的速度。對于STC89C52RC單片機(jī)來講,這個波特率發(fā)生器只能由定時(shí)器1或定時(shí)器2產(chǎn)生,而不能由定時(shí)器0產(chǎn)生,這和我們模擬的通信是完全不同的概念。
如果用定時(shí)器2,需要配置額外的寄存器,默認(rèn)是使用定時(shí)器1的,我們本章內(nèi)容主要是使用定時(shí)器1作為波特率發(fā)生器來講解,方式1下的波特率發(fā)生器必須使用定時(shí)器1的模式2,也就是自動重裝載模式,定時(shí)器的初值具體的計(jì)算公式是:
TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率
和波特率有關(guān)的還有一個寄存器,是一個電源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果寫PCON |=0x80以后,計(jì)算公式就成了
TH1 = TL1 = 256 - 晶振值/12 /16 /波特率
數(shù)字的含義這里解釋一下,256是8位數(shù)據(jù)的溢出值,也就是TL1的溢出值,11059200就是我們板子上單片機(jī)的晶振,12是說1個機(jī)器周期是12個時(shí)鐘周期,值得關(guān)注的是這個16,重點(diǎn)說明。我們在IO口模擬串口通信接收數(shù)據(jù)的時(shí)候,我們采集的是這一位數(shù)據(jù)的中間位置,而實(shí)際上串口模塊比我們模擬的要復(fù)雜和精確一些。他采取的方式是把一位信號采集16次,其中第7、8、9次取出來,這三次中其中兩次如果是高電平,那么就認(rèn)定這一位數(shù)據(jù)是1,如果兩次是低電平,那么就認(rèn)定這一位是0,這樣一旦受到意外干擾讀錯一次數(shù)據(jù),也依然可以保證最終數(shù)據(jù)的正確性。
了解了串口采集模式,在這里要給大家留一個思考題�!熬д裰�/12/2/16/波特率”這個地方計(jì)算的時(shí)候,出現(xiàn)不能除盡,或者出現(xiàn)小數(shù)怎么辦,允許出現(xiàn)多大的偏差?把這部分理解了,也就理解了我們的晶振為何使用11.0592M了。
串口通信的發(fā)送和接收電路,我們主要了解一下他們在物理上有2個名字相同的SBUF寄存器,他們的地址也都是99H,但是一個用來做發(fā)送緩沖,一個用來做接收緩沖。意思就是說,有2個房間,兩個房間的門牌號是一樣的,其中一個只出人不進(jìn)人,另外一個只進(jìn)人不出人,這樣的話,我們就可以實(shí)現(xiàn)UART的全雙工通信,相互之間不會產(chǎn)生干擾。但是在邏輯上呢,我們每次只操作SBUF,單片機(jī)會自動根據(jù)對它執(zhí)行的是“讀”還是“寫”操作來選擇是接收SBUF還是發(fā)送SBUF,后邊通過程序,我們就會徹底了解這個問題。
11.5.3 UART串口程序一般情況下,我們編寫串口通信程序的基本步驟如下所示:
1、配置串口為模式1。
2、配置定時(shí)器T1為模式2,即自動重裝模式。
3、確定波特率大小,計(jì)算定時(shí)器TH1和TL1的初值,如果有需要可以使用PCON進(jìn)行波特率加倍。
4、打開定時(shí)器控制寄存器TR1,讓定時(shí)器跑起來。
這個地方還要特別注意一下,就是在使用T1做波特率發(fā)生器的時(shí)候,千萬不要再使能T1的中斷了。
我們先來看一下由IO口模擬串口通信直接改為使用硬件UART模塊時(shí)程序代碼,看看程序是不是簡單了很多,因?yàn)榇蟛糠值墓ぷ饔布K都替我們做了。程序功能和IO口模擬的是完全一樣的。
#include <reg52.h>
void ConfigUART(unsigned int baud);
void main ()
{
ConfigUART(9600); //配置波特率為9600
while(1)
{
while (!RI); //等待接收完成
RI = 0; //清零接收中斷標(biāo)志位
SBUF = SBUF + 1; //接收到的數(shù)據(jù)+1后,發(fā)送回去;
//等號左邊的SBUF實(shí)際上就是發(fā)送SBUF,因?yàn)閷λ牟僮魇恰皩憽保?/font>
//等號右邊的是接收SBUF,因?yàn)閷λ牟僮魇恰白x”。
while (!TI); //等待發(fā)送完成
TI = 0; //清零發(fā)送中斷標(biāo)志位
}
}
void ConfigUART(unsigned int baud) //串口配置函數(shù),baud為波特率
{
SCON = 0x50; //配置串口為模式1
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x20; //配置T1為模式2
TH1 = 256 - (11059200/12/32) / baud; //計(jì)算T1重載值
TL1 = TH1; //初值等于重載值
ET1 = 0; //禁止T1中斷
TR1 = 1; //啟動T1
}
當(dāng)然了,這個程序還是在主循環(huán)里等待接收中斷標(biāo)志位和發(fā)送中斷標(biāo)志位的方法來編寫的,而實(shí)際工程開發(fā)中,當(dāng)然就不能這么干了,所以就用到了串口中斷,來看一下程序。
#include <reg52.h>
void ConfigUART(unsigned int baud);
void main ()
{
ConfigUART(9600); //配置波特率為9600
while(1);
}
void ConfigUART(unsigned int baud) //串口配置函數(shù),baud為波特率
{
SCON = 0x50; //配置串口為模式1
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x20; //配置T1為模式2
TH1 = 256 - (11059200/12/32) / baud; //計(jì)算T1重載值
TL1 = TH1; //初值等于重載值
ET1 = 0; //禁止T1中斷
TR1 = 1; //啟動T1
ES = 1; //打開串口中斷
EA = 1; //打開總中斷
}
void InterruptUART() interrupt 4
{
if (RI) //接收到字節(jié)
{
RI = 0; //手動清零接收中斷標(biāo)志位
SBUF = SBUF + 1;//接收數(shù)據(jù)+1發(fā)回去,左邊為發(fā)送SBUF,右邊為接收SBUF。
}
if (TI) //字節(jié)發(fā)送完畢
{
TI = 0; //手動清零發(fā)送中斷標(biāo)志位
}
}
大家可以試驗(yàn)一下試試,看看是不是和前邊用IO口模擬通信實(shí)現(xiàn)的效果一致,而主循環(huán)卻完全空出來了,我們就可以隨意添加其它功能代碼進(jìn)去。
11.6 字符和數(shù)據(jù)之間的轉(zhuǎn)換我們學(xué)串口通信的應(yīng)用主要是實(shí)現(xiàn)單片機(jī)和電腦之間的信息互發(fā),可以用電腦控制單片機(jī)的一些信息,可以把單片機(jī)的一些信息狀況發(fā)給電腦上的軟件。下面我們就做一個簡單的例程,實(shí)現(xiàn)單片機(jī)串口調(diào)試助手發(fā)送的數(shù)據(jù),在我們開發(fā)板上的數(shù)碼管上顯示出來。
#include <reg52.h>
sbit ADDR3 = P1^3; //LED選擇地址線3
sbit ENLED = P1^4; //LED總使能引腳
unsigned char code LedChar[] = { //數(shù)碼管顯示字符轉(zhuǎn)換表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //數(shù)碼管
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char T0RH = 0; //T0重載值的高字節(jié)
unsigned char T0RL = 0; //T0重載值的低字節(jié)
unsigned char RxdByte = 0; //串口接收到的字節(jié)
void ConfigTimer0(unsigned int ms);
void ConfigUART(unsigned int baud);
void main ()
{
P0 = 0xFF; //P0口初始化
ADDR3 = 1; //選擇數(shù)碼管
ENLED = 0; //LED總使能
EA = 1; //開總中斷
ConfigTimer0(1); //配置T0定時(shí)1ms
ConfigUART(9600); //配置波特率為9600
while(1)
{ //將接收字節(jié)在數(shù)碼管上以十六進(jìn)制形式顯示出來
LedBuff[0] = LedChar[RxdByte & 0x0F];
LedBuff[1] = LedChar[RxdByte >> 4];
}
}
void ConfigTimer0(unsigned int ms) //T0配置函數(shù)
{
unsigned long tmp;
tmp = 11059200 / 12; //定時(shí)器計(jì)數(shù)頻率
tmp = (tmp * ms) / 1000; //計(jì)算所需的計(jì)數(shù)值
tmp = 65536 - tmp; //計(jì)算定時(shí)器重載值
tmp = tmp + 31; //修正中斷響應(yīng)延時(shí)造成的誤差
T0RH = (unsigned char)(tmp >> 8); //定時(shí)器重載值拆分為高低字節(jié)
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0為模式1
TH0 = T0RH; //加載T0重載值
TL0 = T0RL;
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動T0
}
void ConfigUART(unsigned int baud) //串口配置函數(shù),baud為波特率
{
SCON = 0x50; //配置串口為模式1
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x20; //配置T1為模式2
TH1 = 256 - (11059200/12/32) / baud; //計(jì)算T1重載值
TL1 = TH1; //初值等于重載值
ET1 = 0; //禁止T1中斷
ES = 1; //使能串口中斷
TR1 = 1; //啟動T1
}
void LedScan() //LED顯示掃描函數(shù)
{
static unsigned char index = 0;
P0 = 0xFF; //關(guān)閉所有段選位,顯示消隱
P1 = (P1 & 0xF8) | index; //位選索引值賦值到P1口低3位
P0 = LedBuff[index]; //相應(yīng)顯示緩沖區(qū)的值賦值到P0口
if (index < 5) //位選索引0-5循環(huán),因有6個數(shù)碼管
index++;
else
index = 0;
}
void InterruptTimer0() interrupt 1 //T0中斷服務(wù)函數(shù)
{
TH0 = T0RH; //定時(shí)器重新加載重載值
TL0 = T0RL;
LedScan(); //LED掃描顯示
}
void InterruptUART() interrupt 4
{
if (RI) //接收到字節(jié)
{
RI = 0; //手動清零接收中斷標(biāo)志位
RxdByte = SBUF; //接收到的數(shù)據(jù)保存到接收字節(jié)變量中
SBUF = RxdByte; //接收到的數(shù)據(jù)又直接發(fā)回,這叫回顯-"echo",以提示用戶輸入的信息是否已正確接收
}
if (TI) //字節(jié)發(fā)送完畢
{
TI = 0; //手動清零發(fā)送中斷標(biāo)志位
}
}
大家在做這個實(shí)驗(yàn)的時(shí)候,有個小問題要注意一下。因?yàn)槲覀?font face="Times New Roman">STC89C52RC下載程序是使用了UART串口下載,下載完程序后,程序運(yùn)行起來了,可是下載軟件最后還會通過串口發(fā)送一些額外的數(shù)據(jù),所以程序剛下載進(jìn)去不是顯示00,而可能是其他數(shù)據(jù)。大家只要把開關(guān)關(guān)閉,重新打開一次就好了。
細(xì)心的同學(xué)可能會發(fā)現(xiàn),在串口調(diào)試助手發(fā)送選項(xiàng)和接收選項(xiàng)處,還有個“字符格式發(fā)送”和“字符格式顯示”,這是什么意思呢?
先拋開我們使用的漢字不談,那么我們常用的字符就包含了0~9的數(shù)字、A~Z/a~z的字母、還有各種標(biāo)點(diǎn)符號等。那么在單片機(jī)系統(tǒng)里面我們怎么來表示它們呢?ASCII碼(American Standard Code for Information Interchange,即美國信息互換標(biāo)準(zhǔn)代碼)可以完成這個使命:我們知道,在單片機(jī)中一個字節(jié)的數(shù)據(jù)可以有0~255共256個值,我們?nèi)∑渲械?/font>0~127共128個值賦予了它另外一層涵義,即讓它們分別來代表一個常用字符,其具體的對應(yīng)關(guān)系如下表。
表11-3 ASCII表
這樣我們就在常用字符和字節(jié)數(shù)據(jù)之間建立了一一對應(yīng)的關(guān)系,那么現(xiàn)在一個字節(jié)就既可以代表一個整數(shù)又可以代表一個字符了,但它本質(zhì)上只是一個字節(jié)的數(shù)據(jù),而我們賦予了它不同的涵義,什么時(shí)候賦予它那種涵義就看編程者的意圖了。ASCII碼在單片機(jī)系統(tǒng)中應(yīng)用非常廣泛,我們后續(xù)的課程也會經(jīng)常使用到它,下面我們來對它做一個直觀的認(rèn)識,同學(xué)們一定要深刻理解其本質(zhì)。
對照上述表格,我們就可以實(shí)現(xiàn)字符和數(shù)字之間的轉(zhuǎn)換了,比如還是這個程序,我們發(fā)送的時(shí)候改成字符格式發(fā)送,接收還是用十六進(jìn)制接收,這樣接收和數(shù)碼管好做一下對比。
我們用字符格式發(fā)送一個小寫的a,返回一個十六進(jìn)制的0x61,數(shù)碼管上顯示的也是61,ASCII碼表里字符a對應(yīng)十進(jìn)制是97,等于十六進(jìn)制的0x61;我們再用字符格式發(fā)送一個數(shù)字1,返回一個十六進(jìn)制的0x31,數(shù)碼管上顯示的也是31,ASCII表里字符1對應(yīng)的十進(jìn)制是49,等于十六進(jìn)制的0x31。這下大家就該清楚了:所謂的十六進(jìn)制發(fā)送和十六進(jìn)制接收,都是按字節(jié)數(shù)據(jù)的真實(shí)值進(jìn)行的;而字符格式發(fā)送和字符格式接收,是按ASCII碼表中字符形式進(jìn)行的,但它實(shí)際上最終傳輸?shù)倪€是一個字節(jié)數(shù)據(jù)。這個表格,當(dāng)然不需要大家去記住,理解它,用的時(shí)候過來查就行了。
通信的學(xué)習(xí),不像前邊控制部分那么直觀了,通信部分我們的程序只能獲得一個結(jié)果,而其過程我們卻無法直接看到,所以慢慢的可能大家就會知道有示波器和邏輯分析儀這類測量儀器。如果學(xué)校實(shí)驗(yàn)室或者公司里有示波器或者邏輯分析儀這類儀器,可以拿過來抓一下串口波形,直觀的了解一下。如果暫時(shí)還沒有這些儀器,先知道這么回事,有條件再說。因?yàn)楣ぞ哳惖臇|西有的比較昂貴,有條件可以盡量使用學(xué)校或者公司的。在這里我用一款簡易的邏輯分析儀把串口通信的波形抓出來給大家看一下,大家了解一下即可,如圖11-7所示。
8.JPG (44.15 KB, 下載次數(shù): 350)
下載附件
2013-9-28 14:31 上傳
9.JPG (9.21 KB, 下載次數(shù): 282)
下載附件
2013-9-28 14:31 上傳
圖11-7 邏輯分析儀串口數(shù)據(jù)示意圖
分析儀和示波器的作用,就是把通信過程的波形抓出來進(jìn)行分析。先大概說一下波形的意思。波形左邊是低位,右邊是高位,上邊這個波形是電腦發(fā)送給單片機(jī)的,下邊這個波形是單片機(jī)回發(fā)給電腦的。以上邊的波形為例,左邊第一位是起始位0,從低位到高位依次是10001100,順序倒一下,就是數(shù)據(jù)0x31,也就是ASCII碼表里的‘1’。大家可以注意到分析儀在每個數(shù)據(jù)位都給標(biāo)了一個白色的點(diǎn),表示是數(shù)據(jù),起始位和無數(shù)據(jù)的時(shí)候都沒有這個白點(diǎn)。時(shí)間標(biāo)T1和T2的差值在右邊顯示出來是0.102ms,大概是9600分之一,稍微有點(diǎn)偏差,在容許范圍內(nèi)即可。通過圖11-7,我們可以清晰的了解了串口通信的收發(fā)的詳細(xì)過程。
那我們這里再來了解一下,如果我們使用串口調(diào)試助手,用字符格式直接發(fā)送一個“12”,我們在我們的數(shù)碼管上應(yīng)該顯示什么呢?串口調(diào)試助手應(yīng)該返回什么呢?經(jīng)過試驗(yàn)發(fā)現(xiàn),我們數(shù)碼管顯示的是32,而串口調(diào)試助手返回十六進(jìn)制顯示的是31、32兩個數(shù)據(jù),如圖11-8所示。
10.JPG (48.28 KB, 下載次數(shù): 330)
下載附件
2013-9-28 14:33 上傳
圖11-8 串口調(diào)試助手?jǐn)?shù)據(jù)顯示
我們用邏輯分析儀把這個數(shù)據(jù)抓出來看一下,如圖11-9所示。
11.JPG (35.58 KB, 下載次數(shù): 311)
下載附件
2013-9-28 14:33 上傳
圖11-9 邏輯分析儀抓取數(shù)據(jù)
對于ASCII碼表來說,數(shù)字本身是字符而非數(shù)據(jù),所以如果發(fā)送“12”的話,實(shí)際上是是分別發(fā)送了“1”和“2”兩個字符,單片機(jī)呢,先收到第一個字符“1”,在數(shù)碼管上會顯示出31這個對應(yīng)數(shù)字,但是瞬間馬上就又收到了“2”這個字符,數(shù)碼管瞬間從31變成了32,而我們視覺上呢,根本是沒有辦法發(fā)現(xiàn)這種快速變化的,所以我們感覺數(shù)碼管直接顯示的是32。
11.7 作業(yè)1、能夠理解UART串口通信的基本原理和通信過程。
2、通過IO口模擬UART串口通信把通信的底層操作原理弄明白。
3、學(xué)會通過配置寄存器,實(shí)現(xiàn)串口通信的基本操作過程。
4、了解字符和數(shù)據(jù)之間的轉(zhuǎn)換依據(jù)和方法。
5、完成通過串口控制流水燈流動和停止的程序。
6、完成通過串口實(shí)現(xiàn)蜂鳴器響的程序。
作者: 氯化鈉 時(shí)間: 2013-10-8 21:32
分享給同行
作者: yuanjp9 時(shí)間: 2014-2-18 05:42
閱讀了,謝謝分享!
作者: 雪中梅花 時(shí)間: 2014-7-20 20:36
謝謝老師拜讀了,有匯編語言的實(shí)驗(yàn)程序嗎?
作者: sundh1981 時(shí)間: 2014-8-25 07:55
講的不錯,非常詳細(xì)!
作者: yanhongqiuhe 時(shí)間: 2015-1-16 21:41
寫的真好
作者: yesonjob 時(shí)間: 2015-4-23 13:41
攻克完紅外,有必要學(xué)習(xí)通信了。
作者: hawkins 時(shí)間: 2016-6-7 23:27
寫的真好啊 好好學(xué)習(xí)一下。
作者: changhe203 時(shí)間: 2016-6-12 10:42
真是太好的貼子了,受教了
作者: 流星雨掉到地球 時(shí)間: 2016-11-11 16:09
學(xué)習(xí)了,謝謝
作者: 流星雨掉到地球 時(shí)間: 2016-11-26 18:25
很好,正學(xué)這個呢,
作者: t90s 時(shí)間: 2017-2-22 15:14
雖然 用過硬件UART不過還是皮毛 僅是會用簡單的單字節(jié)發(fā)送、接收!看了樓主的分享受教了,謝謝!
作者: t90s 時(shí)間: 2017-2-22 15:19
也算是用過UART的人,不過簡單應(yīng)用,最近想寫個自定義通訊協(xié)議,到處找靈感,在樓主這受教了,對于我這樣的菜鳥很受用�。�


作者: tlone51hei 時(shí)間: 2017-3-1 11:13
真的很淺顯易懂,謝謝分享。
作者: hi123451 時(shí)間: 2017-5-4 17:05
不錯,真是不太了解UART
作者: wohhhde 時(shí)間: 2017-10-15 13:09
贊一個!
作者: reveal 時(shí)間: 2017-11-25 21:21
有一個問題 if (PIN_RXD) //接收腳為1時(shí),緩沖器最高位置1;為0時(shí)不處理即仍保持移位后的0 { RxdBuf |= 0x80; }、
作者: reveal 時(shí)間: 2017-11-25 21:24
if (PIN_RXD) //接收腳為1時(shí),緩沖器最高位置1;為0時(shí)不處理即仍保持移位后的0 { RxdBuf |= 0x80; }叢機(jī)在接受數(shù)據(jù)時(shí)不都是低電平么 為什么這里是高電平
作者: a1097994700 時(shí)間: 2017-11-27 11:53
詳細(xì),好懂,深入淺出!
作者: caoyexin 時(shí)間: 2018-1-8 17:08
早點(diǎn)來學(xué)習(xí)就好了
作者: 機(jī)智的愚人 時(shí)間: 2018-3-26 18:42
學(xué)習(xí)學(xué)習(xí)
作者: suph007 時(shí)間: 2018-4-22 22:26
這個帖子真的很不錯,正在學(xué)習(xí)這個知識點(diǎn)
作者: keneng 時(shí)間: 2018-7-12 08:32
學(xué)習(xí)之前,先點(diǎn)個贊
作者: Chiang 時(shí)間: 2018-8-9 09:45
大佬,您好!請問一下您講的這個章節(jié)出自哪本書��?在哪里可以買到呢?謝謝!
作者: yasi666 時(shí)間: 2019-1-23 14:30
真的很詳細(xì)呀
作者: 林生222 時(shí)間: 2019-7-3 11:19
學(xué)習(xí)了。。。
作者: 鵬博士PBs 時(shí)間: 2019-7-3 11:52
不錯不錯感謝分享
歡迎光臨 (http://www.torrancerestoration.com/bbs/) |
Powered by Discuz! X3.1 |