4. LPTIM 初始化流程:时钟使能、GPIO 配置、定时器参数配置、中断配置、启动定时器
好,咱们直接进入正题。LPTIM 的初始化,说白了就是五步走:时钟使能 → GPIO 配置 → 定时器参数配置 → 中断配置 → 启动定时器。顺序基本固定,但每一步都有坑。我刚开始用 LPTIM 的时候,就因为在时钟使能这一步少配了一个 bit,折腾了大半天。下面我带你一步步拆开看。
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);
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);
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);
比如上面: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); // 自动重装载中断
4.5 启动定时器 —— 最后一步,别搞反顺序
启动 LPTIM 很简单,调用 HAL_LPTIM_TimeOut_Start_IT() 或 HAL_LPTIM_TimeOut_Start() 即可。但顺序有讲究:先配置,再启动。如果你先启动了定时器,再改参数,LPTIM 可能会直接忽略你的配置。
我个人习惯的完整启动流程:
- 调用
HAL_LPTIM_Init()完成参数配置 - 调用
HAL_LPTIM_TimeOut_Start_IT()启动并开启中断 - 在中断回调函数
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 完整初始化流程图
下面这张图把整个流程串起来了,你可以对照着写代码,不容易漏步骤。
这张图里,每一步都是串行的,不能跳步。尤其是时钟使能,它是后面所有配置的基础。我见过有人先配 GPIO 再开时钟,结果 GPIO 的复用功能没生效——因为时钟没开,寄存器写不进去。