?? printf
字號:
printf在uCOS51上的移植和浮點數(shù)顯示
asdjf@163.com 2003/10/20
printf函數(shù)是C語言里應(yīng)用最為廣泛的函數(shù)之一,我們初學(xué)C語言時實現(xiàn)的第一個程序《Hello the world》,就包含printf語句。它的應(yīng)用十分靈活,可以打印各種類型數(shù)據(jù),可變數(shù)量的變量,表達式,是非常理想的輸出函數(shù),廣泛用于結(jié)果輸出,中間變量顯示,調(diào)試等。然而,編譯器將其作為標準庫函數(shù),不提供源代碼,其本身代碼量也偏大,無法實現(xiàn)嵌入式系統(tǒng)按需裁減的要求,并且有些printf庫代碼不支持重入。
解決方法是把Linux里的相關(guān)源碼簡化后移植到C51里。關(guān)鍵點在于理解變參函數(shù)、參數(shù)傳遞規(guī)則、浮點數(shù)存儲格式。
C編譯器一般將函數(shù)參數(shù)按從右至左的順序依次壓入堆棧(C51在使用reentrant關(guān)鍵字后也這么處理),函數(shù)內(nèi)部處理參數(shù)變量時直接在堆棧上尋址,局部變量緊跟在參數(shù)后面存放,函數(shù)返回時出棧,參數(shù)和局部變量所占用空間自動釋放。例如:
fun(char *fmt,char a,int b long c,float d) reentrant
的堆棧結(jié)構(gòu)如圖1所示:
------------------
|float d 4 bytes |
+10 ------------------
|long c 4 bytes |
+6 ------------------
|int b 2 bytes |
+4 ------------------
|char a 1 bytes |
+3 ------------------
|char *fmt 3bytes|
SP+0-->------------------
| 局部變量 |
------------------
圖1.fun函數(shù)參數(shù)和局部變量在堆棧里的結(jié)構(gòu)
C51編譯器從右向左依次將float/long/int/char/char *壓入仿真堆棧,各種數(shù)據(jù)類型所占空間大小如圖1,例如char占1字節(jié),float占4字節(jié)等。值得一提的是,常數(shù)壓棧的格式:0-255按1字節(jié)壓棧,256-32767壓成2字節(jié),32768(8000H)或以上壓成4字節(jié),帶有l(wèi)/L結(jié)尾的常數(shù)占4字節(jié)。
上面的函數(shù)fun內(nèi)部可以通過函數(shù)名稱訪問各個變量,C編譯器自動把函數(shù)名轉(zhuǎn)換成地址,如:訪問long c轉(zhuǎn)換成訪問SP+6,訪問char a轉(zhuǎn)換成訪問SP+3等。寫成表達式為:
c=0x12345678;======>(SP+6)=0x12345678
a='y';=============>(SP+3)='y'
總之,上面的函數(shù)通過顯式地指定函數(shù)名和數(shù)據(jù)類型完成參數(shù)的傳遞和訪問,內(nèi)部細節(jié)由C編譯器完成,對用戶透明。
這種方式的好處是表達清晰,結(jié)構(gòu)嚴謹,屏蔽底層細節(jié);壞處是不夠靈活,參數(shù)必須在處理前顯式確定并固定不變,這給我們用同一函數(shù)處理不同情況帶來了困難,C的解決方案是引入“變參函數(shù)”(詳見C語言大全),如下:
fun(char *fmt,...) reentrant
...表示有0到N個可變數(shù)量參數(shù),C編譯器此時不檢查參數(shù)匹配,傳遞參數(shù)規(guī)律與一般函數(shù)相同。如果我們用這個函數(shù)取代前一個函數(shù),但仍按前一函數(shù)的調(diào)用方式調(diào)用,那么,參數(shù)在堆棧里的位置仍如圖1所示。此時,函數(shù)形參只有“...”沒有具體變量名,如何引用形參變量呢?觀察圖1堆棧結(jié)構(gòu)可知,如果知道堆棧內(nèi)第一個參數(shù)的起址和每個參數(shù)的數(shù)據(jù)類型及他們的排列順序,就可以通過指針訪問指定的變量。例如:
知道堆棧內(nèi)第一個參數(shù)的起址SP和每個參數(shù)的數(shù)據(jù)類型及排列順序(char*/char/int/long/float),就可以通過SP,SP+3,SP+4,SP+6,SP+10訪問原來必須通過參數(shù)名訪問的fmt,a,b,c,d變量。寫成C語言就是:
fun("yy",'y',(int)2,5L,-12.5);
fun(char *fmt,...) reentrant
{
void *p;
p=&fmt;
//此時*p指向字符串"yy"首址,**p是字符串第一個字符'y'。
p=(char **)p+1;
//此時*((char *)p)為字符'y'。
p=(char *)p+1;
//此時*((int *)p)為0x0002。
p=(int *)p+1;
//此時*((long *)p)為0xC1480000,即-12.5的IEEE-754標準格式。
p=(float *)p+1;
}
測試代碼:
void fun(char *fmt,...) reentrant
{
void *p;
p=&fmt;
PrintChar(**((char **)p));
p=((char **)p) +1;
PrintChar(*((char *)p));
p=((char *)p) +1;
PrintLong(*((int *)p));
p=((int *)p) +1;
PrintLong(*((long *)p));
p=((long *)p) +1;
PrintLong(*((long *)p));
p=((float *)p) +1;
}
顯示結(jié)果:yy0000000200000005C1480000
由上面知,在C里不用顯式使用SP等堆棧指針,而是使用void指針指向各種類型數(shù)據(jù)。變參函數(shù)的參數(shù)傳遞和獲取就是這樣運做的,知道了它的原理,就不難理解printf的實現(xiàn)了。
我所移植的printf支持標準或長二進制/八進制/十進制/十六進制/無符號整數(shù),支持字符、字符串、浮點數(shù)、百分號%顯示。其中,浮點數(shù)在整個范圍內(nèi)被完全支持,統(tǒng)一采用科學(xué)記數(shù)法顯示。對應(yīng)的指示符如下:
c 字符 f 浮點數(shù) s 字符串 % 百分號顯示
d/ld 2字節(jié)/4字節(jié)有符號整數(shù) u/lu 2字節(jié)/4字節(jié)無符號整數(shù)
x/lx 2字節(jié)/4字節(jié)十六進制數(shù) o/lo 2字節(jié)/4字節(jié)八進制數(shù)
b/lb 2字節(jié)/4字節(jié)二進制數(shù)
printf的功能是字符串化數(shù)據(jù),它的第一個參數(shù)是格式化字符串fmt,用其指示第一個參數(shù)在堆棧里的起址和其后各個參數(shù)的數(shù)據(jù)類型。知道了參數(shù)堆棧起址和各個參數(shù)的類型和排放次序,就可以依次取出各個參數(shù)并字符串化。詳細過程參見yyprintf源代碼。同時,注意到參數(shù)是依靠起址和數(shù)據(jù)長度信息依次讀出來的,那么,yyprintf的參數(shù)必須與格式化參數(shù)的指示相同,否則參數(shù)數(shù)據(jù)會亂掉。對于不能肯定的轉(zhuǎn)化數(shù)據(jù)類型建議加上強制類型定義,如(int) 2。特別是常數(shù)的轉(zhuǎn)換類型容易搞錯。
printf大部分代碼與硬件無關(guān),只有參數(shù)堆棧結(jié)構(gòu)和打印一個字符putchar()函數(shù)是硬件相關(guān)的。移植printf時只要修改putchar()函數(shù)和堆棧結(jié)構(gòu)即可。putchar()函數(shù)的功能一般是向串口輸出一個字符,也可以向其他顯示設(shè)備輸出一個字符,取決于你的驅(qū)動程序。我已經(jīng)在uCOS51里實現(xiàn)了PrintChar函數(shù),直接調(diào)用就可以了。其實,在X86、POWERPC、ARM等32位CPU上移植printf更加有效和方便。
測試舉例:
float r=1.9835671E-10,pi=3.1415926;
yyprintf("R=%f Circle area=%f\n",r,pi*r*r);
結(jié)果:
R=1.983567E-10 Circle area=1.236071E-19
源代碼:
//============================================================================================
//
//============================================================================================
void yyprintf(char *fmt,...) reentrant //自編簡單printf等效函數(shù)
{
void *p; //任意指針,可以指向任何類型,C語法不對其嚴格要求。
char ch;
unsigned char j;
p=&fmt;
p=(char **)p+1; //此處p是指向指針的指針,fmt是字符串指針,p是指向fmt的指針
while(1){
while((ch=*fmt++)!='%'){
if(ch=='\0') return;
else if(ch=='\n'){PrintChar(10);PrintChar(13);}
else if(ch=='\t'){
for(j=0;j<TABNum;j++)
PrintChar(' ');
}
else PrintChar(ch);
}
ch=*fmt++;
switch(ch){
case 'c':
PrintChar(*((char *)p));
p=(char *)p+1;
break;
case 'd':
PrintN(*((int *)p),10);
p=(int *)p+1;
break;
case 'x':
PrintN(*((int *)p),16);
p=(int *)p+1;
break;
case 'o':
PrintUN(*((int *)p),8);
p=(int *)p+1;
break;
case 'b':
PrintUN(*((int *)p),2);
p=(int *)p+1;
break;
case 'l':
ch=*fmt++;
switch(ch){
case 'd':
PrintLN(*((long *)p),10);
p=(long *)p+1;
break;
case 'o':
PrintLUN(*((long *)p),8);
p=(long *)p+1;
break;
case 'u':
PrintLUN(*((unsigned long *)p),10);
p=(unsigned long *)p+1;
break;
case 'b':
PrintLUN(*((long *)p),2);
p=(long *)p+1;
break;
case 'x':
PrintLN(*((long *)p),16);
p=(long *)p+1;
break;
default:
return;
}
break;
case 'f':
DispF(*((float *)p));
p=(float *)p+1;
break;
case 'u':
PrintUN(*((unsigned int *)p),10);
p=(unsigned int *)p+1;
break;
case 's':
PrintStr(*((char **)p));
p=(char **)p+1;
break;
case '%':
PrintChar('%');
p=(char *)p+1;
break;
default:
return;
}
}
}
void PrintN(int n,int b) reentrant //十進制顯示整形數(shù)
{
if(b==16){PrintWord(n);return;}
if(n<0){PrintChar('-');n=-n;}
if(n/b)
PrintN(n/b,b);
PrintChar(n%b+'0');
}
void PrintUN(unsigned int n,unsigned int b) reentrant //十進制顯示無符號整形數(shù)
{
if(b==16){PrintWord(n);return;}
if(n/b)
PrintUN(n/b,b);
PrintChar(n%b+'0');
}
void PrintLN(long n,long b) reentrant //十進制顯示長整形數(shù)
{
if(b==16){PrintLong(n);return;}
if(n<0){PrintChar('-');n=-n;}
if(n/b)
PrintLN(n/b,b);
PrintChar(n%b+'0');
}
void PrintLUN(unsigned long n,unsigned long b) reentrant //十進制顯示無符號長整形數(shù)
{
if(b==16){PrintLong(n);return;}
if(n/b)
PrintLUN(n/b,b);
PrintChar(n%b+'0');
}
參考文獻:
1。《ROM版本下系統(tǒng)調(diào)試信息的一種顯示方法》合肥工業(yè)大學(xué) 彭良清 《單片機與嵌入式系統(tǒng)應(yīng)用》p22頁2002(1-6)
TO BE CONTINUED...
浮點數(shù)顯示
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -