第三讲:PWM核心原理——占空比、频率与周期的三角恋

各位同学,今天我们聊点硬核的。PWM——脉冲宽度调制,这玩意儿在按摩仪里就是灵魂。说白了,它就是通过快速开关信号,让负载(比如电机、加热片)感觉到的平均电压发生变化。你想想看,按摩仪的揉捏力度、加热温度,全靠它来调。

我个人习惯把PWM比作一个「开关灯」的游戏。你快速开关灯,人眼就会觉得灯变暗了。开关的速度(频率)和灯亮的时间比例(占空比),决定了你看到的亮度。按摩仪里的电机也是一样,只不过它感受到的是平均电压。

3.1 三个核心参数:占空比、频率、周期

先搞清楚这三个东西是什么,不然后面代码写出来你都不知道在调什么。

参数 符号 单位 说明
周期 T 秒 (s) 一个完整脉冲波形的时间长度
频率 f 赫兹 (Hz) 每秒产生的脉冲个数,f = 1/T
占空比 D 百分比 (%) 高电平时间占整个周期的比例

举个例子:假设周期 T = 10ms,那么频率 f = 100Hz。如果高电平持续了 7ms,占空比就是 70%。

核心公式:

占空比 D = (高电平时间 / 周期 T) × 100%

平均电压 = 峰值电压 × 占空比

嗯,这里要注意:占空比不是越高越好。我在项目中遇到过,有同事把占空比调到95%以上,结果电机直接烧了。为什么?因为高电平时间太长,MOS管散热跟不上。避坑指南:按摩仪电机一般建议占空比不要超过90%,留点余量给散热。

3.2 用Python生成PWM信号

光说不练假把式。我们用Python来模拟一下PWM波形。这里我用的是 matplotlibnumpy,这两个库你肯定装了吧?没装的话先 pip install matplotlib numpy

先看代码:

import numpy as np
import matplotlib.pyplot as plt

# 参数设置
frequency = 50          # 频率 50Hz
period = 1 / frequency  # 周期 0.02s = 20ms
duty_cycle = 0.6        # 占空比 60%
amplitude = 3.3         # 峰值电压 3.3V

# 时间轴:生成两个周期的数据
t = np.linspace(0, 2 * period, 1000)
pwm_signal = np.zeros_like(t)

# 计算每个时间点对应的PWM值
for i, time in enumerate(t):
    # 取模运算,得到当前时间在一个周期内的位置
    pos_in_period = time % period
    if pos_in_period < duty_cycle * period:
        pwm_signal[i] = amplitude
    else:
        pwm_signal[i] = 0

# 画图
plt.figure(figsize=(10, 4))
plt.plot(t * 1000, pwm_signal, 'b-', linewidth=2)
plt.title(f'PWM Signal (f={frequency}Hz, D={duty_cycle*100:.0f}%)')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.ylim(-0.5, amplitude + 0.5)
plt.grid(True, alpha=0.3)
plt.show()

这段代码的逻辑很简单:

  • 先生成时间轴,覆盖两个周期
  • 对每个时间点,判断它在一个周期内的位置
  • 如果位置小于占空比对应的时间,输出高电平;否则输出低电平

运行一下,你会看到一个方波。高电平占60%,低电平占40%。这就是PWM的原始形态。

小技巧: 我建议你把占空比改成0.2、0.5、0.8分别跑一次,观察波形变化。你会发现高电平的「宽度」在变,这就是「脉冲宽度调制」名字的由来。

3.3 不同占空比下的波形对比

光看一个波形不过瘾。我们来个多图对比,看看占空比从10%到90%的变化。

import numpy as np
import matplotlib.pyplot as plt

frequency = 50
period = 1 / frequency
amplitude = 3.3
duty_cycles = [0.1, 0.3, 0.5, 0.7, 0.9]

fig, axes = plt.subplots(5, 1, figsize=(10, 8))
t = np.linspace(0, 3 * period, 1500)

for idx, duty in enumerate(duty_cycles):
    pwm = np.zeros_like(t)
    for i, time in enumerate(t):
        pos = time % period
        pwm[i] = amplitude if pos < duty * period else 0
    
    axes[idx].plot(t * 1000, pwm, 'b-', linewidth=1.5)
    axes[idx].set_ylabel(f'D={duty*100:.0f}%')
    axes[idx].set_ylim(-0.5, amplitude + 0.5)
    axes[idx].set_yticks([])
    axes[idx].grid(True, alpha=0.2)

