时钟节拍

  时钟节拍可谓是 uC/OS 操作系统的心脏,它若不跳动,整个系统都将会瘫痪。时钟节拍就是操作系统的时基,操作系统要实现时间上的管理,必须依赖于时基。
  时钟节拍就是系统以固定的频率产生中断(时基中断),并在中断中处理与时间相关的事件,推动所有任务向前运行。时钟节拍需要依赖于硬件定时器,在 STM32 裸机程序中经常使用的 SysTick 时钟是 MCU的内核定时器,通常都使用该定时器产生操作系统的时钟节拍。
  用户需要先在“os_cfg_app.h”中设定时钟节拍的频率,该频率越高,操作系统检测事件就越频繁,可以增强任务的实时性,但太频繁也会增加操作系统内核的负担加重,所以用户需要权衡该频率的设置。秉火在这里采用默认的 1000 Hz(本书之后若无特别声明,均采用 1000 Hz),也就是时钟节拍的周期为 1 ms。 

   设置时钟节拍的频率 :

                                                            /* ------------------------ TICKS ----------------------- */
#define  OS_CFG_TICK_RATE_HZ            1000u               // 时钟节拍频率 (10 to 1000 Hz)                    
#define  OS_CFG_TICK_TASK_PRIO            10u               // 时钟节拍任务 OS_TickTask() 的优先级
#define  OS_CFG_TICK_TASK_STK_SIZE       128u               // 时钟节拍任务 OS_TickTask() 的栈空间大小
#define  OS_CFG_TICK_WHEEL_SIZE           17u               // OSCfg_TickWheel 数组的大小,推荐使用任务总数/4,且为质数
View Code

  在app.c中的起始任务 AppTaskStart() 中初始化时钟节拍定时器,其实就是初始化 STM32 内核的 SysTick 时钟。

  初始化 SysTick 时钟 :

cpu_clk_freq = BSP_CPU_ClkFreq();                           //获取 CPU 内核时钟频率(SysTick 工作时钟)
    cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;        //根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值
    OS_CPU_SysTickInit(cnts);                                   //调用 SysTick 初始化函数,设置定时器计数值和启动定时器
View Code

  OS_CPU_SysTickInit() 函数的定义位于“os_cpu_c.c” :

void  OS_CPU_SysTickInit (CPU_INT32U  cnts)
{
    CPU_INT32U  prio;

    /* 填写 SysTick 的重载计数值 */
    CPU_REG_NVIC_ST_RELOAD = cnts - 1u;                     // SysTick 以该计数值为周期循环计数定时

    /* 设置 SysTick 中断优先级 */                           
    prio  = CPU_REG_NVIC_SHPRI3;                            
    prio &= DEF_BIT_FIELD(24, 0);
    prio |= DEF_BIT_MASK(OS_CPU_CFG_SYSTICK_PRIO, 24);      //设置为默认的最高优先级0,在裸机例程中该优先级默认为最低

    CPU_REG_NVIC_SHPRI3 = prio;

    /* 使能 SysTick 的时钟源和启动计数器 */                   
    CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_CLKSOURCE |
                            CPU_REG_NVIC_ST_CTRL_ENABLE;
    /* 使能 SysTick 的定时中断 */                            
    CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_TICKINT;
}
View Code

  SysTick 定时中断函数 OS_CPU_SysTickHandler() 的定义也位于“os_cpu_c.c”,就毗邻 OS_CPU_SysTickInit() 函数定义体的上方。

void  OS_CPU_SysTickHandler (void)
{
    CPU_SR_ALLOC();       //分配保存中断状态的局部变量,后面关中断的时候可以保存中断状态


    CPU_CRITICAL_ENTER(); // CPU_CRITICAL_ENTER() 和 CPU_CRITICAL_EXIT() 之间形成临界段,避免期间程序运行时受到干扰
    OSIntNestingCtr++;    //进入中断时中断嵌套数要加1       
    CPU_CRITICAL_EXIT();

    OSTimeTick();        //调用 OSTimeTick() 函数                             

    OSIntExit();         //退出中断,里面回家中断嵌套数减1             
}
View Code

    OSTimeTick ()的定义位于“os_time.c”。

