1. FreeRTOS 的低功耗模式
1.1 Tickless 低功耗模式简介
Tickless 模式是 FreeRTOS 为了降低功耗而提供的一种机制。在默认情况下,FreeRTOS 采用固定间隔的系统滴答(Tick)中断 来管理任务调度,即 每隔固定时间(如 1ms)触发一次中断,即使系统处于空闲状态,Tick 也会继续触发,导致 CPU 无法进入深度低功耗模式。
Tickless 模式的核心思想是:
当系统进入空闲任务时,FreeRTOS 关闭系统滴答定时器,进入深度低功耗模式,并在下一个需要唤醒的时间点自动唤醒 CPU,重新启用系统滴答。
Tickless 模式的工作流程:
当启用
Tickless
模式后,FreeRTOS 的 系统滴答定时器(SysTick) 行为如下:
- 当系统检测到所有任务都进入阻塞(空闲)状态:
- FreeRTOS 计算下一个任务准备就绪的时间(例如 100ms)。
- 关闭滴答定时器(SysTick),减少 CPU 运行次数,降低功耗。
- MCU 进入 低功耗模式。
- 当定时器到达预定时间:
- 由
SysTick
或 RTC 唤醒 CPU。- FreeRTOS 恢复滴答定时器,继续任务调度。
- 如果中断发生:
- 例如串口接收、外部按键等中断触发,MCU 立即唤醒,并执行中断任务。
1.2 Tickless 模式详解
STM32F103xC、STM32F103xD 和 STM32F103xE 增强型产品支持三种低功耗模式,可以在要求低功耗、短启动时间和多种唤醒事件之间达到最佳的平衡。
(1)睡眠模式(Sleep Mode)
只有 CPU 停止,所有外设处于工作状态并可在发生中断/事件时唤醒 CPU。
(2)停机模式(Stop Mode)
在保持 SRAM 和寄存器内容不丢失的情况下,停机模式可以达到最低的电能消耗。在停机模式下,停止所有内部 1.8V 部分的供电,PLL、HSI 的 RC 振荡器和 HSE 晶体振荡器被关闭,调压器可以被置于普通模式或低功耗模式。可以通过任一配置成 EXTI 的信号把微控制器从停机模式中唤醒,EXTI 信号可以是 16 个外部 I/O 口之一、PVD 的输出、RTC闹钟或 USB 的唤醒信号。
(3)待机模式(Standby Mode)
在待机模式下可以达到最低的电能消耗。内部的电压调压器被关闭,因此所有内部1.8V 部分的供电被切断;PLL、HSI 的 RC 振荡器和 HSE 晶体振荡器也被关闭;进入待机模式后,SRAM 和寄存器的内容将消失,但后备寄存器的内容仍然保留,待机电路仍工作。从待机模式退出的条件是:NRST 上的外部复位信号、IWDG 复位、WKUP 引脚上的一个上升边 沿或 RTC 的闹钟到时。
注意:在进入停机或待机模式时,RTC、IWDG 和对应的时钟不会被停止。
主要使用睡眠模式,任何中断或事件都可以唤醒睡眠模式。Tickless 低功耗模式通过调用指令 __WFI 实现睡眠模式
FreeRTOS 系统中的所有其它任务都不在运行时(处于阻塞或挂起),会运行空闲任务。所以想不影响系统运行又降低功耗,可以在空闲任务执行的期间,让 MCU 进入相应的低功耗模式。
由于滴答定时器频繁中断则会影响低功耗,所以 FreeRTOS 的 Tickless 低功耗模式会自动把滴答定时器的中断周期修改为低功耗运行时间,退出低功耗后再补上系统时钟节拍数。
1.3 必要配置宏
#define configUSE_TICKLESS_IDLE 1 // 使能 Tickless 模式,默认为 0
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2 // 系统进入 Tickless 模式前,任务必须保持空闲至少多少 Tick
#define configPRE_SLEEP_PROCESSING(x) do_something_before_sleep(x) // 进入低功耗前执行的函数
#define configPOST_SLEEP_PROCESSING(x) do_something_after_sleep(x) // 退出低功耗后执行的函数
1.4 实验
在二值信号量实验案例中,加入低功耗模式,对比功耗结果,观察是否降低功耗(万用表测量电流或使用示波器或功耗分析仪直接测量 MCU 的电流消耗)。
1 ) FreeRTOS 配置
/* Tickless 模式配置 */
#include "user.h"
#define configUSE_TICKLESS_IDLE 1 // 使能 Tickless 模式,默认为 0
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 10 // 系统进入 Tickless 模式前,任务必须保持空闲至少多少 Tick
#define configPRE_SLEEP_PROCESSING(x) PRE_SLEEP_PROCESSING() // 进入低功耗前执行的函数
#define configPOST_SLEEP_PROCESSING(x) POST_SLEEP_PROCESSING() // 退出低功耗后执行的函数
2 ) 低功耗处理函数
/* 进入低功耗前所需要执行的操作 */
void PRE_SLEEP_PROCESSING(void)
{
printf("Entering Sleep Mode\r\n");
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOC_CLK_DISABLE();
__HAL_RCC_GPIOD_CLK_DISABLE();
}
/* 退出低功耗后所需要执行的操作 */
void POST_SLEEP_PROCESSING(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
printf("Waking Up\r\n");
}
3 ) 任务配置
void task1(void *pvParameters)
{
BaseType_t err;
while (1)
{
if (key[0].flag == 1)
{
err = xSemaphoreGive(semaphore_handle);
if (err != pdTRUE)
printf("task1 释放信号量失败\r\n");
led[0].state = !led[0].state;
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, (GPIO_PinState)led[0].state);
key[0].flag = 0;
}
vTaskDelay(500);
}
}
void task2(void *pvParameters)
{
BaseType_t err = 0;
while (1)
{
err = xSemaphoreTake(semaphore_handle, 10000);
if (err != pdTRUE)
{
printf("task2 获取信号量失败\r\n");
}
else
{
printf("task2 成功获取信号量\r\n");
}
vTaskDelay(1000);
}
}