FreeRTOS源码解析(10)软件定时器

1.创建软件定时器

1.1 源码解析

功能 :动态创建一个软件定时器实例,分配定时器控制块(Timer_t)所需的内存,完成基础初始化。

注意:创建后的定时器处于休眠状态,不会自动开始计时,必须调用 xTimerStart() 等方法才会激活。

参数

  • pcTimerName:定时器名称字符串指针,用于调试和运行时统计(可通过 pcTimerGetName() 获取)。FreeRTOS 不会拷贝该字符串,仅保存指针,因此传入的字符串必须保证在整个定时器生命周期内有效(通常使用字符串字面量即可)。

  • xTimerPeriodInTicks:定时器周期,单位为系统节拍(Tick)。定时器到期后触发回调,若为自动重载模式则以此周期重复触发。

  • xAutoReload

    • pdTRUE:自动重载模式。定时器到期后自动重新启动,周期性地触发回调。

    • pdFALSE:单次模式。定时器到期后进入休眠态,需手动再次调用 xTimerStart() 才能再次触发。

  • pvTimerID:定时器 ID 指针,用户可附加任意数据(如结构体指针),通过 pvTimerGetTimerID() 在回调中获取。传 NULL 表示不使用。

  • pxCallbackFunction:定时器回调函数指针。定时器到期时,由定时器服务任务(Timer Task)调用此函数。回调函数必须符合 void vCallbackFunction( TimerHandle_t xTimer ) 的原型。

返回值

  • 成功:返回定时器句柄(Timer_t *),可用于后续操作(启动、停止、删除等)。

  • 失败:返回 NULL(内存分配失败)。

    TimerHandle_t xTimerCreate( const char * const pcTimerName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
    const TickType_t xTimerPeriodInTicks,
    const BaseType_t xAutoReload,
    void * const pvTimerID,
    TimerCallbackFunction_t pxCallbackFunction )
    {
    Timer_t * pxNewTimer; //指向新分配的定时器控制块的指针,初始化为 pvPortMalloc 的返回值。

    复制代码
      /* 内存分配
          * 使用 FreeRTOS 的动态内存分配函数分配 Timer_t 结构体所需的内存。分配策略取决于 FreeRTOSConfig.h 中配置的堆管理方案(heap_1.c ~ heap_5.c)
          * 若内存不足,pvPortMalloc 返回 NULL,函数将直接返回 NULL 给调用者。 */
      pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of Timer_t is always a pointer to the timer's mame. */
    
      /* 空指针检查与初始化 */
      if( pxNewTimer != NULL )        //分配成功
      {
          /* Status is thus far zero as the timer is not created statically
              * and has not been started.  The auto-reload bit may get set in
              * prvInitialiseNewTimer. */
          /* ucStatus 是定时器的状态字节,各 bit 定义如下:
              *   Bit 0 (0x01) --- tmrSTATUS_IS_ACTIVE               --- 是否已启动(活跃态)
              *   Bit 1 (0x02) --- tmrSTATUS_IS_STATICALLY_ALLOCATED --- 是否由静态分配创建
              *   Bit 2 (0x04) --- tmrSTATUS_IS_AUTORELOAD           --- 是否自动重载
              * 
              * 此处全部清零,表示:
              *   - 未启动(处于休眠态 Dormant)
              *   - 非静态分配(动态创建,后续可安全释放)
              *   - 自动重载位暂为 0,prvInitialiseNewTimer 中会根据 xAutoReload 参数
              *     调用宏将该位置位或保持清零 */
          /* 注意:这里没有设置静态分配的标记位。在删除定时器时,vTimerDelete 函数会检查 ucStatus 中的静态分配标记;此处默认为 0,
              * 意味着该定时器没有静态分配标记,可以被安全释放。这与动态创建的任务、队列等行为一致。 */
          pxNewTimer->ucStatus = 0x00;
          /* 完成剩余字段的填充(包括将 xAutoReload 参数最终编码进 ucStatus 的对应位、设置周期、名称指针、ID 指针、回调函数指针,并将定时器插入内核的定时器列表中等)。 */
          /* 此时定时器处于休眠态(Dormant),未被加入当前活动定时器列表,等待 xTimerStart 等命令才会真正激活 */
          prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, xAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );
      }
    
      return pxNewTimer;  //返回句柄

    }

