天天看點

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

第一部分、章節目錄

1、通信涉及的幾個基礎概念

2、序列槽通信的基本概念、原理

3、S5PV210串行通信接口詳解

4、S5PV210串行通信程式設計實戰

5、uart stdio的移植

6、序列槽實驗燒錄問題總結

第二部分、章節介紹

1.7.1.通信涉及的幾個基礎概念

本節講述通信的發展曆史,通信的基本原理,重點是通信所涉及到的3對主要概念:同步和異步、電平信号和差分信号、并行和串行接口。

1.7.2.序列槽通信的基本概念

本節講述傳輸通信涉及到的基本概念,如:波特率、起始位、資料位、奇偶校驗位、停止位、RS232電平、TTL電平等。主要學習目的是讓大家對序列槽通信涉及到的主要概念做個了解,友善後續課程中使用。

1.7.3.序列槽通信的基本原理

本節講解序列槽通信的基本原理,讓大家明白序列槽通信時資訊時如何在通信線上傳播的,以及各種常見接口的通信線定義。

1.7.4.S5PV210串行通信接口詳解1

本節主要講述序列槽控制器的工作原理框圖,試圖讓大家明白序列槽控制器内部發送器、接收器、波特率發生器、移位器等子產品的工作原理。最後簡單介紹了流控。

1.7.5.S5PV210串行通信接口詳解2

本節詳細介紹了S5PV210這類進階SoC中序列槽的三種擴充模式:FIFO模式、DMA模式、IrDA模式,以及各種模式的工作原理、使用方法。

1.7.6.S5PV210串行通信接口詳解3

本節首先講述序列槽和中斷的關系,希望向大家引入序列槽發送/接收時的中斷模式、輪詢模式這兩個概念;然後詳細分析了序列槽控制器内部的時鐘來源、波特率計算等。

1.7.7.S5PV210串行通信程式設計實戰1

本節開始編寫序列槽通信的程式,首先分析了程式架構,然後重點分析了序列槽控制器的幾個主要寄存器的配置值,最後在C源程式中定義了通路序列槽控制器寄存器的主要宏定義。

1.7.8.S5PV210串行通信程式設計實戰2

本節接上節繼續來編寫序列槽通信的程式,主要包括Tx、Rx對應的GPIO的初始化,波特率的計算和設定,序列槽發送和接收程式的編寫等。最終實作210通過序列槽向外部發送資訊。

1.7.9.uart stdio的移植1

本節開始講基于S5PV210的序列槽輸入/輸出函數來移植stdio。首先講了什麼是标準輸入輸出,然後講了移植stdio的3種途徑,最後指出我們所使用的移植方法。

1.7.10.uart stdio的移植2

本節正式開始動手移植。分析了網絡下載下傳的别人移植好的printf函數源碼,重點講解了如何修改Makefile來使用printf函數源代碼。

1.7.11.uart stdio的移植3

本節補充講了上節課沒講到的連結腳本的問題。然後從C語言角度分析了printf函數實作原理、C語言可變參數、vsprintf函數中的實作細節等。

1.7.12.序列槽實驗燒錄問題總結

本節為補充章節,主要針對前面章節中代碼usb下載下傳無法正常運作,補充講下SD卡運作上燒錄運作代碼的實驗,并且希望大家看見實驗現象以明确代碼無誤。

第三部分、随堂記錄

1.7.1.通信涉及的幾個基礎概念

1.7.1.1、通信的發展曆史

(1)最早通信:烽火台、狼煙;信件;電子通信(電報、電話、網絡信号)

(2)通信中最重要的兩個方面:資訊表示、解析方法 +資訊的傳輸方法

(3)通信雙方事先需要約定好資訊的表示方法和解析方法,做到一緻,否則資訊不能有效傳遞

(4)信号的傳輸方法是指經過編碼後的通信資訊如何在傳輸媒體上傳輸的過程。

總結:通信過程其實分為3個步驟:首先發送方先按照資訊編碼方式對有效資訊進行編碼(程式設計成可以在通信線路上傳輸的信号形态),然後編碼後的資訊在傳輸媒體上進行傳輸,輸送給接收方;最後接收方接收到編碼資訊後進行解碼,解碼後得到可以了解的有效資訊。

1.7.1.2、電子通信概念1:同步通信和異步通信

(1)同步和異步的差別:首先很多地方都有同步和異步的概念,簡單來說就是發送方和接收方按照同一個時鐘節拍工作就叫同步,發送方和接收方沒有統一的時鐘節拍、而各自按照自己的節拍工作就叫異步。

(2)同步通信中,通信雙方按照統一節拍工作,是以配合很好;一般需要發送方給接收方發送資訊同時發送時鐘信号,接收方根據發送方給它的時鐘信号來安排自己的節奏。同步通信用在通信雙方資訊交換頻率固定,或者經常通信時。

