一、定时器中断的基本概念
定时器工作模式
在STM32中,定时器中断通常用于以下场景:
定时更新中断:计数器溢出时触发
捕获/比较中断:输入捕获或输出比较匹配时触发
TIM2特性概览(以STM32F103为例)
16位向上/向下自动重载计数器
16位可编程预分频器
4个独立通道(输入捕获/输出比较/PWM)
支持增量编码器接口
支持触发输入作为外部时钟
二、完整配置步骤
步骤1:使能定时器时钟
所有外设使用前必须先使能时钟:
// STM32F1系列 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // STM32F4系列 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; __DSB(); // 数据同步屏障,确保时钟稳定时钟总线说明:
APB1:低速外设总线(最高36MHz/84MHz,取决于系列)
APB2:高速外设总线
TIM2挂在APB1总线上
步骤2:配置定时器基本参数
2.1 计算定时周期
定时器时钟频率公式:
定时器时钟 = APB1总线时钟 / 预分频系数
中断频率 = 定时器时钟 / (自动重载值 + 1)
2.2 配置预分频器和自动重载值
// 假设系统时钟为72MHz,APB1时钟为36MHz // 目标:产生1ms中断(1000Hz) // 方法1:直接计算配置 void TIM2_Configuration(void) { // 配置预分频器 // 预分频值 = 定时器时钟 / 目标频率 - 1 // 36MHz / 1000Hz = 36000 // 预分频器设为35999(从0开始计数) TIM2->PSC = 35999; // 预分频值 // 配置自动重载值 // 如果需要1ms中断,ARR设为999(1000次计数) TIM2->ARR = 999; // 自动重载值 // 或者更简单的计算: // 1ms中断 = 1kHz频率 // 计数频率 = 36MHz / (PSC+1) = 1kHz // 所以 PSC = 36000-1 = 35999 }2.3 更通用的配置函数
// 通用定时器配置函数 // 输入参数:中断频率(Hz) void TIM2_Init(uint32_t frequency) { uint32_t tim_clock; uint32_t psc, arr; // 获取TIM2时钟频率 // 注意:APB1预分频器可能影响定时器时钟 // STM32中,如果APB1预分频系数≠1,定时器时钟=APB1时钟×2 // 假设系统时钟72MHz,APB1分频系数为2 // 则APB1时钟=36MHz,TIM2时钟=72MHz tim_clock = 72000000; // 72MHz // 计算预分频器和自动重载值 // 原则:ARR尽可能大,PSC尽可能小,提高分辨率 arr = 1000 - 1; // 固定ARR为1000 psc = (tim_clock / frequency / (arr + 1)) - 1; TIM2->PSC = psc; TIM2->ARR = arr; }步骤3:配置中断相关寄存器
3.1 使能更新中断
// 使能更新中断
TIM2->DIER |= TIM_DIER_UIE; // 更新中断使能
3.2 中断优先级配置(NVIC)
// 配置NVIC NVIC_EnableIRQ(TIM2_IRQn); // 使能TIM2中断 NVIC_SetPriority(TIM2_IRQn, 1); // 设置中断优先级中断优先级说明:
STM32使用4位优先级分组
优先级数值越小,优先级越高
建议:
紧急中断:0-1
普通中断:2-3
低优先级:>3
步骤4:启动定时器
// 方法1:使用一个更新事件启动计数器 TIM2->EGR |= TIM_EGR_UG; // 产生更新事件,预装载值生效 TIM2->CR1 |= TIM_CR1_CEN; // 使能计数器 // 方法2:直接启动 TIM2->CR1 |= TIM_CR1_CEN; // 计数器使能步骤5:编写中断服务函数
// TIM2全局中断服务函数 void TIM2_IRQHandler(void) { // 1. 检查中断标志位 if (TIM2->SR & TIM_SR_UIF) { // 2. 清除中断标志位(重要!) TIM2->SR &= ~TIM_SR_UIF; // 3. 执行中断处理代码 // 例如:翻转LED GPIOA->ODR ^= (1 << 5); // 翻转PA5 // 4. 可以添加其他功能 static uint32_t counter = 0; counter++; if (counter >= 1000) // 1秒处理 { counter = 0; // 执行1秒任务 } } // 检查其他中断标志位(如需要) if (TIM2->SR & TIM_SR_CC1IF) // 通道1捕获/比较中断 { TIM2->SR &= ~TIM_SR_CC1IF; // 处理通道1中断 } }三、完整示例代码
#include "stm32f1xx.h" // LED GPIO配置 void LED_Init(void) { // 使能GPIOA时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // 配置PA5为推挽输出 GPIOA->CRL &= ~(GPIO_CRL_MODE5 | GPIO_CRL_CNF5); GPIOA->CRL |= GPIO_CRL_MODE5_0; // 输出模式,最大速度10MHz } // TIM2初始化:1ms中断 void TIM2_Init_1ms(void) { // 1. 使能TIM2时钟 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // 2. 配置定时器参数 // 系统时钟72MHz,APB1分频系数为2 // APB1时钟=36MHz,TIM2时钟=72MHz TIM2->PSC = 7200 - 1; // 分频到10kHz TIM2->ARR = 10 - 1; // 1ms中断 (10kHz/10 = 1kHz) // 3. 配置中断 TIM2->DIER |= TIM_DIER_UIE; // 使能更新中断 // 4. 配置NVIC NVIC_EnableIRQ(TIM2_IRQn); NVIC_SetPriority(TIM2_IRQn, 2); // 中等优先级 // 5. 启动定时器 TIM2->CR1 |= TIM_CR1_CEN; // 使能计数器 } // TIM2中断服务函数 void TIM2_IRQHandler(void) { static uint32_t ms_counter = 0; if (TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; // 清除中断标志 ms_counter++; // 每500ms翻转LED if (ms_counter >= 500) { ms_counter = 0; GPIOA->ODR ^= (1 << 5); // 翻转PA5 } } } int main(void) { // 初始化系统时钟(假设已配置为72MHz) SystemInit(); // 初始化LED LED_Init(); // 初始化定时器 TIM2_Init_1ms(); // 主循环 while(1) { // 主程序可以执行其他任务 // 定时器中断在后台运行 __WFI(); // 等待中断,进入低功耗模式 } return 0; }四、高级配置技巧
1. 精确微秒延时
// 使用TIM2实现微秒级延时 void TIM2_Delay_us(uint32_t us) { // 配置TIM2为1MHz计数频率 // 假设TIM2时钟=72MHz TIM2->PSC = 72 - 1; // 72MHz/72 = 1MHz TIM2->ARR = us - 1; // 设置延时时间 // 清空计数器 TIM2->CNT = 0; // 启动单次模式 TIM2->CR1 |= TIM_CR1_OPM; // 单脉冲模式 TIM2->CR1 |= TIM_CR1_CEN; // 启动计数器 // 等待计数器停止 while (TIM2->CR1 & TIM_CR1_CEN); }2. PWM输出配置
void TIM2_PWM_Init(uint32_t duty_cycle) { // 配置TIM2通道1为PWM输出(PA0) // 1. 使能GPIOA时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // 2. 配置PA0为复用推挽输出 GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA->CRL |= GPIO_CRL_MODE0_1 | GPIO_CRL_CNF0_1; // 复用输出 // 3. 配置TIM2 TIM2->PSC = 7200 - 1; // 10kHz PWM频率 TIM2->ARR = 1000 - 1; // 1000级分辨率 // 4. 配置PWM模式 // 通道1输出比较配置 TIM2->CCMR1 &= ~TIM_CCMR1_OC1M; TIM2->CCMR1 |= (6 << 4); // PWM模式1 // 5. 设置占空比 TIM2->CCR1 = duty_cycle; // 占空比数值 // 6. 使能输出 TIM2->CCER |= TIM_CCER_CC1E; // 7. 启动定时器 TIM2->CR1 |= TIM_CR1_CEN; }3. 输入捕获配置(测量脉冲宽度)
void TIM2_InputCapture_Init(void) { // 配置TIM2通道1为输入捕获(PA0) // 1. 配置GPIO GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA->CRL |= GPIO_CRL_CNF0_0; // 浮空输入 // 2. 配置TIM2 TIM2->PSC = 72 - 1; // 1MHz计数频率,1μs分辨率 // 3. 配置输入捕获 TIM2->CCMR1 &= ~TIM_CCMR1_CC1S; TIM2->CCMR1 |= (1 << 0); // CC1通道配置为输入,IC1映射到TI1 // 4. 配置捕获边沿 TIM2->CCER &= ~TIM_CCER_CC1P; // 上升沿捕获 // 5. 使能捕获中断 TIM2->DIER |= TIM_DIER_CC1IE; NVIC_EnableIRQ(TIM2_IRQn); // 6. 启动定时器 TIM2->CR1 |= TIM_CR1_CEN; } // 中断中处理捕获 void TIM2_IRQHandler(void) { static uint16_t capture1 = 0, capture2 = 0; static uint8_t capture_count = 0; uint16_t pulse_width; if (TIM2->SR & TIM_SR_CC1IF) // 捕获中断 { if (capture_count == 0) { capture1 = TIM2->CCR1; capture_count = 1; // 改为下降沿捕获 TIM2->CCER |= TIM_CCER_CC1P; } else if (capture_count == 1) { capture2 = TIM2->CCR1; capture_count = 0; // 计算脉冲宽度(μs) pulse_width = capture2 - capture1; // 改回上升沿捕获 TIM2->CCER &= ~TIM_CCER_CC1P; } TIM2->SR &= ~TIM_SR_CC1IF; } }五、常见问题与调试技巧
1. 定时器不工作的检查清单
void Debug_TIM2_Status(void) { // 检查时钟是否使能 if (!(RCC->APB1ENR & RCC_APB1ENR_TIM2EN)) { // 时钟未使能 } // 检查定时器是否使能 if (!(TIM2->CR1 & TIM_CR1_CEN)) { // 定时器未启动 } // 检查中断是否使能 if (!(TIM2->DIER & TIM_DIER_UIE)) { // 更新中断未使能 } // 检查NVIC配置 if (!(NVIC->ISER[0] & (1 << TIM2_IRQn))) { // NVIC中断未使能 } // 读取当前计数器值 uint32_t cnt_val = TIM2->CNT; // 检查中断标志 if (TIM2->SR & TIM_SR_UIF) { // 有未处理的中断 } }2. 中断标志未清除的后果
中断会连续触发,导致系统卡死
必须在中服函数开始时清除标志位
3. 计算精度优化
// 使用宏定义提高可读性 #define SYSTEM_CLOCK 72000000UL #define APB1_CLOCK (SYSTEM_CLOCK / 2) #define TIM2_CLOCK (APB1_CLOCK * 2) // APB1分频系数≠1时 // 计算定时参数 #define TIM2_PRESCALER 35999UL #define TIM2_PERIOD 999UL #define TIM2_FREQUENCY (TIM2_CLOCK / (TIM2_PRESCALER + 1) / (TIM2_PERIOD + 1))六、不同STM32系列的差异
STM32F1 vs STM32F4/F7
// 条件编译处理差异 #if defined(STM32F1) // F1系列 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; TIM2->PSC = 35999; TIM2->ARR = 999; #elif defined(STM32F4) || defined(STM32F7) // F4/F7系列 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // F4系列支持32位定时器(TIM2和TIM5) TIM2->PSC = 8399; // 不同时钟配置 TIM2->ARR = 9999; // 自动重载预装载使能 TIM2->CR1 |= TIM_CR1_ARPE; #endif中断向量表差异
// STM32F1: 中断服务函数名固定 void TIM2_IRQHandler(void) __attribute__((interrupt)); // STM32F4/F7: 使用CMSIS标准 void TIM2_IRQHandler(void) { // CMSIS函数操作 if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); // 处理中断 } }总结
配置STM32定时器中断需要遵循以下关键步骤:
时钟使能:确保TIM2时钟已开启
参数计算:根据需求计算PSC和ARR值
中断配置:使能更新中断和NVIC
标志管理:正确清除中断标志
启动定时器:最后使能计数器
掌握定时器中断的底层寄存器操作,不仅能让你写出更高效的代码,还能在复杂应用中灵活应对各种定时需求。建议在实际项目中多练习,从简单的定时闪烁LED开始,逐步尝试PWM输出、输入捕获等高级功能。
通过本文的学习,你应该能够独立配置STM32的定时器中断。下一步可以探索多个定时器协同工作、定时器级联等高级应用场景。