文章目录
一、软件定时器
1.1.简介
定时器,就是一个闹钟,从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可自定义定时器的周期。定时器又分为两种:
- 硬件定时器:芯片本身自带的定时器模块,硬件定时器的精度一般很高,每次在定时时间到达之后就会自动触发一个中断,用户在中断服务函数中处理信息。硬件定时器属于外设,数量有限
- 软件定时器:是指具有定时功能的软件,可以设置定时周期,当指定时间到达后要调用回调函数(也称超时函数),用户在回调函数中处理信息。软件定时的数量理论上来说可以有无限个,由用户自定设定软件定时器的数量
1.2.优缺点
- 优点:硬件定时器数量有限,而软件定时器理论上只需要足够的内存,就可以创建多个;使用简单,成本低
- 缺点:软件定时器相对硬件定时器来说,精度没有那么高(因为它以系统时钟为基准,系统时钟中断优先级又是最低,容易被打断)。对于需要高精度要求的场合,不建议使用软件定时器
1.3.特点
- 可裁剪:软件定时器是可裁剪可配置的功能,如果要使能软件定时器,需将
configUSE_TIMERS
配置成 1 - 单次和周期:软件定时器支持设置成:单次定时器和周期定时器
需注意,软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的 API 函数。
1.4.软件定时器服务任务
在调用函数vTaskStartScheduler( )
开启任务调度器时,会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。软件定时器服务任务作用:
- 负责软件定时器超时的逻辑判断
- 调用超时软件定时器的超时回调函数
- 处理软件定时器命令队列
软件定时器命令队列:FreeRTOS 提供了许多软件定时器相关的 API 函数,这些 API 函数大多是往定时器的队列中写入消息(发送命令),这个队列叫做软件定时器命令队列,是提供给 FreeRTOS 中的软件定时器使用的,用户是不能直接访问的。
1.5.相关配置
- 当 FreeRTOS 的配置项
configUSE_TIMERS
设置为 1,在启动任务调度器时,会自动创建软件定时器的服务 / 守护任务prvTimerTask( );
- 软件定时器服务任务的优先级为
configTIMER_TASK_PRIORITY = 31;
- 定时器的命令队列长度为
configTIMER_QUEUE_LENGTH = 5;
需注意,软件定时器的超时回调函数是在软件定时器服务任务中被调用的,服务任务不是专为某个定时器服务,它还需处理其他定时器,因此定时器的回调函数不能影响其他定时器:
- 回调函数要尽快实行,不能进入阻塞状态,既不能调用那些会阻塞任务的 API 函数,如
vTaskDelay( );
- 访问队列或者信号量的非零阻塞时间的 API 函数也不能调用
1.6.状态
软件定时器共有两种状态:
- 休眠态:软件定时器可以通过其句柄被引用,但因为没有运行,所以其定时超时回调函数不会被执行
- 运行态:当指定时间到达之后,它的超时回调函数会被调用
新创建的软件定时器处于休眠状态,也就是未运行的,需要发送命令队列使之运行。
1.7.单次定时器和周期定时器
FreeRTOS 提供了两种软件定时器:
- 单次定时器:单次定时器的一旦定时超时,只会执行一次它的超时回调函数,不会自动重新开始定时,可以被手动开启,下面是单次定时器的状态转换图
- 周期定时器:周期定时器的一旦启动之后就会在执行完回调函数以后自动重新启动,从而周期性的执行其软件定时器回调函数,下面是周期定时器的状态转换图:
下面是单次定时器和周期定时器的对比图:
二、FreeRTOS 软件定时器相关 API 函数
FreeRTOS 提供了软件定时器的一些相关操作函数,其中常用的软件定时器相关 API 函数还有一些在中断中使用的函数没有写上,如下表所示:
函数 | 描述 |
---|---|
xTimerCreate( ) | 动态方式创建软件定时器 |
xTimerCreateStatic( ) | 静态方式创建软件定时器 |
xTimerStart( ) | 开启软件定时器定时 |
xTimerStop( ) | 停止软件定时器定时 |
xTimerReset( ) | 复位软件定时器定时 |
xTimerChangePeriod( ) | 更改软件定时器的定时超时时间 |
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 | 定时超时时间,单位:系统时钟节拍 |
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);
下面表格是它的形参和描述:
形参 | 描述 |
---|---|
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.1.实验设计
本实验将设计两个任务:
- start_task:用来创建 task1 任务,并创建两个定时器(单次和周期)
- task1:用于按键扫描,并对软件定时器进行开启、停止操作
3.2.软件设计
在 start_task 函数上面定义定时器 1 和 2 的句柄,然后创建这两个定时器:
TimerHandle_t timer1_handle = 0;
TimerHandle_t timer2_handle = 0;
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
timer1_handle = xTimerCreate("timer1", 1000, pdFALSE, (void *)1, timer1_callback); //创建定时器1,单次定时器
timer2_handle = xTimerCreate("timer1", 1000, pdTRUE, (void *)2, timer2_callback); //创建定时器2,周期定时器
xTaskCreate((TaskFunction_t) task1,
(char*) "task1",
(uint16_t) TASK1_STACK_SIZE,
(void*) NULL,
(UBaseType_t) TASK1_PRIO,
(TaskHandle_t*) &task1_handler);
vTaskDelete(NULL);
taskEXIT_CRITICAL();
}
下面是 task1 代码实现,按下 KEY0 开启两个定时器,按下 KEY1 停止两个定时器:
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);
}
}
下面代码是两个定时器的回调函数:
void timer1_callback(TimerHandle_t pxTimer)
{
static uint32_t timer = 0;
printf("timer1的运行次数:%d\r\n",++timer);
}
void timer2_callback(TimerHandle_t pxTimer)
{
static uint32_t timer = 0;
printf("timer2的运行次数:%d\r\n",++timer);
}
下图是运行结果:
由上图可知,单次定时器开启之后只会运行一次,再次运行需要手动开启;周期定时器每隔 1s 运行一次,需要手动停止。