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

圣邦微電子SGM819SxQ車規(guī)級看門狗定時器電路特性與數(shù)據(jù)手冊分享

圣邦微電子車規(guī)級看門狗定時器電路SGM819SxQ特性與典型應(yīng)用電路

定時器自動控制開關(guān)怎么設(shè)置
定時器的基本組成和工作模式
ESP8266的軟件定時器允許在user_init中定義兩個不同的定時器然后去定時兩個不同的操作嗎?
定時器的工作方式介紹
與定時器相關(guān)的寄存器有哪些類型
長持續(xù)時間定時器電路圖 時間定時器的工作原理和功能

評論