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

上一篇 下一篇
任务时间统计相关 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(绿色波形)是绝对延时。


相关推荐
galaxyffang2 小时前
如何理解select、poll、epoll?
操作系统
S火星人S2 小时前
软件调试基础(四【断点和单步执行】4.3【陷阱标志】)
stm32·单片机·嵌入式硬件
NEWEVA__zzera223 小时前
AM32开源项目固件解析(STM32G071)
stm32·单片机·嵌入式硬件
Hello_Embed3 小时前
RS485 双串口通信 + LCD 实时显示(中断版)
c语言·笔记·单片机·学习·操作系统·嵌入式
小痞同学3 小时前
【铁头山羊STM32】HAL库 2.UART部分
stm32·单片机·嵌入式硬件
小痞同学4 小时前
【铁头山羊STM32】HAL库 3.I2C部分
stm32·单片机·嵌入式硬件
安庆平.Я6 小时前
STM32——定时器:基本定时器
stm32·单片机·嵌入式硬件
Hello_Embed6 小时前
串口面向对象封装实例
笔记·stm32·单片机·学习·操作系统
Zeku6 小时前
Linux驱动学习笔记:SPI OLED 驱动源码深度分析
stm32·freertos·linux驱动开发·linux应用开发