μC/OS-Ⅱ源码学习(7)---软件定时器

快速回顾

μC/OS-Ⅱ中的多任务

μC/OS-Ⅱ源码学习(1)---多任务系统的实现

μC/OS-Ⅱ源码学习(2)---多任务系统的实现(下)

μC/OS-Ⅱ源码学习(3)---事件模型

μC/OS-Ⅱ源码学习(4)---信号量

μC/OS-Ⅱ源码学习(5)---消息队列

μC/OS-Ⅱ源码学习(6)---事件标志组

        本文进一步解析μC/OS-Ⅱ中,软件定时器的函数源码。

μC/OSⅡ中的定时器系统

        软件定时器并不属于事件系统,是一类特殊的模型,有自己独特的执行逻辑。更具体来说,除了手动创建、启动和销毁,软件定时器的其它生命周期过程无需用户操作,更不与其它任务相关联,它和定时器任务共同构成了软件定时器系统。

        在定时器系统中,最重要的就是如何检查定时器的到期,对于一个同步系统来说,基本只能使用“轮询”的方式,即定期检查,这种方式存在两个关键问题:

①如何把控轮询间隔,首先轮询间隔必须是固定(否则步长不定,时间精度也就无从谈起),而且间隔不能太长,否则定时器的时间精度就下降了。也不能太短,这样太消耗资源;

②二是当定时器很多时,遍历的时间就会线性增加,如何设计结构提高遍历效率。

        对于问题①,比较好解决,可以选一个稳定且周期性的中断作为轮询出发点(比如STM32里的滴答中断),并根据用户需求进行间隔时长的配置。

        对于问题②,为了提高轮询效率,μC/OSⅡ使用“车轮式”(Wheel)的查询结构,将时间线周期性地均匀缠绕在“车轮”上,好比钟表走时一样,每一根车辐都对应一个时间刻度,进而可以将不同的定时器通过到期时间归属到对应的时刻上,每一时刻只需盘点该时刻下的定时器,可以减少对大量定时器的遍历,提高查询效率。这种算法本质上还是用空间换取时间

相关结构和类型

        和软件定时器相关的变量如下:

//ucos_ii.h
OS_EXT  INT16U        OSTmrFree;                /* 剩余可用的空白定时器 */
OS_EXT  INT16U        OSTmrUsed;                /* 已使用的定时器数量 */
OS_EXT  INT32U        OSTmrTime;                /* 当前时间 */

OS_EXT  OS_EVENT     *OSTmrSem;                 /* 操作定时器的权限 */
OS_EXT  OS_EVENT     *OSTmrSemSignal;           /* 当定时器更新(到期)时,用于提醒定时器任务运行的信号量 */

OS_EXT  OS_TMR        OSTmrTbl[OS_TMR_CFG_MAX];   /* 软件定时器数组 */
OS_EXT  OS_TMR       *OSTmrFreeList;              /* 空白软件定时器链表 */
OS_EXT  OS_STK        OSTmrTaskStk[OS_TASK_TMR_STK_SIZE];    //软件定时器堆栈大小

OS_EXT  OS_TMR_WHEEL  OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE];   //时间车轮数组,每一个成员代表一个时间刻度

        OS_TMR_CFG_MAX表示定时器的最大数量,OS_TMR_CFG_WHEEL_SIZE表示时间车轮的车辐数,也即时间刻度数,下文中的“车辐”和“刻度”表示一个意思。

        两个信号量,OSTmrSem是获取定时器操作权限的信号量,OSTmrSemSignal是滴答中断用来提醒定时器任务执行的信号量。

        这里面比较重要的类型就是定时器OS_TMR时间车轮OS_TMR_WHEEL

OS_TMR

        与任务、事件类似,定时器也是以链表的形式进行遍历检索的(除了存储定时器的原始数组),且是双向链表的形式,方便在链表中进行插入和删除元素。

