天天看點

《 嵌入式系統設計與實踐》一一1.2 嵌入式系統開發

1.2 嵌入式系統開發

嵌入式系統是特殊的,是以也給開發者帶來一些特殊的挑戰。許多嵌入式軟體工程師開發了工具箱來處理各種限制。在我們開始建構自己的系統之前,先來看看開發一個嵌入式系統會有哪些困難。在熟悉了嵌入式系統開發會如何受到限制之後,我們再開始讨論一些設計原則并借此指導我們找到更好的解決方案。

1.2.1 調試

如果在計算機上運作調試軟體,就可以在這台計算機上編譯和調試。系統有足夠的資源在運作程式的同時調試程式。事實上,硬體根本不知道是在調試程式,因為這是由軟體完成的。

嵌入式系統就不是這樣了。除了需要交叉編譯器外,還需要一個交叉調試器。這個調試器運作在計算機上,通過特殊的處理器接口和目标處理器通信(見圖1-1)。這個接口是專門用來在處理器工作時對它進行偵聽的。這個接口通常稱為jtag(發音“jay-tag”),而不管有沒有真正地實作這個廣泛應用的标準。

《 嵌入式系統設計與實踐》一一1.2 嵌入式系統開發

圖1-1:計算機和目标處理器

處理器必須通過擴充某些資源以支援這個調試接口,允許調試器在運作時挂起它,并提供調試資訊。支援調試操作增加了處理器的成本。為了節省成本,一些處理器隻支援一個受限的功能子集。比如,增加一個斷點會讓處理器修改機器代碼以停在斷點處。但是,如果代碼是執行在閃存(或者任何其他的隻讀存儲器)中,那麼處理器将會設定一個内部寄存器(硬體斷點),并在每個執行周期時将其與運作位址比較,如果相等則停止程式運作。這樣可以改變代碼的時序,在調試(或者沒有調試)時出現一些奇怪的問題。内部寄存器也消耗資源,是以常常隻有極其有限的硬體斷點可用(大多數情況下,隻有兩個)。

總之,處理器支援調試,但與純軟體開發相比,就沒有我們習以為常的那麼多調試功能。

在計算機與目标系統之間通信的裝置通常叫做仿真器、線上仿真器(ice)或者jtag擴充卡。這些都可能指同一個東西(不怎麼合适),或者三個不同的裝置。仿真器是為特定的處理器(或者處理器系列)設計的,是以不要以為在一個項目中使用的仿真器可以在另一個項目中正常使用。仿真器會增加成本,尤其是當有多個仿真器或者有一個比較大的團隊在開發系統時。

為了避免購買仿真器或者處理器的限制,許多嵌入式系統都通過其他一些手段實作調試,如使用printf或者一些輕量級日志向一個沒有使用的通信接口輸出。這些方法非常有用,但也會改變系統的時序,導緻一些問題隻有在關掉調試輸出之後才能得到解決。

嵌入式系統的軟體開發有些棘手,因為需要平衡系統的要求和硬體的限制。現在,在待辦事項清單裡加上一項:在不那麼友好的硬體環境中,讓軟體具備比較好的可調試性。

1.2.2 更多挑戰

嵌入式系統是為了完成特定的任務,是以會去掉所有與完成任務不相關的資源。這裡的資源包括:

記憶體(ram)

代碼空間(rom或閃存)

處理器周期或者速度

耗電量(電池壽命)

處理器外設

從某種程度上說,這些是可以互換的。比如,以代碼空間換cpu周期,在寫代碼時可以讓部分代碼占據更多的空間這樣就可以運作得更快。或者可以降低處理器的運作速度以減少耗電量。如果沒有特定的外圍裝置接口,則可以利用輸入/輸出線和處理器周期在軟體裡模拟這個接口。但是,即使再怎麼權衡,以上各種資源依然是非常有限的。資源限制所帶來的挑戰是所有挑戰中最明顯的。

另一類挑戰來自硬體。交叉調試帶來的額外壓力是令人沮喪的。在電路闆調試的過程中,對于一個缺陷是由硬體還是軟體造成通常是不确定的,這讓問題變得更難以解決。與計算機不同,在嵌入式系統中,我們編寫的軟體可能對硬體造成實際的破壞。是以,大多數情況下,需要了解硬體以及硬體能做什麼。雖然,這些知識可能在設計另外一個系統的時候毫無用處,但是你必須面對挑戰,快速學習。

開發和測試完成了之後,就進入系統生産制造階段。這是大多數純軟體開發工程師不曾考慮過的事情。建構一個系統,并且以比較合理的成本去生産它,這是軟體工程師和硬體工程師都該銘記于心的一個目标。對可制造性的支援,是確定系統可以以較高的精度重複制造的一種方法。

制造完成之後,産品就進入市場了。對消費類産品來說,同時意味着千家萬戶會“享受”産品中的缺陷。對于醫療、航空或者其他關鍵産品,這些缺陷将是災難性的。(這就是為什麼現在要做這麼多研究工作的原因)。對于科學研究和監控裝置,應用現場可能是那些裝備難以收回(或者需要巨大的風險和代價才能收回,比如在火山口的裝置),是以這些裝置最好能正常運作。系統在從我們手中誕生之後會帶來什麼樣的生活,這也是設計軟體時必須面對的一個挑戰。

