基于链表的轻量级软件定时器组件


前言

在嵌入式软件领域中,定时器是非常重要及常用的概念,还记得我刚毕业的时候,还是一个只会delay的小菜鸡,然后大佬看不下去了,给我讲了时间片轮询,我才真的入了工程师的门。

比如10hz的频率闪烁一颗LED,我们可以在一个1ms定时器中断中累加一个变量,在主循环中判断这个变量,基于此变量去执行任务,这就是最基础的时间片轮询机制了。

一直在工作了好几年之后,我基本都还是基于此概念去编程。但这样任务量一多,需要用到的时间变量就多起来了,我当时最多只是用一个结构体什么的把变量关系组织起来。毫无工程结构可言,代码一团糟,也不好移植啊啥的,重复性工作极多。

后来接触了nrf52840这颗芯片,说实话,它的sdk简直一团糟,我一度认为难看到极点(当然我认为是我水平不够导致我这样认为的…)。在它的sdk里有一个文件app_timer.c,真的,当时是惊艳到我的,原来软件定时器的接口可以设计得如此精简和漂亮。

呐,就像下面这样。创建并启动timer之后,它就会按照你设置的时长和模式开始工作起来了,时间到了就执行回调函数里面的内容。非常好用~~

APP_TIMER_DEF(my_timer_id);//定义timer

err_code = app_timer_create(&my_timer_id, APP_TIMER_MODE_REPEATED, my_timeout_handler);//创建timer并设定模式,绑定回调函数

static void my_timeout_handler (void * p_context)//timer的回调函数
{
       //add your code here
}

err_code = app_timer_start(my_timer_id, APP_TIMER_TICKS(10), NULL);  //启动timer并定时10ms

err_code = app_timer_stop(my_timer_id);//停止timer

但是上面这一套只能用在nordic他自己的芯片上,我想把它抽象出来,不依赖芯片平台,这样以后的工作效率那不就提升起来了~~。说干就干。


接口设计

1、数据结构

我想要这个软件定时器能基于一个硬件定时器扩展得足够多,并且使用起来尽可能简洁。于是我引入了链表,创建好的定时器就绑定在一个链表上,然后按固定时间去扫描这个链表,并执行对应内容。

链表其实挺难写的,至少让我从头写一个能用的链表,还真得花不少时间,于是,我掏出了Adam Dunkels大神的链表组件…就是写lwip那个大神。嗯,这个链表也贼经典,也是我见到的二重指针的经验用例,以后有空可以写篇博客。

LIST(timer_list); // 创建一个timer_list链表,后面创建的定时器全都挂在timer_list下面

设计软件定时器的回调函数类型,Nordic的软件定时器回调有个 void * p_context,还有很多操作系统啊都会有这种,我大致理解就是它可以传递一个任意类型的指针进去,然后访问这个内容。不过我从来没有用过,应该是一个比较高级的东西…

看不懂,那就无参无返回。

typedef void (*b_timerout_handler_t)(void);
typedef struct{
	void *					   list_next;//使用Adam Dunkels大神的链表组件,结构体第一个内容得是这个
	uint32_t                   end_val;  //定时器时间到的终值   
	uint32_t                   repeat_period;//定时器定时时间的重载值 
	b_timerout_handler_t	   handler;//回调       
	uint8_t            		   active;//定时器工作状态指示
}b_timer_t;

软件定时器一搬有两种模式,一种是单次执行,一种是重复执行。单次执行很简单,时间到了就把定时器停止了就行,重复执行就是时间到了,再重新装载一下定时器终值即可。用个枚举类型来表示。

typedef enum
{
    B_TIMER_MODE_SINGLE_SHOT,                 
    B_TIMER_MODE_REPEATED        
} b_timer_mode_t;

在大神的链表里面有一个挺有意思的宏,在预编译阶段,它用来将s1和s2连接起来,大神用它来--------给变量取名字!!!

#define LIST_CONCAT2(s1, s2) s1##s2
#define LIST_CONCAT(s1, s2) LIST_CONCAT2(s1, s2)

拿来主义,参考参考大神的代码,大神定义链表是这样的。

#define LIST(name) \
         static void *LIST_CONCAT(name,_list) = NULL; \
         static list_t name = (list_t)&LIST_CONCAT(name,_list)

//拆开后就是
static void *name_list = NULL;
static list_t name = (list_t)&name_list; 

//list_t是void **

呐,LIST(name)就是定义了一个list_t类型的 name,它指向了name_list 。
好好好,我也这样搞

#define B_CONCAT(p1, p2)      LIST_CONCAT(p1, p2)
//
typedef b_timer_t * b_timer_id_t;

