在嵌入式开发中,精确测量PWM信号的频率和占空比是一个常见需求。对于STM32开发者来说,HAL库提供了多种实现方式,但大多数教程只介绍最基本的输入捕获方法。本文将深入对比两种截然不同的技术路线:传统输入捕获中断方案与鲜为人知的定时器从模式+编码器接口组合方案。
输入捕获是STM32测量PWM最直观的方法,通过捕获上升沿和下降沿的时间戳来计算周期和占空比。让我们先快速回顾这种方法的实现要点:
在CubeMX中配置输入捕获需要关注以下关键参数:
c复制/* TIM1 输入捕获配置示例 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 71; // 72MHz/(71+1) = 1MHz计数频率
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 30000; // 自动重装载值
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
关键中断配置:
输入捕获的核心逻辑集中在中断回调函数中:
c复制void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
static uint32_t rise_edge, fall_edge, period;
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
if(__HAL_TIM_GET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1) == TIM_INPUTCHANNELPOLARITY_RISING) {
rise_edge = __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
} else {
fall_edge = __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1);
period = fall_edge - rise_edge;
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
// 计算占空比和频率
duty_cycle = (float)(fall_edge - rise_edge) / period * 100;
frequency = (float)1000000 / period; // 假设计数器频率为1MHz
}
}
}
虽然输入捕获方法直观易懂,但在实际应用中存在几个明显缺点:
| 问题类型 | 具体表现 | 影响程度 |
|---|---|---|
| CPU负载 | 每个边沿触发中断 | 高频PWM时中断频繁 |
| 测量精度 | 依赖中断响应时间 | 微秒级抖动 |
| 多路测量 | 需要多个定时器资源 | 资源占用大 |
| 高频信号 | 计数器溢出风险 | 测量范围受限 |
提示:当PWM频率超过10kHz时,输入捕获方案的中断开销可能占用超过50%的CPU时间。
STM32定时器的从模式(slave mode)配合编码器接口(encoder interface)可以构建一个硬件自动化的PWM测量系统,大幅降低CPU干预。
这种方案的核心理念是利用两个定时器协同工作:
信号流向示意图:
code复制PWM信号 → 主定时器(编码器模式) → 从定时器(门控模式)
c复制/* 编码器模式配置 */
TIM_Encoder_InitTypeDef encoder_config = {
.EncoderMode = TIM_ENCODERMODE_TI12,
.IC1Polarity = TIM_ICPOLARITY_RISING,
.IC1Selection = TIM_ICSELECTION_DIRECTTI,
.IC1Prescaler = TIM_ICPSC_DIV1,
.IC1Filter = 0,
.IC2Polarity = TIM_ICPOLARITY_RISING,
.IC2Selection = TIM_ICSELECTION_DIRECTTI,
.IC2Prescaler = TIM_ICPSC_DIV1,
.IC2Filter = 0
};
HAL_TIM_Encoder_Init(&htim2, &encoder_config);
c复制/* 从模式配置 */
TIM_SlaveConfigTypeDef slave_config = {
.SlaveMode = TIM_SLAVEMODE_GATED,
.InputTrigger = TIM_TS_TI1FP1,
.TriggerPolarity = TIM_TRIGGERPOLARITY_NONINVERTED,
.TriggerFilter = 0
};
HAL_TIM_SlaveConfigSynchro(&htim3, &slave_config);
与传统输入捕获相比,新方案的代码量减少约60%:
c复制// 初始化定时器
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
HAL_TIM_Base_Start(&htim3);
// 主循环中读取测量值
void MeasurePWM() {
uint32_t period = __HAL_TIM_GET_COUNTER(&htim2);
uint32_t high_time = __HAL_TIM_GET_COUNTER(&htim3);
if(period > 0) {
float frequency = (float)SystemCoreClock / period;
float duty_cycle = (float)high_time / period * 100;
printf("Freq: %.2f Hz, Duty: %.2f%%\n", frequency, duty_cycle);
}
}
| 指标 | 输入捕获方案 | 从模式+编码器方案 |
|---|---|---|
| 最大测量频率 | 约50kHz | 可达1MHz |
| CPU占用率 | 高(中断频繁) | 极低(无中断) |
| 测量精度 | ±1us | ±0.1us |
| 多路扩展 | 需要多个定时器 | 可级联多个从定时器 |
| 代码复杂度 | 高(需处理多个中断) | 低(仅需读取计数器) |
| 适用场景 | 低频单路PWM | 高频/多路PWM系统 |
输入捕获方案资源需求:
从模式方案资源需求:
注意:从模式方案需要确保两个定时器使用相同的时钟源,否则测量结果会有偏差。
根据项目需求选择合适的测量方案:
选择输入捕获方案当:
选择从模式方案当:
对于从模式方案,可以通过以下方式进一步提升性能:
c复制HAL_DMA_Start(&hdma_tim2, (uint32_t)&htim2->CNT, (uint32_t)buffer, 2);
c复制// TIM2为主,TIM3/TIM4为从
HAL_TIM_SlaveConfigSynchro(&htim3, &slave_config);
HAL_TIM_SlaveConfigSynchro(&htim4, &slave_config);
c复制void AdjustARR(TIM_HandleTypeDef *htim, float expected_freq) {
uint32_t new_arr = (uint32_t)(SystemCoreClock / expected_freq);
__HAL_TIM_SET_AUTORELOAD(htim, new_arr);
}
在实际项目中,我发现从模式方案在电机控制应用中表现尤为出色。例如在无刷电机控制系统中,需要同时监测多路霍尔传感器输出的PWM信号,传统输入捕获方案会导致CPU负载过高,而采用定时器从模式组合则能轻松应对。