登录后台

页面导航

本文编写于 416 天前,最后修改于 416 天前,其中某些信息可能已经过时。

软件时间任务

软件时间用于计划未来的某个时间执行某个函数,固定周期的运行某个函数。被软件时间管理器执行的函数叫做软件时间管理回调函数。软件时间任务的实现是在FreeRTOS内核中控制的。它不依赖硬件支持,也不依赖硬件的时间管理和硬件计数。

软件时间管理器只有在执行回调函数时才会使用处理器时间。

将软件时间管理加入项目中:

  1. 编译FreeRTOS中的源文件FreeRTOS/Source/timers.c as 到你的项目中。
  2. 在FreeRTOSConfig.h文件中设置configUSE_TIMERS为1

软件时间任务回调函数

软件时间任务回调函数是用C语言实现的。唯一特别的是它的原型。它返回空,传递一个软件时间句柄。

void ATimerCallback( TimerHandle_t xTimer );

软件时间任务回调函数从开始执行到结尾,以普通方式返回。它应当尽量短,不可以进入阻塞状态。

软件时间任务时期,是指软件时间任务开始到软件时间任务回调函数运行中间的时间段

一次和自动重载时间任务

  • 一次性时间任务:一旦开始,一个一次性时间任务只会执行一次它的回调函数。一个一次性时间任务可以被重启,但不能自己重启
  • 自动重载时间任务:一旦开始,自动重载任务回按照希望的时间自动重启。会周期性执行它的回调函数

时间任务状态

时间任务可以处于以下两个状态中的一个:

  • 休眠:存在休眠任务,可以用它的句柄引用。因为没有在运行,所以没有执行回调函数
  • 运行:从时间任务进入运行状态或者重置开始,消逝的时间等于时间任务的时间段,时间任务会执行它的回调函数
// 自动重载时间任务状态切换

--------                       --------
| 起点  |--调用xTimerCreate()-->| 休眠  |
--------                       --------
                                |    ^
                                |    |
                           调用       调用
                    xTimerStart(),  xTimerStop()
                    xTimerReset(),   |
                xTimerChangePeriod() |
                                |    |
                                v    |
                              ----------
                        ----->|  运行     |----
                       /      ----------    \
                      |                      |
                       \  时间到调用时间回调 /   
                        --------------------
// 一次性时间任务状态切换

--------                       --------
| 起点 |--调用xTimerCreate()-->| 休眠 |<--------
--------                       --------         \
                                |    ^           \
                                |    |            \
                           调用       调用         |
                    xTimerStart(),  xTimerStop()   /
                    xTimerReset(),   |            /
                xTimerChangePeriod() |           /
                                |    |   时间到调用回调函数
                                v    |         /
                              ----------      /
                              |  运行  |------
                              ----------

实时系统守护(时间服务)任务

所有的时间任务回调函数都在相同的实时系统守护(时间服务)任务上下文执行。

这个守护任务是一个标准的FreeRTOS任务,是调度器开启的时候自动创建的。

它的优先级和栈大小由FreeRTOSConfig.h文件中的configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH指定。
时间回调函数中不能调用会阻塞的FreeRTOS函数,因为如果时间回调函数中有阻塞函数,就会使守护进程任务进入阻塞状态。

守护任务调度

守护任务调度和其他的FreeRTOS任务一样;它只会处理命令或者执行时间任务回调函数,当它是最高优先级任务时,就可以运行。

# 调用xTimerCreate()任务优先级比守护任务优先级高时执行顺序。图42
守护任务       |            --      |
任务1         |------------        |
空闲任务       |              ------|
             t1     t2t3  t4t5

# t2:任务1调用xTimerStart()
# t3:xTimerStart()返回
# t4:守护任务开始处理开启时间任务命令
# t5:守护任务进入阻塞状态
# 调用xTimerCreate()优先级比守护任务优先级低执行顺序。图43
守护任务    |     --          |
任务 1      |-----  -----     |
空闲任务    |            -----|
          t1    t2t3t4 t5
