(学习日记)2024.04.12:UCOSIII第四十节:软件定时器函数接口讲解

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

五十四、UCOSIII:软件定时器函数接口讲解

1、创建软件定时器函数OSTmrCreate()

软件定时器也是内核对象,与消息队列、信号量等内核对象一样,都是需要创建之后才能使用的资源,我们在创建的时候需要指定定时器延时初始值dly、 定时器周期、定时器工作模式、回调函数等。
每个软件定时器只需少许的RAM空间,理论上μC/OS支持无限多个软件定时器,只要RAM足够即可。

创建软件定时器函数OSTmrCreate()源码具体如下:

void  OSTmrCreate (OS_TMR               *p_tmr,          //定时器控制块指针
        CPU_CHAR            *p_name,   //命名定时器,有助于调试
        OS_TICK             dly,            //初始定时节拍数
        OS_TICK             period,     //周期定时重载节拍数
        OS_OPT              opt,            //选项
        OS_TMR_CALLBACK_PTR  p_callback,  //定时到期时的回调函数
        void*p_callback_arg, //传给回调函数的参数
        OS_ERR  *p_err)          //返回错误类型
{
    CPU_SR_ALLOC();
    //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和定义一个局部变
    //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR)
    //,开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return;                         //返回,不执行定时操作
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508//如果启用(默认禁用)了安全关键
    //如果是在调用 OSSafetyCriticalStart()后创建该定时器
    if (OSSafetyCriticalStartFlag == DEF_TRUE)
    {
        *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为“非法创建内核对象”
        return;                                  //返回,不执行定时操作
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
    //如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)    //如果该函数是在中断中被调用
    {
        *p_err = OS_ERR_TMR_ISR;                 //错误类型为“在中断函数中定时”
        return;                                 //返回,不执行定时操作
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测
    if (p_tmr == (OS_TMR *)0)                       //如果参数 p_tmr 为空
    {
        *p_err = OS_ERR_OBJ_PTR_NULL;         //错误类型为“定时器对象为空”
        return;                                     //返回,不执行定时操作
    }

    switch (opt)                            //根据延时选项参数 opt 分类操作
    {
        case OS_OPT_TMR_PERIODIC:                   //如果选择周期性定时
        if (period == (OS_TICK)0)              //如果周期重载实参为0
        {
            *p_err = OS_ERR_TMR_INVALID_PERIOD; //错误类型为“周期重载实参无效”
            return;                            //返回,不执行定时操作
        }
        break;

        case OS_OPT_TMR_ONE_SHOT:                   //如果选择一次性定时
        if (dly == (OS_TICK)0)                 //如果定时初始实参为0
        {
            *p_err = OS_ERR_TMR_INVALID_DLY;    //错误类型为“定时初始实参无效”
            return;                            //返回,不执行定时操作
        }
        break;

        default:                                    //如果选项超出预期
        *p_err = OS_ERR_OPT_INVALID;            //错误类型为“选项非法”
        return;                                //返回,不执行定时操作
    }
#endif

    OS_CRITICAL_ENTER();         //进入临界段,初始化定时器指标
    p_tmr->State          = (OS_STATE           )OS_TMR_STATE_STOPPED;
    p_tmr->Type           = (OS_OBJ_TYPE        )OS_OBJ_TYPE_TMR;
    p_tmr->NamePtr        = (CPU_CHAR          *)p_name;
    p_tmr->Dly            = (OS_TICK            )dly;
    p_tmr->Match          = (OS_TICK            )0;
    p_tmr->Remain         = (OS_TICK            )0;
    p_tmr->Period         = (OS_TICK            )period;
    p_tmr->Opt            = (OS_OPT             )opt;
    p_tmr->CallbackPtr    = (OS_TMR_CALLBACK_PTR)p_callback;
    p_tmr->CallbackPtrArg = (void              *)p_callback_arg;
    p_tmr->NextPtr        = (OS_TMR            *)0;
    p_tmr->PrevPtr        = (OS_TMR            *)0;

#if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量
    OS_TmrDbgListAdd(p_tmr);     //将该定时添加到定时器双向调试链表
#endif
    OSTmrQty++;                  //定时器个数加1

    OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度)
    *p_err = OS_ERR_NONE;         //错误类型为“无错误”
}

定时器创建函数比较简单,主要是根据用户指定的参数将定时器控制块进行相关初始化,并且定时器状态会被设置为OS_TMR_STATE_STOPPED,具体见源码注释即可。

该函数的使用也是很简单,具体如下:

OS_ERR      err;
OS_TMR      my_tmr;   //声明软件定时器对象

/* 创建软件定时器 */
OSTmrCreate ((OS_TMR              *)&my_tmr,             //软件定时器对象
            (CPU_CHAR            *)"MySoftTimer",       //命名软件定时器
            (OS_TICK              )10,
            //定时器初始值,依10Hz时基计算,即为1s
            (OS_TICK              )10,
            //定时器周期重载值,依10Hz时基计算,即为1s
            (OS_OPT               )OS_OPT_TMR_PERIODIC, //周期性定时
            (OS_TMR_CALLBACK_PTR  )TmrCallback,         //回调函数
            (void                *)"Timer Over!",    //传递实参给回调函数
            (OS_ERR              *)err);                //返回错误类型

2、启动软件定时器函数OSTmrStart()

我们知道,在系统初始化的时候,系统会帮我们自动创建一个软件定时器任务,在这个任务中,如果暂时没有运行中的定时器, 任务会进入阻塞态等待定时器任务节拍的信号量。

1. OSTmrStart()

我们在创建一个软件定时器之后,如果没有启动它,该定时器就不会被添加到软件定时器列表中, 那么在定时器任务就不会运行该定时器,而OSTmrStart()函数就是将已经创建的软件定时器添加到定时器列表中, 这样被创建的定时器就会被系统运行,其源码具体如下:

CPU_BOOLEAN  OSTmrStart (OS_TMR  *p_tmr,  (1)       //定时器控制块指针
                        OS_ERR  *p_err)  (2)        //返回错误类型
{
    OS_ERR       err;
    CPU_BOOLEAN  success; //暂存函数执行结果



#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return (DEF_FALSE);             //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
    //如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)   //如果该函数是在中断中被调用
    {
        *p_err = OS_ERR_TMR_ISR;                //错误类型为“在中断函数中定时”
        return (DEF_FALSE);                    //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测
    (p_tmr == (OS_TMR *)0)        //如果启用 p_tmr 的实参为空
    {
        *p_err = OS_ERR_TMR_INVALID;  //错误类型为“无效的定时器”
        return (DEF_FALSE);          //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用(默认启用)了对象类型检测
    if (p_tmr->Type != OS_OBJ_TYPE_TMR)   //如果该定时器的对象类型有误
    {
        *p_err = OS_ERR_OBJ_TYPE;          //错误类型为“对象类型错误”
        return (DEF_FALSE);               //返回 DEF_FALSE,不继续执行
    }
#endif

    OSSchedLock(&err);                           //锁住调度器
    switch (p_tmr->State)             (3)//根据定时器的状态分类处理
    {
        case OS_TMR_STATE_RUNNING:       //如果定时器正在运行,则重启
        OS_TmrUnlink(p_tmr);          (5)//从定时器列表里移除该定时器
        OS_TmrLink(p_tmr, OS_OPT_LINK_DLY);(4)//将该定时器重新插入定时器列表
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_NONE;               //错误类型为“无错误”
        success = DEF_TRUE;                  //执行结果暂为 DEF_TRUE
        break;

        case OS_TMR_STATE_STOPPED:                //如果定时器已被停止,则开启
        case OS_TMR_STATE_COMPLETED:      (6)//如果定时器已完成了,则开启
        OS_TmrLink(p_tmr, OS_OPT_LINK_DLY);  //将该定时器重新插入定时器列表
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_NONE;               //错误类型为“无错误”
        success = DEF_TRUE;                  //执行结果暂为 DEF_TRUE
        break;

        case OS_TMR_STATE_UNUSED:         (7)//如果定时器未被创建
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_TMR_INACTIVE;       //错误类型为“定时器未激活”
        success = DEF_FALSE;                 //执行结果暂为 DEF_FALSE
        break;

        default:                     (8)//如果定时器的状态超出预期
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err = OS_ERR_TMR_INVALID_STATE;    //错误类型为“定时器无效”
        success = DEF_FALSE;                 //执行结果暂为 DEF_FALSE
        break;
    }
    return (success);                             //返回执行结果
}
  • (1):定时器控制块指针,指向要启动的软件定时器。
  • (2):保存返回错误类型。
  • (3):锁住调度器,因为接下来的操作是需要操作定时器列表的, 此时应该锁定调度器,不被其他任务打扰,然后根据定时器的状态分类处理。
  • (4):在然后移除之后需要将软件定时器重新按照周期插入定时器列表中, 调用OS_TmrLink()函数即可将软件定时器插入定时器列表
  • (5):如果定时器正在运行,则重启, 首先调用OS_TmrUnlink()函数将运行中的定时器从原本的定时器列表中移除
  • (6):如果定时器已创建完成了,则开启即可,开启也是将定时器按照周期插入定时器列表中。
  • (7):如果定时器未被创建,那是不可能开启定时器的, 使用会返回错误类型为“定时器未激活”的错误代码,用户需要先创建软件定时器再来开启。
  • (8):如果定时器的状态超出预期,返回错误类型为“定时器无效”的错误代码。

2. OS_TmrLink()

调用OS_TmrLink()函数即可将软件定时器插入定时器列表

void  OS_TmrLink (OS_TMR  *p_tmr,  (1)      //定时器控制块指针
                OS_OPT   opt)    (2)        //选项
{
    OS_TMR_SPOKE     *p_spoke;
    OS_TMR           *p_tmr0;
    OS_TMR           *p_tmr1;
    OS_TMR_SPOKE_IX   spoke;

    //重置定时器为运行状态
    p_tmr->State = OS_TMR_STATE_RUNNING;

    if (opt == OS_OPT_LINK_PERIODIC)
    {
        //如果定时器是再次插入,匹配时间加个周期重载值
        p_tmr->Match = p_tmr->Period + OSTmrTickCtr;(3)
    }
    else
    {
        //如果定时器是首次插入
        if (p_tmr->Dly == (OS_TICK)0)
        {
            //如果定时器的 Dly = 0,匹配时间加个周期重载值
            p_tmr->Match = p_tmr->Period + OSTmrTickCtr;(4)
        }
        else
        {
            //如果定时器的 Dly != 0,匹配时间加个 Dly
            p_tmr->Match = p_tmr->Dly    + OSTmrTickCtr;(5)
        }
    }

    //通过哈希算法决定将该定时器插入定时器轮的哪个列表。
    spoke  = (OS_TMR_SPOKE_IX)(p_tmr->Match % OSCfg_TmrWheelSize);(6)
    p_spoke = &OSCfg_TmrWheel[spoke];

    if (p_spoke->FirstPtr ==  (OS_TMR *)0)(7)
    {
        //如果列表为空,直接将该定时器作为列表的第一个元素。
        p_tmr->NextPtr      = (OS_TMR *)0;
        p_tmr->PrevPtr      = (OS_TMR *)0;
        p_spoke->FirstPtr   = p_tmr;
        p_spoke->NbrEntries = 1u;
    }
    else
    {
        //如果列表非空,算出定时器 p_tmr 的剩余时间
        p_tmr->Remain  = p_tmr->Match - OSTmrTickCtr;       (8)
        //取列表的首个元素到 p_tmr1
        p_tmr1         = p_spoke->FirstPtr; (9)
        while (p_tmr1 != (OS_TMR *)0)
        {
            //如果 p_tmr1 非空,算出 p_tmr1 的剩余时间
            p_tmr1->Remain = p_tmr1->Match
                            - OSTmrTickCtr; (10)
            if (p_tmr->Remain > p_tmr1->Remain)
            {
                //如果 p_tmr 的剩余时间大于 p_tmr1 的
                if (p_tmr1->NextPtr  != (OS_TMR *)0)
                {
                    //如果 p_tmr1后面非空,取p_tmr1后一个定时器为新的p_tmr1进行下一次循环
                    p_tmr1            = p_tmr1->NextPtr;(11)
                }
                else
                {
                    //如果 p_tmr1 后面为空,将 p_tmr 插到 p_tmr1 的后面,结束循环
                    p_tmr->NextPtr    = (OS_TMR *)0;
                    p_tmr->PrevPtr    =  p_tmr1;
                    p_tmr1->NextPtr   =  p_tmr;
                    p_tmr1            = (OS_TMR *)0;        (12)
                }
            }
            else
            {
                //如果 p_tmr 的剩余时间不大于 p_tmr1 的,
                if (p_tmr1->PrevPtr == (OS_TMR *)0) (13)
                {
                    //将 p_tmr 插入 p_tmr1 的前一个,结束循环。
                    p_tmr->PrevPtr    = (OS_TMR *)0;
                    p_tmr->NextPtr    = p_tmr1;
                    p_tmr1->PrevPtr   = p_tmr;
                    p_spoke->FirstPtr = p_tmr;
                }
                else
                {
                    p_tmr0            = p_tmr1->PrevPtr;
                    p_tmr->PrevPtr    = p_tmr0;
                    p_tmr->NextPtr    = p_tmr1;
                    p_tmr0->NextPtr   = p_tmr;
                    p_tmr1->PrevPtr   = p_tmr;              (14)
                }
                p_tmr1 = (OS_TMR *)0;
            }
        }
        //列表元素成员数加1
        p_spoke->NbrEntries++;                      (15)
    }
    if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries)
    {
        //更新列表成员数最大值历史记录
        p_spoke->NbrEntriesMax = p_spoke->NbrEntries;(16)
    }
}
  • (1):定时器控制块指针。
  • (2):插入定时器列表中的选项。
  • (3):重置定时器为运行状态,如果定时器是再次插入, 肯定是周期性定时器,延时时间为 Period,定时器的匹配时间(唤醒时间)Match等于周期重载值Period加上当前的定时器计时时间。
  • (4):如果定时器是首次插入, 如果定时器的延时时间 Dly 等于 0,定时器的匹配时间Match也等于周期重载值加上当前的定时器计时时间。
  • (5):而如果定时器的 Dly 不等于 0,定时器的匹配时间Match则等于Dly的值加上当前的定时器计时时间。
  • (6):通过哈希算法决定将该定时器插入定时器的哪个列表, 这与第一部分讲解的时基列表很像。既然是哈希算法,开始插入的时候也要根据余数进行操作, 根据软件定时器的到达时间(匹配时间或者称为唤醒时间也可以)对 OSCfg_TmrWheelSize 的余数取出OSCfg_TmrWheel[OS_CFG_TMR_WHEEL_SIZE]中对应的定时器列表记录,然后将定时器插入对应的列表中。
  • (7):如果定时器列表为空,直接将该定时器作为列表的第一个元素。
  • (8):如果列表非空,算出定时器 p_tmr 的剩余时间,按照即将唤醒的时间插入定时器列表中。
  • (9):取列表的首个元素到 p_tmr1,遍历定时器列表。
  • (10):如果 p_tmr1 非空,算出 p_tmr1 的剩余时间, 对比p_tmr与p_tmr1的时间,按照升序进行插入列表中。
  • (11):如果 p_tmr 的剩余时间大于 p_tmr1 的, 取p_tmr1后一个定时器作为新的p_tmr1进行下一次循环,直到p_tmr找到合适的地方就插入定时器列表。
  • (12):如果 p_tmr1 后面为空,将 p_tmr 插到 p_tmr1 的后面,结束循环。这些插入操作都是双向链表的插入操作,此处就不重复赘述。
  • (13):如果 p_tmr 的剩余时间不大于 p_tmr1 的, 并且p_tmr1的前一个定时器为空,就直接将 p_tmr 插入 p_tmr1 的前一个位置,并且软件定时器列表的第一个定时器就是p_tmr。
  • (14):而如果的上一个不为空,将 p_tmr 插入 p_tmr1 的前一个位置。
  • (15):对应定时器列表元素成员数加1。
  • (16):更新列表成员数最大值历史记录。

3、软件定时器列表管理

有些情况下,当系统中有多个软件定时器的时候,μC/OS可能要维护上百个定时器。
使用定时器列表会大大降低更新定时器列表所占用的CPU时间, 一个一个检测是否到期效率很低,有没有什么办法让系统快速查找到到期的软件定时器?

1. 软件定时器列表

μC/OS对软件定时器列表的管理就跟时间节拍一样, 采用哈希算法。
OS_TmrLink将不同的定时器变量根据其对 OSCfg_TmrWheelSize余数的不同插入数组OSCfg_TmrWheel[OS_CFG_TMR_WHEEL_SIZE]中去, μC/OS的软件定时器列表示意图具体见图
在这里插入图片描述
定时器列表中包含了OS_CFG_TMR_WHEEL_SIZE条记录,该值是一个宏定义,由用户指定,在os_cfg_app.h文件中。
能记录定时器的多少仅限于处理器的RAM 空间,推荐的设置值为定时器数 /4
定时器列表的每个记录都由 3部分组成:NbrEntriesMax表明该记录中有多少个定时器; NbrEntriesMax表明该记录中最大时存放了多少个定时器;FirstPtr指向当前记录的定时器链表。

可能这样子讲述的不够清晰,下面举个例子来讲述软件定时器采用哈希算法插入对应的定时器列表中的过程。
在这里插入图片描述
如上图所示,我们先假定此时的定时器列表是空的,设置的宏定义OS_CFG_TMR_WHEEL_SIZE为 9, 当前的OSTmrTickCtr为12
我们调用OSTmrStart()函数将定时器插入定时器列表。

假定定时器创建时dly的值为1, 并且这个任务是单次定时模式。
因为OSTmrTickCtr的值为12,定时器的定时值为1,那么在插入定时器列表的时候, 定时器的唤醒时间Match为13(Match = Dly+OSTmrTickCtr),经过哈希算法,得到spoke = 4, 该定时器会被放入定时器会被插入OSCfg_TmrWheel[4]列表中,因为当前定时器列表是空的, OS_TMR会被放在队列中的首位置(OSCfg_TmrWheel[4]中成员变量FirstPtr将指向这个OS_TMR), 并且索引4的计数值加一(OSCfg_TmrWheel[4]的成员变量NbrEntries为 1)。 定时器的匹配值Match被放在OS_TMR的Match成员变量中。
因为新插入的定时器是索引4的唯一一个定时器,所有定时器的NextPtr和PrevPtr都指向NULL(也就是0)。

如果系统此时再插入一个周期Period为10的定时器定时器,定时器的唤醒时间Match为22(Match = Period + OSTmrTickCtr), 那么经过哈希算法,得到spoke = 4, 该定时器会被放入定时器会被插入OSCfg_TmrWheel[4]列表中,但是由于OSCfg_TmrWheel[4]列表已有一个软件定时器, 那么第二个软件定时器会根据Remain的值按照升序进行插入操作,插入完成示意图具体见图
在这里插入图片描述

2. OS_TmrUnlink()

OS_TmrUnlink()函数将运行中的定时器从原本的定时器列表中移除
OS_TmrUnlink()源码具体如下:

void  OS_TmrUnlink (OS_TMR  *p_tmr)         (1)     //定时器控制块指针
{
    OS_TMR_SPOKE    *p_spoke;
    OS_TMR          *p_tmr1;
    OS_TMR          *p_tmr2;
    OS_TMR_SPOKE_IX  spoke;



    spoke   = (OS_TMR_SPOKE_IX)(p_tmr->Match % OSCfg_TmrWheelSize);
    //与插入时一样,通过哈希算法找出
    p_spoke = &OSCfg_TmrWheel[spoke];       (2)
    //该定时器在定时器的哪个列表。

    if (p_spoke->FirstPtr == p_tmr)    (3)//如果 p_tmr 是列表的首个元素
    {
        //取 p_tmr 后一个元素为 p_tmr1(可能为空)
        p_tmr1            = (OS_TMR *)p_tmr->NextPtr;
        p_spoke->FirstPtr = (OS_TMR *)p_tmr1;         //表首改为 p_tmr1
        if (p_tmr1 != (OS_TMR *)0)                  //如果 p_tmr1 确定非空
        {
            p_tmr1->PrevPtr = (OS_TMR *)0;           //p_tmr1 的前面清空
        }
    }
    else(4)//如果 p_tmr不是列表的首个元素
    {
        //将 p_tmr 从列表移除,并将p_tmr前后的两个元素连接在一起.
        p_tmr1          = (OS_TMR *)p_tmr->PrevPtr;

        p_tmr2          = (OS_TMR *)p_tmr->NextPtr;
        p_tmr1->NextPtr = p_tmr2;
        if (p_tmr2 != (OS_TMR *)0)
        {
            p_tmr2->PrevPtr = (OS_TMR *)p_tmr1;
        }
    }
    p_tmr->State   = OS_TMR_STATE_STOPPED;   //复位 p_tmr 的指标
    p_tmr->NextPtr = (OS_TMR *)0;
    p_tmr->PrevPtr = (OS_TMR *)0;
    p_spoke->NbrEntries--;           (5)//列表元素成员减1
}
  • (1):定时器控制块指针,指向要移除的定时器。
  • (2):与插入时一样,通过哈希算法找出该定时器在定时器的哪个列表。
  • (3):如果 p_tmr 是列表的首个元素, 取 p_tmr 后一个元素为 p_tmr1(可能为空),软件定时器列表头部的定时器改为 p_tmr1,如果 p_tmr1 确定非空, 那就将p_tmr删除(p_tmr1的前一个定时器就是p_tmr)。
  • (4):如果 p_tmr不是列表的首个元素,那就将 p_tmr 从列表移除, 并将p_tmr前后的两个元素连接在一起,这其实是双向链表的操作。
  • (5):清除定时器p_tmr 的相关信息,定时器列表元素成员减1。

至此,软件定时器的启动函数就讲解完毕,我们在创建一个软件定时器后可以调用OSTmrStart()函数启动它, 软件定时器启动函数的使用实例具体如下:

void  OS_TmrUnlink (OS_TMR  *p_tmr)         (1)     //定时器控制块指针
{
    OS_TMR_SPOKE    *p_spoke;
    OS_TMR          *p_tmr1;
    OS_TMR          *p_tmr2;
    OS_TMR_SPOKE_IX  spoke;



    spoke   = (OS_TMR_SPOKE_IX)(p_tmr->Match % OSCfg_TmrWheelSize);
    //与插入时一样,通过哈希算法找出
    p_spoke = &OSCfg_TmrWheel[spoke];       (2)
    //该定时器在定时器的哪个列表。

    if (p_spoke->FirstPtr == p_tmr)    (3)//如果 p_tmr 是列表的首个元素
    {
        //取 p_tmr 后一个元素为 p_tmr1(可能为空)
        p_tmr1            = (OS_TMR *)p_tmr->NextPtr;
        p_spoke->FirstPtr = (OS_TMR *)p_tmr1;         //表首改为 p_tmr1
        if (p_tmr1 != (OS_TMR *)0)                  //如果 p_tmr1 确定非空
        {
            p_tmr1->PrevPtr = (OS_TMR *)0;           //p_tmr1 的前面清空
        }
    }
    else(4)//如果 p_tmr不是列表的首个元素
    {
    //将 p_tmr 从列表移除,并将p_tmr前后的两个元素连接在一起.
        p_tmr1          = (OS_TMR *)p_tmr->PrevPtr;

        p_tmr2          = (OS_TMR *)p_tmr->NextPtr;
        p_tmr1->NextPtr = p_tmr2;
        if (p_tmr2 != (OS_TMR *)0)
        {
            p_tmr2->PrevPtr = (OS_TMR *)p_tmr1;
        }
    }
    p_tmr->State   = OS_TMR_STATE_STOPPED;   //复位 p_tmr 的指标
    p_tmr->NextPtr = (OS_TMR *)0;
    p_tmr->PrevPtr = (OS_TMR *)0;
    p_spoke->NbrEntries--;           (5)//列表元素成员减1
}

4、 停止定时器函数OSTmrStop()

OSTmrStop()函数用于停止一个软件定时器。软件定时器被停掉之后可以调用OSTmrStart()函数重启,但是重启之后定时器是从头计时, 而不是接着上次停止的时刻继续计时。
OSTmrStop()源码具体如下:

CPU_BOOLEAN  OSTmrStop (OS_TMR  *p_tmr,       (1)//定时器控制块指针
                        OS_OPT   opt,        (2)//选项
void    *p_callback_arg, (3)//传给回调函数的新参数
                        OS_ERR  *p_err)    (4)//返回错误类型
{
    OS_TMR_CALLBACK_PTR  p_fnct;
    OS_ERR               err;
    CPU_BOOLEAN          success;  //暂存函数执行结果



#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)            //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION();  //执行安全检测异常函数
        return (DEF_FALSE);              //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
//如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)   //如果该函数是在中断中被调用
    {
        *p_err = OS_ERR_TMR_ISR;                //错误类型为“在中断函数中定时”
        return (DEF_FALSE);                    //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测
    if (p_tmr == (OS_TMR *)0)       //如果启用 p_tmr 的实参为空
    {
        *p_err = OS_ERR_TMR_INVALID; //错误类型为“无效的定时器”
        return (DEF_FALSE);         //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用(默认启用)了对象类型检测
    if (p_tmr->Type != OS_OBJ_TYPE_TMR)    //如果该定时器的对象类型有误
    {
        *p_err = OS_ERR_OBJ_TYPE;           //错误类型为“对象类型错误”
        return (DEF_FALSE);                //返回 DEF_FALSE,不继续执行
    }
#endif

    OSSchedLock(&err);                        //锁住调度器
    switch (p_tmr->State)           (5)
    {
        //根据定时器的状态分类处理
        case OS_TMR_STATE_RUNNING:          (6)
        //如果定时器正在运行
        OS_TmrUnlink(p_tmr);
        //从定时器轮列表里移除该定时器
        *p_err = OS_ERR_NONE;
        //错误类型为“无错误”
        switch (opt)
        {
            //根据选项分类处理
            case OS_OPT_TMR_CALLBACK:       (7)
            //执行回调函数,使用创建定时器时的实参
            p_fnct = p_tmr->CallbackPtr;
            //取定时器的回调函数
            if (p_fnct != (OS_TMR_CALLBACK_PTR)0)
            {
                //如果回调函数存在
                (*p_fnct)((void *)p_tmr, p_tmr->CallbackPtrArg);
                //使用创建定时器时的实参执行回调函数
            }
            else
            {
                //如果回调函数不存在
                *p_err = OS_ERR_TMR_NO_CALLBACK;(8)
                //错误类型为“定时器没有回调函数”
            }
            break;

            case OS_OPT_TMR_CALLBACK_ARG:           (9)
            //执行回调函数,使用 p_callback_arg 作为实参
            p_fnct = p_tmr->CallbackPtr;
            //取定时器的回调函数
            if (p_fnct != (OS_TMR_CALLBACK_PTR)0)
            {
                //如果回调函数存在
                (*p_fnct)((void *)p_tmr, p_callback_arg);
                //使用 p_callback_arg 作为实参执行回调函数
            }
            else
            {
                //如果回调函数不存在
                *p_err = OS_ERR_TMR_NO_CALLBACK;
                //错误类型为“定时器没有回调函数”
            }
            break;

            case OS_OPT_TMR_NONE:           //只需停掉定时器
            break;

            default:               (10)//情况超出预期
            OSSchedUnlock(&err);        //解锁调度器
            *p_err = OS_ERR_OPT_INVALID; //错误类型为“选项无效”
            return (DEF_FALSE);         //返回 DEF_FALSE,不继续执行
        }
        OSSchedUnlock(&err);
        success = DEF_TRUE;
        break;

        case OS_TMR_STATE_COMPLETED:        (11)
        //如果定时器已完成第一次定时
        case OS_TMR_STATE_STOPPED:
        //如果定时器已被停止
        OSSchedUnlock(&err);               //解锁调度器
        *p_err   = OS_ERR_TMR_STOPPED;      //错误类型为“定时器已被停止”
        success = DEF_TRUE;                //执行结果暂为 DEF_TRUE
        break;

        case OS_TMR_STATE_UNUSED:           (12)
        //如果该定时器未被创建过
        OSSchedUnlock(&err);               //解锁调度器
        *p_err   = OS_ERR_TMR_INACTIVE;     //错误类型为“定时器未激活”
        success = DEF_FALSE;               //执行结果暂为 DEF_FALSE
        break;

        default:                           (13)//如果定时器状态超出预期
        OSSchedUnlock(&err);               //解锁调度器
        *p_err   = OS_ERR_TMR_INVALID_STATE;//错误类型为“定时器状态非法”
        success = DEF_FALSE;               //执行结果暂为 DEF_FALSE
        break;
    }
    return (success);                           //返回执行结果
}
  • (1):定时器控制块指针,指向要停止的定时器。
  • (2):停止的选项。
  • (3):传给回调函数的新参数。
  • (4):保存返回的错误类型。
  • (5):锁定调度器,然后根据定时器的状态分类处理。
  • (6):如果定时器正在运行,那么就调用OS_TmrUnlink()函数将该定时器从定时器列表中移除。
  • (7):根据选项分类处理,如果需要执行回调函数, 并且使用创建定时器时的实参,那就取定时器的回调函数,如果回调函数存在,就根据创建定时器指定的实参执行回调函数。
  • (8):如果回调函数不存在,返回错误类型为“定时器没有回调函数”的错误代码。
  • (9):如果需要执行回调函数,但是却是使用 p_callback_arg 作为实参, 先取定时器的回调函数,如果回调函数存在就将p_callback_arg 作为实参传递进去并且执行回调函数, 否则就返回错误类型为“定时器没有回调函数”的错误代码。
  • (10):如果情况超出预期,返回错误类型为“选项无效”的错误代码。
  • (11):如果定时器已完成第一次定时或者如果定时器已被停止,返回错误类型为“定时器已被停止”的错误代码。
  • (12):如果该定时器未被创建过,返回错误类型为“定时器未激活”的错误代码。
  • (13):如果定时器状态超出预期,返回错误类型为“定时器状态非法”的错误代码。

软件定时器停止函数的使用很简单,在使用该函数前请确认定时器已经开启,停止后的软件定时器可以通过调用定时器启动函数来重新启动, OSTmrStop()函数的使用实例具体如下:

OS_ERR      err;
OS_TMR      my_tmr;   //声明软件定时器对象
OSTmrStop ((OS_TMR   *)&my_tmr,          //定时器控制块指针
        (OS_OPT     )OS_OPT_TMR_NONE,  //选项
        (void      *)"Timer Over!", //传给回调函数的新参数
        (OS_ERR    *)err);          //返回错误类型

5、删除软件定时器函数OSTmrDel()

OSTmrDel()用于删除一个已经被创建成功的软件定时器,删除之后就无法使用该定时器,并且定时器相应的信息也会被系清空。
要想使用OSTmrDel()函数必须在头文件os_cfg.h中把宏OS_CFG_TMR_DEL_EN定义为1,该函数的源码具体如下

#if OS_CFG_TMR_DEL_EN > 0u//如果启用了 OSTmrDel() 函数
CPU_BOOLEAN  OSTmrDel (OS_TMR  *p_tmr,      (1)     //定时器控制块指针
                    OS_ERR  *p_err)         (2)     //返回错误类型
{
    OS_ERR       err;
    CPU_BOOLEAN  success;  //暂存函数执行结果



#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return (DEF_FALSE);             //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
//如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)    //如果该函数是在中断中被调用
    {
        *p_err  = OS_ERR_TMR_ISR;                //错误类型为“在中断函数中定时”
        return (DEF_FALSE);                     //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测
    if (p_tmr == (OS_TMR *)0)       //如果启用 p_tmr 的实参为空
    {
        *p_err = OS_ERR_TMR_INVALID; //错误类型为“无效的定时器”
        return (DEF_FALSE);         //返回 DEF_FALSE,不继续执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用(默认启用)了对象类型检测
    if (p_tmr->Type != OS_OBJ_TYPE_TMR)    //如果该定时器的对象类型有误
    {
        *p_err = OS_ERR_OBJ_TYPE;           //错误类型为“对象类型错误”
        return (DEF_FALSE);                //返回 DEF_FALSE,不继续执行
    }
#endif

    OSSchedLock(&err);          //锁住调度器
#if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量
    OS_TmrDbgListRemove(p_tmr); //将该定时从定时器双向调试链表移除
#endif
    OSTmrQty--;                             (3)     //定时器个数减1

    switch (p_tmr->State)                         //根据定时器的状态分类处理
    {
        case OS_TMR_STATE_RUNNING:                //如果定时器正在运行
        OS_TmrUnlink(p_tmr);        (4)//从当前定时器列表列表移除定时器
        OS_TmrClr(p_tmr);           (5)//复位定时器的指标
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_NONE;               //错误类型为“无错误”
        success = DEF_TRUE;                  //执行结果暂为 DEF_TRUE
        break;

        case OS_TMR_STATE_STOPPED:                //如果定时器已被停止
        case OS_TMR_STATE_COMPLETED:              //如果定时器已完成第一次定时
        OS_TmrClr(p_tmr);                    //复位定时器的指标
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_NONE;               //错误类型为“无错误”
        success = DEF_TRUE;                  //执行结果暂为 DEF_TRUE
        break;

        case OS_TMR_STATE_UNUSED:                 //如果定时器已被删除
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_TMR_INACTIVE;       //错误类型为“定时器未激活”
        success = DEF_FALSE;                 //执行结果暂为 DEF_FALSE
        break;

        default:                                  //如果定时器的状态超出预期
        OSSchedUnlock(&err);                 //解锁调度器
        *p_err   = OS_ERR_TMR_INVALID_STATE;  //错误类型为“定时器无效”
        success = DEF_FALSE;                 //执行结果暂为 DEF_FALSE
        break;
    }
    return (success);                             //返回执行结果
}
#endif
  • (1):定时器控制块指针,指向要删除的软件定时器。
  • (2):用于保存返回的错误类型。
  • (3):如果程序能执行到这里,说明能正常删除软件定时器,将系统的软件定时器个数减一。
  • (4):调用OS_TmrUnlink()函数从当前定时器列表移除定时器。
  • (5):OS_TmrClr()清除软件定时器控制块的相关信息,表示定时器删除完成。

软件定时器的删除函数使用很简单,具体如下:

OS_ERR      err;
OS_TMR      my_tmr;   //声明软件定时器对象

OSTmrDel ((OS_TMR   *)&my_tmr, //软件定时器对象
(OS_ERR   *)err);    //返回错误类型

最近更新

  1. TCP协议是安全的吗?

    2024-04-12 07:50:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-12 07:50:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-12 07:50:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-12 07:50:04       20 阅读

热门阅读

  1. SQL Server数据库常用语句

    2024-04-12 07:50:04       15 阅读
  2. Qt多线程的学习

    2024-04-12 07:50:04       16 阅读
  3. 初识 QT

    初识 QT

    2024-04-12 07:50:04      13 阅读
  4. Liunx和Windows中重启MySql

    2024-04-12 07:50:04       16 阅读
  5. 浅谈-“cin 输入弊端”

    2024-04-12 07:50:04       22 阅读