FreeRTOS原理剖析:系统节拍时钟分析

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权威指南》

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值