時鍾節拍


  時鍾節拍可謂是 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

            

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM