任务要求——制作一个音乐播放器
具体要求:利用板上的蜂鸣器,播放一首音乐
实现方式:利用PWM生成不同频率,持续时间不同的音调,驱动蜂鸣器播放音乐,程序中制作一个标准的音符库,播放音乐的时候通过查找表的方式把该音符对应的频率的信号播放出来
板卡介绍
基于树莓派Pico的嵌入式系统学习平台,可通过C/C++以及MicroPython编程来学习嵌入式系统的工作原理和应用,板卡功能及管脚映射如下图:
树莓派Pico/RP2040的基本特性
1、板上通过MicroUSB供电,能过够给扩展板提供3.3V的直流电压
2、26根IO用于扩展,支持SPI、I2C、PWM、PIO
3、板上一颗单色LED可用于基本的测试
4、40Pin 邮票孔、双列直插孔的方式连接扩展板、面包板
原理简介
脉冲宽度调制(Pulse Width Modulation,PWM)是利用微控制器的数字输出来对模拟电路进行控制的一种非常有效的技术。
PWM的控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率。其中,一个周期内脉冲时间占总时间的比值称为占空比。
设计思路
- 对照C大调的频率表,制作一个标准音符库
- 利用PWM生成音符库中不同频率的音调
- 制作歌曲的字符串,包含时刻、音调、持续时间等
- 查找表的方式读取字符串,将对应频率的信号播放出来
- 驱动蜂鸣器播放音乐
流程框图
代码实现
我使用的开发软件是thonny,页面简洁,基础功能齐全,简单易上手,非常适合像我这样的初学者。可以在raspberry.org官网下载安装,这里推荐一个硬禾的教学视频直播1:基于STEP Pico的嵌入式系统学习平台板卡介绍+环境搭建 (eetree.cn)
我就是跟着上面的介绍安装并完成初始配置
1、管脚设置
初始化配置相对固定,此处使用开源代码https://gitee.com/picospuch/eetree-mpy-lecture-code
可以看到board中的设置与上图中管脚对照表一一对应:
### board.py --- eetree micropython training board configuration.
class pin_cfg:
yellow_led = 20
blue_led = 21
green_led = 22
red_led = 26
buzzer = 19
mic = 27
i2c0_scl = 17
i2c0_sda = 16
i2c1_scl = 15
i2c1_sda = 14
spi1_mosi = 11
spi1_sck = 10
spi1_dc = 9
spi1_rstn = 8
spi1_cs = 29
adc0 = 26
adc1 = 27
k1 = 12
k2 = 13
pot = 28
本项目主要应用buzzer蜂鸣器。
2、驱动蜂鸣器播放音乐
音符库,根据C大调设置频率(节选主要应用的库):
tones = {
'C4':262,
'C#4':277,
'D4':294,
'D#4':311,
'E4':330,
'F4':349,
'F#4':370,
'G4':392,
'G#4':415,
'A4':440,
'A#4':466,
'B4':494,
'C5':523,
'C#5':554,
'D5':587,
'D#5':622,
'E5':659,
'F5':698,
'F#5':740,
'G5':784,
'G#5':831,
'A5':880,
'A#5':932,
'B5':988,
'C6':1047,
'C#6':1109,
'D6':1175,
'D#6':1245,
'E6':1319,
'F6':1397,
'F#6':1480
}
设置歌曲字符串,每个音符的格式,并用查表法对应,此处引用已经很完善的开源代码:
#歌曲字符串 时刻 音调 持续时间 音色
#0 D4 8 0;0 D5 8 0;0 G4 8 0;8 C5 2 0;10 B4 2 0;12 G4 2 0;14 F4 1 0;15 G4 17 0;16 D4 8 0;24 C4 8 0
#定义music 读取乐符并输出
class music:
def __init__(self, songString='0 D4 8 0', looping=True, tempo=3, duty=2512, pin=None, pins=[Pin(pin_cfg.buzzer)]):
self.tempo = tempo
self.song = songString
self.looping = looping
self.duty = duty
self.stopped = False
self.timer = -1
self.beat = -1
self.arpnote = 0
self.pwms = []
if (not (pin is None)):
pins = [pin]
i = 0
for pin in pins:
self.pwms.append(PWM(pins[i]))
i = i + 1
self.notes = []
self.playingNotes = []
self.playingDurations = []
#找到歌曲的结尾
self.end = 0
splitSong = self.song.split(";")
for note in splitSong:
snote = note.split(" ")
testEnd = round(float(snote[0])) + ceil(float(snote[2]))
if (testEnd > self.end):
self.end = testEnd
#创建一个空的歌曲结构
while (self.end > len(self.notes)):
self.notes.append(None)
#用音符填充歌曲结构
for note in splitSong:
snote = note.split(" ")
beat = round(float(snote[0]));
if (self.notes[beat] == None):
self.notes[beat] = []
self.notes[beat].append([snote[1],ceil(float(snote[2]))]) #Note, Duration
#歌曲结束后回到最近的小节
self.end = ceil(self.end / 8) * 8
PWM控制蜂鸣器读取音符并播放:
def stop(self):
for pwm in self.pwms:
pwm.deinit()
self.stopped = True
#输出“滴”的声音,循环读取音符
def tick(self):
if (not self.stopped):
self.timer = self.timer + 1
#循环
if (self.timer % (self.tempo * self.end) == 0 and (not (self.timer == 0))):
if (not self.looping):
self.stop()
return False
self.beat = -1
self.timer = 0
#合拍子
if (self.timer % self.tempo == 0):
self.beat = self.beat + 1
#从播放列表中删除已播放的音符
i = 0
while (i < len(self.playingDurations)):
self.playingDurations[i] = self.playingDurations[i] - 1
if (self.playingDurations[i] <= 0):
self.playingNotes.pop(i)
self.playingDurations.pop(i)
else:
i = i + 1
#在播放列表中添加新的音符及其持续时间
if (self.beat < len(self.notes)):
if (self.notes[self.beat] != None):
for note in self.notes[self.beat]:
self.playingNotes.append(note[0])
self.playingDurations.append(note[1])
#检查节拍
i = 0
for pwm in self.pwms:
if (i >= len(self.playingNotes)):
pwm.duty_u16(0)
else:
#Play note
pwm.duty_u16(self.duty)
pwm.freq(tones[self.playingNotes[i]])
i = i + 1
#演奏所有音符
if (len(self.playingNotes) > len(self.pwms)):
self.pwms[len(self.pwms)-1].duty_u16(self.duty)
if (self.arpnote > len(self.playingNotes)-len(self.pwms)):
self.arpnote = 0
self.pwms[len(self.pwms)-1].freq(tones[self.playingNotes[self.arpnote+(len(self.pwms)-1)]])
self.arpnote = self.arpnote + 1
return True
else:
return False
song音符串如下,歌曲是我很喜欢的《无羁》(untamed),对照简朴编辑音符串代码如下:
song="0 B5 16 43 0.787401556968689;16 G5 8 43 0.787401556968689;24 E5 8 43 0.787401556968689;32 F#5 8 43 0.787401556968689;40 D5 8 43 0.787401556968689;48 B4 16 43 0.787401556968689;64 E5 8 43 0.787401556968689;72 F#5 4 43 0.787401556968689;76 G5 4 43 0.787401556968689;80 A5 12 43 0.787401556968689;92 D5 8 43 0.787401556968689;100 E5 20 43 0.787401556968689;128 B5 16 43 0.787401556968689;144 G5 8 43 0.787401556968689;152 E5 8 43 0.787401556968689;160 F#5 8 43 0.787401556968689;168 E5 4 43 0.787401556968689;172 D5 4 43 0.787401556968689;176 D5 8 43 0.787401556968689;184 C5 4 43 0.787401556968689;188 D5 4 43 0.787401556968689;192 E5 12 43 0.787401556968689;204 E5 4 43 0.787401556968689;208 A5 4 43 0.787401556968689;212 G5 4 43 0.787401556968689;216 F#5 4 43 0.787401556968689;220 G5 4 43 0.787401556968689;224 F#5 4 43 0.787401556968689;228 F#5 20 43 0.787401556968689;248 G5 4 43 0.787401556968689;252 A5 4 43 0.787401556968689;256 B5 12 43 0.787401556968689;268 A5 4 43 0.787401556968689;272 G5 4 43 0.787401556968689;276 F#5 4 43 0.787401556968689;280 G5 4 43 0.787401556968689;284 E5 4 43 0.787401556968689;288 F#5 8 43 0.787401556968689;296 D6 4 43 0.787401556968689;300 E6 4 43 0.787401556968689;304 D6 16 43 0.787401556968689;320 G5 4 43 0.787401556968689;324 A5 4 43 0.787401556968689;326 B5 4 43 0.787401556968689;330 C6 4 43 0.787401556968689;334 D6 8 43 0.787401556968689;342 A5 8 43 0.787401556968689;350 B5 16 43 0.787401556968689;370 A5 4 43 0.787401556968689;374 B5 4 43 0.787401556968689;378 D6 8 43 0.787401556968689;386 G5 16 43 0.787401556968689;406 F#5 4 43 0.787401556968689;410 G5 4 43 0.787401556968689;414 B5 8 43 0.787401556968689;422 E5 12 43 0.787401556968689;438 D5 4 43 0.787401556968689;442 D6 4 43 0.787401556968689;446 B5 4 43 0.787401556968689;450 G5 8 43 0.787401556968689;462 A5 4 43 0.787401556968689;466 B5 4 43 0.787401556968689;470 A5 4 43 0.787401556968689;474 B5 4 43 0.787401556968689;478 D6 4 43 0.787401556968689;482 E6 16 43 0.787401556968689;502 A5 4 43 0.787401556968689;506 B5 4 43 0.787401556968689;510 D6 4 43 0.787401556968689;514 G6 8 43 0.787401556968689;522 F#6 8 43 0.787401556968689;530 E6 8 43 0.787401556968689;538 D6 8 43 0.787401556968689;546 B5 8 43 0.787401556968689;554 A5 4 43 0.787401556968689;558 D6 4 43 0.787401556968689;562 B5 16 43 0.787401556968689;578 E5 4 43 0.787401556968689;582 F#5 4 43 0.787401556968689;586 G5 4 43 0.787401556968689;590 E5 4 43 0.787401556968689;594 F#5 6 43 0.787401556968689;600 E6 2 43 0.787401556968689;602 E6 4 43 0.787401556968689;606 D6 4 43 0.787401556968689;610 B5 4 43 0.787401556968689;614 B5 20 43 0.787401556968689;636 B5 4 43 0.787401556968689;640 D6 4 43 0.787401556968689;644 G6 8 43 0.787401556968689;652 F#6 8 43 0.787401556968689;660 E6 8 43 0.787401556968689;668 D6 8 43 0.787401556968689;676 B5 4 43 0.787401556968689;680 A5 4 43 0.787401556968689;684 B5 4 43 0.787401556968689;688 F#6 4 43 0.787401556968689;692 E6 16 43 0.787401556968689;708 E6 4 43 0.787401556968689;712 D6 4 43 0.787401556968689;716 E6 8 43 0.787401556968689;724 F#6 12 43 0.787401556968689;736 D6 8 43 0.787401556968689;744 E6 20 43 0.787401556968689"
#song 存放音乐的每个音符,便于music调用读取,音符放于一行,请往右拉
3、主代码
#从上述各个文件导入定义的功能music、sleep、song
from buzzer_music import music
from time import sleep
from the_untamed import song
#music调用歌曲song,读取并播放
mymusic = music(song)
#用蜂鸣器输出“滴”的不同音调声音,从而播放歌曲
while True:
print(mymusic.tick())
sleep(0.04)
总结
初次使用thonny,感觉这个开发软件还是很好上手的,由于是初次学习MicroPython,还有很多不懂的地方,也在逐步认识学习。实验过程中了解到了pwm调制不同频率的原理,并学习了查表法的应用以及嵌入式系统的工作原理。过程中开源代码给了我很大的帮助,也是我学习的主要来源,根据前辈写好的代码,有助于我更好的理解语法,也帮助我学习到了编程的思路。最后对照简谱编辑字符串,并将一首我很喜欢的《无羁》成功播放,真的很有成就感。
未来的计划
本次仅实现利用蜂鸣器播放音乐,接下来希望可以将OLED显示屏以及LED灯一起利用起来,例如将音乐波形显示在OLED屏上,利用板上的按键实现切换歌曲等,以实现一个简易的MP3播放器。