//ucos_ii.h
typedef  struct  os_tmr {
    INT8U            OSTmrType;             /* 初始化后被设置位OS_TMR_TYPE(其它类型无效) */
    OS_TMR_CALLBACK  OSTmrCallback;         /* 定时器回调函数 */
    void            *OSTmrCallbackArg;      /* 需要传给回调函数的参数 */
    void            *OSTmrNext;             /* 和OSTmrPrev共同构成双向链表指针 */
    void            *OSTmrPrev;
    INT32U           OSTmrMatch;            /* 到期时间,即当OSTmrTime=OSTmrMatch时定时器到期 */
    INT32U           OSTmrDly;              /* 开启定时器前的延时 */
    INT32U           OSTmrPeriod;           /* 定时器周期 */
#if OS_TMR_CFG_NAME_EN > 0u
    INT8U           *OSTmrName;             /* 定时器名称 */
#endif
    INT8U            OSTmrOpt;              /* 选项(如OS_TMR_OPT_xxx) */
    INT8U            OSTmrState;            /* 定时器状态,有四种 */
} OS_TMR;

        里面有两个成员可以进一步解析,一个是选项OSTmrOpt

//ucos_ii.h
#define  OS_TMR_OPT_NONE              0u  /* 无选项 */

#define  OS_TMR_OPT_ONE_SHOT          1u  /* 单次定时器,不会自动重启 */
#define  OS_TMR_OPT_PERIODIC          2u  /* 周期性定时器,到期后自动重启 */

#define  OS_TMR_OPT_CALLBACK          3u  /* OSTmrStop() option to call 'callback' w/ timer arg.     */
#define  OS_TMR_OPT_CALLBACK_ARG      4u  /* OSTmrStop() option to call 'callback' w/ new   arg.     */

        另一个是定时器状态OSTmrState

//ucos_ii.h
#define  OS_TMR_STATE_UNUSED       0u    //未使用的(未初始化)
#define  OS_TMR_STATE_STOPPED      1u    //已初始化但未启动的定时器
#define  OS_TMR_STATE_COMPLETED    2u    //已经结束的定时器
#define  OS_TMR_STATE_RUNNING      3u    //正在运行的定时器

OS_TMR_WHEEL

//ucos_ii.h
typedef struct os_tmr_wheel {
    OS_TMR      *OSTmrFirst;      /* 指向定时器的指针 */
    INT16U       OSTmrEntries;     //该车轮片剩余的定时器
} OS_TMR_WHEEL;

        OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE]包含了所有的刻度(详见上一节的图解模型),里面的每个对象都对应一个刻度(OS_TMR_WHEEL类型),刻度上会挂载用户所需的定时器(OSTmrFirst链表)。系统会根据定时器到期时间自动挂载到对应的刻度上。

软件定时器初始化

        在OSInit()中,对系统的软件定时器进行了结构初始化OSTmr_Init():将原始的定时器数组按顺序相连,形成一条空白链表,同时进行白板初始化。

