4. LPTIM 初始化流程:时钟使能、GPIO 配置、定时器参数配置、中断配置、启动定时器

好,咱们直接进入正题。LPTIM 的初始化,说白了就是五步走:时钟使能 → GPIO 配置 → 定时器参数配置 → 中断配置 → 启动定时器。顺序基本固定,但每一步都有坑。我刚开始用 LPTIM 的时候,就因为在时钟使能这一步少配了一个 bit,折腾了大半天。下面我带你一步步拆开看。

核心原则:LPTIM 是低功耗定时器,它的时钟源和主时钟是分开的。初始化时,必须先确保它的时钟已经稳定,否则后续配置全是白搭。

4.1 时钟使能 —— 别让 LPTIM 饿着

LPTIM 挂在 APB 总线上,但它有独立的时钟选择逻辑。我个人习惯,先把 LPTIM 的接口时钟(APB 时钟)使能,再配置它的内核时钟源。

看代码:

// 使能 LPTIM1 的 APB 接口时钟
__HAL_RCC_LPTIM1_CLK_ENABLE();

// 选择 LPTIM1 的内核时钟源为 LSI(约 32 kHz)
// 注意:这一步在 STM32L4 中通过 RCC_CCIPR 寄存器配置
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_LPTIM1;
PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSI;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
我曾经踩过的坑:只使能了 APB 时钟,忘了配置内核时钟源。结果 LPTIM 的计数器根本不走,读寄存器全是 0。后来用示波器量 LPTIM 的输出引脚,才发现它根本没收到时钟脉冲。记住:APB 时钟是给寄存器接口用的,内核时钟才是给计数器用的,两个都得配。

4.2 GPIO 配置 —— 输出引脚要选对

如果你要用 LPTIM 输出 PWM 或单脉冲,那 GPIO 必须配成复用功能。STM32L4 的 LPTIM 输出引脚通常是固定的,比如 LPTIM1_OUT 在 PA0 或 PB5 上。你想想看,如果引脚选错了,代码写得再漂亮也白搭。

配置步骤:

  • 使能 GPIO 时钟(比如 GPIOA)
  • 设置引脚模式为复用推挽输出
  • 选择复用功能为 LPTIM1_OUT(AF1 或 AF2,具体看数据手册)
  • 设置上拉/下拉(一般无上拉下拉即可)
// 以 PA0 作为 LPTIM1_OUT 为例
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;  // 低功耗场景,低速即可
GPIO_InitStruct.Alternate = GPIO_AF1_LPTIM1;   // 查手册确认 AF 号
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
小技巧:如果只是用 LPTIM 做内部定时(不输出波形),GPIO 这步可以跳过。但为了调试方便,我建议还是把输出引脚配出来,关键时刻用示波器看一眼波形,比读寄存器快多了。

4.3 定时器参数配置 —— 算好你的时间

这是核心环节。LPTIM 的配置参数不多,但每个都跟功耗和精度直接相关。我一般用 HAL 库的 HAL_LPTIM_Init() 函数,传入一个 LPTIM_HandleTypeDef 结构体。

关键参数:

参数 说明 我的建议
Instance 选择 LPTIM1 / LPTIM2 / LPTIM3 优先用 LPTIM1,它时钟源选择最多
Clock.Source LPTIM_CLOCKSOURCE_APB / LSI / LSE / HSI16 低功耗场景首选 LSI(32 kHz)
Clock.Prescaler 分频系数:1/2/4/8/16/32/64/128 根据目标频率选,分频越大越省电
Trigger.Source 软件触发或硬件触发 简单定时用软件触发即可
Trigger.ActiveEdge 上升沿/下降沿触发 默认上升沿
UltraLowPowerMode ENABLE / DISABLE 低功耗场景务必 ENABLE

举个例子,我要产生一个 1 秒的定时中断:

LPTIM_HandleTypeDef hlptim1;
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_LSI;   // 32 kHz
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV32; // 32 kHz / 32 = 1 kHz
hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim1.Init.Trigger.ActiveEdge = LPTIM_ACTIVEEDGE_RISING;
hlptim1.Init.UltraLowPowerMode = LPTIM_UFLP_ENABLE;
HAL_LPTIM_Init(&hlptim1);

