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