news 2026/4/23 16:38:07

FreeRTOS源码深入研究:6.软件定时器(software timer)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS源码深入研究:6.软件定时器(software timer)

一、软件定时器的特性

软件定时器是由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; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 12:36:17

2025 洗衣液行业投资逻辑:政策驱动绿色转型,细分赛道暗藏机遇

2025 洗衣液行业投资逻辑&#xff1a;政策驱动绿色转型&#xff0c;细分赛道暗藏机遇 中国洗涤用品工业协会 2025 年数据显示&#xff0c;国内洗衣液市场规模已突破 580 亿元&#xff0c;年复合增长率维持在 6.2% 左右&#xff0c;预计 2030 年将进一步突破 760 亿元。作为刚需…

作者头像 李华
网站建设 2026/4/20 16:32:22

3、开源软件项目剖析与Linux安装准备

开源软件项目剖析与Linux安装准备 开源软件项目的魅力与Linux的诞生 在很多人眼中,Linux就像是一个奇异的突变体,难以想象一个如此复杂且依赖严谨规范的计算机操作系统,竟由全球各地松散的计算机极客志愿者们共同开发。但实际上,开源方式能创造出卓越的软件,往往源于人类…

作者头像 李华
网站建设 2026/4/23 12:54:34

TG买卖60W公民信息,有期徒刑三年无缓刑!

案情回溯罗某某&#xff0c;男&#xff0c;汉族&#xff0c;大学专科文化&#xff0c;户籍所在地江西省吉安市。从2024年3月份开始&#xff0c;罗某某通过翻墙到外网软件telegram上购买、翻墙到外网软件telegram上或在微信群聊里非法下载留存、利用爬虫脚本爬取职称网站上的职称…

作者头像 李华
网站建设 2026/4/23 14:08:27

一个人腰痛的原因是?

以下内容均为真实。 有一位 121212 岁半同学&#xff0c;我们给这位同学取一个化名&#xff0c;就叫小红吧。 小红说&#xff0c;她半年前臀部就特别痛&#xff0c;因为这一点&#xff0c;附近的腰部也很疼&#xff0c;为了写文章&#xff0c;我采访了小红&#xff0c;这是我 3…

作者头像 李华
网站建设 2026/4/23 14:08:03

SG-TCP232-110(单通道串口服务器)特点与功能介绍

一、核心定位与工作模式SG-TCP232-110 是工业级串口转网口协议转换设备&#xff0c;核心功能是实现串口数据与以太网数据的双向透明传输&#xff0c;解决普通串口设备的联网问题。设备集成 RS232 与 RS485 双串口&#xff08;内部连通&#xff0c;仅可单口工作&#xff09;和百…

作者头像 李华