今天給大家分享一款輕量級的定時器調度器——SmartTimer,在單片機”裸跑”的情況下,可以很方便的實現異步編程。
雖然此項目是基于STM32進行開發的,但它可以很方便的移植到其他的單片機上。
項目的 git 地址為(復制在瀏覽器打開):https://github.com/lmooml/SmartTimer
1、基本介紹
SmartTimer可以應用在對實時性要求沒那么高的場合,比如說一個空氣檢測裝置,每 200ms 收集一次甲醛數據,這個任務顯然對實時性要求沒那么高,如果時間上相差幾毫秒,甚至幾十毫秒也沒關系,那么使用SmartTimer非常適合。
而如果開發一個四軸飛行器,無論是對陀螺儀數據的采集、計算,以及對 4 個電機的控制,在時間的控制上都需要非常精確。那么這種場合下 SmartTimer無法勝任,你需要一個帶有搶占優先級機制的實時系統。
不同的場景,選擇不同的工具和架構才是最合理的,SmartTimer只能做它力所能及的事情。
2、一般用法
Runlater: 在單片機編程中,想實現在“xxx毫秒后調用xxx函數”的功能,一般有3種方法:
用阻塞的,非精確的方式,就是用for(i=0;i<0xffff;i++);這種循環等待的方式,來非精確的延遲一段時間,然后再順序執行下面的程序;
利用硬件定時器實現異步的精確延時,把 XXX 函數在定時器中斷里執行;
同樣是利用硬件定時器,但是只在定時器中斷里設置標志位,在系統的主 While 循環中檢測這個標志位,當檢測到標志置位后,去運行 XXX 函數。
從理論上來說,以上 3 種方式中,第 3 種采用定時器設定標志位的方法最好。因為首先主程序不用阻塞,在等待的時間里,MCU 完全可以去做其他的事情,其次 在定時器中斷里不用占用太多的時間,節約中斷資源。 但這種方式有個缺點,就是實現起來相對麻煩一些。因為,如果你要有 N 個runlater的需求,那么就得設置N個標志位,還要考慮定時器的分配、設定。在程序主While循環里也會遍布N個查詢標志位的if語句。如果N足夠多,其實大于5個,就會比較頭疼。這樣會使主While循環看起來很亂。這樣的實現不夠簡潔、優雅。 SmartTimer首先解決的就是這個問題,它可以優雅地延遲調用某函數。 Runloop: 在定時器編程方面還有另一個典型需求,就是“每隔xxx毫秒運行一次XXX函數,一共運行XXX次”。這個實現起來和 runlater差不多,就是加一個運行次數的技術標志。我就不再贅述了。還是那句話: SmartTimer可以優雅地實現 Runloop 功能。 Delay: 并不是說非阻塞就一定比阻塞好,因為在某些場景下,必須得用到阻塞,使單片機停下來等待某個事件。那么,SmartTimer也可以提供這個功能。
3、高級用法
所謂的高級用法,并不是說 SmartTimer 有隱藏模式,能開啟黑科技。而是說,如果你能轉變思路,舉一反三地話,可以利用 SmartTimer 提供的簡單功能實現更加優化、合理的系統結構。 傳統的單片機裸跑一般采用狀態機模式,就是在主While循環里設定一些標志位或是設定好程序進行的步驟,根據事件的進程來跳轉程序。 簡單的說來,這是一種順序執行的程序結構。其靈活性和實時性并不高,尤其是當需要處理的業務越來越多,越來越復雜時,狀態機會臃腫不堪,一不留神(其實是一定以及肯定)就會深埋bug于其中,調試解決BUG時也會異常痛苦。 如果轉換一下思路,不再把業務邏輯中各個模塊的關系看成基于因果(順序),而是基于時間,模塊間如果需要確定次序可以采用標志位進行同步。 那么恭喜你,你已經有了采用實時系統的思想,可以嘗試使用RT-thread等操作系統來完成你的項目了。 但使用操作系統有幾個問題:
第一是當單片機資源有限的時候,使用操作系統恐怕不太合適;
第二是學習操作系統本身有一定的難度,至少你需要花費一定的時間;
第三如果你的項目復雜度沒有那么高,使用操作系統有點大材小用。
其實,利用SmartTimer中的Runloop功能,可以簡單的實現基于時間的主程序框架。
4、Demo
與源碼一起提供的,還有一個Demo程序。這個Demo比較簡單,主要是為了測試SmartTimer的功能。Demo程序基本可以體現Runlater、Runloop、Delay 功能。 同時,也能基本體現基于時間的編程思想(單片機裸跑程序框架)。
5、使用
SmartTimer.h中聲明的公開函數并不多,總共有8個:
void?stim_init?(?void?); void?stim_tick?(void); void?stim_mainloop?(?void?); int8_t?stim_loop?(?uint16_t?delayms,?void?(*callback)(void),?uint16_t?times); int8_t?stim_runlater?(?uint16_t?delayms,?void?(*callback)(void)); void?stim_delay?(?uint16_t?delayms); void?stim_kill_event(int8_t?id); void?stim_remove_event(int8_t?id);
下面將逐一介紹。 前提: SmartTimer 能夠工作的必要條件是:
A.?設置 Systick 的定時中斷(也可以是其他的硬件定時器TIMx,我選擇的是比較簡單的Systick),默認設置為1ms中斷一次,使用者可以根據自己的情況來更改。Systick時鐘的設置在 stim_init 函數中,該函數必須在主程序初始化階段調用一次;
B.?在定時器中斷函數中調用stim_tick();可以說,這個函數是SmartTimer的引擎,如A步驟所述,默認情況下,每1ms,定時器中斷會調用一次stim_tick();
C.?在主While循環中執行stim_mainloop(),這個函數主要有兩個作用,一是執行定時結束后的回調函數;二是回收使用完畢的timer事件的資源。
使用SmartTimer: 做好以上的搭建工作后,就可以開始使用SmartTimer了。 函數?stim_runlater
int8_t stim_runlater ( uint16_t delayms, void (*callback)(void));
該函數接受兩個參數,返回定時事件的id。 參數delayms傳入延遲多長時間,注意這里的單位是根據之前A步驟里,你設置的時間滴答來確定的(默認單位是1ms);第二個參數是回調函數的函數指針,目前只支持沒有參數,且無返回值的回調函數,未來會考慮加入帶參數和返回值的回調。 舉例:
timer_runlater(100,ledflash);?//100豪秒(100*1ms=100ms)后,執行void ledflash(void)函數
如果在stim_init()中,設置的時鐘滴答為10ms執行一次,那么傳入同樣的參數,意義就會改變:timer_runlater(100,ledflash);?//1秒(100*10ms=1000ms=1S)后,執行void ledflash(void)函數
函數?stim_loop
int8_t stim_loop?(?uint16_t delayms,?void?(*callback)(void),?uint16_t?times);
這個函數的參數意義同runlater差不多,就不詳細說明了。 該函數接收3個參數,delayms為延遲時間,callback為回調函數指針,times是循環次數。舉例(以1ms滴答為例):
timer_runloop(50,ledflash,5);?//?每50ms,執行一次ledflash(),總共執行5次 timer_runloop(80,ledflash, TIMER_LOOP_FOREVER);?//?每80ms,執行一次ledflash(),無限循環。
函數?timer_delay
void timer_delay?(?uint16_t delayms);???//延遲xx ms
這個函數會阻塞主程序,并延遲一段時間。
void stim_kill_event(int8_t id); void stim_remove_event(int8_t id);
這兩個函數,可以將之前設定的定時事件取消。比如之前用stim_loop無限循環了一個事件,當獲取某個指令后,需要取消這個任務,則可以用這兩個函數取消事件調度。 這兩個函數的區別是:
void stim_kill_event(int8_t id);?//直接取消事件,忽略未處理完成的調度任務。 void stim_remove_event(int8_t id);//將已經完成計時的調度任務處理完畢之后,再取消事件
6、注意
SmartTimer可接受的Timer event 數量是有上限的,這個上限由smarttimer.h中的宏定義來決定的:
#define????TIMEREVENT_MAX_SIZE????20
默認為20個,你可以根據實際情況增加或減少,但不可多于128 個。
編輯:黃飛
?
評論
查看更多