tiny4412開發板的串列埠介紹與操作
UART原理說明:
通用非同步收發器簡稱UART,即"Universal Asynchronous Receiver Transmitter",它用來傳輸序列資料:傳送資料時,CPU將並行資料寫入UART,UART按照一定的格式在一根電線上序列發出;接收資料時,UART檢測另一根電線上的訊號,將序列收集放在緩衝區中,CPU即可讀取UART獲得這些資料。UART之間以全雙工方式傳輸資料,最精簡的連線方法只有三根電線:TxD用於傳送資料,RxD用於接收資料,Gnd用於給雙方提供參考電平,連線如圖1所示:
圖1. UART連線圖
UART使用標準的TTL/CMOS邏輯電平(0~5V、0~3.3V、0~2.5V或0~1.8V四種)來表示資料,高電平表示1,低電平表示0。為了增強資料的抗干擾能力、提高傳輸長度,通常將TTL/CMOS邏輯電平轉換為RS-232邏輯電平,3~12V表示0,-3~-12V表示1。
TxD、RxD資料線以"位"為最小單位傳輸資料。幀(frame)由具有完整意義的、不可分割的若干位組成,它包含開始位、資料位、較驗位(需要的話)和停止位。傳送資料之前,UART之間要約定好資料的傳輸速率(即每位所佔據的時間,其倒數稱為波特率)、資料的傳輸格式(即有多少個數據位、是否使用較驗位、是奇較驗還是偶較驗、有多少個停止位)。
資料傳輸流程如下:
(1)平時資料線處於"空閉"狀態(1狀態)。
(2)當要傳送資料時,UART改變TxD資料線的狀態(變為0狀態)並維持1位的時間──這樣接收方檢測到開始位後,再等待1.5位的時間就開始一位一位地檢測資料線的狀態得到所傳輸的資料。
(3)UART一幀中可以有5、6、7或8位的資料,傳送方一位一位地改變資料線的狀態將它們傳送出去,首先發送最低位(LSB)。
(4)如果使用較驗功能,UART在傳送完資料位後,還要傳送1個較驗位。有兩種較驗方法:奇較驗、偶較驗──資料位連同較驗位中,"1"的數目等於奇數或偶數。
(5)最後,傳送停止位,資料線恢復到"空閉"狀態(1狀態)。停止位的長度有3種:1位、1.5位、2位。
圖2演示了UART使用7個數據位、偶較驗、2個停止位的格式傳輸字元"A"(二進位制值為0b1000001)時,TTL/CMOS邏輯電平對應的波形。
圖2. TTL/CMOS邏輯電平下,傳輸"A"時的波形
UART還有其他功能,比如流量控制等,想深入瞭解的讀者請自行查閱相關資料。
Exynos4412的UART特性:
Exynos4412中UART,有4個獨立的通道,每個通道都可以工作於中斷模式或DMA模式,即UART可以發出中斷或DMA請求以便在UART、CPU間傳輸資料。UART由波特率發生器、傳送器、接收器和控制邏輯組成。
使用系統時鐘時,Exynos4412的UART波特率可以達到4Mbps。波特率可以通過程式設計進行控制。
Exynos4412 UART的通道0有256位元組的傳送FIFO和256位元組的接收FIFO;通道1、4有64位元組的傳送FIFO和64位元組的接收FIFO;通道2、3有16位元組的傳送FIFO和16位元組的接收FIFO。傳送資料時,CPU先將資料寫入傳送FIFO中,然後UART會自動將FIFO中的資料複製到"傳送移位器"(Transmit Shifter)中,傳送移位器將資料一位一位地傳送到TxDn資料線上(根據設定的格式,插入開始位、較驗位和停止位)。接收資料時,"接收移位器"(Receive Shifter)將RxDn資料線上的資料一位一位接收進來,然後複製到接收FIFO中,CPU即可從中讀取資料。
Exynos4412 UART的每個通道支援的停止位有1位、2位,資料位有5、6、7或8位,支援較驗功能,另外還有紅外發送/接收功能。Exynos4412 UART結構如圖3所示:
圖3. Exynos4412 UART結構圖
Exynos4412 UART的使用:
圖4. 設定Serial連線圖
如圖4所示,我們在使用UART與PC通訊的時候,PC端需要設定波特率、資料位、是否使用校驗位、有多少個停止位、是否使用流控等。UART是工作在非同步模式下,接收器自身實現幀的同步,因此要實現通訊,Exynos4412的UART也要作相同的設定,另外還要選擇所涉及管腳為UART功能、選擇UART通道的工作模式為中斷模式或DMA模式。設定好之後,往某個暫存器寫入資料即可傳送,讀取某個暫存器即可得到接收到的資料。在中斷模式(查詢模式)下,我們一般通過查詢狀態暫存器或設定中斷來獲知資料是否已經發送完成、是否已經接收到資料。下面詳細講解上述設定過程。
1.將所涉及的UART通道管腳設為UART功能
比如UART通道0中,GPA0_0、GPA0_1分別用作RXD0、TXD0,要使用UART通道0時,先設定GPA0CON暫存器將GPA0_0、GPA0_1引腳的功能設為RXD0、TXD0。
2.選擇UART的時鐘源
圖5. UART時鐘
Exynos4412 UART的時鐘源有八種選擇:XXTI、XusbXTI、SCLK_HDMI24M、SCLK_USBPHY0、SCLK_HDMIPHY、SCLKMPLL_USER_T、SCLKEPLL、SCLKVPLL,由CLK_SRC_PERIL0暫存器控制。
選擇好時鐘源後,還可以通過DIVUART0~4設定分頻係數,由CLK_DIV_PERIL0暫存器控制。從分頻器得到的時鐘被稱為SCLK_UART。
SCLK_UART經過圖5中的"UCLK Generator"後,得到UCLK,它的頻率就是UART的波特率。"UCLK Generator"通過這2個暫存器來設定:UBRDEVn、UFRACVALn(在下面描述)。
表1 CLK_SRC_PERIL0的暫存器格式
由於在iROM的程式碼中設定了相關時鐘的暫存器,我們在試驗的使用選擇XusbXTI作為時鐘源。
表2 CLK_DIV_PERIL0的暫存器格式
3.設定波特率:UBRDIVn暫存器(UART BAUD RATE DIVISOR)、UFRACVALn暫存器
根據給定的波特率、所選擇的時鐘源的頻率,可以通過以下公式計算UBRDIVn 暫存器(n為0~4,對應5個UART通道)的值:
UBRDIVn = (int)(UART clock/(buad rate x 16)) – 1
上式計算出來的UBRDIVn暫存器值不一定是整數,UBRDIVn暫存器取其整數部分,小數部分由UFRACVALn暫存器設定,UFRACVALn暫存器的引入,使產生的波特率更加精確。
例如,當UART clock為100MHz時,要求波特率為115200bps,則:
100000000/(115200 x 16) – 1 = 54.25 – 1 = 53.25
UBRDIVn = 整數部分 = 53
UFRACVALn/16 = 小數部分 = 0.25
UFRACVALn = 4
4.設定傳輸格式:ULCONn暫存器(UART LINE CONTROL)
ULCONn暫存器(n為0~4)格式如表3所示。
表3 ULCONn暫存器格式
我們這裡設定為,普通模式、無校驗位、一幀中有一個停止位和8位的資料位。
UART通道被設為紅外模式時,其序列資料的波形與正常模式稍有不同,有興趣的讀者請自行閱讀資料手冊。
5.設定UART工作模式:UCONn暫存器(UART CONTROL)
Exynos4412的UCONn暫存器格式,如表4所示。
表4 UCONn暫存器格式
我們寫程式時使用中斷或查詢方式。
6.UFCONn暫存器(UART FIFO CONTROL)、UFSTATn暫存器(UART FIFO STATUS)
UFCONn暫存器用於設定是否使用FIFO,設定各FIFO的觸發閥值,即傳送FIFO中有多少個數據時產生中斷、接收FIFO中有多少個數據時產生中斷。並可以通過設定UFCONn暫存器來複位各個FIFO。
讀取UFSTATn暫存器可以知道各個FIFO是否已經滿、其中有多少個數據。
不使用FIFO時,可以認為FIFO的深度為1,使用FIFO時Exynos4412的FIFO深度最高可達到256。這兩類暫存器各位的含義請讀者查閱資料手冊。
7.UMCONn暫存器(UART MODEM CONTROL)、UMSTATn暫存器(UART MODEM STATUS)
這兩類暫存器用於流量控制,這裡不介紹。
8.UTRSTATn暫存器(UART TX/RX STATUS)
UTRSTATn暫存器用來表明資料是否已經發送完畢、是否已經接收到資料,格式如下表4所示。下面說的"緩衝區",其實就是圖3中的FIFO,不使用FIFO功能時可以認為其深度為1。
表5 UTRSTATn暫存器格式
9.UERSTATn暫存器(UART ERROR STATUS)
用來表示各種錯誤是否發生,位[0]至位[3]為1時分別表示溢位錯誤、較驗錯誤、幀錯誤、檢測到"break"訊號。讀取這個暫存器時,它會自動清0。
需要注意的是,接收資料時如果使用FIFO,則UART內部會使用一個"錯誤 FIFO"來表明接收FIFO中哪個資料在接收過程中發生了錯誤。CPU只有在讀出這個錯誤的資料時,才會覺察到發生了錯誤。要想清除"錯誤 FIFO",則必須讀出錯誤的資料,並讀出UERSTATn暫存器。
10.UTXHn暫存器(UART TRANSMIT BUFFER REGISTER)
CPU將資料寫入這個暫存器,UART即會將它儲存到緩衝區中,並自動傳送出去。
11.URXHn暫存器(UART RECEIVE BUFFER REGISTER)
當UART接收到資料時,CPU讀取這個暫存器,即可獲得資料。
示例程式詳解,主要涉及到三個檔案,start.S檔案主要設定棧等基本的功能;uart.c檔案主要就是初始化串列埠和定義操作串列埠用到的函式。main.c檔案主要做相關的操作。
start.S檔案內容如下:
.text
.global _start
_start:
ldr sp, =0x02027400 //呼叫C函式之前必須設定棧,棧用於儲存執行環境,給區域性變數分配空間;
//參考ROM手冊P14,我們把棧指向BL2的最上方;
//即:0x02020000(iROM基地址)+5K(iROM程式碼用)+8K(BL1用)+16K(BL2用)
bl main //跳轉到C函式中執行
halt: //死迴圈
b halt
uart.c檔案的內容如下,已經在裡面做了詳細的註釋,這裡不再做詳細的介紹。
//串列埠0使用的引腳
#define GPA0CON (*(volatile unsigned int *)0x11400000)
//選擇時鐘源
#define CLK_SRC_PERIL0 (*(volatile unsigned int *)0x1003C250)
//設定uart0的分頻係數
#define CLK_DIV_PERIL0 (*(volatile unsigned int *)0x1003C550)
#define UBRDIV0 (*(volatile unsigned int *)0x13800028)
#define UFRACVAL0 (*(volatile unsigned int *)0x1380002C)
#define UFCON0 (*(volatile unsigned int *)0x13800008)
#define ULCON0 (*(volatile unsigned int *)0x13800000)
#define UCON0 (*(volatile unsigned int *)0x13800004)
#define UTXH0 (*(volatile unsigned int *)0x13800020)
#define URXH0 (*(volatile unsigned int *)0x13800024)
#define UTRSTAT0 (*(volatile unsigned int *)0x13800010)
void uart0_init()
{
//配置GPA0CON的引腳為串列埠功能;
GPA0CON = 0x22222222;
//設定串列埠0使用的時鐘源;使用XusbXTI作為時鐘源
CLK_SRC_PERIL0 &= ~(0xF); //低四位清零
CLK_SRC_PERIL0 |= (0x1); //低四位設定為0b0001 MOUTUART0 = 24MHz
//uart0的時鐘源為24MHz;設定分頻係數
CLK_DIV_PERIL0 &= ~(0xF); //低四位清零
CLK_DIV_PERIL0 |= (0x2); //SCLK_UART0 = MOUTUART0/(UART0_RATIO + 1)
//MOUTUART0=24,UART0_RATIO=2;所以,SCLK_UART0=8MHz
//程式走到這裡SCLK_UART0(8MHz)時鐘已經獲得了。
/*
* 使能FIFO;Rx FIFO:64 bytes Tx FIFO:32 bytes
*/
UFCON0 =0x111;
/* 設定資料格式: 8n1, 即8個數據位,沒有較驗位,1個停止位 */
ULCON0 = 0x3;
/* 工作於中斷/查詢模式
* 另一種是DMA模式,本章不使用
*/
UCON0 = 0x5;
//SCLK_UART0=8MHz;設定UBRDIV0和UFRACVAL0暫存器;波特率是115200
UBRDIV0 = 0x3;
UFRACVAL0 = 0x5;
}
//從串列埠獲得一個字元
char getc(void)
{
char c;
/* 查詢狀態暫存器,直到有有效資料 */
while (!(UTRSTAT0 & (1<<0)));
c = URXH0; /* 讀取接收暫存器的值 */
return c;
}
//輸出一個字元
void putc(char c)
{
/* 查詢狀態暫存器,直到傳送快取為空 */
while (!(UTRSTAT0 & (1<<2)));
UTXH0 = c; /* 寫入傳送暫存器 */
return;
}
//列印字串
void puts(char *s)
{
while (*s)
{
putc(*s);
s++;
}
}
//將數字按照十六進位制格式列印
void puthex(unsigned long val)
{
/* val = 0x1234ABCD */
unsigned char c;
int i = 0;
putc('0');
putc('x');
for (i = 0; i < 8; i++)
{
c = (val >> ((7-i)*4)) & 0xf;
if ((c >= 0) && (c <= 9))
{
c = '0' + c;
}
else if ((c >= 0xA) && (c <= 0xF))
{
c = 'A' + (c - 0xA);
}
putc(c);
}
putc('\n');
putc('\r');
}
main.c檔案主要做使用試驗使用,內容如下:
int main()
{
char c;
uart0_init();
puts("Test uart by Haitao Cai!\n\r");
puthex(100); //列印十六進位制數
while(1)
{
c = getc(); //從串列埠獲得一個字元
if(c == '\r')
{
puts("Test over!");
break;
}
putc(c);
putc(':');
putc(c+1); //將字元加1輸出
putc('\n');
putc('\r');
}
return 0;
}
將檔案上傳,編譯並燒寫SD卡;開機試驗過程如下:
本文完畢!