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

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


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


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

2024.04.12:UCOSIII第四十节:软件定时器函数接口讲解

  • 五十四、UCOSIII:软件定时器函数接口讲解
    • 1、创建软件定时器函数OSTmrCreate()
    • 2、启动软件定时器函数OSTmrStart()
      • 1. OSTmrStart()
      • 2. OS_TmrLink()
    • 3、软件定时器列表管理
      • 1. 软件定时器列表
      • 2. OS_TmrUnlink()
    • 4、 停止定时器函数OSTmrStop()
    • 5、删除软件定时器函数OSTmrDel()

五十四、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);    //返回错误类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/535474.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

编程技巧(五) mysql中查询将行转为列逗号隔开拼接

让清单成为一种习惯 互联网时代的变革,不再是简单的开发部署上线,持续,正确,安全地把事情做好尤其重要;把事情做好的前提是做一个可量化可执行的清单,让工程师就可以操作的清单而不是专家才能操作: 设定检查点 根据节点执行检查程序操作确认或边读边做 二者选其一不要太…

CSS导读 (元素显示模式)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 三、CSS的元素显示模式 3.1 什么是元素显示模式 3.2 块元素 3.3 行内元素 3.4 行内块元素 3.5 元素…

马斯克预言:下一代Grok 3模型将需10万Nvidia H100 GPU进行训练|TodayAI

特斯拉首席执行官兼xAI创始人埃隆马斯克对人工通用智能&#xff08;AGI&#xff09;的发展做出了一些大胆的预测&#xff0c;并讨论了AI行业面临的挑战。他预测&#xff0c;AGI可能在明年或2026年之前超越人类智能&#xff0c;但训练AGI将需要极大数量的处理器&#xff0c;进而…

priority_queue的使用以及模拟实现

前言 上一期我们对stack和queue进行了使用的介绍&#xff0c;以及对底层的模拟实现&#xff01;以及容器适配器做了介绍&#xff0c;本期我们在来介绍一个容器适配器priority_queue&#xff01; 本期内容介绍 priority_queue的使用 仿函数介绍 priority_queue的模拟实现 什么…

2024年人工智能路线图

今天分享的是人工智能专题系列深度研究报告&#xff1a;《人工智能专题&#xff1a;2024年人工智能路线图》。 秘书制定部门的人工智能战略优先事项和政策&#xff0c;并且是关键的对话者与私营部门、联邦机构、州官员&#xff0c;以及主要的国际同行。这部长在白宫人力资源委员…

C/C++基础----运算符

算数运算符 运算符 描述 例子 两个数字相加 两个变量a b得到两个变量之和 - 两个数字相减 - * 两个数字相乘 - / 两个数字相除 - % 两个数字相除后取余数 8 % 3 2 -- 一个数字递减 变量a&#xff1a;a-- 、--a 一个数字递增 变量a: a 、 a 其中递…

如何在 7 天内掌握C++?

大家好&#xff0c;我是小康&#xff0c;今天我们来聊下如何快速学习 C 语言。 本篇文章适合于有 C 语言编程基础的小伙伴们&#xff0c;如果还没有学习过 C&#xff0c;请看这篇文章先入个门&#xff1a;C语言快速入门 引言&#xff1a; C&#xff0c;作为一门集面向过程和…

精彩回顾 | 「AI 驱动增长,研发数智化升级」分享沙龙成功举办

AI 应用元年&#xff0c;人工智能技术将如何助力企业发展新质生产力&#xff0c;构建增长动能&#xff1f; 日前&#xff0c;LigaAI 与深圳市企业联合会、西云数据联合举办了「AI 驱动增长&#xff0c;研发数智化升级」技术专题沙龙。本次活动围绕「AI」应用实践&#xff0c;邀…

2024-基于人工智能的药物设计方法研究-AIDD

AIDD docx 基于人工智能的药物设计方法研究 AI作为一种强大的数据挖掘和分析技术已经涉及新药研发的各个阶段&#xff0c;有望推动创新药物先导分子的筛选、设计和发现&#xff0c;但基于AI的数据驱动式创新药物设计和筛选方法仍存在若干亟待解决的问题。我们课题组的核心研究…