# t2:任务1调用xTimerCreate()函数。
# t2:守护任务处理"开启时间任务"指令。
# t3:守护任务进入阻塞状态。
# t4:调用xTimerCreate()返回

软件时间任务相关API

复位软件定时器

  • xTimerReset():复位软件定时器,用在任务中,不能用于中断服务函数。此函数是一个宏,真正执行的函数是xTimerGenericCommand()
  • xTimerResetFromISR();复位软件定时器,用在中断服务函数中

创建软件定时器

  • XTimerCreate():此函数用于创建一个软件定时器,所需要的内存通过动态内存管理方法分配。新创建的软件定时器处于休眠状态,也就是未运行
  • xTimerCreateStatic():此函数用于创建一个软件定时器,所需要的内存需要用户自行分配。

开始软件定时器

  • xTimerStart():开启软件定时器。如果软件定时器没有运行的话调用该函数就会计算定时器到期时间。如果软件定时器正在运行的话,调用该函数就会复位软件定时器。
  • xTimerStartFromISR():开启软件定时器,用于中断中。

停止软件定时器

  • xTimerStop()
  • xTimerStopFromISR()

创建一个一次性重载和自动重载的时间任务

// 创建和开启时间任务

/* 用做一次性时间任务的时间段3.333秒,和用于可重载时间任务的时间段0.5秒*/
#define mainONE_SHOT_TIMER_PERIOD  pdMS_TO_TICKS(3333)
#define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS(500)

int main( void ){
    TimerHandle_t xAutoReloadTimer, xOneShotTimer;
    BaseType_t xTimer1Started, xTimer2Started; 

    /* 创建一次性时间任务,引用句柄存放在xOneShotTimer中 */
    xOneShotTimer = xTimerCreate(
                    /* 时间任务名字,不会被FreeRTOS使用 */
                    "OneShot",
                    /* 时间软件tick周期数量 */
                    mainONE_SHOT_TIMER_PERIOD,
                    /* 设置uxAutoReload为pdFALSE,表示一次性时间任务*/
                    pdFALSE,
                    /* 这个时间任务不使用任务ID */
                    0,
                    /* 时间任务回调函数 */
                    prvOneShotTimerCallback
    );
    /* 创建自动重载时间任务,引用句柄存放在xAutoReloadTimer中*/
    xAutoReloadTimer = xTimerCreate(
                    /* 时间任务名字,不会被FreeRTOS使用 */
                    "AutoReload",
                    /* 时间软件tick周期数量 */
                    mainAUTO_RELOAD_TIMER_PERIOD,
                    /* 设置uxAutoReload为pdFALSE,表示一次性时间任务*/
                    pdTRUE,
                    /* 这个时间任务不使用任务ID */
                    0,
                    /* 时间任务回调函数 */
                    prvAutoReloadTimerCallback
    );
    /* 检查两个时间任务是否创建 */
    if(xOneShotTimer != NULL && xAutoReloadTimer != NULL){
      /* 开启软件时间任务,这里的阻塞时间为0。未开启调度器阻塞时间参数无用*/
      xTimer1Started = xTimerStart( xOneShotTimer, 0 );
      xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
      /* xTimerStart()实现使用了时间命令队列,如果时间命令队列已满xTimerStart()会调用失败。调度器开启之前时间守护任务不会被创建。因此在调度器开启之前,所有的发送给时间命令队列的命令都会被保存在队列中。检查2个时间任务是否创建成功。*/
      if( (xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS))
        /* 开启调度器 */
        vTaskStartScheduler();
    }

    /* 如果一切顺利,这里的代码不会运行 */
    for(;;);
}
// 一次性时间任务回调函数
static void prvOneShotTimerCallback( TimerHandle_t xTimer ){
  TickType_t xTimeNow;

  /* 包含当前tick计数 */
  xTimeNow = xTaskGetTickCount();
  /* 打印这个回调函数正在执行 */
  vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
  /* 任务执行次数变量 */
  ulCallCount++;
}
// 自动重载时间任务回调函数
static void prvAutoReloadTimerCallback( TimerHandle_t xTimer ){
  TickType_t xTimeNow;

  /* 包含当前tick计量 */
  xTimeNow = xTaskGetTickCount();
  /* 打印这个回调函数正在执行 */
  vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );
  /* 任务执行次数变量 */
  ulCallCount++;
}