1.2 使用示例

1.2.1 一次性软件定时器

复制代码
/* 软件定时器的句柄 */
TimerHandle_t timer1_handle;
/* 创建一次性软件定时器 */
timer1_handle = xTimerCreate(
    "ergou",         // 软件定时器的名称
    (TickType_t)500, // 超时时间 = 500个 RTOS的时钟节拍
    pdFALSE,         // 是否自动重载:pdTRUE --- 周期型; pdFALSE --- 一次性
    (void *)1,       // 定时器的唯一ID
    timer1_callback);

if (timer1_handle != NULL)
{
    printf("timer1一次性定时器创建成功...\r\n");
}

/* 回调函数 */
void timer1_callback(TimerHandle_t xTimer)
{
    static uint16_t timer1_count = 0;
    printf("timer1超时回调=%d次..\r\n",++timer1_count);
} 

1.2.2 周期性软件定时器

复制代码
/* 软件定时器的句柄 */
TimerHandle_t timer2_handle;
/* 创建周期性软件定时器 */
timer2_handle = xTimerCreate(
    "gousheng",       // 软件定时器的名称
    (TickType_t)1000, // 超时时间 = 1000个 RTOS的时钟节拍
    pdTRUE,           // 是否自动重载:pdTRUE --- 周期型; pdFALSE --- 一次性
    (void *)2,        // 定时器的唯一ID
    timer2_callback);
if (timer2_handle != NULL)
{
    printf("timer2周期性定时器创建成功...\r\n");
}

/* 回调函数 */
void timer2_callback(TimerHandle_t xTimer)
{
    static uint16_t timer2_count = 0;
    printf("timer2超时回调=%d次..\r\n",++timer2_count);
} 

2.启动软件定时器

2.1 源码解析

功能 :启动指定的软件定时器。该宏是对 xTimerGenericCommand 的封装,向定时器服务任务发送一条 tmrCOMMAND_START 命令。定时器服务任务收到命令后,将定时器从休眠态切换到活跃态,并根据定时器周期开始计时。

参数

  • xTimer:要启动的定时器句柄,由 xTimerCreatexTimerCreateStatic 返回。

  • xTicksToWait:命令发送时的最大阻塞时间。若定时器命令队列满,调用任务将阻塞等待,直到队列有空位或超时。对于启动定时器这类非紧急操作,通常传 0(非阻塞)或较短超时。

  • 内部固定展开为:

    • xCommandID = tmrCOMMAND_START,指定操作为"启动定时器"。

    • xOptionalValue = xTaskGetTickCount(),将当前系统节拍计数器值作为附加数据传入。定时器服务任务会利用这个时刻来计算该定时器的首次到期时间(当前时刻 + 定时器周期),从而确保定时器从现在开始计时,而非从创建时刻或某个旧时刻开始。

    • pxHigherPriorityTaskWoken = NULL,表示这是任务级调用,不需要中断唤醒标志。

      #define xTimerStart( xTimer, xTicksToWait )
      xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )

功能 :向定时器服务任务发送一条命令消息,指示其对指定定时器执行 xCommandID 所定义的操作。该函数是 FreeRTOS 定时器 API(xTimerStartxTimerStopxTimerResetxTimerChangePeriodxTimerDelete 等)的统一底层实现,所有定时器操作均通过此函数将命令封装后发往定时器命令队列。

参数

  • xTimer:目标定时器句柄。

  • xCommandID:命令标识,决定对定时器执行何种操作。常见命令:

    • tmrCOMMAND_START:启动定时器

    • tmrCOMMAND_STOP:停止定时器

    • tmrCOMMAND_RESET:复位定时器

    • tmrCOMMAND_CHANGE_PERIOD:修改定时器周期

    • tmrCOMMAND_DELETE:删除定时器

    • 以及对应的 _FROM_ISR 版本命令

  • xOptionalValue:命令携带的可选值,含义取决于具体命令(例如 CHANGE_PERIOD 时为新的周期值,START 时可以为阻塞时间等)。

  • pxHigherPriorityTaskWoken仅用于中断版本xCommandID >= tmrFIRST_FROM_ISR_COMMAND)。若向队列发送消息导致更高优先级任务就绪,该标志被置为 pdTRUE,用于在中断退出时触发上下文切换。任务级调用时该参数被忽略(可为 NULL)。

  • xTicksToWait仅用于任务版本xCommandID < tmrFIRST_FROM_ISR_COMMAND)。若队列满,任务等待发送的阻塞时间。

