软件定时器(Software Timer)

软件定时器(Software Timer)

官方文档 Software Timer Management [[FreeRTOS_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf#page=174&selection=7,0,7,25|FreeRTOS_M

官方文档

Software Timer Management

[[FreeRTOS_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf#page=174&selection=7,0,7,25|FreeRTOS_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide, page 174]]


官方函数文档

5.3 xTimerCreate()

[[FreeRTOS_Reference_Manual_V10.0.0.pdf#page=259&selection=2,0,4,14|FreeRTOS_Reference_Manual_V10.0.0, page 259]]


什么是定时器:

  • 定时器就像一个闹钟,在一定时间后会执行相应的程序。

软件定时器和硬件定时器的区别:

  • 硬件定时器:
    不同的芯片都会提供相应的硬件定时器,而硬件定时器的数量是有限的。
  • 软件定时器:
    FreeRTOS 的软件定时器基于Deamon (Time Service) Task,通过定时器命令队列,对定时器任务发送命令,然后定时器任务调用相应的任务程序,执行软件定时器回调函数。

所以对于软件定时器来说:

  1. 它与硬件无关,与平台无关,在不同平台的 FreeRTOS 的操作系统当中,都是通过同样的函数来调用软件定时器;
  2. 硬件定时器会有相应的数量限制,但是软件定时器会根据( configTIMER_TASK_STACK_DEPTH)和软件的设置,可以有很多软件定时器。

xTimerCreate()

#include "FreeRTOS.h"
#include "timers.h"

TimerHandle_t xTimerCreate( const char *pcTimerName,
						    const TickType_t xTimerPeriod,
						    const UBaseType_t uxAutoReload,
						    void *const pvTimerID,
						    TimerCallbackFunction_t pxCallbackFunction );

函数说明:

  • 创建一个软件定时器,并返回一个句柄,用该句柄可以引用锁创建的软件定时器。
  • 每个软件定时器都需要占用一点 RAM ,用来保持软件定时器的状态。
    1. 如果通过此函数创建了一个软件定时器,那么定时器所需内存将被自动分配在 FreeRTOS 的堆中。
    2. 如果通过xTimerCreateStatic()创建了软件定时器,那么软件定时器所用内存则有程序员所确定。
  • 创建的软件定时器并不会自动运行,这些函数都可以用来启动软件定时器:
    1. xTimerStart()
    2. xTimerStartFromISR()
    3. xTimerResetFromISR()
    4. xTimerChangePeriod()
    5. xTimerChangePeriodFromISR()

参数:

  • pcTimerName:分配给定时器的纯文本名称,纯粹是为了调试。
  • xTimerPeriod:定时器周期。
    • 定时器以滴答周期的倍数指定。pdMS_TO_TICKS()宏可以把以毫秒为单位的时间转换为以刻度为单位的时间。
      例如:
      1. 如果一个时间定时器必须在 100 个刻度到期,xNewPriod可以直接设置为 100。
      2. 如果一个定时器必须在 500ms 后到期,xNewPeriod可以设置为pdMS_TO_TICKS(500),假设configTICK_RATE_HZ小于等于 1000。
  • uxAutoReload:
    • 设置为pdTRUE来定义一个自动重启的定时器。
      • 定时器一旦启动,自动重启的定时器将根据xTimerPeriod反复到期。
    • 设置为pdFALSE来定义一个不自动重启的定时器。
      • 定时器一旦启动,只会过期一次,只能通过手动的方式重启定时器。
  • pvTimerID:分配给定时器的标识符。
    • 标识符可以在定时器创建后用vTimerSetTimer()函数更新。
    • 如果同一个函数被多个定时器回调,定时器标识符可以在回调函数内部调查哪个定时器已到期。此外,定时器标识符可以用在和调用定时器的回调函数之间存储一个值。
  • pxCallbackFunction:定时器过期后调用的回调函数。回调函数必须有被定义的原型。回调函数的返回值必须是void,它的输入参数必须是一个TimerHandle

void vCallbackFunctionExample( TimerHandle_t xTimer );

返回值:

  • NULL:软件定时器因为堆中没有足够的内存而无法被创建。
  • Any other value: 软件定时器成功创建,并且返回一个可以引用此定时器的句柄。

xTimerStart()

#include "FreeRTOS.h"
#include "timers.h"

BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

函数说明:

  • 启动软件定时器。xTimerStartFromISR()是一个等效函数,它可以在 中断 中启动定时器。
  • 如果定时器还没有运行,定时器会计算相对于调用xTimerStart()时的到期时间。
  • 如果定时器未停止、已删除、未重置,则计时器关联的回调函数将在调用xTimerStart()后的'n'个刻度后被调用,其中'n'是计时器的定义周期。

参数:

  • xTimer:计时器将被重置(reset)、启动(started)或者重启(restarted)。
  • xTicksToWait:计时器函数并非由 FreeRTOS 的核心函数提供,而是由定时器服务( or deamon )提供。FreeRTOS API 发送命令到定时器任务的队列,如果队列已满,xTicksToWait指定任务处于阻塞状态的最长时间,以等待计时器命令上的队列空间可用,设置为portMAX_DELAY为等待无限多的时间。

返回值:

  • pdPASS:成功启动定时器。
  • pdFAIL:因为定时器任务的消息队列已满,消息未能在超时时间之内发送到时间定时器的队列。

示例代码

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/queue.h"
#include "freertos/timers.h"

void vCallbackFunction(TimerHandle_t timer1)
{
    printf("[TIMER_1 CALLBACK]: One shot timer.\n");
}

void app_main(void)
{
    // 创建一个定时器,定时器名字"Timer 1",周期1s,周期结束之后重新启动,定时器标识符为0,定时器回调函数为 vCallbackFunction
    TimerHandle_t xTimer1 = xTimerCreate("Timer 1", pdMS_TO_TICKS(1000), pdTRUE, 0, vCallbackFunction);
    // 启动定时器,等待时间为无限
    xTimerStart(xTimer1, portMAX_DELAY);
} 

示例输出

14. 软件定时器(Software Timer)-20240615171747141.webp

根据输出可以观察到定时器(xTimer1)的回调函数在不断被调用。

xTimerStop()

#include "FreeRTOS.h"
#include "timers.h"

BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

函数说明:

  • 停止定时器的运行。xTimerStopISR()的一个等效函数,可以在中断中使用。

参数:

  • xTimer:定时器句柄。
  • xTicksToWait:超时等待时长。

返回值:

  • pdPASS:成功停止定时器。
  • pdFAIL:未能成功停止定时器,因为定时器守护任务的消息队列已满。

示例代码

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/queue.h"
#include "freertos/timers.h"

void vCallbackFunction(TimerHandle_t timer1)
{
    printf("[TIMER_1 CALLBACK]: In timer.\n");
}

void app_main(void)
{
    // 创建一个定时器,定时器名字"Timer 1",周期1s,周期结束之后重新启动,定时器标识符为0,定时器回调函数为 vCallbackFunction
    TimerHandle_t xTimer1 = xTimerCreate("Timer 1", pdMS_TO_TICKS(1000), pdTRUE, 0, vCallbackFunction);

    xTimerStart(xTimer1, portMAX_DELAY);

    for (int delay = 5; delay > 0; delay--) {
        printf("[MAIN]: The timer will stoped in %d second(s).\n", delay);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }

    xTimerStop(xTimer1, portMAX_DELAY);
} 

示例输出

14. 软件定时器(Software Timer)-20240615173910221.webp

定时器在设定for循环结束之后停止。


官方文档函数参考

271 5.1 pcTimerGetName()

[[FreeRTOS_Reference_Manual_V10.0.0.pdf#page=271&selection=0,3,4,16|FreeRTOS_Reference_Manual_V10.0.0, page 271]]


pcTimerGetName()

#include "FreeRTOS.h"
#include "timer.h"

const char *pcTimerGetName( TimerHandle_t xTimer );

函数说明:

  • 返回人类可读的定时器名字。

参数:

  • xTimer:将要取得定时器名字的句柄。

返回值:

  • 返回一个指针,包含定时器的名字。

示例代码:

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/queue.h"
#include "freertos/timers.h"

void vCallbackFunction(TimerHandle_t timer1)
{
	const char *timer_name = pcTimerGetName(timer1);
    printf("[TIMER_1 CALLBACK]: In timer \"%s\".\n", timer_name);
}

void app_main(void)
{
    // 创建一个定时器,定时器名字"Timer 1",周期1s,周期结束之后重新启动,定时器标识符为0,定时器回调函数为 vCallbackFunction
    TimerHandle_t xTimer1 = xTimerCreate("Timer 1", pdMS_TO_TICKS(1000), pdTRUE, 0, vCallbackFunction);

    xTimerStart(xTimer1, portMAX_DELAY);

    for (int delay = 5; delay > 0; delay--) {
        printf("[MAIN]: The timer will stoped in %d second(s).\n", delay);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }

	// 
    const char *timer_name = pcTimerGetName(xTimer1);
    printf("[MAIN]: The timer \"%s\" will stop.\n", timer_name);

    xTimerStop(xTimer1, portMAX_DELAY);
} 

在实际编写程序时,可以根据定时器名字的不同,来让程序执行不同的代码。

pvGetTimerGetTimerID()

#include "FreeRTOS.h"
#include "timer.h"

void *pvTimerGetTimerID( TimerHandle_t xTimer );

函数说明:

  • 获取定时器的ID。

参数:

  • 定时器的句柄。

返回值:

  • (void *):时间定时器的ID。

此处可以用 uint32_t强制转换函数返回值,并用uint32_t来接收返回值。

两个时间定时器使用同一个回调函数,获取NameID

示例代码

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/queue.h"
#include "freertos/timers.h"

void callback_function(TimerHandle_t timer)
{
	// 获取定时器名字
    const char *timer_name = pcTimerGetName(timer);
	// 获取定时器ID
    uint32_t timer_id = (uint32_t)pvTimerGetTimerID(timer);
    printf("[CALLBACK FUNCTION]: In timer \"%s\", ID: %ld\n", timer_name, timer_id);
}

void app_main(void)
{
    TimerHandle_t timer1 = xTimerCreate("Timer 1", pdMS_TO_TICKS(1000), pdTRUE, (void *)0, callback_function);
    TimerHandle_t timer2 = xTimerCreate("Timer 2", pdMS_TO_TICKS(1000), pdTRUE, (void *)1, callback_function);
    const char *timer_name = NULL;

    xTimerStart(timer1, portMAX_DELAY);
    timer_name = pcTimerGetName(timer1);
    for (int delay = 5; delay > 0; delay--) {
        printf("[MAIN]: Timer \"%s\" will stop in %d second(s).\n", timer_name, delay);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    xTimerStop(timer1, portMAX_DELAY);

    xTimerStart(timer2, portMAX_DELAY);
    timer_name = pcTimerGetName(timer2);
    for (int delay = 5; delay > 0; delay--) {
        printf("[MAIN]: Timer \"%s\" will stop in %d second(s).\n", timer_name, delay);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    xTimerStop(timer2, portMAX_DELAY);
}

示例输出

![[14. 软件定时器(Software Timer)-20240615182443875.webp|1000]]
Timer 1 和 Timer 2 分别启动运行停止,因为时间定时器调用了相同的回调函数,所以回调函数打印出了不同的时间定时器名称和ID。

xTimerReset()

#include "FreeRTOS.h"
#include "timer.h"

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

函数说明:

  • 相当于看门狗的“喂狗”,表示让定时器重新启动。在中断过程中,存在等效函数xTimerResetISR()可以用。

参数:

  • xTimer:时间定时器句柄。
  • xTicksToWait:超时等待时间。

返回值:

  • pdPASS:重启时间定时器成功。
  • pdFAIL:重启时间定时器失败。

xTimerChangePeriod()

#include "FreeRTOS.h"
#include "timer.h"

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xTicksToWait );

函数说明:

  • 改变定时器的周期,在中断下,存在等效函数xTimerChangePeriodISR()可以用。

参数:

  • xTimer:需要改变定时器周期的定时器句柄。
  • xNewPeriod:新的周期。
  • xTicksToWait:超时等待时间。

返回值:

  • pdPASS:定时器周期成功修改。
  • pdFAIL:定时器周期修改失败。

示例代码:

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/queue.h"
#include "freertos/timers.h"

void callback_function(TimerHandle_t timer)
{
    const char *timer_name = pcTimerGetName(timer);
    uint32_t *timer_id = (uint32_t *)pvTimerGetTimerID(timer);
    printf("[CALLBACK FUNCTION]: In timer \"%s\", ID: %ld\n", timer_name, *timer_id);
}

void app_main(void)
{
    static uint32_t timer1_id = 1;
    TimerHandle_t timer1 = xTimerCreate("Timer 1", pdMS_TO_TICKS(1000), pdTRUE, (void *)&timer1_id, callback_function);

    xTimerStart(timer1, portMAX_DELAY);

    vTaskDelay(pdMS_TO_TICKS(5000)); // 让 timer1 运行 5s

    xTimerChangePeriod(timer1, pdMS_TO_TICKS(500), portMAX_DELAY); // 改变定时器周期

    vTaskDelay(pdMS_TO_TICKS(5000)); // 再让 timer1 运行 5s

    xTimerStop(timer1, portMAX_DELAY);
}
Comment