第2章:PWM控制原理

各位同学,今天我们来聊聊PWM。说实话,PWM这东西在嵌入式领域太常见了,但真正吃透它的人并不多。我记得刚入行那会儿,以为PWM就是调调占空比让电机转起来就完事了。直到有一次做导弹舵机项目,舵机在高频振动下死活对不准角度,我才意识到——PWM的每个参数背后都有门道。

2.1 什么是PWM?

PWM,全称Pulse Width Modulation,脉宽调制。说白了,就是用数字信号模拟出模拟效果。你想想看,单片机只能输出高电平和低电平,但舵机需要的是连续的角度控制,怎么办?

答案就是:让高电平持续的时间长短不同。舵机内部有个比较器,它会根据高电平的宽度来判断你要转多少度。这就是PWM的核心思想——用脉冲宽度传递信息。

核心公式:

舵机角度 = (占空比 - 最小占空比) / (最大占空比 - 最小占空比) × 角度范围

举个例子:如果舵机范围是0°~180°,对应脉宽0.5ms~2.5ms,那么1.5ms脉宽就是90°。

2.2 频率:别小看这个参数

频率决定了PWM信号每秒重复多少次。对于舵机来说,标准频率是50Hz,也就是20ms一个周期。为什么是50Hz?

我个人的习惯是,先查舵机的手册。大部分模拟舵机内部用的是20ms周期的参考信号,你给个50Hz正好匹配。但数字舵机就不一样了,有些能支持到200Hz甚至更高。

频率选错了会怎样?我曾经在一个项目中,把频率设成了100Hz,结果舵机嗡嗡响,角度还抖得厉害。后来才发现,频率太高,舵机内部的电机还没来得及响应,下一个脉冲又来了,整个系统就乱套了。

避坑指南:

我曾经遇到过频率不匹配导致舵机烧毁的情况。频率太低,舵机内部电机长时间处于驱动状态,电流过大。频率太高,舵机响应不过来,内部逻辑混乱。所以,频率一定要严格按照手册来。

2.3 占空比:角度控制的灵魂

占空比就是高电平时间占整个周期的比例。对于50Hz的PWM,周期是20ms。舵机通常接受的脉宽范围是0.5ms到2.5ms。

我们来算一下:

脉宽(ms) 占空比 舵机角度
0.5 2.5%
1.0 5.0% 45°
1.5 7.5% 90°
2.0 10.0% 135°
2.5 12.5% 180°

嗯,这里要注意:不同品牌的舵机,脉宽范围可能略有差异。有些舵机是0.6ms~2.4ms,有些是0.5ms~2.5ms。我建议你拿到舵机后,先用示波器实测一下它的极限脉宽。

2.4 PWM分辨率:你能控制多精细?

分辨率决定了你能把角度调到多细。比如你的定时器是8位的,那PWM的占空比只能有256个等级。对于180°的舵机,每个等级对应180/256 ≈ 0.7°。

听起来还行?但实际项目中,0.7°的步进在导弹舵机控制中是不够的。导弹飞行时,舵面微小的偏差都会导致弹道偏移。所以,我一般会用16位定时器,分辨率达到65536级,每个等级对应180/65536 ≈ 0.0027°。

计算分辨率的公式:

角度分辨率 = 舵机角度范围 / PWM分辨率等级

PWM分辨率等级 = 定时器时钟频率 / (PWM频率 × 预分频系数)

举个例子:STM32的定时器时钟72MHz,预分频设为1440,PWM频率50Hz,那么分辨率等级 = 72,000,000 / (50 × 1440) = 1000级。每级对应0.18°。

2.5 实际项目中的PWM配置

我习惯用STM32的定时器来做PWM输出。下面是一个典型的配置代码:

// 定时器3配置,输出PWM到舵机
void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置PA6为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 定时器配置,50Hz
    TIM_TimeBaseStructure.TIM_Period = 19999;  // 20ms周期
    TIM_TimeBaseStructure.TIM_Prescaler = 71;  // 72MHz / 72 = 1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // PWM模式1配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1500;  // 初始90°
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    
    TIM_Cmd(TIM3, ENABLE);
}

// 设置舵机角度
void SetServoAngle(uint16_t angle)
{
    // 角度转脉宽:0°->500, 180°->2500
    uint16_t pulse = 500 + (uint16_t)((uint32_t)angle * 2000 / 180);
    TIM_SetCompare1(TIM3, pulse);
}

这段代码里,我用了72MHz的时钟,预分频71,这样定时器计数频率就是1MHz。周期设为19999,也就是20ms。占空比通过TIM_SetCompare1来调整,范围500~2500对应0°~180°。

关键点:

  • 预分频系数决定了计数频率,影响分辨率
  • 周期值决定了PWM频率
  • 比较值决定了占空比,也就是舵机角度

2.6 常见问题与调试技巧

做舵机控制时,我遇到过几个典型问题:

  1. 舵机抖动:通常是电源纹波太大,或者PWM频率不稳定。我建议在舵机电源端加一个470μF的电解电容。
  2. 角度不准:可能是脉宽范围没校准。用示波器实测一下你的PWM输出,看看是不是真的在0.5ms~2.5ms之间。
  3. 响应慢:频率太低。试试把频率提高到100Hz~200Hz,但前提是你的舵机支持。

我记得有一次调试导弹舵机,舵机在某个角度区间总是卡顿。查了半天,发现是PWM分辨率不够,导致角度跳变太大。后来把定时器从8位换成16位,问题就解决了。

重要提醒:

千万不要在舵机通电时用手去掰它的输出轴!舵机内部有位置反馈,你强行掰它,它会拼命往回转,很容易烧坏电机或者齿轮。我见过不止一个新手犯这个错误。

2.7 总结

PWM控制舵机,说白了就是三件事:频率要对、占空比要准、分辨率要够。频率决定了响应速度,占空比决定了角度位置,分辨率决定了控制精度。

做项目时,我建议你先用示波器把PWM波形调好,再接到舵机上。别一上来就接舵机,万一参数不对,舵机乱转起来可是会伤人的。尤其是导弹舵机,力矩大,转起来能把手指打断。

下一章,我们会讲舵机的硬件接口设计,包括电源、信号隔离和驱动电路。到时候我会分享一个我踩过的坑——信号线太长导致舵机失控的案例。