4. Tickless模式配置实战:在STM32L4上配置FreeRTOS Tickless模式
好,咱们进入正题。Tickless模式,说白了就是让RTOS在没事干的时候「打盹」,而不是傻乎乎地每秒几万次地检查有没有活干。我刚开始接触低功耗时,总觉得系统空闲了直接进睡眠不就完了?后来才发现,FreeRTOS的Tickless远不止「睡一觉」那么简单。
这一章,咱们就在STM32L4上把Tickless模式真正跑起来。重点就两个:改配置、写钩子。
4.1 先搞清楚Tickless在干什么
先看一张图,理解一下Tickless的核心逻辑。
看到没?核心就三步:算时间、睡下去、醒来补Tick。但每一步都有坑,咱们一个一个填。
4.2 修改 FreeRTOSConfig.h
这是最直接的一步。我习惯把配置项分成「必开」和「可选」两类,先保证能跑起来。
4.2.1 必开配置
/* FreeRTOSConfig.h */
#define configUSE_TICKLESS_IDLE 1 // 启用 Tickless 模式
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2 // 至少空闲 2 个 Tick 才睡
configUSE_TICKLESS_IDLE 设为 1,告诉内核:空闲时别傻等,去睡。
configEXPECTED_IDLE_TIME_BEFORE_SLEEP 这个值很关键。设太小,频繁进出睡眠反而费电;设太大,响应变慢。我一般设 2~5,具体看你的 Tick 周期。
4.2.2 可选但推荐配置
#define configTICK_RATE_HZ ((TickType_t)1000) // 1ms Tick
#define configSYSTICK_CLOCK_HZ ((uint32_t)1000000) // 1MHz
#define configPRE_SLEEP_PROCESSING(x) PreSleepProcessing(x)
#define configPOST_SLEEP_PROCESSING(x) PostSleepProcessing(x)
configPRE_SLEEP_PROCESSING 和 configPOST_SLEEP_PROCESSING 是两个宏,分别在睡前和醒后调用。这是咱们做低功耗的主战场。
4.3 实现睡前和醒后钩子
这两个函数写在 main.c 或者专门的 lowpower.c 里。我习惯单独建文件,方便维护。
4.3.1 睡前处理:PreSleepProcessing
void PreSleepProcessing(uint32_t ulExpectedIdleTime)
{
/* 1. 关闭不用的外设时钟 */
__HAL_RCC_USART1_CLK_DISABLE();
__HAL_RCC_SPI1_CLK_DISABLE();
__HAL_RCC_I2C1_CLK_DISABLE();
/* 2. 配置 LPTIM1 作为唤醒定时器 */
HAL_LPTIM_TimeOut_Start_IT(&hlptim1, ulExpectedIdleTime, 1000);
/* 3. 进入 STOP 模式 */
HAL_PWR_EnterSTOPMode(PWR_LOWPOWER_REGULATOR_ON, PWR_STOPENTRY_WFI);
}
注意顺序:先关外设,再配定时器,最后进 STOP。关外设的顺序也有讲究——我一般先关通信接口,再关传感器,最后关调试接口。
4.3.2 醒后处理:PostSleepProcessing
void PostSleepProcessing(uint32_t ulExpectedIdleTime)
{
/* 1. 重新配置系统时钟 */
SystemClock_Config();
/* 2. 重新开启外设时钟 */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_I2C1_CLK_ENABLE();
/* 3. 重新初始化外设(有些外设醒来后需要重新配置) */
MX_USART1_UART_Init();
MX_SPI1_Init();
MX_I2C1_Init();
}
醒后的第一件事永远是恢复时钟。STM32L4 从 STOP 模式唤醒后,系统时钟默认回到 MSI,需要手动切回你想要的频率。
4.4 配置 LPTIM 定时器
LPTIM 是 Tickless 模式的核心。它要在系统睡眠时替代 SysTick 计时。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 时钟源 | LSE (32.768kHz) | 精度高,功耗低 |
| 预分频 | 128 | 得到 256Hz 的计数频率 |
| 计数模式 | 连续计数 | 支持多次唤醒 |
| 中断优先级 | 最低 | 避免影响其他中断 |
/* LPTIM1 初始化示例 */
void MX_LPTIM1_Init(void)
{
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_LSE;
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_128;
hlptim1.Init.CounterSource = LPTIM_COUNTERSOURCE_INTERNAL;
hlptim1.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
hlptim1.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
HAL_LPTIM_Init(&hlptim1);
}
4.5 验证 Tickless 是否生效
配置完了,怎么知道它真的在睡?我一般用三种方法验证:
- 电流测量:用万用表或功耗分析仪看平均电流。STOP 模式下 STM32L4 应该降到 µA 级别。
- GPIO 示波器:在睡前拉高一个 GPIO,醒后拉低。用示波器看脉冲宽度,就是实际睡眠时间。
- FreeRTOS 统计:调用
vTaskGetRunTimeStats()看空闲任务占比。
/* 调试用:在睡前醒后翻转 GPIO */
void PreSleepProcessing(uint32_t ulExpectedIdleTime)
{
HAL_GPIO_WritePin(DEBUG_GPIO_Port, DEBUG_Pin, GPIO_PIN_SET); // 拉高
// ... 其他操作
}
void PostSleepProcessing(uint32_t ulExpectedIdleTime)
{
HAL_GPIO_WritePin(DEBUG_GPIO_Port, DEBUG_Pin, GPIO_PIN_RESET); // 拉低
// ... 其他操作
}
关键指标对照:
- 正常 Tick 模式:电流约 1~5 mA(视外设而定)
- Tickless + STOP 模式:电流约 5~50 µA
- 理想情况(无外设):可低至 1 µA 以下
4.6 常见问题与避坑
这部分是我最想说的。Tickless 配置本身不难,但坑都在细节里。
- 唤醒后系统时钟不对:STOP 模式唤醒后,HCLK 默认是 MSI。记得在
PostSleepProcessing里重新调用SystemClock_Config()。 - 外设醒来后不工作:有些外设(如 USART、I2C)在 STOP 模式下会丢失配置,需要重新初始化。我习惯把所有外设的初始化函数在醒后都调一遍。
- 中断丢失:如果睡眠期间有中断发生,系统会立即唤醒。但要注意,某些中断标志位需要在醒后手动清除。
- LPTIM 精度问题:LSE 的精度很高,但 LSI 受温度影响较大。如果产品工作环境温度变化大,建议用 LSE。