嵌入式系統(tǒng)架構(gòu)淺談——訪問硬件的設(shè)計(jì)模式
這系列開始談軟件上面的設(shè)計(jì),對(duì)設(shè)計(jì)模式在面向?qū)ο罄锩鎽?yīng)該各位都知道,或許你在實(shí)際開發(fā)當(dāng)中用到,也或許你見過別人的代碼中用到。當(dāng)你程序的代碼足夠龐大的時(shí)候,你會(huì)發(fā)現(xiàn)維護(hù)寸步難行,牽一發(fā)而動(dòng)全身,這個(gè)時(shí)候你就能夠理解在開發(fā)初期對(duì)程序架構(gòu)的搭建重要性。而架構(gòu)最基本熟知的其中就是設(shè)計(jì)模式,使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性、程序的重用性。嘗試去研究?jī)?yōu)秀的開源代碼,你會(huì)驚嘆別人對(duì)程序的掌控,這時(shí)你會(huì)稍稍明白架構(gòu)的目的所在。文章基于《C嵌入式編程設(shè)計(jì)模式》這本書,英文是Design?Patterns?for?Embedded?Systems in C。主要是做個(gè)筆記,并添加一點(diǎn)個(gè)人的理解,分享出來(lái)與各位探討。比較針對(duì)嵌入式系統(tǒng),單片機(jī),程序已C語(yǔ)言為主,盡管是面向過程,但不妨礙我們使用面向?qū)ο蟮乃季S來(lái)開發(fā)。
1.?訪問硬件的設(shè)計(jì)模式
嵌入式系統(tǒng),特別單片機(jī)最明顯的是對(duì)硬件的直接訪問?;A(chǔ)硬件不僅有CPU,內(nèi)存,鍵盤,傳感器,通訊RS232等這樣的設(shè)備。做單片機(jī)的不得不對(duì)硬件進(jìn)行控制,讀,寫操作,而這篇文章已解決管理和操作這些硬件通常的一個(gè)模式?;蛟S對(duì)你來(lái)說并不陌生,但是是否能夠系統(tǒng)的,詳細(xì)的表達(dá)出來(lái)這就不僅僅只是了解就能達(dá)到的。下面討論的設(shè)計(jì)模式已經(jīng)在操作硬件上得到證明是可靠有效的。簡(jiǎn)單總結(jié)說,硬件代理模式是以封裝詳細(xì)信息為目的的硬件抽象的一個(gè)原型模式,它有可能改變提供給硬件或來(lái)自硬件的信息處理方法。硬件適配器模式擴(kuò)展硬件代理模式,以提供支持不同硬件接口的能力。中介者支持多種硬件設(shè)備的協(xié)調(diào),實(shí)現(xiàn)系統(tǒng)級(jí)行為。觀察者模式是發(fā)布遙感數(shù)據(jù)到需要的軟件元素的方法。去抖動(dòng)模式和中斷模式是硬件設(shè)備接口簡(jiǎn)單重用的方法。定時(shí)器模式擴(kuò)展中斷定時(shí)器為嵌入式系統(tǒng)提供精確時(shí)序。1.1 硬件代理模式
硬件代理模式概念是對(duì)訪問硬件接口的封裝,限制客戶直接訪問硬件造成問題。1.1.1 模式結(jié)構(gòu)
模式結(jié)構(gòu)非常簡(jiǎn)單,可能客戶會(huì)有多個(gè),但是每個(gè)硬件設(shè)備僅有一個(gè)硬件代理,客戶只能訪問代理接口,無(wú)法直接訪問硬件就是這個(gè)模式的目的。1.1.2 角色
1.1.2.1 硬件設(shè)備(HardwareDevice)硬件設(shè)備可以是各種,內(nèi)存,傳感器等,包含了端口地址,內(nèi)存地址,寄存器地址等等元素。與硬件代理的關(guān)聯(lián)是通過軟件尋址方式,對(duì)硬件的讀寫操作。1.1.2.2 硬件代理(HardwareProxy)這個(gè)是系統(tǒng)中的主功能。給上層應(yīng)用提供的硬件訪問接口,上層應(yīng)用無(wú)須詳細(xì)關(guān)心硬件的具體實(shí)現(xiàn)?;旧贤ǔC總€(gè)代理都有initialize()、configure()和disable()函數(shù)。大部分還會(huì)有對(duì)設(shè)備的值讀取訪問,或者寫訪問接口。但是一般不能隨意讀寫,會(huì)詳細(xì)到讀取到最終的值。函數(shù)包括:access():從設(shè)備返回一個(gè)特殊值。大多數(shù)情況下,代理會(huì)對(duì)每個(gè)來(lái)自設(shè)備單獨(dú)的信息提供單獨(dú)的函數(shù)。例如返回傳感器的溫度,濕度值。configure():提供硬件配置的方法。一般會(huì)有參數(shù)列表,通過傳入?yún)?shù)來(lái)配置正確的工作狀態(tài)。disable()、enable():提供設(shè)備的安全禁用或開啟的方法。initialize():用于第一次啟動(dòng)時(shí)候的初始化硬件。mutate():用于向設(shè)備寫入數(shù)據(jù),通??偸怯幸粋€(gè)或更多的輸入?yún)?shù)。marshal()、unmarshal():這兩個(gè)為私有函數(shù),用于把客戶數(shù)據(jù)格式轉(zhuǎn)為硬件所需格式,后者相反,把硬件原始數(shù)據(jù)格式轉(zhuǎn)換為客戶格式。常用于加密解密,壓縮解壓縮等。deviceAddr:是一個(gè)私有變量,提供底層直接訪問硬件的地址。必須隱藏在代理中,不能給客戶訪問的機(jī)會(huì),所以特別注意到一些接口,是否會(huì)通過了指針把該變量暴露出去。1.1.2.3 代理客戶(ProxyClient)客戶代碼調(diào)用硬件代理服務(wù)來(lái)訪問硬件設(shè)備。1.1.3 效果
該模式非常普遍并且具有封裝硬件接口以及編碼系統(tǒng)的所有優(yōu)點(diǎn)。這為不對(duì)客戶端進(jìn)行任何改變而從根本上改變實(shí)際硬件接口提供了靈活性?;旧纤械挠布O(shè)備都能用此模式搭建,注意的是不能暴露細(xì)節(jié),只能返回一個(gè)最后的結(jié)果,特別在讀寫操作,否則就不具備有封裝性了。1.1.4 實(shí)現(xiàn)
可以有很多不同方法用C語(yǔ)言實(shí)現(xiàn),最常見的是如linux驅(qū)動(dòng),使用結(jié)構(gòu)體里的函數(shù)指針統(tǒng)一硬件的接口。然后在具體的硬件設(shè)備上實(shí)現(xiàn)。?1.2 適配器模式
硬件適配器模式提供一種方法,使已經(jīng)存在硬件接口能適用應(yīng)用期望。可以說是在硬件代理模式基礎(chǔ)上,為了能夠適應(yīng)底層不同的硬件設(shè)備,在中間增加一層適配器。比如在通訊上面在硬件上都存在RS232,RS485,程序需要在不同情況下使用232通訊或485通訊,而適配器可以提供統(tǒng)一的接口給客戶層,通過指針指向所需通訊,則可以實(shí)現(xiàn)。最大的特點(diǎn)是在運(yùn)行中選擇,相比使用宏定義需要生成不同執(zhí)行程序,可以在程序中實(shí)現(xiàn)自適應(yīng)的功能。1.2.1 模式結(jié)構(gòu)
?1.2.2?角色
1.2.2.1 硬件適配器(HardwareAdapter)硬件適配器在客戶和硬件代理之間執(zhí)行匹配??蛻舾嬷m配器所需的硬件設(shè)備,適配器執(zhí)行客戶的請(qǐng)求。1.2.2.2 客戶硬件接口(HardwareInterfaceToClient)客戶的硬件接口表示客戶期望硬件代理提供的一組服務(wù)和參數(shù)列表。僅僅作為接口,并沒有實(shí)現(xiàn),是通過適配器提供硬件實(shí)現(xiàn)。1.2.2.3 硬件設(shè)備(HardwareDevice)與硬件代理模式中描述一致。1.2.2.4 硬件代理(HardwareProxy)與硬件代理模式中描述一致。1.2.3 效果
該模式允許使用各種硬件代理,并且在不同的應(yīng)用中使用與它們相關(guān)的硬件設(shè)備,同時(shí)亦有的應(yīng)用使用不同的硬件設(shè)備時(shí)不需要做改變。我個(gè)人理解有點(diǎn)類似是面向?qū)ο笳Z(yǔ)言中的多態(tài)概念。1.2.4 實(shí)現(xiàn)
同樣如linux系統(tǒng)驅(qū)動(dòng),創(chuàng)建一個(gè)結(jié)構(gòu)體的接口代理,硬件設(shè)備使用這些接口具體實(shí)現(xiàn),然后使用一個(gè)指向結(jié)構(gòu)體接口的指針,把需要使用的硬件設(shè)備注冊(cè)到指針上,客戶代碼只需調(diào)用這個(gè)指針,即可操作具體的硬件設(shè)備,而且可以動(dòng)態(tài)的修改指針的指向,從而實(shí)現(xiàn)動(dòng)態(tài)的加載切換。?1.3 中介者模式
中介者模式提供的是為一組硬件設(shè)備復(fù)雜交互協(xié)調(diào)的一個(gè)方法。1.3.1 模式結(jié)構(gòu)
?中介者模式使用一個(gè)中介類來(lái)協(xié)調(diào)各個(gè)設(shè)備集合的行為,來(lái)達(dá)到整理的一個(gè)效果。這里舉一個(gè)具體的例子,比如一臺(tái)車有4個(gè)輪子,也就4個(gè)電機(jī)設(shè)備,當(dāng)向前行駛的時(shí)候四個(gè)輪子都是向前前進(jìn),這時(shí)候中介者就承擔(dān)了控制4個(gè)輪子的責(zé)任。所以說中介者其實(shí)就是一個(gè)中央控制。1.3.2 角色
1.3.2.1 合作者接口(CollaboaratorInterface)是被中介者調(diào)用的接口,對(duì)于硬件通常是initaialize(),enable(),reset()等這類函數(shù),但是具體的是在具體合作者實(shí)現(xiàn)。1.3.2.2 中介者(Mediator)在模式中協(xié)調(diào)所有的具體合作者。中介者對(duì)于每個(gè)具體合作者都有一個(gè)鏈接,以便他能給具體合作者發(fā)送信息。此外,當(dāng)有事情發(fā)生時(shí),每個(gè)具體合作者必須能給中介者發(fā)送消息。中介者提供協(xié)調(diào)的邏輯。1.3.2.3 具體合作者(SpecificCollaborator)表示一個(gè)硬件設(shè)備。可以從中介者獲取命令,也可以發(fā)送信息給中介者。1.3.3 效果
該模式創(chuàng)建中介者來(lái)協(xié)調(diào)合作具體硬件,但是對(duì)客戶來(lái)說又不需要直接耦合硬件設(shè)備,極大的簡(jiǎn)化了整理的設(shè)計(jì)。很多嵌入式系統(tǒng)必須高精度時(shí)間相應(yīng),動(dòng)作的延時(shí)可能造成不可估計(jì)的影響,中介者能夠在這些規(guī)定時(shí)間反應(yīng)很重要。1.3.4 實(shí)現(xiàn)
中介者的實(shí)現(xiàn)可以通過指針數(shù)組,鏈表等,能夠連接到每個(gè)具體的合作者。另外統(tǒng)一接口能夠給中介者代碼上帶來(lái)很多便利。?1.4 觀察者模式
觀察者模式非常的普遍,你可以在任何地方看到它的身影。這模式提供一個(gè)方法來(lái)“監(jiān)聽”所感興趣的消息,而不需要修改數(shù)據(jù)服務(wù)器,這意味著傳感器數(shù)據(jù)很容易分享給所需的客戶。1.4.1 模式結(jié)構(gòu)
?觀察者模式,另外一個(gè)名字是“發(fā)布-訂閱模式”。首先模式下數(shù)據(jù)服務(wù)器不需要清楚客戶,相反是由客戶通知數(shù)據(jù)服務(wù)器,也就是訂閱。訂閱意思是允許數(shù)據(jù)服務(wù)器在通知列表中添加(和刪除)自身。最常見的通知策略是當(dāng)新數(shù)據(jù)到達(dá)服務(wù)器時(shí),服務(wù)器發(fā)送數(shù)據(jù)。但是客戶也能定期更新,向服務(wù)器獲取數(shù)據(jù),以減小服務(wù)器的計(jì)算負(fù)擔(dān),確??蛻艟哂袑?shí)時(shí)數(shù)據(jù)。另外更復(fù)雜的模式是在數(shù)據(jù)服務(wù)器和客戶中間添加一層中央控制器,用于連接服務(wù)器與客戶的通訊,這樣服務(wù)器就完全不需要與客戶直接聯(lián)系。如果有大量使用消息使用觀察者模式,添加中央不失為一種好方法。
1.4.2 角色
1.4.2.1 抽象客戶接口(AbstratClient)它包含了accept(Datum)函數(shù),當(dāng)AbstratClient訂閱時(shí)或者AbstratSubject認(rèn)為有適合發(fā)送數(shù)據(jù)去調(diào)用它。AbstratClient是抽象的,不提供任何具體實(shí)現(xiàn)。1.4.2.2 抽象發(fā)布接口(AbstratSubject)在模式中AbstratSubject是數(shù)據(jù)服務(wù)器。在提供模式相關(guān)的3個(gè)函數(shù)。subscribe(acceptPtr)服務(wù)添加指向接收函數(shù)通知列表的指針。unsubscribe(acceptPtr)函數(shù)從通知列表中刪除接收功能。最后,notify()函數(shù)遍歷通知列表通知訂閱的客戶。1.4.2.3 具體客戶(concreteClient)concreteClient是AbstratClient接口的具體實(shí)現(xiàn)。1.4.2.4 具體發(fā)布(concreteSubject)concreteSubject是AbstratSubject接口的具體實(shí)現(xiàn)。不僅提供函數(shù)的實(shí)現(xiàn),而且提供獲取和管理它發(fā)布數(shù)據(jù)的方法。扮演concreteSubject也可以是硬件設(shè)備,傳感器等。1.4.2.5 數(shù)據(jù)(Datum)該元素是實(shí)際的數(shù)據(jù)包,可以是int,更多的是復(fù)雜的結(jié)構(gòu)體。1.4.2.6 回調(diào)接口(NotificationHandle)NotificationHandle是調(diào)用客戶的accept方法的代表。最常見的實(shí)現(xiàn)方式是函數(shù)指針。1.4.3 效果
觀察者模式是在服務(wù)器分配數(shù)據(jù)的過程,并且在運(yùn)行時(shí)可以動(dòng)態(tài)地管理客戶列表。實(shí)際一個(gè)例子,讀取硬件的值,通常我們可能是使用輪詢的方式讀取,輪詢的弊端是響應(yīng)不及時(shí),讀取間隔時(shí)間很難去固定和評(píng)估。另一種方法是定時(shí)中斷讀取,但是定時(shí)讀取未必每次都會(huì)有數(shù)據(jù)產(chǎn)生。還有是觸發(fā)中斷的方法,如果在中斷讀取數(shù)據(jù)后,需要計(jì)算,在中斷里進(jìn)行可能不太好,原則是盡量不要中斷占用太多的CPU。這個(gè)時(shí)候觀察者模式的好處體現(xiàn)出來(lái)了,首先能夠保證響應(yīng)及時(shí),因?yàn)槭褂玫幕卣{(diào)方式,第二能一個(gè)硬件發(fā)布,多個(gè)接收客戶,一對(duì)多的模式,第三能夠確保每次執(zhí)行客戶回調(diào)都能有數(shù)據(jù)產(chǎn)生。其實(shí)觀察者模式隨處可見,ROS系統(tǒng)的節(jié)點(diǎn)通訊就是基于這個(gè)策略。該模式明顯的缺點(diǎn)是實(shí)現(xiàn)較復(fù)雜,而且當(dāng)然也不是所有情況都適應(yīng),希望各位能夠詳細(xì)分析后,選擇合適的方法。1.4.4 實(shí)現(xiàn)
該模式復(fù)雜的方面在通知句柄的實(shí)現(xiàn),以及通知句柄列表的管理。通知句柄通常是一個(gè)回調(diào)函數(shù)指針。通知列表最簡(jiǎn)單的方式是定義足夠大的數(shù)組來(lái)包含所有潛在用戶,但是實(shí)際占用空間大浪費(fèi)內(nèi)存,所以并不常用。另一個(gè)常見的是使用鏈表管理,也就是給每個(gè)通知句柄添加在鏈表上,這樣只要遍歷鏈表即可通知所有客戶,強(qiáng)烈推薦使用鏈表形式。?1.5 去抖動(dòng)模式
這個(gè)模式用于消除來(lái)自硬件金屬表面間歇性連接引起的多個(gè)假時(shí)間。1.5.1 模式結(jié)構(gòu)
?解決的方案是接受第一次發(fā)生的事件,等待抖動(dòng)減弱,然后再對(duì)讀取它的狀態(tài)。
1.5.2 角色
1.5.2.1 應(yīng)用客戶(ApplicationClient)該元素是去抖動(dòng)最后的接受者。當(dāng)在抖動(dòng)消除后,使用deviceEventReceive()接收最后讀取到的值。1.5.2.2 具體硬件(BouncingDevice)代表了硬件設(shè)備。這個(gè)設(shè)備絕大部分都是全硬件,機(jī)械特性的,所以才會(huì)引起抖動(dòng)的現(xiàn)象。sendEvent()用于發(fā)送事件,激活中斷接收到首次的響應(yīng)。getState()操作時(shí)通過讀取內(nèi)存或IO端口顯示,讀取具體的硬件值。deviceState通常是二值屬性,即ON或OFF。1.5.2.3 硬件客戶(DeviceClient)是用于處理進(jìn)入事件的中斷,去除抖動(dòng),并讀取確保代表實(shí)際設(shè)備狀態(tài)。它的eventRecevie()函數(shù)通過BouncingDevice的sendEvent()函數(shù)激活。同時(shí),它需要設(shè)置延時(shí)定時(shí)器,去抖動(dòng)事件過后,如果狀態(tài)與第一次讀取的一致,證明值是真實(shí)的。這樣它就發(fā)送相應(yīng)的信息給ApplicationClient。舊狀態(tài)保存在變量oldState中,每當(dāng)狀態(tài)發(fā)生改變的時(shí)候更新這個(gè)變量。1.5.2.4 定時(shí)器(DebouncingTimer)這個(gè)定時(shí)器可以通過delay()服務(wù)來(lái)提供空閑等待。可以使用while()等待,或者硬件定時(shí)器實(shí)現(xiàn)。1.5.3 效果
通常去抖動(dòng)的任務(wù)是由軟件來(lái)承擔(dān),這是一個(gè)簡(jiǎn)單的去抖動(dòng),應(yīng)用程序只需要關(guān)心硬件狀態(tài)產(chǎn)生的真實(shí)值才接收。1.5.4 實(shí)現(xiàn)
硬件客戶通常使用中斷來(lái)通知應(yīng)用客戶?;蛘呤褂糜^察者模式混合也可以給等個(gè)客戶提供信號(hào)。在RTOS系統(tǒng)去抖動(dòng)必須注意時(shí)間單位的延時(shí)時(shí)間,比如如果想要45毫秒的延時(shí),那么必須使用大于等于期望時(shí)間最接近時(shí)間精度。如果在等待去抖動(dòng)時(shí),你不介意完全占用CPU,那么這就很簡(jiǎn)單,使用while(loop--)循環(huán)就好了。?1.6 中斷模式
在嵌入式系統(tǒng),硬件設(shè)備很多時(shí)候都是自主發(fā)生,如果你不加以注意,這些事件就會(huì)丟失。當(dāng)一個(gè)你感興趣的事件發(fā)生時(shí),使用中斷來(lái)通知是非常有效的方法?;旧闲酒贾С滞獠坑布袛嗟姆绞健V袛嗄鼙WC響應(yīng)的及時(shí),但是中斷會(huì)搶占CPU的控制,所以中斷里面不適合處理算法等這種耗時(shí)長(zhǎng)的任務(wù)。這個(gè)模式下可以是純軟件的中斷模式。1.6.1?模式結(jié)構(gòu)
?確保中斷函數(shù)一般是沒有入?yún)?,和返回值的?/p>
1.6.2 角色
1.6.2.1 中斷響應(yīng)(InterruptHandler)是中斷模式里面唯一有具體行為的元素。它能夠安裝和卸載中斷向量的功能。install()函數(shù)運(yùn)行時(shí),拷貝傳入的中斷句柄到向量表中,使用合適的中斷服務(wù)程序地址。deinstall()函數(shù)相反,用于卸載回復(fù)原本的向量表。每個(gè)handleInterrupt_x()函數(shù)處理指定的中斷。1.6.2.2 中斷向量表(InterruptVectorTable)就是中斷服務(wù)程序的地址數(shù)組。它依賴在指定的內(nèi)存位置上。當(dāng)中斷號(hào)x出現(xiàn)時(shí),CPU掛起當(dāng)前進(jìn)程,調(diào)用這個(gè)數(shù)組中相應(yīng)的第x個(gè)索引地址。1.6.2.3 向量指針(VectorPtr)VectorPtr是數(shù)據(jù)類型,具體是一個(gè)沒有參數(shù)和返回值的函數(shù)指針。1.6.3 效果
該模式最大的優(yōu)勢(shì)是可以高響應(yīng)處理感興趣的事件。通常情況下,當(dāng)中斷服務(wù)程序執(zhí)行時(shí),關(guān)閉中斷,這意味著中斷服務(wù)程序必須快速執(zhí)行以確保不會(huì)丟失其他中斷。使用中斷有點(diǎn)特別注意是資源的保護(hù)。當(dāng)有可能會(huì)在中斷和普通程序中處理了同一個(gè)元素,設(shè)想當(dāng)普通程序讀取數(shù)據(jù)中途發(fā)生了中斷,而中斷會(huì)導(dǎo)致普通程序暫停,然后在中斷里面修改了數(shù)據(jù)返回。普通函數(shù)將會(huì)讀取損壞的數(shù)據(jù),即部分是新數(shù)據(jù),部分是舊數(shù)據(jù)。解決方法有1.在普通函數(shù)讀取數(shù)據(jù)時(shí)禁止中斷,訪問完成后恢復(fù)中斷。2.使用互斥信號(hào)量。1.6.4 實(shí)現(xiàn)
中斷函數(shù)執(zhí)行之前必須保存現(xiàn)場(chǎng),在執(zhí)行完成需要恢復(fù)現(xiàn)場(chǎng)。事實(shí)上每個(gè)中斷服務(wù)程序必須:- 保存CPU寄存器,包括CPU指令指針和任何處理器標(biāo)志,如進(jìn)位,奇偶校驗(yàn)。
- 清除終端位。
- 執(zhí)行適當(dāng)處理。
- 恢復(fù)CPU寄存器。
- 返回。
1.7 輪詢模式
另一種從硬件獲取數(shù)據(jù)常用的模式是定期檢查,稱為輪詢過程。當(dāng)數(shù)據(jù)或信號(hào)不是很緊急,或者當(dāng)數(shù)據(jù)可用時(shí),硬件沒有能力產(chǎn)生中斷,又或者硬件本身能保留數(shù)據(jù)到下次讀取情況,這是輪詢就非常好用。1.7.1?模式結(jié)構(gòu)
?輪詢模式是讀取硬件上數(shù)據(jù)最簡(jiǎn)單的方法。輪詢能夠定期或不定期進(jìn)行,可以是定時(shí)器讀取,也可以當(dāng)系統(tǒng)需要時(shí)讀取。