目录
1 SysTick初始化
2 SysTick中断服务函数
3 SysTick任务调度
1 SysTick初始化
初始化流程
- 配置SysTick装载值
- 使能SysTick时钟源 使能SysTick中断 使能SysTick
其中装载值1ms、10ms、100ms都可以,但是不要小于1ms
//main.c //--->
osKernelStart(); //--->
xPortStartScheduler(); //--->
vPortSetupTimerInterrupt(); //--->
系统节拍初始化源码
在vPortSetupTimerInterrupt中
void vPortSetupTimerInterrupt( void )
{
/*
1、操作系统,时针对寄存器操作---效率搞
2、首先赋值装载寄存器值 = (CPU频率/配置的周期)-1
2.1 SystemClock_Config()
HAL_RCC_ClockConfig() CPU频率在硬件启动时就已经获取了
2.2 configTICK_RATE_HZ = 1000 由cubemx配置而得
3、配置控制寄存器
3.1、开启时钟源
3.2、使能中断
3.3、使能systick
4、可以参考M4权威指南 9.5章节---systick定时器
*/
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 );
}
2 SysTick中断服务函数
中断服务函数流程:
- 关闭中断
- Tick值增加SysTick任务调度
- 启动PendSV
- 开启中断
系统节拍中断服务函数
void xPortSysTickHandler( void )
{
/* 1、配置中断屏蔽寄存器
2、不让中断打断,SysTick中断服务
3、其实就是进入临界段
*/
vPortRaiseBASEPRI();
{
/*
操作系统调度接口
如果调度器返回true,触发pendSV异常
*/
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
// 清楚可屏蔽中断,就是打开全部中断---》PendSV就执行
vPortClearBASEPRIFromISR();
}
问:如何与操作系统内核联系起来?
3 SysTick任务调度
SysTick调度流程
- 系统节拍数加1,判断是否溢出溢出更新任务锁定时间
- 判断是否有任务需要解除阻塞,获取延时列表第一个任务控制块(时间排序),获取状态列表值判断时间是否到达,未到达退出
- 任务阻塞事件到达,从延时列表中删除,从事件列表中删除,添加到就绪列表
- 如果使用抢占内核,判断任务优先级是否大于当前任务,开启任务调度
- 如果使用时间片调度,判断当前优先级下是否还有其他任务,开启任务调度器
//SysTick任务调度
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
// 返回值,表示是否进行上下文切换
BaseType_t xSwitchRequired = pdFALSE;
/* uxSchedulerSuspended 表示内核是否挂起,pdFalse 内核没有挂起*/
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* tick计数增加1 */
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 判断tick是否溢出 */
if( xConstTickCount == ( TickType_t ) 0U )
{
//如果溢出,更新延时列表
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*
1、如果当前节拍大于时间片的锁定时间
2、说明有任务需要进行调度了,时间片用完了
*/
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )//会一直遍历整个任务延时列表,主要目的是,找到时间片最短的任务,进行调度
{
//判断任务延时列表中,是否为空,也就是说,有没有任务在等待调度
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
//如果没有任务等待,把时间片赋值为最大值,不再调度
xNextTaskUnblockTime = portMAX_DELAY;
break;
} //延时列表不为空
else
{
/*
1、从任务延时列表中,获取第一个任务控制块
1.1 延时列表,插入永远是把时间片最短的任务,放在第一个
2、获取任务控制块的延时时间
*/
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 )
{
/*
1、判断优先级是否大于当前优先级
1.1、大于则进行调度
*/
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
/* 时间片处理机制 */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
/*1、获取就绪列表的长度
1.1就绪列表指的是,当前任务优先级的列表
1.2如果其他任务在就绪列表中,就凯斯调度
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
}
else //内核调度器挂起了
{
//挂起的tick+1
++uxPendedTicks;
}
//如果是抢占模式,要开启调度器
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
//返回调度器状态
return xSwitchRequired;
}