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

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

定时器工作模式

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

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

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

TIM2特性概览(以STM32F103为例)

  • 16位向上/向下自动重载计数器

  • 16位可编程预分频器

  • 4个独立通道(输入捕获/输出比较/PWM)

  • 支持增量编码器接口

  • 支持触发输入作为外部时钟

二、完整配置步骤

步骤1:使能定时器时钟

所有外设使用前必须先使能时钟:

cs 复制代码
// 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 配置预分频器和自动重载值
cs 复制代码
// 假设系统时钟为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 更通用的配置函数
cs 复制代码
// 通用定时器配置函数
// 输入参数:中断频率(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)
cs 复制代码
// 配置NVIC
NVIC_EnableIRQ(TIM2_IRQn);  // 使能TIM2中断
NVIC_SetPriority(TIM2_IRQn, 1);  // 设置中断优先级

中断优先级说明

  • STM32使用4位优先级分组

  • 优先级数值越小,优先级越高

  • 建议:

    • 紧急中断:0-1

    • 普通中断:2-3

    • 低优先级:>3

步骤4:启动定时器

cs 复制代码
// 方法1:使用一个更新事件启动计数器
TIM2->EGR |= TIM_EGR_UG;  // 产生更新事件,预装载值生效
TIM2->CR1 |= TIM_CR1_CEN;  // 使能计数器

// 方法2:直接启动
TIM2->CR1 |= TIM_CR1_CEN;  // 计数器使能

步骤5:编写中断服务函数

cs 复制代码
// 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中断
    }
}

三、完整示例代码

cs 复制代码
#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. 精确微秒延时

cs 复制代码
// 使用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输出配置

cs 复制代码
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. 输入捕获配置(测量脉冲宽度)

cs 复制代码
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. 定时器不工作的检查清单

cs 复制代码
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. 计算精度优化

cs 复制代码
// 使用宏定义提高可读性
#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

cs 复制代码
// 条件编译处理差异
#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

中断向量表差异

cs 复制代码
// 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的定时器中断。下一步可以探索多个定时器协同工作、定时器级联等高级应用场景。

相关推荐
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之rcp命令(实操篇)
linux·服务器·网络·chrome·笔记
爱学习的uu2 小时前
大模型学习1——各类模型接入langchain,模型调用,记忆管理,工具调用
人工智能·python·深度学习·学习·算法·机器学习·langchain
安得权2 小时前
Azure DevOps 学习概况总结
学习·azure·devops
老王熬夜敲代码2 小时前
模版元编程variant
c++·笔记
代码游侠2 小时前
学习笔记——SQLite3 编程与 HTML 基础
网络·笔记·算法·sqlite·html
Q_21932764552 小时前
基于51单片机的智能家居防火防盗报警系统设计
嵌入式硬件·51单片机·智能家居
im_AMBER2 小时前
Leetcode 91 子序列首尾元素的最大乘积
数据结构·笔记·学习·算法·leetcode
QK_002 小时前
STM32--USART(串口)
stm32·单片机·嵌入式硬件
jencepan2 小时前
纳安级功耗,5.5V/2A,23-6封装,单节锂电升压DCDC方案,晶艺LA2112N
单片机·嵌入式硬件