第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 本章小结
本章介绍了蜂鸣器与声音输出:
- 有源蜂鸣器:
- 内置振荡器,通断控制
- 固定频率,适合简单提示
- 无源蜂鸣器:
- 需要 PWM 驱动
- 可变频率,适合音乐播放
- 音频编码:
- 音符频率映射
- 简谱编码格式
- RTTTL 格式解析
- 音量控制:
- 通过 PWM 占空比调节
下一章将学习 ADC 原理与模拟量采集。