原理:为什么软件中断会引入误差?
输入捕获测量PWM频率的常规流程:
- 信号上升沿触发捕获,CCR1记录当前CNT值,同时产生中断。
- 在中断服务程序中读取CCR1,计算与上一次捕获值的差值,得到周期。
- 手动将CNT清零,准备下一次测量。
问题在于:中断响应存在延迟(尤其是高优先级中断嵌套时)。从信号边沿到实际执行CNT=0,可能经过几十个时钟周期。这导致每次测量的起始点不一致,频率越高误差越大。例如72MHz时钟下,10个周期延迟就带来约0.14us误差,对100kHz信号(周期10us)而言误差达1.4%。
解决思路:让硬件自动完成“捕获+复位”,无需软件干预。STM32定时器的从模式复位触发功能正好满足:将输入捕获通道配置为触发源,当检测到上升沿时,硬件自动将CNT清零,同时触发捕获事件。这样,每次捕获的CCR值直接就是周期(以计数为单位),且无任何软件延迟。
配置步骤(以STM32F103 TIM2为例)
1. 定时器基础设置
- 时钟源:内部时钟,预分频器设为72-1(72MHz/72=1MHz,即1us分辨率)。
- 计数模式:向上计数。
- 自动重装载值:0xFFFF(最大,避免溢出干扰)。
2. 输入捕获通道配置
- 通道1(TI1)映射到IC1,捕获边沿:上升沿。
- 输入滤波:根据信号质量设置,例如ICFilter=0x0(无滤波)或0x3(8个时钟采样)。
- 预分频器:不分频。
3. 从模式配置
- 从模式选择:复位模式(Reset Mode)。
- 触发源:TI1FP1(即通道1的滤波后信号)。
这样,当TI1出现上升沿时,硬件自动复位CNT,同时IC1捕获当前CNT值(复位前瞬间的值)。由于复位和捕获同时发生,CCR1中保存的就是上一个周期的计数值。
4. 中断配置
- 使能捕获/比较1中断(CC1IE),用于读取CCR1并计算频率。
- 注意:无需在中断中清零CNT,硬件已自动完成。
完整代码示例(基于HAL库)
// timer.c
#include "stm32f1xx_hal.h"
TIM_HandleTypeDef htim2;
volatile uint32_t captureValue = 0;
volatile uint32_t frequency = 0;
void TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 72 - 1; // 1MHz计数频率
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&htim2);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
HAL_TIM_IC_Init(&htim2);
// 从模式:复位触发,触发源TI1FP1
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig);
// 输入捕获通道1配置
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);
// 使能捕获中断
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC1);
// 启动定时器和捕获
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
// 中断服务函数
void TIM2_IRQHandler(void)
{
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_CC1) != RESET)
{
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
captureValue = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
// 计算频率:计数频率1MHz,周期 = captureValue (us)
if (captureValue > 0)
{
frequency = 1000000 / captureValue; // 单位Hz
}
}
}
}
注意事项
- 信号频率范围:由于CNT为16位,最大计数值65535,对应最低频率约15.26Hz(1MHz/65535)。若需测更低频率,可增大预分频器或使用32位定时器(如TIM5)。
- 输入滤波:若信号有噪声,适当设置ICFilter(如0x3),但会引入额外延迟,需权衡。
- 中断优先级:虽然硬件复位消除了延迟,但中断服务程序应尽量简短,避免影响其他任务。
- 首次捕获:第一次捕获时CNT可能未从0开始,导致第一个周期值不准,可丢弃第一次结果。
- 从模式触发源:必须选择与捕获通道对应的TIxFPx,例如通道1对应TI1FP1,通道2对应TI2FP2。
- HAL库版本:不同系列HAL库函数名可能略有差异,请参考对应手册。
总结
通过从模式复位触发,STM32定时器能够硬件自动完成“捕获+清零”,彻底消除软件中断延迟带来的误差。该方法适用于中高频PWM测量,精度可达计数时钟周期(如1us)。结合输入滤波和适当的分频,可进一步扩展测量范围。掌握这一技巧,你的嵌入式频率测量将更加精准可靠。