1. MultiTimer
今天給大家?guī)?lái)的開(kāi)源項(xiàng)目是 MultiTimer,一款可無(wú)限擴(kuò)展的軟件定時(shí)器,作者0x1abin,目前收獲 95 個(gè) star,遵循 MIT 開(kāi)源許可協(xié)議。
MultiTimer 是一個(gè)軟件定時(shí)器擴(kuò)展模塊,可無(wú)限擴(kuò)展你所需的定時(shí)器任務(wù),取代傳統(tǒng)的標(biāo)志位判斷方式, 更優(yōu)雅更便捷地管理程序的時(shí)間觸發(fā)時(shí)序。
項(xiàng)目地址:https://github.com/0x1abin/MultiTimer
2. 移植MultiTimer
2.1. 移植思路
開(kāi)源項(xiàng)目在移植過(guò)程中主要參考項(xiàng)目的readme文檔,一般只需兩步:
- ① 添加源碼到裸機(jī)工程中;
- ② 實(shí)現(xiàn)需要的接口;
2.2. 準(zhǔn)備裸機(jī)工程
本文中我使用的是小熊派IoT開(kāi)發(fā)套件,主控芯片為STM32L431RCT6:
移植之前需要準(zhǔn)備一份裸機(jī)工程,我使用STM32CubeMX生成,需要初始化以下配置:
- 配置一個(gè)串口用于打印信息
- printf重定向
2.3. 添加MultiTimer到工程中
① 復(fù)制MultiTimer源碼到工程中
② 在keil中添加 MultiTimer的源碼文件
③ 將MultiTimer頭文件路徑添加到keil中
3. 使用MultiTimer
使用時(shí)包含頭文件:
#include "multi_timer.h"
如果遇到multi_timer.c文件中NULL宏定義報(bào)錯(cuò),則在multi_timer.h中添加
頭文件即可。
3.1. 創(chuàng)建Timer對(duì)象
/* USER CODE BEGIN PV */struct Timer timer1;struct Timer timer2;/* USER CODE END PV */
3.2. Timer回調(diào)函數(shù)
/* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */void timer1_callback(){printf("timer1 timeout! ");}void timer2_callback(){printf("timer2 timeout! ");}/* USER CODE END 0 */
3.3. 初始化并啟動(dòng)Timer
始化定時(shí)器對(duì)象,注冊(cè)定時(shí)器回調(diào)處理函數(shù),設(shè)置定時(shí)時(shí)間(ms),循環(huán)定時(shí)觸發(fā)時(shí)間:
/* USER CODE BEGIN 2 */printf("multi timer test... ");//重復(fù)計(jì)時(shí),周期為1000次,即1000ms=1stimer_init(&timer1, timer1_callback, 1000, 1000);timer_start(&timer1);//單次計(jì)時(shí),周期為50次,即50mstimer_init(&timer2, timer2_callback, 50, 0);timer_start(&timer2);/* USER CODE END 2 */
3.4. Timer對(duì)象處理
在循環(huán)中調(diào)用Timer對(duì)象處理函數(shù),處理函數(shù)會(huì)判斷鏈表上的每個(gè)定時(shí)器是否超時(shí),如果超過(guò),則拉起注冊(cè)的回調(diào)函數(shù):
/* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */timer_loop();}/* USER CODE END 3 */
3.5. 提供Timer時(shí)基信號(hào)
MultiTimer中所有的定時(shí)器都是通過(guò)一個(gè)32位的計(jì)數(shù)值_timer_ticks來(lái)判斷的,所以需要一個(gè)硬件定時(shí)器提供時(shí)基信號(hào),遞增該值。
本文中使用的是STM32HAL庫(kù),所以通過(guò)Systick來(lái)提供,無(wú)需設(shè)置額外的定時(shí)器。
在main.c文件的最后編寫(xiě)Systick回調(diào)函數(shù):
/* USER CODE BEGIN 4 */void HAL_SYSTICK_Callback(void){//給multitimer提供時(shí)基信號(hào)timer_ticks(); //1ms ticks}/* USER CODE END 4 */
然后在stm32l4xx_it.c中調(diào)用該回調(diào)函數(shù):
/*** @brief This function handles System tick timer.*/void SysTick_Handler(void){/* USER CODE BEGIN SysTick_IRQn 0 */HAL_SYSTICK_IRQHandler();/* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */}
接下來(lái)編譯下載,看在串口助手中看到打印的日志:
4. MultiTimer設(shè)計(jì)思想解讀
4.1. 軟件定時(shí)器設(shè)計(jì)思想
MultiTimer的設(shè)計(jì)比較簡(jiǎn)潔。
設(shè)置一個(gè)計(jì)數(shù)值_timer_ticks不斷遞增,由定時(shí)器提供的中斷驅(qū)動(dòng),只計(jì)次數(shù),不計(jì)時(shí)間,有了很大的自由度,一般時(shí)基信號(hào)設(shè)置為1ms一次:
/*** @brief background ticks, timer repeat invoking interval 1ms.* @param None.* @retval None.*/void timer_ticks(){_timer_ticks++;}
在程序運(yùn)行時(shí)循環(huán)比較定時(shí)器設(shè)置的超時(shí)值是否大于當(dāng)前_timer_ticks的計(jì)數(shù)值,如果是則再次判斷是否重復(fù)計(jì)數(shù)值是否為0,是則停止定時(shí)器,完成單次計(jì)時(shí)效果,否則修改計(jì)數(shù)值,最后拉起注冊(cè)到該定時(shí)器的回調(diào)函數(shù)執(zhí)行:
/*** @brief main loop.* @param None.* @retval None*/void timer_loop(){struct Timer* target;for(target=head_handle; target; target=target->next) {if(_timer_ticks >= target->timeout) {if(target->repeat == 0) {timer_stop(target);} else {target->timeout = _timer_ticks + target->repeat;}target->timeout_cb();}}}
4.2. 單鏈表操作
MultiTimer的代碼少,非常適合拿來(lái)學(xué)習(xí)單鏈表的操作,學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的過(guò)程是乏味的,不如直接來(lái)個(gè)實(shí)例看看是如何操作的。
① 鏈表的節(jié)點(diǎn)設(shè)計(jì)為一個(gè)軟件定時(shí)器,所以理論上支持的定時(shí)器數(shù)量只受內(nèi)存限制。
typedef struct Timer {uint32_t timeout;uint32_t repeat;void (*timeout_cb)(void);struct Timer* next;}Timer;
定時(shí)器初始化函數(shù)timer_init就是初始化一個(gè)鏈表節(jié)點(diǎn):
void timer_init(struct Timer* handle, void(*timeout_cb)(), uint32_t timeout, uint32_t repeat){// memset(handle, sizeof(struct Timer), 0);handle->timeout_cb = timeout_cb;handle->timeout = _timer_ticks + timeout;handle->repeat = repeat;}
② 設(shè)置鏈表頭指針,只需知道頭指針就能完成對(duì)整個(gè)單鏈表的操作:
//timer handle list head.static struct Timer* head_handle = NULL;
③ 向單鏈表增加一個(gè)節(jié)點(diǎn)
向單鏈表增加一個(gè)節(jié)點(diǎn)有三種方式:
- 在單鏈表尾部增加一個(gè)節(jié)點(diǎn)
- 在單鏈表頭部增加一個(gè)節(jié)點(diǎn)
- 在單鏈表中間增加一個(gè)節(jié)點(diǎn)
MultiTimer中所有的結(jié)點(diǎn)都是定時(shí)器,每個(gè)定時(shí)器之間相互獨(dú)立,不存在先后次序關(guān)系,所以無(wú)論加到中間,還是加到尾部,還是加到頭部,最后的功能都是一樣的,但是在插入算法上有優(yōu)劣性能之分。
先來(lái)看看再單鏈表尾部增加一個(gè)節(jié)點(diǎn)的算法:
( 我會(huì)動(dòng)哦 )
int timer_start(struct Timer* handle){/*** 算法1 —— 向單鏈表尾部添加節(jié)點(diǎn)* 時(shí)間復(fù)雜度O(n)* Mculover666*/struct Timer* target = head_handle;if(head_handle == NULL){/* 鏈表為空 */head_handle = handle;handle->next = NULL;}else{/* 鏈表中存在節(jié)點(diǎn),遍歷找最后一個(gè)節(jié)點(diǎn) */while(target->next != NULL){if(target == handle)return -1;target = target->next;}target->next = handle;handle->next = NULL;}return 0;}
這種算法理解簡(jiǎn)單,實(shí)現(xiàn)簡(jiǎn)單,但是算法時(shí)間復(fù)雜度秒變?yōu)镺(n),當(dāng)n很大時(shí),插入一個(gè)節(jié)點(diǎn)的時(shí)間就會(huì)非常久。
再來(lái)看看在鏈表頭部插入一個(gè)新節(jié)點(diǎn)的情況:
(我會(huì)動(dòng)哦)
int timer_start(struct Timer* handle){/*** 算法2 —— 向單鏈表頭部添加節(jié)點(diǎn)* 時(shí)間復(fù)雜度O(n),如果去掉判斷重復(fù),則時(shí)間復(fù)雜度O(1)* 0x1abin*/struct Timer *target = head_handle;//判斷是否有重復(fù)的定時(shí)器while(target){if(target == handle){return -1;}target = target->next;}handle->next = head_handle;head_handle = handle;return 0;}
這里第二種頭部插入節(jié)點(diǎn)的算法時(shí)間復(fù)雜度依然是O(n),emmm?
其實(shí),這里因?yàn)閱捂湵砉?jié)點(diǎn)是定時(shí)器,在插入的時(shí)候需要對(duì)整個(gè)鏈表進(jìn)行判斷,避免重復(fù)添加同樣的定時(shí)器節(jié)點(diǎn),所以無(wú)論任何一種算法,都需要對(duì)單鏈表進(jìn)行遍歷。
如果在不需要判斷重復(fù)的情況下,尾部插入算法仍然需要遍歷,但是頭部插入算法只需要插入就可以,時(shí)間復(fù)雜度為O(1),算法更優(yōu)。
④ 單鏈表刪除其中一個(gè)節(jié)點(diǎn)
刪除單鏈表的節(jié)點(diǎn)時(shí),因?yàn)楣?jié)點(diǎn)自身只保存有下一個(gè)節(jié)點(diǎn)的指針,并沒(méi)有指向上一個(gè)節(jié)點(diǎn)的指針,所以不能直接入手刪除節(jié)點(diǎn),那么如何刪除單鏈表的節(jié)點(diǎn)呢?
方法是:設(shè)置二級(jí)指針(指向Timer類(lèi)型指針的指針),通過(guò)遍歷鏈表的方式來(lái)尋找節(jié)點(diǎn)中next指針指向刪除節(jié)點(diǎn)的那個(gè)節(jié)點(diǎn),代碼如下。
void timer_stop(struct Timer* handle){struct Timer** curr;for(curr = &head_handle; *curr; ) {struct Timer* entry = *curr;if (entry == handle) {*curr = entry->next;// free(entry);} elsecurr = &entry->next;}}
-
軟件
+關(guān)注
關(guān)注
69文章
5256瀏覽量
90480 -
定時(shí)器
+關(guān)注
關(guān)注
23文章
3350瀏覽量
121253
原文標(biāo)題:MultiTimer,一款可無(wú)限擴(kuò)展的軟件定時(shí)器
文章出處:【微信號(hào):strongerHuang,微信公眾號(hào):strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
SysTick系統(tǒng)滴答定時(shí)器簡(jiǎn)介
TPL5100 ACTIVE 具有電源門(mén)控功能和 MOS 驅(qū)動(dòng)器的 Nano 供電可編程定時(shí)器
?TPL5110 低功耗定時(shí)器技術(shù)文檔總結(jié)
TPL5111 超低功耗系統(tǒng)定時(shí)器技術(shù)手冊(cè)
?TPL5010-Q1 納米功耗系統(tǒng)定時(shí)器(帶看門(mén)狗功能)技術(shù)文檔摘要
TPS3435 Nano IQ精密超時(shí)看門(mén)狗定時(shí)器技術(shù)解析與應(yīng)用指南
德州儀器TPS3436-Q1汽車(chē)級(jí)窗口看門(mén)狗定時(shí)器技術(shù)解析
Texas Instruments DS160PT801X16EVM重定時(shí)器評(píng)估模塊數(shù)據(jù)手冊(cè)
MCU定時(shí)器/計(jì)數(shù)器
TPS3435 納米靜態(tài)電流精密超時(shí)看門(mén)狗定時(shí)器數(shù)據(jù)手冊(cè)
TPS3436-Q1 汽車(chē)級(jí)納米靜態(tài)電流精密窗口看門(mén)狗定時(shí)器數(shù)據(jù)手冊(cè)
TPS3435-Q1 汽車(chē)級(jí)納米靜態(tài)電流精密超時(shí)看門(mén)狗定時(shí)器數(shù)據(jù)手冊(cè)
圣邦微電子SGM819SxQ車(chē)規(guī)級(jí)看門(mén)狗定時(shí)器電路特性與數(shù)據(jù)手冊(cè)分享
圣邦微電子車(chē)規(guī)級(jí)看門(mén)狗定時(shí)器電路SGM819SxQ特性與典型應(yīng)用電路

一款可無(wú)限擴(kuò)展的軟件定時(shí)器MultiTimer
評(píng)論