通信按照傳統(tǒng)的理解就是信息的傳輸與交換。對(duì)于單片機(jī)來(lái)說(shuō),通信則與傳感器、存儲(chǔ)芯片、外圍控制芯片等技術(shù)緊密結(jié)合,成為整個(gè)單片機(jī)系統(tǒng)的“神經(jīng)中樞”。沒(méi)有通信,單片機(jī)所實(shí)現(xiàn)的功能僅僅局限于單片機(jī)本身,就無(wú)法通過(guò)其他設(shè)備獲得有用信息,也無(wú)法將自己產(chǎn)生的信息告訴其它設(shè)備。如果單片機(jī)通信沒(méi)處理好的話,它和外圍器件的合作程度就受到限制,最終整個(gè)系統(tǒng)也無(wú)法完成強(qiáng)大的功能,由此可見(jiàn)單片機(jī)通信技術(shù)的重要性。 UART(Universal Asynchronous Receiver/Transmitter,即通用異步收發(fā)器)串行通信是單片機(jī)最常用的一種通信技術(shù),通常用于單片機(jī)和電腦之間以及單片機(jī)和單片機(jī)之間的通信。 11.1 串行通信的初步認(rèn)識(shí)通信按照基本類型可以分為并行通信和串行通信。并行通信時(shí)數(shù)據(jù)的各個(gè)位同時(shí)傳送,可以實(shí)現(xiàn)字節(jié)為單位通信,但是因?yàn)橥ㄐ啪多占用資源多,成本高。比如我們前邊用到的P0 = 0xfe;一次給P0的8個(gè)IO口分別賦值,同時(shí)進(jìn)行信號(hào)輸出,類似于有8個(gè)車道同時(shí)可以過(guò)去8輛車一樣,這種形式就是并行的,我們習(xí)慣上還稱P0、P1、P2和P3為51單片機(jī)的4組并行總線。 而串行通信,就如同一條車道,一次只能一輛車過(guò)去,如果一個(gè)0xfe這樣一個(gè)字節(jié)的數(shù)據(jù)要傳輸過(guò)去的話,假如低位在前高位在后,那發(fā)送方式就是0-1-1-1-1-1-1-1-1,一位一位的發(fā)送出去的,要發(fā)送8次才能發(fā)送完一個(gè)字節(jié)。 在我們的STC89C52上,有兩個(gè)引腳,是專門(mén)用來(lái)做UART串口通信的,一個(gè)是P3.0一個(gè)是P3.1,還分別有另外的名字叫做RXD和TXD,這兩個(gè)引腳是專門(mén)用來(lái)進(jìn)行UART通信的,如果我們兩個(gè)單片機(jī)進(jìn)行UART串口通信的話,那基本的演示圖如圖11-1所示。 圖11-1 單片機(jī)之間UART通信示意圖 圖中,GND表示單片機(jī)系統(tǒng)電源的參考地,TXD是串行發(fā)送引腳,RXD是串行接收引腳。兩個(gè)單片機(jī)之間要通信,首先電源基準(zhǔn)得一樣,所以我們要把兩個(gè)單片機(jī)的GND相互連起來(lái),然后單片機(jī)1的TXD引腳接到單片機(jī)2的RXD引腳上,即此路為單片機(jī)1發(fā)送而單片機(jī)2接收的通道,單片機(jī)1的RXD引腳接到單片機(jī)2的TXD引腳上,即此路為單片機(jī)2發(fā)送而單片機(jī)2接收的通道。這個(gè)示意圖就體現(xiàn)了兩個(gè)單片機(jī)各自收發(fā)信息的過(guò)程。 當(dāng)單片機(jī)1想給單片機(jī)2發(fā)送數(shù)據(jù)時(shí),比如發(fā)送一個(gè)0xE4這個(gè)數(shù)據(jù),用二進(jìn)制形式表示就是0b11100100,在UART通信過(guò)程中,是低位先發(fā),高位后發(fā)的原則,那么就讓TXD首先拉低電平,持續(xù)一段時(shí)間,發(fā)送一位0,然后繼續(xù)拉低,再持續(xù)一段時(shí)間,又發(fā)送了一位0,然后拉高電平,持續(xù)一段時(shí)間,發(fā)了一位1......一直到把8位二進(jìn)制數(shù)字0b11100100全部發(fā)送完畢。這里就牽扯到了一個(gè)問(wèn)題,就是持續(xù)的這“一段時(shí)間”到底是多久?從這里引入我們通信中的另外重要概念——波特率,也叫做比特率。 波特率就是發(fā)送一位二進(jìn)制數(shù)據(jù)的速率,習(xí)慣上用baud表示,即我們發(fā)送一位數(shù)據(jù)的持續(xù)時(shí)間=1/baud。在通信之前,單片機(jī)1和單片機(jī)2首先都要明確的約定好他們之間的通信波特率,必須保持一致,收發(fā)雙方才能正常實(shí)現(xiàn)通信,這一點(diǎn)大家一定要記清楚。 約定好速度后,我們還要考慮第二個(gè)問(wèn)題,數(shù)據(jù)什么時(shí)候是起始,什么時(shí)候是結(jié)束呢?不管是提前接收還是延遲接收,數(shù)據(jù)都會(huì)接收錯(cuò)誤。在UART串行通信的時(shí)候,一個(gè)字節(jié)是8位,規(guī)定當(dāng)沒(méi)有通信信號(hào)發(fā)生時(shí),通信線路保持高電平,當(dāng)要發(fā)送數(shù)據(jù)之前,先發(fā)一位0表示起始位,然后發(fā)送8位數(shù)據(jù)位,數(shù)據(jù)位是先低后高的順序,數(shù)據(jù)位發(fā)完后再發(fā)一位1表示停止位。這樣本來(lái)要發(fā)送一個(gè)字節(jié)8位數(shù)據(jù),而實(shí)際上我們一共發(fā)送了10位,多出來(lái)的兩位其中一位起始位,一位停止位。而接收方呢,原本一直保持的高電平,一旦檢測(cè)到來(lái)了一位低電平,那就知道了要開(kāi)始準(zhǔn)備接收數(shù)據(jù)了,接收到8位數(shù)據(jù)位后,然后檢測(cè)到停止位,再準(zhǔn)備下一個(gè)數(shù)據(jù)的接收了。我們圖示看一下,如圖11-2所示。 圖11-2 串口數(shù)據(jù)發(fā)送示意圖 像我們的圖11-2串口數(shù)據(jù)發(fā)送示意圖,實(shí)際上是一個(gè)時(shí)域示意圖,就是信號(hào)隨著時(shí)間變化的對(duì)應(yīng)關(guān)系。比如在單片機(jī)的發(fā)送引腳上,左邊的是先發(fā)生的,右邊的是后發(fā)生的,數(shù)據(jù)位的切換時(shí)間就是波特率分之一秒,如果能夠理解時(shí)域的概念,后邊很多通信的時(shí)序圖就很容易理解了。 11.2 串行RS232通信接口在我們的臺(tái)式電腦上,有一個(gè)9針的串行接口,這個(gè)串行接口叫做RS232接口,它和UART通信有關(guān)聯(lián),但是由于現(xiàn)在筆記本電腦都不帶這種9針串口了,所以和單片機(jī)通信越來(lái)越趨向于使用USB虛擬的串口和單片機(jī)通信,因此這一節(jié)的內(nèi)容作為了解內(nèi)容,大家知道有這么回事就行。 我們先來(lái)認(rèn)識(shí)一下這個(gè)標(biāo)準(zhǔn)串口,串口分為9針的和9孔的,習(xí)慣上我們也稱之為公頭和母頭,如圖11-3所示。 圖11-3 RS232通信接口 RS232接口一共有9個(gè)引腳,分別定義是:1、載波檢測(cè)(DCD);2、接收數(shù)據(jù)(RXD);3、發(fā)送數(shù)據(jù)(TXD);4、數(shù)據(jù)終端準(zhǔn)備好(DTR);5、信號(hào)地線(SG);6、數(shù)據(jù)準(zhǔn)備好(DSR);7、請(qǐng)求發(fā)送(RTS);8、清除發(fā)送(CTS);9、振鈴提示(RI)。我們要讓這個(gè)串口和我們單片機(jī)進(jìn)行通信,我們只需要關(guān)心其中的2腳(RXD),3腳(TXD)和5腳(GND)。 雖然這三個(gè)腳的名字和我們單片機(jī)上的串口名字一樣,但是卻不能直接和單片機(jī)對(duì)連直接通信,這是為什么呢?隨著我們了解的內(nèi)容越來(lái)越多,我們得慢慢知道,不是所有的電路都是5V代表高電平而0V代表低電平的。對(duì)于RS232標(biāo)準(zhǔn)來(lái)說(shuō),它是個(gè)反邏輯,也叫做負(fù)邏輯。為何叫負(fù)邏輯?它的TXD和RXD的電壓,-3V到-15V代表是1,3-15V之間的電壓代表是0。低電平代表的是1,而高電平代表的是0,所以稱之為負(fù)邏輯。因此電腦的9針232串口是不能和單片機(jī)直接連接的,需要用一個(gè)轉(zhuǎn)換芯片MAX232來(lái)完成,如圖11-4所示。 圖11-4 MAX232轉(zhuǎn)接圖 這個(gè)芯片就可以實(shí)現(xiàn)把標(biāo)準(zhǔn)RS232串口電平轉(zhuǎn)換成我們單片機(jī)能夠識(shí)別和承受的UART 0V/5V電平標(biāo)準(zhǔn)。從這里大家似乎慢慢有點(diǎn)明白了,其實(shí)RS232串口和UART串口,他們的協(xié)議類型是一樣,只是電平不同而已,而MAX232這個(gè)芯片起到的就是中間人的作用,他把UART電平轉(zhuǎn)換成RS232電平,也把RS232電平轉(zhuǎn)換成UART電平,從而實(shí)現(xiàn)標(biāo)準(zhǔn)RS232接口和單片機(jī)UART之間的通信連接。 11.3 USB轉(zhuǎn)串口通信隨著技術(shù)的發(fā)展,工業(yè)上還有RS232串口通信的大量使用,但是商業(yè)技術(shù)的應(yīng)用上,已經(jīng)慢慢的使用USB轉(zhuǎn)UART技術(shù)取代了RS232串口,絕大多數(shù)筆記本電腦已經(jīng)沒(méi)有串口這個(gè)東西了,那我們要實(shí)現(xiàn)單片機(jī)和電腦之間的通信該如何辦呢? 我們只需要在我們電路上添加一個(gè)USB轉(zhuǎn)串口芯片,就可以成功實(shí)現(xiàn)USB通信協(xié)議和標(biāo)準(zhǔn)UART串行通信協(xié)議的轉(zhuǎn)換,在我們的開(kāi)發(fā)板上,我們使用的是CH340T這個(gè)芯片,如圖11-5所示。 圖11-5 USB轉(zhuǎn)串口電路 左側(cè)J2是一組跳線的組合,大家可以在我們板子左下角的跳線位置找到,我們是把3腳和5腳、4腳和6腳通過(guò)跳線帽短接到一起。右側(cè)的CH340T這個(gè)電路很簡(jiǎn)單,把電源電路,晶振電路接好后,6腳和7腳的DP和DM分別接USB口的2個(gè)數(shù)據(jù)引腳上去,3腳和4腳通過(guò)跳線接到了我們單片機(jī)的TXD和RXD上去。 CH340T的電路里3腳位置加了個(gè)4148的二極管,是一個(gè)小技巧。因?yàn)槲覀兊?/font>STC89C52RC這個(gè)單片機(jī)下載程序需要冷啟動(dòng),就是先點(diǎn)下載后上電,上電瞬間單片機(jī)會(huì)先檢測(cè)需要不需要下載程序。雖然單片機(jī)的VCC是由開(kāi)關(guān)來(lái)控制,但是由于CH340T的3腳是輸出引腳,如果沒(méi)有此二極管,開(kāi)關(guān)后級(jí)單片機(jī)在斷電的情況下,CH340T的3腳和單片機(jī)的P3.0(即RXD)引腳連在一起,有電流會(huì)通過(guò)這個(gè)引腳流入后級(jí)電路并且給后級(jí)的電容充電,造成后級(jí)有一定幅度的電壓,這個(gè)電壓值雖然只有兩三伏左右,但是可能會(huì)影響到我們的冷啟動(dòng)。加了二極管后,一方面不影響通信,另外一個(gè)方面還可以消除這種問(wèn)題。這個(gè)地方可以暫時(shí)作為了解,大家如果自己做這塊電路,可以參考一下。 11.4 IO口模擬UART串口通信為了讓大家充分理解UART串口通信的原理,我們先用P3.0和P3.1這兩個(gè)當(dāng)做IO口來(lái)進(jìn)行模擬實(shí)際串口通信的過(guò)程,原理搞懂后,我們?cè)偈褂眉拇嫫髋渲脤?shí)現(xiàn)串口通信過(guò)程。 對(duì)于 UART串口波特率,常用的值是300、600、1200、2400、4800、9600、14400、19200、28800、38400、57600、115200、128000、256000等速率。IO口模擬UART串行通信程序是一個(gè)簡(jiǎn)單的演示程序,我們使用串口調(diào)試助手下發(fā)一個(gè)數(shù)據(jù),數(shù)據(jù)加1后,再自動(dòng)返回。串口調(diào)試助手,在我們進(jìn)行全板子測(cè)試視頻的時(shí)候,大家已經(jīng)見(jiàn)過(guò),這里我們直接使用STC-ISP軟件自帶的串口調(diào)試助手,可到m.zg4o1577.cn去下載此程序,先把串口調(diào)試助手使用給大家說(shuō)一下,如圖11-6所示。第一步要選擇串口助手菜單,第二步選擇十六進(jìn)制顯示,第三步選擇十六進(jìn)制發(fā)送,第四步選擇COM口,這個(gè)COM口要和自己電腦設(shè)備管理器里的那個(gè)COM口一致,波特率是我們程序設(shè)定好的選擇,我們程序中讓一個(gè)數(shù)據(jù)位持續(xù)時(shí)間是1/9600秒,那這個(gè)地方選擇波特率就是選9600,校驗(yàn)位選N,數(shù)據(jù)位8,停止位1。圖11-6 串口調(diào)試助手示意圖 串口調(diào)試助手的實(shí)質(zhì)就是我們利用電腦上的UART通信接口,通過(guò)這個(gè)UART接口發(fā)送數(shù)據(jù)給我們的單片機(jī),也可以把我們的單片機(jī)發(fā)送的數(shù)據(jù)接收到這個(gè)調(diào)試助手界面上。 因?yàn)槌醮谓佑|通信方面的技術(shù),所以我對(duì)這個(gè)程序進(jìn)行一下解釋,大家可以邊看我的解釋邊看程序,把底層原理先徹底弄懂。 變量定義部分就不用說(shuō)了,直接看main主函數(shù)。首先是對(duì)通信的波特率的設(shè)定,在這里我們配置的波特率是9600,那么串口調(diào)試助手也得是9600。配置波特率的時(shí)候,我們用的是定時(shí)器0的模式2。模式2中,不再是TH0代表高8位,TL0代表低8位了,而只有TL0在進(jìn)行計(jì)數(shù)了。當(dāng)TL0溢出后,不僅僅會(huì)讓TF0變1,而且還會(huì)將TH0中的內(nèi)容重新自動(dòng)裝到TL0中。這樣有一個(gè)好處,我們可以把我們想要的定時(shí)器初值提前存在TH0中,當(dāng)TL0溢出后,TH0自動(dòng)把初值就重新送入TL0了,全自動(dòng)的,不需要程序上再給TL0重新賦值了,配置方式很簡(jiǎn)單,大家可以自己看下程序并且計(jì)算一下初值。 波特率設(shè)置好以后,打開(kāi)中斷,然后等待接收串口調(diào)試助手下發(fā)的數(shù)據(jù)。接收數(shù)據(jù)的時(shí)候,首先要進(jìn)行低電平檢測(cè) while (PIN_RXD),若沒(méi)有低電平則說(shuō)明沒(méi)有數(shù)據(jù),一旦檢測(cè)到低電平,就進(jìn)入啟動(dòng)接收函數(shù)StartRXD()。接收函數(shù)最開(kāi)始啟動(dòng)半個(gè)波特率周期,初學(xué)可能這里不是很明白。大家回頭看一下我們的圖11-2里邊的串口數(shù)據(jù)示意圖,信號(hào)在數(shù)據(jù)位電平變化的時(shí)候去讀,因?yàn)闀r(shí)序上的誤差以及信號(hào)穩(wěn)定性的問(wèn)題很容易讀錯(cuò)數(shù)據(jù),所以我們希望在信號(hào)最穩(wěn)定的時(shí)候去讀數(shù)據(jù)。除了信號(hào)變化的那個(gè)沿的位置外,其他位置都很穩(wěn)定,那么我們現(xiàn)在就約定在信號(hào)中間位置去讀取電平狀態(tài),這樣能夠保證我們信號(hào)讀的是對(duì)的。 一旦讀到了起始信號(hào),我們就把當(dāng)前狀態(tài)設(shè)定成接受狀態(tài),并且打開(kāi)定時(shí)器中斷,第一次是半個(gè)周期進(jìn)入中斷后,對(duì)起始位進(jìn)行二次判斷一下,確認(rèn)一下起始位是低電平,而不是一個(gè)干擾信號(hào)。以后每經(jīng)過(guò)9600分之一秒進(jìn)入一次中斷,并且把這個(gè)引腳的狀態(tài)讀到RxdBuf里邊。等待接收完畢之后,我們?cè)侔堰@個(gè)RxdBuf加1,再通過(guò)TXD引腳發(fā)送出去,同樣需要先發(fā)一位起始位,然后發(fā)8個(gè)數(shù)據(jù)位,再發(fā)結(jié)束位,發(fā)送完畢后,程序運(yùn)行到while (PIN_RXD),等待第二輪信號(hào)接收的開(kāi)始。 #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; //開(kāi)總中斷
while(1) { while (PIN_RXD); //等待接收引腳出現(xiàn)低電平,即起始位 StartRXD(); //啟動(dòng)接收 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() //啟動(dòng)串行接收 { TL0 = 256 - ((256-TH0) >> 1); //接收啟動(dòng)時(shí)的T0定時(shí)為半個(gè)波特率周期 ET0 = 1; //使能T0中斷 TR0 = 1; //啟動(dòng)T0 RxdEnd = 0; //清零接收結(jié)束標(biāo)志 RxdOrTxd = 0; //設(shè)置當(dāng)前狀態(tài)為接收 } void StartTXD(unsigned char dat) //啟動(dòng)串行發(fā)送,dat為待發(fā)送字節(jié)數(shù)據(jù) { TxdBuf = dat; //待發(fā)送數(shù)據(jù)保存到發(fā)送緩沖器 TL0 = TH0; //T0計(jì)數(shù)初值為重載值 ET0 = 1; //使能T0中斷 TR0 = 1; //啟動(dòng)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é)們通過(guò)學(xué)習(xí)我們的程序,也慢慢感受到了,程序的延時(shí)部分已經(jīng)不再使用簡(jiǎn)單的delay來(lái)完成了,我們要通過(guò)我們的程序編寫(xiě)積累,慢慢提高自己靈活運(yùn)用定時(shí)器的能力。一個(gè)小小的定時(shí)器,可以幫我們完成很多很多工作。 11.5 UART串口通信的基本應(yīng)用11.5.1 通信的三種基本類型我們常用的通信通常可以分為單工、半雙工、全雙工通信。 單工就是指只允許一方向另外一方傳送信息,而另一方不能回傳信息。比如我們的電視遙控器,我們的收音機(jī)廣播等,都是單工通信技術(shù)。 半雙工是指數(shù)據(jù)可以在雙方之間相互傳播,但是同一時(shí)刻只能其中一方發(fā)給另外一方,比如我們的對(duì)講機(jī)就是典型的半雙工。 全雙工通信就發(fā)送數(shù)據(jù)的同時(shí)也能夠接受數(shù)據(jù),兩者同步進(jìn)行,就如同我們的電話一樣,我們說(shuō)話的同時(shí)也可以聽(tīng)到對(duì)方的聲音。 11.5.2 UART模塊介紹IO口模擬串口通信,大家了解了串口通信的實(shí)質(zhì),但是我們的單片機(jī)程序卻需要不停的檢測(cè)掃描單片機(jī)IO口收到的數(shù)據(jù),大量占用了CPU資源。這時(shí)候就會(huì)有聰明人想了,其實(shí)我們不是很關(guān)心通信的過(guò)程,我們只需要一個(gè)通信的結(jié)果,最終得到接收到的數(shù)據(jù)就行了。這樣我們可以在單片機(jī)內(nèi)部做一個(gè)硬件模塊,讓他自動(dòng)接收數(shù)據(jù),接收完了,通知我們一下就可以了,我們的51單片機(jī)內(nèi)部就存在這樣一個(gè)UART模塊,要正確使用它,當(dāng)然還得先把對(duì)應(yīng)的特殊功能寄存器配置好。 51單片機(jī)的UART串行口的結(jié)構(gòu)由串行口控制寄存器SCON、發(fā)送和接收電路三部分構(gòu)成,先來(lái)了解一下串口控制寄存器SCON。 表11-1 SCON--串行控制寄存器的位分配(地址:98H) 可位尋址;復(fù)位值:0x00;復(fù)位源:任何復(fù)位 位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 符號(hào) | 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用來(lái)接收停止位 | | | 發(fā)送中斷標(biāo)志位,模式1下,在數(shù)據(jù)位最后一位發(fā)送結(jié)束,開(kāi)始發(fā)送停止位時(shí)由硬件自動(dòng)置1,必須通過(guò)軟件清零。也就是說(shuō),再發(fā)送前我們清零TI,發(fā)送數(shù)據(jù),數(shù)據(jù)發(fā)送到停止位時(shí),TI硬件置1,方便我們CPU查詢發(fā)送完畢狀態(tài)。 | | | 接收中斷標(biāo)志位,當(dāng)接收電路接收到停止位的中間位置時(shí),RI由硬件置1。也就是說(shuō),接收數(shù)據(jù)之前我們必須清零RI,接受數(shù)據(jù)到停止位的中間位置時(shí),RI硬件置1,方便我們CPU查詢到接收狀態(tài)。 |
前邊學(xué)了那么多寄存器的配置,相信SCON這個(gè)地方,對(duì)于大多數(shù)同學(xué)來(lái)說(shuō)已經(jīng)不是難點(diǎn)了,應(yīng)該能看懂并且可以自己配置了。對(duì)于串口的四種模式,模式1是最常用的,就是我們前邊提到的1位起始位,8位數(shù)據(jù)位和1位結(jié)束位。因?yàn)槲覀兊慕坛滩煌诮炭茣?shū),只要有的功能都一一介紹,我們只介紹實(shí)用的技術(shù),所以其他3種模式,真正遇到需要使用的時(shí)候大家再去查資料就行。 在我們使用IO口模擬串口通信的時(shí)候,我們串口的波特率是使用定時(shí)器0的中斷體現(xiàn)出來(lái)的。在實(shí)際串口模塊中,有一個(gè)專門(mén)的波特率發(fā)生器用來(lái)控制發(fā)送數(shù)據(jù)的速度和讀取接收數(shù)據(jù)的速度。對(duì)于STC89C52RC單片機(jī)來(lái)講,這個(gè)波特率發(fā)生器只能由定時(shí)器1或定時(shí)器2產(chǎn)生,而不能由定時(shí)器0產(chǎn)生,這和我們模擬的通信是完全不同的概念。 如果用定時(shí)器2,需要配置額外的寄存器,默認(rèn)是使用定時(shí)器1的,我們本章內(nèi)容主要是使用定時(shí)器1作為波特率發(fā)生器來(lái)講解,方式1下的波特率發(fā)生器必須使用定時(shí)器1的模式2,也就是自動(dòng)重裝載模式,定時(shí)器的初值具體的計(jì)算公式是: TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率 和波特率有關(guān)的還有一個(gè)寄存器,是一個(gè)電源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果寫(xiě)PCON |=0x80以后,計(jì)算公式就成了 TH1 = TL1 = 256 - 晶振值/12 /16 /波特率 數(shù)字的含義這里解釋一下,256是8位數(shù)據(jù)的溢出值,也就是TL1的溢出值,11059200就是我們板子上單片機(jī)的晶振,12是說(shuō)1個(gè)機(jī)器周期是12個(gè)時(shí)鐘周期,值得關(guān)注的是這個(gè)16,重點(diǎn)說(shuō)明。我們?cè)?/font>IO口模擬串口通信接收數(shù)據(jù)的時(shí)候,我們采集的是這一位數(shù)據(jù)的中間位置,而實(shí)際上串口模塊比我們模擬的要復(fù)雜和精確一些。他采取的方式是把一位信號(hào)采集16次,其中第7、8、9次取出來(lái),這三次中其中兩次如果是高電平,那么就認(rèn)定這一位數(shù)據(jù)是1,如果兩次是低電平,那么就認(rèn)定這一位是0,這樣一旦受到意外干擾讀錯(cuò)一次數(shù)據(jù),也依然可以保證最終數(shù)據(jù)的正確性。 了解了串口采集模式,在這里要給大家留一個(gè)思考題。“晶振值/12/2/16/波特率”這個(gè)地方計(jì)算的時(shí)候,出現(xiàn)不能除盡,或者出現(xiàn)小數(shù)怎么辦,允許出現(xiàn)多大的偏差?把這部分理解了,也就理解了我們的晶振為何使用11.0592M了。 串口通信的發(fā)送和接收電路,我們主要了解一下他們?cè)谖锢砩嫌?font face="Times New Roman">2個(gè)名字相同的SBUF寄存器,他們的地址也都是99H,但是一個(gè)用來(lái)做發(fā)送緩沖,一個(gè)用來(lái)做接收緩沖。意思就是說(shuō),有2個(gè)房間,兩個(gè)房間的門(mén)牌號(hào)是一樣的,其中一個(gè)只出人不進(jìn)人,另外一個(gè)只進(jìn)人不出人,這樣的話,我們就可以實(shí)現(xiàn)UART的全雙工通信,相互之間不會(huì)產(chǎn)生干擾。但是在邏輯上呢,我們每次只操作SBUF,單片機(jī)會(huì)自動(dòng)根據(jù)對(duì)它執(zhí)行的是“讀”還是“寫(xiě)”操作來(lái)選擇是接收SBUF還是發(fā)送SBUF,后邊通過(guò)程序,我們就會(huì)徹底了解這個(gè)問(wèn)題。 11.5.3 UART串口程序一般情況下,我們編寫(xiě)串口通信程序的基本步驟如下所示: 1、配置串口為模式1。 2、配置定時(shí)器T1為模式2,即自動(dòng)重裝模式。 3、確定波特率大小,計(jì)算定時(shí)器TH1和TL1的初值,如果有需要可以使用PCON進(jìn)行波特率加倍。 4、打開(kāi)定時(shí)器控制寄存器TR1,讓定時(shí)器跑起來(lái)。 這個(gè)地方還要特別注意一下,就是在使用T1做波特率發(fā)生器的時(shí)候,千萬(wàn)不要再使能T1的中斷了。 我們先來(lái)看一下由IO口模擬串口通信直接改為使用硬件UART模塊時(shí)程序代碼,看看程序是不是簡(jiǎn)單了很多,因?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ā)送回去; //等號(hào)左邊的SBUF實(shí)際上就是發(fā)送SBUF,因?yàn)閷?duì)它的操作是“寫(xiě)”; //等號(hào)右邊的是接收SBUF,因?yàn)閷?duì)它的操作是“讀”。 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; //啟動(dòng)T1 } 當(dāng)然了,這個(gè)程序還是在主循環(huán)里等待接收中斷標(biāo)志位和發(fā)送中斷標(biāo)志位的方法來(lái)編寫(xiě)的,而實(shí)際工程開(kāi)發(fā)中,當(dāng)然就不能這么干了,所以就用到了串口中斷,來(lái)看一下程序。 #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; //啟動(dòng)T1 ES = 1; //打開(kāi)串口中斷 EA = 1; //打開(kāi)總中斷 } void InterruptUART() interrupt 4 { if (RI) //接收到字節(jié) { RI = 0; //手動(dòng)清零接收中斷標(biāo)志位 SBUF = SBUF + 1;//接收數(shù)據(jù)+1發(fā)回去,左邊為發(fā)送SBUF,右邊為接收SBUF。 } if (TI) //字節(jié)發(fā)送完畢 { TI = 0; //手動(dòng)清零發(fā)送中斷標(biāo)志位 } } 大家可以試驗(yàn)一下試試,看看是不是和前邊用IO口模擬通信實(shí)現(xiàn)的效果一致,而主循環(huán)卻完全空出來(lái)了,我們就可以隨意添加其它功能代碼進(jìn)去。 11.6 字符和數(shù)據(jù)之間的轉(zhuǎn)換我們學(xué)串口通信的應(yīng)用主要是實(shí)現(xiàn)單片機(jī)和電腦之間的信息互發(fā),可以用電腦控制單片機(jī)的一些信息,可以把單片機(jī)的一些信息狀況發(fā)給電腦上的軟件。下面我們就做一個(gè)簡(jiǎn)單的例程,實(shí)現(xiàn)單片機(jī)串口調(diào)試助手發(fā)送的數(shù)據(jù),在我們開(kāi)發(fā)板上的數(shù)碼管上顯示出來(lái)。 #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; //開(kāi)總中斷 ConfigTimer0(1); //配置T0定時(shí)1ms ConfigUART(9600); //配置波特率為9600
while(1) { //將接收字節(jié)在數(shù)碼管上以十六進(jìn)制形式顯示出來(lái) 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; //啟動(dòng)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; //啟動(dòng)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個(gè)數(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; //手動(dòng)清零接收中斷標(biāo)志位 RxdByte = SBUF; //接收到的數(shù)據(jù)保存到接收字節(jié)變量中 SBUF = RxdByte; //接收到的數(shù)據(jù)又直接發(fā)回,這叫回顯-"echo",以提示用戶輸入的信息是否已正確接收 } if (TI) //字節(jié)發(fā)送完畢 { TI = 0; //手動(dòng)清零發(fā)送中斷標(biāo)志位 } } 大家在做這個(gè)實(shí)驗(yàn)的時(shí)候,有個(gè)小問(wèn)題要注意一下。因?yàn)槲覀?font face="Times New Roman">STC89C52RC下載程序是使用了UART串口下載,下載完程序后,程序運(yùn)行起來(lái)了,可是下載軟件最后還會(huì)通過(guò)串口發(fā)送一些額外的數(shù)據(jù),所以程序剛下載進(jìn)去不是顯示00,而可能是其他數(shù)據(jù)。大家只要把開(kāi)關(guān)關(guān)閉,重新打開(kāi)一次就好了。 細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn),在串口調(diào)試助手發(fā)送選項(xiàng)和接收選項(xiàng)處,還有個(gè)“字符格式發(fā)送”和“字符格式顯示”,這是什么意思呢? 先拋開(kāi)我們使用的漢字不談,那么我們常用的字符就包含了0~9的數(shù)字、A~Z/a~z的字母、還有各種標(biāo)點(diǎn)符號(hào)等。那么在單片機(jī)系統(tǒng)里面我們?cè)趺磥?lái)表示它們呢?ASCII碼(American Standard Code for Information Interchange,即美國(guó)信息互換標(biāo)準(zhǔn)代碼)可以完成這個(gè)使命:我們知道,在單片機(jī)中一個(gè)字節(jié)的數(shù)據(jù)可以有0~255共256個(gè)值,我們?nèi)∑渲械?/font>0~127共128個(gè)值賦予了它另外一層涵義,即讓它們分別來(lái)代表一個(gè)常用字符,其具體的對(duì)應(yīng)關(guān)系如下表。 表11-3 ASCII表 這樣我們就在常用字符和字節(jié)數(shù)據(jù)之間建立了一一對(duì)應(yīng)的關(guān)系,那么現(xiàn)在一個(gè)字節(jié)就既可以代表一個(gè)整數(shù)又可以代表一個(gè)字符了,但它本質(zhì)上只是一個(gè)字節(jié)的數(shù)據(jù),而我們賦予了它不同的涵義,什么時(shí)候賦予它那種涵義就看編程者的意圖了。ASCII碼在單片機(jī)系統(tǒng)中應(yīng)用非常廣泛,我們后續(xù)的課程也會(huì)經(jīng)常使用到它,下面我們來(lái)對(duì)它做一個(gè)直觀的認(rèn)識(shí),同學(xué)們一定要深刻理解其本質(zhì)。 對(duì)照上述表格,我們就可以實(shí)現(xiàn)字符和數(shù)字之間的轉(zhuǎn)換了,比如還是這個(gè)程序,我們發(fā)送的時(shí)候改成字符格式發(fā)送,接收還是用十六進(jìn)制接收,這樣接收和數(shù)碼管好做一下對(duì)比。 我們用字符格式發(fā)送一個(gè)小寫(xiě)的a,返回一個(gè)十六進(jìn)制的0x61,數(shù)碼管上顯示的也是61,ASCII碼表里字符a對(duì)應(yīng)十進(jìn)制是97,等于十六進(jìn)制的0x61;我們?cè)儆米址袷桨l(fā)送一個(gè)數(shù)字1,返回一個(gè)十六進(jìn)制的0x31,數(shù)碼管上顯示的也是31,ASCII表里字符1對(duì)應(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ù)倪是一個(gè)字節(jié)數(shù)據(jù)。這個(gè)表格,當(dāng)然不需要大家去記住,理解它,用的時(shí)候過(guò)來(lái)查就行了。 通信的學(xué)習(xí),不像前邊控制部分那么直觀了,通信部分我們的程序只能獲得一個(gè)結(jié)果,而其過(guò)程我們卻無(wú)法直接看到,所以慢慢的可能大家就會(huì)知道有示波器和邏輯分析儀這類測(cè)量?jī)x器。如果學(xué)校實(shí)驗(yàn)室或者公司里有示波器或者邏輯分析儀這類儀器,可以拿過(guò)來(lái)抓一下串口波形,直觀的了解一下。如果暫時(shí)還沒(méi)有這些儀器,先知道這么回事,有條件再說(shuō)。因?yàn)楣ぞ哳惖臇|西有的比較昂貴,有條件可以盡量使用學(xué)校或者公司的。在這里我用一款簡(jiǎn)易的邏輯分析儀把串口通信的波形抓出來(lái)給大家看一下,大家了解一下即可,如圖11-7所示。 圖11-7 邏輯分析儀串口數(shù)據(jù)示意圖 分析儀和示波器的作用,就是把通信過(guò)程的波形抓出來(lái)進(jìn)行分析。先大概說(shuō)一下波形的意思。波形左邊是低位,右邊是高位,上邊這個(gè)波形是電腦發(fā)送給單片機(jī)的,下邊這個(gè)波形是單片機(jī)回發(fā)給電腦的。以上邊的波形為例,左邊第一位是起始位0,從低位到高位依次是10001100,順序倒一下,就是數(shù)據(jù)0x31,也就是ASCII碼表里的‘1’。大家可以注意到分析儀在每個(gè)數(shù)據(jù)位都給標(biāo)了一個(gè)白色的點(diǎn),表示是數(shù)據(jù),起始位和無(wú)數(shù)據(jù)的時(shí)候都沒(méi)有這個(gè)白點(diǎn)。時(shí)間標(biāo)T1和T2的差值在右邊顯示出來(lái)是0.102ms,大概是9600分之一,稍微有點(diǎn)偏差,在容許范圍內(nèi)即可。通過(guò)圖11-7,我們可以清晰的了解了串口通信的收發(fā)的詳細(xì)過(guò)程。 那我們這里再來(lái)了解一下,如果我們使用串口調(diào)試助手,用字符格式直接發(fā)送一個(gè)“12”,我們?cè)谖覀兊臄?shù)碼管上應(yīng)該顯示什么呢?串口調(diào)試助手應(yīng)該返回什么呢?經(jīng)過(guò)試驗(yàn)發(fā)現(xiàn),我們數(shù)碼管顯示的是32,而串口調(diào)試助手返回十六進(jìn)制顯示的是31、32兩個(gè)數(shù)據(jù),如圖11-8所示。 圖11-8 串口調(diào)試助手?jǐn)?shù)據(jù)顯示 我們用邏輯分析儀把這個(gè)數(shù)據(jù)抓出來(lái)看一下,如圖11-9所示。 圖11-9 邏輯分析儀抓取數(shù)據(jù) 對(duì)于ASCII碼表來(lái)說(shuō),數(shù)字本身是字符而非數(shù)據(jù),所以如果發(fā)送“12”的話,實(shí)際上是是分別發(fā)送了“1”和“2”兩個(gè)字符,單片機(jī)呢,先收到第一個(gè)字符“1”,在數(shù)碼管上會(huì)顯示出31這個(gè)對(duì)應(yīng)數(shù)字,但是瞬間馬上就又收到了“2”這個(gè)字符,數(shù)碼管瞬間從31變成了32,而我們視覺(jué)上呢,根本是沒(méi)有辦法發(fā)現(xiàn)這種快速變化的,所以我們感覺(jué)數(shù)碼管直接顯示的是32。 11.7 作業(yè)1、能夠理解UART串口通信的基本原理和通信過(guò)程。 2、通過(guò)IO口模擬UART串口通信把通信的底層操作原理弄明白。 3、學(xué)會(huì)通過(guò)配置寄存器,實(shí)現(xiàn)串口通信的基本操作過(guò)程。 4、了解字符和數(shù)據(jù)之間的轉(zhuǎn)換依據(jù)和方法。 5、完成通過(guò)串口控制流水燈流動(dòng)和停止的程序。 6、完成通過(guò)串口實(shí)現(xiàn)蜂鳴器響的程序。
|