本专栏将对FreeRTOS进行快速讲解,带你了解并使用FreeRTOS的各部分内容。适用于快速了解FreeRTOS并进行开发、突击面试、对新手小白非常友好。期待您的后续关注和订阅!
目录
软件定时器和低功耗模式
1 软件定时器
1.1 什么是定时器?
1.2 软件定时器的优缺点
1.3 FreeRTOS软件定时器特点
1.4 软件定时器的状态及转换
1.4.1 定时器状态
1.4.2 定时器周期
1.5 结构体及API函数
1.5.1 结构体
1.5.1 相关API函数
1.6 软件定时器实验
2 低功耗模式
2.1 低功耗模式简介
2.2 Tickless 模式详解
2.3 Tickless 模式相关配置项
2.4 Tickless 模式实验
本章节将软件定时器和低功耗模式放在一起进行讲解!
定时器是一种可以在设定时间后执行某个操作的工具。想象一下,你在厨房里煮饭,需要在20分钟后关火。你可以设定一个厨房计时器(定时器),20分钟后它会提醒你去关火。
硬件定时器:硬件定时器是芯片自带的定时器模块,精度一般很高,每次在定时时间到达之后就会自动触发一个中断,用户在中断服务函数中处理信息。
软件定时器:软件定时器是指具有定时功能的软件模块,可设置定时周期,当指定时间到达后调用回调函数(也称超时函数),用户在回调函数中处理信息。
优点 | 缺点 |
---|---|
硬件定时器数量有限,而软件定时器理论上只需有足够内存,就可以创建多个。 | 软件定时器相对硬件定时器来说,精度没有那么高(因为它以系统时钟为基准,系统时钟中断优先级较低,容易被打断)。对于需要高精度的场合,不建议使用软件定时器。 |
使用简单、成本低。 |
特点 | 描述 |
---|---|
可裁剪 | 软件定时器是可裁剪可配置的功能,需将configUSE_TIMERS 配置项配置成1。 |
单次和周期 | 支持单次定时器和周期定时器。 |
软件定时器服务任务 | 在调用vTaskStartScheduler() 开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。 |
软件定时器服务任务的作用 | 负责软件定时器超时的逻辑判断,调用超时软件定时器的超时回调函数,处理软件定时器命令队列。 |
其具备两种状态,休眠状态和运行状态。
状态 | 描述 |
---|---|
休眠态 | 新创建的软件定时器处于休眠状态,也就是未运行的。 |
运行态 | 运行态的定时器,当指定时间到达之后,它的回调函数会被调用。 |
状态转换 | 软件定时器可以通过其句柄被引用,但因为没有运行,所以其定时超时回调函数不会被执行。要让软件定时器从休眠态转变为运行态,需要发送命令到命令队列。 |
定时器按周期分为两种:
软件定时器结构体如下,了解一下:
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;
以下为软件相关API函数,中断重复部分函数不再进行再详细介绍。
函数 | 描述 |
---|---|
xTimerCreate() |
动态方式创建软件定时器 |
xTimerCreateStatic() |
静态方式创建软件定时器 |
xTimerStart() |
开启软件定时器定时 |
xTimerStartFromISR() |
在中断中开启软件定时器定时 |
xTimerStop() |
停止软件定时器定时 |
xTimerStopFromISR() |
在中断中停止软件定时器定时 |
xTimerReset() |
复位软件定时器定时 |
xTimerResetFromISR() |
在中断中复位软件定时器定时 |
xTimerChangePeriod() |
更改软件定时器的定时超时时间 |
xTimerChangePeriodFromISR() |
在中断中更改定时超时时间 |
(1)xTimerCreate()
函数作用:动态方式创建软件定时器
函数原型:
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 |
软件定时器超时回调函数。 |
返回值:
返回 TimerHandle_t
类型的软件定时器句柄,NULL
表示创建失败。
函数举例:
// 创建一个周期性软件定时器,周期为1000个时钟节拍
TimerHandle_t xMyTimer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdTRUE, 0, vTimerCallback);
if (xMyTimer == NULL) {
// 创建失败处理
} else {
// 创建成功
}
(2)xTimerCreateStatic()
函数作用:静态方式创建软件定时器
函数原型:
TimerHandle_t xTimerCreateStatic(
const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer
);
参数解析:
参数 | 描述 |
---|---|
pcTimerName |
软件定时器名。 |
xTimerPeriodInTicks |
定时超时时间,单位:系统时钟节拍。 |
uxAutoReload |
定时器模式,pdTRUE :周期定时器,pdFALSE :单次定时器。 |
pvTimerID |
软件定时器ID,用于多个软件定时器公用一个超时回调函数。 |
pxCallbackFunction |
软件定时器超时回调函数。 |
pxTimerBuffer |
静态定时器结构体变量。 |
返回值:
返回 TimerHandle_t
类型的软件定时器句柄,NULL
表示创建失败。
函数举例:
// 定义一个静态定时器缓冲区
StaticTimer_t xTimerBuffer;
TimerHandle_t xMyStaticTimer = xTimerCreateStatic("MyStaticTimer", pdMS_TO_TICKS(1000), pdTRUE, 0, vTimerCallback, &xTimerBuffer);
if (xMyStaticTimer == NULL) {
// 创建失败处理
} else {
// 创建成功
}
(3)xTimerStart()
函数作用:开启软件定时器定时
函数原型:
BaseType_t xTimerStart(
TimerHandle_t xTimer,
const TickType_t xTicksToWait
);
参数解析:
参数 | 描述 |
---|---|
xTimer |
待开启的软件定时器的句柄。 |
xTicksToWait |
发送命令到软件定时器命令队列的最大等待时间。 |
返回值:
返回 pdPASS
表示开启成功,pdFAIL
表示开启失败。
函数举例:
if (xTimerStart(xMyTimer, 0) == pdPASS) {
// 开启成功
} else {
// 开启失败处理
}
(4)xTimerStop()
函数作用:xTimerStop()
函数原型:
BaseType_t xTimerStop(
TimerHandle_t xTimer,
const TickType_t xTicksToWait
);
参数解析:
参数 | 描述 |
---|---|
xTimer |
待停止的软件定时器的句柄。 |
xTicksToWait |
发送命令到软件定时器命令队列的最大等待时间。 |
返回值:
返回 pdPASS
表示停止成功,pdFAIL
表示停止失败。
函数举例:
if (xTimerStop(xMyTimer, 0) == pdPASS) {
// 停止成功
} else {
// 停止失败处理
}
(5)xTimerReset()
函数作用:复位软件定时器定时
函数原型:
BaseType_t xTimerReset(
TimerHandle_t xTimer,
const TickType_t xTicksToWait
);
参数解析:
参数 | 描述 |
---|---|
xTimer |
待复位的软件定时器的句柄。 |
xTicksToWait |
发送命令到软件定时器命令队列的最大等待时间。 |
返回值:
返回 pdPASS
表示复位成功,pdFAIL
表示复位失败。
函数举例:
if (xTimerReset(xMyTimer, 0) == pdPASS) {
// 复位成功
} else {
// 复位失败处理
}
(6)xTimerChangePeriod()
函数作用:更改软件定时器的定时超时时间
函数原型:
BaseType_t xTimerChangePeriod(
TimerHandle_t xTimer,
const TickType_t xNewPeriod,
const TickType_t xTicksToWait
);
参数解析:
参数 | 描述 |
---|---|
xTimer |
待更新的软件定时器的句柄。 |
xNewPeriod |
新的定时超时时间,单位:系统时钟节拍。 |
xTicksToWait |
发送命令到软件定时器命令队列的最大等待时间。 |
返回值:
返回 pdPASS
表示更改成功,pdFAIL
表示更改失败。
函数举例:
if (xTimerChangePeriod(xMyTimer, pdMS_TO_TICKS(500), 0) == pdPASS) {
// 更改成功
} else {
// 更改失败处理
}
实验设计:
设计两个任务:start_task
和 task1
。start_task
负责创建 task1
任务,并创建两个定时器(单次和周期),task1
用于按键扫描,并对软件定时器进行开启、停止操作。
任务功能:
start_task
:创建 task1
任务,并创建两个软件定时器(单次定时器和周期定时器)。task1
:扫描按键,根据按键操作开启或停止软件定时器。代码示例:
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include
// 任务句柄
TaskHandle_t StartTask_Handle;
TaskHandle_t Task1_Handle;
TimerHandle_t Timer1_Handle;
TimerHandle_t Timer2_Handle;
// 定时器回调函数声明
void vTimerCallback1(TimerHandle_t xTimer);
void vTimerCallback2(TimerHandle_t xTimer);
// 任务函数声明
void StartTask(void *pvParameters);
void Task1(void *pvParameters);
int main(void)
{
// 创建启动任务
xTaskCreate((TaskFunction_t)StartTask, "StartTask", 128, NULL, 2, &StartTask_Handle);
// 启动调度器
vTaskStartScheduler();
// 主函数不会再执行到这里
while(1);
}
// 启动任务:创建任务1和两个定时器
void StartTask(void *pvParameters)
{
// 创建任务1
xTaskCreate((TaskFunction_t)Task1, "Task1", 128, NULL, 2, &Task1_Handle);
// 创建周期定时器,定时时间为2000ms
Timer1_Handle = xTimerCreate("Timer1", pdMS_TO_TICKS(2000), pdTRUE, (void *)0, vTimerCallback1);
// 创建单次定时器,定时时间为1000ms
Timer2_Handle = xTimerCreate("Timer2", pdMS_TO_TICKS(1000), pdFALSE, (void *)0, vTimerCallback2);
// 启动任务调度器
vTaskDelete(StartTask_Handle);
}
// 定时器回调函数1:周期定时器回调
void vTimerCallback1(TimerHandle_t xTimer)
{
printf("Periodic Timer Callback\n");
}
// 定时器回调函数2:单次定时器回调
void vTimerCallback2(TimerHandle_t xTimer)
{
printf("One-shot Timer Callback\n");
}
// 任务1:按键扫描,并对软件定时器进行开启、停止操作
void Task1(void *pvParameters)
{
while(1)
{
// 模拟按键操作,实际中需要根据硬件按键状态进行判断
int keyPressed = 1; // 模拟按键按下
if(keyPressed)
{
// 开启周期定时器
if(xTimerStart(Timer1_Handle, 0) == pdPASS)
{
printf("Periodic Timer Started\n");
}
// 开启单次定时器
if(xTimerStart(Timer2_Handle, 0) == pdPASS)
{
printf("One-shot Timer Started\n");
}
}
// 模拟按键释放,停止定时器
int keyReleased = 1; // 模拟按键释放
if(keyReleased)
{
// 停止周期定时器
if(xTimerStop(Timer1_Handle, 0) == pdPASS)
{
printf("Periodic Timer Stopped\n");
}
// 停止单次定时器
if(xTimerStop(Timer2_Handle, 0) == pdPASS)
{
printf("One-shot Timer Stopped\n");
}
}
// 模拟任务延时
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
在许多应用场合,对功耗的要求非常严格,例如可穿戴设备和物联网设备。一般 MCU 都有相应低功耗模式,而FreeRTOS 也提供了一个名为 Tickless 的低功耗模式,以便在运行 FreeRTOS 操作系统的应用中方便地实现低功耗。
低功耗模式主要有以下三种:
模式 | 描述 |
---|---|
睡眠模式 | 最低功耗模式,MCU 停止所有活动,等待中断或事件唤醒。 |
停止模式 | 在睡眠模式的基础上关闭更多的时钟源。 |
待机模式 | 关闭所有时钟源,最小功耗,需外部事件唤醒。 |
Tickless 模式的设计思想是在系统进入空闲任务时,MCU 进入低功耗模式;当其他任务准备运行时,唤醒 MCU 退出低功耗模式。
WFI
或 WFE
实现睡眠模式。Tickless 模式的关键在于:
配置项 | 描述 |
---|---|
configUSE_TICKLESS_IDLE |
此宏用于使能低功耗 Tickless 模式。 |
configEXPECTED_IDLE_TIME_BEFORE_SLEEP |
此宏用于定义系统进入相应低功耗模式的最短时长。 |
configPRE_SLEEP_PROCESSING(x) |
此宏用于定义需要在系统进入低功耗模式前执行的事务,如:进入低功耗前关闭外设时钟,以达到降低功耗的目的。 |
configPOST_SLEEP_PROCESSING(x) |
此宏用于定义需要在系统退出低功耗模式后执行的事务,如:退出低功耗后开启之前关闭的外设时钟,以使系统能够正常运行。 |
示例配置:
#define configUSE_TICKLESS_IDLE 1
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
void configPRE_SLEEP_PROCESSING(TickType_t xExpectedIdleTime) {
// 关闭外设时钟,降低功耗
}
void configPOST_SLEEP_PROCESSING(TickType_t xExpectedIdleTime) {
// 开启外设时钟,恢复系统运行
}
实验目的:学习使用 FreeRTOS 中的低功耗 Tickless 模式,并观察该模式是否对功耗有明显降低。
实验设计:将在原先二值信号量的课堂源码中,加入低功耗模式,最后对比这两个实验的功耗结果,观察 Tickless 模式对于降低功耗是否有用。
代码示例:
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
// 定义二值信号量句柄
SemaphoreHandle_t xBinarySemaphore;
void vTask1(void *pvParameters);
void vTask2(void *pvParameters);
int main(void) {
// 创建二值信号量
xBinarySemaphore = xSemaphoreCreateBinary();
if (xBinarySemaphore != NULL) {
// 创建任务1
xTaskCreate(vTask1, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 创建任务2
xTaskCreate(vTask2, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
}
// 主函数不会再执行到这里
for(;;);
}
void vTask1(void *pvParameters) {
for(;;) {
// 等待信号量
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {
// 信号量被取走后的处理
}
}
}
void vTask2(void *pvParameters) {
for(;;) {
// 释放信号量
xSemaphoreGive(xBinarySemaphore);
// 模拟任务处理
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 进入低功耗前处理
void configPRE_SLEEP_PROCESSING(TickType_t xExpectedIdleTime) {
// 关闭外设时钟,降低功耗
}
// 退出低功耗后处理
void configPOST_SLEEP_PROCESSING(TickType_t xExpectedIdleTime) {
// 开启外设时钟,恢复系统运行
}
本专栏将对FreeRTOS进行快速讲解,带你了解并使用FreeRTOS的各部分内容。期待诸君的关注和订阅!