1. STM32定时器输入捕获功能入门指南第一次接触STM32的定时器输入捕获功能时我完全被各种专业术语搞晕了。什么上升沿下降沿、捕获寄存器、计数器溢出...听起来就像天书一样。但当我真正理解了它的工作原理后才发现这个功能简直是为测量脉冲信号量身定做的神器。简单来说输入捕获就是定时器的抓拍功能。当检测到指定边沿上升沿或下降沿时它会立即把当前计数器的值拍照保存到捕获寄存器中。通过比较两次捕获的数值差我们就能精确计算出脉冲的宽度或周期。这就像用秒表记录运动员的跑步时间一样只不过STM32的秒表精度可以达到纳秒级。在实际项目中这个功能最常见的应用场景包括测量PWM信号的占空比计算外部信号的频率检测按键按下的持续时间解码红外遥控信号2. 硬件配置与初始化详解2.1 GPIO配置要点配置输入捕获的第一步是设置正确的GPIO模式。这里有个坑我踩过好几次如果测量高电平脉宽GPIO必须配置为下拉输入反之则要配置为上拉输入。这是因为STM32的输入捕获功能对信号质量非常敏感不正确的上下拉配置会导致误触发。以测量高电平脉宽为例正确的配置应该是GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLDOWN; // 关键配置 HAL_GPIO_Init(GPIOA, GPIO_InitStruct);2.2 定时器基础配置定时器的时钟配置直接影响测量精度。假设使用72MHz的系统时钟经过72分频后得到1MHz的计数频率即每个计数代表1微秒。自动重装载值设为6553516位定时器的最大值这样单次最大可测量65.535ms的脉宽。配置示例TIM_HandleTypeDef htim2; htim2.Instance TIM2; htim2.Init.Prescaler 71; // 72MHz/(711)1MHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 65535; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim2);3. HAL库输入捕获配置实战3.1 输入捕获参数详解HAL库使用TIM_IC_InitTypeDef结构体配置输入捕获参数几个关键参数需要特别注意ICPolarity触发边沿选择TIM_INPUTCHANNELPOLARITY_RISING为上升沿TIM_INPUTCHANNELPOLARITY_FALLING为下降沿ICSelection输入映射方式通常选择TIM_ICSELECTION_DIRECTTI直接映射ICPrescaler输入分频一般保持TIM_ICPSC_DIV1不分频ICFilter数字滤波器可设置为0-15值越大抗干扰能力越强但响应越慢完整配置示例TIM_IC_InitTypeDef sConfigIC; sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV1; sConfigIC.ICFilter 0; HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1);3.2 中断配置技巧输入捕获需要开启两个中断捕获中断和更新中断。前者处理边沿触发事件后者处理计数器溢出。在HAL库中只需调用以下函数HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); // 启动捕获中断 HAL_TIM_Base_Start_IT(htim2); // 启动更新中断在CubeMX中配置时记得勾选这两个中断并设置合适的中断优先级。我的经验是给捕获中断更高的优先级因为它的实时性要求更高。4. 中断服务程序与数据处理4.1 状态机设计思路处理输入捕获中断最可靠的方法是使用状态机。我通常定义三个状态等待第一个上升沿等待下降沿等待第二个上升沿用于频率测量对应的标志位结构体如下typedef struct { uint8_t edge_state; // 当前边沿状态 uint16_t rise_value; // 上升沿捕获值 uint16_t fall_value; // 下降沿捕获值 uint16_t overflow_count; // 溢出次数 } IC_HandleTypeDef;4.2 完整中断处理示例在HAL库中我们需要重写两个回调函数// 定时器溢出回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM2) { icHandle.overflow_count; } } // 输入捕获回调 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM2) { switch(icHandle.edge_state) { case 0: // 等待上升沿 icHandle.rise_value HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); icHandle.overflow_count 0; // 切换为下降沿捕获 sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_FALLING; HAL_TIM_IC_ConfigChannel(htim, sConfigIC, TIM_CHANNEL_1); icHandle.edge_state 1; break; case 1: // 等待下降沿 icHandle.fall_value HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 切换回上升沿捕获 sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; HAL_TIM_IC_ConfigChannel(htim, sConfigIC, TIM_CHANNEL_1); icHandle.edge_state 0; // 计算脉宽 CalculatePulseWidth(); break; } } }5. 测量结果计算与误差处理5.1 脉宽计算算法考虑计数器溢出的情况完整的脉宽计算公式为脉宽 (溢出次数 × 定时周期) (下降沿值 - 上升沿值)转换为代码uint32_t CalculatePulseWidth(void) { uint32_t pulse_width 0; if(icHandle.fall_value icHandle.rise_value) { pulse_width icHandle.overflow_count * 65536 (icHandle.fall_value - icHandle.rise_value); } else { pulse_width (icHandle.overflow_count - 1) * 65536 (65536 - icHandle.rise_value icHandle.fall_value); } return pulse_width; // 返回单位为定时器时钟周期 }5.2 频率测量实现频率测量需要捕获两个上升沿然后计算时间差uint32_t CalculateFrequency(void) { uint32_t period 0; if(second_rise_value first_rise_value) { period overflow_between_edges * 65536 (second_rise_value - first_rise_value); } else { period (overflow_between_edges - 1) * 65536 (65536 - first_rise_value second_rise_value); } return SystemCoreClock / (prescaler 1) / period; // 返回Hz为单位的频率 }5.3 精度提升技巧通过实测发现以下几个方法可以显著提高测量精度使用更高的定时器时钟频率如使用APB总线时钟而非系统时钟适当增加数字滤波值ICFilter消除毛刺多次测量取平均值在信号进入GPIO前添加硬件滤波电路6. 常见问题排查指南6.1 捕获不到中断遇到这种情况建议按以下步骤排查确认GPIO模式配置正确输入模式正确上下拉检查定时器时钟是否使能验证中断优先级配置使用逻辑分析仪检查信号是否真的到达GPIO引脚6.2 测量结果不稳定测量值跳动通常由以下原因导致信号本身有抖动添加硬件滤波中断处理时间过长优化代码或提高优先级定时器配置不当调整预分频值6.3 长脉宽测量不准确当脉宽超过定时器最大周期时必须正确处理溢出中断。常见错误包括未清零溢出计数器在中断中未及时清除标志位计算时未考虑溢出情况7. 进阶应用与优化建议7.1 PWM输入模式STM32的高级定时器支持PWM输入模式可以自动测量周期和占空比。配置方法TIM_IC_InitTypeDef sConfigIC; sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_INDIRECTTI; // 关键区别 sConfigIC.ICPrescaler TIM_ICPSC_DIV1; sConfigIC.ICFilter 0; HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_2); // 从通道 HAL_TIM_PWM_Init(htim2, TIM_CHANNEL_1); // 主通道7.2 使用DMA降低CPU负载对于高频信号测量可以使用DMA直接将捕获值传输到内存HAL_TIM_IC_Start_DMA(htim2, TIM_CHANNEL_1, (uint32_t*)captureBuffer, CAPTURE_BUFFER_SIZE);7.3 低功耗优化技巧在电池供电应用中仅在需要测量时使能定时器使用LPTIM低功耗定时器适当降低定时器时钟频率利用唤醒中断模式