MicroPython-秘籍(全)

MicroPython 秘籍(全)

原文:zh.annas-archive.org/md5/EE140280D367F2C84B38C2F3034D057C

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

MicroPython 是 Python 3 编程语言的精简实现,能够在各种微控制器上运行。它为这些微控制器提供了 Python 编程语言的大部分功能,如函数、类、列表、字典、字符串、读写文件、列表推导和异常处理。

微控制器是通常包括 CPU、内存和输入/输出外围设备的微型计算机。尽管它们的资源相对于 PC 来说更有限,但它们可以制作成更小的尺寸,功耗更低,成本更低。这些优势使得它们可以在以前不可能的广泛应用中使用。

本书将涵盖 MicroPython 语言的许多不同特性,以及许多不同的微控制器板。最初的章节将提供简单易懂的配方,以使这些板与人们和他们的环境互动。主题涵盖从传感器读取温度、光线和运动数据到与按钮、滑动开关和触摸板互动。还将涵盖在这些板上产生音频播放和 LED 动画的主题。一旦打下了这个基础,我们将构建更多涉及的项目,如互动双人游戏、电子乐器和物联网天气机。您将能够将从这些配方中学到的技能直接应用于自己的嵌入式项目。

本书适合对象

这本书旨在帮助人们将 Python 语言的强大和易用性应用于微控制器的多功能性。预期读者具有 Python 的基础知识才能理解本书。

本书内容

第一章,Getting Started with MicroPython,介绍了 Adafruit Circuit Playground Express 微控制器,并教授了在此硬件上使用 MicroPython 的核心技能。

第二章,Controlling LEDs,涵盖了控制 NeoPixel LED、灯光颜色以及如何通过控制板上灯光变化的时间来创建动画灯光秀的方法。

第三章,Creating Sound and Music,讨论了如何在 Adafruit Circuit Playground Express 上制作声音和音乐的方法。将涵盖诸如使板在特定声音频率下发出蜂鸣声以及使用 WAV 文件格式和板载扬声器播放音乐文件等主题。

第四章,Interacting with Buttons,展示了与 Adafruit Circuit Playground Express 上的按钮和触摸板互动的方法。将讨论检测按钮何时被按下或未被按下的基础知识,以及高级主题,如微调电容触摸板的触摸阈值。

第五章,Reading Sensor Data,介绍了从各种不同类型的传感器(如温度、光线和运动传感器)读取传感器数据的方法。

第六章,Button Bash Game,指导我们创建一个名为Button Bash的双人游戏,您可以直接在 Circuit Playground Express 上使用按钮、NeoPixels 和内置扬声器进行游戏。

第七章,Fruity Tunes,解释了如何使用 Adafruit Circuit Playground Express 和一些香蕉创建一个乐器。触摸板将用于与香蕉互动,并在每次触摸不同的香蕉时播放不同的音乐声音。

第八章,“让我们动起来”,介绍了 Adafruit CRICKIT 硬件附加组件,它将帮助我们通过 Python 脚本控制电机和舵机;特别是它们的速度、旋转方向和角度将通过这些脚本进行控制。

第九章,“在 micro:bit 上编码”,涵盖了与 micro:bit 平台交互的方法。将讨论如何控制其 LED 网格显示并与板载按钮交互。

第十章,“控制 ESP8266”,介绍了 Adafruit Feather HUZZAH ESP8266 微控制器,并讨论了它与其他微控制器相比的特点和优势。将涵盖连接到 Wi-Fi 网络、使用 WebREPL 和通过 Wi-Fi 传输文件等主题。

第十一章,“与文件系统交互”,讨论了与操作系统(OS)相关的一些主题,如列出文件、删除文件、创建目录和计算磁盘使用量。

第十二章,“网络”,讨论了如何执行许多不同的网络操作,如 DNS 查找、实现 HTTP 客户端和 HTTP 服务器。

第十三章,“与 Adafruit FeatherWing OLED 交互”,介绍了 Adafruit FeatherWing OLED 硬件附加组件,它可以连接到 ESP8266,为互联网连接的微控制器添加显示,以显示文本图形并使用包含的三个硬件按钮与用户交互。

第十四章,“构建 IoT 天气机”,解释了如何创建一个 IoT 设备,该设备将在按下按钮时从 IoT 设备本身检索天气数据并向用户显示。

第十五章,“在 Adafruit HalloWing 上编码”,介绍了 Adafruit HalloWing 微控制器,它内置了一个 128x128 全彩薄膜晶体管(TFT)显示屏,可以在微控制器上显示丰富的图形图像。

为了充分利用本书

读者应具有 Python 编程语言的基本知识。读者最好具有基本的导入包和使用 REPL 的理解,以充分利用本书。

下载示例代码文件

您可以从www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. www.packtpub.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名并按照屏幕上的说明操作。

下载示例代码文件后,请确保使用以下最新版本解压或提取文件夹:

  • Windows 的 WinRAR/7-Zip

  • Mac 的 Zipeg/iZip/UnRarX

  • Linux 的 7-Zip/PeaZip

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/MicroPython-Cookbook。我们还有来自丰富书籍和视频目录的其他代码包可供使用,网址为github.com/PacktPublishing/。去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781838649951_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。例如: "这个食谱需要在计算机上安装 Python 和pip。"

一个代码块设置如下:

from adafruit_circuitplayground.express import cpx
import time

cpx.pixels[0] = (255, 0, 0) # set first NeoPixel to the color red
time.sleep(60)

当我们希望引起您对代码块的特定部分的注意时,相关的行或项目会以粗体显示:

from adafruit_circuitplayground.express import cpx
import time

RAINBOW = [
 0xFF0000, # red 
 0xFFA500, # orange

任何命令行输入或输出都以如下形式书写:

>>> 1+1
2

粗体:表示一个新术语,一个重要的词,或者您在屏幕上看到的词。例如,菜单或对话框中的单词会以这种形式出现在文本中。例如:"单击工具栏上的串行按钮,以打开与设备的 REPL 会话。"

警告或重要说明会出现在这样的形式。提示和技巧会出现在这样的形式。

章节

在本书中,您会经常看到几个标题(准备工作如何做…它是如何工作的…还有更多…,和另请参阅)。

为了清晰地说明如何完成一个食谱,使用以下章节:

准备工作

这一部分告诉您在食谱中可以期待什么,并描述如何设置食谱所需的任何软件或初步设置。

如何做…

这一部分包含了遵循食谱所需的步骤。

它是如何工作的…

这一部分通常包括对前一部分发生的事情的详细解释。

还有更多…

这一部分包括了有关食谱的额外信息,以使您对食谱更加了解。

另请参阅

这一部分提供了有用的链接,指向食谱的其他有用信息。

第一章:开始使用 MicroPython

现在是使用 MicroPython 等技术的激动人心的时刻。它们使得微小和廉价的硬件设备更容易访问,因为你可以使用高级语言如 Python 来编程。与其他微控制器语言相比,比如从 Web 服务中检索数据可以轻松地用几行代码完成,因为它们与 Python 相比操作的层级更低,需要更多的步骤。这非常有力量,因为你将能够更快地获得结果,并在更短的时间内迭代不同的设计和原型。

在本章中,我们将为您提供运行 MicroPython 所需的软件和硬件的基本技能。您将学习如何更新设备上的固件和库。还将介绍一些加载第一个程序并使用高级功能(如自动重新加载代码)的方法。最后,将介绍一些使用 REPL 的方法,这是一种快速与 MicroPython 设备上的可用组件进行交互和实验的强大方式。

在本章中,我们将涵盖以下内容:

  • 刷新微控制器固件

  • 执行你的第一个程序

  • 使用屏幕访问 REPL

  • 使用 Mu 访问 REPL

  • 在 REPL 中执行命令

  • 使用自动重新加载功能

  • 更新 CircuitPython 库

什么是 MicroPython?

MicroPython 是澳大利亚程序员和物理学家 Damien George 的创造,他在 2013 年发起了一个 Kickstarter 活动,以支持该语言的开发和最初的微控制器硬件。在项目成功之后,越来越多的设备(具有不同制造商的各种芯片组)得到了 MicroPython 的支持,为使用 MicroPython 制作项目时提供了多种选择。

MicroPython 是 Python 3 编程语言的精简实现,能够在硬件资源非常有限的设备上运行,比如微控制器。MicroPython 已经实现了 Python 编程语言的大部分功能,比如函数、类、列表、字典、字符串、读写文件、列表推导和异常处理。

REPL 也已经实现,并且可以通过串行连接进行交互。提供了一系列核心 Python 库,可以实现各种应用。JSON 和socket库允许 Web 客户端和服务器的实现,使得基于 Python 的物联网(IoT)项目在微控制器上成为现实。

通过将最受欢迎和易于使用的编程语言之一引入到嵌入式计算的激动人心的世界中,MicroPython 为创客和企业家打开了新的大门,让他们的创意得以实现。本书将探索不同的方法来利用 MicroPython 语言与各种独特的微控制器设备,每种设备都带来了不同的功能。

在微控制器上运行 MicroPython 的独特和迷人之处之一是它不在操作系统(OS)上运行,而是直接在裸金属上运行。这些独特的特性以多种方式表现出来,比如在硬件上电的瞬间就能运行你的 Python 代码,因为不需要启动操作系统。

另一个方面是 Python 代码直接访问硬件并与之交互,创造了一些在典型 Python 应用程序上不可能实现的硬件可能性。

现在我们知道 MicroPython 可以在微控制器上运行,让我们看看微控制器到底是什么。

什么是微控制器?

微控制器是单芯片上的小型计算机。它们通常包括 CPU、内存和输入/输出外设。它们的计算资源比现代 PC 上可能找到的要有限。

然而,与 PC 相比,它们可以制作成更小的尺寸,可以嵌入各种电子和机械设备中。它们的功耗通常要小得多,因此可以提供数天的电池寿命。它们的单位成本要低得多,这就打开了在广泛地理区域收集传感器数据的数百个这样的设备的可能性,而且仍然是经济可行的。

传统上,在微控制器上创建应用程序是一个困难的过程,因为你必须编写非常低级的代码,这需要时间,而且很难调试。MicroPython 将 Python 的易用性带到了微控制器上。它能够提供与硬件的更轻松交互,同时在资源受限的环境中工作,并提供广泛的功能和高度的响应性。

什么是 CircuitPython?

CircuitPython 是 Adafruit Industries 创建的 MicroPython 分支,使得与微控制器的工作更简单。它通过 Python 库对许多传感器和 Adafruit 设备的组件提供了出色的支持。它还允许代码轻松加载和运行,而无需安装任何额外的软件应用程序,通过将微控制器的存储公开为磁盘驱动器。

一般来说,MicroPython 和 CircuitPython 之间的差异很小,在许多情况下,代码在两种实现上都会运行相同。

什么是 Circuit Playground Express?

Adafruit Circuit Playground Express 是一款价格便宜但功能丰富的微控制器,具有丰富的输入和输出设备,这些设备已经内置在设备中。以下是该设备中的一些主要硬件特性:

  • 10 个迷你 NeoPixels,每个都能显示全色彩范围

  • 作为运动传感器(带有敲击检测和自由落体检测的三轴加速度计)

  • 一个温度传感器

  • 一个光传感器

  • 一个声音传感器

  • 一个迷你扬声器

  • 两个带有标签 A 和 B 的按钮

  • 一个滑动开关

  • 一个红外线接收器和发射器

  • 八个鳄鱼夹友好的输入/输出引脚

  • 支持 I2C 和 PWM 输出

  • 七个电容触摸输入

  • 一个红色 LED

  • 一个复位按钮

  • 一个运行在 3.3V 和 48MHz 的 ATSAMD21 ARM Cortex M0 处理器

  • 2MB 的闪存存储

  • 一个用于连接 PC 的微型 USB 端口

这些将是八章中唯一需要的设备。后面的章节将介绍一组不同的设备。

请参考learn.adafruit.com/welcome-to-circuitpython?view=all获取更多信息。

在哪里购买

Adafruit Circuit Playground Express 可以直接从 Adafruit(www.adafruit.com/product/3333)购买。它也可以从在线零售商购买,如亚马逊和 Pimoroni。

对于本书的目的,我们建议购买 Circuit Playground Express - 基础套件(www.adafruit.com/product/3517),还包括 USB 电缆和电池包,以便项目可以轻松地制作成便携式。

参考

以下是一些参考:

刷新微控制器固件

在这个教程中,我们将展示如何使用最新的 CircuitPython 固件在 Circuit Playground Express 上刷新固件。在开始使用该设备之前,有两个原因需要这样做。首先,该设备还支持 Microsoft MakeCode 编程环境,并且使用 CircuitPython 固件刷新设备可以准备好使用 Python 语言。

其次,CircuitPython 语言正在不断发展,每隔几个月发布一次版本,因此定期更新固件以加载最新版本的语言到板上是个好主意。

准备工作

本章的介绍为我们提供了购买 Circuit Playground Express 的指导,这对本章中的所有教程都是必需的。还需要一个 USB micro B 电缆和运行 macOS、Windows 或 Linux 的计算机。

如何操作...

让我们看看以下步骤:

  1. 下载最新的 CircuitPython Circuit Playground Express UF2 文件(github.com/adafruit/circuitpython/releases/latest)。CircuitPython 3.1.2 版本的 UF2 文件名为adafruit-circuitpython-circuitplayground_express-3.1.2.uf2。对于每个 CircuitPython 版本,都有许多不同的支持的微控制器的uf2文件。确保下载适用于 Circuit Playground Express 设备的文件。

在本教程中,我们将使用最新的稳定版本的 CircuitPython,目前是 3.1.2。

  1. 将 USB 电缆连接到 Circuit Playground Express 和计算机。

  2. 双击位于板中心的复位按钮。如果一切顺利,您将看到所有 LED 变为绿色;否则,很可能是使用的 USB 电缆出现了问题。在某些情况下,如果双击不起作用,请尝试单击复位按钮。

  3. 您将看到一个名为 CPLAYBOOT 的新磁盘出现:

  1. 将 UF2 文件复制到此驱动器中。

  2. 一旦 UF2 文件完全写入设备,固件将被更新,一个新的驱动器将出现,名为 CIRCUITPY:

现在,我们的 Circuit Playground Express 可以使用了。

它是如何工作的...

传统上,需要安装和使用特殊软件来处理微控制器的刷新过程。微软开发了 UF2 方法,大大简化了该过程,不需要任何特殊软件或命令行执行来刷新微控制器。

一旦板子进入引导程序模式,它将期望保存一个 UF2 文件。当 UF2 文件复制到驱动器时,微控制器将检测到文件复制已完成,然后自动进行微控制器刷新并重新启动设备,此时设备将重新连接并准备好使用。

UF2 文件格式可以在github.com/Microsoft/uf2找到。

还有更多...

与以前的方法相比,UF2 方法使刷新微控制器固件的过程更加简单和快速。并非所有 MicroPython 板都支持 UF2 方法,因此需要更复杂的方法来安装特殊软件来进行固件刷新。不同的板和制造商之间所需的确切过程和软件各不相同。

当您使用这个闪存软件时,通常需要知道设备在计算机上显示为的串行设备的确切名称。这些设备的命名在 Windows、Linux 和 macOS 之间有所不同。这种类型的软件通常需要在终端中运行,因此您需要一些命令行知识来与之交互。出于所有这些原因,使用支持的设备(如 Circuit Playground Express)与 UF2 是开始使用 MicroPython 进行实验的首选方式。

另请参阅

关于本文描述的过程,Adafruit 和 Microsoft 网站上有许多资源。以下是一些参考资料:

执行您的第一个程序

在本文中,我们将向您展示如何在 Circuit Playground Express 上加载您的第一个程序,以及如何修改程序并重新加载它。然后,程序将点亮板上可用的十个 NeoPixel 中的一个。

准备工作

一旦 Circuit Playground Express 刷入了 CircuitPython 固件,您可以将 Python 脚本加载到板子上并运行它们。

如何做到...

让我们看看如何做到这一点:

  1. 确保板子通过 USB 电缆连接到计算机,并且CIRCUITPY驱动器出现。

  2. 在驱动器上保存一个文本文件,内容如下,并将其命名为main.py

from adafruit_circuitplayground.express import cpx
import time

cpx.pixels[0] = (255, 0, 0)  # set first NeoPixel to the color red
time.sleep(60)
  1. 保存文件后,弹出驱动器,然后从计算机上断开并重新连接 USB 电缆。

  2. 驱动器上的第一个 NeoPixel 应该点亮为红色。

  3. 在您选择的文本编辑器中打开main.py文件,并将cpx.pixels[0]行更改为cpx.pixels[1]。保存文件。这个更改将使第二个 NeoPixel 点亮,而不是第一个。

  4. 弹出驱动器,然后断开,重新连接 USB 电缆以使更改生效。

它是如何工作的...

当设备打开时,它会寻找某些文件,例如code.pymain.py,如果找到,将作为启动过程的一部分执行。通过这种方式,您可以指定在设备上电时要运行的代码。脚本首先导入adafruit_circuitplayground.express库,以便它可以控制 NeoPixels。通过给它一组适当的 RGB 值,将第一个 NeoPixel 设置为红色。

最后,脚本将休眠 60 秒,以便 LED 在脚本结束执行前保持点亮一分钟。

还有更多...

现在,板子已经加载了一个 Python 脚本,可以从计算机断开连接,并连接电池组。一旦电池组由脚本供电,它应该运行并点亮所选的 NeoPixel。

这是创建便携且廉价的项目的简单方法,可以直接从板上运行代码,无需连接 PC,并且可以通过三节 AAA 电池简单供电。

另请参阅

CircuitPython 在启动时寻找的一些文件的描述在learn.adafruit.com/welcome-to-circuitpython?view=all#naming-your-program-file-7-30中有描述。

使用屏幕访问 REPL

Linux 和 macOS 有强大的终端仿真器,如screen,可以用于通过串行(USB)连接直接连接到设备的读取-求值-打印循环REPL)。本文将展示如何连接到 REPL 并开始交互式地运行 Python 代码。

准备工作

此配方可以在 macOS 或 Linux 计算机上使用,并可能需要screen命令可用。在 macOS 上,Screen 应用程序是内置的,因此无需安装。在 Ubuntu 上,可以使用apt install screen命令安装 Linux Screen。

如何做...

让我们看看如何连接 REPL 并运行代码:

  1. 打开计算机的终端应用程序。

  2. 在 Linux 上运行ls /dev/ttyACM*或在 macOS 上运行ls /dev/tty.*来列出插入设备之前的设备名称。

  3. 使用 USB 电缆将板连接到计算机。

  4. 使用相同的命令再次列出设备名称,以发现板的设备名称。

  5. 如果设备名称为/dev/ttyACM0,则screen命令将是screen /dev/ttyACM0 115200

  6. 在终端中输入命令并启动 Screen 应用程序。

  7. 如果 Screen 能够成功连接,Python REPL 应该会出现在终端上,并显示类似以下文本的输出:

Adafruit CircuitPython 3.1.2 on 2019-01-07; Adafruit CircuitPlayground Express with samd21g18 **>>>** 
  1. 如果提示未出现,可以尝试按下Ctrl + C,然后按Enter,这将停止当前正在运行的 Python 脚本,并使用以下消息运行 REPL:
Press any key to enter the REPL. Use CTRL-D to reload.
  1. 一旦 REPL 提示出现,我们将必须通过评估1+1表达式来测试提示是否正常工作。它应该产生以下输出:
>>> 1+1
2

它是如何工作的...

Circuit Playground Express 通过 USB 连接公开了串行设备,可以通过多种不同的终端仿真程序访问。除了screen之外,还有其他程序,如picocomminicom,也可以使用。

在命令中设置的最后一个参数为 115,200,设置了连接的波特率,应该以该速度设置。一旦成功建立连接,就会开始一个交互式会话,允许直接在设备上评估表达式,并且输出直接显示在终端上。

还有更多...

书中的许多配方将介绍使用 REPL 的脚本的不同部分。这将使您有机会在运行每个代码片段时获得即时反馈。一旦您在 REPL 中输入了不同的片段,您还可以使用 REPL 功能来辅助您对代码进行实验。您可以使用箭头键来浏览已在 REPL 中输入的命令历史记录。例如,如果您刚刚在 REPL 中执行了一行代码,打开了板上的特定像素,您可以按键,通过编辑该行并再次按Enter来更改点亮的像素。

另请参阅

以下是一些参考资料:

使用 Mu 访问 REPL

Mu 是一个易于使用的图形代码编辑器,用 Python 编写,可在 Windows、macOS、Linux 和树莓派上运行。在这个配方中,我们将学习如何安装 Mu 并使用它来访问 Circuit Playground Express 上的 REPL。

准备工作

此配方要求计算机上安装 Python 和pip。Mu 编辑器将使用pip命令安装,因此可以选择在virtualenv中运行此配方。

如何做...

让我们看看如何做到这一点:

  1. 执行以下pip3 install mu-editor命令以安装 Mu 编辑器。

  2. 运行mu-editor命令启动编辑器。

  3. 第一次运行编辑器时,它将询问应以哪种模式运行。在下面的屏幕截图中,选择 Adafruit CircuitPython 模式:

  1. 单击工具栏上的串行按钮以与设备打开 REPL 会话。

  2. 在 Linux 系统上,如果出现“无法连接到设备”错误,则退出编辑器,并使用sudo /full/path/to/mu-editor命令重新启动编辑器,其中给出编辑器的绝对路径。

  3. 一旦成功连接到设备,您可以通过评估1+1表达式来测试 REPL,这应该会产生如下屏幕截图所示的输出:

它是如何工作的...

当您在 Mu 编辑器中点击串行按钮时,它将尝试打开与板的串行连接。如果成功,它会捕获您的输入,将其发送到设备,并显示输出,就像典型的终端仿真器一样。

这个应用程序的美妙之处在于它适用于所有主要的桌面操作系统,并且可以自动找到正确的设备地址,无需手动指定,这是 Typical Terminal emulators 所必需的。它还具有非常简单和易于接近的布局,使得首次用户连接到微控制器变得容易使用。

还有更多...

Mu 编辑器是一个很棒的图形应用程序,当你第一次开始使用 MicroPython 时,它是一个很好的开始。它简单直观的设计使得你可以快速提高生产力,并且很有趣地探索其不同的功能。除了 REPL 功能之外,它还有主要部分的屏幕,可以用来编辑和保存 Python 脚本。它具有代码编辑功能,如代码完成,并将显示有关函数接受参数和函数功能的详细弹出窗口。

另请参阅

以下是一些参考资料:

在 REPL 中执行命令

以下配方展示了 REPL 的不同用法。

准备工作

可以从前面的两个配方中使用任一种方法来获取 REPL。

如何做...

  1. 通过您喜欢的应用程序打开 REPL。

  2. 与 CPython 中的 REPL 提供的许多相同功能在 MicroPython 实现中也可以使用。最后一个返回的值可以通过_访问:

>>> 2 + 2
4
>>> _ + 2
6
  1. 还支持连续行,这样可以通过 REPL 定义函数或for循环,如下面的输出所示:
>>> def add(a, b):
...     return a + b
... 
... 
... 
>>> add(2, 2)
4
>>> 
  1. 即使在受限的微控制器硬件上,也支持任意精度整数。以下代码显示了超出 64 位整数值限制的整数的算术运算:
>>> 2**100 + 2**101
3802951800684688204490109616128

它是如何工作的...

REPL 实现具有我们在 CPython 实现中所熟悉和喜爱的大多数功能。MicroPython 实现必须处理严格的硬件约束,以便在微控制器上运行。但是,即使在这些约束下,两种实现中 REPL 的最终用户体验几乎是相同的,这使得对 Python 开发人员来说很容易过渡。

还有更多...

当您想要尝试某些 MicroPython 库或设备上的某些功能时,REPL 可以成为一个宝贵的工具。它让您可以轻松地导入不同的 Python 模块,并以更直接的方式调用这些库提供的函数,以发现它们实际上如何与硬件交互。这些微控制器上的许多组件可以根据不同的项目需求进行微调。REPL 经常成为进行这种微调的理想场所。

另请参阅

以下是一些参考资料:

使用自动重新加载功能

以下配方显示了如何使用自动重载,以便编辑和运行代码的循环可以变得更快更有趣。

准备工作

在此之前使用的任何方法都可以用于获取 REPL。

如何做到...

让我们看看如何做到这一点:

  1. 打开main.py文件,并保存文件中的print('hi there')语句。

  2. 通过您喜欢的应用程序打开 REPL。打开 REPL 后,按下Ctrl + D。应出现以下输出:

Adafruit CircuitPython 3.1.2 on 2019-01-07; Adafruit CircuitPlayground Express with samd21g18
>>> 
>>> 
soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
main.py output:
hi there

Press any key to enter the REPL. Use CTRL-D to reload.
  1. 编辑main.py文件,并将内容更改为print('hi there again')。应自动显示以下输出:
soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
main.py output:
hi there again

Press any key to enter the REPL. Use CTRL-D to reload.

它是如何工作的...

通过按下Ctrl + D,板子将进入自动重载模式。在这种模式下,您可以在您选择的文本编辑器中打开main.py文件,并且在保存文件的瞬间,板子会检测到发生了变化,并执行软重启。

软重启可以在屏幕输出中看到,然后执行新版本的代码,并立即显示其输出。

还有更多...

在脚本中开始使用一些基本的代码行来使脚本的初始部分运行是非常常见的。一旦您的第一个基本版本运行起来,您将经历许多迭代来微调和增强它,使其表现出您想要的方式。除了这些调整之外,不可避免的错误将出现在您的代码中,因为您在调试它时会出现。在这些密集的编码会话中,自动重载功能将成为您的好朋友,因为它将让您更快地获得结果,并以直观的方式。

另请参阅

以下是一些参考资料:

更新 CircuitPython 库

除了更新固件外,还有一组名为 CircuitPython Library 的 Python 库,其中包含了最新支持的功能。

准备工作

在此之前使用的任何方法都可以用于获取 REPL。

如何做到...

让我们看看如何做到这一点:

  1. 通过您喜欢的应用程序打开 REPL。

  2. 下载最新的 CircuitPython Library Bundle 发布版(github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/latest)。捆绑文件的名称是adafruit-circuitpython-bundle-3.x-mpy-20190212.zip。由于我们的固件使用的是 3.x 版本,因此必须选择也适用于 3.x 版本的捆绑包。始终使用mpy版本,因为这样可以优化使用更少的磁盘空间,并减少内存使用。

在这个配方中,我们使用的是 CircuitPython Library Bundle 的最新自动发布版本,即 3.x 系列的 20190212 版本。

  1. .zip文件提取到计算机上的一个位置。

  2. 如果CIRCUITPY驱动器中不包含lib文件夹,则现在创建一个。

  3. 将提取的lib文件夹的内容复制到设备上的lib文件夹中。

  4. 通过按下Ctrl + D在 REPL 中执行软重启。

  5. 在 REPL 中运行import simpleio

  6. 如果成功执行,则库已成功加载,因为simpleio模块不是固件的一部分,而是从库文件夹导入的。

它是如何工作的...

创建的lib路径是 CircuitPython 在导入 Python 包时查找的标准路径之一。通过将 Python 包添加到此文件夹,可以使其可以被设备上运行的任何脚本导入。

mpy文件是从原始源py文件构建的,并且全部打包在一起,以便更容易安装。

还有更多...

CircuitPython 库正在不断开发,因此重要的是要知道如何在板上更新库,以便获得最新的功能。当您尝试从互联网上找到的项目代码时,您可能偶尔会发现一些示例在您的板上无法运行,因为您正在运行过时的 CircuitPython 库版本。保持板子更新到最新版本,可以帮助防止这种情况发生。

另请参阅

以下是一些参考资料:

第二章:控制 LED

在本章中,我们将介绍控制 Adafruit Circuit Playground Express 附带的一系列 NeoPixel LED 的几种方法。在这些示例中,我们将研究设置像素颜色的各种方法,每种方法都有其自己的权衡。

我们还将演示如何计时操作,以便创建淡入淡出和其他光动画效果。NeoPixels 是允许您的项目与丰富的视觉交互的强大方式。这些示例将为您提供必要的构建模块,以将这些视觉概念纳入您自己的项目中。

在本章中,我们将涵盖以下示例:

  • 打开引脚 13 的 LED

  • 设置 NeoPixel 的亮度

  • 控制单个 NeoPixel 的颜色

  • 使用 RGB 和十六进制代码显示 LED 颜色

  • 使用颜色名称设置 LED 颜色

  • 将所有 NeoPixels 设置为相同的颜色

  • 将一系列 NeoPixels 设置为一种颜色

  • 生成随机的 NeoPixel LED 颜色

  • 使用随机颜色创建 LED 动画

  • 使用彩虹颜色创建 LED 动画

Adafruit Circuit Playground Express 布局

以下图表显示了本章中将使用的 LED 的位置:

由 adafruit.com 提供

引脚 13 的 LED 是第一个示例中将使用的简单的单个红色 LED。板上共有 10 个 NeoPixels。每个 NeoPixel 由红色、绿色和蓝色 LED 组成。通过控制这些 LED 的各自亮度,您将能够将任何 NeoPixel 设置为特定颜色。

打开引脚 13 的 LED

在本示例中,我们将学习如何打开和关闭引脚 13 的 LED。这是板上最简单的 LED,因为它只有一种颜色,并且在 Python 中与之交互也非常简单。出于这些原因,引脚 13 的 LED 是一个很好的起点。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本示例中提供的代码。

如何做...

要做到这一点,请执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.red_led = True
  1. 此阶段应该看到引脚 13 的 LED 变红。

  2. 使用以下代码检查 LED 的当前状态:

>>> cpx.red_led
True
  1. 要关闭 LED,请在 REPL 中运行以下代码:
>>> cpx.red_led = False
  1. 现在引脚 13 的 LED 灯将被关闭。

工作原理...

代码的第一行导入了 Circuit Playground Express 库。该库包含一个名为express的对象类,这是我们将用于与此板上的硬件进行交互的主要类。当导入库时,它会创建一个名为cpx的此类的实例。

cpx对象公开了一个名为red_led的属性。此属性可用于检索 LED 的当前值。如果 LED 打开,则返回True值;否则,如果 LED 关闭,则返回False值。设置此属性的值将打开或关闭 LED,具体取决于设置TrueFalse值。

还有更多...

这是板上最简单的 LED 灯之一,因为它是通过将值设置为TrueFalse来控制的。您无法控制此 LED 的颜色或亮度。本书中的其他示例将控制板上的 NeoPixel 灯,这些灯具有更丰富的功能范围,因此需要更复杂的 API 来控制它们。

另请参阅

您可以使用以下参考资料了解更多信息:

设置 NeoPixel 的亮度

控制像素的亮度将是本教程的主题。根据项目的需要设置像素的亮度非常重要。请注意,您必须将亮度更改为足够明亮的级别,以便像素清晰可见,但不要太亮以至于引起不适。

准备工作

您需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何操作...

为了做到这一点,请执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.pixels.brightness = 1.0
>>> cpx.pixels[0] = (255, 0, 0)
  1. 此时,第一个像素应该是红色,并且亮度全开。运行以下代码行以将亮度级别设置为 50%:
>>> cpx.pixels.brightness = 0.5
  1. 亮度级别可以进一步降至 10%,仍然可以舒适地看到。您可以通过运行以下代码行来实现:
>>> cpx.pixels.brightness = 0.10

它是如何工作的...

brightness 属性接受从 01.0 的值,从最暗到最亮。请注意,此板上的 NeoPixels 可能非常明亮,如果您直接看最高亮度级别的话,可能会对您的眼睛造成压力。

我建议您将亮度级别设置为 10%,因为这样可以更舒适地查看像素。然后,根据项目的需要,您可以调整亮度到最合适的级别。

有时像素将位于薄塑料覆盖物下,您将希望增加亮度级别。另一方面,有时您将直接看着它们,您将希望降低亮度级别。

还有更多...

重要的是要注意,亮度级别的实现方式意味着您只能一次更改所有的 NeoPixels。也就是说,使用亮度属性,您不能使一些像素变亮,一些像素变暗。因此,您设置的亮度值将应用于板上的所有像素。

当像素保持在最大亮度级别的 100% 时,它们具有非常明亮的能力。这种设置更适合的一个例子是当您将设备嵌入塑料容器中。以下照片是从一个 NeoPixel 项目中拍摄的,其中 Circuit Playground Express 板被放置在雪球的底座内部:

在这个项目中,底座是由白色塑料制成的。因此,即使板不直接可见,像素也足够明亮,可以透过白色塑料照亮整个雪球。

本项目中展示的 DIY 雪球套件可以在 www.adafruit.com/product/3722 找到。

另请参阅

您可以使用以下参考资料了解更多信息:

控制单个 NeoPixel 的颜色

这个教程将向您展示如何将特定的 NeoPixel 设置为不同的颜色。然后它将向您展示如何更改板上附带的 10 个 NeoPixels 中的任何一个的颜色。这将是一个有用的教程,因此您可以开始释放这些板载像素的强大和灵活性。

准备工作

你需要在 Circuit Playground Express 上访问 REPL 来运行本教程中提供的代码。

如何做...

要做到这一点,执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.pixels[0] = (255, 0, 0)
  1. 现在,你应该看到第一个 NeoPixel 变成红色。

  2. 当你运行以下代码时,你应该看到第一个 NeoPixel 变成绿色:

>>> cpx.pixels[0] = (0, 255, 0)
  1. 当你运行以下代码时,你应该看到第一个 NeoPixel 变成蓝色:
>>> cpx.pixels[0] = (0, 0, 255)
  1. 以下代码应该检索第一个 NeoPixel 的当前颜色值:
>>> cpx.pixels[0]
(0, 0, 255)
  1. 运行以下代码关闭第一个 NeoPixel:
>>> cpx.pixels[0] = (0, 0, 0)
  1. 运行以下代码,第二个 NeoPixel 应该变成红色:
>>> cpx.pixels[1] = (255, 0, 0)

工作原理...

第一行代码导入了将用于控制 NeoPixels 的cpx对象。这个对象有一个名为pixels的属性,可以像列表一样访问。使用的索引表示要操作的 10 个 NeoPixels 中的哪一个。

在第一个代码片段中,我们将值设置为表示所需颜色的元组,它由红色、绿色和蓝色值组成。每个值应该表示为 0 到 255 之间的整数。通过将值设置为(255, 0, 0),红色 LED 将达到最高值,绿色和蓝色 LED 将关闭。这将创建红色。

按照相同的方法,然后通过为每种颜色提供正确的值来将 NeoPixel 设置为绿色和蓝色。还可以通过简单地访问任何特定示例的值来轻松地检索特定像素的当前 RGB 值。

通过将所有 RGB 分量设置为 0,可以关闭像素,如本教程中的前面代码所示。最后一个前面的代码片段只是通过引用正确的索引值来将第二个像素设置为红色的示例。

还有更多...

在旧版本的库中,你可以将颜色提供为三个整数的列表,而不是三个整数的元组。最好避免这样做,而是坚持使用元组而不是列表。这是因为你的代码将在新版本和旧版本的库中都能工作。

每个 NeoPixel 由红色、绿色和蓝色 LED 组成。当你在这个教程中设置每种颜色的强度时,它直接改变了这些单独 LED 的亮度级别。可以使用消费者显微镜来查看组成每个 NeoPixel 的三个单独的 LED 灯。以下照片是从这些消费者级显微镜中拍摄的,放大倍数为 200 倍。正如你所看到的,单独的红色、绿色和蓝色 LED 清晰可见:

另请参阅

你可以使用以下参考资料了解更多信息:

使用 RGB 和十六进制代码显示 LED 颜色

有一个常见的约定,可以使用十六进制代码来表示任何颜色,它通过表示颜色的红色、绿色和蓝色组件来工作。这个教程演示了如何使用这个十六进制代码约定来设置 NeoPixel 的颜色。当你想要从网络或桌面上的其他应用程序应用特定的颜色设置时,使用这样一个流行的约定将是有用的。

准备工作

你需要在 Circuit Playground Express 上访问 REPL 来运行本教程中提供的代码。

如何做...

要做到这一点,执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.pixels[0] = 0xFF0000
  1. 你应该看到第一个 NeoPixel 变成红色。运行以下代码来检索第一个 NeoPixel 的颜色值:
>>> cpx.pixels[0]
(0, 0, 255)
  1. 运行以下代码将接下来的两个像素设置为绿色和蓝色:
>>> cpx.pixels[1] = 0x00FF00
>>> cpx.pixels[2] = 0x0000FF
  1. 使用以下代码将第四个像素设置为黄色:
>>> cpx.pixels[3] = 0xFFFF00
  1. 使用以下代码显示颜色蓝的整数值,然后使用这个整数值将下一个像素设置为蓝色:
>>> 0x0000FF
255
>>> cpx.pixels[4] = 255

工作原理...

第一个代码片段使用颜色的十六进制表示法将板上的第一个像素设置为红色。像素的接口接受颜色值,可以作为三个整数的元组或十六进制值给出,在 Python 中,这对应于一个整数值。

根据给定的值类型,库会提取颜色的红色、绿色和蓝色组件的正确值,并将像素设置为该颜色。第二个代码片段表明,当读取值时,它们将始终作为三个颜色组件的元组检索。

最后一个代码片段表明,正在使用的十六进制表示法是 Python 语言的一个标准特性,用于指定整数值的十六进制值。等效的整数值也可以用于设置颜色。

还有更多...

十六进制代码表示系统用于描述颜色的红色、绿色和蓝色组件,非常受欢迎。由于其受欢迎程度,很容易找到各种在线工具和桌面应用程序,提供颜色选择器和颜色轮,这些工具将颜色表示为十六进制代码。您可以在这些程序中简单地选择所需的颜色,然后将十六进制值复制并粘贴到您的脚本中。以下截图来自流行的开源图像编辑器 GIMP:

在上述截图中,您可以看到应用程序中提供的颜色轮。这个丰富的界面可以轻松地通过改变色调或饱和度找到您要找的颜色。一旦选择了您想要的颜色,您可以复制十六进制代码值,这在该应用程序中标记为HTML 表示法。然后,您可以使用相同的技术在您的脚本中使用这个值。

GIMP 可在 Linux、macOS 和 Windows 上使用,并可从www.gimp.org免费下载。

另请参阅

您可以使用以下参考资料了解更多信息:

使用颜色名称设置 LED 颜色

使用易读的颜色名称可以使您更容易跟踪应用程序中使用的颜色。本文演示了一种允许您使用常规颜色名称设置像素颜色的技术。通过一组标准的颜色名称引用颜色的功能在流行的语言中可用,包括 CSS。本文向您展示了如何将此功能引入到您的 MicroPython 脚本中。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本文中提供的代码。

如何操作...

要执行此操作,请执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> RGB = dict(black=0x000000, blue=0x0000FF, green=0x00FF00, 
... cyan=0x00FFFF,
... red=0xFF0000, magenta=0xFF00FF, yellow=0xFFFF00, 
... white=0xFFFFFF)
>>> cpx.pixels[0] = RGB['red']
  1. 您应该看到第一个 NeoPixel 变成红色。

  2. 使用以下代码将前八个像素按字母顺序设置为命名颜色之一:

>>> for i, name in enumerate(sorted(RGB)):
...     cpx.pixels[i] = RGB[name]

工作原理...

创建了一个名为 RGB 的全局变量;这是一个用于将颜色名称与它们的 RGB 颜色代码匹配的字典。这允许通过它们的名称检索颜色值,而不是每次需要使用时直接指定它们的十六进制代码。第一个片段使用 RGB 代码将第一个像素设置为红色。

第二个代码块按字母顺序循环遍历每个颜色名称,并将一个像素设置为该颜色。由于颜色查找字典中定义了八种颜色,前八个像素将设置它们的颜色,每个像素将从颜色列表中选择自己的颜色。

还有更多...

使用人类可读的颜色名称可以提高代码的可读性。然而,本教程中描述的技术需要您手动指定每个颜色名称及其相关的十六进制代码。如果只使用少量颜色,这是可以接受的,但如果要支持大量颜色,那么这将变得非常繁琐。另一个需要考虑的因素是,许多这些板子的内存容量有限,因此创建非常大的字典可能会导致板子内存不足。像本例中展示的小颜色查找表不应该引起这些问题。

当你在寻找颜色名称及其相关的十六进制代码时,有许多标准来源可供使用。一个流行的颜色名称列表是万维网联盟W3C),它在 CSS 中使用。另一个标准颜色列表是开源文本编辑器 Vim 提供的。这个颜色名称列表存储在一个名为rgb.txt的文件中,它随每个 Vim 安装包提供。

使用这个颜色列表的好处在于它以一种机器可读的格式呈现,每一行代表一个颜色,颜色组件和名称以空格分隔。这使得解析和使用这些颜色名称变得相对简单。下面的截图显示了一个有用的 Vim 脚本的输出,该脚本解析了这个文件,并为每个颜色名称和其应用的颜色提供了便捷的选择:

这个 Vim 颜色脚本可以在vim.fandom.com/wiki/View_all_colors_available_to_gvim找到。

另请参阅

您可以使用以下参考资料了解更多信息:

将所有 NeoPixels 设置为相同颜色

本教程解释了如何通过一次调用将所有像素设置为一个颜色,而不是循环遍历所有 NeoPixels 并单独设置它们的颜色。您可以使用这种技术来创建一个很好的效果,将所有 10 个 NeoPixels 设置为相同的颜色。它们排列成一个完美的圆圈,所以当它们都设置为相同的颜色时,就会形成一个颜色的环。这也是一种一次性关闭所有 NeoPixels 的简单方法。

准备工作

你需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

要做到这一点,请执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.pixels.fill(0x0000FF)
  1. 你应该看到所有 10 个 NeoPixels 变成蓝色。

  2. 使用以下代码关闭所有 10 个 NeoPixels:

>>> cpx.pixels.fill(0x000000)

工作原理...

在第一个代码片段中,调用了fill方法,并提供了颜色值作为第一个参数。fill方法将循环遍历所有像素,并将它们设置为所需的颜色,这种情况下是蓝色。该方法接受十六进制颜色表示法和三个整数值的元组。

还有更多...

将所有像素设置为相同颜色的操作相对流行,该方法已经为您提供了便利。然而,重要的是要注意,这种方法的实现并不只是简单地循环并为每个像素设置颜色。相反,它使用了一个功能,可以在显示之前设置所有的颜色值。

这个功能的优点是您可以先设置所有颜色,然后一次性调用显示它们。这是设置像素的更好方法,而不是用简单的for循环,因此它提供了另一个使用fill方法的充分理由。

另请参阅

您可以使用以下参考资料找到更多信息:

将一系列 NeoPixel 设置为一个颜色

本教程将探讨如何使用切片功能将特定范围的像素设置为特定颜色。当您想要将像素环转换为显示值从 1 到 10 的值的仪表时,这可能非常有用。基本上,它提供了一种更清晰和简单的方式来将一系列像素设置为特定颜色。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

要做到这一点,请执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.pixels[0:2] = [0xFF0000, 0xFF0000]
  1. 您应该看到前两个 NeoPixels 点亮为红色。

  2. 使用以下代码将接下来的三个像素变为绿色,最后五个像素变为蓝色:

>>> cpx.pixels[2:5] = [0x00FF00] * 3 
>>> cpx.pixels[5:10] = [0x0000FF] * 5

工作原理...

pixels属性在使用slice方法设置值时会理解。但是,它期望如果您为两个像素设置颜色,那么您应该提供一个包含两个颜色值的列表,就像在第一个示例中所做的那样。

在 Python 中,我们可以通过取颜色值的列表并将其乘以所需数量的值来减少这种重复。这是用于将三个像素设置为绿色的方法。

还有更多...

在 Python 中使用的切片表示法简洁而强大。这是一种非常聪明的方式,可以在一行代码中改变一系列像素的颜色。这非常符合 Python 保持代码简短和简洁而不影响可读性的方法。

另请参阅

您可以使用以下参考资料找到更多信息:

生成随机 NeoPixel LED 颜色

这个教程演示了一种可以无限生成随机颜色的技术。然后我们将在特定的 NeoPixel 上使用这些随机颜色。在颜色部分添加随机性可以使项目更有趣,因为您无法预测脚本执行时将出现的确切颜色序列。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

要做到这一点,请执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> from random import randint
>>> randint(0, 255)
186
>>> randint(0, 255)
84
  1. 每次运行前面的代码行时,您应该得到一个介于 0 和 255 之间的随机整数。

  2. 使用以下代码定义一个函数,然后调用该函数确认它是否正常工作:

>>> def get_random_color():
...     return (randint(0, 255), randint(0, 255), randint(0, 255))
...     
...     
... 
>>> get_random_color()
(208, 161, 71)
>>> get_random_color()
(96, 126, 158)
  1. 重复调用以下代码;每次调用时,第一个 NeoPixel 应更改为随机颜色:
>>> cpx.pixels[0] = get_random_color()
  1. 使用以下代码在每次调用时将所有像素设置为相同的随机颜色:
>>> cpx.pixels.fill(get_random_color())

工作原理...

在这个配方中,我们使用了random模块,它是 Python 标准库和 CircuitPython 的一部分。调用randint并提供从 0 到 255 的范围将为每个颜色分量给我们一个随机整数。

然后我们定义get_random_color函数来随机选择三个颜色分量,因此产生一个随机颜色。现在我们有了这个函数,我们可以调用它来设置单个像素或所有像素的颜色,就像在这个配方的最后两个代码片段中演示的那样。

还有更多...

在 MicroPython 项目中使用random模块打开了一系列有趣的可能性,可以创建独特和不同的项目。这个配方涵盖了一个例子,结合随机库和代码来指定颜色,以便可以选择随机颜色。使用这种方法可能会随机选择超过 1600 万种不同的颜色。

另请参阅

您可以使用以下参考资料了解更多信息:

使用随机颜色创建 LED 动画

这个配方将结合本章中以前配方的一些方面,使用随机选择的颜色创建动画。这个配方基于其他配方的技术来创建你的第一个动画。在板上有 10 个像素,有很多选项可以在板上创建引人入胜的视觉动画——这只是其中之一。

做好准备

您需要访问 Circuit Playground Express 上的 REPL 来运行本配方中提供的代码。

如何做...

要做到这一点,执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> from random import randint
>>> import time
>>> def get_random_color():
...     return (randint(0, 255), randint(0, 255), randint(0, 255))
...          
... 
>>> get_random_color()
(10, 41, 10)
  1. 运行以下代码块,板上的像素环周围应该出现一个持续 10 秒的颜色动画:
>>> for i in range(10):
...     cpx.pixels[i] = get_random_color()
...     time.sleep(1)
...     
...     
... 
>>> 
  1. 接下来,运行动画 30 秒,并循环三次所有像素,每次光变化之间延迟 1 秒:
>>> cpx.pixels.fill(0x000000)
>>> for cycle in range(3):
...     for i in range(10):
...         cpx.pixels[i] = get_random_color()
...         time.sleep(1)
...         
...         
... 
>>> 
  1. 对于最后一个动画,运行动画 5 秒,并在每秒更改所有像素颜色一次:
>>> cpx.pixels.fill(0x000000)
>>> for i in range(5):
...     cpx.pixels.fill(get_random_color())
...     time.sleep(1)
...     
...     
... 
>>> 

它是如何工作的...

这个配方中介绍了三种不同的动画。在灯光动画方面,天空是极限。有很多不同的方法来控制颜色变化和时间,每种不同的方法都会产生略有不同的视觉效果。然而,所有动画的一个关键方面是时间;我们可以使用sleep调用来控制动画的节奏,这是time模块的一部分。通过这种方式,我们可以减慢或加快我们创建的动画的速度。

这个配方中的第一个动画是一个简单的for循环,它将每个像素的颜色设置为随机颜色,并在这些颜色变化之间暂停一秒。第二个动画在第一个动画的基础上进行了改进,通过一个外部循环循环 3 次,因此改变了像素 30 次。

最后,最后一个动画采用了不同的方法,将所有像素设置为相同的颜色,然后在每个循环期间一起改变它们。

还有更多...

这个配方中的动画可以进行调整,以创建各种不同的动画。例如,您可以改变动画的速度或动画循环像素的次数。前面的代码可以用在一个接收这两个参数作为参数的函数中。然后可以在一个更大的程序中使用它,该程序将调用该函数以使用不同的设置制作动画。

另请参阅

您可以使用以下参考资料了解更多信息:

使用彩虹颜色创建 LED 动画

这个方案将产生一个遵循彩虹中相同颜色顺序的颜色环。这些颜色将在一定的延迟后依次出现,产生彩虹动画效果。使用自然组合在一起的颜色序列,比如彩虹中找到的颜色,既令人愉悦又引人入胜。这个动画的优势在于学会如何控制正在动画中的确切颜色序列,无论是彩虹序列还是您选择的其他序列。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本方案中提供的代码。

如何做...

要做到这一点,执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> import time
  1. 以下代码块定义了一个颜色值列表,其值和顺序与彩虹中的相同:
>>> RAINBOW = [
... 0xFF0000,   # red
... 0xFFA500,   # orange
... 0xFFFF00,   # yellow
... 0x00FF00,   # green
... 0x0000FF,   # blue
... 0x4b0082,   # indigo
... 0xEE82EE,   # violet
... ]
>>> 
  1. 然后,在开始动画之前,设置一个更舒适的亮度级别并关闭所有像素:
>>> cpx.pixels.brightness = 0.10
>>> cpx.pixels.fill(0x000000)
>>> 
  1. 使用以下代码块,循环遍历彩虹中的七种颜色,并将一个像素设置为每种颜色,每次灯光变化之间有短暂的0.2秒延迟:
>>> for i, color in enumerate(RAINBOW):
...     cpx.pixels[i] = color
...     time.sleep(0.2)
...     
...     
... 
>>> 
  1. 使用以下动画返回到每个像素,并以每次灯光变化的相同速度0.2秒关闭它:
>>> for i in range(len(RAINBOW)):
...     cpx.pixels[i] = 0x000000
...     time.sleep(0.2)
...     
...     
... 
>>> 
  1. 以下代码结合了描述的所有步骤,并将这些步骤包装成一个无限的while循环。将此代码部分添加到main.py文件中,然后创建一个连续的彩虹动画:
from adafruit_circuitplayground.express import cpx import time RAINBOW = [ 0xFF0000, # red 
 0xFFA500, # orange 
 0xFFFF00, # yellow 
 0x00FF00, # green 
 0x0000FF, # blue 
 0x4b0082, # indigo 
 0xEE82EE, # violet
]

cpx.pixels.brightness = 0.10
cpx.pixels.fill(0x000000)
while True:
    for i, color in enumerate(RAINBOW):
        cpx.pixels[i] = color
        time.sleep(0.2)
    for i in range(len(RAINBOW)):
        cpx.pixels[i] = 0x000000
        time.sleep(0.2)

工作原理...

自然界中的彩虹由七种颜色组成:红色、橙色、黄色、绿色、蓝色、靛蓝色和紫罗兰色。我们将这些颜色的值以及它们在自然界中出现的正确顺序存储在一个列表中。设置亮度级别,然后调用fill方法关闭板上的所有像素。

启动一个包含两个循环的无限循环。第一个内部循环将循环遍历彩虹中的每种颜色,并将一个像素设置为每种颜色。然后,第二个内部循环将返回到被着色的七个像素,并关闭每一个。

还有更多...

以下照片显示了此处方案中的彩虹动画在 Circuit Playground Express 上运行:

有许多方法可以从这个彩虹动画中制作更多的衍生动画。例如,您可以添加更多不属于自然彩虹的颜色。我们定义了 7 种颜色,但板上有 10 个像素,所以您可以定义另外 3 种不同的颜色。您还可以使起始像素在每个循环中随机选择,这样动画在每个循环中都从不同的像素开始。

另请参阅

您可以使用以下参考资料了解更多信息:

第三章:创建声音和音乐

本章将介绍使用 Adafruit Circuit Playground Express 上的硬件制作声音和播放音乐的方法。本章首先介绍了让板子以特定频率发出蜂鸣声的基础知识,然后将进一步介绍更高级的主题,如使用 WAV 文件格式和板载扬声器播放音乐文件。本章的技术可以直接用于您可能制作的各种 MicroPython 项目中。本章中产生音频输出的选项范围从产生简单的蜂鸣声到在嵌入式项目中播放歌曲。

在本章中,我们将介绍以下教程:

  • 发出蜂鸣声

  • 控制音调、频率和持续时间

  • 播放音符

  • 播放旋律

  • 发出警报

  • 播放 WAV 文件

  • 将 MP3 文件转换为 WAV 文件

  • 开始和停止音调

Adafruit Circuit Playground Express 布局

以下照片显示了板子上内置扬声器的位置。本章涵盖的所有蜂鸣声和声音都将使用此扬声器进行播放:

由 adafruit.com 提供

发出蜂鸣声

在本教程中,我们将学习如何使扬声器以特定的声音频率发出蜂鸣声,并持续一定的时间。音频输出是引起某人注意的好方法;您可以在到处都能找到它,从响铃电话到门铃。本教程将为您提供向嵌入式项目添加蜂鸣声所需的技能。

准备就绪

您需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。您应该听到以 900 赫兹的频率播放 0.2 秒的蜂鸣声:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.play_tone(900, 0.2)
  1. 执行以下代码,以便以较低频率的蜂鸣声播放更长时间:
>>> cpx.play_tone(500, 0.4)

工作原理...

代码的第一行导入了 Circuit Playground Express 库。cpx对象公开了一个名为play_tone的方法。此方法接受两个参数:频率和持续时间。这些参数指定声音的频率(以赫兹为单位)以及声音将在多长时间内播放(以秒为单位)。

持续时间可以以浮点数给出。这意味着诸如0.2之类的值将对应于 200 毫秒。此方法调用是一个阻塞调用。因此,调用该方法将开始播放音频,并且在指定的时间到达之前不会返回任何内容。

还有更多...

本章介绍的技术是从板子上的扬声器生成蜂鸣声的一种非常直接的方法。但是,在幕后,发生了很多事情。当您指定声音的频率和持续时间时,它将以编程方式构建声波,然后将音频数据输入扬声器以播放声音。音频数据是通过在 Python 代码中构建正弦波来创建的。

构建此音频数据的代码是 Circuit Playground Express 库的一部分,该库已在本教程中导入。您可以下载代码并阅读以了解如何完成此操作。这是了解声波的数学和如何通过软件创建它们的绝佳方式。以下屏幕截图显示了计算机生成的以 500 赫兹播放的音调的外观:

您可以清楚地从前面的屏幕截图中看到,这看起来就像正弦波形。当我们放大以查看单个声音周期时,我们拍摄了该屏幕截图。由于声音以 500 赫兹播放,我们期望一个周期为 1/500 秒长。在这里,我们可以看到第一个波结束的地方——确切地说是在 0.002 秒处。

另请参阅

您可以使用以下参考资料了解更多信息:

控制音调、频率和持续时间

在这个示例中,我们将学习如何以不同的频率和持续时间播放音调。通过重复播放不同频率的音调,每次持续时间都不同,我们可以学会如何超越单个蜂鸣声。这些步骤最终将使我们能够演奏旋律或不同音调,可以发出与警报相同的声音。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本示例中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。您应该听到五个单独的音调,每个音调都会播放 0.2 秒。声音将从较低的音调开始,逐渐变得更高。每次播放音调时,音调的频率将被打印到 REPL 中的输出中:
>>> from adafruit_circuitplayground.express import cpx >>> for i in range(500, 1000, 100): ... print(i) ... cpx.play_tone(i, 0.2)
...     
...     
... 
500
600
700
800
900
>>> 
  1. 使用以下代码播放三种不同的音调。音调将提高音高,并且播放时间也会增加:
>>> from adafruit_circuitplayground.express import cpx
>>> for i in range(200, 500, 100):
...     print(i)
...     cpx.play_tone(i, i/1000)
...     
...     
... 
200
300
400
>>> 

工作原理…

在第一段代码中,for循环将迭代频率值,从 500 开始,每次增加 100,直到结束于 900。这个范围和这些步骤对人耳来说是很容易听到的。在每次迭代中,将打印要播放的频率,然后使用play_tone方法播放。每次迭代中只有声音的频率会改变;它们都会播放 200 毫秒。

在第二段代码中,for循环将迭代一个较低的音调,并且音调较少。对于每次迭代,音调的频率和持续时间都会增加。频率将是i变量的确切值,而持续时间将是以毫秒为单位的i的值。由于play_tone方法期望的值是以秒为单位的,所以我们必须将其除以 1,000。

还有更多...

本章介绍的两个for循环变化了在短暂的时间内播放音调的方式。在这两个示例中,音调在一秒钟内播放,但它们有三个或更多不同的音调。

这是一个很好的起点,可以尝试不同变化的循环。因为每个循环只需要一秒钟,所以你可以快速进行实验,立即听到结果。尝试通过改变音调或音调变化的速度来进行实验。

在两个循环中,音调随着每次迭代而增加。尝试并实验一个音调,使其随着每次迭代而降低。

另请参阅

您可以使用以下参考资料了解更多信息:

演奏一个音符

在这个示例中,我们将学习如何定义一些全局常量,每个常量代表一个特定的音符。然后,我们可以通过引用它们的常量来演奏这些不同的音符。音符是旋律的基本组成部分。这将是演奏旋律的第一步。一旦我们学会了如何演奏一个音符,我们就可以在以后的示例中将多个音符组合在一起演奏旋律。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本食谱中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> E5 = 659
>>> C5 = 523
>>> G5 = 784
>>> cpx.play_tone(E5, 0.15)

您应该听到音箱播放E5音符,持续 0.15 秒。

  1. 使用以下代码播放C5G5音符,持续 0.15 秒:
cpx.play_tone(C5, 0.15)
cpx.play_tone(G5, 0.15)

它是如何工作的...

本食谱的第一行代码导入了 Circuit Playground Express 库。然后,定义了三个全局常量,并以它们关联的音符命名。本食谱使用科学音高记谱法SPN)。这种记谱法通过将音符名称与指定音高的数字相结合来工作。在 E5 的情况下,音符将是 E,音高将是 5。在这里,每个音符都映射到特定的声音频率。

在第一段代码块中,通过在调用play_tone方法时引用E5全局常量来简单地播放E5音符。将持续时间设置为0.15允许每个音符播放 150 毫秒,这样可以为音乐创造一个舒适的节奏。减少或增加此值可以增加或减少音乐音调的播放速度。第二段代码以相同的速度播放其余两个音符。

本章中使用的频率遵循标准钢琴键频率。这相当于标准音乐音高和 12 平均律。

还有更多...

在本食谱中,我们使用了三个音符来演示定义音符然后播放每个音符的过程。当然,还可以定义许多其他音符。

一个很好的学习练习是找到其他流行音符的频率,并经历定义它们并播放它们的过程。尽管三个音符似乎太少,但足以演奏出一个可识别的旋律。在下一个食谱中,我们将看到这三个音符如何组合在一起演奏一首流行的旋律。

另请参阅

您可以使用以下参考资料了解更多信息:

演奏旋律

在本食谱中,我们将学习如何通过播放一系列音符来演奏旋律。单独的音符本身相当无聊。真正的乐趣开始于您可以组合一系列音符并正确计时以演奏旋律。

通过遵循标准的音乐记谱法,将能够以一种 Circuit Playground Express 能够播放的方式在 Python 中指定流行的旋律。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本食谱中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。您应该听到音箱播放E5音符,持续 0.15 秒:
>>> import time
>>> from adafruit_circuitplayground.express import cpx
>>> 
>>> E5 = 659
>>> C5 = 523
>>> G5 = 784
>>> 
>>> def play_note(note, duration=0.15):
...     if note == 0:
...         time.sleep(duration)
...     else:
...         cpx.play_tone(note, duration)
... 
>>> play_note(E5)
  1. 使用以下代码行以两倍速播放相同的音符,然后以一半的速度播放:
>>> play_note(E5, 0.15 / 2)
>>> play_note(E5, 0.15 * 2)
  1. 使用以下代码行播放空白,并使扬声器保持安静,持续时间与正常速度播放一个音符的时间相同:
>>> play_note(0)
  1. 使用以下代码行播放超级马里奥兄弟主题曲的开头部分:
>>> MELODY = (E5, E5, 0, E5, 0, C5, E5, 0, G5)
>>> for note in MELODY:
...     play_note(note)
... 
>>> 
  1. 接下来的代码将结合本食谱中显示的所有代码,以制作一个完整的程序。将其添加到main.py文件中,它将在重新加载代码时播放超级马里奥兄弟主题曲的开头部分:
import time
from adafruit_circuitplayground.express import cpx

E5 = 659
C5 = 523
G5 = 784

MELODY = (E5, E5, 0, E5, 0, C5, E5, 0, G5)

def play_note(note, duration=0.15):
    if note == 0:
        time.sleep(duration)
    else:
        cpx.play_tone(note, duration)

for note in MELODY:
    play_note(note)

它是如何工作的...

初始的代码行导入必要的库并设置程序中其余代码所需的常量。MELODY常量包含构成歌曲的音符序列。在某些音符之间有静音暂停;这些暂停只需指定值为0,表示此时不应播放任何音符。play_note函数期望给出要播放的音符的频率,以及可选的音符播放时间。如果给出频率0,它将调用 sleep 函数保持静音;否则,它将播放音符作为音调。

最后,程序末尾的for循环简单地循环遍历旋律中定义的每个音符,并通过调用play_note函数来播放它。通过这种方式,您可以定义许多不同的旋律和歌曲,并根据用户与设备的交互方式播放不同的歌曲。

有更多...

这个配方是以通用方式编写的:您采用一首流行的旋律,提供音符序列和每个音符的相关频率,然后将旋律添加到您的项目中。这个配方中的旋律让每个音符以相同的持续时间播放。

然而,有许多旋律可能混合四分音符和八分音符。这些旋律将需要为每个音符定义不同的持续时间。可以扩展该配方,以便我们可以跟踪要播放的每个音符以及每个音符需要播放的持续时间。

另请参阅

您可以使用以下参考资料了解更多信息:

发出警报

在这个配方中,我们将学习如何播放低音和高音频率的声音,以创建警报声音。警报声音对于提醒人们引起他们的注意非常有用。这个配方演示了创建警报声音的一种非常简单但有效的方法,然后可以根据项目的需要进行调整。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本配方中提供的代码。

如何做到...

让我们为这个配方执行以下步骤:

  1. 在 REPL 中运行以下代码行。您应该听到高音的蜂鸣声持续 0.5 秒:
>>> from adafruit_circuitplayground.express import cpx
>>> 
>>> BEEP_HIGH = 960
>>> BEEP_LOW = 800
>>> 
>>> cpx.play_tone(BEEP_HIGH, 0.5)
  1. 使用以下代码播放低音的蜂鸣声 0.5 秒:
>>> cpx.play_tone(BEEP_LOW, 0.5)
  1. 使用以下代码播放警报器,通过三个周期从高音到低音,总共播放三秒:
>>> for i in range(3):
...     cpx.play_tone(BEEP_HIGH, 0.5)
...     cpx.play_tone(BEEP_LOW, 0.5)
... 
>>> 
  1. 接下来的代码将结合本配方中显示的所有代码,以制作一个完整的程序。将其添加到main.py文件中,每次重新加载代码时都会播放三秒的警报声音:
from adafruit_circuitplayground.express import cpx

BEEP_HIGH = 960
BEEP_LOW = 800

for i in range(3):
    cpx.play_tone(BEEP_HIGH, 0.5)
    cpx.play_tone(BEEP_LOW, 0.5)

它是如何工作的...

初始的代码行导入必要的库并设置程序中其余代码所需的常量。然后,脚本循环三次,每次迭代都会播放一秒钟的声音。

在每次迭代中,将播放高音持续半秒,然后低音持续半秒。通过这种方式,创建了一个警报声音效果,类似于警报声音。

有更多...

此代码可以放入一个接收参数计数的函数中,该参数指定警报响响多少次或多少秒。然后,对于项目中的任何代码,您都可以调用该函数,使您的板播放 10 秒或 30 秒的警报。您还可以将此教程与书中的其他教程结合起来,使板上的像素以与警报相同的方式闪烁为红色。

另请参阅

您可以使用以下参考资料了解更多信息:

播放 WAV 文件

在本教程中,我们将学习如何使用扬声器播放您选择的 WAV 文件。Circuit Playground Express 上有大量存储空间,可以存储短音频片段,并可以在特定时间播放。

音调、蜂鸣、警报和旋律都很棒;但是,一旦您可以播放 WAV 文件,那么您就可以播放任何类型的声音。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

操作步骤...

让我们执行以下步骤:

  1. hello.wav文件复制到与main.py文件相同的文件夹中的设备上。然后,在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.play_file('hello.wav')
  1. 您应该听到板子播放音频文件时说“你好”。

工作原理...

代码的第一行导入了 Circuit Playground Express 库。cpx对象公开了一个名为play_file的属性方法。该方法接受一个参数,即.wav文件名,该文件将在板上的扬声器上播放。

音频文件应该是 WAV 文件格式;它应该具有 22,050 kHz 的采样率,16 位格式,并且具有单声道音频。此方法将打开音频文件并在扬声器上开始播放。它还将不断轮询音频设备,直到播放完成,并在音频播放完成后返回。

还有更多...

由于板上的硬件限制,您将无法播放诸如 MP3 这样的压缩音乐格式。文件需要以特定的未压缩文件格式,直接输入到板上的播放硬件中。

这样做的一个后果是,未压缩的声音流会更大,因此只能存储短音频片段在设备上。这仍然为播放声音效果或其他短音频片段提供了许多可能性。

另请参阅

您可以使用以下参考资料了解更多信息:

将 MP3 文件转换为 WAV 文件

在本教程中,我们将学习如何将 MP3 文件转换为 WAV 文件,然后可以在 Circuit Playground Express 上播放。MP3 文件是最流行的声音文件格式之一。当您有一个要包含在嵌入式项目中的音频剪辑,但需要将其转换为正确的格式以便正确播放时,本教程非常有用。

准备工作

您需要下载并安装开源音频编辑软件 Audacity。它适用于 Windows、macOS 和 Linux。

Audacity 可以从官方网站www.audacityteam.org/下载。

如何做...

让我们执行以下步骤:

  1. 启动 Audacity 软件,然后选择文件|打开。然后,选择 MP3 文件并单击打开。

  2. 应用程序中应该显示音频文件的详细信息,如下面的屏幕截图所示:

  1. 选择轨道|重新采样,然后应该出现以下对话框:

  1. 将新的采样率设置为22050,然后单击确定。

  2. 现在,选择轨道|立体声轨道转换为单声道。屏幕上应该只有一个单声道,而不是可见的立体声音频流:

音频数据现在已准备好导出为 WAV 格式。

  1. 接下来,选择文件|导出音频。

  2. 将文件格式下拉菜单设置为 WAV(Microsoft)签名 16 位 PCM 的值。

  3. 单击保存按钮。

  4. 现在,您可以将 WAV 文件复制到板上并在设备上播放它。

它是如何工作的...

Circuit Playground Express 板期望音频文件以 WAV 文件格式存在,并且采样率为 22,050 kHz,采用 16 位格式,并且具有音频数据的单声道。Audacity 是一款多功能音频编辑器,可以打开任意数量的音频格式,并执行必要的更改以将音频数据转换为正确的格式。

在这个教程中采取的步骤重新采样音频数据并将音频通道转换为单声道。完成后,音频数据可以导出到正确的 WAV 格式。重要的是要注意,WAV 文件不像其他音频格式那样被压缩,因此它们将占用更多的空间。这与该设备上的存储限制相结合,意味着只能使用短音频剪辑,以便它们可以适应该设备。

还有更多...

这个教程侧重于 MP3 文件格式作为输入格式。但是,Audacity 支持广泛的输入格式,因此您不仅限于该输入格式进行转换。当您想要从更大的音频流中准备一个短音频剪辑时,Audacity 还具有广泛的编辑功能,这将非常有用。

一个很好的例子是,当您有一首可能长达五分钟的歌曲,但您只想要一个短短的五秒钟的片段加载到您的板上。然后,您可以使用 Audacity 的编辑和转换功能来实现最终结果。

另请参阅

您可以使用以下参考资料了解更多信息:

开始和停止音调

在这个教程中,我们将学习如何使用start_tonestop_tone调用在后台播放音调,并在播放声音时控制板上的其他组件。本教程中使用的技术基本上允许您在播放声音时做更多事情。

实施此项目的一个示例是当您想要播放警报声并同时闪烁灯光时。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。您应该听到一个高音的尖叫声,持续 0.5 秒:
>>> from adafruit_circuitplayground.express import cpx
>>> import time
>>> 
>>> BEEP_HIGH = 960
>>> BEEP_LOW = 800
>>> 
>>> cpx.pixels.brightness = 0.10
>>> cpx.start_tone(BEEP_HIGH)
>>> time.sleep(0.5)
>>> cpx.stop_tone()
  1. 使用以下代码在后台播放蜂鸣声,同时以 0.1 秒的间隔将 10 个像素变红。然后在动画结束时蜂鸣声将停止:
>>> cpx.start_tone(BEEP_HIGH)
>>> for i in range(10):
...     cpx.pixels[i] = 0xFF0000
...     time.sleep(0.1)
... 
>>> cpx.stop_tone()
>>> 
  1. 使用以下代码块执行类似的操作,但音调较低。在这里,像素动画将逐个关闭每个像素,最终在动画结束时结束音调:
>>> cpx.start_tone(BEEP_LOW)
>>> for i in range(10):
...     cpx.pixels[i] = 0x000000
...     time.sleep(0.1)
... 
>>> cpx.stop_tone()
>>> 
  1. 接下来的代码将所有在本示例中显示的代码组合在一起,以制作一个完整的程序。将其添加到main.py文件中,它将播放警报声并以警报声开启和关闭像素动画。
from adafruit_circuitplayground.express import cpx
import time

BEEP_HIGH = 960
BEEP_LOW = 800

cpx.pixels.brightness = 0.10

cpx.start_tone(BEEP_HIGH)
for i in range(10):
    cpx.pixels[i] = 0xFF0000
    time.sleep(0.1)
cpx.stop_tone()

cpx.start_tone(BEEP_LOW)
for i in range(10):
    cpx.pixels[i] = 0x000000
    time.sleep(0.1)
cpx.stop_tone()

工作原理...

初始代码行导入必要的库并设置程序中其余代码所需的常量。像素的亮度也设置为更舒适的水平。然后脚本开始在后台播放高音调的蜂鸣声。在循环遍历 10 个像素并在每个循环之间以 0.1 秒的延迟将每个像素变红。

动画完成后,音调播放停止并播放较低的音调。像素再次被循环遍历;然而,这一次,它们逐个关闭。最后,一旦循环结束,音调播放停止。

还有更多...

尽管使用start_tonestop_tone需要比简单调用play_tone更多的代码行,但它们允许您做一些仅使用play_tone是不可能做到的事情。例如,您可以使用脚本在音频在后台播放时执行其他任务。

在这个示例中,灯光和声音输出一起改变。但是,您可以使用相同的技术来播放音调,直到有人按下某个按钮。或者,您可以根据按下不同的按钮来改变正在播放的音调。

另请参阅

您可以使用以下参考资料了解更多信息:

第四章:与按钮交互

本章将介绍与 Adafruit Circuit Playground Express 配备的按钮和触摸板进行交互的方法。您将学习如何检测按钮是否被按下,并且还将探索更高级的主题,例如微调电容触摸板的灵敏度。

在本章中,我们将介绍以下配方:

  • 检测按下按钮

  • 使用按下按钮控制 LED

  • 读取滑动开关

  • 在按钮状态更改时调用函数

  • 使用按下按钮移动活动 LED

  • 按下按钮时播放蜂鸣声

  • 检测触摸板上的触摸

  • 监视触摸板的原始测量值

  • 调整触摸阈值

Adafruit Circuit Playground Express 布局

以下照片显示了标有 A 和 B 的两个按下按钮的位置:

由 adafruit.com 提供

以下照片显示了设备上滑动开关的位置:

由 adafruit.com 提供

以下照片显示了板上七个电容触摸板的位置:

由 adafruit.com 提供

每个触摸板都包含可以导电的不同材料。鳄鱼夹可以用来连接这些材料到触摸板。此外,金属、水和水果都可以导电,足以用作连接器连接到触摸板。

现在,让我们看看如何检测按钮的按下。

检测按下按钮

在本配方中,我们将学习如何创建一个程序,当按下按钮时将打印一条消息。按下按钮是在设备上创建用户交互的好方法。该板配有两个按下按钮 A 和 B,因此您可以通过读取和响应按下按钮事件来创建各种不同的用户交互。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本配方中提供的代码。

如何做...

让我们执行以下步骤:

  1. 首先,在 REPL 中运行以下代码行。这里cpx.button_a的值是False,因为按钮没有被按下:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.button_a
False
  1. 在运行以下代码块时,保持按下按钮 A。这将把值更改为True
>>> cpx.button_a
True
  1. 然后,将以下代码添加到main.py文件中,这将在执行时重复打印按下按钮 A 的状态:
from adafruit_circuitplayground.express import cpx
import time

while True:
    print(cpx.button_a)
    time.sleep(0.05)

它是如何工作的...

第一行代码导入了 Circuit Playground Express 库。cpx对象公开了一个名为button_a的属性。当按钮被按下时,此属性将返回True,当按钮未被按下时,它将返回False

脚本循环运行,每个循环之间延迟 50 毫秒。按钮按下的状态不断打印。运行此程序时,按住并释放按钮,以查看打印输出的变化。

请注意,还有另一个名为button_b的属性,它具有相同的功能,但用于按下按钮 B。

还有更多...

在 Python 中与按下按钮交互的界面非常简单。基本上,它转换为一个布尔值,您可以在脚本执行期间的任何时间检查以检查按钮的当前状态。

在简单的情况下,反复检查按钮状态的轮询模型效果很好。然而,当您想要对每次按下按钮执行单个操作时,而不是持续按下按钮时,它会出现问题。这类似于您期望在桌面上与键盘交互的方式。在这种情况下,您期望一个物理按键按下将转换为一次应用的动作。另一方面,长时间按下的物理按键通常会产生重复的按键动作。

在大多数操作系统上,在释放按键之前会应用大约 500 毫秒的延迟,这被视为重复按键操作。当您尝试实现与按键自然和直观交互的代码时,牢记这些细节是很重要的。

另请参阅

您可以在这里找到更多信息:

使用按钮控制 LED

在这个示例中,我们将学习如何使用两个独立的按钮控制两个单独的 NeoPixels。这是一种有趣而简单的方式,可以使您的设备具有交互性。在这里,您将在按下每个按钮时立即从板上获得反馈,因为像素会做出响应而亮起。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本示例中提供的代码。

如何做...

让我们执行以下步骤:

  1. 首先,在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> 
>>> BLACK = 0x000000
>>> GREEN = 0x00FF00
>>> 
>>> cpx.pixels.brightness = 0.10
  1. 在运行以下代码块时,保持按下按钮 A。您应该会看到紧挨着按钮的像素 2 变成绿色:
>>> cpx.pixels[2] = GREEN if cpx.button_a else BLACK
  1. 释放按钮 A 并运行以下代码块;现在应该看到像素 2 关闭:
>>> cpx.pixels[2] = GREEN if cpx.button_a else BLACK
  1. 将以下代码添加到main.py文件中,它将根据按下按钮 A 或按钮 B 来打开像素 2 和像素 7:
from adafruit_circuitplayground.express import cpx

BLACK = 0x000000
GREEN = 0x00FF00

cpx.pixels.brightness = 0.10
while True:
    cpx.pixels[2] = GREEN if cpx.button_a else BLACK
    cpx.pixels[7] = GREEN if cpx.button_b else BLACK

工作原理...

代码的第一行导入了 Circuit Playground Express 库。定义了绿色和黑色的常量,并将像素亮度设置为舒适的水平。

然后,启动一个无限循环,每次迭代执行两行代码。如果按下按钮 A,则第一行将把像素 2 的颜色设置为绿色,否则将关闭像素。第二行将把像素 7 的颜色设置为绿色,如果按下按钮 B,则将关闭像素。

还有更多...

与本章第一个示例相比,在每次循环之间没有调用sleep函数来引起延迟。在这个特定的示例中,之所以不需要在轮询按钮状态之间设置延迟,是有原因的。如果其中一个按钮被按住,那么其中一个灯将打开并保持打开,而不会出现问题。

在第一个示例中,当按下按钮时将会出现大量的打印语句。仔细观察每种情况,以决定是否需要在每次轮询之间设置延迟。

另请参阅

您可以在这里找到更多信息:

读取滑动开关

在这个示例中,我们将学习如何创建一个程序,该程序将重复打印滑动开关是打开还是关闭。滑动开关有其自身的优势,这个示例将演示如何将其纳入您的项目中。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本示例中提供的代码。

如何做...

让我们执行以下步骤:

  1. 确保将滑动开关翻转到左侧。在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.switch
True
  1. 将滑动开关翻转到右侧。运行以下代码块:
>>> cpx.switch
False
  1. 将以下代码添加到main.py文件中,它将在执行时重复打印滑动开关的状态。将滑动开关向左和向右转动,观察输出的变化:
from adafruit_circuitplayground.express import cpx
import time

while True:
    print(cpx.switch)
    time.sleep(0.05)

工作原理...

第一行代码导入了 Circuit Playground Express 库。cpx对象公开了一个名为switch的属性。当开关处于左侧位置时,此属性将返回True,当开关处于右侧位置时,将返回False

该脚本将无限循环,每个循环之间有 50 毫秒的延迟。滑动开关的状态将不断打印。

还有更多...

按下按钮非常适合重复应用动作,或者当您希望注册单次按钮按下时。然而,滑动开关更适合当您希望人们能够在两种操作模式之间进行选择时。

例如,您可能有一个项目,其中有两种动画模式可以使用滑动开关进行选择。您可以使用滑动开关来启用或禁用项目中的警报声音。根据用户的操作,滑动开关或按钮可能更合适。

Circuit Playground Express 的好处在于两种选项都可用,因此您可以选择最适合您的选项。

另请参阅

您可以在这里找到更多信息:

在按钮状态改变时调用函数

在本文中,我们将学习如何在按钮状态发生变化时调用函数。通常要求仅在按钮状态发生变化时执行操作,而不是在按钮被按下时执行操作。本文演示了一种您可以在项目中实现此要求的技术。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本文中提供的代码。

如何做...

让我们执行以下步骤:

  1. 首先,在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> def button_change(pressed):
...     print('pressed:', pressed)
... 
  1. 这将定义button_change函数,每次按钮状态发生变化时都会调用该函数。运行以下代码,然后重复按下和释放按钮 A:
>>> last = cpx.button_a
>>> while True:
...     if cpx.button_a != last:
...         button_change(cpx.button_a)
...         last = cpx.button_a
... 
pressed: True
pressed: False
pressed: True
pressed: False
pressed: True
pressed: False
  1. 接下来的代码将结合本文中展示的所有代码,制作一个完整的程序。将其添加到main.py文件中;每次按下或释放按钮 A 时,它都会打印一条消息:
from adafruit_circuitplayground.express import cpx

def button_change(pressed):
    print('pressed:', pressed)

last = cpx.button_a
while True:
    if cpx.button_a != last:
        button_change(cpx.button_a)
        last = cpx.button_a

工作原理...

定义了button_change函数,每次按钮状态改变时都会调用该函数。

last全局变量将用于跟踪按钮的上一个状态。然后,启动一个无限循环,它将检查按钮的当前状态是否与其上一个状态不同。如果检测到变化,它将调用button_change函数。

最后,每当按钮状态发生变化时,最新的按钮状态都将保存在last变量中。该脚本实际上实现了一个事件循环,用于检测按钮按下事件,并在检测到这些事件时调用button_change事件处理程序来处理这些事件。

还有更多...

偶尔,您可能希望将按钮按下注册为单个事件,而不管用户按下按钮的时间长短。这个方法通过跟踪按钮的先前状态并仅在按钮按下的结果时调用事件处理程序来实现这一目标。

尽管您需要跟踪按钮的最后状态这一额外步骤,但这种方法的好处在于您不必在轮询按键的延迟时间或重复键盘延迟的时间上纠缠。这个方法只是解决如何响应物理按钮交互的另一种可行方法。

另请参阅

您可以在这里找到更多信息:

使用按钮移动活动 LED

在这个方法中,我们将学习如何根据按下左侧或右侧按钮来顺时针或逆时针移动活动的 NeoPixel。这个方法超越了以前方法中显示的更简单的按钮和 LED 交互。这是一个更复杂的方法,它将产生按钮按下正在使光在面板上以圆周运动的印象。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本方法中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> import time
>>> 
>>> BLACK = 0x000000
>>> BLUE = 0x0000FF
>>> 
>>> cpx.pixels.brightness = 0.10
>>> i = 0
>>> direction = 1
>>> 
>>> 
  1. 运行以下代码并按下按钮以查看其对像素的影响:
>>> while True:
...     if cpx.button_a:
...         direction = 1
...     if cpx.button_b:
...         direction = -1
...     i += direction
...     i = i % 10
...     cpx.pixels.fill(BLACK)
...     cpx.pixels[i] = BLUE
...     time.sleep(0.05)
... 
  1. 接下来的代码将结合本方法中显示的所有代码,以制作一个完整的程序。将此代码块添加到main.py文件中,它将在按下按钮 A 和按钮 B 时将点亮的像素的方向从顺时针改为逆时针:
from adafruit_circuitplayground.express import cpx
import time

BLACK = 0x000000
BLUE = 0x0000FF

cpx.pixels.brightness = 0.10
i = 0
direction = 1
while True:
    if cpx.button_a:
        direction = 1
    if cpx.button_b:
        direction = -1
    i += direction
    i = i % 10
    cpx.pixels.fill(BLACK)
    cpx.pixels[i] = BLUE
    time.sleep(0.05)

它是如何工作的...

代码的第一行导入了 Circuit Playground Express 库和time库。然后设置了颜色常量和亮度级别。i变量将跟踪当前点亮的像素。direction变量将具有值1-1,并将控制像素是顺时针移动还是逆时针移动。

在无限循环中,如果按下按钮 A 或按下按钮 B,则会更改方向。方向应用于位置,并应用模 10 运算,以便位置值在 0 和 10 之间旋转。

在每次迭代中,所有像素都会关闭,然后打开所选像素。通过调用使面板在每次循环迭代之间休眠 50 毫秒来控制灯光动画的速度。

还有更多...

这个方法结合了许多不同的技术,以产生最终的结果。它使用了一个动画效果,让看着面板的人认为光在面板上以圆圈的方式移动。

已实施动画效果以支持方向运动,使其看起来就像光在顺时针或逆时针方向移动。然后,按键与此动画结合在一起,以改变动画的方向。

您可以采用这个基本方法并将其适应不同的场景。例如,您可以用声音效果替换灯光秀,声音效果可以从安静到响亮,或者从响亮到安静,具体取决于按下哪个按钮。此外,您可以使用两个按键来增加或减少亮度级别。有两个按键可以打开许多选项,以便根据按下的按钮来增加或减少特定值。

另请参阅

您可以在这里找到更多信息:

在按钮按下时播放蜂鸣声

在本教程中,我们将学习在按下按钮时播放蜂鸣声。之前的教程允许我们使用按钮与灯进行交互。本教程将向您展示如何在项目中引入按钮和声音交互。

准备工作

您需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在按住按钮 A 的同时在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> if cpx.button_a:
...     cpx.play_tone(500, 0.2)
...     
...     
... 
>>>
  1. 扬声器应该发出低音蜂鸣声。在按住按钮 B 的同时运行以下代码,您应该听到高音蜂鸣声:
>>> if cpx.button_b:
...     cpx.play_tone(900, 0.2)
...     
...     
... 
>>> 
  1. 接下来的代码将结合本教程中显示的所有代码,并向其中添加一个while循环,以使其成为一个完整的程序。将此添加到main.py文件中,当执行时,每次按下按钮 A 或按钮 B 时,它将产生高音或低音蜂鸣声:
from adafruit_circuitplayground.express import cpx

while True:
    if cpx.button_a:
        cpx.play_tone(500, 0.2)
    if cpx.button_b:
        cpx.play_tone(900, 0.2)

它是如何工作的...

第一行代码导入了 Circuit Playground Express 库。然后进入一个无限循环,每次循环迭代都会检查按钮 A 或按钮 B 是否被按下,并在每种情况下播放不同音调的蜂鸣声,持续时间为 0.2 秒。

还有更多...

这个简单的教程演示了如何通过播放不同的音调使板对不同的按钮按下做出反应。另一种使脚本行为的方法是根据按下的按钮不同播放不同的音频.wav文件。滑动开关也可以并入到教程中,以设置两种不同的模式;一种模式可以播放低音调的音符,另一种可以播放高音调的音符。

另请参阅

您可以在这里找到更多信息:

在触摸板上检测触摸

在本教程中,我们将学习如何检测触摸板何时被触摸,并在每次发生此事件时打印一条消息。Circuit Playground Express 配备了许多可以连接到各种对象的触摸板连接器。

基本上,任何可以导电的东西都可以用作与设备交互的方式。您可以使用导线、导电线、水果、水或铜箔与设备交互。

准备工作

您需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。cpx.touch_A1的值为False,因为未触摸触摸板 A1:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.touch_A1
False
  1. 在运行以下代码块时,保持手指触摸触摸板 A1:
>>> cpx.touch_A1
True
  1. 以下代码应添加到main.py文件中。每次按下触摸板 A1 时,这将打印一条消息:
from adafruit_circuitplayground.express import cpx
import time

while True:
    if cpx.touch_A1:
        print('detected touch')
    time.sleep(0.05)

它是如何工作的...

前几行代码导入了 Circuit Playground Express 库和time库。然后脚本进入一个无限循环,在每次循环迭代中检查触摸板 A1 的状态。如果检测到触摸事件,则会打印一条消息。

还有更多...

本教程演示了与触摸板交互的简单方法。但是,当涉及到电容触摸传感器时,细节至关重要。取决于您连接到触摸板的材料的导电性,您可能会发现自己处于两个极端之一;也就是说,传感器可能根本不会检测到某些触摸事件,或者如果有很多环境噪音被错误地检测为多次触摸事件。

这些设备并不像机械按钮那样简单。然而,它们将让您创建可以使用香蕉和橙子与嵌入式设备进行交互的项目(因为它们具有电导性)。

另请参阅

您可以在这里找到更多信息:

监控触摸板的原始测量值

在这个教程中,我们将学习如何监控触摸板的原始测量值,这是验证触摸阈值应该如何调整的非常有用的方法。能够直接读取来自触摸传感器的原始传感器值非常重要。

当您想要正确设置触摸阈值或想要找出为什么触摸板的响应方式与您的预期不符时,这种详细级别是必要的。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本教程中提供的代码。

如何操作...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。输出显示了从原始触摸测量中获取的值以及在创建对象时自动设置的初始阈值值:
>>> import time
>>> import touchio
>>> import board
>>> a1 = touchio.TouchIn(board.A1)
>>> a1.raw_value
1933
>>> a1.threshold
2050
>>> 
  1. 在运行下一段代码时,保持手指触摸触摸板 A1:
>>> a1.raw_value
4065
  1. 在运行下一段代码时,从触摸板 A1 上松开手指:
>>> a1.raw_value
1839
  1. 以下代码应添加到main.py文件中,然后运行。在执行此代码时,它将不断打印原始触摸测量值和当前阈值,并确定当前读数是否被视为触摸事件。此脚本可用于获取实时传感器读数:
import time
import touchio
import board

a1 = touchio.TouchIn(board.A1)
while True:
    touch = a1.raw_value > a1.threshold
    print('raw:', a1.raw_value, 'threshold:', a1.threshold, 'touch:', touch)
    time.sleep(0.5)

工作原理...

前几行代码导入了与触摸板交互所需的不同低级库。创建了一个TouchIn对象并连接到 A1 接口。然后,运行一个无限循环,不断打印与传感器相关的多个值。它打印当前原始触摸测量值的阈值以及当前测量是否应被注册为触摸事件。

最后一个值只是True,但如果原始值超过阈值,那么它就是False。当TouchIn对象首次实例化时,阈值是通过取初始原始值并将其加 100 来设置的。

还有更多...

此脚本非常有用,可以验证从触摸传感器读取的实际值,并决定触摸阈值应设置多低或多高。这也是将不同材料连接到您的板上并查看它们在导电和检测触摸事件方面表现如何的好方法。如果没有这些原始值,您只能猜测实际发生了什么。

本章中其他地方使用的高级属性实际上在底层使用了许多在本配方中介绍的库。查看这些高级代码的源代码很有帮助,因为其中的大部分是用 Python 实现的。此外,它可以让您了解代码实际上是如何与硬件交互的。

另请参阅

您可以在这里找到更多信息:

调整触摸阈值

在本配方中,我们将学习如何通过更改阈值来调整触摸板的灵敏度。这用于决定信号是否将被视为触摸事件。这是一个重要的设置,需要进行微调并设置为正确的值。如果不这样做,那么您的触摸项目将无法正确运行。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本配方中提供的代码。

如何做...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行。此时,触摸阈值将增加200
>>> from adafruit_circuitplayground.express import cpx
>>> import time
>>> 
>>> cpx.adjust_touch_threshold(200)
  1. 在运行下一块代码时,保持手指触摸触摸板 A1:
>>> cpx.touch_A1
True
  1. 应将以下代码添加到main.py文件并运行。该脚本将通过200增加触摸阈值,并在传感器检测到触摸事件时打印消息:
from adafruit_circuitplayground.express import cpx
import time

cpx.adjust_touch_threshold(200)
while True:
    if cpx.touch_A1:
        print('detected touch')
    time.sleep(0.5)

工作原理...

第一行代码导入了 Circuit Playground Express 库。cpx对象公开了一个名为adjust_touch_threshold的方法。可以使用此方法来更改触摸板上的配置阈值。调用时,所有触摸板的阈值都将增加指定的量。

增加阈值会使触摸板变得不那么敏感,而减小此值将使传感器更敏感。如果阈值设置得太低,则许多传感器读数将被错误地检测为触摸事件。另一方面,如果阈值太高,则无法检测到真正的触摸事件。在每次循环迭代之间应用 500 毫秒的休眠函数,以便在每次迭代期间不会检测到大量的触摸事件。

还有更多...

通过实验来决定阈值的最佳值是最好的方法。在开始调整阈值之前,将所有实际导电材料连接到触摸板。然后,在本章中的监视触摸板原始测量配方中,获取传感器读数的实时视图。

您还可以重复触摸所讨论的材料,以查看触摸和释放时读数的变化。根据这些读数,您可以设置可靠读取触摸事件的理想阈值。每次更改材料时重启脚本很重要,因为每次首次运行代码时都会发生初始阈值自动配置。

另请参阅

您可以在这里找到更多信息:

第五章:读取传感器数据

本章将介绍您可以使用的方法,从 Adafruit Circuit Playground Express 上的多个传感器读取传感器数据。我们将涵盖温度和光传感器,以及运动传感器,您还将学习如何使板对传感器事件做出反应,例如板被摇动或光照水平发生变化。访问这些丰富的传感器数据可以使各种各样的项目成为可能。例如,您可以制作一个项目,如果检测到的温度超过了某个水平,就会发出警报声。通过学习如何读取和处理这些传感器数据,您可以使各种嵌入式项目成为现实。

在本章中,我们将涵盖以下主题:

  • Circuit Playground Express 传感器

  • 读取温度读数

  • 从光传感器中读取亮度级别

  • 创建光度计

  • 从运动传感器读取数据

  • 检测单击或双击

  • 检测摇晃

  • 摇晃时发出哔哔声

Circuit Playground Express 传感器

本章将使用三种不同的硬件传感器来从环境中获取传感器读数。以下照片是一个热敏电阻的照片,显示了温度传感器的位置:

由 adafruit.com 提供

以下照片显示了设备上可用的光传感器:

由 adafruit.com 提供

以下照片显示了加速度计,它可以用于检测运动,以及在板上敲击和双击:

由 adafruit.com 提供

现在让我们来检查我们的第一个教程。

读取温度读数

在本教程中,我们将学习如何创建一个循环,重复地从温度传感器中读取当前温度并将其打印出来。这将让我们对传感器进行实验,并查看它对温度变化的反应。本教程中的方法可以在您需要将温度读数纳入项目中时使用。

准备工作

你需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何读取温度读数:

  1. 在 REPL 中运行以下代码。输出显示室温约为 25°C:
>>> from adafruit_circuitplayground.express import cpx
>>> import time
>>> cpx.temperature
25.7499
  1. 现在,通过按压温度传感器来增加温度传感器读数,使用您的体温,同时在进行下一次读数时:
>>> cpx.temperature
30.031
  1. 温度应该已经升高了几度。如果运行以下代码,应返回一个浮点数:
>>> start = time.monotonic()
>>> start
27.409
  1. 在执行以下代码行之前等待几秒钟。它将计算自您将值分配给 start 变量以来的秒数:
>>> time.monotonic() - start
11.37
  1. 如果运行以下代码,应该会显示所有本地变量及其值的列表作为字典:
>>> locals()
{'time': <module 'time'>, 'start': 60.659, '__name__': '__main__'}
>>> 
  1. 以下代码应放入main.py文件中,当执行时,将重复打印当前经过的时间和当前温度读数:
from adafruit_circuitplayground.express import cpx
import time

start = time.monotonic()
while True:
    elapsed = time.monotonic() - start
    temp = cpx.temperature
    print('{elapsed:.2f}\t{temp}'.format(**locals()))
    time.sleep(0.1)

它是如何工作的...

代码的前几行导入了 Circuit Playground Express 库和time模块。cpx对象公开了一个名为temperature的属性-每当访问该值时,该属性将以浮点数的形式返回热敏电阻的当前温度读数。

这个值是以摄氏温度标度表示的。记录开始时间,以便为每个温度读数计算经过的时间。然后脚本进入一个无限循环,计算经过的时间并获取每个循环迭代的温度读数。

经过的时间和温度以制表符分隔打印。在开始下一个循环迭代之前,会应用 0.1 秒的延迟。

还有更多...

该设备上的温度传感器是负温度系数(NTC)热敏电阻。该元件是一个随温度变化而改变电阻的电阻器。通过测量其电阻,我们可以得到温度读数。在 NTC 热敏电阻的情况下,电阻会随着温度的升高而减小。

在这个示例中,时间和温度数据以制表符分隔的格式输出。这种格式使得将数据移入其他应用程序进行分析变得很容易。以下图表是使用从本示例的主脚本输出的数据生成的:

脚本运行了 60 秒后,从 REPL 中获取输出,并将其复制粘贴到我们的电子表格程序 LibreOffice Calc 中。制表符默认将时间和温度数据分隔到各自的列中。然后,使用这个数据表,生成了x-y散点图。

像这样绘制传感器数据,可以很容易地可视化温度读数随时间的变化。在这个特定的数据集中(在脚本执行开始时),温度传感器读取环境室温约为 26°C。在脚本执行约 10 秒后,传感器被触摸加热到接近 30°C。

在前面的图表中可以看到温度的急剧上升,发生在 10 秒的标记处。放开传感器后,它开始缓慢冷却,直到在 40 秒的时间内冷却到 27°C 以下。

另请参阅

以下是关于本示例的一些参考资料:

从光传感器读取亮度级别

在这个示例中,我们将学习如何创建一个循环,以重复从光传感器读取当前的光亮度。从传感器获取实时读数可以是一种有趣的方式,可以用不同的光源来测试传感器的灵敏度。

最终,本示例中的技术可以帮助您构建与环境交互的项目,取决于光的存在或缺失。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

操作步骤...

按照以下步骤学习如何从光传感器读取亮度级别:

  1. 在 REPL 中执行以下代码块:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.light
5
  1. 输出的数字是房间的光照水平。在正常光照条件下,这个数字应该很低。

  2. 现在,在运行以下代码块时,用手电筒照射光传感器:

>>> cpx.light
308
  1. 您应该看到值飙升到一个更高的值。以下代码应该放入main.py文件中,并且在执行时,将重复打印从光传感器读取的当前光照水平:
from adafruit_circuitplayground.express import cpx
import time

while True:
    print(cpx.light)
    time.sleep(0.1)

工作原理...

首先的代码行导入了 Circuit Playground Express 库和time模块。cpx对象公开了一个名为light的属性。这个属性将返回来自光传感器的当前光亮度读数。这个值使用勒克斯单位表示,这是一个用于测量照度的单位。

在这个脚本中,运行一个无限循环,打印当前的光亮度,然后在下一次迭代开始之前休眠 0.1 秒。

还有更多...

一个方便的方法来尝试光传感器是使用大多数智能手机上的手电筒。这个手电筒足够明亮,可以在 Circuit Playground Express 上创建光读数的显著差异。在运行本教程中的主要脚本时,观察当您将手电筒靠近或远离传感器时数值的变化。

该设备上的光传感器是光电晶体。这种设备是一种晶体管,当暴露在不同的光亮度下时,会导致电流流向其电路的差异。这些电气变化可以被读取,然后计算光亮度。

另请参阅

以下是关于这个教程的一些参考资料:

创建一个光度计

在这个教程中,我们将使用 10 个 NeoPixels 创建一个环,显示当前的光亮度。当光亮度增加和减少时,环会变得越来越小和越来越大。这个教程将向您展示一种可以使您的项目与光互动的方法。它还将展示将像素环转换为 10 级刻度的通用技术,您可以在各种项目中使用。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何创建光度计:

  1. 使用 REPL 来运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> import time
>>> 
>>> BLACK = 0x000000
>>> BLUE = 0x0000FF
>>> MAX_LUX = 330
>>> cpx.pixels.brightness = 0.10
>>> 
>>> def gauge(level):
...     cpx.pixels[0:level] = [BLUE] * level
...     
...     
... 
>>> gauge(2)
  1. 此时,您应该看到前两个像素变成蓝色。运行以下代码行,看到前五个像素变成蓝色:
>>> gauge(5)
  1. 以下代码应该放入main.py文件中,当执行时,它将创建一个光度计,随着光线变亮和变暗,它会变得越来越大和越来越小。
from adafruit_circuitplayground.express import cpx
import time

BLACK = 0x000000
BLUE = 0x0000FF
MAX_LUX = 330
cpx.pixels.brightness = 0.10

def gauge(level):
    cpx.pixels[0:level] = [BLUE] * level

last = 0
while True:
    level = int((cpx.light / MAX_LUX) * 10)
    if level != last:
        cpx.pixels.fill(BLACK)
        gauge(level)
        last = level
    time.sleep(0.05)

它是如何工作的...

首先的代码行导入了 Circuit Playground Express 库和time模块。为蓝色和黑色定义了颜色代码。然后将亮度设置为舒适的水平。然后定义了gauge函数。这个函数接收一个整数参数,其值应在 0 到 10 之间。这个值将用于确定在像素环中有多少像素会变成蓝色。这个函数创建了一个类似于经典刻度表的可视显示,根据数值的级别显示一个较小或较大的环。

然后,初始化了last变量。这个变量用于跟踪刻度级别自上次循环以来是否发生了变化。这个额外的步骤是为了防止像素因不必要地在每个循环中关闭和打开而闪烁。刻度级别是通过获取当前光亮度并将其除以其最大可能值来计算的,这在这块板上恰好是 330。

然后将该值乘以 10,这是仪表中的级数。如果仪表级别发生了变化,所有像素将被关闭,然后显示正确的仪表级别。在每次无限循环的迭代过程中执行此过程,每个循环之间延迟 50 毫秒,以在与光传感器交互时产生响应的感觉。

还有更多...

在这个教程中,显示仪表的功能被故意保留在自己的函数中,以鼓励可重用性。它可以在其他项目中使用,或者保留在自己的模块中,可以在需要使用板上的像素显示信息作为仪表时导入并使用。

此教程的另一个方面是为了解决当您不必要地重复打开和关闭像素时出现的光闪烁问题而必须进行的额外工作。当您一次改变许多像素的状态时,如果您的实现不小心,可能会出现闪烁问题。这在功能上并不是一个主要问题;更多的是在人们使用光度计时创造更愉悦的视觉体验。

另请参阅

以下是有关此教程的一些参考资料:

从运动传感器读取数据

在这个教程中,我们将创建一个循环,不断从加速度计中读取数据,并打印xyz轴的数据。打印输出将帮助我们实验传感器对摇动板或以不同方向倾斜的反应。一旦您了解了传感器的工作原理,就可以开始将其纳入项目中,使板对倾斜或加速做出反应。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行此教程中提供的代码。

如何做...

按照以下步骤学习如何从运动传感器中读取数据:

  1. 在将板放在水平表面并使按钮朝上的情况下,在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.acceleration.z
9.46126
  1. 在将板放置并使按钮朝下的情况下运行以下代码块:
>>> cpx.acceleration.z
-9.30804
  1. 以下代码应放入main.py文件中,并在执行时将不断打印加速度计的当前xyz轴数据:
from adafruit_circuitplayground.express import cpx
import time

while True:
    x, y, z = cpx.acceleration
    print('x: {x:.2f} y: {y:.2f} z: {z:.2f}'.format(**locals()))
    time.sleep(0.1)

工作原理...

第一行代码导入了 Circuit Playground Express 库和time模块。启动了一个无限循环,每次循环都会从加速度计中获取读数。读数被解压缩为xyz变量。然后,在脚本进入休眠 0.1 秒之前,打印出每个轴的值,并开始下一次迭代。

还有更多...

在运行此脚本时,尝试以不同方向倾斜板。该传感器非常敏感,可以为您提供与板倾斜相关的相当准确的读数。除了检测板的方向外,它还可以用于检测三个轴上的加速度。在运行脚本的同时,还可以以不同方向摇动板,您应该看到与加速度相关的读数上升。根据您摇动板的方向,不同的轴应该相应地做出反应。

另请参阅

以下是有关此教程的一些参考资料:

检测单次或双次轻敲

在本教程中,我们将学习如何配置板以检测单次或双次轻敲。将使用来自加速度计的传感器数据来检测这些轻敲事件。本教程向您展示如何创建可以对人们敲击板做出反应的应用程序。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何做…

按照以下步骤学习如何检测单次或双次轻敲:

  1. 在 REPL 中执行以下代码块:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.detect_taps = 1
>>> cpx.tapped
False
  1. 轻敲板一次,然后运行以下代码块。您应该会得到第一个值为True,表示检测到了轻敲,然后在下一次检查时得到一个False值,这表示自上次检查以来没有检测到新的轻敲:
>>> cpx.tapped
True
>>> cpx.tapped
False
  1. 以下代码应放入main.py文件中,当执行时,将不断打印自上次检查以来是否检测到了轻敲:
from adafruit_circuitplayground.express import cpx
import time

cpx.detect_taps = 1
while True:
    print('tap detected:', cpx.tapped)
    time.sleep(0.1)

工作原理…

首先导入 Circuit Playground Express 库和time模块。将敲击检测算法配置为通过将detect_taps设置为1来检测单次轻敲。

开始一个无限循环,每次循环将检索tapped属性的值。此属性仅在自上次检查以来加速度计检测到单次轻敲时才返回True。然后调用sleep函数,使其在开始下一次迭代之前延迟 0.1 秒。

还有…

通过将detect_taps设置为2来修改脚本。再次运行它,尝试在板上执行一些单次轻敲。它不应该注册任何内容。

现在尝试执行一些双次轻敲。您应该会看到它们被检测到。尝试改变您用于轻敲板的力量的大小,看看在检测到轻敲之前需要什么级别的力量。

另请参阅

以下是有关本教程的一些参考资料:

检测摇晃

在本教程中,我们将学习如何轮询shake方法并在板被摇晃时打印它。创建可以响应设备被摇晃的项目可能非常有趣。还可以配置板,以便您可以指定在注册为摇晃之前需要轻或重摇晃。这可以开辟新的创造性方式,使人们与您的设备互动。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何做…

按照以下步骤学习如何检测摇晃:

  1. 使用 REPL 运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.shake(20)
False
  1. 在运行以下代码块时,反复摇晃板子:
>>> cpx.shake(20)
True
  1. 以下代码应放入main.py文件中,执行时,将不断打印板子当前是否在摇晃:
from adafruit_circuitplayground.express import cpx
import time

while True:
    print('shake detected:', cpx.shake())
    time.sleep(0.1)

工作原理...

首先导入 Circuit Playground Express 库和time模块。启动一个无限循环,每次循环都会打印shake方法的结果。该方法将根据板子当前是否在摇晃而返回TrueFalse。然后调用sleep函数,使下一次迭代之前延迟 0.1 秒。

还有更多...

修改脚本,并将shake函数的值作为第一个参数设置为20。现在,运行脚本并尝试摇晃它。您会发现需要更少的力量才能使板子注册摇晃事件。第一个参数shake_threshold的默认值为30,数值越低,板子对检测摇晃的敏感度就越高。不要将值设置为10或更低,否则它会变得过于敏感,并不断地认为它已经检测到了摇晃。

另请参阅

有关此方案的参考资料,请参阅:

摇晃时发出哔哔声

在这个方案中,我们将学习如何使板子在每次摇晃时发出哔哔声。这是一种有趣的方式,让板子对运动做出响应。相同的方法也可以用来使像素对摇晃做出响应,而不仅仅是发出哔哔声。

准备工作

您需要访问 Circuit Playground Express 上的 REPL,以运行本方案中提供的代码。

如何做...

按照以下步骤学习如何使板子在每次摇晃时发出哔哔声:

  1. 在 REPL 中运行以下代码行;您应该听到一声哔哔声:
>>> from adafruit_circuitplayground.express import cpx
>>> cpx.play_tone(900, 0.2)
  1. 以下代码应放入main.py文件中,执行时,每次摇晃板子都会发出哔哔声:
from adafruit_circuitplayground.express import cpx
import time

while True:
    if cpx.shake(20):
        cpx.play_tone(900, 0.2)
    time.sleep(0.1)

工作原理...

首先导入 Circuit Playground Express 库和time模块。启动一个无限循环,检查板子当前是否在摇晃。如果检测到摇晃事件,则会播放一个持续 0.2 秒的短暂哔哔声。之后,检查板子是否休眠 0.1 秒,然后再次开始该过程。

还有更多...

您可以将滑动开关纳入此方案中,以便人们可以根据滑动开关的位置选择高或低的摇晃阈值。这样,滑动开关可以用来使检测摇晃变得容易或困难。您可以创建一个游戏,其中每次摇晃都会增加一个计数器并播放一声哔哔声。

当计数器达到 10 时,您可以播放一段胜利的旋律。然后,谁先达到 10 次摇晃就赢了。使用摇晃而不是按按钮来与设备交互,可以是一种有趣的方式来改变人们与您的项目互动的方式。

另请参阅

有关此方案的参考资料,请参阅:

第六章:按钮砸游戏

在本章中,我们将创建一个名为按钮砸的双人游戏,您可以直接在 Circuit Playground Express 上玩,无需计算机。每个玩家必须尽快按下他们的按钮。每次按下按钮都会将该玩家的分数增加一分。通过 NeoPixels 可以直观地显示玩家当前的分数。首先达到 20 分的玩家将赢得游戏。

为了创建这个游戏,我们将通过 NeoPixels 结合按钮输入和灯光输出,并通过内置扬声器进行音频输出。本章包含许多配方,每个配方展示游戏的不同部分,我们将所有这些部分组合在最后一个配方中,以制作完整的游戏。

在本章中,我们将涵盖以下主题:

  • 创建一个类来检测按钮状态变化

  • 创建自己的 Python 模块

  • 将按钮交互添加到事件循环中

  • 创建一个生成器来获取像素颜色

  • 使用 ScoreBoard 类显示分数

  • 使用 ScoreBoard 类检测获胜者

  • 将 ScoreBoard 类添加到事件循环中

技术要求

本章的代码文件可以在 GitHub 存储库的Chapter06文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

本章的许多配方需要将三个音频文件传输到 Circuit Playground Express 板上。这些文件分别为start.wavwin1.wavwin2.wav。它们都可以从 GitHub 存储库的Chapter06文件夹中下载。它们应该保存在与您的main.py文件的顶级文件夹中。

本章中的许多配方都使用了 Circuit Playground Express 库,通常会在脚本的第一行导入,代码的下一行是:

from adafruit_circuitplayground.express import cpx

这个库将帮助我们与板子上的按钮、像素和扬声器进行交互。

Circuit Playground Express 电源

本章将介绍的游戏可以直接在 Circuit Playground Express 上运行,无需连接计算机。这是一个很好的机会,介绍您在这种类型的板子上使项目便携的选项。该板可以从多种不同的便携式电源接收电源。

我们将探讨解决便携式电源问题的两种不同方法。每种方法都使用板子上的不同连接器。我们将首先看一下 Micro B USB 连接器,它出现在以下图片中:

由 adafruit.com 提供

这个连接器可以用来将板子连接到计算机进行供电,并将您的代码和音频文件传输到板子上。一种方法是通过 USB 将便携式移动电源连接到板子上。以下照片显示了板子由其中一个移动电源供电:

这种方法的好处在于,这些移动电源有各种不同的尺寸和容量,因此您有很多选择,可以选择最符合您需求的移动电源。它们是可充电的,可重复使用,并且可以很容易地在大多数电子零售商处购买。

我们将看一下的第二个连接器是 JST 电池输入,它出现在下一张照片中:

由 adafruit.com 提供

有许多便携式电池源可以连接到这个连接器。许多这些电池座价格相当便宜,它们通常支持流行的电池尺寸,如 AAA 电池。由于板子没有内置电池充电功能,您可以安全地使用常规电池或可充电电池。以下照片显示了一个带有开关的电池座:

下一张照片显示了同一个支架,盖子打开,以便查看它使用的三节 AAA 电池:

在上一张照片中显示的电池盒可以在www.adafruit.com/product/727购买到约 2 美元。

创建一个检测按钮状态变化的类

在本教程中,您将学习如何定义一个类,当实例化时,可以跟踪板上特定按钮的按钮按下事件。我们将在本章的后续教程中使用这个类,以便创建对象,用于跟踪按钮 A 和按钮 B 的按下事件。

您将学习如何将常见的代码块放入函数和类中,这将提高项目中的代码重用。它还可以帮助大型项目,以便将大量逻辑分解为更小、独立的函数和类的独立块。这个按钮事件类的实现将故意保持通用,以便它可以轻松地在不同的项目中重用。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL,以运行本教程中将呈现的代码。

如何做...

让我们来看看本教程中需要的步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> class ButtonEvent:
...     def __init__(self, name):
...         self.name = name
...         self.last = False
...         
...         
... 
>>> button = ButtonEvent('button_a')
  1. 在这个阶段,我们已经定义了我们的类并给它一个构造函数。运行下一个代码块来创建这个类的第一个实例,并检查它的name属性:
>>> button = ButtonEvent('button_a')
>>> button
<ButtonEvent object at 20003410>
>>> button.name
'button_a'
  1. 以下代码块将访问cpx库中的属性,指示推按钮是否被按下:
>>> pressed = getattr(cpx, button.name)
>>> pressed
False
  1. 在按住按钮 A 的情况下运行以下代码块。它应该显示推按钮的状态为pressed
>>> pressed = getattr(cpx, button.name)
>>> pressed
True
  1. 以下代码应该放入main.py文件中,当执行时,每当按下按钮 A 时,它将重复打印一条消息:
from adafruit_circuitplayground.express import cpx

class ButtonEvent:
    def __init__(self, name):
        self.name = name
        self.last = False

    def is_pressed(self):
        pressed = getattr(cpx, self.name)
        changed = (pressed != self.last)
        self.last = pressed
        return (pressed and changed)

button = ButtonEvent('button_a')
while True:
    if button.is_pressed():
        print('button A pressed')

至此,编码部分就完成了;现在,让我们看看它是如何工作的。

工作原理...

ButtonEvent类被定义为帮助我们跟踪按钮 A 或按钮 B 的按下事件。当你实例化这个类时,它期望一个参数,指定我们要跟踪的按钮的名称。名称保存在实例的一个属性name中,然后最后一个变量被初始化为值False。每次我们检查新事件时,这个变量将跟踪按钮状态的上次已知值。

每次我们想要检查是否自上次检查以来发生了新的按钮按下事件时,都会调用is_pressed方法。它首先检索物理推按钮的当前状态,以找出它是否被按下。我们将检查该值与其上次已知值,以计算是否发生了变化;我们将这个结果保存在一个名为changed的变量中。然后我们保存当前值以供将来参考。该方法将在按钮状态发生变化且当前被按下时返回True值。

在类定义之后,我们创建了一个该类的实例,用于跟踪按钮 A 的按下事件。然后,启动一个无限循环,不断检查新的按钮按下事件,并在每次检测到其中一个时打印一条消息。

还有更多...

在本教程中,我们只使用了一次该类,以跟踪单个按钮的按下事件;但是因为我们没有在类定义中硬编码任何特定的按钮值,所以我们可以重用这段代码来跟踪许多不同的按钮。我们同样可以轻松地监视按钮 A 和按钮 B 的按下事件。许多 MicroPython 板可以连接许多额外的推按钮。在这些情况下,制作一个通用的观察按钮的类是非常有用的。

还涉及一些逻辑来跟踪先前的按钮状态,以便我们可以检测我们感兴趣的内容,即新的按钮按下事件。通过将所有这些代码放入一个包含的类中,我们可以使我们的代码更易读和更易管理。

另请参阅

以下是一些参考资料:

创建您自己的 Python 模块

在这个示例中,您将学习如何将您创建的代码放入自己的 Python 模块中。我们将从前面的示例中获取代码,该示例帮助我们跟踪按钮按下事件,并将其放入自己的专用模块中。

然后,我们将把这个新创建的模块导入到我们的主 Python 脚本中,并使用它的类定义来跟踪按钮按下事件。当您开始在大型项目上工作并希望将代码拆分为不同的模块时,这可能是一个非常有用的方法。当您发现一个有用的模块并希望将其纳入自己的项目时,这也可能会有所帮助。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本示例中提供的代码。

如何做...

让我们来看看这个食谱所需的步骤:

  1. 以下代码应该放入一个名为button.py的新文件中;这将成为我们以后可以导入的 Python 模块:
from adafruit_circuitplayground.express import cpx

class ButtonEvent:
    def __init__(self, name):
        self.name = name
        self.last = False

    def is_pressed(self):
        pressed = getattr(cpx, self.name)
        changed = (pressed != self.last)
        self.last = pressed
        return (pressed and changed)
  1. 在 REPL 中运行以下代码行:
>>> from button import ButtonEvent
>>> ButtonEvent
<class 'ButtonEvent'>
>>> 
  1. 在这个阶段,我们已经能够从我们的新 Python 模块中导入一个类。下一行代码将创建一个新对象,我们可以用它来检测新的按钮按下事件:
>>> button = ButtonEvent('button_a')
>>> button.is_pressed()
False
  1. 在按住按钮 A 时运行以下代码块,它应该会检测到按钮按下事件:
>>> button = ButtonEvent('button_a')
>>> button.is_pressed()
  1. 以下代码应该放入main.py文件中,当执行时,每当按下按钮 A 时,它将重复打印一条消息:
from button import ButtonEvent

button = ButtonEvent('button_a')
while True:
    if button.is_pressed():
        print('button A pressed')

它是如何工作的...

在我们之前的示例中,我们习惯于使用main.py文件。创建一个新的 Python 模块就像创建一个新文件并将我们的代码放入其中一样简单。我们已经将ButtonEvent类放入了自己的 Python 模块中,名为button

现在,我们可以导入这个类并使用该类创建对象。代码的其余部分创建了一个对象来监视按钮按下事件,并在检测到事件时打印一条消息。

还有更多...

当您创建自己的自定义 Python 模块时,重要的是要注意您给模块的名称。任何 Python 模块的相同命名限制也适用于您的 MicroPython 代码。例如,您不能创建一个模块其中包含空格字符。您还应该确保不要将模块命名为现有的 MicroPython 或 CircuitPython 模块的相同名称。因此,您不应该将您的模块命名为boardmath,因为这些名称已经被使用。

防止这种情况发生的最简单方法是在创建新模块之前进入 REPL,并尝试按该名称导入一个模块。如果出现ImportError,那么您就知道该名称尚未被使用。

另请参阅

以下是一些参考资料:

将按钮交互添加到事件循环

在这个食谱中,我们将开始构建我们的主事件循环。每个玩家将被分配一个单独的按键,在游戏中按下。玩家 1 将被分配按键 A,玩家 2 将被分配按键 B。事件循环将不断检查这些按钮,寻找新的按钮按下事件。当检测到新的按键按下事件时,它将打印一条消息。

这将在本章的下一个食谱中进一步扩展,以添加 Button Bash 游戏的其余功能。事件循环可以在许多类型的软件应用程序中找到。探索它们的使用可以帮助您在必须制作自己的事件循环时,或者在必须与内置事件循环交互时。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本食谱中提供的代码。

如何做到...

让我们来看看这个食谱所需的步骤:

  1. 在 REPL 中执行下一个代码块:
>>> from button import ButtonEvent
>>> 
>>> buttons = {}
>>> buttons[1] = ButtonEvent('button_a')
>>> buttons[2] = ButtonEvent('button_b')
>>> buttons[1].is_pressed()
False
  1. 在这个阶段,我们已经创建了两个对象来监视两个按键。在运行下一个代码块时,按住按键 A:
>>> buttons[1].is_pressed()
True
  1. 以下代码应放入main.py文件中,当执行时,每当按下按键 A 或按键 B 时,它将重复打印一条消息:
from button import ButtonEvent

def main():
    buttons = {1: ButtonEvent('button_a'), 2: ButtonEvent('button_b')}
    while True:
        for player, button in buttons.items():
            if button.is_pressed():
                print('button pressed for player', player)

main()

它是如何工作的...

首先,从button模块导入ButtonEvent类。定义了一个名为main的函数,其中包含了我们主要事件循环的代码。代码的最后一行调用main函数来启动主事件循环的执行。主事件循环首先定义了一个字典,用于跟踪每个玩家的按钮。它定义了一个映射,玩家 1 将被分配按键 A,玩家 2 将被分配按键 B。

启动了一个无限循环,它将循环遍历每个ButtonEvent对象,并检查是否发生了按钮按下事件。如果检测到按钮按下事件,它将打印哪个玩家按下了按钮。

还有更多...

随着您的代码变得越来越大,将主要代码块放入自己的函数中,并调用它来启动执行是一个好主意。随着程序规模的增大,这将使跟踪变量变得更容易,因为它们都将在这个主函数的范围内,而不是驻留在全局命名空间中。这有助于减少一些可能出现在共享同一个大型全局命名空间的大块代码中的丑陋 bug。

本食谱中另一个要注意的事情是使用字典来维护玩家和他们的按钮的关联。字典数据结构是这种需求的一个非常自然的选择。如果我们使用的硬件有更多的按键,我们可以只需向我们的数据结构中为每个玩家添加一个项目。充分利用数据结构是一个很好的主意;它使调试和软件设计变得更加容易。

另请参阅

以下是一些参考资料:

创建一个生成器来获取像素颜色

在这个食谱中,我们将准备用于控制游戏中像素的代码。棋盘上有 10 个像素,所以每个玩家将获得 5 个像素,以表示他们目前获得了多少分。现在,每当玩家按下按钮时,他们都会获得一个点,游戏需要得分 20 分才能赢。因此,我们必须呈现 0 到 20 的分数,但只有 5 个像素。

我们将通过让每个像素的得分由四种颜色表示来实现这一点。因此,对于前四个点,第一个像素将经历黄色、深橙色、红色和品红色。然后,当你达到得分 5 时,第二个像素将点亮黄色并经历相同的循环。

将使用生成器获取与每个玩家每个得分相关的颜色和像素位置的列表。玩家 1 将使用按钮 A,并将拥有紧挨着该按钮的五个像素。这些是像素 0 到 4。玩家 2 将使用按钮 B,并将拥有紧挨着该按钮的五个像素。这些是像素 5 到 9。

两组像素将从 USB 连接器附近开始点亮,并向终点线赛跑,终点线将是 JST 电池输入。这使得玩家 1 的序列为 0 到 4,玩家 2 的序列为 9 到 5。这个示例将涵盖生成器的一个有趣用例,它在一些项目中可能会派上用场,当你需要基于一些复杂的逻辑生成一系列值时。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

操作步骤

让我们来看看这个示例所需的步骤:

  1. 使用 REPL 运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> BLACK = 0x000000
>>> SEQUENCE = [
...     0xFFFF00,   # Yellow
...     0xFF8C00,   # DarkOrange
...     0xFF0000,   # Red
...     0xFF00FF,   # Magenta
...     ]
>>> cpx.pixels.brightness = 0.02
>>> cpx.pixels[0] = SEQUENCE[0]
  1. 在这个阶段,第一个像素应该点亮黄色。在下一段代码中,我们将定义生成器并调用它来为玩家 1 和玩家 2 生成位置和颜色的列表。列表中有 21 个项目。第一个项目代表得分 0,这是一个特殊情况,如果没有人得分,我们希望所有像素都关闭。剩下的 20 个项目代表得分 1 到 20:
>>> PLAYER_PIXELS1 = [0, 1, 2, 3, 4]
>>> PLAYER_PIXELS2 = [9, 8, 7, 6, 5]
>>> 
>>> def generate_colors(positions):
...     yield 0, BLACK
...     for i in positions:
...         for color in SEQUENCE:
...             yield i, color
...             
...             
... 
>>> COLORS = dict()
>>> COLORS[1] = list(generate_colors(PLAYER_PIXELS1))
>>> COLORS[2] = list(generate_colors(PLAYER_PIXELS2))
>>> 
>>> COLORS[1]
[(0, 0), (0, 16776960), (0, 16747520), (0, 16711680), (0, 16711935), (1, 16776960), (1, 16747520), (1, 16711680), (1, 16711935), (2, 16776960), (2, 16747520), (2, 16711680), (2, 16711935), (3, 16776960), (3, 16747520), (3, 16711680), (3, 16711935), (4, 16776960), (4, 16747520), (4, 16711680), (4, 16711935)]
>>> len(COLORS[1])
21
  1. 以下代码应放入colors.py文件中,然后可以在下一个示例中导入,以便访问游戏的颜色数据:
BLACK = 0x000000
SEQUENCE = [
    0xFFFF00,   # Yellow
    0xFF8C00,   # DarkOrange
    0xFF0000,   # Red
    0xFF00FF,   # Magenta
]
PLAYER_PIXELS1 = [0, 1, 2, 3, 4]
PLAYER_PIXELS2 = [9, 8, 7, 6, 5]

def generate_colors(positions):
    yield 0, BLACK
    for i in positions:
        for color in SEQUENCE:
            yield i, color

COLORS = dict()
COLORS[1] = list(generate_colors(PLAYER_PIXELS1))
COLORS[2] = list(generate_colors(PLAYER_PIXELS2))

工作原理

首先,SEQUENCE列表表示将显示在每个像素上以表示玩家得分的四种颜色。然后定义了每个玩家将点亮的五个像素的位置和顺序。然后定义了generate_colors生成器。调用时,它将生成一系列元组,每个元组包含特定得分表示的位置和颜色。这将被转换为每个玩家的列表。

通过这种方式,我们可以立即查找任何得分的相关颜色和像素位置。每个玩家和每个得分的这些颜色和位置值存储在一个名为COLORS的字典中,可以用来通过玩家、数字和得分查找这些值。

还有更多...

Python 的迭代器是该语言的一个非常强大的特性。生成器是迭代器的一种类型,它让你以简洁的方式实现一些强大的解决方案。它们在这个示例中被用作一种辅助方式,用于构建一个具有特殊第一情况和两个嵌套级别的值的列表。

通过将所有这些逻辑放入一个生成器中,我们可以将其包含在一个地方,然后将其用作构建更复杂结构的构建块。在这个示例中,单个生成器被用来构建玩家 1 和玩家 2 的颜色查找数据。

另请参阅

以下是一些参考资料:

使用 ScoreBoard 类显示得分

在这个示例中,我们将准备用于跟踪每个玩家得分并在像素上显示他们当前得分的代码。我们将创建一个名为ScoreBoard的新类,并将其放入一个名为score的新模块中。

这个配方将向您展示一种在基于 MicroPython 的游戏中实现记分牌功能的方法。这个配方将从开始游戏的初始逻辑开始,跟踪得分,然后在像素上显示得分。在接下来的配方中,我们将添加更多功能来处理得分的增加和检测玩家中的一个何时赢得比赛。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本配方中提供的代码。

如何做...

让我们来看看这个配方所需的步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> from colors import COLORS
>>> 
>>> class ScoreBoard:
...     def __init__(self):
...         self.score = {1: 0, 2: 0}
...         cpx.pixels.brightness = 0.02
...         cpx.play_file('start.wav')
...         
...         
... 
>>> board = ScoreBoard()
>>> board.score[1]
0
  1. 运行前面的代码后,您应该听到板子播放游戏启动音频,它说1 2 3 Go!。然后,您应该看到玩家 1 的当前得分为0

  2. 以下代码应该放入score.py文件中,然后我们可以在其他地方导入并使用它:

from adafruit_circuitplayground.express import cpx
from colors import COLORS

class ScoreBoard:
    def __init__(self):
        self.score = {1: 0, 2: 0}
        cpx.pixels.brightness = 0.02
        cpx.play_file('start.wav')

    def show(self, player):
        score = self.score[player]
        pos, color = COLORS[player][score]
        cpx.pixels[pos] = color
  1. 以下代码将从score模块导入ScoreBoard类,将第一个玩家的得分设置为3,然后在像素上显示这个得分。第一个像素应该变成红色:
>>> from score import ScoreBoard
>>> 
>>> board = ScoreBoard()
>>> board.score[1] = 3
>>> board.show(1)

它是如何工作的...

ScoreBoard类在score模块中定义。当类首次实例化时,它准备好开始比赛。它将玩家 1 和 2 的分数初始化为 0。然后,它设置像素的亮度并播放音频剪辑,向玩家宣布比赛的开始。

show方法期望一个参数,这个参数将是要显示得分的玩家的编号。然后,它获取玩家得分的值,并将其与玩家编号一起使用,查找必须设置的像素的颜色和位置。然后,将该像素的颜色设置为正确的颜色。

还有更多...

我们已经开始构建逻辑,向玩家展示当前的记分牌。在竞争激烈的游戏中,重要的是要制作一个有趣和响应灵敏的记分牌,以保持两名玩家参与并努力互相击败。

更新记分牌的代码必须以执行良好的方式实现。如果对记分牌的每次更新都是一个迟钝的过程,玩家会感觉到并对不响应的应用感到沮丧。获取像素的颜色和位置的所有代码都以高效的方式实现,以确保其性能。

另请参阅

以下是一些参考资料:

使用ScoreBoard类检测获胜者。

在这个配方中,我们将扩展ScoreBoard类,以便能够更新玩家得分并检测玩家何时赢得比赛。一旦玩家中的一个赢得了比赛,板子将通过播放带有宣布的音频剪辑来宣布哪个玩家赢得了比赛。

这个配方是完成ScoreBoard类中逻辑的最后一部分。一旦完成,我们就可以将其合并到主事件循环中,并在下一个配方中完成游戏。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本配方中提供的代码。

如何做...

让我们来看看这个配方所需的步骤:

  1. 以下代码应该放入score.py文件中,然后我们可以在其他地方导入并使用它:
from adafruit_circuitplayground.express import cpx
from colors import COLORS

class ScoreBoard:
    def __init__(self):
        self.score = {1: 0, 2: 0}
        cpx.pixels.brightness = 0.02
        cpx.play_file('start.wav')

    def scored(self, player):
        self.score[player] += 1
        self.show(player)
        if self.score[player] == 20:
            cpx.play_file('win%s.wav' % player)

    def show(self, player):
        score = self.score[player]
        pos, color = COLORS[player][score]
        cpx.pixels[pos] = color
  1. 以下代码将从score模块导入ScoreBoard类,并打印出玩家的当前得分:
>>> from score import ScoreBoard
>>> board = ScoreBoard()
>>> board.score
{2: 0, 1: 0}
  1. 下一段代码将增加玩家 1 的得分,导致第一个像素点变成黄色,并打印出当前得分。得分应该显示玩家 1 得到 1 分:
>>> board.scored(1)
>>> board.score
{2: 0, 1: 1}

它是如何工作的...

ScoreBoard类添加了一个额外的方法,当其中一个玩家得分时,它将在score数据结构中递增。scored方法接收一个参数,即玩家编号,并增加该玩家的得分。

然后它会更新像素以显示玩家的最新得分,然后检查玩家的得分是否已经达到 20 分。如果玩家已经达到 20 分,棋盘将播放一条宣布哪个玩家赢得了比赛的公告。

还有更多...

声音和光是与玩家在视频游戏中进行互动的好方法。在这个课程中,声音被有效地用来宣布游戏的开始和结束。在游戏过程中,光被用来激励每个玩家更快地按下按钮,以便他们能够第一个到达终点线。尽管在这个课程中发生了很多事情,但每种方法只有三到四行代码,这使得更容易看到每个部分所涉及的内容。这是将代码分解成较小块的一种方法,通过将不同的部分放入不同的方法中。

另请参阅

以下是一些参考资料:

将 ScoreBoard 类添加到事件循环

本章的最后一个食谱将在本章中将所有先前的食谱结合起来,以创建最终的 Button Bash 游戏。我们将通过添加在上一个食谱中实现的ScoreBoard类来升级事件循环。这是谜题的最后一块。

最终结果是一个只有六行代码的主循环。我们能够通过将本章中创建的三个 Python 模块中的大部分游戏逻辑保留下来来实现这一结果。当您发现代码基础变得过大且集中在一个文件或一个函数中时,您可以在自己的项目中使用类似的方法。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本食谱中提供的代码。

如何做...

让我们来看看这个食谱所需的步骤:

  1. 以下代码应该放入main.py文件中,然后您就可以开始玩 Button Bash 游戏了:
from button import ButtonEvent
from score import ScoreBoard

def main():
    buttons = {1: ButtonEvent('button_a'), 2: ButtonEvent('button_b')}
    board = ScoreBoard()
    while True:
        for player, button in buttons.items():
            if button.is_pressed():
                board.scored(player)

main()
  1. 如果您拥有本章开头提到的便携式电源供应之一,那么您可以将棋盘从计算机上断开,并连接该电源供应。

  2. 现在您可以随身携带游戏,并在每个玩家之间进行回合。要开始下一场比赛,请按下棋盘中央的复位按钮,以开始新的一轮。

工作原理...

我们首先导入ButtonEventScoreBoard对象;它们是我们需要实现事件循环的两个主要对象。在创建了我们的按钮字典之后,我们实例化了一个名为board的新ScoreBoard对象。

这将宣布游戏已经开始,然后我们将进入一个无限循环,该循环将不断检查按钮按下事件。一旦检测到这些事件中的一个,它将调用棋盘对象上的scored方法来增加特定玩家的分数。如果任何玩家已经达到最终得分,那么他们将被宣布为赢家。

还有更多...

现在我们已经有了游戏的基本版本,有许多方法可以改变它并增强它。我们可以创建两种可以通过滑动开关选择的游戏模式。可以有简单和困难模式,其中一个需要得分 10 分,另一个需要得分 20 分。当棋盘启动时,它会检查开关以加载颜色和最终得分的正确参数。

您可以制作一个三局两胜的模式,两名玩家必须反复进行三轮比赛,最终获得两局胜利的人获胜。要看游戏的实际操作,请查看下一张照片,看看两名玩家在 Button Bash 上激烈对抗。

参见

以下是一些参考资料:

第七章:水果曲调

在本章中,您将学习如何使用 Circuit Playground Express 和一些香蕉创建一个乐器。我们将把四根香蕉连接到板上的触摸板,这样您就可以触摸每根香蕉时播放特定的音乐声音。我们将通过点亮每个触摸板旁边的像素来为项目添加一些视觉反馈。这个项目将展示一个创造性、有趣的方式,让您的电容触摸项目生动起来。

通过在项目中使用意想不到的物体,如香蕉,您可以为平凡的 MicroPython 项目增添独特的风味。

在本章中,我们将介绍以下配方:

  • 创建一个类来响应触摸事件

  • 创建一个函数来启用扬声器输出

  • 创建一个播放音频文件的函数

  • 使用 NeoPixel 对象控制像素

  • 创建一个触摸处理程序来播放声音

  • 创建一个触摸处理程序来点亮像素

  • 创建一个事件循环来处理所有触摸事件

技术要求

本章的代码文件可以在 GitHub 存储库的Chapter07文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

本章中的许多配方需要将四个音频文件传输到 Circuit Playground Express 板上。它们都可以从 GitHub 存储库的Chapter07文件夹中下载。它们应该保存在与您的main.py文件同级的文件夹中。

Circuit Playground Express 触摸板

Circuit Playground Express 带有七个电容触摸板。它们中的每一个都可以连接到任何可以导电的物体,触摸该物体将触发传感器。您可以使用良好的电导体,如金属,甚至较弱的导体,如香蕉。

水能导电,许多水果的表面含有足够的水分,可以被触摸板检测到触摸事件。许多水果,如香蕉、酸橙、橙子和苹果,都可以胜任。您可以使用鳄鱼夹将水果连接到触摸板。下一张照片显示了一捆鳄鱼夹:

这些鳄鱼夹子有各种不同的颜色。最好为每个触摸板使用不同颜色的导线。这样会更容易追踪哪个水果连接到哪个触摸板。在这个项目中,我们将使用绿色、红色、黄色和白色的导线。我们将把每个触摸板旁边的像素颜色也设置为绿色、红色、黄色和白色。下一张照片显示了一个香蕉连接到一个触摸板:

鳄鱼夹非常有效,因为它们不需要任何焊接,可以轻松连接到板和各种物体。鳄鱼夹的牙齿也会产生良好的抓地力,从而可以在板和香蕉之间建立良好的电连接。下一张照片更近距离地展示了连接到香蕉上的鳄鱼夹的牙齿:

下一张照片显示了连接到触摸板的鳄鱼夹的更近距离视图:

在之前的章节中,我们使用 Circuit Playground Express 库与板上的不同组件进行交互。当您使用该库播放音频文件时,该库将阻塞您的代码,直到文件播放完成。在这个项目中,我们希望能够立即响应触摸事件,并在当前音频文件播放完成之前播放新的声音。

只有使用直接控制音频播放和触摸板的 CircuitPython 库才能实现这种程度的控制。因此,本章中的代码将不使用 Circuit Playground Express 库。通过采用这种方法,我们还将看到如何更精细地控制板上的组件。

创建一个用于响应触摸事件的类

在这个教程中,您将学习如何定义一个类,以帮助您处理特定触摸板上的触摸事件。当您创建这个类的实例时,您需要指定触摸板的名称和一个回调函数,每次触摸事件开始和结束时都会调用该函数。我们可以将这个类用作构建块,为将连接到香蕉的四个触摸板中的每一个调用一个回调。您可以在自己的项目中使用这种代码风格,每当您想要处理一系列事件时,都可以使用一组回调函数。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何操作...

让我们来看看这个教程所需的步骤:

  1. 在 REPL 中运行以下代码行:
>>> from touchio import TouchIn
>>> import board
>>> 
>>> def handle(name, current):
...     print(name, current)
...     
...     
... 
>>> handle('A1', True)
A1 True
>>> handle('A1', False)
A1 False
>>> 
  1. 在这个阶段,我们已经定义了一个函数,它将通过打印触摸板的名称以及触摸板是否被触摸来处理触摸事件。

  2. 运行下一块代码以创建一个检查触摸事件的类。在定义类之后,它将创建一个实例,然后打印出触摸板的当前触摸状态:

>>> 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
  1. 按住触摸板 A1 上的手指,同时运行下一块代码:
>>> event.touch.value
True
  1. 运行下一块代码以创建一个具有处理触摸事件的方法的类:
>>> 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)
  1. 按住触摸板 A1 上的手指,同时运行下一块代码:
>>> event.process()
A1 True
  1. 以下代码应放入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值。然后,从board Python 模块中检索命名触摸板的引脚值。该引脚用于创建TouchIn实例,也保存为对象的属性。最后,在初始化过程中设置了该触摸板的阈值。

在类上定义的另一个方法将定期调用,以检查触摸板状态的任何变化,并通过调用定义的回调函数来处理这种状态变化。这是通过获取当前触摸状态并将其与最后已知值进行比较来完成的。如果它们不同,就会调用回调函数并保存该值以供将来参考。

定义了一个简单的函数来处理任何触摸事件,只需打印出发生状态变化的触摸板的名称和当前状态。

在这些类和函数定义之后,我们创建了这个类的一个实例,它将监视触摸板 A1。然后我们进入一个无限循环,不断检查状态变化,并在每次发生状态变化时打印出一条消息。

还有更多...

在触摸板上设置触摸阈值总是一个好主意。如果不这样做,当与触摸板交互时会出现很多误报。所选择的值400是适合将香蕉与鳄鱼夹连接的特定设置的值。最好连接实际用于项目的对象,然后将该值微调为合适的值。

在这个示例中,我们混合了函数和类的用法。这种方法在 Python 中是完全可以的,它让你同时拥有两种最好的方式。我们需要在每次调用process方法之间保持状态,这就是为什么我们选择了一个类来实现这个目的。回调函数不需要在调用之间保持任何状态,所以一个简单的函数就可以胜任。

另请参阅

以下是一些参考资料:

创建一个函数来启用扬声器输出

在这个示例中,您将学习如何创建一个函数,当调用时,将启用扬声器。如果在音频播放之前不启用扬声器,那么它将通过引脚 A0 播放,可以连接耳机。

这个项目将使用板子上的扬声器而不是耳机,所以我们需要在脚本开始时使用这个函数来启用扬声器。除了向您展示如何启用扬声器之外,这个示例还将向您介绍数字控制输入/输出引脚的方法。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

如何做...

让我们来看看这个示例所需的步骤:

  1. 在 REPL 中执行下一个代码块:
>>> from digitalio import DigitalInOut
>>> import board
>>> 
>>> 
>>> speaker_control = DigitalInOut(board.SPEAKER_ENABLE)
>>> speaker_control
<DigitalInOut>
  1. 在这个阶段,我们已经创建了一个连接到启用扬声器的引脚的对象。运行下一个代码块来启用扬声器:
>>> speaker_control.switch_to_output(value=True)
  1. 重新加载板子并再次进入 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函数。它不接收任何参数,因为板子上只有一个扬声器需要启用,并且不返回任何东西,因为一旦扬声器启用,它的引脚就不需要再进行交互。DigitalInOut对象用于与启用扬声器的引脚进行交互。创建了这个对象后,调用switch_to_output方法来启用扬声器输出。在定义函数之后,调用它来启用扬声器。

还有更多...

在这个示例中使用的DigitalInOut对象可以用来与各种引脚进行交互。例如,在这块板子上,它可以用来连接读取来自按键 A 和按键 B 的输入的引脚。一旦正确连接和配置这些按键引脚,就可以开始轮询引脚的值,以检查按键是否被按下。

另请参阅

以下是一些参考资料:

创建一个函数来播放音频文件

在这个示例中,您将学习如何创建一个函数,当调用时,将在内置扬声器上播放特定的音频文件。这个示例将说明如何访问音频输出设备,以及如何读取.wav文件的内容,将其转换为音频流,并将该音频流馈送到板载音频播放设备。这个示例中展示的技术可以用于各种需要更精细控制音频文件播放方式的项目中。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

让我们来看看本教程所需的步骤:

  1. 使用 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>
>>> 
  1. 在这个阶段,我们已经启用了扬声器,并创建了一个对象来向扬声器提供音频数据。当您运行下一块代码时,您应该听到扬声器上播放钢琴音符:
>>> file = open('piano.wav', "rb")
>>> audio = WaveFile(file)
>>> speaker.play(audio)
>>> 
  1. 运行下一块代码以再次听到相同的钢琴音符,但这次是通过函数调用播放:
>>> def play_file(speaker, path):
...     file = open(path, "rb")
...     audio = WaveFile(file)
...     speaker.play(audio)
...     
...     
... 
>>> play_file(speaker, 'piano.wav')
  1. 以下代码应放入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方法,以便在主脚本结束执行之前给板子一个播放音频流的机会。

如果您从文件中排除这行代码并重新加载代码,那么脚本将在板子有机会播放文件之前退出,您将听不到任何音频播放。

还有更多...

使用此函数,您可以通过仅传递音频输出对象和文件路径来播放任意数量的音频文件。您还可以将此教程用作进一步尝试与此板附带的音频播放库的起点。例如,有一种方法可以轮询和检查最后提供的流是否仍在播放,或者是否已完成播放。

另请参阅

以下是一些参考资料:

使用 NeoPixel 对象控制像素

在本教程中,您将学习如何使用 NeoPixel 对象控制板上的像素。我们在之前的章节中涵盖了这个对象中的许多方法,但这是我们第一次直接创建 NeoPixel 对象。直接使用 NeoPixel 对象的技能非常有用,而不是通过另一个对象访问它。如果您决定向项目添加额外的环或像素条,那么您将需要直接访问此对象来控制像素。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

让我们来看看本教程所需的步骤:

  1. 在 REPL 中运行以下代码行:
>>> from neopixel import NeoPixel
>>> import board
>>> 
>>> PIXEL_COUNT = 10
>>> pixels = NeoPixel(board.NEOPIXEL, PIXEL_COUNT)
>>> pixels.brightness = 0.05
>>> pixels[0] = 0xFF0000
  1. 在运行上一个代码块之后,第一个像素应该变成红色。运行下一个代码块以使第二个像素变成绿色:
>>> RGB = dict(
...     black=0x000000,
...     white=0xFFFFFF,
...     green=0x00FF00,
...     red=0xFF0000,
...     yellow=0xFFFF00,
... )
>>> pixels[1] = RGB['green']
  1. 运行下一个代码块以关闭第一个像素:
>>> pixels[0] = RGB['black']
  1. 以下代码应放入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:
    pass

工作原理...

NeoPixel类用于访问板上的像素数组。当我们创建此对象时,我们必须指定要连接到的板上的引脚以及连接到该引脚的像素数。

在 Circuit Playground Express 的情况下,板上有 10 个像素。我们将此值保存在全局常量中,以提高代码的可读性。然后将像素的亮度设置为 5%。

在项目中需要的五种不同颜色的名称和十六进制代码在全局字典中定义。白色、绿色、红色和黄色分别与附加电线的四种颜色相关。黑色用于关闭像素。然后,我们将第一个和第二个像素设置为红色和绿色。最后,我们运行一个无限循环,以便我们可以看到这些颜色并阻止脚本退出。

还有更多...

此代码具有与板载的任何 10 个像素进行交互所需的一切。您可以使用此基本代码开始尝试提供对象上可用的不同方法。使用这些不同的方法,您可以一次性更改所有像素的颜色。您还可以关闭默认的自动写入功能,然后直接控制您对颜色所做的更改何时应用。通过此库,可以完全控制像素的低级别控制。

另请参阅

以下是一些参考资料:

创建触摸处理程序以播放声音

在本教程中,我们将创建我们的触摸处理程序的第一个版本。此第一个版本将在检测到触摸事件时播放特定的音频文件。然后,我们可以在以后的教程中使用此处理程序,以将每个触摸板映射到特定的音频文件。我们还将在以后的教程中扩展此处理程序的功能,以在触摸事件中添加光和声音。事件处理程序是许多软件系统的常见部分。本教程将帮助您了解如何在 MicroPython 项目中使用这种常见方法。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本教程中提供的代码。

如何做...

让我们来看看本教程所需的步骤:

  1. 在 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)
  1. 此时,我们已经启用了扬声器,并设置了一个对象来在扬声器上播放音频文件。在下一块代码中,我们将定义一个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)
  1. 当您运行下一块代码时,您应该能听到扬声器上的钢琴声音:
>>> handler.handle('A1', True)
  1. 以下代码应放入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函数播放钢琴声音。本教程中的其余代码支持不断检查新触摸事件并调用已定义的处理程序的过程。

还有更多...

在这个例子中,配方只会在按下单个触摸板时播放一个声音。但是,它也为我们扩展提供了核心结构。您可以尝试使用这个配方并尝试两个触摸板,每个都播放不同的声音。您可以通过将多个定义的事件对象连接到不同的处理程序来实现这一点。在以后的配方中,您将看到单个事件类定义和单个处理程序类定义可以用于连接到四个不同的触摸板并播放四种不同的声音。

另见

以下是一些参考资料:

创建一个触摸处理程序来点亮像素

在这个配方中,我们将创建一个触摸处理程序,通过播放声音和点亮像素来对触摸事件做出反应。当触摸传感器被触发时,处理程序将播放声音并点亮特定的像素。当触摸传感器检测到您已经松开手指时,点亮的特定像素将关闭。

通过这种方式,您可以听到并看到板子对每个配置的触摸板的独特反应。这个配方展示了一种有用的方式,可以根据不同的触发输入创建不同类型的输出。当您添加一些独特的音频和视觉输出以对不同类型的人类输入做出反应时,许多项目可以变得生动起来。

准备好

您需要访问 Circuit Playground Express 上的 REPL 才能运行本配方中提供的代码。

如何做...

让我们来看看这个配方所需的步骤:

  1. 使用 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
  1. 在下一块代码中,我们将定义一个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)
  1. 当您运行下一块代码时,您应该听到扬声器上的钢琴声音,并且第一个像素应该变成红色:
>>> handler.handle('A1', True)
  1. 运行下一块代码,您应该看到第一个像素灯关闭:
>>> handler.handle('A1', False)
  1. 以下代码应放入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 Playground Express 上的 REPL 才能运行本教程中提供的代码。

操作步骤...

让我们来看看完成本教程所需的步骤:

  1. 在 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']),
... )
  1. 我们现在已经导入了我们脚本中所需的所有库,并创建了我们脚本中所需的主要数据结构。运行下一个代码块,扬声器应该会播放钢琴声音:
>>> 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'])
  1. 运行下一个代码块以创建我们事件处理程序的实例:
>>> 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)
  1. 运行下一个代码块以模拟在 2 号触摸板上的触摸事件。您应该听到钢琴声音,并看到一个像素变红:
>>> handler.handle('A2', True)
  1. 以下代码应放入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 具有丰富的数据结构集,有效利用时可以使代码更易读和易维护。

该项目将四根香蕉连接到板上,每根香蕉触摸时都会播放不同的声音。由于代码被构造为立即响应每次触摸,所以甚至可以让两个人同时玩。下一张照片显示了两个人,每人手持一对香蕉,创作音乐并控制板上的像素:

另请参阅

以下是一些参考资料:

第八章:让我们动起来

在本章中,您将学习如何控制电机和舵机。使用直流电机将有助于需要控制车辆车轮的项目。舵机可以帮助您的项目控制机器人手臂的移动。这两种设备都将为我们提供创建机械运动的方式。根据您在项目中尝试创建的运动,您可能希望选择其中一种。它们各自的功能以及它们最适合的地方将在接下来的章节中介绍。

通过本章结束时,您将能够利用所学知识创建各种有趣的项目。这将为您能够构建的项目类型开辟全新的可能性。

在本章中,我们将涵盖以下主题:

  • 调整舵机到正确的脉冲宽度

  • 设置舵机的作用范围

  • 设置舵机的角度

  • 扫描舵机

  • 使用按钮控制舵机

  • 控制多个舵机

  • 打开直流电机

  • 设置直流电机的速度和方向

  • 使用按钮控制直流电机

技术要求

本章的代码文件可以在 GitHub 存储库的Chapter08文件夹中找到github.com/PacktPublishing/MicroPython-Cookbook

本章中的许多配方将使用 Circuit Playground Express 库,该库通常会在脚本的前几行导入,使用以下代码行:

from adafruit_circuitplayground.express import cpx

这个库将帮助我们与板上的按钮和开关进行交互。还有另一个库将在本章的许多配方中导入,使用以下语句:

from adafruit_crickit import crickit

这个库将帮助我们与 CRICKIT 板进行交互,以便我们可以控制舵机和直流电机。

本章中涉及舵机的配方期望两个舵机连接到舵机端口 1 和舵机端口 2。连接舵机电缆时,请确保黄色电线朝向板外。

本章中涉及直流电机的配方期望电机连接的驱动器 1 上连接电机。两根电线可以连接到两个连接器中的任何一个方向。无论如何连接电线,旋转方向都会翻转,取决于电线连接的方式。

直流电机

直流电机将直流电转换为旋转运动。通常是通过驱动运动的电磁体来实现的,因为它们的磁场发生变化。以下插图显示了这种类型电机的内部结构:

来源:https://commons.wikimedia.org/wiki/File:Ejs_Open_Source_Direct_Current_Electrical_Motor_Model_Java_Applet_(DC_Motor)_80_degree_split_ring.gif

直流电机在需要高速旋转运动的应用中表现出色。它们适用于操作遥控汽车上的风扇或车轮。

舵机

舵机比直流电机更复杂,更适合需要对连接到舵机的物体的确切位置进行更多控制的情况。舵机通常包含直流电机、齿轮、控制电路和传感器,用于检测舵机的确切位置。所有这些组件汇集在一起,形成一个设备,让您对舵机指向的确切角度有更精确的控制。

以下图像显示了一个拆卸的舵机,您可以看到其中的直流电机、齿轮和电路:

来源:https://commons.wikimedia.org/wiki/File:Exploded_Servo.jpg

舵机在需要对某个部件的角度进行精确控制的应用中表现出色;例如,需要控制机器人手臂的角度或船舶舵的角度。

Adafruit CRICKIT

Adafruit CRICKIT 是一个可以让您从各种硬件控制许多不同类型的电机的板。不同的 CRICKIT 型号支持树莓派和 FeatherWing 系列产品。

在本章中,我们将使用 CRICKIT 来控制 Circuit Playground Express。以下图片显示了在连接 Circuit Playground Express 之前 CRICKIT 的样子:

由 adafruit.com 提供

要将这两个设备连接在一起,您将需要 6 个六角黄铜支架,每个支架都将用 12 颗螺丝螺入两个设备。以下图片显示了这些支架和螺丝的样子:

由 adafruit.com 提供

连接这些螺丝和支架后,您的两个板应该看起来像下图所示:

由 adafruit.com 提供

最多可以连接四个独立的舵机到板上。支持微型、迷你和标准舵机。舵机的三针连接器应连接到一个可用的舵机插槽,如下图所示:

由 adafruit.com 提供

最多可以连接两个直流电机到板上。每个电机将连接到两个引脚。每个电机连接的引脚对在下图中显示:

由 adafruit.com 提供

连接两个设备后,您可以为每个设备供电,并使用 USB 电缆将 Circuit Playground Express 连接到计算机,方式与本书前几章相同。连接后,您需要使用支持 CRICKIT 硬件的固件刷新固件。

本章使用的 UF2 文件的版本是支持 CRICKIT 的 CircuitPython 3.1.2 版本,名为adafruit-circuitpython-circuitplayground_express_crickit-3.1.2.uf2

有关如何使用此固件刷新板的详细信息,请参阅第一章,使用 MicroPython 入门中有关如何刷新微控制器固件的说明。

购买地址

本章使用了许多组件,所有这些组件都可以从 Adafruit 在线零售商处购买。

Adafruit CRICKIT for Circuit Playground Express 可以直接从 Adafruit 购买(www.adafruit.com/product/3093)。也可以从其他在线零售商购买,如 Pimoroni。

Circuit Playground Bolt-On Kit 可以直接从 Adafruit 购买(www.adafruit.com/product/3816)。该套件包括连接两个板所需的六个六角支架和 12 颗螺丝。本章使用的舵机可以直接从 Adafruit 购买(www.adafruit.com/product/169)。

本章使用的直流电机可以直接从 Adafruit 购买(www.adafruit.com/product/3777)。Adafruit 还出售许多可选的轮子附件,但在本章的示例中并不需要。

CRICKIT 可以通过一个三节 AA 电池盒供电,可以直接从 Adafruit 购买(www.adafruit.com/product/3842)。与其他电源相比,这种电源的好处在于便携和低成本。

调整舵机到正确的脉冲宽度

舵机可以通过发送不同的电脉冲来旋转其臂到特定角度。臂移动到的角度将由电脉冲的宽度控制。在设置这些角度之前,每个舵机必须首先配置正确的最小和最大宽度设置。

本示例将向您展示如何做到这一点。每当您想在项目中使用舵机时,都需要进行此配置。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

如何做...

让我们重温一下本示例所需的步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> from adafruit_crickit import crickit
>>> 
>>> MIN_PULSE=750 
>>> MAX_PULSE=2250
  1. 在这个阶段,我们已经导入了必要的库,并定义了我们想要为这组特定舵机设置的最小和最大脉冲宽度值。以下代码块将使用这些设置配置连接到第一个端口的舵机:
>>> crickit.servo_1.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
  1. 运行下一块代码将舵机移动到最低角度:
>>> crickit.servo_1.angle = 0
  1. 以下代码块将将臂移动到中间位置,即最低和最高值之间:
>>> crickit.servo_1.angle = 90
  1. 在运行下一块代码时,按住手指在触摸板 A1 上:
>>> event.process()
A1 True
  1. 以下代码应放入main.py文件中,当执行时,它将把舵机 1 移动到最低角度 3 秒,然后将其移动到中间范围角度 60 秒:
import time
from adafruit_circuitplayground.express import cpx
from adafruit_crickit import crickit

MIN_PULSE = 750
MAX_PULSE = 2250

crickit.servo_1.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
crickit.servo_1.angle = 0
time.sleep(3)
crickit.servo_1.angle = 90
time.sleep(60)

工作原理...

crickit对象将是我们与连接到电路板的所有舵机和直流电机进行交互的方式。每个舵机连接都有编号,因此您可以通过这个单一对象的属性来控制多个舵机。在将最小和最大脉冲宽度的值保存为常量后,我们通过调用set_pulse_width_range将这些设置应用于第一个舵机电机。

然后我们设置第一个舵机的角度属性的值,这将使舵机移动到角度 0。我们通过调用sleep方法暂停 3 秒,然后使用相同的角度属性将角度更改为 90。

还有更多...

来自不同制造商的舵机电机将期望不同的最小和最大脉冲宽度设置。您通常可以通过查看产品的数据表来找到特定舵机的正确设置。本示例中使用的设置特定于本章开头描述的舵机型号。如果您决定使用不同的舵机组,可以根据需要更改这些设置。用于控制舵机的 Python 库还允许您为每个舵机配置这些设置。这样,您可以通过分别配置每个舵机,同时连接具有不同设置的不同舵机。

脉冲宽度有时以毫秒提供,有时以微秒提供。只需将它们转换为微秒,因为这是这个 Python 模块所期望的单位。本示例中使用的舵机被描述为使用 0.75 毫秒到 2.25 毫秒的脉冲宽度,转换为微秒后变为 750 到 2,250。

另请参阅

以下是一些参考资料:

设置舵机的作用范围

舵机的臂在其运动范围上有所不同。对于角度,您在软件中发出请求以正确映射到舵机实际移动的角度;您需要使用其作用范围配置舵机。一旦配置完成,您将能够准确地将连接到舵机的臂移动到其正确位置。这是配置您计划在其中使用舵机的任何项目中的重要步骤。如果不这样做,您将面临一些奇怪的惊喜,其中舵机臂会不断移动到错误的位置。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

如何做...

让我们重温一下本示例所需的步骤:

  1. 在 REPL 中执行下一块代码:
>>> from adafruit_circuitplayground.express import cpx
>>> from adafruit_crickit import crickit
>>> 
>>> MIN_PULSE = 750
>>> MAX_PULSE = 2250
>>> 
>>> crickit.servo_1.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
  1. 现在已配置了伺服的脉冲宽度。执行以下代码块将伺服移动到最低位置:
>>> crickit.servo_1.angle = 0
  1. 在运行下一个代码块之前,请记下臂的当前位置。运行下一个代码块将伺服移动到最高角度:
>>> crickit.servo_1.angle = 180
  1. 测量这两个位置之间的角度。您应该发现角度为 160 度。运行下一个代码块将伺服返回到 0 度角并配置执行范围:
>>> crickit.servo_1.angle = 0
>>> crickit.servo_1.actuation_range = 160
  1. 运行下一个代码块,软件角度和实际角度应该都是 160 度:
>>> crickit.servo_1.angle = 160
  1. 以下代码应插入到main.py文件中:
import time
from adafruit_circuitplayground.express import cpx
from adafruit_crickit import crickit

MIN_PULSE = 750
MAX_PULSE = 2250

crickit.servo_1.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
crickit.servo_1.angle = 0
time.sleep(3)
crickit.servo_1.actuation_range = 160
crickit.servo_1.angle = 160
time.sleep(60)

执行此脚本时,将伺服 1 移动到最低角度 3 秒,然后将其移动到 160 度角度 60 秒。

工作原理...

前几行代码将配置伺服的脉冲宽度设置。在将执行范围配置为特定伺服的正确值(160 度)之前,角度将设置为 0,持续 3 秒。配置完成后,当软件中的角度设置为 160 度时,实际运动也应为 160 度。

还有更多...

就像脉冲宽度在伺服之间变化一样,运动范围也是如此。大多数伺服不会提供完整的 180 度运动。发现这些设置的一种方法是不配置执行范围,然后在软件中将伺服移动到 0 度和 180 度。

然后,您可以使用量角器来物理测量伺服移动的角度。测量了这个值后,您可以将这个角度作为执行范围的值。以下图片显示了使用量角器测量本章中伺服的最低角度:

放置量角器后,将伺服移动到最高角度。以下图片显示量角器测量角度为 160 度:

当您想在现实世界中进行准确的角度测量时,量角器是最佳选择。

另请参阅

以下是一些参考资料:

设置伺服的角度

一旦您正确配置了伺服,您将能够将伺服臂移动到精确的角度位置。本教程将移动伺服到多个角度,并展示当您尝试将伺服移动到超出其允许运动范围的角度时会发生什么。

一旦我们有能力将伺服移动到特定角度,我们就可以开始将它们纳入我们的项目中,以控制机械臂或将其他伺服附件移动到特定位置。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本教程中提供的代码。

如何操作...

让我们来看看这个教程所需的步骤:

  1. 使用 REPL 运行以下代码行:
>>> from adafruit_circuitplayground.express import cpx
>>> from adafruit_crickit import crickit
>>> 
>>> MIN_PULSE = 750
>>> MAX_PULSE = 2250
>>> 
>>> crickit.servo_1.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
>>> crickit.servo_1.angle = 0
  1. 现在伺服应该处于最低角度。执行以下代码块将伺服移动到最高位置:
>>> crickit.servo_1.angle = 180
  1. 运行以下代码以查看当您超出最大角度范围时会发生什么:
>>> crickit.servo_1.angle = 190
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "adafruit_motor/servo.py", line 111, in angle
ValueError: Angle out of range
  1. 运行以下代码块将伺服返回到 0 度角并将执行范围配置为 160 度:
>>> crickit.servo_1.angle = 0
>>> crickit.servo_1.actuation_range = 160
  1. 运行以下代码块,查看 180 度现在被认为是伺服的范围之外的角度:
>>> crickit.servo_1.angle = 180
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "adafruit_motor/servo.py", line 111, in angle
ValueError: Angle out of range
  1. 运行以下代码块,伺服应该移动到最高角度:
>>> crickit.servo_1.angle = 160
  1. 以下代码应放入main.py文件中,执行时将伺服移动到 0、45、90 和 160 度的角度,每次移动之间有 3 秒的延迟:
import time
from adafruit_circuitplayground.express import cpx
from adafruit_crickit import crickit

MIN_PULSE = 750
MAX_PULSE = 2250

crickit.servo_1.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
crickit.servo_1.angle = 0
crickit.servo_1.actuation_range = 160

crickit.servo_1.angle = 0
time.sleep(3)

crickit.servo_1.angle = 45
time.sleep(3)

crickit.servo_1.angle = 90
time.sleep(3)

crickit.servo_1.angle = 160
time.sleep(3)

工作原理...

代码的前几行将配置舵机的脉冲宽度设置和作用范围。然后,将在舵机上设置 4 个不同的角度。这些角度分别是 0、45、90 和 160 度。在设置每个角度之后,通过调用时间模块上的sleep函数应用 3 秒的延迟。

还有更多...

在这个示例中,我们试验了在配置作用范围之前和之后尝试设置舵机角度时会发生什么。作用范围的默认设置是 180 度。这就是为什么在所有情况下,190 度的值都会被拒绝。一旦我们将作用范围配置为 160,诸如 180 的值当然会被拒绝,因为它们超出了这个范围。

舵机库具有这些检查非常有帮助,因为如果不执行这些检查,设置舵机角度超出正确范围的软件应用程序中的错误可能会损坏您的舵机。此外,通过使用清晰的异常消息抛出ValueError异常,使得更容易调试这些错误的应用程序。

另请参阅

以下是一些参考资料:

扫描舵机

在这个示例中,您将学习如何创建一个脚本,不断地将舵机从最低角度移动到最高角度,然后再次返回,以扫描运动。在某些方面,这段代码类似于我们在前几章中看到的灯光动画,因为我们将改变板的输出,并在每次改变之间设置时间延迟,以创建动画视觉效果。

然而,在舵机的情况下,动画效果将出现在连接的臂上,呈扫描运动。本示例中使用的方法可以适应任何想要一些舵机附件不断从一个位置扫到另一个位置的项目。

准备工作

您将需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

如何操作...

让我们来看看这个示例所需的步骤:

  1. 在 REPL 中运行以下代码:
>>> import time
>>> from adafruit_circuitplayground.express import cpx
>>> from adafruit_crickit import crickit
>>> 
>>> MIN_PULSE = 750
>>> MAX_PULSE = 2250
>>> MAX_ANGLE = 160
>>> STEP = 10
>>> DELAY = 0.1
  1. 在这个阶段,应该导入所需的 Python 库,并将不同的设置定义为我们脚本的常量。执行以下代码块来初始化舵机并将其移动到最低位置:
>>> def init(servo):
...     servo.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
...     servo.angle = 0
...     servo.actuation_range = MAX_ANGLE
...     
...     
... 
>>> init(crickit.servo_1)
  1. 运行以下代码,将舵机从角度0扫到160
>>> def sweep(servo, direction):
...     angle = int(servo.angle)
...     while 0 <= angle <= MAX_ANGLE:
...         print(angle)
...         servo.angle = angle
...         time.sleep(DELAY)
...         angle += STEP * direction
...         
... 
>>> sweep(crickit.servo_1, 1)
0
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
  1. 运行以下代码,将舵机从角度160扫到0
>>> sweep(crickit.servo_1, -1)
160
150
140
130
120
110
100
90
80
70
60
50
40
30
20
10
0
  1. 以下代码应该插入到main.py文件中,当执行时,它将不断地将电机从角度0扫到160,然后返回到0
import time
from adafruit_circuitplayground.express import cpx
from adafruit_crickit import crickit

MIN_PULSE = 750
MAX_PULSE = 2250
MAX_ANGLE = 160
STEP = 10
DELAY = 0.1

def init(servo):
    servo.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
    servo.angle = 0
    servo.actuation_range = MAX_ANGLE

def sweep(servo, direction):
    angle = int(servo.angle)
    while 0 <= angle <= MAX_ANGLE:
        print(angle)
        servo.angle = angle
        time.sleep(DELAY)
        angle += STEP * direction

def main():
    init(crickit.servo_1)
    while True:
        sweep(crickit.servo_1, 1)
        sweep(crickit.servo_1, -1)

main()

工作原理...

首先,定义了一个名为init的函数,它期望将要初始化的舵机的名称作为其第一个参数。当调用此函数时,它将设置最小和最大脉冲宽度,将角度设置为 0,并设置作用范围。接下来,定义了一个名为sweep的函数。这个函数期望第一个参数是要控制的舵机,第二个参数是一个带有值1-1的整数,表示扫描的方向。

值为1将使角度增加,而值为-1将使角度减少。sweep 函数的第一部分将检索舵机角度的当前值并将其强制转换为整数并存储在名为angle的变量中。然后启动一个循环,直到角度的值超出了 0 到 160 的允许范围。在循环的每次迭代中,都会打印当前角度,然后将角度应用于舵机,然后应用延迟;然后,角度将按照定义的步长值进行更改。

然后定义了main函数,当调用时,将初始化舵机并将其移动到角度 0。然后,启动一个无限循环,在每次循环迭代期间执行两个操作。首先调用sweep函数来增加角度从 0 到 160。然后再次调用sweep函数,但这次是将角度从 160 减少到 0。

还有更多...

initsweep函数中尽可能不要硬编码任何值。大多数值都作为可配置的常量设置在脚本顶部,或者作为函数调用时接收的参数。这将使得调整脚本以适应其他设置的舵机变得更加容易。您还可以通过增加和降低这些常量中的值来轻松改变每次sweep迭代中角度变化的量以及完成扫描的速度。

该程序还被分成了 3 个不同的函数,以提高可读性并鼓励将不同的代码块重用到其他项目中。Python 编程语言的一个有趣且相对独特的特性是能够链式比较操作,这在 MicroPython 和 CircuitPython 版本中得到了充分支持。这个特性在sweep函数中用于检查角度是否在 0 到 160 之间。

在其他语言中,您通常需要使用and运算符结合两个比较运算符来表达这一点。然而,在 Python 中,您可以简单地链式比较运算符以更简洁和可读的方式实现相同的结果。

另请参阅

以下是一些参考资料:

使用按钮控制舵机

在这个食谱中,您将学习如何使用 Circuit Playground Express 上的两个按钮来控制舵机的角度。本食谱中的脚本将在按下按钮 A 时增加舵机角度,并在按下按钮 B 时减少角度。每当您想要创建一个项目,让人们可以直接使用不同的输入控件(如按钮)来控制舵机时,这些类型的脚本都非常有用。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本食谱中提供的代码。

操作步骤...

让我们来看看这个食谱所需的步骤:

  1. 在 REPL 中执行以下代码块:
>>> import time
>>> from adafruit_circuitplayground.express import cpx
>>> from adafruit_crickit import crickit
>>> 
>>> MIN_PULSE = 750
>>> MAX_PULSE = 2250
>>> MAX_ANGLE = 160
>>> STEP = 10
>>> DELAY = 0.1
  1. 初始导入已完成,我们准备定义我们的函数。以下代码块将定义并调用一个初始化舵机的函数:
>>> def init(servo):
...     servo.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
...     servo.angle = 0
...     servo.actuation_range = MAX_ANGLE
...     
...     
... 
>>> init(crickit.servo_1)
  1. 运行以下代码将舵机移动 10 度并检查角度的值:
>>> def move(servo, angle, direction):
...     new = angle + STEP * direction
...     if 0 <= new <= MAX_ANGLE:
...         angle = new
...         print(angle)
...         servo.angle = angle
...     return angle
...     
... 
>>> angle = 0
>>> angle = move(crickit.servo_1, angle, 1)
10
>>> angle
10
  1. 运行以下代码再次移动舵机,增加 10 度:
>>> angle = move(crickit.servo_1, angle, 1)
20
  1. 运行以下代码将减少舵机的角度 10 度:
>>> angle = move(crickit.servo_1, angle, -1)
10
  1. 以下代码应插入到main.py文件中:
import time
from adafruit_circuitplayground.express import cpx
from adafruit_crickit import crickit

MIN_PULSE = 750
MAX_PULSE = 2250
MAX_ANGLE = 160
STEP = 10
DELAY = 0.1

def init(servo):
    servo.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
    servo.angle = 0
    servo.actuation_range = MAX_ANGLE

def move(servo, angle, direction):
    new = angle + STEP * direction
    if 0 <= new <= MAX_ANGLE:
        angle = new
        print(angle)
        servo.angle = angle
    return angle

def main():
    init(crickit.servo_1)
    angle = 0
    while True:
        if cpx.button_a:
            angle = move(crickit.servo_1, angle, 1)
        if cpx.button_b:
            angle = move(crickit.servo_1, angle, -1)
        time.sleep(DELAY)

main()

一旦执行,该脚本将在按下按钮 A 和 B 时每次将舵机移动到较低或较高的角度。

工作原理...

在定义全局常量和舵机初始化函数之后,我们继续定义另外两个函数。move函数接受舵机、当前角度和移动方向作为其三个参数。然后根据当前角度步进量和移动方向计算预期的新角度。如果这个新角度在可接受的角度范围内,则打印其值并应用于servoangle变量。最后,返回angle变量的值。

在脚本底部定义并调用的main函数实现了主事件循环。在初始化servo变量并将angle变量设置为0之后,开始了一个无限循环。在循环的每次迭代中,如果按下按钮 A,则将调用move函数来增加舵机角度。然后,检查按钮 B,如果按下,则调用move函数来减小舵机角度。最后,在此循环的每次迭代结束时应用sleep函数。

还有更多...

这个基本的事件循环允许我们通过将舵机移动到不同的方向来对用户输入做出反应。我们可以在许多方向上扩展此脚本的逻辑。例如,我们可以将步进角从 10 减少到 1,以便非常精细地控制舵机,并每次改变一个度的角度。我们还可以减少延迟以加快对每次按钮按下的反应运动。我们可以拿基本脚本并添加控制像素的代码,除了舵机角度,当您按下每个按钮时。

另请参阅

以下是一些参考资料:

控制多个舵机

在这个食谱中,您将学习如何结合使用按钮和滑动开关来控制多个舵机。基本上,我们将使用按钮来控制特定舵机的角度。然后,我们将使用滑动开关来选择我们想要控制的两个连接舵机中的哪一个。

这个食谱建立在一些过去的食谱基础上,增加了额外的数据结构和控制,以管理控制多个舵机所需的额外逻辑。每当您需要找到控制多个舵机的方法时,这个食谱将非常有用。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行此食谱中呈现的代码。

如何做...

让我们来看看这个食谱所需的步骤:

  1. 使用 REPL 运行以下代码行:
>>> import time
>>> from adafruit_circuitplayground.express import cpx
>>> from adafruit_crickit import crickit
>>> 
>>> MIN_PULSE = 750
>>> MAX_PULSE = 2250
>>> MAX_ANGLE = 160
>>> STEP = 10
>>> DELAY = 0.1
>>> 
>>> def init(servo):
...     servo.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
...     servo.angle = 0
...     servo.actuation_range = MAX_ANGLE
...     
...     
... 
>>> 
  1. 初始导入已完成,并且我们已经定义了init函数来帮助初始化舵机。以下代码块将设置一些数据结构,用于跟踪我们的角度和舵机:
>>> servos = [crickit.servo_1, crickit.servo_4]
>>> angles = [0, 0]
  1. 以下代码块将初始化我们舵机列表中的所有舵机:
>>> init(servos[0])
>>> init(servos[1])
  1. 运行以下代码以根据滑动开关位置设置开关变量:
>>> switch = int(cpx.switch)
>>> switch
0
  1. 运行以下代码以将所选舵机移动 10 度:
>>> def move(servo, angle, direction):
...     new = angle + STEP * direction
...     if 0 <= new <= MAX_ANGLE:
...         angle = new
...         print(angle)
...         servo.angle = angle
...     return angle
...     
...     
... 
>>> angles[switch] = move(servos[switch], angles[switch], 1)
10
  1. 运行以下代码以检查调用move函数之前和之后的角度数据结构:
>>> angle = move(crickit.servo_1, angle, 1)
>>> angles
[10, 0]
>>> angles[switch] = move(servos[switch], angles[switch], 1)
20
>>> angles
[20, 0]
  1. 更改滑动开关位置并运行以下代码块以更新所选舵机:
>>> switch = int(cpx.switch)
>>> switch
1
  1. 运行以下代码块以查看调用move函数如何移动另一个舵机:
>>> angles[switch] = move(servos[switch], angles[switch], 1)
10
>>> angles
[20, 10]
  1. 以下代码应插入到main.py文件中:
import time
from adafruit_circuitplayground.express import cpx
from adafruit_crickit import crickit

MIN_PULSE = 750
MAX_PULSE = 2250
MAX_ANGLE = 160
STEP = 10
DELAY = 0.1

def init(servo):
    servo.set_pulse_width_range(MIN_PULSE, MAX_PULSE)
    servo.angle = 0
    servo.actuation_range = MAX_ANGLE

def move(servo, angle, direction):
    new = angle + STEP * direction
    if 0 <= new <= MAX_ANGLE:
        angle = new
        print(angle)
        servo.angle = angle
    return angle

def main():
    servos = [crickit.servo_1, crickit.servo_4]
    angles = [0, 0]
    init(servos[0])
    init(servos[1])
    while True:
        switch = int(cpx.switch)
        if cpx.button_a:
            angles[switch] = move(servos[switch], angles[switch], 1)
        if cpx.button_b:
            angles[switch] = move(servos[switch], angles[switch], -1)
        time.sleep(DELAY)

main()

执行此脚本将移动不同的舵机,具体取决于滑动开关的位置和按钮的按压。

它是如何工作的...

在定义全局常量和舵机初始化函数之后,我们将继续定义另外两个函数。move函数遵循了您在上一个示例中看到的相同结构。但是,main函数已扩展为具有处理多个舵机和滑动开关的附加数据结构和逻辑。

main函数中,创建了一个名为servos的列表,指向要控制的两个舵机。一个名为angles的列表将跟踪每个舵机的角度。然后初始化每个舵机,然后进入无限循环。

在每次循环迭代期间,开关的值将从布尔值转换为整数值 0 或 1。这将允许我们在两个舵机之间切换控制。然后,根据按下按钮 A 还是 B,将调用move函数,并提供正确的servo对象和角度。最后,在每个循环结束时应用sleep

还有更多...

在这个示例中,我们已经以一种使与板交互成为自然过程的方式将三个输入控件和两个输出舵机组合在一起。部分原因是不同的物理输入控件适合映射到不同的逻辑控件。

滑动开关非常适合在两个选项之间切换,因此在选择两个舵机时使用滑动开关是合理的。当您希望通过重复按按钮来重复增加或减少值时,按钮可以很好地工作。

另请参阅

以下是一些参考资料:

打开直流电机

在这个示例中,您将学习如何使用 Circuit Playground Express 和 CRICKIT 板控制直流电机。与舵机相比,直流电机更容易交互,因为它们不需要任何初始配置。这个示例将为您提供打开和关闭直流电机所需的基本技能。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本示例中提供的代码。

如何做...

让我们来看看完成此示例所需的步骤:

  1. 在 REPL 中运行以下代码行:
>>> from adafruit_crickit import crickit
>>> import time
>>> 
>>> crickit.dc_motor_1.throttle = 1
  1. 连接到板上的直流电机现在应该以全速旋转。运行以下代码块以停止直流电机的旋转:
>>> crickit.dc_motor_1.throttle = 0
  1. 以下代码块将停止并启动电机,并延迟一秒:
>>> while True:
...     crickit.dc_motor_1.throttle = 1
...     time.sleep(1)
...     crickit.dc_motor_1.throttle = 0
...     time.sleep(1)
...     
...     
... 
  1. 以下代码应插入到main.py文件中:
from adafruit_crickit import crickit
import time

while True:
    crickit.dc_motor_1.throttle = 1
    time.sleep(1)
    crickit.dc_motor_1.throttle = 0
    time.sleep(1)

当执行此脚本时,将启动一个无限循环,不断启动和停止电机。

它是如何工作的...

直流电机与舵机不同,因此,它们需要更少的代码和交互来使它们运动。在库导入之后,将启动一个无限循环。

在循环的第一行,访问了crickit对象上的dc_motor_1属性。这个对象将让我们与连接到板上第一个电机连接的任何直流电机进行交互。dc_motor_1公开了一个名为throttle的属性,我们可以用它来打开和关闭电机。如果我们将值设置为1,电机就会启动,值为0则关闭电机。

因此,首先将油门设置为1以打开电机;然后应用1秒的延迟,然后关闭电机,并再次应用1秒的延迟。然后循环重新开始,重复这个过程。

还有更多...

直流电机在许多方面与舵机不同,正如本教程所示。它们确实比舵机更容易入门,因为它们不需要任何初始配置。然而,相反,它们不提供对您想要将电机放置在的确切位置的精确控制。

当然,直流电机能够做到舵机无法做到的事情,比如完全 360 度的旋转运动。

另请参阅

以下是一些参考资料:

设置直流电机的速度和方向

在本教程中,您将学习如何控制特定直流电机的速度和旋转方向。您将看到,向油门提供正值或负值将让我们控制电机是顺时针还是逆时针旋转。我们还可以向油门提供小数值,以控制电机的运行功率。

当您使用直流电机控制 MicroPython 驱动的计算机控制车辆上的车轮时,本教程中的技术将非常有用。它们将让您加速或减速汽车。您还可以使用它们使汽车倒车或完全停止。

准备工作

您需要访问 Circuit Playground Express 上的 REPL,以运行本教程中提供的代码。

如何做....

让我们来看看这个教程所需的步骤:

  1. 在 REPL 中执行以下代码块:
>>> from adafruit_crickit import crickit
>>> import time
>>> DELAY = 0.1
>>> 
>>> crickit.dc_motor_1.throttle = 0.5
  1. 直流电机现在将以其全速的 50%运行。以下代码将以其全速的四分之一运行电机:
>>> crickit.dc_motor_1.throttle = 0.25
  1. 以下代码块将以全速将电机移动到相反的方向:
>>> crickit.dc_motor_1.throttle = -1
  1. 运行以下代码块以停止电机:
>>> crickit.dc_motor_1.throttle = 0
  1. 执行以下代码块时,将定义并调用一个函数,该函数将改变电机的速度和方向,从一个方向到相反的方向:
>>> from adafruit_crickit import crickit
>>> def change_throttle(motor, start, increment):
...     throttle = start
...     for i in range(21):
...         print(throttle)
...         motor.throttle = throttle
...         throttle += increment
...         throttle = round(throttle, 1)
...         time.sleep(DELAY)
...         
... 
>>> change_throttle(crickit.dc_motor_1, -1.0, 0.1)
-1.0
-0.9
-0.8
-0.7
-0.6
-0.5
-0.4
-0.3
-0.2
-0.1
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0
>>> 
  1. 以下代码应插入到main.py文件中:
from adafruit_crickit import crickit
import time

DELAY = 0.1

def change_throttle(motor, start, increment):
    throttle = start
    for i in range(21):
        print(throttle)
        motor.throttle = throttle
        throttle += increment
        throttle = round(throttle, 1)
        time.sleep(DELAY)

def main():
    while True:
        change_throttle(crickit.dc_motor_1, -1.0, 0.1)
        change_throttle(crickit.dc_motor_1, 1.0, -0.1)

main()

执行此脚本将使电机一次又一次地从一个方向移动到另一个方向。

工作原理...

定义了change_throttle函数,它将在本教程中执行大部分工作。它期望接收要控制的电机、油门的起始值,最后是每次迭代期间油门应该改变的量。该函数将初始化throttle变量为指定的起始值。

然后,将启动一个for循环,该循环将从油门的最低值到最高值。它首先打印当前油门,然后将throttle变量的值应用于电机。然后将油门增加并四舍五入到小数点后一位。然后应用延迟,然后进行下一次迭代。

main函数将进入一个无限循环,每次迭代调用change_throttle函数两次。第一次调用将油门值从-1.0移动到1.0,以0.1的增量。第二次调用将油门值从1.0移动到-1.0,以-0.1的增量。

还有更多...

此教程可用于演示以不同速度和不同方向运行电机。它创建了一个几乎可视化的动画,您可以看到电机减速和加速。您可以看到它们以一个方向以最大速度运行,然后减速以在另一个方向以最大速度运行。

有各种各样的创意实验可以扩展这个功能。例如,你可以将两个车轮连接到直流电机上,使其像遥控汽车一样移动。你可以配置光传感器以对手电筒做出反应。

或者,你可以将其他东西连接到直流电机上,根据一定的时间表转动。你可以控制电机启动的时间,使用这个配方中使用的时间模块。

另请参阅

以下是一些参考资料:

使用按钮控制直流电机

在这个配方中,我们将使用按钮来增加和减少直流电机的速度。我们可以使用相同的脚本来使用按钮改变旋转方向。基本上,一个按钮会使电机在一个方向上增加速度,另一个按钮会使电机在另一个方向上移动更多。这样,我们可以使用一对按钮来设置任一方向的一系列速度,并将电机完全停止。

当脚本运行时,当前速度和方向将打印到屏幕上。这个配方可以在任何需要将用户输入转换为运动的项目中有用。例如,你可以创建一个项目,将滑轮连接到直流电机上,并使用按钮来提升和降低滑轮。

准备工作

你需要访问 Circuit Playground Express 上的 REPL 来运行本配方中提供的代码。

如何操作...

让我们来看看这个配方所需的步骤:

  1. 使用 REPL 运行以下代码行:
>>> from adafruit_crickit import crickit
>>> from adafruit_circuitplayground.express import cpx
>>> import time
>>> 
>>> STEP = 0.1
>>> DELAY = 0.1
>>> MIN_THROTTLE = -1
>>> MAX_THROTTLE = 1
>>> 
>>> throttle = 0
>>> crickit.dc_motor_1.throttle = throttle
  1. 直流电机速度设置为0油门。以下代码块将定义一个move函数,并调用它三次,参数是将速度增加到 30%强度:
>>> def move(motor, throttle, direction):
...     new = throttle + STEP * direction
...     if MIN_THROTTLE <= new <= MAX_THROTTLE:
...         throttle = round(new, 1)
...         print(throttle)
...         motor.throttle = throttle
...     return throttle
...     
...     
... 
>>> throttle = move(crickit.dc_motor_1, throttle, 1)
0.1
>>> throttle = move(crickit.dc_motor_1, throttle, 1)
0.2
>>> throttle = move(crickit.dc_motor_1, throttle, 1)
0.3
  1. 以下代码块将调用move函数三次,以减速直到电机完全停止:
>>> throttle = move(crickit.dc_motor_1, throttle, -1)
0.2
>>> throttle = move(crickit.dc_motor_1, throttle, -1)
0.1
>>> throttle = move(crickit.dc_motor_1, throttle, -1)
0.0
  1. 以下代码块将调用move函数三次,以负方向移动,将电机设置为 30%的强度,朝相反方向:
>>> throttle = move(crickit.dc_motor_1, throttle, -1)
-0.1
>>> throttle = move(crickit.dc_motor_1, throttle, -1)
-0.2
>>> throttle = move(crickit.dc_motor_1, throttle, -1)
-0.3
  1. 以下代码块将调用move函数三次,以一个方向将电机从相反方向减速到完全停止:
>>> throttle = move(crickit.dc_motor_1, throttle, 1)
-0.2
>>> throttle = move(crickit.dc_motor_1, throttle, 1)
-0.1
>>> throttle = move(crickit.dc_motor_1, throttle, 1)
0.0
  1. 以下代码应该插入到main.py文件中,当执行时,它将根据按下按钮的次数将电机从一个方向移动到另一个方向:
from adafruit_crickit import crickit
from adafruit_circuitplayground.express import cpx
import time

STEP = 0.1
DELAY = 0.1
MIN_THROTTLE = -1
MAX_THROTTLE = 1

def move(motor, throttle, direction):
    new = throttle + STEP * direction
    if MIN_THROTTLE <= new <= MAX_THROTTLE:
        throttle = round(new, 1)
        print(throttle)
        motor.throttle = throttle
    return throttle

def main():
    throttle = 0
    while True:
        if cpx.button_a:
            throttle = move(crickit.dc_motor_1, throttle, 1)
        if cpx.button_b:
            throttle = move(crickit.dc_motor_1, throttle, -1)
        time.sleep(DELAY)

main()

工作原理...

move函数被定义为控制电机运动方向的变化。它可以被调用来增加或减少特定旋转方向上的运动。该函数接受电机对象、当前油门和期望的运动方向。新的油门值被计算出来,如果发现在电机的可接受范围内,该值将被打印并应用于电机。

然后返回油门的最新值,以便主事件循环可以跟踪它。main函数包含一个无限循环,充当主事件循环。在这个循环中,按下按钮 A 会增加电机在一个方向上的速度,按下按钮 B 会增加电机在另一个方向上的速度。

还有更多...

这个教程提供了使用直流电机接收用户输入并生成输出的基本构建模块。您可以以类似的方式扩展此教程,以便滑动开关可以让您使用相同的脚本控制多个直流电机。

您可以在脚本中更改步进值,以使电机更快地改变速度和方向。或者,也许您想减少步进值,以便更精细地控制速度,但需要额外的按钮按下成本。

另请参阅

以下是一些参考资料:

第九章:在 micro:bit 上编码

在本章中,我们将介绍 micro:bit 微控制器。我们将探索其特点及与其他微控制器相比的优势。到本章结束时,您将学会如何在这个微控制器上加载您的代码,控制其 LED 网格显示,并与板上的按钮进行交互。本章以一个不错的项目结束,让您可以使用这个硬件创建一个倒计时器。每个 MicroPython 板都有其自身的优势,了解目前市面上的产品是很有好处的,这样您就可以为您的项目选择合适的硬件。

在本章中,我们将涵盖以下主题:

  • 使用 Mu 将代码闪存到 micro:bit

  • 使用 Mu 在 micro:bit 上获取 REPL

  • 在 LED 显示屏上显示单个字符

  • 显示内置图像

  • 显示滚动文本

  • 显示已按下的按钮

  • 创建倒计时器

技术要求

本章的代码文件可以在本书的 GitHub 存储库的Chapter09文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

您将需要 BBC micro:bit 板和 Mu 文本编辑器来完成本章的示例。

micro:bit

Micro Bit 是由英国广播公司BBC)创建的一块可以用于英国教育目的的板子。它大约是信用卡的一半大小,并且内置了许多输入和输出传感器,考虑到其大小,这令人惊讶。它既有加速度计又有磁力计。它有两个按钮和一个复位按钮。有一个 5 x 5 的 LED 阵列,可以作为基本显示来显示不同的符号和字符。以下照片展示了这块板子的样子:

该板支持使用外部电池盒和 AAA 电池进行便携式电源供应。使用 USB 连接将板子连接到计算机,以便传输脚本并运行 REPL。

使用 Mu 将代码闪存到 micro:bit

本示例将向您展示如何将 Python 脚本闪存到 micro:bit。Mu 文本编辑器内置支持将代码闪存到这种类型的板子,本示例将带您完成这个过程。一旦我们理解了这一点,就可以用它来开发并加载我们需要的脚本到 micro:bit 板上。这是您想要创建项目并尝试 micro:bit 时的必要第一步。

准备工作

您需要安装 Mu 文本编辑器才能完成此操作。请按照第一章中关于安装 Mu 文本编辑器的说明进行操作,使用 MicroPython 入门

操作步骤...

按照以下步骤学习如何使用 Mu 将代码闪存到 micro:bit:

  1. 使用 USB 电缆将 micro:bit 连接到计算机。

  2. 启动 Mu 文本编辑器应用程序。

  3. 单击应用程序最左侧的 Mode 按钮,以打开以下对话框:

  1. 选择 BBC micro:bit 选项,然后按 OK。

  2. 将以下代码块放入主文本编辑器窗口:

from microbit import display
display.show('x')
  1. 按工具栏上的闪存按钮,将代码闪存到板子上。以下屏幕截图中已突出显示了闪存按钮以供参考:

  1. 如果您查看板子上的 LED 网格,现在应该显示x字符。

工作原理...

与 Circuit Playground Express 相比,micro:bit 采用了不同的加载代码到板上的方法。该板需要您使用特定的软件来理解如何将您的 Python 脚本闪存到这些类型的板上。Mu 文本编辑器完全支持这个 MicroPython 板。最初的步骤是需要配置 Mu,以便它预期与连接的 micro:bit 板进行交互。创建的脚本是一个简单的脚本,它从 micro:bit Python 库中导入显示对象,并使用它在 LED 显示器上显示x字符。

还有更多...

在将代码闪存到 micro:bit 上时,最简单的程序是 Mu 文本编辑器。还有其他选项可用,比如一个名为 uFlash 的命令行程序。使用命令行方法的价值在于它可以让您灵活地使用您选择的文本编辑器,以便在准备使用 uFlash 实用程序时编辑代码并将其闪存。

参见

以下是有关此配方的一些参考资料:

使用 Mu 在 micro:bit 上获取 REPL

此配方将建立在我们在上一个配方中介绍的方法之上。就像加载脚本到板上一样重要,当调试脚本时,REPL 也是必不可少的。REPL 将为您提供一个更丰富的界面,当您尝试使用板或尝试弄清楚代码有什么问题时。在 REPL 中,您可以获取回溯信息并查看打印语句的输出。

准备工作

您需要安装和配置 Mu 文本编辑器,并将您的 micro:bit 板连接到计算机上。

如何操作...

按照以下步骤学习如何使用 mu 在 micro:bit 上获取 REPL:

  1. 启动 Mu 文本编辑器应用程序。

  2. 单击工具栏中突出显示的 REPL 按钮,如下截图所示:

  1. REPL 界面现在应该出现在屏幕的下半部分,如下截图所示:

  1. 在 REPL 中运行以下代码行:
>>> 1+1
2
  1. 运行以下代码块:
>>> import microbit
>>> microbit
<module 'microbit'>

microbit库现在已被导入。

工作原理...

Mu 文本编辑器为许多板提供了内置的 REPL 支持,包括 micro:bit。当您单击 REPL 按钮时,编辑器会尝试打开与板的串行连接。如果成功,它将在板上启动一个 REPL 会话。

在 REPL 中打印出的初始文本显示了板上正在使用的 MicroPython 解释器的版本。此时,您在 REPL 提示符中键入的任何命令都将通过串行连接发送到板上进行评估。然后,它们的输出将返回到计算机上显示在 REPL 屏幕上。

还有更多...

MicroPython REPL 带有许多有用的函数,帮助您探索板上可用的不同 Python 模块和对象。您可以在不同的模块和对象上调用help函数以获取有关它们的详细信息。当您探索特定对象并想要了解该对象上可用的属性和方法时,您可以使用dir函数在 REPL 会话中列出它们。

参见

以下是有关此配方的一些参考资料:

在 LED 显示器上显示单个字符

这个食谱将向您展示如何使用板上的 5 x 5 LED 阵列来显示字符和数字。显示对象有一个show方法,可以将字符和数字映射到需要显示在 LED 上的位图图像。这些 LED 是该板上的主要输出形式之一,因此这个食谱将为您提供一个有价值的交互手段,以与您放在板上的脚本进行交互。

准备就绪

您需要安装和配置 Mu 文本编辑器,以及将您的 Micro Bit 板连接到计算机。

如何操作...

按照以下步骤学习如何在 LED 显示器上显示单个字符:

  1. 在 REPL 中运行以下代码行:
>>> from microbit import display
>>> display.show('a')
  1. 显示现在应该显示字母a。运行以下代码块以显示数字1
>>> display.show('1')
  1. 运行以下代码块后,将显示数字2
>>> display.show(2)
  1. 运行以下代码块以关闭显示:
>>> display.clear()
  1. 以下代码应放入主文本编辑器窗口并刷新到板上:
from microbit import display
import time

for i in range(3):
    display.show(i)
    time.sleep(1)

执行后,此代码将显示数字 0、1 和 2,每次更改之间间隔 1 秒。

它是如何工作的...

micro:bit Python 库中的显示对象具有一个show方法,可以用来在显示器上显示数字和字符。最初的两个示例调用了带有字符串数据类型的参数的方法。

当显示数字2时,该值被给定为整数。当show接收其输入时,它可以接受字符串或整数。在食谱中首先导入必要的库,然后启动一个for循环,循环三次。在每次循环中,它显示从0开始的当前迭代次数,然后在再次循环之前休眠一秒。

还有更多...

处理 5 x 5 LED 网格的有限显示分辨率可能具有挑战性。幸运的是,micro:bit 附带的 Python 模块已经完成了在显示器上以可读方式显示所有字母和字符的工作。在这个食谱中,我们已经看到了如何提供字符串和整数作为数据来显示。在下一个食谱中,我们将看到相同的方法也可以接收其他对象,比如图像对象。

另请参阅

关于这个食谱的一些参考资料:

显示内置图像

这个食谱将向您展示如何使用 5 x 5 LED 阵列来显示 micro:bit 库中提供的内置图像之一。有许多可用的图像,从面部表情到动物符号不等。它们非常像表情符号。

在这个食谱中,我们将看到如何在显示器上显示心形和笑脸图标。在 micro:bit 上创建项目时,显示超出文本和数字的符号可能是有用的,就像在这个食谱中所示的那样。如果您在 micro:bit 上制作了游戏,您可能希望在玩家输赢游戏时显示快乐或悲伤的表情。

准备就绪

您需要安装和配置 Mu 文本编辑器,以及将您的 micro:bit 板连接到计算机。

如何操作...

按照以下步骤学习如何显示内置图像:

  1. 在 REPL 中执行以下代码块:
>>> from microbit import display, Image
>>> import time
>>> 
>>> display.show(Image.HAPPY)
  1. 显示应该显示一个笑脸。运行以下代码块以显示一个心形图标:
>>> display.show(Image.HEART)
  1. 以下代码块将显示一个指向 1 点钟的时钟表盘:
>>> display.show(Image.CLOCK1)
  1. 运行以下代码块以显示一个时钟表盘动画,将时钟表盘从 1 点钟移动到 12 点钟:
>>> CLOCK = [getattr(Image, 'CLOCK%s' % i) for i in range(1, 13)]
>>> for image in CLOCK:
...     display.show(image)
...     time.sleep(0.1)
...     
...     
... 
>>>
  1. 以下代码应该放入主文本编辑器窗口并刷入到板上:
from microbit import display, Image
import time

CLOCK = [getattr(Image, 'CLOCK%s' % i) for i in range(1, 13)]
while True:
    for image in CLOCK:
        display.show(image)
        time.sleep(0.1)

一旦执行,此代码将不断地将时钟表盘从 1 点钟移动到 12 点钟,每次更改之间的延迟为0.1秒。

它是如何工作的...

Image对象是microbit Python 库的一部分,其中包含一系列内置图像,可以通过引用其属性名称来访问。show 方法接受这些图像对象,并在调用它们后在网格上显示它们。在 REPL 中的初始示例中,通过引用这些图像的名称,显示了一个笑脸、心形和时钟表盘。

然后创建一个列表,指向正确顺序的 12 个时钟表盘图像中的每一个。然后可以使用此列表创建一个时钟表盘动画。首先,启动一个无限循环。在无限循环的每次迭代期间,将启动一个for循环,该循环将遍历 12 个时钟表盘图像中的每一个,显示它们,然后在开始下一次迭代之前暂停 0.1 秒。通过这种方式,创建了一个时钟表盘动画,时钟表盘在时钟的 12 个位置之间移动。

还有更多...

有许多符号和图像,不仅仅是这个配方中显示的。您可以在 REPL 中通过引用文档或使用内置的dir函数列出它们的名称来探索它们。

该库还支持一种机制,您可以使用它来定义自己的自定义图像,然后可以将其保存在代码中,并在项目中重复使用。在这个配方中,我们向您展示了创建图像动画的一种方法,但display对象的show方法中也内置了对动画的支持,也可以使用。

另请参阅

以下是有关此配方的一些参考资料:

显示滚动文本

这个配方将向您展示一种技术,您可以使用滚动文本功能来向用户显示文本,该功能在microbit库中可用。LED 网格仅能一次显示一个字符。通过使用滚动功能,您可以将消息显示为一系列在显示器上滚动的字符。

通过这种方式,您可以创建项目,向用户显示简短的消息,即使在板上可用的物理显示有限。这个配方还将向您展示如何控制动画的速度,以及如何使文本在显示器上无限循环。

准备工作

您需要安装和配置 Mu 文本编辑器,以及将您的 micro:bit 板连接到计算机。

如何做...

按照以下步骤学习如何显示滚动文本:

  1. 使用 REPL 运行以下代码行:
>>> from microbit import display
>>> display.scroll('hello')
  1. 显示应该显示文本'hello'在显示器上滚动。默认延迟为 150 毫秒。以下代码块将以正常速度的两倍滚动文本:
>>> display.scroll('hello', delay=75)
  1. 以下代码块将以默认速度的一半显示相同的文本:
>>> display.scroll('hello', delay=300)
  1. 运行以下代码块以显示无限循环中的文本:
>>> display.scroll('hello', loop=True)
  1. 通过按下Ctrl + C来终止无限循环。您应该会看到以下消息:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt: 
>>> 
  1. 以下代码应放入主文本编辑器窗口并刷入板:
from microbit import display

TEXT = [
    ('slow', 300),
    ('normal', 150),
    ('fast', 75),

]

for text, delay in TEXT:
    display.scroll(text, delay=delay)

一旦执行,此代码将以三种不同的速度(慢速、正常速度和快速)滚动文本。

工作原理...

scroll 方法是 display 对象的一部分,并提供了我们在显示器上滚动文本所需的所有功能。只需要一个参数,即要显示的文本。一旦调用此方法,它将启动动画,并显示所提供文本中的每个字符,并将字符滚动到屏幕上,直到整个文本都显示出来。

可提供可选的 delay 参数以控制滚动动画的显示速度。延迟的较低值将创建更快的动画,而较高的值将减慢动画速度。主脚本定义了一个包含三条消息的列表,每条消息都有不同的滚动延迟设置。然后执行一个 for 循环,循环遍历每个值,并调用 scroll 方法来显示指定的文本,并为每条消息应用自定义滚动 delay

还有更多...

scroll 方法提供了其他选项,这些选项可能会派上用场。此方法具有在后台运行 scroll 动画的能力。当您希望让消息出现而程序执行其他操作时,这可能很有用。您应该注意使用此食谱中介绍的循环选项。基本上,以这种方式调用 show 方法将使调用永远不会返回,因为它将在 show 方法中启动一个无限循环。

另请参阅

以下是有关此食谱的一些参考资料:

显示哪个按钮被按下

此食谱将向您展示如何通过在按下按钮时在显示器上显示被按下的按钮来响应板上的两个按钮之一。这将是本章中的第一个食谱,我们将看到如何创建一个可以通过显示内置 LED 网格的视觉输出来响应用户输入的交互式项目。

在创建自己的项目时,拥有两个按钮可以打开许多可能性,可以创建响应这些输入的交互式应用程序和游戏。此食谱将为您提供基本构建模块,以便您可以开始构建这些类型的项目。

准备工作

您需要安装和配置 Mu 文本编辑器,以及将 micro:bit 板连接到计算机。

如何做到...

按照以下步骤学习如何显示哪个按钮已被按下:

  1. 在 REPL 中运行以下代码行:
>>> from microbit import display, button_a, button_b
>>> 
>>> button_a.is_pressed()
False
  1. 由于按钮 A 没有被按下,返回的值应为 False。在执行以下代码块时按住按钮 A:
>>> button_a.is_pressed()
True
  1. 在执行以下代码块时按住按钮 B:
>>> button_b.is_pressed()
True
  1. 以下代码应放入主文本编辑器窗口并刷入板:
from microbit import display, button_a, button_b

while True:
    if button_a.is_pressed():
        display.show('a')
    elif button_b.is_pressed():
        display.show('b')
    else:
        display.clear()

一旦执行,此代码将显示字符 ab,如果按下按钮 A 或 B。

工作原理...

在初始导入之后,主脚本进入无限循环。在每次迭代期间,将轮询两个按钮,以查看它们当前是否被按下。如果按下按钮 A 或 B 中的任何一个,则屏幕上将显示按下按钮的字符。循环的最后一部分是检查是否两个按钮都没有被按下,然后清除屏幕的内容。

还有更多...

该示例显示了一个事件循环的基本结构,该循环不断循环并检查输入传感器上的事件,并根据发生的事件采取行动。您可以采用这个基本示例,并以许多方式进行扩展。例如,您可以创建一个脚本,帮助用户在菜单选项列表之间导航和选择。

按下按钮 A 可以显示下一个菜单项。按下按钮 B 后,然后可以选择该菜单项。程序的整体结构在事件循环和在每次循环迭代期间检查每个按钮的状态方面将保持不变。

另请参阅

以下是有关此示例的一些参考资料:

创建一个倒计时器

该示例将向您展示如何使用 micro:bit 板创建一个倒计时器。每次有人按下按钮 A 时,倒计时器就会启动。它会显示倒计时完成前剩余多少秒。它将从数字 9 开始倒数,直到计时器完成,然后清除屏幕。在考虑创建需要将经过的时间纳入脚本的项目时,查阅此类示例可能会很有用。

准备就绪

您需要安装和配置 Mu 文本编辑器,以及将您的 micro:bit 板连接到计算机。

如何做...

按照以下步骤学习如何创建倒计时器:

  1. 在 REPL 中执行以下代码块:
>>> from microbit import display, button_a
>>> import time
>>> 
>>> NUMBERS = list(range(9, 0, -1))
  1. 用于倒计时的数字列表将存储在NUMBERS变量中。以下代码块将显示它们的值:
>>> NUMBERS
[9, 8, 7, 6, 5, 4, 3, 2, 1]
  1. 以下代码块将定义并调用countdown函数。您应该看到显示屏显示从 9 到 1 的倒计时,每次更改之间有一秒的延迟:
>>> def countdown():
...     for i in NUMBERS:
...         display.show(i)
...         time.sleep(1)
...     display.clear()
...     
...     
... 
>>> 
>>> countdown()
  1. 以下代码应放入主文本编辑窗口并刷新到板上:
from microbit import display, button_a
import time

NUMBERS = list(range(9, 0, -1))

def countdown():
    for i in NUMBERS:
        display.show(i)
        time.sleep(1)
    display.clear()

while True:
    if button_a.is_pressed():
        countdown()

执行后,每次按下按钮 A 时,此代码将显示一个 9 秒的倒计时。

它是如何工作的...

在初始导入之后,主脚本进入一个无限循环,不断检查按钮 A 上的按钮按下事件。如果检测到按下按钮 A,则将调用倒计时函数开始倒计时。countdown函数循环遍历从 9 到 1 的数字列表。

在每次循环中,它将显示数字并暂停 1 秒,然后继续到下一个迭代。完成所有九次迭代后,它将清除屏幕以标记计时器的结束。

还有更多...

可以扩展此示例,以便按下按钮 B 时启动不同的计时器。也许按钮 B 会启动一个从 1 到 9 的计时器。您还可以使按钮 A 启动秒表,按钮 B 可以停止秒表并显示经过的时间。

另请参阅

以下是有关此示例的一些参考资料:

第十章:控制 ESP8266

在本章中,我们将介绍 Adafruit Feather HUZZAH ESP8266 微控制器。当您的嵌入式项目需要支持互联网连接时,ESP8266 是最受欢迎的 MicroPython 硬件选项之一。通过板上内置的 Wi-Fi 功能实现此连接。

本章将探讨获取板上 REPL 访问的两种主要方法:通过 USB 连接,以及通过 Wi-Fi 无线连接。我们还将介绍一些配方,涵盖与板上 Wi-Fi 功能的不同交互方面。

在本章结束时,您将学会所有必要的核心技能,以便可以高效地使用该板,并开始使用这种多功能且价格低廉的互联网连接硬件构建自己的嵌入式项目。

在本章中,我们将涵盖以下主题:

  • 通过串行连接使用 REPL

  • 扫描可用的 Wi-Fi 网络

  • 配置 AP 模式的设置

  • 连接到现有的 Wi-Fi 网络

  • 通过 Wi-Fi 使用 WebREPL

  • 使用 WebREPL CLI 传输文件

  • 控制蓝色和红色 LED

技术要求

本章的代码文件可以在本书的 GitHub 存储库的Chapter10文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

本章中的所有配方都使用了 CircuitPython 3.1.2。

Adafruit Feather HUZZAH ESP8266

ESP8266 是由 Espressif Systems 制造的廉价微控制器。它可以运行 MicroPython,并支持完整的 TCP/IP 堆栈。其内置的 Wi-Fi 支持 802.11b/g/n。Adafruit Feather HUZZAH ESP8266 是一款开发板,具有用于电源和数据连接的 USB 支持。

该板上的处理器运行在 80 MHz,并配备了 4 MB 的闪存。该板配有九个 GPIO 引脚,可以连接到许多其他组件。该板有多个不同版本。以下照片显示了带有引脚选项的该板的外观:

该板还具有堆叠式引脚配置,可以在板的顶部插入其他组件,如 OLED 显示屏和按钮。这些升级可以直接插入,无需焊接或面包板。以下照片显示了具有堆叠式引脚的板的版本:

您还可以通过使用可充电锂聚合物电池为板载电源使您的项目具有便携性。这些电池可以使用其 JST 连接器连接到板上。以下照片显示了连接到锂聚合物电池的板:

该板具有内置的 LiPoly 充电器,可以使用指示灯显示充电状态。只要板连接了 USB 电缆,电池就可以充电。

您可以在哪里购买这些?

本章使用 Adafruit Feather HUZZAH ESP8266 微控制器。我们建议购买带有堆叠式引脚的版本。Adafruit 已组装的 Feather HUZZAH ESP8266 Wi-Fi 微控制器带有堆叠式引脚,可以直接从 Adafruit 购买(www.adafruit.com/product/3213)。

通过串行连接使用 REPL

本配方将向您展示如何通过 USB 串行连接获取对 ESP8266 的 REPL 访问。尽管该板的真正功能和激动点来自于无线连接,但我们需要做的第一件事是通过简单的 USB 连接与其连接。设置好此连接后,您可以继续本章中的其余配方,设置无线设置,以便可以拔掉板并完全无线地与其交互。

这个配方将帮助您通过建立与板的初始连接来开始自己的无线嵌入式项目,以建立无线连接。当您的配置板面临连接问题并且您想要访问它以调试可能遇到的任何 Wi-Fi 问题时,它也是一个有价值的工具。

准备工作

此配方可以使用 macOS 或 Linux 计算机,并且需要可用的 screen 命令。在 macOS 上,screen 应用程序是内置的,因此无需安装。在 Ubuntu Linux 上,可以使用 apt install screen命令安装 screen 命令。

如何做...

按照以下步骤学习如何通过串行连接使用 REPL:

  1. 使用 USB 电缆将 ESP8266 连接到计算机。

  2. 打开终端应用程序。

  3. 在大多数 Linux 系统上,设备的名称应该是/dev/ttyUSB0。通过在终端中运行以下命令来确认该设备是否存在:

$ ls /dev/ttyUSB0
/dev/ttyUSB0
  1. 如果 ESP8266 被操作系统成功检测到,上述命令应该成功运行。

  2. 运行以下命令以启动与板的 REPL 会话:

$ sudo screen /dev/ttyUSB0 115200
  1. 上面的命令将启动screen命令,并以每秒 115,200 比特的波特率连接到名为/dev/ttyUSB0的设备。如果连接成功建立,您应该在终端中看到以下消息:
Adafruit CircuitPython 3.1.2 on 2019-01-07; ESP module with ESP8266
>>> 
>>> 
  1. 在 REPL 中执行以下代码:
>>> import math
>>> math.pow(8,2)
64.0
>>> 8**2
64

上面的代码块将导入math库并计算 8 的平方。

工作原理...

ESP8266 通过 USB 连接公开了一个串行设备。然后可以使用类似screen的终端仿真器与该串行设备进行交互。连接到 screen 后,您可以访问 REPL 并开始在板上执行 Python 代码。每当在 REPL 中执行 Python 代码时,命令都会被发送到板上执行,然后命令的结果通过串行连接传输回终端仿真器。

还有更多...

所有主要操作系统上都有许多优秀的免费终端仿真器。picocomminicom是 Unix 系统上 screen 的流行替代品。在 Windows 上,可以使用终端仿真器 PuTTY,而 macOS 有一个名为 CoolTerm 的应用程序可以用于此目的。

另请参阅

以下是关于这个配方的一些参考资料:

扫描可用的 Wi-Fi 网络

这个配方将向您展示如何使用 ESP8266 列出所有可用的 Wi-Fi 网络,并且可以连接到这些网络。我们将介绍 MicroPython 网络库,并探讨如何使用它的对象来初始化板载 Wi-Fi 硬件。

设置好这些组件后,我们可以使用它们来扫描无线网络并将扫描结果存储以供进一步检查。这个配方可以为您提供有用的技术,以便您可以测试板的 Wi-Fi 功能。通常,在以后连接到它们之前,列出无线网络是需要的第一步。

准备工作

您需要访问 ESP8266 上的 REPL 才能运行本配方中提供的代码。

如何做...

按照以下步骤学习如何扫描可用的 Wi-Fi 网络:

  1. 在 REPL 中运行以下代码行:
>>> import network
>>> station = network.WLAN(network.STA_IF)
  1. 网络库现在已经被导入,并且已经创建了一个 WLAN 对象,它将提供一个station接口。以下代码块将激活station接口:
>>> station.active(True)
  1. 以下代码块将扫描所有可用的无线网络并将结果存储在networks变量中:
>>> networks = station.scan()
scandone
  1. 以下代码块将输出networks变量的内容,并显示找到了多少个网络:
>>> networks
[(b'My WiFi', b'\x80*\xa8\x84\xa6\xfa', 1, -72, 3, 0), (b'Why Fi', b'\xc8Q\x95\x92\xaa\xd0', 1, -92, 4, 0), (b'Wi Oh Wi', b'd\xd1T\x9a\xb3\xcd', 1, -90, 3, 0)]
>>> len(networks)
3
  1. 运行以下代码行:
>>> names = [i[0] for i in networks]
>>> names
[b'My WiFi', b'Why Fi', b'Wi Oh Wi']

执行后,代码将提取无线网络的名称并将它们存储在一个名为names的变量中,然后进行检查。

工作原理...

MicroPython 提供了一个名为network的模块,可以用来与 ESP8266 上的 Wi-Fi 硬件进行交互。 WLAN 对象被实例化,并提供了network.STA_IF作为其第一个参数。这将返回一个作为station接口创建的对象。

当您想要将板子连接到现有的 Wi-Fi 网络时,需要station接口。在执行扫描之前,必须通过调用active方法并传入True值来激活该接口。然后,可以在该接口上调用scan方法,该方法将扫描可用的网络。此方法返回一个元组列表,我们将其存储在networks变量中。

然后,我们可以使用len函数计算网络的数量,并循环遍历这个元组列表,提取每个网络的名称。每个网络的名称,或服务集标识符SSID),将存储在元组的第一个值中。我们使用列表推导式从networks变量中的每个项目中检索此值,然后将其保存在names变量中。

还有更多...

本教程创建了一个 WLAN 对象作为station接口。在后续的教程中,我们将学习如何创建另一种类型的 WLAN 对象,该对象可用于将设备配置为 AP。除了使用scan方法获取无线网络的名称之外,还可以检查有关每个网络的其他详细信息,例如它使用的信道和接受的认证模式。

scan方法将其结果作为一个简单的数据结构返回,您可以在程序的其余部分中存储和处理。这使得可以创建定期扫描可用网络并将结果保存到日志文件中的项目成为可能。

另请参阅

以下是关于本教程的一些参考资料:

配置 AP 模式的设置

本教程将向您展示如何在 ESP8266 上配置接入点AP)模式。配置完成后,板子将成为一个 Wi-Fi AP,您可以直接使用标准 Wi-Fi 连接将笔记本电脑和手机连接到板子。

Wi-Fi 是如此普遍,以至于这个功能成为一种非常强大的提供连接的方式。您可以使用本教程中展示的技术将 Wi-Fi AP 功能合并到自己的项目中。这样,即使没有其他接入点可用,您也可以在板子和手机或笔记本电脑之间建立无线连接。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

操作步骤...

按照以下步骤学习如何配置 AP 模式的设置:

  1. 在 REPL 中执行以下代码块:
>>> import network
>>> ap = network.WLAN(network.AP_IF)
  1. network库现在已被导入,并且已经为 AP 模式创建了一个 WLAN 对象。以下代码块将配置并激活 AP:
>>> ap.config(essid='PyWifi', password='12345678')
bcn 0
del if1
pm open,type:2 0
add if1
pm close 7
#7 ets_task(4020f4c0, 29, 3fff96f8, 10)
dhcp server start:(ip:192.168.4.1,mask:255.255.255.0,gw:192.168.4.1)
bcn 100
>>> ap.active(True)
  1. 使用手机或笔记本电脑搜索并加入名为PyWifi的 AP。应该在 REPL 中看到以下输出:
>>> add 1
aid 1
station: b0:35:9f:2c:69:aa join, AID = 1

>>> 
  1. 将另一台设备连接到相同的 AP。您应该在 REPL 输出中看到已连接设备的详细信息,如下面的代码块所示:
>>> add 2
aid 2
station: 34:2d:0d:8c:40:bb join, AID = 2

>>> 
  1. 板子还将报告从 AP 断开连接的设备。从 AP 中断开一个连接的设备,应该在 REPL 中出现以下输出:
>>> station: 34:2d:0d:8c:40:bb leave, AID = 2
rm 2

>>> 
  1. 在 REPL 中运行以下代码:
>>> ap.ifconfig()
('192.168.4.1', '255.255.255.0', '192.168.4.1', '8.8.8.8')
>>> 

上述代码将获取有关 AP 的 IP 地址和子网掩码的详细信息。

工作原理...

MicroPython 固件提供了使用 ESP8266 创建 Wi-Fi 接入点的功能。要使用此功能,我们必须首先创建一个 WLAN 对象,并将network.AP_IF值作为其第一个参数传递。这将返回一个可以用于启用 AP 模式的对象。然后调用config方法,传递所需的 Wi-Fi 网络名称和设备连接到 AP 时将使用的密码。

最后,通过调用active方法并传入True值来激活 AP。然后,板子就准备好接收连接了。当设备加入和离开网络时,这些细节将自动打印为 REPL 会话的输出。

还有更多...

正如我们在本教程中看到的,多个设备可以同时连接到板子上。您可以将此教程作为实验此功能的起点。例如,您可以将笔记本电脑和手机连接到 AP,并尝试 ping Wi-Fi 网络上的不同设备。您甚至可以从笔记本电脑 ping 您的手机或 ESP8266 板。

在后续章节中,我们将学习如何在板子上运行 Web 服务器,然后您将能够超越 ping 并通过 Wi-Fi 使用您的 Web 浏览器与板子进行交互。

另请参阅

以下是有关本教程的一些参考资料:

连接到现有的 Wi-Fi 网络

本教程将向您展示如何将 ESP8266 连接到现有的 Wi-Fi 网络。加入现有的 Wi-Fi 网络有许多好处。这样做可以使不同设备在您的网络上无线访问板子成为可能。它还通过 Wi-Fi 网络的互联网连接为板子提供了互联网连接。您可以使用本教程中展示的方法将自己的嵌入式项目连接到不同的网络,并帮助这些项目实现互联网连接。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何操作...

按照以下步骤学习如何连接到现有的 Wi-Fi 网络:

  1. 使用 REPL 运行以下代码行:
>>> import network
>>> station = network.WLAN(network.STA_IF)
>>> station.active(True)
  1. WLAN 对象现已创建并激活。使用以下代码块验证要连接的 AP 是否出现在可用网络列表中:
>>> networks = station.scan()
scandone
>>> names = [i[0] for i in networks]
>>> names
[b'MyAmazingWiFi', b'Why Fi', b'Wi Oh Wi']
  1. 以下代码行将连接到 Wi-Fi AP:
>>> station.connect('MyAmazingWiFi', 'MyAmazingPassword')
ap_loss
scandone
state: 5 -> 0 (0)
rm 0
reconnect
>>> scandone
state: 0 -> 2 (b0)
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 1
cnt 

connected with MyAmazingWiFi, channel 6
dhcp client start...
ip:192.168.43.110,mask:255.255.255.0,gw:192.168.43.1

>>> 
  1. 以下代码块将返回一个布尔值,指示我们当前是否已连接到 AP:
>>> station.isconnected()
True
  1. 以下代码行将获取有关我们当前网络连接的详细信息,包括板的 IP 地址、子网掩码、网关和 DNS 服务器:
>>> station.ifconfig()
('192.168.43.110', '255.255.255.0', '192.168.43.1', '192.168.43.1')
>>> 
  1. 运行以下代码块:
>>> station.active(False)
state: 5 -> 0 (0)
rm 0
del if0
mode : softAP(86:f3:eb:b2:9b:aa)
>>> 

运行此代码后,板将断开与 AP 的连接。

工作原理...

MicroPython 固件具有连接到现有 Wi-Fi 接入点的能力,使用 ESP8266。为此,您必须创建一个 WLAN 对象,并将network.STA_IF值作为其第一个参数传递。在本教程中,将此对象保存到名为station的变量中。然后,通过调用active方法并传入True值来激活station对象。一旦激活,就可以调用connect方法并传入要连接的 AP 的名称及其关联密码。一旦调用connect方法,连接过程中将打印出大量信息。

我们随时可以通过在站点对象上调用isconnected方法来检查我们是否连接。如果我们连接,它将返回True值,否则返回False值。然后,我们可以通过调用ifconfig方法来检索有关我们的 IP 地址和 DNS 服务器的网络详细信息。最后,可以调用active方法,并使用False参数使板断开网络连接。

还有更多...

本教程中包含了关于 WLAN 对象的多种不同方法,可以调用和使用。它向您展示了如何列出网络、连接到网络、轮询连接状态、获取有关当前网络的网络信息以及如何断开网络连接。

使用这些方法,您可以创建一个定期扫描附近网络以寻找特定网络的程序。每当找到它时,您可以自动连接到它。您还可以编写一个不同的脚本,不断轮询网络连接状态并更新状态 LED,以指示 Wi-Fi 已连接,并在断开连接时关闭。

另请参阅

以下是有关本教程的一些参考资料:

通过 Wi-Fi 使用 WebREPL

本教程将向您展示如何在 ESP8266 板上使用 MicroPython 提供的 WebREPL 功能。 WebREPL 是可以在板上启动的服务,它允许您的网络上的计算机通过网络浏览器无线访问 REPL。我们已经在本章的使用串行连接教程中看到了如何使用串行连接访问 REPL。

本教程将为您提供通过 Wi-Fi 获取 REPL 所需的技能,从而即使在没有直接物理连接的情况下,也可以远程调试和执行板上的代码。

准备就绪

您需要访问 ESP8266 上的 REPL 才能运行本教程中提供的代码。在完成本教程之前,您应该按照前一个教程连接到现有 Wi-Fi 网络,因为您将使用该教程将板连接到您的网络并获取其 IP 地址。

操作步骤...

按照以下步骤学习如何通过 Wi-Fi 使用 WebREPL:

  1. 在 REPL 中运行以下代码行:
>>> import webrepl_setup
  1. WebREPL 配置向导现在将开始,询问您一系列问题,以便配置服务。回答以下问题,使用字母E启用启动时的服务:
WebREPL daemon auto-start status: disabled

Would you like to (E)nable or (D)isable it running on boot?
(Empty line to quit)
> E
  1. 接下来的一系列问题将要求您输入和确认 WebREPL 密码:
To enable WebREPL, you must set password for it
New password (4-9 chars): secret123
Confirm password: secret123
  1. 对于下一个问题,回答y(是),以便可以重新启动板并应用更改:
Changes will be activated after reboot
Would you like to reboot now? (y/n) y
Rebooting. Please manually reset if it hangs.
state: 5 -> 0 (0)
rm 0
del if0
bcn 0
del if1
usl
load 0x40100000, len 31012, room 16 
tail 4
chksum 0x61
load 0x3ffe8000, len 1100, room 4 
tail 8
chksum 0x4e
load 0x3ffe8450, len 3264, room 0 
tail 0
chksum 0x0f
csum 0x0f
boot.py output:
WebREPL daemon started on ws://192.168.4.1:8266
WebREPL daemon started on ws://0.0.0.0:8266
Started webrepl in normal mode
  1. 您可以从前面的输出中看到,一旦板子启动,它将显示 WebREPL 服务已启动以及可用于访问该服务的 URL。

  2. 通过单击克隆或下载按钮从github.com/micropython/webrepl下载 WebREPL 软件。

  3. 将下载的.zip文件解压缩到计算机上的任何文件夹中。

  4. 在任何现代的网络浏览器中打开webrepl.html文件。您应该在网络浏览器中看到以下界面:

  1. 在连接按钮旁边的文本框中输入设备的 WebREPL 服务的 URL。在上一个屏幕截图中,板的 IP 地址是10.0.0.38,因此给出了 URLws://10.0.0.38:8266

  2. 现在,单击“连接”按钮,并在提示时输入 WebREPL 密码。以下屏幕截图显示了一个 WebREPL 会话,其中导入了math模块并显示了 pi 的值:

  1. WebREPL 还具有上传文件到板上的功能。您可以使用此功能上传名为main.py的 Python 脚本到板上。

  2. 单击“发送文件”下的“浏览...”按钮。

  3. 选择您的main.py文件。

  4. 单击“发送到设备”按钮。

  5. 以下屏幕截图显示了上传文件到板上后出现的成功上传消息的示例:

屏幕上的消息确认了文件已发送,并报告了传输的字节数。

工作原理...

MicroPython 固件带有内置的 WebREPL 服务器。一旦导入webrepl_setup模块,它将启动一个交互式向导,让您启用该服务。设置其密码并配置它在每次启动板时运行。一旦此服务运行,它将公开一个 WebSocket 服务,可以接收来自在浏览器中运行的 WebREPL 客户端的连接。

WebREPL 客户端不需要任何特殊的安装——它只是一个 HTML 文件,可以在本地提取到您的计算机,然后在网络浏览器中打开。通过此客户端,您现在可以指定要连接的板的地址,并与板建立连接。一旦连接,您将在网络浏览器中拥有一个交互式 REPL 会话,以及将文件上传到板的能力。

还有更多...

这个配方专注于 WebREPL 客户端之一。在这个配方中显示的客户端旨在在您的网络浏览器中运行,并使通过 Wi-Fi 与板卡一起工作的过程变得简单。还有一个 WebREPL 客户端,可以使用命令行界面CLI)而不是网络浏览器来运行。

与网络浏览器客户端不同,CLI 版本完全由 Python 编写。这为您提供了一个很好的机会来探索 WebREPL 通过 Web 套接字传输文件的内部。下一个配方将更深入地探讨 CLI 版本。

另请参阅

以下是有关此配方的一些参考资料:

使用 WebREPL CLI 传输文件

这个配方将向您展示如何使用 WebREPL CLI 通过 Wi-Fi 从计算机传输文件到 ESP8266。这是一个非常强大的将文件传输到板上的方法。每次您对 Python 代码进行更改并希望尝试最新更改时,都必须将文件上传到板上。一遍又一遍地用您的网络浏览器做这件事可能会变得乏味。

使用 CLI 界面的美妙之处在于,大多数终端会记住它们最后执行的命令,因此您可以用两个简单的按键重新运行上一个命令:只需按下箭头键和Enter键。这将使您的代码、上传和运行循环更快,更高效。

准备工作

您需要访问 ESP8266 上的 REPL 才能运行本配方中提供的代码。在尝试本配方之前,应该先遵循上一个配方通过 Wi-Fi 使用 WebREPL,以确保 WebREPL 正常工作。

操作方法...

按照以下步骤学习如何使用 WebREPL CLI 传输文件:

  1. 通过单击“克隆或下载”按钮从github.com/micropython/webrepl下载 WebREPL 软件。

  2. 将下载的.zip文件解压缩到计算机上的任何文件夹中。

  3. 提取的文件将包含一个名为webrepl_cli.py的脚本。

  4. 打开终端并将工作目录更改为webrepl_cli.py脚本的位置。

  5. 在终端中执行以下命令以查看命令的选项:

$ python webrepl_cli.py --help
webrepl_cli.py - Perform remote file operations using MicroPython WebREPL protocol
Arguments:
  [-p password] <host>:<remote_file> <local_file> - Copy remote file to local file
  [-p password] <local_file> <host>:<remote_file> - Copy local file to remote file
Examples:
  webrepl_cli.py script.py 192.168.4.1:/another_name.py
  webrepl_cli.py script.py 192.168.4.1:/app/
  webrepl_cli.py -p password 192.168.4.1:/app/script.py .
  1. 运行以下命令将main.py脚本上传到板子上。在提示时,您需要输入 WebREPL 密码:
$ python webrepl_cli.py  main.py 10.0.0.38:/
Password: 
op:put, host:10.0.0.38, port:8266, passwd:secret123.
main.py -> /main.py
Remote WebREPL version: (3, 1, 2)
Sent 73 of 73 bytes
  1. 以下命令与前一个命令非常相似:
$ python webrepl_cli.py -p secret123 main.py 10.0.0.38:/
op:put, host:10.0.0.38, port:8266, passwd:secret123.
main.py -> /main.py
Remote WebREPL version: (3, 1, 2)
Sent 73 of 73 bytes

主要区别在于密码是作为命令行选项在命令行中提供的。

工作原理...

本教程从简单的命令行调用webrepl_cli.py脚本开始,以显示有关命令行选项的详细信息。至少验证 Python 是否成功执行脚本并生成您期望的输出是一个好主意。

下次调用该命令时,它将用于将main.py脚本上传到板子上。这绝对是上传脚本的一种可行方式。但是,它的主要缺点是每次上传脚本时都必须输入密码。这可以像前面的例子一样解决,其中密码是在命令行上提供的。通过最后一个例子,可以只需按下几个按键重复运行该命令。

还有更多...

当您反复上传脚本到板子上时,这个命令可以节省真正的时间。您还可以将其与其他命令行软件结合起来,以监视特定文件夹中的更改。例如,您可以通过结合这两个软件,使该命令在每次文件更改时自动上传任何更改到main.py

请注意,正如该命令的文档中所述,文件传输仍处于 alpha 阶段,并且存在一些已知问题。如果发现脚本在上传几次后卡住,最有效的解决方法是进行硬复位。可以通过运行以下代码块来完成:

import machine
machine.reset()

这也可以通过按下板子上的复位按钮来完成。

另请参阅

以下是有关此教程的一些参考资料:

控制蓝色和红色 LED

ESP8266 配备了两个 LED:一个是红色的,另一个是蓝色的。这两个 LED 都可以从加载到板子上的脚本中进行控制。本教程将向您展示如何控制每个 LED,并以一个闪烁红色和蓝色灯的动画结束。

在您自己的项目中,每当您想要向用户发出某种状态信号时,都可以使用本教程中显示的技术。当您正在扫描 Wi-Fi 网络时,您可能希望有一个闪烁的蓝灯,或者在板子失去网络连接时点亮红灯。

准备工作

您需要访问 ESP8266 上的 REPL 才能运行本教程中提供的代码。

如何做...

按照以下步骤学习如何控制蓝色和红色 LED:

  1. 在 REPL 中执行以下代码块:
>>> from machine import Pin
>>> red = Pin(0, Pin.OUT)
>>> red.value(0)
  1. 红色 LED 现在应该已经打开。运行以下代码块以关闭红色 LED:
>>> red.value(1)
  1. 以下代码块将打开蓝色 LED:
>>> blue = Pin(2, Pin.OUT)
>>> blue.value(0)
  1. 以下代码将关闭蓝色 LED:
>>> blue.value(1)
  1. 运行以下代码块:
>>> import time
>>> 
>>> while True:
...     blue.value(0)
...     red.value(1)
...     time.sleep(1)
...     blue.value(1)
...     red.value(0)
...     time.sleep(1)
...     
...     
... 

前面的代码将创建一个灯光动画,每次变化之间有一秒的延迟,可以在红色和蓝色灯之间切换。

工作原理...

首先,从 machine 模块中导入Pin对象。这个对象将让我们直接连接到 ESP8266 板上的通用输入/输出GPIO)引脚。红色 LED 连接在引脚 0 上。分配给red变量的Pin对象连接到红色 LED。一旦创建了这个对象,将其值设置为0会打开灯,将其值设置为 1 会关闭灯。

blue变量被定义为连接到 GPIO 引脚 2 的Pin对象,它映射到蓝色 LED。它可以以相同的方式打开和关闭。这个教程的最后部分是一个无限循环,首先打开蓝色 LED 并关闭红色 LED。

在蓝色 LED 关闭并打开红色 LED 之前应用了 1 秒的休眠延迟。在循环再次从头开始执行相同的操作之前,再次应用 1 秒的休眠延迟。

还有更多...

这个教程向你展示了如何控制板载的两个 LED 灯。额外的 LED 灯可以连接到其他可用的 GPIO 引脚,并且可以以类似的方式进行控制。该板载有 9 个可用的 GPIO 引脚。除了简单的单色 LED 灯外,相同的 GPIO 引脚也可以用于连接 NeoPixels,它们提供了全彩色范围,因为它们结合了不同级别的红色、绿色和蓝色 LED 灯。

另请参阅

以下是关于这个教程的一些参考资料:

第十一章:与文件系统交互

在本章中,我们将涵盖许多与文件系统交互相关的教程。第一个教程将涉及如何在使用 Python 代码修改任何文件之前,对需要这样做的设备重新挂载文件系统。

然后,将涵盖列出、删除和创建文件的教程。还将涵盖更高级的主题,如计算磁盘使用量。本章的教程将为你提供将文件系统交互添加到嵌入式项目中所需的工具。当你想要将传感器数据记录到文件中,或者当你希望你的代码读取并加载一组文件到数据结构中时,这将非常有用。当你必须列出一组要在你的应用程序中显示的图像时,这也会很有帮助。

在本章中,我们将涵盖以下教程:

  • 重新挂载文件系统

  • 列出文件

  • 删除文件

  • 创建一个目录

  • 读取文件的内容

  • 写入文件的内容

  • 计算磁盘使用量

技术要求

本章的代码文件可以在本书的 GitHub 存储库的Chapter11文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

本章中的所有教程都使用了 CircuitPython 3.1.2。

重新挂载文件系统

这个教程将向你展示如何重新挂载文件系统,以便可以从你的 Python 脚本中写入数据。一些开发板,比如 Circuit Playground Express,默认情况下会将连接的设备显示为 USB 驱动器,以便轻松编辑和保存你的代码。然而,这种方法的折衷是,你的 Python 代码无法写入或更改开发板存储中的任何内容。在这些开发板上,你必须重新挂载文件系统,以允许你的脚本向其文件系统写入数据。

通过这个教程的最后,你将知道如何允许数据写入文件系统,以及如何恢复更改,这对于某些项目将变得至关重要。例如,如果你想要使用 Circuit Playground Express 来记录温度读数到日志文件,你将需要利用这种方法。

准备工作

你需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何重新挂载文件系统:

  1. 在 REPL 中运行以下代码行:
>>> f = open('main.py')
  1. Python 代码可以打开一个文件进行读取。但是,如果你尝试打开一个文件进行写入,就像下面的代码块中所示,你将会得到一个OSError实例,因为文件系统处于只读模式:
>>> f = open('hi.txt', 'w')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: 30
>>>
  1. 我们现在将创建一个脚本,用于重新挂载文件系统以允许读写数据。

  2. 以下代码应该保存到boot.py文件中。如果文件不存在,那么你将需要创建它:

import storage
storage.remount('/', False)
  1. 从计算机上弹出CIRCUITPY驱动器。

  2. 从计算机上拔下开发板。

  3. 重新连接开发板到计算机。

  4. 在 REPL 中运行以下代码行,确认你的代码可以将数据写入到开发板的存储中:

>>> f = open('hi.txt', 'w')
>>> f.write('hi there')
8
>>> f.close()
  1. 当在 REPL 中运行以下代码块时,将删除boot.py文件:
>>> import os
>>> os.remove('boot.py')
  1. 要将这些更改应用到启动过程中,再次从计算机中弹出CIRCUITPY驱动器。

  2. 从计算机上拔下开发板。

  3. 重新连接开发板到计算机。

  4. 你应该能够编辑并保存main.py文件的内容,就像之前做的那样。

工作原理...

Circuit Playground Express 为你提供了一种从 Python 脚本中启用对存储的读写的方法。我们将代码放在boot.py文件中,因为这个脚本将在启动过程的早期运行,在main.py文件(其中包含我们的主要代码库)之前运行。

boot.py脚本中,我们导入storage模块,然后调用它的remount函数,第二个参数设置为False,表示文件系统应该以读写模式挂载。无论是创建还是删除文件,每当我们对boot.py文件进行更改,都必须对板子进行硬重置,即拔下并重新连接板子,更改才能生效。如本教程所示,恢复此更改的最简单方法是从 REPL 中删除boot.py文件。

还有更多...

一般来说,只有提供 USB 驱动器编辑功能的板子才需要这个额外的重新挂载文件系统的步骤。例如,ESP8266 没有 USB 驱动器功能,因此不需要这一步。还需要注意的是,一旦你在代码中启用了对文件系统的写入,你就无法在文本编辑器中编辑main.py文件。每当你想要回到编辑代码时,你都需要删除boot.py文件。如果你的项目只需要对文件系统进行只读访问,比如列出文件和读取文件内容,那么你可以在任何模式下安全运行。

另请参阅

以下是关于这个教程的一些参考资料:

列出文件

这个教程将向你展示如何在 MicroPython 中列出文件和目录。我们还将展示你可以使用的技术,以便对列表进行过滤,使其只包括文件或只包括目录。一旦你有了以这种方式与文件系统交互的能力,你就可以在自己的项目中使用它,你的代码将接受一个动态的文件列表,这个列表不需要在程序中硬编码。这样,这些文件可能代表一组可配置的音频文件,你想要播放,或者一组图像,你将在连接的屏幕上显示。

准备工作

你需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何列出文件:

  1. 在 REPL 中执行以下代码块:
>>> import os
>>> os.listdir()
['.fseventsd', '.metadata_never_index', '.Trashes', 'boot_out.txt', 'main.py', 'lib']
  1. 将生成顶级文件夹中所有文件和目录的列表。下面的代码块将生成相同的列表,但是是排序后的。
>>> sorted(os.listdir())
['.Trashes', '.fseventsd', '.metadata_never_index', 'boot_out.txt', 'lib', 'main.py']
  1. 我们还可以列出特定目录中的文件,如下面的代码块所示:
>>> os.listdir('.fseventsd')
['no_log']
  1. 下面的代码块将检查并显示lib路径不是一个文件:
>>> FILE_CODE  = 0x8000
>>> 
>>> os.stat('lib')[0] == FILE_CODE
False
  1. 现在,我们将确认main.py被检测为一个文件:
>>> os.stat('main.py')[0] == FILE_CODE
True
  1. 下面的代码块定义并调用isfile函数来验证两个路径的类型:
>>> def isfile(path):
...     return os.stat(path)[0] == FILE_CODE
...     
...     
... 
>>> isfile('lib')
False
>>> isfile('main.py')
True
>>> 
  1. 下面的代码块将列出根路径中的所有文件:
>>> files = [i for i in sorted(os.listdir()) if isfile(i)]
>>> files
['.Trashes', '.metadata_never_index', 'boot_out.txt', 'main.py']
  1. 现在,我们将列出根路径中的所有目录:
>>> dirs = [i for i in sorted(os.listdir()) if not isfile(i)]
>>> dirs
['.fseventsd', 'lib']
  1. 以下代码应该放入main.py文件中:
import os

FILE_CODE = 0x8000

def isfile(path):
    return os.stat(path)[0] == FILE_CODE

def main():
    files = [i for i in sorted(os.listdir()) if isfile(i)]
    print('files:', files)
    dirs = [i for i in sorted(os.listdir()) if not isfile(i)]
    print('dirs:', dirs)

main()

当执行时,此脚本将打印出根路径中文件和目录的排序列表。

它是如何工作的...

在导入os模块后,定义了一个名为isfile的函数,它将根据提供的路径是文件还是目录返回TrueFalse。定义并调用了main函数,之后它将生成一个路径名列表。第一个列表将检索排序后的路径列表,然后过滤列表,只保留文件。然后打印出这个列表。然后采取同样的方法来获取目录列表并打印出来。

还有更多...

本文介绍了处理文件时可以派上用场的一些技术。它表明,默认情况下,文件列表不会按字母顺序返回,因此如果需要,可以使用内置的sorted函数对文件列表进行排序。它还定义了一个名为isfile的函数,用于检查特定路径是否为文件。如果需要,您可以创建一个等效的isdir函数。本文还展示了使用列表推导的简单方法,以过滤默认列表以生成仅包含特定类型条目的路径的过滤列表,例如文件或目录。

另请参阅

有关本文的一些参考资料:

删除文件

本文将向您展示如何在 MicroPython 中删除文件和目录。有专门的函数用于删除文件和删除目录。我们将向您展示如何为每种类型的路径调用这些不同的函数。然后,我们将向您展示如何创建一个通用函数,可以自动删除任一类型的路径。

在您创建的项目中,有许多情况需要删除文件。您可能创建一个将数据记录到文件中的项目。日志轮换是一种机制,可以让您定期创建新的日志文件并自动删除旧文件。您将需要删除文件的功能来实现日志轮换。在许多 MicroPython 嵌入式项目中,由于这些板上的存储容量有限,删除文件以节省空间的问题变得更加重要。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本文中提供的代码。确保您已经完成了本章的重新挂载文件系统配方,因为您需要对存储系统具有写访问权限才能删除文件。

如何做...

按照以下步骤学习如何删除文件:

  1. 在根路径下创建一个名为hi.txt的文件。

  2. 使用 REPL 运行以下代码行:

>>> import os
>>> os.remove('hi.txt')
  1. hi.txt文件现在已从板的文件系统中删除。运行以下代码块。它应该会出现异常,因为文件不再存在:
>>> os.remove('hi.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 2] No such file/directory
>>> 
  1. 在根路径下创建一个名为mydir的目录。

  2. 以下代码块将删除mydir目录:

>>> os.rmdir('mydir')
  1. 以下代码块定义了isfile函数:
>>> FILE_CODE = 0x8000
>>> 
>>> def isfile(path):
...     return os.stat(path)[0] == FILE_CODE
...     
...     
... 
>>>
  1. 现在我们可以定义一个名为any_remove的函数,它将删除任何类型的路径:
>>> def any_remove(path):
...     func = os.remove if isfile(path) else os.rmdir
...     func(path)
...     
...     
... 
>>> 
  1. 在根路径下创建一个名为hi.txt和一个名为mydir的目录。

  2. 运行以下代码块:

>>> any_remove('hi.txt')
>>> any_remove('mydir')
>>> 

前面的代码块现在使用相同的函数调用删除了此文件和目录。

工作原理...

首先定义的any_remove函数接受路径并设置一个名为func的变量。此变量将存储需要调用以删除提供的路径的可调用对象。检查路径的类型,并根据提供的路径类型设置funcos.removeos.rmdir。然后使用提供的路径调用此函数以执行实际的删除。

更多信息...

本教程介绍了一种您可以使用的技术,用于创建一个方便的函数,该函数接受任何类型的路径,并调用正确的底层函数来删除它。需要记住的一件事是,您只能删除空目录。本教程中的函数和示例支持删除空目录,但如果使用具有文件的目录调用,则会失败。您可以扩展delete函数以执行递归目录列表,然后删除所有子文件夹和目录。

另请参阅

以下是关于本教程的一些参考资料:

创建目录

这个教程将向您展示如何在 MicroPython 中创建一个目录。我们还将向您展示如何创建一个可以多次调用相同路径的函数,只有在目录尚不存在时才会创建目录。然后,我们将定义一个函数,其行为与 Python 标准库中的makedirs函数相同,但在 MicroPython 中没有包含。

这组功能在您需要创建一个可能需要创建特定目录树并用一组特定文件填充的项目时非常有用。当您在像 ESP8266 这样的开发板上工作时,也有必要访问这些功能,因为它只能让您通过 REPL 和 Python 代码创建所需的目录。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 才能运行本教程中提供的代码。确保您已经完成了本章中的重新挂载文件系统教程,因为本教程需要对存储系统进行写访问。

如何操作...

按照以下步骤学习如何创建目录:

  1. 在 REPL 中运行以下代码行:
>>> import os
>>> os.mkdir('mydir')
  1. 现在已经创建了一个名为mydir的目录。当您运行以下代码块时,将引发异常,因为该目录已经存在:
>>> os.mkdir('mydir')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 17] File exists
>>> 
  1. 以下代码块将定义一个根据路径是否存在返回TrueFalse的函数:
>>> def exists(path):
...     try:
...         os.stat(path)
...     except OSError:
...         return False
...     return True
...     
...     
... 
>>> 
  1. 以下代码块将在两个不同的路径上调用exists函数,以验证其是否正常工作:
>>> exists('main.py')
True
>>> exists('invalid_path')
False
  1. 现在我们可以定义一个名为mkdir_safe的函数,它只在目录不存在时才创建目录:
>>> def mkdir_safe(path):
...     if not exists(path):
...         os.mkdir(path)
...         
...         
... 
>>> 
  1. 以下代码块将在相同路径上多次调用mkdir_safe函数,不会引发异常:
>>> mkdir_safe('newdir')
>>> mkdir_safe('newdir')
  1. 我们现在将定义一个可以递归创建目录的函数:
>>> def makedirs(path):
...     items = path.strip('/').split('/')
...     count = len(items)
...     paths = ['/' + '/'.join(items[0:i + 1]) for i in 
...     range(count)]
...     for path in paths:
...         os.mkdir(path)
...         
...         
... 
>>> 
  1. 运行以下代码块:
>>> makedirs('/top/middle/bottom')

执行上述代码块将按正确顺序从上到下创建三个目录。

工作原理...

在本教程中,定义并使用了三个不同的函数,每个函数执行特定的功能。exists函数检查路径是否存在,并返回TrueFalse。此检查尝试在路径上调用stat函数,并捕获可能引发的任何OSError。如果路径存在,则不会引发此异常,并返回True值;否则返回False值。

下一个函数mkdir_safe只是检查路径是否存在,并且仅在不存在路径时调用mkdir函数。最后,定义了makedirs函数,该函数接收具有多个级别的路径。路径被拆分为其各个部分,然后将要创建的路径列表按正确顺序保存在列表中,从最高路径到最低路径。通过循环遍历每个路径,并通过调用mkdir函数来创建每个路径。

还有更多...

这个教程介绍了三个通用函数,每个函数都有特定的目的。通过以这种方式创建代码片段,可以更容易地将一个项目的片段并入其他项目中。其中两个定义的函数——existsmakedirs——是 Python 标准库的一部分,但在 MicroPython 中找不到。这个教程演示了在许多情况下,即使在 Python 标准库中缺少某个函数,您也经常可以在 MicroPython 中创建自己的实现。

另请参阅

以下是关于这个教程的一些参考资料:

读取文件的内容

这个教程将向您展示如何将文件内容读入脚本中的变量。这个教程将涵盖将文件内容作为字符串读取的方法,以及将其作为字节对象读取的方法。您创建的许多项目通常需要打开不同的数据文件,如音频文件、图像和文本文件。这个教程将为您提供基本的构建模块,以便您可以促进这些交互。

准备工作

您需要访问 Circuit Playground Express 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何读取文件的内容:

  1. 在根路径下创建一个名为hi.txt的文件,其中包含以下内容:
hi there
  1. 在 REPL 中执行以下代码块:
>>> f = open('hi.txt')
>>> data = f.read()
>>> f.close()
>>> data
'hi there\n'
  1. 名为hi.txt的文件的内容被读入一个名为data的变量中,然后作为输出显示。下面的代码块也将文件的内容读入一个变量中,但使用with语句:
>>> with open('hi.txt') as f:
...     data = f.read()
...     
... 
>>> data
'hi there\n'
  1. 可以通过一行代码将文件的内容读入一个变量中,如下例所示:
>>> data = open('hi.txt').read()
>>> data
'hi there\n'
  1. 执行以下代码块:
>>> data = open('hi.txt', 'rb').read()
>>> data
b'hi there\n'
>>> type(data)
<class 'bytes'>

上述代码块在执行时,将以bytes对象而不是字符串读取文件内容。

它是如何工作的...

在这个教程中,我们探讨了从文件中读取数据的四种不同方法。第一种方法使用open函数获取文件对象。然后,从这个文件对象中读取数据并关闭。然后,我们可以改进这种较旧的文件处理方式,如第二个例子所示,使用with语句,它将在退出with块时自动关闭文件。第三个例子在一行中打开并读取所有内容。open函数接受文件模式作为第二个参数。如果我们传递rb值,那么它将以二进制模式打开文件。这将导致返回一个字节对象,而不是一个字符串。

还有更多...

您需要根据您希望与之交互的数据选择正确的读取文件数据的方法。如果您的数据文件是纯文本文件,那么默认的文本模式就足够了。但是,如果您要读取.wav文件格式的原始音频数据,您需要将数据读取为二进制数据,可能会出现异常,因为数据可能无法解码为字符串。

另请参阅

以下是关于这个教程的一些参考资料:

写入文件的内容

这个配方将向您展示如何将数据写入输出文件。我们将介绍如何将字符串以及字节写入文件。然后,我们将定义一种对象类型,以便更容易执行这些常见的操作,将文本和二进制数据写入文件。如果您想创建一个将传感器数据保存到日志文件或将一些用户生成的数据记录到板的存储器中的项目,那么您将需要使用我们在这个配方中描述的许多技术。

准备工作

您需要在 Circuit Playground Express 上访问 REPL 以运行本配方中提供的代码。确保您已经完成了本章中的重新挂载文件系统配方,因为这个配方需要对存储系统进行写访问。

如何做...

按照以下步骤学习如何写入文件的内容:

  1. 使用 REPL 运行以下代码行:
>>> with open('hi.txt', 'w') as f:
...     count = f.write('hi there')
...     
...     
... 
>>> count
8
  1. 文本读取hi there已经使用文件对象的write方法写入到名为hi.txt的文件中。然后返回并显示写入的字节数。以下代码块将获取一个字节对象并将其传递给write方法,以便它可以将数据写入提供的文件中:
>>> with open('hi.txt', 'wb') as f:
...     count = f.write(b'hi there')
...     
...     
... 
>>> count
8
>>>
  1. 以下代码块将定义一个Path类,其中包含两个方法。一个方法将初始化新对象,而另一个方法将生成对象的表示:
>>> class Path:
...     def __init__(self, path):
...         self._path = path
...         
...     def __repr__(self):
...         return "Path(%r)" % self._path
...         
... 
>>> 
>>> path = Path('hi.txt')
>>> path
Path('hi.txt')
>>> 
  1. 执行以下代码块后,我们将得到一个Path类,其中包含两个额外的方法,以便我们可以将文本和二进制数据写入文件:
>>> class Path:
...     def __init__(self, path):
...         self._path = path
...         
...     def __repr__(self):
...         return "Path(%r)" % self._path
...         
...     def write_bytes(self, data):
...         with open(self._path, 'wb') as f:
...             return f.write(data)
...             
...     def write_text(self, data):
...         with open(self._path, 'w') as f:
...             return f.write(data)
...             
... 
>>> 
>>> path = Path('hi.txt')
>>> path.write_text('hi there')
8
>>> path.write_bytes(b'hi there')
8
  1. 将以下代码块保存到名为pathlib.py的文件中,以便可以导入:
class Path:
    def __init__(self, path):
        self._path = path

    def __repr__(self):
        return "Path(%r)" % self._path

    def write_bytes(self, data):
        with open(self._path, 'wb') as f:
            return f.write(data)

    def write_text(self, data):
        with open(self._path, 'w') as f:
            return f.write(data)
  1. 以下代码应该放入main.py文件中:
from pathlib import Path

Path('hi.txt').write_text('hi there')

当执行此脚本时,它将把文本hi there消息写入到hi.txt文件中。

它是如何工作的...

在这个配方中,我们首先展示了写入字节和文本数据到文件的两种最直接的方法。然后,我们创建了一个名为Path的类,这个类分为两个阶段构建。第一个版本让我们创建Path对象,这些对象可以跟踪它们的路径,并在请求时返回一个人类可读的表示。

然后,我们添加了辅助方法来帮助写入文本数据或二进制数据。类的名称和方法与 Python 的pathlib模块中的Path对象具有相同的命名和功能。最终的代码块展示了一个简单的示例,导入pathlib模块并调用其write_text方法将一些文本保存到文件中。

还有更多...

在一些项目中,您可能会发现自己处于一种情况中,需要与文件、它们的路径进行交互,并经常需要写入和读取数据。在这些情况下,拥有一个简化文件访问的类将非常有帮助。在这个配方中定义的Path对象非常适合这个目的。我们还遵循了 Python 标准库中的一个模块的相同命名和功能。这将使我们的代码在想要在具有完整 Python 安装的计算机上运行时更具可读性和可移植性。

另请参阅

以下是关于这个配方的一些参考资料:

计算磁盘使用量

这个配方将向您展示如何检查与存储系统相关的一些数字。我们将检索文件系统的块大小、总块数和空闲块数。然后我们可以使用这些数字来计算一些有用的数字,比如总磁盘容量以及磁盘上已使用和空闲的空间。

然后,我们将所有这些代码打包到一个函数中,以便在需要访问这些信息时更容易调用。您可以使用本配方中展示的技术来实现项目中的许多事情。例如,您可以使用它来查找设备上可用的总存储空间,因为这在不同的板之间有所不同。您甚至可以使用它来判断磁盘是否变得太满,以及您的脚本是否应该删除一些旧的日志文件。

准备就绪

您需要访问 Circuit Playground Express 上的 REPL 才能运行此配方中提供的代码。

如何做...

按照以下步骤学习如何计算磁盘使用情况:

  1. 在 REPL 中运行以下代码行:
>>> import os
>>> stats = os.statvfs('/')
>>> stats
(1024, 1024, 2024, 1040, 1040, 0, 0, 0, 0, 255)
  1. 我们现在已经检索到了所有关键的文件系统信息,但它以元组的形式呈现,这使得很难知道哪个数字与什么相关。在以下代码块中,我们将把我们关心的值赋给更易读的变量名:
>>> block_size = stats[0]
>>> total_blocks = stats[2]
>>> free_blocks = stats[3]
  1. 现在我们已经将这些关键信息存储在易读的变量中,我们可以继续以下代码块来计算我们感兴趣的主要值:
>>> stats = dict()
>>> stats['free'] = block_size * free_blocks
>>> stats['total'] = block_size * total_blocks
>>> stats['used'] = stats['total'] - stats['free']
>>> stats
{'free': 1064960, 'used': 1007616, 'total': 2072576}
  1. 以下代码块将所有这些逻辑封装到一个单独的函数中:
>>> def get_disk_stats():
...     stats = os.statvfs('/')
...     block_size = stats[0]
...     total_blocks = stats[2]
...     free_blocks = stats[3]
...     stats = dict()
...     stats['free'] = block_size * free_blocks
...     stats['total'] = block_size * total_blocks
...     stats['used'] = stats['total'] - stats['free']
...     return stats
... 
>>> 
>>> get_disk_stats()
{'free': 1064960, 'used': 1007616, 'total': 2072576}
>>> 
  1. 以下函数将以更易读的方式格式化以字节表示的值:
>>> def format_size(val):
...     val = int(val / 1024)               # convert bytes to KiB
...     val = '{:,}'.format(val)            # add thousand separator
...     val = '{0: >6} KiB'.format(val)     # right align amounts
...     return val
...     
...     
... 
>>> print('total space:', format_size(stats['total']))
total space:  2,024 KiB
>>> 
  1. 我们现在可以创建一个函数,打印与总磁盘大小和使用情况相关的一些关键数字:
>>> def print_stats():
...     stats = get_disk_stats()
...     print('free space: ', format_size(stats['free']))
...     print('used space: ', format_size(stats['used']))
...     print('total space:', format_size(stats['total']))
...     
...     
... 
>>> print_stats()
free space:   1,040 KiB
used space:     984 KiB
total space:  2,024 KiB
>>>
  1. 以下代码应放入main.py文件中:
import os

def get_disk_stats():
    stats = os.statvfs('/')
    block_size = stats[0]
    total_blocks = stats[2]
    free_blocks = stats[3]
    stats = dict()
    stats['free'] = block_size * free_blocks
    stats['total'] = block_size * total_blocks
    stats['used'] = stats['total'] - stats['free']
    return stats

def format_size(val):
    val = int(val / 1024)               # convert bytes to KiB
    val = '{:,}'.format(val)            # add thousand separator
    val = '{0: >6} KiB'.format(val)     # right align amounts
    return val

def print_stats():
    stats = get_disk_stats()
    print('free space: ', format_size(stats['free']))
    print('used space: ', format_size(stats['used']))
    print('total space:', format_size(stats['total']))

print_stats()

当执行此脚本时,它将打印有关磁盘空间的空闲、已使用和总空间的详细信息。

它是如何工作的...

statvfs函数返回与板上的文件系统相关的一些关键数字。在这个元组中,我们关心三个值,它们分别对应block_sizetotal_blocksfree_blocks变量。我们可以将这些值相乘,以字节为单位计算出空闲、已使用和总磁盘空间的数量。然后,format_size函数被定义为将字节值转换为KiB,添加千位分隔符,并右对齐这些值。print_stats函数简单地通过获取文件系统的stats并在每个值上调用format_size函数来组合所有这些代码。

还有更多...

Circuit Playground Express 配备了 2MB 的闪存,可以在此配方的输出中看到。MicroPython 使用 FAT 格式进行文件系统。您可以尝试在板上添加一些文件,然后重新运行脚本以查看文件系统使用情况的变化。请记住,要看到这些变化反映在多个板上,您必须弹出 USB 设备并重新插入以获取最新的文件系统使用情况。

另请参阅

以下是关于此配方的一些参考资料:

第十二章:网络

本章将介绍与执行网络操作相关的各种主题。将呈现一些简单的低级示例,例如使用低级套接字库执行 DNS 查找。还将呈现 HTTP 客户端和服务器实现。然后,我们将向您展示如何创建一个应用程序,该应用程序可以让您使用 Web 浏览器控制微控制器上的 LED 灯。

本章将帮助您创建一个需要从互联网获取信息的 MicroPython 项目。这可以帮助您在需要为人们提供一种使用其手机和计算机上的 Web 浏览器直接连接和与您的 MicroPython 板交互的方式时使用。

在本章中,我们将涵盖以下主题:

  • 执行 DNS 查找

  • 创建一个等待互联网连接的函数

  • 使用原始套接字执行 HTTP 请求

  • 使用 urequests 库执行 HTTP 请求

  • 从 RESTful web 服务获取 JSON 数据

  • 创建一个 HTTP 服务器

  • 创建一个 web 处理程序模块

  • 通过 web 服务器控制 LED 灯

  • 开发一个 RESTful API 来控制 LED 灯

技术要求

本章的代码文件可以在本书的 GitHub 存储库的Chapter12文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

本章使用 Adafruit Feather HUZZAH ESP8266。本章中的所有配方都使用了 CircuitPython 3.1.2。您应该应用我们在第十章中描述的连接到现有 Wi-Fi 网络配方中的配置,控制 ESP8266。此配方将使您能够运行本章中连接到互联网的所有配方,以及涉及您从计算机连接到您的板的配方。

执行 DNS 查找

本教程将向您展示如何编写可以在 MicroPython 上运行的代码,以执行 DNS 查找。每当我们的应用程序尝试连接到主机时,首先要做的一步是使用 DNS 协议查找主机名并获取主机的 IP 地址,以便您可以打开到该 IP 地址的连接。

本教程向您展示了如何执行 DNS 查找,然后将这些代码行打包到一个函数中,您可以在需要获取特定主机的 IP 地址时调用该函数。当您的设备或网络出现网络问题并且需要一些简单的测试来排除故障时,本教程可以在您的项目中发挥作用,或者当您想要跟踪主机名及其相关 IP 地址时。

准备就绪

您需要访问 ESP8266 上的 REPL,以运行本教程中提供的代码。

如何做...

按照以下步骤学习如何执行 DNS 查找:

  1. 在 REPL 中运行以下代码行:
>>> import socket
>>> 
>>> addr_info = socket.getaddrinfo('python.org', 80)
>>> addr_info
[(2, 1, 0, '', ('45.55.99.72', 80))]
  1. 我们已经使用getaddrinfo函数对python.org执行了 DNS 查找,并获取了其 IP 地址。以下代码块将访问返回的数据结构中包含 IP 地址的指定字符串:
>>> ip = addr_info[0][-1][0]
>>> ip
'45.55.99.72'
  1. 现在我们可以将这段代码封装在一个函数中,以返回给定主机名的 IP 地址:
>>> def get_ip(host, port=80):
...     addr_info = socket.getaddrinfo(host, port)
...     return addr_info[0][-1][0]
...     
...     
... 
>>> 
  1. 我们将使用不同主机名调用此函数,以验证其是否正常工作:
>>> get_ip('python.org')
'45.55.99.72'
>>> get_ip('micropython.org')
'176.58.119.26'
>>> get_ip('pypi.org')
'151.101.0.223'
  1. 以下代码应放入main.py文件中:
import socket
import time

BOOTUP_WIFI_DELAY = 5

def get_ip(host, port=80):
    addr_info = socket.getaddrinfo(host, port)
    return addr_info[0][-1][0]

def main():
    print('applying wifi delay...')
    time.sleep(BOOTUP_WIFI_DELAY)
    print('performing DNS lookup...')
    hosts = ['python.org', 'micropython.org', 'pypi.org']
    for host in hosts:
        print(host, get_ip(host))

main()

执行此脚本时,它将对三个主机名执行 DNS 查找,并打印出每次查找的结果。

工作原理...

定义的get_ip函数将接收主机名作为第一个参数,并在每次函数调用结束时执行主机名的 DNS 查找,然后返回 IP 地址。还有一个可选参数,用于指定连接到主机时要使用的 TCP 端口。TCP 端口的默认值设置为端口80。这是 HTTP 协议的端口号。这意味着此函数将适用于托管 web 服务器的主机。

main函数在启动时睡眠五秒,以便让板子在启动时建立到 Wi-Fi 网络的连接,然后执行 DNS 查找。然后,main函数循环遍历三个主机名,并对每个主机名执行 DNS 查找,通过for循环打印出每个条目的主机名和返回的 IP 地址。

还有更多...

这个脚本完成了它的任务,但还有改进的空间。大多数 Wi-Fi 网络上的五秒延迟已经足够了。然而,有些网络可能需要更长的时间,所以这个值应该增加。但是,如果我们把这个值设置得很高,而网络连接很快,那么我们就会不必要地等待太久。在下一个教程中,我们将介绍一种等待互联网连接的方法,以更有效的方式解决这两个限制。每当你在代码中使用硬编码的睡眠数值时,你应该深入挖掘并寻找更好的解决方案。

另请参阅

以下是关于本教程的一些参考资料:

创建一个等待互联网连接的函数

本教程将向您展示如何编写一个函数,在启动时轮询您的 Wi-Fi 连接状态。我们将把这个函数命名为wait_for_networking。一旦它检测到连接已成功建立,并且通过 DHCP 分配了 IP 地址,wait_for_networking函数将返回。

每当你有一个需要互联网连接的项目时,你都会在启动时面临这个问题。如果你的脚本立即启动,在网络连接建立之前连接到互联网,它将引发异常并无法继续执行。使用本教程中的方法将允许你在网络连接正确建立后开始程序的其余执行。

准备工作

您需要在 ESP8266 上访问 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何创建一个等待互联网连接的函数:

  1. 在 REPL 中执行以下代码块:
>>> import network
>>> import time
>>> 
>>> station = network.WLAN(network.STA_IF)
>>> station.isconnected()
False
  1. 当代码运行得足够早时,它将返回一个False值。再次调用isconnected,但在稍后的阶段,将得到以下输出:
>>> station.isconnected()
True
  1. 下面的代码块可以用来在网络连接后检索分配的 IP 地址:
>>> ip = station.ifconfig()[0]
>>> ip
'10.0.0.38'
  1. 以下函数将所有这些代码整合到一个函数中,该函数将等待直到建立网络连接并为设备分配 IP 地址。然后返回 IP 地址:
>>> def wait_for_networking():
...     station = network.WLAN(network.STA_IF)
...     while not station.isconnected():
...         print('waiting for network...')
...         time.sleep(1)
...     ip = station.ifconfig()[0]
...     print('address on network:', ip)
...     return ip
...     
...     
... 
>>> 
>>> ip = wait_for_networking()
address on network: 10.0.0.38
>>> ip
'10.0.0.38'
>>> 
  1. 以下代码应该放入netcheck.py文件中:
import network
import time

def wait_for_networking():
    station = network.WLAN(network.STA_IF)
    while not station.isconnected():
        print('waiting for network...')
        time.sleep(1)
    ip = station.ifconfig()[0]
    print('address on network:', ip)
    return ip
  1. 以下代码块应该放入main.py文件中:
from netcheck import wait_for_networking

def main():
    ip = wait_for_networking()
    print('main started')
    print('device ip:', ip)

main()

当执行这个脚本时,它将等待网络连接建立,然后打印出分配给设备的 IP 地址。

它是如何工作的...

在本教程中,我们创建了一个名为netcheck的 Python 模块。该模块中的wait_for_networking函数将创建一个名为station的 WLAN 对象。station对象将用于以 1 秒的间隔轮询网络,直到建立网络连接。然后,它将使用ifconfig方法从station对象中获取分配的 IP。然后将这个 IP 地址返回给调用函数。本教程中的主要脚本只是从netcheck模块中导入wait_for_networking函数,并在执行开始时调用它,然后打印出返回的 IP 地址。

还有更多...

许多网络连接的项目应该在网络连接后才启动其主要代码块。当你在典型的计算机上运行 Python 时,操作系统会在为你启动其他服务之前确保所有网络连接正常。在 MicroPython 的情况下,没有操作系统,它只是在裸机上运行你的脚本。你必须考虑这些因素,以便你的代码能够正确运行。

另请参阅

以下是关于这个教程的一些参考资料:

使用原始套接字执行 HTTP 请求

这个教程将使用 MicroPython 和 Python 都带有的socket库来执行 HTTP 请求。我们将创建一个接收 URL 作为输入并在执行 HTTP 请求后返回所请求的 Web 服务器的响应的函数。我们还将创建一个函数,它可以解析 URL 并返回 URL 的主机名和路径组件。这些部分将需要执行 HTTP 请求。

有一整类 MicroPython 项目希望连接到互联网上的 Web 服务器并获取不同的结果。你将看到一种使用socket库来实现这一点的方法,它可以直接访问 TCP 套接字,从中读取和写入数据字节。

准备工作

你需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何使用原始套接字执行 HTTP 请求:

  1. 使用 REPL 运行以下代码行:
>>> import socket
>>> import time
>>> 
>>> def parse_url(url):
...     return url.replace('http://', '').split('/', 1)
...     
...     
... 
>>> 
  1. 我们已经成功创建了parse_url函数,它接受一个 URL 并返回hostpath组件:
>>> url = 'http://micropython.org/ks/test.html'
>>> host, path = parse_url(url)
>>> host
'micropython.org'
>>> path
'ks/test.html'
>>> 
  1. 然后使用示例 URL 调用parse_url以演示其功能。在下面的代码块中,我们定义并调用一个函数来查找特定主机名的 IP 地址:
>>> HTTP_PORT = 80
>>> def get_ip(host, port=HTTP_PORT):
...     addr_info = socket.getaddrinfo(host, port)
...     return addr_info[0][-1][0]
...     
...     
... 
>>> ip = get_ip(host)
>>> ip
'176.58.119.26'
>>> 
  1. 现在我们可以定义fetch函数,它接收 URL 作为输入,并从 Web 服务器检索其内容:
>>> HTTP_REQUEST = 'GET /{path} HTTP/1.0\r\nHost: {host}\r\n\r\n'
>>> BUFFER_SIZE = 1024
>>> 
>>> def fetch(url):
...     host, path = parse_url(url)
...     ip = get_ip(host)
...     sock = socket.socket()
...     sock.connect((ip, 80))
...     request = HTTP_REQUEST.format(host=host, path=path)
...     sock.send(bytes(request, 'utf8'))
...     response = b''
...     while True:
...         chunk = sock.recv(BUFFER_SIZE)
...         if not chunk:
...             break
...         response += chunk
...     sock.close()
...     body = response.split(b'\r\n\r\n', 1)[1]
...     return str(body, 'utf8')
...     
...     
... 
>>> 
  1. 现在我们可以调用这个函数并检查它返回的结果:
>>> html = fetch('http://micropython.org/ks/test.html')
>>> html
'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'
>>> print(html)
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Test</title>
    </head>
    <body>
        <h1>Test</h1>
        It's working if you can read this!
    </body>
</html>

>>> 
  1. 以下代码应该放入main.py文件中:
from netcheck import wait_for_networking
import socket

HTTP_REQUEST = 'GET /{path} HTTP/1.0\r\nHost: {host}\r\n\r\n'
HTTP_PORT = 80
BUFFER_SIZE = 1024

def parse_url(url):
    return url.replace('http://', '').split('/', 1)

def get_ip(host, port=HTTP_PORT):
    addr_info = socket.getaddrinfo(host, port)
    return addr_info[0][-1][0]

def fetch(url):
    host, path = parse_url(url)
    ip = get_ip(host)
    sock = socket.socket()
    sock.connect((ip, 80))
    request = HTTP_REQUEST.format(host=host, path=path)
    sock.send(bytes(request, 'utf8'))
    response = b''
    while True:
        chunk = sock.recv(BUFFER_SIZE)
        if not chunk:
            break
        response += chunk
    sock.close()
    body = response.split(b'\r\n\r\n', 1)[1]
    return str(body, 'utf8')

def main():
    wait_for_networking()
    html = fetch('http://micropython.org/ks/test.html')
    print(html)

main()

当这个脚本被执行时,它将获取特定 URL 的内容并输出返回的结果。

它是如何工作的...

parse_url函数将返回我们感兴趣的 URL 的两个主要部分。这是通过删除 URL 的初始部分并使用/字符进行单个字符串分割来完成的。一旦执行了这些操作,我们将得到 URL 的主机名和路径部分,然后我们可以返回它。fetch函数首先调用parse_urlget_ip来获取给定 URL 的主机名、路径和 IP 地址信息。一旦完成,就会创建一个到 Web 服务器的套接字连接。

通过填写名为HTTP_REQUEST的模板来创建 HTTP 请求。然后将此请求通过网络传输到 Web 服务器。结果将不断被读取为一系列块,直到达到响应的末尾。这些数据块被连接在一起成为一个名为 response 的变量。

响应中包含了 HTTP 头和响应体。我们只对响应体感兴趣,所以我们使用split方法来提取它。一旦提取出来,它就会从bytes对象转换为字符串并返回。main函数在调用时将等待网络连接,然后调用fetch函数获取 URL 的 HTML 内容。然后将此内容打印出来。

还有更多...

这个示例中有很多内容。在某种程度上,这是一个学习 TCP 套接字、HTTP、HTML 和 URL 的好方法,因为所有的低级细节都暴露给了你。MicroPython 没有提供与 Python 标准库一起提供的高级 HTTP 请求库。

因此,当您想要从头开始编写代码从 Web 服务器获取内容时,您必须以与本示例中介绍的相同方式使用socket库。在下一个示例中,我们将看到有一个 Python 模块可以与 MicroPython 一起工作,我们可以将其添加到我们的项目中,以使执行这些 HTTP 请求变得更简单。

另请参阅

以下是关于这个示例的一些参考资料:

使用urequests库执行 HTTP 请求

这个示例将使用urequests库,这个库是专门为与 MicroPython 一起使用而编写的。它提供了一种方便的方式来连接到 Web 服务器并执行 HTTP 请求。这个库提供了一个对象,您可以使用它来执行所有的 HTTP 交互。请求完成后,您可以访问这个对象的不同属性,以获取有关完成请求的各种信息。

这个示例将探讨如何使用这个库以及可能想要在其对象上访问的不同属性。当您开始创建需要进行 HTTP 请求的 MicroPython 项目时,这个库将处理许多低级细节,使得您的代码更简单、更易读,并让您专注于手头的任务,而不是被低级 TCP 套接字细节所困扰。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本示例中提供的代码。

如何做...

按照以下步骤学习如何使用urequests执行 HTTP 请求:

  1. github.com/micropython/micropython-lib/blob/master/urequests/urequests.py下载urequests.py

  2. 将这个 Python 模块保存在您的开发板的顶层目录中。

  3. 在 REPL 中运行以下代码行:

>>> import urequests
>>> 
>>> url = 'http://micropython.org/ks/test.html'
>>> req = urequests.get(url)
  1. 我们现在有一个完成的 HTTP 请求,并且其结果存储在req变量中。以下代码块将输出我们从 Web 服务器收到的 HTML 响应:
>>> req.text
'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'
>>> print(req.text)
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Test</title>
    </head>
    <body>
        <h1>Test</h1>
        It's working if you can read this!
    </body>
</html>

>>> 
  1. 我们还可以以二进制形式访问原始内容,如下面的代码块所示:
>>> req.content
b'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'
>>> 
  1. status_code属性为我们提供了响应的 HTTP 状态代码:
>>> req.status_code
200
  1. 以下代码块将尝试访问一个不存在的页面:
>>> url = 'http://micropython.org/no_such_page_exists'
>>> req = urequests.get(url)
  1. 我们现在可以检查状态代码和解释文本的值:
>>> req.status_code
404
>>> req.reason
b'Not Found'
>>> 
  1. 以下代码应该放入main.py文件中:
from netcheck import wait_for_networking
import urequests

def main():
    wait_for_networking()
    url = 'http://micropython.org/ks/test.html'
    html = urequests.get(url).text
    print(html)

main()

当执行此脚本时,它将使用urequests库来获取特定 URL 的内容,并输出页面的 HTML 内容。

工作原理...

urequests库提供了一个名为get的函数,让您可以在 Web 服务器上执行 HTTP GET请求。一旦调用了这个函数,它将返回一个对象,该对象具有许多有用的属性,您可以访问这些属性来检索此请求的结果。

您可以通过访问content属性获取原始响应作为字节对象,或者通过访问text属性获取值作为字符串。最后,您可以使用status_codereason属性来检查请求是否成功或失败,以及失败的原因是什么。然后将此代码封装并放入main函数中。当调用主函数时,它将连接到 Web 服务器并输出返回的 HTML 文档的内容。

还有更多...

本教程展示了一种在 MicroPython 中进行 HTTP 请求的良好方式,而无需深入细节。这是一个很好的例子,说明了一个设计良好的库如何使您的代码更易读和可维护。

该库提供了许多 HTTP 方法,除了基本的 HTTP GET请求。HEADPOSTPUTPATCHDELETE方法也都可用。它们都可以使用与请求方法匹配的函数名称进行访问。在与需要正确指定 HTTP 方法才能正常工作的网络服务进行交互时,这可能非常有用。如果您每次使用urequests库进行 HTTP 调用时都收到警告消息,您可以尝试使用fix_urequests_warnings.py文件来解决此问题。您可以在本书的 GitHub 存储库的Chapter12文件夹中找到此脚本。

另请参阅

以下是关于本教程的一些参考资料:

从 RESTful 网络服务获取 JSON 数据

本教程将向您展示一个示例,以连接到互联网上的服务器,以便使用其 RESTful 网络服务。网络服务将以 JSON 格式提供数据,然后对其进行解析,以便我们可以访问返回数据集的不同部分。

我们将使用一个提供国际空间站ISS)当前位置的网络服务。由于 ISS 以每小时 28,000 公里的惊人速度移动,我们可以观察到它的位置(以经度和纬度表示)随着我们反复调用这个网络服务而发生变化。每当您想要创建一个连接到基于互联网的网络服务丰富世界的 MicroPython 项目时,您可以使用本教程中介绍的技术作为构建这些连接的起点。

准备工作

您将需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何从 RESTful 网络服务获取 JSON 数据:

  1. 在 REPL 中执行以下代码块:
>>> import urequests
>>> import time
>>> 
>>> ISS_API_URL = 'http://api.open-notify.org/iss-now.json'
>>> req = urequests.get(ISS_API_URL)
  1. 我们已成功连接到网络服务并检索到了空间站的位置。以下代码块将检查返回给我们的数据:
>>> req.text
'{"timestamp": 1555012195, "iss_position": {"latitude": "-33.1779", "longitude": "45.0667"}, "message": "success"}'
>>> 
  1. 数据以 JSON 格式提供。我们可以使用以下代码块来解析文本数据并生成一组嵌套字典:
>>> data = req.json()
>>> data
{'message': 'success', 'iss_position': {'longitude': '45.0667', 'latitude': '-33.1779'}, 'timestamp': 1555012195}
  1. 以下代码块显示了我们如何从返回的数据结构中访问纬度和经度数据:
>>> data['iss_position']['latitude']
'-33.1779'
>>> data['iss_position']['longitude']
'45.0667'
  1. 现在,我们将创建并调用track_space_station函数,该函数将在10秒的时间内每秒跟踪空间站的位置:
>>> req.status_code
>>> def track_space_station():
...     for i in range(10):
...         data = urequests.get(ISS_API_URL).json()
...         position = data['iss_position']
...         print(i, 'lat: {latitude} long: {longitude}'.format(**position))
...         time.sleep(1)
...         
...         
... 
>>> track_space_station()
0 lat: -38.5192 long: 52.4146
1 lat: -38.5783 long: 52.5069
2 lat: -38.6570 long: 52.6302
3 lat: -38.7355 long: 52.7538
4 lat: -38.7943 long: 52.8467
5 lat: -38.8726 long: 52.9708
6 lat: -38.9507 long: 53.0952
7 lat: -39.0092 long: 53.1887
8 lat: -39.0871 long: 53.3136
9 lat: -39.1454 long: 53.4075
>>> 
  1. 以下代码应放入main.py文件中:
from netcheck import wait_for_networking
import urequests
import time

ISS_API_URL = 'http://api.open-notify.org/iss-now.json'

def track_space_station():
    for i in range(10):
        data = urequests.get(ISS_API_URL).json()
        position = data['iss_position']
        print(i, 'lat: {latitude} long: {longitude}'.format(**position))
        time.sleep(1)

def main():
    wait_for_networking()
    track_space_station()

main()

当执行此脚本时,它将跟踪 ISS 的位置,并在固定时间内每秒显示纬度和经度的变化。

工作原理...

我们定义了一个名为ISS_API_URL的常量,其中包含我们可以使用的 URL,以检索 ISS 当前位置的信息。当我们通过执行 HTTP GET请求调用此 API 时,服务器会以 JSON 格式返回其输出。然后,我们可以使用返回的请求对象上的json方法将此响应解析为 Python 数据结构。我们可以访问iss_position键以及该字典中的纬度和经度信息。track_space_station函数的其余部分只是循环执行10次迭代,每次循环之间间隔 1 秒,然后调用 API 并打印其解析的结果。

还有更多...

这个示例是 Web 服务世界丰富多彩的一个很好的例子。您可以将这些微型低功耗微控制器连接到各种丰富的信息源。JSON 是这些 Web 服务最流行的序列化格式,因此具有内置支持解析此格式的 MicroPython 功能非常强大。

MicroPython 还完全支持创建 JSON 输出,以便您可以从 MicroPython 项目向 Web 服务提交数据。您可以将一些传感器连接到您的开发板,并使用 Web 服务将传感器数据以 JSON 格式持续上传到远程服务器。

另请参阅

以下是关于此主题的一些参考资料:

创建 HTTP 服务器

这个示例将向您展示如何在 ESP8266 上使用 MicroPython 创建一个可以提供动态内容的 Web 页面的 Web 服务器。每当浏览器访问 Web 服务器时,它将显示开发板的当前正常运行时间(以秒为单位)。

我们将确保生成的网页在计算机网络浏览器和手机浏览器上都能很好地显示。它可以成为您创建的项目的强大工具,因为您可以使用网络浏览器从任何手机或计算机与它们进行交互。

这个示例向您展示了如何创建这样的项目,您可以将任何实时传感器数据或信息直接提交到人们的浏览器,无论他们是从手机还是台式电脑连接的。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本示例中提供的代码。

如何做...

按照以下步骤学习如何创建 HTTP 服务器:

  1. 使用 REPL 运行以下代码行:
>>> from netcheck import wait_for_networking
>>> import socket
>>> import time
>>> 
>>> HTTP_PORT = 80
>>> TCP_BACKLOG = 0
  1. 在这个阶段,我们已经导入了所有必要的 Python 模块并定义了常量。以下代码块将定义一个 HTML 模板,我们将使用它来生成页面,然后将其提交给连接的浏览器:
>>> TEMPLATE = """\
... <!DOCTYPE HTML>
... <html lang="en">
... <head>
...     <title>ESP8266</title>
...     <meta charset="UTF-8">
...     <link rel="icon" href="data:,">
...     <meta name="viewport" content="width=device-width">
... </head>
... <body>
...     <h1>ESP8266</h1>
...     uptime: {uptime}s
...     </body>
...     </html>
... """
>>> 
  1. 以下函数被定义并调用,并将绑定并监听默认的 HTTP 端口:
>>> def socket_listen():
...     sock = socket.socket()
...     sock.bind(('0.0.0.0', HTTP_PORT))
...     sock.listen(TCP_BACKLOG)
...     return sock
...     
...     
... 
>>> ip = wait_for_networking()
address on network: 10.0.0.38
>>> sock = socket_listen()
  1. 以下代码块定义并调用了serve_requests函数,该函数将负责为 Web 服务器发出的任何请求提供服务。调用该函数,然后浏览器分别访问了 Web 服务器三次。每次提供服务时,其详细信息都会被打印出来:
>>> def serve_requests(sock, ip):
...     print('webserver started on http://%s/' % ip)
...     start = time.monotonic()
...     while True:
...         conn, address = sock.accept()
...         print('request:', address)
...         request = conn.makefile('rwb')
...         while True:
...             line = request.readline()
...             if not line or line == b'\r\n':
...                 break
...         uptime = time.monotonic() - start
...         html = TEMPLATE.format(uptime=uptime)
...         conn.send(html)
...         conn.close()
...         
...         
... 
>>> 
>>> serve_requests(sock, ip)
webserver started on http://10.0.0.38/
request: ('10.0.0.151', 47290)
request: ('10.0.0.151', 47292)
request: ('10.0.0.151', 47294)
  1. 以下代码应放入main.py文件中:
from netcheck import wait_for_networking
import socket
import time

HTTP_PORT = 80
TCP_BACKLOG = 0
TEMPLATE = """\
<!DOCTYPE HTML>
<html lang="en">
<head>
    <title>ESP8266</title>
    <meta charset="UTF-8">
    <link rel="icon" href="data:,">
    <meta name="viewport" content="width=device-width">
</head>
<body>
    <h1>ESP8266</h1>
    uptime: {uptime}s
</body>
</html>
"""

def socket_listen():
    sock = socket.socket()
    sock.bind(('0.0.0.0', HTTP_PORT))
    sock.listen(TCP_BACKLOG)
    return sock

def serve_requests(sock, ip):
    print('webserver started on http://%s/' % ip)
    start = time.monotonic()
    while True:
        conn, address = sock.accept()
        print('request:', address)
        request = conn.makefile('rwb')
        while True:
            line = request.readline()
            if not line or line == b'\r\n':
                break
        uptime = time.monotonic() - start
        html = TEMPLATE.format(uptime=uptime)
        conn.send(html)
        conn.close()

def main():
    ip = wait_for_networking()
    sock = socket_listen()
    serve_requests(sock, ip)

main()

当执行此脚本时,每次开发板启动时都会启动一个 Web 服务器,每次被浏览器访问时都会显示服务器的正常运行时间。

工作原理...

在脚本中定义的第一件事是 HTML 模板。此模板将生成有效的 HTML5 网页,可以在移动设备和台式电脑上正确显示。

icon链接标签存在是为了防止 Web 浏览器不必要地请求favicon.ico文件。调用socket_listen函数来绑定并监听默认的 HTTP 端口。然后调用serve_requests函数,将无休止地处理所有传入的 HTTP 请求。Web 服务器的启动时间记录在一个名为start的变量中。我们将使用这个变量来计算每个请求的服务器正常运行时间。

我们调用accept方法,它将阻塞代码,直到有新的请求到达 web 服务器。一旦我们收到这个新请求,我们通过反复调用readline方法来消耗所有的 HTTP 请求头,直到检测到请求头的结束。现在我们可以生成我们的 HTML 响应,并使用send方法将其发送给 HTTP 客户端。传输响应后,我们关闭与客户端的连接。

还有更多...

您可以采用这个方法,并以多种方式进行扩展。您可以从连接到板子的不同传感器和按钮中读取数据,并将这些数据发送回浏览器。您还可以轻松地更改和添加模板的内容。

目前,它只有 HTML 内容,但没有什么可以阻止您向响应中添加 CSS、JavaScript 和图像内容。该板子配备了 4MB 的闪存,因此板子上有足够的空间来添加所有这些内容。以下截图显示了在桌面浏览器上显示的从此方法生成的页面:

以下截图是从安卓智能手机的浏览器中获取的:

在这个方法中设计 HTML 的方式是为了使一个代码库可以为两类设备提供服务。

另请参阅

以下是有关此方法的一些参考资料:

创建 Web 处理程序模块

这个方法将向您展示如何将处理套接字、解析 HTTP 请求头和生成 HTML 所涉及的大量代码和逻辑捆绑到一个单独的 Python 模块中。一旦我们将它放入一个模块中,我们就可以导入这个模块,并将我们的 Web 处理程序传递给它,它将为我们完成所有繁重的工作。

当您创建在微控制器上创建基于 Web 的应用程序的项目时,您会发现这个方法非常有用,而且您希望快速提高生产力,而不会陷入套接字和解析 HTTP 头的所有低级细节中。

准备工作

您需要访问 ESP8266 上的 REPL 才能运行此处提供的代码。

如何做...

按照以下步骤学习如何创建 Web 处理程序模块:

  1. 在 REPL 中运行以下代码行:
>>> from netcheck import wait_for_networking
>>> import socket
>>> 
>>> HTTP_PORT = 80
>>> TCP_BACKLOG = 0
>>> BASE_TEMPLATE = """\
... <!DOCTYPE HTML>
... <html lang="en">
... <head>
...     <title>MicroPython</title>
...     <meta charset="UTF-8">
...     <link rel="icon" href="data:,">
...     <meta name="viewport" content="width=device-width">
... </head>
... <body>
... %s
... </body>
... </html>
... """
>>> 
  1. 我们创建了一个名为BASE_TEMPLATE的变量,它将充当通用模板。现在,我们可以用任何我们想要的内容填充它的body标签。以下代码块定义了socket_listen,它为服务器进行了初始套接字配置:
>>> def socket_listen():
...     sock = socket.socket()
...     sock.bind(('0.0.0.0', HTTP_PORT))
...     sock.listen(TCP_BACKLOG)
...     return sock
...     
...     
... 
  1. 以下代码块具有一个接收 web 处理程序作为参数的函数。当调用时,它将处理传入的请求,以便可以收集它们的请求头,然后解析 HTTP 方法和请求的路径。然后将此信息传递给处理程序,处理程序将返回要发送给 HTTP 客户端的 HTML 内容:
>>> def serve_requests(sock, ip, handler):
...     print('webserver started on http://%s/' % ip)
...     while True:
...         conn, address = sock.accept()
...         stream = conn.makefile('rwb')
...         request = b''
...         while True:
...             line = stream.readline()
...             request += line
...             if not line or line == b'\r\n':
...                 break
...         request = str(request, 'utf8')
...         method, path, _ = request.split(' ', 2)
...         client_ip = address[0]
...         print('request:', client_ip, method, path)
...         html = handler(request, method, path)
...         conn.send(html)
...         conn.close()
...         
...         
... 
>>> 
  1. run_server函数在以下代码块中定义。它提供了一个处理程序,并将创建套接字并调用serve_requests来开始处理所有传入的请求:
>>> def run_server(handler):
...     ip = wait_for_networking()
...     sock = socket_listen()
...     serve_requests(sock, ip, handler)
...     
...     
... 
>>> 
  1. 以下代码块显示了一个示例处理程序,该处理程序创建一个 Web 应用程序,每次有人访问时都会生成随机数:
>>> import random
>>> 
>>> def handler(request, method, path):
...     body = 'random: %s' % random.random()
...     return BASE_TEMPLATE % body
...     
...     
... 
>>> run_server(handler)
address on network: 10.0.0.38
webserver started on http://10.0.0.38/
request: 10.0.0.151 GET /hotneumm
request: 10.0.0.151 GET /
request: 10.0.0.151 GET /
request: 10.0.0.151 GET /
  1. 以下代码应放入web.py文件中:
from netcheck import wait_for_networking
import socket

HTTP_PORT = 80
TCP_BACKLOG = 0
BASE_TEMPLATE = """\
<!DOCTYPE HTML>
<html lang="en">
<head>
    <title>MicroPython</title>
    <meta charset="UTF-8">
    <link rel="icon" href="data:,">
    <meta name="viewport" content="width=device-width">
</head>
<body>
%s
</body>
</html>
"""
def socket_listen():
    sock = socket.socket()
    sock.bind(('0.0.0.0', HTTP_PORT))
    sock.listen(TCP_BACKLOG)
    return sock

def serve_requests(sock, ip, handler):
    print('webserver started on http://%s/' % ip)
    while True:
        conn, address = sock.accept()
        stream = conn.makefile('rwb')
        request = b''
        while True:
            line = stream.readline()
            request += line
            if not line or line == b'\r\n':
                break
        request = str(request, 'utf8')
        method, path, _ = request.split(' ', 2)
        client_ip = address[0]
        print('request:', client_ip, method, path)
        html = handler(request, method, path)
        conn.send(html)
        conn.close()

def run_server(handler):
    ip = wait_for_networking()
    sock = socket_listen()
    serve_requests(sock, ip, handler)
  1. 然后,将以下代码放入main.py文件中:
from web import BASE_TEMPLATE, run_server
import random

def handler(request, method, path):
    body = 'random: %s' % random.random()
    return BASE_TEMPLATE % body

def main():
    run_server(handler)

main()

执行此脚本时,它将在启动时启动一个 Web 服务器,每当有人访问时都会生成随机数。

工作原理

此示例的主要部分是webPython 模块中的代码。此代码提供了BASE_TEMPLATE变量,可以在其body标记中填充任何内容。然后,在模块中定义了三个函数。

socket_listen函数具有我们熟悉的逻辑;即设置套接字、绑定套接字并使其监听端口80serve_requests函数现在接收处理程序,并将 HTTP 请求标头收集到名为request的变量中。然后解析此request以提取正在使用的 HTTP 方法和请求的路径。然后将这三个变量传递给提供的处理程序,该处理程序返回要传输到 HTTP 客户端的 HTML 响应。run_server函数是此模块的主要入口点。您可以调用它并向其提供处理程序,它将设置服务器并开始处理请求。

main.py文件中的代码导入了web模块,并传递了它定义的处理程序。它的处理程序简单地为每个 HTTP 请求生成一个随机数,并将此数字发送回 Web 浏览器。

还有更多...

此示例适合扩展。与套接字处理和处理字节转换相关的大部分代码都由web模块完成。您可以导入此模块并在相对较短的时间内在 MicroPython 板上开始创建 Web 应用程序。

您还可以扩展web模块以添加更多功能。您可以创建更多内置模板或进一步解析 HTTP 标头,以自动获取更多请求信息。

另请参阅

以下是有关此示例的一些参考资料:

通过 Web 服务器控制 LED

此示例将向您展示如何创建一个基于 Web 的应用程序,让人们看到红色和蓝色 LED 灯的状态,并打开和关闭它们。每当您想要创建控制不同硬件组件输出的项目时,例如通过可以从计算机和手机访问的基于 Web 的应用程序控制 LED、屏幕或扬声器时,此示例都会帮助您。

准备工作

您需要访问 ESP8266 上的 REPL 才能运行此示例中提供的代码。

操作步骤

按照以下步骤学习如何通过 Web 服务器控制 LED:

  1. 在 REPL 中执行以下代码块:
>>> from web import BASE_TEMPLATE, run_server
>>> from machine import Pin
>>> 
>>> pins = dict(red=Pin(0, Pin.OUT), blue=Pin(2, Pin.OUT))
>>> state = dict(red=True, blue=True)
>>> 
>>> 
  1. 已创建pinsstate变量,以便我们可以跟踪红色和蓝色 LED 的状态,并访问它们的Pin对象。以下代码块将定义 HTML BODY模板,并显示 LED 的当前状态,并提供按钮以切换它们的开和关:
>>> BODY = """
... Red: {red}<br/>
... Blue: {blue}<br/><br/>
... Toggle Colors:<br/><br/>
... <form action="/red" method=post><input type=submit value=Red></form><br/>
... <form action="/blue" method=post><input type=submit value=Blue></form><br/>
... """
>>> 
  1. 以下代码块中有一个函数,它将布尔值格式化为 HTML 内容中的OnOff标签:
>>> def format(value):
...     return 'On' if value else 'Off'
...     
...     
... 
>>> 
>>> format(True)
'On'
>>> format(False)
'Off'
>>> 
  1. gen_body函数生成 HTML 内容的主体部分:
>>> def gen_body():
...     data = {k: format(v) for k, v in state.items()}
...     return BODY.format(**data)
...     
...     
... 
>>> gen_body()
'\nRed: On<br/>\nBlue: On<br/><br/>\nToggle Colors:<br/><br/>\n<form action="/red" method=post><input type=submit value=Red></form><br/>\n<form action="/blue" method=post><input type=submit value=Blue></form><br/>\n'
>>> 
  1. toggle_color函数将切换 LED 的开和关。第一次调用将关闭红色 LED,而第二次调用将重新打开它:
>>> def toggle_color(color):
...     state[color] = not state[color]
...     pin_value = 0 if state[color] else 1
...     pins[color].value(pin_value)
...     
...     
... 
>>> toggle_color('red')
>>> toggle_color('red')
  1. handler函数将为POST请求切换颜色,并且对于所有请求,它将返回生成的 HTML 主体,其中显示 LED 的状态并提供切换按钮:
>>> def handler(request, method, path):
...     if method == 'POST':
...         color = path.replace('/', '')
...         toggle_color(color)
...     return BASE_TEMPLATE % gen_body()
...     
...     
... 
>>> 
  1. 以下代码应该放入main.py文件中:
from web import BASE_TEMPLATE, run_server
from machine import Pin

pins = dict(red=Pin(0, Pin.OUT), blue=Pin(2, Pin.OUT))
state = dict(red=True, blue=True)

BODY = """
Red: {red}<br/>
Blue: {blue}<br/><br/>
Toggle Colors:<br/><br/>
<form action="/red" method=post><input type=submit value=Red></form><br/>
<form action="/blue" method=post><input type=submit value=Blue></form><br/>
"""

def format(value):
    return 'On' if value else 'Off'

def gen_body():
    data = {k: format(v) for k, v in state.items()}
    return BODY.format(**data)

def toggle_color(color):
    state[color] = not state[color]
    pin_value = 0 if state[color] else 1
    pins[color].value(pin_value)

def handler(request, method, path):
    if method == 'POST':
        color = path.replace('/', '')
        toggle_color(color)
    return BASE_TEMPLATE % gen_body()

def main():
    pins['red'].value(0)
    pins['blue'].value(0)
    run_server(handler)

main()

当执行此脚本时,它将启动一个 Web 应用程序,显示 LED 的当前状态,并提供按钮,可用于切换 LED 的开关。

它是如何工作的...

format函数接受布尔值并返回值以指示OnOff的灯状态。然后,gen_body函数将循环遍历所有 LED 状态值并格式化它们,以便它们可以填充 HTML 模板。然后填充并返回此模板。toggle_color函数接收要切换的 LED 的名称,然后在访问Pin对象之前更新状态数据结构,以便将更改应用于 LED。handler函数将接收传入的请求并在请求为POST请求时切换 LED。然后,它将始终返回生成的主体,以显示 LED 的最新值并提供按钮以打开或关闭它们。main函数初始化 LED Pin对象,然后调用具有定义的处理程序的run_server函数,以便它可以开始处理传入的请求。

还有更多...

这个教程提供了我们需要检查 LED 的当前设置并打开或关闭它们的所有控件。但是,我们可以以许多方式扩展和改进它。我们可以添加一些 CSS 来改善应用程序的外观和感觉。我们还可以使用一些丰富的 JavaScript 控件,可以在某人与 UI 控件交互时创建动画,以便它们更像切换按钮。

另请参阅

以下是关于此教程的一些参考资料:

开发 RESTful API 来控制 LED

这个教程将向您展示如何在 ESP8266 上创建一个 RESTful API,该 API 将允许 API 客户端查询 LED 的状态,并切换它们的开关。在之前的教程中,我们已经看到您可以使用 MicroPython 作为客户端来访问 RESTful web 服务。

现在,我们将扭转这一点,并提供 RESTful web 服务,以便网络上的其他设备和计算机可以连接到板并控制其硬件。每当您需要让网络上的其他计算机和设备远程连接到您的项目并控制其上的组件时,您会发现这个教程非常有用。

准备工作

您将需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。您还需要curl命令行工具,可以从curl.haxx.se/download.html下载。

如何做...

按照以下步骤学习如何开发 RESTful API 来控制 LED:

  1. 使用 REPL 运行以下代码行:
>>> from web import BASE_TEMPLATE, run_server
>>> from machine import Pin
>>> import json
>>> 
>>> pins = dict(red=Pin(0, Pin.OUT), blue=Pin(2, Pin.OUT))
>>> state = dict(red=True, blue=True)
  1. 我们已经导入了json库并设置了pinsstate变量。以下代码块将定义JSON_HEADERS模板,我们将使用它为我们的 JSON 响应提供 HTTP 响应头:
>>> JSON_HEADERS = '''\
... HTTP/1.1 200 OK
... Content-Type: application/json
... 
... '''
>>> 
  1. 以下代码块将执行 RESTful API 调用的 LED 切换:
>>> def toggle_color(color):
...     state[color] = not state[color]
...     pin_value = 0 if state[color] else 1
...     pins[color].value(pin_value)
...     
...     
... 
>>> 
  1. 以下代码中的handler函数将在请求使用POST方法时切换 LED。在所有情况下,它将以 JSON 序列化形式返回状态变量的值:
>>> def handler(request, method, path):
...     if method == 'POST':
...         color = path.replace('/', '')
...         toggle_color(color)
...     return JSON_HEADERS + json.dumps(state) + '\n'
...     
...     
... 
>>> 
  1. 以下代码应该放入main.py文件中:
from web import BASE_TEMPLATE, run_server
from machine import Pin
import json

pins = dict(red=Pin(0, Pin.OUT), blue=Pin(2, Pin.OUT))
state = dict(red=True, blue=True)
JSON_HEADERS = '''\
HTTP/1.1 200 OK
Content-Type: application/json

'''

def toggle_color(color):
    state[color] = not state[color]
    pin_value = 0 if state[color] else 1
    pins[color].value(pin_value)

def handler(request, method, path):
    if method == 'POST':
        color = path.replace('/', '')
        toggle_color(color)
    return JSON_HEADERS + json.dumps(state) + '\n'

def main():
    pins['red'].value(0)
    pins['blue'].value(0)
    run_server(handler)

main()
  1. 执行main.py脚本,以便我们可以开始访问 RESTful API。

  2. 在您的计算机上下载并安装curl命令行(curl.haxx.se/download.html)。

  3. 在终端中运行以下命令:

$ curl http://10.0.0.38/
{"red": true, "blue": true}
  1. 这将检索 LED 的状态。执行以下命令关闭红色 LED:
$ curl -X POST http://10.0.0.38/red
{"red": false, "blue": true}
  1. 当我们运行以下命令时,红色 LED 将被重新打开:
$ curl -X POST http://10.0.0.38/red
{"red": true, "blue": true}

curl命令行是测试和与大多数 RESTful API 交互的绝佳方式。

它是如何工作的...

代码的结构与上一个配方非常相似。一些主要的区别是JSON_HEADERS模板提供了必要的 HTTP 响应头,指示响应的内容类型将是 JSON。json模块中的dumps函数也用于从状态数据结构生成 JSON 数据。在通过curl测试和与 API 交互之前,服务器需要启动。第一个curl命令只是执行一个GET请求,返回 LED 的状态。然后我们在curl中使用-X选项指定我们要使用 POST 方法,这样我们就可以切换 LED 的开关。

还有更多...

这个配方提供了一组基本的 API,这样我们就可以控制板上的灯。我们可以扩展它来响应关于服务器运行时间或磁盘使用情况的请求。您可以创建一个 API,让您远程列出和删除文件。RESTful API 是非常强大的工具,您可以使用它来在网络上连接许多不同的脚本和计算机。我们在这个配方中使用的方法可以轻松扩展以提供更多的服务和功能。

另请参阅

以下是关于这个配方的一些参考资料:

第十三章:与 Adafruit FeatherWing OLED 交互

本章将向您介绍 Adafruit FeatherWing 有机发光二极管(OLED)显示器。Adafruit Feather 是一个标准的板安排,允许将这些板升级插入到彼此之间。它们可以堆叠在一起或作为独立板运行。FeatherWings 是可以插入这些 Feather 板的附件。

在本章中,我们将把 Adafruit FeatherWing OLED 显示器插入 Adafruit Feather HUZZAH ESP8266 MicroPython 板中。这将创建一个功能强大的组合,即具有显示器的微控制器和互联网连接功能,可以输出文本图形,并使用显示器上的三个硬件按钮与用户交互。

本章的配方将帮助您构建一系列项目。您可以制作小型 MicroPython 板,显示一个菜单,您可以通过导航,选择的每个操作都可以将传感器数据发布到网络上的其他服务器或互联网上。您还可以使用它按命令从服务器获取数据并在屏幕上显示。本章将重点介绍显示器的所有主要功能,如显示文本、线条和矩形图形,以及与显示器配备的内置按钮进行交互。

本章将涵盖以下内容:

  • 使用 GPIO 引脚检测按钮按下

  • 连接到 SSD1306 显示器

  • 填充和清除显示器

  • 在显示器上设置像素

  • 在显示器上绘制线条和矩形

  • 在显示器上写文本

  • 在显示器上反转颜色

Adafruit FeatherWing OLED

FeatherWing OLED 显示器使用了一种 OLED,与其他显示技术相比有许多优点。例如,它的功耗比其他显示技术低得多。这使得它非常适用于嵌入式项目,其中需要尽可能降低功耗要求。

OLED 还具有比其他显示技术更高的对比度,使得显示的文本和图形更清晰。屏幕配备了三个用户按钮,并且在引脚和屏幕分辨率方面有许多不同的选项。以下照片显示了其中一个显示器连接到 Adafruit Feather HUZZAH ESP8266 板上:

该板有一个配置,带有需要焊接的松散引脚,另一个版本带有组装好的引脚,无需焊接。在上一张照片中显示的板使用了组装好的引脚,可以直接插入 ESP8266 主板,无需焊接。

购买地址

本章使用了组装好的 Adafruit FeatherWing OLED - 128 x 32 OLED 附加板。这个 FeatherWing 可以直接从 Adafruit 购买(www.adafruit.com/product/3045)。

技术要求

本章的代码文件可以在以下 GitHub 存储库的Chapter13文件夹中找到:github.com/PacktPublishing/MicroPython-Cookbook

本章使用了 Adafruit Feather HUZZAH ESP8266 板和组装好的 Adafruit FeatherWing OLED - 128 x 32 OLED 附加板。本章中的所有配方都使用了 CircuitPython 3.1.2。

本章需要 CircuitPython 库中的一些特定模块,它们将在每个配方的开头提到。有关下载和提取这些库的详细信息,您可以参考《使用 MicroPython 入门》中的更新 CircuitPython 库配方。本章中的所有配方都使用了 20190212 版本的 CircuitPython 库。

使用 GPIO 引脚检测按钮按下

这个食谱将演示如何检查 Adafruit FeatherWing OLED 附带的三个推按钮的状态。我们将轮询这三个按钮,并不断打印它们的状态,以便我们可以检测按钮被按下和释放的时刻。

这些推按钮中的每一个都连接到不同的 GPIO 引脚,因此我们将使用一个字典将按钮名称映射到它们关联的 GPIO 引脚。板上的物理按钮标有ABC。我们将使用相同的命名将按钮事件映射到脚本中的打印语句。

这个食谱很有用,因为它将使您的项目能够根据按下的按钮采取不同的操作。因为这个板上有三个按钮,所以您可以根据自己的应用设计有很多选择。例如,您可以将两个按钮作为上下菜单选项,而第三个按钮可以允许用户选择菜单选项。或者,您可以有一个按钮增加一个设置值,另一个按钮减少一个设置值。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本食谱中提供的代码。

如何操作...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> from machine import Pin
>>> import time
>>> 
>>> PINS = dict(a=0, b=16, c=2)
  1. 我们现在已经导入了必要的 Python 库,并设置了一个PINS字典,它将按钮名称映射到它们关联的 GPIO 引脚,如下所示:
>>> def get_buttons():
...     return dict(
...         a=Pin(PINS['a'], Pin.IN, Pin.PULL_UP),
...         b=Pin(PINS['b']),
...         c=Pin(PINS['c'], Pin.IN, Pin.PULL_UP),
...     )
...     
...     
... 
>>> buttons = get_buttons()
  1. get_buttons函数将返回一个将每个按钮映射到其关联的Pin对象的字典。在这个板上,按钮 A 和 C 需要配置PULL_UP,而按钮 B 不需要。运行以下代码块,它将返回一个值1,表示按钮 A 没有被按下:
>>> buttons['a'].value()
1
  1. 在运行下一段代码块时按住按钮 A,Pin值将显示按钮正在被按下:
>>> buttons['a'].value()
0
  1. 下一段代码创建了names列表,其中包含按钮名称的排序列表。我们定义了一个名为get_status的函数,它将返回三个按钮的状态:
>>> names = sorted(PINS.keys())
>>> 
>>> def get_status(names, buttons):
...     items = [format(i, buttons) for i in names]
...     return ' '.join(items)
...     
...     
... 
>>> 
  1. 运行时,以下代码块调用get_status函数并返回推按钮的当前状态:
>>> get_status(names, buttons)
'a: False b: False c: False'
  1. 在运行下一段代码块时按住按钮 B,推按钮 B 的状态将显示为正在被按下:
>>> get_status(names, buttons)
'a: False b: True c: False'
  1. 以下代码应添加到main.py文件中:
from machine import Pin
import time

PINS = dict(a=0, b=16, c=2)

def format(name, buttons):
    pressed = not buttons[name].value()
    return '{name}: {pressed}'.format(name=name, pressed=pressed)

def get_status(names, buttons):
    items = [format(i, buttons) for i in names]
    return ' '.join(items)

def get_buttons():
    return dict(
        a=Pin(PINS['a'], Pin.IN, Pin.PULL_UP),
        b=Pin(PINS['b']),
        c=Pin(PINS['c'], Pin.IN, Pin.PULL_UP),
    )

def main():
    names = sorted(PINS.keys())
    buttons = get_buttons()
    while True:
        status = get_status(names, buttons)
        print(status)
        time.sleep(0.1)

main()

当执行这个脚本时,它将不断打印出每个按钮的状态,每个循环之间延迟0.1秒。

工作原理...

这个食谱定义了一个名为PINS的数据结构,它将三个按钮分别映射到 ESP8266 上的正确 GPIO 引脚。get_buttons函数为这些按钮中的每一个创建了带有正确PULL_UP设置的Pin对象。get_buttons函数在main函数中被调用,并且返回的字典被保存在buttons变量中。

names变量只是按钮名称的排序列表。它被创建以确保状态更新总是按字母顺序呈现。get_status函数循环遍历每个按钮,并调用format函数生成状态行,每次检查状态时都会打印出来。主循环进入无限循环,在每次迭代中打印按钮状态,然后暂停0.1秒,然后继续下一个循环。

还有更多...

当使用 GPIO 引脚与推按钮交互时,它们需要被正确配置。需要使用正确的引脚,并且需要正确应用PULL_UP设置到每个引脚配置中。这些设置通常可以在板的文档中找到。

在这块板上,按钮 B 不需要设置PULL_UP的原因是按钮和硬件电平已经包含了 100k 的上拉值,因此解决了 ESP8266 在引脚 16 上没有内部上拉的问题。然而,其他两个按钮需要设置PULL_UP

另请参阅

更多信息,请参考以下文档:

连接到 SSD1306 显示器

本教程将向你展示如何使用adafruit_ssd1306库连接到 FeatherWing OLED 显示器。本教程将向你展示如何初始化连接到的I2C总线。然后,我们可以创建一个通过 I2C 总线连接到显示器的SSD1306_I2C对象。

这个教程将在很多方面帮助你;有一整套组件可以使用 I2C 连接,所以这个教程将让你接触到这项技术,以便在你自己的项目中需要使用它时,你会对它很熟悉。

你将了解如何使用可以与 MicroPython 一起工作的显示库,然后可以将其包含在任何你想要添加显示器的项目中。

准备工作

你需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。本教程使用的 CircuitPython 库版本是 20190212。

如何操作...

让我们执行以下步骤:

  1. 下载 CircuitPython 库包。你需要.mpy.py版本的库包。

  2. 将这两个.zip文件解压到你的计算机上。

  3. 在 ESP8266 上安装所有库包中的所有库是不必要的。

  4. 连接到显示器需要三个特定的库。

  5. adafruit_bus_deviceadafruit_framebuf库应该在 ESP8266 上安装它们的.mpy文件。这些库的文件应该被传输到 ESP8266 并放入.lib文件夹中。

  6. 在 REPL 中执行以下代码以验证这两个库是否正确安装在板上:

>>> import adafruit_bus_device
>>> import adafruit_framebuf
  1. adafruit_ssd1306库应该在库中有adafruit_ssd1306.py文件的.py版本。

  2. 该库将尝试使用内置的framebuf MicroPython 库而不是adafruit_framebuf。如果使用framebuf库进行帧缓冲区操作,该库将无法连接到显示。为了解决这个问题,在与adafruit_ssd1306.py相同的目录中下载并运行fix_framebuf_import.py文件。你可以在书的 GitHub 存储库的Chapter13文件夹中找到这个脚本。

  3. 将修复后的adafruit_ssd1306.py文件上传到板的根目录。

  4. 运行以下代码块以验证adafruit_ssd1306库是否正确安装在板上:

>>> import adafruit_ssd1306
>>> 
  1. 在这个阶段,所有额外的库都已经成功安装和导入。运行以下代码块以导入初始化 I2C 总线所需的库:
>>> import board
>>> import busio
  1. 运行以下代码块以初始化 I2C 总线:
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> 
  1. 运行以下代码以创建一个SSD1306_I2C显示对象:
>>> buttons['a'].value()
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
>>>
  1. 将以下代码添加到main.py文件中:
import adafruit_ssd1306
import board
import busio

def main():
    print('initialize I2C bus')
    i2c = busio.I2C(board.SCL, board.SDA)
    print('create SSD1306_I2C object')
    oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
    print('ALL DONE')

main()

当执行这个脚本时,它将初始化 I2C 总线并创建一个SSD1306_I2C对象。

工作原理...

与 FeatherWing OLED 交互所需的库不是 CircuitPython 固件的一部分,因此在使用之前需要进一步安装。需要安装三个库,它们分别是adafruit_ssd1306adafruit_bus_deviceadafruit_framebuf

adafruit_ssd1306库是我们将要交互的主要库,它依赖于我们已安装的其他库才能正常工作。安装这些库后,我们可以开始导入它们并使用它们的代码连接到显示器。第一步是初始化 I2C 总线。通过创建一个 I2C 对象并将其引用传递给 SCL 和 SDA 引脚来完成此操作。然后将对象保存在i2c变量中。通过传递值12832来创建一个SSD1306_I2C对象,这些值是指显示分辨率,因为我们使用的是 128 x 32 OLED。传递的另一个参数是i2c对象。

还有更多...

I2C 是一种非常流行的协议,适用于各种设备。 I2C 相对简单,易于连接和使用,这是它被广泛用于许多微控制器的原因之一。它只需要两根线连接,并且可以使用许多微控制器板上的通用 I/O 引脚。

单个连接可以控制多个设备,这增加了其灵活性。但是,与其他协议相比,这种协议的速度较慢是其缺点之一。这意味着我们可以用它来控制小型单色显示器,但是如果我们想要控制分辨率更高、颜色更多的显示器,那么它的速度就不够快了。

另请参阅

有关更多信息,您可以参考以下内容:

填充和清除显示

本教程将向您展示如何使用adafruit_ssd1306库连接到 FeatherWing OLED 显示器。它将演示如何初始化连接到 OLED 显示器的 I2C 总线。然后,我们可以创建一个使用 I2C 总线连接到显示器的SSD1306_I2C对象。本教程将以多种方式帮助您。

有一整套组件可以使用 I2C 连接;本教程将使您了解这项技术,以便在自己的项目中需要使用它时熟悉它。本教程还将帮助您进行使用可以与 MicroPython 一起工作的显示库的第一步,然后可以将其包含在您可能想要添加显示器的任何项目中。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何操作...

让我们执行以下步骤:

  1. 使用 REPL 运行以下代码行:
>>> import adafruit_ssd1306
>>> import board
>>> import busio
  1. 所需的库现在都已导入。运行下一个代码块以创建i2c对象和名为oledSSD1306_I2C对象:
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
  1. 使用以下代码块,将屏幕上的所有像素设置为白色,并通过调用show方法应用更改:
>>> oled.fill(1)
>>> oled.show()
  1. 现在,我们将使用以下代码块关闭屏幕上的所有像素:
>>> oled.fill(0)
>>> oled.show()
  1. 以下代码块将循环 10 次,并重复打开和关闭屏幕上的所有像素,创建闪烁屏幕的效果:
>>> for i in range(10):
...     oled.fill(1)
...     oled.show()
...     oled.fill(0)
...     oled.show()
...     
...     
... 
>>>
  1. 将以下代码添加到main.py文件中:
import adafruit_ssd1306
import board
import busio

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
    for i in range(10):
        oled.fill(1)
        oled.show()
        oled.fill(0)
        oled.show()

main()

当执行此脚本时,屏幕将闪烁黑白 10 次。

它是如何工作的...

main函数首先设置了i2c对象,并将SSD1306_I2C对象保存为名为oled的变量。oled对象有两种方法,我们将在本教程中使用。fill方法接收一个参数,并将显示器上的所有像素填充为白色或黑色。如果提供1,则像素将变为白色,否则将变为黑色(或关闭)。

在每次更改后必须调用show方法,以使更改在显示器上生效。开始一个for循环,循环 10 次,在每次迭代期间将显示器变为全白,然后变为全黑。

还有更多...

fillshow方法是与显示器交互时的绝佳起点,因为它们相对容易使用。尽管它们看起来很简单,但它们在许多操作中都是必需的。

在后续的示例中,我们将探讨如何绘制线条、矩形和文本。在所有这些情况下,我们需要调用show来将更改呈现到屏幕上。我们还经常调用fill来清除屏幕上的内容,然后再在显示器上写入或绘制新内容。

另请参阅

有关更多信息,您可以参考以下内容:

在显示器上设置像素

本示例将演示如何在屏幕上打开和关闭单个像素。该示例首先通过设置具有特定xy坐标的像素来指示打开或关闭。然后,我们将创建一个简单的动画,重复在特定方向上绘制像素,从而创建一个不断增长长度的线条。我们将把这个简单的线条动画放入自己的函数中,以便我们可以多次调用它并创建一种锯齿线条动画。

当您开始控制显示器并希望控制单个像素时,您会发现这个示例非常有用。控制单个像素的操作成为生成更复杂图形的基本组件。

准备工作

您需要在 ESP8266 上访问 REPL 才能运行本示例中提供的代码。

操作步骤...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> import adafruit_ssd1306
>>> import board
>>> import busio
>>> 
>>> BLACK = 0
>>> WHITE = 1
>>> 
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
  1. 定义BLACKWHITE常量,它们代表两种可能的像素颜色值。然后设置i2coled对象。以下代码块将清除屏幕上的内容:
>>> oled.fill(BLACK)
>>> oled.show()
  1. 以下代码块将为白色绘制像素(x, y),即位置(0, 0):
>>> oled.pixel(0, 0, WHITE)
>>> oled.show()
  1. 以下代码块将关闭位置(0, 0)处的像素,将其颜色设置为黑色:
>>> oled.pixel(0, 0, BLACK)
>>> oled.show()
  1. 以下代码将把位置(10, 30)处的像素颜色设置为白色:
>>> oled.pixel(10, 30, WHITE)
>>> oled.show()
  1. 以下代码块将清除屏幕,然后循环 10 次,逐个设置对角线上的像素,形成一个看起来像是不断增长的线条的动画:
>>> oled.fill(BLACK)
>>> oled.show()
>>> 
>>> for i in range(10):
...     oled.pixel(i, i, WHITE)
...     oled.show()
...     
...     
... 
>>> 
  1. 使用以下代码块,定义一个函数,该函数将从起始位置(x, y)开始执行线条动画,然后在xy方向上移动一定的count次数:
>>> def animate_pixel(oled, x, y, step_x, step_y, count):
...     for i in range(count):
...         x += step_x
...         y += step_y
...         oled.pixel(x, y, WHITE)
...         oled.show()
...         
...         
... 
>>> 
  1. 以下代码块将清除屏幕并调用animate_pixel从位置(0, 0)到(30, 30)绘制由 30 个像素组成的线条:
>>> oled.fill(BLACK)
>>> oled.show()
>>> animate_pixel(oled, x=0, y=0, step_x=1, step_y=1, count=30)
  1. 然后,以下代码块将绘制从位置(30, 30)到(60, 0)的线条。该线条将在上一个动画完成的位置继续进行,但在不同的方向上移动:
>>> animate_pixel(oled, x=30, y=30, step_x=1, step_y=-1, count=30)
  1. 现在定义一个名为zig_zag的函数,它将绘制四个线条动画。每个动画将从上一个动画完成的位置继续进行,如下所示:
>>> def zig_zag(oled):
...     animate_pixel(oled, x=0, y=0, step_x=1, step_y=1, count=30)
...     animate_pixel(oled, x=30, y=30, step_x=1, step_y=-1, 
...     count=30)
...     animate_pixel(oled, x=60, y=0, step_x=1, step_y=1, count=30)
...     animate_pixel(oled, x=90, y=30, step_x=1, step_y=-1, 
...     count=30)
...     
...     
... 
>>> 
  1. 运行以下代码块以清除显示并运行zig_zag线条动画:
>>> oled.fill(BLACK)
>>> oled.show()
>>> zig_zag(oled)
  1. 将以下代码添加到main.py文件中:
import adafruit_ssd1306
import board
import busio

BLACK = 0
WHITE = 1

def animate_pixel(oled, x, y, step_x, step_y, count):
    for i in range(count):
        x += step_x
        y += step_y
        oled.pixel(x, y, WHITE)
        oled.show()

def zig_zag(oled):
    animate_pixel(oled, x=0, y=0, step_x=1, step_y=1, count=30)
    animate_pixel(oled, x=30, y=30, step_x=1, step_y=-1, count=30)
    animate_pixel(oled, x=60, y=0, step_x=1, step_y=1, count=30)
    animate_pixel(oled, x=90, y=30, step_x=1, step_y=-1, count=30)

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
    zig_zag(oled)

main()

当执行此脚本时,它将以锯齿状模式绘制四个线条动画。

工作原理...

main函数设置了oled对象之后,它调用zig_zag函数。zig_zag函数对animate_pixel函数进行了四次调用。每次调用都将线条移动到不同的对角方向。

每个新的线条动画都从上一个动画结束的地方开始,因此看起来像是从开始到结束的一个长动画。animate_pixel函数接受起始的xy位置,并循环执行由count变量指定的次数。

在每次循环迭代中,xy的值会根据指定的xy步长值进行更改。一旦计算出新值,就会在该位置绘制一个像素,并调用show方法立即显示它。

还有更多...

这个教程从一些简单的设置像素开关和显示器上的不同位置的示例开始。然后,它扩展到进行简单的动画和更复杂的之字形动画。下面的照片展示了动画在显示器上完成后的样子:

使用 MicroPython 附带的math模块可以创建许多不同类型的形状和动画。sinecosine函数可用于绘制波形动画。我们还可以使用这些三角函数来绘制圆和椭圆。

另请参阅

有关更多信息,您可以参考以下内容:

在显示器上绘制线条和矩形

这个教程将演示如何使用SSD1306_I2C对象附带的方法,这将让我们绘制水平线、垂直线、正方形和矩形。现在我们可以超越设置单个像素,并探索使用adafruit_ssd1306显示库中的方法绘制更广泛范围的形状。

当您想要在显示器上绘制一些不同的形状时,您会发现这个教程很有用;例如,在显示器上构建一个简单的用户界面。显示器上有足够的分辨率来绘制代表用户界面不同部分的多个框和边框。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何操作...

让我们执行以下步骤:

  1. 在 REPL 中执行以下代码块:
>>> import adafruit_ssd1306
>>> import board
>>> import busio
>>> 
>>> BLACK = 0
>>> WHITE = 1
>>> 
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
>>> oled.fill(BLACK)
>>> oled.show()
  1. 导入必要的模块,创建oled,然后清除显示器。使用下面的代码块,绘制一条从坐标(00)开始,高度为 20 像素的垂直线:
>>> oled.vline(x=0, y=0, height=20, color=WHITE)
>>> oled.show()
  1. 类似地,使用 80 像素宽度从坐标(00)开始绘制一条水平线:
>>> oled.hline(x=0, y=0, width=80, color=WHITE)
>>> oled.show()
  1. 可以使用下一个代码块绘制一个位于(00)位置,宽度为 10 像素,高度为 20 像素的矩形:
>>> oled.rect(x=0, y=0, width=10, height=20, color=WHITE)
>>> oled.show()
  1. 以下函数将绘制HI文本。H字符将使用垂直线和一条水平线绘制。然后使用单个垂直线绘制I字符:
>>> def draw_hi(oled):
...     print('drawing H')
...     oled.vline(x=50, y=0, height=30, color=WHITE)
...     oled.hline(x=50, y=15, width=30, color=WHITE)
...     oled.vline(x=80, y=0, height=30, color=WHITE)
...     oled.show()
...     print('drawing I')
...     oled.vline(x=100, y=0, height=30, color=WHITE)
...     oled.show()
...     
...     
... 
>>> 
  1. 下面的代码块将清除屏幕并调用draw_hi函数在显示器上呈现消息HI
>>> oled.fill(BLACK)
>>> oled.show()
>>> draw_hi(oled)
drawing H
drawing I
>>> 
  1. 使用下面的代码块,定义一个函数,该函数将执行涉及具有特定大小并且在每次迭代中通过步长xy移动位置的方框动画:
>>> def animate_boxes(oled, x, y, step_x, step_y, size, count):
...     for i in range(count):
...         oled.rect(x, y, width=size, height=size, color=WHITE)
...         oled.show()
...         x += step_x
...         y += step_y
...         
...         
... 
>>> 
  1. 接下来,使用下面的代码块调用animate_boxes并绘制六个方框以对角线形式:
>>> animate_boxes(oled, x=0, y=0, step_x=5, step_y=5, size=5, count=6)
  1. 定义并调用draw_x_boxes函数,该函数在两条对角线上绘制一组方框,以创建由小方框组成的大字母X
>>> def draw_x_boxes(oled):
...     animate_boxes(oled, x=0, y=0, step_x=5, step_y=5, size=5, count=6)
...     animate_boxes(oled, x=0, y=25, step_x=5, step_y=-5, size=5, count=6)
...     
...     
... 
>>> 
>>> draw_x_boxes(oled)
  1. 将以下代码添加到main.py文件中:
import adafruit_ssd1306
import board
import busio

BLACK = 0
WHITE = 1

def draw_hi(oled):
    print('drawing H')
    oled.vline(x=50, y=0, height=30, color=WHITE)
    oled.hline(x=50, y=15, width=30, color=WHITE)
    oled.vline(x=80, y=0, height=30, color=WHITE)
    oled.show()
    print('drawing I')
    oled.vline(x=100, y=0, height=30, color=WHITE)
    oled.show()

def animate_boxes(oled, x, y, step_x, step_y, size, count):
    for i in range(count):
        oled.rect(x, y, width=size, height=size, color=WHITE)
        oled.show()
        x += step_x
        y += step_y

def draw_x_boxes(oled):
    animate_boxes(oled, x=0, y=0, step_x=5, step_y=5, size=5, count=6)
    animate_boxes(oled, x=0, y=25, step_x=5, step_y=-5, size=5, count=6)

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
    draw_x_boxes(oled)
    draw_hi(oled)

main()

当执行此脚本时,它将绘制一个由小方块组成的字母X,并绘制由垂直和水平线组成的HI文本。

它是如何工作的...

draw_hi函数使用oled对象上的vlinehline方法来绘制构成H的三条线。在绘制字母H之后,使用vline绘制垂直线来表示字母I

调用draw_x_boxes函数将依次调用animate_boxes函数。对animate_boxes函数的第一次调用会沿对角线绘制六个方框,以形成X字符的第一部分。对animate_boxes的第二次调用也会绘制六个方框,但起始位置不同,并且方向也不同。第二次调用将穿过第一行以形成X字符。

还有更多...

线条绘制和矩形绘制方法可以以许多不同的方式组合,以创建各种形状和图纸。下面的照片显示了一旦在这个示例中运行main.py脚本,显示将会是什么样子:

在下一个示例中,我们将学习如何在显示屏上绘制文本。将盒子和线条绘制结合起来,然后在显示屏的不同部分呈现文本非常有用。

另请参阅

有关更多信息,您可以参考以下内容:

在显示屏上写字

这个示例将演示如何将文本输出到 FeatherWing OLED。该示例将向您展示如何控制要显示的文本的位置和内容。将创建一个文本动画来执行显示倒计时,然后创建一个函数来同时显示所有小写字母、大写字母和数字字符。

这个示例将在您希望使用您的设备与人们交流一些信息时帮助您。因为显示可以显示三行文本,所以它为呈现各种信息提供了很大的空间。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本示例中提供的代码。

如何做...

让我们执行以下步骤:

  1. 下载 CircuitPython 库包。

  2. .zip文件包解压到您的计算机上。

  3. 复制位于 ESP8266 根文件夹包中的font5x8.bin字体文件。

  4. 使用 REPL 运行以下代码行:

>>> import adafruit_ssd1306
>>> import board
>>> import busio
>>> 
>>> BLACK = 0
>>> WHITE = 1
>>> 
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
>>> oled.fill(BLACK)
>>> oled.show()
  1. 现在我们已经清除了显示屏,并准备在屏幕上显示一些文本。使用以下代码块,在颜色为白色的位置(00)上显示'hello'文本:
>>> oled.text('hello', 0, 0, WHITE)
>>> oled.show()
  1. 使用以下代码块清除屏幕并显示三行文本:
>>> oled.fill(BLACK)
>>> oled.show()
>>> 
>>> oled.text('line 1', 0, 0, WHITE)
>>> oled.text('line 2', 0, 10, WHITE)
>>> oled.text('line 3', 0, 20, WHITE)
>>> oled.show()
  1. 定义一个函数,然后调用它;这将在显示屏上从数字 10 倒数到 0:
>>> def countdown(oled, start):
...     for i in range(start, -1, -1):
...         oled.fill(BLACK)
...         oled.text(str(i), 0, 0, WHITE)
...         oled.show()
...         
...         
... 
>>> 
>>> countdown(oled, 10)
  1. 使用以下代码块,定义一个名为ALPHA_NUMERIC的常量。它包含所有小写字母、大写字母和数字字符,这些字符以适合显示的结构组织在一起:
>>> ALPHA_NUMERIC = [
...     'abcdefghijklmnopqrstu',
...     'vwxyzABCDEFGHIJKLMNOP',
...     'QRSTUVWXYZ0123456789',
... ]
  1. 使用以下代码块,定义并调用show_alpha_numeric函数,该函数循环遍历ALPHA_NUMERIC列表,并在单独的行上显示每个字符串:
>>> def show_alpha_numeric(oled):
...     for i, text in enumerate(ALPHA_NUMERIC):
...         oled.text(text, 0, 10 * i, WHITE)
...         oled.show()
...         
...         
... 
>>> oled.fill(BLACK)
>>> show_alpha_numeric(oled)
  1. 将以下代码添加到main.py文件中:
import adafruit_ssd1306
import board
import busio

BLACK = 0
WHITE = 1
ALPHA_NUMERIC = [
    'abcdefghijklmnopqrstu',
    'vwxyzABCDEFGHIJKLMNOP',
    'QRSTUVWXYZ0123456789',
]

def countdown(oled, start):
    for i in range(start, -1, -1):
        oled.fill(BLACK)
        oled.text(str(i), 0, 0, WHITE)
        oled.show()

def show_alpha_numeric(oled):
    for i, text in enumerate(ALPHA_NUMERIC):
        oled.text(text, 0, 10 * i, WHITE)
        oled.show()

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
    oled.fill(BLACK)
    countdown(oled, 10)
    oled.fill(BLACK)
    show_alpha_numeric(oled)

main()

当执行此脚本时,它将执行一个倒计时动画,然后显示一些字母数字文本。

它是如何工作的...

countdown函数启动一个for循环,将从 10 倒数到 0。在每次迭代期间,屏幕被清除,然后当前数字被显示在屏幕上。ALPHA_NUMERIC变量以一种结构化的格式结合了小写字母、大写字母和数字字符,分布在三行上。显示器可以显示 3 行 21 列的文本。这些数据符合这些限制,以便所有字符都可以清晰地显示,而不会裁剪文本。countdown函数循环遍历每行文本,并在正确的位置显示它,以便屏幕上的 3 行文本被正确填充。

还有更多...

在使用文本输出时,您可以代表各种内容,无限可能。您显示的输出可以是从传感器读数到实时从互联网获取的最新新闻标题。下面的照片显示了在调用show_alpha_numeric函数后显示的屏幕:

尽管屏幕在物理上相当小,但它具有良好的分辨率,并且 CircuitPython 库包中提供的字体已经很好地利用了有限的屏幕空间。这使得在非常小的显示器上显示三行文本成为可能。

另请参阅

有关更多信息,请参考以下内容:

在显示器上反转颜色

本教程将演示如何使用invert功能来翻转所有像素的颜色。当您在黑色背景上显示白色文本,然后希望颜色翻转,使屏幕显示白色背景上的黑色文本时,可以使用此功能。与清除屏幕等一些关键操作相比,invert等功能可能会慢得多。我们可以利用这些性能差异,当我们希望快速地向使用屏幕的人显示视觉反馈时使用invert

本教程将帮助您在使用缓慢的微控制器创建项目并需要找到创造性方法使设备更具响应性以改善其可用性时使用。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何操作...

让我们执行以下步骤:

  1. 在 REPL 中运行以下代码行:
>>> import adafruit_ssd1306
>>> import board
>>> import busio
>>> 
>>> BLACK = 0
>>> WHITE = 1
>>> 
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
>>> oled.fill(BLACK)
>>> oled.show()
  1. 完成初始设置后,oled对象可用于开始反转屏幕。使用以下代码块在黑色背景上显示一些白色文本:
>>> oled.invert(True)
  1. 屏幕现在将显示白色背景上的黑色文本。要将颜色翻转回来,请运行以下代码:
>>> oled.invert(False)
  1. invert功能比用于更新屏幕的其他一些方法快得多。使用以下函数来计算这种速度差异:
>>> def measure_time(label, func, args=(), count=3):
...     for i in range(count):
...         start = time.monotonic()
...         func(*args)
...         total = (time.monotonic() - start) * 1000
...         print(label + ':', '%s ms' % total)
...         
...         
... 
>>> 
  1. 使用以下代码块调用measure_time函数,并计算fill操作花费的时间(以毫秒为单位):
>>> measure_time('fill', oled.fill, [BLACK])
fill: 1047.85 ms
fill: 1049.07 ms
fill: 1046.14 ms
>>> 
  1. 现在计时show方法,你会发现它比fill更快:
>>> measure_time('show', oled.show, [])
show: 62.0117 ms
show: 62.0117 ms
show: 61.0352 ms
>>>
  1. 使用以下代码检查text方法的速度:
>>> measure_time('text', oled.text, ['hello', 0, 0, WHITE])
text: 74.9512 ms
text: 75.1953 ms
text: 80.0781 ms
>>> 
  1. 最后,检查invert方法的速度如下:
>>> measure_time('invert', oled.invert, [True])
invert: 0.976563 ms
invert: 1.95313 ms
invert: 0.976563 ms
>>> 
  1. 将以下代码添加到main.py文件中:
import adafruit_ssd1306
import board
import busio
import time

BLACK = 0
WHITE = 1

def measure_time(label, func, args=(), count=3):
    for i in range(count):
        start = time.monotonic()
        func(*args)
        total = (time.monotonic() - start) * 1000
        print(label + ':', '%s ms' % total)

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
    oled.fill(BLACK)
    oled.show()

    measure_time('fill', oled.fill, [BLACK])
    measure_time('show', oled.show, [])
    measure_time('text', oled.text, ['hello', 0, 0, WHITE])
    measure_time('invert', oled.invert, [True])

main()

当执行此脚本时,它会打印出与屏幕相关操作的性能结果。

工作原理

measure_time函数默认循环三轮。它将当前时间保存在start变量中,调用被测试的函数,然后计算函数调用的总执行时间。该值转换为毫秒,然后打印出结果。main函数调用measure_time四次。它调用它来测量fillshowtextinvert方法的执行时间。

还有更多...

从性能结果来看,有一些事情是非常明显的。好消息是结果非常一致。在这个示例中,我们对每个测量都进行了三次读数。在测量执行速度时,最好取多个样本。从样本中看,调用fill大约比调用invert慢 500 倍。为了使应用程序感觉灵敏,操作不应该超过 100 毫秒,否则它会显得迟钝或无响应。像inverttextshow这样的操作速度很快。但由于fill时间太长,我们可能希望在执行fill之前调用invert,以便用户得到我们的应用程序正在响应他们输入的迹象。

另请参阅

欲了解更多信息,请参阅以下内容:

第十四章:构建物联网(IoT)天气机

在本章中,我们将创建一个连接到互联网的天气机,它将在按下按钮时告诉我们随机城市的天气。为了制作这个工作设备,我们将结合本书中涵盖的一些概念和技术。

我们将使用第十二章中展示的一些网络技术,以及第十三章中展示的显示逻辑,介绍如何与 FeatherWing OLED 交互。这些不同的技术将结合起来创建一个设备,通过触摸按钮事件获取实时天气数据,并在有机发光二极管(OLED)显示器上呈现。

本章可以成为一个有用的信息来源,帮助您使用 MicroPython 创建易于交互并提供丰富的视觉输出的物联网连接设备。

在本章中,我们将涵盖以下主题:

  • 从互联网检索天气数据

  • 创建一个获取城市天气的函数

  • 随机选择城市

  • 创建一个用于文本处理的屏幕对象

  • 创建一个函数来显示城市的天气

  • 在获取天气数据时提供视觉反馈

  • 创建一个显示随机城市天气的函数

  • 创建一个物联网按钮来显示全球各地的天气

技术要求

本章的代码文件可以在以下 GitHub 存储库的Chapter14文件夹中找到:github.com/PacktPublishing/MicroPython-Cookbook

本章使用 Adafruit Feather HUZZAH ESP8266 和已组装的 Adafruit FeatherWing OLED 128x32 OLED 附加件。本章的所有教程都使用了 CircuitPython 3.1.2。您需要应用第十章中描述的连接到现有 Wi-Fi 网络教程中的配置,控制 ESP8266。本章还将使用第十二章中描述的创建等待互联网连接的函数教程中的wait_for_networking函数,网络。您还需要执行第十三章中描述的步骤,与 Adafruit FeatherWing OLED 交互

本章的教程使用 Openweather 提供的天气 API 服务。该服务是免费使用的,但您必须注册并获取一个 API 密钥(APPID)才能使用该服务。API 密钥将需要在本章中运行代码。您可以访问openweathermap.org/appid获取 API 密钥。

从互联网检索天气数据

本教程将向您展示如何使用 ESP8266 连接到互联网,并使用 RESTful Web 服务获取实时天气数据。我们将使用的服务为全球 10 万多个城市提供最新的天气信息。每个位置提供了大量的天气信息,因此本教程将展示如何筛选出对我们最感兴趣的项目。

本教程在您的项目中可能很有用,每当您需要向 RESTful 调用传递不同的参数,或者返回的结果非常庞大,您需要找到浏览这些大型数据集的方法。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何做...

让我们按照本教程中所需的步骤进行操作:

  1. 在 REPL 中运行以下代码行:
>>> import urequests >>> >>> API_URL = 'http://api.openweathermap.org/data/2.5/weather' >>> 
  1. API_URL变量现在已经定义,我们将使用它来访问天气 API。在下一个代码块中,我们定义APPIDcity以获取天气数据。确保用你实际的APPID值替换APPID值。现在我们将通过组合这些变量来构建 URL,然后我们可以访问:
>>> APPID = 'put-your-API-key(APPID)-here'
>>> city = 'Berlin'
>>> url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
  1. 以下代码块将连接到天气 API 并检索天气数据:
>>> response = urequests.get(url)
>>> response
<Response object at 3fff1b00>
  1. 我们知道响应使用 JSON 格式,所以我们可以解析它并检查数据中有多少个顶级键:
>>> data = response.json()
>>> len(data)
13
  1. 下一个代码块检查了解析后的天气数据。由于有很多嵌套的数据,所以以当前形式很难理解:
>>> data
{'cod': 200, 'rain': {'1h': 0.34}, 'dt': 1555227314, 'base': 'stations', 'weather': [{'id': 500, 'icon': '10d', 'main': 'Rain', 'description': 'light rain'}, {'id': 310, 'icon': '09d', 'main': 'Drizzle', 'description': 'light intensity drizzle rain'}], 'sys': {'message': 0.0052, 'country': 'DE', 'sunrise': 1555215098, 'sunset': 1555264894, 'id': 1275, 'type': 1}, 'name': 'Berlin', 'clouds': {'all': 75}, 'coord': {'lon': 13.39, 'lat': 52.52}, 'visibility': 7000, 'wind': {'speed': 3.6, 'deg': 40}, 'id': 2950159, 'main': {'pressure': 1025, 'humidity': 93, 'temp_min': 2.22, 'temp_max': 3.89, 'temp': 3.05}}
  1. MicroPython 没有pprint模块。我们将复制并粘贴数据的输出,并在计算机上的 Python REPL 上运行以下操作:
>>> data = {'cod': 200, 'rain': {'1h': 0.34}, 'dt': 1555227314, 'base': 'stations', 'weather': [{'id': 500, 'icon': '10d', 'main': 'Rain', 'description': 'light rain'}, {'id': 310, 'icon': '09d', 'main': 'Drizzle', 'description': 'light intensity drizzle rain'}], 'sys': {'message': 0.0052, 'country': 'DE', 'sunrise': 1555215098, 'sunset': 1555264894, 'id': 1275, 'type': 1}, 'name': 'Berlin', 'clouds': {'all': 75}, 'coord': {'lon': 13.39, 'lat': 52.52}, 'visibility': 7000, 'wind': {'speed': 3.6, 'deg': 40}, 'id': 2950159, 'main': {'pressure': 1025, 'humidity': 93, 'temp_min': 2.22, 'temp_max': 3.89, 'temp': 3.05}}
  1. 在计算机的 REPL 上运行下一个代码块,我们将得到数据的更结构化表示:
>>> import pprint
>>> pprint.pprint(data)
{'base': 'stations',
 'clouds': {'all': 75},
 'cod': 200,
 'coord': {'lat': 52.52, 'lon': 13.39},
 'dt': 1555227314,
 'id': 2950159,
 'main': {'humidity': 93,
          'pressure': 1025,
          'temp': 3.05,
          'temp_max': 3.89,
          'temp_min': 2.22},
 'name': 'Berlin',
 'rain': {'1h': 0.34},
 'sys': {'country': 'DE',
         'id': 1275,
         'message': 0.0052,
         'sunrise': 1555215098,
         'sunset': 1555264894,
         'type': 1},
 'visibility': 7000,
 'weather': [{'description': 'light rain',
              'icon': '10d',
              'id': 500,
              'main': 'Rain'},
             {'description': 'light intensity drizzle rain',
              'icon': '09d',
              'id': 310,
              'main': 'Drizzle'}],
 'wind': {'deg': 40, 'speed': 3.6}}
>>> 
  1. 现在我们可以返回到 MicroPython REPL 并运行以下代码来检查main键:
>>> data['main']
{'pressure': 1025, 'humidity': 93, 'temp_min': 2.22, 'temp_max': 3.89, 'temp': 3.05}
  1. 接下来的代码将让我们访问柏林的温度和湿度数值:
>>> data['main']['temp']
3.05
>>> data['main']['humidity']
93
>>> 
  1. 您可以使用以下代码访问数据的风部分:
>>> data['wind']
{'speed': 3.6, 'deg': 40}
>>> data['wind']['speed']
3.6

通过这种方式,我们可以进一步深入,并获取所请求城市的风速值。

它是如何工作的...

导入urequests库后,我们定义了一些变量,以便我们可以继续准备 URL 来执行 API 调用。API_URL是一个固定的常量,在对网络服务进行调用时不会改变。然后,我们定义一个变量来存储 API 密钥和城市值。这些值被组合在一起,以制作最终的 URL,然后我们使用urequests库的get函数进行调用。

return响应被解析并显示输出。由于数据结构非常庞大,我们使用了一个技巧,将这些数据移动到计算机上的 REPL,这样我们就可以使用pprint函数,并获得返回数据的更清晰的输出格式。这样可以更容易地识别数据结构的不同部分,并开始访问嵌套数据结构中的不同数据元素。然后我们使用字典中的键来访问柏林市的湿度、温度和风速。

还有更多...

在网络服务的世界中,API 密钥的使用非常普遍。这个示例是一个很好的例子,说明了我们如何将这些密钥包含在我们的 API 调用中,以便它们可以成功处理。我们还展示了一个技巧,即将数据结构从 MicroPython REPL 复制到计算机上的 Python REPL。这样我们可以在这两个世界之间跳转,并访问一些在计算机上可用但在 MicroPython 上不可用的模块,比如pprint

另请参阅

以下是一些进一步信息的参考资料:

创建一个获取城市天气的函数

在这个示例中,我们将创建一个连接到天气 API 并获取特定城市天气数据的函数。我们不希望直接在源代码中硬编码诸如 API 密钥之类的值。因此,这个示例还将向您展示如何创建一个 JSON 格式的配置文件,可以存储不同的设置,比如 API 密钥。应用程序将在启动时从这个配置文件中读取值,并在调用天气网络服务时使用它们。

每当您想要将配置值与代码库分开保留时,无论是出于安全原因还是为了更轻松地调整这些设置而不更改应用程序的源代码,这个示例都会对您非常有用。这也可以帮助您在自己的项目中将 API 调用组织成可重用的函数。

准备工作

在 ESP8266 上运行此处方中提供的代码,您将需要访问 REPL。

如何做...

让我们按照这个食谱所需的步骤进行操作:

  1. 在 REPL 中执行下一块代码:
>>> from netcheck import wait_for_networking
>>> import urequests
>>> import json
>>> 
>>> CONF_PATH = 'conf.json'
>>> API_URL = 'http://api.openweathermap.org/data/2.5/weather'
  1. CONF_PATH变量定义了我们的 JSON 配置文件的位置。

  2. 以下内容应放入板的根文件夹中的conf.json文件中。将APPID的值替换为您的实际APPID值:

{"APPID": "put-your-API-key(APPID)-here"}
  1. 下一块代码定义了一个函数,该函数将读取和解析配置文件中提供的设置。然后将这些设置的值返回给调用函数:
>>> def get_conf():
...     content = open(CONF_PATH).read()
...     return json.loads(content)
...     
...     
... 
>>> 
  1. 现在我们将调用get_conf函数并将其结果存储在名为conf的变量中。然后检索并保存APPID的值以供将来使用:
>>> conf = get_conf()
>>> APPID = conf['APPID']
  1. 下一块代码定义了一个函数,该函数接收一个城市名称并执行该城市的天气 API 调用,并返回解析后的天气数据:
>>> def get_weather(APPID, city):
...     url = API_URL + '?units=metric&APPID=' + APPID + '&q=' 
...     + city
...     return urequests.get(url).json()
...     
...     
... 
>>> 
  1. 下一块代码调用get_weather函数获取伦敦市的天气,并将结果存储在名为data的变量中。然后访问并打印出多个不同的数据字段:
>>> data = get_weather(APPID, 'London')
>>> 
>>> print('temp:', data['main']['temp'])
temp: 7.87
>>> print('wind:', data['wind']['speed'])
wind: 3.1
>>> print('name:', data['name'])
name: London
>>> print('country:', data['sys']['country'])
country: GB
>>> 
  1. 下一块代码应该放入main.py文件中。
from netcheck import wait_for_networking
import urequests
import json

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def main():
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    data = get_weather(APPID, 'London')
    print('temp:', data['main']['temp'])
    print('wind:', data['wind']['speed'])
    print('name:', data['name'])
    print('country:', data['sys']['country'])

main()

当执行此脚本时,它将连接到天气 API 并打印出伦敦市检索到的多个数据元素。

它是如何工作的...

主脚本首先调用wait_for_networking来确保网络正常运行,然后调用get_conf来检索应用程序的配置数据,后者解析存储在配置文件中的 JSON 数据。

然后从配置设置中访问APPID的值。然后使用get_weather函数进行 API 调用。此函数接收APPID值和要获取信息的城市名称。有了这两个值,它就可以准备 URL 并进行 API 调用。

然后对结果进行解析并返回到main函数。然后访问数据结构以从返回的 API 调用中获取多个值,并打印出它们的相关标签。

还有更多...

这个食谱展示了一种通用的技术,用于将诸如 API 密钥之类的值存储在源代码之外。JSON 是一种有用的文件格式,特别适用于存储配置值,特别是在使用 MicroPython 时,因为它内置支持解析此文件格式。一些应用程序还使用流行的.ini文件格式进行配置文件,该文件格式在 Python 标准库中得到支持。这个 Python 模块不作为 MicroPython 的主要库的一部分提供,因此最好在您可以的时候避免在 MicroPython 项目中使用它。

另请参阅

以下是一些进一步信息的参考:

随机选择城市

在这个食谱中,我们将使用random模块从固定的城市列表中随机选择城市。我们首先创建一个名为CITIES的全局变量来存储这些值。然后我们可以使用random模块中的特定函数,用于从值列表中选择随机项。

然后,该食谱将循环 10 次,从城市列表中进行随机选择,并输出所选城市的详细信息。每当您需要从固定值列表中随机选择某个选项的项目时,这个食谱将特别有用。例如,您可以创建一个骰子投掷 MicroPython 项目,每次投掷都应该从值 1 到 6 中选择一个值。

准备工作

在 ESP8266 上运行此处方中提供的代码,您将需要访问 REPL。

如何做...

让我们按照这个食谱所需的步骤进行操作:

  1. 使用 REPL 来运行以下代码行:
>>> import random
>>> 
>>> CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
  1. 我们现在已经定义了一个城市列表,我们可以从中随机选择。下一段代码展示了从random Python 模块中获取随机数据的最简单方法之一:
>>> random.random()
0.0235046
>>> random.random()
0.830886
>>> random.random()
0.0738319
  1. 对于我们的目的,我们可以使用choice函数,因为它会从列表中随机选择一个项目。以下代码块使用这种方法随机选择三个城市:
>>> random.choice(CITIES)
'Rome'
>>> random.choice(CITIES)
'Berlin'
>>> 
>>> random.choice(CITIES)
'Oslo'
  1. 以下代码块将循环 10 次,并在每次迭代中打印出一个随机选择的城市:
>>> for i in range(10):
...     city = random.choice(CITIES)
...     print('random selection', i, city)
...     
...     
... 
random selection 0 London
random selection 1 Tokyo
random selection 2 Oslo
random selection 3 Berlin
random selection 4 Bangkok
random selection 5 Tokyo
random selection 6 London
random selection 7 Oslo
random selection 8 Oslo
random selection 9 London
>>> 
  1. 下一段代码应该放入main.py文件中:
import random

CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']

def main():
    for i in range(10):
        city = random.choice(CITIES)
        print('random selection', i, city)

main()

当执行此脚本时,它将打印出 10 个随机选择的城市。

工作原理...

我们首先导入将用于执行城市的随机选择的random模块。重复调用random函数以验证我们可以从模块中获取随机数。我们创建了一个名为CITIES的变量,这是我们想要从中进行随机选择的城市列表。然后使用random模块中的choice函数从这个列表中选择一个随机选择。main函数通过调用choice函数 10 次并打印出每次调用的结果来演示了这种逻辑。

还有更多...

本章只需要选择随机数来创建天气机器运行的不可预测性水平。因此,我们不需要担心生成的随机数的质量。然而,如果我们需要随机数用于某些加密操作的目的,那么我们需要更加小心地生成这些数字。我们还需要详细了解随机数生成器如何通过调用seed函数进行初始化。

另请参阅

以下是一些进一步信息的参考资料:

为文本处理创建一个 Screen 对象

在这个示例中,我们将创建一个Screen对象,它将更容易地将多行输出写入到 FeatherWing OLED 显示器中。我们正在构建的天气机器将希望利用 OLED 显示器的多行输出功能。

为了方便输出,这个示例将创建一个对象,接收多行文本,并将文本正确地定位在其关联的xy坐标上。您会发现这个示例对于任何需要频繁向显示器写入文本内容并希望自动处理多行输出的项目非常有用。

准备就绪

您需要访问 ESP8266 上的 REPL 来运行本示例中提供的代码。

如何操作...

让我们按照这个示例中所需的步骤进行操作:

  1. 在 REPL 中运行以下代码行:
>>> import adafruit_ssd1306
>>> import board
>>> import busio
>>> 
>>> BLACK = 0
>>> WHITE = 1
>>> 
>>> MESSAGE = """\
... top line %s
... middle line
... last line
... """
>>> 
  1. 我们已经导入了必要的模块,并创建了一个名为MESSAGE的变量,我们将用它来生成多行输出消息。下一段代码将创建Screen对象的基本结构,其中包含一个接收oled显示对象的构造函数:
>>> class Screen:
...     def __init__(self, oled):
...         self.oled = oled
...         self.oled.fill(BLACK)
...         self.oled.show()
...         
...         
... 
>>> 
  1. 在以下代码行中,我们创建一个与显示器交互的对象和Screen类的一个实例:
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
>>> screen = Screen(oled)
  1. 我们现在将在Screen对象中添加一个方法,负责将多行文本写入显示器:
>>> class Screen:
...     def __init__(self, oled):
...         self.oled = oled
...         self.oled.fill(BLACK)
...         self.oled.show()
...         
...     def write(self, text):
...         self.oled.fill(BLACK)
...         lines = text.strip().split('\n')
...         for row, line in enumerate(lines):
...             self.oled.text(line, 0, 10 * row, WHITE)
...         self.oled.show()
...         
...         
... 
>>> 
  1. 我们现在创建一个Screen对象并调用它的write方法。您应该会在显示器上看到'hello'文本出现:
>>> screen = Screen(oled)
>>> screen.write('hello')
  1. 下一段代码将在显示器上打印一个占据三行的多行消息:
>>> screen.write('multi \n line \n output')
>>> 
  1. 运行以下代码在显示器上显示 10 条不同的多行消息:
>>> for i in range(10):
...     print(i)
...     screen.write(MESSAGE % i)
...     
...     
... 
0
1
2
3
4
5
6
7
8
9
>>> 
  1. 以下代码应该放入screen.py文件中:
import adafruit_ssd1306
import board
import busio

BLACK = 0
WHITE = 1

class Screen:
    def __init__(self, oled):
        self.oled = oled
        self.oled.fill(BLACK)
        self.oled.show()

    def write(self, text):
        self.oled.fill(BLACK)
        lines = text.strip().split('\n')
        for row, line in enumerate(lines):
            self.oled.text(line, 0, 10 * row, WHITE)
        self.oled.show()

def get_oled():
    i2c = busio.I2C(board.SCL, board.SDA)
    return adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
  1. 下一段代码应该放入main.py文件中:
from screen import Screen, get_oled

MESSAGE = """\
top line %s
middle line
last line
"""

def main():
    oled = get_oled()
    screen = Screen(oled)
    screen.write('hello')

    for i in range(10):
        print(i)
        screen.write(MESSAGE % i)

main()

当执行此脚本时,它将在 OLED 显示器上打印出 10 个多行文本块。

它是如何工作的...

screen对象将其构造函数的单个参数。这个参数是oled变量,它将让我们与显示器交互。保存了对这个对象的引用,然后清除了显示器上的所有像素。它还定义了一个名为write的方法。这个方法接收一个字符串,可以是单行或多行文本。

然后清除显示器,将文本分解为字符串列表,每个字符串代表一个输出行。这些行被循环处理,并分别写入其正确的行。一旦所有行都被处理,就会在显示器上调用show方法来呈现内容。这个配方中的main函数设置了screen对象,然后向显示器发送了一个简单的hello消息。然后循环 10 次并生成一组多行消息,这些消息将依次显示在屏幕上。

还有更多...

Screen对象的设计类似于 Python 中其他文件(如对象)的设计。例如,Python 模块sys有一个stdout对象,它有一个write方法,可以让您将文本输出到屏幕上。将复杂的交互(如文本放置的xy位置)打包到一个单独的对象中,通常会使其余的代码更简单和更易读。

另请参阅

以下是一些进一步信息的参考:

创建一个显示城市天气的函数

在本配方中,我们将创建一个函数,该函数接受城市的名称,查找其天气信息,然后在 OLED 显示器上显示部分信息。为了实现这一点,本配方中的函数将结合本章中涵盖的不同部分。

除了输出到 OLED,它还会将相同的信息打印到标准输出,以便进行调试。当您想要查看像天气机这样的项目如何被分解为结构化设计中互相调用的单独部分时,这个配方对您可能有用。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本配方中提供的代码。

如何做...

让我们按照本配方中所需的步骤进行操作:

  1. 在 REPL 中执行下一段代码:
>>> from screen import Screen, get_oled
>>> from netcheck import wait_for_networking
>>> import urequests
>>> import json
>>> 
>>> CONF_PATH = 'conf.json'
>>> API_URL = 'http://api.openweathermap.org/data/2.5/weather'
>>> CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
>>> WEATHER = """\
... City: {city}
... Temp: {temp}
... Wind: {wind}
... """
>>> 
  1. 在导入所需的模块之后,我们创建了一个名为WEATHER的新变量,它存储了模板,我们将使用它来将天气信息输出到显示器上。运行下一段代码来设置屏幕对象并获取 API 调用的APPID值:
>>> def get_conf():
...     content = open(CONF_PATH).read()
...     return json.loads(content)
...     
...     
... 
>>> def get_weather(APPID, city):
...     url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
...     return urequests.get(url).json()
...     
...     
... 
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 在以下代码行中,我们定义了show_weather函数,该函数接受屏幕、APPID和城市名称,然后将获取并显示该城市的天气信息:
>>> def show_weather(screen, APPID, city):
...     weather = get_weather(APPID, city)
...     data = {}
...     data['city'] = city
...     data['temp'] = weather['main']['temp']
...     data['wind'] = weather['wind']['speed']
...     text = WEATHER.format(**data)
...     print('-------- %s --------' % city)
...     print(text)
...     screen.write(text)
...     
...     
... 
>>> 
  1. 运行下一段代码来调用show_weather函数以获取东京市的天气。您在标准输出上看到的文本也应该显示在 OLED 显示器上:
>>> show_weather(screen, APPID, 'Tokyo')
-------- Tokyo --------
City: Tokyo
Temp: 13.67
Wind: 6.7

>>> 
  1. 当我们执行以下代码块时,它将循环遍历所有城市,并在屏幕上显示它们的天气信息:
>>> for city in CITIES:
...     show_weather(screen, APPID, city)
...     
...     
... 
-------- Berlin --------
City: Berlin
Temp: 10.03
Wind: 3.6

-------- London --------
City: London
Temp: 8.56
Wind: 8.7

-------- Paris --------
City: Paris
Temp: 9.11
Wind: 5.1

-------- Tokyo --------
City: Tokyo
Temp: 13.55
Wind: 6.7

-------- Rome --------
City: Rome
Temp: 11.69
Wind: 6.2

-------- Oslo --------
City: Oslo
Temp: 10.13
Wind: 2.1

-------- Bangkok --------
City: Bangkok
Temp: 30.66
Wind: 5.1

>>> 
  1. 下一段代码应该放入main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
import urequests
import json

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    for city in CITIES:
        show_weather(screen, APPID, city)

main()

当执行此脚本时,它将循环遍历所有城市名称,并在 OLED 显示器上显示它们的天气信息。

它是如何工作的...

show_weather函数在这个示例中承担了大部分的工作。当调用时,它首先通过调用get_weather函数收集天气数据。然后,它将这些信息填充到一个名为data的字典中,包括城市名称、温度和风速。

然后,这些值被填入WEATHER模板中,该模板用作控制如何在屏幕上呈现这些信息的模板。生成的文本既输出到标准输出显示器上,也显示在 OLED 显示器上。主函数将配置多个变量,以便可以进行 API 调用并更新屏幕。然后,它循环遍历城市列表,并为每个城市调用show_weather

还有更多...

Python 在字符串模板方面提供了很多选项。在这个示例中使用的是内置于 Python 和 MicroPython 中的字符串格式化函数,这使它成为一个理想的选择。通常最好将模板保存在它们自己的变量中,就像在这个示例中所做的那样。这样可以更容易地更改标签并可视化预期结果的外观。

show_weather函数在标准输出和 OLED 显示器上输出相同的文本。处理文本输出的一个强大方面是可以在许多设备上复制相同的输出。您还可以进一步扩展这一点,并在文本日志文件中记录每次屏幕更新,以帮助调试。

另请参阅

以下是一些进一步信息的参考资料:

在获取数据时提供视觉反馈

在这个示例中,我们将增强上一个示例中的代码,以便在每次开始获取特定城市的天气数据时添加视觉反馈。这个示例的第一部分是进行一些测量,以找出show_weather函数有多慢。这将让我们了解函数是否足够慢,以至于用户可以看到。

然后,我们将使用显示器上的invert功能提供即时的视觉反馈,表明我们已经开始获取天气数据。这个示例将帮助您了解微控制器的硬件限制所面临的性能挑战,并且如何克服这些挑战,有时为应用程序的用户提供某种反馈。

准备工作

您将需要访问 ESP8266 上的 REPL 来运行本示例中呈现的代码。测量执行时间和反转颜色的方法是基于第十三章中在显示器上反转颜色一节中介绍的,与 Adafruit FeatherWing OLED交互。在继续本示例之前,最好先复习一下那个示例。

操作步骤

让我们按照这个示例中所需的步骤进行操作:

  1. 使用 REPL 来运行以下代码行:
>>> import time
>>> 
>>> def measure_time(label, func, args=(), count=3):
...     for i in range(count):
...         start = time.monotonic()
...         func(*args)
...         total = (time.monotonic() - start) * 1000
...         print(label + ':', '%s ms' % total)
...         
...         
... 
>>> 
  1. measure_time函数现在已经定义。在继续之前,请确保将上一个示例中main.py文件中的所有函数定义、模块导入和全局变量粘贴到 REPL 中。然后,运行以下代码块:
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 我们现在已经有了测量show_weather函数执行时间所需的一切。运行下一个代码块来进行三次测量:
>>> measure_time('show_weather', show_weather, [screen, APPID, 'Rome'])
-------- Rome --------
City: Rome
Temp: 9.34
Wind: 2.6

show_weather: 2047.0 ms
-------- Rome --------
City: Rome
Temp: 9.3
Wind: 2.6

show_weather: 1925.9 ms
-------- Rome --------
City: Rome
Temp: 9.36
Wind: 2.6

show_weather: 2019.04 ms
>>> 
  1. 从这些测量中,我们可以看到每次调用大约需要 2 秒的执行时间。我们现在将在show_weather函数的开头和结尾添加对invert方法的调用,如下面的代码块所示:
>>> def show_weather(screen, APPID, city):
...     screen.oled.invert(True)
...     weather = get_weather(APPID, city)
...     data = {}
...     data['city'] = city
...     data['temp'] = weather['main']['temp']
...     data['wind'] = weather['wind']['speed']
...     text = WEATHER.format(**data)
...     print('-------- %s --------' % city)
...     print(text)
...     screen.write(text)
...     screen.oled.invert(False)
...     
...     
... 
>>> 
  1. 执行以下代码块时,将在show_weather函数执行的开始和结束提供视觉反馈:
>>> show_weather(screen, APPID, 'Rome')
-------- Rome --------
City: Rome
Temp: 9.3
Wind: 2.6

>>> 
  1. 下一段代码应该放在main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
import urequests
import json
import time

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    screen.oled.invert(True)
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)
    screen.oled.invert(False)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    for city in CITIES:
        show_weather(screen, APPID, city)
        time.sleep(1)

main()

当执行此脚本时,它将循环遍历每个城市,并使用新的反转颜色视觉反馈调用show_weather函数。

工作原理...

measure_time函数帮助我们测量了show_weather函数的执行时间。该函数正在从互联网获取数据,解析数据,然后执行一些屏幕操作来显示它。测得的执行时间大约为 2 秒。与台式电脑相比,微控制器的计算能力有限。在台式电脑上,类似这样的操作可能需要几百毫秒,但在微控制器上可能需要更长时间。由于这种明显的执行时间,我们通过在执行的开始处反转颜色来增强show_weather函数。这种颜色反转将在几毫秒内显示,并且会在任何其他处理之前显示。然后,在执行结束时,反转的颜色将恢复到正常状态,以指示函数已完成执行。

还有更多...

在以后的教程中,当我们将按钮连接到show_weather函数时,视觉反馈将变得非常重要。屏幕更新延迟 2 秒是非常明显的,用户需要某种视觉反馈来指示机器正在执行操作,而不是卡住了。本教程中展示的invert方法非常适合这个目的,并且不需要太多额外的代码来实现其结果。

另请参阅

以下是一些进一步信息的参考资料:

创建一个函数来显示随机城市的天气

在这个教程中,我们将创建一个函数,每次调用时都会选择一个随机城市并在屏幕上显示其天气信息。该函数将使用random模块中的choice函数来选择一个随机城市,然后使用show_weather函数来显示该城市的天气信息。

在您想要向项目中添加一些随机性以使与该设备的交互更加不可预测的情况下,本教程可能对您有用。这可以在您的项目中创建一些意想不到的和令人惊讶的行为,使其更有趣。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本教程中提供的代码。

如何操作...

让我们来看看这个教程需要哪些步骤:

  1. 在 REPL 中运行以下代码行:
>>> import random
>>> 
>>> def show_random_weather(screen, APPID):
...     city = random.choice(CITIES)
...     show_weather(screen, APPID, city)
...     
...     
... 
>>> 
  1. show_random_weather函数现在已经定义。在继续之前,请确保将上一个教程中的main.py文件中的所有函数定义,模块导入和全局变量粘贴到 REPL 中。然后,运行以下代码块:
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 运行下一段代码,将显示随机城市的天气:
>>> show_random_weather(screen, APPID)
-------- Bangkok --------
City: Bangkok
Temp: 30.01
Wind: 5.1

>>> 
  1. 现在我们将循环三次并调用show_random_weather函数来测试其功能:
>>> for i in range(3):
...     show_random_weather(screen, APPID)
...     
...     
... 
-------- Rome --------
City: Rome
Temp: 9.08
Wind: 2.6

-------- Berlin --------
City: Berlin
Temp: 8.1
Wind: 3.6

-------- London --------
City: London
Temp: 5.41
Wind: 6.2

>>> 
  1. 下一段代码应该放在main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
import urequests
import json
import time
import random

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    screen.oled.invert(True)
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)
    screen.oled.invert(False)

def show_random_weather(screen, APPID):
    city = random.choice(CITIES)
    show_weather(screen, APPID, city)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    for i in range(3):
        show_random_weather(screen, APPID)

main()

当执行此脚本时,它将循环三次并在每次迭代中选择一个随机城市,然后显示其天气信息。

工作原理...

show_random_weather函数期望两个参数作为其输入。屏幕和APPID需要作为输入参数,以进行所需的 API 调用并更新屏幕内容。在random模块的choice函数上调用CITIES列表,以选择一个随机城市。一旦选择了这个城市,就可以使用show_weather函数获取并显示其天气。在这个示例中,main函数循环三次,并在每个for循环迭代中调用show_random_weather函数。

还有更多...

这个示例是互联网连接的天气机器的最后几个部分之一。我们已经构建和测试了应用程序的每个部分,以确认每个部分在构建上一层的附加逻辑之前都是正常的。这个示例的所有代码和逻辑都是自包含的,这提高了代码的可读性,也有助于故障排除。如果发生任何错误,通过确切地知道异常是在哪个函数中引发的,将更容易进行故障排除。

另请参阅

以下是一些进一步信息的参考资料:

创建一个显示世界各地天气的物联网按钮

在这个示例中,我们将为我们的互联网连接的天气机器添加最后的修饰。我们将在本章中介绍的代码大部分,并在main函数中添加一个事件循环,以便我们可以通过显示世界各地随机城市的天气来响应按钮按下事件。这个示例将为您提供一个很好的例子,说明您如何向现有代码库添加事件循环,以创建用户交互性。

准备工作

您需要访问 ESP8266 上的 REPL 来运行本示例中提供的代码。

如何操作...

让我们按照这个示例中所需的步骤进行操作:

  1. 在 REPL 中执行下一个代码块:
>>> from machine import Pin
>>> 
>>> BUTTON_A_PIN = 0
>>> 
  1. 现在导入Pin对象,以便我们可以与板载按钮进行交互。在继续之前,请确保将上一个示例中的main.py文件中的所有函数定义、模块导入和全局变量粘贴到 REPL 中。然后运行以下代码块:
>>> button = Pin(BUTTON_A_PIN, Pin.IN, Pin.PULL_UP)
>>> 
  1. button变量现在可以读取按钮 A 的状态。运行下一个代码块来检测当前是否按下按钮 A:
>>> not button.value()
False
>>> 
  1. 在按下按钮 A 时,执行以下代码块:
>>> not button.value()
True
>>> 
  1. 运行下一个代码块来准备screenAPPID变量:
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 以下代码块将启动一个事件循环。每次按下按钮 A 时,应显示一个随机城市的天气:
>>> while True:
...     if not button.value():
...         show_random_weather(screen, APPID)
...         
...         
... 
-------- London --------
City: London
Temp: 6.62
Wind: 4.6

-------- Paris --------
City: Paris
Temp: 4.53
Wind: 2.6

-------- Rome --------
City: Rome
Temp: 10.39
Wind: 2.6
>>> 
  1. 下一个代码块应放入main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
from machine import Pin
import urequests
import json
import time
import random

BUTTON_A_PIN = 0
CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    screen.oled.invert(True)
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)
    screen.oled.invert(False)

def show_random_weather(screen, APPID):
    city = random.choice(CITIES)
    show_weather(screen, APPID, city)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    button = Pin(BUTTON_A_PIN, Pin.IN, Pin.PULL_UP)
    show_random_weather(screen, APPID)
    while True:
        if not button.value():
            show_random_weather(screen, APPID)

main()

当执行此脚本时,它将启动一个事件循环,每次按下按钮 A 时都会获取并显示一个随机城市的天气。

工作原理...

在这个示例中的main函数创建了一个名为ButtonPin对象,它将连接到按钮 A。我们可以使用这个button变量来轮询按钮的状态。然后,我们显示一个随机城市的天气,以便应用程序的起始状态是显示屏上显示的天气。然后,启动一个无限循环,这将是我们的事件循环,用于处理任何按钮事件。在每个循环中,我们检查按钮 A 是否被按下。如果是,则调用show_random_weather函数在屏幕上显示一个随机城市的天气。

还有更多...

此处的食谱对单个按钮的按下做出反应,显示随机天气。我们可以将按钮 B 和 C 连接到我们的主“事件”循环,并让它们产生其他功能。按下按钮 A 可能会更改城市,而 B 和 C 可以让您滚动并查看与当前选择的城市相关的更多天气信息。下一张照片显示了连接到互联网的天气机在显示东京市天气信息时的样子:

这个食谱也可以更改为从网络服务中获取和显示任何信息。您可以获取最新的新闻头条并显示它们,或者从 RESTful 笑话 API 中显示一个随机笑话。拥有多行文本显示和互联网连接,您可以做的事情是无限的。

另请参阅

以下是一些进一步信息的参考资料:

第十五章:在 Adafruit HalloWing 微控制器上编码

在本章中,我们将创建一个讲笑话的机器。我们将使用 Adafruit HalloWing M0 Express 板,该板配有全彩 TFT 显示屏和电容式触摸传感器。每次按下触摸按钮时,都会呈现一个新的笑话谜语。您可以尝试猜出谜底,当您准备好时,触摸按钮以显示谜底。再次按下触摸按钮会随机选择一个新的谜语并开始另一场游戏。

本章将是一个有用的信息来源,并帮助您构建项目,让您利用全彩屏幕的强大功能,具有足够的分辨率来呈现多行文本和全彩图像。

我们将在本章中介绍以下内容:

  • 发现 I2C 设备

  • 使用 I2C 从加速计读取数据

  • 使用加速计检测翻转板

  • 控制屏幕亮度

  • 显示位图图像

  • 列出所有图像文件

  • 创建一个讲笑话的机器

Adafruit HalloWing M0 Express

Adafruit HalloWing 是一款带有内置 1.44 英寸 128 x 128 全彩 TFT 显示屏的微控制器。用于显示图像的软件完全支持显示全彩位图图像文件。设备上有 8MB 的存储空间,这为您提供了足够的空间来存储和显示大量图像。该板还配备有 3 轴加速计、光传感器和 4 个电容式触摸板。以下屏幕截图显示了 TFT 屏幕显示位图图像:

该板可以由便携式电源供电。它支持可充电锂聚合物电池和 USB 便携式电源银行。

购买地点

Adafruit HalloWing M0 Express 板可以直接从 Adafruit(www.adafruit.com/product/3900)购买。

技术要求

本章的代码文件可以在本书的 GitHub 存储库的Chapter15文件夹中找到,网址为github.com/PacktPublishing/MicroPython-Cookbook

本章使用加载了 CircuitPython 固件的 Adafruit HalloWing M0 Express 板。本章中的所有配方都使用了 CircuitPython 版本 4.0.0-rc.1。

您可以从circuitpython.org/board/hallowing_m0_express/下载固件图像。

本章中的许多配方需要一组位图图像传输到 Adafruit HalloWing 设备。它们都可以从本书的 GitHub 存储库的Chapter15文件夹中下载。它们应该保存在顶层文件夹中,与您的main.py文件一起。

发现 I2C 设备

这个配方将向您展示如何使用i2c对象扫描连接到总线的 I2C 设备。I2C 协议支持多个设备连接到单个 I2C 连接。连接到设备的第一步之一是扫描和列出所有检测到的设备。这个配方将帮助您排除 I2C 设备,以确认它已连接并且可以在扫描中找到。它还可以帮助您构建可以自动扫描和检测多个设备的 Python 脚本。

准备工作

您需要访问 Adafruit HalloWing 板上的 REPL 来运行本配方中提供的代码。

如何操作...

按照以下步骤学习如何发现 I2C 设备:

  1. 在 REPL 中运行以下代码行:
>>> import board
>>> import busio
  1. 所需的库已经被导入。运行以下代码行以创建将用于扫描的i2c对象:
>>> i2c = busio.I2C(board.SCL, board.SDA)
  1. 以下代码行将一直循环,直到在 I2C 总线上获得锁定:
>>> while not i2c.try_lock():
...     print('getting lock...')
...     
...     
... 
>>> 
  1. 以下代码块执行扫描并列出所有检测到的设备:
>>> i2c.scan()
[24]
  1. 我们可以再次执行扫描,并将返回的设备地址转换为十六进制格式:
>>> [hex(x) for x in i2c.scan()]
['0x18']
  1. 以下代码块应放入main.py文件中:
import board
import busio

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    while not i2c.try_lock():
        print('getting lock...')
    devices = [hex(x) for x in i2c.scan()]
    print('devices found:', devices)

main()

当执行此脚本时,它将打印出所有发现设备的地址。

工作原理...

主要函数设置了i2c对象。然后重复调用try_lock方法,直到获得锁。这个锁是需要执行 I2C 总线扫描的。然后调用scan方法,它返回一个设备地址列表。然后将每个地址转换为十六进制表示法,并保存为字符串列表在设备的变量中。最后,输出这个变量的内容,并指示这是在总线上发现的设备列表。

还有更多...

一些 I2C 操作,比如扫描,需要一个锁。如果您尝试在没有先获取锁的情况下执行扫描,您将收到一个运行时错误,指示此函数需要一个锁。在下一个教程中,我们将看到还有其他不需要锁的操作。I2C 的地址经常使用十六进制表示法来引用,这就是为什么我们将值从整数转换为十六进制值的原因。

Adafruit HalloWing M0 Express 板配备了一个 I2C 设备——一个加速度计,其地址应为0x18。我们的扫描证实了这一点。如果您不确定您的设备的具体地址值,您可以使用扫描方法来检测这些值。

另请参阅

以下是关于这个教程的一些参考资料:

使用 I2C 从加速度计读取数据

这个教程将向您展示如何使用 I2C 协议连接板载加速度计。一旦我们有了一个 I2C 对象,我们将使用 Python 的adafruit_lis3dh库创建一个LIS3DH_I2C对象。这个对象将让我们从加速度计中读取实时传感器数据。这个教程将帮助您在想要创建一个利用板的方向来创建交互体验的项目时。例如,您可以创建一个根据板被摇动而改变当前显示图像的项目。

准备工作

您需要访问 Adafruit HalloWing 设备上的 REPL 来运行本教程中提供的代码。

如何做...

按照以下步骤学习如何使用 I2C 从加速度计读取数据:

  1. 在 REPL 中执行以下代码块:
>>> from adafruit_lis3dh import LIS3DH_I2C
>>> import board
>>> import busio
>>> import time
>>> 
>>> ACCEL_ADDRESS = 0x18
  1. 所需的库现在已经被导入,并且加速度计地址已经在ACCEL_ADDRESS常量中定义。运行以下代码块来创建一个i2c对象,并使用该对象来创建一个LIS3DH_I2C对象:
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> accel = LIS3DH_I2C(i2c, address=ACCEL_ADDRESS)
  1. 以下代码块将获取加速度计方向数据并显示其值:
>>> accel.acceleration
acceleration(x=0.143678, y=-0.0287355, z=-9.48272)
  1. 我们还可以使用以下代码块访问特定信息,比如x轴方向数据:
>>> accel.acceleration.x
0.124521
  1. 以下循环用于每 0.1 秒打印一次实时加速度计传感器数据:
>>> while True:
...     print(accel.acceleration)
...     time.sleep(0.1)
...     
...     
... 
acceleration(x=0.162835, y=-0.00957851, z=-9.47314)
acceleration(x=0.162835, y=-0.0478925, z=-9.52104)
acceleration(x=0.0957851, y=-0.057471, z=-9.30073)
acceleration(x=0.172413, y=-0.00957851, z=-9.51146)
acceleration(x=0.153256, y=-0.0478925, z=-9.48272)
acceleration(x=0.153256, y=-0.057471, z=-9.53062)
acceleration(x=0.162835, y=-0.057471, z=-9.53062)
  1. 以下代码应该放入main.py文件中:
from adafruit_lis3dh import LIS3DH_I2C
import board
import busio
import time

ACCEL_ADDRESS = 0x18

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    accel = LIS3DH_I2C(i2c, address=ACCEL_ADDRESS)
    while True:
        print(accel.acceleration)
        time.sleep(0.1)

main()

当执行此脚本时,它将每 0.1 秒打印一次来自加速度计的传感器数据。

工作原理...

ACCEL_ADDRESS常量包含 Adafruit HalloWing M0 Express 板上加速度计的地址。一旦我们创建了一个i2c对象,我们将它和ACCEL_ADDRESS一起创建一个LIS3DH_I2C对象,我们将保存在一个名为accel的变量中。然后启动一个无限循环,每次迭代都从加速度计中读取传感器数据并将其打印出来。然后循环等待 0.1 秒延迟,然后开始下一次迭代。

还有更多...

Adafruit HalloWing 设备上使用的加速度计的名称为 LIS3DH,这就是为什么知道如何与该设备通信的 Python 库被称为adafruit_lis3dh。该传感器可用于检测板的方向和加速度。在下一个教程中,我们将使用这些方向数据来检测板何时被翻转。

参见

以下是关于这个教程的一些参考资料:

使用加速度计检测板翻转

这个教程将向您展示如何创建一个函数,用于检测板何时被翻转。为了实现这一点,我们将使用从加速度计获取的方向数据。我们将专注于z轴数据,因为这将指示板是面朝上还是面朝下。本教程中提出的方法在您创建项目并希望以比只是按按钮更有创意的方式与项目进行交互时可能对您有用。当有人发现他们只需翻转您的板就可以与之交互时,这可以创造一种有趣的交互水平。

准备工作

您将需要在 Adafruit HalloWing 设备上访问 REPL 以运行本教程中提供的代码。

如何做...

按照以下步骤学习如何使用加速度计检测板翻转:

  1. 使用 REPL 运行以下代码行:
>>> from adafruit_lis3dh import LIS3DH_I2C
>>> import board
>>> import busio
>>> import time
>>> 
>>> ACCEL_ADDRESS = 0x18
  1. 所需的库已被导入并不断定义。运行以下代码块以创建i2cLIS3DH_I2C对象:
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> accel = LIS3DH_I2C(i2c, address=ACCEL_ADDRESS)
  1. 现在我们可以检查z轴的方向数据:
>>> accel.acceleration.z
-9.43483
  1. 将板翻转,使其显示面朝下,然后运行以下代码块:
>>> accel.acceleration.z
9.50188
  1. z轴的方向值将是一个正数或负数,具体取决于板是面朝上还是面朝下。执行以下代码块来计算这个值:
>>> face = 'up' if accel.acceleration.z < 0 else 'down'
>>> face
'up'
  1. 在将板翻转至面朝下和面朝上时运行以下代码块:
>>> while True:
...     face = 'up' if accel.acceleration.z < 0 else 'down'
...     print('board is face', face)
...     time.sleep(0.1)
...     
...     
... 
board is face up
board is face up
board is face up
board is face down
board is face down
board is face up
board is face up
board is face up
  1. 以下代码应放入main.py文件中:
from adafruit_lis3dh import LIS3DH_I2C
import board
import busio
import time

ACCEL_ADDRESS = 0x18

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    accel = LIS3DH_I2C(i2c, address=ACCEL_ADDRESS)
    while True:
        face = 'up' if accel.acceleration.z < 0 else 'down'
        print('board is face', face)
        time.sleep(0.1)

main()

当执行此脚本时,它将每 0.1 秒打印板是面朝上还是面朝下。

工作原理...

一旦i2caccel变量设置好,我们就可以开始从加速度计中访问方向数据。当板面朝上时,z值将是一个负数,当板面朝下时,z值将是一个正数。我们可以利用这部分信息来计算板是面朝上还是面朝下。开始一个无限循环,并将变量 face 保存为updown值,取决于板的当前方向。然后在循环等待 0.1 秒延迟之前,打印这些信息,然后开始下一次迭代。

还有更多...

这个教程向您展示了如何使用加速度计的一部分信息来检测板的物理方向的变化。一旦我们检测到这种变化,我们可以使脚本改变其输出,例如,每当板的面值发生变化时。加速度计还足够精确,可以提供板相对于z轴的指向角度。我们可以利用这些信息来改变我们的应用程序的行为,取决于板向某个方向倾斜的程度。

参见

以下是关于这个教程的一些参考资料:

控制屏幕亮度

这个食谱将向您展示如何在 Adafruit HalloWing 设备附带的 TFT 显示屏上控制亮度级别。亮度可以通过提供 0 到 1 之间的分数值设置为最大级别或较低级别。亮度设置还可以用于通过将亮度级别设置为 0 来关闭显示器。在您不希望屏幕一直开启的项目中,这个食谱可能对您有用,并且希望打开和关闭屏幕。当您希望将背光的亮度级别调整到较低级别以减少功耗时,它也可能有所帮助。

准备工作

您需要访问 Adafruit HalloWing 设备上的 REPL 才能运行此食谱中提供的代码。

如何做...

按照以下步骤学习如何控制 Adafruit HalloWing 设备的屏幕亮度:

  1. 在 REPL 中运行以下代码行:
>>> import board
>>> import time
  1. 已导入所需的库,您可以运行以下代码块将亮度级别设置为 50%:
>>> board.DISPLAY.brightness = 0.5
  1. 以下代码块将通过将亮度设置为 0%关闭显示器:
>>> board.DISPLAY.brightness = 0
  1. 现在我们可以用以下代码块将亮度设置为最大级别:
>>> board.DISPLAY.brightness = 1.0
  1. 以下函数将在 11 次迭代中将亮度从最低级别提升到最大级别:
>>> def fade_in():
...     for i in range(0, 11):
...         brightness = i / 10
...         print('brightness:', brightness)
...         board.DISPLAY.brightness = brightness
...         time.sleep(0.1)
...         
...         
... 
>>> 
  1. 运行以下代码块。您应该看到显示器逐渐变亮到最大亮度:
>>> fade_in()
brightness: 0.0
brightness: 0.1
brightness: 0.2
brightness: 0.3
brightness: 0.4
brightness: 0.5
brightness: 0.6
brightness: 0.7
brightness: 0.8
brightness: 0.9
brightness: 1.0
>>> 
  1. 以下代码应放入main.py文件中:
import board
import time

def fade_in():
    for i in range(0, 11):
        brightness = i / 10
        print('brightness:', brightness)
        board.DISPLAY.brightness = brightness
        time.sleep(0.1)

def main():
    while True:
        fade_in()

main()

执行此脚本时,它将使屏幕从黑色渐变到完全亮度,每次渐变之间延迟 0.1 秒。

工作原理...

主函数启动一个无限循环,不断调用fade_in函数。每次调用fade_in函数都会启动一个for循环,循环遍历 11 个亮度值。这些值从显示关闭到将显示设置为最大亮度不等。每次迭代都会计算亮度级别并存储在亮度变量中。该值被打印,然后应用于DISPLAY对象上的亮度属性。然后应用 0.1 秒的延迟,然后应用淡入循环的下一次迭代。

还有更多...

这个食谱演示了设置显示屏亮度有多容易。它还向您展示了如何在 Python 中实现屏幕淡入淡出等屏幕效果。当您想要通过关闭显示器的背光来关闭显示器时,亮度属性可能特别有用。您可能会创建一个使用这种技术来优化电量消耗的电池操作设备。

另请参阅

这个食谱有一些参考资料:

显示位图图像

该食谱将向您展示如何创建一个接收位图图像路径的函数,获取该图像,并在 HalloWing 屏幕上显示它。有许多不同的对象和选项可用于操作屏幕的内容。即使我们只想显示单个图像,我们也必须与许多不同的对象进行交互。该食谱让您了解在将图像呈现在板的屏幕上所涉及的内容。如果您正在使用 HalloWing 设备进行需要显示不同图像的项目,并且希望以简单的方式更改当前显示的图像,这个食谱可能会有所帮助。

准备工作

您将需要访问 Adafruit HalloWing 设备上的 REPL 来运行本食谱中提供的代码。

如何做...

按照以下步骤学习如何在 HalloWing 设备屏幕上显示位图图像:

  1. 在 REPL 中执行以下代码块:
>>> import board
>>> from displayio import OnDiskBitmap, ColorConverter, TileGrid, Group
  1. 现在已经导入了displayio模块中的必要对象。运行以下代码块以将文件对象打开为二进制流:
>>> path = 'joke_01_question.bmp'
>>> f = open(path, 'rb')
  1. 我们将使用此文件对象来创建我们的位图对象,然后准备pixel_shader对象:
>>> bitmap = OnDiskBitmap(f)
>>> pixel_shader = ColorConverter()
  1. 然后,这两个对象被用作创建名为spriteTileGrid对象的参数:
sprite = TileGrid(bitmap, pixel_shader=pixel_shader)
  1. 以下代码块创建了一个组对象,并将sprite附加到其中:
>>> group = Group()
>>> group.append(sprite)
  1. 我们可以使用以下代码块在显示器上显示此组,并调用wait_for_frame使代码块等待直到显示器完全更新。现在,我们将关闭文件对象,因为它不再需要:
>>> board.DISPLAY.show(group)
>>> board.DISPLAY.wait_for_frame()
>>> f.close()
  1. 运行以下代码块来定义show_image函数并调用它在显示屏上显示不同的图像:
>>> def show_image(path):
...     with open(path, 'rb') as f:
...         bitmap = OnDiskBitmap(f)
...         pixel_shader = ColorConverter()
...         sprite = TileGrid(bitmap, pixel_shader=pixel_shader)
...         group = Group()
...         group.append(sprite)
...         board.DISPLAY.show(group)
...         board.DISPLAY.wait_for_frame()
...         
...         
... 
>>> show_image('joke_01_response.bmp')
>>> 
  1. 以下代码应放入main.py文件中:
import board
from displayio import OnDiskBitmap, ColorConverter, TileGrid, Group

IMAGES = ['joke_01_question.bmp', 'joke_01_response.bmp']

def show_image(path):
    with open(path, 'rb') as f:
        bitmap = OnDiskBitmap(f)
        pixel_shader = ColorConverter()
        sprite = TileGrid(bitmap, pixel_shader=pixel_shader)
        group = Group()
        group.append(sprite)
        board.DISPLAY.show(group)
        board.DISPLAY.wait_for_frame()

def main():
    while True:
        for image in IMAGES:
            show_image(image)

main()

当执行此脚本时,它将重复在显示屏上显示两个不同位图之间切换的图像。

工作原理...

该食谱中的show_image函数负责在屏幕上显示位图。它接收一个参数,即位图文件的路径。该文件被打开进行读取,然后用于创建一个名为位图的OnDiskBitmap对象。

ColorConverter对象用于创建pixel_shader变量。创建了一个TileGrid对象,并需要显示位图以及将要使用的像素着色器。这两个参数都已提供,并且新的TileGrid对象保存在sprite变量中。sprite变量不能直接提供给DISPLAY对象,因此我们必须创建一个Group对象并将sprite附加到其中。

现在我们可以在DISPLAY对象上调用show方法来显示group变量。调用wait_for_frame方法以确保图像在屏幕上完全显示后继续。主函数启动一个无限循环,不断调用show_image来连续更改当前显示的图像。

接下来...

在 HalloWing 设备上显示位图图像需要使用许多不同类型的对象。部分原因是每个对象在图像在屏幕上显示方面提供了广泛的灵活性。例如,您可以控制图像的xy坐标,或者使用其他不来自文件的位图对象。

显示器可以显示分辨率高达 128 x 128 的图像,保存在 24 位像素 BMP 文件格式中。您可以使用开源的 GIMP 图像编辑器创建这些图像。

在 GIMP 应用程序中创建新图像时,应设置正确的分辨率,如下截图所示:

当您准备保存图像时,请使用文件菜单中的导出功能,并以 BMP 文件格式保存图像。在执行此操作时,请确保选择正确的每像素位数设置,如下截图所示:

重要的是要知道,您还可以使用较小分辨率的图像,并且这将自动检测并正确显示在屏幕上。较小的图像也往往会更快地显示在屏幕上。

另请参阅

以下是有关此配方的一些参考资料:

列出所有图像文件

此配方将向您展示如何列出特定目录中的所有图像文件。在讲笑话的机器中,我们将每个笑话问题和响应创建为一对图像。此配方将允许您列出板上的所有位图图像。然后,我们将扩展此功能以进一步过滤列表,并拥有所有笑话问题的位图图像。此配方可在您创建的任何项目中使用,其中您希望检索要显示的图像列表或音频文件或在您的项目中播放。

准备工作

您需要访问 Adafruit HalloWing 设备上的 REPL 来运行此配方中提供的代码。

如何做...

按照以下步骤学习如何列出图像文件:

  1. 使用 REPL 运行以下代码行:
>>> import os
>>> paths = sorted(os.listdir())
>>> paths
['.Trashes', '.fseventsd', '.metadata_never_index', 'boot_out.txt', 'joke_01_question.bmp', 'joke_01_response.bmp', 'joke_02_question.bmp', 'joke_02_response.bmp', 'joke_03_question.bmp', 'joke_03_response.bmp', 'joke_04_question.bmp', 'joke_04_response.bmp', 'main.py']
  1. 我们现在已经检索并输出了板上根目录中所有路径的排序列表。我们将使用以下代码块仅列出位图图像文件:
>>> images = [i for i in paths if i.endswith('.bmp')]
>>> images
['joke_01_question.bmp', 'joke_01_response.bmp', 'joke_02_question.bmp', 'joke_02_response.bmp', 'joke_03_question.bmp', 'joke_03_response.bmp', 'joke_04_question.bmp', 'joke_04_response.bmp']
  1. 我们可以进一步扩展这一点,只列出笑话问题图像文件,如下面的代码块所示:
>>> questions = [i for i in paths if i.endswith('question.bmp')]
>>> questions
['joke_01_question.bmp', 'joke_02_question.bmp', 'joke_03_question.bmp', 'joke_04_question.bmp']
>>> 
  1. 以下代码块将选择第一个问题图像并将其保存到一个变量中:
>>> question = questions[0]
>>> question
'joke_01_question.bmp'
  1. 以下代码块可用于根据问题图像计算笑话的响应图像的名称:
>>> response = question.replace('question.bmp', 'response.bmp')
>>> response
'joke_01_response.bmp'
  1. 我们将使用以下代码块来确认计算的响应图像是否存在为文件:
>>> response in paths
True
  1. 以下代码应放入main.py文件中:
import os

def get_questions():
    paths = sorted(os.listdir())
    return [i for i in paths if i.endswith('question.bmp')]

def main():
    questions = get_questions()
    for question in questions:
        response = question.replace('question.bmp', 'response.bmp')
        print(question, response)

main()

执行此脚本时,将列出所有问题图像并计算其相关的响应图像。

它是如何工作的...

此配方中的get_questions函数将排序后的文件名列表保存在paths变量中。然后,它通过检查文件名中是否出现question.bmp字符串来过滤列表,仅包括问题图像。然后函数返回过滤后的列表。

主函数调用get_questions函数,并将其结果保存到questions变量中。每个问题都会循环遍历,并在我们将文件名中的question.bmp值替换为response.bmp时计算其响应图像。然后打印问题和响应文件名,然后开始下一次循环。

还有更多...

将使用多张图像来创建讲笑话的机器。我们本可以将所需图像的名称保存在脚本本身中,而不是直接在文件系统中列出它们。但是,此配方中采取的方法更好,因为它避免了我们直接在应用程序中硬编码图像列表。这意味着我们可以在不更改应用程序代码的情况下在板上拥有 5 个笑话甚至 50 个笑话。

每次启动讲笑话的机器时,它都会自动抓取最新的笑话图像列表。以下屏幕截图显示了下一个配方中将用于创建讲笑话的机器的笑话问题和响应:

您可以看到,文件名遵循简单的命名约定,使您在图像查看器中查看每个问题和响应时更容易。这种命名约定还使得计算特定问题图像的相关响应图像变得更加容易。

另请参阅

以下是有关此配方的一些参考资料:

创建一个讲笑话机

这个教程将向您展示如何列出特定目录中的所有图像文件。在我们创建的讲笑话机中,每个笑话问题和回答都将作为一对图像提供。这个教程将向我们展示如何列出板上所有的位图图像。

然后,我们将扩展此功能,以进一步过滤列表,以便我们手头有所有笑话问题的位图图像。如果您想创建一个项目,其中需要检索图像或音频文件的列表,以便在项目中显示或播放它们,那么这个教程将对您有所帮助。

准备工作

您需要访问 Adafruit HalloWing 设备上的 REPL,以运行本教程中提供的代码。

如何做...

按照以下步骤学习如何创建一个讲笑话机:

  1. 在 REPL 中运行以下代码行:
>>> from displayio import OnDiskBitmap, ColorConverter, TileGrid, Group
>>> import board
>>> import random
>>> import time
>>> import touchio
>>> import os
  1. 我们现在已经导入了所有必要的模块。以下代码块将创建一个TouchIn对象:
>>> touch = touchio.TouchIn(board.TOUCH1)
>>>
  1. 在以下代码块中,我们将检查触摸板的状态:
>>> touch.value
False
  1. 在执行以下代码块时触摸板:
>>> touch.value
True
  1. 以下代码块将定义wait_for_touch函数,该函数将一直循环,直到检测到触摸事件为止:
>>> def wait_for_touch(touch):
...     while not touch.value:
...         print('waiting...')
...         time.sleep(0.1)
...         
...         
... 
>>> 
  1. 我们将使用以下代码块来调用wait_for_touch。执行此函数后,等待片刻,然后触摸板以确认函数在检测到触摸事件后是否从其while循环中返回:
>>> wait_for_touch(touch)
waiting...
waiting...
waiting...
waiting...
>>> 
  1. 以下代码块将保存questions变量中的问题图像列表:
>>> def get_questions():
...     paths = sorted(os.listdir())
...     return [i for i in paths if i.endswith('question.bmp')]
...     
...     
... 
>>> 
>>> questions = get_questions()
>>> questions
['joke_01_question.bmp', 'joke_02_question.bmp', 'joke_03_question.bmp', 'joke_04_question.bmp']
  1. 我们将使用以下代码块来随机选择问题列表中的问题:
>>> question = random.choice(questions)
>>> question
'joke_04_question.bmp'
  1. 以下代码应放入main.py文件中:
from displayio import OnDiskBitmap, ColorConverter, TileGrid, Group
import board
import random
import time
import touchio
import os

def show_image(path):
    print('showing image', path)
    with open(path, 'rb') as f:
        bitmap = OnDiskBitmap(f)
        pixel_shader = ColorConverter()
        sprite = TileGrid(bitmap, pixel_shader=pixel_shader)
        group = Group()
        group.append(sprite)
        board.DISPLAY.show(group)
        board.DISPLAY.wait_for_frame()

def wait_for_touch(touch):
    while not touch.value:
        print('waiting...')
        time.sleep(0.1)

def get_questions():
    paths = sorted(os.listdir())
    return [i for i in paths if i.endswith('question.bmp')]

def main():
    touch = touchio.TouchIn(board.TOUCH1)
    questions = get_questions()
    while True:
        question = random.choice(questions)
        response = question.replace('question.bmp', 'response.bmp')
        show_image(question)
        wait_for_touch(touch)
        show_image(response)
        wait_for_touch(touch)

main()

执行此脚本时,它将启动讲笑话机,并在每次按触摸板时让您在显示器上看到笑话问题和回答。

工作原理...

main函数创建一个连接到板上第一个触摸板连接器的TouchIn对象。通过调用get_questions()函数检索问题图像列表,并将返回的列表保存在questions变量中。

然后启动一个无限事件循环,首先选择一个随机问题,并计算与该问题相关的响应图像。然后通过调用show_image函数在屏幕上显示问题图像。然后调用wait_for_touch函数,该函数循环检查触摸事件,每 100 毫秒检查一次。

一旦检测到触摸事件,函数将返回,然后调用show_image函数显示响应图像。然后再次调用wait_for_touch函数,以便用户在决定通过按触摸板加载另一个问题之前可以看到响应。一旦按下触摸板,当前循环迭代结束,进程将以新选择的随机问题重新开始。

还有更多...

讲笑话机是使用该板的输入和输出功能的一种有趣方式。它使用板的图形显示来显示不同的笑话问题和回答,以及电容触摸传感器作为输入,以使应用程序加载下一个问题或显示加载问题的答案。

这个基本教程可以以许多方式进行扩展。由于该板带有四个触摸板,您可以创建一个简单的菜单系统,让人们可以从不同类别的笑话中进行选择。您甚至可以通过拥有骰子六个面的图像,并在每次按触摸板时显示一个随机面,来创建一个数字骰子的项目。

另请参阅

以下是关于这个配方的一些参考资料:

posted @ 2024-05-04 21:26  绝不原创的飞龙  阅读(20)  评论(0编辑  收藏  举报