时间任务ID

每个软件时间任务都有一个ID。它是一个标签值,可以给程序员使用。这个ID可以放在一个空指针之中(void *),所以也可以存放一个整数在里面,指向其它目标,或只是用做一个函数指针。
当软件时间任务创建时会设定一个时间任务ID的初始值,之后可以使用vTimerSetTimerID()函数设置这个ID值,也可以使用pvTimerGetTimerID()函数获取。
和其它软件时间任务函数不同,vTimerSetTimerID()和pvTimerGetTimerID()会直接访问软件时间任务,不会发送命令给时间命令队列。

// vTimerSetTimerID()函数原型
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );

/* 参数
 * xTimer: 将被更新ID值软件时间任务句柄。这个句柄是使用xTimerCreate()函数创建的软件时间任务返回的句柄。
 * pvNewID: 软件时间任务要被设置成的ID。
 */
// pvTimerGetTimerID()原型
void *pvTimerGetTimerID( TimerHandle_t xTimer );

/* 参数
 * xTimer: 要获取的软件时间任务句柄。这个句柄是使用xTimerCreate()函数创建的软件时间任务返回的句柄。
 * 返回值:返回这个软件时间任务的ID值。
 */

改变时间任务周期

每个FreeRTOS官方接口都提供了一个或多个实例项目。大部分项目会自检,LED灯用于反馈项目状态;如果自检通过,LED会慢慢闪烁,如果自检错误,那么LED灯会快速闪烁。
一些实例项目在任务中执行自检,例用vTaskDelay()控制LED闪烁的速度。其它一些实例用一个软件时间任务实现自检,使用时间任务周期控制LED闪烁的速度。

xTimerChangePeriod()函数

软件时间任务周期可以使用xTimerChangePeriod()函数改变。
如果用xTimerChangePeriod()改变已经运行的时间任务周期,那么这个时间任务会使用一个新的周期值重新计算它的期满时间。这个重新计算期满时间和什么时候调用xTimerChangePeriod()函数有关,和时间任务什么时候开启无关。
如果用xTimerChangePeriod()函数改变休眠态的时间任务,这个时间任务没有在运行状态,那么这个时间任务会计算一个期满时间,并切换为运行状态,就是说这个时间任务会进入运行态。
注意:不要在中断处理中调用xTimerChangePeriod()函数,要在中断处理函数中使用中断安全版本xTimerChangePeriodFromISR()代替。

// xTimerChangePeriod()函数原型
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, 
                               TickType_t xNewTimerPeriodInTicks, 
                               TickType_t xTicksToWait );
/* 参数
 * xTimer: 将被更新为一个新的周期值的时间任务句柄。这个句柄是通过xTimerCreate()函数创建软件时间任务的句柄。
 * xNewTimerPeriodInTicks: 新的软件时间任务周期值,以tick数量方式指定。可以使用pdMS_TO_TICKS()宏将时钟时间ms转换为tick数量。
 * xTicksToWait: xTimerChangePeriod()会通过时间命令队列发送一个"改变周期"命令给守护任务。当时间命令队列已满时,xTicksToWait指定一个调用任务可以保持在阻塞状态,等待这个队列有可用空间的最大的tick数量。
 * 如果时间命令队列已满,xTicksToWait为0时,xTimerChangePeriod()会立刻返回。
 * 可以使用pdMS_TO_TICKS()宏将ms的时钟周期转换为特定的tick数量
 * 如果FreeRTOSConfig.h中的INCLUDE_vTaskSuspend设置为1。可以将xTicksToWait设置为portMAX_DELAY,让xTimerChangePeriod()函数在时间命令队列有空闲位置之前一起阻塞下去,就是不会因为时间到期而强制返回。
 * 如果在开启调度器之前调用这个函数,那么xTicksToWait就没有作用,相当于xTicksToWait为0。
 * 返回值
 * 可能有两种返回值
 * pdPASS: 如果命令成功发送给时间命令队列就返回pdPASS.
 *         如果阻塞时间不为0,那么这个函数返回前调用它的任务可能进入阻塞状态,等待时间命令队列有可用空间。但最后队列会在到期前成功将数据写入队列。
 * pdFALSE: 如果因为时间命令队列已满,"改变周期"命令无法写入队列就会返回pdFALSE。
 * 如果阻塞时间不为0,那么调用任务就可能进入阻塞态,等待守护任务让时间命令队列有空闲空间。但阻塞的时间会在队列有空间空间发生前到期。
 */