//os_tmr.c
void  OSTmr_Init (void)
{
#if OS_EVENT_NAME_EN > 0u
    INT8U    err;
#endif
    INT16U   ix;
    INT16U   ix_next;
    OS_TMR  *ptmr1;
    OS_TMR  *ptmr2;

    OS_MemClr((INT8U *)&OSTmrTbl[0],      sizeof(OSTmrTbl));     /* 清除定时器数组 */
    OS_MemClr((INT8U *)&OSTmrWheelTbl[0], sizeof(OSTmrWheelTbl));       /* Clear the timer wheel                      */

    for (ix = 0u; ix < (OS_TMR_CFG_MAX - 1u); ix++) {     /* 遍历定时器数组对每个定时器进行初始化,并用指针连成链表 */
        ix_next = ix + 1u;
        ptmr1 = &OSTmrTbl[ix];
        ptmr2 = &OSTmrTbl[ix_next];
        ptmr1->OSTmrType    = OS_TMR_TYPE;
        ptmr1->OSTmrState   = OS_TMR_STATE_UNUSED;
        ptmr1->OSTmrNext    = (void *)ptmr2;     /* OSTmrNext指针指向下一个定时器 */
#if OS_TMR_CFG_NAME_EN > 0u
        ptmr1->OSTmrName    = (INT8U *)(void *)"?";
#endif
    }
    ptmr1               = &OSTmrTbl[ix];
    ptmr1->OSTmrType    = OS_TMR_TYPE;
    ptmr1->OSTmrState   = OS_TMR_STATE_UNUSED;
    ptmr1->OSTmrNext    = (void *)0;     /* 最后一个定时器的下一个为空 */
#if OS_TMR_CFG_NAME_EN > 0u
    ptmr1->OSTmrName    = (INT8U *)(void *)"?";
#endif
    OSTmrTime           = 0u;
    OSTmrUsed           = 0u;
    OSTmrFree           = OS_TMR_CFG_MAX;
    OSTmrFreeList       = &OSTmrTbl[0];      //链表头指向数组首,正式形成OSTmrFreeList链表
    OSTmrSem            = OSSemCreate(1u);    //创建定时器操作权限信号量,初始值为1(第一次操作必定要成功)
    OSTmrSemSignal      = OSSemCreate(0u);    //创建定时器提醒信号量,初始值为0(等待Tick中段释放)

#if OS_EVENT_NAME_EN > 0u
    OSEventNameSet(OSTmrSem,       (INT8U *)(void *)"uC/OS-II TmrLock",   &err);
    OSEventNameSet(OSTmrSemSignal, (INT8U *)(void *)"uC/OS-II TmrSignal", &err);
#endif

    OSTmr_InitTask();     //初始化定时器任务
}

软件定时器任务

        在μC/OSⅡ中,软件定时器的运行离不开定时器任务的管理,这是一个特殊的任务,需要使用时,还要明确指定定时器任务的优先级,不能与其它任务优先级相冲突:

//用户自定义定时器任务优先级
#define  OS_TASK_TMR_PRIO     10

        在上一节定时器初始化函数的最后,还对定时器任务进行了初始化OSTmr_InitTask()

//os_tmr.c
static  void  OSTmr_InitTask (void)
{
#if OS_TASK_NAME_EN > 0u
    INT8U  err;
#endif

#if OS_TASK_CREATE_EXT_EN > 0u     //使用OSTaskCreateExt创建任务
    #if OS_STK_GROWTH == 1u    //堆栈增长方向,由高到地
    (void)OSTaskCreateExt(OSTmr_Task,
                          (void *)0,                                       /* No arguments passed to OSTmrTask()      */
                          &OSTmrTaskStk[OS_TASK_TMR_STK_SIZE - 1u],        /* Set Top-Of-Stack                        */
                          OS_TASK_TMR_PRIO,
                          OS_TASK_TMR_ID,
                          &OSTmrTaskStk[0],                                /* Set Bottom-Of-Stack                     */
                          OS_TASK_TMR_STK_SIZE,
                          (void *)0,                                       /* No TCB extension                        */
                          OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);      /* Enable stack checking + clear stack     */
    #else
    (void)OSTaskCreateExt(OSTmr_Task,
                          (void *)0,                                       /* No arguments passed to OSTmrTask()      */
                          &OSTmrTaskStk[0],                                /* Set Top-Of-Stack                        */
                          OS_TASK_TMR_PRIO,
                          OS_TASK_TMR_ID,
                          &OSTmrTaskStk[OS_TASK_TMR_STK_SIZE - 1u],        /* Set Bottom-Of-Stack                     */
                          OS_TASK_TMR_STK_SIZE,
                          (void *)0,                                       /* No TCB extension                        */
                          OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);      /* Enable stack checking + clear stack     */
    #endif
#else
    #if OS_STK_GROWTH == 1u
    (void)OSTaskCreate(OSTmr_Task,
                       (void *)0,
                       &OSTmrTaskStk[OS_TASK_TMR_STK_SIZE - 1u],
                       OS_TASK_TMR_PRIO);
    #else
    (void)OSTaskCreate(OSTmr_Task,
                       (void *)0,
                       &OSTmrTaskStk[0],
                       OS_TASK_TMR_PRIO);
    #endif
#endif

#if OS_TASK_NAME_EN > 0u
    OSTaskNameSet(OS_TASK_TMR_PRIO, (INT8U *)(void *)"uC/OS-II Tmr", &err);
#endif
}

        直接看定时器任务OSTmr_Task():该任务会周期性获得OSTmrSemSignal信号量并执行,主要功能是检索当前所在时间车轮的刻度,并对该刻度上挂载的软件定时器对象进行遍历,检查是否到期,如果到期就执行对应的回调函数,并根据定时器配置决定是否需要再次挂载

