1. 项目概述
PWM信号测量是嵌入式开发中的一项基础但至关重要的技能。在电机控制、电源管理、LED调光等应用中,准确获取PWM信号的频率和占空比往往是系统正常运行的前提。STM32系列MCU的输入捕获功能为此提供了硬件级的解决方案,而HAL库则大大简化了开发流程。
我曾在一个无人机电调项目中,需要实时监测来自飞控的PWM控制信号。当时由于对输入捕获理解不够深入,导致测量结果频繁跳变,后来通过系统性地梳理STM32的定时器机制才彻底解决问题。本文将分享基于HAL库的完整实现方案,包含那些手册上不会写的实战细节。
2. 硬件原理与配置
2.1 定时器输入捕获原理
STM32的通用定时器(如TIM2-TIM5)具有专门的输入捕获通道。当检测到边沿跳变时,硬件会自动记录当前计数器值,并可选地触发中断。测量PWM的关键在于:
- 频率测量:捕获两个上升沿的时间间隔
- 占空比测量:捕获上升沿与相邻下降沿的时间间隔
注意:不同系列STM32的定时器结构略有差异。例如F1系列的基本定时器没有输入捕获功能,而F4/H7系列的高级定时器支持更复杂的捕获模式。
2.2 硬件连接方案
推荐电路设计要点:
- 信号输入端串联100Ω电阻保护IO口
- 并联10pF电容滤除高频噪声
- 对于3.3V以上信号,需添加电平转换电路
c复制// 典型引脚配置(以STM32F407 TIM3_CH1为例)
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
3. HAL库实现详解
3.1 定时器初始化
关键配置参数计算示例:
- 假设系统时钟84MHz,预分频设为84-1 → 计数器时钟1MHz(1μs分辨率)
- 自动重装载值设为65535(16位最大值)
- 捕获极性设置为上升沿和下降沿交替触发
c复制TIM_HandleTypeDef htim3;
void MX_TIM3_Init(void)
{
TIM_IC_InitTypeDef sConfigIC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 84-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&htim3);
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
}
3.2 捕获中断处理
采用双沿捕获法的核心逻辑:
- 上升沿触发时记录计数器值T1
- 下降沿触发时记录计数器值T2
- 下一个上升沿触发时记录T3
- 计算:
- 周期 = T3 - T1
- 高电平时间 = T2 - T1
- 占空比 = (T2 - T1) / (T3 - T1)
c复制void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static uint32_t t1, t2;
static uint8_t capture_stage = 0;
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
switch(capture_stage) {
case 0: // 第一个上升沿
t1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
capture_stage = 1;
break;
case 1: // 下降沿
t2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);
// 计算占空比
duty_cycle = (float)(t2 - t1) / (float)period * 100;
capture_stage = 0;
break;
}
}
}
4. 精度优化与抗干扰
4.1 提高测量精度的方法
-
时钟源选择:
- 使用外部晶振可获得更稳定的时钟基准
- 启用定时器的时钟同步功能(如TIMx_SMCR寄存器的ECE位)
-
数字滤波配置:
- 通过TIMx_CCMRx寄存器的ICF位设置合适的滤波器
- 计算公式:采样频率 = fCK_INT / (N+1)
-
多次采样平均:
c复制#define SAMPLE_NUM 5 uint32_t sum_period = 0; for(int i=0; i<SAMPLE_NUM; i++) { sum_period += period; while(!new_measurement); // 等待新测量完成 new_measurement = 0; } float avg_period = (float)sum_period / SAMPLE_NUM;
4.2 常见干扰处理方案
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 测量值跳变 | 信号抖动 | 增加数字滤波器值 |
| 偶发错误数据 | 中断冲突 | 提高捕获中断优先级 |
| 低频测量不准 | 计数器溢出 | 改用32位定时器或分频方案 |
| 占空比误差大 | 边沿检测延迟 | 校准IO口响应时间 |
5. 扩展应用实例
5.1 多通道并行测量
通过DMA实现多通道捕获的配置要点:
- 启用TIMx_DCR寄存器的DBL位设置突发模式
- 配置DMA循环模式传输捕获寄存器值
- 示例DMA初始化:
c复制hdma_tim3_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim3_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim3_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim3_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_tim3_ch1.Init.Mode = DMA_CIRCULAR;
5.2 超低频PWM测量方案
对于频率低于10Hz的信号,可采用以下优化:
- 降低定时器时钟分频(如1kHz计数频率)
- 启用定时器从模式+触发输入
- 配合看门狗定时器检测信号丢失
c复制// 从模式配置示例
TIM_SlaveConfigTypeDef sSlaveConfig;
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig);
6. 调试技巧与问题排查
6.1 逻辑分析仪调试法
-
同时捕获以下信号:
- PWM输入信号
- 捕获中断触发信号(通过GPIO模拟)
- 计算结果的串口输出
-
使用PulseView或Saleae软件分析时序关系
6.2 典型问题排查指南
-
无捕获中断:
- 检查定时器时钟是否使能
- 验证GPIO复用功能配置
- 测量输入信号电压是否达标
-
测量值偏小:
- 检查预分频器设置
- 确认计数器方向(UP/DOWN)
- 测试不同输入信号频率对比
-
占空比误差大:
- 校准输入比较器的响应时间
- 检查中断延迟(可通过Toggle GPIO测量)
- 尝试调整数字滤波器值
在最近的一个工业控制器项目中,我们发现当PWM频率超过1MHz时,测量结果会出现系统性偏差。最终通过以下措施解决:
- 将IO口速度设置为最高(Very_High)
- 使用定时器的输入预分频功能(ICPSC=2分频)
- 在捕获中断中禁用其他非关键中断