軟件定時器是用程序模擬出來的定時器,可以由一個硬件定時器模擬出成千上萬個軟件定時器,這樣程序在需要使用較多定時器的時候就不會受限于硬件資源的不足,這是軟件定時器的一個優點,即數量不受限制。
但由于軟件定時器是通過程序實現的,其運行和維護都需要耗費一定的CPU資源,同時精度也相對硬件定時器要差一些。
在Linux,uC/OS,FreeRTOS等操作系統中,都帶有軟件定時器,原理大同小異。典型的實現方法是:通過一個硬件定時器產生固定的時鐘節拍,每次硬件定時器中斷到,就對一個全局的時間標記加一,每個軟件定時器都保存著到期時間。
程序需要定期掃描所有運行中的軟件定時器,將各個到期時間與全局時鐘標記做比較,以判斷對應軟件定時器是否到期,到期則執行相應的回調函數,并關閉該定時器。
以上是單次定時器的實現,若要實現周期定時器,即到期后接著重新定時,只需要在執行完回調函數后,獲取當前時間標記的值,加上延時時間作為下一次到期時間,繼續運行軟件定時器即可。
軟件定時器需要一個硬件時鐘源作為基準,這個時鐘源有一個固定的節拍(可以理解為秒針的每次滴答),用一個32位的全局變量tickCnt來記錄這個節拍的變化:
static?volatile?uint32_t?tickCnt?=?0;????//軟件定時器時鐘節拍
每來一個節拍就對tickCnt加一(記錄滴答了多少下):
/*?需在定時器中斷內執行?*/
void?tickCnt_Update(void)
{
?tickCnt++;
}
一旦開始運行,tickCnt將不停地加一,而每個軟件定時器都記錄著一個到期時間,只要tickCnt大于該到期時間,就代表定時器到期了。
軟件定時器的數據結構決定了其執行的性能和功能,一般可分為兩種:數組結構和鏈表結構。什么意思呢?這是(多個)軟件定時器在內存中的存儲方式,可以用數組來存,也可以用鏈表來存。
兩者的優劣之分就是兩種數據結構的特性之分:數組方式的定時器查找較快,但數量固定,無法動態變化,數組大了容易浪費內存,數組小了又可能不夠用,適用于定時事件明確且固定的系統;鏈表方式的定時器數量可動態增減,易造成內存碎片(如果沒有內存管理),查找的時間開銷相對數組大,適用于通用性強的系統,Linux,uC/OS,FreeRTOS等操作系統用的都是鏈表式的軟件定時器。
本文使用數組結構:
static?softTimer?timer[TIMER_NUM];????????//軟件定時器數組
數組和鏈表是軟件定時器整體的數據結構,當具體到單個定時器時,就涉及軟件定時器結構體的定義,軟件定時器所具有的功能與其結構體定義密切相關,以下是本文中軟件定時器的結構體定義:
typedef?struct?softTimer?{
?uint8_t?state;???????????//狀態
?uint8_t?mode;????????????//模式
?uint32_t?match;??????????//到期時間
?uint32_t?period;?????????//定時周期
?callback?*cb;????????????//回調函數指針
?void?*argv;??????????????//參數指針
?uint16_t?argc;???????????//參數個數
}softTimer;
定時器的狀態共有三種,默認是停止,啟動后為運行,到期后為超時。
typedef?enum?tmrState?{
?SOFT_TIMER_STOPPED?=?0,??//停止
?SOFT_TIMER_RUNNING,??????//運行
?SOFT_TIMER_TIMEOUT???????//超時
}tmrState;
模式有兩種:到期后就停止的是單次模式,到期后重新定時的是周期模式。
typedef?enum?tmrMode?{
?MODE_ONE_SHOT?=?0,???????//單次模式
?MODE_PERIODIC,???????????//周期模式
}tmrMode;
不管哪種模式,定時器到期后,都將執行回調函數,以下是該函數的定義,參數指針argv為void指針類型,便于傳入不同類型的參數。
typedef?void?callback(void?*argv,?uint16_t?argc);
上述結構體中的模式state和回調函數指針cb是可選的功能,如果系統不需要周期執行的定時器,或者不需要到期后自動執行某個函數,可刪除此二者定義。
首先是軟件定時器的初始化,對每個定時器結構體的成員賦初值,雖說static變量的初值為0,但個人覺得還是有必要保持初始化變量的習慣,避免出現一些奇奇怪怪的BUG。
void?softTimer_Init(void)
{
?uint16_t?i;
?for(i=0;?i??timer[i].state?=?SOFT_TIMER_STOPPED;
??timer[i].mode?=?MODE_ONE_SHOT;
??timer[i].match?=?0;
??timer[i].period?=?0;
??timer[i].cb?=?NULL;
??timer[i].argv?=?NULL;
??timer[i].argc?=?0;
?}
}
啟動一個軟件定時器不僅要改變其狀態為運行狀態,同時還要告訴定時器什么時候到期(當前tickCnt值加上延時時間即為到期時間),單次定時還是周期定時,到期后執行哪個函數,函數的參數是什么,交代好這些就可以開跑了。
void?softTimer_Start(uint16_t?id,?tmrMode?mode,?uint32_t?delay,?callback?*cb,?void?*argv,?uint16_t?argc)
{
?assert_param(id??assert_param(mode?==?MODE_ONE_SHOT?||?mode?==?MODE_PERIODIC);
?
?timer[id].match?=?tickCnt_Get()?+?delay;
?timer[id].period?=?delay;
?timer[id].state?=?SOFT_TIMER_RUNNING;
?timer[id].mode?=?mode;
?timer[id].cb?=?cb;
?timer[id].argv?=?argv;
?timer[id].argc?=?argc;
}
上面函數中的assert_param()用于參數檢查,類似于庫函數assert()。
本文中軟件定時器有三種狀態:停止,運行和超時,不同的狀態做不同的事情。停止狀態最簡單,啥事都不做;運行狀態需要不停地檢查有沒有到期,到期就執行回調函數并進入超時狀態;超時狀態判斷定時器的模式,如果是周期模式就更新到期時間,繼續運行,如果是單次模式就停止定時器。
這些操作都由一個更新函數來實現:
void?softTimer_Update(void)
{
?uint16_t?i;
?
?for(i=0;?i???switch?(timer[i].state)?{
???????case?SOFT_TIMER_STOPPED:
?????break;
??
????case?SOFT_TIMER_RUNNING:
?????if(timer[i].match?<=?tickCnt_Get())?{
??????timer[i].state?=?SOFT_TIMER_TIMEOUT;
??????timer[i].cb(timer[i].argv,?timer[i].argc);???????//執行回調函數
?????}
?????break;
???
????case?SOFT_TIMER_TIMEOUT:
?????if(timer[i].mode?==?MODE_ONE_SHOT)?{
?????????timer[i].state?=?SOFT_TIMER_STOPPED;
?????}?else?{
??????timer[i].match?=?tickCnt_Get()?+?timer[i].period;
?????????timer[i].state?=?SOFT_TIMER_RUNNING;
?????}
?????break;
??
????default:
?????printf("timer[%d]?state?error!\r\n",?i);
?????break;
???}
??}
}
如果定時器跑到一半,想把它停掉,就需要一個停止函數,操作很簡單,改變目標定時器的狀態為停止即可:
void?softTimer_Stop(uint16_t?id)
{
?assert_param(id??timer[id].state?=?SOFT_TIMER_STOPPED;
}
又如果想知道一個定時器是在跑著呢還是已經停下來?也很簡單,返回它的狀態:
uint8_t?softTimer_GetState(uint16_t?id)
{
?return?timer[id].state;
}
或許這看起來很怪,為什么要返回,而不是直接讀?別忘了在前面3.2節中定義的定時器數組是個靜態全局變量,該變量只能被當前源文件訪問,當外部文件需要訪問它的時候只能通過函數返回,這是一種簡單的封裝,保持程序的模塊化。
最后,當然是來驗證一下我們的軟件定時器有沒達到預想的功能。
定義三個定時器:
定時器TMR_STRING_PRINT只執行一次,1s后在串口1打印一串字符;
定時器TMR_TWINKLING為周期定時器,周期為0.5s,每次到期都將取反LED0的狀態,實現LED0的閃爍;
定時器TMR_DELAY_ON執行一次,3s后點亮LED1,跟第一個定時器不同的是,此定時器的回調函數是個空函數nop(),點亮LED1的操作通過主循環中判斷定時器的狀態來實現,這種方式在某些場合可能會用到。
static?uint8_t?data[]?=?{1,2,3,4,5,6,7,8,9,0};
int?main(void)
{
?USART1_Init(115200);
?TIM4_Init(TIME_BASE_MS);
?TIM4_NVIC_Config();
?LED_Init();
?
?printf("I?just?grabbed?a?spoon.\r\n");
?
?softTimer_Start(TMR_STRING_PRINT,?MODE_ONE_SHOT,?1000,?stringPrint,?data,?5);
?softTimer_Start(TMR_TWINKLING,?MODE_PERIODIC,?500,?LED0_Twinkling,?NULL,?0);
?softTimer_Start(TMR_DELAY_ON,?MODE_ONE_SHOT,?3000,?nop,?NULL,?0);
?
?while(1)?{
??softTimer_Update();
??if(softTimer_GetState(TMR_DELAY_ON)?==?SOFT_TIMER_TIMEOUT)?{
???LED1_On();
??}
?}
}
原文鏈接:https://blog.csdn.net/qq_26904271/article/details/83833168
本文來源網絡,免費傳達知識,版權歸原作者所有。如涉及作品版權問題,請聯系我進行刪除。