1. 定时器基础:从软件延时至硬件精准计时
第一次接触STM32定时器时,我完全被那些专业术语搞懵了。后来才发现,理解定时器最好的方式就是从我们最熟悉的软件延时开始对比。还记得刚学单片机时写的那个经典延时函数吗?
c复制void delay_us(uint32_t us) {
us *= 72;
while(us--);
}
这个函数虽然简单,但存在三个致命问题:首先CPU完全被占用,不能执行其他任务;其次延时精度受系统时钟和编译器优化影响;最后在多任务系统中会引发调度问题。而硬件定时器完美解决了这些痛点。
STM32的定时器本质是一个带自动重载功能的计数器,它的核心工作原理就像沙漏:
- 时钟源:相当于沙漏的上部空间,提供稳定的时基(通常来自系统时钟)
- 预分频器(PSC):控制沙子流动速度,72MHz时钟分频1000后得到72KHz
- 计数器(CNT):记录流下的沙子数量,每个时钟脉冲加1
- 自动重载寄存器(ARR):沙漏翻转时的刻度值,达到这个值就触发中断并重置
计算定时时间的公式很简单:
code复制定时时间 = (ARR + 1) × (PSC + 1) / 时钟频率
举个例子,要实现1秒定时(时钟72MHz):
- 设置PSC=7199(7200分频)→ 得到10KHz
- 设置ARR=9999(10000计数)→ 10KHz下计满10000次正好1秒
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基本定时器实战:精准LED闪烁
基本定时器(TIM6/TIM7)是STM32定时器家族中最简单的成员,特别适合初学者入门。我以TIM6实现500ms定时中断为例,分享具体配置步骤:
- 初始化结构体配置:
c复制TIM_HandleTypeDef htim6;
htim6.Instance = TIM6;
htim6.Init.Prescaler = 7199; // 72MHz/(7199+1)=10KHz
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 4999; // 10KHz下5000次计数=500ms
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
- 中断配置技巧:
- 在HAL_TIM_Base_MspInit()中使能时钟和NVIC
- 建议将定时器中断优先级设置为中等(如优先级分组4,子优先级0)
- 更新中断使能要放在HAL_TIM_Base_Start_IT()之前
- 中断服务函数:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM6) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
实际调试时我踩过一个坑:如果发现定时不准,检查以下三点:
- 确认系统时钟配置正确(使用HAL_RCC_GetSysClockFreq()验证)
- 检查APB1总线时钟是否经过倍频(STM32F1系列有×2的情况)
- 确保没有在其他地方修改了预分频值
3. 通用定时器进阶:PWM呼吸灯实战
通用定时器(TIM2-TIM5)的强大之处在于PWM生成能力。我曾用TIM3制作呼吸灯效果,关键配置如下:
PWM模式配置要点:
c复制TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 250; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
动态调节占空比:
c复制// 在main循环中渐变占空比
for(int i=0; i<=500; i++) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(10);
}
硬件连接注意事项:
- 确认GPIO已配置为复用推挽输出
- 检查TIMx_CHy通道与GPIO的映射关系(参考芯片数据手册)
- 对于大功率LED,建议增加MOS管驱动电路
实测发现,PWM频率选择有讲究:
- 低于100Hz会出现肉眼可见的闪烁
- 1-5KHz适合普通LED调光
- 20KHz以上可消除可闻噪声(用于电机控制)
4. 高级定时器应用:电机控制三要素
高级定时器(TIM1/TIM8)在电机控制中大显身手,主要体现在三个核心功能:
4.1 互补输出与死区时间
H桥电路需要互补的PWM信号,但功率器件开关需要时间,这时死区时间就至关重要。配置代码示例:
c复制TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
sBreakDeadTimeConfig.DeadTime = 100; // 约5.56us
sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
4.2 刹车功能
紧急情况下需要立即停止PWM输出,刹车功能就是为此设计的:
c复制// 配置刹车输入引脚(通常连接过流检测电路)
// 发生刹车时自动关闭输出,并触发中断
void HAL_TIMEx_BreakCallback(TIM_HandleTypeDef *htim) {
// 执行安全处理程序
}
4.3 重复计数器
输出指定数量的PWM脉冲在步进电机控制中很实用:
c复制htim1.Init.RepetitionCounter = 99; // 输出100个脉冲后停止
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
在无刷电机控制项目中,我将这三个功能结合使用:
- 用PWM通道驱动上桥臂
- 用互补输出驱动下桥臂
- 设置2us死区时间防止直通
- 电流检测引脚连接刹车输入
- 重复计数器实现精准步进控制
5. 定时器组合应用技巧
实际项目中,我经常组合使用多个定时器实现复杂功能:
案例1:高精度脉冲捕获
- 使用TIM2输入捕获模式测量脉冲宽度
- 配合TIM6作为基准时钟源
- 通过定时器级联实现32位扩展计数
案例2:多通道同步采样
- TIM1主模式输出触发信号
- TIM3/TIM4从模式同步启动
- 配合ADC实现多通道同步采样
案例3:编码器接口
c复制TIM_Encoder_InitTypeDef sConfig;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
HAL_TIM_Encoder_Init(&htim4, &sConfig);
这种配置下,定时器会自动根据编码器脉冲方向和数量更新计数值。
调试多定时器系统时,我总结了几点经验:
- 使用示波器观察各个定时器的触发时序
- 注意定时器时钟来源(APB1/APB2总线)
- 复杂系统建议使用时基单元生成时序图
- 关键操作放在定时器中断回调函数中执行
