一、软件定时器介绍
freeRTOS软件定时器的时基是基于系统时钟节拍实现的,可以创建很多个,在硬件定时器资源不充足的情况下非常有用。
软件定时器一般用作周期性地执行函数,在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。
二、软件定时器的两种模式
FreeRTOS 提供的软件定时器支持单次模式和周期模式, 单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数。 单次模式(One-shot timers):当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。 周期模式(Auto-reload timers):这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除
?三、软件定时器的两种状态
休眠态(Dormant):A Dormant software timer exists, and can be referenced by its handle, but is not running, so its callback functions will not execute.
运行态(Running):A Running software timer will execute its callback function after a time equal to its? period has elapsed since the software timer entered the Running state, or since the software timer was last reset
自动重载定时器执行回调函数后重新进入运行状态,一次性定时器执行回调函数后进入休眠状态。
四、守护任务和定时器命令队列
1、守护任务(daemon task)
daemon task又称作timer service task,这是因为早期这个任务是执行定时器回调函数的,现在freertos版本的该任务还有别的用途,因此,更多地叫做daemon task。
当rtos调度器开始调度的时候,daemon task自动被创建,daemon task的优先级(configTIMER_TASK_PRIORITY)和栈大小(configTIMER_TASK_STACK_DEPTH)在?FreeRTOSConfig.h设置。
守护任务为了更好的管理FreeRTOS的定时器组件而专门创建了一个定时器任务。
2、定时器命令队列(The Timer Command Queue)
The Timer Command Queue是一个标准的队列,在调度器开始调度的时候也会自动创建,队列的长度在FreeRTOSConfig.h文件中configTIMER_QUEUE_LENGTH指定。
软件定时器API 会通过?定时器命令队列?发送定时器命令(‘start a timer’, ‘stop a timer’ and ‘reset a timer’) 给守护任务,守护任务来执行相关命令和定时器回调函数。
(这是实现软件定时器的核心原理)
发送到定时器命令队列的命令包含一个时间戳。例如,如果发送“启动计时器”命令来启动一个周期为 10 的计时器,则时间戳用于确保10个周期是从发送出命令开始算起,而不是命令由守护任务处理后开始算起。
3、示例
- t1:task1状态为Running state,daemon task 在?the Blocked state
- t2:task1调用?xTimerStart().xTimerStart() 向定时器命令队列发送命令,导致守护进程任务离开阻塞状态。 task1的优先级高于守护程序任务,因此守护程序任务不会抢占task1。task1仍处于 Running 状态,守护程序任务已离开 Blocked 状态并且进入就绪状态。
- t3:task1执行完成xTimerStart函数,继续执行后面的内容
- t4:task1调用某些API函数导致进行阻塞状态,daemon task 在进入运行态
- t5:daemon task完成task1发送的命令,此时命令队列为空,daemon task又进入阻塞状态。idle task进入运行态
- t1:task1状态为Running state,daemon task 在?the Blocked state
- t2:task1调用?xTimerStart().xTimerStart() 向定时器命令队列发送命令,导致守护进程任务离开阻塞状态。 task1的优先级低于守护任务,因此守护任务抢占task1,task1变为就绪态,此时task1还没有执行完成xTimerStart函数。守护程序进入 Running 状态。
- t3:daemon task完成task1发送的命令,此时命令队列为空,daemon task又进入阻塞状态。task1从就绪态进入运行态
- t4:task1执行完成xTimerStart函数,继续执行后面的内容
- t5:task1调用某些API函数导致进行阻塞状态,idle task进入运行态
五、相关API函数
API原型 | 函数说明 | 参数说明 | 返回值 |
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
| 用于创建一个软件定时器,创建成功后定时器处于休眠状态。 可以在任务调度器调度之前创建定时器。 |
| NULL?表示没有足够的堆空间分配给队列而导致创建失败。 非?NULL?值表示队列创建成功。此返回值应当保存下来,以作为 操作此定时器的句柄 |
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
| 启动在休眠状态的定时器,或者复位(重新启动)在运行状态的定时器; 可以在任务调度器调度之前启动定时器,但是定时器不会在调度器工作之前真正启动定时器; 在中断服务程序里应该使用xTimerStartFromISR()来启动定时器 |
- xTimer:目标定时器的句柄
- xTicksToWait:如果定时器命令队列已满,调用任务应保持在阻塞状态以等待命令队列上腾出可用空间
| pdPASS:启动定时器的命令成功发送到定时器命令队列 pdFALSE:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入) |
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
| 设置定时器ID |
- xTimer:目标定时器的句柄
- pvNewID :定时器id,void *类型,所以可以保存任意类型。
| 无 |
void *pvTimerGetTimerID( TimerHandle_t xTimer );
| 获取定时器id,通常用在回调函数中。 当多个定时器的回调函数相同,在回调函数获取id从而辨识是哪一个定时器调用的回调函数。 |
| 获取到的id |
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );
| 修改定时器的定时时间。 在休眠状态下调用该API,会使得定时器开始运行,不需要执行start函数;在运行状态下调用该API,修改定时时间,定时器还处在运行状态。 中断模式下应使用xTimerChangePeriodFromISR() |
- xTimer:目标定时器的句柄
- xNewTimerPeriodInTicks:定时时间,以ticks为单位
- xTicksToWait:如果定时器命令队列已满,调用任务应保持在阻塞状态以等待命令队列上腾出可用空间
| pdPASS:更改定时器时间命令成功发送到定时器命令队列 pdFALSE:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入) |
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
| 复位定时器,即重新启动定时器,定时器的超时时间从reset定时器开始算起,并不是从一开始启动的时间算起。 在休眠模式下调用该API,会自动启动定时器。 中断模式下应使用
xTimerResetFromISR()
|
- xTimer:目标定时器的句柄
- xTicksToWait:如果定时器命令队列已满,调用任务应保持在阻塞状态以等待命令队列上腾出可用空间
| pdPASS:复位定时器的命令成功发送到定时器命令队列 pdFALSE:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入) |
六、定时器使用示例
1、one-shot和auto-reload定时器举例
#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 = xTimerCreate( "OneShot", mainONE_SHOT_TIMER_PERIOD,pdFALSE,
0, prvOneShotTimerCallback );
xAutoReloadTimer = xTimerCreate( "AutoReload", mainAUTO_RELOAD_TIMER_PERIOD, pdTRUE,
0, prvAutoReloadTimerCallback );
if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
{
xTimer1Started = xTimerStart( xOneShotTimer, 0 );
xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
/* The timer service task does not get created until the scheduler is started,
so all commands sent to the command queue will stay in the queue until after
the scheduler has been started. */
if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
{
vTaskStartScheduler();
}
}
for( ;; );
}
static void prvOneShotTimerCallback( TimerHandle_t xTimer )
{
TickType_t xTimeNow;
xTimeNow = xTaskGetTickCount();
vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
ulCallCount++;
}
static void prvAutoReloadTimerCallback( TimerHandle_t xTimer )
{
TickType_t xTimeNow;
xTimeNow = uxTaskGetTickCount();
vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );
ulCallCount++;
}
ref:
【FreeRTOS操作系统教程】第19章?FreeRTOS定时器组_硬汉Eric2013_新浪博客
FreeRTOS软件定时器_hqy450665101的博客-CSDN博客_freertos软件定时器
|