系統(tǒng)調(diào)試信息的顯示方法
掃描二維碼
隨時(shí)隨地手機(jī)看文章
摘要:提出在目標(biāo)系統(tǒng)脫離開發(fā)系統(tǒng)運(yùn)行時(shí),如何通過串口在Windows的超級(jí)終端軟件中顯示調(diào)試信息的一個(gè)具體方法。該方法有助于改進(jìn)調(diào)試質(zhì)量、縮短調(diào)試周期。
1 ROM版本目標(biāo)系統(tǒng)的調(diào)試問題
一般的目標(biāo)系統(tǒng)在開發(fā)工具環(huán)境下的調(diào)試并不困難,但最終系統(tǒng)必須脫離開發(fā)工具獨(dú)立運(yùn)行,即使在開發(fā)工具環(huán)境下完全正常的系統(tǒng),ROM版本也往往會(huì)出現(xiàn)各種問題。原因有兩人:一是開發(fā)工具硬件環(huán)境和最終的目標(biāo)硬件環(huán)境不完全相同;二是外部因素不同,實(shí)驗(yàn)室中無法模擬現(xiàn)場(chǎng)的很多外部條件。對(duì)于硬件的調(diào)試,可以使用示波器等儀器;對(duì)于軟件的調(diào)試,一般方法則是顯示軟件運(yùn)行中的各種信息(如變量)。
我們知道,C語言中的“printf()”函數(shù)是學(xué)習(xí)C語言的人最了解和熟悉的一個(gè)函數(shù)。很多C語言教材一開始就以顯示“hello word”字符串來描述C語言的基本特片,其中唯一的語句就是調(diào)用“printf ()”函數(shù)。雖然該函數(shù)可以在屏幕上輸出信息,但一般的用戶軟件中只在調(diào)試時(shí)用它來顯示某些中間變量的結(jié)果,一旦程序調(diào)試完成,就將其刪除了,真正的應(yīng)用信息(如菜單字符等)顯示往往其他的輸出函數(shù)。對(duì)于使用和學(xué)習(xí)C語言的程序員來說,printf()由于可以同時(shí)輸出不同類型的數(shù)據(jù),因此,它的使用是軟件調(diào)試的重要手段之一。
在TURBO C2.0編譯器中,printf()函數(shù)的實(shí)現(xiàn)依賴于操作系統(tǒng)。在嵌入式系統(tǒng)中,往往沒有操作系統(tǒng)或者操作系統(tǒng)不提供這個(gè)功能,也可能沒有顯示輸出部件,或顯示設(shè)備的空間有限,只能用于顯示應(yīng)用信息。最常用的方法是通過目標(biāo)系統(tǒng)的一個(gè)串口將信息發(fā)送給PC機(jī)來顯示,PC機(jī)上可以使用Windows的“超級(jí)終端”軟件接受和顯示信息,如圖1所示。
這種系統(tǒng)的硬件很簡(jiǎn)單,我們只說明軟件的實(shí)現(xiàn)方法。為此,我們必須設(shè)計(jì)專用的、可以顯示各種數(shù)據(jù)類型的printf()函數(shù),以達(dá)到從串口或其他途徑輸出信息的目的。一般的C函數(shù)不同,printf()函數(shù)的參數(shù)數(shù)量和類型是可變的,這是編寫該函數(shù)的難點(diǎn)。要解決這個(gè)難是,必須先了解C函數(shù)參數(shù)傳遞的原理。
2 C函數(shù)的參數(shù)傳遞原理
C語言是一種計(jì)算機(jī)程序設(shè)計(jì)語言。它既具有高級(jí)語言的特點(diǎn),又具有匯編語言的特點(diǎn)。它可以作為工作系統(tǒng)設(shè)計(jì)語言,編寫系統(tǒng)應(yīng)用程序,也可以作為應(yīng)用程序設(shè)計(jì)語言,編寫不依賴計(jì)算機(jī)硬件的應(yīng)用程序。因此,它的應(yīng)用范圍廣泛,不僅僅是在軟件開發(fā)上,而且各類科研都需要用到C語言,具體應(yīng)用比如單片機(jī)以及嵌入式系統(tǒng)開發(fā)。
C語言 avr單片機(jī)c語言高級(jí)程序設(shè)計(jì)是一種成功的系統(tǒng)描述語言,用C語言開發(fā)的UNIX操作系統(tǒng)就是一個(gè)成功的范例;同時(shí)C語言又是一種通用的程序設(shè)計(jì)語言,在國(guó)際上廣泛流行。世界上很多著名的計(jì)算公司都成功的開發(fā)了不同版本的C語言,很多優(yōu)秀的應(yīng)用程序也都使用C語言開發(fā)的,它是一種很有發(fā)展前途的高級(jí)程序設(shè)計(jì)語言。
在大部分情況下,C語言是通過堆棧存儲(chǔ)器來傳遞參數(shù)(也有例外,C51的小模式則通過寄存器傳遞參數(shù))。對(duì)于非指針類型,傳遞的不是原來類型的數(shù)據(jù),而是對(duì)參數(shù)進(jìn)行了類型轉(zhuǎn)換,如字符類型(char)變成整型(int)拷貝到堆棧中、浮點(diǎn)類型(float)變成雙精度類型(double),如表1所列。表1中未列出的,則沒有轉(zhuǎn)換[4]。
表1
對(duì)于像字符數(shù)組之類的指針參數(shù),是將指針拷貝到堆棧中,而不是將數(shù)組中的所有內(nèi)容傳送到堆棧中。比如,對(duì)函數(shù)fun(char *STr,int i,float &a)的調(diào)用:
char str[10]=“welcome”;
int i=100; float a=1.14;
……
fun(str,i,&a);
各個(gè)參烽str,i在堆棧中按先右后左的次序存放,表2所列為調(diào)用函數(shù)fun( )開始時(shí)堆棧中的參數(shù)存放情況。此時(shí)函數(shù)fun()的代碼上尚未執(zhí)行,函數(shù)中的局部變量也是在堆棧中,所以在函數(shù)執(zhí)行結(jié)束后,局部變量將消失。
[!--empirenews.page--]
表2 函數(shù)調(diào)用時(shí)的參數(shù)在堆棧中的存儲(chǔ)情況(X86環(huán)境)
表2說明了兩個(gè)問題:第一個(gè)問題是,每個(gè)參數(shù)在堆棧中的存儲(chǔ)長(zhǎng)度和參數(shù)的類型有關(guān)。對(duì)于指針類型參數(shù),參數(shù)長(zhǎng)度和編譯模式有關(guān):大模式下,地址包括段地址和偏移地址,共4字節(jié);而小模式下,地址只有段內(nèi)偏移,占2字節(jié)。第二個(gè)問題是,如果知道其中的一個(gè)參數(shù)地址和參數(shù)的類型,則可以得到任意參數(shù)的數(shù)值,并不需要知道參數(shù)的名稱。比如在函數(shù)fun()中,可用以下代碼顯示各個(gè)參數(shù)的內(nèi)容:
void fun(char *str,int i,float *a)
{
void *p
p=&str;
printf("str=%s",str); p=(char **)p+1;
printf("i=%d" ((int*)p));p=(int *)p+1;
printf("i=%d" *((float *)p));
}
3 PC機(jī)上的printf()函數(shù)的設(shè)計(jì)實(shí)現(xiàn)
現(xiàn)在,可以編寫自己的printf()函數(shù)了。以下給出TC20編譯環(huán)境下的具體實(shí)現(xiàn)代碼,在其他環(huán)境下,可以根據(jù)該原理進(jìn)行移植。也可以按位顯示二進(jìn)制數(shù)。對(duì)于其他類型,讀者可以根據(jù)需要增刪。
在實(shí)際應(yīng)用中,可以修改其中的putchar()函數(shù),將字符發(fā)到串口,就可以達(dá)到上述目的了。這里我們編寫的函數(shù)還增加了數(shù)字的二進(jìn)制顯示,這對(duì)于很多位域應(yīng)用是很有用處的。
/*printf()函數(shù)的實(shí)現(xiàn)代碼,為和庫(kù)函數(shù)區(qū)別,特在各函數(shù)前增加前綴“my”*/
void myprintf(char *fmt,…)
{
void *p;
char ch;
p=&fmt;p=(char**)p+1;/*指向堆棧中的下一個(gè)參數(shù)*/
while(1){
while((ch=*fmt++)!=‘%‘{/*讀入格式字符串*/
if(ch= =‘0‘)return;
putchar(ch);
};
ch=*fmt++;
switch(ch){ /*格式字符分析*/
/*因?yàn)樽址麉?shù)傳遞時(shí)也轉(zhuǎn)換成整形參數(shù)傳遞,故同樣處理*/
case ‘c‘:
case‘d‘:
case‘x‘:
case‘0‘:
case‘b‘:
if(ch= =‘c‘)myputchar(*(int *)p));
if(ch= =‘d‘)myprintn(*((int *)p),10);
if(ch= =‘x‘)myprintn(*((int *)p),16);
if(ch= =‘o‘)myprintn(*((int *)p),8);
if(ch= =‘b‘)myprintn(*((int *)p),2);
p=(int)p+1; /*指針移動(dòng)*/
break;
case‘s‘:
myputs(*((char **)p));
p=(char **)p+1; /*指針移動(dòng)*/
break;
default;
};
}
}
[!--empirenews.page--]
void myputs(char str) /*顯示一個(gè)字符*/
{
while((*str)!=‘0‘)myputchar(‘str++);
}
/*顯示任意進(jìn)制的數(shù)值,b為二、八、十、十六等進(jìn)制數(shù)*/
void myprintn(int,n,int b)
{
if(b= =16){ myprintx(n); return; }
if(n<0){ myputchar(‘-‘); n=-n; };
if(n/b)
myprintn(n/b,b);
myputchar(n%b+‘0‘);
}
void myprintx(int n) /*以十六進(jìn)制顯示1個(gè)數(shù)字*/
{
signed char i;
for(i=3;i>=0;i--)
if(((n》i*4)&0x0f)>=10)
/*當(dāng)10,11…時(shí),顯示‘a(chǎn)‘,‘b‘,…‘f‘,*/
myputchar(((n》i*4)&0x0f)-10+‘a(chǎn)‘);
else myputchar(((n》i*4)&0x0f)+‘0‘);
}
/*
*在很多嵌入式系統(tǒng)中,并不存在PC一樣的標(biāo)準(zhǔn)顯示設(shè)備,
*通過修改該函數(shù),可以將字符“ch”發(fā)送到串口,或者目
*標(biāo)系統(tǒng)中的LED、LCD等顯示器件。這樣,就可以在脫
*離開發(fā)系統(tǒng)情況下顯示調(diào)試信息,從而調(diào)試目標(biāo)系統(tǒng)的軟
*件或硬件。
*/
void myputchar(int ch)
{
……;/*此函數(shù)可供修改,將字符“ch”送到SBUF或其他顯示器件就可以了*/
}
4 超級(jí)終端軟件的使用
超級(jí)終端,即計(jì)算機(jī)顯示終端,是計(jì)算機(jī)系統(tǒng)的輸入、輸出設(shè)備。計(jì)算機(jī)顯示終端伴隨主機(jī)時(shí)代的集中處理模式而產(chǎn)生,并隨著計(jì)算技術(shù)的發(fā)展而不斷發(fā)展。迄今為止,計(jì)算技術(shù)經(jīng)歷了主機(jī)時(shí)代、PC時(shí)代和網(wǎng)絡(luò)計(jì)算時(shí)代這三個(gè)發(fā)展時(shí)期,終端與計(jì)算技術(shù)發(fā)展的三個(gè)階段相適應(yīng),應(yīng)用也經(jīng)歷了字符啞終端、圖形終端和網(wǎng)絡(luò)終端這三個(gè)形態(tài)。超級(jí)終端是一個(gè)通用的串行交互軟件,很多嵌入式應(yīng)用的系統(tǒng)有與之交換的相應(yīng)程序,通過這些程序,可以通過超級(jí)終端與嵌入式系統(tǒng)交互,使超級(jí)終端成為嵌入式系統(tǒng)的“顯示器”。
打開Windows的“超級(jí)終端”軟件,再打開“hypertrm”,新建一個(gè)終端會(huì)話。在該會(huì)話的“屬性u(píng)35774設(shè)置u32456終端仿真”菜單下,將終端仿真類型設(shè)置為VT100[5];在“屬性u(píng)35774設(shè)置u32456終端設(shè)置u23383字符集”菜單下設(shè)置字符集為“ASCII”;在“屬性u(píng)36830連接到u37197配置u24120常規(guī)u26368最快速度”下設(shè)置通信波特率和目錄系統(tǒng)一致,并將該對(duì)話框下“僅以該速度連接打開”設(shè)置選中;在“屬性u(píng)36830連接到u37197配置u36830連接u36830連接首選項(xiàng)”下設(shè)置傳送數(shù)據(jù)位數(shù)、校驗(yàn)方式。
超級(jí)終端的原理是將用戶輸入隨時(shí)發(fā)向串口(采用TCP協(xié)議時(shí)是發(fā)往網(wǎng)口,這里只說串口的情況),但并不顯示輸入。它顯示的是從串口接收到的字符。所以,嵌入式系統(tǒng)的相應(yīng)程序應(yīng)該完成的任務(wù)便是:
1、將自己的啟動(dòng)信息、過程信息主動(dòng)發(fā)到運(yùn)行有超級(jí)終端的主機(jī);
2、將接收到的字符返回到主機(jī),同時(shí)發(fā)送需要顯示的字符(如命令的響應(yīng)等)到主機(jī)。
在用超級(jí)終端顯示時(shí),唯一要求發(fā)送的數(shù)據(jù)必須以ASCII碼形式發(fā)送(上述printf()函數(shù)就是如此)。如果要求交互式雙向數(shù)據(jù)傳送,請(qǐng)參考VT100文檔[5]。對(duì)于字符和控制的說明,這里不再描述。