FreeRTOS 软件定时器

裸机的定时器属于的是我们芯片上的一个外设,那这个外设就属于硬件。所以,在裸机所介绍的定时器是属于硬件定时器。而本章节主要介绍的是软件定时器,那我们一会也会来介绍一下它们两个之间有什么区别。

1. 软件定时器的简介

1.1 软件定时器

  • 定时器:
    从指定的时刻开始,经过一个指定时间(超时时间),然后触发一个超时事件,用户可自定义定时器的周期。

类似于生活中的闹钟。

  • 硬件定时器:
    芯片本身自带的定时器模块(外设),硬件定时器的精度一般很高(相对于软件定时器),每次在定时时间到达之后就会自动触发一个中断(溢出中断),用户在中断服务函数中处理信息。

硬件定时器不单单只有溢出中断这么一个功能,还有很多非常强大的一个功能,比如输出 PWM,电机控制就离不开 PWM 控制,还有输入捕获、输出比较、互补输出等等一大堆,所以说硬件定时器非常的强大,这就是它的一个优点;那缺点是什么,缺点就是说它是硬件的,所以它肯定个数是有限的,一个芯片不可能说给你那么多定时器,你定时器多,成本也高,所以它个数是有限的。

  • 软件定时器:
    是指具有定时功能的软件,可设置定时周期,当指定时间到达后要调用回调函数(也称超时函数),用户在回调函数中处理信息

只不过软件定时器的话它就没有像什么输出 PWM、输入捕获这些功能了。那它优点又是什么,优点就是说软件的嘛,我不花钱了,成本很低,然后个数呢只要你内存够多,它可以说是无限个,所以说这是软件的一个好处了。

1.1.1 软件定时器优缺点?

  • 优点:
  1. 硬件定时器数量有限,而软件定时器理论上只要有足够内存,就可以创建多个;
  2. 使用简单、成本低
  • 缺点:
  1. 软件定时器相对硬件定时器来说,精度没有那么高。 对于需要高精度要求的场合,不建议使用软件定时器。

因为它以系统时钟为基准,系统时钟中断优先级又是最低,每个中断都可以打断它,那它这个定时能准么?它这软件定时肯定没有硬件定时那么准,因为很多中断都可以打断它,它容易被打断,所以它定时肯定就没有那么精确。

1.1.2 FreeRTOS软件定时器特点

  • 可裁剪:软件定时器是可裁剪可配置的功能, 如果要使能软件定时器,需将 configUSE_TIMERS 配置项配置成 1。

软件定时器只是一个功能而已,可以选择使用它也可以选择不使用它。

  • 单次和周期:软件定时器支持设置成:单次定时器或周期定时器。

单次定时器:只允许一次,软件定时器超时了调用一次一次超时回调函数,那么它就不再工作了。
周期定时器:它可以反复循环的工作。它计时,超时之后调用一次回调函数,接着又重新开始计时,然后又超时之后又会调用一次超时回调函数,就这样反复循环。

注意:软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的 API 函数。(比如系统延时)

软件定时器服务任务:在调用函数 vTaskStartScheduler() 开启任务调度器的时候,它会创建两个任务,一个是空闲任务,还会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。

而这个软件定时器任务就是用来调用处理软件定时器的。大家注意这是任务,超时回调函数是软件定时器,它两个是有区别的。我们创建完定时器之后,它不会工作,它怎么工作?就是通过这个软件定时器服务任务去逻辑判断啊,去判断定时器有没有超时啊这些,这个大家一定要理清楚。

软件定时器服务任务作用:

  1. 负责软件定时器超时的逻辑判断

也就是说比如我创建了三个定时器,那我怎么知道哪个超时了,就通过这个逻辑判断。

  1. 调用超时软件定时器的超时回调函数

同样的有三个定时器,那么比如定时器 1 超时了,那么就通过这个任务去调用定时器 1 的超时回调函数;如果是定时器 2 超时了,那么就通过这个任务去调用定时器 2 的超时回调函数。所以大家要理清楚,这个软件定时器服务任务它并不是定时器,它只是一个任务,只是这个任务处理很多事情。

  1. 处理软件定时器命令队列

