系統程式-- 第11 章嵌入式系統 - SlideShare

文章推薦指數: 80 %
投票人數:10人

在嵌入式系統當中,通常沒有作業系統可以使用,此時,系統程式設計師必須能從硬體開始,以最原始的方式,逐步建構出整個系統。

在這樣的過程當中,我們可以進一步理解軟 ... SlideShareusescookiestoimprovefunctionalityandperformance,andtoprovideyouwithrelevantadvertising.Ifyoucontinuebrowsingthesite,youagreetotheuseofcookiesonthiswebsite.SeeourUserAgreementandPrivacyPolicy. SlideShareusescookiestoimprovefunctionalityandperformance,andtoprovideyouwithrelevantadvertising.Ifyoucontinuebrowsingthesite,youagreetotheuseofcookiesonthiswebsite.SeeourPrivacyPolicyandUserAgreementfordetails. Home Explore Login Signup Successfullyreportedthisslideshow. WeuseyourLinkedInprofileandactivitydatatopersonalizeadsandtoshowyoumorerelevantads.Youcanchangeyouradpreferencesanytime. Createyourfreeaccounttoreadunlimiteddocuments. 系統程式--第11章嵌入式系統 UpcomingSlideShare Loadingin…5 × Youarereadingapreview. Createyourfreeaccounttocontinuereading. SignUp 1 1of32 Likethisdocument?Whynotshare! Share Email     WhattoUploadtoSlideShare by SlideShare 12648014 views BeAGreatProductLeader(Amplify,... by AdamNash 1786566 views TrillionDollarCoachBook(BillCa... by EricSchmidt 1759550 views APIdaysParis2019-Innovation@s... by apidays 2365583 views Afewthoughtsonworklife-balance by WimVanderbauwhede 1656107 views Isvcstillathingfinal by MarkSuster 1478907 views Facebook Twitter LinkedIn Size(px) Starton ShowrelatedSlideSharesatend of PrevSlideShare NextSlideShares Youarereadingapreview. Createyourfreeaccounttocontinuereading. SignUp UpcomingSlideShare WhattoUploadtoSlideShare Next Education Jan.15,2019 1,967views 4Likes Share 系統程式--第11章嵌入式系統 Education Jan.15,2019 1,967views 作者:陳鍾誠 Readmore 鍾誠陳鍾誠 Follow 助理教授 at 國立金門大學 交⼤資訊⼯程學系備審資料⾱詠祥 鍾誠陳鍾誠 smallpt:GlobalIlluminationin99linesofC++ 鍾誠陳鍾誠 西洋史(你或許不知道但卻影響現代教育的那些事) 鍾誠陳鍾誠 區塊鏈(比特幣背後的關鍵技術)--十分鐘系列 鍾誠陳鍾誠 區塊鏈(比特幣背後的關鍵技術)--十分鐘系列 鍾誠陳鍾誠 梯度下降法(隱藏在深度學習背後的演算法)--十分鐘系列 鍾誠陳鍾誠 用十分鐘理解《微分方程》 鍾誠陳鍾誠 系統程式--前言 鍾誠陳鍾誠 系統程式--附錄 鍾誠陳鍾誠 系統程式--第12章系統軟體實作 鍾誠陳鍾誠 RelatedBooks Freewitha30daytrialfromScribd Seeall FightingForward:YourNitty-GrittyGuidetoBeatingtheLiesThatHoldYouBack HannahBrencher (4/5) Free NoOneSucceedsAlone:LearnEverythingYouCanfromEveryoneYouCan RobertReffkin (4/5) Free Dedicated:TheCaseforCommitmentinanAgeofInfiniteBrowsing PeteDavis (5/5) Free Let'sTalkAboutHardThings AnnaSale (4/5) Free HighConflict:WhyWeGetTrappedandHowWeGetOut AmandaRipley (5/5) Free NeverSplittheDifference:NegotiatingAsIfYourLifeDependedOnIt ChrisVoss (4.5/5) Free BoundariesUpdatedandExpandedEdition:WhentoSayYes,HowtoSayNoToTakeControlofYourLife HenryCloud (4/5) Free Girl,WashYourFace:StopBelievingtheLiesAboutWhoYouAresoYouCanBecomeWhoYouWereMeanttoBe RachelHollis (3.5/5) Free Uninvited:LivingLovedWhenYouFeelLessThan,LeftOut,andLonely LysaTerKeurst (4.5/5) Free MaybeYouShouldTalktoSomeone:ATherapist,HERTherapist,andOurLivesRevealed LoriGottlieb (4.5/5) Free Girl,StopApologizing:AShame-FreePlanforEmbracingandAchievingYourGoals RachelHollis (3.5/5) Free LessFret,MoreFaith:An11-WeekActionPlantoOvercomeAnxiety MaxLucado (4.5/5) Free The7HabitsofHighlyEffectivePeoplePersonalWorkbook StephenR.Covey (4/5) Free Dry:AMemoir AugustenBurroughs (4.5/5) Free TheSubtleArtofNotGivingaF*ck:ACounterintuitiveApproachtoLivingaGoodLife MarkManson (4.5/5) Free The7HabitsofHighlyEffectivePeople StephenR.Covey (4/5) Free RelatedAudiobooks Freewitha30daytrialfromScribd Seeall ReshapeYourBodyImage StacieGarland (3/5) Free BeyondSmallTalk:HowtoHaveMoreDynamic,CharismaticandPersuasiveConversations PatrickKing (4.5/5) Free TheAuthenticLeader:FiveEssentialTraitsofEffective,InspiringLeaders MerindaSmith (3.5/5) Free OutwiththeOld,InwiththeYou TessBrigham (4.5/5) Free ABodytoLove:CultivateCommunity,BodyPositivity,andSelf-LoveintheAgeofSocialMedia AngelinaCaruso (5/5) Free MyFriendFear:HowtoMoveThroughSocialAnxietyandEmbracetheLifeYouWant RoseBerry (5/5) Free ExtraordinaryAwakenings:WhenTraumaLeadstoTransformation SteveTaylor (3.5/5) Free StressandStressors:AvoidingandManagingStressandBurnoutatWork BrandyPayne (0/5) Free Self-HelpfortheHelpless:ABeginner'sGuidetoPersonalDevelopment,UnderstandingSelf-care,andBecomingYourAuthenticSelf ShelleyWilson (5/5) Free MindsetShifts:EmbracingaLifeofPersonalGrowth TaraOmorogbe (4.5/5) Free TheDesignThinkingMindset:HowtoAccessthePowerofInnovation DarinEich (5/5) Free MinimalFinance:ForgingYourOwnPathtoFinancialFreedom SamDixonBrown (3.5/5) Free LiveYourLife:MyStoryofLovingandLosingNickCordero AmandaKloots (4.5/5) Free FeedingtheSoul(BecauseIt'sMyBusiness):FindingOurWaytoJoy,Love,andFreedom TabithaBrown (5/5) Free TheFullSpiritWorkout:A10-StepSystemtoShedYourSelf-Doubt,StrengthenYourSpiritualCore,andCreateaFun&FulfillingLife KateEckman (4/5) Free BloomForward:HealingfromTrauma EmmyMarie (4/5) Free 4Likes Statistics Notes 竣博張 , chsh-teacher at chsh 2yearsago zero2005s 2yearsago SimonLin 2yearsago chunganchao 2yearsago Views Totalviews 1,967 OnSlideShare 0 FromEmbeds 0 NumberofEmbeds 0 Actions Shares 0 Downloads 0 Comments 0 Likes 4 Nonotesforslide 系統程式--第11章嵌入式系統 1. 1 第11章嵌入式系統 在前面的章節中,我們已經介紹了電腦的硬體架構、組合語言、組譯器、連結器、 載入器、C語言等主題。

現在,是讓我們將這些概念全部整合起來的時候了,在 本章中,我們將利用嵌入式系統這個主題,整合前述的所有內容,完整的說明如 何使用C語言、組合語言、連結器等工具,建構出電腦的軟硬體系統。

在嵌入式系統當中,通常沒有作業系統可以使用,此時,系統程式設計師必須能 從硬體開始,以最原始的方式,逐步建構出整個系統。

在這樣的過程當中,我們 可以進一步理解軟硬體系統,這是為何在本章探討此一主題的原因。

目前,開發嵌入式系統程式時,通常是將嵌入式的硬體,連接到個人電腦上,然 後透過跨轉編譯器(CrossCompiler),產生出可在嵌入式的硬體上執行的二進位 執行檔。

再將這個二進位檔燒錄到該電腦的記憶體(EPROM或Flash)當中,然 後,按下啟動鍵,重新啟動電腦以執行該系統。

嵌入式程式設計師所必須做的工作,包含撰寫啟動程式(11.5節)、設定中斷向 量函數(11.4節)、撰寫驅動程式(11.2節)等等。

然後,撰寫專案編譯檔(make 檔),以整合系統中所有的程式(11.6節),形成一個完整的嵌入式系統。

11.1輸出入 對當今的CPU而言,存取輸出入裝置的方法,通常可以分為兩類,第一類是採 用專用的輸出入指令,第二類是使用記憶體映射的輸出入方式。

以下,我們將分 別介紹這兩種輸出入方法。

專用的輸出入指令 在某些處理器中會提供專用的輸出入指令,像是測式裝置TD(TestDevice)、讀取 裝置RD(ReadfromDevice)、寫入裝置WD(WritetoDevice)等等。

我們可以使用 TD指令測試輸入裝置是否有讀到資料,當發現資料進來後,再利用RD指令將 資料讀入暫存器當中,以完成輸入工作。

舉例而言,假如輸入裝置0x09代表鍵盤,那麼,範例11.1將會不斷的用TD指 令測試鍵盤是否有資料輸入。

一但有按鍵資料進入時,再用RDR1,inDev將資 2. 2 料讀入暫存器R1當中。

最後,再利用STBR1,key將讀入的資料存到變數key 當中。

範例11.1從輸入裝置讀入一個位元組的程式 組合語言(輸入資料)說明 inLoop: TDinDev JEQinLoop RDR1,inDev STBR1,key inDevBYTE0x09 keyRESB1 inLoop標籤 測試輸入裝置0x09。

若沒有輸入,跳回到inLoop繼續測試 讀取輸入值到暫存器R1當中。

將讀到的值存入變數data 同樣的,我們也可以利用TD指令測試輸出裝置是否已準備好,當裝置準備就緒 後,再利用WD指令將資料寫入該輸出裝置中,以完成輸出工作。

舉例而言,假如輸出裝置0xF3代表具備字元顯示能力的螢幕,那麼,範例11.2 將會不斷的用TD指令測試螢幕是否已準備就緒,當螢幕準備就緒後,再用LDB R1,ch將字元'A'載入到暫存器R1當中。

最後,再利用WDR1,ch將R1中 的字元輸出到螢幕中,如此,螢幕上將會顯示出字元'A'。

範例11.2將字元A顯示到螢幕的程式 組合語言(輸出資料)說明 oLoop: TDoDev JEQoLoop LDBR1,ch WDR1,oDev oDevBYTE0xF3 chWORD'A' oLoop標籤 測試輸出裝置0xF3 若該裝置未就緒,則跳回oLoop,直到就序為止 將欲輸出的資料(字元‘A’)載入到暫存器R1中 將字元‘A’輸出到輸出裝置0xF3當中 雖然,範例11.1與範例11.2分別代表輸入與輸出,但是,兩者都利用了等待迴 圈,以等候輸入裝置的資料進入,或等候輸出裝置就緒。

這種等待迴圈會讓CPU 陷入忙碌狀態,而無法進行其他計算,因此稱為忙碌等待(BusyWaiting)。

當然,我們也可以在等待迴圈當中,不斷檢查所有的輸入裝置,一但發現有輸入 進來,就執行對應的動作,這種利用等待迴圈等候裝置的方法,稱為輪詢法 (Polling)。

3. 3 舉例而言,假如系統中有三個輸入裝置,包含鍵盤(0x09)、滑鼠(0x0A)與網路 卡(0x0B)。

那麼,我們就可以利用程式輪流詢問這三個裝置,一但有輸入事件發 生,則將資料讀入變數inData,並將該輸入裝置的代號儲存於inDev變數中, 以利後續進行輸入處理。

範例11.3輪流詢問鍵盤、滑鼠與網路卡三個裝置的程式 組合語言(輸入資料)說明 inLoop: LDR1,0 TestKeyboard: TDkeyboard JNETestMouse LDR1,keyboard TestMouse: TDmouse JNETestNet LDR1,mouse TestNet: TDnet JNEEndTest LDR1,net EndTest: STR1,inDev RDR2,inDev STR2,inData …處理輸入(省略)… JMPinLoop keyboardBYTE0x09 mouseBYTE0x0A netBYTE0x0B inDevRESB1 inDataRESW1 輪詢迴圈開始 清除R1中的值 檢查鍵盤輸入 測試鍵盤是否有按下 如果沒有則測試下一個裝置 (如果有)則將按鍵資料放入R1 檢查滑鼠輸入 測試滑鼠是否有輸入 如果沒有則測試下一個裝置 (如果有)則將滑鼠資料放入R1 檢查網路輸入 測試網路是否有輸入 如果沒有則結束輸入偵測 (如果有)則將網路資料放入R1 結束輸入偵測 將輸入裝置代號放入inDev變數中 讀取輸入值,放入R2暫存器中 將輸入值存入inData變數中 …處理輸入的程式碼(省略)… 輪詢迴圈結束,回到inLoop 鍵盤的裝置代號為0x09 滑鼠的裝置代號為0x0A 網路的裝置代號為0x0B 變數inDev用來儲存輸入裝置代號 變數inData用來儲存輸入值 對於簡易的嵌入式系統而言,輪詢是一種常見的作法。

然而,如果希望電腦的效 能可以發揮得淋漓盡致,那麼,被輪詢迴圈卡住CPU的作法,就不是一個好的 解決方式了。

因此,對於較注重效能的電腦而言,往往會利用所謂的中斷機制代 替輪詢,以避免輪詢所造成的效能問題,增進電腦的效率,有關中斷機制的說明, 4. 4 我們將於後文中詳細的說明。

記憶體映射輸出入 所謂的記憶體映射,是利用記憶體存取指令進行輸出入的方法,在採用這類方式 的CPU當中,很可能沒有專用的輸出入指令,於是利用記憶體存取指令替代輸 出入指令,以達成輸出入的功能。

目前,使用記憶體映射輸出入的方式越來越常見,甚至,在許多現今的CPU當 中,往往將輸出入指令完全取消,在本書當中的CPU0即是如此。

那麼,這類 的電腦要如何進行輸出入呢? 當然,在這類的電腦當中,還是必須能進行輸出入,否則該電腦就無法與鍵盤、 螢幕等裝置溝通,如此一來,電腦的功能也就所剩無幾,可以說是無法使用了。

在這類沒有輸出入指令的電腦,要進行輸出入時,會分配給每一個周邊裝置一些 記憶體位址空間。

當程式使用ST、STB等記憶體寫入指令,將資料寫入該周邊 裝置的記憶體位址時,對應的輸出裝置就會進行輸出動作。

同樣的,當程式使用 LD、LDB等記憶體讀取指令,讀取這些位址時,就會讀取到該周邊裝置的輸入 資料,如此,即可透過記憶體存取的指令,存取這些周邊裝置。

對於程式設計師而言,『將周邊裝置視為記憶體的一部分』,這個方法聽起來很奇 怪,但是,若程式師能改以硬體設計人員的立場,去思考這件事,就會覺得不那 麼奇怪了。

對於硬體設計人員而言,CPU、記憶體與輸出入裝置,都透過匯流排被連接在一 起。

對於CPU而言,輸出入裝置與記憶體都被視為是『外部裝置』,CPU只不 過是透過將位址傳入到位址匯流排上,然後透過資料匯流排傳送或接收資料,以 與這些外部裝置溝通。

因此,不論是記憶體或輸出入裝置,對CPU而言其實運 作原理都一樣。

既然如此,那為何不將輸出入裝置當成是記憶空間的一部分,給這些輸出入裝置 一些『記憶體位址』呢?也就是說,將記憶體空間分成兩區,其中一區是真正的 記憶體位址,另一區則是輸出入裝置的『記憶體映射位址』。

如此、只要CPU指 定的位址是輸出入的映射位址,則會進行輸出入動作,如果CPU指定的位址是 記憶體位址,則會進行記憶體存取動作。

根據這樣的設計,我們就能利用記憶體 存取指令進行輸出入裝置的存取了。

5. 5 記憶體映射通常是輸出入控制器的工作。

對於輸出裝置而言,當輸出控制器發現 控制匯流排上,出現記憶體寫入訊號,且位址匯流排上的記憶體位址,代表某個 輸出暫存器時(也就是該暫存器被映射到輸出位址上時),就將資料匯流排上的資 料存入該暫存器當中。

同樣的,對於輸入裝置而言,當有資料輸入時,會被暫時 儲存在輸入介面卡上的暫存器當中,等到輸入控制器發現控制匯流排上,出現記 憶體讀取訊號,而且位址匯流排上的位址,代表某輸入暫存器時,才將該暫存器 中的資料取出,傳送到資料匯流排上,讓CPU取得該記憶體映射後的暫存器資 料。

這就是記憶體映射的硬體實作方法。

圖11.1顯示了利用裝置控制器進行記 憶體輸出入映射控制的方式。

圖11.1裝置控制器與記憶體映射機制 在本書當中所使用的處理器CPU0,並沒有專用的輸出入指令,因此,CPU0只 能透過記憶體映射的方式進行輸出入。

在CPU0當中,組合語言程式設計師可以 透過LD、ST、LDB、STB等指令,進行周邊裝置的存取,其前提是在硬體接線上, 必須以記憶體映射的方式設計線路,也就是硬體工程師在設計輸出入控制器或介 面卡時,就必須以記憶體映射的方式『解釋』位址匯流排上的資料。

6. 6 既然CPU0是透過記憶體映射的方式進行輸出入動作,那麼,其輸出入程式設 計方式就與一般的記憶體存取無異。

只是這些記憶體位址,對輸出入控制器而言, 具有特別意義而已。

接下來,我們將以組合語言範例,實際說明CPU0的記憶體映射輸出入方式。

然而,光是有CPU卻沒有輸出入裝置,撰寫出來的輸出入程式將毫無意義。

因 此,筆者只好繼續扮演硬體工程師,親自利用CPU0設計一台陽春型的電腦,稱 為Machine0,簡稱M0。

在下一節當中,我們將介紹M0的硬體結構,然後, 利用CPU0的組合語言,導引讀者寫出M0上的鍵盤控制程式。

簡易電腦M0 簡易電腦M0是一種單晶片實驗板,採用CPU0作為處理器,包含16K的 FLASH與64K的RAM。

16K的FLASH扮演了唯獨記憶體的角色,之所以選用 FLASH而不採用一般的ROM是為了測試方便。

因為可以利用燒錄器將程式與資 料燒錄到Flash中,圖11.2顯示了簡易電腦M0的外觀與基本架構。

圖11.2簡易電腦M0的基本架構 程式設計師可以透過個人電腦所控制的燒錄裝置,將程式燒錄到M0的Flash 當中,只要啟動程式正確的燒錄到其中,M0就可以順利的開機,M0當中的RAM 7. 7 則可用來儲存堆疊與變數等動態資料。

另外,M0上還有一組鍵盤,共有16個按鍵,還有一個七段顯示器,可用來顯 示一個數字。

鍵盤上的每一個按鍵都有編號,從K0開始直到KF,代表Key0、 Key1、…、KeyF的縮寫,而七段顯示器的每一根光棒(LED)也都有編號,從SEG_A 開始編到SEG_G為止。

簡易電腦M0完全採用記憶體映射的方式進行輸出入,輸出入的映射位址位於定 址空間的最後部分,其中的0xFFFFFF00是七段顯示器的映射區域,而 0xFFFFFF01與0xFFFFFF02則是鍵盤按鈕的映射區域。

這些映射方式均記載於其 硬體手冊當中。

表格11.1是M0的硬體對應手冊。

請讀者仔細對照圖11.2與 表格11.1,就可以找到每個按鍵與光棒各被映射到哪一個位元上。

表格11.1簡易電腦M0的硬體對映手冊(DataSheet) Regbit76543210 IO_REG0 0xFFFFFF00 SEG_GSEG_FSEG_ESEG_DSEG_CSEG_BSEG_A IO_REG1 0xFFFFFF01 KFKEKDKCKBKAK9K8 IO_REG2 0xFFFFFF02 K7K6K5K4K3K2K1K0 M0上的所有按鈕,都是在按下去時,對應的位元才會變成1,在未按鈕的情況 下該位元會是0。

同樣的,七段顯示器上的光棒也是以1作為點亮的狀態,而0 作為熄滅的狀態。

根據圖11.2與表格11.1,我們就可以開始撰寫簡易電腦的程式了。

假如,我們 希望寫一個程式,可以讓七段顯示器顯示出0這個數字,那應該怎麼做呢? 根據我們的目標,我們必須讓七段顯示器外圍的六根亮棒都亮起來,然後讓中心 的亮棒熄滅。

因此,我們必須將SEG_A...SEG_F都設成1,而SEG_G則設成0。

根據這樣的想法,程式將出奇的簡單,只要將記憶體位址0xFFFFFF00之處的 byte設定為0x3F,就能讓該七段顯示器顯示出字元0,該程式如範例11.4所 示。

範例11.4讓M0的七段顯示器顯示數字0的程式(絕對定址版-錯誤示範) 組合語言C語言(對照版)C語言(真實版) LDIR1,0x3FR1=0x3F;(*(unsignedchar*)0xFFFFFF00)= 8. 8 STBR1,[0xFFFFFF00][0xFFFFFF00]=R10x3F; 在範例11.4當中,組合語言的部分並不難,其原理是利用LDIR1,0x3F指令, 先將0x3F的數值載入到R1暫存器當中。

然後再利用STBR1,0xFFFFFF00指令, 將R1的內容(0x3F)存入到七段顯示器的記憶體映射位址0xFFFFFF00中。

如此, 即可讓該七段顯示器顯示出數字0。

然而,上述組合語言程式對CPU0而言,卻是錯誤的做法,原因是CPU0的LD 與ST指令,其格式中的位移Cx部分只有16個位元。

因此,最大的定址範圍 是0到65535,也就是十六進位的0x0000000到0x0000FFFF而已。

然而,STB R1,0xFFFFFF00這個指令,其範圍已超出Cx可定址範圍,因此,必須加上某些 暫存器當作基底。

為了解決定址範圍的問題,我們將範例11.4改寫成範例11.5, 利用R8當作STB指令的基底,以解決Cx範圍過小的問題。

範例11.5讓M0的七段顯示器顯示數字0的程式(絕對定址版-正確示範) 組合語言C語言(對照版)C語言(真實版) LDR8,IO_BASE LDBR1,[0x3F] STBR1,[R8+0x00] IO_BASEWORD0xFFFFFF00 R8=IO_BASE R1=[0x3F]; [R8+0x00]=R1 IO_BASE=0xFFFFFF00 (*(unsignedchar*) 0xFFFFFF00)=0x3F; 上述的組合語言程式即可達成記憶體映射輸出的功能,並不難,是嗎? 假如您尚未使用C語言寫過單晶片或嵌入式系統上的程式,那麼,範例11.4 的C語言部分反而顯得匪夷所思了,筆者有必要在此多作解釋。

首先,(unsignedchar*)0xFFFFFF00的意思是,將0xFFFFFF00這個數字,型態 轉換為記憶體位址。

接著,最前面的*,則代表要存取這個記憶體位址的內容。

因此,整個指令的意義就是,將0x3F指定給0xFFFFFF00這個unsignedchar型 態的指標變數當中,也就是將0xFFFFFF00記憶體位址的內容設定為0x3F了。

當然,這樣的程式真的很難讀懂。

還好,C語言當中有#define這個巨集指令, 可以讓我們將上述程式改寫,以利程式閱讀者理解。

範例11.6就顯示了這個修 改的過程,將直接存取的方式改用定義的方式,這會讓程式的可讀性變得較高, 也會更加容易維護。

範例11.6讓M0的七段顯示器顯示數字0的程式–修改後容易讀的版本 C語言 9. 9 #defineBYTEunsignedchar #defineSEG7_REG(*(BYTE*)0xFFFFFF00) SEG7_REG=0x3F; 上述範例可說是使用C語言實作記憶體映射寫入的最簡單版本,但是卻有可能 導致錯誤,這個錯誤主要是編譯器的最佳化動作所造成的,我們將在後續進行解 說。

讓我們將範例11.6改寫為正確的版本,其方法是加上volatile這個關鍵字,將 程式修改為範例11.7的形式,以告知編譯器該變數是揮發性的(volatile),要求 編譯器不可以對該變數進行最佳化的動作,這是規格ISOC99所定義的語法。

範例11.7讓M0的七段顯示器顯示數字0的程式–使用volatile關鍵字 C語言 #defineBYTEunsignedchar #defineSEG7_REG(*(volatileBYTE*)0xFFFFFF00) SEG7_REG=0x3F; 舉例而言,在範例11.8當中,我們在while迴圈中使用inBit這個變數,但是 卻在中斷服務函數中修改了這個變數的值,這樣的程式看來並沒有甚麼大問題, 但卻會導致編譯器在最佳化時產生錯誤的組合語言碼。

在範例11.8(a)的C語言當中,編譯器看到inBit在while迴圈當中並沒有被 改變,因此就可能認為inBit可以事先被載入到暫存器R1當中,而不會造成任 何問題,於是就利用最佳化的機制,將(b)欄中將inBit載入到R1的動作提出 到迴圈之外,因而導致該迴圈永遠不會結束,成為無窮迴圈。

範例11.8一個未使用volatile關鍵字而造成錯誤的範例 (a)C語言(b)組合語言 staticintinBit=0; intmain(){ ... while(1){ if(inBit)dosomething(); } } LDR1,inBit LOOP: CMPR1,R0 JNELOOP 10. 10 //中斷服務常式. voidISR(){ inBit=1; } ISR: STIinBit,1 RET inBitWORD0 但是,在範例11.8(b)的組合語言中,即使ISR()被觸發,程式也不會離開LOOP 迴圈了,因為該組合語言檢查的是R1而非inBit。

在範例11.9當中,我們將staticintinBit=0改為volatilestaticintinBit=0,關鍵字 volatile告訴編譯器inBit是揮發性的,於是編譯器就不會將inBit事先載入, 而是在需要的時候才載入到R1進行比較。

因此,一但中斷發生呼叫ISR()之後, inBit就會被設定為1,於是程式檢查到R1=1之後就會離開LOOP迴圈,而不 會造成無窮迴圈的情況了。

範例11.9一個使用volatile關鍵字而讓中斷機制正確運作的案例 C語言組合語言 volatilestaticintinBit=0; intmain(){ ... while(1){ if(inBit)dosomething(); } } //中斷服務常式. voidISR(){ inBit=1; } … LOOP: LDR1,inBit CMPR1,R0 JNELOOP … ISR: STIinBit,1 RET inBitWORD0 如果我們要為M0撰寫輸入程式,也只要利用記憶體存取指令即可。

方法不難, 請讀者參考圖11.2與表格11.1,會發現可以從0xFFFFFF01與0xFFFFFF02兩 個記憶體位址中,取出鍵盤的按鍵值。

但是由於總共有16個按鍵,分為兩個 byte,因此,應分兩次的讀取,分別儲存在兩個byte中。

然而,若要方便使用, 則可將兩個byte合併後放入一個暫存器當中,將會比較容易使用。

因此,在範 11. 11 例11.10的組合語言版當中,這兩個byte被合併後放入暫存器R3當中,以便 後續處理使用。

範例11.10讀取M0鍵盤暫存器的組合語言程式 組合語言版C語言(對照版) LDR8,IoBase LDBR1,[R8+1] LDBR2,[R8+2] SHLR2,8 ORR3,R1,R2 … IoBaseWORD0xFFFFFF00 R8=IoBase; R1=[R8+1]; R2=[R8+2]; R2=R2<<8; R3=R2|R1; … intIoBase=0xFFFFFF00; 在C語言當中,要儲存16個位元的資料,可以利用unsignedshort型態的變 數,這是因為unsignedshort占據兩個byte,而且是屬於無正負號的16位元整 數型態。

由於unsignedshort的宣告實在太長了,因此,在C語言當中,常常會用 #defineUNIT16unsignedshort這樣語句定義為較短的UNIT16寫法,如範例 11.11所示,如此會讓程式更加清楚 範例11.11讀取M0鍵盤暫存器的C語言程式 C語言(真實版) #defineBYTEunsignedchar #defineUINT16unsignedshort #defineKEY(*(volatileUNIT16*)0xFFFFFF01) 一旦取得按鍵資料後,即可檢查看看哪些按鈕被按下。

舉例而言,若我們想知道 對應到數字5的按鈕是否被按下,那應該怎麼做呢? 由於在M0的架構圖11.2當中,數字5按鈕的標示為K5,而K5在硬體對映 手冊(表格11.1)中,對應到IO_REG1(0xFF01)中的位元5。

因此,只要檢查 位元5是否為1,就可以知道該按鈕是否被按下了,範例11.12即分別示範了 如何以組合語言與C語言檢查按鍵是否被按下的程式片段 範例11.12檢查按鍵5是否被按下的程式片段 組合語言版C語言(對照版)C語言(真實版) LDR8,IoBaseR8=IoBaseUNIT16isK5hit=KEY&0x20; 12. 12 LDR3,[R8+1] LDIR7,0x20 ANDR4,R3,R7 R3=[R8+1] R7=0x20 R4=R3&R7 在範例11.13當中,更是利用這種檢查方法,以決定是否要在七段顯示器上顯示 數字5,於是這個程式,透過記憶體映射的方式,將輸入與輸出結合再一起了。

範例11.13以程式檢查按鍵5是否被按下,若是,則顯示數字5 組合語言版C語言(對照版)C語言(真實版) … LDR8,IoBase LDR3,[R8+1] LDIR7,0x20 ANDR4,R3,R7 CMPR4,0 JNEL2 LDBR1,0x76 STBR1,[R8+0x00] L2: … IoBaseWORD0xFFFFFF00 … R8=IoBase R3=[R8+1] R7=0x20 R4=R3&0x20; If(R4!=0) gotoL2; R1=0x76; [R8+0x00]=R1; L2: … intIoBase=0xFFFFFF00; … UNIT16isK5hit=key&0x20; If(isK5hit!=0) SEG7_REG=0x76; … 利用此種方法,我們就可以寫出一個完整的程式,讓M0實驗板在數字按鍵被 按下時,於七段顯示器當中顯示對應的數字,我們將在下一節中說明這個程式的 寫法。

在本節中,我們介紹了兩種存取輸出入裝置的方式,組合語言可以使用『專用指 令』或『記憶體映射』的方式,進行輸出入的動作,而C語言則只能用記憶體 映射的方式進行輸出入,這也是為何『記憶體映射』的方式越來越受歡迎的原因 之一。

11.2驅動程式 在現代的電腦中,通常周邊裝置都會附有驅動程式,對使用者而言,驅動程式是 一個需要安裝的軟體,而對於程式設計師而言,驅動程式則是用來控制特定輸出 入裝置的程式。

13. 13 通常,對於一個周邊裝置,程式設計人員就會寫出一組驅動程式,這組程式負責 設定周邊裝置,並進行低階的輸出入動作,在本節中,我們將說明驅動程式的撰 寫方式。

對於沒有作業系統的電腦而言,驅動程式只是一堆控制特定輸出入裝置的程式, 驅動程式的撰寫者必須負責設計這些程式,將輸出入的功能包裝成函數,然後其 他的程式設計人員只要呼叫這些函數進行輸出入即可。

如此,就不需要讓每個人 都透過低階的指令直接控制輸出入裝置,讓專案得以順利的分工與撰寫。

舉例而言,如果我們針對M0電腦中的七段顯示器撰寫驅動程式,那麼,我們可 以利用範例11.14中的seg7_show()函數對該裝置的輸出動作進行包裝,該函數 就是一個最簡單的驅動程式。

範例11.14M0電腦的驅動程式(七段顯示器+鍵盤) C語言程式檔(driver.h)說明 #defineBYTEunsignedchar #defineUINT16unsignedshort #defineBOOLunsignedchar #defineSEG7_REG(*(volatileBYTE*)0xFFFFFF00) #defineKEY_REG1(*(volatileBYTE*)0xFFFFFF01) #defineKEY_REG2(*(volatileBYTE*)0xFFFFFF02) #defineKEY(KEY_REG2<<8|KEY_REG1) 定義BYTE型態 定義UNIT16型態 定義BOOL型態 七段顯示器的映射位址 鍵盤暫存器的映射位址 七段顯示器的映射位址 C語言程式檔(driver.c)說明 #defineBYTEseg7map[]={/*0*/0x3F, /*1*/0x18,/*2*/0x6D,/*3*/0x67, /*4*/0x53,/*5*/0x76,/*6*/0x7E, /*7*/0x23,/*8*/0x7F,/*9*/0x77}; #definecharkeymap[]={'1','2','3','+', '4','5','6','-', '7','8','9','*', '#','0','?','/'}; //七段顯示器驅動程式 voidseg7_show(charc){ SEG7_REG=map7seg[c-'0']; } 七段顯示器的顯示表,例 如:顯示0時SEG_G應熄 滅,其他應點亮,因此應顯 示二進位的00111111,也 就是0x3F。

keymap是鍵盤的字元地 圖,在keyboard_getkey() 中可用來查出對應字元。

在七段顯示器中輸出b數 字 14. 14 //鍵盤驅動程式 charkeyboard_getkey(){ UNIT16key=KEY; for(inti=0;i<16;i++){ UNIT16mask=0x0001< intmain(){ while(1){ while(!keyboard_ishit()){} charkey=keyboard_getkey(); if(key>='0'&&key<='9') seg7_show(key); } } 引用driver.h 主程式開始 無窮迴圈 等待鍵盤被按下 取得按鍵 檢查是否為數字鍵 顯示數字於七段顯示器 在上述範例中,我們已經說明了撰寫驅動程式的方法,我們針對M0電腦的鍵盤 與七段顯示器撰寫了兩組『驅動程式』,分別『驅動』這兩個裝置,我們希望透 過這個範例可以清楚的說明輸出入程式的設計原理。

15. 15 11.3輪詢機制 輸出入系統的設計是嵌入式系統的主要工作之一,在現代的電腦中,進行輸出入 的方法有兩種,第一種是採用輪詢的機制,輪流詢問各個輸出入裝置。

第二種是 採用中斷的機制,由裝置主動通知CPU輸出入的狀況。

我們將在下列兩節當中 分別探討這兩種機制。

在前一節當中,我們已經介紹過輪詢輸出入的實際案例,但是,設計不良的輪詢 程式可能讓整個系統陷入無窮迴圈,或者讓其他程式難以順利執行。

因此,嵌入 式系統通常會使用一個主要的輪詢迴圈,以訊息傳遞(MessagePassing)的方式控 制程式的執行順序,這是嵌入式系統常見的一種程式設計模式(DesignPattern)。

採用訊息傳遞的輪詢機制,可以在整個系統的最上層,撰寫一個大迴圈(通常是 一個無窮迴圈),這個迴圈不斷的詢問各個裝置的狀態,一旦發現輸出入裝置有 資料進入或有狀態改變時,就呼叫對應的函數進行處理。

這也是『輪詢』一詞的 由來,因為該程式輪流詢問各個裝置是否有資料進來。

範例11.16顯示了上層的訊息傳遞架構,該程式使用輪詢機制,利用msg變數 傳遞訊息,然後利用mouse,keyboard等變數記錄各裝置的輸入與狀態。

透過這 種方式,嵌入式系統也可以管理許多輸出入程式,而不容易出現錯誤。

範例11.16採用訊息傳遞的輪詢機制- MessagePassing.cMessagePassing.h #include msg_tmsg; keyboard_tkeyboard; mouse_tmouse; intmain(){ while(1){ if(getMessage(&msg)) processMessage(&msg); } } voidprocessMessage(msg_t*msg){ if(msg->source==KEYBOARD){... #ifndefMESSAGE_PASSING_H #defineKEYBOARD1 #defineMOUSE2 typedefstruct{ intsource; }msg_t; typedefstruct{ charkey; }keyboard_t; typedefstruct{ 16. 16 }elseif(msg->source==MOUSE){... } } intgetMessage(msg_t*msg){ if(keyboardHit()){ msg->source=KEYBOARD;… }elseif(mouseHit()){ msg->source=MOUSE;… } return0; } intkeyboardHit(){…} intmouseHit(){…} intx,y; }mouse_t; externmsg_tmsg; externkeyboard_tkeyboard; externmouse_tmouse; voidprocessMessage(msg_t*msg); intgetMessage(msg_t*msg); intkeyboardHit(); intmouseHit(); #endif 如果採用上述的輪詢方式,那麼,確實可以不需要一個作業系統,因為輪詢迴圈 會扮演協調角色,所有的程式都必須按照這種規定進行撰寫,在偵測到輸入訊息 時才進行取得的動作,如此就不會有程式因為輪詢迴圈而霸佔了整個CPU的執 行權,整個系統因此而可以順暢的運作。

但是,在範例11.16的架構下,只要有一個程式不按照規矩辦事,例如利用輪巡 迴圈進行輸出入動作,那麼整個系統可能就會陷入崩潰的命運,這也正是輪詢機 制的缺點之一。

輪詢機制的另一個缺點,是浪費了許多時間在輪流詢問的動作上,這對執行效能 有不利的影響。

因此,現代的CPU通常會支援中斷的機制,使用中斷機制,除 了讓電腦更有效率之外,還可以透過中斷機制實作作業系統中的行程切換系統, 讓程式得以在互不干擾的情況下,有效率的使用輸出入裝置,在下一節當中,我 們將會說明中斷機制的原理。

11.4中斷機制 中斷機制是由輸出入裝置,利用中斷訊號,主動回報輸出入裝置情況給CPU的 一種技術。

這種技術必須依靠硬體的配合,當輸出入裝置想要回報訊息時,可透 過匯流排,傳遞中斷訊號給CPU。

此時,CPU會暫停目前正在執行的程式,跳到 對應的中斷向量上,該中斷向量內會包含一個跳向中斷函數的指令,讓CPU開 17. 17 始執行該中斷函數。

CPU0的中斷機制 中斷機制的設計必須仰賴CPU的配合,舉例而言,假如我們為CPU0加入中斷 控制線,並且加入中斷控制電路,就可以在CPU0上實作中斷機制。

當裝置需要回報訊息給CPU0時,會將中斷代號放入到中斷控制線上,此時,CPU 就會根據此中斷代號,跳到對應的中斷向量位址中,引發中斷機制。

範例11.17顯示了一個CPU0的中斷設定程式,這個程式會被燒錄到CPU0的中 斷向量位址0x0000開頭之處,當CPU0接收到中斷訊號時,就會跳到對應的位 址中。

舉例而言,假如CPU0收到代碼4的中斷請求訊號時,就會跳到記憶體 位址0x000C之處,然後再跳轉到IrqHnd這個程式區,開始處理軟體中斷。

範例11.17CPU0的中斷向量 記憶體位址中斷向量說明 0000 0004 0008 000C InterruptVector: JMPResetHandler JMPUnexpected JMPSwiHnd JMPIrqHnd 中斷向量開始 1.重開機(Reset) 2.非預期中斷(Unexpected) 3.軟體中斷(SoftwareInterrput) 4.中斷請求(InterruptRequest) 範例11.18顯示了CPU0的中斷處理函數,在這個程式中,由於未對Unexpected 中斷進行處理,因此,乾脆讓Unexpected中斷進入無窮迴圈,在該中斷出現時 直接當機,以免產生不可預期的情況,這是嵌入式系統常見的一個技巧。

範例11.18CPU0的中斷處理程式(組合語言) 中斷處理的呼叫端(組合語言)說明 Unexpected: JMPUnexpected SwiHnd: PUSH{R1..R14} CALLCSwiHandler POP{R14..R1} RET 未預期中斷 不處理、無窮迴圈 軟體中斷 保留暫存器R1..R14 跳到CSwiHandler函數 恢復暫存器R1..R14 處理完後返回原程式 … 18. 18 IrqHnd: PUSH{R1..R14} CALLCIrqHandler POP{R14..R1} RET … 中斷請求 保留暫存器R1..R14 跳到CIrqHandler函數 恢復暫存器R1..R14 處理完後返回原程式 … 在CPU0的組譯器中,PUSH{R1..R14}這樣的指令是一種簡便的寫法,實際上會 被展開為PUSHR1,PUSHR2,….,PUSHR14,同樣的,POP{R14..R1}也會被展開為 POPR14,POPR13,…POPR1。

在範例11.18中處理未定義事件(UndefHnd)中斷時,首先利用PUSH{R1..R14} 保留除了PC之外的暫存器。

然後,就利用CALLCUdefHandler指令,跳到 CUdefHandler函數中,以處理未定義事件中斷。

等到處理完後,再利用POP {R1..R14}恢復暫存器的內容。

範例11.19顯示了中斷處理函數CIrqHandler()的C語言程式,我們可以利用連 結器將範例11.19與範例11.18連結在一起,讓範例11.18的CALLCIrqHandler 指令可以順利的連結到C語言的CIrqHandler()函數上。

範例11.19CPU0的中斷處理函數(C語言) 中斷處理函數(C語言) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 voidCIrqHandler(void){ intid=rINT; if((id>=0)&&(id=0)&&(id intcount=0; voidtimer_ISR(){ intnum=count%10; seg7show(num); count++; } intmain(){ register_irq(TIMER_IRQ_ID,timer_ISR); enable_irq(); while(1){} } 引用driver.h 計數器,從0開始不停向上數 時間中斷的服務函數 中斷時會執行此函數 顯示count除10後的餘數 繼續將count向上數 主程式 註冊timer_ISR為時間中斷函數 啟動中斷機制 無窮迴圈 如果讀者仔細閱讀範例11.21,應該會發現一件奇怪的事,第13行的while(1){} 實際上是一個無窮迴圈,因此,該程式在啟動中斷機制之後,就進入了一個無窮 迴圈。

因此,理論上這個程式應該會直接當機,甚麼事都不會做才對,但是,由 於中斷機制是由硬體直接驅動的,因此,每當時間中斷發生時,timer_ISR()函數 仍然會被呼叫,因此,七段顯示器上就會顯示下一個數字。

透過中斷機制,七段 顯示器會反覆的從0數到9,造成類似電子錶秒數跳動的感覺。

中斷機制與輪詢是嵌入式系統中經常使用的兩種輸出入方式,中斷機制由於仰賴 硬體的配合,因此,只能在支援中斷的處理器中實現。

目前,除了非常低階且原 始的單晶片處理器之外,大部分的處理器都能支援中斷機制,像是ARM、IA32、 MIPS等處理器,都能支援中斷機制。

有了中斷機制,作業系統才有可能實現真正的多行程執行環境,作業系統可以利 用時間中斷,收回處理器的控制權,以便安排另一個程式開始執行。

接著,我們 先來理解如何讓電腦啟動,以及如何設定中斷向量等主題,這將是下一節啟動程 式的內容。

21. 21 11.5啟動程式 啟動程式就是一個相當特殊的程式,其功能是建立電腦的程式執行環境,讓其他 程式得以順利執行。

撰寫啟動程式的設計師,必須依靠各種感覺器官,去感覺程 式是否正常,因為在啟動時,包含螢幕在內的各種裝置不見得能正常運作(甚至, 許多嵌入式系統根本就沒有螢幕),因此、可能會使用『嗶一聲』等原始的方法, 代表程式還在正常的執行,這也是嵌入式系統常用的方法。

電腦開機後,第一個被執行的記憶體位址,稱為啟動位址,啟動程式的第一個指 令,必須被正確的燒錄到該位址中,才能正確的啟動。

然而,程式設計師對啟動 程式通常會感到迷惑。

因為啟動程式必須在電腦一開機時就存在記憶體中,因此, 只能將啟動程式放到ROM或FLASH等永久性儲存體當中。

如果將啟動程式放到 揮發性記憶體,像是DRAM或SRAM中,那麼,當使用者關閉電源後重新開機時, 啟動程式將消失無蹤,電腦也就無法順利啟動了。

對嵌入式系統而言,啟動程式中除了包含機器指令之外,也會包含資料區域,像 是.data段與.bss段。

對於.data段而言,這些資料一開始會被儲存在ROM 當中,但是,在啟動之後必須被搬入到RAM當中,否則,程式將無法修改這些 資料。

因此,嵌入式的啟動程式會將資料區從ROM搬移到RAM,才能讓這些具有初值 的變數進入可修改狀態。

因此,啟動程式必須將自己先從ROM搬到RAM之後, 才能開始執行其主要功能。

啟動程式必須建立程式的執行環境,讓其他程式得以順利執行,其主要任務包含 下列四項: 1.設定中斷向量,啟動中斷機制。

2.設定CPU與主機板的各項參數,讓CPU與主機板得以進入正確的狀態。

3.將存放在永久儲存體中的程式與資料搬到記憶體中。

4.設定高階語言的執行環境,包含設定堆疊(Stack)、堆積(Heap)等區域。

為了更詳細的說明啟動程式的功能,我們將再度以基於CPU0處理器的M0電腦 為例,以範例的方式說明啟動程式的設計方式。

M0電腦的記憶體配置 22. 22 M0電腦擁有16K的ROM與48K的RAM,ROM的存取位址為0x0000~0x3FFF, 而RAM的存取位址為0x4000~0xFFFF。

如圖11.3(a)所示。

在啟動時,ROM當中已經燒錄有整個系統的程式與資料,其中,啟動程式段 (*.stext)被燒錄在ROM開頭的0x0000區域。

M0電腦在重開機時,會從啟動位 址0x0000開始執行,因此,啟動程式當中的重開機中斷必須被燒錄在0x0000 的位址上,如此才能順利開機。

由於M0電腦是嵌入式系統,沒有硬碟,所有程式都被燒錄在ROM當中,因此, 除了啟動程式之外,所有的程式區(*.text)、資料區(*.data)都被燒錄在ROM當 中。

但是,BSS段(*.bss)並不需要燒錄,因為這個區段的變數沒有設定初值, 燒錄或不燒錄都無所謂,只要能記住BSS段的大小即可。

所以,BSS段被放在ROM 的最後部分,其空間可以一直延伸到RAM的區域,如圖11.3(b)所示。

圖11.3M0電腦的記憶體配置圖 另外,堆疊區被放置在RAM的最後部分,從0xFFFF開始向低位址的方向延伸, 如此,堆疊區與堆積區可共用RAM的最後的一個區塊,只要兩者不發生重疊的 情形即可。

根據圖11.3(b),我們可以撰寫M0電腦的連結檔,如範例11.22的檔案M0.ld 所示。

其中,程式區塊.text0x0000:{…}代表程式區會從0x0000位址開始, 23. 23 而_stext=.當中的點符號『.』代表目前位址,該指令要求連結器創建一個_stext 符號,該符號的值為目前位址(0x0000)。

然後,開始將程式放入這個區域,首先 是啟動程式(*.stext),接著是一般程式(*.text),然後,利用『.=ALIGN(4)』這 個指令,讓點符號『.』所代表的目前位址,向後移動到4的倍數之處,以便進 行32位元電腦的對齊動作。

接著,由於資料區塊.data:{…}沒有指定起始位址,因此會緊接在上一個區塊之 後。

然後,BSS區塊.bss:{…}會跟在資料區塊之後。

由於BSS段是未設初值的資 料,因此,就算BSS段很大,無法被放入ROM當中也無所謂,只要能放入RAM 區域當中即可。

範例11.22M0電腦的連結檔 M0電腦的連結檔(M0.ld)說明 SECTIONS { .text0x0000:{ _stext=.; *(.stext) *(.text) .=ALIGN(4); _etext=.; } .data:{ _sdata=.; *(.data) .=ALIGN(4); _edata=.; } .bss:{ _sbss=.; *(.bss) .=ALIGN(4); _ebss=.; } _end=.; 程式段:起始位址為0x00000000 設定程式段起點_stext為目前位址 插入所有的.stext段的目的碼 插入所有的.text段的目的碼 以4byte為單位進行對齊 設定程式段終點_etext為目前位址 資料段:緊接在程式段之後 設定資料段起點_sdata為目前位址 插入所有的.data段的目的碼 以4byte為單位進行對齊 設定資料段終點_edata為目前位址 BSS段: 設定BSS段起點_sbss為目前位址 插入所有的.bss段的目的碼 以4byte為單位進行對齊 設定BSS段終點_ebss為目前位址 設定終點_end為目前位址 24. 24 .stack0xFFFF:{ _STACK=.; } } 堆疊段:位址為0xFFFF 設定使用者堆疊起點為0xFFFF 在這些區塊中,我們創建了_stext,_etext,_sdata,_edata,_sbss,_ebss,等符號, 並設定其位址,以便在組合語言或C語言中可以取得這些符號的位址,提供啟動 程式使用。

最後,我們設定堆疊區塊.stack0xFFFF:{…},並將堆疊符號_STACK設定為該區 塊的位址,也就是0xFFFF,這樣,我們就能在啟動程式中正確的設定堆疊暫存 器R13(SP)的值了。

利用範例11.22的M0.ld連結檔,我們可以建構出如圖11.3(b)的映像檔,然 後燒錄到M0電腦的ROM之中。

然後,我們必須撰寫啟動程式,利用該程式設 定電腦的基本執行環境,讓後續的程式可以正確的執行。

M0電腦的啟動程式 在M0電腦的啟動程式執行前,其記憶體映像就如同圖11.3(b)的映像檔所示, ROM區域燒錄有1.啟動程式(*.stext)2.其他程式(*.text)3.資料(*.data)等 區塊。

在啟動程式執行完畢後,會將所有的程式與資料都搬到RAM當中,如此, 才能讓這些資料得以被寫入或修改,並且分配出BSS區段、堆積段(heap)與堆 疊段(stack)等空間,這些空間可為C語言與組合語言提供一個良好的執行環 境。

其中的資料段可以存放『具有初值的變數』,BSS段可以存放『無初值變數』使 用,堆疊段可以存放函數的『參數』、『返回點』與『區域變數』等2,而堆積段 則可以儲存C語言當中的動態配置記憶體,像是malloc指令所需要的記憶空 間。

必須注意的是,堆疊段是由高位址空間向低位址的方向增長的,而堆積段則是由 低位址空間向高位址方向增長的,因此,整個系統在執行時期的記憶體配置會如 圖11.3(c)所示。

M0電腦啟動程式的主要功能,就是改造圖11.3(b)的『初始 環境』,以便形成如圖11.3(c)的『執行環境』。

2關於C語言函數呼叫的堆疊配置方式,請參考第3章的實務案例-『編譯器與副程式』一節。

25. 25 範例11.23顯示了該啟動程式的組合語言部分,程式的開頭就是中斷向量表,包 含了四個跳躍指令。

接著,該程式定義了這些中斷的處理函數,包含重開機中斷 (ResetHandler)、非預期中斷(Unexpected)、軟體中斷(SwiHandler)、中斷請求 (IrqHandler)等,其中,後三個中斷已經在11.4節中介紹過,在此不再重複敘述, 我們將焦點放在啟動中斷的ResetHandler程式上。

範例11.23M0電腦的啟動程式 M0電腦的啟動程式(組合語言) 檔案:boot.s 說明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 .globalstext,main .globalCSwiHandler .globalCIrqHandler RamStartEQU0x4000 .section".stext" InterruptVector: JMPResetHandler JMPUnexpected JMPSwiHandler JMPIrqHandler Unexpected: JMPUnexpected SwiHandler: PUSH{R1..R14} CALLCSwiHandler POP{R14..R1} RET IrqHandler: PUSH{R1..R14} CALLCIrqHandler POP{R14..R1} RET 中斷向量開始 中斷1:重開機(Reset)啟動中斷 中斷2:非預期中斷(Unexpected) 中斷3:軟體中斷(SoftwareInterrput) 中斷4:中斷請求(InterruptRequest) 中斷2:非預期中斷 不處理,因此進入無窮迴圈 中斷3:軟體中斷 保存暫存器 呼叫軟體中斷處理函數(C語言) 恢復暫存器 返回原程式 中斷4:中斷請求 保存暫存器 呼叫中斷請求處理函數(C語言) 恢復暫存器 返回原程式 26. 26 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 ResetHandler: LDIR12,0xD0 MoveToRam: LDR1,RamStart LDIR2,0x0000 LOOP1:LDR3,[R0+R2] STR3,[R1+R2] ADDR2,R2,1 CMPR2,R1 JNELOOP1 JumpToRam: LDR15,RamCinit Cinit: LDR1,RamSbss LDR2,RamEbss LOOP2:CMPR1,R2 JNEInitStacks STR0,[R1] JMPLOOP2 InitStacks: LDSP,StackBase LDIR12,0x00 MainLoop: CALLmain JMPMainLoop RamSbssWORDRamStart+_sbss RamEbssWORDRamStart+_ebss RamCinitWORDRamStart+Cinit StackBaseWORD_STACK 重開機處理程式 設定狀態暫存器,同時禁止所有中斷 將機器碼從ROM(或Flash)搬到RAM 從0x0000搬到RamStart=0x4000 跳到RAM版本的Cinit標記中。

C語言環境設定 首先清除BSS段 設定RAM中BSS段的內容為0 堆疊初始化 允許中斷 無窮迴圈 進入C語言的主程式。

當M0電腦一開機後,會先執行位址0000的JMPResetHandler指令,然後跳 入ResetHandler標記的程式區。

接著,啟動程式會使用LDIR12,0xD0這樣一個 指令,禁止所有中斷發生。

但是,這個指令可能會令讀者費解,為何該指令會禁 止中斷的發生呢? 27. 27 這牽涉到CPU0的架構,請讀者參考的CPU0的狀態暫存器位元圖(圖11.4)。

由 於在CPU0當中,R12就是狀態暫存器SW,其中第7,6兩個位元是I,T位元, 代表中斷的禁止位元,因此,LDIR12,0xD0這樣的指令會使I,T兩個位元變成0, 於是禁止了中斷的發生,以免在重開機時還有中斷產生,讓開機程序無法順利完 成。

圖11.4CPU0的狀態暫存器R12之位元圖 接著,在一般的CPU處理程序上,可能會有一連串的設定動作,例如設定暫存 器的初值、清除快取、設定記憶體控制暫存器、設定時間中斷頻率等等。

但是由 於我們假設M0當中這些參數都是直接燒錄在電路當中,因此,不需要在開機時 用程式設定,這可以省掉許多動作,但相對也缺少了許多彈性。

然後,在MoveToRam標記的程式區段中,程式會將原先位於ROM當中的程式 與資料,原封不動的搬到RAM當中。

然後,在JumpToRam標記的區塊中,利用 LDR15,RamCinit這個指令設定程式計數器,讓程式從ROM跳入到RAM區域的 Cinit標記中,開始執行RAM中的Cinit標記後的指令。

在Cinit標記的區段中,程式會先清除BSS區段為0,然後利用LDSP,StackBase 這個指令設定堆疊指標。

接著,在MainLoop標記的區段中,使用CALLmain呼 叫C語言的主程式,完成整個啟動過程。

在啟動完畢之後,就可以進入C語言的主程式main當中,如果主程式如同上一 節的範例11.21所示,那麼,M0電腦的七段顯示器就會反覆的從0數到9,如 同電子錶般的跳動著。

11.6系統整合 在一個嵌入式系統開發的過程當中,開發人員會建構出許多相關檔案,包含組合 語言、C語言程式、資料檔、連結檔等等,要能有效率的編譯與連結這些檔案, 最好是使用專案整合工具(例如GNU的Make工具)。

28. 28 GNU的Make是專案建置實相當常見的工具,在本節中,我們將說明如何利用 make工具整合專案的建置過程,讓專案的建置自動化。

當嵌入式專案開始時, 最好能撰寫一個Makefile檔,整合專案中的所有檔案。

舉例而言,假如GNU已經針對CPU0開發了一組專用工具,其編譯器名稱為 cpu0gcc,連結器名稱為cpu0ld。

而且我們已經撰寫了driver.h、driver.c、main.c, boot.s、M0.ld等檔案,那麼,我們就可以撰寫如範例11.24的Makefile檔,以 便對這些檔案進行專案編譯、連結的動作。

範例11.24M0電腦的專案檔 專案檔:Makefile說明 CC=cpu0gcc LD=cpu0ld OBJCOPY=cpu0objcopy OBJS=driver.oboot.omain.o FLAGS=-I. all:$(OBJS) $(CC)-TM0.ld-oM0.o$(OBJS) $(OBJCOPY)-Obinary-SM0.oM0.bin .c.o: $(CC)$(FLAGS)-c-o$@$< .s.o: $(CC)$(FLAGS)-c-o$@$< clean: rm*.bin*.o 使用cpu0gcc編譯器 使用cpu0ld連結器 使用cpu0objcopy 目的檔列表 編譯參數 (-I.代表在目前路徑下搜尋*.h檔) 全部編譯連結 使用M0.ld連結,輸出M0.o 將目的檔轉換為二進位檔 編譯C語言程式 編譯組合語言程式 清除上一次的輸出檔 利用Makefile檔與GNUmake工具,我們可以將所有程式編譯、連結成目的檔 後,再透過objcopy工具將目的檔轉換成二進位檔(像是範例11.24中的 M0.bin),然後,就可以利用燒錄工具,將該二進位檔燒錄到嵌入式系統中,接 著,按下重新開機鍵,看看程式是否能正常執行。

嵌入式系統在開發時,通常會先在測試板上開發,這樣可以方便開發人員撰寫與 測試程式。

開發人員可以利用個人電腦上的操作介面,像是MS.Windows上的 『超級終端機』程式,利用簡易的通訊介面(例如UART),透過COM連接埠與 測試板溝通,利用指令或選單,將編譯完成的二進位檔傳送到測試板的RAM當 中,以便執行該程式。

29. 29 當開發人員想要將程式燒錄到測試板的ROM(在測試板上通常是Flash)上的時 候,通常必須透過ICE(IntegratedCircuitEnvironment)裝置,將該二進位檔燒錄 到測試板的Flash中。

由於Flash是永久儲存體,電源關閉後資料仍然會存在, 因此,當下次重開機時,就會透過該Flash中的程式啟動該嵌入式系統,於是該 系統就變成了目標系統的原型機,可以在量產之前先提供給客戶使用,以確認功 能是否正確,系統的運作是否正常等。

因此,測試板在嵌入式系統的開發上是相 當有用的,特別是針對系統開發人員而言。

嵌入式裝置是系統程式設計的一個進階領域,該領域整合了軟體、韌體與硬體的 背景知識,是學習系統程式的絕佳戰場。

對於有心學習系統程式的讀者而言,可 以進一步參考嵌入式系統的專業書籍,或者購買嵌入式實驗板,學習期程式設計 方法,相信會有更多的收穫。

11.7實務案例:新華CreatorS3C2410實驗板 為了說明嵌入式系統的開發過程,筆者使用新華電腦的CreatorS3C2410實驗板 作為範例,重點式的說明開發的流程。

該實驗板的範例主要架構在Cygwin環境下,舉例而言,如果我們想編譯其中的 LCD這個範例,可以進入/usr/var/creator/LCD這個資料夾後,執行make指令, 以便重建整個專案,範例11.25顯示了該建置過程。

範例11.25新華CreatorS3C2410實驗板範例LCD的建置過程 在Cygwin中的編譯執行過程(TIMER範例)說明 ccc@ccc-kmit2/usr/var/creator/LCD $ls Makefiledemo.cdemo_ram.mapdemo_rom.maplcd.c commondemo_ram.lddemo_rom.ldgnu ccc@ccc-kmit2/usr/var/creator/LCD $makeclean rm*.bin*.axf*.o*.s ccc@ccc-kmit2/usr/var/creator/LCD $make /usr/local/bin/arm-elf-gcc-nostartfiles-g -I/usr/var/creator/LCD/common-I/u 列出資料夾中的檔案 清除專案(上次的輸出) 重建專案 30. 30 …略… /usr/local/bin/arm-elf-gcc-Tdemo_ram.ld–Wl,-M, -Map=demo_ram.map-o"demo_ram.axf"demo.osbrk.o driver.oirq.ommu.o2410slib.olcd.ohead_ram.o arm-elf-objcopy-Obinary-Sdemo_ram.axfdemo_ram.bin …略… 製作demo_ram.axf目的檔 將該目的檔轉為二進位的 demo_ram.bin 一但建置完畢後,我們就可以開啟MS.Windows中位於『開始/附屬應用程式/ 通訊/超級終端機』程式,以便將demo_ram.bin檔案,傳送到該實驗板中,以 下是我們的操作過程。

首先,當超級終端機啟動後,按下實驗板的重開機按鈕,會進入圖11.5的起始 畫面,這個畫面是由實驗板中一個預先燒錄的啟動程式,利用UART協定傳送給 超級終端機所顯示出來的。

此時,使用者可以選擇功能1–DownloadtoRam&Go 這個功能,以便將方才所建置的demo_ram.bin檔案上傳。

圖11.5透過超級終端機,顯示該實驗板的起始功能表 接著,我們必須按下『傳送/傳送檔案』的功能,然後選擇所要上傳的檔案 demo_ram.bin,接著,按下開始按鈕,確定選取該檔案。

31. 31 圖11.6按下傳送功能,選取欲上傳的檔案demo_ram.bin 接著,會顯示傳送檔案的對話框,我們必須將通訊協定設為Zmodem,然後按 下傳送按鈕,將檔案上傳並且開始執行。

圖11.7選擇通訊協定為Zmodem,按下傳送鈕,開始上傳 上傳完畢後,實驗板上預設的啟動程式就會直接執行該上傳程式,若一切正常, 我們會在實驗板的LCD螢幕中看到該範例印出了"Hello!"的字串,這代表程式 正確的被執行了。

透過這個範例,我們希望讀者能較為實際的感受到嵌入式系統的開發流程,如果 您手上也有嵌入式的實驗板,也可以趁現在操作看看。

當然,每一張實驗板的設 32. 32 計會有所不同,操作的過程也會不同,您必須根據該實驗板製造商的說明文件進 行操作,才能順利的執行這些程式,然後才能進一步開發自己的程式。

習題 11.1請說明何謂專用輸出入指令? 11.2請說明何謂記憶體映射輸出入? 11.3請說明何謂中斷機制?中斷發生時程是會跳到哪裡呢? 11.4請說明啟動程式應該做些甚麼事呢? 11.5請寫出一個完整的C語言程式,讓M0實驗板在數字按鍵被按下時,於七 段顯示器當中顯示對應的數字。

11.6同上題,但是請以CPU0的組合語言撰寫。

交⼤資訊⼯程學系備審資料⾱詠祥 鍾誠陳鍾誠 smallpt:GlobalIlluminationin99linesofC++ 鍾誠陳鍾誠 西洋史(你或許不知道但卻影響現代教育的那些事) 鍾誠陳鍾誠 區塊鏈(比特幣背後的關鍵技術)--十分鐘系列 鍾誠陳鍾誠 區塊鏈(比特幣背後的關鍵技術)--十分鐘系列 鍾誠陳鍾誠 梯度下降法(隱藏在深度學習背後的演算法)--十分鐘系列 鍾誠陳鍾誠 用十分鐘理解《微分方程》 鍾誠陳鍾誠 系統程式--前言 鍾誠陳鍾誠 系統程式--附錄 鍾誠陳鍾誠 系統程式--第12章系統軟體實作 鍾誠陳鍾誠 作者:陳鍾誠 Views Totalviews 1,967 OnSlideshare 0 Fromembeds 0 Numberofembeds 0 Actions Downloads 0 Shares 0 Comments 0 Likes 4 4Likes Statistics Notes × ShareClipboard × Facebook Twitter LinkedIn Link Publicclipboardsfeaturingthisslide × Nopublicclipboardsfoundforthisslide Selectanotherclipboard × Lookslikeyou’veclippedthisslidetoalready. Createaclipboard Youjustclippedyourfirstslide! Clippingisahandywaytocollectimportantslidesyouwanttogobacktolater.Nowcustomizethenameofaclipboardtostoreyourclips. Name* Description Visibility OtherscanseemyClipboard Cancel Save SpecialOffertoSlideShareReaders × TheSlideSharefamilyjustgotbigger.Younowhaveunlimited*accesstobooks,audiobooks,magazines,andmorefromScribd. Activateyourfree60daytrial Cancelanytime.



請為這篇文章評分?