1. STM32F103 PWM信号测量概述
在无人机、航模和机器人控制系统中,PWM信号是最常见的控制信号之一。作为嵌入式开发者,我经常使用STM32F103系列单片机来处理这类信号。接收机输出的PWM信号通常具有50Hz的频率(周期20ms),脉宽在1ms到2ms之间变化,对应着不同的控制指令,比如油门大小、方向角度等。
STM32F103提供了两种主要的PWM测量方法:通用输入捕获模式和PWM输入模式。这两种方法我都实际应用过多次,各有优缺点。通用输入捕获模式更灵活,可以用于任何定时器通道;而PWM输入模式则是专门为测量PWM信号设计的硬件方案,使用起来更简单可靠。
2. 测量方案对比与选择
2.1 方案对比
| 特性 | 通用输入捕获模式 | PWM输入模式 |
|---|---|---|
| 原理 | 软件记录上升沿和下降沿时刻并计算差值 | 硬件自动测量周期和脉宽 |
| 使用定时器 | 所有通用和高级定时器 | 仅高级定时器(TIM1,TIM8) |
| 硬件资源 | 占用1个通道 | 占用2个输入通道 |
| 软件复杂度 | 较高,需处理边沿切换 | 极低,硬件自动完成 |
| 测量精度 | 高 | 最高 |
| 适用场景 | 多通道测量或通用定时器 | 单通道精确测量 |
2.2 方案选择建议
根据我的经验,选择方案时需要考虑以下几点:
-
如果需要测量多个PWM通道,通用输入捕获模式是更好的选择,因为它可以灵活配置到多个定时器通道上。
-
如果只需要测量1-2个通道,且使用的是高级定时器(TIM1或TIM8),强烈推荐使用PWM输入模式。我在四轴飞行器项目中就采用了这种方案,测量结果非常稳定。
-
资源受限的情况下,比如其他功能已经占用了高级定时器,就只能使用通用输入捕获模式了。
3. PWM输入模式实现详解
3.1 硬件连接
以TIM1为例,PWM信号应连接到PA8引脚(TIM1_CH1)。在实际布线时要注意:
- 信号线尽量短
- 避免与高频信号线平行走线
- 必要时可加10kΩ上拉电阻
3.2 软件配置
以下是完整的初始化代码,我添加了详细注释:
c复制void TIM1_PWM_Input_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE);
// 2. 配置GPIO PA8为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 定时器时基配置
TIM_TimeBaseInitStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; // 1MHz计数频率
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
// 4. 配置PWM输入模式
TIM_SelectInputTrigger(TIM1, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
// 5. 配置输入捕获通道
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_ICInit(TIM1, &TIM_ICInitStructure);
// 配置另一个通道用于捕获脉宽
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
TIM_ICInit(TIM1, &TIM_ICInitStructure);
// 6. 使能中断
TIM_ITConfig(TIM1, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
// 7. 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 8. 使能定时器
TIM_Cmd(TIM1, ENABLE);
}
3.3 中断处理
中断服务函数中直接读取捕获值:
c复制void TIM1_CC_IRQHandler(void) {
if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET) {
PWM_Period = TIM_GetCapture1(TIM1);
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
}
if (TIM_GetITStatus(TIM1, TIM_IT_CC2) != RESET) {
PWM_Pulse = TIM_GetCapture2(TIM1);
TIM_ClearITPendingBit(TIM1, TIM_IT_CC2);
}
}
3.4 测量值转换
在主循环中将计数值转换为实际物理量:
c复制period_us = PWM_Period; // 周期(us)
pulse_width_us = PWM_Pulse; // 脉宽(us)
if (period_us > 0) {
frequency_hz = 1000000.0f / period_us;
duty_cycle = (float)pulse_width_us / period_us * 100.0f;
}
4. 通用输入捕获模式实现
4.1 硬件连接
以TIM2为例,PWM信号连接到PA0引脚(TIM2_CH1)。连接注意事项与PWM输入模式相同。
4.2 软件配置
c复制void TIM2_IC_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// GPIO配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 定时器时基配置
TIM_TimeBaseInitStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; // 1MHz
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 输入捕获配置
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
// 使能中断
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
// NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
4.3 中断处理
c复制void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
if (Is_First_Captured == 0) {
IC_Val1 = TIM_GetCapture1(TIM2);
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling);
Is_First_Captured = 1;
} else {
IC_Val2 = TIM_GetCapture1(TIM2);
if (IC_Val2 > IC_Val1) {
Difference = IC_Val2 - IC_Val1;
} else {
Difference = (0xFFFF - IC_Val1) + IC_Val2 + 1;
}
if (TIM_GetCaptureCPolarity(TIM2, TIM_Channel_1) == TIM_ICPolarity_Falling) {
PulseWidth = Difference;
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising);
} else {
Period = Difference;
Is_First_Captured = 0;
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
}
5. 关键注意事项与优化建议
5.1 定时器配置要点
-
时钟配置:我通常将定时器时钟配置为1MHz(预分频72-1),这样每个计数对应1us,方便计算。对于50Hz的PWM信号(周期20000us),ARR值设为65535足够。
-
滤波设置:如果信号有抖动,可以增加数字滤波。TIM_ICInitStructure.TIM_ICFilter可设置为0x0到0xF,值越大滤波效果越强,但会引入延迟。
5.2 常见问题解决
-
测量值跳动:可能是信号质量问题。解决方法:
- 检查硬件连接
- 增加滤波设置
- 软件上可以做滑动平均滤波
-
计数器溢出:在通用输入捕获模式中必须处理。我的经验是:
- 判断两次捕获值的大小关系
- 如果后值小于前值,说明发生了溢出
- 使用公式(0xFFFF - IC_Val1) + IC_Val2 + 1计算实际差值
-
中断优先级:PWM测量对实时性要求较高,建议设置较高的抢占优先级。
5.3 性能优化技巧
-
减少中断处理时间:在中断服务函数中只做必要的操作,其他处理放到主循环中。
-
使用DMA:对于多通道测量,可以考虑使用DMA将捕获值传输到内存,减少CPU干预。
-
定时器级联:对于需要测量更长周期的情况,可以使用两个定时器级联。
6. 实际应用案例
在最近的一个四轴飞行器项目中,我使用TIM1的PWM输入模式测量接收机的4个通道信号。配置如下:
- TIM1_CH1(PA8) - 油门
- TIM1_CH2(PA9) - 横滚
- TIM2_CH1(PA0) - 俯仰
- TIM2_CH2(PA1) - 偏航
其中TIM1的两个通道使用PWM输入模式,TIM2的两个通道使用通用输入捕获模式。测量结果通过串口发送到上位机显示,实测精度可以达到±1us,完全满足飞行控制需求。
7. 扩展应用
除了测量接收机信号,这些技术还可以应用于:
- 舵机控制信号检测
- 电机转速测量(通过霍尔传感器)
- 超声波测距
- 红外遥控信号解码
我在这些应用中都成功使用过上述方法,效果很好。特别是PWM输入模式,硬件自动处理的特性使得测量非常稳定可靠。
