S3C2440 6 串列埠的printf實現

2022-09-09 19:18:23 字數 4246 閱讀 3745

三.結合uart實現

在之前stm32的學習中,我在串列埠輸出除錯資訊的時候,經常採用printf()函式作為串列埠輸出函式,這樣不僅方便除錯而且**易讀。

在s3c2440的學習中,對於uart同樣需要對串列埠輸出資訊進行除錯,那麼在這裡可不可以使用printf函式呢?

當然是可以的,不過相比於stm32中簡單的配置,s3c2440中對printf的使用要深入到printf函式的原理,將printf函式進行徹頭徹尾的刨析,然後結合底層的uart輸出,使用printf列印除錯資訊,其實現的前提是:

對printf函式的刨析,由my_printf.c實現,由my_printf.c呼叫uart底層的putchar函式即可實現uart的printf輸出。

在標準庫中定義了printf函式,其宣告如下:

可以看出printf的引數為:(const char *format, …)

其中:format代表固定引數,…代表可變引數,固定引數中含有格式字元

(聽起來有點懵逼,下面解讀一下引數的內容)

顧名思義,固定引數就是一串固定的字串, *format就是字串的首位址,根據首位址我們可以將字串的全部內容輸出。欸,可是固定引數中也有特殊的字元啊,那就是格式字元,格式字元用來說明可變引數的資料型別和個數,根據固定引數中的格式字元,可以將可變引數按照格式列印出來。**所以利用printf列印資訊的主要步驟就是解析固定引數以及固定引數中的格式字元,將可變引數在格式字元位置輸出,直到固定引數解析完畢!**這樣就可以將引數中像表達的內容完整地列印出來。

格式字元:

例如:

printf

("i'm %s, my id is %d ,my score is %.2f !!!\n"

,"bob",25

,98.2

);

執行結果如下:

printf是從固定引數」i』m %s, my id is %d ,my score is %.2f !!!\n」的首位址開始解析的,逐個輸出字元,直到第乙個格式字元%s,對應了後面可變引數的「bob」字串,在格式字元的位置上列印可變引數,然後繼續解析固定引數繼續列印字元,直到第二個格式字元%d,在%d的位置列印可變引數25,然後繼續解析直到第三個格式字元%.2f,根據格式字元列印出98.20,然後繼續解析,直到最後乙個轉義字元「\n」,結束了固定引數的解析,也完成了資料的列印輸出。

沒遇到%就直接輸出,遇到%根據格式符前導碼分情況處理。

我們了解了引數組成,以及printf列印輸出的流程,那麼問題來了,固定引數的首位址是直接傳進來的,那可變引數的位址怎麼得到呢???

實際上,引數都儲存在棧上的,而且固定引數和可變引數的位址是連續的,對!!!因為是連續的,所以我們就可以通過指標的移動和取值,由固定引數得到可變引數的位址,從而列印出可變引數。

下面有一段**來解釋獲取可變引數的過程:

(參考自百問科技**)

#include

struct person

;/*

*int printf(const char *format, ...);

*依據:x86平台,函式呼叫時引數傳遞是使用堆疊來實現的

*目的:將所有傳入的引數全部列印出來

*/int

push_test

(const

char

*format,..

.)intmain

(void

)

這是已知可變引數情況下的輸出,目的就是理解如何通過固定引數得到可變引數,通過棧空間的移動取值就可以!

在stdarg.h中,有解決變參問題的一些巨集定義,改進後作注釋如下:

/* 引數指標 */

typedef

char

* va_list;

/* 考慮到位址對齊,對齊大小為sizeof(int) */

/* 比如,如果sizeof(n)在1-4之間,那麼_intsizeof(n)=4;如果sizeof(n)在5-8之間,那麼 _intsizeof(n)=8。 */

/* & ~(sizeof(int) - 1)相當於將0~3掩蓋掉 sizeof(int) - 1 相當於增加0~3 如此,就只取決於sizeof(n)的大小 */

#define _intsizeof(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

/* 得到第乙個可變引數的起始位址,傳給ap */

#define va_start(ap,v) ( ap = (va_list)&v + _intsizeof(v) )

/* 移動指標到下乙個引數,取值 */

#define va_arg(ap,t) (*(t *)(ap = ap + _intsizeof(t), ap - _intsizeof(t)))

/* 結束,防止ap成為野指標 */

解決了變參問題後,剩下的就是對printf函式的封裝了,封裝引出的介面就是:outc()輸出乙個字元、outs()輸出字串,到時候只要將這倆個介面和uart的putchar()函式、puts()函式對應起來,就可以實現uart使用printf了。

printf輸出列印的核心部分:輸出固定引數及可變引數的函式

/*reference :   int vprintf(const char *format, va_list ap); */

/* 輸出固定引數及可變引數的函式 */

static

intmy_vprintf

(const

char

*fmt, va_list ap)

//format : %08d, %8d,%d,%u,%x,%f,%c,%s

fmt++;if

(*fmt ==

'0')

lead=

' ';

maxwidth=0;

while

(*fmt >=

'0'&&

*fmt <=

'9')

switch

(*fmt)

}return0;

}

裡面的out_num()函式是根據格式字元輸出不同形式的數字,其函式定義如下:

static

intout_num

(long n,

int base,

char lead,

int maxwidth)

while

((m /

= base)!=0

);if( maxwidth && count < maxwidth)

if(n <0)

*--s =

'-';

return

outs

(s);

}

所以,printf函式的封裝就如下:

int

printf

(const

char

*fmt,..

.)

上面我們知道,對printf函式的刨析,由my_printf.c實現,由my_printf.c呼叫uart底層的putchar函式即可實現uart的printf輸出,呼叫介面就是outc()、outs()函式,只要如下設定就ok:

static

intoutc

(int c)

static

int outs (

const

char

*s)

對uart的配置已經在之前的部落格中有詳細介紹了。

有問題可以q我,一起學習:2723808286

s3c2410 s3c2440串列埠波特率的計算

要正確計算串列埠波特率,首先要搞清楚晶元的時鐘工作原理,這部分在s3c2410 s3c2440datasheet上寫的比較詳細,但對新手來說結合thisway同志 s3c2410完全開發流程 中的timer和clock兩個實驗,邊做實驗邊看資料,更容易理解。我這裡只根據我的失敗經驗談一下設定波特率暫...

289 S32K144串列埠的查詢接收模式

完整的s32k144的學習彙總如下 繼續s32k144的學習,還是繼續串列埠的學習。因為我覺得前面實現的這種阻塞收發模式雖然穩定,但是還有改進空間。尤其是dma的使用,在這種模式下似乎是不奏效的。其實,傳送功能倒還好一點,接收功能,我覺得還是得實現查詢的方式。檢視介面資訊,發現其實這個似乎也是已經實...

STC12C5A60S2筆記8(串列埠)

1.基本特性 1 概念 1.1 並行通訊 資料的各位同時進行傳輸,傳輸速度快 1.2 序列通訊 通過一根或兩根資料線傳輸資料,資料在訊號線上依次傳輸 序列通訊分為同步和非同步兩種 1.2.1 非同步通訊 接收器和傳送器使用各自的時鐘,非同步 每個字元要用起始位和停止位作為資料傳輸的開始和結束 在非同...