数学中的函数一词泛指发生在集合之间的一种对应关系和变化过程。在程序设计领域,函数实际上就是一段具有特定功能、完成特定任务的程序,以减少重复编写程序的工作量。使用 PWM 模拟信号除了能够控制灯光亮度外,还能控制许多其他的电子设备,蜂鸣器就是其中一种。在本节课程中,我们将了解关于蜂鸣器的基本知识,和它的使用方法,并学习如何使用函数来简化程序,完成一段简单的旋律。

知识锦囊

蜂鸣器

蜂鸣器是一种常见的发声装置,它常被作为发声器用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中。我们常见的蜂鸣器又分为无源蜂鸣器与有源蜂鸣器。
第8课 用函数让蜂鸣器唱歌 - 图1第8课 用函数让蜂鸣器唱歌 - 图2
这其中的“源”并非指电源,而是指震荡源。
pico插图-07.png
震荡源即为震荡电路,它能将输入的直流电流信号转换为震荡电流信号输出,从而驱动蜂鸣器发声。
有源蜂鸣器就内置有一个震荡源,在使用时为其提供恒定直流电压即可工作,其发声频率由内部震荡电路决定,音调一般无法改变。
而无源蜂鸣器则没有震荡源,只能靠外界输入 PWM (方波)信号来实现磁场交变,所以我们可以使用 PWM 信号驱动振动装置从而使蜂鸣器发声。而通过调整 PWM 信号的频率和占空比,我们就能控制蜂鸣器的音调高低和音量大小。
在本节课程中,我们将要使用蜂鸣器来演奏一段简单的旋律,但在开始前,我们还需要学习如何通过定义函数来降低工作量。

定义函数

在数学定义中,函数是指发生在集合之间的一种对应关系和变化过程。而在程序设计领域,函数实际上就是一段具有特定功能、能够完成特定任务的程序。当我们编写好这段程序后,就可以在之后的程序里重复的调用这段程序,来处理相同任务,以提高代码的重复利用率。
在之前的学习中,你已经使用了许多 Python 的内置函数,例如 print() , Pin() 等。当然,我们也可以自己自定义函数来进行使用。
在 Python 中我们使用 def 来定义函数,其定义函数的语法如下:

  1. def 函数名(参数列表):
  2. 函数体

在定义函数时,函数代码块以 def 关键词开头,后接函数名称和小括号” () “,小括号后的冒号” : “表示函数体的开始。函数体,即为定义一个函数功能的所有代码组成的整体,当你完成了函数的定以后,每一次调用该函数时系统都会执行一次函数体中的代码。

直接来看示例程序:

def HelloWorld():
    print("Hello World")

HelloWorld()

该程序的执行结果为:

Hello World

在该程序中,我们定义了一个名为 HelloWorld 的函数,在每次使用 “HelloWorld()” 调用该函数时,都会执行一次函数体中的 “print(“Hello World”) “ 语句。

在函数名后的括号中,我们还可以写入需要被引入该函数的参数,例如:

a = 4
b = 5
def max(a, b):
    if a > b:
        return a
    else:
        return b
print(max(a, b))

在该程序中,我们创建了一个名为 max() 的函数,该函数会在参数列表中引入两个参数。在函数体中,程序会比较两个数的大小,并返回较大的数。
其中 return [表达式] 代表函数的结束,它会返回一个值给调用它的函数。例如在该程序中,我们会通过 if 条件判断来判断出 a,b 中较大的那一个数,并返回它的值。所以该程序的执行结果为5,即 a,b 中较大的 b 的值。
该程序的执行结果为:

5

这就是函数的定义方法。

实践操作

项目一:演奏基本音节

首先,我们先来控制蜂鸣器演奏 do、re、mi、fa、sol、la、si 七个基本音节。

硬件连接

在该项目中,我们所需要使用的硬件有:

  • 树莓派 Pico
  • 树莓派 Pico 拓展板
  • Grove - 蜂鸣器

插接好 Pico 和拓展板,使用 Grove 数据线将蜂鸣器连接到 A1 接口。
页面_9.png

编写程序

我们要想使用蜂鸣器来演奏乐曲,就需要学会控制蜂鸣器发出的声音的两个维度:音量和音调。
对于音量而言,我们同样可以像控制 LED 灯亮度一样,通过改变 PWM 信号的占空比来实现。而对于音调来说,声音的音调高低是由声音的频率决定的,所以我们只需要修改 PWM 信号的频率,即可实现对音调的控制。
我们先来编写一个程序,让蜂鸣器发出 do、re、mi、fa、sol、la、si 七个音节。
在该程序中。我们需要定义引脚,并使用 sleep() 函数控制每个音节的持续时间,所以我们需要在程序的开头声明用到的函数库:

