05. µCOS-Ⅲ的时间管理

一、µC/OS-Ⅲ的系统时钟节拍

  任何的操作系统都需要时钟节拍,在 µC/OS-Ⅲ 中有一个用于记录系统时钟节拍的计数器,这个计数器是一个全局变量,定义在文件 os.h 中,如下所示:

OS_EXT OS_TICK OSTickCtr;                  /* Cnts the #ticks since startup or last set  */

  其中 OS_EXT 是一个宏定义,OS_TICK 是 µC/OS-Ⅲ 重定义的数据类型。

#define  OS_EXT  extern
typedef unsigned  int      CPU_INT32U;                  /* 32-bit unsigned integer       */
typedef CPU_INT32U         OS_TICK;                     /* Clock tick counter    <32>/64 */

  上面代码展开后,如下所示:

extern unsigned int OSTickCtr;

  可以看出,这个系统时钟节拍计数器对于 STM32 而言,就是一个全局的 32 比特无符号长整形变量。OSTickCtr 就是用来记录自系统启动后,系统时钟节拍产生的个数。每当产生一个系统时钟节拍,OSTickCtr 都会被加 1。系统时钟节拍就像是操作系统的心跳,为操作系统的正常运行提供时基,在 µC/OS-Ⅲ 的许多系统服务中,都需要使用到系统时钟节拍,例如软件定时器和一系列需要计算超时的事件。

  系统时钟节拍是由 SysTick(滴答定时器)产生的,SysTick 可以说就是为操作系统提供心跳而设计的。既然系统时钟节拍的来源是 SysTick,那么就需要对 SysTick 进行配置。在 µC/OS-Ⅲ 内核启动前,就已经对 SysTick 进行了多次配置,例如:HAL 库的初始化函数 HAL_Init() 、 用户编写的 STM32 时钟的配置函数 System_Clock_Init()、用户编写的延时初始化函数 Delay_Init() 。在以上三个函数中都对 SysTick 做了配置,但是在 µC/OS-Ⅲ 内核启动后,还必须使用函数 OS_CPU_SysTickInit() 对 SysTick 进行配置。

  在 os_cfg_app.h 配置文件中通过配置项 OS_CFG_TICK_RATE_HZ 来配置系统时钟节拍的频率的。

#define  OS_CFG_TICK_RATE_HZ                            1000u

  由于 µC/OS-Ⅲ 系统时钟节拍是来自 SysTick,那么 µC/OS-Ⅲ 系统时钟节拍的处理,也就是在 SysTick 的中断服务函数中完成的。

/**
 * @brief 滴答定时器中断服务函数
 * 
 */
void SysTick_Handler(void)
{
    if (OSRunning == OS_STATE_OS_RUNNING)                                       // 当UCOS系统运行了,才执行正常的调度处理
    {   
        // 在硬件回调滴答定时器时,就直接调用µC/OS-Ⅲ中的时钟函数
        // 1s内调用多少次,是由配置参数 OS_CFG_TICK_RATE_HZ 决定的
        OS_CPU_SysTickHandler();                              
    }
    HAL_IncTick();
}

  在 SysTick 的中断服务函数中,会先通过全局变量OS_STATE_OS_RUNING 判断 µC/OS-Ⅲ 内核是否已经开始运行了,只有当 µC/OS-Ⅲ 内核开始运行之后,才会调用函数 OC_CPU_SysTickHandler() 处理系统时钟节拍等事务。

  实际上函数 OS_CPU_SysTickHandler() 是 µC/OS-Ⅲ 源代码中提供的 SysTick 中断服务函数,µC/OS-Ⅲ 官方的原意是修改启动文件中的中断向量表,让函数 OS_CPU_SysTickHandler() 成为 SysTick 的中断服务函数,但是由于例程都是基于 HAL 库开发的,需要调用 HAL 库的函数 HAL_IncTick(),并且原本的基础工程就使用了函数 SysTick_Handler() 作为 SysTick 的中断服务函数,因此只能在函数 SysTick_Handler() 中调用函数 OS_CPU_SysTickHandler()。

  函数 OS_CPU_SysTickHandler() 定义在文件 os_cpu_c.c 中,具体的代码如下所示:

/*
*********************************************************************************************************
*                                          SYS TICK HANDLER
*
* Description: Handle the system tick (SysTick) interrupt, which is used to generate the uC/OS-III tick
*              interrupt.
*
* Arguments  : None.
*
* Note(s)    : 1) This function MUST be placed on entry 15 of the Cortex-M vector table.
*********************************************************************************************************
*/
void  OS_CPU_SysTickHandler  (void)
{
    CPU_SR_ALLOC();                                                             // 临界区保护

    CPU_CRITICAL_ENTER();                                                       // 进入临界区
    OSIntEnter();                                                               // 进入中断后,先调用函数 OSIntEnter()
    CPU_CRITICAL_EXIT();                                                        // 退出临界区

    OSTimeTick();                                                               // 处理与 µC/OS-Ⅲ 系统时钟节拍相关事务
    OSIntExit();                                                                // 中断返回前,调用函数 OSIntExit()
}

  函数 OSTimeTick(),该函数定义在文件 os_time.c 中,具体的代码如下所示:

/*
************************************************************************************************************************
*                                                 PROCESS SYSTEM TICK
*
* Description: This function is used to signal to uC/OS-III the occurrence of a 'system tick' (also known as a
*              'clock tick').  This function should be called by the tick ISR.
*
* Arguments  : none
*
* Returns    : none
*
* Note(s)    : none
************************************************************************************************************************
*/
void  OSTimeTick (void)
{
    // 如果 µC/OS-Ⅲ 系统还未运行,则不进行处理
    if (OSRunning != OS_STATE_OS_RUNNING) {
        return;
    }

    OSTimeTickHook();                                                           // 调用系统时钟节拍钩子函数

#if (OS_CFG_SCHED_ROUND_ROBIN_EN > 0u)
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);                                  // 处理时间片
#endif

#if (OS_CFG_TICK_EN > 0u)
    OS_TickUpdate(1u);                                                          // 更新系统时钟节拍
#endif
}

  在该函数中就会调用系统时钟节拍的钩子函数,用户可以自行指定这个钩子函数。

  OS_SchedRoundRobin() 函数传入的是当前任务所在的就绪态任务链表,因为时间片调度就是在最高优先级的任务之间进行调度,该函数定义在文件 os_core.c 中,具体的代码如下所示:

/*
************************************************************************************************************************
*                                        RUN ROUND-ROBIN SCHEDULING ALGORITHM
*
* Description: This function is called on every tick to determine if a new task at the same priority needs to execute.
*
*
* Arguments  : p_rdy_list    is a pointer to the OS_RDY_LIST entry of the ready list at the current priority
*              ----------
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

void  OS_SchedRoundRobin (OS_RDY_LIST  *p_rdy_list)
{
    OS_TCB  *p_tcb;
    CPU_SR_ALLOC();

    if (OSSchedRoundRobinEn != OS_TRUE) {                                       //  检查是否使能了时间片调度
        return;
    }

    CPU_CRITICAL_ENTER();
    p_tcb = p_rdy_list->HeadPtr;                                                // 获取任务控制块

    if (p_tcb == (OS_TCB *)0) {
        CPU_CRITICAL_EXIT();
        return;
    }

#if (OS_CFG_TASK_IDLE_EN > 0u)                                                  // 此宏用于使能空闲任务
    // 如果使能了空闲任务,就需要判断获取到的任务控制块是否为空闲任务,空闲任务是不需要进行时间片调度的
    if (p_tcb == &OSIdleTaskTCB) {
        CPU_CRITICAL_EXIT();
        return;
    }
#endif

    // 如果任务剩余时间片大于0,就更新任务的剩余时间片(减1)
    if (p_tcb->TimeQuantaCtr > 0u) {
        p_tcb->TimeQuantaCtr--;
    }

    // 如果任务剩余时间片更新后依然大于0,则说明任务时间片还未超时,无需进行任务调度,因此返回
    if (p_tcb->TimeQuantaCtr > 0u) {
        CPU_CRITICAL_EXIT();
        return;
    }

    // 任务已无剩余时间片,因此需要切换到同任务优先级的任务去运行,因此判断任务所在的就绪态任务链表中是否有其他任务,
    // 果任务所在就绪态任务链表的链表头等于链表尾,则说明该任务链表就只有一个任务,需进行任务切换,此返回
    if (p_rdy_list->HeadPtr == p_rdy_list->TailPtr) {
        CPU_CRITICAL_EXIT();
        return;
    }

    // 判断任务调度器是否处于锁定状态,如果任务调度器被锁定,就无需仅从任务切换,因此返回
    if (OSSchedLockNestingCtr > 0u) {
        CPU_CRITICAL_EXIT();
        return;
    }

    // 任务时间片超时,且符合任务切换的条件,那么接下来就要进行任务切换,步骤如下:
    //  1、将当前任务移动到所在就绪态任务链表的末尾,
    //  2、获取更新后就绪态任务链表的链表头任务
    //  3、根据任务时间片设置任务剩余时间片
    OS_RdyListMoveHeadToTail(p_rdy_list);
    p_tcb = p_rdy_list->HeadPtr;
    if (p_tcb->TimeQuanta == 0u) {                                              // 如果任务时间片为0,就是用默认时间片
        p_tcb->TimeQuantaCtr = OSSchedRoundRobinDfltTimeQuanta;
    } else {
        p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta;
    }
    CPU_CRITICAL_EXIT();
}

  在处理任务的时间片后,接下来就是调用函数 OS_TickUpdate() 更新系统时钟节拍计数器以及处理系统时钟节拍相关的其他事务了。函数 OS_TickUpdate() 传入的参数为 1,这个参数代表系统时钟节拍的增量,这里表述更新系统时钟节拍的增量为 1,说明过了一个系统时钟节拍。函数 OS_TickUpdate() 定义在文件 os_tick.c 中,具体的代码如下所示:

/*
************************************************************************************************************************
*                                                      TICK UPDATE
*
* Description: This function updates the list of task either delayed pending with timeout.
*              The function is internal to uC/OS-III.
*
* Arguments  : ticks          the number of ticks which have elapsed
*              -----
*
* Returns    : none
*
* Note(s)    : This function is INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/
void  OS_TickUpdate (OS_TICK  ticks)
{
#if (OS_CFG_TS_EN > 0u)
    CPU_TS  ts_start;
#endif
    CPU_SR_ALLOC();

    CPU_CRITICAL_ENTER();

    OSTickCtr += ticks;                                                         // 更新系统时钟节拍计数器

    OS_TRACE_TICK_INCREMENT(OSTickCtr);                                         // 用于调式

// 此宏用于使能时间戳功能,如果使能了时间戳功能,则会计算处理 Tick 任务链表的耗时
#if (OS_CFG_TS_EN > 0u)
    ts_start   = OS_TS_GET();
    OS_TickListUpdate(ticks);                                                   // 处理 Tick 任务链表
    OSTickTime = OS_TS_GET() - ts_start;
    if (OSTickTimeMax < OSTickTime) {
        OSTickTimeMax = OSTickTime;
    }
#else
    OS_TickListUpdate(ticks);                                                   // 处理 Tick 任务链表
#endif

#if (OS_CFG_DYN_TICK_EN > 0u)                                                   // 此宏用于使能动态时钟节拍管理
    if (OSTickList.TCB_Ptr != (OS_TCB *)0) {
        OSTickCtrStep = OSTickList.TCB_Ptr->TickRemain;
    } else {
        OSTickCtrStep = 0u;
    }

    OS_DynTickSet(OSTickCtrStep);
#endif
    CPU_CRITICAL_EXIT();
}

  函数 OS_TickUpdate() 主要完成两件事,分别为更新系统时钟节拍和处理 Tick 任务链表。其中更新系统时钟节拍,就是将系统时钟节拍计数器的值加上该函数的增量入参,而处理 Tick 任务链表,则是调用函数 OS_TickListUpdate() 完成的。函数 OS_TickListUpdate() 定义在文件 os_tick.c 中,具体的代码如下所示:

/*
************************************************************************************************************************
*                                 UPDATE THE LIST OF TASKS DELAYED OR PENDING WITH TIMEOUT
*
* Description: This function updates the delta list which contains tasks that are delayed or pending with a timeout.
*
* Arguments  : ticks          the number of ticks which have elapsed.
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/
static  void  OS_TickListUpdate (OS_TICK  ticks)
{
    OS_TCB        *p_tcb;
    OS_TICK_LIST  *p_list;
#if (OS_CFG_DBG_EN > 0u)
    OS_OBJ_QTY     nbr_updated;
#endif
#if (OS_CFG_MUTEX_EN > 0u)
    OS_TCB        *p_tcb_owner;
    OS_PRIO        prio_new;
#endif

#if (OS_CFG_DBG_EN > 0u)
    nbr_updated = 0u;
#endif
    p_list      = &OSTickList;                                                  // 获取 Tick 任务链表
    // 获取 Tick 任务链表中的第一个任务,因为 Tick 任务链表中的任务是按照挂起超时先后排序的
    p_tcb       = p_list->TCB_Ptr;
    if (p_tcb != (OS_TCB *)0) {                                                 // 如果 Tick 任务链表中没有任务,则不需要处理
        if (p_tcb->TickRemain <= ticks) {                                       // 先判断时钟节拍的增量会不会导致任务挂起超时
            // 如果会导致任务挂起超时,则更新时钟节拍的增量,并清零任务的剩余挂起时间
            ticks              = ticks - p_tcb->TickRemain;
            p_tcb->TickRemain  = 0u;
        } else {
            // 如果不会导致任务挂起超时,则更新任务的剩余挂起时间
            p_tcb->TickRemain -= ticks;
        }

        while (p_tcb->TickRemain == 0u) {                                       // 在这个循环中处理完 Tick 任务链表中所有挂起超时的任务
#if (OS_CFG_DBG_EN > 0u)
            nbr_updated++;
#endif

            switch (p_tcb->TaskState) {                                         // 根据任务的状态,处理挂起超时的任务
                case OS_TASK_STATE_DLY:
                    // 如果任务只是因延时被挂起,那么仅需将延时挂起超时的任务设置为就绪态,并将任务添加到就绪态任务链表中即可
                    p_tcb->TaskState = OS_TASK_STATE_RDY;
                    OS_RdyListInsert(p_tcb);
                    break;

                case OS_TASK_STATE_DLY_SUSPENDED:
                    // 如果任务在延时期间被其他任务使用函数OsTaskSuspend() 挂起,则在任务挂起延时超时后,将任务设置为挂起态,
                    // 因为被函数 OsTaskSuspend() 挂起的任务只能调用函数 OSTaskResume() 后恢复
                    p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
                    break;

                default:
#if (OS_CFG_MUTEX_EN > 0u)                                                      // 此宏用于使能互斥信号量
                    // 判断任务是否挂起等待互斥信号量,如果是,则获取持有该互斥信号量的任务
                    p_tcb_owner = (OS_TCB *)0;
                    if (p_tcb->PendOn == OS_TASK_PEND_ON_MUTEX) {
                        p_tcb_owner = (OS_TCB *)((OS_MUTEX *)((void *)p_tcb->PendObjPtr))->OwnerTCBPtr;
                    }
#endif

#if (OS_MSG_EN > 0u)                                                            // 此宏用于使能消息队列功能或任务内嵌消息队列功能
                    // 因为任务直到挂起超时都未等待到消息,因此设置接收到的消息为空
                    p_tcb->MsgPtr  = (void *)0;
                    p_tcb->MsgSize = 0u;
#endif
#if (OS_CFG_TS_EN > 0u)
                    p_tcb->TS      = OS_TS_GET();
#endif
                    OS_PendListRemove(p_tcb);                                   // 将任务从挂起态任务链表中移除

                    switch (p_tcb->TaskState) {                                 // 根据任务的状态,处理任务
                        case OS_TASK_STATE_PEND_TIMEOUT:
                            // 如果任务只是等待事件挂起超时,那么仅需将等待事件挂起超时的任务设置为就绪态,并将任务添加到就绪态任务链表即可
                            OS_RdyListInsert(p_tcb);
                            p_tcb->TaskState  = OS_TASK_STATE_RDY;
                            break;

                        case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
                            // 如果任务在挂起期间被其他任务调用函数 OsTaskSenpend() 挂起,则在任务等待事件挂起超时后,将任务设置为挂起态,
                            // 因为被函数 OsTaskSuspend() 挂起的任务只能调用函数 OSTaskResume() 后恢复
                            p_tcb->TaskState  = OS_TASK_STATE_SUSPENDED;
                            break;

                        default:
                            break;
                    }
                    p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT;                 // 设置任务的挂起状态为挂起超时
                    p_tcb->PendOn     = OS_TASK_PEND_ON_NOTHING;                // 设置任务挂起等待对象为无

#if (OS_CFG_MUTEX_EN > 0u)                                                      // 此宏用于使能互斥信号量
                    if (p_tcb_owner != (OS_TCB *)0) {                           // 判断任务是否有等待的互斥信号量
                        // 判断所等待互斥信号量的持有者是否继承了挂起超时任务的任务优先级
                        // 如果是,则需要令互斥信号量的持有者继承其他挂起任务的任务优先级
                        if ((p_tcb_owner->Prio != p_tcb_owner->BasePrio) &&
                            (p_tcb_owner->Prio == p_tcb->Prio)) {       /* Has the owner inherited a priority?                  */
                            prio_new = OS_MutexGrpPrioFindHighest(p_tcb_owner);
                            prio_new = (prio_new > p_tcb_owner->BasePrio) ? p_tcb_owner->BasePrio : prio_new;
                            if (prio_new != p_tcb_owner->Prio) {
                                OS_TaskChangePrio(p_tcb_owner, prio_new);
                                OS_TRACE_MUTEX_TASK_PRIO_DISINHERIT(p_tcb_owner, p_tcb_owner->Prio);
                            }
                        }
                    }