(3)異步通信又叫異步通知。在雙方通信的頻率不固定時(有時3ms收發一次,有時3天才收發一次)不适合使用同步通信,而适合異步通信。異步通信時接收方不必一直在意發送方,發送方需要發送資訊時會首先給接收方一個資訊開始的起始信号,接收方接收到起始信号後就認為後面緊跟着的就是有效資訊,才會開始注意接收資訊,直到收到發送方發過來的結束标志。

1.7.1.3、電子通信概念2:電平信号和差分信号

(1)電平信号和差分信号是用來描述通信線路傳輸方式的。也就是說如何在通信線路上表達1和0.

(2)電平信号的傳輸線中有一個參考電平線(一般是GND),然後信号線上的信号值是由信号線電平和參考電平線的電壓差決定。

(3)差分信号的傳輸線中沒有參考電平,所有都是信号線。然後1和0的表達靠信号線之間的電壓差。

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

總結:電平信号的2根通信線之間的電平差異容易受到幹擾,傳輸容易失敗;差分信号不容易受到幹擾是以傳輸品質比較穩定,現代通信一般都使用差分信号,電平信号幾乎沒有了。

總結2:看起來似乎相同根數的通信線下,電平信号要比差分信号要快;但是實際還是差分信号快,因為差分信号抗幹擾能力強,是以1個發送周期更短。

1.7.1.4、電子通信概念3:并行接口和串行接口

(1)串行、并行主要是考慮通信線的根數,就是發送方和接收方同時可以傳遞的資訊量的多少

(2)譬如在電平信号下,1根參考電平線+1根信号線可以傳遞1位二進制;如果我們有3根線(2根信号線+1根參考線)就可以同時發送2位二進制;如果想同時發送8位二進制就需要9根線。

(3)在差分信号下,2根線(彼此差分)可以同時發送1位二進制;如果需要同時發送8位二進制,需要16根線。

總結:聽起來似乎并行接口比串行接口要快(串行接口一次隻能發送1位二進制,而并行接口一次可以發送多位二進制)要更優秀;但是實際上串行接口才是王道,用的比較廣。因為更省信号線,而且對傳輸線的要求更低、成本更低;而且串行時可以通過提高通信速度來提高總體通信性能,不一定非得要并行。

總結2:其實這麼多年發展,最終勝出的是:異步、串行、差分,譬如USB和網絡通信。

1.7.2.序列槽通信的基本概念

1.7.2.1、序列槽通信的特點:異步、電平信号、串行

(1)異步:序列槽通信的發送方和接收方之間是沒有統一的時鐘信号的。

(2)電平信号:序列槽通信出現的時間較早,速率較低,傳輸的距離較近,是以幹擾還不太明顯,是以當時使用了電平信号傳輸。後期出現的傳輸協定都改成差分信号傳輸了。

(3)串行通信:序列槽通信每次同時隻能傳輸1個二進制位。

1.7.2.2、RS232電平和TTL電平

(1)電平信号是用信号線電平減去參考線電平得到電壓差,這個電壓差決定了傳輸值是1還是0.

(2)在電平信号時多少V代表1,多少V代表0不是固定的,取決于電平标準。譬如RS232電平中-3V~-15V表示1;+3~+15V表示0;TTL電平則是+5V表示1,0V表示0.

(3)不管哪種電平都是為了在傳輸線上表示1和0. 差別在于适用的環境和條件不同。RS232的電平定義比較大,适合幹擾大、距離遠的情況;TTL電平電壓範圍小,适合距離近且幹擾小的情況。

(4)我們台式電腦後面的序列槽插座就是RS232接口的,在工業上用序列槽時都用這個,傳輸距離小于15米;TTL電平一般用在電路闆内部兩個晶片之間。

(5)對程式設計來說,RS232電平傳輸還是TTL電平是沒有差異的。是以電平标準對硬體工程師更有意義,而軟體工程師隻要略懂即可。(把TTL電平和RS232電平混接是不可以的)

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

1.7.2.3、波特率

(1)波特率(bandrate),指的是序列槽通信的速率,也就是序列槽通信時每秒鐘可以傳輸多少個二進制位。譬如每秒種可以傳輸9600個二進制位(傳輸一個二進制位需要的時間是1/9600秒,也就是104us),波特率就是9600.

(2)序列槽通信的波特率不能随意設定,而應該在一些值中去選擇。一般最常見的波特率是9600或者115200(低端單片機如51常用9600,高端單片機和嵌入式SoC一般用115200).

