设计一个在裸机下使用的简单软件定时器(2):软件设计

0 前言

在RTOS中,我们经常用到软件定时器来为我们处理一些对于实时性要求不高的定时任务。在裸机开发中,我们可能也有很多需要定时执行的任务,为了优雅地执行这些定时任务,本文设计一个在裸机下使用的简单软件定时器,提供类RTOS软件定时器的功能。

1 软件设计

为了使软件定时器更加灵活,软件定时器应该具备增加、删除定时任务的功能,因此我们必须设计有增加一个定时器函数、删除一个定时器函数。在《设计一个在裸机下使用的简单软件定时器(1):框架+数据结构分析》中,我们已经知道需要一个时钟滴答获取函数和定时任务回调函数,这样分析下来,我们至少需要设计4个函数。下面是这4个函数的详细设计:

1.1 检查定时器

/**
 * @brief 检查定时器--中断内执行
 * 
 */
void check_timer(void)
{
    timert *p = timerRoot.next;
    for (; p != NULL; p = p->next)
    {
        p->tickCount++;
        if (p->tickCount >= p->cycle)
        {
            p->runFlg = 1;
            p->tickCount = 0;
        }
    }
}

检查定时器函数放到1ms的定时器中断执行,主要功能是获取时钟滴答,设置定时器任务执行标志。详细流程如下:
遍历所有定时器,将所有定时器的时钟滴答+1,如果时钟滴答大于等于设置的定时器周期,则将回调执行标志置1同时时钟滴答清0,通知主循环内的任务回调函数执行相应任务。

1.2 定时任务处理

/**
 * @brief 定时任务处理--主循环内执行
 * 
 */
void timer_task_pro(void)
{
    timert *last;
    timert *p = timerRoot.next;
    for (; p != NULL; p = p->next)
    {
        if ((p->runFlg == 1) && (p->fun != NULL))
        {
            p->fun();
            p->runFlg = 0;
            if (p->runType == 1)
            {
                p->delFlg = 1;
            }
        }
    }
    
    p = timerRoot.next;
    /* 删除无效定时器 */
    for (; p != NULL; p = p->next)
    {
        if (p->delFlg == 1)
        {
            last = p->prev;
            del_timer(p);
            p = last;
            if (p == NULL)
            {
                break;
            }
        }
    }
}

定时任务处理放到主循环内执行,主要功能是执行使能的定时任务,同时删除无效的定时器。详细流程如下:
(1)执行回调任务
遍历所有定时器,查看任务执行标志是否使能和函数指针是否有效,如果任务执行标志使能且函数指针有效则执行定时任务,执行完定时任务后将任务执行标志失能,同时判断是否属于单次执行任务,如果是单次执行任务则将定时器删除标志使能
(2)删除无效定时器
遍历所有定时器,如果发现定时器的删除标志使能则将定时器从链表删除(删除定时器要失能中断,避免链表异常操作)。

1.3 添加一个定时器

timert timerRoot = {0}; /* 定时器链表根节点 */
/**
 * @brief 添加一个软件定时器
 *
 * @param runType 执行方式:0-周期执行 1-只执行一次
 * @param cycle 执行周期
 * @param fun 回调函数
 * @param time 软件定时器
 */
void add_timer(uint8_t runType, uint32_t cycle, funp fun, timert *time)
{
    timert *p = &timerRoot;
    timert *lastp = p;
    /* 进入临界区,避免链表异常操作 */
    __disable_irq();
    for (; p != NULL; p = p->next)
    {
        /* 找到最后一个软件定时器 */
        lastp = p;
        if (lastp = time)
        {
        	return ;
        }
    }
    lastp->next = time;
    time->runType = runType;
    time->cycle = cycle;
    time->fun = fun;
    time->prev = lastp;
    time->next = NULL;
    time->tickCount = 0;
    time->runFlg = 0;
    time->delFlg = 0;
    __enable_irq();
}

添加一个定时器就是将定时器添加到链表的末尾,同时设置定时器的各项值。详细流程如下:
首先失能中断,避免链表操作被打断(任何对链表的增、删操作都应该保证不被打断),找到最后一个软件定时器,将新增的软件定时器增加到最后一个软件定时器之后,同时设置新增软件定时器的各项初值,最后使能中断。

1.4 删除一个定时器

/**
 * @brief 删除一个软件定时器
 *
 * @param time 定时器
 */
void del_timer(timert *time)
{
    
    int findFlg = 0;
    timert *p = &timerRoot;
    /* 进入临界区,避免链表异常操作 */
    __disable_irq();
    /* 判断是否还在定时器链表内 */
    for (; p != NULL; p = p->next)
    {
        if (p == time)
        {
            findFlg = 1;
            break;
        }
    }
    if (findFlg == 1)
    {
        p = time->prev;
        if (p != NULL)
        {
            p->next = time->next;
        }
        p = time->next;
        if (p != NULL)
        {
            p->prev = time->prev;
        }
        time->prev = NULL;
        time->next = NULL;
    }
    __enable_irq();
}

删除一个定时器就是将定时器从链表中删除。详细流程如下:
首先失能中断,避免链表操作被打断(任何对链表的增、删操作都应该保证不被打断),判断定时器是否还在链表内,如果还在链表内则将其删除,最后使能中断。

2 总结

(1)一个简单的软件定时器软件设计主要包括检查定时器、定时任务处理、增加一个定时器、删除一个定时器四个方面。
(2)对定时器链表的增、删操作一定不要被打断,否则将可能导致异常的链表操作。

你可能感兴趣的:(单片机开发,Linux开发,RTOS,软件定时器,裸机,定时任务,stm32)