STM32定时器输入捕获测量PWM占空比:误差来源与补偿策略深度解析
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式开发中,利用STM32定时器输入捕获模式测量PWM信号的频率和占空比是常见需求,但实际测量结果常因硬件限制、软件处理不当而引入误差。本文深入剖析输入捕获测量PWM占空比的三大误差来源:捕获时刻量化误差、中断响应延迟、计数器溢出处理缺陷,并针对每种误差给出具体的补偿策略与代码实现。通过理论分析与实践验证,帮助开发者将测量精度从微秒级提升至纳秒级,适用于电机控制、电源监测等对占空比精度要求苛刻的嵌入式场景。
# STM32定时器输入捕获测量PWM占空比:误差来源与补偿策略深度解析
## 一、输入捕获测量PWM的基本原理
STM32定时器的输入捕获模式通过检测边沿信号(上升沿或下降沿)并记录当前计数器值,从而计算信号的时间参数。测量PWM占空比通常采用**双通道捕获**或**单通道交替捕获**:
- **双通道捕获**:通道1捕获上升沿,通道2捕获下降沿,通过两次捕获值之差计算高电平时间。
- **单通道交替捕获**:同一通道连续捕获上升沿和下降沿,但需在中断中切换触发边沿。
核心公式:
- 周期 = (捕获值2 - 捕获值1) × 定时器时钟周期
- 占空比 = 高电平时间 / 周期 × 100%
## 二、三大误差来源深度剖析
### 1. 捕获时刻量化误差
定时器计数器以固定时钟频率递增,捕获事件发生在两个计数时钟之间时,捕获值只能取最近的整数计数值,导致**±1个计数时钟周期的量化误差**。例如,定时器时钟为72MHz,则量化误差为±13.9ns。
**补偿策略**:
- 提高定时器时钟频率(如使用PLL倍频)
- 使用定时器输入滤波器和预分频器优化信号质量
- 多次测量取平均,利用随机性抵消量化误差
### 2. 中断响应延迟误差
在捕获中断中读取CCR寄存器时,若中断响应不及时(如被高优先级中断抢占),会导致捕获值读取延迟。但需注意:**CCR寄存器是硬件锁存的**,捕获事件发生时自动保存计数器值,因此中断延迟不会影响捕获值本身,但会影响后续计算的时间基准。若在中断中执行复杂计算,可能丢失后续捕获事件。
**补偿策略**:
- 使用DMA传输捕获值,减少CPU介入
- 中断服务函数中仅做数据保存,计算放到主循环
- 设置中断优先级为最高,避免抢占
### 3. 计数器溢出处理缺陷
当PWM周期大于定时器溢出周期时,计数器会回绕,导致捕获值差值为负或异常。例如,16位定时器在72MHz下最大测量周期为910μs,若PWM周期为1ms,则捕获值会溢出。
**补偿策略**:
- 使用32位定时器(如TIM2/TIM5)
- 在中断中检测计数器溢出标志(UIF),并维护溢出计数变量
- 计算时考虑溢出次数,公式修正为:
`时间差 = (捕获值2 + 溢出次数×溢出周期) - 捕获值1`
## 三、完整代码示例(基于STM32F407,TIM2,72MHz)
以下代码实现双通道捕获测量PWM占空比,包含溢出处理和DMA传输优化。
```c
// 定时器初始化
void TIM2_Capture_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
DMA_InitTypeDef DMA_InitStruct;
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
// GPIO配置:PA0为TIM2_CH1,PA1为TIM2_CH2
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM2);
// 定时器时基:72MHz,不分频
TIM_TimeBaseStruct.TIM_Prescaler = 0;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = 0xFFFFFFFF; // 32位定时器,最大周期
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// 输入捕获配置:CH1上升沿,CH2下降沿
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICFilter = 0x0F; // 输入滤波器,滤除毛刺
TIM_ICInit(TIM2, &TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM2, &TIM_ICInitStruct);
// DMA配置:将捕获值自动传输到内存
DMA_InitStruct.DMA_Channel = DMA_Channel_0; // TIM2_CH1映射到DMA1_Stream5_Channel0
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1;
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&capture_buf[0];
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStruct.DMA_BufferSize = 2;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_Init(DMA1_Stream5, &DMA_InitStruct);
// 使能DMA和定时器
DMA_Cmd(DMA1_Stream5, ENABLE);
TIM_DMACmd(TIM2, TIM_DMA_CC1DE | TIM_DMA_CC2DE, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
// 主循环中计算占空比
void Calculate_DutyCycle(void) {
static uint32_t last_capture1, last_capture2;
uint32_t capture1 = capture_buf[0];
uint32_t capture2 = capture_buf[1];
uint32_t period, high_time;
float duty;
// 考虑溢出(32位定时器,溢出周期为2^32/72MHz ≈ 59.6秒,此处简化处理)
if (capture2 > capture1) {
high_time = capture2 - capture1;
} else {
high_time = (0xFFFFFFFF - capture1) + capture2 + 1;
}
// 周期计算:需要捕获两个上升沿,此处假设通过DMA循环获取,实际需增加一个通道或使用PWM输入模式
// 为简化,假设周期已知或通过另一通道获取
period = 10000; // 示例值
duty = (float)high_time / period * 100.0f;
printf("Duty Cycle: %.2f%%\r\n", duty);
}
```
## 四、补偿策略实践与注意事项
### 1. 硬件滤波优化
- 设置`TIM_ICFilter`为适当值(如0x0F),可滤除高频噪声,但会增加信号延迟,需权衡。
- 在PCB布局上,PWM信号线远离高频干扰源,并加RC滤波。
### 2. 软件补偿技巧
- **多次采样取平均**:连续测量N次,去除最大值和最小值后取平均,可有效减小随机误差。
- **校准偏移**:使用已知占空比的信号(如50%)进行校准,计算偏移量并修正。
- **使用PWM输入模式**:STM32部分定时器支持PWM输入模式,硬件自动捕获周期和占空比,减少软件干预。
### 3. 代码注意事项
- **DMA缓冲区大小**:若使用DMA传输,确保缓冲区足够大,避免数据覆盖。
- **中断优先级**:若使用中断而非DMA,务必设置NVIC优先级为最高,且中断服务函数尽量短。
- **溢出处理**:即使使用32位定时器,在极端长时间运行下也可能溢出,建议增加溢出计数变量。
- **编译器优化**:在计算时间差时,使用`volatile`关键字声明捕获变量,防止编译器优化导致读取错误。
## 五、总结
通过深入分析输入捕获的误差来源,并采用DMA传输、溢出补偿、滤波优化等策略,可将PWM占空比测量精度提升至纳秒级。实际应用中,建议结合具体硬件环境进行校准,并针对信号特性选择合适的滤波参数。掌握这些技巧,将使你的嵌入式系统在电机控制、电源管理等场景中表现更出色。