基于链表的软件定时器实现(转)
Posted citroen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于链表的软件定时器实现(转)相关的知识,希望对你有一定的参考价值。
软件定时器在实际应用比较重要,本文旨在实现一种便于移植,易扩展功能,效率高的软件定时器。本定时器是基于排序链表,将最近将触发的定时器置于链表头,后续新增定时器将计算出其合适位置插入。
主要数据结构及数据
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; } }
以上是关于基于链表的软件定时器实现(转)的主要内容,如果未能解决你的问题,请参考以下文章