软件定时器

        软件定时器简介

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

  • 硬件定时器:由芯片或微控制器提供,独立于CPU,可以在后台运行,不受任务调度器的影响,具有更高的精度和分辨率,具有更高的精度和分辨率,占用硬件资源,不会增加CPU的负载
  • 软件定时器:FreeRTOS提供的功能来模拟定时器,依赖系统的任务调度器来进行计时和任务调度,FreeRTOS内部基于系统节拍tick实现的一种定时事件机制,精度和分辨率可能受到任务调度的影响,不需要额外的硬件资源,但可能会增加系统的负载A(软件定时器不是独立硬件,而是依赖于系统节拍中断,FreeRTOS调度器,一个内部的定时器任务和一个内部的命令队列)

当定时器的周期到期时,定时器的回调函数会被执行,定时器回调函数在定时器服务任务的上下文中执行,在定时器回调函数中不能调用导致阻塞的API函数

软件定时器服务任务是任务调度器中一个特殊任务,专门用于管理和维护软件定时器的正常运行,如果configUSE_TIMERS 设置为1,在开启任务调度器的时候,会自动创建软件定时器服务的任务,它主要负责软件定时器超时逻辑判断、调用超时软件定时器的超时回调函数、处理软件定时器命令队列

        软件定时器的状态

FreeRTOS中的软件定时器有两种类型:一次性定时器和周期性定时器

FreeRTOS中的软件定时器有三种状态:

  • 未创建:软件定时器被创建之前的状态,在这个状态下,定时器的数据结构已经被定义,但尚未通过xTimerCreate()函数创建
  • 已创建:软件定时器已被成功创建,但尚未启动,在这个状态下,可对定时器
  • 已运行:软件定时器已经被启动,正在运行中,在这个状态下定时器会按照预定的周期定时触超时事件,执行注册的回调函数

        软件定时器相关API

函数 描述
xTimerCreate() 动态方式创建软件定时器
xTimerCreateStatic() 静态方式创建软件定时器
xTimerStart() 开启软件定时器定时
xTimerStartFromISR() 在中断中开启软件定时器定时
xTimerStop() 停止软件定时器定时
xTimerStopFromISR() 在中断中停止软件定时器定时
xTimerReset() 复位软件定时器定时
xTimerResetFromISR() 在中断中复位软件定时器定时
xTimerChangPeriod() 更改软件定时器的定时超时时间
xTimerChangPeriodFromISR() 在中断中更改定时超时时间

        软件定时器历程

首先是要在配置文件进行以下的显式定义

/* 软件定时器相关定义 */
#define configUSE_TIMERS 1                                          /* 1: 使能软件定时器, 默认: 0。使能后需指定下面3个 */
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)        /* 定义软件定时器任务的优先级 */
#define configTIMER_QUEUE_LENGTH 5                                  /* 定义软件定时器命令队列的长度*/
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小*/
  • configUSE_TIMERS:表示是否启用软件定时器
  • configTIMER_TASK_PRIORITY:定时器服务任务优先级,决定回调执行的及时性,一般不要太低否则高优先级任务一直跑时,定时器回调可能延后
  • configTIMER_QUEUE_LENGTH:定时器命令队列长度,调用xTimerStart()、xTimerStop()、xTimerReset()等本质上都是往这里发命令,如果太小命令可能发送失败
  • configTIMER_TASK_STACK_DEPTH:定时器服务任务栈大小,回调里做的事情太多栈太小可能会溢出

软件定时器的整个工作流程通常是先调用xTimerCreate()创建一个软件定时器,然后调用xTimerStart(),这个API本质不是立刻启动硬件,而是向定时器命令队列发送一个启动命令,定时器服务任务取出命令,开始管理这个定时器,到了设定tick数后,定时器服务任务执行自定义的回调函数,如果是自动重装载定时器则会继续下一轮,如果是单次定时器,执行一次就停止

  • 注意:软件定时器的回调函数不是在中断中执行的,是在FreeRTOS的定时器服务任务上下文中执行,所以在回调中适合设置标志位、发消息到队列、发信号量、通知任务、做很短小的状态切换不适合长事件堵塞、vTaskDelay()、大量的printf、执行复杂计算、访问可能长时间等待的资源,因为一个回调执行太久会拖慢其他软件定时器,所有软件定时器本质上都是靠同一个定时器服务任务调度,所以最好的是在回调函数中唤醒另一个任务在任务中做复杂的任务
#include "FreeRTOS_demo.h"
#include "FreeRTOS.h"//这两个头文件是必须要引入的
#include "task.h"
#include "LED.h"
#include "Key.h"
#include "stdio.h"
#include "timers.h"
//启动任务的配置
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void * pvParameters);//启动任务