#endif
                    break;
            }

            p_list->TCB_Ptr = p_tcb->TickNextPtr;                               // 获取 Tick任务链表中的下一个任务*
            p_tcb           = p_list->TCB_Ptr;
  
            if (p_tcb == (OS_TCB *)0) {                                         // 如果获取到的任务为空,则说明 Tick任务链表遍历完成
#if (OS_CFG_DBG_EN > 0u)
                p_list->NbrEntries = 0u;
#endif
                break;
            } else {
#if (OS_CFG_DBG_EN > 0u)
                p_list->NbrEntries--;
#endif
                p_tcb->TickPrevPtr = (OS_TCB *)0;
                // 更新当前遍历到的 Tick 任务链表中任务的剩余挂起时间
                if (p_tcb->TickRemain <= ticks) {
                    ticks              = ticks - p_tcb->TickRemain;
                    p_tcb->TickRemain  = 0u;
                } else {
                    p_tcb->TickRemain -= ticks;
                }
            }
        }
    }
#if (OS_CFG_DBG_EN > 0u)
    p_list->NbrUpdated = nbr_updated;
#endif
}

  函数 OS_TickListUpdate() 主要就是判断 Tick 任务链表中的任务是否超时,如果任务挂起时间超时,则会对齐进行进一步的处理。同时,函数 OS_TickListUpdate() 中还设计了一些与互斥信号量优先级继承相关的内容。

  在 µC/OS-Ⅲ 的时钟节拍处理过程中,最主要就是完成以下两件事:

  1. 更新系统时钟节拍计数器(全局变量 OSTickCtr)。
  2. 处理 Tick 任务链表。

  其中系统时钟节拍计数器 OSTickCtr 被用于 µC/OS-Ⅲ 中许多与时间相关的处理中,还有就是处理 Tick 任务链表,Tick 任务链表主要就是存放一些因延时或等待事件而被挂起的任务,这些任务被挂起时,都设置了一个挂起的超时时间,例如延时的时间就是被延时任务的挂起超时时间,还有最大等待时长就是因等待事件而被挂起的任务的挂起超时时间。

