嵌入式開源項目精選專欄之前發布過一篇關于MultiTimer的文章, MultiTimer | 一款可無限擴展的軟件定時器,這周有小伙伴在群里提醒我 MutilTimer 和文章寫的不太一樣,第一反應是重構了,大佬們技術水平提升一個段位后都喜歡重構項目,去github看看發生了什么。
master分支上還是之前的v1版本,和文章是一樣的:

development分支上果然重構了項目,發布了v2版本:

同步更新下教程。
本期給大家帶來的開源項目是 MultiTimer,一款可無限擴展的軟件定時器,作者0x1abin,目前收獲 399 個 star,遵循 MIT 開源許可協議。
MultiTimer 是一個軟件定時器擴展模塊,可無限擴展你所需的定時器任務,取代傳統的標志位判斷方式, 更優雅更便捷地管理程序的時間觸發時序。
項目地址:https://github.com/0x1abin/MultiTimer
開源項目在移植過程中主要參考項目的readme文檔,一般只需兩步:
本文中我使用的是小熊派IoT開發套件,主控芯片為STM32L431RCT6:

移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:
① 復制MultiTimer源碼到工程中:

② 在keil中添加 MultiTimer的源碼文件:

③ 將MultiTimer頭文件路徑添加到keil中:

① 復制MultiTimer源碼到工程中:

② 在 Makefile 中添加 MultiTimer的源碼文件:

③ 添加MultiTimer頭文件路徑:
使用時包含頭文件:
#include?"multi_timer.h"
MultiTimer中的時基信號需要安裝,API如下:
/**
?*?@brief?Platform?ticks?function.
?*?
?*?@param?ticksFunc?ticks?function.
?*?@return?int?0?on?success,?-1?on?error.
?*/
int?MultiTimerInstall(PlatformTicksFunction_t?ticksFunc);
PlatformTicksFunction_t 函數指針定義如下:
typedef?uint64_t?(*PlatformTicksFunction_t)(void);
本文中使用的是STM32HAL庫,所以通過Systick來提供,無需設置額外的定時器。
編寫獲取系統 tick 的函數:
/*?Private?user?code?---------------------------------------------------------*/
/*?USER?CODE?BEGIN?0?*/
uint64_t?PlatformTicksGetFunc(void)
{
??return?(uint64_t)HAL_GetTick();
}
/*?USER?CODE?END?0?*/
在main函數中安裝該tick函數:
/*?USER?CODE?BEGIN?2?*/
printf("MultiTimer?v2?Port?on?BearPi?board?by?mculover666!\r\n");
MultiTimerInstall(PlatformTicksGetFunc);
/*?USER?CODE?END?2?*/
軟件定時器抽象為 MultiTimer 結構體:
struct?MultiTimerHandle?{
????MultiTimer*?next;
????uint64_t?deadline;
????MultiTimerCallback_t?callback;
????void*?userData;
};
typedef?struct?MultiTimerHandle?MultiTimer;
所以直接使用 MultiTimer 類型創建軟件定時器:
/*?USER?CODE?BEGIN?PV?*/
MultiTimer?timer1;
/*?USER?CODE?END?PV?*/
回調函數類型定義如下:
typedef?void?(*MultiTimerCallback_t)(MultiTimer*?timer,?void*?userData);
按照回調函數格式,創建超時回調函數:
/*?Private?user?code?---------------------------------------------------------*/
/*?USER?CODE?BEGIN?0?*/
void?timer1_callback(MultiTimer*?timer,?void*?userData)
{
????printf("timer1?timeout!\r\n");
}
/*?USER?CODE?END?0?*/
啟動定時器的API如下:
/**
?*?@brief?Start?the?timer?work,?add?the?handle?into?work?list.
?*?
?*?@param?timer?target?handle?strcut.
?*?@param?timing?Set?the?start?time.
?*?@param?callback?deadline?callback.
?*?@param?userData?user?data.
?*?@return?int?0:?success,?-1:?fail.
?*/
int?MultiTimerStart(MultiTimer*?timer,?uint64_t?timing,?MultiTimerCallback_t?callback,?void*?userData);
初始化定時器對象,注冊定時器回調處理函數,設置超時時間(ms):
/*?USER?CODE?BEGIN?2?*/
printf("MultiTimer?v2?Port?on?BearPi?board?by?mculover666!\r\n");
MultiTimerStart(&timer1,?1000,?timer1_callback,?NULL);
/*?USER?CODE?END?2?*/
Timer對象處理函數API定義如下:
/**
?*?@brief?Check?the?timer?expried?and?call?callback.
?*?
?*?@return?int?The?next?timer?expires.
?*/
int?MultiTimerYield(void);
在主循環中調用Timer對象處理函數,處理函數會判斷鏈表上的每個定時器是否超時,如果超過,則拉起注冊的回調函數:
/*?Infinite?loop?*/
/*?USER?CODE?BEGIN?WHILE?*/
while?(1)
{
??/*?USER?CODE?END?WHILE?*/
??/*?USER?CODE?BEGIN?3?*/
??MultiTimerYield();
}
?/*?USER?CODE?END?3?*/
接下來編譯下載,看在串口助手中看到打印的日志:
在定時器超時函數中,重啟定時器即可。
void?timer1_callback(MultiTimer*?timer,?void*?userData)
{
????printf("timer1?timeout!\r\n");
????//?restart
????MultiTimerStart(&timer1,?1000,?timer1_callback,?NULL);
}

相對于v1版本,v2版本明顯涉及簡潔很多,c文件實現只有4個函數,82行代碼。
v2版本中使用注冊機制由用戶提供tick,這樣設計有個好處是,可移植性更強,無需干預系統tick中斷,只有MultiTimer得到調度的時候,它才可以通過我們安裝的API獲取到系統tick,以此為基準來判斷定時器是否超時。
v2版本還優化了鏈表插入機制,之前是簡單粗暴直接單鏈表插入節點,現在通過超時時間排序插入,更加優雅:
除了插入的更加優雅之外,這樣做還有兩個對于軟件定時器性能的提升,在調度的時候:

軟件定時器實現思想可以參考之前v1版本的教程。
猜你喜歡:
分享嵌入式軟件調試方法及幾個有用的工具!
嵌入式大雜燴周記 | 第 6 期?FlexibleButton
在公眾號聊天界面回復1024,可獲取嵌入式資源;回復?m?,可查看文章匯總。