(学习日记)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;                                //返回,不执行定时操作}
#endifOS_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);     //将该定时添加到定时器双向调试链表
#endifOSTmrQty++;                  //定时器个数加1OS_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,不继续执行}
#endifOSSchedLock(&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_TRUEbreak;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_TRUEbreak;case OS_TMR_STATE_UNUSED:         (7)//如果定时器未被创建OSSchedUnlock(&err);                 //解锁调度器*p_err   = OS_ERR_TMR_INACTIVE;       //错误类型为“定时器未激活”success = DEF_FALSE;                 //执行结果暂为 DEF_FALSEbreak;default:                     (8)//如果定时器的状态超出预期OSSchedUnlock(&err);                 //解锁调度器*p_err = OS_ERR_TMR_INVALID_STATE;    //错误类型为“定时器无效”success = DEF_FALSE;                 //执行结果暂为 DEF_FALSEbreak;}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,匹配时间加个 Dlyp_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_tmr1p_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;}}//列表元素成员数加1p_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_tmr1if (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_tmr1if (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,不继续执行}
#endifOSSchedLock(&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_TRUEbreak;case OS_TMR_STATE_UNUSED:           (12)//如果该定时器未被创建过OSSchedUnlock(&err);               //解锁调度器*p_err   = OS_ERR_TMR_INACTIVE;     //错误类型为“定时器未激活”success = DEF_FALSE;               //执行结果暂为 DEF_FALSEbreak;default:                           (13)//如果定时器状态超出预期OSSchedUnlock(&err);               //解锁调度器*p_err   = OS_ERR_TMR_INVALID_STATE;//错误类型为“定时器状态非法”success = DEF_FALSE;               //执行结果暂为 DEF_FALSEbreak;}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,不继续执行}
#endifOSSchedLock(&err);          //锁住调度器
#if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量OS_TmrDbgListRemove(p_tmr); //将该定时从定时器双向调试链表移除
#endifOSTmrQty--;                             (3)     //定时器个数减1switch (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_TRUEbreak;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_TRUEbreak;case OS_TMR_STATE_UNUSED:                 //如果定时器已被删除OSSchedUnlock(&err);                 //解锁调度器*p_err   = OS_ERR_TMR_INACTIVE;       //错误类型为“定时器未激活”success = DEF_FALSE;                 //执行结果暂为 DEF_FALSEbreak;default:                                  //如果定时器的状态超出预期OSSchedUnlock(&err);                 //解锁调度器*p_err   = OS_ERR_TMR_INVALID_STATE;  //错误类型为“定时器无效”success = DEF_FALSE;                 //执行结果暂为 DEF_FALSEbreak;}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.rhkb.cn/news/306473.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ArcGIS和ArcGIS Pro快速加载ArcGIS历史影像World Imagery Wayback