大家可以联想一下,我们创建了 1、2、3 三个定时器,那么这些定时器我们一开始创建的时候,它都是休眠的,休眠就是说这三个其实都是不工作的,那怎么工作呢?就通过命令队列去控制它。

1.2 软件定时器的命令队列

FreeRTOS 提供了许多软件定时器相关的 API 函数,这些 API 函数大多都是往定时器的队列中写入消息(发送命令),这个队列叫做软件定时器命令队列,是提供给 FreeRTOS 中的软件定时器(相关的 API 函数)使用的,用户是不能直接访问的。

它专门来接收命令的,比如我要命令这个定时器开启,命令它关闭,命令它复位,这些都是属于命令,它实质其实就是一个数据,然后在这里其实就把队列来传输这个数据,所以这个就叫命令队列。其实这个队列就是来传输数据的,这不过这个数据它是来控制软件定时器的,所以我们称它为命令队列。

在这里插入图片描述

软件定时器服务任务就是来做一些逻辑性的操作。

1.3 软件定时器的相关配置

  • 当FreeRTOS 的配置项 configUSE_TIMERS 设置为1,在启动任务调度器时,会自动创建软件定时器的服务/守护任务 prvTimerTask( ) ;
  • 软件定时器服务任务的优先级为 configTIMER_TASK_PRIORITY = 31(默认优先级最大,可配置);

这样的话只要一有软件定时器超时,软件定时器服务任务就可以立马去调用它的超时回调函数了,就保证了软件定时器的实时性。所以我们配置为最大优先级。

  • 定时器的命令队列长度为 configTIMER_QUEUE_LENGTH = 5 ;(可配置)