//任务1的配置
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void * pvParameters);//启动任务

//定时器1的回调函数(单次)
void timer1_callback(TimerHandle_t xTimer);
//软件定时器的句柄
TimerHandle_t timer1_handle;
//定时器2的回调函数(周期)
void timer2_callback(TimerHandle_t xTimer);
//软件定时器的句柄
TimerHandle_t timer2_handle;
void freertos_start(void)//启动FreeRTOS
{
    /*创建一次性软件定时器*/
    timer1_handle = xTimerCreate(
        (char*)"timer1",//软件定时器的名称
        (TickType_t)500,//超时的时间等于500个FreeRTOS时钟节拍
        (BaseType_t)pdFALSE,//是否自动重载,pdFALSE一次性
        (void*)1,//定时器的唯一ID
        (TimerCallbackFunction_t)timer1_callback//定义好的回调函数
    );
    if(timer1_handle != NULL)
    {
        printf("timer1创建成功");
    }
    /*创建周期性软件定时器*/
    timer2_handle = xTimerCreate(
        (char*)"timer2",//软件定时器的名称
        (TickType_t)500,//超时的时间等于500个FreeRTOS时钟节拍
        (BaseType_t)pdTRUE,//是否自动重载,pdFALSE一次性
        (void*)2,//定时器的唯一ID
        (TimerCallbackFunction_t)timer2_callback//定义好的回调函数
    );
    if(timer2_handle != NULL)
    {
        printf("timer2创建成功");
    }
    //1.创建一个启动任务
    xTaskCreate((TaskFunction_t) start_task,                //任务函数地址,前面是类型强转
                (char*) "start_task",                       //取任务名
                (configSTACK_DEPTH_TYPE) START_TASK_STACK,  //任务栈大小,最小是128字节
                (void *) NULL,                              //不需要形参就给一个NULL
                (UBaseType_t) START_TASK_PRIORITY,          //任务优先级,这里的优先级默认为5,有5个优先级
                (TaskHandle_t *) &start_task_handle);       //任务句柄
                //任务的相关信息会保存到一个tcb地址,然后这个地址会赋值给这个任务句柄,所以这只用创建一个任务句柄即可
    //2.启动调度器
    vTaskStartScheduler();//这是必须要启动的,会自动创建空闲函数
}
void start_task(void * pvParameters)//启动任务,用来创建其他task
{
    //进入临界区,临界区里面的代码不会被打断
    taskENTER_CRITICAL();
    
    //创建3个任务
    xTaskCreate((TaskFunction_t) task1,                
                (char*) "task1",                       
                (configSTACK_DEPTH_TYPE) TASK1_STACK,  
                (void *) NULL,                            
                (UBaseType_t) TASK1_PRIORITY,          
                (TaskHandle_t *) &task1_handle);
    //退出临界区
    taskEXIT_CRITICAL();
    //启动任务只需要执行一次,用完就删除自己
    vTaskDelete(NULL);
}

void task1(void * pvParameters)//任务1:用于按键扫描,并对软件定时器进行开启、停止操作
{
   uint8_t key = 0;
   BaseType_t res = 0;
   while(1)
   {
        key = Key_Detect();
        if(key==KEY1_PRESS)
        {
            /*开启软件定时器*/
            //往任务队列发送启动任务命令,然后定时器任务识别到这个命令才会启动,所以有一个等待时间
            res = xTimerstart(timer1_handle,portMAX_DELAY);//timer句柄,等待时间
            if(res != pdFAIL)
            {
                printf("timer1启动成功");//时间到了就执行回调函数
            }
            res = xTimerstart(timer2_handle,portMAX_DELAY);//timer句柄,等待时间
            if(res != pdFAIL)
            {
                printf("timer2启动成功");
            }
        }
        else if(key==KEY2_PRESS)
        {
            /*停止软件定时器*/
            res = xTimerstop(timer1_handle,portMAX_DELAY);//timer句柄,等待时间
            if(res != pdFAIL)
            {
                printf("timer1停止成功");
            }
            res = xTimerstop(timer2_handle,portMAX_DELAY);//timer句柄,等待时间
            if(res != pdFAIL)
            {
                printf("timer2停止成功");
            }
        }
        vTaskDelay(500);
   }
}

void timer1_callback(TimerHandle_t xTimer)
{
    static uint16_t timer1_count = 0;
    printf("timer1超时回调了%d次",++timer1_count);
}
void timer2_callback(TimerHandle_t xTimer)
{
    static uint16_t timer2_count = 0;
    printf("timer1超时回调了%d次",++timer2_count);
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