void  OSTimeTick (void)
{
    OS_ERR  err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
    CPU_TS  ts;
#endif


    OSTimeTickHook();                                //调用用户可自定义的钩子函数,可在此函数中定义在时钟节拍到来时的事件

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u                 //如果使能(默认使能)了中断发送延迟

    ts = OS_TS_GET();                                //获取时间戳     
    OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK,      //任务信号量暂时发送到中断队列,退出中断后由优先级最高的延迟发布任务
                (void      *)&OSRdyList[OSPrioCur],  //就绪发送给时钟节拍任务 OS_TickTask(),OS_TickTask() 接收到该信号量
                (void      *) 0,                     //就会继续执行。中断发送延迟可以减少中断时间,将中断级事件转为任务级
                (OS_MSG_SIZE) 0u,                    //,提高了操作系统的实时性。
                (OS_FLAGS   ) 0u,
                (OS_OPT     ) 0u,
                (CPU_TS     ) ts,
                (OS_ERR    *)&err);

#else                                                //如果禁用(默认使能)了中断发送延迟

   (void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB,     //直接发送信号量给时钟节拍任务 OS_TickTask()    
                       (OS_OPT  ) OS_OPT_POST_NONE,
                       (OS_ERR *)&err);


#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u                 //如果使能(默认使能)了(同优先级任务)时间片轮转调度
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);       //检查当前任务的时间片是否耗尽,如果耗尽就调用同优先级的其他任务运行
#endif

#if OS_CFG_TMR_EN > 0u                               //如果使能(默认使能)了软件定时器
    OSTmrUpdateCtr--;                                //软件定时器计数器自减
    if (OSTmrUpdateCtr == (OS_CTR)0u) {              //如果软件定时器计数器减至0
        OSTmrUpdateCtr = OSTmrUpdateCnt;             //重载软件定时器计数器
        OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB,       //发送信号量给软件定时器任务 OS_TmrTask()
                      (OS_OPT  ) OS_OPT_POST_NONE,
                      (OS_ERR *)&err);
    }
#endif

#endif
}
View Code

  在函数 OSTimeTick () 会发送信号量给时基任务 OS_TickTask() ,任务 OS_TickTask() 接收到信号量后就会进入就绪状态,准备运行。 

void  OS_TickTask (void  *p_arg)
{
    OS_ERR  err;
    CPU_TS  ts;


    p_arg = p_arg;                                           //预防编译警告,没有实际意义

    while (DEF_ON) {                                         //循环运行
        (void)OSTaskSemPend((OS_TICK  )0,                    //等待来自时基中断的信号量,接收到信号量后继续运行
                            (OS_OPT   )OS_OPT_PEND_BLOCKING,
                            (CPU_TS  *)&ts,
                            (OS_ERR  *)&err);            
        if (err == OS_ERR_NONE) {                            //如果上面接受的信号量没有错误
            if (OSRunning == OS_STATE_OS_RUNNING) {          //如果操作系统正在运行
                OS_TickListUpdate();                         //更新所有任务的时间等待时间(如延时、超时等)
            }
        }
    }
}
View Code

  OS_TickListUpdate() 函数的定义位于“os_tick.c”。在一个任务将要进行延时或超时检测的时候,内核会将这些任务插入 OSCfg_TickWheel 数组的不同元素(一个元素组织一个节拍列表)中。插入操作位于 OS_TickListInsert() 函数(函数的定义也位于“os_tick.c”,就在OS_TickListUpdate() 函数定义的上方),通过任务的 TickCtrMatchTickCtrMatch=OSTickCtr 当前+需延时或超时节拍数)对 OSCfg_TickWheelSize 的取余(哈希算法)来决定将其插入OSCfg_TickWheel 数组的哪个元素(列表)。相对应的,在 OS_TickListUpdate() 函数中查找到
