06-TIM定时中断.md 16 KB

06-TIM定时中断

定时器分类 (STM32F103C8T6)

类型 实例 总线 分辨率 主要特性
高级 TIM1 APB2 (72MHz) 16位 互补PWM、刹车、死区、编码器接口、重复计数器
通用 TIM2 / TIM3 / TIM4 APB1 (36MHz) 16位 PWM输入/输出、编码器、中断/DMA触发
基本 TIM6 / TIM7 APB1 (36MHz) 16位 仅定时中断(无外部引脚)

注意:TIM2 在 F103xC/D/E 及 F107 是 32 位计数器。C8T6 为 16 位。

时基单元

graph LR
    subgraph TIMER_CLOCK
        CLK_SRC["时钟源"] --> PSC["PSC<br/>预分频器<br/>16位"]
    end
    
    subgraph TIMER_CORE
        PSC --> CNT["CNT<br/>计数器<br/>16位"]
        CNT --> ARR["ARR<br/>自动重装<br/>寄存器<br/>16位"]
        CNT --> RCR["RCR<br/>重复计数器<br/>(仅高级TIM)"]
    end
    
    ARR --> UPDATE["更新事件/中断<br/>UEV"]
    RCR --> UPDATE
    
    UPDATE --> UPDATE_EVENT["更新事件"]
    UPDATE_EVENT --> SR["SR 寄存器<br/>UIF 标志位"]
寄存器 位宽 作用
PSC (Prescaler) 16bit 对时钟分频,timer_clk = CLK / (PSC+1)
CNT (Counter) 16bit 计数当前值,可向上/向下/中央对齐
ARR (Auto-Reload) 16bit 自动重装值,CNT 达到 ARR 后溢出发送更新事件
RCR (Repetition) 16bit 高级定时器专用,每 (RCR+1) 个溢出才产生更新事件

定时时间计算

定时频率:     f_timer = TIM_CLK / (PSC + 1) / (ARR + 1)
定时时间:     T       = (PSC + 1) × (ARR + 1) / TIM_CLK

示例 (TIM2, 36MHz):
  PSC = 36 - 1 = 35  →  f_cnt = 36MHz / 36 = 1MHz → 1μs
  ARR = 1000 - 1     →  T = 1000 × 1μs = 1ms

时钟源选择

graph TD
    subgraph CLOCK_SOURCES
        INT_CLK["内部时钟<br/>(CK_INT)"] --> TIM_CLK["TIM_CLK"]
        ETR_MODE2["外部模式2<br/>(ETR 引脚)"] --> TIM_CLK
        
        subgraph EXTERNAL_MODE1
            TI1F_ED["TI1F_ED<br/>(双边沿)"] --> TRGI
            TI1FP1["TI1FP1"] --> TRGI
            TI2FP2["TI2FP2"] --> TRGI
            ETRF["ETRF<br/>(外部触发)"] --> TRGI
        end
        TRGI["TRGI"] --> TIM_CLK
    end
    
    TIM_CLK --> PSC_INPUT["→ PSC"]

内部时钟(默认)

void TIM_InternalClockConfig(TIM_TypeDef* TIMx);   // 默认已使能,可不用调用

使用 RCC 提供的时钟:TIM1→PCLK2(72MHz),TIM2/3/4→PCLK1(36MHz)。

外部时钟模式2 (ETR)

通过外部引脚(如 TIM2_ETR = PA0)提供时钟源。

void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, 
                              uint16_t TIM_ExtTRGPrescaler,
                              uint16_t TIM_ExtTRGPolarity,
                              uint16_t ExtTRGFilter);

外部时钟模式1 (TIx)

通过 CH1/CH2 引脚输入时钟,可用于编码器模式。

API 表格

定时器时基初始化

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
TIM_TimeBaseInitTypeDef 成员 取值 说明
TIM_Prescaler 0~65535 预分频值
TIM_CounterMode TIM_CounterMode_Up / Down / CenterAligned1/2/3 计数模式
TIM_Period 0~65535 ARR 自动重装值
TIM_ClockDivision TIM_CKD_DIV1 / DIV2 / DIV4 时钟分频(滤波采样)
TIM_RepetitionCounter 0~255 重复计数值(仅 TIM1/TIM8)

定时器控制函数

函数 原型 说明
TIM_Cmd void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState) 开启/关闭定时器
TIM_ITConfig void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState) 中断使能
TIM_SetCounter void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter) 设置 CNT 值
TIM_GetCounter uint16_t TIM_GetCounter(TIM_TypeDef* TIMx) 读取 CNT 值

中断标志位

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

输出比较 (OC - Output Compare)

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare);

