软件时间任务
软件时间用于计划未来的某个时间执行某个函数,固定周期的运行某个函数。被软件时间管理器执行的函数叫做软件时间管理回调函数。软件时间任务的实现是在FreeRTOS内核中控制的。它不依赖硬件支持,也不依赖硬件的时间管理和硬件计数。
软件时间管理器只有在执行回调函数时才会使用处理器时间。
将软件时间管理加入项目中:
- 编译FreeRTOS中的源文件FreeRTOS/Source/timers.c as 到你的项目中。
- 在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,那么调用这个函数的任务可能进入阻塞状态,等待时间命令队列中有空闲空间可用。但在阻塞时间期满后时间命令队列中方才出现可用空间。
*/