from machine import Pin, PWM
from time import sleep

接着定义引脚

buzzer = PWM(Pin(27))

接下来我们需要使用 freq() 函数和 duty_u16() 函数来定义音调与音量:

buzzer.freq(1046) #DO
buzzer.duty_u16(1000)

buzzer.freq(1175) #RE
buzzer.duty_u16(1000)

buzzer.freq(1318) #MI
buzzer.duty_u16(1000)    

buzzer.freq(1397) #FA
buzzer.duty_u16(1000)

buzzer.freq(1568) #SO
buzzer.duty_u16(1000) 

buzzer.freq(1760) #LA
buzzer.duty_u16(1000)   

buzzer.freq(1967) #SI
buzzer.duty_u16(1000)

最后,使用 sleep() 函数规定每一个音节的发声时间长短,程序就完成了,完整的程序如下:

from machine import Pin, PWM
from time import sleep

buzzer = PWM(Pin(27))
while True:
    buzzer.freq(1046) #DO
    buzzer.duty_u16(1000)
    sleep(1)   
    buzzer.freq(1175) #RE
    buzzer.duty_u16(1000)
    sleep(1)        
    buzzer.freq(1318) #MI
    buzzer.duty_u16(1000)    
    sleep(1) 
    buzzer.freq(1397) #FA
    buzzer.duty_u16(1000)
    sleep(1) 
    buzzer.freq(1568) #SO
    buzzer.duty_u16(1000) 
    sleep(1)    
    buzzer.freq(1760) #LA
    buzzer.duty_u16(1000)   
    sleep(1)  
    buzzer.freq(1967) #SI
    buzzer.duty_u16(1000)
    sleep(1)

image.png
使用 USB 数据线将 Pico 连接到电脑,点击运行按钮将程序保存到任意位置,你就能听到蜂鸣器演奏七个基本音节了。
演奏基本音节.mp4
image.png

项目二:两只老虎

接下来,我们通过定义函数简化程序,控制蜂鸣器演奏两只老虎完整的旋律。乐谱如下:
第8课 用函数让蜂鸣器唱歌 - 图7

编写程序

当我们编写一段简单的乐曲时,我们可以使用逐行编写的方法来完成程序,但用这种方法所编写的程序过于冗长和重复,如果我们所想要复现一整首歌曲,不论是从提高代码重复利用率的角度,还是从程序可读性的角度出发,我们都需要使用自定义函数来简化程序。在任务一的基础上,我们可以将每一个音节都定义为一个函数,当我们想要演奏该音节时,只需要调用一次该函数即可。
以音节 DO 为例,定义函数的程序如下:

def DO():
    buzzer.freq(1046) #1
    buzzer.duty_u16(1000)

将七个音节与停止发声的执行程序都按这样的方式定义为函数:

def DO():
    buzzer.freq(1046) #1
    buzzer.duty_u16(1000)
def RE():    
    buzzer.freq(1175) #2
    buzzer.duty_u16(1000)
def MI():          
    buzzer.freq(1318) #3
    buzzer.duty_u16(1000)    
def FA():     
    buzzer.freq(1397) #4
    buzzer.duty_u16(1000)
def SO():    
    buzzer.freq(1568) #5
    buzzer.duty_u16(1000) 
def LA():    
    buzzer.freq(1760) #6
    buzzer.duty_u16(1000)   
def SI():    
    buzzer.freq(1967) #7
    buzzer.duty_u16(1000)
def N():
    buzzer.duty_u16(0) #close

在之后的程序中,我们只需要对这些函数进行调用,即可完成乐曲,演奏两只老虎的程序如下:

from machine import Pin, I2C, ADC, PWM
from time import sleep

buzzer = PWM(Pin(27))
vol = 1000
def DO():
    buzzer.freq(1046) #1
    buzzer.duty_u16(vol)
def RE():    
    buzzer.freq(1175) #2
    buzzer.duty_u16(vol)
def MI():          
    buzzer.freq(1318) #3
    buzzer.duty_u16(vol)    
def FA():     
    buzzer.freq(1397) #4
    buzzer.duty_u16(vol)
def SO():    
    buzzer.freq(1568) #5
    buzzer.duty_u16(vol) 
def LA():    
    buzzer.freq(1760) #6
    buzzer.duty_u16(vol)   
def SI():    
    buzzer.freq(1967) #7
    buzzer.duty_u16(vol)
def N():
    buzzer.duty_u16(0) #close

