zoukankan      html  css  js  c++  java
  • 时钟节拍

      时钟节拍可谓是 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

                

     

  • 相关阅读:
    coco2d-js demo程序之滚动的小球
    【leetcode】Happy Number(easy)
    【leetcode】Remove Linked List Elements(easy)
    【leetcode】LRU Cache(hard)★
    【QT】计时器制作
    【leetcode】Min Stack(easy)
    【leetcode】Compare Version Numbers(middle)
    【leetcode】Excel Sheet Column Title & Excel Sheet Column Number (easy)
    【leetcode】Binary Search Tree Iterator(middle)
    【leetcode】Number of Islands(middle)
  • 原文地址:https://www.cnblogs.com/tianxxl/p/10365322.html
Copyright © 2011-2022 走看看