1. STM32基本定时器概述
在嵌入式开发领域,定时器是最基础也是最核心的外设之一。STM32系列微控制器提供了丰富多样的定时器资源,其中基本定时器(Basic Timer)是最简单但应用最广泛的一类。作为STM32入门学习的第六个关键节点,掌握基本定时器的原理和使用方法,将为后续更复杂的外设开发打下坚实基础。
基本定时器在STM32中通常指TIM6和TIM7(具体型号可能有所不同),它们具有以下核心特点:
- 16位自动重装载计数器
- 16位可编程预分频器
- 触发DAC的同步电路
- 在更新事件时产生中断/DMA请求
与通用定时器相比,基本定时器没有输入捕获和输出比较功能,结构更简单,非常适合用于时基生成、定时中断触发等基础场景。在实际项目中,我经常用它来实现精确延时、周期性任务调度、PWM波生成的时间基准等。
2. 基本定时器工作原理详解
2.1 时钟源与预分频机制
STM32基本定时器的时钟通常来自APB1总线(低速外设总线)。时钟信号进入定时器后首先经过预分频器,这个设计允许我们对高速时钟进行分频处理,从而获得更长的定时周期。
预分频器的计算公式为:
code复制定时器时钟 = APB1时钟 / (预分频值 + 1)
例如,当APB1时钟为36MHz,预分频值设为35时:
code复制36MHz / (35+1) = 1MHz
这意味着计数器每1μs计数一次。
注意:STM32的APB1预分频器存在一个特殊设计——当APB1预分频系数不为1时,定时器时钟会倍频。例如APB1分频系数为2时,定时器时钟实际为APB1时钟的2倍。这点在计算时需要特别注意。
2.2 计数器与自动重装载
基本定时器采用向上计数模式,从0开始计数到自动重装载值(ARR),然后产生更新事件并重新从0开始计数。这个过程的定时周期计算公式为:
code复制定时周期 = (ARR + 1) × (PSC + 1) / 定时器时钟频率
其中:
- ARR:自动重装载值(Auto-Reload Register)
- PSC:预分频值(Prescaler)
例如,要实现1ms的定时中断:
- 时钟源:36MHz APB1
- 预分频PSC设为35 → 定时器时钟=1MHz
- ARR设为999 → (999+1)×(35+1)/36MHz = 1ms
2.3 中断与DMA触发
当计数器达到重装载值时,硬件会设置更新中断标志位(UIF)。如果开启了更新中断,就会触发中断服务程序。此外,基本定时器还可以配置为在更新事件时触发DMA请求,这在需要定期传输数据的场景非常有用。
3. 基本定时器配置实战
3.1 硬件环境准备
以STM32F103C8T6(蓝桥杯常用开发板)为例:
- 使用TIM6基本定时器
- 目标功能:实现500ms周期性LED闪烁
- 硬件连接:PC13接LED(开发板通常内置)
3.2 CubeMX配置步骤
- 打开CubeMX,选择对应型号
- 在Pinout & Configuration标签页中激活TIM6
- 配置参数:
- Prescaler (PSC): 35999
- Counter Mode: Up
- Counter Period (ARR): 4999
- auto-reload preload: Enable
- 开启TIM6中断:
- NVIC Settings中勾选TIM6 global interrupt
- 生成代码
计算验证:
- APB1时钟=36MHz
- 预分频后时钟=36MHz/(35999+1)=1kHz
- 定时周期=(4999+1)/1kHz=500ms
3.3 代码实现关键点
在生成的工程中,需要补充以下代码:
c复制/* 在main.c的USER CODE BEGIN 2部分启动定时器 */
HAL_TIM_Base_Start_IT(&htim6);
/* 在stm32f1xx_it.c中实现中断服务程序 */
void TIM6_IRQHandler(void) {
HAL_TIM_IRQHandler(&htim6);
}
/* 在main.c中重写回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM6) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
3.4 常见问题排查
-
定时不准:
- 检查APB1时钟配置是否正确
- 确认没有在其他地方修改了定时器时钟
- 使用逻辑分析仪测量实际输出
-
中断不触发:
- 确认NVIC中已使能定时器中断
- 检查中断优先级设置是否合理
- 确保没有在其他地方关闭了全局中断
-
代码进入Error_Handler:
- 检查定时器参数是否超出范围
- 确认定时器时钟已使能(__HAL_RCC_TIM6_CLK_ENABLE())
4. 进阶应用技巧
4.1 微秒级延时实现
虽然HAL库提供了HAL_Delay()函数,但它基于SysTick且精度不高。利用基本定时器可以实现更精确的微秒级延时:
c复制void delay_us(uint16_t us) {
__HAL_TIM_SET_COUNTER(&htim6, 0);
while(__HAL_TIM_GET_COUNTER(&htim6) < us);
}
使用前提:
- 定时器已配置为1MHz时钟(1us计数一次)
- 不与其他定时功能冲突
4.2 多任务时间片调度
通过合理设置定时器中断周期,可以实现简单的多任务调度:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
static uint8_t counter = 0;
counter++;
if(counter % 5 == 0) task1(); // 每5个周期执行一次
if(counter % 2 == 0) task2(); // 每2个周期执行一次
task3(); // 每个周期都执行
}
4.3 定时器级联应用
当需要更长定时周期时,可以将多个定时器级联使用。例如用TIM6中断来计数,每N次中断视为一个更长周期:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
static uint16_t second_counter = 0;
if(++second_counter >= 1000) { // 假设TIM6配置为1ms中断
second_counter = 0;
one_second_task();
}
}
5. 性能优化与注意事项
-
中断响应优化:
- 保持中断服务程序尽可能简短
- 避免在中断中进行复杂计算或函数调用
- 使用标志位+主循环处理模式
-
低功耗考虑:
- 不需要定时器时及时关闭时钟
- 在睡眠模式下,选择能唤醒MCU的定时器
- 合理配置预分频降低运行频率
-
调试技巧:
- 利用定时器的更新事件触发调试引脚
- 使用__HAL_TIM_GET_FLAG()检查状态
- 在调试器中监控CNT寄存器值
-
HAL库替代方案:
对于性能敏感的应用,可以直接操作寄存器:c复制TIM6->PSC = 35999; TIM6->ARR = 4999; TIM6->CR1 |= TIM_CR1_CEN; // 启动定时器 TIM6->DIER |= TIM_DIER_UIE; // 使能更新中断
通过本专题的系统学习,开发者应该能够熟练掌握STM32基本定时器的各项功能和应用场景。在实际项目中,建议先从基本定时器入手,等完全掌握后再过渡到更复杂的通用定时器和高级定时器。