输入捕获 (IC - Input Capture)

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);

编码器接口

void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, 
                                 uint16_t TIM_EncoderMode,
                                 uint16_t TIM_IC1Polarity, 
                                 uint16_t TIM_IC2Polarity);

完整代码示例

1. 定时中断 1ms 翻转 LED (TIM2)

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

void Timer_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 1. 开启 TIM2 时钟 (APB1)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 2. 时基配置: PCLK1=36MHz, PSC=36-1=36分频→1MHz, ARR=1000-1→1kHz(1ms)
    TIM_TimeBaseStructure.TIM_Prescaler = 36 - 1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // 3. 清除更新标志位 (防止初始化后立刻进中断)
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    
    // 4. 使能更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    // 5. NVIC 配置
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 6. 启动定时器
    TIM_Cmd(TIM2, ENABLE);
}

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {
        // 翻转 PC13 板载 LED
        GPIO_WriteBit(GPIOC, GPIO_Pin_13, 
                      (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

int main(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    
    GPIO_SetBits(GPIOC, GPIO_Pin_13);      // 初始灭
    
    Timer_Init();
    
    while (1)
    {
        // 主循环空闲 (LED 在中断中翻转)
    }
}

2. 外部时钟计次 (TIM2_ETR = PA0)

对射式红外传感器接 PA0,每次遮挡计数器 +1。

#include "stm32f10x.h"
#include "OLED.h"

void Timer_ETR_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    // 1. GPIO 配置 (PA0 作为 TIM2_ETR)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   // 外部信号输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 2. 定时器时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 3. 时基: 不分频, ARR=65535 (最大)
    TIM_TimeBaseStructure.TIM_Prescaler = 0;                // 不分频
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 65535;               // 最大范围
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // 4. 外部时钟模式2: ETR 引脚输入
    TIM_ETRClockMode2Config(TIM2, 
                            TIM_ExtTRGPSC_OFF,              // 无预分频
                            TIM_ExtTRGPolarity_NonInverted, // 不反相
                            0x0F);                          // 滤波 15 个采样
    // 5. 启动
    TIM_Cmd(TIM2, ENABLE);
}

uint16_t Timer_GetCounter(void)
{
    return TIM_GetCounter(TIM2);
}

int main(void)
{
    OLED_Init();
    Timer_ETR_Init();
    
    uint16_t lastCount = 0;
    
    OLED_ShowString(1, 1, "ETR Count:");
    
    while (1)
    {
        uint16_t now = Timer_GetCounter();
        if (now != lastCount)
        {
            lastCount = now;
            OLED_ShowNum(2, 1, now, 5);
        }
    }
}

3. 输出比较 — PWM 呼吸灯 (TIM3_CH1 = PA6)

#include "stm32f10x.h"
#include "Delay.h"

void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 1. GPIO: PA6 = TIM3_CH1 复用推挽
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 2. TIM3 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // 3. 时基: 36MHz / 36 = 1MHz, ARR=1000 → PWM 频率=1kHz
    TIM_TimeBaseStructure.TIM_Prescaler = 36 - 1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 4. OC1 配置: PWM1 模式
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;                      // 初始占空比 0
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    
    TIM_Cmd(TIM3, ENABLE);
}

void PWM_SetCompare1(uint16_t compare)
{
    TIM_SetCompare1(TIM3, compare);
}

int main(void)
{
    PWM_Init();
    
    while (1)
    {
        // 渐亮
        for (uint16_t i = 0; i < 1000; i++)
        {
            PWM_SetCompare1(i);
            Delay_ms(1);
        }
        // 渐灭
        for (uint16_t i = 1000; i > 0; i--)
        {
            PWM_SetCompare1(i);
            Delay_ms(1);
        }
    }
}

4. 输入捕获 — 频率测量 (TIM4_CH1 = PB6)

#include "stm32f10x.h"
#include "OLED.h"

void IC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    
    TIM_TimeBaseStructure.TIM_Prescaler = 36 - 1;           // 1μs
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 65535;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
    
    // IC1: 上升沿捕获, 不分频, 无滤波
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x0;
    TIM_ICInit(TIM4, &TIM_ICInitStructure);
    
    TIM_Cmd(TIM4, ENABLE);
}

int main(void)
{
    OLED_Init();
    IC_Init();
    
    OLED_ShowString(1, 1, "Freq (Hz):");
    
    while (1)
    {
        // 基础频率测量:等待捕获完成,读 CCR1
        // 实际应用中应使用更新中断 + 捕获中断计算频率
        uint16_t cap1 = TIM_GetCapture1(TIM4);
        OLED_ShowNum(2, 1, cap1, 5);
        Delay_ms(100);
    }
}