//os_tmr.c
static  void  OSTmr_Task (void *p_arg)
{
    INT8U            err;
    OS_TMR          *ptmr;
    OS_TMR          *ptmr_next;
    OS_TMR_CALLBACK  pfnct;
    OS_TMR_WHEEL    *pspoke;
    INT16U           spoke;

    p_arg = p_arg;      /* 引用一下传参防止编译告警 */
    for (;;) {
        OSSemPend(OSTmrSemSignal, 0u, &err);        /* 等待定时器信号量提醒时间到期 */
        OSSchedLock();     //给调度器上锁
        OSTmrTime++;      /* 当前时间+1 */
        spoke  = (INT16U)(OSTmrTime % OS_TMR_CFG_WHEEL_SIZE);    /* 定位当前时间所处的车轮片 */
        pspoke = &OSTmrWheelTbl[spoke];    //取出该片
        ptmr   = pspoke->OSTmrFirst;
        while (ptmr != (OS_TMR *)0) {
            ptmr_next = (OS_TMR *)ptmr->OSTmrNext;     /* 要提前获取下一个,因为当前定时器可能会被后续操作移除出链表 */
            if (OSTmrTime == ptmr->OSTmrMatch) {       /* 当前时间匹配定时器内设定的时间 */
                OSTmr_Unlink(ptmr);       /* 时间匹配,说明该定时器已到期,则从车轮中取出 */
                if (ptmr->OSTmrOpt == OS_TMR_OPT_PERIODIC) {
                    OSTmr_Link(ptmr, OS_TMR_LINK_PERIODIC);      /* 重新加入到时间车轮 */
                } else {
                    ptmr->OSTmrState = OS_TMR_STATE_COMPLETED;   /* 标记定时器已完成 */
                }
                pfnct = ptmr->OSTmrCallback;     /* 获取当前定时器绑定的回调函数 */
                if (pfnct != (OS_TMR_CALLBACK)0) {
                    (*pfnct)((void *)ptmr, ptmr->OSTmrCallbackArg);     //执行回调函数
                }
            }
            ptmr = ptmr_next;
        }
        OSSchedUnlock();
    }
}

        其中涉及到两个关键函数:OSTmr_Unlink()OSTmr_Link(),先看前者:

