在介绍本文之前,向大家推荐个非常容易入门的人工智能学习网站,建议点击收藏❤️
目录:
1. 前言
软件定时器类似于生活中的闹钟,定时时间到了就会干一些我们指定的事情。
软件定时器有由FreeRTOSD内核实现,并受内核控制,开启调度的时候会自动创建一个定时器后台任务。软件定时器不需要硬件定时器支持。接下来了解软件定时器的一些基本概念以及如何使用软件定时器。
2. 软件定时器有两种状态
- 休眠:已创建但未开始运行的定时器
- 运行:开始运行了的定时器
3. 软件定时器有两种类型
单次和自动重载。
- 单次定时器一旦启动,时间到了只执行一次回调函数。单次定时器可以手动重新启动,但是不会自动重新启动。
- 自动重新加载计时器 一旦启动,自动重新加载计时器将在每次到期时重新启动,从而定期执行其回调函数。
4. 定时器服务任务
所有软件计时器回调函数都在同一RTOS守护进程(或“计时器服务”)任务的上下文中执行。守护程序任务,是在启动调度程序时,自动创建的标准FreeRTOS任务。其优先级和堆栈大小分别由configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH编译时间配置常量设置。这两个常量都在FreeRTOSConfig.h中定义。
软件计时器回调函数不得调用会导致调用任务进入阻塞状态的API函数,否则将导致守护程序任务进入阻塞状态。
5. 定时器命令队列
软件计时器API函数将命令从调用任务发送到称为“计时器命令队列”的队列上的守护程序任务。计时器命令队列是在启动调度程序时自动创建的标准FreeRTOS队列。定时器命令队列的长度由FreeRTOSConfig.h中的configTIMER_QUEUE_LENGTH编译时间配置常量设置。
6. 软件定时器的常用API
6.1 xTimerCreate()
可以在调度器运行之前创建软件计时器,也可以在调度器启动后从任务创建软件计时器。
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
- pcTimerName:定时器的描述性名称。
- xTimerPeriodInTicks:指定的定时器周期。单位是系统节拍。
- uxAutoReload:设置是否可自动重新计时。
- pvTimerID:每个定时器都有一个ID,当多个软件计时器使用相同的回调函数时,ID特别有用,因为它可以用来判断是哪个定时器到期产生的回调。
- pxCallbackFunction:指定的回调函数。
6.2 xTimerStart()和xTimerStop()
xTimerStart()开启定时器,该定时器从休眠态切换到运行态。xTimerStop()停止定时器,定时器切换到休眠态。xTimerStart()可以在调度程序之前调用,但是这样做的话也要到调度程序启动时才会实际运行。
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
- xTimer:正在启动或重置的定时器句柄。
- xTicksToWait:如果为0且定时器命令队列已满,xTimerStart()会运行失败。
6.3 xTimerChangePeriod()
更改软件定时器的周期。如果用于更改已在运行的定时器周期,则该定时器将使用新的周期值计算到期时间。如果用于更改休眠中的定时器,则该定时器将切换到运行状态。
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );
- pcTimerName:定时器的描述性名称。
- xTimerPeriodInTicks:定时器的新周期。
- xTicksToWait:如果为0且定时器命令队列已满,xTimerChangePeriod()会运行失败。
6.4 vTimerSetTimerID() 和 pvTimerGetTimerID()
每个定时器都有一个ID,开发者可以设置或者获取软件定时器的ID。
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
void *pvTimerGetTimerID( TimerHandle_t xTimer );
在实际应用中,常常将相同的回调函数分配给多个软件定时器,此时这两个API就显得至关重要。
6.5 xTimerReset()
重置软件定时器意味着重启定时器,同时还可以重新设置周期值。
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
- xTimer:需要重置的定时器句柄。
- xTimerPeriodInTicks:想修改成的周期值时间。
- xTicksToWait:命令超时时间,如果定时器命令队列满了的话,超时会导致重置失败。
7. 软件定时器生活示例
接下来设计了一个示例,该示例来源于生活。
7.1 需求定义
小爱同学可以提供给我们提醒功能,现在即将实现:让小爱同学凌晨4点叫你起来学习RTOS,然后学习2个小时后叫你去跑步锻炼。
你:小爱同学~
小爱:哎!
你:明天凌晨4点叫我起来学习
小爱:好的
你:小爱同学~
小爱:干嘛~
你:明天6点提醒我去跑步
小爱:好的~
7.2需求分析
因为4点起床实在是太早了,所以也不是每天,所以这里我们用单次定时器,上面定了两个闹钟,所以我们需要创建两个定时器。用RTOS实现以上提醒功能,需要:
- 先创建个任务(小爱同学)
- 任务中创建两个定时器(让小爱知道几点提醒你)
- 两个定时器可以用同一个回调服务函数(通过定时器ID来判断提醒你学习还是锻炼)
7.3 代码实现
7.3.1 创建任务
TaskHandle_t XiaoAi;
void XiaoAiTiXing( void *pvParameters );
int main( void )
{
//买一个小爱同学回家~
xTaskCreate(XiaoAiTiXing, "我是小爱同学\r\n", 1000, NULL, 1, &XiaoAi);
vTaskStartScheduler();
for( ;; );
return 0;
}
void XiaoAiTiXing( void *pvParameters )
{
/* Print out the name of this task. */
vPrintString(pcTaskGetName(XiaoAi));
for( ;; )
{
while (1)
{
Sleep(pdMS_TO_TICKS(1000));
}
}
}
7.3.2 任务中创建定时器并开启
这里为了更好得看到输出效果,我们多创建了一个定时器,用于每秒打印一个输出。
void XiaoAiTiXing( void *pvParameters )
{
vPrintString(pcTaskGetName(XiaoAi));
Clock = xTimerCreate("Clock", pdMS_TO_TICKS(1000), pdTRUE, 0, ShiJianDao);
Learn = xTimerCreate("learn", pdMS_TO_TICKS(4000), pdFALSE, 1, ShiJianDao);
Run = xTimerCreate("run", pdMS_TO_TICKS(6000), pdFALSE, 2, ShiJianDao);
xTimerStart(Clock, 0);
xTimerStart(Learn, 0);
xTimerStart(Run, 0);
for( ;; )
{
while (1)
{
Sleep(pdMS_TO_TICKS(1000));
}
}
}
7.3.3 定时器到期回调函数里干相应的事情
static void ShiJianDao(TimerHandle_t timer)
{
static uint8_t i = 0;
uint8_t timerId = pvTimerGetTimerID(timer);
//时间到了在这里提醒
switch (timerId)
{
case 0:
i++;
printf("现在是凌晨%d点\r\n", i);
break;
case 1:
vPrintString("现在是凌晨4点,该起来学习啦\r\n");
break;
case 2:
vPrintString("现在是早上6点,该去跑步啦\r\n");
break;
default:
break;
}
printf("\r\n");
}