1. 程式人生 > >UART串列埠通訊淺談之(一)--基礎概述

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),等待第二輪訊號接收的開始。

 

  1. #include <reg52.h>

  2. sbit PIN_RXD = P3^0; //接收引腳定義

  3. sbit PIN_TXD = P3^1; //傳送引腳定義

  4. bit RxdOrTxd = 0; //指示當前狀態為接收還是傳送

  5. bit RxdEnd = 0; //接收結束標誌

  6. bit TxdEnd = 0; //傳送結束標誌

  7. unsigned char RxdBuf = 0; //接收緩衝器

  8. unsigned char TxdBuf = 0; //傳送緩衝器

  9. void ConfigUART(unsigned int baud);

  10. void StartTXD(unsigned char dat);

  11. void StartRXD();

  12. void main ()

  13. {

  14. ConfigUART(9600); //配置波特率為9600

  15. EA = 1; //開總中斷

  16.  
  17. while(1)

  18. {

  19. while (PIN_RXD); //等待接收引腳出現低電平,即起始位

  20. StartRXD(); //啟動接收

  21. while (!RxdEnd); //等待接收完成

  22. StartTXD(RxdBuf+1); //接收到的資料+1後,傳送回去

  23. while (!TxdEnd); //等待發送完成

  24. }

  25. }

  26. void ConfigUART(unsigned int baud) //串列埠配置函式,baud為波特率

  27. {

  28. TMOD &= 0xF0; //清零T0的控制位

  29. TMOD |= 0x02; //配置T0為模式2

  30. TH0 = 256 - (11059200/12) / baud; //計算T0過載值

  31. }

  32. void StartRXD() //啟動序列接收

  33. {

  34. TL0 = 256 - ((256-TH0) >> 1); //接收啟動時的T0定時為半個波特率週期

  35. ET0 = 1; //使能T0中斷

  36. TR0 = 1; //啟動T0

  37. RxdEnd = 0; //清零接收結束標誌

  38. RxdOrTxd = 0; //設定當前狀態為接收

  39. }

  40. void StartTXD(unsigned char dat) //啟動序列傳送,dat為待發送位元組資料

  41. {

  42. TxdBuf = dat; //待發送資料儲存到傳送緩衝器

  43. TL0 = TH0; //T0計數初值為過載值

  44. ET0 = 1; //使能T0中斷

  45. TR0 = 1; //啟動T0

  46. PIN_TXD = 0; //傳送起始位

  47. TxdEnd = 0; //清零傳送結束標誌

  48. RxdOrTxd = 1; //設定當前狀態為傳送

  49. }

  50. void InterruptTimer0() interrupt 1 //T0中斷服務函式,處理序列傳送和接收

  51. {

  52. static unsigned char cnt = 0; //bit計數器,記錄當前正在處理的位

  53. if (RxdOrTxd) //序列傳送處理

  54. {

  55. cnt++;

  56. if (cnt <= 8) //低位在先依次傳送8bit資料位

  57. {

  58. PIN_TXD = TxdBuf & 0x01;

  59. TxdBuf >>= 1;

  60. }

  61. else if (cnt == 9) //傳送停止位

  62. {

  63. PIN_TXD = 1;

  64. }

  65. else //傳送結束

  66. {

  67. cnt = 0; //復位bit計數器

  68. TR0 = 0; //關閉T0

  69. TxdEnd = 1; //置傳送結束標誌

  70. }

  71. }

  72. else //序列接收處理

  73. {

  74. if (cnt == 0) //處理起始位

  75. {

  76. if (!PIN_RXD) //起始位為0時,清零接收緩衝器,準備接收資料位

  77. {

  78. RxdBuf = 0;

  79. cnt++;

  80. }

  81. else //起始位不為0時,中止接收

  82. {

  83. TR0 = 0; //關閉T0

  84. }

  85. }

  86. else if (cnt <= 8) //處理8位資料位

  87. {

  88. RxdBuf >>= 1; //低位在先,所以將之前接收的位向右移

  89. if (PIN_RXD) //接收腳為1時,緩衝器最高位置1;為0時不處理即仍保持移位後的0

  90. {

  91. RxdBuf |= 0x80;

  92. }

  93. cnt++;

  94. }

  95. else //停止位處理

  96. {

  97. cnt = 0; //復位bit計數器

  98. TR0 = 0; //關閉T0

  99. if (PIN_RXD) //停止位為1時,方能認為資料有效

  100. {

  101. RxdEnd = 1; //置接收結束標誌

  102. }

  103. }

  104. }

  105. }