嵌入式知識-ARM裸機-學習筆記(6):串口通信詳解與基於S5PV210的uartstdio移植

嵌入式知識-ARM裸機-學習筆記(6):串口通信詳解與基於S5PV210的uartstdio移植

一、串口通信原理

1. 串口通信基本概念

通信中最重要的三個方面: 信息表示(編碼)、解析方法(解碼)、信息的傳輸方法。通信雙方事先需要約定好信息的表示方法和解析方法,做到一致,否則信息不能有效傳遞。信號的傳輸方法是指經過編碼後的通信信息如何在傳輸介質上傳輸的過程,傳輸方法與編解碼方法無關。
在這裏插入圖片描述
通信的過程:首先發送方先按照信息編碼方式對有效信息進行編碼(編程成可以在通信線路上傳輸的信號形態),編碼後的信息在傳輸介質上進行傳輸,輸送給接收方;最後接收方接收到編碼信息後進行解碼,解碼後得到可以理解的有效信息。

同步通信和異步通信
在這裏插入圖片描述
(1)同步通信:通信雙方按照統一節拍工作,一般需要發送方給接收方發送信息同時發送時鐘信號,接收方根據發送方給它的時鐘信號來安排自己的節拍。(同步通信用在通信雙方信息交換頻率固定,或者經常通信時)
(2)異步通信:又叫異步通知。異步通信時接收方不必一直等待發送方,發送方需要發送信息時會首先給接收方一個信息開始的起始信號,接收方接收到起始信號後就認爲後面緊跟着的就是有效信息,纔會開始注意接收信息,直到收到發送方發過來的結束標誌(異步通信用在雙方通信的頻率不固定時)
(3)同步和異步的區別:發送方和接收方按照同一個時鐘節拍工作就叫同步,發送方和接收方沒有統一的時鐘節拍、而各自按照自己的節拍工作就叫異步,異步一般帶有一根時鐘線,負責傳輸時鐘,控制接收方的節拍(大部分情況下的判斷依據)

電平信號和差分信號:
(1)電平信號和差分信號是用來描述通信線路傳輸方式的,即通過什麼方式在通信線路上來表達邏輯0或1。
(2)電平信號的傳輸線中有一個參考電平線(一般是GND),通過信號線上的信號電平和參考電平線的電壓差決定信息表達是0或1。這種方式的傳播一般容易受到外界干擾。
(3)差分信號的傳輸線中沒有參考電平,所有都是信號線。通過信號線之間的電壓差來表達邏輯0或1。由於兩條信號線受到干擾環境的影響基本相同,因此兩者之間受到的干擾可以在通過電壓差計算時相互抵消。

串行通信和並行通信:
(1)串行、並行主要是考慮通信線的根數,就是發送方和接收方同時可以傳遞的信息量的多少。例如同時發送8位二進制數,對於電平信號,需要9根線(8根信號線+1根參考線);對於差分信號,需要16根線(2根組成1對,一共8對)。
(2)在實際使用時,串行接口應用更廣泛,因爲更省信號線,而且對傳輸線的要求更低、成本更低;而且串行時可以通過提高通信速度來提高總體通信性能,不一定非得要並行。2條線的串行通信方式每次只能傳輸1個二進制位。
最常用的搭配方式爲:異步、串行、差分,譬如USB和網絡通信。

波特率(bandrate):
指的是串口通信的速率,也就是串口通信時每秒鐘可以傳輸多少個二進制位。譬如每秒種可以傳輸9600個二進制位(傳輸一個二進制位需要的時間是1/9600秒,也就是104us),波特率就是9600。一般最常見的波特率是9600或者115200(低端單片機如51常用9600,高端單片機和嵌入式SoC一般用115200)。通信雙方必須事先設定相同的波特率這樣才能成功通信,如果發送方和接收方按照不同的波特率通信則根本收不到。

