嵌入式中,調(diào)試手段通常有兩種,一是遠程gdb,一是直接printf。如果是調(diào)試自己玩的小板子,用gdb有點大張旗鼓了,大多數(shù)情況下printf就可以搞定。不過printf的問題是stdiolib的size太大,稍微有點程序,加上幾個常用的庫,比如stdio和string,超過16k甚至32k(已經(jīng)大于一些低端芯片的flash容量了)是很正常的事情,而且通常比較慢,程序越多,越麻煩。道理很簡單,標準C語言庫的規(guī)范中,Printf()必須處理大量的數(shù)據(jù)格式,包括字符串、字符、(各種長度的有符號和無符號)數(shù)字,以及浮點值。而且格式字符串還要包括用于更改文本對齊、基數(shù)、間距、字段寬度和精度的調(diào)節(jié)器和指示器。符合這個規(guī)范的代碼必然會是冗長和繁重的。一些嵌入式系統(tǒng)庫倒是提供了一些之針對整數(shù)的printf,但還是有問題,首先是還是太大,其次是你沒有自己的調(diào)整權限。
其實printf也就是IO的調(diào)用包裝而已,我們完全可以自己寫一個簡易版本的printf滿足自己的需要,并隨時根據(jù)需要裁剪。具體來說,printf在這里要起的作用就是將調(diào)試字符串從嵌入式目標空閑的串口壓出,并在運行于宿主工作站的終端模擬器上顯示結(jié)果。下面就簡單介紹一下,如何來自己寫一個簡易printf函數(shù)。
要寫printf,首先要知道什么是可變參數(shù)傳遞,我們來看看標準庫里面,是如何定義可變參數(shù)實現(xiàn)的:
#define _AUPBND (sizeof (acpi_native_int) - 1)
#define _ADNBND (sizeof (acpi_native_int) - 1)
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T,_AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) +(_bnd(A,_AUPBND))))
關于可變參數(shù)的原理,網(wǎng)上有一些文章,總結(jié)來說,就是我們可以通過Intel80×86機器的對齊特性來獲得所有的參數(shù),因為在Intel80×86機器上,每個變量的地址都要是sizeof(int)的倍數(shù),這樣能提升CPU運行的效率。也就是說,所有參數(shù)的首地址都要是4的倍數(shù),就算你是char型的,那浪費3個byte也要安排你占第四個坑。
好,由于C語言傳遞參數(shù)時是用push指令從右到左將參數(shù)逐個壓棧,因此我們通過棧指針跳4n格來訪問第n個參數(shù),不要忘了,參數(shù)的地址都是字對齊的。這里,我們用#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) &(~(bnd)))來計算類型為X的參數(shù)在棧中占據(jù)的字對齊后的字節(jié)數(shù)。bnd是sizeof (acpi_native_int) –1,acpi_native_unit在32位機的定義是:
typedef u32 acpi_native_uint;
所以( ~(bnd))就是0xfffffffc 。 因此,_bnd(X,bnd) 宏在32位機下就是
( (sizeof(X) + 3)&0xfffffffc )
很明顯,其作用是–倘若sizeof(X)不是4的整數(shù)倍,將其變?yōu)?的整數(shù)倍。
va_start(ap,A) 負責初始化參數(shù)指針ap,將函數(shù)參數(shù)A右邊第一個參數(shù)的地址賦給ap,這個第一個參數(shù)通常就是printf里面的”%x%d%f%d”。
va_arg(ap,T) 可以獲得ap指向參數(shù)的值,并使ap指向下一個參數(shù),T用來指明當前參數(shù)類型。
在這里,上述代碼還是麻煩,而且sizeof我們也不能直接用,所以我們不如干脆直接寫一個不那么麻煩而有針對性的可變參數(shù)操作定義:
有了這幾個定義,print函數(shù)就好寫了,為了節(jié)省空間,這個簡單的print()只支持“%s”,“%d”和”%c”格式的分類符,暫時不需要其他功能,比如格式對齊之類的,當然,可以根據(jù)自己的需要擴展這個函數(shù)。
int print( const char *fmt, 。.. )
{
const char *s;
char c;
int d;
va_list ap;
va_start(ap, fmt);
while( *fmt != ‘\0’ )
{
if( *fmt != ‘%’ )
{
uart_putc(*fmt++);
continue;
}
switch(*++fmt)
{
case ‘s’:
{
s = va_arg(ap, const char *);
uart_puts(s);
break;
}
case ‘d’:
{
d = va_arg(ap, int);
uart_putints(d, 10);
break;
}
case ‘c’:
{
c = va_arg(ap, char);
uart_putc(c);
break;
}
default:
uart_putc(*fmt);
}
fmt++;
}
va_end(ap);
return 1;
}
這里面有一些函數(shù),uart_putc是串口驅(qū)動程序,給串口送東西的,uart_puts是簡單的多重putc包裝。uart_putints則需要做一些atoi的轉(zhuǎn)換,一個比較簡單但是有效的atoi程序宏定義如下:
#define ATOI(X, result) \
do{ \
char *lptr = X; \
result = 0; \
while (1) \
{ \
if ((*lptr 》= ‘0’) && (*lptr 《= ‘9’)) \
{ \
result *= 10; \
result += *lptr - ‘0’; \
lptr++; \
} \
else \
{ \
break; \
} \
} \
}while(0)
責任編輯:gt
-
嵌入式
+關注
關注
5152文章
19672瀏覽量
317534 -
C語言
+關注
關注
180文章
7632瀏覽量
141728
發(fā)布評論請先 登錄
信號處理原理-簡易版
如何學習嵌入式系統(tǒng)
FAT精減版(嵌入式應用的版本)--嵌入式FAT文件系統(tǒng)源碼免費下載
嵌入式操作系統(tǒng)有哪些
什么是嵌入式系統(tǒng)
單片機實現(xiàn)簡易版shell的方法和原理是什么
什么是嵌入式系統(tǒng)?深嵌入式系統(tǒng)又是什么
嵌入式系統(tǒng)是否需要操作系統(tǒng)
學習嵌入式系統(tǒng)需要涉及哪些工作
嵌入式Linux中printf重定向到串口

評論