基於連結串列的軟體定時器實現(轉)
軟體定時器在實際應用比較重要,本文旨在實現一種便於移植,易擴充套件功能,效率高的軟體定時器。本定時器是基於排序連結串列,將最近將觸發的定時器置於連結串列頭,後續新增定時器將計算出其合適位置插入。
主要資料結構及資料
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; } }