znlgis 博客

GIS开发与技术分享

第10章:蜂鸣器与声音输出

本章介绍有源蜂鸣器和无源蜂鸣器的工作原理、PWM 音频控制以及音乐播放实现。


10.1 有源蜂鸣器

10.1.1 工作原理

有源蜂鸣器内置振荡电路,只需提供直流电源即可发声:

     ┌─────────────┐
     │   振荡器    │
VCC ─┤             ├→ 固定频率声音
     │   压电片    │
GND ─┤             │
     └─────────────┘

特点

  • 内置振荡电路
  • 只能发出固定频率(通常 2-4kHz)
  • 控制简单:通电响,断电停

10.1.2 驱动电路

GPIO ─── [R1 1K] ─── NPN 基极
                       │
                     集电极 ─── 蜂鸣器+ ─── VCC
                       │
                     发射极 ─── GND

10.1.3 控制代码

'''
实验:有源蜂鸣器控制
接线:S 端连接 GP20
'''
from machine import Pin
import time

buzzer = Pin(20, Pin.OUT)

# 响 1 秒
buzzer.value(1)
time.sleep(1)
buzzer.value(0)

# 蜂鸣提示
def beep(times=1, duration=0.1, interval=0.1):
    for _ in range(times):
        buzzer.value(1)
        time.sleep(duration)
        buzzer.value(0)
        time.sleep(interval)

# 短促提示
beep(3, 0.05, 0.05)

10.1.4 报警模式

'''
实验:多种报警模式
'''
from machine import Pin
import time

buzzer = Pin(20, Pin.OUT)

def alarm_sos():
    """SOS 摩尔斯电码"""
    # S: · · ·
    for _ in range(3):
        buzzer.value(1)
        time.sleep(0.1)
        buzzer.value(0)
        time.sleep(0.1)
    time.sleep(0.2)
    
    # O: — — —
    for _ in range(3):
        buzzer.value(1)
        time.sleep(0.3)
        buzzer.value(0)
        time.sleep(0.1)
    time.sleep(0.2)
    
    # S: · · ·
    for _ in range(3):
        buzzer.value(1)
        time.sleep(0.1)
        buzzer.value(0)
        time.sleep(0.1)

def alarm_siren():
    """警报器效果(快慢交替)"""
    for _ in range(5):
        buzzer.value(1)
        time.sleep(0.5)
        buzzer.value(0)
        time.sleep(0.5)

alarm_sos()

10.2 无源蜂鸣器(8002b 功放模块)

10.2.1 工作原理

无源蜂鸣器没有内置振荡器,需要外部提供方波信号:

PWM 信号 ─→ 8002b 功放 ─→ 喇叭
             放大 8.5 倍

特点

  • 可以发出不同频率的声音
  • 通过改变 PWM 频率控制音调
  • 通过改变占空比控制音量

10.2.2 音调与频率

音符 频率 (Hz) 音符 频率 (Hz)
C4 (Do) 262 C5 (Do) 523
D4 (Re) 294 D5 (Re) 587
E4 (Mi) 330 E5 (Mi) 659
F4 (Fa) 349 F5 (Fa) 698
G4 (So) 392 G5 (So) 784
A4 (La) 440 A5 (La) 880
B4 (Si) 494 B5 (Si) 988

10.2.3 基础播放代码

'''
实验:无源蜂鸣器播放音阶
接线:S 端连接 GP21
'''
from machine import Pin, PWM
import time

buzzer = PWM(Pin(21))

# 音符频率表
NOTES = {
    'C4': 262, 'D4': 294, 'E4': 330, 'F4': 349,
    'G4': 392, 'A4': 440, 'B4': 494,
    'C5': 523, 'D5': 587, 'E5': 659, 'F5': 698,
    'G5': 784, 'A5': 880, 'B5': 988, 'C6': 1047,
    'R': 0  # 休止符
}

def play_tone(freq, duration_ms=500, duty=1000):
    """播放指定频率的音调"""
    if freq == 0:
        buzzer.duty_u16(0)
    else:
        buzzer.freq(freq)
        buzzer.duty_u16(duty)
    time.sleep_ms(duration_ms)
    buzzer.duty_u16(0)
    time.sleep_ms(50)  # 音符间隔