返回值

  • pdPASS:命令成功发送到队列。

  • pdFAIL:发送失败(例如队列满且阻塞超时,或队列尚未创建)。

    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; //返回值,初始化为 pdFAIL,仅在成功发送消息后改为 pdPASS
    DaemonTaskMessage_t xMessage; //命令消息结构体,包含命令 ID 和定时器参数,将被发送到定时器命令队列。

    复制代码
      /* 断言检查,确保传入的定时器句柄有效(非 NULL),需用户自定义实现 */
      configASSERT( xTimer );
    
      /* Send a message to the timer service task to perform a particular action
          * on a particular timer definition. */
      /* 队列存在性检查,xTimerQueue 全局的定时器命令队列句柄 */
      if( xTimerQueue != NULL )
      {
          /* Send a command to the timer service task to start the xTimer timer. */
          xMessage.xMessageID = xCommandID;       //消息类型,此处为定时器命令 ID
          xMessage.u.xTimerParameters.xMessageValue = xOptionalValue; //命令附加数据(如新周期值)
          xMessage.u.xTimerParameters.pxTimer = xTimer;       //目标定时器指针
    
          /* 命令ID划分:FreeRTOS 内部定义了两套命令,一套用于任务调用(如 tmrCOMMAND_START),一套用于中断调用(如 tmrCOMMAND_START_FROM_ISR),
              * 两者数值不同。tmrFIRST_FROM_ISR_COMMAND 是中断类命令的起始值。所有小于该值的命令为任务版本,否则为中断版本 */
          if( xCommandID < tmrFIRST_FROM_ISR_COMMAND )        //任务版本
          {
              /* 若调度器正在运行(taskSCHEDULER_RUNNING),则使用带有阻塞时间的 xQueueSendToBack,允许调用者在队列满时等待。 */
              if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING )
              {
                  /* 大部分定时器 API 会传入 0(非阻塞),但在某些内部场景中可能允许短时等待 */
                  /* 将封装好的命令消息 xMessage 发送到定时器命令队列 xTimerQueue 的尾部(FIFO 入队)。
                   *
                   * 【队列特性】xTimerQueue 是一个普通队列,仅负责传递命令,不涉及时间排序。
                   * 它是调用者任务与定时器服务任务之间唯一的通信通道。
                   *
                   * 【定时器服务任务】定时器服务任务(Timer Service Task / Daemon Task)是
                   * FreeRTOS 在调度器启动时自动创建的一个后台任务,专门负责处理所有软件定时器。
                   * 它的核心职责:
                   *   - 阻塞读取 xTimerQueue,接收来自其他任务的定时器命令;
                   *   - 维护活动定时器列表 pxCurrentTimerList(按到期时间升序排列);
                   *   - 检查并处理到期定时器,执行用户注册的回调函数;
                   *   - 对周期定时器自动重新计算到期时间并插回列表。
                   * 所有对定时器列表的修改都集中在该任务中完成,天然线程安全,无需额外加锁。
                   *
                   * 【回调执行上下文】注意:用户注册的定时器回调函数是在定时器服务任务的上下文中
                   * 执行的,而非调用 xTimerStart 等 API 的任务。因此回调应简短、非阻塞,
                   * 不能调用会阻塞定时器服务任务的 API。
                   *
                   * 【发送行为】xQueueSendToBack() 将消息拷贝到队列尾部:
                   *   - 若队列有空位:立即入队,返回 pdPASS。
                   *   - 若队列已满:
                   *        xTicksToWait > 0 → 当前任务阻塞等待,直到有空位或超时。
                   *        xTicksToWait == 0 → 立即返回 pdFAIL,不阻塞。
                   *
                   * 【阻塞时间来源】此处 xTicksToWait 由上层定时器 API 宏(如 xTimerStart)传入:
                   *   通常为 0(非阻塞),避免调用任务因队列满而被延迟;
                   *   部分场景可能传入 portMAX_DELAY,确保命令一定送达。
                   *
                   * 【时间排序的时机】排序不在此处发生。
                   *   定时器服务任务从队列取出命令后,根据命令类型(如 tmrCOMMAND_START)
                   *   计算定时器的绝对到期时间(当前Tick + 周期),
                   *   并调用 prvInsertTimerInActiveList() 将定时器按到期时间升序插入到
                   *   活动定时器列表 pxCurrentTimerList 中。 */
                  xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait );
              }
              /* 若调度器尚未启动或已挂起,则使用非阻塞方式(tmrNO_DELAY),防止死锁或阻塞调度器 */
              else    
              {
                  xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY );
              }
          }
          else    //中断版本
          {
              /* 使用 xQueueSendToBackFromISR,这是中断安全的队列发送函数,无阻塞时间参数,但需要 pxHigherPriorityTaskWoken 指针用于返回是否需要上下文切换 */
              xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
          }
          /* 调试宏,记录本次命令发送的详细信息,需用户自定义实现 */
          traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );
      }
      else    //说明定时器服务尚未初始化,无法发送命令,函数直接返回 pdFAIL。
      {
          mtCOVERAGE_TEST_MARKER();   //代码覆盖率测试,实际为空
      }
    
      return xReturn; //最终返回发送结果

    }