//os_tmr.c
static  void  OSTmr_Unlink (OS_TMR *ptmr)
{
    OS_TMR        *ptmr1;
    OS_TMR        *ptmr2;
    OS_TMR_WHEEL  *pspoke;
    INT16U         spoke;

    spoke  = (INT16U)(ptmr->OSTmrMatch % OS_TMR_CFG_WHEEL_SIZE);    //查看到期时间属于哪个片段
    pspoke = &OSTmrWheelTbl[spoke];    //取出该片

    if (pspoke->OSTmrFirst == ptmr) {      /* 目标定时器处于片内首位 */
        ptmr1              = (OS_TMR *)ptmr->OSTmrNext;
        pspoke->OSTmrFirst = (OS_TMR *)ptmr1;
        if (ptmr1 != (OS_TMR *)0) {
            ptmr1->OSTmrPrev = (void *)0;
        }
    } else {
        ptmr1            = (OS_TMR *)ptmr->OSTmrPrev;     /* 目标定时器位于中间某个位置 */
        ptmr2            = (OS_TMR *)ptmr->OSTmrNext;
        ptmr1->OSTmrNext = ptmr2;
        if (ptmr2 != (OS_TMR *)0) {
            ptmr2->OSTmrPrev = (void *)ptmr1;
        }
    }
    ptmr->OSTmrState = OS_TMR_STATE_STOPPED;     //设置为已停止状态
    ptmr->OSTmrNext  = (void *)0;
    ptmr->OSTmrPrev  = (void *)0;
    pspoke->OSTmrEntries--;
}

        再看重新链接函数OSTmr_Link()

//os_tmr.c
static  void  OSTmr_Link (OS_TMR  *ptmr,
                          INT8U    type)
{
    OS_TMR       *ptmr1;
    OS_TMR_WHEEL *pspoke;
    INT16U        spoke;

    ptmr->OSTmrState = OS_TMR_STATE_RUNNING;     //设置为运行态
    if (type == OS_TMR_LINK_PERIODIC) {     /* 使用周期时间还是单次延时值作为重启的计算时间 */
        ptmr->OSTmrMatch = ptmr->OSTmrPeriod + OSTmrTime;
    } else {
        if (ptmr->OSTmrDly == 0u) {
            ptmr->OSTmrMatch = ptmr->OSTmrPeriod + OSTmrTime;
        } else {
            ptmr->OSTmrMatch = ptmr->OSTmrDly    + OSTmrTime;
        }
    }
    spoke  = (INT16U)(ptmr->OSTmrMatch % OS_TMR_CFG_WHEEL_SIZE);    //获取该定时器应处于的车轮片
    pspoke = &OSTmrWheelTbl[spoke];

    //插入到车轮片首位
    if (pspoke->OSTmrFirst == (OS_TMR *)0) {
        pspoke->OSTmrFirst   = ptmr;
        ptmr->OSTmrNext      = (OS_TMR *)0;
        pspoke->OSTmrEntries = 1u;
    } else {
        ptmr1                = pspoke->OSTmrFirst;
        pspoke->OSTmrFirst   = ptmr;
        ptmr->OSTmrNext      = (void *)ptmr1;
        ptmr1->OSTmrPrev     = (void *)ptmr;
        pspoke->OSTmrEntries++;
    }
    ptmr->OSTmrPrev = (void *)0;
}

软件定时器的创建

        创建定时器的函数为:

OS_TMR *OSTmrCreate (INT32U dlyINT32U  period, INT8U opt, OS_TMR_CALLBACK callback, void *callback_argINT8U *pnameINT8U *perr);

        配置信息很丰富,dly表示定时器首次调用的延时,period表示周期性定时器的执行周期,可选项opt包括:

//ucos_ii.h
#define  OS_TMR_OPT_ONE_SHOT   1u   /* 单次定时器,不会自动重启 */
#define  OS_TMR_OPT_PERIODIC   2u   /* 周期定时器,到期后会自动重启 */

        接着来看源码:

//os_tmr.c
OS_TMR  *OSTmrCreate (INT32U           dly,
                      INT32U           period,
                      INT8U            opt,
                      OS_TMR_CALLBACK  callback,
                      void            *callback_arg,
                      INT8U           *pname,
                      INT8U           *perr)
{
    OS_TMR   *ptmr;

#ifdef OS_SAFETY_CRITICAL
    if (perr == (INT8U *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

#if OS_ARG_CHK_EN > 0u
    switch (opt) {      /* 校验选项参数 */
        case OS_TMR_OPT_PERIODIC:
             if (period == 0u) {     //周期性定时器的周期不能为0
                 *perr = OS_ERR_TMR_INVALID_PERIOD;
                 return ((OS_TMR *)0);
             }
             break;

        case OS_TMR_OPT_ONE_SHOT:
             if (dly == 0u) {     //单次定时器的初次调用延时不能为0(否则就是立即执行,没有意义,不如直接调用函数)
                 *perr = OS_ERR_TMR_INVALID_DLY;
                 return ((OS_TMR *)0);
             }
             break;

        default:    //非法选项,返回空
             *perr = OS_ERR_TMR_INVALID_OPT;
             return ((OS_TMR *)0);
    }
#endif
    if (OSIntNesting > 0u) {      /* 不能在中断内调用创建函数 */
        *perr  = OS_ERR_TMR_ISR;
        return ((OS_TMR *)0);
    }
    OSSchedLock();    //给调度器上锁
    ptmr = OSTmr_Alloc();      /* 从空白定时器链表中取一个定时器 */
    if (ptmr == (OS_TMR *)0) {     //如无可用定时器,则标记错误并返回空
        OSSchedUnlock();
        *perr = OS_ERR_TMR_NON_AVAIL;
        return ((OS_TMR *)0);
    }
    /* 填装定时器 */
    ptmr->OSTmrState       = OS_TMR_STATE_STOPPED;     /* 未运行状态 */
    ptmr->OSTmrDly         = dly;
    ptmr->OSTmrPeriod      = period;
    ptmr->OSTmrOpt         = opt;
    ptmr->OSTmrCallback    = callback;
    ptmr->OSTmrCallbackArg = callback_arg;
#if OS_TMR_CFG_NAME_EN > 0u
    ptmr->OSTmrName        = pname;
#endif
    OSSchedUnlock();    //解锁调度器
    *perr = OS_ERR_NONE;
    return (ptmr);    //返回创建好的定时器对象指针
}

        发现了一个盲点,只有定时器任务相关的函数会使用OSSchedLock()OSSchedUnLock()函数进行调度器上锁解锁操作,其它的源码中是不存在这样的操作的,取而代之直接使用临界区OS_ENTER_CRITICAL()OS_EXIT_CRITICAL()囊括一整段代码,那么是为什么呢?

        我们知道进入临界区本质上就是关闭中断(除了NMI和硬件FAULT):

//os_cpu_a.asm
OS_CPU_SR_Save
    MRS     R0, PRIMASK  	;读取PRIMASK到R0,R0为返回值 
    CPSID   I				;PRIMASK=1,关中断(NMI和硬件FAULT可以响应)
    BX      LR			    ;返回

OS_CPU_SR_Restore
    MSR     PRIMASK, R0	   	;读取R0到PRIMASK中,R0为参数
    BX      LR				;返回

        当关闭中断后,相当于执行连续、不可打断的代码。而上锁调度器仅仅是无法切换任务,其使用条件是不如临界区严格的。

//os_core.c
void  OSSchedLock (void)
{
#if OS_CRITICAL_METHOD == 3u     /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif

    if (OSRunning == OS_TRUE) {      /* 系统正在运行 */
        OS_ENTER_CRITICAL();
        if (OSIntNesting == 0u) {      /* 不能在中断中调用 */
            if (OSLockNesting < 255u) {      /* 防止调度器锁上溢 */
                OSLockNesting++;      /* 调度器锁+1 */
            }
        }
        OS_EXIT_CRITICAL();
    }
}

        笔者的理解是,有些对事件和任务的操作是可以在中断中进行的(典型的如Post操作),因此在不希望发生这些事情的场合(如Pend操作),就进入临界区,防止中断内相关操作的干扰。

        而定时器的生态链是比较封闭的,不与其它任务和事件相关联,无需担心意外的操作导致出错。此外,定时器有实时性要求(比如0延时立即执行),不希望被切换到其它大型任务,这样就无法保证设定的延时,所以在进行操作前都会对调度器上锁。

软件定时器的运行

        使用OSTmrStart()运行对应的定时器,直接分析源码:

//os_tmr.c
BOOLEAN  OSTmrStart (OS_TMR   *ptmr,
                     INT8U    *perr)
{
#ifdef OS_SAFETY_CRITICAL
    if (perr == (INT8U *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

#if OS_ARG_CHK_EN > 0u
    if (ptmr == (OS_TMR *)0) {
        *perr = OS_ERR_TMR_INVALID;
        return (OS_FALSE);
    }
#endif
    if (ptmr->OSTmrType != OS_TMR_TYPE) {       /* 验证定时器类型是否正确 */
        *perr = OS_ERR_TMR_INVALID_TYPE;
        return (OS_FALSE);
    }
    if (OSIntNesting > 0u) {         /* 不能在中断中调用 */
        *perr  = OS_ERR_TMR_ISR;
        return (OS_FALSE);
    }
    OSSchedLock();
    switch (ptmr->OSTmrState) {
        case OS_TMR_STATE_RUNNING:       /* 本来就在运行状态,先脱离再重新链接重启 */
             OSTmr_Unlink(ptmr);       /* 脱离时间车轮 */
             OSTmr_Link(ptmr, OS_TMR_LINK_DLY);       /* 重新链接时间车轮 */
             OSSchedUnlock();
             *perr = OS_ERR_NONE;
             return (OS_TRUE);

        case OS_TMR_STATE_STOPPED:
        case OS_TMR_STATE_COMPLETED:     /* 原来处于停止或完成状态,直接链接上并重启 */
             OSTmr_Link(ptmr, OS_TMR_LINK_DLY);      /* 链接到时间车轮上 */
             OSSchedUnlock();
             *perr = OS_ERR_NONE;
             return (OS_TRUE);

        case OS_TMR_STATE_UNUSED:        /* 未初始化的定时器,标记错误并返回 */
             OSSchedUnlock();
             *perr = OS_ERR_TMR_INACTIVE;
             return (OS_FALSE);

        default:     //非法选项,标记错误并返回
             OSSchedUnlock();
             *perr = OS_ERR_TMR_INVALID_STATE;
             return (OS_FALSE);
    }
}

软件定时器的到期

        在开启后,如何保证时间到期后能触发呢?这就涉及到系统节拍了(下一篇文章)。可以在系统节拍服务函数中释放信号量OSTmrSemSignal以便定时器任务OSTmr_Task()执行(回到前面的定时器任务一节)。或者更常用的写法,在OSTimeTickHook()钩子函数里执行OSTmrSignal()释放信号量OSTmrSemSignal

//os_cpu_c.c
void  OSTimeTickHook (void)
{
#if OS_APP_HOOKS_EN > 0
    App_TimeTickHook();    //用户定义的回调函数
#endif

#if OS_TMR_EN > 0
    OSTmrCtr++;    //系统滴答和定时器检测频率是不同的,用一个计数器来进行同步
    if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) {
        OSTmrCtr = 0;
        OSTmrSignal();     //释放OSTmrSemSignal信号量,可以继续执行定时器任务
    }
#endif
}

        其中OS_TICKS_PER_SEC表示系统滴答的频率,比如200表示一秒内Tick200次。而OS_TMR_CFG_TICKS_PER_SEC表示定时器任务检测的频率(就是前面说到的“轮询”间隔了),比如40表示一秒内检测40次。二者做个除法,表示滴答中断5次,才进行一次定时器任务,这也符合宏定义本身的定义。

        最后是OSTmrSignal()函数:用于释放信号量OSTmrSemSignal

//os_tmr.c
INT8U  OSTmrSignal (void)
{
    INT8U  err;

    err = OSSemPost(OSTmrSemSignal);    //释放OSTmrSemSignal信号量使定时器任务就绪
    return (err);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值