一、先说结论

用法

本质

适合场景

定时中断

数内部时钟

系统节拍、周期性任务

外部时钟

数外部引脚

计数、测频、编码器

👉 定时器的核心不是“定时”,而是“计数”。


二、为什么要把这两件事放在一起写?

在第一篇里,我用 EXTI 检测红外遮挡;

在第二篇里,我用 TIM2 定时中断​ 给 EXTI 消抖。

但当时我忽略了一个问题:

如果我只是想“数遮挡次数”,为什么不用 TIM 直接数?

于是这次,我同时试了两种用法:

  1. TIM 定时中断:系统心跳

  2. TIM 外部时钟:数外部脉冲


三、TIM 定时中断:给系统“心跳”

1️⃣ 配置思路

  • 时钟源:内部时钟(APB1)

  • 预分频 PSC

  • 自动重装 ARR

  • 更新中断

  • NVIC

2️⃣ 核心代码

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_Period = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 10000 - 1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);

3️⃣ 中断里只做一件事

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update))
    {
        Num++;
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

✅ 不延时

✅ 不刷屏

✅ 不碰硬件


四、TIM 外部时钟:数外部脉冲

1️⃣ 什么是外部时钟模式 2?

TIM 可以不直接数内部时钟,而是:

数某个 GPIO 引脚上的边沿

这正是红外计数的“理想形态”。


2️⃣ 硬件连接

TIM

STM32

外部模块

ETR

PA0

红外 DO


3️⃣ 核心配置思路

  • 使用 外部时钟模式 2

  • 时钟来自 PA0(TIM2_ETR)

  • 开启 GPIOA 时钟

  • 配置 PA0 为上拉输入

  • 使用最大滤波器,抗抖动


4️⃣ 关键代码

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    //开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef    GPIO_InitStructure;                                              //GPIO初始化
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF,           //外部时钟配置
                        TIM_ExtTRGPolarity_NonInverted, 0x0F);              

TIM_TimeBaseInitTypeDef     TIM_TimeBaseInitStructure;                  //时基单元初始化
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

TIM_ClearFlag(TIM2, TIM_FLAG_Update);                                    //中断输出配置
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                       //NVIC中断分组

NVIC_InitTypeDef     NVIC_InitStructure;                                           //NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

TIM_Cmd(TIM2, ENABLE);                                                            //TIM使能

uint16_t Timer_GetCounter(void)
{
    return TIM_GetCounter(TIM2);    //返回定时器TIM2的CNT
}


int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    Timer_Init();        //定时中断初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Num:");            //1行1列显示字符串Num:
    OLED_ShowString(2, 1, "CNT:");            //2行1列显示字符串CNT:
    
    while (1)
    {
        OLED_ShowNum(1, 5, Num, 5);            //不断刷新显示Num变量
        OLED_ShowNum(2, 5, Timer_GetCounter(), 5);        //不断刷新显示CNT的值
    }
}

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)        //判断是否是TIM2的更新事件触发的中断
    {
        Num ++;                                                //Num变量自增,用于测试定时中断
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);            //清除TIM2更新事件的中断标志位
                                                            //中断标志位必须清除
                                                            //否则中断将连续不断地触发,导致主程序卡死
    }
}

TIM2的ETR引脚固定为PA0的硬件限制说明
STM32的TIM2定时器外部触发输入(ETR)引脚硬件绑定在PA0(部分型号可能复用为PA5/PB3,需查阅具体芯片手册)。

红外遮挡计数原理详解
当红外对管的光路被遮挡时,接收端会产生上升沿/下降沿信号,通过TIM2的ETR引脚触发下列硬件自动流程:

  1. 信号经输入滤波器(可通过TIMx_SMCR寄存器的ETF[3:0]配置消抖时间)
  2. 边沿检测器(TIMx_SMCR的ETP位选择上升/下降沿)
  3. 计数器CNT立即+1(无需CPU干预)
    典型应用场景示例:
  • 传送带物品计数(每遮挡一次计1件)
  • 旋转编码器脉冲采集

硬件消抖与EXTI方案对比优势

  1. 硬件消抖:通过TIMx_SMCR寄存器的ETF[3:0]直接配置滤波时钟周期(例如文中设置0x6表示8个时钟周期滤波),消除机械抖动
  2. 无EXTI需求:ETR信号直接接入定时器硬件单元,比EXTI中断方案减少以下耗时步骤:
    • 中断优先级配置
    • 上下文保存/恢复
    • 软件计数器操作
  3. 实时性保障:CNT自增动作在检测到有效边沿后立即执行,延迟仅取决于TIM2时钟频率(例如72MHz下延迟<14ns)

五、两种用法对比

对比项

定时中断

外部时钟

时钟来源

内部

外部 GPIO

是否进 IRQ

✅ 是

❌ 否

CPU 占用

几乎为零

适合

心跳、任务调度

计数、测频

红外场景

消抖

直接计数

👉 红外计数,最优解是外部时钟,不是 EXTI。


六、我踩过的坑

  1. ETR 引脚固定

    不是随便一个 GPIO 都能当外部时钟
  2. 极性配置反了

    计数方向完全不对
  3. 忘了 TIM 仍然是“计数器”

    一直把它当“定时器”用

七、给同样在入门的朋友建议

1️⃣ 不要把 TIM 只当成 Delay 的替代品

2️⃣ 学会把 TIM 当“计数器”用

3️⃣ 外部事件能交给硬件,就别麻烦 CPU

4️⃣ 工程里,“少进中断”本身就是一种优化


八、总结一句话

定时中断,是让系统知道“时间到了”;

外部时钟,是让系统知道“发生了几次”。

真正会用 STM32 的人,不是会写 EXTI,而是会用 TIM。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