1. 什么是TIM输入捕获模式
第一次接触STM32的输入捕获功能时,我完全被那些专业术语搞晕了。后来在实际项目中用了几次才发现,这其实就是个"信号秒表"功能。想象一下,你手里拿着秒表,看到信号线上出现跳变就按下计时键,记录下当前时间——这就是输入捕获的本质。
STM32的每个通用定时器都有4个独立的输入捕获通道,可以监测GPIO引脚上的电平跳变。当检测到指定边沿(上升沿或下降沿)时,会自动把当前计数器的值锁存到捕获寄存器中。这个功能特别适合测量:
- PWM信号的频率和占空比
- 脉冲宽度
- 信号周期
- 外部事件的时间间隔
我最近做的一个智能车项目就用这个功能来测量电机转速。通过霍尔传感器产生的脉冲信号,用输入捕获计算转速,比用外部中断+定时器的方式稳定多了。
2. 硬件电路连接要点
在实际接线时,我踩过不少坑。这里分享几个关键注意事项:
2.1 引脚映射关系
不同型号STM32的定时器通道对应引脚不同。以STM32F103C8T6为例:
- TIM2_CH1 → PA0
- TIM3_CH1 → PA6
- TIM4_CH1 → PB6
一定要查清楚数据手册中的"Alternate function mapping"表格。我有次调试半天没反应,最后发现是引脚接错了。
2.2 信号电平匹配
STM32的IO口有几种输入模式:
- 浮空输入:适合有外部上/下拉电阻的电路
- 上拉输入:默认高电平,适合开漏输出的信号源
- 下拉输入:默认低电平
测量PWM信号时,我习惯配置为上拉输入,这样即使信号线断开也不会误触发。
2.3 抗干扰设计
工业现场常有干扰,可以:
- 在信号线上加100Ω电阻和100nF电容组成低通滤波
- PCB布局时缩短信号走线
- 使用屏蔽线缆传输信号
有次在电机旁测量,信号抖动严重,加了RC滤波后立刻稳定了。
3. 寄存器配置详解
理解了硬件原理后,来看关键的寄存器配置。以TIM3_CH1为例:
3.1 时基单元配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 72-1; // 72分频 TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);这里72MHz主频经过72分频得到1MHz计数频率,每个计数代表1μs。Period设为最大值65535,可以测量最长65.535ms的周期。
3.2 输入捕获配置
TIM_ICInitTypeDef TIM_ICInitStructure; 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(TIM3, &TIM_ICInitStructure);滤波参数很关键,在噪声环境中可以设置为0xF(最大滤波),但会引入延迟。
4. PWM测量实战代码
测量PWM需要同时捕获上升沿和下降沿,有两种实现方式:
4.1 单通道切换极性
uint32_t riseTime, fallTime, period, duty; void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_CC1)) { if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)) { // 上升沿 riseTime = TIM_GetCapture1(TIM3); TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling); // 切换为下降沿 } else { // 下降沿 fallTime = TIM_GetCapture1(TIM3); period = riseTime - fallTime; duty = (riseTime - fallTime) * 100 / period; TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising); // 切换回上升沿 } TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); } }这种方法节省硬件资源,但频繁切换会增加软件开销。
4.2 PWMI双通道模式
更高效的方式是使用PWM输入模式,硬件自动完成双沿捕获:
TIM_ICInitTypeDef TIM_ICInitStructure; 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_PWMIConfig(TIM3, &TIM_ICInitStructure); // 自动配置互补通道 TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // 选择触发源 TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); // 从模式复位此时:
- CCR1保存周期值
- CCR2保存高电平时间
- 占空比 = CCR2/CCR1
5. 精度优化技巧
在实际项目中,我总结了这些提高测量精度的方法:
5.1 时钟源选择
- 使用外部晶振(8MHz)比内部RC振荡器更稳定
- 在RCC配置中开启PLL倍频到72MHz
- 定时器时钟源选APB1(36MHz)时要开启x2倍频
5.2 分频系数计算
测量不同频率范围时的分频策略:
- 高频信号(>10kHz):预分频设为72-1,计时频率1MHz
- 低频信号(<1kHz):预分频设为720-1,计时频率100kHz
- 超低频信号:使用32位定时器(如TIM2)
5.3 中断优化
采用DMA传输捕获值可以减少中断延迟:
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM3->CCR1; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&captureValues; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_Init(DMA1_Channel6, &DMA_InitStructure); TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);6. 常见问题排查
调试输入捕获时,我遇到过这些典型问题:
6.1 捕获不到信号
检查步骤:
- 用示波器确认信号确实到达MCU引脚
- 检查GPIO模式是否正确配置为输入
- 验证定时器时钟是否使能
- 检查中断优先级和使能位
6.2 测量值不稳定
可能原因:
- 信号抖动:增加滤波器参数
- 中断延迟:优化中断服务函数
- 计数器溢出:减小预分频或使用32位定时器
6.3 占空比计算错误
常见计算误区:
// 错误:未考虑计数器复位 duty = CCR2 / CCR1 * 100; // 正确:加1补偿 duty = (CCR2 + 1) * 100 / (CCR1 + 1);高频时这个+1补偿特别重要,我有次测量100kHz PWM,误差达到5%,就是漏了这个细节。