1. STM32定时器基础概念与分类
STM32系列微控制器内置了多种定时器资源,根据功能复杂度可分为三大类:
基本定时器(TIM6/TIM7):
- 16位向上计数器
- 仅支持定时功能
- 无外部IO接口
- 典型应用:系统时钟基准、简单延时
通用定时器(TIM2-TIM5):
- 16位可向上/向下计数
- 支持PWM输出、输入捕获
- 每个定时器4个外部IO
- 典型应用:电机控制、信号测量
高级定时器(TIM1/TIM8):
- 包含通用定时器所有功能
- 支持三相电机互补输出
- 每个定时器8个外部IO
- 典型应用:变频器、复杂PWM控制
注意:不同STM32子系列定时器配置可能不同,使用前需查阅对应型号的参考手册。
2. 基本定时器功能实现详解
2.1 时钟源配置
STM32定时器时钟源来自APB1总线(基本定时器挂载在APB1),时钟配置需注意:
// 使能TIM6时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);时钟路径: APB1预分频器 → 定时器时钟(TIMxCLK) → 预分频器(PSC) → 计数器时钟(CK_CNT)
计算公式: CK_CNT = TIMxCLK / (PSC + 1)
例如APB1时钟为36MHz,预分频系数2时: TIMxCLK = 36MHz × 2 = 72MHz 若PSC=71,则CK_CNT = 72MHz / (71+1) = 1MHz
2.2 定时器初始化结构体
标准库使用TIM_TimeBaseInitTypeDef结构体配置定时器:
typedef struct { uint16_t TIM_Prescaler; // 预分频值 (0-65535) uint16_t TIM_CounterMode; // 计数模式(基本定时器只能向上) uint32_t TIM_Period; // 自动重装载值(0-65535) uint16_t TIM_ClockDivision; // 时钟分频(基本定时器无此功能) uint8_t TIM_RepetitionCounter; // 重复计数器(高级定时器专用) } TIM_TimeBaseInitTypeDef;基本定时器只需配置TIM_Prescaler和TIM_Period两个成员。
2.3 定时时间计算
定时时间公式: T = (ARR + 1) × (PSC + 1) / TIMxCLK
示例:实现1ms定时
- TIMxCLK = 72MHz
- 设置PSC=71 → CK_CNT=1MHz
- 设置ARR=999 → 中断周期=(999+1)/1MHz=1ms
3. 完整代码实现与解析
3.1 硬件设计
本例使用TIM6实现1秒定时控制LED闪烁,硬件连接:
- LED连接任意GPIO(如PA5)
- 无需外部电路,仅使用MCU内部资源
3.2 软件实现
3.2.1 定时器配置
void TIM6_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 使能TIM6时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // 定时器基础配置 TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 71999; // 预分频值 TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); // 清除更新中断标志 TIM_ClearFlag(TIM6, TIM_FLAG_Update); // 使能更新中断 TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); // 启动定时器 TIM_Cmd(TIM6, ENABLE); }配置说明:
- 时钟72MHz,预分频71999 → CK_CNT=72MHz/72000=1kHz
- ARR=999 → 中断周期=1kHz/1000=1s
3.2.2 中断优先级配置
void TIM6_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }3.2.3 中断服务程序
volatile uint32_t timerCount = 0; // 全局计时变量 void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) { timerCount++; TIM_ClearITPendingBit(TIM6, TIM_FLAG_Update); } }3.2.4 主函数实现
int main(void) { // 初始化LED GPIO LED_Init(); // 配置定时器 TIM6_Config(); TIM6_NVIC_Config(); while (1) { if (timerCount >= 1) { // 1秒到达 timerCount = 0; LED_Toggle(); // 翻转LED状态 } } }4. 进阶应用与问题排查
4.1 精确延时实现
利用定时器实现微秒级延时函数:
void Delay_us(uint32_t us) { TIM6->CNT = 0; // 计数器清零 TIM6->ARR = us - 1; // 设置重装载值 TIM_Cmd(TIM6, ENABLE); // 启动定时器 while (!TIM_GetFlagStatus(TIM6, TIM_FLAG_Update)); // 等待更新标志 TIM_ClearFlag(TIM6, TIM_FLAG_Update); // 清除标志 TIM_Cmd(TIM6, DISABLE); // 关闭定时器 }4.2 常见问题排查
定时器不工作:
- 检查时钟是否使能
- 验证APB1预分频系数
- 确认TIM_Cmd()已调用
中断不触发:
- 检查NVIC配置
- 确认TIM_ITConfig()已使能中断
- 确保中断服务程序名称正确
定时时间不准确:
- 检查时钟树配置
- 确认PSC和ARR计算正确
- 考虑中断响应延迟
调试技巧:在中断服务程序中设置断点,通过查看CNT寄存器值验证定时器是否正常运行。
5. 不同场景下的配置建议
5.1 短时间定时(us级)
- 使用较高的CK_CNT频率(减小PSC)
- 设置较小的ARR值
- 考虑直接查询标志位代替中断
5.2 长时间定时(秒级)
- 使用较大的PSC值降低CK_CNT
- 结合软件计数器扩展定时范围
- 启用中断处理
5.3 低功耗应用
- 选择RUN模式下可用的最低时钟频率
- 合理配置自动唤醒功能
- 考虑使用RTC替代定时器
通过本文的详细讲解和代码实现,开发者可以快速掌握STM32基本定时器的使用方法,并能够根据实际需求进行灵活配置和应用扩展。