#define _B_TIMER_DEF(timer_id)                                   \
    static b_timer_t B_CONCAT(timer_id,_data) = {                \
            .active = 0,                                         \
    };                                                           \
    const b_timer_id_t timer_id = &B_CONCAT(timer_id,_data);
    
//定义一个名称为timer_test1_id的软件定时器~~
_B_TIMER_DEF(timer_test1_id)

2、创建定时器

创建定时器,就是将宏定义的timer_test1_id给初始化一下,并且添加到链表timer_list中。代码很简单

uint8_t b_timer_create(b_timer_id_t const * p_timer,
                            b_timer_mode_t mode,
                            b_timerout_handler_t timeout_handler)
{
	if (p_timer == NULL || timeout_handler == NULL) return 1;
	b_timer_id_t p_t = (b_timer_id_t) *p_timer;
	p_t->handler = timeout_handler;
	p_t->repeat_period = (mode == B_TIMER_MODE_REPEATED) ? 1 : 0;
	p_t->active = 0;
  	list_add(timer_list,p_t);//用大神的接口,将p_t添加到链表timer_list中~
	return 0;
}

3、删除定时器

uint8_t b_timer_delete(b_timer_id_t const * p_timer)
{
	b_timer_id_t p_t = (b_timer_id_t) *p_timer;
	p_t->active = 0;
	list_remove(timer_list,p_t);//从timer_list中移除p_t
	return 0;
}

4、启动定时器

static uint32_t sys_timer_now = 0;

uint32_t b_get_sys_timer(void)
{
    return sys_timer_now;
}

uint8_t b_timer_start(b_timer_id_t const * p_timer, uint32_t timeout_ticks)
{
	b_timer_id_t p_t = (b_timer_id_t) *p_timer;

	if (p_t->active)
	{
			return 0;
	}

	p_t->end_val = b_get_sys_timer() + timeout_ticks;

	if (p_t->repeat_period)
	{
		p_t->repeat_period = timeout_ticks;
	}
	
	p_t->active = 1;
	
	return 0;
}	

5、停止定时器

uint8_t b_timer_stop(b_timer_id_t const *      p_timer)
{
	b_timer_id_t p_t = (b_timer_id_t) *p_timer;
	p_t->active = 0;
	return 0;
}	

6、定时器更新

void timer_update(void)//一般我是在硬件定时器中断中调用,比如1ms一次。也可以主循环周期调用,但那样就可能更不怎么准了
{
	b_timer_id_t item = NULL;
	uint32_t cur_sys_timer = sys_timer_now;
	
	cur_sys_timer++;//调用一次,时基加1。

	//遍历链表
	for(item = (b_timer_id_t)list_head(timer_list); item != NULL ; item = list_item_next(item))
	{
		if(item->active){
			if(cur_sys_timer == item->end_val){ //必须是==判断,不能≥,因为item->end_val如果溢出,那么cur_sys_timer就会一直>end_val。
        		if(item->repeat_period){//根据模式去决定重装载终值还是停止定时器。
					item->end_val = cur_sys_timer + item->repeat_period;
				}else{
					item->active = 0;
				}
				item->handler();//回调
			}
		}
	}
	sys_timer_now = cur_sys_timer;
}

使用实例

随便找个demo演示下~~

1、硬件定时器初始化,并在中断中调用timer_update

void Timer6_Handler(void)
{
    TIM_ClearINT(TIM6);
    TIM_Cmd(TIM6, DISABLE);
    timer_update();//timer_update调用频率确定软件定时器单tick时间。
    //Add user code here
    TIM_Cmd(TIM6, ENABLE);
}

2、定义软件定时器,并创建回调函数

_B_TIMER_DEF(timer_test1_id)
_B_TIMER_DEF(timer_test2_id)

void timer_test1(void)
{
  xprintf("我是timer_test1\r\n");
}

void timer_test2(void)
{
  xprintf("我是timer_test2,停止timer_test1\r\n");
  b_timer_stop(&timer_test1_id);
}

3、创建并配置定时器

b_timer_create(&timer_test1_id,B_TIMER_MODE_REPEATED,timer_test1);//重复模式
b_timer_create(&timer_test2_id,B_TIMER_MODE_SINGLE_SHOT,timer_test2);//单次模式

4、启动定时器

b_timer_start(&timer_test1_id,100);//100tick执行一次
b_timer_start(&timer_test2_id,1000);//1000tick执行一次

5、效果
在这里插入图片描述
可见,执行了10次timer1回调,1次timer2回调,并且关闭了timer1。

ojbk,能用了。

仓库地址,好用点个赞呀哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子恒赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值