為什麼波特率不可以随便指定?主要是因為:

  • 第一,通信雙方必須事先設定相同的波特率這樣才能成功通信,如果發送方和接收方按照不同的波特率通信則根本收不到,是以波特率最好是大家熟知的而不是随意指定的。
  • 第二,常用的波特率經過長久發展,就形成了共識,大家常用就是9600或者115200.

1.7.2.4、起始位、資料位、奇偶校驗位、停止位

(1)序列槽通信時,收發是一個周期一個周期進行的,每周期傳輸n個二進制位。這一個周期就叫做一個通信單元,一個通信單元是由:起始位+資料位+奇偶校驗位+停止位組成的。

(2)起始位表示發送方要開始發送一個通信單元;資料位是一個通信單元中發送的有效資訊位;奇偶校驗位是用來校驗資料位,以防止資料位出錯的;停止位是發送方用來表示本通信單元結束标志的。

(3)起始位的定義是序列槽通信标準事先指定的,是由通信線上的電平變化來反映的。

(4)資料位是本次通信真正要發送的有效資料,序列槽通信一次發送多少位有效資料是可以設定的(一般可選的有6、7、8、9,99%情況下我們都是選擇8位資料位。因為我們一般通過序列槽發送的文字資訊都是ASCII碼編碼的,而ASCII碼中一個字元剛好編碼為8位。)

(5)奇偶校驗位是用來給資料位進行奇偶校驗(把待校驗的有效資料逐個位的加起來,總和為奇數奇偶校驗位就為1,總和為偶數奇偶校驗位就為0)的,可以在一定程度上防止位反轉。

(6)停止位的定義是序列槽通信标準事先指定的,是由通信線上的電平變化來反映的。常見的有1位停止位,1.5位停止位,2位停止位等。99%情況下都是用1位停止位。

總結:序列槽通信時因為是異步通信,是以通信雙方必須事先約定好通信參數,這些通信參數包括:波特率、資料位、奇偶校驗位、停止位(序列槽通信中起始位定義是唯一的,是以一般不用選擇)

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

1.7.3.序列槽通信的基本原理

1.7.3.0、補充通信概念:單工通信和雙工通信

(1)單工就是單方向,雙工就是雙方同時收發,同時隻能但方向但是方向可以改變叫半雙工

(2)如果隻能A發B收則單工,A發B收或者B發A收(兩個方向不能同時)叫半雙工,A發B收同時B發A收叫全雙工。

1.7.3.1、三根通信線:Rx Tx GND

(1)任何通信都要有資訊傳輸載體,或者是有線的或者是無線的。

(2)序列槽通信是有線通信,是通過序列槽線來通信的。

(3)序列槽通信線最少需要2根(GND和信号線),可以實作單工通信,也可以使用3根通信線(Tx、Rx、GND)來實作全雙工。

(4)一般開發闆都會引出SoC上序列槽引腳直接輸出的TTL電平的序列槽(X210開發闆沒有),插座用插針式插座,每個序列槽引出的都有3個線(Tx、Rx、GND),可以用這些插座直接連接配接外部的TTL電平的序列槽裝置。

1.7.3.2、收發雙方事先規定好通信參數(波特率、資料位、奇偶校驗位、停止位等)

(1)序列槽通信屬于基層基本性的通信規約,它自己本身不會去協商通信參數,需要通信前通信雙方事先約定好通信參數(一般4個最重要的)

(2)序列槽通信的任何一個關鍵參數設定錯誤,都會導緻通信失敗。譬如波特率調錯了,發送方發送沒問題,接收方也能接收,但是接收到全是亂碼···

1.7.3.3、資訊以二進制流的方式在信道上傳輸

(1)、序列槽通信的發送方每隔一定時間(時間固定為1/波特率,機關是秒)将有效資訊(1或者0)放到通信線上去,逐個二進制位的進行發送。

(2)接收方通過定時(起始時間由讀到起始位标志開始,間隔時間由波特率決定)讀取通信線上的電平高低來區分發送給我的是1還是0。依次讀取資料位、奇偶校驗位、停止位,停止位就表示這一個通信單元(幀)結束,然後中間是不定長短的非通信時間(發送方有可能緊接着就發送第二幀,也可能半天都不發第二幀,這就叫異步通信),下來就是第二幀·····

總結:第一,波特率非常重要,波特率錯了整個通信就亂套了;第二,資料位、奇偶校驗位、停止位也很重要,否則可能認不清資料。第三,通過序列槽不管發數字、還是文本還是指令還是什麼,都要先對發送内容進行編碼,編碼成二進制再進行逐個位的發送。

(3)序列槽發送的一般都是字元,一般都是ASCII碼編碼後的字元,是以一般設定資料位都是8,友善剛好一幀發送1個字元。

