FreeRTOS个人笔记-延时函数

根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。

配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!

延时函数

常用的延时阻塞函数vTaskDelay()

#if ( INCLUDE_vTaskDelay == 1 )

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

		if( xTicksToDelay > ( TickType_t ) 0U )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			vTaskSuspendAll();					//挂起任务调度器
			{
				traceTASK_DELAY();
				//将当前任务添加到延时列表
				prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
			}
			xAlreadyYielded = xTaskResumeAll();	//恢复任务调度器
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		
		if( xAlreadyYielded == pdFALSE )
		{
			portYIELD_WITHIN_API();		//强制切换任务,将PendSV的bit28 置1
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelay */

上面函数调用了prvAddCurrentTaskToDelayedList()函数,如下

//xCanBlockIndefinitely:是否可以永久阻塞	pdFALSE:不允许永久阻塞,即不允许挂起当前任务	pdTRUE:允许永久阻塞,支持挂起当前任务
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 ) )		//支持挂起
		{
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );		//将任务插入到挂起列表
		}
		else
		{
			//计算唤醒任务的时间 
			xTimeToWake = xConstTickCount + xTicksToWait;

			/* 列表项将按唤醒时间顺序插入 */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			if( xTimeToWake < xConstTickCount )		//唤醒时间如果溢出了,则会添加到延时溢出列表
			{
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				//唤醒时间没有溢出,添加到延时列表
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				//如果下一个要唤醒的任务就是当前延时的任务,那么就需要重置下一个任务的解除阻塞时间 xNextTaskUnblockTime 为唤醒当前延时任务的时间xTimeToWake。
				if( xTimeToWake < xNextTaskUnblockTime )
				{
					xNextTaskUnblockTime = xTimeToWake;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */
	{
		
		xTimeToWake = xConstTickCount + xTicksToWait;

		
		listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

		if( xTimeToWake < xConstTickCount )
		{
			vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		( void ) xCanBlockIndefinitely;
	}
	#endif /* INCLUDE_vTaskSuspend */
}

vTaskDelay ()的延时是相对的,是不确定的,它的延时是等 vTaskDelay ()调用完毕后开始计算的。并且 vTaskDelay()延时时间到了之后,如果有高优先级的任务或者中断正在执行,
被延时阻塞的任务并不会马上解除阻塞,所有每次执行任务的周期并不完全确定。

vTaskDelayUntil()延时是绝对的,适用于周期性执行的任务。 当(*pxPreviousWakeTime + xTimeIncrement)时间到达后,vTaskDelayUntil()函数立刻返回,如果任务是最高优先级的,
那么任务会立马解除阻塞,所以说 vTaskDelayUntil()函数的延时是绝对性的。

绝对延时vTaskDelayUntil()常用于较精确的周期运行任务,比如我有一个任务, 希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的。

vTaskDelayUntil()如下

#if ( INCLUDE_vTaskDelayUntil == 1 )
	
	//pxPreviousWakeTime,保存任务最后一次解除阻塞的的时刻。第一次使用时,该变量必须初始化为当前时间,之后这个变量会在vTaskDelayUntil()函数内自动更新。
	//xTimeIncrement,周期循环时间。 当 时 间 等 于 (*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数 xTimeIncrement 的值,调用该函数的任务会按照固定频率执行。
	void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
	{
        TickType_t xTimeToWake;
        BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

		configASSERT( pxPreviousWakeTime );
		configASSERT( ( xTimeIncrement > 0U ) );
		configASSERT( uxSchedulerSuspended == 0 );

		vTaskSuspendAll();
		{
			//获取开始进行延时的时间点
			const TickType_t xConstTickCount = xTickCount;

			//计算延时到达的时间,也就是唤醒任务的时间
			xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

			/* pxPreviousWakeTime 中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了*/
			if( xConstTickCount < *pxPreviousWakeTime )
			{
				/* 如果唤醒的时间小于上次唤醒时间,并且唤醒时间大于开始计时的时间,这样子就是相当于没有溢出,也就是保证了周期性延时时间大于任务主体代码的执行时间*/
				if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				//只是唤醒时间溢出或者都没溢出的情况,保证了延时时间大于任务主体代码的执行时间
				if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}

			//更新上一次的唤醒时间
			*pxPreviousWakeTime = xTimeToWake;

			if( xShouldDelay != pdFALSE )
			{
				traceTASK_DELAY_UNTIL( xTimeToWake );

				/* prvAddCurrentTaskToDelayedList()函数需要的是阻塞时间而不是唤醒时间,因此减去当前的滴答计数。 */
				prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		xAlreadyYielded = xTaskResumeAll();

		//强制执行一次上下文切换
		if( xAlreadyYielded == pdFALSE )
		{
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelayUntil */

xTimeIncrement        :任务周期时间。
pxPreviousWakeTime    :上一次唤醒任务的时间点。
xTimeToWake            :本次要唤醒任务的时间点。
xConstTickCount        :进入延时的时间点。

绝对延时vTaskDelayUntil()实例

void vTaskA( void * pvParameters )
{
	/* 用于保存上次时间。调用后系统自动更新 */
	static portTickType PreviousWakeTime;
	/* 设置延时时间,将时间转为节拍数 */
	const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
	
	/* 获取当前系统时间 */
	PreviousWakeTime = xTaskGetTickCount();
	
	while (1)
	{
		/* 调用绝对延时函数,任务时间间隔为 1000 个 tick */
		vTaskDelayUntil( &PreviousWakeTime, TimeIncrement );

		// ...
		// 这里为任务主体代码
		// ...
	}
}

本节官方封装了两个函数,可以直接使用。

vTaskDelay()相对延时函数。

vTaskDelayUntil()绝对延时函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值