第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% | 0° |
| 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 常见问题与调试技巧
做舵机控制时,我遇到过几个典型问题:
- 舵机抖动:通常是电源纹波太大,或者PWM频率不稳定。我建议在舵机电源端加一个470μF的电解电容。
- 角度不准:可能是脉宽范围没校准。用示波器实测一下你的PWM输出,看看是不是真的在0.5ms~2.5ms之间。
- 响应慢:频率太低。试试把频率提高到100Hz~200Hz,但前提是你的舵机支持。
我记得有一次调试导弹舵机,舵机在某个角度区间总是卡顿。查了半天,发现是PWM分辨率不够,导致角度跳变太大。后来把定时器从8位换成16位,问题就解决了。
重要提醒:
千万不要在舵机通电时用手去掰它的输出轴!舵机内部有位置反馈,你强行掰它,它会拼命往回转,很容易烧坏电机或者齿轮。我见过不止一个新手犯这个错误。
2.7 总结
PWM控制舵机,说白了就是三件事:频率要对、占空比要准、分辨率要够。频率决定了响应速度,占空比决定了角度位置,分辨率决定了控制精度。
做项目时,我建议你先用示波器把PWM波形调好,再接到舵机上。别一上来就接舵机,万一参数不对,舵机乱转起来可是会伤人的。尤其是导弹舵机,力矩大,转起来能把手指打断。
下一章,我们会讲舵机的硬件接口设计,包括电源、信号隔离和驱动电路。到时候我会分享一个我踩过的坑——信号线太长导致舵机失控的案例。