1.7.3.4、回顧:RS232電平和TTL電平的差別?串行通信為什麼是異步的?為什麼是串行而不是并行?

1.7.3.5、DB9接口介紹

(1)DB9接口是序列槽通信早期比較常用的一種規範化接口。

(2)串行通信在早期是計算機與外界通信的主要手段,那時候的計算機都有标準配置的序列槽以實作和外部通信。那時候就定義了一套标準的序列槽規約,DB9接口就是标準接口。

(3)DB9接口中有9根通信線,其中3根很重要,為GND、Tx、Rx,必不可少;剩餘6根都是和流控有關的,現代我們使用序列槽都是用來做調試一般都禁用流控,是以這6根沒用。

(4)現在一般使用序列槽時要記得把流控禁止掉,不然可能發生意想不到的問題。

1.7.4.S5PV210串行通信接口詳解1

1.7.4.0、序列槽的名稱

(1)S5PV210的資料手冊中序列槽控制器在section8.1

(2)=序列槽的官方名==稱叫:universal asynchronous reciver and transmitter,通用異步收發器

英文縮寫是uart,中文簡稱序列槽。

1.7.4.1、S5PV210的序列槽控制器工作原理框圖

(1)整個序列槽控制器包含transmitter和receiver兩部分,兩部分功能彼此獨立,transmitter負責210向外部發送資訊,receiver負責從外部接收資訊到210内部。

(2)總線角度來講,序列槽控制器是接在APB總線上的。對我們程式設計有影響的是:将來計算序列槽控制器的源時鐘時是以APB總線來計算的。

(3)transmitter由發送緩沖區和發送移位器構成。我們要發送資訊時,首先将資訊進行編碼(一般用ASCII碼)成二進制流,然後将一幀資料(一般是8位)寫入發送緩沖區(從這裡以後程式就不用管了,剩下的發送部分是硬體自動的),發送移位器會自動從發送緩沖區中讀取一幀資料,然後自動移位(移位的目的是将一幀資料的各個位分别拿出來)将其發送到Tx通信線上。

(4)receiver由接收緩沖區和接收移位器構成。當有人通過序列槽線向我發送資訊時,資訊通過Rx通信線進入我的接收移位器,然後接收移位器自動移位将該二進制位儲存入我的接收緩沖區,接收完一幀資料後receiver會産生一個中斷給CPU,CPU收到中斷後即可知道receiver接收滿了一幀資料,就會來讀取這幀資料。

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

總結:發送緩沖區和接收緩沖區是關鍵。發送移位器和接收移位器的工作都是自動的,不用程式設計控制的,是以我們寫序列槽的代碼就是:首先初始化(初始化的實質是讀寫寄存器)好序列槽控制器(包括發送控制器和接收控制器),然後要發送資訊時直接寫入發送緩沖區,要接收資訊時直接去接收緩沖區讀取即可。可見,序列槽底層的工作(譬如怎麼移位的、譬如起始位怎麼定義的、譬如TTL電平還是RS232電平等)對程式員是隐藏的,程式員不用去管。軟體工程師對序列槽操作的接口就是發送/接收緩沖區(實質就是寄存器,操作方式就是讀寫記憶體)

(5)序列槽控制器中有一個波特率發生器,作用是産生序列槽發送/接收的節拍時鐘。波特率發生器其實就是個時鐘分頻器,它的工作需要源時鐘(APB總線來),然後内部将源時鐘進行分頻(軟體設定寄存器來配置)得到目标時鐘,然後再用這個目标時鐘産生波特率(硬體自動的)。

1.7.4.2、自動流控(AFC:Auto flow control)

(1)為什麼需要流控?流控的目的是讓序列槽通信非常可靠,在發送方速率比接收方快的時候流控可以保證發送和接收不會漏掉東西。

(2)現在為什麼不用流控?現在計算機之間有更好更進階(usb、internet)的通訊方式,序列槽已經基本被廢棄了。現在序列槽的用途更多是SoC用來輸出調試資訊的。由于調試資訊不是關鍵性資訊、而且由于硬體發展序列槽本身速度已經相對慢的要死了,是以硬體都能協調發送和接收速率,是以流控已經失去意義了,是以現在基本都廢棄了。

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

1.7.5.S5PV210串行通信接口詳解2

  • 1.7.5.0、本來序列槽的功能就是上節講過的部分,但是後來的技術發展給序列槽疊加了一些進階功能,在像210這類的進階SoC的序列槽控制器中,都有這類進階功能。

1.7.5.1、FIFO模式及其作用

(1)典型的序列槽設計,發送/接收緩沖區隻有1位元組,每次發送/接收隻能處理1幀資料。這樣在單片機中沒什麼問題,但是到複雜SoC中(一般有作業系統的)就會有問題,會導緻效率低下,因為CPU需要不斷切換上下文。

