1. STM32定时器基础与HAL库优势
第一次接触STM32定时器时,我被密密麻麻的寄存器配置吓到了。直到发现STM32CubeMX+HAL库这个黄金组合,才真正体会到什么叫"解放生产力"。定时器作为STM32最常用的外设之一,不仅能实现精准延时,还能玩转PWM、输入捕获等高级功能。
STM32的定时器分为三大类:基本定时器(TIM6/TIM7)、通用定时器(TIM2-TIM5)和高级定时器(TIM1/TIM8)。就拿最常见的通用定时器来说,它就像个多功能瑞士军刀:
- 16位向上/向下计数器(最大65535)
- 可编程预分频器(1-65536分频)
- 4个独立通道支持PWM输出
- 硬件自动重装载机制
HAL库的妙处在于,它用HAL_TIM_Base_Start_IT()这样的函数封装了底层寄存器操作。我实测过,用标准外设库要写20行的初始化代码,HAL库3行就能搞定。特别是在中断处理上,HAL库的回调函数机制让代码更整洁——你只需要重写HAL_TIM_PeriodElapsedCallback(),不用再纠结中断标志位清除的问题。
2. CubeMX定时器配置实战
打开CubeMX新建工程时,建议直接选择"Access to MCU Selector"搜索你的芯片型号。我常用STM32F103C8T6,它的TIM2-TIM4都是通用定时器。配置过程就像搭积木:
2.1 时钟树配置
在Clock Configuration标签页,你会看到类似蜘蛛网的时钟树。以72MHz主频为例:
- 在HSE输入框输入外部晶振频率(比如8MHz)
- 将PLLMUL设为9倍频
- 系统时钟源选择PLLCLK
- APB1分频系数设为2(此时定时器时钟=72MHz)
关键点:APB1总线上的定时器(如TIM2)有个隐藏福利——当APB1分频系数不为1时,定时器时钟会自动×2。这就是为什么APB1显示36MHz,但TIM2实际获得72MHz时钟。
2.2 定时器参数设置
转到Timers标签选择TIM2,配置界面主要关注这几个参数:
- Prescaler:预分频值(实际分频系数=Prescaler+1)
- Counter Mode:计数模式(Up/Down/Center-aligned)
- Counter Period:自动重载值(ARR)
- auto-reload preload:建议Enable
假设要实现1ms定时,计算公式为:
定时时间 = (Prescaler+1)*(Counter Period+1)/时钟频率代入72MHz时钟:
(719+1)*(99+1)/72000000 = 0.001s所以Prescaler填719,Counter Period填99。
3. 中断配置与代码实战
3.1 中断优先级设置
在NVIC Settings中勾选"TIM2 global interrupt"。优先级设置有个经验法则:
- 系统滴答定时器(SysTick)设为最高优先级
- 通信接口(USART/I2C)设为中优先级
- 普通定时器设为最低优先级
我曾经因为没设优先级,导致USART数据接收被定时器中断打断,出现数据丢失。血的教训啊!
3.2 代码生成与移植
点击GENERATE CODE生成工程后,重点看这两个函数:
// 在main.c的初始化部分添加 HAL_TIM_Base_Start_IT(&htim2); // 在用户代码区重写回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2){ static uint32_t tick = 0; tick++; if(tick >= 1000){ // 1秒到达 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); tick = 0; } } }避坑指南:
- 一定要用
HAL_TIM_Base_Start_IT()而非HAL_TIM_Base_Start(),后者不会开启中断 - 回调函数中要进行定时器实例判断,否则所有定时器中断都会触发该函数
- 中断服务函数中避免使用
HAL_Delay(),可能引发死锁
4. 高级应用与性能优化
4.1 微秒级延时实现
HAL库的HAL_Delay()最小单位是毫秒,要实现微秒延时可以这样改造:
void delay_us(uint16_t us) { __HAL_TIM_SET_COUNTER(&htim2, 0); while(__HAL_TIM_GET_COUNTER(&htim2) < us); }前提是配置TIM2为1MHz时钟(Prescaler=71,ARR=65535)。实测误差在±0.5us以内,比软件延时稳定多了。
4.2 定时器级联
遇到需要长定时的情况(如1小时),可以级联定时器。我曾用TIM3作主定时器,TIM4为从定时器:
- 在TIM3配置中开启"Trigger Output"
- 在TIM4的Slave Mode选择"External Clock Mode 1"
- TIM3每触发一次,TIM4计数加1
这样组合起来,32位计数器最大能计时约4000秒。有个项目需要记录设备运行时间,我就是用这个方法实现的。
4.3 低功耗优化
在电池供电项目中,可以这样优化定时器:
- 使用LPTIM(低功耗定时器)
- 在回调函数中唤醒MCU后立即进入STOP模式
- 将APB1时钟分频到最低
实测STM32L4系列采用此法,整机功耗可降至8μA以下。记得在进入低功耗前关闭不需要的外设时钟。