logo头像

学如逆水行舟

一起玩转树莓派(4)——用开关控制蜂鸣器发声

一起玩转树莓派(4)——用开关控制蜂鸣器发声

在本系列的前几篇博客中,我们使用桌面软件控制过红绿灯,也编程点亮过炫彩的3色LED 灯。在这些实验的过程中,相信你对树莓派GPIO引脚如何输出高低电平,以及如何使用PWM技术来控制电压输出等都有了了解。本篇博客,我们将再探索一些树莓派编程更多新鲜好玩的领域,来尝试使用硬件来控制硬件。

现在,我们将尝试完成这样一个实验,使用按压开关控制蜂鸣器的发声。将开关元件和蜂鸣器都连接到树莓派,当按下开关时,让蜂鸣器发声,当松开开关时,蜂鸣器停止发声。

一、主角登场

开始动手实验前,我们先来认识一下将要出场的主角们,当然,无论什么实验,树莓派都是当之无愧的第一主角,但我相信你已经对它已经有了足够的了解(如果没有,可以查看本系列的前几篇博客),因此我们将介绍的重点放在两个新登场的“角色”上:蜂鸣器与按压开关。

1.本实验的“主角甲”——有源蜂鸣器

蜂鸣器,顾名思义,其是一种发声元件。广泛应用于计算机、打印机、电话、玩具等电子设备中。

蜂鸣器可以分为“有源蜂鸣器”和“无源蜂鸣器”。其中的源并非指电源,而是指蜂鸣器内部是否有震荡源。有源蜂鸣器自带一个震荡源模块,其使用起来非常简单,只要接通直流电,其就会自动发出声音。而无源蜂鸣器内部没有震荡源,需要外接使用一定频率的方波来驱动其发声。关于无源蜂鸣器更多深入的用法,我们后续博客再专门介绍。这里,我们先着重理解有源蜂鸣器的工作原理。

本实验中,我们采用的是低电平触发的有源蜂鸣器,元件如下图所示:

其有3个引脚,GND引脚用来接地,VCC引脚用来接3.3V的电源,I/O引脚用来进行蜂鸣器是否播放声音的控制。你可能有些疑惑,为什么低电平也可以触发元件的“开”状态,这要归功于PNP型三极管的功劳,对于PNP型三极光,当输入管脚为低电平时,三极管处于导通状态,使得蜂鸣器被加电压,当输入管脚为高电平时,三极管处于截止状态,蜂鸣器无电压。原理如下图所示:

现在,我们先来编写一段简单的Python程序,来使蜂鸣器发出声音。

2. 触发蜂鸣器播放声音

我们使用的蜂鸣器的3个引脚,其中VCC接电源,GND接地,I/O引脚我们可以选择树莓派BCM编码为27的GPIO引脚,通过查表,可以得到其物理编码为13。连线示意如下:

编写Python程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#coding:utf-8

# 导入GPIO控制薄块
import RPi.GPIO as GPIO
# 导入time模块
import time

# 定义引脚
fm = 13
# 设置使用的引脚编码模式
GPIO.setmode(GPIO.BOARD)
# 进行引脚的初始化,因为是低电平触发,初始时设置为高电平
GPIO.setup(fm,GPIO.OUT, initial=GPIO.HIGH)

# 进行一长两短的声音播放
# 播放1秒声音
GPIO.output(fm, GPIO.LOW)
time.sleep(1)
GPIO.output(fm, GPIO.HIGH)
time.sleep(0.1)
# 播放0.5秒声音
GPIO.output(fm, GPIO.LOW)
time.sleep(0.5)
GPIO.output(fm, GPIO.HIGH)
time.sleep(0.1)
# 播放0.5秒声音
GPIO.output(fm, GPIO.LOW)
time.sleep(0.5)
# 停止蜂鸣器播放
GPIO.output(fm, GPIO.HIGH)
GPIO.cleanup()

在树莓派上运行代码,你应该已经可以听到蜂鸣器发出一长两短的鸣叫声了。

3.本实验的“主角乙”——按压开关

按压开关是电子设备中最为常用的一种元件,关于按压开关本身的结构和原理,其实非常简单,其按键内部会有弹簧进行控制,当没有外力作用时,弹簧会将开关自动顶起,接触器上升。当外力将按键按下时,接触器下降。通过接触器的上升和下降来控制电路的导通。如下图所示:

本次实验,我们使用的按压开关元件如下图所示:

我们使用的此开关当按键按下式,会向引脚输入低电平,当按键松开时,会向引脚输入低电平。在连线时,按压开关的“-”引脚接地,中间引脚节3.3V电压,“S”引脚接GPIO信号引脚,我们可以将其接BCM编码为GPIO5的引脚,其物理编码为29。连线如下图所示:

下面,我们要来学习点新东西了,如何获取到GPIO引脚的输入。

4. 使用GPIO输入信号

之前,我们都是将GPIO作为输出引脚进行使用,即让GPIO输出高电平或低电平从而控制元件。其实还有很多场景,需要读取GPIO的输入信号,例如各种传感器和开关元件。通常,我们可以采用两种方式来获取GPIO的输入信号:轮询式中断式

轮询式很好理解,即我们不停的询问当前GPIO引脚:输入的信号是什么啊?之后GPIO会将当前的输入信息告诉我们,这种不停的读取某个GPIO引脚的值的方式就是轮询式获取输入信号。更多时候,我们会采取中断式,中断式是指在程序运行过程中,如果出现外部事件,则中断当前程序运行来处理外部事件。在本实验中,开关的按下和松开就可以理解为中断事件。