(2)解決方案就是想辦法擴充序列槽控制器的發送/接收緩沖區,譬如将發送/接收緩沖器設定為64位元組,CPU一次過來直接給發送緩沖區64位元組的待發送資料,然後transmitter慢慢發,發完再找CPU再要64位元組。但是序列槽控制器本來的發送/接收緩沖區是固定的1位元組長度的,是以做了個變相的擴充,就是FIFO。

(3)FIFO就是first in first out,先進先出。fifo其實是一種資料結構,這裡這個大的緩沖區叫FIFO是因為這個緩沖區的工作方式類似于FIFO這種資料結構。

1.7.5.2、DMA模式及其作用

(1)DMA direct memory access,直接記憶體通路。DMA本來是DSP中的一種技術,DMA技術的核心就是在交換資料時不需要CPU參與,子產品可以自己完成。

(2)DMA模式要解決的問題和上面FIFO模式是同一個問題,就是序列槽發送/接收要頻繁的折騰CPU造成CPU反複切換上下文導緻系統效率低下。

(3)傳統的序列槽工作方式(無FIFO無DMA)效率是最低的,适合低端單片機;高端單片機上CPU事物繁忙是以都需要序列槽能夠自己完成大量資料發送/接收。這時候就需要FIFO或者DMA模式。FIFO模式是一種輕量級的解決方案,DMA模式适合大量資料迸發式的發送/接收時。

1.7.5.3、IrDA模式及其用法

(1)IrDA其實就是紅外,紅外就是紅外線通信(電視機、空調遙控器就是紅外通信的)。

(2)紅外通信的原理是發送方固定間隔時間向接收方發送紅外信号(表示1或0)或者不發送紅外信号(表示0或者1),接收方每隔固定時間去判斷有無紅外線信号來接收1和0.

(3)分析可知,紅外通信和序列槽通信非常像,都是每隔固定時間發送1或者0(判斷1或0的實體方式不同)給接收方來通信。是以210就利用序列槽通信來實作了紅外發送和接收。

(4)210的某個序列槽支援IrDA模式,開啟紅外模式後,我們隻需要向序列槽寫資料,這些資料就會以紅外光的方式向外發射出去(當然是需要一些外部硬體支援的),然後接收方接收這些紅外資料即可解碼得到我們的發送資訊。

1.7.6.S5PV210串行通信接口詳解3

1.7.6.1、串行通信與中斷的關系

(1)序列槽通信分為發送/接收2部分。發送方一般不需要(也可以使用)中斷即可完成發送,接收方必須(一般來說必須,也可以輪詢方式接收)使用中斷來接收。

(2)發送方可以選擇使用中斷,也可以選擇不使用中斷。

  • 使用中斷的工作情景是:發送方先設定好中斷并綁定一個中斷處理程式,然後發送方丢一幀資料給transmitter,transmitter發送耗費一段時間來發送這一幀資料,這段時間内發送方CPU可以去做别的事情,等transmitter發送完成後會産生一個TXD中斷,該中斷會導緻事先綁定的中斷處理程式執行,在中斷處理程式中CPU會切換回來繼續給transmitter放一幀資料,然後CPU切換離開;
  • 不使用中斷的工作情景是:發送方事先禁止TXD中斷(當然也不需要給相應的中斷處理程式了),發送方CPU給一幀資料到transmitter,然後transmitter耗費一段時間來發送這幀資料,這段時間CPU在這等着(CPU沒有切換去做别的事情),待發送方發送完成後CPU再給它一幀資料繼續發送直到所有資料發完。CPU是怎麼知道transmitter已經發送完了?原來是有個狀态寄存器,狀态寄存器中有一個位叫發送緩沖區空标志,transmitter發送完成(發送緩沖區空了)就會給這個标志位置位,CPU就是通過不斷查詢這個标志位為1還是0來指導發送是否已經完成的。
(3)因為序列槽通信是異步的,異步的意思就是說發送方占主導權。也就是說發送方随時想發就能發,但是接收方隻有時刻等待才不會丢失資料。是以這個差異就導緻發送方可以不用中斷,而接收方不得不使用中斷模式。

1.7.6.2、210串行通信接口的時鐘設計

(1)序列槽通信為什麼需要時鐘?因為序列槽通信需要一個固定的波特率,是以transmitter和receiver都需要一個時鐘信号。

(2)時鐘信号從哪裡來?源時鐘信号是外部APB總線(PCLK_PSYS,66MHz)提供給序列槽子產品的(這就是為什麼我們說序列槽是挂在APB總線上的),然後進到序列槽控制器内部後給波特率發生器(實質上是一個分頻器),在波特率發生器中進行分頻,分頻後得到一個低頻時鐘,這個時鐘就是給transmitter和receiver使用的。