起始位、數據位、奇偶校驗位、停止位
在這裏插入圖片描述
(1)串口通信時,收發是一個週期一個週期進行的,每週期傳輸n個二進制位。這一個週期就叫做一個通信單元,一個通信單元是由:起始位+數據位+奇偶校驗位+停止位組成的(又叫做一幀)
(2)起始位表示發送方要開始發送一個通信單元,起始位是串口通信標準事先指定的,是由通信線上的電平變化來反映的,也就是時序數據位是一個通信單元中發送的有效信息位,也就是本次通信真正要發送的有效數據,大部分情況下數據位是8位,因爲通過串口發送的文字信息都是ASCII碼編碼的,而ASCII碼中一個字符剛好編碼爲8位。奇偶校驗位是用來校驗數據位,把待校驗的有效數據逐個位的加起來,總和爲奇數奇偶校驗位就爲1,總和爲偶數奇偶校驗位就爲0。停止位是發送方用來表示本通信單元結束標誌的,一般爲1位。
總結:串口通信時因爲是異步通信,所以通信雙方必須事先約定好通信參數,這些通信參數包括:波特率、數據位、奇偶校驗位、停止位(串口通信中起始位定義是唯一的,所以一般不用選擇)。

通信方式(單工、雙工、半雙工)
(1)單工就是單方向傳輸,表示只能A發B收。
(2)雙工就是雙方同時收發,A發B收的同時也能B發A收。
(3)半雙工就是隻能單方向但是方向可以改變,A發B收或者B發A收(兩個方向不能同時)。

2. 常見的電平標準

TTL電平
供電範圍在0~5V。
對輸出:大於2.4V是高電平;小於0.4V是低電平。
對輸入:大於2V是高電平;小於0.8V是低電平。

在這裏插入圖片描述
RS232
對輸出:輸出“1”時的電平應在-3~-15 V之間,輸出“0”時的電平應在+3~+15 V之間。
對輸入:輸入電平在-3~-15 V之間被認爲“1”,在+3~+15 V之間被認爲“0”。
當線路上不傳送數據(空閒)時,發送器輸出爲“1”。
雙向傳輸,全雙工通信,最高傳輸速率20kbps。

RS485
對輸出:邏輯"1"以兩線間的電壓差爲+2v ~ +6表示;邏輯"0"以兩線間的電壓差爲-2V ~ -6V 表示。
對輸入:A比B高200mV以上即認爲是邏輯"1",A 比B 低200mV 以上即認爲是邏輯"0"。
雙向差分傳輸,半雙工通信,最高傳輸速率10Mbps。

RS422
與RS485的電平標準相同,發送口與接收口不同,如若將其並連就變成了RS485。
相當於兩個半雙工的RS485構成了一個全雙工通信,最高速率10Mbps。

3. 串口通信基本原理

通信線(RX TX GND)
(1)任何通信都要有信息傳輸載體,或者是有線的或者是無線的。串口通信是有線通信,是通過串口線來通信的。
(2)串口通信線最少需要2根(GND和信號線),可以實現單工通信,也可以使用3根通信線(Tx、Rx、GND)來實現全雙工。一般開發板都會引出SoC上串口引腳直接輸出的TTL電平的串口(X210開發板沒有),插座用插針式插座,每個串口引出的都有3個線(Tx、Rx、GND),可以用這些插座直接連接外部的TTL電平的串口設備。

信息在信道上傳輸方式
串口通信的發送方每隔一定時間(時間固定爲1/波特率,單位是秒)將有效信息(1或者0)放到通信線上去,逐個二進制位的進行發送。
接收方通過定時(起始時間由讀到起始位標誌開始,間隔時間由波特率決定)讀取通信線上的電平高低來區分發送給我的是1還是0。依次讀取數據位、奇偶校驗位、停止位,停止位就表示這一個通信單元(幀)結束,然後中間是不定長短的非通信時間(發送方有可能緊接着就發送第二幀,也可能半天都不發第二幀,這就叫異步通信),接着發送第二幀·····