注意:软件定时器的超时回调函数是在软件定时器服务任务中被调用的,服务任务不是专为某个定时器服务的,而是为全部软件定时器服务的,它还要处理其他定时器。(也就是说比如我创建了 n 多个软件定时器,那这个软件定时器服务任务它不是只处理其中一个软件定时器,而是 n 多个软件定时器都由这一个服务任务去处理。

所以,定时器的回调函数不要影响其他“人”(软件定时器):

  1. 回调函数要尽快实行,就执行速度一定要快,不能进入阻塞状态,即不能调用那些会阻塞任务的 API 函数。

如:调用延时函数 vTaskDelay(10) 延时个 10s,那这个服务任务就阻塞了,这时候其他软件定时器比如超时的,得不到及时的响应了,那这样肯定是不行的。

  1. 访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。

如果你要调用,那么这个阻塞时间要设置为 0,那这时候你才能调用这些 API 函数。

1.4 软件定时器的状态

软件定时器共有两种状态:

  • 休眠态:软件定时器可以通过其句柄被引用,但因为没有运行,所以其定时超时回调函数不会被执行。

因为它根本就没有工作,没有计时,无法去调用它的超时回调函数。

  • 运行态:运行态的定时器,当指定时间到达之后,那么此时它超时了,它的超时回调函数就会被调用

注意:新创建的软件定时器处于休眠状态 ,也就是未运行的!

问题:如何让软件定时器从休眠态转变为运行态?

发送命令队列:比如我发送开启软件定时器,那它这时候就开始工作了,这时候它就由休眠态变为运行态。

1.5 单次定时器和周期定时器

FreeRTOS 提供了两种软件定时器:

  • 单次定时器:单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数,不会自动重新开启定时,不过可以被手动重新开启。

也就是说它只会定时超时一次,一次之后它就不会工作了,除非你去手动开启它。

  • 周期定时器:周期定时器的一旦启动以后就会在(它超时之后)执行完(超时)回调函数,执行完之后它还会自动的重新启动 ,然后重新开始计数,计数到超时时间之后,又会调用它的回调函数,从而周期地执行其软件定时器回调函数。

示意图: 在这里插入图片描述

  • Timer1:周期定时器,定时超时时间为 2 个单位时间,开启后,一直以2个时间单位间隔重复执行;
  • Timer2:单次定时器,定时超时时间为 1 个单位时间,开启后,则在第一个超时后就不在执行了。

1.6 软件定时器的状态转换图

  • 单次定时器状态转换图:
    在这里插入图片描述
  • 周期定时器状态转换图:
    在这里插入图片描述

问题1:如何让软件定时器从休眠态转变为运行态?

通过调用命令队列:发送命令队列,比如开启(xTimerStart())、复位(xTimerReset())以及更改它的超时时间(xTimerChangePeriod()),这些都是可以让它转换成运行态。

问题2:如何让软件定时器从运行态转变为休眠态?

  1. 调用 xTimerStop() 函数,就停止定时器这个命令队列。
  2. 单次定时器还有一个途径:单次定时器一旦超时一次之后,就调用这个超时回调函数之后,它也就不会再工作了,所以这时候它又回到休眠态了。

1.7 软件定时器结构体成员介绍

typedef    struct
    {
        const char * 				pcTimerName				/* 软件定时器名字(只有调试这么一个作用) */
        ListItem_t 					xTimerListItem			/* 软件定时器列表项 */
        TickType_t 					xTimerPeriodInTicks;    /* 软件定时器的周期(超时时间) */     
        void * 						pvTimerID				/* 软件定时器的 ID(防止大家都用同一个回调函数) */
        TimerCallbackFunction_t	 	pxCallbackFunction; 	/* 软件定时器的回调函数 */
        #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t 				uxTimerNumber			/*  软件定时器的编号,调试用(不用关注)  */
        #endif
        uint8_t 					ucStatus;               /*  软件定时器的状态(标记单次模式/周期模式)  */
    } xTIMER;

软件定时器有两个列表:

  1. 软件定时器列表。
  2. 软件定时器的溢出列表。

为什么它有个溢出列表?因为软件定时器是以系统时钟节拍计数器为基础的,所以系统时钟节拍计数值是会溢出的,所以相应的软件定时器也是有溢出的可能的。

所以结构体内部就有一个列表项,用来插入到对应的一个列表里面,这个跟阻塞列表与阻塞溢出列表很类似,同样的一个道理。那这是它列表项的一个介绍

比如定时器 1 和定时器 2 都用同一个回调函数,那么在这个回调函数里面,怎么知道哪个是定时器 1 的,哪个是定时器 2 的,就哪个定时器触发这个回调函数的,回调函数可以通过这个 ID 来判断,那这个是 ID 的一个作用。

2. FreeRTOS软件定时器相关API函数

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

2.1 创建软件定时器API函数

TimerHandle_t   xTimerCreate(   const char * const 		    pcTimerName,
								const TickType_t 		    xTimerPeriodInTicks,
								const UBaseType_t 	    	uxAutoReload,
								void * const 			    pvTimerID,
								TimerCallbackFunction_t     pxCallbackFunction  ); 
形参描述
pcTimerName软件定时器名
xTimerPeriodInTicks定时超时时间,单位:系统时钟节拍(比如系统时钟节拍是 1ms 中断一次,那么我定义一个 5,也就是说超时时间就是 5ms)
uxAutoReload定时器模式, pdTRUE:周期定时器, pdFALSE:单次定时器
pvTimerID软件定时器 ID,用于多个软件定时器公用一个超时回调函数(可以判断是哪一个定时器触发的超时回调函数)
pxCallbackFunction软件定时器超时回调函数
返回值描述
NULL软件定时器创建失败
其他值软件定时器创建成功,返回其句柄

2.2 开启软件定时器API函数

BaseType_t   xTimerStart(TimerHandle_t 	xTimer, const TickType_t 	xTicksToWait); 
形参描述
xTimer待开启的软件定时器的句柄
xTickToWait发送命令到软件定时器命令队列的最大等待时间(队列发送的阻塞时间)
返回值描述
pdPASS软件定时器开启成功
pdFAIL软件定时器开启失败

2.3 停止软件定时器API函数

BaseType_t   xTimerStop(TimerHandle_t 	xTimer, const TickType_t 	xTicksToWait); 
形参描述
xTimer待停止的软件定时器的句柄
xTickToWait发送命令到软件定时器命令队列的最大等待时间(队列发送的阻塞时间)
返回值描述
pdPASS软件定时器停止成功
pdFAIL软件定时器停止失败

2.4 复位软件定时器API函数

BaseType_t  xTimerReset(TimerHandle_t 	xTimer, const TickType_t 	xTicksToWait); 

该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时。

就比如说有一个定时器的超时时间是 10s,那现在开启这个定时器,调用那一个开启定时器函数,调用之后这个定时器开启计数了,比如它从 0 开始计数,计数到 5 的时候,这时候调用到这个复位函数,这时候怎么办?这时候以这个时刻给它重新复位,复位成开启时刻重新定时,就是说你要从这个时刻继续计数 10s,你才会超时,就这么一个概念。

形参描述
xTimer待复位的软件定时器的句柄
xTickToWait发送命令到软件定时器命令队列的最大等待时间(队列发送的阻塞时间)
返回值描述
pdPASS软件定时器复位成功
pdFAIL软件定时器复位失败

示意图:
在这里插入图片描述
这个就是复位软件定时器它的一个作用了。

2.5 更改软件定时器超时时间API函数

BaseType_t  xTimerChangePeriod( TimerHandle_t 		xTimer,
					     		const TickType_t 	xNewPeriod,
                                const TickType_t 	xTicksToWait); 
形参描述
xTimer待更新的软件定时器的句柄
xNewPeriod新的定时超时时间,单位:系统时钟节拍
xTickToWait发送命令到软件定时器命令队列的最大等待时间(队列发送的阻塞时间)
返回值描述
pdPASS软件定时器定时超时时间更改成功
pdFAIL软件定时器定时超时时间更改失败

3. FreeRTOS软件定时器实验

实验目的:学习 FreeRTOS 的软件定时器相关API函数的使用 。
实验设计:将设计两个任务:start_task、task1

两个任务的功能如下:
start_task:用来创建task1任务,并创建两个定时器(单次和周期)
task1:用于按键扫描,并对软件定时器进行开启、停止操作

3.1 软件定时器的相关配置

  • 当 FreeRTOS 的配置项 configUSE_TIMERS = 1 时,在启动任务调度器时,会自动创建软件定时器的服务/守护任务 prvTimerTask( ) ;
  • 软件定时器服务任务的优先级为 configTIMER_TASK_PRIORITY = 31(默认优先级最大,可配置);
  • 定时器的命令队列长度为 configTIMER_QUEUE_LENGTH = 5(可配置)

3.2 任务函数实现

#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/******************************************************************************************************/
/*FreeRTOS配置*/

/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO         1
#define START_TASK_STACK_SIZE   128
TaskHandle_t    start_task_handler;
void start_task( void * pvParameters );

/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );

