0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

什么是FreeRTOS的延时

汽车电子技术 来源:物联网IoT开发 作者:杰杰mcu 2023-02-14 09:45 次阅读

01

FreeRTOS 时间管理

时间管理包括两个方面:系统节拍以及任务延时管理。

2

系统节拍:

在前面的文章也讲得很多,想要系统正常运行,那么时钟节拍是必不可少的,FreeRTOS的时钟节拍通常由SysTick提供,它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。FreeRTOS的时钟节拍isr中核心的工作就是调用 vTaskIncrementTick() 函数。具体见上之前的文章。

3

今天主要讲解延时的实现

FreeRTOS提供了两个系统延时函数:

**相对延时函数vTaskDelay() **

绝对延时函数vTaskDelayUntil()。

这些延时函数可不像我们以前用裸机写代码的延时函数操作系统不允许CPU在死等消耗着时间,因为这样效率太低了。

同时,要告诫学操作系统的同学,千万别用裸机的思想去学操作系统。

4

任务延时

任务可能需要延时,两种情况,一种是任务被vTaskDelay或者vTaskDelayUntil延时,另外一种情况就是任务等待事件(比如等待某个信号量、或者某个消息队列)时候指定了 timeout (即最多等待timeout时间,如果等待的事件还没发生,则不再继续等待),在每个任务的循环中都必须要有阻塞的情况出现,否则比该任务优先级低的任务就永远无法运行。

5

相对延时与绝对延时的区别

相对延时:vTaskDelay():

相对延时是指每次延时都是从任务执行函数 vTaskDelay() 开始,延时指定的时间结束

绝对延时:vTaskDelayUntil():

绝对延时是指调用 vTaskDelayUntil() 的任务每隔x时间运行一次。也就是任务周期运行。

6

相对延时:vTaskDelay()

相对延时 vTaskDelay() 是从调用 vTaskDelay() 这个函数的时候开始延时,但是任务执行的时候,可能发生了中断,导致任务执行时间变长了,但是整个任务的延时时间还是1000tick ,这就不是周期性了,简单看看下面代码:

void vTaskA( void * pvParameters )  
 {  
    while(1) 
     {  
         //  ...
         //  这里为任务主体代码
         //  ...

         /* 调用相对延时函数,阻塞1000个tick */
         vTaskDelay( 1000 );  
     }  
}

可能说的不够明确,可以看看图解。

图片

当任务运行的时候,假设被某个高级任务或者是中断打断了,那么任务的执行时间就更长了,然而延时还是延时1000tick这样子,整个系统的时间就混乱了。

如果还不够明确,看看 vTaskDelay() 的源码

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

        /* 延迟时间为零只会强制切换任务。 */
        if( xTicksToDelay > ( TickType_t ) 0U )     (1)
        {
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll();                      (2)
            {
                traceTASK_DELAY();
                /*将当前任务从就绪列表中移除,并根据当前系统节拍
                计数器值计算唤醒时间,然后将任务加入延时列表 */
                prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
            }
            xAlreadyYielded = xTaskResumeAll();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

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

(1):如果传递进来的延时时间是0,只能进行强制切换任务了,调用的是 portYIELD_WITHIN_API() ,它其实是一个宏,真正起作用的是 portYIELD() ,下面是它的源码:

#define portYIELD()                                                \\
{                                                                \\
    /* 设置PendSV以请求上下文切换。 */                         \\
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;             \\

    __dsb( portSY_FULL_READ_WRITE );                            \\
    __isb( portSY_FULL_READ_WRITE );                            \\
}

(2):挂起当前任务

然后将当前任务从就绪列表删除,然后加入到延时列表。是调用函数 prvAddCurrentTaskToDelayedList() 完成这一过程的。由于这个函数篇幅过长,就不讲解了,有兴趣可以看看,我就简单说说过程。在FreeRTOS中有这么一个变量,是用来记录systick的值的。

PRIVILEGED_DATA static volatile TickType_t xTickCount     = ( TickType_t ) 0U;

在每次tick中断时xTickCount加一,它的值表示了系统节拍中断的次数,那么啥时候唤醒被加入延时列表的任务呢?其实很简单,FreeRTOS的做法将 xTickCount (当前系统时间)+ xTicksToDelay (要延时的时间)即可。当这个相对的延时时间到了之后就唤醒了,这个 (xTickCount+ xTicksToDelay) 时间会被记录在该任务的任务控制块中。

看到这肯定有人问,这个变量是TickType_t类型(32位)的,那肯定会溢出啊,没错,是变量都会有溢出的一天,可是FreeRTOS乃是世界第一的操作系统啊,FreeRTOS使用了两个延时列表:

** xDelayedTaskList1和xDelayedTaskList2,**

并使用两个列表指针类型变量pxDelayedTaskListpxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)如果内核判断出xTickCount+xTicksToDelay溢出,就将当前任务挂接到列表指针 pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。当时间到了,就会将延时的任务从延时列表中删除,加入就绪列表中,当然这时候就是由调度器觉得任务能不能运行了,如果任务的优先级大于当前运行的任务,那么调度器才会进行任务的调度。

7

绝对延时:vTaskDelayUntil()

vTaskDelayUntil()参数指定了确切的滴答计数值

调用 vTaskDelayUntil() 是希望任务以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的。

图片

下面看看 vTaskDelayUntil() 的使用方法,注意了,这 vTaskDelayUntil() 的使用方法与 vTaskDelay() 不一样:

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

         /* 调用绝对延时函数,任务时间间隔为1000个tick */
         vTaskDelayUntil( &PreviousWakeTime,TimeIncrement );  

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

     }  
}

在使用的时候要将延时时间转化为系统节拍,在任务主体之前要调用延时函数。

任务会先调用 vTaskDelayUntil() 使任务进入阻塞态,等到时间到了就从阻塞中解除,然后执行主体代码,任务主体代码执行完毕。会继续调用 vTaskDelayUntil() 使任务进入阻塞态,然后就是循环这样子执行。即使任务在执行过程中发生中断,那么也不会影响这个任务的运行周期,仅仅是缩短了阻塞的时间而已。

下面来看看vTaskDelayUntil()的源码:

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();                                  (1)
        {
            /* 保存系统节拍中断次数计数器 */
            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();

        /* 如果xTaskResumeAll尚未执行重新安排,我们可能会让自己入睡。*/
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

与相对延时函数vTaskDelay不同,本函数增加了一个参数pxPreviousWakeTime用于指向一个变量,变量保存上次任务解除阻塞的时间,此后函数 vTaskDelayUntil() 在内部自动更新这个变量。由于变量xTickCount可能会溢出,所以程序必须检测各种溢出情况,并且要保证延时周期不得小于任务主体代码执行时间。

就会有以下3种情况,才能将任务加入延时链表中。

请记住这几个单词的含义:

** xTimeIncrement:任务周期时间

pxPreviousWakeTime:上一次唤醒的时间点

xTimeToWake:下一次唤醒的系统时间点

xConstTickCount:进入延时的时间点**

第三种情况:常规无溢出的情况。

以时间为横轴,上一次唤醒的时间点小于下一次唤醒的时间点,这是很正常的情况。

图片

第二种情况:唤醒时间计数器 (xTimeToWake) 溢出情况。

也就是代码中*if( ( xTimeToWake < pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )

图片

第一种情况:唤醒时间 (xTimeToWake) 与进入延时的时间点 (xConstTickCount) 都溢出情况。

也就是代码中*if( ( xTimeToWake < pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )

图片

从图中可以看出不管是溢出还是无溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。也就是说任务执行的时间不允许大于延时的时间,总不能存在每10ms就要执行一次20ms时间的任务吧。计算的唤醒时间合法后,就将当前任务加入延时列表,同样延时列表也有两个。每次系统节拍中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。

8

总结

如果任务调用相对延时,其运行周期完全是不可测的,如果任务的优先级不是最高的话,其误差更大,就好比一个必须要在5ms内相应的任务,假如使用了相对延时 1ms ,那么很有可能在该任务执行的时候被更高优先级的任务打断,从而错过5ms内的相应,但是调用绝对延时,则任务会周期性将该任务在阻塞列表中解除,但是,任务能不能运行,还得取决于任务的优先级,如果优先级最高的话,任务周期还是比较精确的(相对vTaskDelay来说),如果想要更加想精确周期性执行某个任务,可以使用系统节拍钩子函数 vApplicationTickHook() ,它在tick中断服务函数中被调用,因此这个函数中的代码必须简洁,并且不允许出现阻塞的情况。

本文是杰杰原创,转载请说明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61347
  • 定时中断
    +关注

    关注

    0

    文章

    19

    浏览量

    8492
  • Systick
    +关注

    关注

    0

    文章

    62

    浏览量

    12950
收藏 人收藏

    评论

    相关推荐

    stm32中FREERTOS延时函数osDelayUntil()死机的原因?

    我在使用STM32F4跑freertos的时候发现一旦使用osDelayUntil()函数,就会死机,但是用osDelay()函数就不会,按理说不是都可以用的吗?有知道原因的吗,谢谢!
    发表于 03-22 07:56

    【设计技巧】从单片机到操作系统(7)-FreeRTOS延时介绍

    本文转自公众号物联网IoT开发1 FreeRTOS 时间管理时间管理包括两个方面:系统节拍以及任务延时管理。2系统节拍:在前面的文章也讲得很多,想要系统正常运行,那么时钟节拍是必不可少
    发表于 08-01 08:00

    FreeRTOS如何使用delay作为系统延时、任务调度

    请教一个问题,最近在学习使用FreeRTOS,想像原子一样在delay.c里添加RTOS的系统支持,即使用tick时钟作延时。现在有几个问题:1、在启动任务调度器前,如果调用了delay_ms
    发表于 06-10 04:37

    FreeRTOSV8.2.3在探索者开发板上的移植怎么实现?

    、首先定义任务函数 [C] 纯文本查看 复制代码2、编写main函数 [C] 纯文本查看 复制代码vTaskDelay(2000/portTICK_PERIOD_MS);//FreeRTOS延时函数
    发表于 07-30 08:02

    esp32s3延时出现问题求解

    不管是用的官方模板,还是自己写的,使用的是freertos延时,或者是while(i--),都有问题。主要程序是这样的:while(1){ vTaskDELAY(100); printf(“run
    发表于 02-10 06:43

    怎么去解决esp32s3延时出错的问题?

    不管是用的官方模板,还是自己写的,使用的是freertos延时,或者是while(i--),都有问题。主要程序是这样的:while(1){ vTaskDELAY(100); printf(“run
    发表于 03-03 08:25

    esp32s3延时问题如何解决?

    不管是用的官方模板,还是自己写的,使用的是freertos延时,或者是while(i--),都有问题。主要程序是这样的:while(1){ vTaskDELAY(100); printf(“run
    发表于 03-08 08:38

    ETH-CH32v20x_v307在Freertos中添加以太网

    Freertos中,主函数作为一个线程,同时将中断函数复制过来,增加freertos进入和释放中断函数,中断声明也不要忘记 修改驱动文件即eth_driver.c中的延时函数,修改成fre
    发表于 08-09 10:41

    请问FreeRTOS中GPIO模拟SPI延时如何处理?

    FreeRTOS中想使用GPIO模拟SPI与设备进行通讯,SPI传输速度要求在100K以上,FreeRTOS延时不能实现微妙级别的延时。模拟时的gpio翻转
    发表于 11-10 07:56

    FreeRTOS中相对延时与绝对延时的区别

    FreeRTOS中相对延时和绝对延时的区别
    的头像 发表于 03-12 10:32 7871次阅读
    <b class='flag-5'>FreeRTOS</b>中相对<b class='flag-5'>延时</b>与绝对<b class='flag-5'>延时</b>的区别

    FreeRTOS中相对延时和绝对延时的区别

    嵌入式软件代码中延时是很常见的,只是延时种类有很多,看你用什么延时。 1 一个延时的问题 问题:周期性(固定一个时间)去处理某一件事情。你会通过什么方式去实现? 比如:间隔10ms去采
    的头像 发表于 11-24 15:44 1765次阅读
    <b class='flag-5'>FreeRTOS</b>中相对<b class='flag-5'>延时</b>和绝对<b class='flag-5'>延时</b>的区别

    FreeRTOS中相对延时和绝对延时的区别

    对计时精度要求比较高的地方适合定时器,像本章节说的周期性采集传感器数据,要求不适合很高,那么就引入本文说的绝对延时
    的头像 发表于 11-29 10:19 1722次阅读

    初入FreeRTOS

    目录一、FreeRTOS介绍1、初识FreeRTOS,什么是 FreeRTOS2、FreeRTOS的特点二、FreeRTOS移植1、
    发表于 12-06 21:06 37次下载
    初入<b class='flag-5'>FreeRTOS</b>

    FreeRTOS高级篇9---FreeRTOS系统延时分析

    FreeRTOS提供了两个系统延时函数:相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil()。相对延时是指每
    发表于 01-26 17:34 6次下载
    <b class='flag-5'>FreeRTOS</b>高级篇9---<b class='flag-5'>FreeRTOS</b>系统<b class='flag-5'>延时</b>分析

    FreeRTOS系列第11篇---FreeRTOS任务控制

    FreeRTOS任务控制API函数主要实现任务延时、任务挂起、解除任务挂起、任务优先级获取和设置等功能。
    发表于 01-26 17:54 11次下载
    <b class='flag-5'>FreeRTOS</b>系列第11篇---<b class='flag-5'>FreeRTOS</b>任务控制