(3)序列槽通信中時鐘的設定主要看寄存器設定。重點的有:寄存器源設定(為序列槽控制器選擇源時鐘,一般選擇為PCLK_PSYS,也可以是SCLK_UART),還有波特率發生器的2個寄存器。

(4)波特率發生器有2個重要寄存器:UBRDIVn和UDIVSLOTn,其中UBRDIVn是主要的設定波特率的寄存器,UDIVSLOTn是用來輔助設定的,目的是為了校準波特率的。

1.7.7.S5PV210串行通信程式設計實戰1

1.7.7.1、整個程式流程分析

(1)整個序列槽通信相關程式包含2部分:uart_init負責初始化序列槽,uart_putc負責發送一個位元組

1.7.7.2、序列槽控制器初始化關鍵步驟

(1)初始化序列槽的Tx和Rx引腳所對應的GPIO(查原理圖可知Rx和Rx分别對應GPA0_1和GPA0_0)

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

(2)GPA0CON(0xE0200000),bit[3:0] = 0b0010 bit[7:4] = 0b0010

(3)初始化這幾個關鍵寄存器UCON0 ULCON0 UMCON0 UFCON0 UBRDIV0 UDIVSLOT0

《1.7.ARM裸機第七部分-序列槽通信詳解》第一部分、章節目錄第二部分、章節介紹第三部分、随堂記錄

1.7.7.3、主要的幾個寄存器

(1)ULCON0 = 0x3 // 0校驗位、8資料位、1停止位

(2)UCON = 0x5 // 發送和接收都是polling mode

(3)UMCON0 = 0x0 // 禁止modem、afc

(4)UFCON0 = 0x0 // 禁止FIFO模式

(5)UBRDIV0和UDIVSLOT0和波特率有關,要根據公式去算的

1.7.7.4、在C源檔案中定義通路寄存器的宏

定義好了通路寄存器的宏之後,将來寫代碼時直接使用即可。

1.7.8.S5PV210串行通信程式設計實戰2

#define GPA0CON		0xE0200000
#define UCON0 		0xE2900004
#define ULCON0 		0xE2900000
#define UMCON0 		0xE290000C
#define UFCON0 		0xE2900008
#define UBRDIV0 	0xE2900028
#define UDIVSLOT0	0xE290002C
#define UTRSTAT0	0xE2900010
#define UTXH0		0xE2900020	
#define URXH0		0xE2900024	

#define rGPA0CON	(*(volatile unsigned int *)GPA0CON)
#define rUCON0		(*(volatile unsigned int *)UCON0)
#define rULCON0		(*(volatile unsigned int *)ULCON0)
#define rUMCON0		(*(volatile unsigned int *)UMCON0)
#define rUFCON0		(*(volatile unsigned int *)UFCON0)
#define rUBRDIV0	(*(volatile unsigned int *)UBRDIV0)
#define rUDIVSLOT0	(*(volatile unsigned int *)UDIVSLOT0)
#define rUTRSTAT0		(*(volatile unsigned int *)UTRSTAT0)
#define rUTXH0		(*(volatile unsigned int *)UTXH0)
#define rURXH0		(*(volatile unsigned int *)URXH0)

// 序列槽初始化程式
void uart_init(void)
{
	// 初始化Tx Rx對應的GPIO引腳
	rGPA0CON &= ~(0xff<<0);			// 把寄存器的bit0~7全部清零
	rGPA0CON |= 0x00000022;			// 0b0010, Rx Tx
	
	// 幾個關鍵寄存器的設定
	rULCON0 = 0x3;
	rUCON0 = 0x5;
	rUMCON0 = 0;
	rUFCON0 = 0;
	
	// 波特率設定	DIV_VAL = (PCLK / (bps x 16))-1   
	// PCLK_PSYS用66MHz算		餘數0.8
	//rUBRDIV0 = 34;	
	//rUDIVSLOT0 = 0xdfdd;
	
	// PCLK_PSYS用66.7MHz算		餘數0.18
	// DIV_VAL = (66700000/(115200*16)-1) = 35.18
	rUBRDIV0 = 35;
	// (rUDIVSLOT中的1的個數)/16=上一步計算的餘數=0.18
	// (rUDIVSLOT中的1的個數 = 16*0.18= 2.88 = 3
	rUDIVSLOT0 = 0x0888;		// 3個1,查官方推薦表得到這個數字
}