while True:

    DO()
    sleep(0.25)
    RE()
    sleep(0.25)
    MI()
    sleep(0.25)
    DO()
    sleep(0.25)
    N()
    sleep(0.01)

    DO()
    sleep(0.25)
    RE()
    sleep(0.25)
    MI()
    sleep(0.25)
    DO()
    sleep(0.25)

    MI()
    sleep(0.25)
    FA()
    sleep(0.25)
    SO()
    sleep(0.5)

    MI()
    sleep(0.25)
    FA()
    sleep(0.25)
    SO()
    sleep(0.5)
    N()
    sleep(0.01)

    SO()
    sleep(0.125)
    LA()
    sleep(0.125)
    SO()
    sleep(0.125)
    FA()
    sleep(0.125)
    MI()
    sleep(0.25)
    DO()
    sleep(0.25)

    SO()
    sleep(0.125)
    LA()
    sleep(0.125)
    SO()
    sleep(0.125)
    FA()
    sleep(0.125)
    MI()
    sleep(0.25)
    DO()
    sleep(0.25)

    RE()
    sleep(0.25)
    SO()
    sleep(0.25)
    DO()
    sleep(0.5)
    N()
    sleep(0.01)

    RE()
    sleep(0.25)
    SO()
    sleep(0.25)
    DO()
    sleep(0.5)

在程序的第五行,我们添加了一个名为 vol 的变量,并在之后的程序中用它来表示 PWM 信号中的占空比数值,现在,每当我们想要修改蜂鸣器的音量时,只需要修改变量 vol 的值即可。

vol = 1000

仔细看看我们的程序,比起逐行直接编写程序已经简化了不少。现在在该程序中,总是重复的部分只剩下了 sleep() 函数,由于要构成一首完整的乐曲,许多音节发声的时间都不同,所以我们不能简单的将 sleep() 函数直接添加进函数体中—-我们需要创建一个带有参数的自定义函数,同样拿音节 DO 来举例,定义音节 DO 的程序如下:

def DO(time):
    buzzer.freq(1046) #1
    buzzer.duty_u16(vol)
    sleep(time)

当我们使用该函数时,需要在括号内填上传入函数体的参数,该参数会被函数体中的 sleep() 函数调用,使我们可以自由控制音节的发音时间。例如当需要音节 DO 发音0.5秒时,我们可以这样编写程序:

DO(0.5)

怎么样,是不是又简洁了许多。通过这样定义函数,我们可以让程序的编写更加简洁,下面是该程序最终的样子:

from machine import Pin, I2C, ADC, PWM
from time import sleep

buzzer = PWM(Pin(27))
vol = 1000
def DO(time):
    buzzer.freq(1046) #1
    buzzer.duty_u16(vol)
    sleep(time)
def RE(time):    
    buzzer.freq(1175) #2
    buzzer.duty_u16(vol)
    sleep(time)
def MI(time):          
    buzzer.freq(1318) #3
    buzzer.duty_u16(vol)
    sleep(time)
def FA(time):     
    buzzer.freq(1397) #4
    buzzer.duty_u16(vol)
    sleep(time)
def SO(time):    
    buzzer.freq(1568) #5
    buzzer.duty_u16(vol)
    sleep(time)
def LA(time):    
    buzzer.freq(1760) #6
    buzzer.duty_u16(vol)
    sleep(time)
def SI(time):    
    buzzer.freq(1967) #7
    buzzer.duty_u16(vol)
    sleep(time)
def N(time):
    buzzer.duty_u16(0) #close
    sleep(time)
while True:

    DO(0.25)
    RE(0.25)
    MI(0.25)
    DO(0.25)
    N(0.01)

    DO(0.25)
    RE(0.25)
    MI(0.25)
    DO(0.25)

    MI(0.25)
    FA(0.25)
    SO(0.5)

    MI(0.25)
    FA(0.25)
    SO(0.5)  
    N(0.01)

    SO(0.125)
    LA(0.125)
    SO(0.125)
    FA(0.125)
    MI(0.25)
    DO(0.25)

    SO(0.125)
    LA(0.125)
    SO(0.125)
    FA(0.125)
    MI(0.25)
    DO(0.25)

    RE(0.25)
    SO(0.25)
    DO(0.5)
    N(0.01)

    RE(0.25)
    SO(0.25)
    DO(0.5)

image.png
使用 USB 数据线将 Pico 连接到电脑,点击运行按钮将程序保存到任意位置,就能听见蜂鸣器演奏两只老虎了。
两只老虎.mp4

思维拓展

请使用本节课所学,编写可以演奏《小星星》的程序。
乐谱如下:
第8课 用函数让蜂鸣器唱歌 - 图9
程序参考如下:
image.png