欢迎光临
我们一直在努力

FreeRTOS 学习:(二十六)FreeRTOS 专用延时函数(相对延时、绝对延时)

上一篇下一篇
任务时间统计相关 API 函数

目 录

  • FreeRTOS 专用延时函数
    • 1)vTaskDelay()
      • ① vTaskDelay() 函数声明:
      • ② vTaskDelay() 源码精简版:
    • 2)vTaskDelayUntil()
      • ① vTaskDelayUntil() 函数声明:
      • ② vTaskDelayUntil() 源码精简版:
    • 3)实验示例

FreeRTOS 专用延时函数

FreeRTOS 专用的延时函数主要有两个:

在这里插入图片描述

  • 相对延时(第一个):指每次延时都是从执行函数 vTaskDelay() 开始,直到延时指定的时间结束,再继续回来执行
  • 绝对延时(第二个):指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务(周期性精确延时函数)
  • 两者的调用方式都是在任务函数体内部调用(具体是在任务函数体中的 while(1){} 循环中调用)

1)vTaskDelay()

函数 vTaskDelay() 在文件 tasks.c 中有定义,要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1 。

① vTaskDelay() 函数声明:

void vTaskDelay ( const TickType_t xTicksToDelay );

  • 输入参数 xTicksToDelay :表示任务 要延迟(阻塞)的系统节拍(tick)数,每个 tick 的实际时间长度由 FreeRTOS 配置项 configTICK_RATE_HZ 决定,其本质是 SysTick 定时器每次产生中断的时间间隔(溢出时间)。

② vTaskDelay() 源码精简版:

#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() 函数声明:

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() 源码精简版:

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(); // 恢复任务调度器
...
}

调用举例:

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 文件代码如下:

#ifndef __FREERTOS_CODE_H
#define __FREERTOS_CODE_H

void freertos_code(void);

#endif

main.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 文件代码如下:

#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(绿色波形)是绝对延时。

在这里插入图片描述


赞(0)
未经允许不得转载:171主机测评 » FreeRTOS 学习:(二十六)FreeRTOS 专用延时函数(相对延时、绝对延时)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址