// 序列槽發送程式,發送一個位元組
void uart_putc(char c)
{                  	
	// 序列槽發送一個字元,其實就是把一個位元組丢到發送緩沖區中去
	// 因為序列槽控制器發送1個位元組的速度遠遠低于CPU的速度,是以CPU發送1個位元組前必須
	// 确認序列槽控制器目前緩沖區是空的(意思就是序列槽已經發完了上一個位元組)
	// 如果緩沖區非空則位為0,此時應該循環,直到位為1
	while (!(rUTRSTAT0 & (1<<1)));
	rUTXH0 = c;
}

// 序列槽接收程式,輪詢方式,接收一個位元組
char uart_getc(void)
{
	while (!(rUTRSTAT0 & (1<<0)));
	return (rURXH0 & 0x0f);
}
           

1.7.8.1、序列槽Tx、Rx對應的GPIO的初始化

給GPA0CON的相應bit位指派為相應值,用C語言位操作來完成。
           

1.7.8.2、UCON、ULCON、UMCON、UFCON等主要控制寄存器

依據上節中分析的值進行依次設定即可。
           

1.7.8.3、波特率的計算和設定

(1)第一步,用PCLK_PSYS和目标波特率去計算DIV_VAL: DIV_VAL = (PCLK / (bps x 16)) ?1

(2)第二步,UBRDIV0寄存器中寫入DIV_VAL的整數部分

(3)第三步,用小數部分*16得到1個個數,查表得uBDIVSLOT0寄存器的設定值

1.7.8.4、序列槽發送和接收函數的編寫

(1)寫發送函數,主要發送前要用while循環等待發送緩沖區為空才能發送。

1.7.8.5、綜合調試

注意Makefile的修改。

1.7.8.6、擴充練習-更改波特率後再調試

自己練習。注意程式中改了波特率後,SecureCRT也要相應修改,不然收不到東西。

1.7.9.uart stdio的移植1

1.7.9.1、什麼是stdio

(1)#include <stdio.h>

(2)stdio:standard input output,标準輸入輸出

(3)标準輸入輸出就是作業系統定義的預設的輸入和輸出通道。一般在PC機的情況下,标準輸入指的是鍵盤,标準輸出指的是螢幕。

(4)printf函數和scanf函數可以和底層輸入/輸出函數綁定,然後這兩個函數就可以和stdio綁定起來。也就是說我們直接調用printf函數輸出,内容就會被從标準輸出輸出出去。

(5)在我們這裡,标準輸出當然不是螢幕了,而是序列槽。标準輸入也不是鍵盤,而是序列槽。

1.7.9.2、printf函數的工作原理

(1)printf函數工作時内部實際調用了2個關鍵函數:

一個是vsprintf函數(主要功能是格式化列印資訊,最終得到純字元串格式的列印資訊等待輸出),另一個就是真正的輸出函數putc(操控标準輸出的硬體,将資訊發送出去)

1.7.9.3、移植printf函數的三種思路

(1)我們希望在我們的開發闆上使用printf函數進行(序列槽)輸出,使用scanf函數進行(序列槽)輸入,就像在PC機上用鍵盤和螢幕進行輸入輸出一樣。是以需要移植printf函數/scanf函數

(2)我們說的移植而不是編寫,我們不希望自己完全從新編寫而是想盡量借用也有的代碼(叫移植)

(3)一般移植printf函數可以有3個途徑擷取printf的實作源碼:最原始最原本的來源就是linux核心中的printk。難度較大、關鍵是麻煩;稍微簡單些的方法是從uboot中移植printf;更簡單的方法就是直接使用别人移植好的。

(3)我們課程中使用第三種方法,别人移植好的printf函數來自于友善之臂的Tiny210的裸機教程中提供的。

1.7.9.4、移植好的printf介紹

參考視訊中講解。

#include "vsprintf.h"
#include "string.h"
#include "printf.h"

extern void putc(unsigned char c);
extern unsigned char getc(void);

#define	OUTBUFSIZE	1024
#define	INBUFSIZE	1024

// 自己定義了2個全局變量數組,分别作為發送/接收緩沖區。
// 将來發送時先将要發送的資訊格式化送入發送緩沖區,然後putc函數直接從發送緩沖區取
// 資料發送出去。
static char g_pcOutBuf[OUTBUFSIZE];
static char g_pcInBuf[INBUFSIZE];


// putc函數真正和輸出裝置綁定,這個函數需要我們自己去實作,這就是移植的關鍵
// printf("a = %d, b = %s.\n", a, p);
int printf(const char *fmt, ...)
{
	int i;
	int len;
	va_list args;

	va_start(args, fmt);
	len = vsprintf(g_pcOutBuf,fmt,args);
	va_end(args);
	for (i = 0; i < strlen(g_pcOutBuf); i++)
	{
		putc(g_pcOutBuf[i]);
	}
	return len;
}