# 播放音阶
for note in ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5']:
    play_tone(NOTES[note], 300)

10.3 音乐播放

10.3.1 音乐编码格式

使用简谱编码:

# 格式: (音符, 时值)
# 时值: 4=四分音符, 8=八分音符, 2=二分音符

# 小星星
TWINKLE = [
    ('C4', 4), ('C4', 4), ('G4', 4), ('G4', 4),
    ('A4', 4), ('A4', 4), ('G4', 2),
    ('F4', 4), ('F4', 4), ('E4', 4), ('E4', 4),
    ('D4', 4), ('D4', 4), ('C4', 2),
]

10.3.2 音乐播放器

'''
实验:音乐播放器
'''
from machine import Pin, PWM
import time

buzzer = PWM(Pin(21))

NOTES = {
    'C4': 262, 'D4': 294, 'E4': 330, 'F4': 349,
    'G4': 392, 'A4': 440, 'B4': 494,
    'C5': 523, 'D5': 587, 'E5': 659, 'F5': 698,
    'G5': 784, 'A5': 880, 'B5': 988, 'C6': 1047,
    'R': 0
}

def play_melody(melody, tempo=120):
    """
    播放旋律
    tempo: 每分钟节拍数 (BPM)
    """
    # 四分音符时长 (毫秒)
    beat_ms = int(60000 / tempo)
    
    for note, duration in melody:
        note_ms = int(beat_ms * (4 / duration))
        
        freq = NOTES.get(note, 0)
        
        if freq > 0:
            buzzer.freq(freq)
            buzzer.duty_u16(1000)
            time.sleep_ms(int(note_ms * 0.9))  # 90% 发声
            buzzer.duty_u16(0)
            time.sleep_ms(int(note_ms * 0.1))  # 10% 间隔
        else:
            buzzer.duty_u16(0)
            time.sleep_ms(note_ms)

# 小星星
TWINKLE = [
    ('C4', 4), ('C4', 4), ('G4', 4), ('G4', 4),
    ('A4', 4), ('A4', 4), ('G4', 2),
    ('F4', 4), ('F4', 4), ('E4', 4), ('E4', 4),
    ('D4', 4), ('D4', 4), ('C4', 2),
]

# 欢乐颂
ODE_TO_JOY = [
    ('E4', 4), ('E4', 4), ('F4', 4), ('G4', 4),
    ('G4', 4), ('F4', 4), ('E4', 4), ('D4', 4),
    ('C4', 4), ('C4', 4), ('D4', 4), ('E4', 4),
    ('E4', 4), ('D4', 8), ('D4', 2),
]

play_melody(TWINKLE, tempo=120)
time.sleep(1)
play_melody(ODE_TO_JOY, tempo=100)

buzzer.duty_u16(0)

10.3.3 RTTTL 格式解析

RTTTL(Ring Tone Text Transfer Language)是手机铃声格式:

name:d=duration,o=octave,b=bpm:notes

示例:
Jingle:d=8,o=5,b=140:e,e,e,e,e,e,e,g,c,d,e
'''
实验:RTTTL 解析播放器
'''
def parse_rtttl(rtttl_string):
    """解析 RTTTL 字符串"""
    parts = rtttl_string.split(':')
    name = parts[0]
    
    # 解析默认值
    defaults = {}
    for item in parts[1].split(','):
        key, value = item.split('=')
        defaults[key.strip()] = int(value)
    
    # 解析音符
    melody = []
    for note_str in parts[2].split(','):
        note_str = note_str.strip()
        
        # 解析时值
        duration = defaults.get('d', 4)
        if note_str[0].isdigit():
            i = 0
            while note_str[i].isdigit():
                i += 1
            duration = int(note_str[:i])
            note_str = note_str[i:]
        
        # 解析音符
        note = note_str[0].upper()
        note_str = note_str[1:]
        
        # 解析升号
        if note_str and note_str[0] == '#':
            note += '#'
            note_str = note_str[1:]
        
        # 解析八度
        octave = defaults.get('o', 4)
        if note_str and note_str[0].isdigit():
            octave = int(note_str[0])
        
        melody.append((note + str(octave), duration))
    
    return name, defaults.get('b', 120), melody

