貴陽學(xué)院計(jì)科系 杜隆胤
瀘州醫(yī)學(xué)院生物醫(yī)學(xué)工程系 曹高飛
當(dāng)前,嵌入式應(yīng)用已經(jīng)深入人們生活,嵌入式開發(fā)正如火如荼。相應(yīng)地,也激起了高校計(jì)算機(jī)及相關(guān)專業(yè)學(xué)生的嵌入式學(xué)習(xí)熱潮。但是在內(nèi)地一些嵌入式學(xué)習(xí)環(huán)境較差的地方,由于硬件資源短缺給學(xué)習(xí)帶來了不少困難。本文擬通過一種簡單可行的辦法以規(guī)避嵌入式開發(fā)對硬件環(huán)境的依賴,使學(xué)習(xí)者能將精力集中到嵌入式軟件開發(fā)上來,從而降低學(xué)習(xí)門檻,提升學(xué)習(xí)速度。
嵌入式開發(fā)語言非常多,包括具有對硬件操作獨(dú)具優(yōu)勢的低級語言——匯編語言、同時(shí)具備低級語言和高級語言特性的C語言以及面向?qū)ο缶幊痰腃++語言等等。其中、匯編語言因?yàn)榻咏跈C(jī)器語言,在硬件底層操作方面獨(dú)具優(yōu)勢,如果編程得當(dāng)可獲得很好的時(shí)間和空間效率,但它掌握起來相對困難且編程效率較低,而且程序可讀性差,后期調(diào)試和維護(hù)困難大,所以一般在資源相對較少的應(yīng)用場合和與硬件聯(lián)系緊密的程序段使用;面向?qū)ο蟮母呒壵Z言編程接近人的思維方式,更貼近人類生活,能有效地提高編程效率為程序的可維護(hù)性,但編制的程序時(shí)間效率和空間效率都不高,且對底層硬件操作較一般需要通過低級語言程序來實(shí)現(xiàn),因此一般只適合于各種資源都很豐富的大型的嵌入式系統(tǒng);C同時(shí)具備低級語言和高級語言的優(yōu)勢、在講求時(shí)間和空間效率基礎(chǔ)上兼?zhèn)湟讓W(xué)、編程效率高等特點(diǎn),在中小型嵌入式開發(fā)中以及大型系統(tǒng)的驅(qū)動(dòng)程序開發(fā)中得到了廣泛的應(yīng)用。
Keil是目前最流行的開發(fā)80C51系列單片機(jī)的嵌入式的集成開發(fā)環(huán)境,它提供了C編譯器、宏匯編、連接器、庫管理和一個(gè)功能強(qiáng)大的仿真調(diào)試器等在內(nèi)的完整開發(fā)方案,并通過一個(gè)集成開發(fā)環(huán)境(μVision)將這些功能組合起來。[1]特別地,Keil支持的軟件仿真為缺少硬件環(huán)境開發(fā)調(diào)試提供了方便,很多嵌入式愛好者又在Internet網(wǎng)上提供了不少自制的軟仿真DLL插件,為嵌入式開發(fā)學(xué)習(xí)者提供了很多便利。
但是,當(dāng)前的仿真大都停留在觀察寄存器值的層次上,很難如實(shí)反映外接部件的最終實(shí)現(xiàn)效果,即使能找到個(gè)別能仿真外圍設(shè)備的插件又和開發(fā)者需要的存在一些差距,開發(fā)者自己開發(fā)適合自己的仿真環(huán)境又難度太大且耗時(shí)耗力。這矛盾促使了筆者尋找一種開發(fā)者自己能搭建仿真環(huán)境方法的動(dòng)機(jī)。
現(xiàn)有的軟件仿真方式很難達(dá)到即契合開發(fā)實(shí)際、如實(shí)反應(yīng)設(shè)備最終運(yùn)行效果的要求,那么、有沒有一種不費(fèi)事又能達(dá)到以上要求的軟件仿真方式呢?本文就Keil環(huán)境下的C程序如何在TC下編譯并仿真進(jìn)行討論。至于其他環(huán)境,如VC或BorlandC,GCC等。讀者可以參照本文實(shí)現(xiàn)通過宏定義的軟仿真。
首先,變量長度問題。
在嵌入式開發(fā)中,變量長度是非常敏感的,因此在處理變量長度是一定要小心。為使得編寫的程序具有較好的編譯環(huán)境適應(yīng)性,一般都不會(huì)直接使用int、long、short和byte等變量類型進(jìn)行定義,而是使用型如int8、int16等能直接表示變量類型及長度的定義方式,這要求在使用這些定義語句前先進(jìn)行相應(yīng)的定義。如在TC下就可以作如下的宏定義:
在Keil環(huán)境下卻要把以上定義卻為如下形式:
因此一般嵌入式開發(fā)都會(huì)將此部分內(nèi)容針對不同的環(huán)境編織成不同的頭文件,然后include到源文件中。同事可以在一個(gè)總的配置頭文件中用如下方式為調(diào)試和最后編譯配置不同的頭文件。
這樣就只需要在不同環(huán)境下編譯時(shí)改變宏定義DEBUG的值即可。
其次,關(guān)鍵字問題。
Keil中有部分關(guān)鍵字不是ANSI C標(biāo)準(zhǔn),因此對這些關(guān)鍵字需要進(jìn)行特別的處理。主要有sfr、sbit和interrupt。
1.sfr與sbit
sfr是Keil為能直接訪問80C51中的(特殊功能寄存器(SFR)而提供了一個(gè)專用的關(guān)鍵詞,其用法是:
sfrt變量名=寄存器地址值。[2]
但是TC中卻沒有此用法,因此該定義此部分內(nèi)容的配置文件作一定的修改??梢詫fr定義的特殊功能寄存器名字定義為對應(yīng)長度的無符號整型變量。如:
sfr p0=0x80;
響應(yīng)的替換語句為:
unsigned char p0;
這樣只能是簡單地解決了sfr的問題,而sbit就非常難模擬了。sbit是Keil為訪問C51中特殊寄存器的位變量而設(shè)置的,但在TC下沒為位變量,無法直接替換該關(guān)鍵字。
一般地,在嵌入式開發(fā)中,為了提高程序的可移植性,不會(huì)直接在程序里對特殊寄存器賦值,而以響應(yīng)功能函數(shù)的方式出現(xiàn)。[3]比如在電路中用p1_1以共陽極方式控制指示燈L1,則可以在配置文件中設(shè)置如下宏定義:
#define SetL1(a) p1_1=!a;
那么在程序中需要點(diǎn)亮或熄滅指示燈L1時(shí)就可以執(zhí)行語句:
SetL1(1); //點(diǎn)亮指示燈L1
SetL1(0); //熄滅指示燈L1
這樣,在芯片更改或電路變動(dòng)后只修改宏定義文件就能使程序適應(yīng)新的硬件環(huán)境,從而增強(qiáng)了程序的可移植性。如此,為實(shí)現(xiàn)基于宏的軟仿真,可以在對應(yīng)TC仿真環(huán)境下的配置文件中按功能進(jìn)行相應(yīng)的宏定義,即編寫相應(yīng)的函數(shù)實(shí)現(xiàn)指示燈控制的模擬。筆者稱此仿真為功能級仿真,而相對的只反映變量值變化的仿真方法可稱為變量級仿真。
2.interrupt
關(guān)鍵字interrupt是Keil環(huán)境中定義中斷函數(shù)用的,使用該擴(kuò)展屬性的函數(shù)定義語法如下:
返回值 函數(shù)名 interrupt n
其中n對應(yīng)中斷源的編號,其值從0開始,在80C51單片機(jī)中,編號從0到4,個(gè)編號對應(yīng)外中斷、定時(shí)器中斷和串行口中斷等。由于中斷與芯片聯(lián)系非常緊密,很難仿真。當(dāng)然、TC下也可以利用PC鍵盤產(chǎn)生的中斷來模擬,但工作量較大。所以,遇到像“void timer0() interrupt 1”之類的語句,只能先把“()”后面部分注釋掉了,不不予仿真。
第三,功能仿真。
常用的嵌入式外接部件主要包括:液晶顯示,flash存儲(chǔ)器,指示燈,按鍵,揚(yáng)聲器等,如果以查詢方式控制,就比較容易仿真。
1.液晶顯示的仿真
液晶顯示器可以利用TC的圖形化函數(shù)進(jìn)行。因?yàn)橐话泓c(diǎn)陣液晶都是以描點(diǎn)為基礎(chǔ)的,可以用TC下圖形化描點(diǎn)函數(shù)(void far putpixel(int x,int y,int color);)代替。在TC下進(jìn)行圖形編程必須進(jìn)行一系列的初始化,而嵌入式編程一開始也需要對硬件環(huán)境進(jìn)行初始化,因此可以編寫一個(gè)代替嵌入式環(huán)境初始化的函數(shù)以實(shí)現(xiàn)圖形模式的啟動(dòng)。這同樣可以在宏定義部分實(shí)現(xiàn)。一般嵌入式系統(tǒng)啟動(dòng)后就會(huì)進(jìn)入一個(gè)無限循環(huán),而在TC下仿真時(shí)不可以能讓程序無限運(yùn)行下去,因此需要設(shè)置一個(gè)出口,同時(shí)調(diào)用closegraph()以關(guān)閉圖形模式。
2.flash存儲(chǔ)仿真
對應(yīng)外存儲(chǔ)器的仿真,可以在硬盤上開辟一個(gè)文件進(jìn)行模擬。但是在編寫相應(yīng)的仿真存取函數(shù)時(shí),需要考慮flash的頁面大小以及頁內(nèi)地址反轉(zhuǎn)問題,即在某頁內(nèi)從起始地址以猝發(fā)方式連續(xù)存儲(chǔ)數(shù)據(jù)時(shí),一旦存儲(chǔ)的數(shù)據(jù)超過一頁容量,頁內(nèi)地址將反轉(zhuǎn)到頁面起始處,導(dǎo)致覆蓋先前寫入數(shù)據(jù)的現(xiàn)象。
為實(shí)現(xiàn)該功能的仿真,也需要在初始化函數(shù)中加入打開硬盤文件的語句,系統(tǒng)出口處加入文件關(guān)閉函數(shù)。在編寫存取功能的仿真函數(shù)時(shí)配合seek()函數(shù)進(jìn)行存取位置的定位,存數(shù)據(jù)時(shí)注意猝發(fā)方式的地址及頁面大小即可實(shí)現(xiàn)基于宏定義的軟仿真。
3.指示燈仿真
如果配置文件中關(guān)于指示燈的配置為如下形式:
#define SetLx(a) p1_x=!a //x=0,1,2,…,7
那么在仿真配置文件中就可以相應(yīng)作如下宏定義:
#define SetL0(a) setl1(a)
setl1(a)是為實(shí)現(xiàn)軟仿真編寫的控制指示燈的函數(shù),以其實(shí)現(xiàn)TC環(huán)境下640*480的圖形環(huán)境中劃分一部分區(qū)域?qū)iT顯示指示燈的明滅狀態(tài)。
4.按鍵仿真
一般按鍵應(yīng)該在配置頭文件中作如下配置:
#define IsKey0Down() (p3_5==1)
或者就直接有一個(gè)int IsKey0Down()函數(shù)。仿真中可以用鍵盤上某些鍵對應(yīng)按鍵,此時(shí)可以使用boiskey()函數(shù)來檢測鍵盤是否有按鍵以及何鍵被按下。假如用鍵盤上的鍵“A”模擬“K0”鍵,則可編寫如下函數(shù):
在配置頭文件中作如下定義即可:
#define IsKey0Down() iskey0down()
以上方式只能模擬系統(tǒng)中只有一個(gè)按鍵的情形,在系統(tǒng)有多個(gè)按鍵的情況需要考慮一次判斷多個(gè)按鍵。則將以上函數(shù)需稍作修改,以適應(yīng)讀一次鍵值作多個(gè)判斷的需要。
5.揚(yáng)聲器仿真
揚(yáng)聲器的仿真類似于指示燈,只是注意揚(yáng)聲器一般打開一會(huì)就會(huì)立即關(guān)閉,在此不再贅述。
本文討論的基于宏的軟仿真方法實(shí)質(zhì)為利用C的空定義,在配置頭文件中實(shí)現(xiàn)基本功能的宏定義替換以實(shí)現(xiàn)嵌入式程序的仿真。
本文所討論的仿真方法一般只適應(yīng)于沒有中斷的嵌入式系統(tǒng),而嵌入式系統(tǒng)又不可能沒有中斷,因此,此方法只實(shí)用于嵌入式前期學(xué)習(xí),不適于系統(tǒng)開發(fā)。但是,在完全以循環(huán)查詢方式編寫的嵌入式系統(tǒng)中,幾乎全部功能都能以宏定義方式實(shí)現(xiàn)TC下的軟仿真。
嵌入式開發(fā)中因?yàn)檐浖c硬件聯(lián)系非常緊密,因此初學(xué)時(shí)難度較大,利用仿真方式進(jìn)行學(xué)習(xí)能降低初學(xué)者入門難度。目前相對較好的Keil+DLL仿真方式依賴他人提供,很難適合開發(fā)者(學(xué)習(xí)者)的需要。
本文立足于程序初學(xué)者相對容易掌握的TC下的宏定義的方式搭建用戶自制的軟仿真環(huán)境,如此也使得開發(fā)者(學(xué)習(xí)者)比較容易根據(jù)需要對仿真環(huán)境進(jìn)行增刪修改,使仿真更能貼近任務(wù)。但因?yàn)樵摲椒ㄒ缶哂幸欢ǖ腡C下的C編程基礎(chǔ),而且本身很難實(shí)現(xiàn)中斷和數(shù)據(jù)收發(fā)的仿真,所以還有待進(jìn)一步改善甚至尋找更便捷且更貼近任務(wù)的仿真方法。
[1]郭天祥.新概念51單片機(jī)C語言教程——入門、提高、開發(fā)、拓展[M].電子工業(yè)出版社,2009,01.
[2]Keil Elektronik GmbH.and Keil Software,Inc.Keil User’s Guide[J].1988.
[3]凌明.嵌入式系統(tǒng)高級C語言編程[M].北京航空航天大學(xué)出版社,2011-1-1.
[4]丁明亮,唐前輝.51單片機(jī)應(yīng)用設(shè)計(jì)與仿真-基于Keil C與Proteus.北京航空航天大學(xué)出版社,2009-2-1.