千家信息网

FreeRTOS系统延时实例分析

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章主要讲解了"FreeRTOS系统延时实例分析",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"FreeRTOS系统延时实例分析"吧!FreeRTO
千家信息网最后更新 2026年11月02日FreeRTOS系统延时实例分析

这篇文章主要讲解了"FreeRTOS系统延时实例分析",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"FreeRTOS系统延时实例分析"吧!

FreeRTOS提供了两个系统延时函数:相对延时函数vTaskDelay()和绝对延时函数

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

绝对延时是指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。换句话说:任务以固定的频率执行。

1. 相对延时函数vTaskDelay()

考虑下面的任务,任务A在执行任务主体代码后,调用相对延时函数vTaskDelay()进入阻塞状态。系统中除了任务A外,还有其它任务,但是任务A的优先级最高。

void vTaskA( void * pvParameters )   {       /* 阻塞500ms. 注:宏pdMS_TO_TICKS用于将毫秒转成节拍数,FreeRTOS V8.1.0及        以上版本才有这个宏,如果使用低版本,可以使用 500 / portTICK_RATE_MS */       const portTickType xDelay = pdMS_TO_TICKS(500);          for( ;; )       {           //  ...         //  这里为任务主体代码         //  ...                 /* 调用系统延时函数,阻塞500ms */         vTaskDelay( xDelay );       }  }

对于这样一个任务,执行过程如图1-1所示。当任务A获取CPU使用权后,先执行任务A的主体代码,之后调用系统延时函数vTaskDelay()进入阻塞状态。任务A进入阻塞后,其它任务得以执行。

FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪状态。由于任务A的优先级最高,会抢占CPU,再次执行任务主体代码,不断循环。

从图1-1可以看出,任务A每次延时都是从调用延时函数vTaskDelay()开始算起的,延时是相对于这一时刻开始的,所以叫做相对延时函数。

从图1-1还可以看出,如果执行任务A的过程中发生中断,那么任务A执行的周期就会变长,所以使用相对延时函数vTaskDelay(),不能周期性的执行任务A。

图1-1:相对延时函数执行示意图

我们来看一下源码。

void vTaskDelay( const TickType_t xTicksToDelay ){BaseType_t xAlreadyYielded = pdFALSE;    /* 如果延时时间为0,则不会将当前任务加入延时列表 */    if( xTicksToDelay > ( TickType_t ) 0U )    {        vTaskSuspendAll();        {            /* 将当前任务从就绪列表中移除,并根据当前系统节拍计数器值计算唤醒时间,然后将任务加入延时列表 */            prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );        }        xAlreadyYielded = xTaskResumeAll();    }    /* 强制执行一次上下文切换*/    if( xAlreadyYielded == pdFALSE )    {        portYIELD_WITHIN_API();    }}

如果延时大于0,则会将当前任务从就绪列表删除,然后加入到延时列表。是调用函数prvAddCurrentTaskToDelayedList()完成这一过程的。我们在前面一系列博文中多次提到,tasks.c中定义了很多局部静态变量,其中有一个变量xTickCount定义如下所示:

static volatile TickType_t xTickCount = ( TickType_t ) 0U;

这个变量用来计数,记录系统节拍中断的次数,它在启动调度器时被清零,在每次系统节拍时钟发生中断后加1。相对延时函数会使用到这个变量,xTickCount表示了当前的系统节拍中断次数,这个值加上参数规定的延时时间(以系统节拍数表示)xTicksToDelay,就是下次唤醒任务的时间,xTickCount+ xTicksToDelay会被记录到任务TCB中,随着任务一起被挂接到延时列表。

我们知道变量xTickCount是TickType_t类型的,它也会溢出。在32位架构中,当xTicksToDelay达到4294967295后再增加,就会溢出变成0。为了解决xTickCount溢出问题,FreeRTOS使用了两个延时列表:xDelayedTaskList1和xDelayedTaskList2,并使用两个列表指针类型变量pxDelayedTaskList和pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)。顺便说一下,上面的两个延时列表指针变量和两个延时列表变量都是在tasks.c中定义的静态局部变量。

如果内核判断出xTickCount+ xTicksToDelay溢出,就将当前任务挂接到列表指针pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。
每次系统节拍时钟中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。

2. 绝对延时函数vTaskDelayUntil()

考虑下面的任务,任务B首先调用绝对延时函数vTaskDelayUntil ()进入阻塞状态,阻塞时间到达后,执行任务主体代码。系统中除了任务B外,还有其它任务,但是任务B的优先级最高。

