FreeRTOS软件定时器使用以及例程

FreeRTOS软件定时器概述

FreeRTOS的软件定时器(Software Timer)提供了一种基于系统节拍(Tick)的定时功能,允许创建周期性或单次触发的定时任务。软件定时器由内核调度,通过回调函数实现定时逻辑,适用于低精度定时场景(如秒级或毫秒级)。

软件定时器是基于 FreeRTOS 内核提供的时间管理功能实现的,允许开发者创建、启动、停止、删除和管理定时器,从而实现在任务中对时间的灵活控制。

软件定时器与硬件定时器的主要区别如下:

软件定时器

硬件定时器

FreeRTOS提供的功能来模拟定时器,依赖系统的任务调度器来进行计时和任务调度

由芯片或微控制器提供,独立于 CPU,可以在后台运行,不受任务调度器的影响

精度和分辨率可能受到任务调度的影响

具有更高的精度和分辨率

不需要额外的硬件资源,但可能会增加系统的负载

占用硬件资源,不会增加 CPU 的负载

软件定时器能够让函数在未来的设定时间执行。由定时器执行的函数称为定时器的回调函数。从定时器启动到其回调函数执行之间的时间被称为定时器的周期。简而言之,当定时器的周期到期时,定时器的回调函数会被执行

软件定时器特性:

  • 单次模式(One-shot):定时器触发一次后自动停止。
  • 自动重载模式(Auto-reload):定时器周期性触发直到手动停止。
  • 回调函数:定时器触发时执行用户定义的函数。
  • 动态/静态创建:支持从堆内存或用户指定内存创建定时器。

软件定时器的状态:

FreeRTOS 中的软件定时器有三种状态,分别是:

  • 未创建(Uncreated):软件定时器被创建之前的状态。在这个状态下,定时器的数据结构已经被定义,但尚未通过 xTimerCreate() 函数创建。
  • 已创建(Created):软件定时器已被成功创建,但尚未启动。在这个状态下,可以对定时器进行配置,如设置定时器的周期、回调函数等,但定时器并未开始计时
  • 已运行(Running):软件定时器已经被启动,正在运行中。在这个状态下,定时器会按照预定的周期定时触发超时事件,执行注册的回调函数

单次定时器和周期定时器

在 FreeRTOS 中,软件定时器主要有两种类型:一次性定时器和周期性定时器。

  • 一次性定时器(One-shot Timer): 这种定时器在触发一次超时后就会停止,不再执行。适用于只需在特定时间执行一次任务或动作的场景。
  • 周期性定时器(Periodic Timer): 这种定时器会在每个超时周期都触发一次,循环执行。适用于需要在固定的时间间隔内重复执行任务或动作的场景。

软件定时器实现步骤

配置FreeRTOS启用软件定时器

FreeRTOSConfig.h中启用相关宏:

#define configUSE_TIMERS             1    // 启用软件定时器
#define configTIMER_TASK_PRIORITY    (configMAX_PRIORITIES - 1)  // 定时器任务优先级
#define configTIMER_QUEUE_LENGTH     10   // 定时器命令队列长度

软件定时器相关API:

xTimerCreate()

动态方式创建软件定时器

xTimerCreateStatic()

静态方式创建软件定时器

xTimerStart()

开启软件定时器定时

xTimerStartFromISR()

在中断中开启软件定时器定时

xTimerStop()

停止软件定时器定时

xTimerStopFromISR()

在中断中停止软件定时器定时

xTimerReset()

复位软件定时器定时

xTimerResetFromISR()

在中断中复位软件定时器定时

xTimerChangePeriod()

更改软件定时器的定时超时时间

xTimerChangePeriodFromISR()

在中断中更改定时超时时间

使用xTimerCreate()函数动态创建定时器:

TimerHandle_t xTimerCreate(
    const char * const pcTimerName,             // 定时器名称(调试用)
    const TickType_t xTimerPeriod,              // 周期(Tick数)
    const UBaseType_t uxAutoReload,             // 自动重载模式(TRUE为周期性,FALSE为单次)
    void * const pvTimerID,                     // 定时器ID(标识用)
    TimerCallbackFunction_t pxCallbackFunction  // 回调函数
);

 启动/停止定时器

  • 启动定时器:xTimerStart(xTimer, xTicksToWait)
    • xTicksToWait:命令发送到定时器任务队列的最大等待时间(若队列满)。
  • 停止定时器:xTimerStop(xTimer, xTicksToWait)

修改定时器周期

使用xTimerChangePeriod()动态调整周期:

xTimerChangePeriod(
    xTimer,
    pdMS_TO_TICKS(2000),  // 新周期2000ms
    0
);

示例一:创建,开启,关闭周期和单次定时器

实验一:

  • start_task:用来创建task1任务,
  • task1:用于按键扫描,根据按键检测的键值创建/关闭一次性定时器/周期性定时器。
#define configUSE_TIMERS				1
#define configTIMER_TASK_PRIORITY		( 2 )

TimerHandle_t timer1_handle = 0; /* 单次定时器 */
TimerHandle_t timer2_handle = 0; /* 周期定时器 */

/*******************启动任务重创建两个定时器************************/
 timer1_handle = xTimerCreate("timer1",
                                 500,
                                 pdFALSE,
                                 (void *)1,
                                 timer1_callback);
 timer2_handle = xTimerCreate("timer2",
                                 2000,
                                 pdTRUE,
                                 (void *)2,
                                 timer2_callback);

/*****************************task1*******************************/
void task1(void *p)
{
    my_printf("task1 running............\r\n");
    HAL_Delay(3);
    uint8_t key;
    while (1)
    {
        Key_scan();
        key = Key_GetValue();
        if(key) My_OLED_ShowNum(2, 1, key, 2);
        // HAL_Delay(1);
        if (key == 1)
        {
            xTimerStart(timer1_handle, portMAX_DELAY);          //如果链表满了,就一直阻塞
        }
        else if(key == 3)
        {
            xTimerStart(timer2_handle, portMAX_DELAY);
        }
        else if(key == 2)
        {
            xTimerStop(timer1_handle, portMAX_DELAY);
        }
        else if( key == 4)
        {
            xTimerStop(timer2_handle, portMAX_DELAY);
        }
        vTaskDelay(1);
    }
}


void timer1_callback(TimerHandle_t pxTimer)
{
    static uint32_t timer = 0;
    printf("timer1的运行次数=%d\r\n", ++timer);
    HAL_GPIO_TogglePin(LED_RED_GPIO_Port,LED_RED_Pin);
    HAL_Delay(2);
}

void timer2_callback(TimerHandle_t pxTimer)
{
    static uint32_t timer = 0;
    printf("timer2的运行次数=%d\r\n", ++timer);
    HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin);
    HAL_Delay(2);
}




实验现象:

示例二:中断中开启/关闭周期定时器
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    uint8_t key_num = 0;
    BaseType_t  pxHigherPriorityTaskWoken;
    if (GPIO_Pin == KEY_2_Pin)                                  // 按键2 产生的中断信号:KEY_2、PA1、闲时上拉、按下时低电平、下降沿触发
    {
//        HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);     // 反转红色LED灯
        key_num =2;
        printf("KEY_2 按下\r\n");                                 // 通过串口助手输出提示
        My_OLED_ShowNum(2, 1, key_num, 2);
        xTimerStartFromISR(timer2_handle, &pxHigherPriorityTaskWoken);
    }

    if (GPIO_Pin == KEY_3_Pin)                                  // 按键3 产生的中断信号:KEY_3、PA4、闲时上拉、按下低高电平、下降沿触发
    {
        // HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);     // 反转红色LED灯
        key_num =3;
        printf("KEY_3 按下\r");                                 // 通过串口助手输出提示
        My_OLED_ShowNum(2, 1, key_num, 2);
        xTimerStopFromISR(timer2_handle, &pxHigherPriorityTaskWoken);
    }
}


注意事项

  1. 定时器任务优先级:软件定时器的回调函数在独立的定时器任务中执行,优先级需合理设置(通常较高)。
  2. 回调函数限制:避免在回调函数中使用阻塞操作(如vTaskDelay)。
  3. 资源竞争:若回调函数访问共享资源,需添加互斥机制(如信号量)。
  4. 静态创建:使用xTimerCreateStatic()时需预先分配内存。

通过合理配置和API调用,FreeRTOS软件定时器可以高效管理定时任务,适用于事件触发、状态轮询等场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值