STM32低功耗异步脉冲计数:LPTIM配置技巧,告别频繁唤醒
👁 3 阅读 · 2026-08-27 · 嵌入式
在电池供电的嵌入式系统中,低功耗是核心需求。当需要对外部脉冲(如流量计、编码器)进行计数时,若采用主控频繁唤醒的方式,功耗将急剧上升。本文深入探讨如何利用STM32的LPTIM(低功耗定时器)在停机模式下异步计数脉冲,仅在达到阈值时唤醒CPU,实现微安级待机与精准计数的完美平衡。你将掌握LPTIM的硬件连接、关键寄存器配置、完整代码示例及避坑指南。
# STM32 低功耗模式下使用 LPTIM 进行异步脉冲计数:配置技巧与实战
在物联网传感器、智能水表等电池供电设备中,主控MCU大部分时间应处于低功耗模式。若需对外部脉冲(如流量计、风速计)计数,传统做法是让MCU定期唤醒并读取GPIO状态,这会导致频繁唤醒,功耗高达数十微安甚至毫安级。而STM32的LPTIM(Low-Power Timer)专为此场景设计,它能在停机(Stop)模式下由外部信号异步驱动,独立于CPU运行,仅在计数溢出或比较匹配时唤醒CPU。本文将详解其原理与配置。
## 一、LPTIM异步计数原理
LPTIM是一个16位低功耗定时器,其核心特性是:
- **异步时钟源**:可使用外部时钟(如LSE 32.768kHz)或外部触发信号(如GPIO)作为计数时钟,无需系统时钟(HSI/HSE)。
- **低功耗运行**:在CPU进入Stop模式后,LPTIM仍可依靠LSE或外部信号继续运行。
- **事件唤醒**:当计数值达到预设的自动重载值(ARR)或比较值(CMP)时,可产生中断并唤醒CPU。
对于脉冲计数,我们通常将外部脉冲信号连接到LPTIM的输入端(如LPTIM1_IN1),配置为外部时钟源模式。每个脉冲上升沿(或下降沿)触发一次计数,当计数值等于ARR时,产生唤醒事件。这样,CPU只需在每N个脉冲后醒来处理一次,功耗大幅降低。
## 二、硬件连接与引脚选择
以STM32L4系列为例,LPTIM1的输入引脚通常为:
- LPTIM1_IN1:PC0(或PA6,取决于封装)
- LPTIM1_IN2:PC1(或PA7)
请查阅具体型号的数据手册,确认引脚复用功能。硬件上,外部脉冲信号需经过电平转换(如3.3V逻辑)后直接连接至输入引脚,无需额外上拉(内部可配置)。注意脉冲频率应低于LPTIM最大计数频率(通常为LSE/2或外部信号最大频率)。
## 三、关键寄存器配置步骤
配置分为初始化、低功耗模式设置、中断处理三部分。
### 1. 初始化LPTIM(正常模式)
```c
// 使能LPTIM时钟(以LPTIM1为例)
__HAL_RCC_LPTIM1_CLK_ENABLE();
// 配置LPTIM输入引脚(PC0)为复用功能
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 根据信号类型选择
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_LPTIM1; // 查手册确认AF号
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 配置LPTIM参数
LPTIM_HandleTypeDef hlptim1;
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK; // 初始化时用APB时钟,便于配置
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE; // 软件触发启动
hlptim1.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
hlptim1.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim1.Init.CounterSource = LPTIM_COUNTERSOURCE_EXTERNAL; // 外部脉冲计数
hlptim1.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO; // 输入1来自GPIO
hlptim1.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
HAL_LPTIM_Init(&hlptim1);
// 设置自动重载值(例如每100个脉冲唤醒一次)
HAL_LPTIM_SetAutoreload(&hlptim1, 100);
// 使能比较中断(可选,若需在特定值中断)
HAL_LPTIM_Compare_Set(&hlptim1, 50); // 比较值50
HAL_LPTIM_Compare_Start_IT(&hlptim1, 50); // 使能比较中断
```
### 2. 进入低功耗模式前切换时钟源
为了在Stop模式下继续计数,需将LPTIM时钟切换为LSE(或外部时钟)。在进入Stop模式前,执行:
```c
// 使能LSE时钟(若未开启)
__HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET); // 等待就绪
// 将LPTIM时钟源切换为LSE
__HAL_RCC_LPTIM_CONFIG(RCC_LPTIMCLKSOURCE_LSE);
// 重新初始化LPTIM,使用LSE时钟
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_LSE;
HAL_LPTIM_Init(&hlptim1);
// 重新设置ARR和比较值(因为时钟变了,计数频率不同)
HAL_LPTIM_SetAutoreload(&hlptim1, 100);
HAL_LPTIM_Compare_Set(&hlptim1, 50);
// 启动LPTIM计数(软件触发)
HAL_LPTIM_Counter_Start(&hlptim1, 100); // 启动并设置ARR
```
### 3. 进入Stop模式并等待唤醒
```c
// 使能LPTIM中断(在NVIC中)
HAL_NVIC_SetPriority(LPTIM1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(LPTIM1_IRQn);
// 进入Stop模式(使用WFI)
HAL_SuspendTick(); // 暂停SysTick,避免唤醒
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后恢复
HAL_ResumeTick();
SystemClock_Config(); // 重新配置系统时钟(因为Stop模式可能关闭HSE)
```
### 4. 中断服务函数
```c
void LPTIM1_IRQHandler(void)
{
HAL_LPTIM_IRQHandler(&hlptim1);
}
void HAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
// 处理计数达到比较值的事件
uint32_t count = HAL_LPTIM_ReadCounter(hlptim); // 读取当前计数值
// 例如:累计脉冲数,更新状态,发送数据等
// 注意:此处应快速处理,避免长时间占用
}
void HAL_LPTIM_AutoReloadMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
// 处理计数达到ARR的事件(如果使能了自动重载中断)
}
```
## 四、完整代码示例(基于STM32L4 HAL库)
以下是一个完整示例,实现每100个脉冲唤醒一次,并累计计数。
```c
#include "stm32l4xx_hal.h"
LPTIM_HandleTypeDef hlptim1;
volatile uint32_t pulse_count = 0; // 累计脉冲数
void LPTIM1_Init(void)
{
// 使能GPIOA和LPTIM时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_LPTIM1_CLK_ENABLE();
// PC0 -> LPTIM1_IN1
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_LPTIM1;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 初始化LPTIM(先使用APB时钟)
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK;
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim1.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
hlptim1.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim1.Init.CounterSource = LPTIM_COUNTERSOURCE_EXTERNAL;
hlptim1.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
hlptim1.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
HAL_LPTIM_Init(&hlptim1);
// 使能LSE时钟
__HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET);
// 切换LPTIM时钟源为LSE
__HAL_RCC_LPTIM_CONFIG(RCC_LPTIMCLKSOURCE_LSE);
// 重新初始化LPTIM(使用LSE)
HAL_LPTIM_DeInit(&hlptim1);
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_LSE;
HAL_LPTIM_Init(&hlptim1);
// 设置ARR为100,比较值为50(可选)
HAL_LPTIM_SetAutoreload(&hlptim1, 100);
HAL_LPTIM_Compare_Set(&hlptim1, 50);
// 使能比较中断
HAL_LPTIM_Compare_Start_IT(&hlptim1, 50);
// 启动计数(软件触发)
HAL_LPTIM_Counter_Start(&hlptim1, 100);
// 使能NVIC中断
HAL_NVIC_SetPriority(LPTIM1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(LPTIM1_IRQn);
}
void EnterStopMode(void)
{
HAL_SuspendTick();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
HAL_ResumeTick();
SystemClock_Config(); // 重新配置系统时钟
}
void LPTIM1_IRQHandler(void)
{
HAL_LPTIM_IRQHandler(&hlptim1);
}
void HAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
pulse_count += 50; // 每50个脉冲触发一次(因为比较值50)
// 注意:实际计数可能因ARR溢出而丢失,需结合ARR中断处理
// 此处简化,实际应读取计数器并计算
}
int main(void)
{
HAL_Init();
SystemClock_Config();
LPTIM1_Init();
while (1)
{
// 进入Stop模式,等待脉冲计数达到比较值唤醒
EnterStopMode();
// 唤醒后处理业务,例如发送数据、更新显示等
// 注意:此时LPTIM仍在计数,但CPU已运行
}
}
```
## 五、注意事项与优化技巧
- **时钟源选择**:LSE(32.768kHz)是常用选择,但LPTIM的最大计数频率有限(通常为LSE/2),若脉冲频率过高,需使用外部时钟直接输入(如LPTIM_IN1作为时钟),但此时无法使用LSE,需权衡。
- **计数溢出处理**:LPTIM是16位,最大计数65535。若脉冲数可能超过,需在中断中读取计数器并累加,或使用ARR中断(每65535个脉冲唤醒一次)来避免溢出丢失。
- **中断唤醒延迟**:从Stop模式唤醒到CPU执行中断服务函数有延迟(通常几微秒),若脉冲频率极高,可能丢失脉冲。建议脉冲频率低于1kHz。
- **GPIO滤波**:外部脉冲可能含有毛刺,可在GPIO配置中启用数字滤波(如使用LPTIM的输入滤波器),但会增加功耗和延迟。
- **调试技巧**:在调试时,可暂时禁用低功耗模式,用逻辑分析仪观察LPTIM输出(如MFO引脚)来验证计数。
- **功耗优化**:在Stop模式下,关闭不必要的外设时钟,将未使用的GPIO设为模拟输入或低功耗状态。
## 六、总结
通过LPTIM的异步计数功能,STM32可以在Stop模式下保持对脉冲的精准计数,仅在达到预设阈值时唤醒CPU,将平均功耗降至微安级。本文提供了从原理到代码的完整指南,帮助开发者快速实现低功耗脉冲计数方案。在实际项目中,还需根据具体脉冲特性(频率、电平)调整配置,并充分测试边界情况。