2.2 使用示例

复制代码
BaseType_t res = 0;
/* 开启软件定时器 */
res = xTimerStart(timer1_handle, portMAX_DELAY);
if (res != pdFAIL)
{
    printf("timer1单次定时器启动成功\r\n");
}

res = xTimerStart(timer2_handle, portMAX_DELAY);
if (res != pdFAIL)
{
    printf("timer2周期定时器启动成功\r\n");
}

3.停止软件定时器

3.1 源码解析

功能 :停止指定的软件定时器。该宏是对 xTimerGenericCommand 的封装,向定时器服务任务发送一条 tmrCOMMAND_STOP 命令。定时器服务任务收到后,将定时器从活跃态切换到休眠态,定时器不再计时,回调也不再触发。

参数

  • xTimer:要停止的定时器句柄。

  • xTicksToWait:命令发送时的最大阻塞时间。若定时器命令队列满,调用任务将阻塞等待。通常传 0 表示非阻塞。

  • 内部固定展开为:

    • xCommandID = tmrCOMMAND_STOP,指定操作为"停止定时器"。

    • xOptionalValue = 0U。停止操作不需要附加的时间戳信息,因此传入 0。

    • pxHigherPriorityTaskWoken = NULL,表示任务级调用。

【停止软件定时器本质上调用的函数接口和启动软件定时器调用一致,具体该函数解析可查看本篇章2.启动软件定时器】

复制代码
#define xTimerStop( xTimer, xTicksToWait ) \
    xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )

3.2 使用示例

复制代码
BaseType_t res = 0;
/* 停止软件定时器 */
res = xTimerStop(timer1_handle, portMAX_DELAY);
if (res != pdFAIL)
{
    printf("timer1单次定时器停止成功\r\n");
}
res = xTimerStop(timer2_handle, portMAX_DELAY);
if (res != pdFAIL)
{
    printf("timer2周期定时器停止成功\r\n");
}

4.删除软件定时器

4.1 源码解析

功能 :删除指定定时器。向定时器服务任务发送 tmrCOMMAND_DELETE 命令,服务任务收到后从定时器列表中移除该定时器,并根据 ucStatus 中的静态分配标记决定是否释放内存。

参数

  • xTimer:要停止的定时器句柄。

  • xTicksToWait:命令发送时的最大阻塞时间。若定时器命令队列满,调用任务将阻塞等待。通常传 0 表示非阻塞。

  • 内部固定展开为:

    • xCommandID = tmrCOMMAND_DELETE,指定操作为"删除定时器"。

    • xOptionalValue = 0U。停止操作不需要附加的时间戳信息,因此传入 0。

    • pxHigherPriorityTaskWoken = NULL,表示任务级调用。

