一、前言
rt-thread采用軟件定時器線程模式或硬件定時器中斷模式來實現系統定時器管理。而rt-thread操作系統在默認情況下是采用的硬件定時器中斷模式的方式,用戶可以通過宏定義RT_USING_TIMER_SOFT來修改定時器管理模式。
硬件定時器中斷模式是利用MCU芯片本身提供的硬件定時器功能,一般是由外部晶振提供給芯片輸入時鍾,芯片向軟件模塊提供一組配置寄存器,接受控制輸入,到達設定時間值后芯片中斷控制器產生時鍾中斷(比如stm32的嘀嗒定時器中斷),在硬件定時器中斷服務中檢查rt-thread系統定時器是否超時。硬件定時器中斷模式的精度一般很高,可以達到納秒級別,並且是中斷觸發方式(如滴答定時器中斷,其他MCU硬件定時器中斷)。
軟件定時器線程模式是指由操作系統提供的一類系統接口,它構建在MCU硬件定時器基礎之上,使系統能夠提供不受數目限制的定時器服務。在該模式中定時器的超時檢查在線程入口函數中進行,但定時器的精度仍取決於MCU硬件定時器精度,因為在此模式下系統當前時鍾計數rt_tick仍然在MCU硬件定時器中斷服務(如stm32的嘀嗒定時器中斷)中遞增,定時器超時檢查時需要比較rt_tick與timeout_tick。
二、定時器基本工作原理
無論是軟件定時器線程模式,還是硬件定時器中斷模式, 在rt-thread定時器模塊中都維護兩個變量:1、當前系統的時間點rt_tick(當MCU硬件定時器中斷時加1);2、定時器鏈表rt_soft_timer_list(軟件定時器線程模式)以及rt_timer_list(硬件定時器中斷模式)。在兩種模式下,定時器超時檢查函數中一旦檢查到定時器超時,則先將該定時器從鏈表中移除,然后執行超時函數后。定時器在創建或初始化時默認為單次定時,若此時定時器內核對象標志設為RT_TIMER_FLAG_PERIODIC,則執行超時函數后會重新啟動該定時器即將該定時器重新加入定時器鏈表中。
在硬件定時器中斷模式下不存在定時器線程,系統中新創建的定時器都會被按照超時時間點timeout_tick從小到大排序的方式插入到rt_timer_list鏈表中,rt_timer_list的每個節點保留了一個定時器的信息,並且在這個節點加入定時器鏈表之前就計算好了定時器的超時時間點,即timeout_tick。在MCU硬件定時器中斷服務中,除了rt_tick加1以外,還通過定時器超時檢查函數rt_timer_check檢查定時器鏈表rt_timer_list中的定時器是否超時,即rt_tick是否趕上timeout_tick,若定時器超時,則調用定時器超時函數。
在軟件定時器線程模式下則存在定時器線程,系統中新創建的定時器都會被按照超時時間點timeout_tick從小到大排序的方式插入到rt_soft_timer_list鏈表中,rt_soft_timer_list的每個節點保留了一個定時器的信息,並且在這個節點加入定時器鏈表之前就計算好了定時器的超時時間點,即timeout_tick。在線程入口函數rt_thread_timer_entry中通過不斷獲取當前rt_tick值,將其與定時器超時時間點timeout_tick對比從而判斷定時器是否超時,並進行定時器超時檢查函數,一旦發現定時器超時就調用定時器超時函數rt_soft_timer_check,即定時器超時處理函數。
三、定時器管理控制塊:在include/rtdef.h中定義
/** * clock & timer macros */ #define RT_TIMER_FLAG_DEACTIVATED 0x0 /**< 非激活,默認 */ #define RT_TIMER_FLAG_ACTIVATED 0x1 /**< 激活 */ #define RT_TIMER_FLAG_ONE_SHOT 0x0 /**< 單次定時,默認 */ #define RT_TIMER_FLAG_PERIODIC 0x2 /**< 周期性定時*/ #define RT_TIMER_FLAG_HARD_TIMER 0x0 /**< 硬件定時器中斷模式,定時器超時檢查及超時函數調用在MCU硬件定時器中斷服務中執行,默認 */ #define RT_TIMER_FLAG_SOFT_TIMER 0x4 /**< 軟件定時器線程模式,定時器超時檢查及超時函數調用在定時器線程入口函數中執行 */ #define RT_TIMER_CTRL_SET_TIME 0x0 /**< set timer control command */ #define RT_TIMER_CTRL_GET_TIME 0x1 /**< get timer control command */ #define RT_TIMER_CTRL_SET_ONESHOT 0x2 /**< change timer to one shot */ #define RT_TIMER_CTRL_SET_PERIODIC 0x3 /**< change timer to periodic */ #ifndef RT_TIMER_SKIP_LIST_LEVEL #define RT_TIMER_SKIP_LIST_LEVEL 1 #endif /* 1 or 3 */ #ifndef RT_TIMER_SKIP_LIST_MASK #define RT_TIMER_SKIP_LIST_MASK 0x3 #endif /** * timer structure */ struct rt_timer { struct rt_object parent; //內核對象 rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL];//鏈表節點 void (*timeout_func)(void *parameter); //定時器超時函數 void *parameter; //定時器超時函數參數 rt_tick_t init_tick; //定時器定時時間間隔,即每隔多長時間超時 rt_tick_t timeout_tick; //定時器超時時間點,即超時那一刻的時間點 }; typedef struct rt_timer *rt_timer_t;
四、軟件定時器線程模式相關函數:在src/timer.c中
軟件定時器線程初始化: void rt_system_timer_thread_init(void); 在該函數中初始化軟件定時器線程模式下定時器鏈表數組,以及初始化軟件定時器線程。
軟件定時器線程入口函數: /* system timer thread entry */ //軟件定時器線程入口函數 static void rt_thread_timer_entry(void *parameter) { rt_tick_t next_timeout; while (1)//軟件定時器優先級設置最高優先級0,且線程入口函數中為死循環,因此若函數中沒有掛起自身線程和執行線程調度,則始終只運行這個線程 { /* get the next timeout tick */ //得到軟件定時器線程模式中定時器鏈表的下一個定時器的超時時間點 next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list); if (next_timeout == RT_TICK_MAX) //定時器鏈表為空,即無定時器。RT_TICK_MAX is defined to be 0xffffffff in rtdef.h { /* no software timer exist, suspend self. */ rt_thread_suspend(rt_thread_self());//若定時器鏈表為空,則掛起當前線程,繼續線程調度 rt_schedule(); } else { rt_tick_t current_tick; /* get current tick */ current_tick = rt_tick_get(); //獲取當前時間點 if ((next_timeout - current_tick) < RT_TICK_MAX/2)//離定時器超時時間點很近了,但是還差一段時間 { /* get the delta timeout tick */ next_timeout = next_timeout - current_tick; //計算還差多長時間 rt_thread_delay(next_timeout); //休眠一段時間,delay函數將自身線程掛起並啟動自身線程定時器后,執行線程調度運行其他就緒線程 } } /* check software timer */ rt_soft_timer_check(); //預計的時間到了,檢查是否該產生定時器超時事件。以前的版本中在這里添加了調度器鎖(先進入臨界區,檢查完后再退出臨界區) } }
定時器超時檢查函數: void rt_soft_timer_check(void);
在該函數中掃描定時器鏈表rt_soft_timer_list中產生超時的定時器,將其移除定時器鏈表並執行定時器超時函數,若為周期性定時器則重新啟動該定時器,即重新將其加入定時器鏈表中。
上面代碼中,為什么定時器超時檢查函數中判斷定時器超時的條件是((current_tick - t→timeout_tick) < RT_TICK_MAX/2)?
因為系統時鍾rt_tick溢出后會自動回繞,取定時器比較最大值是定時器最大值的一半,即RT_TICK_MAX/2(在比較兩個定時器值時,值是32位無符號數,相減運算將會自動回繞)。
由此可見,rt-thread系統支持的定時器最長定時時間為RT_TICK_MAX/2,即248天(10ms/tick),124天(5ms/tick),24.5天(1ms/tick)。
五、硬件定時器中斷模式相關函數:在src/timer.c中
定時器超時檢查函數: void rt_timer_check(void);
該函數在MCU硬件定時器中斷函數中調用,主要功能為掃描定時器鏈表rt_timer_list中產生超時的定時器,將其移除定時器鏈表並執行定時器超時函數,若為周期性定時器則重新啟動該定時器,即重新將其加入定時器鏈表中。
此函數與rt_soft_timer_check基本大致相同,只不過一個是查找硬件定時器中斷模式中定時器鏈表rt_timer_list,一個是查找軟件定時器線程模式中定時器鏈表rt_soft_timer_list.
得到下一定時器超時時間點: rt_tick_t rt_timer_next_timeout_tick(void) { return rt_timer_list_next_timeout(rt_timer_list);//得到硬件定時器中斷模式中定時器鏈表的下一個定時器的超時時間點 }
六、定時器通用函數接口:在src/timer.c中
定時器創建: rt_timer_t rt_timer_create(const char *name,//定時器名稱 void (*timeout)(void *parameter),//定時器超時函數 void *parameter,//定時器超時函數參數 rt_tick_t time,//定時器定時時間間隔 rt_uint8_t flag)//定時器內核對象標志 定時器初始化: void rt_timer_init(rt_timer_t timer,//定時器句柄 const char *name,//定時器名稱 void (*timeout)(void *parameter),//定時器超時函數 void *parameter,//定時器超時函數參數 rt_tick_t time,//定時器定時時間間隔 rt_uint8_t flag)//定時器內核對象標志
#define RT_TIMER_FLAG_DEACTIVATED 0x0 /* 默認為非激活態 */
#define RT_TIMER_FLAG_ACTIVATED 0x1 /* 激活狀態 */
#define RT_TIMER_FLAG_ONE_SHOT 0x0 /* 默認為單次定時 */
#define RT_TIMER_FLAG_PERIODIC 0x2 /* 周期定時 */
#define RT_TIMER_FLAG_HARD_TIMER 0x0 /* 默認為硬件定時器中斷模式 */
#define RT_TIMER_FLAG_SOFT_TIMER 0x4 /* 軟件定時器線程模式 */
定時器刪除:
rt_err_t rt_timer_delete(rt_timer_t timer);
調用這個函數接口后,系統會把這個定時器從rt_timer_list鏈表中刪除,然后釋放相應的定時器控制塊占有的內存
定時器脫離:
rt_err_t rt_timer_detach(rt_timer_t timer);
脫離定時器時,系統會把定時器對象從系統容器的定時器鏈表中刪除,但是定時器對象所占有的內存不會被釋放。
定時器啟動: rt_err_t rt_timer_start(rt_timer_t timer); 當定時器被創建或者初始化以后,並不會被立即啟動,必須在調用啟動定時器函數接口后,才開始工作。 調用定時器啟動函數接口后,定時器的狀態將更改為激活狀態(RT_TIMER_FLAG_ACTIVATED),並按照超時順序插入到rt_timer_list隊列鏈表中。 定時器停止: rt_err_t rt_timer_stop(rt_timer_t timer); 調用定時器停止函數接口后,定時器狀態將更改為停止狀態,並從rt_timer_list鏈表中脫離出來不參與定時器超時檢查。當一個(周期性)定時器超時時,也可以調用這個函數接口停止這個(周期性)定時器本身。
定時器控制: rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void *arg); #define RT_TIMER_CTRL_SET_TIME 0x0 /* 設置定時器定時時間間隔 */ #define RT_TIMER_CTRL_GET_TIME 0x1 /* 獲得定時器定時時間間隔 */ #define RT_TIMER_CTRL_SET_ONESHOT 0x2 /* 設置定時器為單一超時型 */ #define RT_TIMER_CTRL_SET_PERIODIC 0x3 /* 設置定時器為周期型定時器 */