本文關鍵字:
1、嵌入式系統采用大容量EPROM來固化程序的專用系統,正在智能儀器和自動化等領域里 得到廣泛應用。傳統設計方法用匯編語言編寫程序,這主要是從保證速度和節省存儲 空間考慮,但編程費時,調試和排錯很不容易。微電子技術的飛速發展,使高性能微 處理器和大容量存儲器的價格變得十分便宜,速度和存儲容量不再是困擾設計者的主 要問題。人們將ROMBIOS和CRT顯示器等外設加進這類專用系統,并嘗試用 高級語言來開發其軟件,即把通用計算機上的軟件和硬件“嵌入”專用系統,構成所 謂的嵌入式系統(EmbeddedSystem)。由于C語言容易編程、代碼緊 湊、可移植性和可維護性好,因而被普遍用于嵌入式程序的設計。
大多數嵌入式系統無操作系統支持,要由設計者提供所有低級I/O功能。系統 I/O資源有限,程序必須固化在EPROM中,不能象在DOS下那樣從磁盤裝入 和由用戶編程。設計者要編寫一個定位程序(Locator),把EXE格式的應 用程序轉換成可固化進EPROM的二進制文件(ROM圖)。還要編寫一個啟動程 序(runtimeStartupCode),與ROM圖一起嵌入EPROM, 先由它建立數據區和對系統硬件作必要初始化,然后調Main函數,執行應用程序。 若想發揮C語言之優勢,使用一些標準I/O語句,如用printf驅動顯示器等, 則要在嵌入式程序中加進經過修改的庫函數??傊?,C語言編程會使系統開發面臨一 些新問題,要求設計人員具備軟硬件方面的綜合知識,才能正確進行系統調試和排錯。
當然,如果擁有專用的嵌入式系統開發工具,設計工作便要省勁些。它們通常配 有定位程序和可供設計者修改的啟動程序樣板,有些還能通過串口或并口,在PC機 上聯機調試程序,甚至有源級代碼調試功能。利用工控機來設計系統,事情就更簡單。 不過,專用開發工具和工控機價格昂貴,因此許多人在設計嵌入式系統時選擇自己編 寫定位程序和啟動程序,甚至編寫可嵌入的I/O庫函數。本文就嵌入式系統的程序 設計方法及設計中可能遇到的問題作些討論,供打算設計嵌入式程序的讀者參考,有關編寫定位程序和啟動程序的具體方法將另文介紹。
2、嵌入式程序的定位
2.1 EXE文件格式和DOS重定位
DOS下的EXE文件是一種可重定位文件 (Re-locatableFile),它由重定位標頭和裝入模塊組成。后者含 一段或幾段程序代碼,段數與類型取決于程序規模和編譯時所用的內存模式,然后是 初始化與未初始化的數據及堆棧,還可能有程序排錯信息。代碼、數據和堆棧段地址 均是參考到程序開頭的相對地址。標頭放在裝入模塊之前,含若干定位控制信息和一 張定位表??刂菩畔‥XE文件大小、標頭長度、需要重定位的項數和位置、裝 入模塊的開頭和堆棧的相對地址等。定位表是一組形式為段址:偏址的遠指針,指示 裝入模塊中要重定位的那些段址相對于模塊開頭處的位置。裝進RAM后,加載程序 建立起程序段前綴PSP,并根據系統當前可用RAM地址修改這些段址,對裝入模 塊重定位,使程序中所有參考絕對地址的量正確指向模塊裝入后的起始地址,然后執 行(圖1)。因此它可在RAM中的任何位置上執行。圖 DOS對EXE文件的定位過程
2.2 嵌入式程序的定位
嵌入式系統有ROM和RAM兩類內存,程序被固化進ROM,而程序 變量和堆棧應設在RAM中。因此,對EXE文件的重定位過程與DOS下不一樣。定位程序必須根據系統的ROM和RAM地址,對定位表中各遠指針指向的字進行修改。定位程序最后以一種可加載進測試系統或
可燒入EPROM的形式輸出程序,即ROM圖,它可以是二進制或Intel的HEX格式,根據EPROM編程器、仿真器或調試程序的要求而定。
可用兩種方法把EXE文件轉換為ROM圖:
一是使用EXE2BIN命令。若EXE文件定位表中不含有定位遠指針,EXE2BIN便將它轉換成COM文件,它是可固化進EPROM的二進制文件,否則便放棄轉換。這僅適用于較小的單段程序。較新版本的EXE2BIN在發現EXE文件中含重定位項時,會提示用戶提供一個基地址,進行重定位。若選用適當的內存模式,并限制使用遠指針,它也可能用一個基地址進行定位。但對于規模較大的程序,EXE2BIN無能為力。
二是根據標頭和MAP文件所提供的信息進行定位,適用于所有的EXE文件(圖2)。若在連接時進行限定,可生成只含內存分配段表的簡單MAP文件。段表的每行描述一個段,按代碼段、數據段和堆棧段的次序排列。MicrosoftC和BorlandC的MAP文件每行長度略有區別,但行上各參數(段始址、段末址、段長、段名、段類)的位置是固定的。 定位程序根據第一個RAM段的段名,從MAP文件中抽取出它的起始地址,它就是數據區的相對始址。再從標頭內容計算出裝入模塊大小,即要占用的ROM容量。將系統ROM始址加上代碼段在裝入模塊中的
相對地址,便得到程序開始執行的地址。然后,對定位指針進行自小到大排序。根據各段的始末地址逐段析出段址,并從裝入模塊中讀入該段代碼或數據。接著按定位指針順序考察待定位的段址,若它落在該段范圍內,便進行定位操作,即把此段址修改成實際的ROM或RAM地址。直到將屬于這個段的定位指針全部處理好后,便把這段內容寫到輸出文件。對所有段都進行定位后,便獲得ROM圖。 圖2 80x86系統上嵌入式程序的定位過程
3、啟動程序
ROM中程序執行前,先要在RAM中建立堆棧和數據區。串數據等常數與程序一起固化在ROM中,程序可以訪問它們,但RAM的存取速度比E-PROM高,因此也被復制進RAM,以提高讀出速度。還需要建立C程序運行的環境,如對段寄存器和堆棧指針初始化、對靜態變量和RAM區清零、建立堆(heap)等。程序運行前還應設置必要的中斷矢量,并讓各未用中斷指向一個只含RET指令的啞函數,以防止錯誤中斷引起系統的混亂。此外,還要對系統硬件進行初始化,并根據具體的系統,加入出錯時中止程序或重啟動的程序段等。這些工作都由用匯編語言編寫的啟動程序完成。啟動程序是嵌入式程序的開頭部分,應與應用程序一起固化在EPROM 中,并首先在系統上運行(圖2)。它應包含進各模塊中可能出現的所有段類,并合理安排它們的次序。當它作為第一個模塊和應用程序等一起連接時,LINK將按照該次序歸并類名相同的段。
寫好啟動程序是設計好嵌入式程序的關鍵。各類C編譯均提供自己的啟動程序模塊(C0.ASM),可以此為樣板,經簡化和修改形成適用本系統的啟動程序,也可以先搭一個啟動程序骨架,再逐步完善。
4 嵌入式程序的運行
嵌入式系統大多不能從鍵盤接受命令,而要在系統通電或復位時,自動執行ROM中的程序。各系統的復位地址不盡相同,以工作在實模式的80x86嵌入式系統為例,CPU復位后將執行F000:FFF0H處的代碼。這是系統ROM的高址端,僅有16字節空間,設計者可用DEBUG命令在ROM圖的這個位置上,放一條無條件遠跳轉指令JMPFARPTRstart,轉到ROM開頭,從那里執行啟動程序(見圖2)。啟動程序完成上述的初始化后調main函數,執行應用程序。 80286以上的CPU復位時,CS:IP初值仍是F000:FFF0H。但
A20以上地址線在CS寄存器被第一次裝進新的內容前,一直保持高電平,即開始地址指向最高地址端。如只要求系統工作在實模式,可由譯碼電路將開始的高地址反射到低端的1MB空間,復位矢量仍是F000:FFF0H。當上述JMP指令一 執行,CS被改寫,A20以上地址線將變低而進入實模式。要是希望啟動后進入保護模式,就不需要進行地址反射,但是相應的復位地址上只能放一條近跳轉指令,保證不改變CS值。然后進行必要的初始化,盡快進入保護模式。
5、嵌入式程序的編譯和連接
綜上所述,設計嵌入式系統時要在PC機上編寫三個程序:應用程序MYPROG.C;定位程序LO-CATOR.C;啟動程序STARTUP.ASM。然后按以下步驟進行編譯和連接,生成可編程的ROM圖:
?將應用程序編譯成MYPROG.OBJ。
?將定位程序編譯和連接成可執行程序LO-CATOR.EXE。
?將啟動程序編譯成STARTUP.OBJ。
?對STARTUP.OBJ、MYPROG.OBJ及必要的庫函數進行連接,生成EMBED.EXE和簡化的MAP文件EMBED.MAP。STARTUP 必須是LINK行上的第一個模塊,保證它先執行。
?執行LOCATOR.EXE,以EMBED.EXE、EMBED.MAP、第一個RAM段的段類名、RAM和ROM始址為輸入參數,實現定位,輸出ROM圖EMBED.BIN。
?用DEBUG命令在EMBED.BIN的F000:FFF0H位置上加進指令JMPFARPTRstart,形成最終的ROM圖。
6、其它幾個問題
6.1 系統內存考慮
為確保正確復位,設計硬件時要讓ROM地址空間覆蓋復位矢量。例如8086系統的最高地址為F000:FFFFH,若采用128KB的ROM,其地址范圍應取E000:0000-F000:FFFFH。 RAM地址則應從0開始,由于開頭1KB字節RAM要保留給中斷矢量表,通常如圖2那樣將RAM數據區設在地址0040:0000H處。
常數先固化在ROM中,然后被復制進RAM,因此占用的存儲器空間是DOS下的兩倍??稍诔绦蛟O計中設法限制要復制進RAM的常數數量。例如,系統若支持CRT顯示器,可能需要在屏幕上顯示各種消息和菜單提示。這時,可把所有顯示函數和有關文本串放進一個模塊,再用指針來存取它們。比如,本來可用下面語句打印提示:
printf("PressStoStart");
printf("PerssQtoQuit");
若程序中有許多類似的語句,便可能存在較多重復串。要是對各子串都用指針訪問,編譯就會把其中重復的串(如Press,to等)合并,省下不少內存空間。即把上面語句改為:
printf("%s%s%s%s","Press","S","to","Start") ;
printf("%s%s%s%s","Perss","Q","to","Quit");
6.2 標準I/O函數的使用
用C語言編寫嵌入式程序的過程與DOS下一樣,只是要避免使用不能被固化到 ROM中的庫函數。在DOS下,許多低級I/O函數(如putch,getch) 均通過DOS中斷21與硬件接口,高級函數printf,scanf等也使用該中斷。若希望在無ROMBIOS的系統上使用這些函數,應編寫一個模仿 DOSINT21的函數,這樣便能使用除磁盤I/O函之外的大多數標準I/O 函數,縮短程序開發時間,并保證較好的可移植性。當然,如果使用的是現成的嵌入式系統開發軟件包,廠商將告知哪些函數可被固化進ROM,不必自己編寫INT21函數。
編寫嵌入式支持函數時要防止使用與DOS有關的庫函數。比較起來,BC提供 的庫比MSC的更獨立于DOS。例如,MSC的printf函數要依賴幾個低級 的DOS函數。所以在仿真INT21的控制臺I/O函數時,建議用BC的 printf函數。
6.3 配置參數的保護
斷電時,嵌入式系統應能保持那些用于系統恢復或外設配置的數據,可用電池供 電的RAM或EEPROM來存放它們。但在復位時,啟動程序要把有初值的變量復 制進RAM,并對其余RAM區清0,結果會清除應保持的數據。為此,可把這類數據歸入一個專門段,不包括在初始化和未初始化數據區中,不讓啟動程序修改它們。