嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 超级精简系列之七:超级精简的软件定时器实现

超级精简系列之七:超级精简的软件定时器实现

一. 前言

前面我们实现了链表这个非常基础的轮子,《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;
#define TIMER_START_B 0#define TIMER_MODE_B 1

定义如下接口

/** * \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

#include <unistd.h>#include <stdio.h>#include <time.h>
#include "timer.h"
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

四. 总结

可以看到有了好的链表的轮子,基于此可以实现很多常用的组件,基于链表实现的软件定时器,几乎是水到渠成的事。