二、µC/OS-Ⅲ任务延时相关函数

  µC/OS-Ⅲ 中于任务延时相关的函数如下所示:

// 以系统时钟节拍为单位进行任务延时
void  OSTimeDly (OS_TICK   dly,                 // 任务延时的系统时钟节拍数
                 OS_OPT    opt,                 // 延时选项
                 OS_ERR   *p_err);              // 指向接收错误代码变量的指针

  函数 OSTimeDle()的延时选项(opt)描述,如下所示:

OS_OPT_TIME_DLY             // 任务延时的结束时刻为 OSTickCtr = OSTickCtr + dly,整个任务延迟延迟dly
OS_OPT_TIME_TIMEOUT         // 任务延时的结束时刻为 OSTickCtr = OSTickCtr + dly,整个任务延迟延迟dly
OS_OPT_TIME_MATCH           // 任务延时的结束时刻为 OSTickCtr = dly(绝对时间),一个周期中只能执行一次
OS_OPT_TIME_PERIODIC        // 任务延时的结束时刻为 OSTickCtr = OSTCBCurPtr -> TickCtrPrev+dly,调用延迟函数后,再延迟dly

  函数 OSTimeDly() 的错误代码描述,如下所示:

OS_ERR_NONE             // 开始任务延时成功
OS_ERR_OPT_INVALID      // 无效的任务操作选项
OS_ERR_OS_NOT_RUNING    // µC/OS-Ⅲ 内核还未运行
OS_ERR_SCHED_LOCKED     // 任务调度器已锁定
OS_ERR_TIME_DLY_ISR     // 在中断中非法调用该函数
OS_ERR_TIME_ZERO_DLY    // 有效的延时时长为 0 个系统时钟节拍
// 以时、分、秒、毫秒为单位进行任务延时
void  OSTimeDlyHMSM (CPU_INT16U   hours,        // 任务延时的小时数
                     CPU_INT16U   minutes,      // 任务延时的分钟数
                     CPU_INT16U   seconds,      // 任务延时的秒数
                     CPU_INT32U   milli,        // 任务延时的毫秒数
                     OS_OPT       opt,          // 延时选项
                     OS_ERR      *p_err);       // 指向接收错误代码变量的指针

  函数 OSTimeDlyHMSM() 的延时选项(opt)描述,如下所示:

OS_OPT_TIME_HMSM_STRICT         // 延时时间参数严格按照实际的时间格式,小时数(0~99)分钟数(0~59)秒数(0~59)毫秒数(0~999)
OS_OPT_TIME_HMSM_NON_STRICT     // 延时时间参数无需严格按照实际的时间格式,小时数(0~999)分钟数(0~9999)秒数(0~65535)毫秒数(0~4294967295)
OS_OPT_TIME_DLY                 // 任务延时的结束时刻为 OSTickCtr = OSTickCtr + dly,整个任务延迟延迟dly
OS_OPT_TIME_TIMEOUT             // 任务延时的结束时刻为 OSTickCtr = OSTickCtr + dly,整个任务延迟延迟dly
OS_OPT_TIME_MATCH               // 任务延时的结束时刻为 OSTickCtr = dly(绝对时间),一个周期中只能执行一次
OS_OPT_TIME_PERIODIC            // 任务延时的结束时刻为 OSTickCtr = OSTCBCurPtr -> TickCtrPrev + dly,调用延迟函数后,再延迟dly

  函数 OSTimeDlyHMSM() 的错误代码描述,如下所示:

OS_ERR_NONE                         // 开始任务延时成功
OS_ERR_OPT_INVALID                  // 无效的任务操作选项
OS_ERR_OS_NOT_RUNING                // µC/OS-Ⅲ 内核还未运行
OS_ERR_SCHED_LOCKED                 // 任务调度器已锁定
OS_ERR_TIME_DLY_ISR                 // 在中断中非法调用该函数
OS_ERR_TIME_INVALID_HOURS           // 无效的任务延时小时数
OS_ERR_TIME_INVALID_MINUTES         // 无效的任务延时分钟数
OS_ERR_TIME_INVALID_SECONDS         // 无效的任务延时秒数
OS_ERR_TIME_INVALID_MILLISECONDS    // 无效的任务延时毫秒数
OS_ERR_TIME_ZERO_DLY                // 有效的延时时长为 0 个系统时钟节拍

函数 OSTimeDlyHMSM() 的函数实现与函数 OSTimeDly() 类似,仅仅是多了一步将以 “时、分、秒、毫秒” 为单位的延时时长转换为以 “系统时钟节拍” 为单位的延时时长。

使用该函数须将宏 OS_CFG_TIME_DLY_HMSM_EN 置 1。

// 恢复被延迟的任务
void  OSTimeDlyResume (OS_TCB  *p_tcb,          // 指向任务控制块的指针
                       OS_ERR  *p_err);         // 指向接收错误代码变量的指针

  函数 OSTimeDlyResume() 的错误代码描述,如下所示:

OS_ERR_NONE                 // 开始任务延时成功
OS_ERR_OS_NOT_RUNING        // µC/OS-Ⅲ 内核还未运行
OS_ERR_STATE_INVALID        // 任务处于无效状态
OS_ERR_TASK_NOT_DLY         // 任务未进行任务延时
OS_ERR_TASK_SUSPEND         // 任务被函数 OSTaskSuspend() 挂起
OS_ERR_TCB_INVALID          // 无效的任务控制块指针参数
OS_ERR_TIME_DLY_RESUME_ISR  // 在中断中非法调用该函数

使用该函数须将宏 OS_CFG_TIME_DLY_RESUME_EN 置 1。

三、实验例程

  main() 函数内容如下:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    LED_Init();
  
    UC_OS3_Demo();
  
    return 0;
}

  µC/OS-Ⅲ例程入口函数:

/**
 * @brief µC/OS-Ⅲ例程入口函数
 * 
 */
void UC_OS3_Demo(void)
{
    OS_ERR error = {0};

    OSInit(&error);                                                             // 初始化µC/OS-Ⅲ

    // 创建开始任务
    OSTaskCreate((OS_TCB *   )  &start_task_tcb,                                // 任务控制块
                (CPU_CHAR *  )  "start_task",                                   // 任务名
                (OS_TASK_PTR )  Start_Task,                                     // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  START_TASK_PRIORITY,                            // 任务优先级
                (CPU_STK *   )  start_task_stack,                               // 任务堆栈
                (CPU_STK_SIZE)  START_TASK_STACK_SIZE / 10,                     // 任务栈的使用警戒线
                (CPU_STK_SIZE)  START_TASK_STACK_SIZE,                          // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    OSStart(&error);                                                            // 开始任务调度
}

  START_TASK 任务配置:

/**
 * START_TASK 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define START_TASK_PRIORITY     5
#define START_TASK_STACK_SIZE   256

OS_TCB  start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];

void Start_Task(void *p_arg);

/**
 * @brief 开始任务的任务函数
 * 
 * @param p_arg 任务参数
 */
void Start_Task(void *p_arg)
{
    OS_ERR error = {0};
    CPU_INT32U cnts = 0;

    CPU_Init();                                                                 // 初始化CPU库

    CPU_SR_ALLOC();                                                             // 临界区保护

    cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
    OS_CPU_SysTickInit(cnts);                                                   // 根据配置的节拍频率配置SysTick中断及优先级

    CPU_CRITICAL_ENTER();                                                       // 进入临界区

    // 创建任务1
    OSTaskCreate((OS_TCB *   )  &task1_tcb,                                     // 任务控制块
                (CPU_CHAR *  )  "task1",                                        // 任务名
                (OS_TASK_PTR )  Task1,                                          // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  TASK1_PRIORITY,                                 // 任务优先级
                (CPU_STK *   )  task1_stack,                                    // 任务堆栈
                (CPU_STK_SIZE)  TASK1_STACK_SIZE / 10,                          // 任务栈的使用警戒线
                (CPU_STK_SIZE)  TASK1_STACK_SIZE,                               // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    // 创建任务2
    OSTaskCreate((OS_TCB *   )  &task2_tcb,                                     // 任务控制块
                (CPU_CHAR *  )  "task2",                                        // 任务名
                (OS_TASK_PTR )  Task2,                                          // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  TASK2_PRIORITY,                                 // 任务优先级
                (CPU_STK *   )  task2_stack,                                    // 任务堆栈
                (CPU_STK_SIZE)  TASK2_STACK_SIZE / 10,                          // 任务栈的使用警戒线
                (CPU_STK_SIZE)  TASK2_STACK_SIZE,                               // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    CPU_CRITICAL_EXIT();                                                        // 退出临界区

    OSTaskDel(NULL, &error);                                                    // 删除任务
}

  TASK1 任务配置:

/**
 * TASK1 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define TASK1_PRIORITY          4
#define TASK1_STACK_SIZE        256

OS_TCB  task1_tcb;
CPU_STK task1_stack[TASK1_STACK_SIZE];

void Task1(void *p_arg);

/**
 * @brief 任务1的任务函数
 * 
 * @param p_arg 任务参数
 */
void Task1(void *p_arg)
{
    OS_ERR error = {0};

    while (1)
    {
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
        OSTimeDly(1000, OS_OPT_TIME_DLY, &error);
    }
}

  TASK2 任务配置:

/**
 * TASK2 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define TASK2_PRIORITY          3
#define TASK2_STACK_SIZE        256

OS_TCB  task2_tcb;
CPU_STK task2_stack[TASK2_STACK_SIZE];

void Task2(void *p_arg);

/**
 * @brief 任务2的任务函数
 * 
 * @param p_arg 任务参数
 */
void Task2(void *p_arg)
{
    OS_ERR error = {0};
  
    while (1)
    {
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
        OSTimeDlyHMSM(0, 0, 1, 0, OS_OPT_TIME_HMSM_NON_STRICT | OS_OPT_TIME_DLY, &error);
    }
}
posted @ 2024-02-14 19:09  星光映梦  阅读(20)  评论(0编辑  收藏  举报