1. 程式人生 > >UART串列埠通訊淺談之(二)--暫存器設定

UART串列埠通訊淺談之(二)--暫存器設定

1.1 通訊的三種基本型別

常用的通訊通常可以分為單工、半雙工、全雙工通訊。

單工就是指只允許一方向另外一方傳送資訊,而另一方不能回傳資訊。比如我們的電視遙控器,我們的收音機廣播等,都是單工通訊技術。

半雙工是指資料可以在雙方之間相互傳播,但是同一時刻只能其中一方發給另外一方,比如我們的對講機就是典型的半雙工。

全雙工通訊就傳送資料的同時也能夠接受資料,兩者同步進行,就如同我們的電話一樣,我們說話的同時也可以聽到對方的聲音。

1.2 UART模組介紹

IO口模擬串列埠通訊,瞭解了串列埠通訊的實質,但是微控制器程式卻需要不停的檢測掃描微控制器IO口收到的資料,大量佔用了CPU資源。這時候就會有人想了,其實我不是很關心通訊的過程,只需要一個通訊的結果,最終得到接收到的資料就行了。這樣我們可以在微控制器內部做一個硬體模組,讓它自動接收資料,接收完了,通知我們一下就可以了,STC51微控制器內部就存在這樣一個UART模組,要正確使用它,當然還得先把對應的特殊功能暫存器配置好。

STC51微控制器的UART序列口的結構由序列口控制暫存器SCON、傳送和接收電路三部分構成,先來了解一下串列埠控制暫存器SCON。

表1-1 SCON--序列控制暫存器的位分配(地址:98H)

       可位定址;復位值:0x00;復位源:任何復位

7

6

5

4

3

2

1

0

符號

SM0

SM1

SM2

REN

TB8

RB8

TI

RI

表1-2 SCON--序列控制暫存器的位描述

符號

描述

7

SM0

這兩位共同決定了串列埠通訊的模式0到模式3共4種模式。我們最常用的就是模式1,也就是SM0=0,SM1=1,下邊我們重點就講模式1,其他模式從略。

6

SM1

5

SM2

多機通訊控制位(很少用),模式1直接清零。

4

REN

使能序列接收。由軟體置位使能接收,軟體清零則禁止接收

3

TB8

模式2和3中將要傳送的第9位資料(很少用)

2

RB8

模式2和3中接收第9位資料(很少用),模式1用來接收停止位

1

TI

傳送中斷標誌位,模式1下,在資料位最後一位傳送結束,開始傳送停止位時由硬體自動置1,必須通過軟體清零。也就是說,再發送前我們清零TI,傳送資料,資料傳送到停止位時,TI硬體置1,方便我們CPU查詢傳送完畢狀態。

0

RI

接收中斷標誌位,當接收電路接收到停止位的中間位置時,RI由硬體置1。也就是說,接收資料之前我們必須清零RI,接受資料到停止位的中間位置時,RI硬體置1,方便我們CPU查詢到接收狀態。

   對於串列埠的四種模式,模式1是最常用的,就是前邊提到的1位起始位,8位資料位和1位結束位。其他3種模式,真正遇到需要使用的時候大家再去查資料就行。

在我們使用IO口模擬串列埠通訊的時候,我們串列埠的波特率是使用定時器0的中斷體現出來的。在實際串列埠模組中,有一個專門的波特率發生器用來控制傳送資料的速度和讀取接收資料的速度。對於STC89C52RC微控制器來講,這個波特率發生器只能由定時器1或定時器2產生,而不能由定時器0產生,這和我們模擬的通訊是完全不同的概念。

如果用定時器2,需要配置額外的暫存器,預設是使用定時器1的,本章內容主要是使用定時器1作為波特率發生器來講解,方式1下的波特率發生器必須使用定時器1的模式2,也就是自動重灌載模式,定時器的初值具體的計算公式是:

           TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率

和波特率有關的還有一個暫存器,是一個電源管理暫存器PCON,它的最高位可以把波特率提高一倍,也就是如果寫PCON |=0x80以後,計算公式就成了:           TH1 = TL1 = 256 - 晶振值/12 /16 /波特率

數字的含義這裡解釋一下,256是8位資料的溢位值,也就是TL1的溢位值,11059200就是我們板子上微控制器的晶振,12是說1個機器週期是12個時鐘週期,值得關注的是這個16,重點說明。在IO口模擬串列埠通訊接收資料的時候,我們採集的是這一位資料的中間位置,而實際上串列埠模組比我們模擬的要複雜和精確一些。它採取的方式是把一位訊號採集16次,其中第7、8、9次取出來,這三次中其中兩次如果是高電平,那麼就認定這一位資料是1,如果兩次是低電平,那麼就認定這一位是0,這樣一旦受到意外干擾讀錯一次資料,也依然可以保證最終資料的正確性。