在對所有這些問題了然于心,并且在設計系統的時候有了确定的方法解決這些問題之後,還有一個最大的挑戰,這對所有的工程師來說都很常見的挑戰:變更。不僅僅産品目标會變更,項目的需求也會在整個項目周期内變更。最初,可能隻是想試驗一些新的想法,去做一些嘗試。随着對産品目标的認識逐漸加深以及對硬體越來越了解,我們就會開始設計更多的機制讓軟體變得可調試、健壯和靈活。在資源受限的環境裡,需要決定在開發時間,記憶體、代碼空間以及處理器周期這幾個方面能提供哪些基礎設施。通常,最初的設計并不是在你開發完成後所得到的那個,并且開發似乎永無止境。

不幸的是,為了特定的應用目的設計出來的嵌入式系統有一個副作用:當應用發生變化時,系統可能難以支援變更。設計開發嵌入式系統并不僅僅是關于嚴格的限制和系統的最終完成,這裡的挑戰是要找出這些限制中哪些會在産品開發的後期産生問題。是以,需要能夠預測可能導緻變更的原因,設計足夠靈活的軟體來适應可能發生的應用程式變化。

1.2.3 解決問題的原則

嵌入式系統就像個智力拼圖,每一小部分都互相鎖在一起(隻能以一種方式)。有時候,雖然可以使用蠻力将各個部分拼在一起,但結果卻可能和盒子上的圖像相差甚遠。我們應該摒棄這樣的觀點,即在項目結束時将最終的結果作為唯一釋出的代碼版本。

事實上,智力拼圖有個時間次元揭示了其整個生命周期的不同變化:概念設計、原型化、電路闆調試、系統調試、測試、釋出、維護,如此循環往複。靈活性并不僅僅指代碼現在能做什麼,而且指在其整個生命周期裡面能做什麼。我們的目标是要做到足夠靈活,這樣才能在滿足産品目标的同時能夠很好地處理資源限制和其他一些嵌入式系統内在的設計挑戰。

我們可以應用軟體設計上的很多優秀的設計原則來讓系統變得更加靈活。通過使用子產品,我們将功能分離在子系統裡,并隐藏各個子系統的資料。使用封裝,我們設計子系統之間的接口,以使各個子系統互相獨立。一旦我們擁有了松耦合的多個子系統(或者對象),就可以在修改軟體的某一部分時相信這個修改不會影響其他部分。這樣我們就可以分拆我們的系統,然後在需要的時候按照不同的方式再把它們組裝起來。

知道在哪裡将一個系統分解為各個部分需要更多的實踐。一個比較好的原則是考慮哪些部分會獨立地發生變化。在嵌入式系統裡,應用這一原則需要我們考慮各個不同的實體對象。比如,如果傳感器x需要通過通道通信y通信,那麼這兩個獨立的對象就是兩個候選子系統(也是兩個代碼子產品)。

我們把子系統分解為對象之後,就可以對這些對象進行測試。我很幸運,曾經在一些項目中有非常優秀的品質保證(qa)團隊。在其他的一些項目中,不曾有過任何人在我的代碼和那些将要使用我的系統的人們之間承擔qa的角色。我發現在軟體正式釋出之前捕獲的缺陷就像禮物一樣。錯誤發現的越早,解決這些錯誤的成本越低,對大家越有好處。

當然,不必等着别人給我們送禮物。測試和品質向來是互相關聯的。寫測試代碼對系統進行測試可以讓系統品質更高,給代碼提供一些文檔,别人會認為我們開發出來的軟體卓爾不群。

對代碼文檔化是另一個減少缺陷的方法。但如下這樣注釋代碼,讓人很難了解詳細的程度。

i++; // increment the index

不需要這樣做,其實這樣的代碼行很少需要注釋。寫注釋的目的是為了像你一樣的開發人員,在一年之後再看你寫的這些代碼,那個時候的你可能正忙于其他事情并且忘記了當初你怎麼想出這個創造性的解決方案,你可能甚至已經忘記了你寫過這些代碼。是以,請在代碼裡留下些痕迹以幫助你自己找回記憶(檔案和函數頭)。總的說來,假設讀者和你具有相同的心智和背景,隻需寫清楚這段代碼做了什麼,而不是如何做。

最後,在資源有限的系統開發過程中,我們常常會有盡早和盡可能多地去優化代碼的想法。抑制住這個欲望。實作所有的功能、讓系統運作、完成測試,然後再回來按照要求讓代碼更小或者運作得更快。

時間是有限而寶貴的,是以在各個子系統能夠運作後再來專注于那些最消耗資源的部分,看看能否得到更好的結果。為了運作速度去優化一個很少運作的函數不會帶來任何好處,反而會減少花費在那些運作頻率非常高的函數上的時間。有一點可以肯定的是,處理系統的資源限制需要一些優化。但在調優之前請務必搞清楚系統的資源消耗情況。

“我們應該忘記小的性能提升,在97%的情況下,不成熟的優化是萬惡之源”

——donald knuth

繼續閱讀