一、软件定时器的特性
软件定时器是由FreeRTOS内核提供的纯软件功能,通过守护任务(prvTimerTask)统一管理所有定时器,不依赖于硬件定时器外设。软件定时器可以让某个任务只执行一次(一次性:启动后,回调函数只会被调用一次),也可以让任务周期性执行(自动加载定时器:启动后,时间到之后会自动启动它,使得回调函数被周期性得调用)。
当FreeRTOS的配置项configUSE_TIMERS被设置为1时,在启动调度器时,会自动创建守护任务(RTOS Damemon Task)
在传统定时器设计中,每次发生硬件节拍中断时,中断处理函数会立即检查是否有定时器超时。如果发现超时,会直接在中断上下文中调用对应的定时器函数。但这种方式存在明显问题:若定时器任务函数执行时间过长,会阻塞整个中断系统,影响系统的实时响应。
FreeRTOS采用了更高效的机制:当定时器超时,中断函数通过写队列来唤醒守护任务。守护任务中一直不断的在读队列,一旦读取到超时消息的队列,便从队列中读取并处理,然后在守护任务的上下文中执行定时器任务函数。这样,定时器任务函数的执行不再受中断环境限制,增强了系统的稳定性和响应性。
我们自己编写的任务函数要使用定时器时,是通过"定时器命令队列"(timer command queue)与守护任务交互的。守护任务的优先级为:configTIMER_TASK_PRIORITY
二、软件定时器的相关函数源码研究
说明:这里只研究常见的、常用的函数源码
2.1定时器控制块结构体(tmrTimerControl)
/* 该结构体用于管理软件定时器的所有属性 */ typedef struct tmrTimerControl { const char *pcTimerName; /* 定时器名称字符串 */ /* 用于将定时器插入内核管理的链表中(如就绪链表或阻塞链表) */ ListItem_t xTimerListItem; /* 定时器周期(启动定时器和运行回调函数两者的的间隔),以 Tick 为单位 */ TickType_t xTimerPeriodInTicks; /* 自动重载标志,设置为 pdTRUE 时,为周期性定时器。 设置为 pdFALSE 时,为一次性定时器 */ UBaseType_t uxAutoReload; void *pvTimerID; /* 定时器标识 ID */ /* 回调函数指针,指向用户定义的函数。定时器超时时,内核将调用此函数处理超时事件 */ TimerCallbackFunction_t pxCallbackFunction; /* 当启用“跟踪功能”时,分配定时器编号,用于内核调试识别定时器 */ #if( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTimerNumber; #endif /* 仅在同时启用静态和动态分配时存在 */ /* 静态分配标志:设置为 pdTRUE 表示定时器内存由用户静态分配,删除定时器时不尝试释放内存;否则为动态分配,删除时需要释放内存 */ #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) uint8_t ucStaticallyAllocated; #endif } xTIMER; typedef xTIMER Timer_t;2.2定时器创建函数
与队列一样,创建定时器有:动态分配内存 和 静态分配内存。原型如下:
/* 使用动态分配内存的方法创建定时器 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到 * xTimerPeriodInTicks: 周期, 以Tick为单位 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器 * pxCallbackFunction: 回调函数 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL */ TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ); /* 使用静态分配内存的方法创建定时器 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到 * xTimerPeriodInTicks: 周期, 以Tick为单位 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器 * pxCallbackFunction: 回调函数 * pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL */ TimerHandle_t xTimerCreateStatic(const char * const pcTimerName, TickType_t xTimerPeriodInTicks, UBaseType_t uxAutoReload, void * pvTimerID, TimerCallbackFunction_t pxCallbackFunction, StaticTimer_t *pxTimerBuffer );这里只分析动态创建的源码
TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ) { Timer_t *pxNewTimer; //指向新定时器控制块的指针 /* 动态分配定时器结构体内存,大小为Timer_t结构体的大小 * 如果内存分配失败,pvPortMalloc返回NULL */ pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); /* 检查内存分配是否成功 */ if( pxNewTimer != NULL ) { /* 调用初始化函数 */ prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer ); #if( configSUPPORT_STATIC_ALLOCATION == 1 ) { /* 标记为动态分配,以便删除定时器时正确释放内存 */ pxNewTimer->ucStaticallyAllocated = pdFALSE; } #endif } return pxNewTimer; }static void prvInitialiseNewTimer( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction, Timer_t *pxNewTimer ) { /* 检查定时器指针是否有效,防止对空指针进行操作 */ if( pxNewTimer != NULL ) { /* 确保定时器服务任务所需的内部基础设施已创建和初始化 */ prvCheckForValidListAndQueue(); /* 使用函数参数初始化定时器结构体成员 */ pxNewTimer->pcTimerName = pcTimerName; //定时器名称 pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;//定时器周期 pxNewTimer->uxAutoReload = uxAutoReload; //自动重载标志 pxNewTimer->pvTimerID = pvTimerID; //定时器ID标识 pxNewTimer->pxCallbackFunction = pxCallbackFunction; //超时回调函数指针 /* 初始化定时器结构体中的链表节点,设置为一个独立的、未连接的状态, 为后续插入定时器管理链表做准备 */ vListInitialiseItem( &( pxNewTimer->xTimerListItem ) ); traceTIMER_CREATE( pxNewTimer ); }FreeRTOS用两个有序链表管理活跃的定时器。
static List_t xActiveTimerList1 = {0};
static List_t xActiveTimerList2 = {0};链表按超时时间升序排列(最近超时的在前面)
定时器何时加入链表?当调用xTimerStart()、xTimerReset()等启动函数时(下面会提到)
2.3定时器删除函数
#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) ) #define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) ) #define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD, ( xNewPeriod ), NULL, ( xTicksToWait ) ) #define xTimerDelete( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, ( xTicksToWait ) ) #define xTimerReset( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) ) #define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U ) #define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U ) #define xTimerResetFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )通过“timers.h”可知,以上函数都是通过调用统一的xTimerGenericCommand函数实现所有定时器操作的,这样可以简化架构设计,提高代码复用性。接下来详细研究这个函数。
定时器通用命令函数,用于向定时器守护任务发送各种操作命令
参数:
TimerHandle_t xTimer:目标定时器句柄,指向要操作的定时器控制块
const BaseType_t xCommandID:命令标识符,指定要执行的操作类型
const TickType_t xOptionalValue:参数值,不同命令有不同的含义
BaseType_t * const pxHigherPriorityTaskWoken:用于ISR版本中传递任务唤醒状态
const TickType_t xTicksToWait:任务上下文发送命令时的最大阻塞时间
BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionalValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait ) { BaseType_t xReturn = pdFAIL; //函数返回值 DaemonTaskMessage_t xMessage; //定时器命令消息结构体,用于封装发送到队列的数据 configASSERT( xTimer ); /* 检查定时器命令队列是否已创建 */ if( xTimerQueue != NULL ) { /* 填充命令消息结构体的字段 */ /* 设置消息ID,即要执行的命令类型 */ xMessage.xMessageID = xCommandID; /* 设置可选参数值,不同类型命令使用此字段传递额外信息 */ xMessage.u.xTimerParameters.xMessageValue = xOptionalValue; /* 设置目标定时器指针,使守护任务知道操作哪个定时器 */ xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer; /* 判断命令类型:是否是中断服务程序的请求 命令ID小于tmrFIRST_FROM_ISR_COMMAND表示是任务上下文调用 */ if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ) { /* 如果调度器正在运行,按正常方式发送 */ if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ) { /* 将命令消息发送到定时器队列尾部,使用指定的阻塞时间 */ xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait ); } else { /* 调度器未运行(可能在初始化阶段),以无延迟方式发送 */ xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY ); } } /* 中断服务程序(ISR)上下文发送命令分支 */ else { xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken ); } traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn ); } else { mtCOVERAGE_TEST_MARKER(); } return xReturn; }