19.S5PV210串口基本操作


19.1.通信涉及的幾個基礎概念
(1)通信的發展歷史;最早通信是烽火臺狼煙,郵局信件;現代化通信是電子通信,譬如電報/電話/互聯網。通信三要素=信息表示方法+信息解析方法+信息傳輸方法;通信雙方事先需要約定好信息的表示方法和解析方法,否則信息不能有效傳遞;信號的傳輸方法是指經過編碼後的通信信息如何在傳輸介質上傳輸的過程。通信的標準流程,首先發送方先按照信息編碼方式對有效信息進行編碼(編程成可以在通信線路上傳輸的信號形態);然後編碼後的信息在傳輸介質上進行傳輸,輸送給接收方;最後接收方接收到編碼信息後進行解碼,解碼後得到可以理解的有效信息。
(2)同步通信和異步通信;發送方和接收方按照同一個時鐘節拍工作就叫同步,發送方和接收方各自按照自己的時鐘節拍工作就叫異步。同步通信中,通信雙方按照統一節拍工作,一般需要發送方給接收方發送信息同時發送時鐘信號,接收方根據發送方給它的時鐘信號來安排自己的節奏,同步通信一般應用於通信雙方信息交換頻率固定場景或通信雙方經常通信場景。異步通信中,接收方不必一直在意發送方,發送方發送信息時需要首先給接收方起始信號,接收方接收到起始信號後就認爲後面緊跟有效信息,纔會開始注意接收信息,直到收到發送方發過來的結束標誌,異步通信一般應用於通信雙方的通信頻率不固定場景。
(3)電平信號和差分信號;電平信號和差分信號是用來描述通信線路傳輸方式,即如何在通信線路上表達1和0;電平信號的傳輸線中有一個參考電平線(一般是GND),然後信號線上的信號值是由信號線電平和參考電平線的電壓差決定;差分信號的傳輸線中沒有參考電平,所有都是信號線,然後1和0的表達靠信號線之間的電壓差。電平信號的2根通信線之間的電平差異容易受到干擾,差分信號不容易受到干擾因此傳輸質量比較穩定,現代通信一般都使用差分信號,電平信號幾乎沒有了;看起來似乎相同根數的通信線下,電平信號要比差分信號要快,但是實際還是差分信號快,因爲差分信號抗干擾能力強,因此1個發送週期更短。
(4)並行接口和串行接口;串行和並行主要是考慮通信線的根數,即發送方和接收方同時可以傳遞的信息量的多少;譬如在電平信號下,1根參考電平線+1根信號線可以傳遞1位二進制,如果我們有3根線(2根信號線+1根參考線)就可以同時發送2位二進制,如果想同時發送8位二進制就需要9根線;在差分信號下,2根線(彼此差分)可以同時發送1位二進制;如果需要同時發送8位二進制,需要16根線。聽起來似乎並行接口比串行接口要快,但實際上串行接口才是王道,用的比較廣,因爲更省信號線,而且對傳輸線的要求更低,成本更低,而且串行時可以通過提高通信速度來提高總體通信性能,不一定非得要並行。
(5)單工通信和雙工通信;單工就是單方向通信,雙工就是雙方同時收發,只能單方向通信但是方向可以改變叫半雙工;如果只能A發B收則單工,A發B收或者B發A收(兩個方向不能同時)叫半雙工,A發B收同時B發A收叫全雙工。


