特點
(1)在經歷特定的時間段後,執行特定操作; (2)根據給定周期執行特定操作。 傳統的作法是利用前後台方式:設定硬體定時器,使其在後台以特定周期對各相關操作的標誌變數作計數操作;前台則不斷對各標誌變數巡迴查詢,若發現標誌變數達到預定值,則執行特定操作。可見,上述需求需直接操作硬體定時器實現,其過程繁瑣,且需要用戶對相關硬體有深入了解。因此,設計、實現了一種使用方便的低端系統時鐘管理器。 時鐘管理器在實現中,將與硬體密切相關的部分組成一獨立模組(檔案)。針對不同的目標系統處理器,更換該模組即可。為使表述不過抽象,以8051系列單片機為目標系統處理器、c51為工具語言闡述該嵌入式時鐘管理器的設計與實現。
結構
該時鐘管理器模組(檔案)結構如圖1所示。
(1)configclk.h定義了有關係統裁剪、配置的可調參數,通過對configclk.h中相關宏參數的配置,即可實現對該時鐘管理器系統的配置和裁剪。 (2)clk_impl.*功能模組用來封裝目標系統的一個硬體定時器,以禁止不同處理器間的硬體差異,起到hal(hardwareabstractlayer)作用。系統時鐘在此構建。 (3)clk.*模組在clk_impl.*提供的hal基礎上進一步封裝,通過一個鉤子(hook)函式,為系統提供時鐘脈衝,且脈衝寬度可調(配置configclk.h中的相關宏參即可)。 (4)wdlib.*模組為用戶套用提供多個軟體定時器。
實現
硬體定時器的底層封裝 硬體定時器底層封裝在圖1所示的clk_impl.*中實現。其中定義了一個初始化接口函式和一個定時器中斷的isr(interrupt service routine)。令選用的硬體時鐘為定時器0(可在configclk.h中配置)。 (1)初始化接口函式void_clkinit(void){ } 用戶通過調用該接口函式,可周期性地執行相應的isr—clktick_isr,從而形成邏輯上的系統時鐘。另外,本接口函式不為用戶直接訪問,而在上層模組clk.*中被調用。 (2)定時器0的isr—clktick_isr void clktick_isr (void) interrupt 1 using reg_grp_for_ sys_clk{ } 其中:reg_grp_for_sys_clk為定義於configclk.h中的可調參數,用來設定本isr的工作暫存器組。 2.2 時鐘脈衝的提供 時鐘脈衝在圖1所示的clk.*中實現。 本文提供三個用戶接口函式和一個用戶可修改、但不可調用的鉤子函式(clktick_isr_hook僅能在clktick_isr中被調用)。其用戶接口聲明如下: extern void constructclk(void); extern void destructclk(void); extern uint8 getclkrate(void); 其中:constructclk用以構建系統時鐘,要使用本文所述的時鐘管理器,需首先通過調用_clkinit(定義於clk_impl.*模組)實現對本函式的調用;destructclk用以解析業已構建的系統時鐘;getclkrate用以獲取系統當前的時鐘節拍率(即定義於configclk.h中的宏sys_clk_rate的當前值)。 clktick_isr_hook由系統聲明,用戶可修改其定義,其最終僅為系統作周期性調用。用戶可將自己需進行的周期性操作放於其中,後面敘述的軟體定時器的“守護”例程(wddaemon)正是置於此處而被周期調用。由於置於其中的操作將在中斷執行,所以這些操作應儘可能簡短、省時。 2.3 軟體定時器的提供 本功能在圖1所示的wdlib.*中實現。 其為用戶提供了可快速、便捷地實現用戶定時需求的接口函式和一個被周期性調用的定時器守護例程wddaemon。 extern void constructwdog(void);//為使用定時器系統作初始化操作 extern void destructwdog(void)//置定時器系統為初始態 extern wdog_id wdcreate(void);//建立一個定時器,並返回其id extern status wdcancel(wdog_id wdid);//終止指定定時器並復位 extern status wddelete(wdog_id wdid);//刪除指定定時器 extern status wdstart(wdog_id wdid,uint16 ticks,voidfuncptr wdr);//啟動指定定時器,它會在指定時間後觸發給定操作 其中:wdog_id為定時器id類型,即uint8。傳送給wdstart的參數“uint16 ticks”指明定時時間長度,單位為系統時鐘節拍,1節拍=1/sys_clk_rate(s)。因該參數的類型定為uint16,故定時器的最大定時長度為216×(1/sys_clk_rate),即216/sys_clk_rate(s)。 定時器的實現方案有靜態數組法和delta列表法兩種方法。這兩種方法各有優缺點:前者邏輯簡單,rom用量小,但效率較低(與定時器數目相關);後者邏輯複雜,rom用量大,但效率較高(與定時器數目無關)。套用中使用哪種方案,可在configclk.h中配置選擇。 2.3.1 靜態數組法 靜態數組法的數據結構如下: struct wdnode { bool flag;//標明本結點是否已被使用 uint16 ticks;//用以定時的節拍數 voidfuncptr rout;//定時到時需執行的操作 } data wdlistmax_wdog_num_]; 其中:_max_wdog_num_指出了系統中允許的最大定時器數,其值決定於套用需求及系統資源量,可在configclk.h中設定。一個定時器結點占用5b的ram空間。具有給定數據結構的靜態數組是方案實施的基礎。 另外,該靜態數組作為軟體定時器的全局變數而存在,當系統中有多個定時器活動時,它們都將訪問該全局靜態數組。重要的是:它們的活動是異步的,所以,對該靜態數組(臨界資源)的訪問需作臨界保護。對於51系統,應採用開關中斷的方式實現,且應確保不會影響關中斷前的中斷狀態。 (1)用戶接口定義 上述用戶接口皆基於該靜態數組進行,限於篇幅,這裡給出關鍵接口wdstart的定義。 status wdstart(wdog_id wdid,uint16 ticks, voidfuncptr wdr) { if(wdid<_max_wdog_num_) { if(wdlistdid].flag) {//判斷給定定時器id有效否 rtx_enter_critical();//進入臨界區 wdlistdid].ticks=ticks;//操作靜態數組中的特定定時結點 wdlistdid].rout=wdr; rtx_exit_critical();//退出臨界區 return ok;//定時器啟動成功 } } return error;//給定定時器id無效 } 調用該接口函式,即可啟動已創建(wdcreate)的軟體定時器。當經歷ticks節拍後,給定函式wdr將被執行,以完成用戶的定時需求。 (2)定時器守護例程 定時器守護例程wddaemon被置於前述的鉤子函式clktick_isr_hook中,以使其周期性執行。由於本例程自身的特點,它應作為clktick_isr_hook的最後一個調用函式。本例程是軟體定時器實現的核心,而其關鍵又是對系統棧的調整,為說明其實現流程,給出了如圖2所示的wddaemon的棧(stack)結構。 由圖2可知:wddaemon的返回地址沒有入棧,因其為clktick_isr_hook中的最後一個函式調用,故其返回地址被最佳化掉。wddaemon將棧頂的8b數據上移2b,然後將定時器指定函式的地址插入騰出的棧空間(2b)中。如此,該地址將會被iret彈入ip中。由於iret指令的執行而使中斷系統復位以重新回響外部中斷,同時也使定時器指定函式在非中斷態執行,從而不過分影響系統的回響速度。 2.3.2 delta列表法 delta列表法僅維護有效定時器的鍊表,且鍊表中的定時器結點按定時剩餘時間由小到大排列,使距timeout點最近的定時器作為鍊表的首結點。鍊表中定時器結點的順序由其獨特的結點插入算法決定:如有5個定時器,其定時長度分別為10、14、21、32和39,當其組成delta列表時,定時值最小的結點為首結點,其定時存儲值為10,而後依序排列,其定時存儲值分別為4、7、11、7,即後一個定時器的定時存儲值由自己的實際定時值與相鄰的前一個定時器的實際定時值相減而得。可見,除首結點外的所有定時器的計數操作在其插入delta列表時就已完成。因而當定時器守護例程確定timeout的定時器時,只需對首結點進行減1或刪除的操作,而不需遍歷整個列表,從而使delta列表的操作與定時器數量無關。這使delta列表法在大量定時器管理中大顯其能。 該法在系統中實現的數據結構為一靜態雙向鍊表: struct wdnode { bool flag; uint16 ticks; voidfuncptr rout; uint8 prior; uint8 next; } idata wdlistmax_wdog_num_]; uint8 headidx; //索引首結點 有了delta列表法的思路及其實現的數據結構,在靜態數組法具體實現的基礎上,便可得此法的具體實現。 套用中如果目標系統rom較小,且系統中啟用的定時器少,則用靜態數組法;若目標系統rom較大,且系統中用到的定時器較多,則用delta列表法。
套用
針對前述的嵌入式系統中的定時需求,利用定時器管理系統給出其實現代碼。 假定“特定操作”為void specfunc(void),“特定時間段”長度為10分鐘。 (1)在經歷特定的時間段後,執行特定操作。 #include ″clk.h″ #include ″wdlib.h″ void main(void ) { wdog_id wdid; constructclk();constructwdog(); wdid=wdcreate(); wdstart(wdid,10*one_minute,specfunc); while(1); } (2)以給定周期周期性地執行特定操作。 基於前者,只需在void specfunc(void)函式體的最後加入下述代碼即可: wdstart(wdid,10*one_minute,specfunc); 註:該給定周期為10分鐘。 由於本時鐘管理器只需一個硬體定時器的支持,所以其具有廣泛的適用性。使用時,只需進行簡單的配置,即可為裸露的目標系統加以簡單的軟體抽象層。其友好的用戶接口有效降低了嵌入式系統的開發難度,提高了目標系統的可靠性。筆者已在實際項目中多次使用了該時鐘管理器。基於該時鐘管理器的目標系統運行穩定、可靠,從而充分說明該時鐘管理器設計的實用性和科學性。