一、先说结论用法本质适合场景定时中断​数内部时钟系统节拍、周期性任务外部时钟​数外部引脚计数、测频、编码器定时器的核心不是“定时”而是“计数”。二、为什么要把这两件事放在一起写在第一篇里我用 EXTI 检测红外遮挡在第二篇里我用TIM2 定时中断​ 给 EXTI 消抖。但当时我忽略了一个问题如果我只是想“数遮挡次数”为什么不用 TIM 直接数于是这次我同时试了两种用法TIM 定时中断系统心跳TIM 外部时钟数外部脉冲三、TIM 定时中断给系统“心跳”1️⃣ 配置思路时钟源内部时钟APB1预分频 PSC自动重装 ARR更新中断NVIC2️⃣ 核心代码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️⃣ 什么是外部时钟模式 2TIM 可以不直接数内部时钟而是数某个 GPIO 引脚上的边沿这正是红外计数的“理想形态”。2️⃣ 硬件连接TIMSTM32外部模块ETRPA0红外 DO3️⃣ 核心配置思路使用外部时钟模式 2时钟来自PA0TIM2_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引脚触发下列硬件自动流程信号经输入滤波器可通过TIMx_SMCR寄存器的ETF[3:0]配置消抖时间边沿检测器TIMx_SMCR的ETP位选择上升/下降沿计数器CNT立即1无需CPU干预典型应用场景示例传送带物品计数每遮挡一次计1件旋转编码器脉冲采集✅硬件消抖与EXTI方案对比优势硬件消抖通过TIMx_SMCR寄存器的ETF[3:0]直接配置滤波时钟周期例如文中设置0x6表示8个时钟周期滤波消除机械抖动无EXTI需求ETR信号直接接入定时器硬件单元比EXTI中断方案减少以下耗时步骤中断优先级配置上下文保存/恢复软件计数器操作实时性保障CNT自增动作在检测到有效边沿后立即执行延迟仅取决于TIM2时钟频率例如72MHz下延迟14ns五、两种用法对比对比项定时中断外部时钟时钟来源内部外部 GPIO是否进 IRQ✅ 是❌ 否CPU 占用有几乎为零适合心跳、任务调度计数、测频红外场景消抖直接计数红外计数最优解是外部时钟不是 EXTI。六、我踩过的坑ETR 引脚固定不是随便一个 GPIO 都能当外部时钟极性配置反了计数方向完全不对忘了 TIM 仍然是“计数器”一直把它当“定时器”用七、给同样在入门的朋友建议1️⃣ 不要把 TIM 只当成 Delay 的替代品2️⃣ 学会把 TIM 当“计数器”用3️⃣ 外部事件能交给硬件就别麻烦 CPU4️⃣ 工程里“少进中断”本身就是一种优化八、总结一句话定时中断是让系统知道“时间到了”外部时钟是让系统知道“发生了几次”。真正会用 STM32 的人不是会写 EXTI而是会用 TIM。