FIFO模式及其作用
對於典型的串口設計,發送/接收緩衝區只有1字節,因此每次發送/接收時只能處理1幀數據。在複雜的SoC中CPU的時鐘遠高於串口發送端,會導致CPU需要不斷切換上下文(每發完1幀數據就需要重新切換回發送端),大大降低了速率。
如何像icache一樣提供一個解決兩者速率差異較大的方法?解決方案就是想辦法擴展串口控制器的發送/接收緩衝區,例如將發送/接收緩衝器設置爲64字節,CPU一次過來直接給發送緩衝區64字節的待發送數據,然後transmitter慢慢發,發完再找CPU再要64字節。但是串口控制器本來的發送/接收緩衝區是固定的1字節長度的,所以做了個變相的擴展,就是FIFO(first in first out),先進入緩衝區的先出來,從而不影響順序
CPU來一次直接給FIFO了64字節的內容,然後FIFO一個字節一個字節的給發送緩衝區,此時就不需要CPU的參與,大大提高了效率。

DMA模式及其作用
DMA direct memory access,直接內存訪問。 DMA本來是DSP中的一種技術,DMA技術的核心就是在交換數據時不需要CPU參與,模塊可以自己完成。DMA模式要解決的問題和上面FIFO模式是同一個問題,就是串口發送/接收要頻繁的折騰CPU造成CPU反覆切換上下文導致系統效率低下。
傳統的串口工作方式(無FIFO無DMA)效率是最低的,適合低端單片機;高端單片機上CPU事物繁忙所以都需要串口能夠自己完成大量數據發送/接收。這時候就需要FIFO或者DMA模式。FIFO模式是一種輕量級的解決方案,DMA模式適合大量數據迸發式的發送/接收時。

二、S5PV210串口配置過程

1. 配置前的分析

在這裏插入圖片描述
首先說明一下爲什麼串口叫UART,universal asynchronous reciver and transmitter,通用異步收發器,即可知UART的通信方式是異步的。

(1)從圖中可以看出,整個串口控制器包含transmitterreceiver兩部分,兩部分功能彼此獨立,transmitter負責210向外部發送信息,receiver負責從外部接收信息到210內部。
(2)從之前的時鐘部分可知,串口控制器是接在APB總線上的。對我們編程有影響的是:將來計算串口控制器的源時鐘時是以APB總線來計算的。
(3)transmitter由發送緩衝區和發送移位器構成。 在發送信息時,首先將信息進行編碼成二進制流,然後將一幀數據寫入發送緩衝區,發送移位器會自動從發送緩衝區中讀取一幀數據,然後自動移位(移位的目的是將一幀數據的各個位分別拿出來)將其發送到TX(發送端)通信線上。
(4)receiver由接收緩衝區和接收移位器構成。 當有人通過串口線向我發送信息時,信息通過RX(接收端)通信線進入我的接收移位器,然後接收移位器自動移位將該二進制位保存到我的接收緩衝區,接收完一幀數據後receiver會產生一箇中斷給CPU,CPU收到中斷後即可知道receiver接收滿了一幀數據,就會來讀取這幀數據。
總結:發送緩衝區和接收緩衝區是關鍵。發送移位器和接收移位器的工作都是自動的,不用編程控制的,所以我們寫串口的代碼就是:首先初始化串口控制器(初始化的實質是讀寫寄存器,包括髮送控制器和接收控制器),然後將要發送信息時直接寫入發送緩衝區,要接收信息時直接去接收緩衝區讀取即可。

串口通信分爲發送/接收2部分。發送方一般不需要(也可以使用)中斷即可完成發送,接收方必須(一般來說必須,也可以輪詢方式接收)使用中斷來接收。本實驗採用輪詢的方式接收,通過狀態寄存器中有一個位叫發送緩衝區空標誌,transmitter發送完成(發送緩衝區空了)就會給這個標誌位置位,CPU就是通過不斷查詢這個標誌位爲1還是0來知道發送是否已經完成的。

