| 上一篇 | 下一篇 |
|---|---|
| 任务时间统计相关 API 函数 |
目 录
- [FreeRTOS 专用延时函数](#FreeRTOS 专用延时函数)
-
- 1)vTaskDelay()
-
- [① vTaskDelay() 函数声明:](#① vTaskDelay() 函数声明:)
- [② vTaskDelay() 源码精简版:](#② vTaskDelay() 源码精简版:)
- 2)vTaskDelayUntil()
-
- [① vTaskDelayUntil() 函数声明:](#① vTaskDelayUntil() 函数声明:)
- [② vTaskDelayUntil() 源码精简版:](#② vTaskDelayUntil() 源码精简版:)
- 3)实验示例
FreeRTOS 专用延时函数
FreeRTOS 专用的延时函数主要有两个:

- 相对延时(第一个) :指每次延时都是从执行函数
vTaskDelay()开始,直到延时指定的时间结束,再继续回来执行 - 绝对延时(第二个) :指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务(周期性精确延时函数)
- 两者的调用方式都是在任务函数体内部调用 (具体是在任务函数体中的
while(1){}循环中调用)
1)vTaskDelay()
函数 vTaskDelay() 在文件 tasks.c 中有定义,要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1 。
① vTaskDelay() 函数声明:
c
void vTaskDelay ( const TickType_t xTicksToDelay );
- 输入参数
xTicksToDelay:表示任务 要延迟(阻塞)的系统节拍(tick)数 ,每个 tick 的实际时间长度由 FreeRTOS 配置项configTICK_RATE_HZ决定,其本质是 SysTick 定时器每次产生中断的时间间隔(溢出时间)。
② vTaskDelay() 源码精简版:
c
#if ( INCLUDE_vTaskDelay == 1 )
void vTaskDelay ( const TickType_t xTicksToDelay )
{
...
/* 判断入口参数是否大于零 */
if( xTicksToDelay > ( TickType_t ) 0U )
{
vTaskSuspendAll(); // 挂起任务调度器(临界区代码保护)
{
...
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); // 将当前任务移到阻塞列表中
}
xAlreadyYielded = xTaskResumeAll(); // 恢复任务调度器
}
... /* 其他代码 */
}
#endif /* INCLUDE_vTaskDelay */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ) 函数将当前任务移到阻塞列表之后。当任务的阻塞时间到期后,FreeRTOS 会在系统节拍中断(SysTick Handler)中判断并将其从延迟列表移除,并重新加入就绪列表;若其优先级最高,随后就会被调度执行(这个函数本身很复杂,这里不展开讲解了)。
2)vTaskDelayUntil()
函数 vTaskDelayUntil() 会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用此函数。
函数 vTaskDelay() 在文件 tasks.c 中有定义,要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1 。
① vTaskDelayUntil() 函数声明:
c
void vTaskDelayUntil ( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
-
输入参数
pxPreviousWakeTime:上一次任务延时结束被唤醒的时间点- 任务中第一次调用函数
vTaskDelayUntil()的话需要将pxPreviousWakeTime初始化进入任务的while()循环体的时间点值。 - 在以后的运行中函数
vTaskDelayUntil()会自动更新pxPreviousWakeTime。 - 这个唤醒节拍时刻的最大值可以设置为 2 16 − 1 2^{16}-1 216−1、 2 32 − 1 2^{32}-1 232−1 这些,会自动回绕,不用担心会超出。
- 任务中第一次调用函数
-
输入参数
xTimeIncrement:任务**需要延时的时间节拍数**(相对于 pxPreviousWakeTime 本次延时的节拍数)。 -
几个重要时间节点的关系图如下:

(1)为任务主体,也就是任务真正要做的工作,(2)是任务函数中调用vTaskDelayUntil()对任务进行延时,(3)为其他任务在运行。任务的延时时间是xTimeIncrement,这个延时时间是相对于pxPreviousWakeTime的,可以看出 任务总的执行时间一定要小于任务的延时时间xTimeIncrement! 也就是说如果使用vTaskDelayUntil()的话任务相当于任务的执行周期永远都是xTimeIncrement,而任务一定要在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行了,这个延时值就是绝对延时时间,因此函数vTaskDelayUntil()也叫做绝对延时函数。
② vTaskDelayUntil() 源码精简版:
c
void vTaskDelayUntil ( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
...
vTaskSuspendAll(); // 挂起任务调度器(临界区代码保护)
{
const TickType_t xConstTickCount = xTickCount; // 记录进入函数 vTaskDelayUntil() 的时间点值
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; // 根据延时时间 xTimeIncrement 来计算任务下一次要 // 唤醒的时间点
... /* 判断进入函数的时间是否小于上一次被唤醒时间,理论上要大于,小于则执行一些有关时间溢出的操作 */
*pxPreviousWakeTime = xTimeToWake; // 将pxPreviousWakeTime的值更新为 xTimeToWake,为下一次延时做准备
/* 判断是否允许进行延时 */
if( xShouldDelay != pdFALSE )
{
...
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE ); // 将当前任务移到阻 // 塞列表中
}
...
}
xAlreadyYielded = xTaskResumeAll(); // 恢复任务调度器
...
}
调用举例:
c
void vTaskFunction(void *pvParameters)
{
TickType_t xLastWakeTime = xTaskGetTickCount(); // 初始化
const TickType_t xFrequency = pdMS_TO_TICKS(10); // 每10ms一次
for (;;)
{
// 执行任务代码(比如采集传感器)
// 精确延时到下一个周期点(唤醒点)
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
当延时时间<任务执行时间时:
- 当周期太短、任务执行太慢时,
vTaskDelayUntil()会"追赶"周期基准,但不再延时,直接进入下一轮,可能导致实际周期变长。 - 所以尽量使延时时间>任务执行时间。
3)实验示例
① 实验目的: 学习 FreeRTOS 相对延时和绝对延时 API 函数的使用,并了解其区别
② 实验设计: 将设计三个任务:start_task、task1、task2,其中 task1 和 task2 优先级相同均为 2 。
这三个任务的功能如下:
-
start_task:用来创建 task1/task2 任务。
-
task1:用于展示相对延时函数
vTaskDelay()的使用。- 翻转LED0(时间很短,相对于 10ms 可忽略)+ 死等延时10ms + 相对延时500ms,则电平会持续 510ms 。
-
task2:用于展示绝对延时函数
vTaskDelayUntil()的使用。- 翻转LED1(时间很短,相对于 10ms 可忽略)+ 死等延时10ms + 绝对延时500ms,则电平会持续 500ms 。
但是由于使用了死等延时函数,会破坏任务调度,所以不能死等太长,能看到现象就行了。
③ 代码:
FreeRTOS_Code.h 文件代码如下:
c
#ifndef __FREERTOS_CODE_H
#define __FREERTOS_CODE_H
void freertos_code(void);
#endif
main.c 文件代码如下:
c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_Code.h"
int main(void)
{
HAL_Init();
sys_stm32_clock_init(RCC_PLL_MUL9); // 设置时钟,72M
delay_init(72); // 延时函数初始化
usart_init(115200); // 初始化串口
LED_Init(); // 初始化LED
KEY_Init(); // 按键初始化
// TIM6_Init(4999,7199); // TIM6初始化,溢出时间为1s
// TIM7_Init(4999,7199); // TIM7初始化,溢出时间为1s
freertos_code(); //FreeRTOS代码
}
FreeRTOS_Code.c 文件代码如下:
c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_Code.h"
/* ----------------------------------------------------------------------------------- */
#define START_TASK_PRIO 1 // 任务优先级
#define START_STK_SIZE 128 // 任务堆栈大小
TaskHandle_t StartTask_Handler; // 任务句柄
void start_task(void *pvParameters); // 任务函数
#define TASK1_PRIO 2 // 任务优先级
#define TSAK1_STK_SIZE 100 // 任务堆栈大小
TaskHandle_t Task1_Handler; // 任务句柄
void task1(void *p_arg); // 任务函数
#define TASK2_PRIO 3 // 任务优先级
#define TSAK2_STK_SIZE 100 // 任务堆栈大小
TaskHandle_t Task2_Handler; // 任务句柄
void task2(void *p_arg); // 任务函数
char task_buff[500]; // 保存信息存储区
/* ----------------------------------- 主函数 ---------------------------------------- */
void freertos_code(void)
{
/* 创建开始任务 */
xTaskCreate((TaskFunction_t )start_task, // 任务函数
(const char* )"start_task", // 任务名称
(uint16_t )START_STK_SIZE, // 任务堆栈大小
(void* )NULL, // 传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, // 任务优先级
(TaskHandle_t* )&StartTask_Handler); // 任务句柄
vTaskStartScheduler(); // 开启任务调度器
}
/* ---------------------------------- 任务函数 --------------------------------------- */
/**
* @brief 开始任务函数
* @param 无
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); // 进入临界区 -----------
/* 创建 TASK1 任务 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TSAK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
/* 创建 TASK2 任务 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TSAK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
vTaskDelete(StartTask_Handler); // 删除开始任务
taskEXIT_CRITICAL(); // 退出临界区 -----------
}
/**
* @brief TASK1 任务函数
* @param 无
* @retval 无
* @note 演示相对延时函数,每510ms翻转一次LED0
*/
void task1(void *pvParameters)
{
while(1)
{
LED0=!LED0;
delay_ms(10);
vTaskDelay(500);
}
}
/**
* @brief TASK2 任务函数
* @param 无
* @retval 无
* @note 演示相对延时函数,每500ms翻转一次LED1
*/
void task2(void *pvParameters)
{
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
while(1)
{
LED1=!LED1;
delay_ms(10);
vTaskDelayUntil(&xLastWakeTime,500);
}
}
④ 实验现象:
通道1(黄色波形)是相对延时,通道2(绿色波形)是绝对延时。