需要注意,对于作为输入使用的GPIO引脚来说,当我们未接任何输入源时,其值可能是任意的,即其值是浮动且不可控的,因此我们需要为其配置一个默认值,硬件上,我们通常使用下拉电阻或上拉电阻。软件上我们也可以通过GPIO库的相关接口来实现同样的功能,例如使用下面的方式初始化引脚:

1
2
3
4
# 将引脚初始化为输入引脚,同时设置默认值为高电平
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# 将引脚初始化为输入引脚,同时设置默认值为低电平
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

若要获取到某个输入引脚当前的电平情况,可以使用如下方法:

1
2
# 1表示高电平 0表示低电平
GPIO.input(channel)

例如可以通过如下的方式轮训检查某个引脚的输入:

1
2
while GPIO.input(channel) == GPIO.LOW:
time.sleep(0.01)

我们通常只需要关心电平的变化,以及变化方向,以本实验为例,当按下开关时,电平由低变高,当松开开关时,电平由高到低。下面方法可以中断当前程序的运行,等待电平变化的信号:

1
2
# 当channel引脚由低电平变到高电平时会触发向后执行, timeout设置超时时间
GPIO.wait_for_edge(channel, GPIO.RISING, timeout=5000)

GPIO.RISING表示要等待的是由低到高的电平变化,与之对应的还有GPIO.FALLING表示由高到低的电平变化,GPIO.BOTH表示两个方向的电平变化都会触发。

对于本实验的场景,GPIO.wait_for_edge方法并不实用,如果我们要采用轮询的方式获取GPIO输入信号,则可以实用下面的方法:

1
2
3
4
5
# 为channel输入引脚添加[从低到高]的电平变化监听
GPIO.add_event_detect(channel, GPIO.RISING)
# 下面的判断可以放在轮询中,只要有[从低到高]的电平变化,即会命中if判断
if GPIO.event_detected(channel):
print('从低到高变化')

需要注意,GPIO.add_event_detect方法比直接轮询通过GPIO.input方法获取信号要可靠的多,直接轮询可能会错过某些电平变化事件,而GPIO.add_event_detect 不会。在使用GPIO.add_event_detect方法时,我们也可以直接设置回调函数,当有变化发生时,即会执行到回调函数,例如:

1
2
3
4
5
# 定义回调函数
def my_callback(channel):
pass
# 注册回调函数 bouncetime参数用来进行防抖
GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback, bouncetime=200)

上面示例代码中,bouncetime参数能够提供软件防抖的功能,其单位为毫秒,200毫秒内的多次电平变化来回将被忽略。

最后,如果你不再需要监听,可以使用如下方法移除:

1
GPIO.remove_event_detect(channel)

5. 获取按压开关元件的状态

现在,我们可以尝试使用树莓派读取按压开关元件的开关状态了。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#coding:utf-8

# 导入GPIO控制薄块
import RPi.GPIO as GPIO
# 导入time模块
import time

# 设置使用的引脚编码模式
GPIO.setmode(GPIO.BOARD)

# 定义开关引脚
swi = 29
# 进行开关引脚的初始化,设置为输入引脚,且默认为高电平
GPIO.setup(swi, GPIO.IN, pull_up_down=GPIO.PUD_PU)
# 定义状态变化的回调函数
def switch(channel):
# 高电平的开关松开
if GPOI.input(channel):
print("开关松开")
# 低电平为开关按下
else:
print("开关按下")

# 添加输入引脚电平变化的回调函数
GPIO.add_event_detect(swi, GPIO.BOTH, callback=switch, bouncetime=200)
# 开启循环
while True:
pass

在树莓派运行上面代码,通过按压和松开开关,观察控制台的输出是否正确。

二、开关控制蜂鸣器播放

如果你成功做完了前面的工作,那么好了,本节的内容将非常简单,只需要将上面编写的蜂鸣器程序和开关程序进行结合,你就可以使用开关来控制蜂鸣器了。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#coding:utf-8

# 导入GPIO控制薄块
import RPi.GPIO as GPIO
# 导入time模块
import time

# 设置使用的引脚编码模式
GPIO.setmode(GPIO.BOARD)

# 定义开关引脚
swi = 29
# 定义蜂鸣器引脚
fm = 13
# 进行开关引脚的初始化,设置为输入引脚,且默认为高电平
GPIO.setup(swi, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# 进行蜂鸣器引脚的初始化,因为是低电平触发,初始时设置为高电平
GPIO.setup(fm,GPIO.OUT, initial=GPIO.HIGH)
# 定义状态变化的回调函数
def switch(channel):
# 高电平的开关松开
if GPI0.input(channel):
GPIO.output(fm, GPIO.HIGH)
# 低电平为开关按下
else:
GPIO.output(fm, GPIO.LOW)

# 添加输入引脚电平变化的回调函数
GPIO.add_event_detect(swi, GPIO.BOTH, callback=switch, bouncetime=200)
# 开启循环
while True:
pass

现在,运行程序,用开关来控制蜂鸣器的声音播放吧。恭喜!你已经能够使用硬件来控制硬件了!

三、休息一下吧

通过本篇博客,你应该收获了GPIO输入信号的编程方法,以及通过GPIO读取有输入功能的传感器数据的方法。本篇博客涉及的内容很多,重点在于GPIO输入引脚中断的相关用法,需要你多多练习。说了这么多,你有没有发现这次我们完成的实验很像生活中的一种东西?没错,就是门铃,你已经可以使用树莓派实现一个简单的门铃器件啦。

专注技术,懂的热爱,愿意分享,做个朋友

QQ:316045346