19.2.串口通信的基本概念
(1)串口通信爲異步+電平信號+串行;異步=串口通信的發送方和接收方之間是沒有統一的時鐘信號的;電平信號=串口通信使用了電平信號傳輸;串行通信=串口通信每次同時只能傳輸1個bit位。RS232電平和TTL電平;電平信號是用信號線電平減去參考線電平得到電壓差,該電壓差決定了傳輸值是1還是0;在電平信號時多少V代表1,多少V代表0不是固定的,取決於電平標準;譬如RS232電平中-3V~-15V表示1,+3~+15V表示0;TTL電平則是+5V表示1,0V表示0。不管哪種電平都是爲了在傳輸線上表示1和0,區別在於適用的環境和條件不同;RS232的電平定義比較大,適合干擾大+距離遠的情況;TTL電平電壓範圍小,適合距離近+干擾小的情況。我們臺式電腦後面的串口插座就是RS232接口的,在工業上用串口時都用這個,傳輸距離小於15米;TTL電平一般用在電路板內部兩個芯片之間進行通信。
(2)波特率(baudrate);指的是串口通信的速率,也就是串口通信時每秒鐘可以傳輸多少個二進制位,譬如每秒種可以傳輸9600個二進制位(傳輸1個二進制位需要的時間是1/9600秒,也就是104us),波特率就是9600。最常見的串口波特率是9600或者115200(低端單片機如51常用9600,高端單片機和嵌入式SoC一般用115200)。波特率不可以隨便指定的原因,首先通信雙方必須事先設定相同的波特率這樣才能成功通信,如果發送方和接收方按照不同的波特率通信則根本收不到,因此波特率最好是大家熟知的而不是隨意指定的,其次常用的波特率經過長久發展,就形成了共識,大家常用就是9600或者115200。
(3)起始位+數據位+奇偶校驗位+停止位;串口通信時的數據收發是以週期爲單位進行傳輸的,每個週期傳輸n個二進制位,該週期就叫做1個通信單元,1個通信單元=起始位+數據位+奇偶校驗位+停止位。起始位表示發送方要開始發送1個通信單元;數據位是1個通信單元中發送的有效信息位;奇偶校驗位是用來校驗數據位,以防止數據位出錯的;停止位是發送方用來表示本通信單元結束標誌的。
(4)起始位的定義是串口通信標準事先指定的,是由通信線上的電平變化來反映的。數據位是本次通信真正要發送的有效數據,串口通信每次發送多少位有效數據是可以設定的,有6/7/8/9可選,99%情況下我們都是選擇8位數據位,因爲我們一般通過串口發送的文字信息都是ASCII碼編碼的,而ASCII碼中1個字符剛好編碼爲8位。奇偶校驗位是用來給數據位進行奇偶校驗;把待校驗的有效數據逐個位的加起來,總和爲奇數奇偶校驗位就爲1,總和爲偶數奇偶校驗位就爲0;可以在一定程度上防止位反轉。停止位的定義是串口通信標準事先指定的,是由通信線上的電平變化來反映的,常見的停止位有1位/1.5位/2位等,99%情況下都是用1位停止位。
(5)串口通信時因爲是異步通信,所以通信雙方必須事先約定好通信參數,這些通信參數=波特率+數據位+奇偶校驗位+停止位,串口通信中起始位定義是唯一的,所以一般不用選擇。


19.3.串口通信的基本原理
(1)三根通信線Rx+Tx+GND;任何通信都要有信息傳輸載體,或者是有線的或者是無線的;串口通信是有線通信,是通過串口線來通信的;串口通信線最少需要2根(GND和信號線),可以實現單工通信,也可以使用3根通信線Tx+Rx+GND來實現全雙工;一般開發板都會引出SoC上串口引腳直接輸出的TTL電平的串口,插座用插針式插座,每個串口引出的都有3個線Tx+Rx+GND,可以用這些插座直接連接外部的TTL電平的串口設備。
(2)收發雙方事先規定好通信參數=波特率+數據位+奇偶校驗位+停止位等;串口通信屬於基層基本性的通信規約,它自己本身不會去協商通信參數,需要通信前通信雙方事先約定好通信參數;串口通信的任何一個關鍵參數設置錯誤,都會導致通信失敗,譬如波特率調錯了,發送方發送沒問題,接收方也能接收,但是接收到全是亂碼。
(3)信息以二進制流的方式在信道上傳輸;串口通信的發送方每隔一定時間(時間固定爲1/波特率,單位是秒)將有效信息(1或者0)放到通信線上去,逐個二進制位的進行發送;接收方通過定時(起始時間由讀到起始位標誌開始,間隔時間由波特率決定)讀取通信線上的電平高低來區分發送給我的是1還是0,依次讀取數據位+奇偶校驗位+停止位,停止位就表示這個通信單元(幀)結束,然後中間是不定長短的非通信時間,發送方有可能緊接着就發送第二幀,也可能半天都不發第二幀。
(4)波特率非常重要,波特率錯了整個通信就亂套了;數據位+奇偶校驗位+停止位也很重要,否則可能認不清數據;通過串口不管發數字/文本/命令,都要先對發送內容進行編碼,編碼成二進制再進行逐個位的發送;串口發送的一般都是字符,一般都是ASCII碼編碼後的字符,所以一般設置數據位都是8,方便剛好一幀發送1個字符。
(5)DB9接口介紹;DB9接口是串口通信早期比較常用的一種規範化接口;串行通信在早期是計算機與外界通信的主要手段,那時候的計算機都有標準配置的串口以實現和外部通信,那時候就定義了一套標準的串口規約,DB9接口就是標準接口;DB9接口中有9根通信線,其中3根很重要GND+Tx+Rx,剩餘6根都是和流控有關的,現代我們使用串口都是用來做調試一般都禁用流控;現在一般使用串口時要記得把流控禁止掉,不然可能發生意想不到的問題。


