4. 低功耗定时器(LPTIM)应用:从原理到实战唤醒
低功耗定时器,简称 LPTIM,是低功耗设计里绕不开的一个外设。说白了,它就是在芯片大部分模块都睡大觉的时候,还能保持清醒、默默计时的那个“哨兵”。
我刚开始接触 CMS32 的低功耗设计时,有个很深的体会:很多工程师把精力都花在了怎么进 Deep Sleep,却忽略了怎么出来。结果呢?功耗是降下来了,但系统醒不过来,或者醒来不及时,整个产品就废了。LPTIM 就是解决这个“怎么出来”问题的关键。
4.1 LPTIM 工作原理:它凭什么能省电?
LPTIM 和普通定时器最大的区别,在于它的时钟源和电源域。
- 独立时钟域:LPTIM 可以在主时钟(HSE、HSI)都关闭的情况下,依靠低速时钟(LSI、LSE)继续工作。
- 低功耗电源域:在 Deep Sleep 模式下,VDD 域掉电,但 LPTIM 所在的 VDD_LP 域仍然供电。
- 异步操作:LPTIM 的计数逻辑与系统时钟完全异步,不需要等待总线时钟同步。
核心要点:LPTIM 的省电本质,是“用最少的晶体管、最慢的时钟,干最关键的活”。它不参与复杂运算,只负责数数,数到了就喊一嗓子。
我记得有一次评审一个同事的设计,他把 LPTIM 配成了 1ms 中断一次。我说你这不是浪费吗?LPTIM 的优势是长周期定时,你非让它干普通定时器的活,功耗优势全没了。他后来改成 1s 中断一次,系统平均电流降了 30%。
4.2 异步时钟源配置:选对时钟,事半功倍
LPTIM 的时钟源选择,直接决定了它的功耗和精度。CMS32 系列通常支持以下几种异步时钟源:
| 时钟源 | 典型频率 | 功耗 | 精度 | 适用场景 |
|---|---|---|---|---|
| LSI(内部低速振荡器) | 32 kHz / 128 kHz | 极低(~1 µA) | 低(±10%) | 普通唤醒、看门狗 |
| LSE(外部低速晶振) | 32.768 kHz | 低(~3 µA) | 高(±50 ppm) | RTC 校准、精确唤醒 |
| HSE/HSI 分频 | 可配置 | 高 | 高 | 调试、快速唤醒 |
我的习惯:量产项目我首选 LSE,虽然多一颗晶振,但精度和稳定性带来的好处远大于那点成本。如果对时间精度要求不高(比如只是每隔几秒唤醒一次),用 LSI 就够了,还能省一颗晶振的钱。
配置异步时钟源时,有个坑我必须提醒你:时钟切换必须在 LPTIM 停止状态下进行。我曾经在项目里犯过这个错——LPTIM 正在跑,我直接改了时钟选择位,结果计数器直接乱掉,系统提前唤醒,打乱了整个调度。
避坑指南:配置 LPTIM 时钟源的正确顺序是:
- 停止 LPTIM 计数(CLR CEN)
- 等待当前计数周期结束(检查 CNT 寄存器归零)
- 修改时钟源选择位(CFGR.CKSEL)
- 重新使能 LPTIM(SET CEN)
少一步都不行,别问我怎么知道的。
4.3 Deep Sleep 模式唤醒实战:让 LPTIM 当你的闹钟
好了,理论说完了,咱们直接上代码。下面是一个完整的实战案例:系统进入 Deep Sleep,LPTIM 每 10 秒唤醒一次,执行一次传感器采样,然后继续睡。
4.3.1 硬件连接
- LPTIM1 使用 LSE(32.768 kHz)作为时钟源
- 唤醒引脚:LPTIM1_OUT 连接到 EXTI 线
- 传感器:I2C 接口,挂载在 LPUART 域(Deep Sleep 不掉电)
4.3.2 代码实现
/* LPTIM 配置:10 秒唤醒周期 */
void LPTIM_Config(void)
{
/* 1. 使能 LPTIM 时钟 */
RCC->APB1ENR |= RCC_APB1ENR_LPTIM1EN;
/* 2. 选择 LSE 作为时钟源 */
RCC->BDCR |= RCC_BDCR_LSEON;
while(!(RCC->BDCR & RCC_BDCR_LSERDY)); // 等待 LSE 稳定
/* 3. 配置 LPTIM 参数 */
LPTIM1->CFGR = 0;
LPTIM1->CFGR |= (0x01 << LPTIM_CFGR_CKSEL_Pos); // LSE 时钟
LPTIM1->CFGR |= (0x00 << LPTIM_CFGR_PRESC_Pos); // 不分频
/* 4. 设置自动重载值:32768 Hz * 10s = 327680 */
LPTIM1->ARR = 327680 - 1;
/* 5. 使能比较匹配中断 */
LPTIM1->IER |= LPTIM_IER_CMPMIE;
/* 6. 使能 LPTIM */
LPTIM1->CR |= LPTIM_CR_ENABLE;
while(!(LPTIM1->ISR & LPTIM_ISR_CNTOK)); // 等待计数器就绪
/* 7. 启动计数 */
LPTIM1->CR |= LPTIM_CR_CNTSTRT;
}
/* 进入 Deep Sleep 并等待 LPTIM 唤醒 */
void EnterDeepSleep_WaitLPTIM(void)
{
/* 配置 LPTIM 中断为唤醒源 */
EXTI->IMR |= EXTI_IMR_IM23; // LPTIM1 比较匹配中断线
EXTI->RTSR |= EXTI_RTSR_TR23; // 上升沿触发
/* 进入 Deep Sleep */
PWR->CR1 |= PWR_CR1_LPDS; // 低功耗 Deep Sleep
PWR->CR1 |= PWR_CR1_PDDS; // 掉电模式
__WFI(); // 等待中断
/* 唤醒后:清除标志 */
LPTIM1->ICR |= LPTIM_ICR_CMPMCF;
EXTI->PR |= EXTI_PR_PR23;
}
/* 主循环 */
int main(void)
{
LPTIM_Config();
while(1)
{
/* 进入 Deep Sleep,等待 10 秒唤醒 */
EnterDeepSleep_WaitLPTIM();
/* 唤醒后:采样传感器 */
Sensor_ReadData();
ProcessData();
/* 继续睡... */
}
}
关键点解析:
CNTOK标志必须等待,否则 LPTIM 可能还没准备好就开始计数- EXTI 配置要匹配 LPTIM 的中断线号,不同芯片可能不同
- 唤醒后第一件事:清中断标志,否则会一直唤醒
4.3.3 实测数据
我在一个实际项目中用这套方案,测出来的数据如下:
| 模式 | 电流 | 说明 |
|---|---|---|
| Run(48 MHz) | 12.3 mA | 全速运行 |
| Sleep | 3.2 mA | CPU 停,外设跑 |
| Deep Sleep(LPTIM 唤醒) | 8.5 µA | LPTIM + LSE 运行 |
| Deep Sleep(无 LPTIM) | 2.1 µA | 只能外部中断唤醒 |
你看,LPTIM 只多花了 6.4 µA,就换来了 10 秒一次的自主唤醒能力。对于电池供电的产品来说,这个代价非常划算。
4.4 知识结构图:LPTIM 唤醒全流程
下面这张图把整个流程串起来了,你可以对照着看代码,理解每一步在干什么。
4.5 几个实用技巧
- 首次唤醒时间校准:LSE 起振需要时间(通常 1-2 秒),第一次配置后建议等待
LSERDY标志,不要急着进 Deep Sleep。 - 避免频繁唤醒:LPTIM 的功耗优势在长周期(秒级)才明显。如果你需要毫秒级唤醒,建议用普通定时器 + Sleep 模式,别用 Deep Sleep。
- 多通道利用:CMS32 的 LPTIM 通常有多个比较通道,可以设置不同的唤醒时间点,实现“先做 A,过一会再做 B”的时序。
我的小技巧:调试 LPTIM 唤醒时,我会先让 LPTIM 输出一个 PWM 波形到 GPIO,用示波器看实际周期对不对。确认无误后,再关掉 PWM,切到中断唤醒模式。这样能快速定位是配置问题还是硬件问题。
嗯,LPTIM 这块内容就讲到这里。你可能会问:如果我想在 Deep Sleep 下用多个唤醒源怎么办?比如 LPTIM 唤醒 + 按键唤醒?这个我们后面讲 EXTI 和 RTC 的时候会详细展开。现在先把 LPTIM 玩熟,它是低功耗唤醒的基石。