
1. 项目概述从“捕获”一个信号说起搞嵌入式开发尤其是玩STM32的定时器这玩意儿绝对是绕不过去的坎。它就像单片机里的“瑞士军刀”计时、PWM输出、输入捕获、编码器接口功能多到让人眼花缭乱。今天我们不聊别的就深挖其中一个既基础又极其重要的功能输入捕获。这个实验的标题叫“输入捕获实验实操”听起来平平无奇但我敢说能把这里面的门道彻底搞明白你的STM32水平能上一个不小的台阶。输入捕获到底是干嘛的简单说它就是让定时器这个“秒表”去精准地测量一个外部信号的某些特征比如高电平的宽度、低电平的宽度或者一个完整方波的周期。听起来是不是和PWM测量、超声波测距、红外解码这些应用瞬间联系起来了没错这些场景的核心技术之一就是输入捕获。我见过太多初学者照着例程把代码跑通屏幕上显示出一个频率值就觉得“我会了”。但一旦信号有点毛刺或者想同时测占空比程序就罢工了然后就开始对着寄存器手册和满屏的代码发呆。这次我们不满足于“跑通”我们要“吃透”。我会带你从STM32定时器的底层结构开始一步步拆解输入捕获的完整配置流程、中断处理逻辑以及那些例程里通常不会写的“坑”和应对技巧。无论你是正在学习江科大STM32教程的学生还是工作中需要用到信号测量的工程师这篇超详细的实操指南都能让你不仅知其然更知其所以然真正把输入捕获这把利器握在手里。2. 核心原理与硬件结构拆解2.1 定时器与输入捕获通道是如何协作的在开始写代码之前我们必须先弄清楚STM32的定时器特别是通用定时器如TIM2, TIM3, TIM4, TIM5是怎么实现输入捕获功能的。你可以把一个通用定时器想象成一个不断向上计数的“自动卷轴”旁边配了几个灵敏的“触发器”和“抓拍器”。这个“卷轴”就是定时器的计数器寄存器CNT它由内部时钟驱动不停地从0累加到自动重装载值ARR然后清零重新开始周而复始。每个定时器有多个独立的输入通道比如TIM2有CH1-CH4每个通道都像是一个独立的“抓拍站”。当这个通道对应的外部引脚例如TIM2_CH1对应PA0上发生你预设的“事件”比如上升沿时“抓拍站”会瞬间动作把当前“卷轴”上显示的数字也就是CNT的值咔嚓一下复制到属于自己的“照片存储器”里这个存储器就是捕获/比较寄存器CCRx对于通道1就是CCR1。这个过程就是一次“捕获”。通过连续捕获两次比如一次上升沿一次下降沿我们就能得到两个CNT值它们的差值乘以计数一个数所代表的时间由时钟频率和预分频器决定就是这个脉冲的宽度。这就是输入捕获测量脉宽或周期的基本原理。2.2 关键寄存器功能解析理解原理后我们需要认识几个关键的寄存器它们是我们在代码中配置的对象TIMx_CCMR1/2捕获/比较模式寄存器这个寄存器用于设置某个通道是工作在输入模式还是输出模式。对于输入捕获我们需要将其配置为输入模式并选择输入信号映射到哪个捕获通道CCxS位以及是否使用预分频器ICxPS位和数字滤波器ICxF位。滤波器对于消除信号抖动至关重要。TIMx_CCER捕获/比较使能寄存器这里负责使能或失能具体的捕获通道CCxE位并设置捕获的极性——是上升沿触发还是下降沿触发CCxP位。这是决定我们“抓拍”时机的开关。TIMx_SMCR从模式控制寄存器在更高级的应用中比如PWM输入模式可以同时测量周期和占空比会用到这个寄存器来配置定时器的从模式将某个输入通道设置为触发源。TIMx_DIERDMA/中断使能寄存器要使捕获事件能触发中断必须在这里使能对应的捕获/比较中断CCxIE位或更新中断。TIMx_SR状态寄存器当捕获事件发生时相应的捕获/比较标志位CCxIF会被硬件置1。我们在中断服务函数里就是通过查询这个位来判断是哪个通道发生了捕获并在处理完成后手动清除该标志位。注意很多新手容易忘记在中断服务程序中清除标志位导致程序不断重复进入中断最终卡死。这是一个非常经典的错误。2.3 测量方案选择单次捕获 vs PWM输入模式根据不同的测量需求我们通常有两种主流的软件实现方案方案一单通道双边沿捕获手动切换极性这是最基础、最灵活的方法。我们初始化一个通道先设为上升沿捕获。在上升沿中断中我们记录下CCRx的值然后立即在中断里将捕获极性改为下降沿并清除计数器CNT可选取决于你是否想测量绝对时间差。当下一个下降沿到来时再次进入中断记录第二个值。两者的差值即为高电平脉宽。之后再切换回上升沿开始下一轮测量。这种方法逻辑清晰但需要频繁在中断中修改配置且对连续两个边沿之间的处理速度有要求。方案二PWM输入模式硬件自动关联这是STM32定时器提供的一个“豪华套餐”功能。它专门用于测量PWM信号可以自动测量周期和占空比。你需要将一路PWM信号同时连接到定时器的两个通道上例如TI1连接到CH1和CH2。在寄存器配置上需要设置从模式为“复位模式”触发源为TI1。这样CH1被配置为上升沿捕获并且每次上升沿捕获时硬件会自动将计数器CNT清零。CH2被配置为下降沿捕获。周期CH1的捕获值CCR1就是两个上升沿之间的时间即周期。占空比CH2的捕获值CCR2是上升沿到下降沿的时间即高电平宽度。占空比 CCR2/CCR1。 这种方法硬件自动完成大部分工作效率高且稳定特别适合固定频率PWM的测量但占用了两个捕获通道。3. 详细配置步骤与代码实现我们以STM32F103C8T6的TIM3通道1对应PA6引脚为例实现方案一单通道测脉宽。使用标准外设库StdPeriph_Lib进行讲解思路同样适用于HAL库。3.1 硬件与软件环境准备硬件STM32F103C8T6核心板BluePill板常见ST-Link V2调试下载器示波器或信号发生器用于产生待测方波信号或者一个按键用于产生不定宽度的脉冲杜邦线若干软件Keil MDK-ARM或STM32CubeIDESTM32F1标准外设库引脚连接将信号发生器或按键的一端另一端接地的输出连接到MCU的PA6引脚。同时建议用示波器探头同时监测该引脚以验证信号是否正常输入。3.2 GPIO与定时器基础初始化首先需要开启相关外设的时钟并配置GPIO为输入模式。// 1. 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // TIM3挂在APB1总线上 // 2. 配置GPIOA.6为浮空输入或上拉输入根据信号源决定 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入 // GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 如果信号源是开漏输出可用上拉 GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 初始化定时器基本时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period 0xFFFF; // 自动重装载值ARR设为最大值65535 TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 预分频器PSC关键参数 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; // 时钟分频与滤波器相关通常为DIV1 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // 向上计数模式 TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure);关键参数详解预分频器TIM_Prescaler这是决定测量精度的核心。假设系统主频SYSCLK 72MHzAPB1定时器时钟也是72MHz。预分频器设为71即72-1意味着计数器CNT的时钟频率为72MHz / (711) 1MHz。所以CNT每增加1代表时间过去了1 / 1MHz 1us。如果ARR设为65535那么定时器最大能测量的连续时间为65535 * 1us 65.535ms。你需要根据待测信号的预期频率范围来调整PSC和ARR。频率高PSC可以小一些以提高分辨率周期长ARR要设大或者考虑使用定时器溢出中断来扩展测量范围。3.3 输入捕获通道详细配置接下来配置TIM3的通道1为输入捕获模式。// 4. 配置TIM3通道1为输入捕获模式 TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel TIM_Channel_1; // 选择通道1 TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; // 初始化为上升沿捕获 TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; // 映射到TI1输入 TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; // 输入预分频每个边沿都捕获 TIM_ICInitStructure.TIM_ICFilter 0x0; // 滤波器值0表示不滤波 TIM_ICInit(TIM3, TIM_ICInitStructure); // 5. 使能捕获通道和捕获中断 TIM_Cmd(TIM3, ENABLE); // 使能定时器3 TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // 使能TIM3的通道1捕获中断 // 6. 配置NVIC嵌套向量中断控制器 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel TIM3_IRQn; // TIM3全局中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);配置要点解析TIM_ICSelectionDirectTI表示输入信号直接连接到这个捕获单元。如果是PWM输入模式另一个通道需要配置为IndirectTI。TIM_ICPrescaler这是输入信号的预分频。DIV1表示每个有效边沿都触发捕获。如果设为DIV2则是每2个边沿触发一次可以用于降低中断频率。TIM_ICFilter数字滤波器。当输入信号有高频抖动比如机械按键时这个功能非常有用。它的值范围是0x0到0xF代表以采样频率fDTS由TIM_ClockDivision决定连续采样多少次相同的电平才认为是一个有效的边沿。例如设置ICFilter0xF可以很好地滤除短时间抖动。3.4 中断服务程序与核心测量逻辑这是整个输入捕获程序的“大脑”。我们采用状态机的方式来实现单通道测量高电平脉宽。// 定义全局变量用于存储捕获值和状态 volatile uint32_t Capture_Val1 0, Capture_Val2 0; volatile uint8_t Capture_State 0; // 0:等待上升沿1:已捕获上升沿等待下降沿 volatile uint32_t High_Pulse_Width 0; // 最终计算出的高电平脉宽单位us void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_CC1) ! RESET) // 判断是否是通道1的捕获中断 { switch(Capture_State) { case 0: // 第一次捕获应为上升沿 Capture_Val1 TIM_GetCapture1(TIM3); // 读取上升沿时刻的CCR1值 Capture_State 1; // 重要切换为下降沿捕获 TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling); // 可选清除计数器使测量更直观从0开始计下降沿的时间 // TIM_SetCounter(TIM3, 0); break; case 1: // 第二次捕获应为下降沿 Capture_Val2 TIM_GetCapture1(TIM3); // 读取下降沿时刻的CCR1值 Capture_State 0; // 计算高电平脉宽 if (Capture_Val2 Capture_Val1) { High_Pulse_Width (Capture_Val2 - Capture_Val1); // 差值 } else { // 处理计数器溢出未清CNT的情况下 High_Pulse_Width (0xFFFF - Capture_Val1 Capture_Val2 1); } // 将脉宽值转换为微秒根据PSC71计算 // High_Pulse_Width_us High_Pulse_Width * 1; // 因为1CNT1us // 切换回上升沿捕获准备下一次测量 TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising); break; } TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); // 必须清除中断标志位 } // 通常也需要处理更新溢出中断以扩展测量范围此处省略 }中断逻辑精讲状态机设计使用Capture_State变量清晰地管理测量流程避免了使用多个标志位的混乱。极性切换TIM_OC1PolarityConfig函数用于在中断中动态改变捕获边沿。这是单通道测脉宽的关键操作。溢出处理在case 1的计算中我们考虑了计数器溢出的情况。如果Capture_Val2 Capture_Val1说明在两次捕获之间计数器CNT从ARR65535溢出回到了0。此时脉宽 (ARR - Val1 1) Val2。如果我们在捕获上升沿后清空了计数器TIM_SetCounter(TIM3, 0)则不需要这个判断直接相减即可但这样会丢失两次上升沿之间的绝对时间信息无法测量周期。清除标志位TIM_ClearITPendingBit是绝对必要的一步否则中断会连续触发程序无法继续执行主循环。3.5 主程序与测量结果显示主程序初始化后在一个循环里打印或显示测量结果即可。#include stdio.h // 如果使用串口打印 // ... 假设已初始化USART1... int main(void) { // 系统时钟初始化通常由SystemInit()完成确保72MHz // 初始化串口用于打印 USART1_Init(); // 初始化输入捕获调用前面的初始化函数 InputCapture_Init(); printf(STM32 Input Capture Test Start...\r\n); while(1) { if(High_Pulse_Width ! 0) // 如果有新的测量结果 { // 计算并打印。注意High_Pulse_Width的单位是“计数次数” // 根据预分频计算实际时间Time_us High_Pulse_Width * (PSC1) / SystemCoreClock * 1e6 // 我们这里PSC71, SysClk72e6所以 Time_us High_Pulse_Width * 1; printf(High Pulse Width: %lu us\r\n, High_Pulse_Width); High_Pulse_Width 0; // 清除本次结果等待下一次 } Delay_ms(100); // 简单延时避免打印刷屏 } }4. 进阶应用与深度优化4.1 实现PWM输入模式测量周期与占空比使用PWM输入模式可以更优雅地测量标准PWM信号。我们以TIM2的CH1PA0为例测量输入到PA0的PWM。void PWM_InputMode_Init(void) { // GPIO和TIM时钟开启省略... // 配置PA0为浮空输入TIM2_CH1 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 1us分辨率 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 关键配置PWM输入模式 // 1. 配置输入通道映射 TIM_ICInitTypeDef TIM_ICInitStructure; // 通道1配置为上升沿捕获直接映射到TI1 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(TIM2, TIM_ICInitStructure); // 通道2配置为下降沿捕获间接映射到TI1 TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Falling; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_IndirectTI; // 注意这里是Indirect TIM_ICInit(TIM2, TIM_ICInitStructure); // 2. 配置从模式控制器为复位模式触发源为TI1 TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); // 选择TI1作为触发源 TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); // 从模式复位模式 TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable); // 3. 使能中断 TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2, ENABLE); TIM_Cmd(TIM2, ENABLE); // ... 配置NVIC ... } // 中断服务函数中 volatile uint32_t Period 0, Duty 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { Period TIM_GetCapture1(TIM2); // CCR1的值即为周期单位计数次数 TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } if (TIM_GetITStatus(TIM2, TIM_IT_CC2) ! RESET) { Duty TIM_GetCapture2(TIM2); // CCR2的值即为高电平脉宽 TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); // 此时可以计算占空比Duty_Cycle (float)Duty / Period; } }配置完成后硬件会自动在每次PWM周期的上升沿将计数器复位CCR1捕获的是周期值CCR2捕获的是高电平脉宽。这种方法无需在中断中切换极性效率更高测量更稳定。4.2 使用DMA传输捕获数据以降低CPU负载在高速信号测量如高频PWM或需要连续捕获大量数据的场景下频繁进入中断会消耗大量CPU资源。此时可以将捕获/比较寄存器CCRx与DMA关联让DMA在每次捕获事件发生时自动将CCRx的值搬运到指定的内存数组中。// 1. 开启DMA时钟 (DMA1) RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 2. 配置DMA通道例如TIM2_CH1的捕获对应DMA1_Channel5 DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(TIM2-CCR1); // 外设地址捕获寄存器 DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)Capture_Buffer; // 内存数组地址 DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; // 方向从外设到内存 DMA_InitStructure.DMA_BufferSize BUFFER_SIZE; // 传输数据量 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; // 外设地址固定 DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; // 内存地址递增 DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; // 16位数据 DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环模式存满后从头开始 DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel5, DMA_InitStructure); // 3. 使能DMA DMA_Cmd(DMA1_Channel5, ENABLE); // 4. 配置TIM2的DMA请求 TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE); // 使能通道1捕获的DMA请求这样每次TIM2_CH1发生捕获CCR1的值会自动存入Capture_Buffer数组完全不需要CPU干预。CPU只需要在需要时例如缓冲区半满或全满时通过DMA中断去处理这批数据即可极大地提高了系统效率。4.3 高精度时间测量与误差补偿追求高精度测量时需要考虑以下因素并补偿中断响应延迟从捕获事件发生到CPU进入中断服务函数读取CCR存在延迟。这个时间虽然短通常在微秒级以下但对于纳秒级或极高频率的测量不可忽略。解决方案是使用DMA或者确保中断优先级最高、服务函数极其精简。时钟精度定时器的时钟源APB总线时钟的精度决定了测量的绝对精度。如果使用内部RC振荡器HSI精度较差±1%。对于高精度应用必须使用外部晶振HSE。软件开销在中断服务函数中除了必要的CCR读取和状态切换不要做复杂运算如浮点计算、printf。将原始计数值存入变量在主循环中进行换算和显示。多次平均对于存在抖动的信号可以进行连续多次测量然后取平均值以平滑随机误差。5. 实战调试与经典问题排查5.1 常见问题速查表现象可能原因排查步骤与解决方案完全捕获不到信号1. GPIO模式配置错误。2. 定时器时钟未开启。3. 捕获通道未使能(CCxE)。4. 中断未使能或NVIC未配置。5. 信号电平不匹配如3.3V MCU测5V信号。1. 确认GPIO配置为浮空/上拉/下拉输入而非输出或复用。2. 检查RCC_APBxPeriphClockCmd是否调用。3. 检查TIM_ICInit或TIM_CCxCmd是否执行。4. 检查TIM_ITConfig和NVIC_Init。5. 使用电平转换电路或分压电阻。只能捕获一次后续无反应中断标志位未清除这是最常见的原因在中断服务函数末尾务必调用TIM_ClearITPendingBit(TIMx, TIM_IT_CCx)。测量值波动大不准1. 信号本身有抖动如按键。2. 预分频器(PSC)设置不当分辨率过低。3. 未使用输入滤波器。1. 用示波器观察信号源是否干净。2. 增大系统时钟或减小PSC以提高计时分辨率。3. 增加TIM_ICInitStructure.TIM_ICFilter的值如0x8。测量值恒为0或非常小1. 两次捕获的边沿极性设置反了。2. 在第一次捕获后误清了计数器(CNT)导致第二次捕获值很小。3. 信号脉宽本身极窄。1. 确认TIM_ICPolarity的初始值和切换逻辑。2. 检查代码是否有TIM_SetCounter。3. 用示波器验证信号。测量值巨大或溢出1.ARR值设置过小计数器频繁溢出。2. 未正确处理计数器溢出当不清CNT时。3. 信号周期超过定时器最大测量范围。1. 增大ARR值。2. 在中断中增加溢出处理逻辑见3.4节代码。3. 结合定时器更新溢出中断来扩展范围见下文。PWM输入模式测量错误1. 两个通道的TIM_ICSelection配置错误一个DirectTI一个IndirectTI。2. 从模式(SMCR)未正确配置为复位模式(Reset)。3. 触发源(TS)未选择正确。1. 仔细对照数据手册检查通道映射。2. 确认TIM_SelectSlaveMode和TIM_SelectInputTrigger调用正确。5.2 扩展测量范围处理定时器溢出当待测信号的周期超过定时器最大计数值ARR时就需要处理溢出。我们可以在使能捕获中断的同时使能定时器的更新中断TIM_IT_Update。TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_Update, ENABLE); // 使能捕获和更新中断 // 在NVIC中配置同一个TIM3中断入口。 // 在中断服务函数中增加对更新中断的处理 volatile uint32_t Overflow_Count 0; void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) // 处理溢出 { Overflow_Count; TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } if (TIM_GetITStatus(TIM3, TIM_IT_CC1) ! RESET) { // ... 原有的捕获处理逻辑 ... // 在计算最终时间时需要将溢出次数考虑进去 // Total_Ticks Overflow_Count * (ARR 1) (Capture_Val2 - Capture_Val1); // 注意在捕获上升沿和下降沿时需要记录对应的Overflow_Count快照 // 因为两次捕获之间可能发生多次溢出。 } }这样通过结合CNT值和溢出次数就可以测量任意长的时间理论上是(ARR1) * 2^32个计数周期范围大大扩展。5.3 使用示波器进行联合调试示波器是调试输入捕获功能的“眼睛”。验证信号首先用示波器确认输入到MCU引脚的信号是否符合预期频率、幅值、波形。验证触发可以在GPIO引脚上再连接一个示波器通道在中断服务函数里翻转这个引脚GPIO_WriteBit(GPIOx, GPIO_Pin_x, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOx, GPIO_Pin_x)))。这样每次进入中断示波器上就会看到一个脉冲。通过观察这个脉冲与输入信号边沿的关系可以精确判断中断是否被正确触发以及触发延迟。测量软件开销通过上述方法可以测量出从信号边沿到中断服务函数开始执行的时间这对于评估和优化高精度测量方案至关重要。输入捕获功能就像STM32定时器模块里的一把精密卡尺用好了你能轻松应对各种数字信号测量任务。从简单的按键消抖到复杂的电机编码器读数其底层逻辑都是相通的。希望这篇超详细的拆解能帮你不仅写出能跑的代码更能写出稳定、高效、可靠的代码。在实际项目中多结合示波器观察多思考中断与主循环的数据交互你会对嵌入式系统的实时性有更深的理解。