# 使用示例
rtttl = "Jingle:d=8,o=5,b=140:e,e,e,e,e,e,e,g,c,d,e"
name, tempo, melody = parse_rtttl(rtttl)
print(f"播放: {name}")
play_melody(melody, tempo)

10.4 声音效果

10.4.1 警报声效

'''
实验:警报音效
'''
def siren_effect(duration_s=3):
    """警笛效果"""
    start = time.time()
    while time.time() - start < duration_s:
        # 上升音
        for freq in range(500, 1500, 50):
            buzzer.freq(freq)
            buzzer.duty_u16(1000)
            time.sleep_ms(10)
        # 下降音
        for freq in range(1500, 500, -50):
            buzzer.freq(freq)
            buzzer.duty_u16(1000)
            time.sleep_ms(10)
    buzzer.duty_u16(0)

def laser_sound():
    """激光音效"""
    for freq in range(2000, 200, -50):
        buzzer.freq(freq)
        buzzer.duty_u16(1000)
        time.sleep_ms(5)
    buzzer.duty_u16(0)

def power_up():
    """开机音效"""
    for freq in [262, 330, 392, 523]:
        buzzer.freq(freq)
        buzzer.duty_u16(1000)
        time.sleep_ms(100)
    buzzer.duty_u16(0)

10.4.2 按键音

'''
实验:触摸按键音
'''
from machine import Pin, PWM
import time

touch = Pin(3, Pin.IN)
buzzer = PWM(Pin(21))

def click_sound():
    """点击音效"""
    buzzer.freq(1000)
    buzzer.duty_u16(1000)
    time.sleep_ms(20)
    buzzer.duty_u16(0)

while True:
    if touch.value() == 1:
        click_sound()
        while touch.value() == 1:
            time.sleep(0.01)
    time.sleep(0.01)

10.5 音量控制

10.5.1 PWM 占空比控制

'''
实验:音量控制
占空比越大,音量越大
'''
def play_with_volume(freq, volume_percent):
    """
    volume_percent: 0-100
    """
    buzzer.freq(freq)
    duty = int(volume_percent / 100 * 65535)
    buzzer.duty_u16(duty)

# 渐响
for vol in range(0, 101, 10):
    play_with_volume(440, vol)
    time.sleep(0.2)

# 渐弱
for vol in range(100, -1, -10):
    play_with_volume(440, vol)
    time.sleep(0.2)

buzzer.duty_u16(0)

10.5.2 电位器调节音量

'''
实验:旋钮调节音量
'''
from machine import Pin, PWM, ADC
import time

buzzer = PWM(Pin(21))
potentiometer = ADC(26)

buzzer.freq(440)

while True:
    # 读取电位器(0-65535)
    pot_value = potentiometer.read_u16()
    
    # 映射到占空比
    buzzer.duty_u16(pot_value)
    
    time.sleep(0.05)

10.6 多音蜂鸣器

10.6.1 和弦模拟

由于硬件限制,单个蜂鸣器无法同时发出多个音符。可以通过快速切换模拟和弦:

'''
实验:模拟和弦
'''
def chord(notes, duration_ms=500):
    """通过快速切换模拟和弦"""
    end_time = time.ticks_ms() + duration_ms
    
    while time.ticks_ms() < end_time:
        for freq in notes:
            buzzer.freq(freq)
            buzzer.duty_u16(1000)
            time.sleep_us(500)  # 500us 切换
    
    buzzer.duty_u16(0)

# C 大三和弦 (C-E-G)
chord([262, 330, 392], 1000)

10.7 本章小结

本章介绍了蜂鸣器与声音输出:

  1. 有源蜂鸣器
    • 内置振荡器,通断控制
    • 固定频率,适合简单提示
  2. 无源蜂鸣器
    • 需要 PWM 驱动
    • 可变频率,适合音乐播放
  3. 音频编码
    • 音符频率映射
    • 简谱编码格式
    • RTTTL 格式解析
  4. 音量控制
    • 通过 PWM 占空比调节

下一章将学习 ADC 原理与模拟量采集。