STM32G4定时器捕获进阶:单定时器双通道测量PWM频率和占空比(避坑float类型)
在嵌入式开发中,精确测量PWM信号的频率和占空比是一个常见但颇具挑战性的任务。本文将深入探讨如何利用STM32G4系列单片机的定时器捕获功能,通过单一定时器的双通道协作实现这一目标,同时分享一个关键的数据类型选择陷阱——为什么必须使用float类型才能正确计算占空比。
1. 硬件连接与基本概念
测量PWM信号通常需要两个关键参数:周期(决定频率)和脉宽(决定占空比)。在STM32G4上,我们可以利用定时器的输入捕获功能,通过巧妙配置实现单定时器双通道测量。
硬件连接建议:
- PWM信号输出引脚连接到定时器的通道1(直接模式)
- 同一PWM信号同时连接到定时器的通道2(间接模式)
- 推荐使用杜邦线连接,确保信号质量
关键术语解释:
- 直接模式:捕获信号直接来自外部引脚
- 间接模式:捕获信号由另一个通道的事件触发
- 占空比:高电平时间占整个周期的百分比
2. CubeMX配置详解
正确配置CubeMX是实现这一功能的基础。以下是关键配置步骤:
2.1 定时器基本配置
- 选择TIM2(或其他支持多通道的定时器)
- 时钟源选择内部时钟
- 预分频器(Prescaler)设置为79(假设系统时钟80MHz,得到1MHz计数频率)
- 自动重装载值保持默认65535
2.2 通道配置
/* TIM2通道1配置(直接模式) */ 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); /* TIM2通道2配置(间接模式) */ sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2);关键参数说明:
| 参数 | 通道1设置 | 通道2设置 | 说明 |
|---|---|---|---|
| 极性 | 上升沿 | 下降沿 | 分别捕获周期和脉宽 |
| 选择 | 直接TI | 间接TI | 建立主从通道关系 |
| 预分频 | 无 | 无 | 保持原始信号时序 |
| 滤波器 | 0 | 0 | 根据信号质量调整 |
注意:间接模式配置后,通道2的捕获将由通道1的事件触发,这是实现单定时器测量的关键。
3. 中断回调函数实现
回调函数是处理捕获数据的核心,需要特别注意通道区分和数据类型选择。
float period = 0, pulseWidth = 0; // 必须使用float类型 float frequency = 0, dutyCycle = 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { // 通道1捕获周期值(上升沿到上升沿) period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SetCounter(htim, 0); // 重置计数器 // 通道2捕获脉宽值(上升沿到下降沿) pulseWidth = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); // 计算频率和占空比 frequency = 1000000.0 / period; // 1MHz时钟 dutyCycle = (pulseWidth / period) * 100.0; // 重新启动捕获 HAL_TIM_IC_Start(htim, TIM_CHANNEL_1); HAL_TIM_IC_Start(htim, TIM_CHANNEL_2); } } }代码关键点解析:
- 数据类型选择:必须使用float类型存储period和pulseWidth,否则整数除法会导致占空比计算错误
- 通道区分:通过
htim->Channel判断中断来源 - 计数器重置:每次捕获周期后重置计数器,确保测量准确性
- 单位转换:1MHz时钟下,计数值直接对应微秒
4. 常见问题与解决方案
4.1 占空比始终为0的问题
这是开发者最常遇到的坑,根本原因是数据类型选择不当:
// 错误示例:使用unsigned int导致占空比始终为0 unsigned int period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); unsigned int pulseWidth = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); float dutyCycle = (pulseWidth / period) * 100; // 整数除法结果为0 // 正确做法:使用float类型 float period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); float pulseWidth = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); float dutyCycle = (pulseWidth / period) * 100;4.2 测量不稳定的处理方案
- 增加数字滤波:在CubeMX中适当配置ICFilter参数
- 多次测量取平均:在回调函数中实现滑动平均算法
- 检查硬件连接:确保信号质量良好,避免接触不良
4.3 高频率PWM的测量限制
测量范围受定时器时钟和自动重装载值限制:
| 时钟频率 | 最大可测频率 | 分辨率 |
|---|---|---|
| 1MHz | ~500kHz | 1us |
| 10MHz | ~5MHz | 0.1us |
提示:对于更高频率测量,可考虑降低时钟分频或使用定时器的外部时钟模式。
5. 实战优化技巧
5.1 动态调整测量模式
根据信号特点动态改变捕获极性:
// 在回调函数中动态切换捕获边沿 if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { static uint8_t edge = 0; TIM_IC_InitTypeDef sConfigIC; if(edge == 0) { sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; edge = 1; } else { sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; edge = 0; } HAL_TIM_IC_ConfigChannel(htim, &sConfigIC, TIM_CHANNEL_1); }5.2 使用DMA减少CPU开销
对于高频测量,可配置DMA自动传输捕获值:
// 启用TIM2通道1和通道2的DMA请求 HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t*)&period, 1); HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_2, (uint32_t*)&pulseWidth, 1);5.3 低功耗优化策略
- 仅在需要测量时使能定时器
- 使用定时器的单次模式
- 适当降低时钟频率
在实际项目中,我发现最容易被忽视的是数据类型的选择问题。曾经花费数小时调试占空比测量,最终发现只是因为使用了整数类型而非浮点数。这个教训让我在后续开发中格外重视变量类型的定义。