5. 编码器接口 (TIM3_CH1=PA6, TIM3_CH2=PA7)

#include "stm32f10x.h"
#include "OLED.h"

void Encoder_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    TIM_TimeBaseStructure.TIM_Prescaler = 0;                // 不分频
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 65535;               // 最大
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 编码器模式: TI1+TI2, 双边缘
    TIM_EncoderInterfaceConfig(TIM3,
                               TIM_EncoderMode_TI12,
                               TIM_ICPolarity_Rising,
                               TIM_ICPolarity_Rising);
    
    TIM_Cmd(TIM3, ENABLE);
}

int16_t Encoder_Get(void)
{
    return (int16_t)TIM_GetCounter(TIM3);
}

int main(void)
{
    OLED_Init();
    Encoder_Init();
    
    OLED_ShowString(1, 1, "Encoder:");
    
    while (1)
    {
        int16_t val = Encoder_Get();
        OLED_ShowSignedNum(2, 1, val, 6);
        Delay_ms(50);
    }
}

通用定时器配置步骤总结

1. RCC_APB1PeriphClockCmd(TIMx, ENABLE)
2. GPIO_Init() — 根据功能配复用推挽(AF_PP)或浮空输入(IN_FLOATING)
3. TIM_TimeBaseInit() — PSC, ARR, CounterMode
4. 可选: TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE) — 中断使能
5. 可选: NVIC_Init() — 中断优先级
6. 可选: TIM_OCxInit() — 输出比较
7. 可选: TIM_ICInit() — 输入捕获
8. 可选: TIM_EncoderInterfaceConfig() — 编码器
9. TIM_Cmd(TIMx, ENABLE) — 启动定时器

TIM 中断 NVIC 通道

定时器 中断通道 IRQn
TIM1 TIM1_BRK_IRQn / TIM1_UP_IRQn / TIM1_TRG_COM_IRQn / TIM1_CC_IRQn 24/25/26/27
TIM2 TIM2_IRQn 28
TIM3 TIM3_IRQn 29
TIM4 TIM4_IRQn 30
TIM5 TIM5_IRQn 50
TIM6 TIM6_IRQn 54
TIM7 TIM7_IRQn 55

常见坑点/注意事项

  1. ARR/PSC 16位范围 — 最大 65535。如需更长定时:级联两个定时器或软件计数器累加
  2. 影子寄存器 — PSC 和 ARR 有影子寄存器,写后在下一次更新事件生效。TIM_Prescaler 写后立即对时钟分频生效,但 PSC 缓冲到 UE 才更新;ARR 预加载可通过 TIM_ARRPreloadConfig 控制
  3. TIMxCLK 频率 — TIM1 在 APB2(72MHz),TIM2/3/4 在 APB1(36MHz)。若 APB1 分频不为 1,则定时器时钟 = PCLK1 × 2(36MHz × 2 = 72MHz 仅当 APB1 分频 > 1 时)。SystemInit 默认 APB1/2,所以 TIM2/3/4 时钟 = 36MHz × 2 = 72MHz? — 标准库 SystemInit 中 APB1 预分频=2(除2),则 PCLK1 = 36MHz。定时器时钟 = PCLK1 × 2 = 72MHz(当 APB1 预分频≠1 时)
  4. 初始化后立刻进中断 — TIM_TimeBaseInit 会置位 UIF,需要在 TIM_ITConfig 前调用 TIM_ClearITPendingBit
  5. TIM_OC1Init 会重设 PSC/ARR — 如果在 OC Init 之前已经设置了 PSC/ARR,OC Init 内部会重新调用 TimeBaseInit,需在 OC Init 之后重新配置 PSC/ARR 或先设好。正确做法是 TimeBaseInit → OCInit 顺序
  6. 输出比较极性TIM_OCPolarity_High:CCR ≥ CNT 时输出高;TIM_OCPolarity_Low 反之
  7. 编码器模式方向 — 读取 CNT 得相对位置,正转 +1 反转 -1;编码器为 16 位,超过范围会溢出
  8. 输入捕获必须正确选择 TIxTIM_ICSelection_DirectTI 选择对应通道的输入引脚,TIM_ICSelection_IndirectTI 选择交叉输入(CH1 用 CH2 的引脚)
  9. PWM 频率 — PWM 频率 = TIM_CLK / (PSC+1) / (ARR+1),分辨率 = ARR+1 级。如需 1kHz 且 ARR=999,则占空比步进 0.1%
  10. OC 输出与 GPIO — 一定要用 GPIO_Mode_AF_PP,否则 PWM 信号不会从引脚输出