news 2026/6/26 7:27:26

STM32定时器中断配置详解:以TIM2为例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32定时器中断配置详解:以TIM2为例

一、定时器中断的基本概念

定时器工作模式

在STM32中,定时器中断通常用于以下场景:

  1. 定时更新中断:计数器溢出时触发

  2. 捕获/比较中断:输入捕获或输出比较匹配时触发

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定时器中断需要遵循以下关键步骤:

  1. 时钟使能:确保TIM2时钟已开启

  2. 参数计算:根据需求计算PSC和ARR值

  3. 中断配置:使能更新中断和NVIC

  4. 标志管理:正确清除中断标志

  5. 启动定时器:最后使能计数器

掌握定时器中断的底层寄存器操作,不仅能让你写出更高效的代码,还能在复杂应用中灵活应对各种定时需求。建议在实际项目中多练习,从简单的定时闪烁LED开始,逐步尝试PWM输出、输入捕获等高级功能。

通过本文的学习,你应该能够独立配置STM32的定时器中断。下一步可以探索多个定时器协同工作、定时器级联等高级应用场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/17 4:45:59

msjet40.dll文件丢失损坏找不到 打不开程序软件问题 下载方法

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/6/24 16:25:55

yolov11目标检测实战:基于PyTorch-CUDA-v2.7的端到端流程

YOLO目标检测实战&#xff1a;基于PyTorch-CUDA-v2.7的端到端流程 在智能监控、自动驾驶和工业质检等前沿领域&#xff0c;目标检测早已不再是实验室里的概念验证&#xff0c;而是真正驱动业务的核心技术。然而&#xff0c;哪怕是最先进的模型&#xff0c;若卡在环境配置这一步…

作者头像 李华
网站建设 2026/6/20 19:19:09

AI 实战篇:从零训练你的大模型 ——MiniMind 全流程实战

最近在GitHub上挖到一个宝藏项目——MiniMind&#xff0c;一个能让大家从零开始&#xff0c;用极低成本和极短时间玩转大模型全流程的开源项目。从预训练&#xff08;Pretrain&#xff09;到监督微调&#xff08;SFT&#xff09;&#xff0c;从LoRA轻量化训练到DPO偏好优化&…

作者头像 李华
网站建设 2026/6/22 17:44:21

收藏!AI 智能体到底是什么?8 大核心技术拆解,程序员小白也能看懂

在大模型应用爆发的当下&#xff0c;AI 智能体&#xff08;Agent&#xff09;已经成为下一代 AI 应用的核心形态 —— 它能自主理解任务、规划流程、调用工具&#xff0c;甚至协同其他 Agent 完成复杂工作。但对于刚入门的程序员或技术小白来说&#xff0c;“智能体” 总显得抽…

作者头像 李华