軟件定時器在實際應用比較重要,本文旨在實現一種便於移植,易擴展功能,效率高的軟件定時器。本定時器是基於排序鏈表,將最近將觸發的定時器置於鏈表頭,后續新增定時器將計算出其合適位置插入。
主要數據結構及數據
typedef struct m_tm_tcb_struct
{
uint32_t time; //初次觸發時間
uint32_t period; //周期時間,如果是只執行1次,則設為0
void *pdata; //定時器私有參數
m_timeout_handler phandler; //定時器回調函數
struct m_tm_tcb_struct *next;//鏈表
}m_tm_tcb;
static m_tm_tcb *ptm_list_header;
static uint32_t m_timeouts_last_time; //上次觸發的時間
例一:我們將依次新增5個定時器,定時周期為3,5,8,8,12;當前時間為n;插入第1個時,如果鏈表空,則設置m_timeouts_last_time = n;設置第一個元素time=3; next=null;插入第2個,設置time2=5-3 = 2(相對與第一個時間),next=NULL;插入第3個,設置time = 8 - 3 - 2 = 3(相對與第二個時間);同理,第4個,time = 0; 第5個 time = 4。
上述是按照排好序的方式添加,如果打亂呢?
例二:依次添加周期為3,5,12,8的定時器。前3個定時器的添加與上個例子相同,插入完成后time1=3,time2=2,time3=7; 插入第4個,time4-time1>0? 成立,time4-=time1=5;與鏈表第2個元素時間對比,time4-time2>0?,成立,time4-=time2 = 3;在與鏈表第3個元素時間對比時,time4-time3>0?不成立,此定時器應該插入在2、3兩個元素之間;但是原來第3個元素的觸發時間就增加了time4的時間,因此將time3 -= time4 = 4。
最后,上述添加是同時添加5個定時器,實際運用時不可能全部同時添加。
例三:添加第1個定時器,間隔5;過了4個時間片后,新增第2個定時器,間隔3;顯然第2個定時器應該在第1個觸發后2個時間片再觸發;雖然time2-time1>0不成立;這里就新增1個變量記錄上次觸發時間:m_timeouts_last_time;添加定時器時將計算出現在距離上次觸發時間diff = now-m_timeouts_last_time;time2+=diff;這個例子中time2=3+(4-0)=7;然后按照例2進行添加。
定時器主處理函數可在中斷中調用,也可在普通任務中(無OS時就是主循環)調用,判斷now-m_timeouts_last_time>ptm_list_header->time?成立,則代表有定時器觸發,需要進行處理;處理后表頭直針后移,看新元素時間是否為0(相對前一個)如果為0,則進行處理,直到不為0;如果時周期性定時器,則可在添加是將定時器結構體內period設置為周期時間,在定時器回調執行完成后重新添加定時器即可。
該定時器使用過程中的注意事項,如果定時器主處理函數如果放在中斷中,則代表超時回調函數也在中斷中處理,此時不應將回調函數寫的太長,例如:擦、寫flash;串口用阻塞的方式發送太長的數據(115200大約在10kB/S,10個字節對應1ms,如果,回調執行超過1ms,明顯會使定時器不准確);如果在普通任務調用則無此問題。比較好的方法其實是使用前后台處理機制,將回調函數必要參數傳回普通任務中,在任務中去執行,這樣想寫多長也不會影響實時性。后續的另一篇文章中,我將實現一種通用的無OS的前后台調度器,降低中斷平面、任務平面的耦合性。
/** * @file m_timeouts.h * Timer implementations */ #ifndef M_TIMEOUTS_H #define M_TIMEOUTS_H #include "m_common.h" //定時器回調函數 typedef void (* m_timeout_handler)(void *arg); //定時器結構體 typedef struct m_tm_tcb_struct { uint32_t time; //初次觸發時間 uint32_t period; //周期時間,如果是只執行1次,則設為0 void *pdata; //定時器私有參數 m_timeout_handler phandler; //定時器回調函數 struct m_tm_tcb_struct *next;//鏈表 }m_tm_tcb; //定時器初始化 void m_timeout_init(void); //添加定時器 int8_t m_timeout_add(m_tm_tcb *tm); //刪除定時器 int8_t m_timeout_delete(m_tm_tcb *tm); //定時器處理函數 void m_timeout_process(void); #endif ---------------------
/** * @file m_timeouts.c * Timer implementations */ #include "m_timeouts.h" static m_tm_tcb *ptm_list_header; static uint32_t m_timeouts_last_time; //上次觸發的時間。 uint32_t tm_get_now(void) { return HAL_GetTick(); } //定時器初始化 void m_timeout_init(void) { ptm_list_header = NULL; } //添加定時器,單次運行; int8_t m_timeout_add(m_tm_tcb *tm) { uint32_t diff=0,now,msecs; m_tm_tcb *p; now = tm_get_now(); //鏈表為空 M_ENTER_CRITICAL(); if(ptm_list_header == NULL) { m_timeouts_last_time = now; ptm_list_header = tm; tm->next = NULL; M_EXIT_CRITICAL(); return 0; } else { diff = now - m_timeouts_last_time; msecs = tm->time; tm->time += diff; } if(ptm_list_header->time > tm->time) { ptm_list_header->time -= tm->time; tm->next = ptm_list_header; ptm_list_header = tm; } else { for(p = ptm_list_header; p!=NULL; p=p->next) { tm->time -= p->time; if(p->next == NULL || p->next->time > tm->time) { if(p->next != NULL) { p->next->time -= tm->time; } else if(tm->time > msecs) { tm->time = msecs+ptm_list_header->time; } tm->next = p->next; p->next = tm; break; } } } M_EXIT_CRITICAL(); return 0; } //刪除定時器 int8_t m_timeout_delete(m_tm_tcb *tm) { m_tm_tcb *prev, *t; M_ENTER_CRITICAL(); for(t=ptm_list_header, prev=NULL; t!=NULL; prev=t, t=t->next) { if(t == tm) { if(t->next) t->next->time += tm->time; if(prev == NULL) { ptm_list_header = t->next; } else { prev->next = t->next; } M_EXIT_CRITICAL(); return 0; } } M_EXIT_CRITICAL(); return -1; } //定時器處理函數 void m_timeout_process(void) { m_tm_tcb *tmptm = ptm_list_header; uint32_t diff = tm_get_now() - m_timeouts_last_time; while(tmptm && (diff >= tmptm->time)) { diff -= tmptm->time; M_ENTER_CRITICAL(); m_timeouts_last_time += tmptm->time; ptm_list_header = tmptm->next; M_EXIT_CRITICAL(); if(tmptm->period) { tmptm->time = tmptm->period; m_timeout_add(tmptm); } if(tmptm->phandler) tmptm->phandler(tmptm->pdata); tmptm = ptm_list_header; } }