【停止软件定时器本质上调用的函数接口和启动软件定时器调用一致,具体该函数解析可查看本篇章2.启动软件定时器】

复制代码
#define xTimerDelete( xTimer, xTicksToWait ) \
    xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, ( xTicksToWait ) )

4.2 使用示例

复制代码
BaseType_t res = 0;
res = xTimerDelete( timer1_handle, portMAX_DELAY );
if( res != pdFAIL )
{
    printf("timer1 定时器删除成功\r\n");
    timer1_handle = NULL;   // 防止野指针
}

5.其它常用定时器操作

操作 说明
复位定时器 xTimerReset( xTimer, xTicksToWait ) 重新从当前时刻开始计时,相当于先 Stop 再 Start
修改周期 xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait ) 动态修改定时器周期,适用于需要变速计时的场景
两者底层均调用 xTimerGenericCommand,分别传入 tmrCOMMAND_RESETtmrCOMMAND_CHANGE_PERIOD

6.定时实现原理

定时器服务任务维护一个按到期时间排序的活动定时器列表:

  • 调用者调用 xTimerStart,内部通过 xTimerGenericCommandtmrCOMMAND_START 命令和定时器周期值 打包成消息,发送到定时器命令队列 xTimerQueue

  • 定时器服务任务从队列取出命令后,读取当前 Tick ,计算绝对到期时间 = 当前Tick + 周期 ,将定时器按到期时间升序插入活动列表 pxCurrentTimerList

  • 服务任务计算最近到期时间 - 当前Tick 作为阻塞时长,调用 xQueueReceive 在命令队列上阻塞等待。若阻塞时间到期,队列接收返回 errQUEUE_EMPTY,表示有定时器到期(或无新命令)。

  • 服务任务检查活动列表中所有到期(绝对到期时间 ≤ 当前 Tick)的定时器,执行其回调。

  • 对于自动重载定时器,重新计算下次到期时间并插回列表;对于单次定时器,处理后直接从活动列表移除,不再重新插入。

7.回调函数注意事项

  • 回调在定时器服务任务 的上下文中执行,不是调用 xTimerStart 的那个任务。

  • 回调应简短、非阻塞,不能调用会阻塞定时器服务任务的 API(如 vTaskDelay)。

  • 若需要在回调中处理复杂逻辑,建议在回调中向处理任务发送信号量或通知,让处理任务去执行。

8.声明

(1)Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.

(2)文中代码来自FreeRTOS,遵循MIT许可证,许可证可参考:https://opensource.org/licenses/MIT

复制代码
/*
 * FreeRTOS Kernel V10.5.1
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

【以上内容为个人在学习FreeRTOS过程中的源码解读笔记,欢迎大家在评论区讨论指正。】

【如果本篇内容对你有帮助,不妨点个关注,你的支持是我持续更新的动力!】

相关推荐
都在酒里4 小时前
STM32 ADC采样详解(标准库版):普通模式与DMA模式,附完整可用代码
stm32·单片机·嵌入式硬件
a83331964 小时前
C语言嵌入汇编详解
汇编·单片机·语言
三佛科技-134163842125 小时前
LED化妆镜方案开发, LED化妆镜MCU主控芯片如何选择?(FT60F011、FT60F021、FT61FC4F、FT62FC33、FT32F103)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
踏着七彩祥云的小丑5 小时前
嵌入式测试学习第 14 天:数字电路基础:高低电平、0和1、逻辑电平
单片机·嵌入式硬件
拾知_H5 小时前
STM32/PWM占空比配置
stm32·单片机·嵌入式·定时器·pwm
星华云5 小时前
[STM32] 硬件I2C主模式时序
stm32·单片机·嵌入式硬件
木子单片机5 小时前
基于51单片机汽车智能灯光控制系统
stm32·单片机·嵌入式硬件·汽车·51单片机·keil
fie88895 小时前
无刷直流电机(BLDC)控制程序 - STM32实现方案
stm32·单片机·嵌入式硬件
LCG元5 小时前
STM32实战:基于STM32F103的智能鹌鹑孵化箱(温湿度+翻蛋控制)
stm32·单片机·嵌入式硬件