第3章 PWM基础与电机开环控制
好,咱们进入第三章。这一章我打算聊聊PWM——脉冲宽度调制。说实话,PWM这东西,搞嵌入式的人天天见。但你真的理解它和电机转速之间的关系吗?我见过不少工程师,代码能跑,波形也能看,但一问到「为什么占空比变了转速就变了」,就支支吾吾了。
这一章,咱们就把这个基础打扎实。从原理到实战,再到示波器实测,一步到位。
3.1 PWM原理:说白了就是开关
PWM,全称Pulse Width Modulation。翻译过来就是「脉冲宽度调制」。什么意思呢?
你想想看,一个直流电机,你给它通12V,它就全速转。你给它断掉,它就停。那如果我想让它半速转呢?
最简单的办法——快速开关。开一会儿,关一会儿。开的时间长,转得快;开的时间短,转得慢。这就是PWM的核心思想。
核心公式:
占空比 = Ton / (Ton + Toff) × 100%
等效电压 = Vcc × 占空比
举个例子。12V供电,占空比50%,那电机感受到的平均电压就是6V。转速自然就降下来了。
我在项目中遇到过一个问题:有人觉得PWM频率越高越好。其实不是。频率太高,MOS管的开关损耗会急剧增加。频率太低,电机运转会「咔咔咔」地抖动。我个人习惯,直流有刷电机用20kHz左右,听不到噪音,效率也还行。
3.2 占空比与转速关系:不是线性的!
很多新手会以为:占空比50%,转速就是50%。
错。
为什么会这样?因为电机有死区电压。说白了,电压太低,电机根本转不起来。我实测过一款小电机,占空比低于15%,它纹丝不动。
另外,负载也会影响。空载时,10%占空比可能就转了。带负载时,可能要到30%才动。
| 占空比 | 空载转速(RPM) | 带载转速(RPM) |
|---|---|---|
| 0% | 0 | 0 |
| 20% | 800 | 0(未启动) |
| 40% | 1800 | 1200 |
| 60% | 2800 | 2400 |
| 80% | 3700 | 3500 |
| 100% | 4200 | 4100 |
看到没?低占空比区域,非线性非常明显。所以做按摩仪的时候,我建议你留个「启动占空比」——先给一个较高的占空比让电机转起来,再降到目标值。
避坑指南:我曾经在按摩仪项目里,直接给20%占空比想让它慢转。结果电机嗡嗡响就是不转。后来加了软启动,先给50%持续200ms,再降到20%,问题解决。
3.3 开环调速实现:代码走起
开环控制,就是没有反馈。你给多少,它就转多少。简单粗暴,适合入门。
下面我用STM32的定时器来演示。嗯,这里要注意,不同MCU的PWM配置大同小异,关键是理解原理。
// 定时器PWM初始化(以STM32F103为例)
void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置PA0为复用推挽输出(TIM2_CH1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置定时器
TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 配置PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比0%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 5. 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
// 设置占空比函数
void PWM_SetDuty(uint16_t duty)
{
if(duty > 1000) duty = 1000; // 限幅
TIM_SetCompare1(TIM2, duty);
}
这里有个关键点:TIM_Period = 999,TIM_Prescaler = 71。算一下频率:72MHz / (71+1) / (999+1) = 1kHz。如果你想用20kHz,把Period改成49就行。
占空比怎么算?比如你想设50%,那就PWM_SetDuty(500)。因为Period是999,0~999对应0%~100%。
注意:不同MCU的PWM分辨率不一样。8位定时器只能设0~255,16位可以设0~65535。分辨率越高,转速控制越精细。按摩仪电机一般8位就够了,但如果你要做精密控制,建议用16位。
3.4 示波器实测波形分析
代码写完了,怎么验证?上示波器。
我记得第一次调PWM的时候,代码跑起来电机不转。我以为是程序写错了。结果一量波形——嗯,根本没输出。原来是GPIO配置成了推挽输出,忘了改成复用推挽。
所以,示波器是嵌入式工程师的眼睛。咱们来看看典型的PWM波形长什么样。
正常波形:
- 频率稳定:周期一致,没有抖动
- 占空比准确:高电平宽度符合设定值
- 上升沿/下降沿陡峭:没有明显的斜坡
常见问题波形:
- 波形毛刺多:可能是电源纹波大,或者探头接地没接好
- 占空比不准:定时器预分频或重装载值算错了
- 频率漂移:晶振不稳定,或者代码里有中断干扰
我建议你养成一个习惯:每次改完PWM参数,先看波形,再连电机。这样可以避免很多低级错误。
实战技巧:用示波器测量时,把探头打到10×档,带宽限制打开。这样能滤掉高频噪声,看到更真实的波形。另外,接地夹子尽量短,别用那个长长的弹簧地线——那玩意儿就是个天线。
3.5 开环控制的局限性
开环控制虽然简单,但有个致命问题:你没法知道电机实际转了多少。
比如按摩仪里,你设了50%占空比,但电池电压从12V掉到了10V,那实际转速就变了。用户会感觉「怎么按着按着没劲了」。
再比如,按摩头压到骨头上了,阻力变大,转速下降。开环控制不会自动补偿,用户体验就很差。
所以,开环控制适合做原型验证、功能测试。真正产品级的按摩仪,一定要上闭环——也就是后面章节要讲的PID控制。
但别急,先把开环玩明白。地基打不牢,楼盖得再高也得塌。
好,这一章就到这儿。下一章咱们聊聊电机驱动电路——H桥怎么搭,MOS管怎么选,续流二极管怎么配。这些都是实战中绕不开的硬骨头。