参考文档是韦东山老师的第16章 软件定时器(software timer) | 百问网
软件定时器
是什么
定时器定时器,我们可以初步把他了解为具有硬件定时器部分功能的定时器,也就运行到达某些规定的时刻中,CPU会跳转进入回调函数执行中断任务。但是软件定时器比硬件定时器多了一些介质("定时器命令队列"(timer command queue)和守护任务)。
根据文档我们可以知道,软件定时器的本质有点像队列通知,在我们使用函数创建定时器,并在需要触发的时候在某个任务使用Star开启定时器(这时候写入定时器队列,该队列在FreeRTOS中自带),只要“某个任务”进入阻塞,那么根据优先级运行到“定时器队列”所在的任务函数中(也就是守护任务),在守护任务中,会执行软件定时器的回调函数(自己写的)。这些状态的跳转可以参考下面的图片
API函数
/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
/* 启动定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
实验
创建软件定时器,再创建一个任务通知以及接收任务通知的任务,当任务通知到来时使用xTimerChangePeriod间接启动定时器
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
static TaskHandle_t TaskAh;
static TaskHandle_t TaskBh;
static TimerHandle_t TaskTimerh;
uint32_t timer = 0;
uint32_t timer_last = 0;
void TaskA(void *param)
{
//发送通知]
uint32_t value = 0;
while(1)
{
xTaskNotify(TaskBh,value ,eSetValueWithOverwrite);
vTaskDelay(pdMS_TO_TICKS(1000));
value++;
}
}
void TaskB(void *param)
{
//接收并打印
uint32_t value ;
while (1)
{
//如果发现数值变换
if(timer != timer_last)
{
ESP_LOGI("timer","%lu",timer);
timer_last = timer;
}
xTaskNotifyWait(0x00,ULLONG_MAX,&value,portMAX_DELAY);
ESP_LOGI("ev","notify value:%lu",value);
xTimerChangePeriod(TaskTimerh, pdMS_TO_TICKS(1000) , pdMS_TO_TICKS(10));
}
}
//回调函数干的事尽量少一点
void TaskTimer_cb()
{
timer++;
}
void app_main()
{
//taskDISABLE_INTERRUPTS();本来想进入临界区创建任务后再退出的,但是发现不管用,所以使用了后面的挂起和开始
xTaskCreatePinnedToCore(TaskA , "TaskA" , 2048 , NULL , 5 , &TaskAh , 1);
vTaskSuspend(TaskAh);
xTaskCreatePinnedToCore(TaskB , "TaskB" , 2048 , NULL , 5 , &TaskBh , 1);
vTaskSuspend(TaskBh);
TaskTimerh = xTimerCreate( "Timer" , pdMS_TO_TICKS(1000) , pdFALSE , NULL , TaskTimer_cb);
//taskENABLE_INTERRUPTS();
vTaskResume(TaskAh);
vTaskResume(TaskBh);
}