int scanf(const char * fmt, ...)
{
	int i = 0;
	unsigned char c;
	va_list args;
	
	while(1)
	{
		c = getc();
		putc(c);
		if((c == 0x0d) || (c == 0x0a))
		{
			g_pcInBuf[i] = '\0';
			break;
		}
		else
		{
			g_pcInBuf[i++] = c;
		}
	}
	
	va_start(args,fmt);
	i = vsscanf(g_pcInBuf,fmt,args);
	va_end(args);

	return i;
}
           

1.7.10.uart stdio的移植2

1.7.10.1、修改Makefile進行printf移植

1.7.10.2、Makefile及gcc的庫檔案介紹

1.7.10.3、多檔案夾裸機工程的結構解析

1.7.10.4、編譯運作及測試

1.7.11.uart stdio的移植3

1.7.11.1、在移植後的uart stdio項目中添加link.lds連結腳本,指定連接配接位址到0xd0020010

1.7.11.2、gcc可變參數及va_arg介紹

(1)printf函數中首先使用了C語言的可變參數va_start/va_arg/va_end;

(2)建議大家先去baidu“C語言可變參數”,然後按照别人的教程、部落格實際寫幾個簡單的變參的使用示例,先明白可變參數怎麼工作,然後再來分析這裡。

1.7.11.3、vsprintf函數詳解

printf

vsprintf

vsnprintf

number

vsprintf函數的作用是按照我們的printf傳進去的格式化标本,對變參進行處理,然後将之格式化後緩存在一個事先配置設定好的緩沖區中。

printf後半段調用putc函數将緩沖區中格式化好的字元串直接輸出到标準輸出。

1.7.12.序列槽實驗燒錄問題總結

1.7.12.1、usb下載下傳的問題

(1)USB下載下傳時在Win7 X64系統下,下載下傳前面章節的小代碼時沒問題,下載下傳序列槽通信的小代碼時也沒問題,下載下傳uart stdio的移植就有問題了。有時候下載下傳不動、有時候能下載下傳但是不運作、有時候又正常下載下傳運作。我已經試過下載下傳其他的dnw或dnw驅動更新,都無法解決。

1.7.12.2、SD卡鏡像燒錄

(1)SD卡燒錄鏡像做裸機實驗,在第四部分1.4.2節中有講過。

(2)本次我們在Windows下燒錄(linux下的燒錄參考以前的)

(3)Windows下燒錄鏡像是使用九鼎提供的工具(X210CD光牒資料\A盤\tools\x210_Fusing_Tool.exe),注意運作時右鍵“以管理者身份運作”。

1.7.12.3、啟動方式設定

(1)X210開發闆的啟動方式的選擇,請參考1.2.11節。其實就是OM5的問題,OM5設定為VCC則從USB啟動,OM5設定成GND,則從iNand/SD卡啟動。

(2)開發闆選擇從iNand啟動後,還要確定iNand中uboot是被擦除的。

(3)關于如何破壞uboot的問題,大家可以參考之前課程中講的在linux/android系統中破壞uboot的方法。我之前講過在uboot中破壞uboot的方法:movi write u-boot 0x30000000。很多同學反映擦除後錯亂,進不了系統也從SD卡啟動不了,隻能通過USB刷機來解決。後來又分析,改為:mw 0x30000000 0x0 0x100000,然後再movi write u-boot 0x30000000 。但是回報結果有人說可以了,有人說還是不行·······

(4)不管怎麼擦除uboot,總之首先確定你的闆子SD卡啟動是成功的。怎麼確定?先用SD卡燒錄啟動之前的LED閃爍的項目,確定看到現象就證明燒錄SD卡方法和啟動SD卡都成功了,再做本節課的實驗。

1.7.12.4、連結腳本的影響

1.7.12.5、bin檔案大于16KB怎麼辦?

通過USB下載下傳最多也隻能下載下傳96KB大小的bin,如果bin大于96KB肯定SRAM放不下會出錯。如果用SD卡啟動,那麼mkv210_image.c決定了bin檔案最大不能超16KB。

超過了怎麼辦?2種解法:

  • 第一,在USB下載下傳時,可以先下載下傳一個x210_usb.bin,然後再将裸機程式連接配接到0x23E00000,然後再修改dnw中下載下傳位址,将裸機代碼下載下傳到0x23E00000運作。(這時不需要重定位了)
  • 第二,在SD卡啟動時,将整個裸機工程分為2部分;第一部分大小16KB以内,第二部分放剩下的(放在SD卡的後面的某個扇區開始的位置,譬如放在第50個扇區開始的位置),然後在裸機代碼中進行重定位(SD卡中重定位)。這個暫時沒講,以後如果有用到就講。

上述筆記均來自朱有鵬老師課程,僅僅是想找個地方記筆記而已啦,内容僅供大家參考!

繼續閱讀