在本章中,您将学习如何使用 Circuit Playery Express 和一些香蕉制作乐器。我们将把四个香蕉连接到板上的触摸板上,这样你可以为你触摸的每一根香蕉播放特定的音乐声音。我们将为项目添加一些视觉反馈,每次接触触摸板时,在每个触摸板旁边点亮一个像素。本项目将展示一种创造性、有趣的方式,让您的电容式触摸项目充满活力。
通过在项目中使用诸如香蕉之类的意外对象,您可以为平凡的 MicroPython 项目添加独特的扭曲。
在本章中,我们将介绍以下配方:
- 创建类以对触摸事件作出反应
- 创建函数以启用扬声器输出
- 创建播放音频文件的函数
- 使用 NeoPixel 对象控制像素
- 创建用于播放声音的触摸处理程序
- 创建触摸处理程序以点亮像素
- 创建事件循环以处理所有触摸事件
本章的代码文件可在 GitHub 存储库的Chapter07文件夹中找到,位于https://github.com/PacktPublishing/MicroPython-Cookbook 。
本章中的许多配方需要将四个音频文件传输到电路板。它们都可以从 GitHub 存储库中的Chapter07文件夹下载。它们应该与您的main.py文件一起保存在顶级文件夹中。
Circuit Playerd Express 配备七个电容式触摸板。它们中的每一个都可以连接到任何可以导电的物体上,触摸该物体将触发传感器。你可以使用好的导体,比如金属,甚至是较弱的导体,比如香蕉。
水导电,许多水果的表面有足够的水分让触摸板能够检测到触摸事件。许多水果,如香蕉、酸橙、橙子和苹果,都可以完成这项工作。您可以使用鳄鱼夹将水果连接到触摸板。以下照片显示了一捆鳄鱼夹:
这些鳄鱼夹有多种不同的颜色。最好为每个触摸板使用不同颜色的导线。这将使跟踪哪个水果连接到哪个触摸板变得更容易。在这个项目中,我们将使用绿色、红色、黄色和白色的电线。我们将每个焊盘旁边像素的颜色设置为绿色、红色、黄色和白色。下一张照片显示一根香蕉连接到一个触摸板:
鳄鱼夹工作非常好,因为它们不需要任何焊接,并且可以轻松连接到电路板和各种物体。鳄鱼夹上的牙齿也会产生良好的抓地力,以便在电路板和香蕉之间建立良好的电气连接。下面这张照片展示了附在香蕉上的鳄鱼牙齿的近景:
下一张照片显示了附在触摸板上的鳄鱼牙齿的近景:
在前面的章节中,我们使用了 Circuit Playplace Express 库与电路板上的不同组件进行交互。使用此库播放音频文件时,库将阻止您的代码,直到文件播放完成。在这个项目中,我们希望能够立即响应触摸事件,并在不等待当前音频文件完成播放的情况下播放新声音。
只有使用直接控制音频播放和触摸板的 CircuitPython 库,才能实现这种级别的控制。因此,本章中的任何代码都不会使用 Circuit Playplace Express 库。通过采用这种方法,我们还将了解如何对电路板上的组件进行更精细的控制。
在本教程中,您将学习如何定义一个类,该类可以帮助您在特定触摸板上处理触摸事件。创建此类的实例时,可以指定焊盘名称和回调函数,每次触摸事件开始和结束时都会调用该函数。我们可以使用这个类作为构建块,为将连接到香蕉的四个触摸板中的每一个调用回调。只要您想用一组回调函数处理各种事件,就可以在自己的项目中使用这种风格的代码。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 在 REPL 中运行以下代码行:
>>> from touchio import TouchIn
>>> import board
>>>
>>> def handle(name, current):
... print(name, current)
...
...
...
>>> handle('A1', True)
A1 True
>>> handle('A1', False)
A1 False
>>> - 在这个阶段,我们定义了一个函数,它将通过打印触摸板的名称以及触摸板是否被触摸来处理触摸事件。
- 运行下一段代码创建一个类,该类将检查触摸事件。定义类后,它将创建一个实例,然后打印出键盘的当前触摸状态:
>>> class TouchEvent:
... THRESHOLD_ADJUSTMENT = 400
...
... def __init__(self, name, onchange):
... self.name = name
... self.last = False
... self.onchange = onchange
... pin = getattr(board, name)
... self.touch = TouchIn(pin)
... self.touch.threshold += self.THRESHOLD_ADJUSTMENT
...
...
...
>>> event = TouchEvent('A1', handle)
>>> event.touch.value
False- 在运行下一段代码时,将手指放在触摸板 A1 上:
>>> event.touch.value
True- 运行下一段代码创建一个类,该类具有处理触摸事件的方法:
>>> class TouchEvent:
... THRESHOLD_ADJUSTMENT = 400
...
... def __init__(self, name, onchange):
... self.name = name
... self.last = False
... self.onchange = onchange
... pin = getattr(board, name)
... self.touch = TouchIn(pin)
... self.touch.threshold += self.THRESHOLD_ADJUSTMENT
...
... def process(self):
... current = self.touch.value
... if current != self.last:
... self.onchange(self.name, current)
... self.last = current
...
...
...
>>> event = TouchEvent('A1', handle)- 在运行下一段代码时,将手指放在触摸板 A1 上:
>>> event.process()
A1 True- 以下代码应放入
main.py文件中:
from touchio import TouchIn
import board
class TouchEvent:
THRESHOLD_ADJUSTMENT = 400
def __init__(self, name, onchange):
self.name = name
self.last = False
self.onchange = onchange
pin = getattr(board, name)
self.touch = TouchIn(pin)
self.touch.threshold += self.THRESHOLD_ADJUSTMENT
def process(self):
current = self.touch.value
if current != self.last:
self.onchange(self.name, current)
self.last = current
def handle(name, current):
print(name, current)
event = TouchEvent('A1', handle)
while True:
event.process()执行时,每当触摸板 A1 上的触摸事件开始或结束时,该脚本将重复打印消息。
TouchEvent类的定义是为了帮助我们跟踪触摸板的最后一个已知状态,并通过调用指定的回调函数来响应其状态的变化。定义了一个默认的触摸阈值400,以便该类的子类可以覆盖该值。构造函数希望第一个参数是要监视的触摸板的名称,以及检测到状态更改时将调用的回调函数。
名称和回调函数与实例上的属性一起保存。最后一个已知状态初始化为False值。然后,从boardPython 模块中检索命名触摸板的 pin 值。此 pin 用于创建一个TouchIn实例,该实例也保存为对象上的属性。最后,作为初始化过程的一部分,在该触摸板上设置阈值。
在类上定义的另一个方法将定期调用,以检查触摸板状态中的任何更改,并通过调用已定义的回调函数来处理此状态更改。这是通过获取当前触摸状态并将其与上一个已知值进行比较来实现的。如果它们不同,则调用回调并保存值以供将来参考。
定义了一个简单的函数来处理任何触摸事件,只需打印出状态发生变化的触摸板的名称和当前状态。
在这些类和函数定义之后,我们创建这个类的一个实例来监视 touchpad A1。然后,我们进入一个无限循环,该循环反复检查状态变化,并在每次发生变化时打印出一条消息。
在触摸板上设置触摸阈值总是一个好主意。如果不这样做,在与触摸板交互时会出现大量误报。选择的值400适用于将香蕉与鳄鱼夹连接的特定设置。最好将用于项目的实际对象连接起来,然后将该值微调为合适的值。
在这个配方中,我们混合了函数和类的用法。这种方法在 Python 中非常好,它让您可以两全其美。我们需要跟踪每次调用 process 方法之间的状态,这就是我们为此选择类的原因。回调不需要跟踪调用之间的任何状态,因此一个简单的函数可以很好地完成这项工作。
以下是一些参考资料:
TouchIn类的文档可在中找到 https://circuitpython.readthedocs.io/en/3.x/shared-bindings/touchio/TouchIn.html 。boardPython 模块的文档可在中找到 https://circuitpython.readthedocs.io/en/3.x/shared-bindings/board/init.html#module-董事会。
在本食谱中,您将学习如何创建一个函数,当调用该函数时,该函数将启用扬声器。如果在音频播放之前未启用扬声器,则将通过插脚 A0 播放,插脚 A0 可以连接耳机。
这个项目将使用板上的扬声器而不是耳机,所以我们需要这个功能在脚本开始时启用扬声器。除了向您展示如何启用扬声器外,本食谱还将向您介绍数字控制输入/输出引脚的方法。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 执行 REPL 中的下一个代码块:
>>> from digitalio import DigitalInOut
>>> import board
>>>
>>>
>>> speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
>>> speaker_control
<DigitalInOut>- 在此阶段,我们创建了一个连接到 pin 的对象,该对象将启用扬声器。运行下一段代码以启用扬声器:
>>> speaker_control.switch_to_output(value=True)- 重新加载电路板并再次输入 REPL。下一段代码将定义启用扬声器的功能,并将其调用:
>>> from digitalio import DigitalInOut
>>> import board
>>>
>>> def enable_speakers():
... speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
... speaker_control.switch_to_output(value=True)
...
...
...
>>> enable_speakers()首先定义enable_speakers函数。它不接收任何参数,因为电路板上只有一个扬声器需要启用,并且它不返回任何内容,因为一旦启用了扬声器,就不需要再次与其 pin 交互。DigitalInOut对象用于与启用扬声器的 pin 进行交互。创建此对象后,将调用switch_to_output方法以启用扬声器输出。定义函数后,将调用该函数以启用扬声器。
此配方中使用的DigitalInOut对象可用于与各种引脚进行交互。例如,在该电路板上,它可用于连接从按钮 A 和按钮 B 读取输入的引脚。一旦正确连接和配置这些按钮引脚,就可以开始轮询引脚值,以检查按钮是否按下。
以下是一些参考资料:
DigitalInOut对象的示例用法可在中找到 https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-digital-in-out 。- 有关
DigitalInOut对象的文档可在中找到 https://circuitpython.readthedocs.io/en/3.x/shared-bindings/digitalio/DigitalInOut.html 。
在本教程中,您将学习如何创建一个函数,该函数在调用时将在内置扬声器上播放特定的音频文件。此配方将说明如何访问音频输出设备,以及如何读取.wav文件的内容,将其转换为音频流,并将该音频流提供给车载音频播放设备。此配方中显示的技术可用于需要对音频文件播放方式进行更精细控制的各种项目。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 使用 REPL 运行以下代码行:
>>> from digitalio import DigitalInOut
>>> from audioio import WaveFile, AudioOut
>>> import board
>>> import time
>>>
>>> def enable_speakers():
... speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
... speaker_control.switch_to_output(value=True)
...
...
...
>>> enable_speakers()
>>> speaker = AudioOut(board.SPEAKER)
>>> speaker
<AudioOut>
>>> - 在这个阶段,我们启用了扬声器,并创建了一个对象来向扬声器提供音频数据。当您运行下一段代码时,您应该会听到扬声器播放钢琴音符:
>>> file = open('piano.wav', "rb")
>>> audio = WaveFile(file)
>>> speaker.play(audio)
>>> - 运行下一段代码,再次听到相同的钢琴音符,但这次通过函数调用播放:
>>> def play_file(speaker, path):
... file = open(path, "rb")
... audio = WaveFile(file)
... speaker.play(audio)
...
...
...
>>> play_file(speaker, 'piano.wav')- 以下代码应放入
main.py文件中,执行时,每次重新加载电路板时,它将播放一个钢琴音符:
from digitalio import DigitalInOut
from audioio import WaveFile, AudioOut
import board
import time
def play_file(speaker, path):
file = open(path, "rb")
audio = WaveFile(file)
speaker.play(audio)
def enable_speakers():
speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
speaker_control.switch_to_output(value=True)
enable_speakers()
speaker = AudioOut(board.SPEAKER)
play_file(speaker, 'piano.wav')
time.sleep(100)首先,扬声器被启用,这样我们就可以在没有耳机的情况下听到音频播放。然后使用AudioOut类访问音频输出设备。然后使用扬声器音频对象和将播放的音频文件的路径调用play_file函数。此函数以二进制模式打开文件。
然后,这个文件对象被用来创建一个WaveFile对象,它将把数据作为音频流返回给我们。然后将该音频数据提供给AudioOut对象上的play方法以开始播放。此方法立即返回,不等待播放完成。这就是为什么之后会调用sleep方法,以便在主脚本结束执行之前,让电路板有机会播放音频流。
如果您从文件中排除这一行代码并重新加载代码,那么脚本将在电路板有机会播放该文件之前退出,并且您将听不到任何音频被播放。
使用此功能,只需传递音频输出对象和文件路径,即可播放任意数量的音频文件。您还可以使用此配方作为起点,进一步试验此电路板附带的音频播放库。例如,有一种方法可以轮询并检查最后提供的流是否仍在播放,或者是否已完成播放。
以下是一些参考资料:
- 有关
AudioOut对象的文档可在中找到 https://circuitpython.readthedocs.io/en/3.x/shared-bindings/audioio/AudioOut.html 。 - 有关
WaveFile对象的文档可在中找到 https://circuitpython.readthedocs.io/en/3.x/shared-bindings/audioio/WaveFile.html 。
在本配方中,您将学习如何使用 Neopix 对象控制电路板上的像素。在前面的章节中,我们介绍了该对象中的许多方法,但这是我们第一次直接创建 Neopix 对象。拥有直接使用 NeoPixel 对象的技能,而不是通过另一个对象访问它,这是非常有用的。如果您决定在项目中添加额外的环形或条形像素,您将需要这些技能。在这些情况下,您需要直接访问此对象以控制像素。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 在 REPL 中运行以下代码行:
>>> from neopixel import NeoPixel
>>> import board
>>>
>>> PIXEL_COUNT = 10
>>> pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
>>> pixels.brightness = 0.05
>>> pixels[0] = 0xFF0000- 运行前一段代码后,第一个像素应变为红色。运行下一段代码,使第二个像素变为绿色:
>>> RGB = dict(
... black=0x000000,
... white=0xFFFFFF,
... green=0x00FF00,
... red=0xFF0000,
... yellow=0xFFFF00,
... )
>>> pixels[1] = RGB['green']- 运行下一段代码以关闭第一个像素:
>>> pixels[0] = RGB['black']- 应将以下代码放入
main.py文件中,执行时将前两个像素涂成红色和绿色:
from neopixel import NeoPixel
import board
PIXEL_COUNT = 10
RGB = dict(
black=0x000000,
white=0xFFFFFF,
green=0x00FF00,
red=0xFF0000,
yellow=0xFFFF00,
)
pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
pixels.brightness = 0.05
pixels[0] = RGB['red']
pixels[1] = RGB['green']
while True:
passNeoPixel类用于访问电路板上的像素阵列。当我们创建这个对象时,我们必须在电路板上指定要连接的管脚以及连接到该管脚的像素数。
对于 Circuit Playway Express,电路板上有 10 个像素。我们将该值保持在全局常量中,以提高代码可读性。然后我们将像素的亮度设置为 5%。
项目中所需的五种不同颜色的名称和十六进制代码在全局字典中定义。白色、绿色、红色和黄色分别与连接导线的四种颜色有关。黑色用于关闭像素。然后,我们将第一个和第二个像素设置为红色和绿色。最后,我们运行一个无限循环,这样我们就可以看到这些颜色并阻止脚本退出。
这段代码包含了与电路板附带的 10 个像素中的任何一个进行交互所需的一切。您可以使用这些基本代码,开始在提供的对象上使用不同的方法进行实验。使用这些不同的方法,您可以在一次调用中更改所有像素的颜色。您还可以关闭默认的自动写入功能,然后直接控制何时应用对颜色所做的更改。像素的这种低级控制都可以通过这个库获得。
以下是一些参考资料:
- 有关测试像素特征的方法的文档可在中找到 https://circuitpython.readthedocs.io/projects/neopixel/en/latest/examples.html 。
- 有关 Neopix 驱动程序的概述,请参见https://circuitpython.readthedocs.io/projects/neopixel/en/latest/ 。
在这个配方中,我们将使用触摸处理器的第一个 t 版本。第一个版本将在每次检测到触摸事件时播放特定的音频文件。然后,我们可以在以后的食谱中使用该处理程序,以便将每个触摸板映射到特定的音频文件。我们还将在未来的菜谱中扩展此处理程序的功能,为触摸事件添加灯光和声音。事件处理程序是许多软件系统的常见部分。这个食谱将帮助您了解如何在 MicroPython 项目中使用这种常用方法。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 执行 REPL 中的下一个代码块:
>>> from touchio import TouchIn
>>> from digitalio import DigitalInOut
>>> from audioio import WaveFile, AudioOut
>>> import board
>>> def enable_speakers():
... speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
... speaker_control.switch_to_output(value=True)
...
...
...
>>> def play_file(speaker, path):
... file = open(path, "rb")
... audio = WaveFile(file)
... speaker.play(audio)
...
...
...
>>> enable_speakers()
>>> speaker = AudioOut(board.SPEAKER)- 此时,我们已经启用了扬声器,并设置了一个在扬声器上播放音频文件的对象。在下一段代码中,我们将定义一个
Handler类,然后创建一个使用speaker对象的实例:
>>> class Handler:
... def __init__(self, speaker):
... self.speaker = speaker
...
... def handle(self, name, state):
... if state:
... play_file(self.speaker, 'piano.wav')
...
>>> handler = Handler(speaker)- 当您运行下一段代码时,您应该会听到扬声器上的钢琴声:
>>> handler.handle('A1', True)- 应将以下代码放入
main.py文件中,当执行时,每次触摸触摸板 A1 时,它将播放钢琴声音:
from touchio import TouchIn
from digitalio import DigitalInOut
from audioio import WaveFile, AudioOut
import board
def enable_speakers():
speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
speaker_control.switch_to_output(value=True)
def play_file(speaker, path):
file = open(path, "rb")
audio = WaveFile(file)
speaker.play(audio)
class Handler:
def __init__(self, speaker):
self.speaker = speaker
def handle(self, name, state):
if state:
play_file(self.speaker, 'piano.wav')
class TouchEvent:
THRESHOLD_ADJUSTMENT = 400
def __init__(self, name, onchange):
self.name = name
self.last = False
self.onchange = onchange
pin = getattr(board, name)
self.touch = TouchIn(pin)
self.touch.threshold += self.THRESHOLD_ADJUSTMENT
def process(self):
current = self.touch.value
if current != self.last:
self.onchange(self.name, current)
self.last = current
enable_speakers()
speaker = AudioOut(board.SPEAKER)
handler = Handler(speaker)
event = TouchEvent('A1', handler.handle)
while True:
event.process()定义的Handler类将用于对触摸事件做出反应。它需要构造函数中有一个参数,即处理音频播放的speaker对象。此对象将保存到对象实例上的属性。然后,该类定义了一个方法,该方法将在每次发生触摸事件时调用。该方法要求第一个参数是触摸板的名称,第二个参数是指示触摸板状态的布尔值。
调用该方法时,它检查是否正在触摸键盘;如果是,则调用play_file函数来播放钢琴声音。配方中的其余代码支持不断检查新触摸事件的过程,并调用已定义的处理程序。
本例中的配方仅在按下单个触摸板时播放一种声音。然而,它也为我们的扩张创造了核心结构。你可以尝试一下这个方法,尝试两个触摸板,每个触摸板播放不同的声音。可以通过将多个已定义的事件对象连接到不同的处理程序来实现这一点。在后面的食谱中,您将看到单个事件类定义和单个处理程序类定义可用于连接到四个不同的焊盘并播放四种不同的声音。
以下是一些参考资料:
AudioOut类的源代码可以在找到 https://github.com/adafruit/circuitpython/blob/3.x/shared-bindings/audioio/AudioOut.c 。WaveFile类的源代码可以在找到 https://github.com/adafruit/circuitpython/blob/3.x/shared-bindings/audioio/WaveFile.c 。
在这个配方中,我们将创建一个触摸处理程序,通过播放声音和点亮像素来对触摸事件做出反应。当触摸传感器被触发时,处理器将播放声音并点亮特定像素。当触摸传感器检测到您已松开手指时,点亮的特定像素将关闭。
通过这种方式,您可以听到和看到电路板对每个配置的触摸板做出独特的反应。此配方显示了一种基于不同触发输入创建不同类型输出的有用方法。当您添加独特的音频和视频输出组合,对不同类型的人工输入做出反应时,许多项目都会变得栩栩如生。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 使用 REPL 运行以下代码行。这将设置扬声器并创建与像素交互的对象:
>>> from touchio import TouchIn
>>> from digitalio import DigitalInOut
>>> from audioio import WaveFile, AudioOut
>>> from neopixel import NeoPixel
>>> import board
>>>
>>> PIXEL_COUNT = 10
>>>
>>> def enable_speakers():
... speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
... speaker_control.switch_to_output(value=True)
...
...
...
>>> def play_file(speaker, path):
... file = open(path, "rb")
... audio = WaveFile(file)
... speaker.play(audio)
...
>>>
>>> enable_speakers()
>>> speaker = AudioOut(board.SPEAKER)
>>> pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
>>> pixels.brightness = 0.05- 在下一段代码中,我们将定义一个
Handler类,然后创建一个实例,我们将把对象传递给该实例,以处理扬声器和像素:
>>> class Handler:
... def __init__(self, speaker, pixels):
... self.speaker = speaker
... self.pixels = pixels
...
... def handle(self, name, state):
... if state:
... play_file(self.speaker, 'piano.wav')
... self.pixels[0] = 0xFF0000
... else:
... self.pixels[0] = 0x000000
...
...
>>> handler = Handler(speaker, pixels)- 当您运行下一段代码时,您应该听到扬声器上的钢琴声,并且第一个像素应变为红色:
>>> handler.handle('A1', True)- 运行下一段代码,您会看到第一个像素灯关闭:
>>> handler.handle('A1', False)- 以下代码应放入
main.py文件中:
from touchio import TouchIn
from digitalio import DigitalInOut
from audioio import WaveFile, AudioOut
from neopixel import NeoPixel
import board
PIXEL_COUNT = 10
def enable_speakers():
speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
speaker_control.switch_to_output(value=True)
def play_file(speaker, path):
file = open(path, "rb")
audio = WaveFile(file)
speaker.play(audio)
class Handler:
def __init__(self, speaker, pixels):
self.speaker = speaker
self.pixels = pixels
def handle(self, name, state):
if state:
play_file(self.speaker, 'piano.wav')
self.pixels[0] = 0xFF0000
else:
self.pixels[0] = 0x000000
class TouchEvent:
THRESHOLD_ADJUSTMENT = 400
def __init__(self, name, onchange):
self.name = name
self.last = False
self.onchange = onchange
pin = getattr(board, name)
self.touch = TouchIn(pin)
self.touch.threshold += self.THRESHOLD_ADJUSTMENT
def process(self):
current = self.touch.value
if current != self.last:
self.onchange(self.name, current)
self.last = current
enable_speakers()
speaker = AudioOut(board.SPEAKER)
pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
pixels.brightness = 0.05
handler = Handler(speaker, pixels)
event = TouchEvent('A1', handler.handle)
while True:
event.process()执行脚本时,每次触摸触摸板 A1 时,它将播放钢琴声音并点亮一个像素。
定义的Handler类将在每次检测到触摸事件时播放声音并点亮像素。该类的构造函数获取扬声器和像素对象,并将它们保存到实例中供以后使用。每次调用handle方法时,它都会检查触摸板当前是否按下。
如果按下该按钮,将点亮一个像素并播放声音。如果释放焊盘,则同一像素将关闭。脚本的其余部分负责初始化扬声器和像素,以便处理程序可以使用它们,并创建一个无限循环,该循环将在每次检测到事件时继续调用处理程序。
此配方中的脚本每次点亮一个特定像素。您可以将其扩展为在每次按下触摸板时使用随机颜色。按下触摸板的时间越长,有办法点亮更多像素。另一个有趣的实验是让棋盘在每次事件发生时播放随机声音。现在,我们已经添加了声音和灯光,有更多的选择来将创造力应用到这个项目中,并创建一个更独特的项目。
以下是一些参考资料:
- 可在找到将莱姆斯连接到 Circuit Playerd Express 的项目 https://learn.adafruit.com/circuit-playground-express-piano-in-the-key-of-lime/ 。
TouchIn类的源代码可以在找到 https://github.com/adafruit/circuitpython/blob/3.x/shared-bindings/touchio/TouchIn.c 。
本章中的最后一个配方采用了本章中之前的所有配方,并将它们结合起来,完成了香蕉动力音乐机。除了前面的方法,我们还需要创建一个事件循环,将所有这些逻辑组合成一个结构,可以处理所有四个触摸板及其相关音频文件和像素。在学习了这个方法之后,您将能够创建通用的事件循环和处理程序,这些事件循环和处理程序可以扩展以满足您可能创建的嵌入式项目的不同需求。
要运行此配方中提供的代码,您需要访问电路操场 Express 上的 REPL。
让我们回顾一下此配方所需的步骤:
- 在 REPL 中运行以下代码行:
>>> from touchio import TouchIn
>>> from digitalio import DigitalInOut
>>> from audioio import WaveFile, AudioOut
>>> from neopixel import NeoPixel
>>> import board
>>>
>>> PIXEL_COUNT = 10
>>> TOUCH_PADS = ['A1', 'A2', 'A5', 'A6']
>>> SOUND = dict(
... A1='hit.wav',
... A2='piano.wav',
... A5='tin.wav',
... A6='wood.wav',
... )
>>> RGB = dict(
... black=0x000000,
... white=0xFFFFFF,
... green=0x00FF00,
... red=0xFF0000,
... yellow=0xFFFF00,
... )
>>> PIXELS = dict(
... A1=(6, RGB['white']),
... A2=(8, RGB['red']),
... A5=(1, RGB['yellow']),
... A6=(3, RGB['green']),
... )- 我们现在已经导入了我们需要的所有库,并在脚本中创建了我们需要的主要数据结构。运行下一段代码,扬声器应播放钢琴声:
>>> def play_file(speaker, path):
... file = open(path, "rb")
... audio = WaveFile(file)
... speaker.play(audio)
...
...
>>> def enable_speakers():
... speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
... speaker_control.switch_to_output(value=True)
...
...
>>> enable_speakers()
>>> speaker = AudioOut(board.SPEAKER)
>>> play_file(speaker, SOUND['A2'])- 运行下一段代码以创建事件处理程序的实例:
>>> class Handler:
... def __init__(self, speaker, pixels):
... self.speaker = speaker
... self.pixels = pixels
...
... def handle(self, name, state):
... pos, color = PIXELS[name]
... if state:
... play_file(self.speaker, SOUND[name])
... self.pixels[pos] = color
... else:
... self.pixels[pos] = RGB['black']
...
...
>>> class TouchEvent:
... THRESHOLD_ADJUSTMENT = 400
...
... def __init__(self, name, onchange):
... self.name = name
... self.last = False
... self.onchange = onchange
... pin = getattr(board, name)
... self.touch = TouchIn(pin)
... self.touch.threshold += self.THRESHOLD_ADJUSTMENT
...
... def process(self):
... current = self.touch.value
... if current != self.last:
... self.onchange(self.name, current)
... self.last = current
...
...
>>> pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
>>> pixels.brightness = 0.05
>>> handler = Handler(speaker, pixels)- 运行下一段代码,模拟触摸板 2 上的触摸事件。您应该听到钢琴声,并看到其中一个像素变为红色:
>>> handler.handle('A2', True)- 以下代码应放入
main.py文件中,执行时,每次按下四个配置的触摸板中的一个时,它将播放不同的声音并点亮不同的像素:
from touchio import TouchIn
from digitalio import DigitalInOut
from audioio import WaveFile, AudioOut
from neopixel import NeoPixel
import board
PIXEL_COUNT = 10
TOUCH_PADS = ['A1', 'A2', 'A5', 'A6']
SOUND = dict(
A1='hit.wav',
A2='piano.wav',
A5='tin.wav',
A6='wood.wav',
)
RGB = dict(
black=0x000000,
white=0xFFFFFF,
green=0x00FF00,
red=0xFF0000,
yellow=0xFFFF00,
)
PIXELS = dict(
A1=(6, RGB['white']),
A2=(8, RGB['red']),
A5=(1, RGB['yellow']),
A6=(3, RGB['green']),
)
def play_file(speaker, path):
file = open(path, "rb")
audio = WaveFile(file)
speaker.play(audio)
def enable_speakers():
speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
speaker_control.switch_to_output(value=True)
class Handler:
def __init__(self, speaker, pixels):
self.speaker = speaker
self.pixels = pixels
def handle(self, name, state):
pos, color = PIXELS[name]
if state:
play_file(self.speaker, SOUND[name])
self.pixels[pos] = color
else:
self.pixels[pos] = RGB['black']
class TouchEvent:
THRESHOLD_ADJUSTMENT = 400
def __init__(self, name, onchange):
self.name = name
self.last = False
self.onchange = onchange
pin = getattr(board, name)
self.touch = TouchIn(pin)
self.touch.threshold += self.THRESHOLD_ADJUSTMENT
def process(self):
current = self.touch.value
if current != self.last:
self.onchange(self.name, current)
self.last = current
def main():
enable_speakers()
speaker = AudioOut(board.SPEAKER)
pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
pixels.brightness = 0.05
handler = Handler(speaker, pixels)
events = [TouchEvent(i, handler.handle) for i in TOUCH_PADS]
while True:
for event in events:
event.process()
main()main函数包含我们的事件循环。此函数首先初始化扬声器和像素。然后,它创建一个处理程序实例。这个单一处理程序实例足够通用,它将用作所有四个触摸板的处理程序。
然后,创建一个事件列表,其中每个事件都连接到四个焊盘中的一个。启动一个无限循环,该循环遍历每个事件对象,并在检测到触摸板状态发生变化时调用其process方法来调用事件处理程序。
脚本顶部的常量用于指定要使用的触摸板的名称、要为每个触摸板播放的声音文件以及按下触摸板时要设置的像素位置和颜色。
该脚本大量使用了大量数据结构,因此不需要在函数和类定义中硬编码值。字典被用作一种自然结构,将每个焊盘名称映射到应该播放的音频文件名。数据结构列表用于定义将要连接的触摸板的名称。最后,使用元组字典将触摸板映射到相关的像素位置和颜色。Python 有一组丰富的数据结构,有效利用这些数据结构可以使代码更具可读性和可维护性。
这个项目将四根香蕉连接到电路板上,每根香蕉在触摸时发出不同的声音。因为代码的构造是为了立即响应每次触摸,所以您甚至可以让两个人同时玩。下一张照片显示了两个人,每人拿着一对香蕉,创作音乐,并控制着黑板上的像素:
以下是一些参考资料:
- 有关使用 CircuitPython 提供音频输出的文档,请参见https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-audio-out 。
NeoPixel类的源代码可以在找到 https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel 。




