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