2. 時鐘分析

從圖中可以看出波特率的產生需要時鐘的提供(Clock Source),所以transmitter和receiver都需要一個時鐘信號。
由上述可得,源時鐘信號是外部APB總線(PCLK_PSYS,66MHz)提供給串口模塊的,然後進到串口控制器內部後給波特率發生器(實質上是一個分頻器),在波特率發生器中進行分頻,分頻後得到一個低頻時鐘,這個時鐘就是給transmitter和receiver使用的。

3. 編程詳解

C語言代碼(uart.c文件):

#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;	//0校驗位,8數據位,1停止位
	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);	//返回這個值得0到7bit位
}

整個串口通信主要由3個函數組成:uart_init負責初始化串口,uart_putc負責發送一個字節,uart_getc負責接收一個字節。

(1)uart_init()函數用來初始化串口,其中包括引腳的選擇以及一些關鍵參數的設置:
在這裏插入圖片描述
通過原理圖可以得到,若想使用串口UART0,即需要設置收發引腳RXD0/TXD0,對應SoC引腳爲GPA0_0和GPA0_1,找到對應的寄存器GPA0CON(Address = 0xE020_0000)。
在這裏插入圖片描述
通過設置GPA0CON寄存器的[7:0]爲0x22,即可配置UART0的輸入輸出端口。

接着需要設置數據的接口模式、數據位、校驗方式、停止位,找到對應的寄存器ULCON0(Address = 0xE290_0000)。
在這裏插入圖片描述
通過設置ULCON0寄存器的[6:0]爲0x3,即將串口配置爲:普通模式、無校驗、1個停止位、8個數據位。

接着需要設置傳輸方式,找到對應的寄存器UCON0(Address = 0xE290_0004)。
在這裏插入圖片描述
通過設置UCON0寄存器的[5:0]爲0x5,即將串口配置爲:PCLK爲時鐘源,中斷全關,polling mode(輪詢模式)。

設置控制流控,找到對應的寄存器UMCON0(Address = 0xE290_000C),將其設置爲0x0全部禁止。

設置是否使用FIFO,找到對應的寄存器UFCON0(Address = 0xE290_0008),將其設置爲0x0全部禁止。

最後是設置波特率,找到對應寄存器UBRDIV0(Address = 0xE290_0028)和UDIVSLOT0(Address = E290_002C)。
在這裏插入圖片描述
在這裏插入圖片描述
已知PCLK=66.7Mhz,根據公式可得DIV_VAL = (66700000/(11520016)-1) = 35.18,即爲35;UDIVSLOT中的1的個數 = 160.18= 2.88 = 3,查找3對應的碼爲0x0888,波特率配置完成。

(2)uart_putc()函數用來發送數據,通過調用來發送一字節的內容。其實就是向發送端緩衝區寫內容,發送端緩衝區寄存器爲UTXH0(Address = 0xE290_0020),一次寫一個字節,因爲是char型。
在這裏插入圖片描述
但是要注意,由於CPU的速度遠高於串口,因此CPU給緩衝區發完數據以後就去幹別的工作了,所以需要一個標誌來表示緩衝區已發送完畢。這就需要用到緩衝區狀態寄存器UTRSTAT0(Address = 0xE290_0010),當發送端緩衝區爲1時爲空,因此可以設置一個條件,當UTRSTAT0=0x10時,則請求CPU發送下一字節。
在這裏插入圖片描述
(3)uart_getc()函數的原理與uart_putc()類似,這裏不再贅述。

main.c文件:

void main(void)
{
	uart_init();
	
	while(1)
	{
		uart_putc('b');
		delay();		//這裏的delay函數來自於led.c的延時函數
		
	}
}

通過調用uart_putc函數來一直髮字符b。

start.s文件:
在這裏插入圖片描述
在這裏調用main.c文件中的main函數。