19.4.S5PV210串行通信接口
(1)串口名稱爲通用異步收發器,英文縮寫是uart,中文簡稱串口;整個串口控制器包含transmitter和receiver兩部分,兩部分功能彼此獨立,transmitter負責210向外部發送信息,receiver負責從外部接收信息到210內部;串口控制器是接在APB總線上的,將來計算串口控制器的源時鐘時是以APB總線來計算的(見圖1)。
(2)transmitter由發送緩衝區和發送移位器構成,我們要發送信息時,首先將信息進行編碼(一般用ASCII碼)成二進制流,然後將一幀數據(一般是8位)寫入發送緩衝區(從這裏以後程序就不用管了,剩下的發送部分是硬件自動的),發送移位器會自動從發送緩衝區中讀取一幀數據,然後自動移位(移位的目的是將一幀數據的各個位分別拿出來)將其發送到Tx通信線上。
(3)receiver由接收緩衝區和接收移位器構成,當有人通過串口線向我發送信息時,信息通過Rx通信線進入我的接收移位器,然後接收移位器自動移位將該二進制位保存入我的接收緩衝區,接收完一幀數據後receiver會產生一箇中斷給CPU,CPU收到中斷後即可知道receiver接收滿了一幀數據,就會來讀取這幀數據。
(4)發送緩衝區和接收緩衝區是關鍵,發送移位器和接收移位器的工作都是自動的,不用編程控制的,則我們首先初始化串口控制器(包括髮送控制器和接收控制器),然後要發送信息時直接寫入發送緩衝區,要接收信息時直接去接收緩衝區讀取即可;串口底層的工作對程序員是隱藏的,軟件工程師對串口操作的接口就是發送/接收緩衝區(實質就是寄存器,操作方式就是讀寫內存)。
(5)串口控制器中有一個波特率發生器,作用是產生串口發送/接收的節拍時鐘;波特率發生器其實就是個時鐘分頻器,它的工作需要源時鐘(APB總線來),然後內部將源時鐘進行分頻(軟件設置寄存器來配置)得到目標時鐘,然後再用這個目標時鐘產生波特率(硬件自動的)。
(6)自動流控AFC;流控的目的是讓串口通信非常可靠,在發送方速率比接收方快的時候流控可以保證發送和接收不會漏幀;現在計算機之間有更好更高級的通訊方式,串口已經基本被廢棄了;現在串口的用途更多是SoC用來輸出調試信息的,由於調試信息不是關鍵性信息,而且由於硬件的發展串口本身的速度相對來說非常慢,所以硬件自己都能協調發送和接收速率,因此流控已經失去意義了,所以現在基本都廢棄了。