// 设置自动重装载值:1 kHz 时钟,计 1000 次 = 1 秒
HAL_LPTIM_TimeOut_Start_IT(&hlptim1, 1000, 0);
算时间公式:定时时间 = (Prescaler × (Autoreload + 1)) / Clock_Frequency
比如上面:32 × 1000 / 32000 = 1 秒。简单吧?

4.4 中断配置 —— 别让中断抢了 CPU 的睡眠

LPTIM 的中断配置分两步:一是使能 NVIC 中断,二是使能 LPTIM 内部的中断源。

嗯,这里要注意:LPTIM 的中断优先级建议设得高一点,因为它要在低功耗模式下唤醒 CPU。如果优先级太低,可能被其他中断堵住,导致唤醒延迟。

// 使能 LPTIM1 全局中断
HAL_NVIC_SetPriority(LPTIM1_IRQn, 1, 0);  // 抢占优先级 1,子优先级 0
HAL_NVIC_EnableIRQ(LPTIM1_IRQn);

// 在 HAL_LPTIM_TimeOut_Start_IT() 内部会自动使能超时中断
// 如果手动配,可以调用:
// __HAL_LPTIM_ENABLE_IT(&hlptim1, LPTIM_IT_ARRM);  // 自动重装载中断
我曾经犯过的错:在低功耗模式下(比如 STOP 模式),LPTIM 的中断可以唤醒 MCU。但我忘了在进入 STOP 模式前把 LPTIM 的中断使能打开,结果定时到了,MCU 死活不醒。后来查勘误手册才发现,LPTIM 的中断唤醒功能需要额外配置 EXTI 线。具体做法是把 LPTIM 的输出事件连接到 EXTI,再使能 EXTI 中断。这部分我会在后面的章节细讲,这里先提个醒。

4.5 启动定时器 —— 最后一步,别搞反顺序

启动 LPTIM 很简单,调用 HAL_LPTIM_TimeOut_Start_IT() 或 HAL_LPTIM_TimeOut_Start() 即可。但顺序有讲究:先配置,再启动。如果你先启动了定时器,再改参数,LPTIM 可能会直接忽略你的配置。

我个人习惯的完整启动流程:

  1. 调用 HAL_LPTIM_Init() 完成参数配置
  2. 调用 HAL_LPTIM_TimeOut_Start_IT() 启动并开启中断
  3. 在中断回调函数 HAL_LPTIM_TimeOutCallback() 中处理业务
// 启动 LPTIM1,自动重装载值 1000,无触发延时
HAL_LPTIM_TimeOut_Start_IT(&hlptim1, 1000, 0);

// 中断回调函数
void HAL_LPTIM_TimeOutCallback(LPTIM_HandleTypeDef *hlptim)
{
    if (hlptim->Instance == LPTIM1)
    {
        // 1 秒到了,做你该做的事
        // 比如翻转一个 LED,或者唤醒 MCU
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}
调试小技巧:启动后,可以读一下 hlptim1.Instance->CNT 寄存器,看看计数器是不是在跑。如果一直是 0,说明时钟没过来,回头检查时钟配置。如果数值在变化,恭喜你,LPTIM 活了。

4.6 完整初始化流程图

下面这张图把整个流程串起来了,你可以对照着写代码,不容易漏步骤。

LPTIM 初始化流程图 LPTIM 初始化完整流程 ① 时钟使能 ② GPIO 配置(可选) ③ 定时器参数配置 ④ 中断配置 ⑤ 启动定时器 ✅ 正常运行 注意:进入低功耗模式前,需额外配置 EXTI 线使能 LPTIM 唤醒功能

这张图里,每一步都是串行的,不能跳步。尤其是时钟使能,它是后面所有配置的基础。我见过有人先配 GPIO 再开时钟,结果 GPIO 的复用功能没生效——因为时钟没开,寄存器写不进去。