一. 前言
前面我们实现了链表这个非常基础的”轮子”,《https://mp.weixin.qq.com/s/8auensNh3q0T0dOayM0d2A》,基于此我们就可以实现更多的通用组件。这一篇分享比较常见的软件定时器的实现。
二. 实现
主要思想如下:通过链表管理定时器。
定义定时器结构
/**
* \struct timer_info
* 定时器信息结构体.
*
* 通过next_item_pst连接后续节点.
*/
struct timer_info
{
uint8_t mode; /**< bit[0] 0:停止 1:使能 bit[1] 0:单次模式 1:持续模式 */
timer_callback_pf func; /**< 回调函数 */
uint32_t period; /**< 周期 */
uint32_t time; /**< 当前值 */
};
链表节点itemobj_p指向上述定时器结构,则可以通过链表管理定时器。
按照以下需求设计:
1.为了减少依赖,通用,具备可移植性,不使用动态内存分配,实例由调用者提供。
2.定时器精度由调用者决定,调用loop函数间隔不一样,精度就不一样,非常灵活。
3.实际应用可以在硬件中断服务函数中调用loop提高实时性,也可以在其他线程循环中调用, 可以实现任意组定时器。
4.定时器组数,每组的定时器个数无限制。
5.定时器不使用绝对时间戳的方式,绝对时间戳会有麻烦和恼人的溢出问题,而是使用一个人周期设置值和实时计数值,通过实时计数值减少到0来确定定时到,定时到了重新加载周期设置值到实时计数值,重新开始递减。虽然多了一个变量多占空了空间,但是这是值得的。
6.模块本身只提供纯粹的定时器框架,至于实际应用的可重入,比如多线程调用等问题,由具体环境再进行一层封装即可,也可再封装使用动态分配实例。
其他数据结构如下
/**
* \enum timer_res_e
* 返回结果枚举.
*/
typedef enum
{
TIMER_OK = 0,
TIMER_PARAM_ERR = 1,
TIMER_ENQUEUE_ERR = 2,
TIMER_DEQUEUE_ERR = 3,
}timer_res_e;
typedef void(*timer_callback_pf)(void*); /**< 定时器回调函数 */
/**
* \struct timer_info
* 定时器信息结构体.
*
* 通过next_item_pst连接后续节点.
*/
struct timer_info
{
uint8_t mode; /**< bit[0] 0:停止 1:使能 bit[1] 0:单次模式 1:持续模式 */
timer_callback_pf func; /**< 回调函数 */
uint32_t period; /**< 周期 */
uint32_t time; /**< 当前值 */
};
typedef struct timer_info timer_info_st; /**< 重定义节点类型 */
/**
* \enum TIMER_MODE_e
* 定时器模式枚举.
*
*/
typedef enum
{
TIMER_MODE_S = 0x00, /**< 单次模式 */
TIMER_MODE_C = 0x02, /**< 持续模式 */
}TIMER_MODE_e;
/**
* \enum TIMER_STATE_e
* 定时器状态枚举.
*
*/
typedef enum
{
TIMER_STATE_STOP = 0x00, /**< 停止 */
TIMER_STATE_START = 0x01, /**< 运行 */
}TIMER_STATE_e;
定义如下接口
/**
* \fn timer_list_init
* 初始化定时器链表
* 初始化链表为空
* \param[in] t_list \ref list_st 指向定时器链表的指针,用户提供实例
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_list_init(list_st* t_list);
/**
* \fn timer_init
* \brief 定时器初始化.
* \param[in] t_list \ref list_st 链表,用户提供实例
* \param[in] t_item \ref list_item_st 链表节点,用户提供实例.
* \param[in] t_info \ref timer_info_st 定时器信息,用户提供实例.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_init(list_st* t_list, list_item_st* t_item, timer_info_st* t_info);
/**
* \fn timer_start
* \brief 启动定时器.
* \param[in] t_item \ref list_item_st 定时器节点.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_start(list_item_st* t_item);
/**
* \fn timer_stop
* \brief 停止定时器.
* \param[in] t_item \ref list_item_st 定时器节点.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_stop(list_item_st* t_item);
/**
* \fn timer_set_period
* \brief 设置定时器周期.
* \param[in] t_item \ref list_item_st 定时器节点.
* \param[in] period 定时器周期,单位为timer_loop调用间隔.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_set_period(list_item_st* t_item, uint32_t period);
/**
* \fn timer_restart
* \brief 重启定时器.
* 与start的区别是除了设置启动标志还会重置定时器计数值
* \param[in] t_item \ref list_item_st 定时器节点.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_restart(list_item_st* t_item);
/**
* \fn timer_remove
* \brief 移除定时器.
* \param[in] t_list \ref list_st 定时器链表.
* \param[in] t_item \ref list_item_st 定时器节点.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_remove(list_st* t_list, list_item_st* t_item);
/**
* \fn timer_loop
* \brief 定时器周期处理函数,定时器单位即该函数的调用周期.
* \param[in] t_list \ref list_st 定时器链表.
* \retval TIMER_OK
* \retval TIMER_PARAM_ERR
*/
timer_res_e timer_loop(list_st* t_list);
初始化链表,用户提供链表实例
timer_res_e timer_list_init(list_st* t_list)
{
/* 参数检查 */
if(t_list == 0)
{
return TIMER_PARAM_ERR;
}
/* 初始化链表为空 */
t_list->next_item_pst = 0;
}
初始化定时器,即将定时器节点添加到链表中
timer_res_e timer_init(list_st* t_list, list_item_st* t_item, timer_info_st* t_info)
{
/* 参数检查 */
if((t_list == 0) || (t_item == 0) || (t_info == 0))
{
return TIMER_PARAM_ERR;
}
/* 初始化节点 */
t_item->itemobj_p = t_info;
t_item->next_item_pst = 0;
t_item->itemvalue_t = 0;
/* 添加节点到链表中 */
if(LIST_OK == list_enqueuetail(t_list, t_item))
{
return TIMER_ENQUEUE_ERR;
}
else
{
return TIMER_OK;
}
}
启动定时器
timer_res_e timer_start(list_item_st* t_item)
{
/* 参数检查 */
if(t_item == 0)
{
return TIMER_PARAM_ERR;
}
/* 启动定时器 */
timer_info_st *timer_item = (timer_info_st *)(t_item->itemobj_p);
timer_item->mode |= (1<<TIMER_START_B);
return TIMER_OK;
}
停止定时器
timer_res_e timer_stop(list_item_st* t_item)
{
/* 参数检查 */
if(t_item == 0)
{
return TIMER_PARAM_ERR;
}
/* 停止定时器 */
timer_info_st *timer_item = (timer_info_st *)(t_item->itemobj_p);
timer_item->mode &= ~(1<<TIMER_START_B);
return TIMER_OK;
}
设置定时器周期
timer_res_e timer_set_period(list_item_st* t_item, uint32_t period)
{
/* 参数检查 */
if(t_item == 0)
{
return TIMER_PARAM_ERR;
}
/** 设置周期 */
timer_info_st *timer_item = (timer_info_st *)(t_item->itemobj_p);
timer_item->period = period;
return TIMER_OK;
}
重启定时器,会重新加载定时器计数值
timer_res_e timer_restart(list_item_st* t_item)
{
/* 参数检查 */
if(t_item == 0)
{
return TIMER_PARAM_ERR;
}
/* 重启定时器即重新设置定时器的周期,并设置启动位 */
timer_info_st *timer_item = (timer_info_st *)(t_item->itemobj_p);
timer_item ->time = timer_item->period;
timer_item->mode |= (1<<TIMER_START_B);
return TIMER_OK;
}
从链表移除定时器
timer_res_e timer_remove(list_st* t_list, list_item_st* t_item)
{
/* 参数检查 */
if((t_list == 0) || (t_item == 0))
{
return TIMER_PARAM_ERR;
}
/* 链表中移除定时器节点 */
if(LIST_OK != list_dequeueitem(t_list, t_item))
{
return TIMER_DEQUEUE_ERR;
}
return TIMER_OK;
}
用户周期调用loop处理
timer_res_e timer_loop(list_st* t_list)
{
list_st *list = t_list; /* 待遍历的链表 */
list_item_st **curr = 0; /* curr用于遍历节点的next_item_pst成员的地址 其值为next_item_pst */
list_item_st *item = 0; /* 遍历节点指针 */
timer_info_st *timer = 0;
/* 参数检查 */
if(t_list == 0)
{
return TIMER_PARAM_ERR;
}
/* 遍历链表 */
curr = &(list -> next_item_pst);
while(*curr != 0)
{
item = *curr;
timer = (timer_info_st *)(item->itemobj_p);
if((timer->mode & (1<<TIMER_START_B)) != 0)
{
/*定时器是使能的*/
if(timer->time > 1)
{
timer->time--;
}
else
{
/* 进行回调 */
if(timer->func)
{
timer->func(item);
}
if((timer->mode & (1<<TIMER_MODE_B)) != 0)
{
/* 如果是持续模式继续设置周期 */
timer->time = timer->period;
}
else
{
/* 单次模式 停止定时器 */
timer->mode &= ~(1<<TIMER_START_B);
}
}
}
/* 继续遍历 */
curr = &(item -> next_item_pst);
}
return TIMER_OK;
}
三. 测试
timer_main.c
void t1_cb(void* t)
{
printf("t1:%ld\r\n",time(0));
}
void t2_cb(void* t)
{
printf("t2:%ld\r\n",time(0));
}
int main(void)
{
/* 分配链表实例并初始化 */
list_st t_list;
timer_list_init(&t_list);
/* 分配节点实例
* 分配定时器实例,并初始化
* 一个链表添加两个定时器
*/
list_item_st t_item1;
timer_info_st t_info1=
{
.func = t1_cb,
.mode = TIMER_MODE_S,
.period = 5,
.time = 0,
};
timer_init(&t_list, &t_item1, &t_info1);
list_item_st t_item2;
timer_info_st t_info2=
{
.func = t2_cb,
.mode = TIMER_MODE_C,
.period = 3,
.time = 0,
};
timer_init(&t_list, &t_item2, &t_info2);
/* 启动 */
timer_start(&t_item1);
timer_start(&t_item2);
while(1)
{
/* 周期调用lopp处理 */
sleep(1);
timer_loop(&t_list);
}
}
gcc list.c timer_main.c timer.c -o timer
./timer
可以看到定时器1,3秒后执行一次停止,单次模式。
定时器2,3秒执行一次,连续模式。
./timer
t1:1703947253
t2:1703947253
t2:1703947256
t2:1703947259
t2:1703947262
t2:1703947265
四. 总结
可以看到有了好的链表的”轮子”,基于此可以实现很多常用的组件,基于链表实现的软件定时器,几乎是水到渠成的事。