axes[-1].set_xlabel('Time (ms)')
plt.suptitle('PWM Waveforms at Different Duty Cycles', fontsize=14)
plt.tight_layout()
plt.show()

跑完这段代码,你会看到5个波形从上到下排列。占空比10%的时候,脉冲窄得像一根针;占空比90%的时候,脉冲宽得几乎占满整个周期。

我记得第一次做按摩仪原型时,就是靠这个图来跟硬件工程师沟通的。我说「我要30%的力度」,他一看图就明白了。说白了,波形图就是工程师之间的通用语言。

3.4 频率对波形的影响

占空比决定了「量」,频率决定了「质」。频率太低,电机会一抖一抖的,像在打嗝。频率太高,MOS管开关损耗会变大,发热严重。

来看看不同频率下的波形:

import numpy as np
import matplotlib.pyplot as plt

duty_cycle = 0.5
amplitude = 3.3
frequencies = [20, 50, 100, 200]

fig, axes = plt.subplots(4, 1, figsize=(10, 8))
t = np.linspace(0, 0.1, 2000)  # 固定100ms时间窗口

for idx, freq in enumerate(frequencies):
    period = 1 / freq
    pwm = np.zeros_like(t)
    for i, time in enumerate(t):
        pos = time % period
        pwm[i] = amplitude if pos < duty_cycle * period else 0
    
    axes[idx].plot(t * 1000, pwm, 'b-', linewidth=1.5)
    axes[idx].set_ylabel(f'f={freq}Hz')
    axes[idx].set_ylim(-0.5, amplitude + 0.5)
    axes[idx].set_yticks([])
    axes[idx].grid(True, alpha=0.2)

axes[-1].set_xlabel('Time (ms)')
plt.suptitle('PWM at Different Frequencies (D=50%)', fontsize=14)
plt.tight_layout()
plt.show()

你看,20Hz的时候,100ms内只有2个脉冲;200Hz的时候,100ms内有20个脉冲。脉冲越密集,电机运转越平滑。

避坑指南: 我曾经把频率调到500Hz以上,结果电机嗡嗡响,手摸上去发烫。后来查资料才知道,按摩仪电机的最佳频率范围是20-200Hz。低于20Hz会有明显抖动,高于200Hz效率下降。记住这个范围,少走弯路。

3.5 占空比与平均电压的关系

最后,我们来验证一下核心公式:平均电压 = 峰值电压 × 占空比。

import numpy as np

amplitude = 3.3
duty_cycles = np.linspace(0, 1, 11)

print("占空比 | 理论平均电压 | 实际平均电压")
print("-" * 40)

for duty in duty_cycles:
    # 模拟一个周期的PWM信号
    samples = 1000
    t = np.linspace(0, 1, samples)
    pwm = np.where(t < duty, amplitude, 0)
    avg_voltage = np.mean(pwm)
    
    theoretical = amplitude * duty
    print(f"{duty*100:5.0f}%   | {theoretical:5.2f}V        | {avg_voltage:5.2f}V")

运行结果:

占空比 | 理论平均电压 | 实际平均电压
----------------------------------------
  0%   |  0.00V        |  0.00V
 10%   |  0.33V        |  0.33V
 20%   |  0.66V        |  0.66V
 30%   |  0.99V        |  0.99V
 40%   |  1.32V        |  1.32V
 50%   |  1.65V        |  1.65V
 60%   |  1.98V        |  1.98V
 70%   |  2.31V        |  2.31V
 80%   |  2.64V        |  2.64V
 90%   |  2.97V        |  2.97V
100%   |  3.30V        |  3.30V

完美吻合。这说明什么?说明PWM控制电压是线性可调的。你想想看,按摩仪要多大力度,直接算占空比就行,多方便。

好了,这一讲的核心内容就这些。总结一下:

  • 占空比控制「量」,频率控制「质」
  • 按摩仪电机频率建议20-200Hz
  • 占空比不要超过90%,留散热余量
  • 平均电压 = 峰值电压 × 占空比,线性关系

下一讲,我们会用树莓派的GPIO口实际输出PWM信号,让电机真正转起来。到时候你会看到,代码里调一个数字,电机转速就跟着变,那种掌控感,很爽。


公众号:蓝海资料掘金营,微信deep3321