8

一起玩转树莓派(5)——让蜂鸣器播放音乐

 3 years ago
source link: https://my.oschina.net/u/2340880/blog/5135289
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一起玩转树莓派(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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK