本节主要介绍以下内容:
1,延时函数介绍(了解)
2,延时函数解析(熟悉)
3,延时函数演示实验(掌握)
4,课堂总结(掌握)
一、延时函数介绍(了解)
函数 | 描述 |
vTaskDelay() | 相对延时 |
xTaskDelayUntil() | 绝对延时 |
相对延时:指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束
举个例子:从vTaskDelay()的前一句到vTaskDelay()执行结束,延迟100ms。
绝对延时:指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务
举个例子:任务从开始执行到结束共100ms。
可以看下面这张图,xTimeIncrement就是绝对延时的时间,这三部分加起来就是绝对延时时间。
这个函数在官网里面也有说明:必须任务主体小于延迟时间
(1)为任务主体,也就是任务真正要做的工作
(2)是任务函数中调用vTaskDelayUntil()对任务进行延时
(3)为其他任务在运行
1.1 vTaskDelay()
参数:
xTicksToDelay | 调用任务应阻塞的 tick 周期数。 |
用法示例:
void vTaskFunction( void * pvParameters )
{
/* Block for 500ms. */
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
for( ;; )
{
/* Simply toggle the LED every 500ms, blocking between each toggle. */
vToggleLED();
vTaskDelay( xDelay );
}
}
1.2 xTaskDelayUntil()
参数
pxPreviousWakeTime | 指向一个变量的指针,该变量 用于保存任务最后一次解除阻塞的时间。 该变量在第一次使用前 必须用当前时间进行初始化(见下方示例)。 在这之后,该变量 会在 vTaskDelayUntil() 中自动更新。 |
xTimeIncrement | 周期时间段。 该任务将在 (*pxPreviousWakeTime + xTimeIncrement)时间解除阻塞。 配合相同的 xTimeIncrement 参数值 调用 vTaskDelayUntil 将导致任务 以固定的间隔期执行。 |
用法示例:
// Perform an action every 10 ticks.
void vTaskFunction( void * pvParameters )
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, xFrequency );
// Perform action here.
}
}
二,延时函数解析(熟悉)
又开始了我们对源码进行解读的时间了,大家有没有觉得解读源码是一件很有成就感的事情呢?
2.1 void vTaskDelay( const TickType_t xTicksToDelay )
#if ( INCLUDE_vTaskDelay == 1 )
void vTaskDelay( const TickType_t xTicksToDelay )
{
/* 只有在延时时间大于0的时候,
* 才需要进行任务阻塞,
* 否则相当于强制进行任务切换,而不阻塞任务 */
BaseType_t xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
/* 挂起任务调度器 */
vTaskSuspendAll();
{
/* 用于调试,不用理会 */
traceTASK_DELAY();
/* A task that is removed from the event list while the
* scheduler is suspended will not get placed in the ready
* list or removed from the blocked list until the scheduler
* is resumed.
*
* This task cannot be in an event list as it is the currently
* executing task. */
/* 将任务添加到阻塞态任务列表中 */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
/* 恢复任务调度器运行,
* 调用此函数会返回是否需要进行任务切换 */
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Force a reschedule if xTaskResumeAll has not already done so, we may
* have put ourselves to sleep. */
/* 根据标志进行任务切换 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskDelay */
/*-----------------------------------------------------------*/
注意:
1. 使用函数 vTaskDelay()进行任务延时时,被延时的任务为调用该函数的任务,及调用该
函数时,系统中正在运行的任务,此函数无法指定将其他任务进行任务延时。
2. 函数 vTaskDelay()传入的参数 xTicksToDelay是任务被延时的具体延时时间,时间的单
位为系统时钟节拍,这里要特别注意,很多 FreeRTOS的初学者可能会一会 此函数延时的时间
单位为微妙、毫秒、秒等物理时间单位,FreeRTOS是以系统时钟节拍作为计量的时间单
位的 ,而系统时钟节拍对应的物理时间长短于 FreeRTOSConfig.h文件中的配置项ConfigTICK_RATE_HZ有关,配置项 configTICK_RATE_HZ是用于配置系统时钟节拍的频率的,
3. 在使用此函数进行任务延时时,如果传入的参数为 0,那表明不进行任务延时,而是强
制进行一次任务切换。
2.2 vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement )
函数vTaskDelayUntil()用于以一个绝对的时间阻塞任务,适用于需要按照一定频率运行的任务,函数 vTaskDelayUntil()实际上是一个宏,在 task.h文件中有定义,具体的代码如下所示:
/*
* vTaskDelayUntil() is the older version of xTaskDelayUntil() and does not
* return a value.
*/
#define vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement ) \
{ \
( void ) xTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement ); \
}
从上面的代码可以看出,宏vTaskDelayUntil()实际上就是函数 xTaskDelayUntil(),函数
xTaskDelayUntil()在 task.c文件中有定义,具体的代码如下所示:
#if ( INCLUDE_xTaskDelayUntil == 1 )
BaseType_t xTaskDelayUntil( 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();
{
/* Minor optimisation. The tick count cannot change in this
* block. */
const TickType_t xConstTickCount = xTickCount;
/* 计算任务下一次阻塞超时的时间,
* 这个阻塞超时时间是相对于上一次阻塞超时的时间的
*/
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
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( xTimeToWake - xConstTickCount, pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 恢复任务调度器运行,
* 调用此函数会返回是否需要进行任务切换
*/
xAlreadyYielded = xTaskResumeAll();
/* 根据标志进行任务切换 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xShouldDelay;
}
#endif /* INCLUDE_xTaskDelayUntil */
/*-----------------------------------------------------------*/
从上面的代码可以看出,函数xTaskDelayUntil()对任务进行延时的操作,是相对于任务上
一次阻塞超时的时间,而不是相对于系统当前的时钟节拍计数器的值,因此,函数能够更准确
地以一定的频率进行任务延时,更加适用于需要按照一定频率运行的任务。
三,延时函数演示实验(掌握)
2.1、实验目的:
学习 FreeRTOS 相对延时和绝对延时 API 函数的使用
2.2、实验设计:
将设计三个任务:start_task、task1、task2
三个任务的功能如下:
- start_task:用来创建task1和task2任务
- task1:用于展示相对延时函数vTaskDelay ( )的使用
- task2:用于展示绝对延时函数vTaskDelayUntil( )的使用
为了直观显示两个延时函数的区别,将使用LED0(PB1) 和 LED1(PB0) 的翻转波形来表示
2.3 实验代码
demo.c
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 5
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,演示相对延时函数 */
void task1( void * pvParameters )
{
while(1)
{
LED0_TOGGLE(); /* PB1 */
delay_ms(20);
vTaskDelay(500);
}
}
/* 任务二,演示绝对延时函数 */
void task2( void * pvParameters )
{
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();/*获取当前系统节拍计数值*/
while(1)
{
LED1_TOGGLE(); /* PB0 */
delay_ms(20);
vTaskDelayUntil(&xLastWakeTime,500);
}
}
四,总结