期任务时,为了能快速检测到到期的任务,通过 OSTickCtr OSCfg_TickWheelSize 的取余来决定操作 OSCfg_TickWheel 数组的哪个元素(列表)。TickCtrMatch 不变,OSTickCtr 一直在计数(逢一个时钟节拍加 1),OSTickCtr 等于 TickCtrMatch 时,延时或超时完成,所以此时它
俩对 OSCfg_TickWheelSize 的取余肯定相等,也就找到了到期任务在 OSCfg_TickWheel 数组的哪个元素了。这样就大大缩小了查找范围了,不用遍历 OSCfg_TickWheel 整个数组,缩小为1/OSCfg_TickWheelSize。但是在代码中,OSTickCtr TickCtrMatch OSCfg_TickWheelSize
取余相等,不一定该两变量就相等,只是可能相等,所以还得进一步判断 OSTickCtr TickCtrMatch 是否相等,所以在代码中可以看到对查找到元素(列表)还进行了进一步的判断(遍历)。在一个节拍列表中,是 TickCtrMatch 从小到大排序的,所以当遍历到 OSTickCtr
TickCtrMatch 相等时,还要继续遍历,因为下一个 TickCtrMatch 可能和当前的 TickCtrMatch相等;如若当遍历到 OSTickCtr TickCtrMatch 不相等时,后面的肯定也不相等,就无需继续遍历了。

    OS_TickListInsert() 函数中将任务插入 OSCfg_TickWheel 数组

spoke   = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize); //使用哈希算法(取余)来决定任务存于 OSCfg_TickWheel 数组
    p_spoke = &OSCfg_TickWheel[spoke];                                       //的哪个元素(节拍列表),与查找到期任务时对应,可方便查找。
View Code
        节拍列表更新函数 OS_TickListUpdate()