19.5.S5PV210串口高級功能
(1)FIFO模式及其作用;在典型的串口設計中,發送/接收緩衝區只有1字節,每次發送/接收只能處理1幀數據,這樣在單片機中沒什麼問題,但是到有操作系統的複雜SoC中會導致效率低下,因爲CPU需要不斷切換上下文;解決方案就是想辦法擴展串口控制器的發送/接收緩衝區,譬如將發送/接收緩衝器設置爲64字節,CPU一次過來直接給發送緩衝區64字節的待發送數據,然後transmitter慢慢發,發完再找CPU再要64字節;但是串口控制器本來的發送/接收緩衝區是固定的1字節長度的,所以做了個變相的擴展,就是FIFO,因爲該緩衝區的工作方式類似於FIFO這種先進先出的數據結構。
(2)DMA模式及其作用;DMA直接內存訪問,DMA本來是DSP中的一種技術,DMA技術的核心就是在交換數據時不需要CPU參與,模塊可以自己完成;DMA模式要解決的問題是串口發送/接收要頻繁的折騰CPU造成CPU反覆切換上下文導致系統效率低下;傳統的串口工作方式(無FIFO無DMA)效率是最低的,適合低端單片機;高端單片機上CPU事物繁忙所以都需要串口能夠自己完成大量數據發送/接收,這時候就需要FIFO或者DMA模式;FIFO模式是一種輕量級的解決方案,DMA模式適合大量數據迸發式的發送/接收時。
(3)IrDA模式及其用法;IrDA其實就是紅外即紅外線通信,紅外通信的原理是發送方固定間隔時間向接收方發送紅外信號(表示1或0)或者不發送紅外信號(表示0或者1),接收方每隔固定時間去判斷有無紅外線信號來接收1和0;紅外通信和串口通信非常像,都是每隔固定時間發送1或者0(判斷1或0的物理方式不同)給接收方來通信,因此210就利用串口通信來實現了紅外發送和接收;210的某個串口支持IrDA模式,開啓紅外模式後,我們只需要向串口寫數據,這些數據就會以紅外光的方式向外發射出去(需要紅外發射和接收管的外部硬件支持),然後接收方接收這些紅外數據即可解碼得到我們的發送信息。


19.6.S5PV210串口中斷和時鐘源
(1)串行通信和中斷;串口通信分爲發送/接收2部分,發送方一般不需要(也可以使用)中斷即可完成發送,接收方必須(也可以輪詢方式接收)使用中斷來接收數據。因爲串口通信是異步的,異步的意思就是說發送方佔主導權,即發送方隨時想發就能發,但是接收方只有時刻等待纔不會丟失數據,所以該差異就導致發送方可以不用中斷,而接收方不得不使用中斷模式。
(2)發送方使用中斷的工作情景,發送方先設置好中斷並綁定中斷處理程序,然後發送方發送1幀數據給transmitter,然後transmitter發送耗費時間來發送該幀數據,該段時間內發送方CPU可以去做別的事情,等transmitter發送完成後會產生TXD中斷,該中斷會導致事先綁定的中斷處理程序執行,在中斷處理程序中CPU會切換回來繼續給transmitter發送1幀數據,然後CPU切換離開。
(3)不使用中斷的工作情景,發送方事先禁止TXD中斷,發送方CPU給1幀數據到transmitter,然後transmitter耗費時間來發送該幀數據,該段時間CPU在這等着,CPU沒有切換去做別的事情,發送方發送完成後CPU再給它1幀數據繼續發送直到所有數據發完,CPU通過不斷查詢狀態寄存器中的發送緩衝區空標誌爲1還是0來指導發送是否已經完成,該狀態寄存器若transmitter發送完成(發送緩衝區空了)硬件會自動給該標誌位置位。
(4)S5PV210串行通信接口的時鐘設計;串口通信需要1個固定的波特率,所以transmitter和receiver都需要1個時鐘信號;串口的源時鐘信號是外部APB總線(PCLK_PSYS=66MHz)提供的,源時鐘進入到串口控制器內部後給波特率發生器,在波特率發生器中進行分頻,分頻後得到1個低頻時鐘,該時鐘就是給transmitter和receiver使用的。
(5)串口通信中時鐘的設置主要看寄存器設置;重點有串口時鐘源寄存器設置(爲串口控制器選擇源時鐘,一般選擇爲PCLK_PSYS,也可以是SCLK_UART);其次還有波特率發生器的2個寄存器,波特率發生器有2個重要寄存器UBRDIVn和UDIVSLOTn,其中UBRDIVn是主要的設置波特率的寄存器,UDIVSLOTn是用來輔助設置的,目的是爲了校準波特率。