ArcGIS在线历史影像网站 World Imagery Wayback(网址:https://livingatlas.arcgis.com/wayback/)提供了数期历史影像在线浏览服务,之前不少自媒体作者在文中宣称其能代表Google Earth历史影像。 1、一点对比 (1)同一级别下的版本覆盖面 以下述区域为例,自2014年2月20…

java 邮件发送表格

邮件发送表格 问题导入效果图 实现方案1. 拼接HTML文件&#xff08;不推荐&#xff09;2. excel 转HTML使用工具类来转化依赖工具类代码示例 使用已工具包 如 aspose-cells依赖代码示例 3.使用模板生成流程准备模板工具类代码示例 问题导入 在一些定时任务中&#xff0c;经常会…

liunx环境变量学习总结

环境变量 在操作系统中&#xff0c;环境变量是一种特殊的变量&#xff0c;它们为运行的进程提供全局配置信息和系统环境设定。本文将介绍如何自定义、删除环境变量&#xff0c;特别是对重要环境变量PATH的管理和定制&#xff0c;以及与环境变量相关的函数使用。 自定义环境变…

《哈迪斯》自带的Lua解释器是哪个版本?

玩过《哈迪斯》&#xff08;英文名&#xff1a;Hades&#xff09;吗&#xff1f;最近在研究怎么给这款游戏做MOD&#xff0c;想把它的振动体验升级到更高品质的RichTap。N站下载了一些别人做的MOD&#xff0c;发现很多都基于相同的格式&#xff0c;均是对游戏.sjon文件或.lua文…

LeetCode-64. 最小路径和【数组 动态规划 矩阵】

LeetCode-64. 最小路径和【数组 动态规划 矩阵】 题目描述&#xff1a;解题思路一&#xff1a;动态规划五部曲。定推初遍举解题思路二&#xff1a;动态规划优化空间&#xff0c;直接改grid解题思路三&#xff1a;dfs 题目描述&#xff1a; 给定一个包含非负整数的 m x n 网格 …

Python代码识别minist手写数字【附pdf】

一、概述 对于人类而言&#xff0c;要识别图片中的数字是一件很容易的事情&#xff0c;但是&#xff0c;如何让机器学会理解图片上的数字&#xff0c;这似乎并不容易。那么&#xff0c;能否找出一个函数&#xff08;模型&#xff09;&#xff0c;通过输入相关的信息&#xff0…

最新版手机软件App下载排行网站源码/App应用商店源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 一款简洁蓝色的手机软件应用app下载排行&#xff0c;app下载平台&#xff0c;最新手机app发布网站响应式织梦模板。 主要有&#xff1a;主页、app列表页、app介绍详情页、新闻资讯列…

Linux虚拟网络设备:底层原理与性能优化深度解析

在深入探讨Linux虚拟网络设备的底层原理之前&#xff0c;重要的是要理解这些设备如何在Linux内核中实现&#xff0c;以及它们如何与操作系统的其他部分交互以提供高效且灵活的网络功能。虚拟网络设备在现代网络架构中发挥着关键作用&#x1f511;&#xff0c;特别是在云计算☁️…

LeetCode-1143. 最长公共子序列【字符串 动态规划】

LeetCode-1143. 最长公共子序列【字符串 动态规划】 题目描述&#xff1a;解题思路一&#xff1a;动规五部曲解题思路二&#xff1a;1维DP解题思路三&#xff1a;0 题目描述&#xff1a; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。…

关于转义符 \ 在php正则中的匹配问题

今天做题遇到一个很经典的问题&#xff0c;记录一下&#xff0c;先看一段代码 <?php $str&#xff0c;&#xff0c;"\\"; $pattern&#xff0c;&#xff0c;"/\\/"; if(preg_match($partern,$str,$arr)) { &#xff0c;&#xff0c;&#xff0c;&…

windows wireshark抓包rtmp推流出现TCP Retransmission

解决办法&#xff1a;tcp.port1935 && !(tcp.analysis.retransmission)

每日一题---OJ题: 合并两个有序链表

嗨!小伙伴们,好久不见啦! 今天我们来看看一道很有意思的一道题---合并两个有序链表 嗯,题目看上去好像不难,我们一起画图分析分析吧! 上图中,list1有3个结点,分别为1,2,3 ; list2中有3个结点,分别为1,3,4, 题目要求我们要将这两个链表合并到一起,并且是升序,最后将链表返回。 …

使用DSP28335在CCS中生成正弦波

DSP芯片支持数学库&#xff0c;那如何通过DSP芯片生成一个正弦波呢&#xff1f;通过几天研究&#xff0c;现在将我的方法分享一下&#xff0c;如有错误&#xff0c;希望大家及时指出&#xff0c;共同进步。 sin函数的调用 首先看下一sin函数 的使用。 //头文件的定义 #includ…

OpenHarmony开发学习:【源码下载和编译】

本文介绍了如何下载鸿蒙系统源码&#xff0c;如何一次性配置可以编译三个目标平台&#xff08;Hi3516&#xff0c;Hi3518和Hi3861&#xff09;的编译环境&#xff0c;以及如何将源码编译为三个目标平台的二进制文件。 坑点总结&#xff1a; 下载源码基本上没有太多坑&#xff…

如何实现小程序滑动删除组件+全选批量删除组件

如何实现小程序滑动删除组件全选批量删除组件 一、简介 如何实现小程序滑动删除组件全选批量删除组件 采用 uni-app 实现&#xff0c;可以适用微信小程序、其他各种小程序以及 APP、Web等多个平台 具体实现步骤如下&#xff1a; 下载开发者工具 HbuilderX进入 【Dcloud 插…

部署GlusterFS群集

目录 一、部署GlusterFS群集 1. 服务器节点分配 2. 服务器环境&#xff08;所有node节点上操作&#xff09; 2.1 关闭防火墙 2.2 磁盘分区&#xff0c;并挂载 2.3 修改主机名&#xff0c;配置/etc/hosts文件 3. 安装、启动GlusterFS&#xff08;所有node节点上操作&…

Postman实现API自动化测试

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 背景介绍 相信大部分开发人员和测试人员对 postman 都十分熟悉…

Python 爬虫基础——http请求和http响应

写本篇文章&#xff0c;我认为是能把自己所理解的内容分享出来&#xff0c;说不定就有和我一样有这样思维的共同者&#xff0c;希望本篇文章能帮助大家&#xff01;✨✨ 文章目录 一、 &#x1f308;python介绍和分析二、 &#x1f308;http请求三、 &#x1f308;http响应四、…

Day:005 | Python爬虫:高效数据抓取的编程技术(爬虫效率)

爬虫之多线程-了解 单线程爬虫的问题 因为爬虫多为IO密集型的程序&#xff0c;而IO处理速度并不是很快&#xff0c;因此速度不会太快如果IO卡顿&#xff0c;直接影响速度 解决方案 考虑使用多线程、多进程 原理&#xff1a; 爬虫使用多线程来处理网络请求&#xff0c;使用线程…

【Canvas技法】在Canvas按圆周绘制图形或是标注文字时,角度累加的方向为顺时针,起点为x轴正向

【图解说明】 【核心代码】 // 画圆弧及方向for(var i0;i<4;i){var startMath.PI/2*i;var endstartMath.PI/2;var x1180*Math.cos(start);var y1180*Math.sin(start);var x2180*Math.cos(end);var y2180*Math.sin(end);ctx.beginPath();ctx.arc(0,0,180,start,end,false);ct…