回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

Beetle 树莓派RP2350 用PWM做几个小应用: 呼吸灯 播放音乐 失火报警 简单

头像 米菲爸爸 2025.05.15 53 0

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

评论

user-avatar