Kali中间人攻击

中间人攻击 中间人攻击&#xff08;Man-in-the-Middle Attack&#xff0c;简称MITM&#xff09;是一种网络安全攻击&#xff0c;其中攻击者插入自己&#xff08;作为“中间人”&#xff09;在通信的两个端点之间&#xff0c;以窃取或篡改通过的数据。攻击者可以监视通信&#x…

我为什么选择成为程序员?

前言&#xff1a; 我选择成为程序员不是兴趣所在&#xff0c;也不是为了职业发展&#xff0c;全是生活所迫&#xff01; 第一章&#xff1a;那年&#xff0c;我双手插兜&#xff0c;对外面的世界一无所知 时间回到2009年&#xff0c;时间过得真快啊&#xff0c;一下就是15年前…

多线程回答的滚瓜烂熟,面试官问我虚线程了解吗?我说不太了解!

Java虚拟线程&#xff08;Virtual Threads&#xff09;标志着Java在并发编程领域的一次重大飞跃&#xff0c;特别是从Java 21版本开始。这项新技术的引入旨在克服传统多线程和线程池存在的挑战。 多线程和线程池 在Java中&#xff0c;传统的多线程编程依赖于Thread类或实现Ru…

Green Hills 自带的MULTI调试器查看R7芯片寄存器

Green Hills在查看芯片寄存器时需要导入 .grd文件。下面以R7为例&#xff0c;演示一下过程。 首先打开MULTI调试器&#xff0c;如下所示View->Registers&#xff1a; 进入如下界面&#xff0c;选择导入寄存器定义文件.grd&#xff1a; 以当前R7芯片举例&#xff08;dr7f7013…

室内定位中文综述阅读

1 室内高精度定位技术总结与展望 [4]柳景斌,赵智博,胡宁松等.室内高精度定位技术总结与展望[J].武汉大学学报(信息科学 版),2022,47(07):997-1008.DOI:10.13203/j.whugis20220029. 1.1.1 WiFi‐RTT定位 2016 年 12 月&#xff0c;随着新版 IEEE802.11 标准的公布&#xff0c…

力扣55. 跳跃游戏

Problem: 55. 跳跃游戏 文章目录 题目描述思路复杂度Code 题目描述 思路 将题目稍作转化&#xff1a;验证最远走到的距离是否超出组数&#xff1b; 1.获取数组nums的长度n&#xff0c;定义int变量farthest初始化为0&#xff1b; 2.从0~n-1循环每次更新farthes的长度farthest …

2024年3月文章一览

2024年3月编程人总共更新了12篇文章&#xff1a; 1.2024年2月文章一览 2.Programming Abstractions in C阅读笔记&#xff1a;p308-p311 3.Programming Abstractions in C阅读笔记&#xff1a;p312-p326 4.Programming Abstractions in C阅读笔记&#xff1a;p327-p330 5.…

查询卖家已卖出的交易数据

要获取淘宝订单详情数据&#xff0c;你需要使用淘宝开放平台的API来获取数据。以下是获取淘宝订单详情数据的步骤&#xff1a; 在淘宝开放平台上创建一个应用&#xff0c;获取到AppKey和AppSecret。 使用OAuth 2.0授权方式&#xff0c;获取到授权码。 第三方公司授权 使用授…

快速排序(单边循环和双边循环)

快速排序 单边循环快排 pv指向分区中最后一个元素&#xff0c;i&#xff0c;j指向分区中第一个元素&#xff0c;j所指向的元素和pv指向的元素比较大小&#xff0c;如果比pv所指大&#xff0c;则j&#xff0c;否则与i所指元素交换位置&#xff0c;i&#xff0c;j&#xff1b;当…

雪亮工程视频联网综合管理/视频智能分析系统建设方案(二)

一、我国雪亮工程当前建设需求 1&#xff09;加强社会治安防控感知网络建设 加强社会治安防控智能感知网络建设&#xff0c;针对城中村、背街小巷、城乡结合部等重点区域建设安装视频监控设备&#xff0c;减少死角和盲区&#xff0c;与已有感知系统结合&#xff0c;形成高低搭…