UART串列埠通訊淺談之(一)--基礎概述
通訊按照傳統的理解就是資訊的傳輸與交換。UART(Universal Asynchronous Receiver/Transmitter,即通用非同步收發器)序列通訊是微控制器最常用的一種通訊技術,通常用於微控制器和電腦之間以及微控制器和微控制器之間的通訊。
以下我們以STC98C52微控制器為例子,簡單講述序列通訊。
1.1 序列通訊的初步認識
通訊按照基本型別可以分為並行通訊和序列通訊。並行通訊時資料的各個位同時傳送,可以實現位元組為單位通訊,但是因為通訊線多佔用資源多,成本高。比如使用STC89C52的P0口設定為P0 = 0xfe;一次給P0的8個IO口分別賦值,同時進行訊號輸出,類似於有8個車道同時可以過去8輛車一樣,這種形式就是並行的,我們習慣上還稱P0、P1、P2和P3為51微控制器的4組並行匯流排。
而序列通訊,就如同一條車道,一次只能一輛車過去,如果一個0xfe這樣一個位元組的資料要傳輸過去的話,假如低位在前高位在後,那傳送方式就是0-1-1-1-1-1-1-1-1,一位一位的傳送出去的,要傳送8次才能傳送完一個位元組。
在我們的STC89C52上,有兩個引腳,是專門用來做UART串列埠通訊的,一個是P3.0一個是P3.1,還分別有另外的名字叫做RXD和TXD,這兩個引腳是專門用來進行UART通訊的,如果我們兩個微控制器進行UART串列埠通訊的話,那基本的演示圖如圖1-1所示。
圖1-1 微控制器之間UART通訊示意圖
圖中,GND表示微控制器系統電源的參考地,TXD是序列傳送引腳,RXD是序列接收引腳。兩個微控制器之間要通訊,首先電源基準得一樣,所以我們要把兩個微控制器的GND相互連起來,然後微控制器1的TXD引腳接到微控制器2的RXD引腳上,即此路為微控制器1傳送而微控制器2接收的通道,微控制器1的RXD引腳接到微控制器2的TXD引腳上,即此路為微控制器2傳送而微控制器2接收的通道。這個示意圖就體現了兩個微控制器各自收發資訊的過程。
當微控制器1想給微控制器2傳送資料時,比如傳送一個0xE4這個資料,用二進位制形式表示就是0b11100100,在UART通訊過程中,是低位先發,高位後發的原則,那麼就讓TXD首先拉低電平,持續一段時間,傳送一位0,然後繼續拉低,再持續一段時間,又傳送了一位0,然後拉高電平,持續一段時間,發了一位1......一直到把8位二進位制數字0b11100100全部發送完畢。這裡就牽扯到了一個問題,就是持續的這“一段時間”到底是多久?從這裡引入我們通訊中的另外重要概念——波特率,也叫做位元率。
波特率就是傳送一位二進位制資料的速率,習慣上用baud表示,即我們傳送一位資料的持續時間=1/baud。在通訊之前,微控制器1和微控制器2首先都要明確的約定好他們之間的通訊波特率,必須保持一致,收發雙方才能正常實現通訊,這一點大家一定要記清楚。
約定好速度後,我們還要考慮第二個問題,資料什麼時候是起始,什麼時候是結束呢?不管是提前接收還是延遲接收,資料都會接收錯誤。在UART序列通訊的時候,一個位元組是8位,規定當沒有通訊訊號發生時,通訊線路保持高電平,當要傳送資料之前,先發一位0表示起始位,然後傳送8位資料位,資料位是先低後高的順序,資料位發完後再發一位1表示停止位。這樣本來要傳送一個位元組8位資料,而實際上我們一共傳送了10位,多出來的兩位其中一位起始位,一位停止位。而接收方呢,原本一直保持的高電平,一旦檢測到來了一位低電平,那就知道了要開始準備接收資料了,接收到8位資料位後,然後檢測到停止位,再準備下一個數據的接收了。我們圖示看一下,如圖1-2所示。
圖1-2 串列埠資料傳送示意圖
如圖1-2串列埠資料傳送示意圖,實際上是一個時域示意圖,就是訊號隨著時間變化的對應關係。比如在微控制器的傳送引腳上,左邊的是先發生的,右邊的是後發生的,資料位的切換時間就是波特率分之一秒,如果能夠理解時域的概念,後邊很多通訊的時序圖就很容易理解了。
1.2 USB轉串列埠通訊
隨著技術的發展,工業上還有RS232串列埠通訊的大量使用,但是商業技術的應用上,已經慢慢的使用USB轉UART技術取代了RS232串列埠,絕大多數膝上型電腦已經沒有串列埠這個東西了,那我們要實現微控制器和電腦之間的通訊該如何辦呢?
我們只需要在我們電路上新增一個USB轉串列埠晶片,就可以成功實現USB通訊協議和標準UART序列通訊協議的轉換,在我們的開發板上,我們使用的是CH340G這個晶片,如圖1-3所示。
圖1-3 USB轉串列埠電路
CH340G這個電路很簡單,把電源電路,晶振電路接好後,6腳和7腳的UD+和UD-分別接USB口的2個數據引腳上去,3腳和4腳接到了我們微控制器的TXD和RXD上去。
CH340G的電路里2腳位置加了個4148的二極體,是一個小技巧。因為我們的STC89C52RC這個微控制器下載程式需要冷啟動,就是先點下載後上電,上電瞬間微控制器會先檢測需要不需要下載程式。雖然微控制器的VCC是由開關來控制,但是由於CH340G的2腳是輸出引腳,如果沒有此二極體,開關後級微控制器在斷電的情況下,CH340G的2腳和微控制器的P3.0(即RXD)引腳連在一起,有電流會通過這個引腳流入後級電路並且給後級的電容充電,造成後級有一定幅度的電壓,這個電壓值雖然只有兩三伏左右,但是可能會影響到我們的冷啟動。加了二極體後,一方面不影響通訊,另外一個方面還可以消除這種問題。這個地方可以暫時作為了解,大家如果自己做這塊電路,可以參考一下。
1.3 IO口模擬UART串列埠通訊
為了讓大家充分理解UART串列埠通訊的原理,我們先用P3.0和P3.1這兩個當做IO口來進行模擬實際串列埠通訊的過程,原理搞懂後,我們再使用暫存器配置實現串列埠通訊過程。
對於UART串列埠波特率,常用的值是1200、2400、4800、9600、14400、19200、28800、38400、57600、115200、128000、256000等速率。IO口模擬UART序列通訊程式是一個簡單的演示程式,我們使用串列埠除錯助手下發一個數據,資料加1後,再自動返回。波特率是我們程式設定好的選擇,我們程式中讓一個數據位持續時間是1/9600秒,那這個地方選擇波特率就是選9600,校驗位選N,資料位8,停止位1。
串列埠除錯助手的實質就是我們利用電腦上的UART通訊介面,通過這個UART介面傳送資料給我們的微控制器,也可以把我們的單片機發送的資料接收到這個除錯助手介面上。
因為初次接觸通訊方面的技術,所以我對這個程式進行一下解釋,大家可以邊看我的解釋邊看程式,把底層原理先徹底弄懂。
變數定義部分就不用說了,直接看main主函式。首先是對通訊的波特率的設定,在這裡我們配置的波特率是9600,那麼串列埠除錯助手也得是9600。配置波特率的時候,我們用的是定時器0的模式2。模式2中,不再是TH0代表高8位,TL0代表低8位了,而只有TL0在進行計數了。當TL0溢位後,不僅僅會讓TF0變1,而且還會將TH0中的內容重新自動裝到TL0中。這樣有一個好處,我們可以把我們想要的定時器初值提前存在TH0中,當TL0溢位後,TH0自動把初值就重新送入TL0了,全自動的,不需要程式上再給TL0重新賦值了,配置方式很簡單,大家可以自己看下程式並且計算一下初值。
波特率設定好以後,開啟中斷,然後等待接收串列埠除錯助手下發的資料。接收資料的時候,首先要進行低電平檢測 while (PIN_RXD),若沒有低電平則說明沒有資料,一旦檢測到低電平,就進入啟動接收函式StartRXD()。接收函式最開始啟動半個波特率週期,初學可能這裡不是很明白。大家回頭看一下我們的圖1-2裡邊的串列埠資料示意圖,訊號在資料位電平變化的時候去讀,因為時序上的誤差以及訊號穩定性的問題很容易讀錯資料,所以我們希望在訊號最穩定的時候去讀資料。除了訊號變化的那個沿的位置外,其他位置都很穩定,那麼我們現在就約定在訊號中間位置去讀取電平狀態,這樣能夠保證我們訊號讀的是對的。
一旦讀到了起始訊號,我們就把當前狀態設定成接受狀態,並且開啟定時器中斷,第一次是半個週期進入中斷後,對起始位進行二次判斷一下,確認一下起始位是低電平,而不是一個干擾訊號。以後每經過9600分之一秒進入一次中斷,並且把這個引腳的狀態讀到RxdBuf裡邊。等待接收完畢之後,我們再把這個RxdBuf加1,再通過TXD引腳傳送出去,同樣需要先發一位起始位,然後發8個數據位,再發結束位,傳送完畢後,程式執行到while (PIN_RXD),等待第二輪訊號接收的開始。
-
#include <reg52.h>
-
sbit PIN_RXD = P3^0; //接收引腳定義
-
sbit PIN_TXD = P3^1; //傳送引腳定義
-
bit RxdOrTxd = 0; //指示當前狀態為接收還是傳送
-
bit RxdEnd = 0; //接收結束標誌
-
bit TxdEnd = 0; //傳送結束標誌
-
unsigned char RxdBuf = 0; //接收緩衝器
-
unsigned char TxdBuf = 0; //傳送緩衝器
-
void ConfigUART(unsigned int baud);
-
void StartTXD(unsigned char dat);
-
void StartRXD();
-
void main ()
-
{
-
ConfigUART(9600); //配置波特率為9600
-
EA = 1; //開總中斷
-
while(1)
-
{
-
while (PIN_RXD); //等待接收引腳出現低電平,即起始位
-
StartRXD(); //啟動接收
-
while (!RxdEnd); //等待接收完成
-
StartTXD(RxdBuf+1); //接收到的資料+1後,傳送回去
-
while (!TxdEnd); //等待發送完成
-
}
-
}
-
void ConfigUART(unsigned int baud) //串列埠配置函式,baud為波特率
-
{
-
TMOD &= 0xF0; //清零T0的控制位
-
TMOD |= 0x02; //配置T0為模式2
-
TH0 = 256 - (11059200/12) / baud; //計算T0過載值
-
}
-
void StartRXD() //啟動序列接收
-
{
-
TL0 = 256 - ((256-TH0) >> 1); //接收啟動時的T0定時為半個波特率週期
-
ET0 = 1; //使能T0中斷
-
TR0 = 1; //啟動T0
-
RxdEnd = 0; //清零接收結束標誌
-
RxdOrTxd = 0; //設定當前狀態為接收
-
}
-
void StartTXD(unsigned char dat) //啟動序列傳送,dat為待發送位元組資料
-
{
-
TxdBuf = dat; //待發送資料儲存到傳送緩衝器
-
TL0 = TH0; //T0計數初值為過載值
-
ET0 = 1; //使能T0中斷
-
TR0 = 1; //啟動T0
-
PIN_TXD = 0; //傳送起始位
-
TxdEnd = 0; //清零傳送結束標誌
-
RxdOrTxd = 1; //設定當前狀態為傳送
-
}
-
void InterruptTimer0() interrupt 1 //T0中斷服務函式,處理序列傳送和接收
-
{
-
static unsigned char cnt = 0; //bit計數器,記錄當前正在處理的位
-
if (RxdOrTxd) //序列傳送處理
-
{
-
cnt++;
-
if (cnt <= 8) //低位在先依次傳送8bit資料位
-
{
-
PIN_TXD = TxdBuf & 0x01;
-
TxdBuf >>= 1;
-
}
-
else if (cnt == 9) //傳送停止位
-
{
-
PIN_TXD = 1;
-
}
-
else //傳送結束
-
{
-
cnt = 0; //復位bit計數器
-
TR0 = 0; //關閉T0
-
TxdEnd = 1; //置傳送結束標誌
-
}
-
}
-
else //序列接收處理
-
{
-
if (cnt == 0) //處理起始位
-
{
-
if (!PIN_RXD) //起始位為0時,清零接收緩衝器,準備接收資料位
-
{
-
RxdBuf = 0;
-
cnt++;
-
}
-
else //起始位不為0時,中止接收
-
{
-
TR0 = 0; //關閉T0
-
}
-
}
-
else if (cnt <= 8) //處理8位資料位
-
{
-
RxdBuf >>= 1; //低位在先,所以將之前接收的位向右移
-
if (PIN_RXD) //接收腳為1時,緩衝器最高位置1;為0時不處理即仍保持移位後的0
-
{
-
RxdBuf |= 0x80;
-
}
-
cnt++;
-
}
-
else //停止位處理
-
{
-
cnt = 0; //復位bit計數器
-
TR0 = 0; //關閉T0
-
if (PIN_RXD) //停止位為1時,方能認為資料有效
-
{
-
RxdEnd = 1; //置接收結束標誌
-
}
-
}
-
}
-
}