Makefile文件中添加uart.c文件和main.c文件:
在這裏插入圖片描述

三、S5PV210的uartstdio移植過程

由於我們第二章設計出的putc函數只能打印1個字符,而我們想通過串口實現printf打印功能,這就用到了文件系統移植的部分。

1. 移植前的準備

首先介紹一下要移植的頭文件stdio(standard input output,標準輸入輸出),在使用時要通過 #include <stdio.h> 來實現頭文件的包含。標準輸入輸出就是操作系統定義的默認的輸入和輸出通道。一般在PC機的情況下,標準輸入指的是鍵盤,標準輸出指的是屏幕。
printf函數和scanf函數可以和底層輸入/輸出函數綁定,然後這兩個函數就可以和stdio綁定起來。也就是說我們直接調用printf函數輸出,內容就會被從標準輸出出去。
我們希望在我們的開發板上使用printf函數進行(串口)輸出,使用scanf函數進行(串口)輸入,就像在PC機上用鍵盤和屏幕進行輸入輸出一樣。即將標準輸入輸出改爲串口。

移植思路: 通過別人移植好的printf函數來移植到我們所需的板卡上。
在這裏插入圖片描述
在這裏插入圖片描述
我們要做的就是將別人編寫好的printf函數中的putc()函數與我們編寫的驅動串口打印的uart_putc()函數相關聯起來,即可實現通過printf實現串口打印。

2. 移植過程

在lib文件夾中,我們可以看到有一個Makefile,這個Makefile即是用來聯繫lib文件夾中的各文件,因此我們需要通過自己的Makefile文件來支持這個子Makefile文件
介紹Makefile文件:
在這裏插入圖片描述
(1)通過子Makefile文件內容可以發現,它通過${CC}的形式來調用編譯器,因此我們需要將我們的編譯器進行名字的簡化(統一化),再通過export將其導出,以使得外部的文件可以調用,這樣在子Makefile中即可實現交叉編譯工具的調用
(2)通過objs := 的方式實現文件名的統一管理,在之後只需 $(obj)的方式,代表之間的所有文件。
(3)在母Makefile文件中需要對子Makefile進行包含,首先需要objs += lib/libc.a 使得子Makefile中的規則libc.a成爲生成uart.bin的一個條件,然後我們還需要執行該規則的執行方式爲cd lib; make; cd … (cd lib表示進入到lib文件夾中,make表示編譯該目錄下的Makefile,cd …退回到之前的目錄)。

(4)CPPFLAGS是C預編譯器的flag,它包含的可選項有 -nostdlib (不使用標準庫,stdio.h使用我們自己的); -nostdinc(不用標準的頭文件) ;-I$(INCDIR)/include (指定頭文件目錄,這裏的INCDIR表示Makefile所在的當前目錄,即當前目錄顯得include文件夾中)。
(5)CFLAGS 是C編譯器的flag,它包含的可選項有 -Wall (警告信息);-O2 (編譯器的優化選項,2是一個等級);-fno-builtin (全用我們現有的文件不用標準的)。
(6)最後要在clean中包括對子Makefile的管理。

介紹main.c文件:
在這裏插入圖片描述
此時通過printf即可實現串口打印,接下來即需要將uart_putc()函數與putc()函數進行綁定。

介紹uart.c文件:
在這裏插入圖片描述
最終我們移植完成,uart.c文件中的putc函數被printf.c文件所調用,因此在printf.c文件中,執行打印函數print時,即將信息發送到了串口的輸出緩衝區中;main.c文件通過調用printf函數實現打印功能。母Makefile文件調用子Makefile文件,兩者各自管理所涉及範圍,從而實現協同編譯,即完成了文件系統移植。

在ubuntu中進行編譯:
在這裏插入圖片描述
將SecureCRT中的波特率設置與UART0串口相同,將bin文件燒錄到板卡中,即可實現main.c文件中的打印信息。
在這裏插入圖片描述

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