1. 任务要求
基于STEP Pico实现音乐播放超级玛丽
2. STEP Pico平台介绍 参考链接 硬禾STEP Pico介绍
STEP Pico上使用树莓派RP2040的25号引脚(GPIO19)驱动一个NPN三极管来控制无源蜂鸣器的鸣响。硬件原理图如下
。
3. 音乐播放原理
十二平均律,Do、Re、Mi、Fa、Sol、La、Si以及其中的几个半音,每12个为一组,低八度频率减半,高八度频率加倍。每一个音调都有它们的固定频率,我们只需要用IO的PWM方式发送对应频率的方波驱动无源蜂鸣器就能够让无源蜂鸣器鸣奏出相应的曲调了。完整的频率表参考链接 完整音调频率表
4. 软件开发环境
本应用程序使用的软件开发环境为Thonny
1, 首先通过USB线把STEP Pico插入电脑,这时候电脑会识别出一个串口号。
2,选择Thonny菜单的 运行->选择解释器。按照下图设置, Thonny与STEP Pico连接好之后将会在底部的Shell窗口显示出MicroPython的版本信息。
3, 用Thonny把附件的中 board.py buzzer_music.py music.py super_mary.py四个文件保存到STEP Pico(Thonny中显示的 Raspberry Pi Pico也就是我们连接到电脑上的STEP Pico)中.
4, 点击Thonny中的绿色三角按钮运行当前脚本,也可以按键盘上的F5按键运行当前脚本,这个时候我们就能够听到STEP Pico中鸣奏出的超级玛丽旋律了。
5. 软件代码介绍
本音乐播放器的软件代码使用Python语言编写,Python语言是一种解释型的高级的语言,程序不需要编译,程序语句可以直接执行。解释执行Python的的程序代码需要一个解释器。本音乐播放器使用的STEP Pico板子内部已经安装好了MicroPython, 我们只需要把我们编写的Python程序文件放在STEP Pico板的FLASH中,STEP Pico板子上电运行MicoPython程序之后解释运行我们的编写的Python程序,实现一个音乐播放器的功能了。
程序运行流程
music类在buzzer_music.py文件中实现,super_mary数组在super_mary.py中定义。
产生pwm的IO口在board.py文件的pin_cfg类中定义,产生pwm波形的底层配置在MicoPython中已实现。
Python程序文件说明
board.py 为STEP Pico的硬件引脚描述,其中buzzer=19也就是使用了RP2040的GPIO19引脚。
### board.py --- eetree micropython training board configuration.
class pin_cfg:
yellow_led = 20
blue_led = 21
green_led = 22
red_led = 26
###定义驱动蜂鸣器产生PWM波形的IO口为STEP Pico生的RP2040的19号引脚
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_music.py中定义了音调频率表以及用于音乐播放的music类
"""
Micropython (Raspberry Pi Pico)
Plays music written on onlinesequencer.net through a passive piezo buzzer.
Uses fast arpeggios with a single buzzer to simulate polyphony
Also supports multiple buzzers at once for real polyphony
https://github.com/james1236/buzzer_music
"""
from board import pin_cfg
from machine import Pin, PWM
from math import ceil
#各个音调音阶频率的定义
tones = {
'C0':16,
'C#0':17,
'D0':18,
'D#0':19,
'E0':21,
'F0':22,
'F#0':23,
'G0':24,
'G#0':26,
'A0':28,
'A#0':29,
'B0':31,
'C1':33,
'C#1':35,
'D1':37,
'D#1':39,
'E1':41,
'F1':44,
'F#1':46,
'G1':49,
'G#1':52,
'A1':55,
'A#1':58,
'B1':62,
'C2':65,
'C#2':69,
'D2':73,
'D#2':78,
'E2':82,
'F2':87,
'F#2':92,
'G2':98,
'G#2':104,
'A2':110,
'A#2':117,
'B2':123,
'C3':131,
'C#3':139,
'D3':147,
'D#3':156,
'E3':165,
'F3':175,
'F#3':185,
'G3':196,
'G#3':208,
'A3':220,
'A#3':233,
'B3':247,
'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,
'G6':1568,
'G#6':1661,
'A6':1760,
'A#6':1865,
'B6':1976,
'C7':2093,
'C#7':2217,
'D7':2349,
'D#7':2489,
'E7':2637,
'F7':2794,
'F#7':2960,
'G7':3136,
'G#7':3322,
'A7':3520,
'A#7':3729,
'B7':3951,
'C8':4186,
'C#8':4435,
'D8':4699,
'D#8':4978,
'E8':5274,
'F8':5588,
'F#8':5920,
'G8':6272,
'G#8':6645,
'A8':7040,
'A#8':7459,
'B8':7902,
'C9':8372,
'C#9':8870,
'D9':9397,
'D#9':9956,
'E9':10548,
'F9':11175,
'F#9':11840,
'G9':12544,
'G#9':13290,
'A9':14080,
'A#9':14917,
'B9':15804
}
#Time, Note, Duration, Instrument (onlinesequencer.net schematic format)
#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
###默认把board.py文件中pin_cfg类中定义的buzzer引脚赋值给pin
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
###配置生成PWM波形的IO口,配置方法在MicroPython中实现
for pin in pins:
self.pwms.append(PWM(pins[i]))
i = i + 1
self.notes = []
self.playingNotes = []
self.playingDurations = []
#Find the end of the song
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
#Create empty song structure
while (self.end > len(self.notes)):
self.notes.append(None)
#Populate song structure with the notes
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
#Round up end of song to nearest bar
self.end = ceil(self.end / 8) * 8
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
#Loop
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
#On Beat
if (self.timer % self.tempo == 0):
self.beat = self.beat + 1
#Remove expired notes from playing list
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
#Add new notes and their durations to the playing list
"""
#Old method runs for every note, slow to process on every beat and causes noticeable delay
ssong = song.split(";")
for note in ssong:
snote = note.split(" ")
if int(snote[0]) == beat:
playingNotes.append(snote[1])
playingDurations.append(int(snote[2]))
"""
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])
#Only need to run these checks on beats
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
#Play arp of all playing notes
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
music.py为音乐播放的Application
from buzzer_music import music
from time import sleep
from super_mary import super_mary
###把super_mary传递到music类中,调用music类鸣奏出super mary的曲调
mymusic = music(super_mary)
while True:
print(mymusic.tick())
sleep(0.04)
super_mary.py文件为超级玛丽的编曲数组
super_mary = '0 E5 1 0;0 F#4 1 0;0 D4 1 0;2 E5 2 0;2 D4 2 0;6 E5 2 0;6 D4 2 0;10 D4 1 0;10 F#4 1 0;10 C5 1 0;2 F#4 2 0;6 F#4 2 0;12 E5 2 0;12 F#4 2 0;12 D4 2 0;16 G5 2 0;16 G4 2 0;16 B4 2 0;23 G4 2 0;23 G4 2 0;30 C5 3 0;30 E4 3 0;30 G4 3 0;36 G4 1 0;36 C4 1 0;36 E4 1 0;42 E4 3 0;42 G3 3 0;42 C4 3 0;48 A4 2 0;48 F4 2 0;48 C4 2 0;52 B4 2 0;52 G4 2 0;52 D4 2 0;58 A4 2 0;58 C4 2 0;58 F4 2 0;56 A#4 1 0;56 F#4 1 0;56 C#4 1 0;62 C4 3 0;62 G4 3 0;62 E4 3 0;65 G4 3 0;65 E5 3 0;65 C5 3 0;68 E5 3 0;68 G5 3 0;68 B4 3 0;71 A5 2 0;71 C5 2 0;71 F5 2 0;75 D5 1 0;77 E5 2 0;75 A4 1 0;77 B4 2 0;75 F5 1 0;77 G5 2 0;81 E5 2 0;81 A4 2 0;81 C5 2 0;85 E4 1 0;85 C5 1 0;85 A4 1 0;87 F4 1 0;87 B4 1 0;87 D5 1 0;89 B4 1 0;89 G4 1 0;89 D4 1 0;95 C5 3 0;95 E4 3 0;95 G4 3 0;101 G4 1 0;101 C4 1 0;101 E4 1 0;107 E4 3 0;107 G3 3 0;107 C4 3 0;113 A4 2 0;113 F4 2 0;113 C4 2 0;117 B4 2 0;117 G4 2 0;117 D4 2 0;123 A4 2 0;123 C4 2 0;123 F4 2 0;121 A#4 1 0;121 F#4 1 0;121 C#4 1 0;127 C4 3 0;127 G4 3 0;127 E4 3 0;130 G4 3 0;130 E5 3 0;130 C5 3 0;133 E5 3 0;133 G5 3 0;133 B4 3 0;136 A5 2 0;136 C5 2 0;136 F5 2 0;140 D5 1 0;142 E5 2 0;140 A4 1 0;142 B4 2 0;140 F5 1 0;142 G5 2 0;146 E5 2 0;146 A4 2 0;146 C5 2 0;150 E4 1 0;150 C5 1 0;150 A4 1 0;152 F4 1 0;152 B4 1 0;152 D5 1 0;154 B4 1 0;154 G4 1 0;154 D4 1 0;159 C4 4 0;162 G5 1 0;162 E5 1 0;164 F#5 1 0;164 D#5 1 0;164 G4 1 0;166 F5 1 0;166 D5 1 0;168 D#5 2 0;168 B4 2 0;172 E5 2 0;172 C5 2 0;176 G#4 1 0;176 E4 1 0;178 F4 1 0;178 A4 1 0;180 G4 2 0;180 C5 2 0;184 A4 1 0;184 C4 1 0;186 C5 1 0;188 F4 1 0;188 D5 1 0;170 C5 2 0;174 F4 2 0;182 C5 1 0;186 E4 1 0;190 C4 3 0;207 F5 2 0;207 G5 2 0;207 C6 2 0;211 F5 1 0;211 G5 1 0;211 C6 1 0;213 F5 2 0;213 G5 2 0;213 C6 2 0;217 G4 2 0;221 C4 3 0;193 G5 1 0;193 E5 1 0;195 F#5 1 0;195 D#5 1 0;195 G4 1 0;197 F5 1 0;197 D5 1 0;199 D#5 2 0;199 B4 2 0;203 E5 2 0;203 C5 2 0;224 G5 1 0;224 E5 1 0;226 F#5 1 0;226 D#5 1 0;226 G4 1 0;228 F5 1 0;228 D5 1 0;230 D#5 2 0;230 B4 2 0;234 E5 2 0;234 C5 2 0;240 F4 1 0;238 E4 1 0;238 G#4 1 0;240 A4 1 0;242 G4 2 0;242 C5 2 0;246 C4 1 0;246 A4 1 0;248 E4 1 0;248 C5 1 0;250 D5 1 0;250 F4 1 0;256 D#5 3 0;256 G#4 3 0;262 D5 1 0;262 F4 1 0;267 E4 2 0;267 C5 2 0;252 C4 1 0;273 G4 1 0;275 G4 2 0;279 D4 2 0;283 C4 4 0;286 G5 1 0;286 E5 1 0;288 F#5 1 0;288 D#5 1 0;288 G4 1 0;290 F5 1 0;290 D5 1 0;292 D#5 2 0;292 B4 2 0;296 E5 2 0;296 C5 2 0;300 G#4 1 0;300 E4 1 0;302 F4 1 0;302 A4 1 0;304 G4 2 0;304 C5 2 0;308 A4 1 0;308 C4 1 0;310 C5 1 0;312 F4 1 0;312 D5 1 0;294 C5 2 0;298 F4 2 0;306 C5 1 0;310 E4 1 0;314 C4 3 0;331 F5 2 0;331 G5 2 0;331 C6 2 0;335 F5 1 0;335 G5 1 0;335 C6 1 0;337 F5 2 0;337 G5 2 0;337 C6 2 0;317 G5 1 0;317 E5 1 0;319 F#5 1 0;319 D#5 1 0;319 G4 1 0;321 F5 1 0;321 D5 1 0;323 D#5 2 0;323 B4 2 0;327 E5 2 0;327 C5 2 0;341 G4 2 0;345 C4 3 0;348 G5 1 0;348 E5 1 0;350 F#5 1 0;350 D#5 1 0;350 G4 1 0;352 F5 1 0;352 D5 1 0;354 D#5 2 0;354 B4 2 0;358 E5 2 0;358 C5 2 0;364 F4 1 0;362 E4 1 0;362 G#4 1 0;364 A4 1 0;366 G4 2 0;366 C5 2 0;370 C4 1 0;370 A4 1 0;372 E4 1 0;372 C5 1 0;374 D5 1 0;374 F4 1 0;380 D#5 3 0;380 G#4 3 0;386 D5 1 0;386 F4 1 0;391 E4 2 0;391 C5 2 0;376 C4 1 0;397 G4 1 0;399 G4 2 0;403 D4 2 0;407 C5 1 0;407 G#4 1 0;409 A4 2 0;409 C5 2 0;413 A4 2 0;413 C5 2 0;407 G#3 3 0;413 E4 1 0;417 C5 1 0;417 G#4 1 0;419 C#5 2 0;419 A#4 2 0;423 E5 1 0;423 G4 1 0;425 C5 2 0;425 E4 2 0;429 E4 1 0;429 A4 1 0;431 G4 2 0;431 C4 2 0;438 C5 1 0;438 G#4 1 0;440 C5 2 0;440 A4 2 0;444 C5 2 0;444 A4 2 0;448 C5 1 0;459 G4 2 0;463 C5 1 0;463 G#4 1 0;465 A4 2 0;465 C5 2 0;469 A4 2 0;469 C5 2 0;463 G#3 3 0;469 E4 1 0;473 C5 1 0;473 G#4 1 0;475 C#5 2 0;475 A#4 2 0;479 E5 1 0;479 G4 1 0;481 C5 2 0;481 E4 2 0;485 E4 1 0;485 A4 1 0;487 G4 2 0;487 C4 2 0;454 G4 2 0'
演示效果
相关参考链接