1. 系统节拍时钟相关的API函数
函数 | 描述 |
---|---|
SysTick_Handler() | 系统节拍中断服务函数,即SysTick定时器中断函数 |
xPortSysTickHandler() | 调用函数xTaskIncrementTick(),根据其返回值判断是否触发PendSV中断 |
xTaskIncrementTick() | 系统节拍中断服务函数最终调用该函数完成主要工作,主要是判断下一个阻塞任务是否超时和是否需要进行任务切换等 |
2. 系统节拍时钟定时器
2.1 系统节拍时钟定时器SysTick介绍
在FreeRTOS中,系统的运行是由系统节拍时钟驱动的,同时任务的延时和阻塞都是以系统节拍时钟周期为单位,系统时钟节拍定时器使用的是SysTick定时器,在FreeRTOSConfig.h中需要设置:
#define configTICK_RATE_HZ (1000) //时钟节拍频率,这里设置为1000,周期就是1ms
在FreeRTOS中,系统节拍时钟周期为时钟节拍频率的导数,即T=1/f,比如设置时钟节拍频率为1000,则系统节拍时钟周期为1/1000(s),即为1ms。
2.2 系统节拍时钟定时器配置
在开启任务调度器时,会调用函数vTaskStartScheduler(),该函数会调用函数xPortStartScheduler()配置系统节拍定时器,如下:
BaseType_t xPortStartScheduler( void )
{
/*****************************************
此处省略部分代码
*****************************************/
/*
* 每个中断占据一个8位的优先级寄存器设置,数值越小,优先级越高
* 设置PendSV和SysTick中断的优先级为0xF0,即最低优先级
*/
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 初始化系统节拍定时器,即系统的SysTick定时器 */
vPortSetupTimerInterrupt();
/*****************************************
此处省略部分代码
*****************************************/
return 0; /* 一般不会返回 */
}
可以知道,调用了portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI设置SysTick的中断优先级,其中:
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
根据《Cortex M3权威指南》中可以知道,每个中断占据一个8位的优先级寄存器设置,数值越小,优先级越高。同时,4个相临的寄存器组成32位的寄存器。在0xE000 ED20地址,PendSV和SysTick分别占据次高8位和最高8位。
又因为每个8位优先级寄存器中高4位才有效,则执行
/* 获取高4位,即0xF0 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
最后,将configKERNEL_INTERRUPT_PRIORITY 右移24位,即设置SysTick中断的优先级为0xF0,即最低优先级。
配置完SysTick优先级后,就会调用函数vPortSetupTimerInterrupt()初始化SysTick定时器,并开启SysTick定时器。
2.3 函数 vPortSetupTimerInterrupt()
void vPortSetupTimerInterrupt( void )
{
/* 如果启用低功耗TickLess模式 */
#if configUSE_TICKLESS_IDLE == 1
{
/* 计算一个时钟节拍包含多少个时钟计数,即等于系统节拍时钟重装载值 */
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
/*
* 计算进入低功耗模式的最大时钟节拍
* xMaximumPossibleSuppressedTicks = 0xFFFF FF/(180000000/1000) ≈ 93
*/
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
/* 计算低功耗模式需要的补偿时间,需要赋值给FreeRTOS系统时钟 */
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
}
#endif /* configUSE_TICKLESS_IDLE */
/*
* 设置系统节拍时钟重装载值,该时钟是一个递减的24位数的定时器
* configSYSTICK_CLOCK_HZ 为系统时钟频率,configTICK_RATE_HZ 为时钟节拍频率
* 可以得到,节拍时钟中断周期为:T = ( 1/configTICK_RATE_HZ ) s
*/
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
/* 使能时钟/使能中断/开启系统节拍时钟 */
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
3. 系统节拍时钟定时器源代码分析
3.1 中断函数SysTick_Handler()
系统节拍时钟的中断函数为SysTick_Handler(),如果前期配置configTICK_RATE_HZ为1000,则中断函数周期为1ms,即1ms中断一次。
函数源代码如下:
void SysTick_Handler(void)
{
/* 如何调度器已经开启,运行或者挂起都算开启 */
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
3.2 函数xPortSysTickHandler()
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI(); /* 关闭中断 */
{
/* 如果返回值标记了任务切换,即有优先级高的任务 */
if( xTaskIncrementTick() != pdFALSE )
{
/* 设置PendSV中断位 */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR(); /* 打开中断 */
}
其中:
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
通过向中断控制及状态寄存器ICSR(地址:0xE000 ED04)的第28位写入1,触发PendSV中断,从而执行任务切换。
3.3 函数xTaskIncrementTick()
该函数记录系统节拍定时器数xTickCount,同时会判断下一个唤醒的任务是否到来。当调度器挂起时,也会记录挂起的总系统节拍数uxPendedTicks。
函数原型如下:
/********************************************************
参数:无
返回:pdTRUE:需要进行任务切换
pdFALSE:不需要进行任务切换
*********************************************************/
BaseType_t xTaskIncrementTick( void )
函数源代码如下:
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK( xTickCount );
/* 如果调度器没有挂起 */
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* 系统时钟节拍加1 */
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 系统时钟节拍为0,说明xConstTickCount溢出,则交互两个延时列表 */
if( xConstTickCount == ( TickType_t ) 0U )
{
/* 交换两个延时列表,延时列表变成溢出延时列表,溢出延时列表变成延时列表 */
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果系统节拍数xConstTickCount大于或等于xNextTaskUnblockTime,说明下一个唤醒任务的时间到或者超时 */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
/* 如果延时列表为空 */
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY; /* 下一个唤醒任务事件设置portMAX_DELAY */
break; /* 跳出for( ;; )循环 */
}
else /* 如果延时列表中有阻塞的任务 */
{
/* 得到延时列表中第一个列表项, 延时列表中用延时时间长短排序,时间短的在前 */
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
/* 获取下一个唤醒任务的延时节拍数 */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
/* 如果下一个唤醒任务的延时节拍数大于系统节拍数 */
if( xConstTickCount < xItemValue )
{
/* 设置下一个唤醒任务的延时时间 */
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 将唤醒的任务从延时列表中移除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 如果任务没有事件阻塞,如信号量等 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 将任务添加到就绪列表中 */
prvAddTaskToReadyList( pxTCB );
/* 如果使用抢占式内核 */
#if ( configUSE_PREEMPTION == 1 )
{
/* 唤醒的任务优先级比当前运行任务的优先级高 */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE; /* 标记需要进行任务切换, xSwitchRequired为返回值*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
/* 如果使用抢占式内核和使用时间片调度方式 */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
/* 如果当前运行任务的优先级下有多个任务 */
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE; /* 标记需要进行任务切换, xSwitchRequired为返回值*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
/* 如果使用时间片钩子函数 */
#if ( configUSE_TICK_HOOK == 1 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook(); /* Tick钩子函数 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else /* 如果任务调度器挂起 */
{
/*
* 记录调度器挂起时的中断节拍数
* 在vTaskResumeAll()中恢复调度器会调用uxPendedTicks次xTaskIncrementTick来恢复
*/
++uxPendedTicks;
/* 如果使用时间片钩子函数 */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook(); /* Tick钩子函数 */
}
#endif
}
/* 如果使用抢占式内核 */
#if ( configUSE_PREEMPTION == 1 )
{
/* xYieldPending为全局变量,为pdTRUE时,标记需要进行任务切换 */
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE; /* 标记需要进行任务切换, xSwitchRequired为返回值*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired;
}
参考资料:
【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》
【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》
【3】: 《Cortex M3权威指南》