19.7.S5PV210簡單的串口使用
(1)整個串口通信程序=uart_init負責初始化串口+uart_putc負責發送1個字節;我們操作GEC210的串口0;首先初始化串口的Tx(對應原理圖上的GPA0_1引腳)和Rx(對應原理圖上的GPA0_0引腳)引腳所對應的GPIO(GPA0CON(0xE0200000)的bit[3:0]=0b0010;bit[7:4]=0b0010)。
(2)初始化這幾個關鍵寄存器=ULCON0(0xE290_0000;0x03=普通模式+0校驗位+1停止位+8數據位)+UCON0(0xE290_0004;0x05=選擇PCLK_PSYS做爲串口時鐘源+不使用迴環模式+發送和接收都是中斷/輪詢模式(此處均使用輪詢模式))+UMCON0(0xE290_000C;0x00=禁止流控的一切配置)+UFCON0(0xE290_0008;0x00=禁止FIFO模式)+UBRDIV0(0xE290_0028;設置波特率)+UDIVSLOT0(0xE290_002C;設置波特率)。
(3)在C源文件中定義訪問寄存器的宏,定義好了訪問寄存器的宏之後,將來寫代碼時直接使用即可;UTRSTAT0(0xE290_0010;發送和接收緩衝區空或滿狀態寄存器)+UTXH0(0xE290_0020;數據位接收緩衝區寄存器)+URXH0(0xE290_0024;數據位發送緩衝區寄存器)。
(4)波特率的計算和設置;用PCLK_PSYS(66MHz或66.7MHz)和目標波特率去計算DIV_VAL的值(DIV_VAL=(PCLK/(bps*16))-1);DIV_VAL的整數部分寫入UBRDIV0寄存器;DIV_VAL的小數部分*16得到”1的個數”的值,查表得到uBDIVSLOT0寄存器的設置值(見圖2)。


19.8.串口輸入輸出的移植
(1)標準輸入輸出就是操作系統定義的默認的輸入和輸出通道;一般在PC機的情況下,標準輸入指的是鍵盤,標準輸出指的是屏幕;printf函數和scanf函數可以和210串口底層輸入/輸出函數綁定,即我們直接調用printf函數輸出,內容就會被從串口輸入輸出。
(2)printf函數的工作原理;printf函數內部實際調用了2個關鍵函數=vsprintf函數(格式化打印信息,最終得到純字符串格式的打印信息等待輸出)+輸出函數putc(操控標準輸出的硬件,將信息發送出去)。
(3)我們希望在我們的開發板上使用printf函數進行(串口)輸出,使用scanf函數進行(串口)輸入,則需要移植printf函數/scanf函數;移植printf函數可以有3個途徑獲取printf的實現源碼=最原始最原本的來源就是linux內核中的printk(難度較大,關鍵是麻煩)+稍微簡單些的方法是從uboot中移植printf+更簡單的方法就是直接使用別人移植好的;此處別人移植好的printf函數來自於友善之臂的Tiny210的裸機教程中提供的。
(4)gcc可變參數及va_arg;printf函數中首先使用了C語言的可變參數va_start/va_arg/va_end(此處和C語言的可變參數有關);vsprintf函數(printf->vsprintf->vsnprintf->number);vsprintf函數的作用是按照我們的printf傳進去的格式化標本,對變參進行處理,然後將之格式化後緩存在一個事先分配好的緩衝區中;printf後半段調用putc函數將緩衝區中格式化好的字符串直接輸出到標準輸出。
(5)bin文件若大於16KB的解決辦法;通過USB下載最多也只能下載96KB大小的bin,如果bin大於96KB肯定SRAM放不下會出錯;如果用SD卡啓動,那麼mkv210_image.c決定了bin文件最大不能超過16KB;解決方法1->在USB下載時,可以先下載1個x210_usb.bin,然後再將裸機程序連接到0x23E00000,然後再修改dnw中下載地址,將裸機代碼下載到0x23E00000運行;解決方法2->在SD卡啓動時,將整個裸機工程分爲2部分,第1部分大小16KB以內,第2部分放剩下的(放在SD卡的後面的某個扇區開始的位置,譬如放在第50個扇區開始的位置),然後在裸機代碼中進行重定位(SD卡中重定位)。