/**
 * 定时器超时回调函数的声明
 */
void timer1_callback( TimerHandle_t pxTimer );
void timer2_callback( TimerHandle_t pxTimer );
/******************************************************************************************************/

/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{    
    xTaskCreate((TaskFunction_t         )   start_task,
                (char *                 )   "start_task",
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   START_TASK_PRIO,
                (TaskHandle_t *         )   &start_task_handler );
    vTaskStartScheduler();
}

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

void start_task( void * pvParameters )
{
    taskENTER_CRITICAL();               /* 进入临界区 */
    
    /* 单次定时器 */
    timer1_handle = xTimerCreate( 	"timer1", 
                                    500,
                                    pdFALSE,
                                    (void *)1,
                                    timer1_callback );
                            
    /* 周期定时器 */
    timer2_handle = xTimerCreate( 	"timer2", 
                                    2000,
                                    pdTRUE,
                                    (void *)2,
                                    timer2_callback );
                                    
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                                             
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
}

/* 任务一,按键扫描并控制软件定时器 */
void task1( void * pvParameters )
{
    uint8_t key = 0;
    while(1) 
    {
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
            xTimerStart(timer1_handle,portMAX_DELAY);
            xTimerStart(timer2_handle,portMAX_DELAY);
        }else if(key == KEY1_PRES)
        {
            xTimerStop(timer1_handle,portMAX_DELAY);
            xTimerStop(timer2_handle,portMAX_DELAY);
        }
        vTaskDelay(10);
    }
}

/* timer1的超时回调函数 */
void timer1_callback( TimerHandle_t pxTimer )
{
    static uint32_t timer = 0;
    printf("timer1的运行次数:%d\r\n",++timer);
}

/* timer2的超时回调函数 */
void timer2_callback( TimerHandle_t pxTimer )
{
    static uint32_t timer = 0;
    printf("timer2的运行次数:%d\r\n",++timer);
}

4. 总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值