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

news2024/11/30 2:25:43

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


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


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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CUDA 12.4文档1:使用GPU好处CUDA可扩展编程模型

本博客参考官方文档进行介绍&#xff0c;全网仅此一家进行中文翻译&#xff0c;走过路过不要错过。 官方网址&#xff1a;https://docs.nvidia.com/cuda/cuda-c-programming-guide/ 本文档分成多个博客进行介绍&#xff0c;在本人专栏中含有所有内容&#xff1a; https://bl…

Linux网卡:连接虚拟与现实的桥梁

在介绍Linux网卡之前&#xff0c;让我们先迈入时光机&#x1f570;️&#xff0c;回到1980年代末期&#xff0c;互联网正在逐步从一个科研网络向公众网络转变&#xff0c;Linux——一个自由和开源的操作系统诞生了&#x1f427;。Linux的出现&#xff0c;对于计算机科学领域来说…

【微命令】git 如何修改某个分支的名字(git branch -m newbranch)

简要信息&#xff0c;快速记录 命令 # 切换到某个需要修改的分支 git checkout oldbranch# 修改分支名字 git branch -m newbranch假设作为git设计者&#xff0c;要用来修改branch的命令&#xff0c;那么就是 git branch作为前缀&#xff0c;然后进一步修改的命令是branch相关…

CorelDRAW21.2.4中文最新官方和谐版下载

CorelDRAW是一款由加拿大Corel公司出品的平面设计软件&#xff0c;也被称为CDR。它是一款功能强大的矢量图形制作和排版软件&#xff0c;主要面向绘图设计师和印刷输出人员。该软件提供了矢量插图、页面布局、图片编辑和设计工具&#xff0c;广泛应用于排版印刷、矢量图形编辑及…

24/04/11总结

IO流(First edition): IO流&#xff1a;用于读入写出文件中的数据 流的方向&#xff08;输入指拿出来,输出指写进去) 输入流:读取 输出流:写出 操作文件类型 字节流:所有类型文件 字符流:纯文本 字节流: InputStream的子类:FileInputStream:操作本地文件的字节输入流 OutputSt…

【HTML】制作一个简单的实时字体时钟

目录 前言 HTML部分 CSS部分 JS部分 效果图 总结 前言 无需多言&#xff0c;本文将详细介绍一段HTML代码&#xff0c;具体内容如下&#xff1a; 开始 首先新建文件夹&#xff0c;创建一个文本文档&#xff0c;两个文件夹&#xff0c;其中HTML的文件名改为[index.html]&am…

二分查找-图文详解,看不懂你来打我。。。

一、查找算法 在计算机科学和算法领域&#xff0c;搜索是一项基本的任务。在海量数据中寻找特定的元素是一项常见的任务&#xff0c;而二分查找&#xff08;Binary Search&#xff09;是一种非常高效的搜索算法&#xff0c;特别适用于有序数组。 二、二分查找 二分查找是一种…

【阿里淘天笔试题汇总】2024-04-10-阿里淘天春招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新淘天近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

【前端Vue】Vue3+Pinia小兔鲜电商项目第5篇:整体认识和路由配置,本资源由 收集整理【附代码文档】

Vue3ElementPlusPinia开发小兔鲜电商项目完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;认识Vue3&#xff0c;使用create-vue搭建Vue3项目1. Vue3组合式API体验,2. Vue3更多的优势,1. 认识create-vue,2. 使用create-vue创建项目,1. setup选项的写法和执行…

基于Springboot的箱包存储系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的箱包存储系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

linux的io的知识大全

C语言的io操作 写文件 #include<stdio.h> #include<string.h>#define FILE_NAME "log.txt" int main() {FILE * fp fopen(FILE_NAME, "w");if(fpNULL){printf("fopen error!\n");}const char* msg "hello zk\n";int c…

【复现】浙大恩特客户资源管理系统 SQL注入漏洞_71

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 浙大恩特客户资源管理系统是一款针对企业客户资源管理的软件产品。该系统旨在帮助企业高效地管理和利用客户资源&#xff0c;提升…

Golang 基于共享变量的并发锁

一、互斥锁 先看一个并发情况&#xff0c;同时操作一个全局变量&#xff0c;如果没有锁会怎么样 假设有1000个goroutines并发进行银行余额的扣除&#xff0c;每次都扣除10元&#xff0c;起始的总余额是10000&#xff0c;理论上并发执行完应该是0对不对&#xff0c;但实际却不…

GeoServer:忘记密码重置

操作步骤 1. 找到data_dir/security/usergroup/default目录下的users.xml文件&#xff0c; 2.修改password为plain:geoserver&#xff0c; 这里无论原来的密码是什么&#xff0c;改为plain:geoserver之后&#xff0c;就可以通过admin&#xff1a;geoserver默认账户密码登录了。…

RAG应用开发实战(01)-RAG应用框架和解析器

1 开源解析和拆分文档 第三方的工具去对文件解析拆分&#xff0c;去将我们的文件内容给提取出来&#xff0c;并将我们的文档内容去拆分成一个小的chunk。常见的PDF word mark down, JSON、HTML。都可以有很好的一些模块去把这些文件去进行一个东西去提取。 优势 支持丰富的文…

电商新宠:淘宝拍立淘API接口助力精准搜索商品信息

淘宝拍立淘API接口&#xff0c;作为电商领域的新宠&#xff0c;正以其独特的图像识别技术为精准搜索商品信息提供强大的助力。这项基于深度学习和计算机视觉技术的先进服务&#xff0c;使得用户能够通过上传图片来快速搜索淘宝平台上的相关商品&#xff0c;极大地提升了购物体验…

鸿蒙让我赚到了第一笔桶金!年薪33.6W!

抢人&#xff01;抢人&#xff01;抢人&#xff01; 所谓抢滩鸿蒙&#xff0c;人才先行。鸿蒙系统火力全开后&#xff0c;抢人已成鸿蒙市场的主题词&#xff01; 智联招聘数据显示&#xff0c;春节后首周&#xff0c;鸿蒙相关职位数同比增长163%&#xff0c;是去年同期的2.6倍…

分布式锁-redission可重入锁原理

5.3 分布式锁-redission可重入锁原理 在Lock锁中&#xff0c;他是借助于底层的一个voaltile的一个state变量来记录重入的状态的&#xff0c;比如当前没有人持有这把锁&#xff0c;那么state0&#xff0c;假如有人持有这把锁&#xff0c;那么state1&#xff0c;如果持有这把锁的…

RX8901CE可在最高+105℃工作温度下工作,助力光伏逆变器稳定运行

光伏逆变器是一种由半导体器件组成的电力调整装置&#xff0c;主要用于把直流电力转换成交流电力。一般由升压回路和逆变桥式回路构成&#xff0c;而且很多时候会配有储能单元进行峰谷电压平整。作为光伏电站中关键的部件&#xff0c;光伏逆变器的可靠运行影响着整个光伏电站的…

ubuntu如何截图? ubuntu中截屏的三种方法

文章目录 1.ubuntu主要用途2.ubuntu如何截图&#xff1f;2.1 方法一&#xff1a;键盘按键快捷键截屏 2.2 方法二&#xff1a;系统自带软件2.3 方法三&#xff1a;第三方软件 Reference 1.ubuntu主要用途 1、桌面操作系统&#xff1a;Ubuntu可用作个人电脑或笔记本电脑的操作系…