void  OS_TickListUpdate (void)
{
    CPU_BOOLEAN        done;
    OS_TICK_SPOKE     *p_spoke;
    OS_TCB            *p_tcb;
    OS_TCB            *p_tcb_next;
    OS_TICK_SPOKE_IX   spoke;
    CPU_TS             ts_start;
    CPU_TS             ts_end;
    CPU_SR_ALLOC();                                                   //使用到临界段(在关/开中断时)时必需该宏,该宏声明和定义一个局部变
                                                                      //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR)
                                                                      //,开中断时将该值还原。
    OS_CRITICAL_ENTER();                                              //进入临界段
    ts_start = OS_TS_GET();                                           //获取 OS_TickTask() 任务的起始时间戳
    OSTickCtr++;                                                      //时钟节拍数自加
    spoke    = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize);   //使用哈希算法(取余)缩小查找到期任务位于 OSCfg_TickWheel 数组的
    p_spoke  = &OSCfg_TickWheel[spoke];                               //哪个元素(一个节拍列表),与任务插入数组时对应,下面只操作该列表。
    p_tcb    = p_spoke->FirstPtr;                                     //获取节拍列表的首个任务控制块的地址
    done     = DEF_FALSE;                                             //使下面 while 体得到运行
    while (done == DEF_FALSE) {
        if (p_tcb != (OS_TCB *)0) {                                   //如果该任务不空(存在)
            p_tcb_next = p_tcb->TickNextPtr;                          //获取该列表中紧邻该任务的下一个任务控制块的地址  
            switch (p_tcb->TaskState) {                               //根据该任务的任务状态处理
                case OS_TASK_STATE_RDY:                               //如果任务状态均是与时间事件无关,就无需理会
                case OS_TASK_STATE_PEND:
                case OS_TASK_STATE_SUSPENDED:
                case OS_TASK_STATE_PEND_SUSPENDED:
                     break;

                case OS_TASK_STATE_DLY:                               //如果是延时状态
                     p_tcb->TickRemain = p_tcb->TickCtrMatch          //计算延时的的剩余时间 
                                       - OSTickCtr;
                     if (OSTickCtr == p_tcb->TickCtrMatch) {          //如果任务期满 
                         p_tcb->TaskState = OS_TASK_STATE_RDY;        //修改任务状态量为就绪状态
                         OS_TaskRdy(p_tcb);                           //让任务就绪
                     } else {                                         //如果任务未期满(由于升序排列,该列表后面的任务肯定也未期满)
                         done             = DEF_TRUE;                 //不再遍历该列表,退出 while 循环
                     }
                     break;

                case OS_TASK_STATE_PEND_TIMEOUT:                      //如果是有期限等待状态
                     p_tcb->TickRemain = p_tcb->TickCtrMatch          //计算期限的的剩余时间
                                       - OSTickCtr;
                     if (OSTickCtr == p_tcb->TickCtrMatch) {          //如果任务期满 
#if (OS_MSG_EN > 0u)                                                  //如果使能了消息队列(普通消息队列或任务消息队列)
                         p_tcb->MsgPtr     = (void      *)0;          //把任务保存接收到消息的地址的成员清空
                         p_tcb->MsgSize    = (OS_MSG_SIZE)0u;         //把任务保存接收到消息的长度的成员清零
#endif
                         p_tcb->TS         = OS_TS_GET();             //记录任务结束等待的时间戳
                         OS_PendListRemove(p_tcb);                    //从等待列表移除该任务
                         OS_TaskRdy(p_tcb);                           //让任务就绪
                         p_tcb->TaskState  = OS_TASK_STATE_RDY;       //修改任务状态量为就绪状态
                         p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT;  //记录等待状态为超时
                         p_tcb->PendOn     = OS_TASK_PEND_ON_NOTHING; //记录等待内核对象变量为空
                     } else {                                         //如果任务未期满(由于升序排列,该列表后面的任务肯定也未期满)
                         done              = DEF_TRUE;                //不再遍历该列表,退出 while 循环
                     }
                     break;

                case OS_TASK_STATE_DLY_SUSPENDED:                     //如果是延时中被挂起状态
                     p_tcb->TickRemain = p_tcb->TickCtrMatch          //计算延时的的剩余时间 
                                       - OSTickCtr;
                     if (OSTickCtr == p_tcb->TickCtrMatch) {          //如果任务期满
                         p_tcb->TaskState  = OS_TASK_STATE_SUSPENDED; //修改任务状态量为被挂起状态
                         OS_TickListRemove(p_tcb);                    //从节拍列表移除该任务
                     } else {                                         //如果任务未期满(由于升序排列,该列表后面的任务肯定也未期满)
                         done              = DEF_TRUE;                //不再遍历该列表,退出 while 循环
                     }
                     break;

                case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:            //如果是有期限等待中被挂起状态
                     p_tcb->TickRemain = p_tcb->TickCtrMatch          //计算期限的的剩余时间
                                       - OSTickCtr;
                     if (OSTickCtr == p_tcb->TickCtrMatch) {          //如果任务期满 
#if (OS_MSG_EN > 0u)                                                  //如果使能了消息队列(普通消息队列或任务消息队列)
                         p_tcb->MsgPtr     = (void      *)0;          //把任务保存接收到消息的地址的成员清空
                         p_tcb->MsgSize    = (OS_MSG_SIZE)0u;         //把任务保存接收到消息的长度的成员清零
#endif
                         p_tcb->TS         = OS_TS_GET();             //记录任务结束等待的时间戳
                         OS_PendListRemove(p_tcb);                    //从等待列表移除该任务
                         OS_TickListRemove(p_tcb);                    //从节拍列表移除该任务
                         p_tcb->TaskState  = OS_TASK_STATE_SUSPENDED; //修改任务状态量为被挂起状态
                         p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT;  //记录等待状态为超时
                         p_tcb->PendOn     = OS_TASK_PEND_ON_NOTHING; //记录等待内核对象变量为空
                     } else {                                         //如果任务未期满(由于升序排列,该列表后面的任务肯定也未期满)
                         done              = DEF_TRUE;                //不再遍历该列表,退出 while 循环
                     }
                     break;

                default:
                     break;
            }
            p_tcb = p_tcb_next;                                       //遍历节拍列表的下一个任务
        } else {                                                      //如果该任务为空(节拍列表后面肯定也都是空的)
            done  = DEF_TRUE;                                         //不再遍历该列表,退出 while 循环
        }
    }
    ts_end = OS_TS_GET() - ts_start;                                  //获取 OS_TickTask() 任务的结束时间戳,并计算其执行时间
    if (OSTickTaskTimeMax < ts_end) {                                 //更新 OS_TickTask() 任务的最大运行时间
        OSTickTaskTimeMax = ts_end;
    }
    OS_CRITICAL_EXIT();                                               //退出临界段
}
View Code

            

 

原文地址:https://www.cnblogs.com/tianxxl/p/10365322.html