這裏寫圖片描述


這裏寫圖片描述


19.uart_printf/Makefile
# 210裸機項目中多個Makefile協同工作的模板
CC      = arm-linux-gcc
LD      = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR      = arm-linux-ar

INCDIR  := $(shell pwd)
# C預處理器的flag,flag就是編譯器可選的選項
# -I指定頭文件的的目錄
CPPFLAGS    := -nostdlib -nostdinc -I$(INCDIR)/include
# C編譯器的flag
CFLAGS      := -Wall -O2 -fno-builtin

#導出這些變量到全局,其實就是給子文件夾下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS

name:=uart_printf
objs := start.o clock.o sdram_init.o  main.o uart.o
objs += lib/libc.a

$(name).bin:$(objs)
    $(LD) -Tlink.lds -o $(name).elf $^
    $(OBJCOPY) -O binary $(name).elf $(name)_usb.bin
    $(OBJDUMP) -D $(name).elf > $(name)_elf.dis
    gcc mkv210_image.c -o mkgec210
    ./mkgec210 $(name)_usb.bin $(name)_sd.bin

lib/libc.a:
    cd lib; make;   cd ..

%.o : %.S
    $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c 

%.o : %.c
    $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c

clean:
    rm *.o *.elf *.bin *.dis mkgec210 -f
    cd lib; make clean; cd ..
19.uart_printf/lib/Makefile
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o

libc.a: $(objs)
    ${AR} -r -o $@ $^

%.o:%.c
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

%.o:%.S
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

clean:
    rm -f libc.a *.o    
19.uart_printf/uart.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:串口移植輸出函數
 * 功能:串口移植printf函數功能
 */
#include "uart.h"

// 初始化串口
void uart_init(void)
{
    // 初始化RXD和TXD的GPIO引腳
    rGPA0CON &= ~(0xff<<0);            // 清零寄存器
    rGPA0CON |= 0x00000022;         // bit[3:0]=0b0010;bit[7:4]=0b0010

    // 初始化關鍵寄存器
    rULCON0 = 0x03;                 // 普通模式+0校驗位+1停止位+8數據位
    rUCON0  = 0x05;                 // 選擇PCLK_PSYS做爲串口時鐘源+不使用迴環模式+發送和接收都是中斷/輪詢模式
    rUMCON0 = 0x00;                 // 禁止流控的一切配置
    rUFCON0 = 0x00;                 // 禁止FIFO模式

    // 設置串口波特率  
#if 1                               // 使用66.7MHz計算
    rUBRDIV0 = 35;                  // DIV_VAL = (66700000/(115200*16)-1) = 35.18
    rUDIVSLOT0 = 0x0888;            // 1的個數 = 16*0.18= 2.88 = 3個1,查官方推薦表
#endif

#if 0                               // 使用66MHz計算
    rUBRDIV0 = 34;                  // DIV_VAL = (66000000/(115200*16)-1) = 34.08
    rUDIVSLOT0 = 0xDFDD;            // 1的個數 = 16*0.8= 11 = 11個1,查官方推薦表
#endif
}

// 串口發送1個字符
void putc(char c)
{
    while (!(rUTRSTAT0 & (1<<1)));  // 若發送緩衝區爲空,則發送數據
    rUTXH0 = c;
}

// 串口接收1個字符
char getc(void)
{
    while (!(rUTRSTAT0 & (1<<0)));  // 若接收到數據,則返回接收到的數據
    return (rURXH0 & 0x0f);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章