PWM 即脉冲宽度调制(Pulse Width Modulation),是一种通过对一系列脉冲的宽度进行调制,从而等效地获得所需要模拟信号的技术,在电子控制和嵌入式系统领域应用广泛。
工作原理: PWM 技术的核心是产生一系列方波信号,通过改变方波信号高电平(脉冲)持续时间和整个周期的比例,来实现对输出信号的控制。虽然输出的是数字信号,但在一定时间内,通过调整脉冲宽度,其平均电压或功率等效于一个模拟信号。
关键参数
脉冲周期(T):一个完整脉冲信号从开始到结束所经历的时间,单位通常为秒(s)或毫秒(ms)。
脉冲宽度(t):脉冲信号中高电平持续的时间,也称为占空时间。
占空比(D):脉冲宽度与脉冲周期的比值,通常用百分比表示,计算公式为 (D = \frac{t}{T} \times 100%)。占空比是 PWM 技术中最关键的参数,通过改变占空比可以控制输出信号的平均电压或功率。
应用场景
电机控制:通过 PWM 信号控制电机的平均电压,从而调节电机的转速。例如,在机器人、无人机等设备中,常常使用 PWM 技术来精确控制电机的运行。
LED 亮度调节:改变 PWM 信号的占空比,能够调整 LED 灯的平均电流,实现对 LED 亮度的调节。
电源管理:在开关电源中,PWM 技术可以控制开关管的导通和关断时间,稳定输出电压,提高电源转换效率。
在 MicroPython 里,一般借助 PWM 类来生成 PWM 信号,下面是一个简单示例: 实现了一个LED的呼吸灯效果
from machine import PWM
import utime
# 设置LED灯引脚为输出模式,然后打开LED灯(1为启用)。
led_pwm = PWM(5, freq = 1000)
led_pwm.duty_u16(32768) # 设置PWM占空比为50%(范围为0-65535)
# 循环改变PWM占空比
while True:
for duty_cycle in range(0, 65536, 2048): #range(start, stop, step)
led_pwm.duty_u16(duty_cycle)
utime.sleep(0.05)
utime.sleep(1)
#reversed()函数的作用是返回一个反转的迭代器(元组、列表、字符串、range)
for duty_cycle in reversed(range(0, 65536, 4096)):
led_pwm.duty_u16(duty_cycle)
utime.sleep(0.05)
utime.sleep(1)
Micropython在最新的版本中对PWM的调用有了重大修改,放弃了原duty()函数改为了duty_u16()函数,这主要和 MicroPython 硬件抽象层的设计演进、精度提升以及兼容性有关。
精度提升
- duty() 函数通常使用 10 位分辨率,取值范围是 0 - 1023。这意味着它只能提供 1024 个不同的占空比设置,在某些对精度要求较高的应用场景下,可能无法满足需求。
- duty_u16() 函数采用 16 位无符号整数作为参数,取值范围是 0 - 65535。这使得它能提供 65536 个不同的占空比设置,大幅提高了占空比调节的精度,能更细腻地控制像电机转速、LED 亮度等。
统一接口
- 不同的硬件平台支持的 PWM 分辨率可能不同,duty() 函数的 10 位分辨率难以适配所有硬件。使用 duty_u16() 函数可以提供一个统一的接口,不管硬件实际支持的分辨率是多少,都能通过 16 位数值进行占空比设置,增强了代码的可移植性。
符合现代硬件特性
- 如今的很多微控制器支持更高的 PWM 分辨率,如 12 位、16 位甚至更高。duty_u16() 函数能更好地发挥这些硬件的性能,充分利用硬件资源。
下面的代码中我们实现了用PWM来调用Speaker从而实现简单的音乐播放。我们把频率节奏放在一个dict类里(实际上应该放在一个单独的文件里需要的时候加载,这样就不会影响程序逻辑)
from machine import Pin, PWM
import time
# 初始化PWM对象
buzzer = PWM(Pin(5), 400)
buzzer.duty_u16(32768)
# 音符与对应的频率,使用 dict 类型存储
NOTE_FREQUENCY = {
"B0": 31,
"C1": 33,
"CS1": 35,
"D1": 37,
"DS1": 39,
"E1": 41,
"F1": 44,
"FS1": 46,
"G1": 49,
"GS1": 52,
"A1": 55,
"AS1": 58,
"B1": 62,
"C2": 65,
"CS2": 69,
"D2": 73,
"DS2": 78,
"E2": 82,
"F2": 87,
"FS2": 93,
"G2": 98,
"GS2": 104,
"A2": 110,
"AS2": 117,
"B2": 123,
"C3": 131,
"CS3": 139,
"D3": 147,
"DS3": 156,
"E3": 165,
"F3": 175,
"FS3": 185,
"G3": 196,
"GS3": 208,
"A3": 220,
"AS3": 233,
"B3": 247,
"C4": 262,
"CS4": 277,
"D4": 294,
"DS4": 311,
"E4": 330,
"F4": 349,
"FS4": 370,
"G4": 392,
"GS4": 415,
"A4": 440,
"AS4": 466,
"B4": 494,
"C5": 523,
"CS5": 554,
"D5": 587,
"DS5": 622,
"E5": 659,
"F5": 698,
"FS5": 740,
"G5": 784,
"GS5": 831,
"A5": 880,
"AS5": 932,
"B5": 988,
"C6": 1047,
"CS6": 1109,
"D6": 1175,
"DS6": 1245,
"E6": 1319,
"F6": 1397,
"FS6": 1480,
"G6": 1568,
"GS6": 1661,
"A6": 1760,
"AS6": 1865,
"B6": 1976,
"C7": 2093,
"CS7": 2217,
"D7": 2349,
"DS7": 2489,
"E7": 2637,
"F7": 2794,
"FS7": 2960,
"G7": 3136,
"GS7": 3322,
"A7": 3520,
"AS7": 3729,
"B7": 3951,
"C8": 4186,
"CS8": 4435,
"D8": 4699,
"DS8": 4978
}
def play_notes(note_array):
for note in note_array:
if note in NOTE_FREQUENCY:
frequency = NOTE_FREQUENCY[note]
buzzer.freq(frequency)
buzzer.duty_u16(32768) # 设置音量
time.sleep(0.2) # 播放 0.5 秒
else:
# 空
#print(f"未找到音符 {note} 的频率")
buzzer.duty_u16(0) # 静音
time.sleep(0.1) # 音符间间隔 0.1 秒
# 示例音符数组
# Mario theme note array
mario = [
"E7", "E7", 0, "E7", 0, "C7", "E7", 0,
"G7", 0, 0, 0, "G6", 0, 0, 0,
"C7", 0, 0, "G6", 0, 0, "E6", 0,
0, "A6", 0, "B6", 0, "AS6", "A6", 0,
"G6", "E7", 0, "G7", "A7", 0, "F7", "G7",
0, "E7", 0, "C7", "D7", "B6", 0, 0,
"C7", 0, 0, "G6", 0, 0, "E6", 0,
0, "A6", 0, "B6", 0, "AS6", "A6", 0,
"G6", "E7", 0, "G7", "A7", 0, "F7", "G7",
0, "E7", 0, "C7", "D7", "B6", 0, 0,
]
if __name__ == '__main__':
play_notes(mario)
# 清理资源
buzzer.deinit()
点击 播放就可以听到熟悉的音乐了。
最后我们用上面学的知识来制作一个简单的火灾报警系统
物料如下
Beetle 树莓派RP2350 x1
火焰传感器 x1 → DO口使用pin9
喇叭 x1 → 一边接pin5 一边接地
from machine import Pin, PWM
import time
# 初始化PWM对象
buzzer = PWM(Pin(5), 400)
buzzer.duty_u16(0)
fire_sensor = Pin(9, Pin.IN)
#1 safe
#0 fire
def check_fire_status():
isFire = fire_sensor.value()
print("isFire", isFire)
if isFire == 1:
pass
else:
alarm_sound()
time.sleep(1) # 延时1秒
# 发出警报声的函数
def alarm_sound():
# 循环播放警报声,模拟持续的火警警报
for _ in range(10): # 可根据需要调整循环次数
buzzer.freq(1500) # 更高的高音调
buzzer.duty_u16(32768) # 最大音量
time.sleep(0.2) # 持续0.2秒
buzzer.duty_u16(0) # 静音
time.sleep(0.1) # 暂停0.1秒
buzzer.freq(1000) # 相对低音调
buzzer.duty_u16(32768) # 最大音量
time.sleep(0.2) # 持续0.2秒
buzzer.duty_u16(0) # 静音
time.sleep(0.1) # 暂停0.1秒
while(1):
check_fire_status()
更多RP2350视频请访问
https://www.bilibili.com/video/BV18G55zrEqX/
树莓派Pico自虐之旅
https://www.bilibili.com/video/BV1aU4y1H7J5
评论