软件定时器的基本原理

前后台系统和多任务操作系统,在简单功能上差别不大,唯一不顺手的就是前后台系统没有合适的软件定时器。封装一层软件定时器接口,对后续功能开发事半功倍。

定义结构体数组,其基础成员如下:

static uint32_t hw_interrupt_timer_ticks = 0;//timer ticks
typedef void (*timer_callback)(void);
//软件定时器最大个数
#define SW_TIMER_MAX  10

typedef struct
{
    
    
    uint32 delay;                //延迟时间,实际是当前时间+期望延时的时间后的tick
    uint32 auto_repeat;			 //执行后自动重载时间,为0表示只执行一次
    timer_callback callback;     //回调函数
} timer_struct;
timer_struct my_timer[SW_TIMER_MAX] = {
    
    0};    //定时器参数数组,最大支持10个

//启动定时器,index可按需求定为枚举类型,必须小于SW_TIMER_MAX
void timer_start(uint8 index, uint32 delay, bool loop, timer_callback callback)void timer_stop(uint8 index)//停止定时器,delay清零,回调函数置为NULL
bool timer_is_running(uint8 index)//当前定时器是否在计时运行中,判断delay是否非0

对应用提供的启动定时器接口实现可以参考如下:

static void timer_start(uint8 index, uint32 delay, bool loop, timer_callback callback)
{
    
    
    if((index >= SW_TIMER_MAX) || (delay == 0) || (callback == NULL))
    {
    
    
        return;
    }

    my_timer[index].delay = hw_timer_ticks + delay;
    if(loop)//自动重载
    {
    
    
        my_timer[index].auto_repeat = delay;
    }
    else
    {
    
    
        my_timer[index].auto_repeat = 0;
    }

    my_timer[index].callback = callback;
}

使用硬件定时中断,例如1ms中断一次,每次中断hw_timer_ticks自增一次。主程序中查询hw_timer_ticks 大于delay表示当前定时器任务超时,执行callback。这种在8位单片机系统合适,如果硬件资源有限或者系统时钟太低,可以调整硬件定时中断的精度。这样多个定时器时事件的执行只需要一个硬件定时器。

static void timer_loop(void)
{
    
    
    uint8 i = 0, n = 0, min = 0;
    for(i = 0 ; i < SW_TIMER_MAX ; i++)
    {
    
    
        if(my_timer[i].delay > 0 && my_timer[i].callback != NULL)
        {
    
    
            if(hw_timer_ticks >= my_timer[i].delay)
            {
    
    
                if(my_timer[i]->auto_repeat > 0)
                {
    
    
                    my_timer[i].delay = hw_timer_ticks + my_timer[i]->auto_repeat;
                }
                else
                {
    
    
                    my_timer[i].delay = 0; //stop
                }
                my_timer[i].callback();
            }
        }
    }
}

实现基础框架后,应用层只需要简单的操作就是实现,例如定时每60秒读取一次ADC结果,和延时30秒后关闭LED显示。

static void timer_start(READ_ADC_TIMER_ID, 60*1000, TRUE, read_adc_callback);
static void timer_start(LED_ON_TIMEROUT_TIMER_ID, 30*1000, FALSE, led_on_timeout_callback);

这种方案的缺点是my_timer数组下标index与应用绑定,即使没有全部使用,主程序每次都要查询全部数组成员;11个定时任务即使明确最多只有9个同时在运行,该接口也无法满足需求。

增加申请定时器资源的接口,在10个定时器池中获取没有被使用的,将index下标返回,再传入timer_statrt,同样的定义,可以满足10个定时任务同时运行,提高了定时器的利用率。

因为主程序轮流查询定时器状态,加上前面的回调函数执行的时间,实际执行回调时间与期望的延时时间存在一定误差。在硬件资源充足的情况下,可以将数组改为链表,按delay从小到大排序,新启动的定时器按顺序插入到合适的位置,这样主程序查询速度会加快,在最近的软件定时器,也就是第一个软件定时器超时前可以休眠省电。或者分为多个数组映射不同的优先级和时间精度,这样提高回调函数执行的时间准确度。

工欲善其事,必先利其器,基础件完美才能支撑复杂多变的应用层。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/chengjunchengjun/article/details/108821006