一起玩轉樹莓派(5)——讓蜂鳴器播放音樂

一起玩轉樹莓派(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效果如下圖所示:

現在,盡情的玩耍吧!

專注技術,懂的熱愛,願意分享,做個朋友

QQ:316045346

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章