如何基于Cortex-A9的UART從頭實(shí)現(xiàn)printf函數(shù)
0. 前言
Uart在一個(gè)嵌入式系統(tǒng)中是一個(gè)非常重要的模塊,他承擔(dān)了CPU與用戶交互的橋梁。用戶輸入信息給程序、CPU要打印一些信息給終端都要依賴UART。
本文將以Exynos4412的UART控制器為基礎(chǔ),講解UART的原理以及驅(qū)動(dòng)程序如何編寫。
1. UART是什么
UART是通用異步收發(fā)傳輸器(Universal Asynchronous Receiver/Transmitter),通常稱作UART,是一種異步收發(fā)傳輸器,是設(shè)備間進(jìn)行異步通信的關(guān)鍵模塊。UART負(fù)責(zé)處理數(shù)據(jù)總線和串行口之間的串/并、并/串轉(zhuǎn)換,并規(guī)定了幀格式;通信雙方只要采用相同的幀格式和波特率,就能在未共享時(shí)鐘信號(hào)的情況下,僅用兩根信號(hào)線(Rx 和Tx)就可以完成通信過程,因此也稱為異步串行通信。UART總線雙向通信,可以實(shí)現(xiàn)全雙工傳輸和接收。在嵌入式設(shè)計(jì)中,UART用于主機(jī)與輔助設(shè)備通信,如汽車音響與外接AP之間的通信,與PC機(jī)通信包括與監(jiān)控調(diào)試器和其它器件,如EEPROM通信。
通常需要加入一個(gè)合適的電平轉(zhuǎn)換器,如SP3232E、SP3485,UART還能用于RS-232、RS-485 通信,或與計(jì)算機(jī)的端口連接。UART 應(yīng)用非常廣泛,手機(jī)、工業(yè)控制、PC 等應(yīng)用中都要用到UART。
在這里插入圖片描述
2. UART通信方式
UART使用的是 異步,串行通信方式。
串行通信
串行通信是指利用一條傳輸線將資料一位位地順序傳送。好比是一列縱隊(duì),每個(gè)數(shù)據(jù)元素依次縱向排列。如下圖所示,傳輸時(shí)一個(gè)比特一個(gè)比特的串行傳輸,每個(gè)時(shí)鐘周期傳輸一個(gè)比特,這種傳輸方式相對(duì)比較簡(jiǎn)單,速度較慢,但是使用總線數(shù)較少,通常一根接收線,一根發(fā)送線即可實(shí)現(xiàn)串行通信。
它的缺點(diǎn)是要增加額外的數(shù)據(jù)來控制一個(gè)數(shù)據(jù)幀的開始和結(jié)束。特點(diǎn)是通信線路簡(jiǎn)單,利用簡(jiǎn)單的線纜就可實(shí)現(xiàn)通信,降低成本,適用于遠(yuǎn)距離通信,但傳輸速度慢的應(yīng)用場(chǎng)合。
并行通信
并行通信好比一排橫隊(duì),齊頭并進(jìn)同時(shí)傳輸。這種通信方式每個(gè)時(shí)鐘周期傳輸?shù)臄?shù)據(jù)量和其總線寬度成正比,但是實(shí)現(xiàn)較為復(fù)雜。
在這里插入圖片描述
異步通信
異步通信以一個(gè)字符為傳輸單位,通信中兩個(gè)字符間的時(shí)間間隔多少是不固定的,然而在同一個(gè)字符中的兩個(gè)相鄰位間的時(shí)間間隔是固定的。
在異步通信技術(shù)中,數(shù)據(jù)發(fā)送方和數(shù)據(jù)接收方?jīng)]有同步時(shí)鐘,只有數(shù)據(jù)信號(hào)線,只不過發(fā)送端和接收端會(huì)按照協(xié)商好的協(xié)議(固定頻率)來進(jìn)行數(shù)據(jù)采樣。數(shù)據(jù)發(fā)送方以每秒鐘57600bits的速度發(fā)送數(shù)據(jù),接收方也以57600bits的速度去接收數(shù)據(jù),這樣就可以保證數(shù)據(jù)的有效和正確。通常異步通信中使用波特率(Baud-Rate)來規(guī)定雙方傳輸速度,其單位為bps(bits per second每秒傳輸位數(shù))。
同步通信
在發(fā)送數(shù)據(jù)信號(hào)的時(shí)候,會(huì)同時(shí)送出一根同步時(shí)鐘信號(hào), 用來同步發(fā)送方和接收方的數(shù)據(jù)采樣頻率。如下圖所示,同步通信時(shí),信號(hào)線1是一根同步時(shí)鐘信號(hào)線,以固定的頻率進(jìn)行電平的切換,其頻率周期為t,在每個(gè)電平的上升沿之后進(jìn)行對(duì)同步送出的數(shù)據(jù)信號(hào)線2進(jìn)行采樣(高電平代表1,低電平代表0),根據(jù)采樣數(shù)據(jù)電平高低取得輸出數(shù)據(jù)信息。如果雙方?jīng)]有同步時(shí)鐘的話,那么接收方就不知道采樣周期,也就不能正常的取得數(shù)據(jù)信息。
在這里插入圖片描述
3. 幀格式
數(shù)據(jù)傳送速率用波特率來表示,即每秒鐘傳送的二進(jìn)制位數(shù)。例如數(shù)據(jù)傳送速率為120字符/秒,而每一個(gè)字符為10位(1個(gè)起始位,7個(gè)數(shù)據(jù)位,1個(gè)校驗(yàn)位,1個(gè)結(jié)束位),則其傳送的波特率為10×120=1200字符/秒=1200波特。數(shù)據(jù)通信格式如下圖:
在這里插入圖片描述
其中各位的意義如下:
- 起始位:先發(fā)出一個(gè)邏輯”0”信號(hào),表示傳輸字符的開始。
- 數(shù)據(jù)位:可以是5~8位邏輯”0”或”1”。如ASCII碼(7位),擴(kuò)展BCD碼(8位)。小端傳輸
- 校驗(yàn)位:數(shù)據(jù)位加上這一位后,使得“1”的位數(shù)應(yīng)為偶數(shù)(偶校驗(yàn))或奇數(shù)(奇校驗(yàn))
- 停止位:它是一個(gè)字符數(shù)據(jù)的結(jié)束標(biāo)志??梢允?位、1.5位、2位的高電平。
- 空閑位:處于邏輯“1”狀態(tài),表示當(dāng)前線路上沒有資料傳送。
注:異步通信是按字符傳輸?shù)?,接收設(shè)備在收到起始信號(hào)之后只要在一個(gè)字符的傳輸時(shí)間內(nèi)能和發(fā)送設(shè)備保持同步就能正確接收。
下一個(gè)字符起始位的到來又使同步重新校準(zhǔn)(依靠檢測(cè)起始位來實(shí)現(xiàn)發(fā)送與接收方的時(shí)鐘自同步的)
關(guān)于RS-232、RS-422、RS-485等標(biāo)準(zhǔn),大家可以參考文章《一篇文章了解什么是串口,UART、RS-232、RS-422、RS-485 》
4. Exynos4412 Uart
本文討論UART 是基于Cortex-A9架構(gòu)的Exynos4412 為例。
1)特性
- Exynos4412 中UART,有4 個(gè)獨(dú)立的通道,每個(gè)通道都可以工作于中斷模式或DMA 模式,即UART 可以發(fā)出中斷或 DMA 請(qǐng)求以便在UART 、CPU 間傳輸數(shù)據(jù)。使用系統(tǒng)時(shí)鐘時(shí),Exynos4412 的 UART 波特率可以達(dá)到 4Mbps 。每個(gè)UART通道包含兩個(gè)FIFO用來接收和發(fā)送:
- 通道 0有 256 字節(jié)的發(fā)送 FIFO 和 256 字節(jié)的接收FIFO
- 通道 1、4有 64 字節(jié)的發(fā)送 FIFO 和 64 字節(jié)的接收FIFO
- 通道 2、3有 16 字節(jié)的發(fā)送FIFO 和 16 字節(jié) 的接收 FIFO 。
UART include:
- 波特率可以通過編程進(jìn)行 。
- 紅外接收/發(fā)送
- 每個(gè)通道支持停止位有 1位、 2位
- 數(shù)據(jù)位有 5、6、7或 8位
每個(gè)UART還包括
- 波特率發(fā)生器、發(fā)送器、接收器、控制邏輯組成。
2)Uart控制器
功能模塊
在這里插入圖片描述
每個(gè)UART包含一個(gè)波特率產(chǎn)生器,發(fā)送器,接收器和一個(gè)控制單元,如上圖所示:
- 發(fā)送數(shù)據(jù) CPU 先將數(shù)據(jù)寫入發(fā)送FIFO 中,然后 UART 會(huì)自動(dòng)將FIFO 中的數(shù)據(jù)復(fù)制到“發(fā)送移位器” (Transmit Shifter )中,發(fā)送移位器將數(shù)據(jù)一位一位地發(fā)送到 TxDn 數(shù)據(jù)線上 (根據(jù)設(shè)定的格式,插入開始位 、校驗(yàn)和停止)。
- 接收數(shù)據(jù) “移位器” (Receive Shifter )將 RxDn 數(shù)據(jù)線上的數(shù)據(jù)一位一位的接收進(jìn)來,然后復(fù)制到FIFO 中, CPU即可從中讀取數(shù)據(jù)。
UART是以異步方式實(shí)現(xiàn)通信的,其采樣速度由波特率決定,波特率產(chǎn)生器的工作頻率可以由PCLK(外圍設(shè)備頻率),F(xiàn)CLK/n(CPU工作頻率的分頻),UEXTCLK(外部輸入時(shí)鐘)三個(gè)時(shí)鐘作為輸入頻率,波特率設(shè)置寄存器是可編程的,用戶可以設(shè)置其波特率決定發(fā)送和接收的頻率。
發(fā)送器和接收器包含了64Byte的FIFO和數(shù)據(jù)移位器。UART通信是面向字節(jié)流的,待發(fā)送數(shù)據(jù)寫到FIFO之后,被拷貝到數(shù)據(jù)移位器(1字節(jié)大小)里,數(shù)據(jù)通過發(fā)送數(shù)據(jù)管腳TXDn發(fā)出。
同樣道理,接收數(shù)據(jù)通過RXDn管腳來接收數(shù)據(jù)(1字節(jié)大小)到接收移位器,然后將其拷貝到FIFO接收緩沖區(qū)里。
(1)數(shù)據(jù)發(fā)送 發(fā)送的數(shù)據(jù)幀可編程的,它的一個(gè)幀長(zhǎng)度是用戶指定的,它包括一個(gè)開始位,5~8個(gè)數(shù)據(jù)位,一個(gè)可選的奇偶校驗(yàn)位和1~2個(gè)停止位,數(shù)據(jù)幀格式可以通過設(shè)置ULCONn寄存器來設(shè)置。發(fā)送器也可以產(chǎn)生一個(gè)終止信號(hào),它是由一個(gè)全部為0的數(shù)據(jù)幀組成。在當(dāng)前發(fā)送數(shù)據(jù)被完全傳輸完以后,該模塊發(fā)送一個(gè)終止信號(hào)。在終止信號(hào)發(fā)送后,它可以繼續(xù)通過FIFO(FIFO)或發(fā)送保持寄存器(NON-FIFO)發(fā)送數(shù)據(jù)。
(2)數(shù)據(jù)接收 同樣接收端的數(shù)據(jù)也是可編程的,接收器可以偵測(cè)到溢出錯(cuò)誤奇偶校驗(yàn)錯(cuò)誤,幀錯(cuò)誤和終止條件,每個(gè)錯(cuò)誤都可以設(shè)置一個(gè)錯(cuò)誤標(biāo)志。• 溢出錯(cuò)誤 :在舊數(shù)據(jù)被讀取到之前,新數(shù)據(jù)覆蓋了舊數(shù)據(jù) • 奇偶校驗(yàn)錯(cuò)誤:接收器偵測(cè)到了接收數(shù)據(jù)校驗(yàn)結(jié)果失敗,接收數(shù)據(jù)無效 • 幀錯(cuò)誤 :接收到的數(shù)據(jù)沒有一個(gè)有效的停止位,無法判定數(shù)據(jù)幀結(jié)束 • 終止條件 :RxDn接收到保持邏輯0狀態(tài)持續(xù)長(zhǎng)于一個(gè)數(shù)據(jù)幀的傳輸時(shí)間
(3)自動(dòng)流控AFC(Auto Float Control) UART0和UART1支持有nRTS和nCTS的自動(dòng)流控。在AFC情況下,通信雙方nRTS和nCTS管腳分別連接對(duì)方的nCTS和nRTS管腳。通過軟件控制數(shù)據(jù)幀的發(fā)送和接收。在開啟AFC時(shí),發(fā)送端接收發(fā)送前要判斷nCTS信號(hào)狀態(tài),當(dāng)接收到nCTS激活信號(hào)時(shí),發(fā)送數(shù)據(jù)幀。該nCTS管腳連接對(duì)方nRTS管腳。接收端在準(zhǔn)備接收數(shù)據(jù)幀前,其接收器FIFO有大于32個(gè)字節(jié)的空閑空間,nRTS管腳會(huì)發(fā)送激活信號(hào),當(dāng)其接收FIFO小于32個(gè)字節(jié)的空閑空間,nRTS必須置非激活狀態(tài)。
在這里插入圖片描述
3)選擇時(shí)鐘源
Exynos4412 UART的時(shí)鐘源有八種選擇:XXTI 、XusbXTI 、SCLK_HDMI24M 、SCLK_USBPHY0 、 SCLK_HDMIPHY 、SCLKMPLL_USER_T 、SCLKEPLL 、SCLKVPLL ,由 CLK_SRC_PERIL0 寄存器控制。
選擇好時(shí)鐘源后,還可以通過 DIVUART0 ~4設(shè)置分頻系數(shù),由 CLK_DIV_PERIL0 寄存器控制。從分頻器得到的時(shí)鐘被稱為SCLK UART 。
SCLK UART 經(jīng)過上圖中的“ UCLK Generator”后,得到UCLK ,它的頻率就是UART 的波特率。“ Generator UCLK Generator ”通過這 2個(gè)寄存器來設(shè)置:UBRDIVn(UART BAUD RATE DIVISOR) 、UFRACVALn 。
4)UART配置寄存器
在這里插入圖片描述
ULCONn
- bite [6] 紅外模式 選擇串口0是否使用紅外模式:0 = 正常通信模式 1 = 紅外通信模式
- bite [5:3] 校驗(yàn)?zāi)J?設(shè)置串口0在數(shù)據(jù)接收和發(fā)送時(shí)采用的校驗(yàn)方式:0xx = 無校驗(yàn) 100 = 奇校驗(yàn) 101 = 偶校驗(yàn) 110 = 強(qiáng)制校驗(yàn)/檢測(cè)是否為1 111 = 強(qiáng)制校驗(yàn)/檢測(cè)是否為0
- [2] 停止位 設(shè)置串口0停止位數(shù):0 = 每個(gè)數(shù)據(jù)幀一個(gè)停止位 1 = 每個(gè)數(shù)據(jù)幀二個(gè)停止位
- [1:0] 數(shù)據(jù)位 設(shè)置串口0數(shù)據(jù)位數(shù):00 = 5個(gè)數(shù)據(jù)位 01 = 6個(gè)數(shù)據(jù)位 10 = 7個(gè)數(shù)據(jù)位 11 = 8個(gè)數(shù)據(jù)位
該寄存器我們通用的配置是:
- ULCON2 = 0x3; //Normal mode, No parity,One stop bit,8 data bits
UCONn
- [15:12] FCLK分頻因子 當(dāng)UART0選擇FCLK作為時(shí)鐘源時(shí),設(shè)置其FCLK的分頻因子 UART0 工作時(shí)鐘頻率 = FCLK/ FCLK分頻因子 + 6
- [11:10] UART時(shí)鐘源選擇 選擇UART0的工作時(shí)鐘PCLK,UEXTCLK,F(xiàn)CLK/n:00,10 = PCLK 01 = UEXTCLK 11 = FCLK/n 當(dāng)選擇FCLK/n作為UART0工作時(shí)鐘時(shí)還要做其它設(shè)置,具體請(qǐng)讀者自行查看硬件手冊(cè)
- [9] 發(fā)送數(shù)據(jù)中斷產(chǎn)生類型 設(shè)置UART0中斷請(qǐng)求類型,在非FIFO傳輸模式下,一旦發(fā)送數(shù)據(jù)緩沖區(qū)為空,立即產(chǎn)生中斷信號(hào),在FIFO傳輸模式下達(dá)到發(fā)送數(shù)據(jù)觸發(fā)條件時(shí)立即產(chǎn)生中斷信號(hào):0 = 脈沖觸發(fā) 1 = 電平觸發(fā)
- [8] 接收數(shù)據(jù)中斷產(chǎn)生類型 設(shè)置UART0中斷請(qǐng)求類型,在非FIFO傳輸模式下,一旦接收到數(shù)據(jù),立即產(chǎn)生中斷信號(hào),在FIFO傳輸模式下達(dá)到接收數(shù)據(jù)觸發(fā)條件時(shí)立即產(chǎn)生中斷信號(hào):0 = 脈沖觸發(fā) 1 = 電平觸發(fā)
- [7] 接收數(shù)據(jù)超時(shí) 設(shè)置當(dāng)接收數(shù)據(jù)時(shí),如果數(shù)據(jù)超時(shí),是否產(chǎn)生接收中斷:0 = 不開啟超時(shí)中斷 1 = 開啟超時(shí)中斷 10 = 7個(gè)數(shù)據(jù)位 11 = 8個(gè)數(shù)據(jù)位
- [6] 接收數(shù)據(jù)錯(cuò)誤中斷 設(shè)置當(dāng)接收數(shù)據(jù)時(shí),如果產(chǎn)生異常,如傳輸中止,幀錯(cuò)誤,校驗(yàn)錯(cuò)誤時(shí),是否產(chǎn)生接收狀態(tài)中斷信號(hào):0 = 不產(chǎn)生錯(cuò)誤狀態(tài)中斷 1 = 產(chǎn)生錯(cuò)誤狀態(tài)中斷
- [5] 回送模式 設(shè)置該位時(shí)UART會(huì)進(jìn)入回送模式,該模式僅用于測(cè)試 0 = 正常模式 1 = 回送模式
- [4] 發(fā)送終止信號(hào) 設(shè)置該位時(shí),UART會(huì)發(fā)送一個(gè)幀長(zhǎng)度的終止信號(hào),發(fā)送完畢后,該位自動(dòng)恢復(fù)為0 0 = 正常傳輸 1 = 發(fā)送終止信號(hào)
- [3:2] 發(fā)送模式 設(shè)置采用哪個(gè)方式執(zhí)行數(shù)據(jù)寫入發(fā)送緩沖區(qū) 00 = 無效 01 = 中斷請(qǐng)求或查詢模式 10 = DMA0請(qǐng)求
- [1:0] 接收模式 設(shè)置采用哪個(gè)方式執(zhí)行數(shù)據(jù)寫入接收緩沖區(qū) 00 = 無效 01 = 中斷請(qǐng)求或查詢模式 10 = DMA0請(qǐng)求
該寄存器通用配置為:
- UCON2 = 0x5; //Interrupt request or polling mode
一般裸機(jī)情況下,采用輪詢模式。
UTRSTATn
UTRSTAT n寄存器用來表明數(shù)據(jù)是否已經(jīng)發(fā)送完畢、是否已經(jīng)接收到數(shù)據(jù),格式如下圖所示,上面說的“緩沖區(qū)”,其實(shí)就是下圖中的 FIFO ,不使用 FIFO 功能時(shí)可以認(rèn)為其深度為 1。
當(dāng)我們讀取數(shù)據(jù)時(shí),就輪詢檢查bit[0]置1之后,然后再從URXHn寄存器讀取數(shù)據(jù);當(dāng)我們讀取數(shù)據(jù)時(shí),就輪詢檢查bit[1]置1之后,然后再向UTXHn寄存器寫入數(shù)據(jù)來發(fā)送數(shù)據(jù);
在這里插入圖片描述
在這里插入圖片描述
UTXHn寄存器(UART TRANSMIT BUFFER REGISTER)
CPU 將數(shù)據(jù)寫入這個(gè)寄存器, UART即會(huì)將它保存到緩沖區(qū)中,并自動(dòng)發(fā)送出去。
URXHn寄存器(UART RECEIVE BUFFER REGISTER)
當(dāng) UART 接收到數(shù)據(jù)時(shí),讀取這個(gè)寄存器,即可獲得數(shù)據(jù)。
UFRACVALn 計(jì)算波特率
在這里插入圖片描述
根據(jù)給定的波特率、所選擇時(shí)鐘源頻率,可以通過以下公式計(jì)算 UBRDIVn 寄存器 (n 為 0~4,對(duì)應(yīng) 5個(gè) UART 通道 )的值。
- UBRDIVn = (int)( UART clock / ( buad rate x 16) ) – 1
上式計(jì)算出來的 UBRDIVn 寄存器值不一定是整數(shù), UBRDIVn 寄存器取其整數(shù)部分,小部分由 UFRACVALn 寄存器設(shè)置, UFRACVALn 寄存器的引入,使產(chǎn)生波特率更加精確。「【舉例】」當(dāng)UART clock為100MHz時(shí),要求波特率為115200 bps,則:
- 100000000/(115200 x 16) – 1 = 54.25 – 1 = 53.25
- UBRDIVn = 整數(shù)部分 = 53
- UFRACVALn/16 = 小數(shù)部分 = 0.25
- UFRACVALn = 4
5)電路圖
外設(shè)電路圖:
在這里插入圖片描述
SP3232EEA 用來將TTL電平轉(zhuǎn)換成RS232電平。我們使用的是COM2。
外設(shè)與核心板連接電路圖
在這里插入圖片描述
可見UART的收發(fā)引腳連接到了GPA上,打開exynos4412芯片手冊(cè):
在這里插入圖片描述
我們只需要將GPA1 的低8位設(shè)置為0x22。
6.實(shí)例代碼
裸機(jī)代碼,主要實(shí)現(xiàn)uart_init()、putc()、getc()這三個(gè)函數(shù)。
uart_init()
該函數(shù)主要配置UART的,波特率115200,數(shù)據(jù)位:8,奇偶校驗(yàn)位:0,終止位:1,不設(shè)置流控。
如下圖:是運(yùn)行在windows下常用的串口工具配置信息,配置信息必須完全一致。
在這里插入圖片描述
putc()
該函數(shù)是向串口發(fā)送一個(gè)數(shù)據(jù)data,他的實(shí)現(xiàn)邏輯就是輪詢檢查寄存器UART2.UTRSTAT2 ,判斷其bite【1】是否置1,如果置1,則向UART2.UTXH2存入要發(fā)送的數(shù)據(jù)即可。
getc()
該函數(shù)是從串口接收一個(gè)數(shù)據(jù)data,他的實(shí)現(xiàn)邏輯就是輪詢檢查寄存器UART2.UTRSTAT2 ,判斷其bite【0】是否置1,如果置1,說明數(shù)據(jù)準(zhǔn)備好,則可以從寄存器UART2.URXH2取出數(shù)據(jù)。
代碼
- /*
- * UART2
- */
- typedef struct {
- unsigned int ULCON2;
- unsigned int UCON2;
- unsigned int UFCON2;
- unsigned int UMCON2;
- unsigned int UTRSTAT2;
- unsigned int UERSTAT2;
- unsigned int UFSTAT2;
- unsigned int UMSTAT2;
- unsigned int UTXH2;
- unsigned int URXH2;
- unsigned int UBRDIV2;
- unsigned int UFRACVAL2;
- unsigned int UINTP2;
- unsigned int UINTSP2;
- unsigned int UINTM2;
- }uart2;
- #define UART2 ( * (volatile uart2 *)0x13820000 )
- /* GPA1 */
- typedef struct {
- unsigned int CON;
- unsigned int DAT;
- unsigned int PUD;
- unsigned int DRV;
- unsigned int CONPDN;
- unsigned int PUDPDN;
- }gpa1;
- #define GPA1 (* (volatile gpa1 *)0x11400020)
- void uart_init()
- { /*UART2 initialize*/
- GPA1.CON = (GPA1.CON & ~0xFF ) | (0x22); //GPA1_0:RX;GPA1_1:TX
- UART2.ULCON2 = 0x3; //Normal mode, No parity,One stop bit,8 data bits
- UART2.UCON2 = 0x5; //Interrupt request or polling mode
- //Baud-rate : src_clock:100Mhz
- UART2.UBRDIV2 = 0x35;
- UART2.UFRACVAL2 = 0x4;
- }
- void putc(const char data)
- { while(!(UART2.UTRSTAT2 & 0X2));
- UART2.UTXH2 = data;
- if (data == '\n')
- putc('\r');
- }
- char getc(void)
- { char data;
- while(!(UART2.UTRSTAT2 & 0x1));
- data = UART2.URXH2;
- if ((data == '\n')||(data == '\r'))
- {
- putc('\n');
- putc('\r');
- }else
- putc(data);
- return data;
- }
puts/gets
- void puts(const char *pstr)
- { while(*pstr != '\0')
- putc(*pstr++);
- }
- void gets(char *p)
- { char data;
- while((data = getc())!= '\r')
- { if(data == '\b')
- {p--;
- }
- *p++ = data;
- }
- if(data == '\r')
- *p++ = '\n';
- *p = '\0';
- }
7.如何裸機(jī)程序可以支持printf函數(shù)
首先看下文件的目錄結(jié)構(gòu):
代碼架構(gòu)
老規(guī)矩,關(guān)注,后臺(tái)回復(fù)【armprintf】,就可以得到代碼。
這里我們只貼出部分文件的代碼。
「cpu/start.s」改文件主要是實(shí)現(xiàn)異常向量表,實(shí)現(xiàn)各個(gè)模式的棧初始化
- .text
- .global _start
- _start:
- b reset
- ldr pc,_undefined_instruction
- ldr pc,_software_interrupt
- ldr pc,_prefetch_abort
- ldr pc,_data_abort
- ldr pc,_not_used
- ldr pc,=irq_handler
- ldr pc,_fiq
- _undefined_instruction: .word _undefined_instruction
- _software_interrupt: .word _software_interrupt
- _prefetch_abort: .word _prefetch_abort
- _data_abort: .word _data_abort
- _not_used: .word _not_used
- _irq: .word irq_handler
- _fiq: .word _fiq
- reset:
- ldr r0,=0x40008000
- mcr p15,0,r0,c12,c0,0 @ 協(xié)處理器指令設(shè)置異常向量表地址
- init_stack:
- ldr r0,stacktop /*get stack top pointer*/
- /********svc mode stack********/
- mov sp,r0
- sub r0,#128*4 /*512 byte for irq mode of stack*/
- /****irq mode stack**/
- msr cpsr,#0xd2
- mov sp,r0
- sub r0,#128*4 /*512 byte for irq mode of stack*/
- /***fiq mode stack***/
- msr cpsr,#0xd1
- mov sp,r0
- sub r0,#0
- /***abort mode stack***/
- msr cpsr,#0xd7
- mov sp,r0
- sub r0,#0
- /***undefine mode stack***/
- msr cpsr,#0xdb
- mov sp,r0
- sub r0,#0
- /*** sys mode and usr mode stack ***/
- msr cpsr,#0x10
- mov sp,r0 /*1024 byte for user mode of stack*/
- b main @跳轉(zhuǎn)到c語言的main函數(shù)
- .align 4
- /**** swi_interrupt handler ****/
- /**** irq_handler ****/
- irq_handler:
- sub lr,lr,#4
- stmfd sp!,{r0-r12,lr}
- .weak do_irq @該函數(shù)可以沒有定義
- bl do_irq @跳轉(zhuǎn)到中斷入口
- ldmfd sp!,{r0-r12,pc}^
- stacktop: .word stack+4*512 @定義棧頂
- .data
- stack: .space 4*512 @分配一塊棧空間
「lib/printf.c」
該文件主要實(shí)現(xiàn)打印函數(shù)printf一些格式控制,一些字符串轉(zhuǎn)換算數(shù)運(yùn)算需要借助頭文件ctype.h、stdarg.h中一些宏。其中vsprintf 具體的實(shí)現(xiàn)我們就不再詳解,有興趣讀者自行研究。
- ……
- void printf (const char *fmt, ...)
- {
- va_list args;
- unsigned int i;
- char printbuffer[100];
- va_start (args, fmt);
- /* For this to work, printbuffer must be larger than
- * anything we ever want to print.
- */
- i = vsprintf (printbuffer, fmt, args);//對(duì)輸入的參數(shù)進(jìn)行格式整理
- va_end (args);
- puts (printbuffer); //調(diào)用上一章我們封裝的puts函數(shù)實(shí)現(xiàn)向串口打印書字符串
- }
「main.c」該文件可以直接調(diào)用printf()函數(shù)來打印信息了。
- void delay_ms(unsigned int num)
- {
- int i,j;
- for(i=num; i>0;i--)
- for(j=1000;j>0;j--)
- ;
- }
- /*
- * 裸機(jī)代碼,不同于LINUX 應(yīng)用層, 一定加循環(huán)控制
- */
- int main (void)
- {
- int i = 0;
- while (1) {
- printf("aaaaaaaaaaaaa\n");
- delay_ms(500);
- }
- return 0;
- }
「Makefile」
- CROSS_COMPILE = arm-none-eabi-
- NAME =gcd
- CFLAGS=-mfloat-abi=softfp -mfpu=vfpv3 -mabi=apcs-gnu -fno-builtin -fno-builtin-function -g -O0 -c -I ./include -I ./lib
- LD = $(CROSS_COMPILE)ld
- CC = $(CROSS_COMPILE)gcc
- OBJCOPY = $(CROSS_COMPILE)objcopy
- OBJDUMP = $(CROSS_COMPILE)objdump
- OBJS=./cpu/start.o ./driver/uart.o \
- ./driver/_udivsi3.o ./driver/_divsi3.o ./driver/_umodsi3.o main.o ./lib/printf.o
- #=============================================================================#
- all: $(OBJS)
- $(LD) $(OBJS) -T map.lds -o $(NAME).elf
- $(OBJCOPY) -O binary $(NAME).elf $(NAME).bin
- $(OBJDUMP) -D $(NAME).elf > $(NAME).dis
- %.o: %.S
- $(CC) $(CFLAGS) -c -o $@ $<
- %.o: %.s
- $(CC) $(CFLAGS) -c -o $@ $<
- %.o: %.c
- $(CC) $(CFLAGS) -c -o $@ $<
- clean:
- rm -rf $(OBJS) *.elf *.bin *.dis *.o