瞭解了串列埠採集模式,在這裡要給大家留一個思考題。“晶振值/12/2/16/波特率”這個地方計算的時候,出現不能除盡,或者出現小數怎麼辦,允許出現多大的偏差?把這部分理解了,也就理解了我們的晶振為何使用11.0592M了。

串列埠通訊的傳送和接收電路,主要了解一下他們在物理上有2個名字相同的SBUF暫存器,他們的地址也都是99H,但是一個用來做傳送緩衝,一個用來做接收緩衝。意思就是說,有2個房間,兩個房間的門牌號是一樣的,其中一個只出人不進人,另外一個只進人不出人,這樣的話,我們就可以實現UART的全雙工通訊,相互之間不會產生干擾。但是在邏輯上呢,我們每次只操作SBUF,微控制器會自動根據對它執行的是“讀”還是“寫”操作來選擇是接收SBUF還是傳送SBUF,後邊通過程式,我們就會徹底瞭解這個問題。

1.3 UART串列埠程式

一般情況下,編寫串列埠通訊程式的基本步驟如下所示:

1、配置串列埠為模式1。

2、配置定時器T1為模式2,即自動重灌模式。

3、確定波特率大小,計算定時器TH1和TL1的初值,如果有需要可以使用PCON進行波特率加倍。

4、開啟定時器控制暫存器TR1,讓定時器跑起來。

這個地方還要特別注意一下,就是在使用T1做波特率發生器的時候,千萬不要再使能T1的中斷了。

我們先來看一下由IO口模擬串列埠通訊直接改為使用硬體UART模組時程式程式碼,看看程式是不是簡單了很多,因為大部分的工作硬體模組都替我們做了。程式功能和IO口模擬的是完全一樣的。

 
  1. #include <reg52.h>

  2. void ConfigUART(unsigned int baud);

  3. void main ()

  4. {

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

  6.  
  7. while(1)

  8. {

  9. while (!RI); //等待接收完成

  10. RI = 0; //清零接收中斷標誌位

  11. SBUF = SBUF + 1; //接收到的資料+1後,傳送回去;

  12. //等號左邊的SBUF實際上就是傳送SBUF,因為對它的操作是“寫”;

  13. //等號右邊的是接收SBUF,因為對它的操作是“讀”。

  14. while (!TI); //等待發送完成

  15. TI = 0; //清零傳送中斷標誌位

  16. }

  17. }

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

  19. {

  20. SCON = 0x50; //配置串列埠為模式1

  21. TMOD &= 0x0F; //清零T1的控制位

  22. TMOD |= 0x20; //配置T1為模式2

  23. TH1 = 256 - (11059200/12/32) / baud; //計算T1過載值

  24. TL1 = TH1; //初值等於過載值

  25. ET1 = 0; //禁止T1中斷

  26. TR1 = 1; //啟動T1

  27. }

當然了,這個程式還是在主迴圈裡等待接收中斷標誌位和傳送中斷標誌位的方法來編寫的,而實際工程開發中,當然就不能這麼幹了,所以就用到了串列埠中斷,來看一下程式。

 
  1. #include <reg52.h>

  2. void ConfigUART(unsigned int baud);

  3. void main ()

  4. {

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

  6.  
  7. while(1);

  8. }

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

  10. {

  11. SCON = 0x50; //配置串列埠為模式1

  12. TMOD &= 0x0F; //清零T1的控制位

  13. TMOD |= 0x20; //配置T1為模式2

  14. TH1 = 256 - (11059200/12/32) / baud; //計算T1過載值

  15. TL1 = TH1; //初值等於過載值

  16. ET1 = 0; //禁止T1中斷

  17. TR1 = 1; //啟動T1

  18. ES = 1; //開啟串列埠中斷

  19. EA = 1; //開啟總中斷

  20. }

  21. void InterruptUART() interrupt 4

  22. {

  23. if (RI) //接收到位元組

  24. {

  25. RI = 0; //手動清零接收中斷標誌位

  26. SBUF = SBUF + 1;//接收資料+1發回去,左邊為傳送SBUF,右邊為接收SBUF。

  27. }

  28. if (TI) //位元組傳送完畢

  29. {

  30. TI = 0; //手動清零傳送中斷標誌位

  31. }

  32. }


大家可以試驗一下試試,看看是不是和前邊用IO口模擬通訊實現的效果一致,而主迴圈卻完全空出來了,我們就可以隨意新增其它功能程式碼進去。