时钟节拍类似于人体心脏的跳动,人体依赖心脏的跳动将血液输入身体各个部位,支撑生命活动。时钟节拍的是操作系统的时基,操作系统依赖于时钟节拍推动 CPU 去执行指令。
1 时钟节拍原理
时钟节拍是系统以固定的频率产生中断(时基中断),并在中断处理与时间相关的事件,推动所有任务向前运行。时钟节拍需要依赖于硬件定时器,STM32 通常使用 systick 时钟作为 MCU 的内核定时器。
2 系统时钟初始化
初始化流程void BSP_Tick_Init (void)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
//获取CPU内核时钟频率(SysTick 工作时钟)
cpu_clk_freq = BSP_CPU_ClkFreq();
//根据用户设置的时钟节拍频率计算 SysTick 定时器的计数值
#if (OS_VERSION >= 30000u)
/* Determine nbr SysTick increments */
cnts = (cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz);
#else
/* Determine nbr SysTick increments. */
cnts = (cpu_clk_freq / (CPU_INT32U)OS_TICKS_PER_SEC);
#endif
//调用 SysTick 初始化函数,设定定时器的计数值,并启动定时器
/* Init uC/OS periodic time src (SysTick). */
OS_CPU_SysTickInit(cnts);
}
3 系统时钟中断管理
根据系统时钟的初始化,在系统计数达到后,产生时钟中断,并调用中断处理函数 OS_CPU_SysTickHandler
。
/*
*********************************************************************************************************
* SYS TICK HANDLER
*
* Description: Handle the system tick (SysTick) interrupt, which is used to generate the uC/OS-II tick
* interrupt.
*
* Arguments : None.
*
* Note(s) : 1) This function MUST be placed on entry 15 of the Cortex-M3 vector table.
*********************************************************************************************************
*/
void OS_CPU_SysTickHandler (void)
{
CPU_SR_ALLOC(); //分配保存中断状态的局部变量,后面关中断的时候可以保存中断状态
//ISR表示 interrupt service routine
//CPU_CRITICAL_ENTER 和 CPU_CRITICAL_EXIT 之间形成临界区,避免期间程序运行时受到干扰
CPU_CRITICAL_ENTER();
OSIntNestingCtr++; /* Tell uC/OS-III that we are starting an ISR */
CPU_CRITICAL_EXIT();
OSTimeTick(); /* Call uC/OS-III's OSTimeTick() */
//退出中断,中断嵌套计数减一
OSIntExit(); /* Tell uC/OS-III that we are leaving the ISR */
}
在 OS_CPU_SysTickHandler
函数中调用了 UCOS 的时间片处理函数 OSTimeTick
,对系统的时间片进行处理。
/*
************************************************************************************************************************
* 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
************************************************************************************************************************
*/
void OSTimeTick (void)
{
OS_ERR err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
CPU_TS ts;
#endif
OSTimeTickHook(); /* Call user definable hook */
//如果使能了中断发送延迟
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
ts = OS_TS_GET(); /* Get timestamp */
//任务信号量暂时发送到中断队列中,退出中断后由优先级最高的延迟发布任务
//就绪发送给时钟节拍任务 OS_TickTask, OS_TickTask 接收到该信号量就会继续执行
//中断发送延迟可以减少中断时间,将中断事件转化为任务级,可以提高操作系统的实时性
OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, /* Post to ISR queue */
(void *)&OSRdyList[OSPrioCur],
(void *) 0,
(OS_MSG_SIZE) 0u,
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err);
#else
//如果禁止了中断发送延迟,直接发送信号量给时钟节拍任务 OS_TickTask
(void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
//如果使能了同优先级任务时间片轮转调度,检查当前任务的时间片是否耗尽
//如果耗尽就调用同优先级的其他任务
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif
//如果使能了软件定时器,软件定时器自减
//如果软件定时器减至 0,重载软件定时器计数器
//发送信号量给软件定时器任务
#if OS_CFG_TMR_EN > 0u
OSTmrUpdateCtr--;
if (OSTmrUpdateCtr == (OS_CTR)0u) {
OSTmrUpdateCtr = OSTmrUpdateCnt;
OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB, /* Signal timer task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
}
#endif
#endif
}
4 时基任务
在 OSTimeTick
函数中给时基任务、定时器任务都发送了信号量。这里先介绍时基任务。时基任务是在 OS 初始化函数 void OSInit (OS_ERR *p_err)
中创建。
/*
************************************************************************************************************************
* INITIALIZE TICK TASK
*
* Description: This function is called by OSInit() to create the tick task.
*
* Arguments : p_err is a pointer to a variable that will hold the value of an error code:
*
* OS_ERR_TICK_STK_INVALID if the pointer to the tick task stack is a NULL pointer
* OS_ERR_TICK_STK_SIZE indicates that the specified stack size
* OS_ERR_PRIO_INVALID if the priority you specified in the configuration is invalid
* (There could be only one task at the Idle Task priority)
* (Maybe the priority you specified is higher than OS_CFG_PRIO_MAX-1
* OS_ERR_?? other error code returned by OSTaskCreate()
*
* Returns : none
*
* Note(s) : This function is INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/
void OS_TickTaskInit (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
/* Clear the tick counter */
//清除系统计数值
OSTickCtr = (OS_TICK)0u;
OSTickListDly.TCB_Ptr = (OS_TCB *)0;
OSTickListTimeout.TCB_Ptr = (OS_TCB *)0;
#if OS_CFG_DBG_EN > 0u
OSTickListDly.NbrEntries = (OS_OBJ_QTY)0;
OSTickListDly.NbrUpdated = (OS_OBJ_QTY)0;
OSTickListTimeout.NbrEntries = (OS_OBJ_QTY)0;
OSTickListTimeout.NbrUpdated = (OS_OBJ_QTY)0;
#endif
/* ---------------- CREATE THE TICK TASK ----------- */
if (OSCfg_TickTaskStkBasePtr == (CPU_STK *)0) {
*p_err = OS_ERR_TICK_STK_INVALID;
return;
}
if (OSCfg_TickTaskStkSize < OSCfg_StkSizeMin) {
*p_err = OS_ERR_TICK_STK_SIZE_INVALID;
return;
}
/* Only one task at the 'Idle Task' priority */
if (OSCfg_TickTaskPrio >= (OS_CFG_PRIO_MAX - 1u)) {
*p_err = OS_ERR_TICK_PRIO_INVALID;
return;
}
OSTaskCreate((OS_TCB *)&OSTickTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Tick Task"),
(OS_TASK_PTR )OS_TickTask,
(void *)0,
(OS_PRIO )OSCfg_TickTaskPrio,
(CPU_STK *)OSCfg_TickTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_TickTaskStkLimit,
(CPU_STK_SIZE)OSCfg_TickTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
}
/*
************************************************************************************************************************
* TICK TASK
*
* Description: This task is internal to uC/OS-III and is triggered by the tick interrupt.
*
* Arguments : p_arg is an argument passed to the task when the task is created (unused).
*
* Returns : none
*
* Note(s) : This function is INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/
void OS_TickTask (void *p_arg)
{
OS_ERR err;
CPU_TS ts_delta;
CPU_TS ts_delta_dly;
CPU_TS ts_delta_timeout;
CPU_SR_ALLOC();
/* Prevent compiler warning */
/* Wait for signal from tick interrupt */
(void)&p_arg;
while (DEF_ON) {
//等待信号量
(void)OSTaskSemPend((OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)0,
(OS_ERR *)&err);
if (err == OS_ERR_NONE) {
/* Keep track of the number of ticks */
OS_CRITICAL_ENTER();
OSTickCtr++;
#if (defined(TRACE_CFG_EN) && (TRACE_CFG_EN > 0u))
/* Record the event. */
TRACE_OS_TICK_INCREMENT(OSTickCtr);
#endif
OS_CRITICAL_EXIT();
//遍历更新时延任务列表
ts_delta_dly = OS_TickListUpdateDly();
//遍历更新超时任务列表
ts_delta_timeout = OS_TickListUpdateTimeout();
/* Compute total execution time of list updates */
ts_delta = ts_delta_dly + ts_delta_timeout;
if (OSTickTaskTimeMax < ts_delta) {
OSTickTaskTimeMax = ts_delta;
}
}
}
}
5 总结
本章阐述了时钟节拍的工作原理,看似微小却是整个 uC/OS 系统的命脉。时钟节拍的运行依赖于 CPU 的定时器, STM32 专门为此量身定制了 SysTick 时钟。每个时钟节拍到来时,时基任务就会执行,节拍任务的重点是更新节拍任务列表。在节拍列表中,存放的均是与时间事件(如延时或超时)相关的任务。如果任务到期,则需要更新响应的任务状态。
网友评论