void vTaskB( void * pvParameters )  {      static portTickType xLastWakeTime;      const portTickType xFrequency = pdMS_TO_TICKS(500);      // 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同     xLastWakeTime = xTaskGetTickCount();      for( ;; )      {          /* 调用系统延时函数,周期性阻塞500ms */                vTaskDelayUntil( &xLastWakeTime,xFrequency );           //  ...         //  这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同         //  ...      }  }

对于这样一个任务,执行过程如图2-1所示。当任务B获取CPU使用权后,先调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态。任务B进入阻塞后,其它任务得以执行。FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪状态。

由于任务B的优先级最高,会抢占CPU,接下来执行任务主体代码。任务主体代码执行完毕后,会继续调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态,周而复始。

从图2-1可以看出,从调用函数vTaskDelayUntil()开始,每隔固定+
-周期,任务B的主体代码就会被执行一次,即使任务B在执行过程中发生中断,也不会影响这个周期性,只是会缩短其它任务的执行时间!所以这个函数被称为绝对延时函数,它可以用于周期性的执行任务A的主体代码。

图2-1:绝对延时函数执行示意图

函数vTaskDelayUntil()是如何做到周期性的呢,我们来看一下源码。

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ){TickType_t xTimeToWake;BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;    vTaskSuspendAll();    {        /* 保存系统节拍中断次数计数器 */        const TickType_t xConstTickCount = xTickCount;        /* 计算任务下次唤醒时间(以系统节拍中断次数表示)   */        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;        /* *pxPreviousWakeTime中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了 */        if( xConstTickCount < *pxPreviousWakeTime )        {            /*只有当周期性延时时间大于任务主体代码执行时间,才会将任务挂接到延时列表.*/            if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )            {                xShouldDelay = pdTRUE;            }        }        else        {            /* 也都是保证周期性延时时间大于任务主体代码执行时间 */            if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )            {                xShouldDelay = pdTRUE;            }        }        /* 更新唤醒时间,为下一次调用本函数做准备. */        *pxPreviousWakeTime = xTimeToWake;         if( xShouldDelay != pdFALSE )        {            /* 将本任务加入延时列表,注意阻塞时间并不是以当前时间为参考,因此减去了当前系统节拍中断计数器值*/            prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );        }    }    xAlreadyYielded = xTaskResumeAll();     /* 强制执行一次上下文切换 */    if( xAlreadyYielded == pdFALSE )    {        portYIELD_WITHIN_API();    }}

与相对延时函数vTaskDelay不同,本函数增加了一个参数pxPreviousWakeTime用于指向一个变量,变量保存上次任务解除阻塞的时间。这个变量在任务开始时必须被设置成当前系统节拍中断次数(见上文的任务B举例),此后函数vTaskDelayUntil()在内部自动更新这个变量。

由于变量xTickCount可能会溢出,所以程序必须检测各种溢出情况,并且要保证延时周期不得小于任务主体代码执行时间。这很好理解,不可能出现每5毫秒执行一个需要20毫秒才能执行完的任务。
如果我们以横坐标表示变量xTickCount的范围,则横坐标左端为0,右端为变量xTickCount所能表示的最大值。在如图2-2所示的三种情况下,才可以将任务加入延时列表。

图2-2中

*pxPreviousWakeTime和xTimeToWake之间表示任务周期性延时时间,

*pxPreviousWakeTime和xConstTickCount之间表示任务B主体代码执行时间。

图2-2中

第一种情况处理系统节拍中断计数器(xConstTickCount)和唤醒时间计数器(xTimeToWake)溢出情况;

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

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

从图中可以看出,不管是溢出还是无溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。表现在图2-2中,就是变量xTimeToWake总是大于变量xConstTickCount(每溢出一次的话相当于加上一次最大值Max)。

图2-2:将任务加入延时列表的三种情况

计算的唤醒时间合法后,就将当前任务加入延时列表,同样延时列表也有两个。每次系统节拍中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。

感谢各位的阅读,以上就是"FreeRTOS系统延时实例分析"的内容了,经过本文的学习后,相信大家对FreeRTOS系统延时实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

任务 函数 时间 系统 变量 阻塞 主体 代码 周期 节拍 周期性 情况 两个 状态 计数器 优先级 指向 指针 次数 过程 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 手磨咖啡 山东特亿宝互联网科技有限公司 新华互联网科技学校 互联网科技财经 互联网科技金融 贷财互联网科技 广州互联网络科技有限公司 苏州大垚互联网科技 滨湖区互联网智能科技产品供应 现代科技和互联网有关系吗 中国的互联网科技企业的崛起 湖南诚度互联网科技有限公司 互联网科技与管理 广州市恰铎互联网科技有限公司 华夏恒生科技与互联网科技 下一代互联网重大应用科技园 吉林省码上互联网科技电话 湖北辰鑫科技互联网 福仕德教育科技互联网 互联网科技创新方面的资料 广州凡科互联网科技 互联网科技大会 居家互联网科技有限公司 海南购全球互联网科技 农村开互联网科技讲堂 来此购互联网科技有限公司 荆州市昕露互联网络科技有限公司 世界互联网科技成果奖 互联网时代科技型公司的机会 安徽九吞吴互联网科技有限公司
0