一起玩轉樹莓派(5)——讓蜂鳴器播放音樂
前面部落格中,我們嘗試使用開關控制有源蜂鳴器的播放。有源蜂鳴器的一大特點是使用簡單,無需複雜的程式控制即可發聲,然而其缺陷也很明顯,其發聲的頻率是一定的,我們無法通過頻率控制器音調高低。本次實驗,我們将嘗試使用無源蜂鳴器來進行音樂的播放。
一、實驗前的準備
在開始本實驗前,我們先來對樂理隻是和無源蜂鳴器的工作原理進行簡單的學習。
1、關于音調的基礎樂理知識
簡單的實體學知識告訴我們,聲音的聲調高低是有聲波的頻率決定的,而聲音的大小是有聲波的振幅決定的。人耳可以聽到的聲音頻率在20Hz-20000Hz之間。頻率低于20Hz的聲波被稱為次聲波,我們聽不到。同樣,頻率高于20000Hz的聲波稱為超音波,我們也聽不到。生活中,我們都喜歡聽美妙的音樂,音樂是由很多不同的音調和參差的拍子組合而成的。小時候音樂課上常說的Do,Re,Mi,Fa,So,La,Xi就是最常見的音調。我們以C大調為例,其7個音階與對應的頻率如下表所示:
| Do | Re | Mi | Fa | So | La | Xi |
| 262 | 294 | 330 | 350 | 393 | 441 | 495 |
與上表對應,如果是高音音調,隻需要将上面的頻率乘以2,如果是低音音調,需要将上面的音頻除以2。
要演奏出美妙的旋律,僅僅隻控制音調是不夠的,我們還需要把握每個音調演奏的節奏,即每個音調播放的時長。在樂曲中,節奏的把控是通過節拍來定義的,例如常見的四分之四節拍,指的是四分音符為一拍,每小節有四拍,這樣,每遇到一個四分音符我們就播放這個音調一拍的機關時間,如果遇到八分音符,我們就播放二分之一拍的機關時間,如果遇到二分音符,我們就播放兩拍的機關時間。
現在,你可以練習一下,将喜歡的歌曲翻譯為程式設計符号,音調翻譯成頻率,對應的節拍翻譯為毫秒時間。後面,我們将使用樹莓派控制蜂鳴器來播放它。
2、無源蜂鳴器播放聲音原理
之前,我們分析過有源蜂鳴器的工作原理,其很好了解,電壓觸發器内部的振蕩源工作,震蕩源的震動産生一定頻率的聲波,進而發出聲音讓我們聽到。無源蜂鳴器内部沒有震蕩源,如果直接通過直流電,其無法産生周期性的聲波,是以簡單的接通電源是無法使無源蜂鳴器工作的。無源蜂鳴器内部沒有震蕩源,但卻有震蕩片,當通電時,無源蜂鳴器通過電磁感應現象來吸引震蕩片,當失去電流時,震蕩片位置還原。是以,我們隻要周期性的給蜂鳴器通電,其就會産生周期性的振蕩,進而産生聲波,發出聲音。
由于無源蜂鳴器的這種特點,我們可以非常容易的通過控制加壓的頻率來實作播放不同音調的聲音。相比有源蜂鳴器而言,無源蜂鳴器成本更低,且可以控制音調高低,唯一的不足之處是程式要略微複雜一些。
本次實驗,我們使用的無源蜂鳴器是低電平觸發的,如下圖所示:

二、使用樹莓派制作電子琴
明白了無源蜂鳴器的工作原理,本實驗對你來說應該非常簡單,使用到的知識都是我們之前實驗有涉及過的。當你聽到視訊頻率來控制無緣蜂鳴器的發聲時,你一定已經想到了,使用PWM脈沖寬度調制技術,其剛好可以産生指定頻率的脈沖電壓。
1.開始連線
連線本身非常簡單,隻是在開始之前,我們要先确定所使用的樹莓派引腳。無緣蜂鳴器有3個引腳,其中VCC接3.3V電源,GMD接地,I/O控制引腳接一個樹莓派的GPIO功能引腳,我們選擇BCM編碼為17的GPIO引腳,即實體引腳11。筆者這裡使用的擴充闆連接配接如下圖所示:
這一步非常簡單,下面我們來開始程式的編寫。
2.開始動手程式設計
在本實驗中,我們将編寫這個一個程式,其類似一個簡易的電子琴,有7個按鍵來發出不同音調的7種聲音,同時我們内置一首示例樂曲。筆者這裡選用周傑倫的《花海》前奏作為示例樂曲。完整的程式示例代碼如下(我建議先自主動手實踐,遇到問題再參考示例代碼):
#coding:utf-8
# 導入UI子產品
import tkinter as Tkinter
import RPi.GPIO as GPIO
import time
# 定義音調頻率
# C調低音
CL = [0, 131, 147, 165, 175, 196, 211, 248]
# C調中音
CM = [0, 262, 294, 330, 350, 393, 441, 495]
# C調高音
CH = [0, 525, 589, 661, 700, 786, 882, 990]
# 定義樂譜
# 音調 0表示休止符
songP = [
CM[1],CM[2],CM[3],CM[5],CM[5],CM[0],CM[3],CM[2],CM[1],CM[2],CM[3],CM[0],
CM[1],CM[2],CM[3],CM[7],CH[1],CH[1],CH[1],CM[7],CH[1],CM[7],CM[6],CM[5],CM[0],
CM[1],CM[2],CM[3],CM[5],CM[5],CM[0],CM[3],CM[2],CM[1],CM[2],CM[1],CM[0],
CM[1],CM[2],CM[3],CM[5],CM[1],CM[0],CM[1],CL[7],CL[6],CL[7],CM[1],CM[0]
]
# 音調對應的節拍
songT = [
2,2,2,1,5,4,2,2,2,1,5,4,
2,2,2,1,5,2,2,2,1,3,2,4,4,
2,2,2,1,5,4,2,2,2,1,3,5,
2,2,2,1,5,4,2,2,2,2,8,2
]
# 定義标準節拍時間
metre = 0.125
# 初始化要使用的引腳
io = 11
GPIO.setmode(GPIO.BOARD)
GPIO.setup(io, GPIO.OUT)
pwm = GPIO.PWM(io, 440)
# 開始設定占空比為100 則無緣蜂鳴器不會發聲
pwm.start(100)
# 播放示例樂曲的方法
def playSong():
# 修改占空比為50 此時頻率生效
pwm.ChangeDutyCycle(50)
# 周遊所有音調
for i in range(0, len(songP)):
# 0表示休止 禁聲
if songP[i] == 0:
pwm.ChangeDutyCycle(100)
# 更改頻率控制音調
else:
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(songP[i])
# 通過節拍控制播放時間
time.sleep(songT[i] * metre)
# 禁聲
pwm.ChangeDutyCycle(100)
# 分别播放各個音階的方法
def playDo():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[1])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
def playRe():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[2])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
def playMi():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[3])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
def playFa():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[4])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
def playSo():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[5])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
def playLa():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[6])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
def playXi():
pwm.ChangeDutyCycle(50)
pwm.ChangeFrequency(CM[7])
time.sleep(0.5)
pwm.ChangeDutyCycle(100)
# UI相關設定
# 首頁面設定
top = Tkinter.Tk()
top.geometry('360x300')
top.minsize(420, 300)
top.maxsize(420, 300)
top.title("自制電子琴")
l = Tkinter.Label(top, text='自制電子琴', font=('Arial', 18), width=30, height=2)
l.pack()
# UI上的按鈕布局
songBtn = Tkinter.Button(top, text="示例歌曲:花海", command=playSong)
songBtn.place(x=30,y=30,width=120,height=40)
doBtn = Tkinter.Button(top,bitmap="gray50", text="Do", compound=Tkinter.LEFT, command=playDo)
doBtn.place(x=0,y=105,width=60,height=200)
reBtn = Tkinter.Button(top,bitmap="gray50", text="Re", compound=Tkinter.LEFT, command=playRe)
reBtn.place(x=60,y=105,width=60,height=200)
miBtn = Tkinter.Button(top,bitmap="gray50", text="Mi", compound=Tkinter.LEFT, command=playMi)
miBtn.place(x=120,y=105,width=60,height=200)
faBtn = Tkinter.Button(top,bitmap="gray50", text="Fa", compound=Tkinter.LEFT, command=playFa)
faBtn.place(x=180,y=105,width=60,height=200)
soBtn = Tkinter.Button(top,bitmap="gray50", text="So", compound=Tkinter.LEFT, command=playSo)
soBtn.place(x=240,y=105,width=60,height=200)
laBtn = Tkinter.Button(top,bitmap="gray50", text="La", compound=Tkinter.LEFT, command=playLa)
laBtn.place(x=300,y=105,width=60,height=200)
xiBtn = Tkinter.Button(top,bitmap="gray50", text="Xi", compound=Tkinter.LEFT, command=playXi)
xiBtn.place(x=360,y=105,width=60,height=200)
stopButton = Tkinter.Button(top, text="關閉")
stopButton.place(x=340,y=30,width=60,height=40)
# 進入消息循環
top.mainloop()
上面代碼有比較詳盡的注釋,程式運作的UI效果如下圖所示:
現在,盡情的玩耍吧!
專注技術,懂的熱愛,願意分享,做個朋友