重置软件时间任务

重置软件时间任务意味着重启这个时间任务;这个时间任务关联的期望到期时间会在重置时重新计算,而不是时间任务初始化时。

# 开启和重置一个周期为6个ticks的时间任务

|<----期满时间计算----->|
           t7(t1 + 6)
                 |<----期满时间计算----->|
                           t11(t5+6)
                                |<----期满时间计算----->|
                                            t15(t9+6)
t1  t2  t3  t4  t5  t6  t7  t8  t9  t10 t11 t12 t13 t14 t15
# t1: 开启时间任务
# t5: 重置时间任务
# t9: 重置时间任务
# t15: 执行时间任务
  • 时间任务1在t1时刻开启。它的周期是6,因此时间任务1初始化后计算出期满时间是t7,就是6个ticks后。
  • 时间任务1在t7前重置,因此是在它执行回调函数前。时间任务1在t5时刻重置,因此在这个时候时间任务1回调函数执行时间重新计算后是t11,就是在重置后的6个ticks。
  • 时间任务1在t11前重置,因此是在它执行回调函数前。时间任务1在t9时刻重置,因此在这个时候任务1回调函数执行时间重新计算后是t15,就是在重置后的6个ticks。
  • 时间任务1没有再次重置,t15时间期满,执行它的回调函数。

xTimerReset()函数

时间任务可以使用xTimerReset()重置。
xTimerReset()也可以用来重置一个休眠态的时间任务。
注意:不要在中断处理函数中使用xTimerReset(),而应当使用中断安全版本xTimerResetFromISR()代替。

// xTimerReset()函数原型
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 参数
 * xTimer: 将被重置或开启的软件时间任务句柄。这个句柄是调用xTimerCreate()创建软件时间任务的句柄引用。
 * xTicksToWait: xTimerReset()函数用时间命令队列将"重置"命令发送给守护任务。xTicksToWait指定了当时间命令队列已经满时,调用xTimerReset()函数的任务最长可阻塞的tick数量,用于等待时间队列任务有空闲空间。
 * 如果xTicksToWait为0,xTimerReset()函数会立即返回。
 * 如果FreeRTOSConfig.h文件中的INCLUDE_vTaskSuspend设置为1。可以将 xTicksToWait设置为portMAX_DELAY,使xTimerReset()函数一直等待时间命令函数出现空闲空间,也就是说阻塞时间无限,时间命令队列一直没有空闲空间,这个函数就一直等待下去。
 * 返回值 
 * 可能有两种返回值
 * 1. pdPASS: 如果数据成功写入到时间命令参数,就返回pdPASS。
 *            如果阻塞时间不为0,调用这个函数的任务可能进入阻塞状态,等待时间命令队列中有空闲空间可用。但在阻塞时间期满前数据会成功写入时间命令队列,也会返回pdPASS。
 * 2. pdFALSE: 因为时间命令队列已经满,"重置"命令无法写入到时间命令队列,就会返回pdFALSE。
 *             如果阻塞时间不为0,那么调用这个函数的任务可能进入阻塞状态,等待时间命令队列中有空闲空间可用。但在阻塞时间期满后时间命令队列中方才出现可用空间。
 */
博主已关闭本页面的评论功能