前言
软件定时器使用tick作为时间片轮转调度以及延迟操作的时间度量单位,tick是实现定时触发功能的基础。tick计数发生在每次时钟中断处理的过程,时钟中断是定时产生的,系统在默认情况下为1ms触发一次,即一个tick代表1ms,用户可根据应用需要调整该时间。 软件定时器是用来在指定时间或者触发一次或多次某个功能函数的调用。这种由定时器来执行的函数叫做定时器回调函数,定时器回调函数以及触发时间由应用程序来设定。当定时器的触发时间到来,则定时器的回调函数会被执行。软件定时器分为以下两种模式:
单次模式
定时器会从应用程序设置的初始时间开始,以tick为计时单位进行倒计时,当计数值减为0时调用回调函数执行。回调函数执行完毕,则定时器停止。
周期性模式
周期定时器在时间到期执行完回调函数后,重新开始计时,直到下次时间到期,再次执行回调函数,然后一直循环下去。
创建、删除定时器
TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction );
const char * const pcTimerName:定时器名称,和线程的名称同一个道理
const TickType_t xTimerPeriodInTicks:指定多少个tick执行一次
const UBaseType_t uxAutoReload:pdTRUE表示永久执行定时器、pdFALSE表示单次定时器
void * const pvTimerID:回调函数可以使用这个参数来分辨是那个定时器
TimerCallbackFunction_t pxCallbackFunction:回调函数,定时器超时执行的函数
这里的ID获取可以通过下面两个函数来修改或查询,这两个函数不涉及命令,直接通过定时器的结构体来获取信息;
void *pvTimerGetTimerID( TimerHandle_t xTimer ); void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );
TimerHandle_t xTimer:需要删除的定时器句柄
TickType_t xTicksToWait:删除等待时间,可设置为portMAX_DELAY:一定等到成功才返回;
可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count;
定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。如果队列满了,"命令"就无法即刻写入队列,所以需要设置一个超时时间xTicksToWait,等待一会。
启动、停止定时器
和前面队列的使用方法类似在中断中使用定时器的话需要调用中断专用的API,中断中使用xTimerStopFromISR,xTimerStartFromISR;
// 启动定时器 BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait ); // 停止定时器 BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
TimerHandle_t xTimer:需要操作的定时器句柄
TickType_t xTicksToWait:启动等待时间,可设置为portMAX_DELAY:一定等到成功才返回;
可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count;
返回值: pdFAIL表示"停止或者启动命令"在xTicksToWait个Tick内无法写入队列, pdPASS表示成功
定时器复位
我们可以把定时器简单理解为有两个状态:运行态、冬眠态;当我们的定时器创建成功后进入冬眠态,当我们调用复位、启动灯函数将定时器转换为运行态;这里的复位同样在中断中调用需要执行中断的哈数xTimerResetFromISR;
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
TimerHandle_t xTimer:需要操作的定时器句柄
TickType_t xTicksToWait:命令等待时间,可设置为portMAX_DELAY:一定等到成功才返回;
可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count;
修改定时器超时时间
使用xTimerChangePeriod()函数,处理能修改它的周期外,还可以让定时器的状态从冬眠态转换为运行态。修改定时器的周期时,会使用新的周期重新计算它的超时时间。中断中调用xTimerChangePeriodFromISR进行操作;
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xTicksToWait );
TimerHandle_t xTimer:需要操作的定时器句柄
TickType_t xNewPeriod:新的定时器超时时间
TickType_t xTicksToWait:命令等待时间,可设置为portMAX_DELAY:一定等到成功才返回;
可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count;
示例
配置文件可能需要修改
/* 2. 配置文件FreeRTOSConfig.h中 */ #define configUSE_TIMERS 1 /* 使能定时器 */ #define configTIMER_TASK_PRIORITY 31 /* 守护任务的优先级, 尽可能高一些 */ #define configTIMER_QUEUE_LENGTH 5 /* 命令队列长度 */ #define configTIMER_TASK_STACK_DEPTH 32 /* 守护任务的栈大小 */
需要包含头文件
#include "timers.h"
定时器的创建的启动删除使用示例,该例子在主线程中创建了一个线程,然后我们在线程中创建了一个定时器以没200个tick执行一次回调函数,回调函数中打印hello xTimer并反转LED1,在线程中我们打印hello FreeRTOS并以500个tick反转LED0;
TimerHandle_t timer; static void timer_led_callback(TimerHandle_t xTimer) { HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin); printf("hello xTimer!\r\n"); } static void led_task(void *par) { timer = xTimerCreate("test_timer",200,pdTRUE,NULL,timer_led_callback); xTimerStart(timer, 0); while(1) { HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin); vTaskDelay(500); /* 延时500个tick */ printf("hello FreeRTOS!\r\n"); } xTimerDelete(timer, portMAX_DELAY); }
实验效果
结尾
我是凉开水白菜,我们下文见~