FreeRTOS-时间管理(延时、系统时钟)源码分析

本文详细分析了FreeRTOS中的时间管理机制,重点讨论了相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil()的实现原理。通过任务阻塞、等待状态的转换,以及关键函数如xTaskIncrementTick()的解析,揭示了系统如何围绕systick定时器中断计数器进行任务调度。同时,文章还提到了在时基溢出时的任务处理策略以及调度器在延时操作中的挂起与恢复机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在写FreeRTOS的应用程序时,经常需要使用到延时函数,当执行到延时函数时,会将任务从就绪状态变为延时等待状态,这里会放弃CPU的占用权进入阻塞态,将CPU让给其它任务使用,直到延时时间结束会重新变为就绪态。FreeRTOS中的延时函数有两种模式,一种是相对模式,另一种是绝对模式。相对延时函数使用 vTaskDelay() ,相对延时函数是指每次执行都是从函数接口处开始计时,计时到指定时间结束回来继续往下执行。绝对延时函数使用 vTaskDelayUntil() ,绝对延时函数是指每隔一段指定的时间执行一次 vTaskDelayUntil() ,那些需要按照一定频率运行的任务可以使用绝对模式。


先分析一下相对模式的应用方式,一般我们是这样使用的:

void led_task(void *pvParameters)
{
	while(1)
    {
        LED0=!LED0;
        vTaskDelay(500); /* LED灯每隔500ms亮灭一次 */
    }
}

函数的实现如下:

#if ( INCLUDE_vTaskDelay == 1 )

	void vTaskDelay( const TickType_t xTicksToDelay )
	{
		BaseType_t xAlreadyYielded = pdFALSE;

		/* 如果延时时间大于0 */	
		if( xTicksToDelay > ( TickType_t ) 0U )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			
			/* 挂起任务调度器,调用几次就得恢复几次 */
			vTaskSuspendAll();
			{
				traceTASK_DELAY();

				/* 将任务从就绪列表中删除,添加当前任务到延时或溢出延时列表 */
				prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
			}
			/* 恢复任务调度器,这个函数在下面单独进行分析 */
			xAlreadyYielded = xTaskResumeAll();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}


		if( xAlreadyYielded == pdFALSE )
		{
			/* 如果 xTaskResumeAll 函数没有进行调度的话就在这里进行任务切换,此 */
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif

上面主要涉及到几个函数:

vTaskSuspendAll
prvAddCurrentTaskToDelayedList
xTaskResumeAll
portYIELD_WITHIN_API
void vTaskSuspendAll( void )
{
	/* 挂起调度器不需要关闭可屏蔽中断,这个变量递增后在systick中断函数里面判断到 uxSchedulerSuspended 
		不为 pdFALSE就不会去判断延时列表是否有任务可以执行,这里挂起主要为了避免调度器和即将运行的函数操作发生冲突,
		在systick操作了延时列表和计数器,接下来的函数操作中也会操作延时列表和计数器,所以需暂时屏蔽 */
	/* 挂起计数器(支持嵌套) */
	++uxSchedulerSuspended;
}

portYIELD_WITHIN_API() 实际上就是操作中断控制及状态寄存器ICSR,请求一次PendSV中断,PendSV任务切换具体步骤前面任务“FreeRTOS-任务切换源码分析”已经分析了,

#define portYIELD()																\
{																				\
	/* Set a PendSV to request a context switch. */								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

延时函数主要就是将任务从就绪状态切换为等待状态,使用 prvAddCurrentTaskToDelayedList 来实现:

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;

	/* 将任务从就绪列表中删除 */
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* 清除对应就绪优先级标志 */
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); 
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* 如果允许任务挂起 */
	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			/* 如果设置了最大延时时间
<think>好的,我现在需要解决用户提出的FreeRTOS添加后系统延时不对的问题,特别是系统延时配置错误的解决方案。首先,我得回忆一下FreeRTOS中关于系统延时的相关知识,并结合用户提供的引用内容来整理思路。 用户提到系统延时不对,可能的原因有哪些呢?根据经验,可能包括系统时钟配置错误、任务优先级设置问题、中断优先级冲突或者延时函数本身的使用不当。接下来需要逐一分析这些可能性,并结合引用中的信息来验证。 首先看引用[4],里面提到了System作为hal库维护时钟,us延时。这可能意味着用户在使用HAL库配置系统时钟时存在问题,导致FreeRTOS时钟基准不准确。FreeRTOS的心跳时钟(tick)依赖于硬件定时器的正确配置,如果定时器的中断频率设置错误,比如configTICK_RATE_HZ定义不正确,会导致任务调度和延时时间不准确。例如,如果configTICK_RATE_HZ设置为1000,理论上每个tick是1ms,但如果实际定时器配置错误,可能导致每个tick的时间不同,从而影响延时。 接着,引用[1]提到检查osKernelInitialize()是否成功。虽然这里提到的是osError,但系统初始化是否正确确实会影响整个系统的运行,包括时钟延时功能。如果FreeRTOS内核没有正确初始化,后续的延时函数可能无法正常工作。因此,需要确认FreeRTOS的初始化流程是否正确,特别是定时器相关的初始化代码是否正确调用。 然后,引用[2]提到FreeRTOS支持中断优先级配置和优先级反转保护。如果中断优先级配置不当,尤其是系统心跳定时器的中断优先级设置过低,可能会被其他高优先级中断抢占,导致系统心跳不及时,进而影响延时准确性。根据FreeRTOS的要求,SysTick中断和PendSV中断的优先级必须设置为最低,以确保任务切换不会被打断。用户需要检查FreeRTOSConfig.h中的配置,比如configKERNEL_INTERRUPT_PRIORITY是否正确设置。 另外,引用[5]中提到了任务函数的结构,任务应该在死循环中调用系统延时函数,比如vTaskDelay()。如果用户错误地使用了阻塞式延时或者在中断服务程序中使用不正确的延时函数,可能导致延时不准。例如,在中断中使用vTaskDelay()是不允许的,应该使用xTaskResumeFromISR()等适当的方法。 可能还需要检查用户是否修改了系统时钟源,比如HAL库默认使用的外部晶振是否与实际硬件匹配,如果时钟源配置错误,会导致整个系统时钟频率错误,进而影响FreeRTOS的tick计算。例如,如果系统主频应该是72MHz,但实际配置成了其他频率,定时器的计数值就会出错。 此外,用户提供的引用[3]提到了FreeRTOS的移植模板,可能涉及到port.c和portmacro.h文件的配置是否正确。在移植过程中,如果定时器中断的处理函数没有正确实现,或者堆栈设置不当,也可能导致系统延时异常。需要确认移植层代码是否正确,尤其是SysTick_Handler的实现是否调用了xPortSysTickHandler(),以及相关的中断服务程序是否被正确链接。 总结起来,解决系统延时不准的问题需要从以下几个方面入手: 1. 检查系统时钟配置,确保定时器中断频率正确。 2. 确认FreeRTOS内核正确初始化,特别是定时器相关部分。 3. 验证中断优先级设置,特别是SysTick和PendSV的中断优先级是否为最低。 4. 确保任务中正确使用vTaskDelay()延时函数,避免在中断中使用不合适的延时。 5. 检查硬件时钟源配置是否正确,例如外部晶振频率是否与实际一致。 6. 确认FreeRTOS移植代码正确,特别是定时器中断处理函数和系统心跳配置。 在用户提供的引用中,引用[4]提到获取FreeRTOS源码的官网,可能需要确认用户使用的FreeRTOS版本是否存在已知的bug,或者是否有相关的补丁需要应用。另外,引用[2]提到的时间片轮转和优先级反转保护功能虽然高级,但如果配置不当也可能间接影响任务调度和延时,但可能性较低,应优先排查基础配置问题。 最后,可能需要建议用户使用调试工具,如逻辑分析仪或调试器,测量实际的延时时间,对比预期值,从而定位问题所在。例如,通过GPIO翻转来测量vTaskDelay(100)是否精确延时了100个tick,从而判断是时钟配置问题还是其他原因。</think>### FreeRTOS 系统延时配置错误解决方案 #### 1. **检查系统时钟配置** - **确认系统主频与硬件匹配** 若硬件使用外部晶振(如8MHz),需确保HAL库的时钟配置正确生成目标主频(如72MHz)。时钟树配置错误会导致定时器基准频率偏差,直接影响延时精度[^4]。 - **验证SysTick定时器中断频率** 检查`FreeRTOSConfig.h`中`configTICK_RATE_HZ`的值(通常为1000,对应1ms/tick)。若该值与实际定时器中断频率不一致,延时时间会成比例偏差。 #### 2. **确认FreeRTOS内核初始化** - **确保`osKernelInitialize()`成功执行** 系统初始化失败可能导致定时器未正确启动,从而无法触发心跳中断。需检查初始化代码逻辑及返回值[^1]。 - **检查SysTick中断服务程序** 确认`SysTick_Handler()`中调用`xPortSysTickHandler()`,并确保中断向量表正确映射[^3][^4]。 #### 3. **中断优先级配置** - **设置SysTick和PendSV为最低优先级** FreeRTOS要求SysTick和PendSV中断优先级必须最低,避免被高优先级中断阻塞。在`FreeRTOSConfig.h`中配置: ```c #define configKERNEL_INTERRUPT_PRIORITY 0xF0 // Cortex-M中优先级数值越大,逻辑优先级越低 ``` 确保与硬件中断优先级分组(如`NVIC_PriorityGroup_4`)匹配[^1][^2]。 #### 4. **正确使用延时函数** - **任务中调用`vTaskDelay()`而非阻塞延时** 在任务循环中使用`vTaskDelay(pdMS_TO_TICKS(100))`实现精确延时,避免使用`HAL_Delay()`或空循环[^5]。 - **禁止在中断中使用`vTaskDelay()`** 中断服务程序(ISR)中需使用非阻塞方法(如信号量或任务通知),并通过`xTaskResumeFromISR()`唤醒任务[^5]。 #### 5. **移植层代码验证** - **检查`port.c`和`portmacro.h`实现** 确认定时器初始化、中断处理及上下文切换代码正确,尤其是`vPortSetupTimerInterrupt()`和任务堆栈设置。 - **优化延时函数依赖** 若使用`SystemCoreClock`计算延时,需在时钟变更后更新该值,确保`pdMS_TO_TICKS()`宏计算准确[^4]。 #### 6. **调试与测试** - **使用GPIO或调试器测量实际延时** 在任务中翻转GPIO引脚,通过逻辑分析仪测量`vTaskDelay(100)`的实际时间,验证是否与预期一致[^4]。 - **排查优先级反转或任务阻塞** 高优先级任务长期占用CPU可能导致低优先级任务延时感知不准,需通过`vTaskPrioritySet()`调整或引入时间片轮转。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值