20-个简单的树莓派项目-全-

20 个简单的树莓派项目(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

你是否曾想过你可以花 35 美元买到一台计算机?不,我们不是在说从当铺买来的古董计算机。我们说的是树莓派——一块大约和信用卡大小的计算机板。但不要被它脆弱的外观欺骗,它比看起来更强大。

树莓派是由树莓派基金会创始人埃本·厄普顿(Eben Upton)在英国开发的。厄普顿和他的同事们注意到,申请剑桥大学计算机科学专业的学生技能有所下降,原因是新一代学生从小就接触到易于使用的计算机。这些学生从未需要了解计算机是如何运作的,也没有多少机会进行编程或修理。厄普顿开发树莓派是为了推动学校教授基础计算机科学知识。因此,树莓派是一个裸板而非装在机箱里的计算机:这样每个人都可以看到计算机的各个组件。

自 2012 年首次发布以来,树莓派已售出超过一千万块。树莓派不仅在学生中受欢迎,而且在电子爱好者、修理工、计算机科学家以及各个年龄段的“孩子”中也很流行。

树莓派可以作为一台普通的计算机使用——你可以上网、发邮件、写文档、看视频等等——但这并不是它的主要用途。树莓派是一个你可以实验、破解和玩耍的工具,来构建你自己的程序和发明。树莓派和类似的开发板使得进入电子学和编程的世界变得不仅可能,而且轻松,这也导致了全球范围内创意发明的涌现。现在,轮到你发明一些东西了。

本书适合谁阅读?

本书适合任何希望充分利用树莓派的初学者,无论是想探索电子学和编程世界的孩子,还是希望帮助孩子和学生学习的家长与教育者,或是希望使用树莓派将创意付诸实践的爱好者和制造者。

我们不假设读者有任何关于树莓派、电路或编程的先前知识。如果你具备一些基础技能,本书将帮助你进一步发展这些技能,并为你提供下一步的创意。

关于本书

20 个简单的树莓派项目是一本集合了 20 个树莓派项目的书。我们认为,发现新概念的最佳方式是通过实践,而项目书正是开始的绝佳途径。

本书首先简要介绍树莓派和 Python 编程语言,然后直接进入项目部分。本书采用“边做边学”的方式,这意味着你不需要花费无数小时学习理论知识,才能实际构建一些有趣的东西。

你将通过构建有趣且互动的项目来学习,例如家庭监控系统和 Wi-Fi 控制机器人。你还将设计用户友好的界面来控制电子设备,制作物联网项目,建立网站,创建自己的游戏,以及更多内容。

如果你没有任何编程或电路制作经验,不用担心。每个项目都会提供逐步的指导,帮助你搭建电路、绘制电路图,以及编写程序代码来控制树莓派。你可以从书中复制代码,或者你可以访问 www.nostarch.com/RaspberryPiProject/ 下载代码。

你的树莓派学习不应该仅限于本书中的项目,这就是为什么每个项目都有一个“进一步探索”的部分,我们会提供一些建议,帮助你进一步发展自己的项目,并将本书中学到的概念结合起来,创造更复杂和有趣的东西。我们希望到本书结尾时,你能够掌握必要的技能,将自己的项目创意付诸实践。

这本书需要什么?

本书中的项目是基于树莓派 3 开发的。如果你有较旧型号的树莓派,仍然可以跟着做,但使用树莓派 3 时项目会更容易构建。较旧版本的性能较低,而且非常老的版本 GPIO 引脚较少。

我们不知道树莓派 4 是否会发布,或者何时发布,但到目前为止,所有树莓派版本都与旧版本兼容,因此未来版本也应该与本书中的项目兼容。

本书中的大多数项目都涉及使用树莓派与外部世界交互,利用电路进行操作。这意味着除了树莓派之外,你还需要一些电子元件。在每个项目的开始部分,我们会提供所需元件的清单,标明元件的成本和预计的构建时间。带有一个\(符号的项目可能会花费不到 10 美元的元件,带有两个\)符号的项目则可能在 10 到 50 美元之间(价格范围较广,但元件的价格因购买地点而异)。如果你希望一次性购买所有元件,也可以参考第 9 页的“项目元件清单”,那是本书所需所有元件的完整列表。

本书的组织结构

本书分为七个部分。第一部分是树莓派简介,对于正确设置树莓派至关重要,后续部分包含各个项目。以下是本书的组织结构:

入门篇

在这一部分,我们将向你展示如何开始使用树莓派,并首次设置它。我们还将介绍本书中使用的编程语言——Python 的基础知识。

LED

你将从制作 LED 项目开始。如果你是电子学初学者,本部分将教你构建电路的基本概念。

显示器

在本部分,你将学习如何使用几种类型的显示器,使你的电子项目更加互动。这些项目不仅仅是显示示例文本;你还将学习如何从网络获取数据,并编写一个乒乓游戏。

传感器

这些项目使用了多种传感器。传感器非常棒,因为它们能让你的项目对周围的世界作出反应并与之互动。几乎任何你能想到的东西都有传感器。你将学习如何使用温度传感器、PIR 运动传感器等。

相机

本部分的项目使用了树莓派摄像头模块。你将学习如何使用摄像头拍照以及如何进行视频流传输,之后你将运用这些技能制作像是入侵探测器和家庭监控系统等酷炫的项目。

Web 应用

在这里,你将建立自己的网站,然后通过创建自己的 Web 服务器学习如何远程控制树莓派的 GPIO。此外,你还将探索 Node-RED,这是一个用于轻松构建物联网应用的强大工具。

游戏与玩具

本部分的项目是为你制作可以玩耍的物品。你将制作一个数字鼓组,使用 Scratch 编程并设计你自己的游戏,还将制作一个可以通过智能手机控制的 Wi-Fi 机器人。

在书的最后,你还会找到一些参考信息,包括当前所有可用树莓派板的 GPIO 指南,以及一个可以帮助你识别不同电阻值的电阻颜色表。

本书中的项目是独立的,这意味着你无需遵循特定顺序,你可以随时选择任何你想要制作的项目。

我们希望你在制作本书中的项目时能够玩得开心,并享受这个学习过程。

image

第一章:教程

在本教程中,你将为接下来的项目做好所有必要的准备。我们将向你介绍树莓派主板及其最重要和最有趣的部分。接着,我们将引导你完成设置树莓派的过程,包括所有必需的硬件和软件。

这是一个简单的过程,让你可以将树莓派用于许多任务,包括作为一台常规计算机!我们将介绍如何将树莓派设置为计算机,并展示如何浏览桌面和文件系统。我们还会向你展示如何使用终端,也就是命令行,来给树莓派下达指令。最后,我们将向你介绍 Python 编程语言,这是我们在项目中使用的编程语言。

完成本教程后,你将准备好进行项目的实施。

开始使用树莓派

我们将从探索树莓派主板的用途开始,收集所需的资源来使其启动,并将操作系统加载到一个刚格式化的 microSD 卡上,然后将其插入树莓派主板中。

树莓派是一款信用卡大小的微型计算机板,售价大约为 $35。它拥有普通计算机所需的一切:处理器、内存、用于连接显示器、鼠标和键盘的端口,以及连接互联网的能力。树莓派主板甚至有一个普通计算机没有的特殊功能:通用输入输出引脚(GPIO)。这些 GPIO 让树莓派与现实世界互动,使你能够构建智能电子项目,并用树莓派进行控制。

你可以使用树莓派做大多数常规计算机能做的事情,例如浏览网页、编辑文档、玩游戏、编程等。图 0-1 展示了树莓派作为桌面计算机使用的场景。

image

图 0-1: 将树莓派作为桌面计算机使用

然而,请记住,树莓派的性能不如更昂贵的计算机,因此它有一些局限性。首先,它无法满足某些现代软件的要求,因此并非所有软件都能在树莓派上运行。其次,它运行的是 Linux 操作系统,而不是 Windows 或 macOS,这些系统可能你更加熟悉。

树莓派的用途

那么,如果树莓派和普通计算机差不多,但有一些限制,它的优势在哪里呢?树莓派为你提供了实验的自由,这些是你在自己的计算机上可能不敢或无法尝试的。计算机通常很贵,而且修复起来更加困难,因此如果你不完全了解自己在做什么,你可能不愿意去胡乱改动。然而,正如 Linux 创始人林纳斯·托瓦兹(Linus Torvalds)在接受 BBC 新闻采访时所说,树莓派让你“能够承受失败”。你可以在树莓派上玩弄硬件和软件,而不用担心弄坏昂贵的东西或丢失重要文件!

树莓派可以通过两种主要方式进行实验:

创建你自己的程序 Pi 配备了一套免费的软件开发工具。如果你犯了错,可以直接重置你的 Pi 并重新开始。

创建你自己的电子项目 Pi 板卡具有 GPIO 接口,允许你连接传感器和其他可以与现实世界互动的电子设备。你可以制作有用的物品,并让你的项目 24 小时运转而不消耗太多电力。

树莓派有无穷的用途。以下是世界各地人们制作的一些项目示例:复古游戏机,用于玩老式街机游戏;通过连接多个 Pi 板卡构建的超级计算机;云服务器,用于存储和访问数据;媒体中心,用于在电视上组织和播放媒体;家庭自动化系统,用于控制和监控家庭设备;一系列超酷且实用的机器人,例如农业机器人和自动驾驶机器人——这里只列举了一些。要了解一个非常酷的 Pi 项目,可以看看图 0-2 中展示的 SeeMore 雕塑,它通过关联 256 节点树莓派集群的运动,展示了并行算法的美妙。

image

图 0-2: SeeMore 项目

区分不同的板卡

树莓派板卡有多个版本,如图 0-3 所示。

image

图 0-3: 树莓派板卡的时间轴

本书中我们将使用树莓派 3 Model B,这是我们推荐购买的板卡。然而,如果你已经拥有旧版树莓派,它应该能与本书中的大部分项目兼容。(如果你使用的是 26 个 GPIO 而不是 40 个 GPIO 的型号,可能需要修改某些项目的电路,这可能需要一些研究。你也可以查阅《树莓派 GPIO 引脚指南》(位于第 261 页),以了解旧版板卡的引脚分配。)

树莓派板卡在多个方面有所不同,包括 GPIO 数量、连接器类型和数量。表格 0-1 展示了 Model B 板卡的不同特点。(注意,较新的板卡具有更好的特性,如更大的 RAM 和更强的处理器。你还可以看到 Pi 3 是第一款具有内置蓝牙和 Wi-Fi 的板卡。)

表格 0-1: Model B 板卡的特性

特性 Pi 1 Model B Pi 1 Model B+ Pi 2 Model B Pi 3 Model B
处理器 700 MHz 单核 ARMv6 700 MHz 单核 ARMv6 900 MHz 四核 ARMv7 1.2GHz 四核 ARMv8
RAM 512MB 512MB 1GB 1GB
USB 端口 2 4 4 4
GPIOs 26 40 40 40
以太网端口
HDMI
存储 SD MicroSD MicroSD MicroSD
Wi-Fi - - -
蓝牙 - - -
价格 $35 | $25 $35 | $35
电源 MicroUSB MicroUSB MicroUSB MicroUSB

了解树莓派及其配件

让我们更仔细地看看树莓派板。图 0-4 显示了标注的树莓派 3 Model B 板。

image

图 0-4: 树莓派 3 Model B

树莓派 3 Model B 包含以下组件:

USB 端口 连接外设,如鼠标和键盘。

以太网端口 连接树莓派到互联网。

音频插孔 连接音频设备。

CSI(摄像头串行接口)连接器 连接一个小型摄像头。

HDMI(高清多媒体接口)连接器 连接显示器或电视。

MicroUSB 电源输入 为树莓派供电。(树莓派板上没有电源开关。)MicroUSB 接口仅用于电源输入。

DSI(显示串行接口)连接器 允许你通过 15 针排线轻松连接兼容 DSI 的显示屏,如 LCD 触摸屏。

MicroSD 卡槽 存储 microSD 卡,其中包含操作系统以及树莓派工作所需的所有内容。

处理器 作为树莓派的大脑。它配备了 Broadcom BCM2837 系统芯片(SoC),具有 64 位四核 CPU,最大运行速度为 1.2 GHz。

天线 接收无线局域网和蓝牙信号。

GPIO 引脚 允许你连接传感器以收集数据或发送输出信号来控制输出设备,如电动机或 LED。

如你所见,树莓派只是一个裸露的电子板,单独不能做太多事情。你需要一些配件才能开始使用(参见 图 0-5)。

image

图 0-5: 一台键盘,左至右依次为 HDMI 电缆、microSD 卡、鼠标和电源适配器

这里是你为树莓派需要购买的配件清单:

电源适配器

树莓派 3 应该使用 2.5 A 5 V 电源来供电。

MicroSD 卡

我们建议购买一张 8GB(或更高)10 类 microSD 卡。如果你使用的是树莓派 1 Model B,你需要使用标准大小的 SD 卡,而不是 microSD 卡。

显示器和 HDMI 电缆

你的树莓派可以与显示器或电视配合使用,并通过 HDMI 连接。大多数现代电视和显示器都配备 HDMI 插座,因此你可以直接通过 HDMI 电缆将其连接到树莓派板。如果你有一台旧显示器或电视,它可能配有 VGA 或 DVI 插口。你仍然可以将树莓派连接到这些屏幕,但需要使用 VGA 或 DVI 转 HDMI 适配器。

鼠标和键盘

你可以使用任何具有 USB 插头的鼠标和键盘。你还可以使用无线鼠标和键盘,只要它们带有 USB 接收器。

MicroSD 卡适配器和写卡器(可选) 用于你的 PC。

大多数现代计算机都有 SD 卡槽。你需要将 microSD 卡插入 SD 卡适配器,并将其连接到计算机。如果计算机没有 SD 卡槽,你还需要一个通过 USB 连接的 SD 卡读写器。

以太网线或 Wi-Fi 加密狗(可选)

Raspberry Pi 3 内置 Wi-Fi,但旧版本没有。这意味着旧版本需要通过以太网线或 Wi-Fi 加密狗来连接互联网。如果你购买了 Wi-Fi 加密狗,确保它与你的 Pi 板兼容。你可以访问 elinux.org/RPi_USB_Wi-Fi_Adapters 以查看加密狗是否兼容。

保护壳(可选)

Raspberry Pi 没有附带外壳,直接使用也可以。但我们建议为你的板子购买一个保护壳,这样会更坚固。如果你不想花钱,也可以使用 3D 打印、LEGO 或任何你喜欢的方式自己制作外壳。只要确保外壳能够让你访问 GPIO 引脚,这样你就可以构建电子项目。

注意

你也可以通过电脑运行你的 Raspberry Pi,无需键盘、鼠标或显示器。这种方式称为 无头模式。

如果你不确定你的外设是否与 Raspberry Pi 兼容,可以访问 elinux.org/RPi_VerifiedPeripherals 查找兼容和不兼容的外设列表。

项目部件清单

除了这些设备,你还需要一些电子元件来完成本书中的项目。每个项目会在开始时提供一份详细的所需部件清单,你可以轻松地在大多数在线电子商店找到这些元件,或者通过快速的 Google 搜索找到。我们推荐像 Element14/Farnell、Adafruit、ThePiHut 和 SparkFun 这样的商店。或者,你也可以通过亚马逊购买部件——确保选择高评价的产品。

注意

对于那些以 * 开头的部件,我们建议你购买一个混合包。这些组件非常便宜,你需要大量的它们,所以大宗包更具性价比。

以下是你在本书项目中所需的所有部件清单:

Raspberry Pi 3 Model B

Raspberry Pi Zero W(可选)

面包板

5 毫米 LED(至少两个不同颜色的 LED)

跳线(公对公和公对母)

330 Ω 电阻(至少三个)

4.7 kΩ 电阻

按键(至少八个)

10 kΩ 电位器(至少两个)

MCP3008 芯片

共阴极 RGB LED

WS2812B 可寻址 RGB LED 灯带

排针

逻辑电平转换模块 BSS 138

16×2 Hitachi HD44780 兼容 LCD

0.96 英寸 OLED 显示屏

Sense HAT

PIR 移动传感器 HC-SR501

蜂鸣器

MQ-2 气体和烟雾传感器

Raspberry Pi 摄像头模块 v2

继电器模块 HL-52S

塑料盒外壳

12 V 灯和灯座

12 V 电源适配器

公 DC 插孔电源插座

DHT22 温湿度传感器

MotoZero 扩展板

智能机器人车底盘套件

四节 AA 电池

便携式充电器

打火机

耳机或带有扬声器的显示器以及 HDMI 电缆

你还需要一个烙铁以及焊料、焊锡丝和助焊工具等配件。此外,针头钳、迷你剪钳和剥线器也会非常有用。

上传操作系统

Raspberry Pi 运行的是 Linux 操作系统,这是一款由全球专家志愿者共同开发的开源软件。因为 Linux 是开源的,所有人都可以访问源代码。

注意

Raspberry Pi 有 Android 和 Windows 版本,但它们的表现不如 Linux 系统。最好坚持使用 Linux,它是 Raspberry Pi 支持最广泛的操作系统。

有多种 Linux 发行版可以用于 Raspberry Pi,但对于初学者来说,推荐使用 Raspbian,它在 Raspberry Pi 社区中获得了最多的支持。Raspbian 也可以免费下载。

因为 Raspberry Pi 没有硬盘,所以你需要将操作系统安装在 microSD 卡上,而这需要另一台日常使用的个人电脑。

最简单的方式是通过新设备自带软件(NOOBS)来安装 Raspbian,NOOBS 是一个简单的操作系统安装器,包含了 Raspbian。

下载 NOOBS

要下载 NOOBS,请访问www.raspberrypi.org/downloads/noobs/。NOOBS 提供两种版本:NOOBS 和 NOOBS LITE。NOOBS LITE 没有 Raspbian,所以你需要选择 NOOBS 版本(参见图 0-6)。

image

图 0-6: 下载 NOOBS

点击下载 ZIP按钮以下载 NOOBS。(这是一个大文件,下载可能需要一些时间。)找到文件并保存在你的下载文件夹中,或者其他你保存下载文件的地方,但暂时不要解压它。

你需要将 NOOBS 文件的内容复制到一张空白的 microSD 卡中,但首先你需要格式化这张卡。你的 microSD 卡至少需要 8GB 才能正常运行。按照你计算机操作系统的指引进行操作。

在 Windows 或 macOS 上格式化 microSD 卡

将 microSD 卡连接到计算机后,按照以下步骤格式化你的 microSD 卡:

  1. 访问www.sdcard.org/downloads/formatter_4/,下载适用于 Windows 或 Mac 的 SD 卡格式化工具(参见图 0-7)。

    image

    图 0-7: SD 卡格式化工具下载

  2. 接受条款后,ZIP 文件应开始下载。

  3. 运行安装文件在你的计算机上安装 SD 卡格式化工具。然后,连接你的 microSD 卡到计算机,打开 SD 卡格式化应用程序。

  4. 如果你使用的是 Windows,在 SD 卡格式化工具对话框中,从下拉菜单中选择你的 SD 卡驱动器,并选择覆盖格式化选项。点击格式化按钮进行格式化(参见图 0-8)。

    image

    图 0-8: Windows 上的 SD 卡格式化工具对话框

  5. 如果您使用的是 macOS,在 SD 卡格式化器对话框中,从下拉菜单中选择 microSD 卡驱动器,选择覆盖格式,并将您的 microSD 卡命名为BOOT,如图 0-9 所示。然后,点击格式化

    image

    图 0-9: macOS 上的 SD 卡格式化器对话框

在 Linux 上格式化 microSD 卡

将您的 microSD 卡连接到计算机,然后按照以下说明在 Linux 上格式化您的 microSD 卡:

  1. 访问gparted.org/并下载 GParted 软件。

  2. 安装并打开 GParted。

  3. 如图 0-10 所示,在右上角选择您的 microSD 卡。

    image

    图 0-10: 在 Ubuntu 上使用 GParted

  4. 在窗口的下半部分,您应该看到列出的一系列分区。右键点击其中一个分区并删除它。重复此过程,删除 microSD 卡的所有分区,使所有内存未分配。

  5. 右键点击未分配的分区,选择创建新分区

  6. 将文件系统设置为fat32,输入标签BOOT,并点击添加按钮,如图 0-11 所示。最后,点击主窗口顶部的绿色勾选标志以恢复操作。

    image

    图 0-11: 在 GParted 中创建新的 microSD 卡分区

最终,您的 microSD 卡应该像图 0-12 一样。

image

图 0-12: 使用 GParted 格式化的 microSD 卡

将 Raspbian 加载到 microSD 卡上

在 microSD 卡正确格式化后,将其插入计算机,从NOOBS.zip文件中提取文件,并将文件复制到卡中。然后安全弹出 microSD 卡。

现在,您的 microSD 卡已准备好启动 Raspberry Pi,您可以将 Pi 设置为完全功能的桌面计算机。

将您的 Raspberry Pi 设置为桌面计算机

将 Raspberry Pi 用作桌面计算机就像拥有一个学习中心,允许您构建项目并编写自己的软件,而无需担心弄坏计算机。这也是初学者使用 Raspberry Pi 与物理世界互动的最简单方式。

连接您的 Raspberry Pi

以下是将您的 Pi 用作桌面计算机所需的所有设备列表:

  • 安装了 NOOBS 的 microSD 卡

  • 显示器或电视

  • HDMI 电缆

  • 鼠标

  • 键盘

  • 电源适配器

电源适配器应是最后连接的设备,但其他组件无需按照特定顺序连接。为了简便起见,我们建议您按照以下步骤操作:

注意

如果您的 microSD 卡上没有安装 NOOBS,请返回“下载 NOOBS”的第 10 页,查看如何安装它。

  1. 将 microSD 卡插入 Pi 上的 microSD 卡槽。

  2. 使用 HDMI 电缆将显示器或电视连接到 Raspberry Pi 的 HDMI 接口。

  3. 将鼠标和键盘连接到可用的 USB 端口。

  4. 将电源适配器连接到 microUSB 电源输入端口。

  5. 将电源适配器插入电源插座。

图 0-13 展示了这些连接。

image

图 0-13: 将树莓派连接到必要外设

当你为树莓派通电时,你应该会看到一个红色 LED 和一个绿色 LED 亮起,正如图 0-14 所示。这些被称为状态 LED,它们为你提供一些有用的信息。

image

图 0-14: 树莓派主板状态 LED

当红色 LED 亮起时,表示树莓派已连接到电源。当绿色 LED 闪烁时,表示 microSD 卡正在活动。

首次启动树莓派

一旦你为树莓派主板通电,它应该会自动启动。如果你正确设置了 NOOBS,几秒钟后,你应该会看到图 0-15 所示的安装窗口。

image

图 0-15: NOOBS 安装窗口

注意

正确选择你的键盘布局非常重要,否则你输入时屏幕上会显示错误的字符。

默认情况下,应该会选择 Raspbian。在 NOOBS 安装窗口外部的屏幕底部,你需要选择你的语言和键盘布局,按地区分类。

在设置语言和键盘偏好后,点击安装窗口左上角的安装按钮。接下来,系统会提示你是否覆盖 microSD 卡;选择,安装就会开始。

当安装完成后,点击确定,系统应提示你重启树莓派。重启后,你应该会看到 Raspbian 桌面环境,界面类似于图 0-16。

image

图 0-16: Raspbian 桌面环境

注意

你只会在第一次启动时看到安装菜单。下次启动树莓派时,你将直接进入桌面环境。

恭喜!你成功将你的树莓派设置为桌面电脑。

你不应该被要求输入用户名和密码,但未来可能会要求你这么做。默认的用户名是pi,默认的密码是raspberry。你可以通过点击任务栏主菜单,选择偏好设置树莓派配置来更改密码。在系统标签页中,点击更改密码按钮来更改密码,如图 0-17 所示。

image

图 0-17: 在树莓派上更改密码

我们建议在本书中的项目中使用默认用户账户。不过,如果你需要创建一个新用户账户,请参阅“添加新用户账户”框。

添加新用户帐户

要在树莓派上添加一个新的用户帐户,打开终端并输入以下命令,将用户名替换为你希望给用户设置的名字:

pi@raspberrypi:~ $ sudo adduser *username*

系统会提示你输入密码两次(在输入密码时,终端不会显示字符)。然后,系统会要求你提供有关用户的其他信息,你可以按 ENTER 键跳过这些字段。最后,终端会提示你确认信息。如果信息无误,输入Y并按 ENTER 键。现在,你应该已成功创建了一个新的用户帐户。

桌面导览

一旦你开始探索桌面环境,你会发现它和普通计算机非常相似:它有一个任务栏和一个带有废纸篓的桌面区域。你可以打开、拖动、调整窗口大小和关闭窗口;创建、重命名、移动和删除文件夹;启动应用程序;并做大多数你通常在普通桌面计算机上做的事情。

使用任务栏

任务栏位于屏幕顶部,右侧有几个有用的图标。图 0-18 展示了每个图标的功能。

image

图 0-18: 任务栏右侧的图标

任务栏的左侧有任务栏菜单和一些有用的快速启动程序图标(见图 0-19)。

image

图 0-19: 任务栏快速启动图标

我们鼓励你点击这些图标,探索这些程序。

探索程序菜单

当你点击任务栏菜单(任务栏上的树莓派图标)时,应该会弹出一个下拉菜单,其中包含多个子菜单(见图 0-20)。

image

图 0-20: 任务栏菜单

探索每个菜单项,看看里面有什么。例如,编程子菜单包含了多种编程工具,可以用来编写 Java 和 Python 等语言的软件。你可以在空闲时自己探索这些工具。

文件管理器

任务栏的左侧有一个文件夹图标,点击它会打开文件管理器窗口,如图 0-21 所示。文件管理器是一个包含多个文件夹的文件夹,你可以用它来组织你的文件。你可以创建、重命名、移动和删除文件夹。

image

图 0-21: 文件管理器

访问终端

另一个重要的程序是终端,你在使用树莓派时会频繁用到它。简单来说,终端通过命令行发送基于文本的指令,与树莓派进行交互。要访问终端,点击任务栏上的快速启动终端图标(如图 0-19 所示),或者进入任务栏菜单,选择附件终端。你应该会看到一个类似于图 0-22 的窗口。

image

图 0-22: 树莓派终端

在终端中,你可以输入特定的命令来控制你的树莓派。例如,你可以使用命令ls foldername查看文件夹的内容,或者使用mkdir foldername创建一个新文件夹。基本上,你可以做电脑上能做的所有操作,只不过是通过输入特定的文本命令,而不是点击图标。

不要被终端吓到。它可能看起来有点令人生畏,但在本书中,每个项目都会提供你需要输入的命令,因此跟随起来很容易。

将 Pi 连接到互联网

你可以通过 Wi-Fi 或将 Pi 通过以太网线连接到调制解调器来连接互联网。通过 Wi-Fi 连接就像在普通电脑上一样简单。

点击任务栏右侧的第二个图标,如图 0-23 所示。选择你要连接的网络,输入网络凭据,完成连接。

image

图 0-23: 连接 Wi-Fi

如果你使用的是以太网线,只需将其插入 Pi 的以太网端口。

访问互联网并浏览网页

树莓派浏览器是 Chromium。要打开 Chromium,请点击任务栏上的Chromium图标(蓝色地球图标),或者前往任务栏菜单,选择互联网Chromium

图 0-24 展示了一个打开的 Chromium 窗口。

image

图 0-24: Chromium 网页浏览器

正如你所见,它与 Google Chrome 非常相似。实际上,Chromium 是 Google Chrome 的开源浏览器。

连接外部设备

要使用外部设备,如 USB 可移动磁盘,只需将设备插入其中一个可用的 USB 端口。一个像图 0-25 所示的窗口应该会弹出。选择在文件管理器中打开以查看设备内容或将文件移动到设备上。

image

图 0-25: 可移动介质插入窗口

完成后,通过点击任务栏上的弹出图标并选择你的设备来安全弹出设备。

关机、重启和注销

当你关闭 Pi 时,必须确保正确关闭它。如果直接拔掉电源,可能会损坏 Pi。最简单的关闭 Pi 的方法是从任务栏菜单中选择关机,然后点击关机,如图 0-26 所示。

image

图 0-26: 关机菜单选项

在你拔掉电源适配器前,等待绿灯停止闪烁。

你也可以通过关机菜单重启或注销树莓派。如果你想重新登录,只需输入用户名和密码即可。注销选项在你需要切换帐户时非常有用。

你的树莓派现在准备好运行一些程序了!但是首先,让我们了解一些 Python 编程基础,包括用户输入、变量、数学运算符、条件语句和循环。

开始使用 Python

Python 是你将在本书中大多数项目中使用的编程语言。它是最简单且易学的编程语言之一,所以即使你从未编程过,它也是一个很好的起点。

你的树莓派安装了两个版本的 Python:Python 2 和 Python 3。在本书中,我们将尽可能使用 Python 3,因为 Python 3 是最新版本。Python 2 仍然被许多程序员使用并且有频繁的更新,但它正在逐渐被淘汰。

注意

我们只在项目 12 中使用 Python 2,该项目使用了一些当时在 Python 3 中无法使用的包。

Python 3 集成开发环境

要在 Python 中编写代码,你需要 Python 3 IDLE(集成开发环境),它会在你安装 Raspbian 时自动安装。将你的树莓派连接到显示器、鼠标和键盘后,通过任务栏主菜单启动 IDLE,然后选择 编程Python 3 (IDLE)。图 Figure 0-27 显示了Python Shell,即打开的 IDLE 窗口。

image

图 0-27: Python Shell

Python Shell 是你输入并运行 Python 指令的地方,Python 使用解释器来运行你的指令,解释器实际上是理解并执行你代码的部分。

当 Shell 打开时,你应该看到三个箭头 >>>,如图 Figure 0-27 所示——这叫做提示符,它表示 Shell 已准备好接收你的代码。一些 Python 可以运行的最简单指令是数学运算。试试输入以下内容:

>>> 2 + 2

按下 ENTER,你应该会看到以下结果:

4
>>>

你可以看到结果前没有提示符。这是为了表明它是 Python 的输出,而不是你的输入。

基础 Python 指令

Python 可以理解多种指令。你可以通过将这些指令或命令组合起来来构建程序。我们将在这里介绍不同种类的指令。

数学运算符

Python 能够执行大多数数学运算。表 0-2 列出了 Python 中使用的数学运算符。

表 0-2: Python 中的数学运算符

运算符 数学运算
+ 加法
- 减法
* 乘法
/ 除法
// 除法,去掉小数点后的部分
% 除法后的余数

试试更多这样的运算,熟悉一下 Shell。

Python 关系运算符

Python 可以使用关系运算符进行比较,这些运算符比较两边的值并显示它们之间的关系。一个值,通常是数字或字母,是程序处理的基本组成部分。

表 0-3 展示了在 Python 中使用的不同关系运算符。

表 0-3: Python 中的关系运算符

运算符 描述
== 等于
!= 不等于
> 大于
< 小于
>= 大于或等于
<= 小于或等于

例如,在 Python Shell 中输入以下内容:

>>> 2 > 4

你应该得到:

False
>>>

Python 检查2是否大于4并告诉你不是。另一方面,如果你输入:

>>> 2 < 4

你将得到:

True
>>>
给变量赋值

一个变量就像你创建的一个箱子,用来存储一个值。你给变量起个名字,当你需要使用这个值时,只需输入变量的名字即可。图 0-28 展示了这个概念。要给变量赋值,你使用等号(=),将变量名写在左侧,值写在右侧。

image

图 0-28: 给变量赋值

变量对于存储程序中的信息非常有用,这样你可以在以后引用或重用这些信息。例如,输入以下内容:

>>> my_variable = 10

在这一行,你创建了一个名为my_variable的变量,并将值 10 赋给它,如图 0-28 所示。

然后,如果你像这样在 Python Shell 中输入变量名:

>>> my_variable

它应该返回存储的值作为输出:

10
>>>

你给变量起的名字不能包含空格,并且是区分大小写的,因此my_variablemy_VariableMyVariable是不同的。

数据类型

变量可以存储多种类型的值,而不仅仅是整数。这就是数据类型的作用。数据类型是对值的分类,它告诉解释器可以对值执行哪些操作,以及如何存储它。表 0-4 展示了你最常使用的数据类型。

表 0-4: Python 数据类型

数据类型 描述
Int 整数(整体数字)
Float 带小数点的数字
String 由引号括起来的字符集
Boolean 真或假

让我们来看一下表中每种数据类型的例子:

>>> a = 5
>>> b = 7.2
>>> c = 'Hello'
>>> d = True

这些是四个赋值语句。第一个赋值给a的是一个整数,即一个整数。b变量包含一个浮点数值,即带小数的数字。第三个值'Hello'是一个字符串,即一串字符。(注意,字符串必须放在单引号或双引号内。)最后,True是一个布尔值,这是一种只能取TrueFalse的类型。你在之前使用>操作符时看到了这种数据类型。

Python 编辑器

Python shell 很适合用于简单代码片段的实验,但它一次只能执行一行代码。要编写一个程序,也就是一个 脚本,你需要将许多命令组合在一起一次性执行。编写脚本的正确地方是 Python 编辑器,它就像一个用于编写代码的文字处理器。在编辑器中,你可以创建、编辑和保存 Python 文件。然后,通过运行文件来执行这些脚本。Python 文件的扩展名是 .py

要在 Python 编辑器中打开一个新文件,首先进入 Python shell 然后选择 文件新建文件。 应该会打开一个像图 0-29 这样的窗口。

image

图 0-29: Python 编辑器和 Python shell

Python 编辑器没有提示符,因为你输入的命令不会立即执行。你将它们保存到文件中,稍后再运行。让我们写一个非常简单的 Python 脚本来试用这个编辑器。

你的第一个 Python 程序

传统上,你编写的第一个程序是一个简单的程序,它显示“Hello, world!”的信息。要在 Python 中做到这一点,输入以下内容:

#this script prints Hello, world!
print('Hello, world!')

第一行是一个注释。注释必须以井号(#)开头,表示 Python 解释器应该忽略井号后面的所有文本。注释对于提醒你或使用你脚本的人代码的作用非常有用。

第二行是实际打印 Hello, world! 的代码,使用了 print() 函数。函数告诉 Python 执行某个特定的操作;在这个例子中,print() 函数告诉 Python 显示你输入的括号中的内容。括号中的信息叫做函数的 参数print() 函数是 Python 的内置函数,这意味着它总是可以用来执行打印操作。

注意

所有 Python 脚本都需要一个 .py 扩展名。不要忘记在文件名中添加扩展名。

在你运行代码之前,需要先保存脚本。点击 文件另存为,然后输入脚本的名称——例如,hello_world.py——并选择一个文件夹。

要运行你的脚本,按 F5 键,或者进入 运行运行模块。结果会显示在 Python shell 中,而不是编辑器里。你应该会看到一个显示 Hello, world! 的消息,如图 0-30 所示。

image

图 0-30: 运行 hello_world.py 脚本

现在,尝试更改 print() 函数的参数,看看在 Python shell 中显示不同的消息。

请求用户输入

你可以通过请求用户输入来提升你的程序功能,这意味着用户需要输入一些信息才能让程序继续。 在编辑器中,输入以下代码片段来请求用户输入他们的名字,并使用该名字打印一个问候消息:

username = input('What is your name?')
print('Hello ', username)

你使用 input() 函数向用户询问输入。在这个例子中,用户会被问到 What is your name?。这个文本会显示给用户,并且程序会在用户输入之前不会继续执行。无论用户输入什么,都会被赋值给 username 变量,以便我们在程序中稍后使用它。

将这两行代码保存在一个新的文件中,命名为 username.py 并运行该文件,看看会发生什么。你应该会在终端看到提示让你输入名字,当你输入后,你应该会看到问候语。

input() 函数只接受字符串类型的数据,所以如果你想要获取一个数字,并在程序中使用它,你需要将其转换为整数或浮动数。例如,如果你想以整数形式获取用户的年龄,你需要使用 int() 函数:

age = int(input('How old are you?'))

同样,转换输入为浮动数,你可以使用 float() 函数:

height = float(input('How tall are you?'))

如果你想对输入进行数学运算,将字符串转换为数字是必要的。

通过条件语句做决策

要编写有用的程序,你几乎总是需要根据某个条件的真假来执行不同的操作。在编程中,这些叫做 条件语句,它们有以下结构:

if something is true:
    do_something()
elif this is true instead:
    do_something_else()
(...)
else:
    do_another_something_else()

例如,下面的代码片段尝试根据你选择的颜色来猜测你在想的水果:

  print('Pick a fruit:')
  print('strawberry, banana, or kiwi?')
  color = input ('Enter the color of the fruit you chose: ')
➊ if (color == 'red'):
    ➋ print('Your fruit is a strawberry.')
➌ elif (color == 'yellow'):
      print('Your fruit is a banana.')
➍ elif (color == 'green'):
      print('Your fruit is a kiwi.')
➎ else:
      print('Invalid input.')

第一个 if 条件测试用户是否输入了 red ➊。 如果是,程序猜测水果是草莓,并在 ➋ 输出 Your fruit is a strawberry. 的语句。

elif 语句代表 else if,只有在第一个 if 条件不成立时才会运行。所以如果用户没有输入 red,代码会检查输入是否为 yellow ➌。如果是,程序会猜测水果是香蕉。如果不是,它将检查颜色是否为 green ➍,如果是,程序会猜测你选择了猕猴桃。

注意

Python 的标准缩进是四个空格,而不是一个制表符。然而,在 IDLE 中,你可以按 Tab 键自动插入四个空格。

注意这里一个非常重要的地方。一些行的开头会有几个空格——精确来说是四个空格。这叫做 缩进。缩进对定义 代码块 很重要。例如,你在 ➊ 有一个 if 语句,接下来的行在 ➋ 有缩进。缩进告诉 Python,这一行 ➋ 属于前面的 if 语句,因此该行只有在 if 条件为真时才会执行。位于 ➌ 的这一行没有缩进,这意味着它不属于同一个代码块,会单独执行。缩进在 Python 中至关重要,它让解释器知道什么时候执行哪些代码,因此在编写本书时,请特别注意空格。

最后,如果用户拼写错误或者没有输入小写颜色,else 语句 ➎ 会执行,程序会提示用户输入无效。

总结来说,脚本会依次检查每个条件。当某个条件为假时,它会跳到下一个条件,依此类推。当某个条件为真时,它会执行相应的缩进代码,并结束该语句。注意,程序中没有elif语句的数量限制。else语句也不是必须的,但如果有,它必须放在最后。

尝试使用你自己的示例创建一些if/else语句,使用print()input()函数——这是最好的学习方式。

while 和 for 循环

循环允许你在条件满足时多次执行一段代码。循环有两种类型:while循环和for循环。例如,你可以用while循环打印从 1 到 10 的所有数字。请在 Python 编辑器中输入以下内容:

number = 1
while (number <= 10):
    print(number)
    number = number + 1

属于while循环的代码(由缩进标识)会在变量number的值小于或等于(<=)10 时执行。在每次循环中,当前的number会被打印出来,然后再加 1。

你也可以使用for循环打印从 1 到 10 的数字,像这样:

number = 1
for number in range(1,11):
    print(number)

for循环会在变量number的值处于 1 和 11 之间时执行。range()函数会自动为number变量分配下一个值,直到你指定的最终数字的下一个数值。

当你希望重复一段代码指定次数时,应该使用for循环。使用while循环当你希望代码重复执行,直到某个条件不再满足为止。在某些情况下,你可以使用任意一个,但正如你在本书中逐渐了解的那样,通常一个比另一个更高效。

制作一个简单的计算器

为了测试你新的编程技能,你将构建一个计算器。这个程序的脚本非常简单,并且使用了你刚学到的所有概念。你的计算器将执行以下操作:

  • 显示欢迎信息

  • 询问用户要执行什么操作

  • 让用户输入一个数字

  • 让用户输入另一个数字

  • 执行计算

  • 打印结果

打开 Python shell,选择文件新建文件。将以下代码复制到 Python 编辑器中。(请记住,所有脚本也可以通过书籍资源下载,网址是 www.nostarch.com/RaspberryPiProject/。)

➊ #Python Calculator
➋ running = True
➌ welcome_message = '***Welcome to Python Calculator***'
➍ print(welcome_message)

➎ while running:
      print('Operations')
      print('1 = Addition')
      print('2 = Subtraction')
      print('3 = Multiplication')
      print('4 = Division')
      print('5 = Quit program')
   ➏ operation = int(input('Enter a number to choose an operation: '))
   ➐ if operation == 1:
         print('Addition')
      ➑ first = int(input('Enter first number: '))
        second = int(input('Enter second number: '))
      ➒ print('Result = ', first + second)
   ➐ elif operation == 2:
          print('Subtraction')
        ➑ first = int(input('Enter first number: '))
          second = int(input('Enter second number: '))
        ➒ print('Result = ', first - second)
   ➐ elif operation == 3:
          print('Multiplication')
        ➑ first = int(input('Enter first number: '))
          second = int(input('Enter second number: '))
        ➒ print('Result = ', first * second)
   ➐ elif operation == 4:
          print('Division')
        ➑ first = int(input('Enter first number: '))
          second = int(input('Enter second number: '))
        ➒ print('Result = ', first / second)
   ➐ elif operation == 5:
          print('Quitting program... ')
        ➓ running = False

让我们解析一下计算器程序是如何工作的。第一行只是一个注释,告诉任何阅读该程序的人脚本的功能 ➊。然后,在 ➋ 和 ➌ 处,我们为变量赋值,并在 ➍ 处打印欢迎信息。

接下来我们启动一个while循环 ➎,只要running被设置为True,它就会继续运行;也就是说,只要用户没有退出程序。我们要求用户选择一个数学运算,使用数字 1 到 5,程序将他们选择的值存储在operation变量中 ➏。我们使用一系列的if/elif语句来根据输入选择要执行的正确运算 ➐。然后,我们要求用户提供进行运算的数字 ➑,每个if/elif语句块的最后一行执行选定的数学运算并打印结果 ➒。

如果用户输入5,则running变量被设置为False,并且while循环停止 ➓。

运行脚本

将你的脚本保存为calculator.py。然后要运行它,按F5或进入RunRun Module。图 0-31 展示了你应该得到的结果。

image

图 0-31: 运行calculator.py

进一步拓展

恭喜你!你已经构建了一个工作中的计算器程序。现在,看看你能做什么来改进它。举个例子,你可能注意到用户输入只能接受整数;如果计算器能够接受浮点数会更有用,看看你能否添加这个功能。你还可以尝试的另一个改进是允许用户使用超过两个数字进行计算。

第二章:LED

使 LED 闪烁

在这个第一个项目中,你将把一个 LED 连接到你的 Pi,并通过 Python 脚本让它闪烁。学习如何使用 GPIO 引脚使 LED 闪烁是你 Pi 教育中的重要一步;一旦你知道如何控制 LED,就可以控制几乎任何输出,无论是电机、灯泡,甚至是烤面包机。

image

所需材料

树莓派

面包板

5 毫米 LED**

330 Ω 电阻

跳线

介绍 GPIO 引脚

通用输入/输出(GPIO) 引脚允许你将电子硬件(如 LED 和传感器)连接到你的 Pi。它们既可以用来读取信息,也可以用来发送信息,使你的 Pi 能与现实世界进行交互。

Raspberry Pi 3 Model B 板具有一排 40 个 GPIO 引脚,如图 1-1 所示。此布局与 Pi 2 Model B 和 Pi 1 Model B+ 相同,但与 Pi 1 Model A 和 B 略有不同,后者只有前 26 个引脚。如果你使用的不是 Raspberry Pi 3 Model B,请查看“树莓派 GPIO 引脚指南”中的第 261 页。

image

图 1-1: 树莓派 GPIO 布局

有两种方式来表示一个 GPIO 引脚:其名称(通常称为 GPIO 编号或 Broadcom 编号)或它对应的引脚编号(通常称为物理编号)。例如,GPIO 25 对应引脚 22。在本书中,我们将使用 GPIO 引脚的名称进行引用。GPIO 引脚可以设置为 HIGH,输出 3.3 V 并打开组件,或者设置为 LOW,输出 0 V 并关闭组件。

功能 名称 编号 编号 名称 功能
DC 电源 3.3 V 1 2 5 V DC 电源
SDA1, I²C GPIO 2 3 4 5 V DC 电源
SCL1, I²C GPIO 3 5 6 GND
GPIO_GCLK GPIO 4 7 8 GPIO 14 TXD0
GND 9 10 GPIO 15 RXD0
GPIO_GEN0 GPIO 17 11 12 GPIO 18 GPIO_GEN1
GPIO_GEN2 GPIO 27 13 14 GND
GPIO_GEN3 GPIO 22 15 16 GPIO 23 GPIO_GEN4
功能 名称 编号 编号 名称 功能
--- --- --- --- --- ---
DC 电源 3.3 V 17 18 GPIO 24 GPIO_GEN5
SPI_MOSI GPIO 10 19 20 GND
SPI_MISO GPIO 9 21 22 GPIO 25 GPIO_GEN6
SPI_CLK GPIO 11 23 24 GPIO 8 SPI_CE0_N
GND 25 26 GPIO 7 SPI_CE1_N
I²C ID EEPROM DNC 27 28 DNC I²C ID EEPROM
GPIO 5 29 30 GND
GPIO 6 31 32 GPIO 12
GPIO 13 33 34 GND
GPIO 19 35 36 GPIO 16
GPIO 26 37 38 GPIO 20
GND 39 40 GPIO 21

警告

GPIO 引脚设计为 3.3 V 工作,因此如果你将它们连接到更高电压的设备上,可能会永久损坏你的 Raspberry Pi。

表格中灰色高亮的引脚 27 和 28 是 DNC,即“不连接”引脚。树莓派还具有八个 GND(地)引脚—表格中以黑色高亮显示—用于将地连接到电路。还有四个电源引脚,其中两个是 5V 引脚,两个是 3.3V 引脚,分别以红色和橙色高亮显示,用于提供电力。几个引脚具有特殊功能;例如,黄色高亮的引脚用于串行外设接口(SPI)通信,绿色高亮的引脚用于集成电路间(I²C)通信。你将在项目 3 和项目 7 中了解这些通信协议。

介绍 LED

LED 有各种尺寸、形状和颜色,有些甚至可以混合颜色以产生几乎任何颜色。在这个项目中,你将使用一个简单的 5 毫米红色 LED。

LED,即发光二极管,顾名思义,是一种发光的二极管。二极管是具有极性的电子元件,意味着它们只允许电流单向流动,从正极流向负极。LED 与所有二极管一样,有一个正连接称为阳极,和一个负连接称为阴极。LED 的两个引脚,或引线,长度不同,帮助你区分哪个是正极,哪个是负极,如图 1-2 所示。较长的引线是阳极(+),较短的引线是阴极(–)。

image

图 1-2: 5 毫米红色 LED

找到合适的电阻器

LED 只能承受一定的电流,超过电流限制会导致 LED 过载并烧毁,这可能会损坏 LED 甚至 Pi 板。为防止这种情况发生,你必须始终将 LED 与电阻器串联连接:一个限制通过电流的小元件。

电阻器有各种不同的阻值,你需要选择一个足够强大的电阻器来保护你的组件,同时又不会强到限制组件的性能。例如,较强的电阻器可能会使 LED 发出的光变暗。合适的电阻值取决于你使用的 LED——你在电子项目中使用的大多数 LED 可以承受最大 20 mA 的电流。对于本项目中的 LED,选择一个 220 Ω到 470 Ω之间的电阻器都可以,在这个范围内,较低的电阻值会导致 LED 稍微更亮一些。

注意

我们建议你购买一个 1/4 W 的电阻器组合,涵盖广泛的电阻值范围。这些可以满足你大多数的需求。

电阻值由电阻上的色环表示。电阻通常有四个色环,如图 1-3 所示。前两个代表值的前两个数字。第三个是乘数,表示前两个数字后面的零的个数。第四个是电阻的公差,标明实际电阻值可能偏高或偏低的百分比。例如,一个 330Ω的电阻,公差为 5%,其值可能在 313.5Ω和 346.5Ω之间。

image

图 1-3: 330Ω电阻

如果需要帮助识别电阻,请查看“解码电阻值”中的电阻色环卡,在第 264 页上。

电路接线

现在你准备好构建第一个电路了。本书中的所有项目都使用无焊接面包板,它为你提供了一种便宜且简便的方法来构建电路,而无需使用焊接连接组件。如果你以前从未使用过面包板,确保在开始之前阅读“面包板是如何工作的?”,该章节位于第 42 页。

要将 LED 连接到树莓派,请按照以下步骤操作。参考图 1-4 中的接线图,并在需要时查看图 1-1 以确认引脚位置。

  1. 将蓝色面包板轨道连接到树莓派的任意一个 GND 引脚(6、9、14、20、25、30、34 和 39 号引脚都提供 GND)。

  2. 将 LED 插入面包板。

  3. 将 330Ω电阻插入面包板,使一端连接到 LED 的阳极(较长的正极引脚)。

  4. 使用跳线将电阻的另一端连接到 GPIO 25(第 22 号引脚)。

  5. 将 LED 的阴极(较短的负极引脚)连接到 GND 电源轨。

image

清单 1-1: 将 LED 接到树莓派上。带弯曲的 LED 引脚是阳极。

我们已经告诉你将电阻放置的位置,但其实电阻连接到阳极还是阴极并不重要,只要它连接到其中一个就行。你可能会想,既然我们只需要 GND 轨道来连接那一个点,为什么不直接将 LED 的阴极连接到 6 号引脚(GND)呢?原因是,使用面包板上的 GND 轨道是一种良好的做法,尤其在更复杂的电路中,它会变得更加有用。

面包板是如何工作的?

使用面包板接线就像插拔组件一样简单。面包板上有排孔,通过下面的铜条连接在一起,所以你只需要将组件插入这些孔,然后用跳线将它们连接起来。面包板的孔是以特定方式连接的,这里通过橙色线条展示了这一点。

image

每个单独的行都是电气隔离的。水平线是电源轨,用于连接电源:蓝色轨道用于连接 GND,红色轨道用于连接电源。在面包板的中间有一个分隔,分开两边的行。您应将组件放置在这些行中。位于同一行且位于中心分隔两侧的组件将通过下面的铜条连接。

编写脚本

为了保持所有项目的组织性,在桌面环境中创建一个名为Projects的文件夹,用于保存所有项目的脚本。在新创建的文件夹内,创建一个名为LEDs的新文件夹,您将在其中保存本书中的 LED 项目。

简单来说,闪烁 LED 项目的工作原理如下:

  1. LED 亮起 1 秒钟——GPIO 25 设置为 HIGH。

  2. LED 关闭 1 秒钟——GPIO 25 设置为 LOW。

  3. LED 再次亮起 1 秒钟——GPIO 25 设置为 HIGH。

  4. LED 再次关闭 1 秒钟——GPIO 25 设置为 LOW。

这种模式会持续进行,直到您告诉程序停止。

在本书中,我们将使用一个名为 gpiozero 的 Python 库来控制树莓派的 GPIO。gpiozero 库提供了一系列接口,用于操作日常组件,如 LED、按钮、电位器、传感器等。

输入脚本

打开Python 3 (IDLE)并进入FileNew File以创建一个新脚本。将以下代码复制到 Python 编辑器中,并将脚本保存在LEDs文件夹中的blinking_led.py文件中(记得你可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

  #importing necessary libraries
➊ from gpiozero import LED
➋ from time import sleep

  #create an object called led that refers to GPIO 25
➌ led = LED(25)

  #create variable called delay that refers to delay time in seconds
➍ delay = 1

➎ while True:
      #set led to on for the delay time
    ➏ led.on()
      print('LED set to on')
    ➐ sleep(delay)
      #set led to off for the delay time
    ➑ led.off()
      print('LED set to off')
      sleep(delay)

这里有很多内容,所以我们将逐一讲解每个主要的代码部分。

导入库

在➊处,您从 gpiozero 库导入LED以控制 LED 连接的 GPIO。然后,您导入time模块中的sleep()函数➋。

声明引脚

在➌处,您创建了一个名为ledLED对象,该对象指向 GPIO 25,这是 LED 连接的 GPIO。当您创建并使用这个LED对象时,Python 会知道 GPIO 25 是一个输出,因此应设置为 HIGH 或 LOW。声明后,您可以使用led来引用 GPIO 25。

启动 while 循环

在➎处,您开始了一个条件为Truewhile循环,这意味着该循环将永远运行,直到您手动停止程序。紧随循环声明之后的代码行被缩进,告诉 Python 这是循环中的内容,只有在while条件满足时才会执行。

设置数字输出

接下来,你需要设置 LED 的数字输出。你使用 led.on() 函数 ➏ 将 GPIO 25 设置为高电平,从而点亮 LED;使用 led.off() 函数 ➑ 将 GPIO 25 设置为低电平,熄灭 LED。每次 LED 状态变化之间都有 1 秒的暂停,使用 sleep() 函数 ➐ 创建闪烁效果。代码会在当前位置暂停,并等待 delay 变量中指定的时间(以秒为单位) ➍ 后再继续执行下一行代码。这允许你保持 LED 在指定时间内开启或关闭。

运行脚本

要运行脚本,首先保存它,然后按 F5 或前往 运行运行模块。你的电路应该类似于图 1-5,LED 每秒钟开关一次。要停止正在运行的程序,请按 CTRL-C。

image

图 1-4: 完成的项目

恭喜你——你刚刚完成了第一个工作项目!

更进一步

学习电子和编程的最佳方式就是通过实验。这里有两个你可以尝试的简单修改想法:

  • 更改脚本中的延迟时间。

  • 修改电路和脚本以控制多个 LED。

看看你能否利用在这个项目中学到的知识来完成每个任务。

按钮 LED 手电筒**

这个 LED 手电筒将教你如何使用按钮,这是电子电路中的一项基本技能。你将学习如何在电路中使用按钮来触发一个事件——在这个例子中,是点亮一个 LED——当按钮按下时触发事件,当按钮释放时停止事件。

image

所需零件

树莓派

面包板

5 毫米 LED

330 Ω 电阻

跳线

介绍开关和按钮

开关无处不在。你用它们来打开灯、调节搅拌机的速度、点燃炉子,还有更多。市面上有各种各样的开关,但你在家里可能会看到的有按钮开关、翻转开关、旋转开关和磁性簧片开关。图 2-1 展示了几种常见的电子开关。

image

图 2-1: 电子设备中使用的几种开关

开关可以充当 中断器,中断电流以允许或限制电力流向某个组件,或者充当 分流器,将电流引导到电路的其他部分。这个项目将使用一个简单的按钮——一个中断器开关,它在电子设备中非常受欢迎,因为它便宜、完美适配面包板,并且易于操作。

按钮有两种主要类型:常开型和常闭型。常开型 按钮,如图 2-2 所示,默认电路是开路的,防止电流通过它。当你按下按钮时,两个触点连接,电路闭合,电流开始流动。

image

图 2-2: 常开按钮的内部连接

常闭按钮在按钮未被按下时回路是闭合的,允许电流通过,直到你按下按钮打开电路并停止电流流动。

按钮可以有两脚或四脚。四脚按钮在原型项目中更为常见,因为它们能很好地适配面包板。图 2-2 的右侧展示了一个典型的常开四脚按钮的示意图。脚 1 和脚 2 总是连接的,脚 3 和脚 4 也是如此。

在这个项目中,你将使用一个常开四脚按钮。这个按钮只在按下时允许电流通过,因此与普通的开关不同,项目中的 LED 只有在按钮被按下时才会亮起。

连接电路

在这个项目中,只有在按下按钮时,LED 才会亮起,因此你的 Raspberry Pi 需要能够判断按钮是被按下还是未被按下。你的 Raspberry Pi 将通过 GPIO 引脚获取这些信息。

按照这些步骤连接电路,使用图 2-3 作为参考:

  1. 将 GND 连接到蓝色电源轨。

  2. 将一个 LED 插入面包板,并通过一个 330 Ω的电阻将 LED 的正极连接到 GPIO 25,像在项目 1 中那样。将 LED 的负极连接到 GND 轨道。

  3. 将按钮插入面包板的中间,使得两个脚位位于中间的分隔两侧。确保顶部两个脚与底部两个脚之间没有连接。(记住,面包板中间的这一条孔排是断开的。)

  4. 将按钮的一侧连接到 GPIO 2,另一侧连接到 GND,如图 2-3 所示。

image

图 2-3: LED 手电筒电路

编写脚本

这个脚本会根据按下或释放按钮的状态触发不同的事件。以下的pseudoscript——一个以简单英语描述的代码指令版本——可以帮助你理解 Python 脚本。编写 pseudoscript 是概述程序的一种好方法。程序应该做的事情如下:

  • 当按钮被按下时,LED 设置为亮起。

  • 当按钮没有被按下(即被释放时),LED 保持关闭。

打开Python 3 (IDLE),并点击文件新建文件,创建一个新的脚本。将以下代码复制到 Python 编辑器中,然后将脚本保存为led_flashlight.pyLEDs文件夹中(记得你可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

➊ from gpiozero import LED, Button
  from signal import pause

➋ led = LED(25)
  button = Button(2)
➌ button.when_pressed = led.on
➍ button.when_released = led.off

➎ pause()

这个脚本很容易理解。你首先导入 LED 和 Button 库来控制 LED 和按钮;然后,从 signal 库导入pause ➊。pause() ➎函数让程序在所有代码执行完后仍然保持运行状态,以检测事件——在这种情况下,它会持续检查按钮的状态。

你创建了一个叫做ledLED对象,它指向 GPIO 25,这是 LED 连接的引脚;同时,创建了一个叫做buttonButton对象,它指向 GPIO 2,这是按钮连接的引脚 ➋。

button.when_pressed函数在按下按钮时调用led.on,打开 LED ➌。同样,button.when_released函数在按钮释放时调用led.off,关闭 LED ➍。

按下F5或前往运行运行模块来运行脚本。当按下按钮时,LED 应该亮起。要停止程序,按下 CTRL-C。

进一步探讨

通过在这个简单项目中学到的内容,你可以创建无数其他项目。以下是一些可以测试你技能的想法:

  • 添加更多的按钮和 LED。

  • 修改代码,使得按下按钮时改变 LED 状态,这样你就不必按住按钮来保持 LED 闪烁。

  • 构建一个红绿灯。

LED 调光开关**

在这个项目中,你将通过控制电位器来调节 LED 的亮度,从而构建一个调光器。这个项目的关键在于你能利用树莓派读取模拟输入并输出脉冲宽度调制信号。这些将在未来的项目和树莓派学习中成为非常有用的技能。

image

所需零件

树莓派

面包板

10 kΩ 电位器

MCP 3008 芯片

5 毫米 LED

330 Ω 电阻

跳线

介绍电位器

电位器就像是电源调节旋钮,广泛应用于你日常生活中的各种场景,如控制收音机音量、调整显示器亮度、设定风扇转速等等。你在这个项目中使用的电位器如图 3-1 所示。

image

图 3-1: 一个 10 kΩ 电位器

电位器,也称为电位调节器,是一种手动可调的可变电阻器。这意味着你可以通过旋转旋钮来改变它对电路施加的电阻,从而改变到达特定元件的电流量。

电位器有三个引脚,如图 3-2 所示。两个外部引脚,分别用蓝色和红色标出,连接到一个电阻元件;而第三个引脚,黑色标出,连接到一个可调的导电滑臂

image

图 3-2: 电位器的工作原理

摇臂的位置决定了电路中的电阻大小。在这个项目中,你将通过控制电路中的电阻来调节 LED 的亮度。

使用树莓派读取模拟信号

树莓派的 GPIO 只能读取数字信号,这意味着它们只能读取 HIGH(3.3 V)或 LOW(0 V)的状态,但无法读取两者之间的信号。然而,电位计是模拟输入,旋转旋钮会改变其输出电压,从 0 V 到 3.3 V。你希望树莓派能够读取这些中间值——如 1 V、1.4 V、1.8 V 等——这样就能实现亮度的渐变,而不仅仅是开与关的切换。为此,你需要通过模拟到数字转换器芯片将模拟信号转换为数字信号,然后利用脉冲宽度调制产生模拟信号的仿真效果。在开始构建之前,让我们先了解这两个主题。

模拟到数字转换器

如图 3-3 所示,模拟到数字转换器(ADC)芯片(MCP3008)将电位计的模拟信号转换为数字信号。

image

图 3-3: MCP3008 芯片模拟到数字转换器

该芯片有 16 个引脚,其中 8 个是模拟输入,你可以将其连接到模拟设备。其他 8 个引脚连接到树莓派的 GPIO。芯片引脚布局如图 3-4 所示。要识别每个引脚,请将芯片按图示方向放置,顶部有一个半圆形标记。

image

图 3-4: MCP3008 芯片

下表列出了每个引脚的功能。

引脚 符号 描述
1 CH0 模拟输入(通道 0)
2 CH1 模拟输入(通道 1)
3 CH2 模拟输入(通道 2)
4 CH3 模拟输入(通道 3)
5 CH4 模拟输入(通道 4)
6 CH5 模拟输入(通道 5)
7 CH6 模拟输入(通道 6)
8 CH7 模拟输入(通道 7)
9 DGND 数字地
10 CS/SHDN 芯片选择/关机输入
11 D[IN] 串行数据输入
12 D[OUT] 串行数据输出
13 CLK 串行时钟
14 AGND 模拟地
15 V[REF] 参考电压输入
16 V[DD] +2.7 V 到 5.5 V 电源

脉冲宽度调制

如前所述,树莓派的 GPIO 只能设置为 HIGH 或 LOW,但不能输出两者之间的电压。然而,你可以通过脉冲宽度调制(PWM)输出“伪”中间电压,这也是你在此项目中调节 LED 亮度的方式。

如果你快速交替改变 LED 的电压在 HIGH 和 LOW 之间,你的眼睛无法跟上 LED 开关的速度;你会看到亮度的渐变变化。这基本上就是 PWM 的工作原理——通过产生在 HIGH 和 LOW 之间快速变化的输出,工作频率非常高。占空比是 LED 设为 HIGH 的时间比例。图 3-5 展示了 PWM 如何工作。

image

图 3-5: PWM 工作原理

50%的占空比对应 50%的 LED 亮度,0%的占空比表示 LED 完全关闭,100%的占空比表示 LED 完全亮起。通过改变占空比,你可以产生不同亮度的效果。

电路连接

对于此电路,你需要将 LED、电位器和 MCP3008 芯片连接到树莓派。按照这些说明构建电路,并以图 3-6 为参考。

  1. 将 GND 连接到蓝色面包板轨道。

  2. 将 3.3 V 连接到红色面包板轨道。

  3. 将 LED 插入面包板,将较长的 LED 引脚通过 330 Ω电阻连接到 GPIO 17,将较短的引脚连接到 GND 轨道。

  4. 将 MCP3008 芯片放置在面包板的中央,并按照下表所示进行连接。

    MCP3008 树莓派
    1 电位器中间引脚
    9 GND
    10 GPIO 8
    11 GPIO 10
    12 GPIO 9
    13 GPIO 11
    14 GND
    15 3.3 V
    16 3.3 V

    注意

    在为电路供电之前,确保你已经按照图 3-4 中的引脚图正确连接了 MCP3008 芯片,否则可能会损坏芯片。

  5. 将电位器的一个外部引脚连接到 GND,另一个引脚连接到 3.3 V——哪个引脚连接哪个电源都无所谓。如果你还没有,将中间引脚连接到 MCP3008 芯片的引脚 1。

image

图 3-6: 使用电位器控制 LED 亮度的电路

编写脚本

Pi 通过 SPI 通信读取 MCP3008 芯片的模拟值,因此你需要首先启用 SPI。

从任务栏主菜单中选择首选项树莓派配置。在“接口”标签页中,启用 SPI,如图 3-7 所示,然后点击确定

image

图 3-7: 启用 SPI 通信

你的脚本需要能够完成以下任务:

  • 从电位器通过 MCP3008 芯片读取模拟输入值。

  • 使用 PWM 控制 LED 的亮度。

  • 根据从电位器读取的输入值更改 PWM 的占空比。

输入脚本

打开Python 3 (IDLE),然后点击文件新建文件以创建一个新的脚本。将以下代码复制到 Python 编辑器中,并将脚本保存在LEDs文件夹下,命名为brightness_controller.py(记得可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

  #import necessary libraries
  from gpiozero import PWMLED, MCP3008
  from time import sleep

  #create an object called pot that refers to MCP3008 channel 0
➊ pot = MCP3008(0)

  #create a PWMLED object called led that refers to GPIO 17
➋ led = PWMLED(17)

➌ while True:
      #pot.value accesses the current pot reading
    ➍ if(pot.value < 0.001):
          #if the pot value is very small, the led is turned off
        ➎ led.value = 0
    ➏ else:
          #change led brightness according to the pot value
          led.value = pot.value
      #print the pot value
      print(pot.value)
      #pause for 0.1 seconds
      sleep(0.1)

和往常一样,首先导入所需的库。然后创建一个名为pot ➊的对象,指向 MCP3008 的通道 0,这是电位器连接的通道。通道 0 对应 MCP3008 的引脚 1,通道 1 对应引脚 2,依此类推。

设置 PWM 引脚并读取模拟值

gpiozero 库允许你通过使用PWMLED对象来控制 LED 的亮度。因此,在➋,你创建了一个名为ledPWMLED对象,它指向 LED 连接的引脚,在这个例子中是 GPIO17。

使用 gpiozero 库读取模拟值时,你只需通过pot.value获取电位器的值。记住,pot指的是 MCP3008 的通道 0。你会获得介于01之间的小数值。

调整亮度

要使用 PWM 调整 LED 亮度,你需要改变它的占空比。要调整占空比,你只需将01之间的一个值赋给led.value,其中0表示 LED 完全熄灭,1表示 LED 完全亮起。

在这个脚本中,一个始终为Truewhile循环 ➌ 使程序保持运行。这个循环不断检查电位器的值:如果从电位器读取的值低于0.001 ➍,占空比设置为0,这将关闭 LED ➎。否则,代码进入else块 ➏,其中占空比根据从电位器读取的值变化。

运行脚本

F5或进入运行运行模块来运行脚本。现在你应该能够旋转电位器来控制 LED 的亮度。

进一步探索

学会如何读取模拟值和使用 PWM 控制输出引脚,为你打开了丰富的项目创作空间。作为入门,你可以尝试以下几个项目:

  • 用同一个电位器控制多个 LED。

  • 构建一个 LED 条形图,通过电位器控制。

  • 使用电位器控制 LED 的闪烁速度。

多彩 LED 的图形用户界面**

在这个项目中,我们将介绍多彩 RGB LED,你将学会如何在 Tkinter 中构建一个简单的图形用户界面来控制你的电子设备。

image

所需组件

树莓派

面包板

共阴 RGB LED

三个 330 Ω电阻

跳线

介绍 RGB LED

RGB LED 是三个 LED 集成在一个模块中——红色、绿色和蓝色——通过组合这三种颜色,几乎可以产生任何颜色。

使用 RGB LED,你当然可以产生红色、绿色和蓝色光,通过调节每个 LED 的强度,你还可以产生其他颜色。例如,要产生纯蓝色光,你需要将蓝色 LED 设置为最大强度,而将绿色和红色 LED 设置为最小强度。要产生白光,你需要将三个 LED 都设置为最大强度。图 4-1 展示了一个简化的 RGB 色彩混合图,帮助你理解如何结合红色、绿色和蓝色产生不同的颜色。

image

图 4-1: 简单的 RGB 色彩混合

RGB LED 有两种类型:共阴极LED,其中每个颜色的 LED 共享一个负极连接,以及共阳极LED,其中每个 LED 共享一个正极连接。图 4-2 展示了共阳极和共阴极 LED。

image

图 4-2: 共阳极和共阴极 RGB LED

RGB LED 有四个引脚——每种 LED 一个,还有一个用于共阳极或共阴极的引脚。你可以通过引脚的长度来识别每个引脚,参考图 4-3。

image

图 4-3: 共阳极和共阴极 RGB LED 引脚

当 LED 朝向你时,阳极或阴极(最长引脚)位于左侧第二个位置,引脚的顺序应该是:红色、阳极或阴极、绿色和蓝色。我们将在本项目中使用共阴极 LED,但如果你已经有共阳极 LED,也可以使用它;只需注意电路连接中的差异。

电路连接

将 RGB LED 连接到树莓派非常简单,与连接普通 LED 没有太大区别。你需要三个限流电阻——每种 LED 颜色一个。

按照以下说明操作,使用图 4-4 作为参考。

  1. 将 LED 引脚连接到面包板,并为每个颜色引脚串联一个 330 Ω限流电阻。

  2. 将 GND 连接到面包板的蓝色轨道,并根据下表将 RGB LED 连接到树莓派的 GPIO,红色连接到 GPIO 23,阴极连接到 GND,绿色连接到 GPIO 24,蓝色连接到 GPIO 25。查看图 4-3,确保你正确地定向 LED。

注意

如果你使用的是共阳极 RGB LED,请将最长的引脚(阳极)连接到 3.3V。

RGB LED 树莓派
第一根引脚:红色 GPIO 23
第二根引脚:阴极 GND
第三根引脚:绿色 GPIO 24
第四根引脚:蓝色 GPIO 25

image

图 4-4: 将共阴极 RGB LED 连接到树莓派

编写脚本

在你的脚本中,你将使用一个名为 Tkinter 的库来创建一个桌面环境中的用户界面窗口,利用它来控制 RGB LED。Tkinter 已经预装在你的 Raspbian 系统中,所以你只需要在脚本中导入它。

界面窗口将有三个滑块(值范围从01)来控制内置红色、绿色和蓝色 LED 的亮度,并且有一个关闭按钮来退出程序。通过调整滑块的值,你可以改变 RGB LED 的颜色。

现在,打开Python 3 (IDLE),然后进入文件新建文件以创建一个新的脚本。接着,将以下代码输入 Python 编辑器,并将脚本保存为rgb_led_controller.py,保存在LEDs文件夹中(记得你可以从www.nostarch.com/RaspberryPiProject/下载所有脚本):

  #import necessary libraries
➊ from gpiozero import PWMLED
  from tkinter import *

  #change the RGB LED color
➎ def change_color(self):
      red.value = red_slider.get()
      green.value = green_slider.get()
      blue.value = blue_slider.get()
      print(self)

  #close the window
➌ def close_window():
      window.destroy()

  #create a PWMLED object for each color
➍ red = PWMLED(23)
  green = PWMLED(24)
  blue = PWMLED(25)

  #create window
➎ window = Tk()
  window.title('RGB LED Controller')
  window.geometry('300x200')

  #create three sliders to control each RGB LED lead
➏ red_slider = Scale(window, from_=0, to=1, resolution = 0.01,
  orient=HORIZONTAL, label='Red', troughcolor='red', length=200,
  command=change_color)
  red_slider.pack()

  green_slider = Scale(window, from_=0, to=1, resolution = 0.01,
  orient=HORIZONTAL, label='Green', troughcolor='green', length=200,
  command=change_color)
  green_slider.pack()

  blue_slider = Scale(window, from_=0, to=1, resolution = 0.01,
  orient=HORIZONTAL, label='Blue', troughcolor='blue', length=200,
  command=change_color)
  blue_slider.pack()

  #create close button
➐ close_button = Button(window, text='Close', command=close_window)
  close_button.pack()

➑ mainloop()

用户自定义函数

要定义一个新函数,你使用关键字def,后面跟上你想给该函数起的名称以及一对括号。

在括号后添加一个冒号(:),然后告诉函数执行什么操作。Python 通过冒号和缩进来识别哪些操作是函数定义的一部分:冒号后面的每一行缩进代码都属于该函数。之后,你可以通过输入函数名称来运行你在函数内部设置的操作,这也叫做调用函数。

首先,你从 gpiozero 库导入PWMLED来通过 PWM 控制 LED,然后导入 tkinter 库来构建用户界面➊。接下来,你创建了一个控制 LED 颜色的函数。记住,函数基本上是一个可重用的代码块,用来执行某些操作。Python 有许多内置函数,如print()int()input(),你也可以像在这个脚本中那样,自己定义用户自定义函数。阅读“用户自定义函数”框了解更多内容。

控制 LED 亮度并关闭窗口

change_color()函数➋根据滑块的值变化来改变 LED 的占空比,从而改变 LED 的亮度和产生的颜色。每次滑块移动时都会调用此函数,因此你应该会看到 LED 亮度的即时变化。在➍,你创建了PWMLED对象来引用 RGB LED 的每种颜色。

close_window()函数➌使用window.destroy()关闭窗口。每当你点击 Tkinter 界面中的关闭按钮时,这个函数就会被调用。接下来我们将探讨如何创建 Tkinter 界面。

使用 Tkinter 设计用户界面

在➎,你定义了界面的主窗口。你创建了一个名为window的变量,它是一个tkinter窗口,通过window = Tk()这行代码生成。接着,你为窗口设置了标题,并通过title()geometry()方法分别设置了窗口的尺寸。

创建滑块和按钮

创建窗口后,你可以开始添加控件,称为小部件,比如按钮、标签和滑块。你使用一个Button小部件来关闭窗口,并使用三个Scale小部件来将滑块添加到窗口中。

然后,使用以下语法创建三个滑块,每个滑块对应一个 RGB 颜色➏:

*slider_name* = Scale(*master*, *option*, *option*, ...)

master 参数是你将添加小部件(widget)的窗口,每个选项参数则允许你个性化你的滑块。在rgb_led_controller.py脚本中,你使用了以下选项:

  • from_定义了范围的下限。

  • to定义了范围的上限。在我们的例子中,范围是从 0 到 1。

  • resolution设置滑块的分辨率——即两个连续值之间的最小差值。在这里,分辨率是0.01

  • orient定义了滑块的方向,可以是HORIZONTALVERTICAL。此脚本将滑块设置为HORIZONTAL

  • label设置滑块的标签。你根据每个滑块控制的 LED 颜色为每个滑块加上标签。

  • troughcolor 设置滑块的背景颜色。每个滑块的背景颜色与它所控制的 LED 颜色相同。

  • length 定义了滑块的长度(以像素为单位)。所有滑块的长度都设置为 200

  • command 指定了每次滑动滑块时将调用的过程。在这个案例中,滑动滑块会调用 change_color() 函数,它会改变 LED 的占空比,从而改变其发出的颜色。

创建滑块后,你使用 pack() 方法将小部件放置在主 window 中。由于没有向 pack() 方法传递任何参数,因此小部件会根据默认设置放置:小部件只填充它们的最小尺寸,并且它们被放置在窗口的顶部或上面的控件旁边。

接下来,你将使用以下语法创建关闭按钮 ➐:

*Button_name* = Button(*master*, *option*, *option*, ...)

在你的代码中,你使用了这些选项:

  • text 定义了按钮上将显示的文本。

  • command 指定了按下按钮时将调用的过程——在这里,是 close_window() 函数,它会关闭窗口。

使用主循环

脚本的最后一条语句调用了 mainloop() 函数 ➑,这是一个无限循环,允许窗口绘制和事件处理。mainloop() 函数是让程序持续运行的关键。

运行脚本

F5 或者去 运行运行模块 来运行脚本。你的界面窗口应该弹出,如 图 4-5 所示。

image

图 4-5: RGB LED 控制器用户界面

恭喜!你已经制作了自己的组件用户界面!现在试着移动滑块来改变 LED 的颜色。当你改变每个滑块的位置时,RGB LED 的颜色应该会相应变化。

要关闭程序,只需按下“关闭”按钮。

进一步扩展

现在你已经知道如何制作图形用户界面了,我们鼓励你编辑其参数,定制界面外观以符合你的个人喜好。以下是一些你可以尝试的其他项目创意:

  • 构建一个图形用户界面,用于控制 LED 的开关。

  • 创建一个图形用户界面,用于控制 LED 的亮度。

  • 修改用户界面以控制多个 LED。

彩虹灯带**

在这个项目中,你将使用一个可寻址的 RGB LED 灯带创建一个彩虹灯光效果。你将使用按钮来启动和停止彩虹效果,并通过两个电位器来控制彩虹的速度和亮度。

image

所需零件

树莓派

面包板

WS2812B 可寻址 RGB LED 灯带

逻辑电平转换模块 BSS 138

两个 10 kΩ 电位器

MCP 3008 芯片

按钮

三个针脚

跳线

所需软件

WS281X 库

介绍 WS2812B 可寻址 RGB LED 灯带

对于彩虹灯光效果,你将使用 WS2812B RGB LED 条,该条有多种不同的尺寸可供选择。该条呈卷轴状,如图 5-1 所示,你可以根据需要切割出合适的长度。

image

图 5-1: 卷轴上的 WS2812B 可寻址 RGB LED 条

图 5-1 中的条形灯长 5 米,包含 300 个串联的可寻址 WS2812B RGB LED,稍后你将切割出 14 个 LED 用于这个项目。条形灯沿整条长度上都有切割标记,如图 5-2 所示。

image

图 5-2: WS2812B 可寻址 RGB LED 条的引脚

每个 LED 的颜色和亮度可以单独控制,使你可以轻松制作出惊艳的效果。每个 LED 都内建了一个集成电路(IC),这意味着你可以只通过一个 GPIO 引脚来控制整个条形灯,该引脚连接到条形灯末端的中间引脚——数据引脚(见图 5-2)。

按照以下步骤为这个项目准备 LED 条:

  1. 沿着条形灯上的切割标记切割 14 个 LED。

  2. 根据图 5-2 所示,将引脚焊接到 5V、数据和 GND 引脚。

注意

我们发现,树莓派的 5V 引脚(例如引脚 2 或引脚 4)能够为 20 个或更少的 LED 提供电力,但在使用较长条形灯的项目中,你需要使用外部的 5V 电源来提供足够的电流。

现在你需要确定电源。LED 条需要一个 5V 的电源。你可以通过每个 LED 所需的功率来确定所需的电流。每个 LED 在全亮状态下(即产生白光时)最多消耗 60 mA,但由于你不太可能长时间让所有 LED 都达到最大亮度,因此可以安全地估计每个 LED 需要 20 mA 的电流。所以,如果你的条形灯有 14 个 LED,你将需要一个大约为 20 × 14 = 280 mA 的 5V 电源。

控制条形灯的数据引脚需要 5V 信号,但树莓派的 GPIO 引脚工作在 3.3V 电压下。为了获得所需的 5V 信号,你将使用一个名为逻辑电平转换器的组件。

引入逻辑电平转换器

逻辑电平转换器可以将 3.3V 信号转换为 5V 信号。逻辑电平转换器有很多种类型,但在这个项目中你将使用图 5-3 中所示的双通道逻辑电平转换器双向模块。(要找到与我们使用的相同的逻辑电平转换器模块,可以在线搜索logic level converter module bss138。)

该模块的双向性允许你双向转换数据——从 3.3 V 转换到 5 V,或从 5 V 转换到 3.3 V。在这个项目中,你不需要将 5 V 转换为 3.3 V,但在你的工具包中拥有这个更灵活的模型(相较于单向模型)将来对其他项目可能会很有帮助。这个逻辑电平转换器还有两个通道,通道 1 和通道 2。在这个项目中,你只需使用其中一个通道来控制 LED 灯带的数据引脚。

image

图 5-3: 双通道逻辑电平转换器双向模块

很可能你的模块会配有分离的引脚排针,因此你需要将引脚焊接到模块上,使其适合面包板。取下两排六个引脚的排针,并将每个引脚焊接到每个小孔上。

该模块有一个低电压端(图 5-3 左侧),你需要将所有 3.3 V 的设备连接到此端,而高电压端(右侧)用于连接 5 V 的设备。在这个项目中,你需要使用标红的引脚之一,因为你要发送 3.3 V 数据并将其转换为 5 V。

要使用逻辑电平转换器,首先将两侧的 GND 引脚连接在一起,将低电压侧的 3.3 V 和高电压侧的 5 V 连接好。然后,将 Pi 的数据连接到 TX1 引脚之一——你可以选择通道 1 或通道 2——并在相应的 TX0 引脚上获取 5 V 数据。

连接电路

此时,你应该已经将 LED 灯带剪裁成所需的尺寸(14 个 LED),并已将排针焊接到灯带的末端以及逻辑电平转换器上。现在,你可以开始连接电路了。为此,你需要将一个按钮、两个电位计通过 MCP3008 芯片连接起来,并使用逻辑电平转换器模块将可寻址 RGB LED 灯带连接在一起,如图 5-4 所示。

image

图 5-4: 控制 RGB LED 灯带的电路

警告

请记住,你不能将 5 V 连接到 Pi 的 GPIO 引脚,因为那样可能会永久损坏你的板子。

注意

要识别 MCP3008 引脚,定位芯片,使其正面朝向你,顶部有一个半圆形的缺口。第一个引脚在左上角,最后一个引脚在右上角。有关完整的 MCP3008 引脚图,请参见“模拟到数字转换器”第 55 页。

  1. 将 GND 和 3.3 V 引脚连接到面包板的轨道。

  2. 将 MCP3008 芯片插入面包板的中间,两侧跨越中心隔板。

  3. 将两个电位计插入面包板,将一个的外引脚连接到 GND,另一个的外引脚连接到 3.3 V。

  4. 按照下表连接 MCP3008 芯片。连接哪个电位计到哪个引脚并不重要,它们的工作方式相同。

    MCP3008 连接到
    1 一个电位计的中间引脚
    2 另一个电位计的中间引脚
    9 GND
    10 GPIO 8
    11 GPIO 10
    12 GPIO 9
    13 GPIO 11
    14 GND
    15 3.3 V
    16 3.3 V
  5. 将一个按键插入面包板,跨过中间分隔线。在中间分隔线的一侧,将一个引脚连接到 GND,另一个引脚连接到 GPIO 2。

  6. 将 RGB LED 条形灯的引脚插入面包板。

  7. 将逻辑电平转换器插入面包板。按照指示连接低电压端。

    逻辑电平转换器 树莓派
    TX1 (通道 2) GPIO 18
    LV 3.3 V
    GND GND
  8. 按照指示连接高电压端。

    逻辑电平转换器 连接到
    TX0 (通道 2) RGB LED 条形灯的数据引脚(中间引脚)
    HV 5 V
    GND GND
  9. 连接好逻辑电平转换器后,按指示连接 RGB LED 条形灯。

    RGB LED 条形灯 连接到
    5 V 5 V
    Din 逻辑电平转换器 TX0 引脚
    GND GND

注意

如果您选择使用超过 20 个 LED 的条形灯做这个项目,您需要将 5 V 电源连接到条形灯的 5 V 引脚,将 GND 电源连接到 GND 导轨。

编写脚本

该脚本依赖于 WS281X 库来控制各个 LED,因此您需要先安装该库,然后启用串行外设接口(SPI)通信,条形灯需要通过 SPI 与树莓派进行通信。

安装 WS281X 库

安装 WS281X 库有几个步骤,因为它需要您首先设置其依赖的库。

  1. 打开终端窗口并安装 scons、python3-dev 和 swig 库:

    pi@raspberrypi:~ $ sudo apt install scons python3-dev swig
    
  2. 仍然在终端中,导航到桌面,创建一个名为 Libraries 的文件夹,然后进入新创建的文件夹:

    pi@raspberrypi:~ $ cd ~/Desktop
    pi@raspberrypi:~/Desktop $ mkdir Libraries
    pi@raspberrypi:~/Desktop $ cd Libraries
    pi@raspberrypi:~/Desktop/Libraries $
    
  3. 克隆该库以进行下载。

    pi@raspberrypi:~/Desktop/Libraries $ git clone https://
    github.com/jgarff/rpi_ws281x.git
    
  4. 转到 rpi_ws281x 库文件夹并运行 scons 命令:

    pi@raspberrypi:~/Desktop/Libraries $ cd rpi_ws281x
    pi@raspberrypi:~/Desktop/Libraries/rpi_ws281x $ sudo scons
    
  5. 转到 python 文件夹并在树莓派上安装 WS281X 库:

    pi@raspberrypi:~/Desktop/Libraries/rpi_ws281x $ cd python
    pi@raspberrypi:~/Desktop/Libraries/rpi_ws281x/python $ sudo
    python3 setup.py install
    

现在您已经准备好在代码中使用 WS281X 库。

启用 SPI 通信

要与 MCP3008 芯片通信,您需要启用 SPI 通信。打开任务栏主菜单,选择 首选项树莓派配置。在“接口”选项卡中,点击 SPI 行中的 启用,如 图 5-5 所示,然后点击 确定

image

图 5-5: 启用 SPI 通信

输入脚本

让我们回顾一下电路如何工作,以便在输入脚本之前帮助您更好地理解:

  • 您的 RGB LED 条形灯将显示一个移动的彩虹。

  • 一个电位器控制彩虹动画的速度。

  • 另一个电位器控制彩虹的亮度。

  • 按键用于启动和停止彩虹动画。

故障排除:像素乱跳

在写本文时,较新版本的 Raspbian 存在条形灯像素的问题。用于控制条形灯的引脚与模拟音频输出共享,因此像素可能会出现问题并无法正常工作。如果在加载代码时发生这种情况,您需要在 config.txt 文件中添加两行。进入终端并输入以下内容:

pi@raspberrypi:~ $ sudo nano /boot/config.txt

在打开的文件中,添加以下两行(可以放在任何位置):

hdmi_force_hotplug = 1
hdmi_force_edid_audio = 1

按下 CTRL-X 保存文件,然后在提示时输入Y并按回车键。重新启动你的 Pi,使更改生效,然后继续进行库的安装。

打开Python 3 (IDLE),然后进入文件新建文件来创建一个新的脚本。将列表 5-1 中的代码复制到 Python 编辑器中,并将脚本保存在LEDs文件夹内,命名为rainbow_effect.py(记得你可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

列表 5-1: 彩虹灯带rainbow_effect.py代码

  #based on Tony DiCola's NeoPixel library strandtest example

  #import necessary libraries
➊ from neopixel import *
  from time import sleep
  from gpiozero import Button, MCP3008

  #LED strip configuration
➋ LED_COUNT = 14 #number of LED pixels
  LED_PIN = 18 #GPIO pin connected to the pixels (must support PWM!)
  LED_FREQ_HZ = 800000 #LED signal frequency in Hz (usually 800 kHz)
  LED_DMA = 5 #DMA channel to use for generating signal (try 5)
  LED_INVERT = False #set True to invert the signal

  #create pot objects to refer to MCP3008 channel 0 and 1
➌ pot_brightness = MCP3008(0)
  pot_speed = MCP3008(1)

  #connect pushbutton to GPIO 2, pull-up
  button_start = Button(2)

  #animation running control variable
  running_animation = False

  #generate rainbow colors across 0-255 positions
➍ def wheel(pos):
      if pos < 85:
          return Color(pos * 3, 255 - pos * 3, 0)
      elif pos < 170:
          pos -= 85
          return Color(255 - pos * 3, 0, pos * 3)
      else:
          pos -= 170
          return Color(0, pos * 3, 255 - pos * 3)

  #draw rainbow that uniformly distributes itself across all pixels
➎ def rainbowCycle(strip):
      for j in range(256):
          for i in range(strip.numPixels()):
              strip.setPixelColor(i, wheel((int(i * 256 /
  strip.numPixels()) + j) & 255))
          strip.show()
➏         sleep((pot_speed.value*40)/1000.0)

  #function to start and stop the animation
➐ def start_animation():
      global running_animation
      if running_animation == True:
          running_animation = False
      else:
          running_animation = True

  #assign a function that runs when the button is pressed
➑ button_start.when_pressed = start_animation

  #create NeoPixel object with appropriate configuration
➒ strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA,
  LED_INVERT, int(pot_brightness.value*255))

  #initialize the strip
  strip.begin()
➓ while True:
      if running_animation == True:
          #set LED strip brightness
          strip.setBrightness(int(pot_brightness.value*255))
          rainbowCycle(strip)

首先,你导入你将用于控制项目的库 ➊。你需要 neopixel 库来控制 LED 灯带,time 库来导入sleep()函数以控制延迟时间,另外从 gpiozero 库导入Button()MCP3008()接口,用于分别读取按钮和电位器的值。

设置灯带参数

在➋处,你创建了配置 RGB LED 灯带的变量,包括 LED 的数量和所使用的 GPIO 引脚。然后,在➌处,你创建了用于表示两个电位器的对象,一个是 MCP3008 通道 0(引脚 1)上的亮度,另一个是 MCP3008 通道 1(引脚 2)上的速度,还有一个对象用于 GPIO 2 上的按钮。你还创建了一个名为running_animation的变量,用于启动和停止动画,它是一个布尔值,默认值为False(关闭)。

创建彩虹效果函数

在➋和➎处,你创建了产生移动彩虹效果的函数。这些函数与 neopixel 库自带的strandtest.py示例中使用的函数相同。简单来说,wheel()函数通过将每个颜色参数在 0 到 255 之间变化来生成颜色光谱。每种颜色由红色、绿色和蓝色(RGB)参数组成,通过在 0 到 255 之间变化这些参数会产生不同的颜色,进而实现彩虹效果。rainbowCycle()函数则在你的 LED 灯带的数量上分布彩虹效果。

➏处的这一行设置了sleep()函数的延迟时间。为了计算延迟时间,你将从一个电位器读取的值(范围在 0 到 1 之间)乘以 40,然后将结果除以 1,000。将电位器的值乘以 40 可以产生明显的延迟时间,否则延迟时间会太短,导致彩虹效果太快,你来不及看到灯光的变化。除以 1,000 会将延迟时间转换为毫秒。

控制按钮

使用 gpiozero 库,你可以将特定的动作分配给按钮按下事件,方法如下:

button.when_pressed = *function_name*

function_name函数是指一个通用函数,当按钮被按下时会被调用;该函数必须在被调用之前定义。在这个案例中,那个函数是start_animation ➑,在➐处定义。注意function_name后面没有括号。这是因为我们只是将一个函数赋值给另一个函数,而不是直接运行它。在我们的例子中,我们告诉代码当触发button_start.when_pressed函数时运行start_animation函数。

当按钮被按下时,running_animation值会变化。当running_animation变量为False且按钮被按下时,它会变为True,反之亦然。这样你就可以启动和停止彩虹效果。

使用 while 循环控制动画

在➒处,你创建了一个名为stripAdafruit_Neopixel对象,它接收你之前在➋处定义的条带参数。为了控制条带 LED 的亮度,你使用int(pot_brightness.value*255)。亮度根据从一个电位器读取的值(在 0 和 1 之间)来变化。你将这个值乘以 255,因为条带 LED 的亮度范围是 0 到 255。使用int()函数将数字四舍五入为整数。这样,你就可以通过旋转电位器来调整 LED 亮度。

然后,你使用strip.begin(),这是在对Adafruit_Neopixel对象进行其他调用之前必须先调用的。

while循环 ➓ 使程序持续运行。接着,在开始动画之前,你设置条带的亮度。如果running_animation变量等于TruerainbowCycle()函数将运行,启动动画。如果你按下按钮,running_animation变量会变为False,动画停止。

运行脚本

要运行这个脚本,你需要使用终端窗口。从 Python 3 IDLE 编辑器运行会导致权限错误。

将脚本保存为rainbow_effect.py,放在Projects目录中的LEDs文件夹内,然后打开终端。接着,导航到LEDs文件夹并运行脚本:

pi@raspberrypi:~ $ cd ~/Desktop/Projects/LEDs
pi@raspberrypi:~/Desktop/Projects/LEDs $ sudo python3
rainbow_effect.py

现在,你可以通过旋转各自的电位器来控制速度和亮度,按下按钮则可以停止和启动动画。

恭喜!你为你的家增添了一个超棒的装饰!

进一步探索

这里有一些简单的想法,你可以尝试一下,如果你想增加对条带的控制能力:

  • 在条带的中间点亮一个特定的 LED。

  • 将所有 LED 点亮为一个单一的颜色。

  • 添加一个按钮来切换预设效果。

  • 像圣诞灯一样闪烁 LED。

  • 发明你自己的效果。

第三章:显示屏

LCD 提醒

在这个项目中,你将把一个字符型 LCD 连接到树莓派上,以显示滚动的提醒信息。你将首先显示短消息的静态文本,然后学习如何显示适合长消息的滚动文本。

image

所需部件

树莓派

面包板

16×2 Hitachi HD44780 兼容 LCD

10 kΩ 可调电阻

跳线线

所需软件

Adafruit_CharLCD 库

液晶显示屏介绍

最简单且最便宜的显示屏是 液晶显示器(LCD)。LCD 广泛应用于日常电子设备中,如自动售货机、计算器(见 图 6-1)、停车计时器和打印机,非常适合显示文本或小图标。

image

图 6-1: 带 LCD 的计算器

LCD 的大小是根据屏幕上能够显示的字符的行数和列数来测量的。一个 16×2 的 LCD 可以显示 2 行每行 16 个字符。你会找到从 8×1 到 40×4 不等的不同尺寸。

LCD 还可以在背景颜色上有所不同。你可以找到各种各样的背景颜色,包括 RGB 背景灯,允许你创建任何颜色。

最常见的 LCD 模块使用的是 Hitachi HD44780 芯片,它允许你使用自定义字符。图 6-2 展示了一个标准的 16×2 Hitachi HD44780 兼容 LCD;我们建议在本项目中使用这种类型。大多数 16×2 屏幕是兼容的,但在购买之前,最好检查一下该部件的数据手册以确保兼容性。你也可以使用 20×4 的 LCD,只要它们是 Hitachi HD44780 兼容的。

image

图 6-2: 标准 16×2 Hitachi HD44780 兼容 LCD

如果你仔细查看 图 6-2,你会看到由 5×8 像素组成的 32 个矩形。开关像素的组合构成了字符形状。

焊接插针

更有可能的是,你的 LCD 模块将带有单独的插针,如 图 6-3 所示。你需要将插针焊接到模块上,以使其适用于面包板。将插针放入可用的孔中——每个 16 个孔中应有 16 个插针——长端朝下并将其焊接到位。

image

图 6-3: 带有单独插针的 LCD

LCD 模块引脚图

LCD 模块的引脚编号从 1 到 16,当引脚位于屏幕上方时,从左到右排列:

PIN 符号 描述
1 VSS
2 VDD 电源
3 V0 对比度调整
4 RS 寄存器选择
5 R/W 读/写选择
6 E 启用
7–14 DB0–DB7 数据引脚
15 LEDA 背光阳极(5V)
16 LEDK 背光阴极(–)

警告

树莓派的 GPIO 设计为 3.3V,但大多数 LCD 为 5V。只要您仅从树莓派发送数据而不是从屏幕读取数据到树莓派,这不是问题。使用 5V 屏幕时,请勿将显示器的 R/W 引脚连接到树莓派。* 该引脚以 5V 发送数据,很可能会烧坏您的树莓派!*

VSS 是接地引脚,应连接到 GND。VDD 是电源引脚,应该根据 LCD 的类型连接到 5V 或 3.3V;大多数需要 5V。

V0 允许您在 LCD 连接到电位器时调整字符与背光之间的对比度。

RS、R/W 和 E 是控制引脚。仅使用屏幕显示文本时(如本项目所示),应将 R/W 引脚永久连接到地面;换句话说,您将只向 LCD 写入数据,而不从中读取数据。

引脚 7 到 14 是数据引脚,用于发送信息。引脚 15 和 16 是背光的阳极和阴极。

尽管 LCD 模块有 16 个引脚,但您只需要其中的 6 个来与树莓派通信:4、6、11、12、13 和 14。

接线您的电路

现在您的 LCD 已准备好使用,可以开始构建此项目的电路。按照以下步骤连接 LCD 和树莓派,并参考图 6-4 的电路图进行接线。

  1. 将面包板的电源轨连接到 5V 和 GND。

  2. 要为 LCD 供电,将 5V 从电源轨连接到 LCD 引脚 2(VDD)和 LCD 引脚 15(LEDA)。将 LCD 引脚 1(VSS)、引脚 5(R/W)和引脚 16(LEDK)连接到面包板的 GND 轨。

  3. 添加一个电位器来调整对比度:将一个外部引脚连接到 GND,另一个外部引脚连接到 5V,然后将中间引脚连接到 LCD 引脚 3(V0)。

  4. 按照下表连接树莓派的 GPIO 引脚。

LCD 树莓派
4 (RS) GPIO 27
6 (E) GPIO 22
11 (DB4) GPIO 25
12 (DB5) GPIO 24
13 (DB6) GPIO 23
14 (DB7) GPIO 18

image

图 6-4: LCD 与树莓派的接线

完成接线后,尝试旋转电位器调整 LCD 的对比度;您应该能看到背光变化。如果对比度没有变化,请在继续之前仔细检查接线。

编写脚本

在编写脚本以在 LCD 上显示消息之前,您需要安装字符 LCD 的 Python 库 Adafruit_CharLCD。该库具有许多功能,便于控制 LCD。

为字符 LCD 安装 Python 库

打开树莓派桌面任务栏中的终端。在安装 Adafruit_CharLCD 库之前,您需要安装以下依赖项:

pi@raspberrypi:~ $ sudo apt update
pi@raspberrypi:~ $ sudo apt install build-essential python3
python-dev python-smbus python3-pip git-core

当提示时,输入 y 并按下 ENTER。

导航到桌面,创建一个名为Libraries的文件夹,然后使用以下命令更改目录到新创建的文件夹:

pi@raspberrypi:~ $ cd ~/Desktop
pi@raspberrypi:~/Desktop $ mkdir Libraries
pi@raspberrypi:~/Desktop $ cd Libraries
pi@raspberrypi:~/Desktop/Libraries $

通过输入以下命令下载安装文件:

pi@raspberrypi:~/Desktop/Libraries $ git clone https://github.com/
adafruit/Adafruit_Python_CharLCD.git

导航到 Adafruit_Python_CharLCD 目录:

pi@raspberrypi:~/Desktop/Libraries $ cd Adafruit_Python_CharLCD

最后,执行以下命令来安装 Adafruit_CharLCD 库:

pi@raspberrypi:~/Desktop/Libraries/Adafruit_Python_CharLCD $ sudo
python3 setup.py install

恭喜你!你成功安装了字符 LCD 的 Python 库。我们鼓励你浏览 Examples 文件夹并看看。

显示字符消息

转到你的 Projects 文件夹,并创建一个名为 Displays 的新文件夹。打开 Python 3 (IDLE),并转到 文件新建文件 创建一个新脚本。然后,将以下代码输入到 Python 编辑器中,并将脚本保存为 character_lcd.py(记得你可以在 www.nostarch.com/RaspberryPiProject/ 下载所有脚本):

➊ import Adafruit_CharLCD as LCD

  #Raspberry Pi pin configuration
➋ lcd_rs = 27
  lcd_en = 22
  lcd_d4 = 25
  lcd_d5 = 24
  lcd_d6 = 23
  lcd_d7 = 18
  lcd_backlight = 4

  #define the LCD size
➌ lcd_columns = 16
  lcd_rows = 2

  #initialize the LCD
➍ lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, 
  lcd_d7, lcd_columns, lcd_rows, lcd_backlight)

  #write your message
➎ lcd.message('It works\nYou rock!')

你首先在➊导入之前安装的字符 LCD 库。然后,在➋配置你的 Pi 引脚。在➌定义你的 LCD 大小。如果你使用的是 20×4 的 LCD,需要相应地更改这两行代码。

之后,LCD 在 ➍ 初始化,你在函数 lcd.message('string') 中写下你的消息,消息放在单引号之间 ➎。\n 转义字符告诉 LCD 在下一行显示随后的文本。随时可以更改这个消息为你喜欢的任何内容!

F5 或转到 运行运行模块 来运行脚本,你的 LCD 应显示文本,如图 6-5 所示。

image

图 6-5: 显示静态消息的电路

添加其他功能

了解一些其他有用的函数是值得的,你可以用它们做的不仅仅是显示文本;例如,你可能想将光标设置到某个位置,或者清除显示以准备新消息。你安装的库为你提供了以下功能:

  • lcd.message(string) 显示括号中写的消息。

  • lcd.clear() 清除显示。

  • lcd.show_cursor(boolean) 在消息后显示光标。

  • lcd.blink(boolean) 显示一个闪烁的光标。

  • lcd.move_right() 将显示的消息向右移动一个字符。

  • lcd.move_left() 将显示的消息向左移动一个字符。

  • lcd.home() 将光标设置到第一列和第一行(0,0)。

  • lcd.set_cursor(int, int) 将光标设置到指定的列和行。例如,lcd.set_cursor(2, 1) 将光标设置到第三列和第二行。

斜体中的数据类型告诉你需要输入什么类型的值作为参数;例如,布尔值可以输入 TrueFalse

滚动提醒消息

这个 LCD 屏幕相当小,所以如果你尝试显示超过 32 个字符的消息,显示会卡住。因此,现在我们将向你展示如何编写一个脚本,显示一个较长的滚动消息,就像一个你不能错过的医生预约提醒。在第一行,你将有一个消息的标题,比如“提醒”或者“别忘了”,第二行则是你的提醒消息会滚动显示。

显示滚动消息并不像显示静态消息那样简单,所以在编写代码之前,让我们思考一下我们想要发生什么:

  • 第一行显示静态标题。

  • 第二行显示滚动消息。

  • 滚动消息从右向左移动。

  • 字符应从最右侧的列出现。

  • 字符应在最左侧的列消失。

消息会永远滚动,直到被停止。在 Displays 文件夹内,创建一个新的 Python 3 (IDLE) 脚本,命名为 scrolling_text.py,并输入以下代码:

  import Adafruit_CharLCD as LCD
➊ import time

  #Raspberry Pi pin configuration
  lcd_rs = 27
  lcd_en = 22
  lcd_d4 = 25
  lcd_d5 = 24
  lcd_d6 = 23
  lcd_d7 = 18
  lcd_backlight = 4

  #define the LCD size
  lcd_columns = 16
  lcd_rows = 2

  #initialize the LCD
  lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6,
  lcd_d7, lcd_columns, lcd_rows, lcd_backlight)

  #write your message
➋ title = "Don't forget!"
➌ reminder = "You have a doctor appointment next Monday"

  #set the delay for scroll
➍ delay = 0.3

  #write a function to scroll the message
➎ def scroll_message(reminder, delay):
      padding = ' ' * lcd_columns
      reminder_message = padding + reminder + ' '
    ➏ for i in range(len(reminder_message)):
          lcd.set_cursor(0, 1)
          lcd.message(reminder_message[i:(i+lcd_columns)])
          time.sleep(delay)

➐ lcd.clear()
  lcd.home()
  lcd.message(title)

  #scroll the message in an infinite loop
➑ while True:
      scroll_message(reminder, delay)

你已经熟悉导入 Adafruit_CharLCD 库、配置 Raspberry Pi 引脚、定义屏幕大小和初始化 LCD。

对于这个示例,你还需要导入时间库 ➊ 来使用与时间相关的函数。在 ➋ 和 ➌ 处,你分别为将要显示的标题和提醒消息分配文本。delay ➍ 是角色在移动到左侧一个字符之前停留在一个位置的时间。在这个例子中,你将延迟设置为 0.3 秒;延迟越小,文本滚动越快。

在 ➎ 处,你创建一个名为 scroll_message(``*string*, *float*) 的函数,该函数接受两个参数:一个字符串作为你的 reminder,和一个浮动数值作为 delay。在函数内部,你首先创建一个 padding 变量,它由若干个空白字符组成,空白字符的数量与 LCD 的列数相等。这将最初填满所有字符槽,创造字符逐渐出现的错觉。接着,你创建一个名为 reminder_message 的新变量,它是 paddingreminder 和一个空格的连接。你需要添加这个额外的空格来创造 reminder_message 消失的效果。

该函数通过一个 for ➏ 循环从 0reminder_message 的长度进行循环。函数 len(object) 返回一个对象的长度——在这个例子中,就是 reminder_message 字符串中的字符数量,这告诉我们循环的次数。

注意

Python 使用 从零开始的索引,这意味着索引从零开始计数。例如,字符串的第一个字符的索引是 0

在循环内部,代码首先设置光标到第一列、第二行的位置,你希望开始显示提醒的地方。在下一行,reminder_message[i:(i+lcd_columns)] 截取你的 reminder_message,返回从索引 i 到索引 i+lcd_columns 之间的字符(不包括 i+lcd_columns)。

每次循环运行时,你将显示消息的不同部分;这就是实际产生滚动效果的原因(见 图 6-6)。

image

图 6-6: 截断字符串以产生滚动效果

显示完整消息后,代码会等待 delay 变量中分配的秒数。

在 ➐ 处,你清除屏幕,并从第一列、第一行显示 title 消息。

最后,在 ➑ 处,你创建了一个始终为 Truewhile 循环。这是一个小技巧,让某个程序永远运行下去。在这个循环内部,你调用函数 scroll_message(``*string*, *float*),并传入你自己的参数:reminderdelay

运行脚本

F5键或进入运行运行模块来运行脚本。不幸的是,我们无法展示书中文字的滚动效果,但你大致能理解!

进一步深入

我们建议你根据你在此处以及之前学到的编码技巧,修改我们提供的示例脚本,并尝试使用 “添加其他功能” 中展示的函数,这些内容出现在 第 91 页,以熟悉 LCD。当你完成后,下面是一些可以尝试的项目:

  • 使用 LCD 显示屏构建天气预报器——查看 项目 7,我们将展示如何获取天气数据。

  • 根据天气情况显示消息,例如“别忘了带伞。”

  • 在 LCD 上显示传感器数据——查看 项目 9–12,学习如何读取传感器数据。

Mini Weather Forecaster**

在这个项目中,你将构建一个天气预报器,显示你所在地当天的天气信息。你将学习如何发出 API 请求,这对于依赖频繁更新数据的项目非常有用,并且学习如何使用 OLED 显示屏。

image

所需部件

树莓派

面包板

0.96 英寸 OLED 显示屏

跳线

所需软件

Adafruit_SSD1306 库

介绍 OLED 显示屏

本项目使用的 有机发光二极管(OLED) 显示屏是 SSD1306 型号:一款单色、0.96 英寸、128×64 像素的显示屏,如 图 7-1 所示。与 LCD 显示屏相比,LCD 每个字符预留了 5×8 像素的空间,而 OLED 显示屏更为灵活。它允许您选择哪些像素是开(点亮)或关(熄灭)的,能够在显示屏的任意位置生成自定义文本和图像。OLED 显示屏也不需要背光,因此在暗环境中对比度很好。此外,OLED 的像素仅在点亮时消耗电能,因此它比 LCD 显示屏消耗更少的电量。

image

图 7-1: SSD1306 0.96 英寸单色 OLED 显示屏

注意

一些 OLED 显示屏使用 SPI 通信而不是 I²C—这些显示屏会有一组不同的引脚。在购买 OLED 显示屏之前,请务必检查引脚布局。

该 OLED 显示屏通常有四个引脚,GND、VCC、SCL 和 SDA(见 图 7-1),但您可能会发现一些型号额外配有复位引脚。有些显示屏的引脚顺序也可能不同—VCC、GND、SCL、SDA—因此在按照本项目说明操作时,请注意查看标签。

图 7-1 中的 OLED 显示屏使用内部集成电路(I²C)通信协议与树莓派进行通信,您需要使用 SDA 和 SCL 引脚(分别为 GPIO 2 和 GPIO 3)。

使用 OpenWeatherMap API

应用程序编程接口(API)是一组由软件开发人员编写的函数,用于使任何人都能使用他们的数据或服务。例如,OpenWeatherMap 项目 (openweathermap.org/ ) 提供了一个 API,允许用户使用多种编程语言请求天气数据。在本项目中,您将使用该 API 请求所选位置的天气预报。学习如何使用 API 与树莓派配合工作是一项很棒的技能,因为它可以让您访问各种不断变化的信息,如当前股价、货币兑换率、最新新闻、交通更新、推文等。

OpenWeatherMap 的免费计划提供了完成此项目所需的一切。要使用 API,您需要一个 API 密钥,称为 APIID。获取 APIID 的方法:

  1. 打开浏览器,访问 *openweathermap.org/appid/

  2. 按下 注册 按钮并创建一个免费账户。

  3. 您将看到一个包含多个标签的仪表板。选择 API 密钥 标签,如 图 7-2 所示。

    image

    图 7-2: OpenWeatherMap 的 API 密钥

  4. 在 API 密钥标签页中,你将看到一个默认的密钥(如图 7-2 所示);这是你需要从网站提取信息的唯一密钥。复制并粘贴此密钥到某个地方,稍后你会用到它。如果你喜欢,可以为每个单独的项目创建一个新密钥,但如果你不熟悉使用 API,我们建议你直接使用默认密钥。

  5. 要获取你选择位置的天气信息,请输入以下 URL:

    http://api.openweathermap.org/data/2.5/weather?q=*your_city*,
    *your_country_code*&APPID=*your_unique_API_key*
    

    警告

    API 对用户是唯一的,不应与他人共享。在这种情况下,拥有你 API 密钥的人只能请求天气信息,但如果你在使用社交媒体 API 时,可能会遇到安全问题——例如,陌生人获取你的个人信息。不要与任何人分享你的 API 密钥。

    将 your_city 替换为你想要获取数据的城市,将 your_country_code 替换为该城市的国家代码,将 your_unique_API_key 替换为步骤 4 中获得的唯一 API 密钥。例如,葡萄牙波尔图市的更新 API URL 将是:

    http://api.openweathermap.org/data/2.5/weather?q=Porto,
    PT&APPID=801d2603e9f2e1c70e042e4f5f6e0---
    
  6. 将你的 URL 复制到浏览器中,API 将返回与你当地天气相关的一大堆信息。列表 7-1 显示的是我们写这个项目时波尔图(葡萄牙)的天气。

    列表 7-1: API 响应

    {"coord":{"lon":8.61,"lat":41.15},"weather":[{"id":802,
    "main":"Clouds","description":"scattered clouds","icon":"03d"}],
    "base":"stations","main":{"temp":280.704,"pressure":1010.06,
    "humidity":96,"temp_min":280.704,"temp_max":280.704,
    "sea_level":1041.03,"grnd_level":1010.06},"wind":{"speed":1.01,
    "deg":74.0017},"clouds":{"all":36},"dt":1487153693,
    "sys":{"message":0.0042,"country":"PT","sunrise":1487143701,
    "sunset":1487182157},"id":2735943,"name":"Porto","cod":200}
    

注意

有关如何使用 API 获取天气信息的更多信息,请访问 openweathermap.org/current

现在可能看起来不太重要,但接下来你将看到如何使用标签和段落组织这些数据,使其更易于阅读。

理解 JSON 语法

如你所见,选择位置的天气数据以特定的方式存储,使用了像 {}[] : "", 等符号。这种语法是 JavaScript 对象表示法(JSON),一种用于数据交换的标准,方便计算机处理。在 JSON 语法中:

  • 数据以名称/值对的形式表示。

  • 每个名称后面都跟一个冒号(:)。

  • 名称/值对之间用逗号分隔。

  • 花括号用于表示对象。

  • 方括号用于表示数组。

列表 7-2 显示了如何组织 API 信息,使其更易理解。

列表 7-2: 重排后的 API JSON 信息,结构更加清晰

{
   "coord":{
      "lon":-8.61,
      "lat":41.15
   },
   "weather":[{
         "id":803,
         "main":"Clouds",
         "description":"broken clouds",
         "icon":"04d"
      }
   ],
   "base":"stations",
   "main":{
      "temp":288.15,
      "pressure":1020,
      "humidity":93,
      "temp_min":288.15,
      "temp_max":288.15
   },
   "visibility":10000,
   "wind":{
      "speed":3.6,
      "deg":230
   },
   "clouds":{
      "all":75
   },
   "dt":1488726000,
   "sys":{
      "type":1,
      "id":5959,
      "message":0.002,
      "country":"PT",
      "sunrise":1488697277,
      "sunset":1488738646
   },
   "id":2735943,
   "name":"Porto",
   "cod":200
}

现在,你可以更轻松地看到 API 提供的各种信息。

发起 API 请求

现在你有了一个返回你当地天气数据的 URL。为了展示如何使用 Python 访问这些信息,我们将给出一个示例。

列表 7-3 中的简单代码片段请求波尔图(葡萄牙)的当前最大温度(单位:开尔文),并在 Python shell 中打印出来。用你自己的 URL 替换我们的 URL,你将获得所选位置的相同信息。

列表 7-3: 请求最大温度

➊ import requests
➋ weather_data = requests.get('http://api.openweathermap.org/data/2.5/
  weather?q=Porto,PT&APPID=801d2603e9f2e1c70e042e4f5f6e0---')
➌ temp_max = weather_data.json().get('main').get('temp_max')
  print(temp_max)

在 ➊ 处,你导入了 requests 库,这是发起 API 请求所必需的。

在 ➋ 步,你创建了一个名为 weather_data 的变量,用来存储 API 请求返回的数据。要发起 API 请求获取信息,你使用命令 requests.get('your_url'),其中 your_url 是你 URL 的参数,并且需要放在单引号中。

在 ➌ 步,你创建了 temp_max 变量来存储你所请求的特定数据。在这个例子中,你需要的是最大温度。

为了获取该值,你首先使用 .json() 方法将 weather_data 变量转换为 JSON 格式。然后,使用 .get() 方法,你可以访问包含最大温度值的 temp_max 变量。你可以在 清单 7-2 中看到,main 是你需要访问的 temp_max 数据的顶级父元素,所以你需要先通过 main 来获取。

同样地,要访问风速信息,你需要输入:

weather_data.json().get('wind').get('speed')

你需要通过 wind(风速数据的父元素)来请求关于风速的信息。

如果你只想获取城市名称,输入:

weather_data.json().get('name')

学会了如何在 Python 中发起 API 请求之后,你已经准备好开始这个项目了!

requests 库

requests 库,也叫做“HTTP for Humans”,是一个 Apache2 许可的 Python 库,用于发送 超文本传输协议(HTTP) 请求。这个强大的库使得通过 HTTP 连接到 Web 服务器变得简单。这一功能让你能够轻松地从任何网页请求信息,就像你在这里所做的那样。

电路连接

只需按照表中显示的引脚图将 OLED 显示器连接到树莓派。记住,不同型号的引脚顺序可能不同,因此请根据引脚标签来连接。

OLED 显示器 树莓派
VCC 3.3 V
GND GND
SDA GPIO 2 (SDA)
SCL GPIO 3 (SCL)
RST(如果存在) GPIO 24

对照 图 7-3 检查你的电路,然后继续进行软件部分。

image

图 7-3: 将 OLED 显示器连接到树莓派

编写脚本

在输入脚本之前,你需要安装 Adafruit_SSD1306 库来使用 OLED 显示器和树莓派。这个库使得在显示器上写文本和绘制图像变得简单。你还需要启用 I²C 通信,以便 OLED 和树莓派之间能够通信。

安装 OLED 显示器的库

如果你还没有做过,先在桌面上创建一个名为 Libraries 的文件夹。然后,打开终端并导航到你树莓派上的 Libraries 文件夹:

pi@raspberrypi:~ $ cd Desktop/Libraries

克隆 OLED 库:

pi@raspberrypi:~/Desktop/Libraries $ git clone https://github.com/
adafruit/Adafruit_Python_SSD1306.git

安装 Adafruit_Python_SSD1306 库:

pi@raspberrypi:~/Desktop/Libraries $ cd adafruit/
Adafruit_Python_SSD1306
pi@raspberrypi:~/Desktop/Libraries/adafruit/Adafruit_Python_SSD1306
$ sudo python3 setup.py install

启用 I²C 通信

OLED 使用 I²C 通信协议与树莓派进行通信,因此你需要在树莓派上启用 I²C 通信。前往桌面主菜单,选择 PreferencesRaspberry Pi Configuration。在 Interfaces 选项卡中,启用 I²C,如 图 7-4 所示,然后按 OK

image

图 7-4: 启用 I²C 通信

I²C 通信协议

I²C,即 集成电路互联,是一种通信协议,允许多个 从属 集成电路与一个主芯片之间进行通信。从属设备是响应主设备的设备。主芯片可以与所有从设备通信,但从设备只能与主设备通信。主从设备都可以传输数据,但传输始终由主设备控制。在这个案例中,树莓派是主芯片,OLED 集成电路是从设备。树莓派通过其 GPIO 引脚的 SDA 和 SCL 引脚支持 I²C 通信。使用这种通信协议的最大优势是,你只需要通过 SDA 和 SCL 引脚就能连接多个设备——无需使用额外的引脚。

输入脚本

打开 Python 3 (IDLE) 并转到 文件新建文件 来创建一个新脚本。将 清单 7-4 中显示的代码复制到 Python 编辑器中,并将脚本保存在 Displays 文件夹中,命名为 weather_forecast.py(记住,你可以在 www.nostarch.com/RaspberryPiProject/ 下载所有脚本):

清单 7-4: weather_forecast.py 脚本

➊ import time
  import Adafruit_SSD1306
  import requests

  from PIL import Image
  from PIL import ImageDraw
  from PIL import ImageFont

  #Raspberry Pi pin configuration
➋ RST = 24

➌ #128x32 display with hardware I2C
  #disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST)

  #128x64 display with hardware I2C
  disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

  #set your unique OpenWeatherMap.org URL
➍ open_weather_map_url = 'http://api.openweathermap.org/data/2.5/
  weather?q=Porto,PT&APPID=801d2603e9f2e1c70e042e4f5f6e0---'

  #initialize display
➎ disp.begin()

  while True:
      #clear display
      disp.clear()
      disp.display()

      #create blank image for drawing
      #make sure to create image with mode '1' for 1-bit color
      width = disp.width
      height = disp.height
      image = Image.new('1', (width, height))

      #get drawing object to draw on image
      draw = ImageDraw.Draw(image)

      #draw a black filled box to clear the image
      draw.rectangle((0,0,width,height), outline=0, fill=0)

      #define constants to define drawing area
      padding = 2
      top = padding

      #move left to right, keeping track of the current x position
      #for drawing text
      x = padding

      #load default font
      font = ImageFont.load_default()

      #openWeatherMap.org weather data request
➏     weather_data = requests.get(open_weather_map_url)

      #display location
➐     location = weather_data.json().get('name') + ' - '
  + weather_data.json().get('sys').get('country')
      draw.text((x, top), location, font=font, fill=255)

      #display description
      description = 'Desc ' + weather_data.json().get('weather')[0]
  .get('main')
      draw.text((x, top+10), description,  font=font, fill=255)

      raw_temperature = weather_data.json().get('main')
  .get('temp')-273.15

      #temperature in Celsius
      temperature = 'Temp ' + str(raw_temperature) + '*C'

      #uncomment for temperature in Fahrenheit
      #temperature = 'Temp ' + str(raw_temperature*(9/5.0)+32) + '*F'
      #display temperature
      draw.text((x, top+20), temperature, font=font, fill=255)

      #display pressure
      pressure = 'Pres ' + str(weather_data.json().get('main')
  .get('pressure')) + 'hPa'
      draw.text((x, top+30), pressure, font=font, fill=255)

      #display humidity
      humidity = 'Humi ' + str(weather_data.json().get('main')
  .get('humidity')) + '%'
      draw.text((x, top+40), humidity, font=font, fill=255)

      #display wind
      wind = 'Wind ' + str(weather_data.json().get('wind')
  .get('speed')) + 'mps ' + str(weather_data.json().get('wind')
  .get('deg')) + '*'
      draw.text((x, top+50), wind, font=font, fill=255)

      #display image
➑     disp.image(image)
      disp.display()
      time.sleep(10)

和往常一样,你的代码从导入所需的库开始 ➊。Adafruit_SSD1306 库包含了 OLED 显示驱动类。你从 Python Imaging Library(PIL)中导入了三个模块——ImageImageDrawImageFont——来创建一个带有文本的图像,这些文本将显示在 OLED 上。

OLED 库

Adafruit_SSD1306 库将 OLED 显示器上显示的所有内容都称为“图像”——即使是文本。你在这里使用的三个模块各自的角色如下:

  • Image 创建一个新图像。

  • ImageDraw 在图像内绘制文本或图标,并显示你将在实际 OLED 显示器上看到的内容。

  • ImageFont 设置文本字体。

初始化 OLED 显示屏

即使你的显示屏没有重置引脚,你也需要在代码中设置 RST 引脚。如果你的显示屏有重置引脚,它应该连接到 GPIO 24。所以,无论哪种情况,你都需要在此处 ➋ 将 RST 设置为 24

在 ➌ 处,你创建了一个显示类。对于 128×32 显示屏,创建 SSD1306_128_32 类;对于 128×64 显示屏,创建 SSD1306_128_64 类。我们在代码中给出了这两种选项,你只需取消注释与你的显示屏尺寸匹配的那一行,并注释掉另一行。

在 ➎ 处,你初始化显示库并准备显示屏,以便可以在其上绘制文本。我们对代码进行了大量注释,以帮助你理解每一行的作用。

发起 API 请求

在 ➍ 处,你创建了一个名为 open_weather_map_url 的变量来保存 API URL。确保用你自己的 API URL 更新这一行。

在 ➏ 处,你发出 API 请求,之后有几个代码块起到类似的作用。我们将解释 ➐ 处的代码,然后你就能理解其他代码块的功能。你创建一个名为 location 的变量来获取位置。这个变量是多个字符串的拼接。首先,使用 weather_data.json().get('name') 获取位置,在这个例子中返回 Porto。然后用 + ' - ' 添加一个连字符,再用 weather_data.json().get('sys').get('country') 获取国家代码;在这个例子中,返回 PT。因此,location 变量返回 Porto – PT

在 OLED 显示屏上绘制文本

要在显示屏上绘制文本,可以使用 draw.text() 函数,函数接受以下参数:

x 和 y 坐标  文本开始绘制的位置

text  要显示的文本

font  文本显示的字体

fill  像素亮度—255 是最大值

例如,要在 OLED 预报器的顶行显示位置,请使用以下代码:

draw.text((x, top), location, font=font, fill=255)

xtop 坐标在 ➎ 处定义。这个例子使用了默认的库字体,不过你可以通过下载字体文件并修改代码,来探索其他字体。

显示天气描述、温度、气压、湿度和风速的代码块都很相似。请注意,你需要递增 top 变量才能在显示屏的下一行绘制文本。

最后,在 ➑ 处的代码行将图像显示在 OLED 上。末尾的延时时间决定了循环更新天气信息的速度——在这个例子中,是每 10 秒更新一次。

运行脚本

F5 或前往 运行运行模块 来运行脚本。恭喜你,现在你有了一个小型天气预报器,能不断更新你所选地点的天气数据!

进一步探索

你可以使用 API 获取比天气更多的信息。使用你最喜欢的搜索引擎,输入类似 free API for 的查询,来找到你可以访问的 API。以下是一些可以让你开始的想法:

  • 交通

  • 推文

  • 最新新闻

  • 股票价格

  • 当前比特币汇率

使用带有 Sense HAT 的 Pong**

在这里,你将使用 Sense HAT 构建你自己的 LED Pong 游戏。Sense HAT 是一个附加板,可以为你的 Pi 增加更多功能,提供额外的特性,如 LED 矩阵、操纵杆以及从外界获取信息的多个传感器。

image

所需部件

Raspberry Pi(具有 40 个 GPIO 引脚的版本)

Sense HAT

你将使用 Sense HAT 的 LED 矩阵来显示游戏,并用操纵杆来玩。如果你没有硬件,不用担心:你还将学习如何使用 Sense HAT 模拟器,在没有硬件的情况下创建相同的游戏。

介绍 Pong

“Pong”是最早创建的视频游戏之一,是一款非常流行的 2D 乒乓球游戏,可以单人或双人模式玩。你将创建单人版,所以这更像是打壁球:你用球拍把球弹回墙壁,然后在球回来的时候用球拍接住它。如果你错过了球,就输了。

介绍 Raspberry Pi Sense HAT

Raspberry Pi Sense HAT 具有一个 8×8 RGB LED 矩阵、一个五按钮操纵杆、一个陀螺仪、一个加速度计、一个磁力计、一个温度传感器、一个气压传感器和一个湿度传感器,所有这些都集成在一个包中,如图 8-1 所示。

image

图 8-1: Raspberry Pi Sense HAT

安装电路板

注意

Sense HAT 与 Raspberry Pi 1 Model A 和 B 不兼容,但如果你有不兼容的板子,可以使用模拟器来构建这个项目。

这个项目不需要太多的硬件组装——你只需要将 Sense HAT 安装到 Pi 上,剩下的工作都在代码中完成。

将 Sense HAT 上的 40 个 GPIO 引脚连接到 Raspberry Pi 上的 40 个 GPIO 引脚;两块板子应该完全对齐。当你第一次成功地将 Sense HAT 安装到一个通电的 Pi 上时,LED 矩阵会显示一个发光的彩虹背景,如图 8-2 所示。

image

图 8-2: Sense HAT 欢迎彩虹

使用 Sense HAT 模拟器

如果你没有 Sense HAT 或兼容的板子,或者你只想先测试脚本,可以使用 Sense HAT 模拟器在你的计算机上构建 Pong 游戏。模拟器是一个虚拟的 Sense HAT,你可以与其互动来测试脚本。要从桌面主菜单启动它,进入编程Sense HAT 模拟器。这将打开模拟器窗口,如图 8-3 所示。

image

图 8-3: Sense HAT 模拟器窗口

Sense HAT 模拟器自带一些存储在文件示例中的示例;只需选择你想要的示例,然后运行文件,在模拟器窗口中查看代码效果。

使用 Sense HAT 功能和控制

在开始构建游戏之前,了解如何控制 LED 矩阵和读取操纵杆输入是非常重要的。让我们看一些你将在 Pong 脚本中使用的示例。

控制 LED 矩阵

Sense HAT LED 矩阵有 8 列和 8 行,共有 64 个 RGB LED。你可以通过单独控制每个 LED 来显示文本和创建图像。你还可以设置每个 LED 的颜色。

显示文本

列表 8-1 中的代码会在点阵上以蓝色显示滚动文本“Hello World!”。

列表 8-1: 在 Sense HAT LED 矩阵上显示文本

➊ from sense_hat import SenseHat
  #uncomment the following line if you are using the emulator
➋ #from sense_emu import SenseHat
  sense = SenseHat()
➌ sense.show_message('Hello World!', text_colour = [0, 0, 255])

首先导入SenseHat类 ➊。如果你使用的是模拟器,请删除或注释掉这一行,并取消注释➋处的代码。

show_message() 函数 ➌ 接受要显示的消息——文本字符串——作为第一个参数,然后接受多个选项作为进一步的参数:

注意

sense_hat 库使用英国拼写“colour”,因此你必须在代码中使用“colour”。

  • 使用 text_colour = [*r*, *g*, *b*] 来设置文本的 RGB 颜色,将 *r*, *g*, *b* 替换为介于 0255 之间的整数(就像你在项目 5 中做的那样)。

  • 使用 scroll_speed = *x*,其中 x 是一个浮点数,来控制文本在显示屏上滚动的速度。默认的滚动速度设置为每次文本向左移动一个像素时暂停 0.1 秒。

  • 使用 back_colour = [*r*, *g*, *b*] 来设置背景颜色,将 *r*, *g*, *b* 替换为与 text_colour 相同的整数值。

控制特定的 LED

要控制单个 LED,你需要通过它在矩阵中的位置来引用每个想要点亮的 LED。为此,Sense HAT 使用 (x, y) 坐标系统。例如,图 8-4 中的 LED 坐标就在图示旁边列出。

image

图 8-4: Sense HAT 坐标系统

为了用对应的颜色点亮图 8-4 中的 LED,你可以使用清单 8-2 中的代码。

清单 8-2: 使用 set_pixel() 点亮特定的 LED

from sense_hat import SenseHat
#uncomment the following line if you are using the emulator
#from sense_emu import SenseHat
sense = SenseHat()
#set blue pixel
sense.set_pixel(0, 1, 0, 0, 255)
#set green pixel
sense.set_pixel(7, 6, 0, 255, 0)
#set pink pixel
sense.set_pixel(2, 5, 255, 51, 153)

函数 sense.set_pixel(*x*, *y*, *r*, *g*, *b*) 点亮特定的 LED,其中 x 是 x 坐标;y 是 y 坐标;r、g 和 b 设置颜色。

显示图片

与其控制单个 LED,你可以使用函数 sense.set_pixels() 来更快速地显示图像。你可以插入一个包含所有 64 个 LED 颜色的列表,而不是输入坐标。看看代码清单 8-3,它显示了一个悲伤的表情。

清单 8-3: 使用 set_pixels() 显示图像

from sense_hat import SenseHat
#uncomment the following line if you are using the emulator
#from sense_emu import SenseHat
sense = SenseHat()

#red color
X = [255, 0, 0]

#no color
N = [0, 0, 0]

#sad face array
sad_face = [
N, N, X, X, X, X, N, N,
N, X, N, N, N, N, X, N,
X, N, X, N, N, X, N, X,
X, N, N, N, N, N, N, X,
X, N, N, X, X, N, N, X,
X, N, X, N, N, X, N, X,
N, X, N, N, N, N, X, N,
N, N, X, X, X, X, N, N
]

sense.set_pixels(sad_face)

注意

sad_face 数组中的红色 X 不会在你的代码中显示为红色。我们只是用红色 X 来突出它们,这样更容易可视化 LED 的显示效果。

你创建一个变量来存储点亮的 LED 的颜色(X),另一个变量来存储背景颜色(N)——你可以将背景设置为任何颜色,或者设置为 0 使其保持不亮。然后你需要创建一个数组,将每个 64 个 LED 设置为 XN。图 8-5 显示了清单 8-3 中的代码执行后的结果:

image

图 8-5: 在 LED 矩阵上显示一个悲伤的表情

在你的绘图中,你可以包含任意多的颜色;你只需要更改颜色参数。我们鼓励你通过更改颜色并绘制自己的图像来练习使用 LED 矩阵。

现在你知道如何控制 LED 矩阵了,接下来我们来看如何编程控制摇杆。

从摇杆读取数据

随 Sense HAT 一起提供的摇杆有五个控制选项:

  • 向上移动

  • 向下移动

  • 向右移动

  • 向左移动

  • 按下

你需要告诉程序每个控制选项应该让 Pi 做什么。列表 8-4 中的脚本设置了与每个摇杆控制相关联的事件,并在计算机屏幕上显示哪个控制被使用的信息:

列表 8-4: 将事件与每个摇杆控制关联

  from signal import pause

  from sense_hat import SenseHatm
  #uncomment the following line if you are using the emulator
  #from sense_emu import SenseHat
  sense = SenseHat()

➊ def move_up(event):
      print('joystick was moved up')

  def move_down(event):
      print('joystick was moved down')

  def move_right(event):
      print('joystick was moved right')

  def move_left(event):
      print('joystick was moved left')

  def move_middle(event):
      print('joystick was pressed')

➋ sense.stick.direction_up = move_up
  sense.stick.direction_down = move_down
  sense.stick.direction_right = move_right
  sense.stick.direction_left = move_left
  sense.stick.direction_middle = move_middle

  pause()

首先,你需要告诉你的 Pi 在每个摇杆控制被触发时应该采取什么行动。你通过定义一系列函数来执行这些动作。例如,当摇杆被向上移动时,你调用move_up()函数➊来打印消息joystick was moved upevent参数告诉 Pi 摇杆将向这些函数发送信息。然后,你使用sense.stick.direction_up = move_up➋将move_up函数与摇杆的向上移动关联起来。

其他的运动功能也以相同的方式工作。

编写脚本

现在,你知道了如何在 LED 矩阵上显示文本和图形,以及如何在摇杆使用时触发某些事件,你已经准备好开始编写游戏脚本了。

这是游戏的目标:

  • 一个长 3 像素、宽 1 像素的球拍应该出现在第 0 列。

  • 每次你移动摇杆向上或向下时,球拍应该相应地移动。

  • 球应该从一个随机位置开始并朝对角线移动。

  • 当球碰到某物——墙壁、天花板或球拍——时,它应该朝相反的方向对角线移动。

  • 如果球击中第 0 列,意味着你错过了球,那么你就输了,游戏结束。

输入脚本

打开Python 3 (IDLE),然后点击文件新建文件来创建一个新的脚本。接着将列表 8-5 中的代码复制到新文件中,并将脚本保存为pong_game.py,存放在Displays文件夹内(记得你可以在www.nostarch.com/RaspberryPiProject/下载所有脚本)。

列表 8-5: Pong 游戏代码

  #based on raspberrypi.org Sense HAT Pong example

  #import necessary libraries
➊ from random import randint
  from time import sleep

  #use this line if you are using the Sense HAT board
  from sense_hat import SenseHat
  #uncomment the following line if you are using the emulator
  #from sense_emu import SenseHat

  #create an object called sense
➋ sense = SenseHat()

  #set bat position, random ball position, and velocity
➌ y = 4
➍ ball_position = [int(randint(2,6)), int(randint(1,6))]
➎ ball_velocity = [1, 1]

  #red color
  X = [255, 0, 0]
  #no color
  N = [0, 0, 0]

  #sad face array
  sad_face = [
  N, N, X, X, X, X, N, N,
  N, X, N, N, N, N, X, N,
  X, N, X, N, N, X, N, X,
  X, N, N, X, N, N, N, X,
  X, N, N, X, N, N, N, X,
  X, N, X, N, N, X, N, X,
  N, X, N, N, N, N, X, N,
  N, N, X, X, X, X, N, N
  ]

  #draw bat at y position
➏ def draw_bat():
      sense.set_pixel(0, y, 0, 255, 0)
      sense.set_pixel(0, y+1, 0, 255, 0)
      sense.set_pixel(0, y-1, 0, 255, 0)

  #move bat up
➐ def move_up(event):
      global y
      if y > 1 and event.action=='pressed':
          y -= 1

  #move bat down
  def move_down(event):
      global y
      if y < 6 and event.action=='pressed':
          y += 1

  #move ball to the next position
➑ def draw_ball():
      #ball displayed on current position
      sense.set_pixel(ball_position[0], ball_position[1], 75, 0, 255)
      #next ball position
      ball_position[0] += ball_velocity[0]
      ball_position[1] += ball_velocity[1]
      #if ball hits ceiling, calculate next position
      if ball_position[0] == 7:
          ball_velocity[0] = -ball_velocity[0]
      #if ball hits wall, calculate next position
      if ball_position[1] == 0 or ball_position[1] == 7:
          ball_velocity[1] = -ball_velocity[1]
      #if ball reaches 0 position, player loses and game quits
      if ball_position[0] == 0:
          sleep(0.25)
          sense.set_pixels(sad_face)
          quit()
      #if ball hits bat, calculate next ball position
      if ball_position[0] == 1 and y - 1 <= ball_position[1] <= y+1:
          ball_velocity[0] = -ball_velocity[0]

  #when joystick moves up or down, trigger corresponding function
➒ sense.stick.direction_up = move_up
  sense.stick.direction_down = move_down

  #run the game
➓ while True:
      sense.clear()
      draw_bat()
      draw_ball()
      sleep(0.25)

这段代码做了很多事情。让我们一步一步地解析它。

导入必要的库

在➊处,你从 rand 库导入了randint()函数,用来生成伪随机整数,从 time 库导入了sleep()函数,用来设置延迟时间。

在➋处,你创建了一个名为sense的对象,它将在代码中引用 Sense HAT。

创建球拍

球拍是一个 3 像素长、1 像素宽的条形,移动在最左侧的列上。在➌处,你定义了球拍的起始位置为从顶部向下 4 像素,y = 4。完整的球拍在draw_bat()函数中以绿色绘制➏,它在起始位置的顶部(y - 1)和底部(y + 1)各加上一个像素,使得球拍总长度为 3 像素。

移动球拍

球拍仅在 y 轴上移动,因此它的 x 坐标始终为0,但随着玩家移动球拍,y 坐标需要变化。换句话说,玩家只能上下移动球拍。move_up()move_down()函数(在➐处定义)控制这些移动。在➒处,你通过调用move_up()move_down()分别告诉 Pi 当玩家向上或向下移动操纵杆时采取什么动作。

仔细看看move_up()函数(move_down()函数的工作方式类似):

#move bat up
def move_up(event):
    global y
    if y > 1 and event.action=='pressed':
        y -= 1

move_up()函数接受event作为参数。基本上,event参数允许你将一些有关操纵杆的信息传递给函数——比如操纵杆使用的时间、推动的方向,以及它是被按下、释放还是保持——这样 Pi 就知道如何做出反应。

提示

在 Python 中y -= 1 等同于 y = y - 1

当玩家向上移动操纵杆时,函数通过从变量y中减去1来将球拍的 y 坐标向上移动。但首先,代码检查y > 1;否则,球拍可能会移动出矩阵。

声明变量作用域

注意,y被定义为global变量。并非程序中的所有变量都可以在程序的所有位置访问,所以可能有些地方无法调用某些变量。变量的作用域是程序中可以访问它的区域。在 Python 中,有两种基本的变量作用域:局部全局

在主代码主体中定义的变量是全局的,这意味着它可以在代码中的其他任何地方访问。在函数内部定义的变量是局部变量,所以在函数内部对局部变量的操作不会影响外部的变量,即使它们有相同的名称。

由于你希望y在定义它的函数内以及整个代码中都能使用,所以它需要被声明为global。否则,当你移动操纵杆时,什么也不会发生,因为y变量只是在函数内部发生了变化,而不是在代码的主主体中发生变化。

创建球

要制作一个移动的球,首先需要一个起始位置和一个速度。在➍处,你使用列表设置球的起始位置。列表是由方括号定义的,[*第 0 个元素*,*第 1 个元素*,...,*第 n 个元素*],每个元素之间用逗号分隔。列表中的元素是零索引,这意味着第一个元素的索引是 0,而不是 1。这里,我们的第 0 个元素是 x 坐标,第 1 个元素是 y 坐标。

当你开始游戏时,球的位置是随机的,由randint()函数生成。该随机位置的 y 轴范围是 1 到 6,x 轴范围是 2 到 6。这些数字确保球不会出现在天花板、墙壁或球拍旁边。

移动球

一旦你为小球设置了起始位置,你需要给它一个速度 ➎ 使其开始移动。你创建了一个小球速度的列表,其中第 0 个元素是 x 坐标的速度,第 1 个元素是 y 坐标的速度。

你需要将速度加到或从当前小球位置中减去,使小球向前或向后移动。➑处的draw_ball()函数是用来显示和移动小球的地方,小球总是沿对角线移动。如果它向前,它就会继续向前;如果它向后,它就会继续向后,除非它碰到天花板或球拍,这时它会反向移动。

保持游戏运行

一旦一切准备就绪,你就可以添加一个while循环来保持游戏运行 ➓。while循环从清除显示开始;然后,它调用draw_bat()函数来绘制球拍,并调用draw_ball()来显示小球。

最后一行中的sleep()函数定义了小球移动到另一个位置所需的时间,因此你可以使用此函数来决定小球的移动速度。如果增加延迟时间,游戏会变慢且更容易;如果减少延迟时间,游戏会加速。我们鼓励你尝试不同的延迟时间。

运行脚本

恭喜!经过大量编程,你获得了回报:你可以在你的 Sense HAT 上玩 Pong 游戏!按F5或者进入RunRun Module来运行脚本。

当你输了且游戏结束时,LED 矩阵会显示一个悲伤的表情,如图 8-6 所示。

image

图 8-6: 游戏结束时 LED 矩阵显示悲伤的表情

进一步提升

这里有一些升级游戏的想法:

  • 随着游戏的进行,减少延迟时间以增加游戏的难度。

  • 添加一个得分系统,每次小球击中球拍时得一分,并在屏幕上显示分数。

  • 插入一个条件,当你按下操纵杆时重新开始游戏。

第四章:传感器

一体化气象传感器站

在这个项目中,你将构建一个本地气象站,使用 Sense HAT 检测温度、湿度和气压。你还将创建一个图形用户界面,实时显示温度、湿度和气压的读数。

image

所需组件

树莓派(具有 40 个 GPIO 引脚的版本)

Sense HAT

将 Sense HAT 作为气象站

Sense HAT 是一个非常优秀的小型且价格实惠的气象站,因为它配备了温度、湿度和气压传感器。使用 Sense HAT 读取传感器值非常简单,因此这是一个学习传感器读取的好起点。

温度传感器

注意

与真实值相比,温度读数可能会偏差几度。Sense HAT 安装在 Pi 上,树莓派处理器的热量可能会略微影响结果。

如其名称所示,温度传感器测量温度。默认情况下,Sense HAT 读取的是摄氏度温度,因此如果你更喜欢华氏度,你需要进行转换。方法是,将摄氏度乘以 9,再除以 5,最后加上 32,公式如下所示。

image

你可以将此公式添加到你的代码中,以便自动进行转换。

湿度传感器

有两种常见的湿度表示方式:绝对湿度和相对湿度。绝对湿度是指某一体积空气中水蒸气的质量,与温度无关,单位为千克/立方米(kg/m³)。空气能够容纳的水蒸气量随温度变化。温度越高,空气可以容纳的水蒸气量越多。相对湿度以百分比表示,是指当前空气中的水蒸气量与在某一温度下空气最大能够容纳的水蒸气量的比例。

Sense HAT 记录相对湿度,因为它对天气预报更有用:相对湿度百分比越高,降水的概率越大。由于相对湿度随着温度变化,因此它总是与温度传感器一起使用。

气压传感器

气压传感器读取大气压力,即某一点的空气“重量”,单位为 hPa(百帕斯卡),相当于 mbar(毫巴)。为什么测量气压很有趣?因为气压的变化可以帮助你预测天气。气压上升通常是好天气的标志,而气压下降则通常预示着恶劣天气,如降雨或风暴。

压力变化非常微小。你需要仔细观察气压计的读数才能发现趋势。

读取温度、湿度和气压

现在让我们看看如何从传感器读取数据,并将其打印到 Python 命令行。

像在 项目 8 中一样将 Sense HAT 安装到你的 Pi 上,并确保它连接良好。当它首次连接时,Sense HAT 应该显示一个彩虹背景,这个彩虹背景与你启动 Pi 时屏幕上看到的彩虹相匹配(参见 图 9-1)。

image

图 9-1: Sense HAT 彩虹背景

在你的 Projects 文件夹内创建一个名为 Sensors 的新文件夹。然后打开 Python 3 (IDLE),选择 文件新建 创建一个新的脚本文件,命名为 weather_data.py,并输入 清单 9-1 中的代码(记住你可以在 www.nostarch.com/RaspberryPiProject/ 下载所有脚本)。

清单 9-1: 使用 Sense HAT 读取温度、湿度和压力

➊ from sense_hat import SenseHat
  #from sense_emu import SenseHat

  from time import sleep

  #create an object called sense
➋ sense = SenseHat()

  while True:
   ➌ temperature = sense.temperature
   ➍ temperature = str(round(temperature, 2))
   ➎ print('Temperature: ' + temperature + '*C\n')
     humidity = sense.humidity
     humidity = str(round(humidity, 2))
     print ('Humidity: ' + humidity + '%\n')

     pressure = sense.pressure
     pressure = str(round(pressure, 2))
     print('Pressure: ' + pressure + 'hPa\n')

     sleep(1)

首先,从 sense_hat 库中导入 SenseHat 类 ➊。然后,你创建一个名为 sense 的对象,来引用 Sense HAT ➋。

获取传感器读数 ➌ 非常简单,这得益于以下这些恰如其分命名的函数:

  • sense.temperature 获取温度读数。

  • sense.humidity 获取湿度读数。

  • sense.pressure 获取压力读数。

读数精确到几位小数,因此你使用 round() 函数来对数字进行四舍五入,使结果更易读。round() 函数 ➍ 接受两个参数:你想要四舍五入的数字和你希望设置的小数位数,顺序为:首先是数字,其次是小数位数——在这里,它被设置为两位小数。你还使用 str() 函数,它将传入的参数转换为字符串。你需要将读数转换为字符串,这样才能将它们与你将在命令行中打印的文本进行连接 ➎。

现在你几乎是一个气象学家了!接下来,你将为你的天气数据构建一个用户界面。

构建你的读数用户界面

让我们把这个项目提升到另一个层次,构建一个酷炫的用户界面来显示传感器读数。你的界面应包含:

  • 一个显示温度、湿度和压力的桌面窗口

  • 以从 0 到 100 百分比的垂直进度条显示湿度

  • 以数字形式显示的温度和压力

  • 每个读数的标签

图 9-2 显示了一个用户界面的草图布局,应该能够帮助你理解如何处理代码。

image

图 9-2: 图形用户界面草图

你还可以编辑代码来选择字体类型、大小和颜色,以及标签和读数在窗口中的位置。下表列出了所有标题和值及其显示方式。

小部件 选项
窗口标题 文本:“本地气象站”
湿度标题 文本:“湿度”,字体:Helvetica,大小:18,垂直间距:3
湿度值 字体:Courier,大小:20,颜色:蓝色,位置:北
湿度进度条 方向:垂直,大小:20,颜色:蓝色,位置:北,长度:200,最大值:100
温度标题 文本:“温度”,字体:Helvetica,大小:18,位置:南
温度值 字体:Courier,大小:20,颜色:红色,位置:北
压力标题 文本:“压力”,字体:Helvetica,大小:18,位置:南
压力值 字体:Courier,大小:20,颜色:绿色,位置:北

编写脚本

打开Python 3 (IDLE),然后选择文件新建文件,以创建一个新的脚本。将 Listing 9-2 中的代码复制到 Python 编辑器中,并将脚本保存为weather_station.py,并保存在Sensors文件夹中(请记住,您可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

LISTING 9-2: 在图形用户界面中显示 Sense HAT 的读数

  #import necessary libraries
➊ from tkinter import *
  from tkinter import ttk
  import time
  from sense_hat import SenseHat
  #from sense_emu import SenseHat

  #create an object called sense
  sense = SenseHat()

  #create window
➋ window = Tk()
  window.title('Local Weather Station')
  window.geometry('200x480')

  #create humidity label for title and value
➌ humidity_label = Label(window, text = 'Humidity', font =
  ('Helvetica', 18), pady = 3)
  humidity_label.pack()

➍ humidity = StringVar()

➎ humidity_value=Label(window, textvariable = humidity,font =
  ('Courier', 20), fg = 'blue', anchor = N, width = 200)
  humidity_value.pack()

  #create humidity canvas
➏ humidity_canvas = Canvas(window, width = 200, height = 200)
  humidity_canvas.pack()

  #create humidity progress bar
➐ humidity_bar = DoubleVar()

➑ progressbar_humidity = ttk.Progressbar(humidity_canvas, variable =
  humidity_bar, orient = VERTICAL, length = 200, maximum = 100)
  progressbar_humidity.pack(fill=X, expand=1)

  #create temperature label for title and value
  temperature_label = Label(window, text = 'Temperature', font =
  ('Helvetica', 18),anchor = S, width = 200, height = 2)
  temperature_label.pack()

  temperature = StringVar()

  temperature_value = Label(window, textvariable = temperature, font =
  ('Courier', 20),fg = 'red', anchor = N, width = 200)
  temperature_value.pack()

  #create pressure label for title and value
  pressure_label = Label(window, text = 'Pressure', font =
  ('Helvetica', 18), anchor = S, width = 200, height = 2)
  pressure_label.pack()

  pressure = StringVar()

  pressure_value = Label(window, textvariable = pressure, font =
  ('Courier', 20), fg = 'green', anchor = N, width = 200)
  pressure_value.pack()

➒ def update_readings():
      humidity.set(str(round(sense.humidity, 2)) + '%')
      humidity_bar.set(sense.humidity)
      temperature.set(str(round(sense.temperature, 2)) + '°C')
      #temperature.set(str(round(sense.temperature*(9/5)+32, 2))
  + '*F')
      pressure.set(str(round(sense.pressure)) + 'hPa')
      window.update_idletasks()
      window.after(3000, update_readings)

➓ update_readings()
  window.mainloop()

和往常一样,您首先通过导入必要的库开始编写代码 ➊。您可能会想,既然我们在上一行中已经从 tkinter 库导入了所有内容,为什么还需要单独导入 ttk 呢?在这种情况下,当您使用通配符*导入时,您只导入了库文件夹中存储的一个子集——没有特别的原因,这是库的作者选择的方式——因此我们需要单独导入 ttk 库,它在此用户界面中也是必需的。

要收集天气数据,您需要使用物理 Sense HAT 和 sense_hat 库。

创建用户界面

在导入所有库后,您实现了创建用户界面部分的代码。首先,您创建一个 200×480 像素的窗口,并为其设置标题为Local Weather Station ➋。然后,您为湿度标题创建一个标签 ➌,其设置如第 131 页中的表格所示。接着,在 ➍,您创建一个名为humidity的字符串变量,用于存储湿度值。此值随后将在 ➎ 处显示。

➏处的代码行创建了一个画布,用于放置进度条——画布就像是为进度条预留的空间。之后,代码初始化了一个名为humidity_bardouble类型变量 ➐,这是进度条接受的变量类型。最后,➑处的代码创建了湿度进度条并将其放置在画布上。

显示温度和压力标题及值的过程与 ➌、➍ 和 ➎ 中的步骤相同。

自动更新读数

在 ➒ 处,您定义了update_readings()函数,该函数每三秒更新一次显示的值,以确保天气读数始终保持最新。

以下行更新temperature变量:

temperature.set(str(round(sense.temperature, 2)) + '*C')

让我们将这一行拆解成各个部分:

  • sense.temperature 从 Sense HAT 获取温度读数。

  • round(sense.temperature,2) 将温度读数四舍五入到小数点后两位。

  • str(round(sense.temperature,2)) 将四舍五入后的读数转换为字符串。

  • (str(round(sense.temperature,2)) + '*C') 将摄氏度符号附加到字符串中。

  • temperature.set(str(round(sense.temperature, 2)) + '*C') 更新 temperature 变量为最新的读数。

脚本采用类似的方法来更新 pressurehumidity 变量。

window.update_idletasks() 函数在监控时保持窗口的最新状态。最后,window.after(3000, update_readings)update_readings 作为事件添加到 mainloop() 中,并告诉树莓派每 3,000 毫秒(3 秒)调用一次这个函数。

在 ➓ 处,你调用了 update_readings() 函数和保持窗口运行的 window.mainloop() 函数。

最后,你可以通过注释和取消注释这两行代码来以华氏度显示温度:

#temperature.set(str(round(sense.temperature, 2)) + '*C')
temperature.set(str(round(sense.temperature*(9/5)+32, 2)) + '*F')

运行脚本

F5 键或进入 RunRun Module 来运行脚本。你应该能看到项目开始时所示的用户界面上显示你的天气数据。

恭喜你!你已经制作了自己的天气站,你现在正式是一个初级气象学家。

进一步提升

这里有一些自定义此项目的想法:

  • 将华氏温度转换添加到代码中,并以 °F 显示温度。

  • 更改图形用户界面的布局、字体颜色、大小和类型,以适应你的个人喜好。

  • 使用 Sense HAT 上的 LED 矩阵显示天气信息。例如,你可以显示文本、相对柱状图,或用绿色和红色箭头表示温度、湿度或气压的升降。

  • 在这个部分的其余项目中,你将学习如何用 Python 发送电子邮件以及如何保存传感器读数。利用这些技能,你可以将天气数据发送到你的电子邮件,或构建一个天气站数据记录器。一定不要错过下一个项目!

带电子邮件通知的入侵报警

在这个项目中,你将创建一个入侵报警系统,通过电子邮件通知你。该报警系统将利用被动红外(PIR)运动传感器检测是否有人闯入禁区。当 PIR 传感器探测到运动时,它将发送一封警告邮件。

image

所需组件

树莓派

面包板

PIR 运动传感器 HC-SR501

两个 5 毫米 LED(不同颜色)

两个 330 Ω 电阻

按钮

跳线

介绍 PIR 运动传感器

你可能在许多不同的应用中见过运动传感器。它们用于安防灯、商业建筑中经过时自动开启的灯光以及入侵报警系统。

PIR 运动传感器(见图 10-1)通过测量其视野内物体发出的红外线来工作。它根据红外线光的变化来检测运动,这表示温度的变化。这使它非常适合检测人类或动物,因为它能感知到在其范围内移动的生物体,但不会感知到像被风吹动的叶子这样的无生命物体。您可以编程让 Pi 对红外线光的变化做出反应,例如触发事件,如打开灯、响铃,或者像我们在这个项目中所做的那样,发送电子邮件。

image

图 10-1: PIR 运动传感器

该传感器在检测到运动时输出高电平(HIGH),没有检测到运动时输出低电平(LOW),并且它只有 3 个引脚:VCC、GND 和数据。数据输出一个 3.3V 的信号,非常适合您的 Pi!

使用 PYTHON 发送电子邮件

Python 的电子邮件库使得通过 Python 发送电子邮件变得简单。我们将在编写脚本之前先组装各个部分。

查找您的 SMTP 服务器详细信息

要通过代码发送电子邮件,您需要包括您的简单邮件传输协议(SMTP)服务器详细信息。SMTP 是电子邮件传输的互联网标准,每个电子邮件提供商都有不同的 SMTP 服务器。

这些详细信息包括您的服务提供商的服务器地址端口,以及是否使用传输层安全性(TLS)。TLS 是一种用于在两个电子邮件服务器之间建立安全连接的协议。要获取这些信息,只需在互联网上搜索SMTP 服务器设置,并加上您的电子邮件提供商名称。您将把这些详细信息插入到脚本中以进行个性化设置。

电子邮件发送脚本

注意

不要将您的文件命名为 email.py ,因为这是 Python 库的名称,您的脚本将无法工作。

打开Python 3(IDLE),并进入文件新建文件以创建一个新的脚本。将示例 10-1 中的代码复制到 Python 编辑器中,并将脚本保存为send_email.py,放在Sensors文件夹中(记得,您可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

示例 10-1: 电子邮件通知脚本

➊ import smtplib
  from email.mime.text import MIMEText

  #replace the next three lines with your credentials
➋ from_email_addr = '*YOUR_EMAIL@gmail.com*'
  from_email_password = '*YOUR_EMAIL_PASSWORD*'
  to_email_addr = '*TO_YOUR_OTHER_EMAIL@gmail.com*'

  #set your email message
➌ body = 'Motion was detected in your room.'
  msg = MIMEText(body)

  #set sender and recipient
  msg['From'] = from_email_addr
  msg['To'] = to_email_addr

  #set your email subject
  msg['Subject'] = 'INTRUDER ALERT'

  #connecting to server and sending email
  #edit the following line with your provider's SMTP server details
➍ server = smtplib.SMTP('*smtp.gmail.com*', *587*)
  #comment out the next line if your email provider doesn't use TLS
  server.starttls()
➎ server.login(from_email_addr, from_email_password)
  server.sendmail(from_email_addr, to_email_addr, msg.as_string())
  server.quit()
  print('Email sent')

警告

如果您在while循环中使用下面的代码片段➎,并且没有延迟,您的收件箱将被成千上万封电子邮件填满,您的账户可能会被封锁,所以如果在其他项目中使用此代码片段,请务必添加延迟!

首先,您需要导入用于 SMTP 和电子邮件相关功能的库:smtplib 和 MIMEText ➊。接下来,您需要为发件人电子邮件地址、该电子邮件的密码以及收件人电子邮件地址创建变量➋。我们建议您创建一个第二个电子邮件账户来接收通知,因为您将为发送邮件的账户授予不太安全的应用程序访问权限。确保为这些字符串输入您自己的信息。

➌ 处的代码块编写电子邮件。首先创建一个body变量来存储你的电子邮件正文文本。然后创建一个名为msg的对象,该对象使用msg = MIMEText(body)生成电子邮件本身。你可以通过更改bodymsg['Subject']变量中的字符串来随意更改电子邮件正文和主题。

在 ➍ 处,您与 SMTP 服务器建立通信。将提供商的 SMTP 服务器地址作为字符串传递给smtplib.SMTP()的第一个参数,并将端口作为 int 传递给第二个参数。在此脚本中,我们使用的是 Gmail SMTP 服务器和端口。如果您使用其他电子邮件提供商,请确保更改这些值。

server.starttls()函数对于使用 TLS 加密消息的电子邮件提供商是必需的。如果你的电子邮件提供商不使用 TLS,你可以删除或注释掉该行。

接下来,脚本登录到发送电子邮件的帐户 ➎,发送电子邮件,并停止与服务器的通信。最后,脚本将'Email sent'消息打印到 Python shell,以告知用户已发送电子邮件。

运行电子邮件发送脚本

现在是时候看看你的脚本的实际效果了!保存你的脚本并按 F5 或转到 RunRun Module 来运行脚本。然后检查你发送消息的电子邮件收件箱。你应该收到一封新邮件。你可以在图 10-2 中看到我们使用此脚本收到的电子邮件。

image

图 10-2: 使用 send_email.py 发送的电子邮件

如果您没有收到电子邮件,请验证 send_email.py 中的电子邮件和 SMTP 信息是否正确。另请验证您是否已授予权限,允许安全性较低的应用程序在您的电子邮件帐户设置中使用您的帐户。

电路接线

现在让我们将你的 PIR 传感器连接到你的 Raspberry Pi,以便它可以在传感器检测到移动时向你发送电子邮件。你还将在你的系统中包含两个 LED,一个用于指示警报是否已启动,另一个用于指示警报是否已被触发,以及一个用于启动和禁用传感器的按钮。

按照以下步骤构建入侵者警报电路,使用图 10-3 作为参考。

image

图 10-3: 入侵者警报电路

  1. 将 Pi 的 GND 连接到面包板的蓝色导轨之一。

  2. 将红色 LED 和绿色 LED 插入面包板。通过一个 330 Ω 电阻将绿色 LED 的正极引脚连接到 GPIO 18,电阻位于 LED 引脚和 GPIO 引脚之间,并将负极引脚连接到 GND 导轨。通过另一个 330 Ω 电阻将红色 LED 的正极引脚连接到 GPIO 17,并将负极引脚连接到 GND 导轨。

  3. 将按钮插入面包板的中间,使其桥接中心断裂处,如图 10-3 所示。将右下角的引脚连接到 GND 导轨,将左下角的引脚连接到 GPIO 2。

  4. 按照下表中的连接方式连接 PIR 运动传感器。

PIR 动作传感器 树莓派
GND GND
输出 GPIO 4
VCC 5 V

编写脚本

打开Python 3 (IDLE),并进入文件新建文件,创建一个新的脚本。将清单 10-2 中的代码输入新文件,并将脚本保存为intruder_alarm.py,保存在Sensors文件夹中(记得你可以在www.nostarch.com/RaspberryPiProject/下载所有脚本):

清单 10-2: 入侵者报警脚本

  #import necessary libraries
➊ from gpiozero import LED, Button, MotionSensor
  import smtplib
  from email.mime.text import MIMEText
  from signal import pause

  #create objects to refer to each LED, the button, and the PIR sensor
➋ led_status = LED(17)
  led_triggered = LED(18)
  button = Button(2)
  pir = MotionSensor(4)

  #control variables
➌ motion_sensor_status = False
  email_sent = False

  #arm or disarm the PIR sensor
➍ def arm_motion_sensor():
      global email_sent
      global motion_sensor_status
      if motion_sensor_status == True:
          motion_sensor_status = False
          led_status.off()
          led_triggered.off()
      else:
          motion_sensor_status = True
          email_sent = False
          led_status.on()

  #send email when motion is detected and the PIR sensor is armed
➎ def send_email():
      global email_sent
      global motion_sensor_status
      if(motion_sensor_status == True and email_sent == False):

          #replace the next three lines with your credentials
          from_email_addr = '*YOUR_EMAIL@gmail.com*'
          from_email_password = '*YOUR_EMAIL_PASSWORD*'
          to_email_addr = '*TO_YOUR_OTHER_EMAIL@gmail.com*'

         #set your email message
          body = 'Motion was detected in your room.'
          msg = MIMEText(body)

          #set sender and recipient
          msg['From'] = from_email_addr
          msg['To'] = to_email_addr

          #set your email subject
          msg['Subject'] = 'INTRUDER ALERT'

          #connect to server and send email
          #edit this line with your provider's SMTP server details
          server = smtplib.SMTP('*smtp.gmail.com*', *587*)
          #comment out this line if your provider doesn't use TLS
          server.starttls()
          server.login(from_email_addr, from_email_password)
          server.sendmail(from_email_addr, to_email_addr,
  msg.as_string())
          server.quit()
          email_sent = True
          led_triggered.on()
          print('Email sent')

  #assign a function that runs when the button is pressed
➏ button.when_pressed = arm_motion_sensor
  #assign a function that runs when motion is detected
➐ pir.when_motion = send_email

➑ pause()

这段代码非常简单,应该和清单 10-1 中的内容非常熟悉。首先,导入所需的库 ➊,并创建gpiozero对象来引用 LED、按钮和动作传感器 ➋。接着在 ➌ 处创建motion_sensor_statusemail_sent控制变量,以确定动作传感器是否被触发,以及邮件是否已发送。然后创建arm_motion_sensor()函数,当你按下按钮时,这个函数会启动和关闭动作传感器 ➍。在 ➎ 处,send_email()函数会在传感器检测到运动时发送电子邮件,前提是传感器被激活且email_sent变量为False

最后,你为事件分配函数:当按下按钮时,会调用arm_motion_sensor()函数 ➏,而当检测到运动时,会调用send_email()函数 ➐。代码末尾的pause()函数使得脚本持续运行,以便检测事件 ➑。

注意,send_email()函数中有一个if语句条件,只有在检测到运动并且email_sent变量为False时,脚本才会发送电子邮件。当电子邮件发送后,email_sent变量会变为True,此时脚本将不再发送电子邮件。你可以通过按下按钮两次将email_sent变量重置为False,从而重新激活报警。

这个条件防止脚本发送大量不必要的电子邮件。例如,如果你把狗留在家里,而它触发了传感器;有了这个条件,你只会收到一封说检测到运动的电子邮件。如果没有这个条件,你将会收到无尽的邮件,直到你的狗离开传感器范围。

按下F5或进入运行运行模块来运行脚本。按下按钮激活传感器;红色状态 LED 应亮起。通过在动作传感器前挥动手测试报警。当运动被检测到时,你应该会在收件箱中收到一封新邮件,并且触发的绿色 LED 会亮起。

将此电路放置在一个战略位置,等待看看是否有人在你不在时进入你的房间。

进一步拓展

本项目演示了如何使用 PIR 移动传感器与树莓派配合使用,以及如何通过 Python 发送电子邮件。这些是非常实用的技能,你可以将它们与其他项目中学到的知识结合起来,发明你自己的设备。以下是一些你可以使用移动传感器构建的简单项目想法:

  • 在你的报警电路中加入一个蜂鸣器,这样当检测到运动时,不仅会发送电子邮件,还会触发报警声。

  • 自动化控制你房间的灯光,使其在你进入时自动打开。你可能需要一个继电器来实现这一功能—请参考项目 16,其中我们介绍了如何使用继电器。

  • 使用继电器和光敏电阻制作一个安全夜灯,只有在黑暗中检测到运动时才会打开。

燃气和烟雾报警器**

在这个项目中,你将使用 MQ-2 燃气和烟雾传感器以及蜂鸣器构建一个燃气和烟雾报警器。每次传感器检测到大气中超过一定阈值的气体或烟雾时,蜂鸣器就会响起。

image

所需部件

树莓派

面包板

MQ-2 燃气和烟雾传感器

MCP 3008 芯片

蜂鸣器

5 毫米 LED

330 Ω 电阻

按钮

打火机

跳线

介绍 MQ-2 燃气和烟雾传感器

MQ-2 燃气和烟雾传感器对烟雾以及以下可燃气体敏感:丙烷、丁烷、甲烷、酒精和氢气。图 11-1 展示了传感器的正反面。

image

图 11-1: MQ-2 燃气和烟雾传感器,正反面图

MQ-2 具有两种输出气体浓度的方式。第一种方式是读取大气中的气体浓度,并通过模拟输出引脚 AO 输出模拟信号,气体浓度越高,输出电压越高。

第二种方式是设定一个阈值,如果气体浓度超过该阈值,则从数字输出引脚 DO 输出高电平信号,如果气体浓度低于该阈值,则输出低电平信号。MQ-2 后面内置有一个电位器,你可以用螺丝刀调整它来改变这个阈值。

传感器背面还配有一个电源 LED,指示传感器是否开启,以及一个数字输出 LED,当检测到的气体浓度超过设定阈值时,LED 会亮起。

你将读取模拟信号,该信号提供了气体浓度的定量测量,使你能够更好地定义阈值,超过该阈值时,蜂鸣器会警告你气体浓度过高。请记住,树莓派只能读取数字信号,因此,要使用树莓派读取模拟信号,你将使用一个模拟到数字转换模块(MCP3008 芯片),该模块在项目 3 中首次介绍。

介绍蜂鸣器

当蜂鸣器接收到来自树莓派的数字信号时,它会发出警报。你将使用的蜂鸣器,如图 11-2 所示,是非常简单的。

image

图 11-2: 蜂鸣器

蜂鸣器的外壳内包含一个在接收到电压时按特定频率振动的圆盘。接线压电蜂鸣器很简单。你需要做的就是将一根电线连接到树莓派的 GND 引脚,另一根连接到一个 GPIO 引脚。

电路接线

要构建烟雾和气体探测器报警电路,你需要将 LED 和按钮连接到树莓派;你应该已经知道如何根据之前的项目进行接线。你还需要将压电蜂鸣器和 MQ-2 传感器连接到树莓派——后者通过 MCP3008 芯片连接)。按照这些说明,使用 图 11-3 作为参考。

image

图 11-3: 烟雾和气体探测器电路图

  1. 将 GND 连接到蓝色面包板导轨,将 3.3 V 连接到红色导轨。

  2. 将 MCP3008 芯片放置在面包板的中央,使其引脚平行地位于中央分隔线两侧,如 图 11-3 所示,并按照下表接线。

    MCP3008 连接到
    1 MQ-2 AO 引脚
    9 GND
    10 GPIO 8
    11 GPIO 10
    12 GPIO 9
    13 GPIO 11
    14 GND
    15 3.3 V
    16 3.3 V

    记住,当 MCP3008 的半圆形标记在顶部时,引脚 1 是左侧的顶部引脚;完整的 MCP3008 引脚图请参见 “模拟到数字转换器” 第 55 页 ([ch03.xhtml#page_55])。

  3. 将 MQ-2 气体和烟雾传感器插入面包板,并按照指示接线。

    MQ-2 传感器 连接到
    VCC 5 V
    GND GND
    DO 无连接
    AO MCP3008 引脚 1
  4. 将 LED 插入面包板。通过一个 330 Ω 电阻将正极引线连接到 GPIO 17,将负极引线连接到 GND 导轨。

  5. 将按钮插入面包板的中央,确保两个引线位于中心分隔线的两侧。将右下方的引线连接到 GND 电源轨,将左下方的引线连接到 GPIO 2,确保连接的两个引线位于分隔线的同一侧。

  6. 将蜂鸣器插入面包板,并将黑线连接到 GND,将红线连接到 GPIO 27。

电路接好后,接下来就是上传代码了。

编写脚本

打开 Python 3 (IDLE),然后转到 文件新建文件 来创建一个新的脚本。将 清单 11-1 中的代码复制到 Python 编辑器,并将脚本保存为 smoke_detector.py,保存在 Sensors 文件夹中。(记住,你可以从 www.nostarch.com/RaspberryPiProject/ 下载所有脚本):

清单 11-1: 烟雾和气体探测器脚本

  #import necessary libraries
➊ from gpiozero import LED, Button, Buzzer, MCP3008
  from time import sleep

➋ led = LED(17)
  button = Button(2)
  buzzer = Buzzer(27)
  gas_sensor = MCP3008(0)

➌ gas_sensor_status = False

➍ threshold = 0.1

➎ def arm_gas_sensor():
      global gas_sensor_status
      if gas_sensor_status == True:
          gas_sensor_status = False
          led.off()
      else:
          gas_sensor_status = True
          led.on()
➏ button.when_pressed = arm_gas_sensor

➐ while True:
➑     #print(gas_sensor.value)
      #check if the gas sensor is armed and
      #reached the threshold value
      if(gas_sensor_status == True and gas_sensor.value > threshold):
          buzzer.beep()
      else:
          buzzer.off()
      sleep(2)

首先,您从 gpiozero 库导入 LEDButtonBuzzerMCP3008 类,以及从 time 库导入 sleep 函数 ➊;然后,您创建 gpiozero 对象来引用 LED、按钮、MCP3008(MQ-2 气体传感器)和蜂鸣器 ➋。接下来,您创建一个 gas_sensor_status 变量,用来指示烟雾传感器是否已武装 ➌;如果该变量为 True,则表示传感器已武装,如果为 False,则表示未武装。您需要设置一个 threshold 值,以便只有当气体浓度超过该阈值时,蜂鸣器才会响起 ➍。我们稍后将介绍如何找出您的阈值值。

arm_gas_sensor() 函数 ➎ 通过将 gas_sensor_status 变量的值切换为当前值的相反值(无论是 True 还是 False),来使传感器处于武装或解除武装状态。当该函数被调用时,它会执行这一操作。 ➏ 时,您设置函数在按下按钮时调用,这样您就可以手动武装或解除武装传感器。您还设置了一个 LED,当传感器被武装时,它会亮起;这样,您就可以通过视觉识别其状态。

最后一段代码是一个 while 循环 ➐,它会不断检查传感器是否已武装,以及气体浓度是否超过阈值。如果传感器已武装且气体浓度超过阈值,蜂鸣器会通过 buzzer.beep() 函数发出声音。最后,buzzer.off() 函数会停止蜂鸣器。

设置阈值

为了准确设置安全的气体浓度阈值,您首先需要根据环境来校准传感器。这意味着您需要在没有气体存在时测量气体浓度,然后将阈值设置为略高于此值。首先,找出您所在环境的气体浓度通常是多少:

  1. 取消注释 ➑ 处的代码行,然后保存并运行脚本。

  2. 您应该能在 Python shell 中看到 gas_sensor 的值。这些值是在传感器范围内没有气体或烟雾时读取的值。您的 threshold 值应该稍高于这些值。例如,如果您的默认值是 0.07,我们建议将阈值设置为 0.1,但这取决于您所需的灵敏度水平。

  3. 拿一个打火机并按下触发器(不要点燃打火机)。将打火机靠近传感器,释放一些气体。Python shell 中显示的 gas_sensor 值应该会增加。您的 threshold 值应该低于暴露传感器到气体时获得的最大值。

    警告

    使用打火机和气体时一定要小心;不要长时间按住气体触发器,且不要在气体已释放到空气中时点燃打火机。

  4. 根据上两步获得的值,调整 threshold 值 ➍ 使其介于这两者之间,这样它既不太灵敏,也不太迟钝。

  5. 注释掉 ➑ 处的 print 语句并保存脚本。

运行脚本

按下F5或选择运行运行模块来运行脚本。按下按钮以启动传感器。红色 LED 灯应该亮起。然后,使用打火机将一些气体释放到传感器旁,直到蜂鸣器响起,测试一下。

警告

此燃气和烟雾报警器不应替代现成的烟雾探测器。

恭喜!你现在拥有了一套燃气和烟雾报警器,可以监控你的家并警告你火灾发生!

进一步扩展

本项目的目的是向你展示如何读取输出模拟信号的传感器。现在,你可以为这个项目添加更多功能。例如,你可以修改脚本,当燃气或烟雾超过阈值时发送电子邮件,正如我们在项目 10 中所做的那样。

温湿度数据记录器**

在本项目中,你将构建一个数据记录器,自动存储温度和湿度数据。你将学习如何读取和记录来自环境的数据,这在许多应用中都非常有用。

image

所需组件

树莓派

面包板

DHT22 温湿度传感器(DHT11 和 AM2302 也可以使用)

4.7 kΩ电阻

跳线

所需软件

Adafruit_Python_DHT 库

你将使用 DHT22 温湿度传感器来收集数据,这些数据将保存在一个.txt文件中,之后可以用于构建图表、图形和其他可视化内容。本项目为你提供了数据收集的基础,这在许多使用传感器的不同应用中非常有用——例如,监测土壤湿度、测量鱼缸水温,甚至在你家周围检测到意外运动的准确时间。你可以将本项目中的概念应用到任何传感器上。

介绍 DHT22 传感器

DHT22(见图 12-1)是一款数字温湿度传感器,内置芯片将模拟信号转换为数字信号,因此无需使用模拟到数字转换器。这使得布线变得非常简单。

image

图 12-1: DHT22 温湿度传感器

电路接线

这是一个简单的电路,只需要通过电阻将 DHT22 传感器连接到你的 Pi。按照这些说明,并参考图 12-2 中的电路图。

  1. 将 Pi 上的 GND 和 3.3 V 分别连接到面包板的蓝色和红色电源轨。

  2. 按照以下表格连接 DHT22 传感器,传感器面对你时,插针从左到右编号为 1。确保将电阻连接在传感器的第 2 脚和面包板的红色电源轨之间。

DHT22 树莓派
1 3.3 V
2 GPIO 4;还需通过一个 4.7 kΩ电阻连接至 3.3 V
3 不连接
4 GND

image

图 12-2: 将 DHT22 传感器连接到 Pi

编写脚本

你将使用 Adafruit_Python_DHT 库,它可以让你轻松控制 DHT22 传感器并读取数据。

安装 DHT22 库

如果你使用的是类似的传感器,如 DHT11 或 AM2302(Adafruit 版 DHT22 的有线版本),这个库也可以使用。

打开终端并输入以下内容:

pi@raspberrypi:~ $ sudo apt update
pi@raspberrypi:~ $ sudo apt install build-essential python-dev

从终端开始,进入桌面,如果尚未创建名为Libraries的文件夹,则创建该文件夹,然后进入新创建的文件夹:

pi@raspberrypi:~ $ cd ~/Desktop
pi@raspberrypi:~/Desktop $ mkdir Libraries
pi@raspberrypi:~/Desktop $ cd Libraries
pi@raspberrypi:~/Desktop/Libraries $

使用以下命令克隆该库:

pi@raspberrypi:~/Desktop/Libraries $ git clone https://github.com/
adafruit/Adafruit_Python_DHT.git

最后,进入Adafruit_Python_DHT目录,并使用以下命令安装库:

pi@raspberrypi:~/Desktop/Libraries $ cd Adafruit_Python_DHT
pi@raspberrypi:~/Desktop/Libraries/Adafruit_Python_DHT $ sudo python
setup.py install

安装完必要的库后,接下来是编写脚本的步骤。

输入脚本

DHT22 库不支持 Python 3,因此你需要使用 Python 2.7。打开Python 2.7 (IDLE),然后进入文件新建文件以创建一个新的脚本。将 Listing 12-1 中的代码复制到 Python 编辑器,并将脚本保存为temperature_humidity_data_logger.py,并保存在Sensors文件夹中(记得你可以从www.nostarch.com/RaspberryPiProject/下载所有脚本):

LISTING 12-1: 温湿度数据记录脚本

  #import necessary libraries
➊ import Adafruit_DHT
  import time

  #comment and uncomment the lines below depending on your sensor
  #sensor = Adafruit_DHT.DHT11
➋ sensor = Adafruit_DHT.DHT22
  #sensor = Adafruit_DHT.AM2302

  #DHT pin connects to GPIO 4
  sensor_pin = 4

  #create a variable to control the while loop
  running = True

  #new .txt file created with header
➌ file = open('sensor_readings.txt', 'w')
➍ file.write('time and date, temperature, humidity\n')

  #loop forever
  while running:
      try:
          #read the humidity and temperature
       ➎ humidity, temperature = Adafruit_DHT.read_retry(sensor,
  sensor_pin)

          #uncomment the line below to convert to Fahrenheit
       ➏ #temperature = temperature * 9/5.0 + 32

          #sometimes you won't get a reading and
          #the results will be null
          #the next statement guarantees that
          #it only saves valid readings
       ➐ if humidity is not None and temperature is not None:
              #print temperature and humidity
              print('Temperature = ' + str(temperature) +
  ', Humidity = ' + str(humidity))
              #save time, date, temperature and humidity in .txt file
           ➑ file.write(time.strftime('%H:%M:%S %d/%m/%Y') + ', ' +
                  str(temperature) + ', ' + str(humidity) + '\n')
          else:
              print('Failed to get reading. Try again!')
          #wait 10s between each sensor reading
       ➒ time.sleep(10)
      #if KeyboardInterrupt triggered, stop loop and close .txt file
      except KeyboardInterrupt:
          print ('Program stopped')
          running = False
       ➓ file.close()

首先,导入你刚刚安装的 Adafruit_DHT 库➊以及内置的 time 库。然后,在➋处取消注释与你使用的传感器对应的那一行。如果你使用的是 DHT22,就无需做任何更改。

➎处的代码读取温湿度并将读数分别保存在temperaturehumidity变量中。如果你希望温度读取为华氏度,请取消注释➏处的代码行来进行摄氏度到华氏度的转换。

有时传感器无法读取数据,并将null结果发送到 Pi。➐处的if语句确保只有当数据不为null时,Pi 才保存数据。你还使用time.strftime("%H:%M:%S %d/%m/%Y")为每次读取添加时间戳——括号中的参数表示你希望时间和日期以小时、分钟、秒、日、月和年为顺序显示。

这个脚本每隔 10 秒读取并记录一次温湿度,但你可以通过在➒处更改延迟时间来调整这个间隔。传感器每 2 秒可以读取一次数据,但不能更快。

创建、写入和关闭.txt 文件

DHT22 的温湿度读数会自动保存在一个.txt文件中,该文件通过open()函数➌创建并存储在file变量中。此函数接受你希望文件名作为参数,在本例中,'w'表示你希望文件处于写入模式,即程序可以写入并修改文件。写入模式会覆盖同名的现有文件。

注意

每次运行代码时,它都会覆盖 sensor_readings.txt 文件中已经存在的内容。如果你不希望发生这种情况,请更改第➍处的文件名,以便每次运行脚本时都创建一个新文件。

file.write() 函数向文件写入内容,并接受一个字符串作为参数。例如,在第➍处使用file.write('time and date, temperature, humidity\n'),你将“time and date, temperature, humidity”写入文件。在第➐处,你将传感器数据写入文件,在第➑处写入时间戳。\n告诉 Python 在下一行开始显示文本,这叫做换行

最后,file.close() 函数 ➓ 保存并关闭文件。

运行脚本

F5键或进入运行运行模块来运行脚本。让脚本运行几个小时,以收集足够的数据,当你对数据记录时间满意时,通过按 CTRL-C 停止脚本。然后,你应该会在Sensors文件夹中得到一个包含所有数据的sensor_readings.txt文件。

进一步深入

在这个项目中,你学到了一个非常有用的概念:数据记录。现在你可以在其他监测项目中使用数据记录。这里有一些想法:

  • 使用 PIR 运动传感器,每当它检测到运动时,会生成一个时间戳。

  • 使用 Sense HAT 构建一个天气站数据记录器。

  • 搜索其他监测传感器应用程序——例如土壤湿度、雨量和光照传感器——以构建一个温室数据记录器。

第五章:摄像头

带有照片捕捉功能的入侵检测器

本项目将教你如何使用 Raspberry Pi Camera Module v2,结合 PIR 动态传感器,它将用于检测并拍摄入侵者的照片。当运动传感器探测到运动时,它会触发拍照事件,帮助你知道谁在你外出时进入了你的家。

image

所需组件

Raspberry Pi

面包板

Raspberry Pi Camera Module v2

PIR 动态传感器 HC-SR501

按钮

跳线

介绍 Raspberry Pi 摄像头模块 V2

如 图 13-1 所示,Raspberry Pi Camera Module v2 配备了一颗 8 MP 的 Sony IMX219 图像传感器,具有固定焦距镜头。它支持 3280×2464 像素的静态图像,并支持 1080p 30 帧、720p 60 帧和 640×480 90 帧的视频分辨率——这些都意味着它在这个尺寸下是非常不错的摄像头!在本项目中,你将只使用其静态图像功能。

image

图 13-1: Raspberry Pi Camera Module v2

该摄像头与所有 Raspberry Pi 型号(1、2、3 和 Zero)兼容,并附带一根 15 cm 的带状电缆,使其可以轻松连接到 Raspberry Pi 上的 CSI 接口,该接口专为与摄像头连接而设计。如果你希望摄像头距离 Pi 超过 15 cm,你可以找到并购买更长的电缆。

Raspberry Pi Camera Module v2 是 Raspberry Pi 最受欢迎的扩展之一,因为它为用户提供了一种经济实惠的方式,可以拍摄静态照片并录制全高清的视频。一个有趣的 Camera Module v2 项目来自 Naturebytes 社区,该社区提供套件,能够远程捕捉野生动物照片。图 13-2 展示了野生动物相机的实际应用。

image

图 13-2: 配备 PIR 动态传感器的 Raspberry Pi 摄像头对准鸟类喂食器

Naturebytes 套件还配备了 PIR 动态传感器,因此,如果鸟类停在 图 13-2 中的喂食器上,它将触发摄像头拍摄鸟类的照片。你将使用相同的原理来构建本项目的入侵检测器。

构建入侵检测器

入侵检测器由一个 PIR 动态传感器、一个按钮和一个连接到 Pi 的摄像头模块组成。你将使用内置的 picamera 库,它可以简化对摄像头的控制。

启用摄像头

在你可以使用摄像头模块之前,你需要先启用 Pi 的摄像头软件。在桌面环境下,进入主菜单并选择 首选项Raspberry Pi 配置。你应该会看到类似于 图 13-3 中的窗口。

image

图 13-3: 启用摄像头软件

在摄像头行选择 启用,然后点击 确定,这样你就可以开始使用了。

连接摄像头

启用相机软件后,关闭你的 Pi,然后将相机连接到 CSI 端口。确保相机连接时蓝色字母朝上,并且按照图 13-4 所示的方向进行连接。然后重新启动 Pi。

image

图 13-4: 连接树莓派相机到 CSI 端口

接线电路

注意

在移动相机时要小心。排线非常脆弱,如果接触到 GPIO 端口,可能会永久损坏相机。可以尝试使用一些模型粘土或粘性胶带来固定相机。

连接相机后,按照以下说明连接电路的其余部分,参考图 13-5。

  1. 将一个 GND 引脚连接到面包板的 GND 轨道。

  2. 将按钮插入面包板,使其横跨中心分隔。将一条引脚连接到 GND,另一条引脚连接到按钮同侧的 GPIO 2。

  3. 按照下表所示的连接方式接线 PIR 运动传感器。

    PIR 运动传感器 树莓派
    GND GND
    OUT GPIO 4
    VCC 5 V

image

图 13-5: 入侵检测器电路

编写脚本

要控制相机,你将使用内置的 picamera 库。它是一个非常简单的库,所以这个脚本将轻松完成。下面是代码应该执行的操作概述:

  1. 初始化相机。

  2. 当 PIR 运动传感器检测到运动时,拍照。

  3. 将照片保存在你的桌面文件夹中。

  4. 按顺序命名照片,这样你就能知道它们拍摄的顺序——例如,image_1.jpgimage_2.jpg,依此类推。

  5. 当按下按钮时停止相机。如果不包括此功能,你将无法退出屏幕上弹出的相机预览。

输入脚本

转到你的项目文件夹,创建一个名为相机的新文件夹。然后打开Python 3 (IDLE),转到文件新建,创建一个名为burglar_detector.py的新脚本,并将以下代码复制到其中(记得你可以在www.nostarch.com/RaspberryPiProject/下载所有脚本)。

注意

你不能将文件命名为 picamera.py ,因为 picamera 是一个 Python 库名称,不能使用该名称。

  #import the necessary libraries
➊ from gpiozero import Button, MotionSensor
  from picamera import PiCamera
  from time import sleep
  from signal import pause

  #create objects that refer to a button,
  #a motion, sensor, and the PiCamera
➋ button = Button(2)
  pir = MotionSensor(4)
  camera = PiCamera()

  #start the camera
  camera.rotation = 180
➌ camera.start_preview()

  #create image names
➍ i = 0

  #stop the camera when the pushbutton is pressed
➎ def stop_camera():
      camera.stop_preview()
      #exit the program
      exit()

  #take a photo when motion is detected
➏ def take_photo():
      global i
      i = i + 1
      camera.capture('/home/pi/Desktop/image_%s.jpg' % i)
      print('A photo has been taken')
➐    sleep(10)

  #assign a function that runs when the button is pressed
➑ button.when_pressed = stop_camera
  #assign a function that runs when motion is detected
➒ pir.when_motion = take_photo

  pause()

首先导入所需的库 ➊;正如我们所说,程序使用 picamera 库来控制相机。你应该已经熟悉这里使用的其他模块,这些模块在之前的项目中都有使用。然后创建对象来引用按钮、PIR 运动传感器和相机 ➋,并通过camera.start_preview() ➌初始化相机。根据相机的方向,可能还需要使用camera.rotation = 180将其旋转 180 度,以免拍摄倒立的照片。如果测试代码时图像倒立,请返回并将旋转设置为0,或者注释掉这一行。

接下来,你初始化一个从 0 开始的 i 变量 ➍。在 ➏ 处定义的 take_photo() 函数将使用这个变量来计数并给图片编号,每拍一张照片,文件名中的数字就递增 1。

然后你定义了 stop_camera() 函数,使用 camera.stop_preview() 方法停止摄像头 ➎。在 ➏ 处,你定义了刚才提到的 take_photo() 函数,用来拍照。为此,你使用 camera.capture() 方法,并在括号中指定你希望保存图片的目录。在这种情况下,我们将图片保存到 Desktop 文件夹,并将图片命名为 image_%s.jpg,其中 %s 会被我们之前在 i 中递增的数字替代。如果你想将文件保存到其他文件夹,只需将该目录替换为你选择的文件夹路径。

接着,你添加了一个 10 秒的延迟 ➐,这意味着只要 PIR 传感器检测到运动,摄像头将每隔 10 秒拍一次照片。你可以自由增加或减少延迟时间,但要小心不要通过将延迟时间设置得过小来让 Pi 被大量图片压垮。

在 ➑ 处,你定义了按下按钮时触发 stop_camera() 函数的行为。这个函数停止摄像头预览并退出程序。exit() 函数会弹出一个窗口,询问你是否要关闭程序;要关闭它,只需点击确定。最后,当检测到运动时,你通过触发 take_photo() 函数来让摄像头拍照 ➒。

运行脚本

按下F5或进入运行运行模块来运行脚本。在脚本运行时,你应该能在屏幕上看到摄像头所看到的预览画面。要关闭摄像头预览,按下按钮并在弹出的窗口中点击确定

恭喜!你的入侵探测器已经准备好抓捕窃贼了。将入侵探测器放在一个战略位置,稍后回来查看任何保存的照片。图 13-6 显示了我们入侵探测器拍摄的一张照片,捕捉到有人从我们的实验室偷走一台电脑。

image

图 13-6: 用入侵探测器拍摄的照片

进一步探索

如你所见,带有摄像头的项目非常有趣!这是一个改进你安全系统的想法:重新设计你的项目,使得当传感器检测到运动时,Raspberry Pi 拍照,发送电子邮件通知,并触发警报。你应该已经掌握了如何利用第 9 项–第 12 项中学到的技能来完成这一切。

家庭监控摄像头**

在这个项目中,你将创建一个家庭监控摄像头系统,它将实时视频流传输到网页上,你可以通过任何连接到与 Raspberry Pi 相同网络的设备上的浏览器访问。这意味着你可以在不离开沙发的情况下监控家中的任何地方!

image

所需零件

Raspberry Pi

树莓派摄像头模块 v2

在本项目中,你需要将摄像头连接到树莓派,就像我们在“连接摄像头”中展示的那样,位于第 165 页。如果你还没有启用软件摄像头,请返回第 13 项目,按照说明设置摄像头,然后再继续。

将视频录制到文件

在构建你的家庭监控摄像头系统之前,你需要学习如何将视频录制到文件中。

参考第 13 项目,通过 CSI 端口将树莓派摄像头模块 v2 连接到你的树莓派。创建一个新的脚本文件,命名为record_file.py,在Python 3 (IDLE)中保存,并将其放入Cameras文件夹中,输入 LISTING 14-1 中的代码。

LISTING 14-1: 将视频录制到文件

➊ import picamera

➋ camera = picamera.PiCamera()

➌ camera.resolution = (640, 480)
➍ camera.start_recording('videotest.h264')
➎ camera.wait_recording(60)
➏ camera.stop_recording()

  print('Finished recording')

和往常一样,首先导入 picamera 库来控制摄像头 ➊。你创建一个名为camera的对象来引用摄像头 ➋,然后将摄像头的分辨率设置为 640×480 ➌。摄像头的分辨率是可配置的;视频录制的最大分辨率为 1920×1080,最小分辨率为 64×64。为了启用最大分辨率,还需要通过添加camera.framerate = 15这一行代码来设置帧率为 15。你现在可以尝试使用不同的分辨率测试此脚本,看看哪种最适合你,或者你可以先使用我们的设置,稍后再进行调整。

然后,摄像头开始录制到名为videotest.h264的文件中 ➍。当然,你可以更改文件名,但应保持文件扩展名为.h264,因为这是视频文件的格式。接着,你需要指定摄像头录制的时间 ➎。在这个例子中,摄像头录制 60 秒。wait_recording()方法也会反复检查是否有错误,比如磁盘空间不足以继续录制。

最后,你停止视频录制 ➏,并打印一条消息表示录制已完成。按F5或转到运行运行模块来运行脚本。你的视频文件位于脚本所在的Cameras文件夹中。从终端中输入以下命令导航到视频文件夹并观看:

pi@raspberrypi:~ $ cd ~/Desktop/Projects/Cameras
pi@raspberrypi:~/Desktop/Projects/Cameras $ omxplayer videotest.h264

这会打开一个新窗口并全屏播放整个视频。Figure 14-1 显示了我们视频录制测试的截图。

image

FIGURE 14-1: 使用树莓派摄像头录制视频

编写脚本

现在是关键部分:你将构建一个托管在树莓派上的网页——也就是一个Web 服务器——用于直播视频流。(我们将在第 15 项目、16 项目和 17 项目中详细讨论 Web 服务器。)

本项目的脚本较为复杂,因此我们不会详细解释每一行。以下是代码应该执行的概览:

  1. 初始化 Web 服务器和 Pi 摄像头。

  2. 设置网页服务器,在树莓派的 IP 地址和端口 8000 下显示一个你可以使用 HTML 自定义的网页。

  3. 设置网页以显示摄像头的视频流。

  4. 使得网页服务器可以从任何连接到同一网络的浏览器访问。

输入脚本

打开Python 3 (IDLE),然后进入文件新建来创建一个新脚本。输入清单 14-2 中的代码,并将其保存在Cameras文件夹中,命名为surveillance_system.py(记得你可以从www.nostarch.com/RaspberryPiProject/下载所有脚本)。

这个脚本是基于* picamera.readthedocs.io/en/latest/recipes2.html*上的流媒体示例。

清单 14-2: 将视频流传输到网页

  import io
  import picamera
  import logging
  import socketserver
  from threading import Condition
  from http import server

➊ PAGE="""\
  <html>
  <head>
  <title>Raspberry Pi - Surveillance Camera</title>
  </head>
  <body>
  <center><h1>Raspberry Pi - Surveillance Camera</h1></center>
  <center><img src="stream.mjpg" width="640" height="480"></center>
  </body>
  </html>
  """

  class StreamingOutput(object):
      def __init__(self):
          self.frame = None
          self.buffer = io.BytesIO()
          self.condition = Condition()

      def write(self, buf):
          if buf.startswith(b'\xff\xd8'):
              #new frame, copy the existing buffer's content and
              #notify all clients it's available
              self.buffer.truncate()
              with self.condition:
                  self.frame = self.buffer.getvalue()
                  self.condition.notify_all()
              self.buffer.seek(0)
          return self.buffer.write(buf)

  class StreamingHandler(server.BaseHTTPRequestHandler):
      def do_GET(self):
          if self.path == '/':
              self.send_response(301)
              self.send_header('Location', '/index.html')
              self.end_headers()
          elif self.path == '/index.html':
              content = PAGE.encode('utf-8')
              self.send_response(200)
              self.send_header('Content-Type', 'text/html')
              self.send_header('Content-Length', len(content))
              self.end_headers()
              self.wfile.write(content)
          elif self.path == '/stream.mjpg':
              self.send_response(200)
              self.send_header('Age', 0)
              self.send_header('Cache-Control', 'no-cache, private')
              self.send_header('Pragma', 'no-cache')
              self.send_header('Content-Type', 
  'multipart/x-mixed-replace; boundary=FRAME')
              self.end_headers()
              try:
                  while True:
                      with output.condition:
                          output.condition.wait()
                          frame = output.frame
                      self.wfile.write(b'--FRAME\r\n')
                      self.send_header('Content-Type', 'image/jpeg')
                      self.send_header('Content-Length', len(frame))
                      self.end_headers()
                      self.wfile.write(frame)
                      self.wfile.write(b'\r\n')
              except Exception as e:
                  logging.warning(
                      'Removed streaming client %s: %s',
                      self.client_address, str(e))
          else:
              self.send_error(404)
              self.end_headers()

  class StreamingServer(socketserver.ThreadingMixIn,
  server.HTTPServer):
      allow_reuse_address = True
      daemon_threads = True

➋ with picamera.PiCamera(resolution='640x480', framerate=24) as
  camera:
      output = StreamingOutput()
      camera.start_recording(output, format='mjpeg')
      try:
          address = ('', 8000)
          server = StreamingServer(address, StreamingHandler)
          server.serve_forever()
      finally:
          camera.stop_recording()

清单 14-2 比我们之前写的脚本要复杂,并且解释视频流所需的每个类和函数超出了本书的范围,因此我们不会在这里详细介绍。

当然,仍然有定制的空间。你可以编辑网页的外观以及摄像头设置:

  • 在➊处,你使用 HTML 定义网页内容;在这里,你可以更改网页的标题和标题。有关 HTML 的更多信息,请查看项目 15,并学习如何使用 CSS 为网页设置样式。

  • 在➋处,你初始化了摄像头;在这里,你可以更改摄像头的分辨率和帧率。

运行脚本

F5或进入运行运行模块来运行脚本。脚本运行后,你的摄像头将向网页流式传输视频。要访问此网页,你需要找到树莓派的 IP 地址,并输入网址http://:8000,将替换为你树莓派的 IP 地址。

要找到树莓派的 IP 地址,请打开终端并输入以下命令:

pi@raspberrypi:~ $ hostname -I

这将打印出树莓派的 IP 地址,如图 14-2 中所示。

image

图 14-2: 查找树莓派的 IP 地址

恭喜你——你已经建立了自己的家庭监控系统!你可以通过连接到本地网络的计算机、智能手机或平板电脑浏览器访问视频流。在这个例子中,由于我们的 IP 地址是 192.168.1.112,我们输入http://192.168.1.112:8000。确保使用你自己的 IP 地址。

进一步探索

在这个项目中,你学会了如何录制视频,并且如何构建一个可以流式传输实时视频的网页服务器。你可以将所学内容与其他项目结合,进一步增强它们。例如:

  • 编辑项目 13,使得当树莓派在你外出时检测到家里有运动时,它会录制视频一段指定时间。

  • 使用项目 15 中学到的技巧,使用 CSS 自定义流媒体网页。

第六章:Web 应用程序

创建你的第一个网站

在这个项目中,你将构建一个简单的网站,其中包含标题、段落、图片、链接和按钮。你将使用 HTML 来创建页面,并使用 CSS 来进行样式设置。你在这里学到的技能可以用来构建任何你想要的网站。

image

所需部件

树莓派

设置项目文件

对于这个项目,你不需要任何电路;所有工作都在你的树莓派桌面计算机上完成。你将使用文本编辑器程序创建 HTML 和 CSS 文件。要访问文本编辑器,从桌面的主菜单进入配件文本编辑器

这个项目需要几个文件,因此我们建议你为这个项目创建一个专门的文件夹。在你的Projects目录下创建一个名为Web_Applications的新文件夹,然后再创建一个名为Project_15的项目文件夹。

打开文本编辑器后,使用 CTRL-N 创建两个空白文件;你需要为每个文件做一次。然后将这些文件保存在Project_15文件夹中,并将它们命名为index.htmlstyle.css,如图 15-1 所示。你可以在www.nostarch.com/RaspberryPiProject/下载脚本。

image

图 15-1: 创建 HTML 和 CSS 文件

设置 HTML 网页

HTML,即超文本标记语言,是用于创建网页的主要标记语言。网页浏览器是为了读取 HTML标签而设计的,这些标签告诉浏览器如何在页面上显示内容。让我们来看看标签是如何工作的。

设置基本内容

以下代码片段显示了 HTML 文档的基本结构。使用文本编辑器打开你的index.html文件,并输入列表 15-1 中的内容。

列表 15-1: HTML 网页的基本结构

  <!DOCTYPE html>
➊ <html>
➋ <head>
  </head>
➌ <body>
  </body>
  </html>

该文档只是 HTML 标签的列表,用< >符号括起来。任何 HTML 文档的第一行总是<!DOCTYPE html>。这告诉网页浏览器该文档是一个 HTML 文件。

剩余的结构需要被夹在<html> ➊和</html>标签之间,前者表示网页的开始,后者表示网页的结束。请注意,关闭标签必须在<符号后加上/,这对所有 HTML 的闭合标签都适用。然而,并不是所有的 HTML 标签都需要闭合标签,稍后你会看到。

HTML 文档有两个主要部分:头部和正文。头部,位于<head> ➋和</head>标签之间,是你插入 HTML 文档数据的地方,这些数据不会直接显示在页面上,但能为网页增加功能,如显示在浏览器标签中的标题、脚本、样式等。正文,位于<body> ➌和</body>标签之间,包含页面内容,如标题、文本、图片、表格等。

添加标题、标题和段落

要查看您的网页效果,请打开任务栏菜单并启动 Chromium 浏览器,然后将index.html文件拖入 Chromium 浏览器中。此时,您应该只看到一个空白页面,因为您尚未向 HTML 文件中添加任何内容。在本节中,您将添加标题、标题和段落。

使用标题标签

标题应放在<title></title>标签之间,这些标签应该放在<head></head>标签之间。为您的文件添加一个标题,如下所示:

<head>
<title>Max - The Dog</title>
</head>

注意

在保存 HTML 文件后,您可以简单地刷新网页,而无需再次将文件拖入浏览器;更改会立即更新。

如果您保存了index.html并再次将文件拖入浏览器,不要惊讶于仍然看到一个空白页面。标题会显示在浏览器标签中,而不是页面本身。我们将网页命名为“Max – The Dog”,但您可以将页面命名为任何您喜欢的名字。

使用标题标签

您可以使用标题来组织网页上的文本。标题标签以h开头,后面跟着一个数字,表示标题级别。例如,<h1></h1>是标题 1 的标签,即最高级别;<h2></h2>是标题 2 的标签,以此类推,直到标题 6,这是标题的最低级别。标题标签应放在<body></body>标签之间。现在,创建几个标题并将其放入文件的主体部分:

<body>
  <h1>MAX - THE DOG</h1>
  <h2>About Max</h2>
</body>

我们在页面上添加了两个标题:“MAX – THE DOG”是顶级标题,下面的标题是“关于 Max”。现在是时候在这些标题下添加一些文本了。

使用段落标签

大多数可读内容应该放在段落中。每个段落需要放在<p></p>标签之间。为您的文件添加一两个段落,如下所示:

  <h2>About Max</h2>
  <p>Howdy, I'm Max. I'm an aspiring police dog living in Portugal.
I enjoy barking, running, and playing catch. I like meat, bones, and
leftovers.</p>
  <p>My favorite TV series is Inspector Rex.</p>

现在,您已经有了自己的网页!可以随意添加任意数量的段落和标题。

查看您的网页

保存您的index.html文档并刷新网页。图 15-2 显示了此时 Max 的网页样子。

image

图 15-2: 一个简单的 HTML 页面

如您所见,HTML 除了将原始文本添加到页面外,几乎没有其他功能;它主要是由一系列段落组成,看起来并不漂亮。稍后,您将使用 CSS 来为细节进行样式设置,使页面看起来更加吸引人。

添加链接、图片和按钮

任何一个自尊的网页都不应仅仅包含文字。我们将展示如何添加图片、按钮、更多页面以及指向其他页面的链接。

包含超链接

要添加指向互联网上其他页面的超链接,可以将<a>标签放在<body></body>标签之间的任何位置。例如,您可以像这样插入指向《Inspector Rex》维基百科页面的超链接:

<a href="https://en.wikipedia.org/wiki/Inspector_Rex">Inspector
Rex</a>

a代表锚点,位于这些标签之间的文本称为锚文本,在此情况下是Inspector Rex。在<a>标签内部,我们包括了href属性,它指定了链接应指向的地址。锚文本是页面访问者在页面上看到的内容;当他们点击该文本时,Inspector Rex 的维基百科页面会打开。

大多数网站都有多个页面,你可以在这些页面之间进行导航。页面之间的导航也是通过超链接完成的。例如,如果你想插入一个新页面——比如 Max 的照片画廊——你可以像这样链接到它:

<a href="gallery.html">Take a look at my gallery</a>

单独使用该链接不会带你到任何地方,因为它指向的页面还不存在。你需要创建gallery.html文件并将其保存在与你的index.html文件相同的文件夹中。在这个项目中你不会创建其他页面,但现在你知道如何去做了。

包含图片

图片让任何网站看起来更吸引人。要插入图片,你使用<img>标签,它没有闭合标签,因为你在尖括号<>中列出要显示的图片。你想要包含的图片文件必须保存在网站的文件夹中——在此情况下是Project_15——才能显示在网页上。要插入名为max.jpg的图片,你使用以下代码:

<img src="max.jpg">

src表示图片文件的位置。因为我们将图片保存在网站的文件夹中,所以只需使用图片文件名即可。在代码中将max.jpg替换为你自己的图片文件名。你也可以通过将max.jpg替换为超链接,使用指向图片的链接。

你可以通过手动编辑图片文件或在<img>标签内使用height属性来调整图片的大小。例如,要将图片高度调整为 350 像素,你可以使用:

<img src="max.jpg" height="350">

宽度会根据高度自动调整。你也可以使用width属性来改变宽度,且高度会自动调整。

<img>标签必须位于<p><div>标签内部——你可以使用<div>标签在你的页面内容中创建一个部分(参见 Listing 15-2)。

包含按钮

要在你的页面中包含一个按钮,插入你想要的按钮标签,放在<button></button>标签之间。我们想插入一个指向有趣警犬追逐视频的按钮,所以我们使用:

<button>Funny police dog chase</button>

为了使按钮可点击并将你重定向到视频页面,在<a>超链接标签之间插入按钮标签,指向相关的视频:

<a href="https://youtu.be/znM9YD2J3Cw"><button>Funny police dog
chase</button></a>
查看 Max 的 HTML 文件

我们已经在 Max 的网页中添加了一张图片、一个链接和一个按钮。Listing 15-2 中的代码显示了目前 HTML 文档的样子。

LISTING 15-2: Max 网页的 HTML 文档,包含一个链接、一张图片和一个按钮

  <!DOCTYPE html>
  <html>
  <head>
  <title>Max - The Dog</title>
  </head>
  <body>
➊ <header>
➋   <div class="title">
      <h1>MAX - THE DOG</h1>
➌   </div>
➍ </header>

➎ <main>
    <h2>About Max</h2>
    <p>Howdy, I'm Max. I'm an aspiring police dog living in Portugal.
  I enjoy barking, running, and playing catch. I like meat, bones, and
  leftovers.</p>
    <p>My favorite TV series is <a href="https://en.wikipedia.org/
  wiki/Inspector_Rex">Inspector Rex</a>.</p>
    <p><img src="max.jpg" width="350"></p>
    <a href="https://youtu.be/znM9YD2J3Cw"><button>Funny police dog
  chase</button></a>
➏ </main>
  </body>
  </html>

请注意,我们添加了三个新标签:

  • <header> ➊和</header> ➍包含了介绍性内容。在这些标签内部是一级标题。

  • <div class="title"> ➋ 和 </div> ➌ 用于定义一个区块或部分。class属性名为title,表示 HTML 元素是某个特定类的成员——在这个例子中是title类,但你可以为它指定任何你想要的名字。使用class属性很有用,因为它允许你为属于同一类的多个元素在 CSS 中定义相同的样式。

  • <main> ➎ 和 </main> ➏ 用于指定文档的主要内容。这里应该放置文档特有的内容——即网站中不重复的内容。单个 HTML 文档中不应有多个<main>元素。

我们这样构建 HTML 内容,因为这使得以后使用 CSS 来格式化页面更加容易。图 15-3 展示了在列表 15-2 中的 HTML 文件下,网页的外观。

image

图 15-3: Max 的网页,包含链接、图像和按钮

使用 CSS 样式化你的页面

现在你将添加CSS(层叠样式表),这是一种用于描述网页元素渲染后外观的样式表语言。你可以将 CSS 直接添加到 HTML 文件中,或者在一个单独的文件中进行引用。在这个项目中,你将为 CSS 文档创建一个单独的文件;这样,阅读 CSS 和 HTML 文件时会更加清晰,便于理解它们的作用。

嵌入样式表

在进入 CSS 文档本身之前,你需要将样式表嵌入到index.html文件中,这样 HTML 就知道要引用外部 CSS 文件。为此,在<head></head>标签之间添加以下行:

<link rel="stylesheet" type="text/css" href="style.css">

这个<link>标签告诉 HTML 文件你正在使用外部样式表来格式化页面的外观。rel属性指定外部文件的性质,在这种情况下,它是一个样式表——CSS 文件——将用于改变页面的外观。type属性设置为"text/css",表示你正在使用 CSS 文件来定义样式。href属性指示文件的位置;同样,由于文件位于网站的文件夹中,你只需要引用文件名。

在你添加了指向样式表的链接后,你的头部应该像列表 15-3 一样。

列表 15-3: 在 HTML 文档中链接到 CSS 样式表

<head>
    <title>Max - The Dog</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>

使用这个额外的标签,样式表与 HTML 文档连接起来。创建一个单独的样式表是很有用的,因为你可以通过一行代码将同样的样式表嵌入到多个网页中。

样式化你的 HTML 内容

要样式化你的 HTML 内容,CSS 使用选择器来定义一组 CSS 规则应用于哪些元素。它们有属性,而这些属性又有。代码将如下所示:

*selector* {
    *property*: *value*;
}

定义某个选择器样式的规则集应位于大括号{}之间。你用冒号(:)为属性赋值,每个值后面应以分号(;)结尾。每个选择器可以有多个属性,通常也有多个。

样式化头部

如果你按照指示操作,你应该在Project_15文件夹中有一个style.css文件以及一个index.html文件。打开style.css文件并通过输入列表 15-4 中的内容来编辑头部样式。

列表 15-4: 使用 CSS 样式化头部

  header {
➊   background: url(background.jpg);
➋   background-size: cover;
➌   height: 70vh;
  }

在这个列表中,header是选择器,这意味着你将在大括号中描述的样式规则将应用于网页的头部区域。记住,头部区域是位于<header></header>标签之间的内容,通常包括介绍性内容。在这里,我们插入了一张背景图像——一张 Max 的美丽照片。为此,我们使用background属性,其值为url(background.jpg) ➊;在url()内,你写入图像的路径目录。将background.jpg替换为你希望用作背景的图像名称。因为你要将图像文件保存在与 HTML 和 CSS 文件相同的文件夹中,所以只需引用图像文件名。

cover值赋给background-size属性➋。这表示背景图像应该填充整个头部区域。

height属性的值为70vh ➌,表示头部的高度。你可以使用多种单位来设置高度,但我们使用vh(视口高度),它相对于视口高度的 1%的比例,确保头部元素能够适应视口。这条指令意味着无论浏览器窗口的大小如何,头部将占据网页窗口的 70%。你可以尝试其他百分比值,看看页面效果如何。

保存 CSS 文件并刷新包含 HTML 文档的浏览器标签,以查看你新样式化的网页效果。

样式化 title 类

现在你将编辑title类,它包含任何位于<div class="title"></div>标签之间的内容。这些标签位于<header></header>标签之间,这意味着headertitle类的祖先。将列表 15-5 中的代码片段添加到你的 CSS 文件中。

列表 15-5: 样式化title

  .title {
➊   position: absolute;
➋   top: 50%;
➌   left: 50%;
➍   transform: translate(-50%, -40%);
➎   color: white;
➏   text-align: center;
  }

要选择具有特定类的元素,使用一个点(.)后跟类名,如.title。我们将一步步教你每个元素的操作。

调整位置

你可以使用topbottomleftright属性来定位元素,但首先你需要设置position属性➊。

元素在页面上的定位方式取决于position值是否设置为staticrelativefixedabsolute

位置属性

position 属性可以具有以下值:

static

一个 static 值的元素根据页面的正常流进行定位,并且不受 topbottomleftright 属性的影响。默认情况下,HTML 元素是 static

relative

一个 relative 值的元素相对于其默认位置进行定位,使用 topbottomleftright 属性。

fixed

一个 fixed 值的元素即使在页面滚动时也会保持在相同的位置。要将元素定位在视口上,使用 topbottomleftright 属性。

absolute

一个 absolute 值的元素相对于其最近的祖先进行定位。要调整位置,使用 topbottomleftright 属性。

在这种情况下,我们使用的是 absolute 值。这意味着每个元素相对于其最近的祖先进行定位,在本例中是标题区域。查看图 15-4 以了解 title 类元素如何相对于标题进行定位。

image

图 15-4: 在标题区域内定位 title 类元素

top ➋ 和 left ➌ 属性指定了每个元素相对于其祖先的位置,以百分比形式表示,表示元素在页面中出现的位置,其中0%表示其祖先的最左边和最上边。topleft50%值将 title 类区域的左上角移动到其祖先的中间。这意味着 title 类区域的左上角,而不是其中心点,将被定位在标题区域的中心位置;请查看图 15-4(A)。你可以通过 transform 属性来调整此位置。

transform 属性 ➍,结合 translate(–50%, –40%) 值,可以移动元素的位置。–50% 值将 title 类元素相对于其大小向左移动 50%,这会使它在水平上居中于标题区域——请查看图 15-4(B)。我们还将 –40% 应用于垂直位置,将其从底部向上移动 40%(请参见图 15-4(C))。尝试使用不同的值,看看它如何变化。–50%–40% 对于 Max 的页面来说效果很好,但根据你的背景图像,你可能需要将文本移动到不同的位置。

设置文本颜色和对齐方式

color ➎ 和 text-align ➏ 属性分别定义了文本颜色和文本对齐方式。你可以通过颜色名称(HTML 识别基本颜色名称)或使用十六进制或 RGB 颜色代码来设置颜色。我们使用十六进制代码。你可以搜索十六进制颜色选择器来查找特定颜色的十六进制参考。text-align属性有几个值,如leftrightcenterjustify,用于将文本左对齐、右对齐、居中或两端对齐。

样式化标题、段落和链接

Listing 15-6 样式化标题、段落和链接。将这些样式添加到你的 CSS 文档中。

LISTING 15-6: 样式化标题、段落和超链接

  h1 {
➊   font-size: 4rem;
  }
  h2 {
    font-size: 2.5rem;
  }
  p {
    font-size: 1.3rem;
  }
➋ main {
    max-width: 500px;
    margin:0 auto;
  }
  a {
➌   text-decoration: none;
  }

要定义文本大小,请使用font-size属性 ➊。定义字体大小有几种单位,但我们将使用rem单位。Rem是根元素上font-size的计算值——我们使用一种字体大小作为页面的参考。这意味着所有字体大小都是相对的,减少了使用旧字体大小单位时可能出现的问题,因为这些单位在不同屏幕尺寸的设备上查看页面时可能会导致不规则性。

当在根元素的font-size属性中使用时,rem 单位指的是该属性的初始值。因此,1rem 等于 HTML 元素的字体大小——也就是大多数浏览器中默认的 16 像素。

在 Listing 15-6 中,我们分别为标题 1、标题 2 和段落定义了42.51.3rem 的字体大小,使每个标题级别比上一个标题稍小,最低级别的字体稍大于默认文本。

main部分格式化页面的主要内容 ➋。我们将内容的最大宽度设置为500px。定义元素的宽度可以防止元素扩展到其容器的边界;在这种情况下,它防止文本在网页浏览器窗口中水平扩展。然后,我们将边距设置为auto,以使元素水平居中。

超链接默认是带下划线的。将text-``decoration设置为none ➌以去除下划线。

样式化按钮

要样式化按钮,请将 Listing 15-7 中的样式复制到你的style.css文档文件中。

LISTING 15-7: 按钮样式

button {
  display: block;
  margin: 0 auto;
  padding: 10px 20px;
  font-size: 1.7rem;
  border-radius: 4px;
  color: #fff;
  background-color: #009933;
  border: none;
}

设置displayblock确保按钮以块级元素的形式显示,就像段落一样;例如,按钮将不会和文本元素在同一行。我们使用padding属性为内容添加空白区域。我们为按钮的上下边距设置10px,左右边距设置20px——这决定了按钮内容周围的空间。注意,这里我们使用十六进制颜色代码来设置按钮文本和按钮背景的颜色。其他按钮属性不言自明。你可以通过调整这些属性来定制按钮的样式。你也可以在网上搜索button properties CSS来了解更多属性和值。

保存你的style.css文件并刷新浏览器,以查看你所做的更改。现在你应该有一个简单的网页,类似于项目开始时展示的页面。

进一步发展

这个项目只是一个快速介绍,讲解如何使用 HTML 和 CSS 构建一个简单的网页。你可以通过无数种方式编辑和改进它。我们鼓励你尝试这里介绍的所有选项。为了获得灵感,你可以:

  • 向你的主页添加多个页面,并使用超链接相互连接。

  • 创建一个展示你树莓派项目的网页。

  • 在网上搜索更多 CSS 属性和值,并编辑网页的外观。

将你的电子设备连接到网络**

在这个项目中,你将创建自己的物联网网络服务器,使用手机远程控制灯泡。你构建的简单网络服务器可以添加到其他项目中,以便控制其他电子设备。

image

所需组件

树莓派

继电器模块 HL-52S

12 伏灯泡和灯座

12 伏电源适配器

母 DC 条形直流电源插座

塑料盒外壳

跳线

所需软件

Flask 框架

在这个项目中,你将创建自己的网络服务器,通过浏览器控制电子设备。你将使用动态网页和按钮来控制一个 12 伏灯泡。

介绍网络服务器

网络服务器是提供网页的计算机。它存储网站的文件,包括所有的 HTML 文档和相关的资源,如图片、CSS 样式表、字体和视频。当用户向服务器的 URL 发出请求时,它还会将这些文件传送到用户设备的网页浏览器中。

当你在浏览器中访问一个网页时,你实际上是通过超文本传输协议(HTTP)向服务器发送请求。这只是一个在互联网上请求和返回信息的过程。服务器通过 HTTP 将你请求的网页返回。

在这个项目中,你将使用你的 Raspberry Pi 在本地网络上托管一个网络服务器,如图 16-1 所示。

image

图 16-1: 你的树莓派正在运行一个网络服务器

作为服务器,树莓派可以通过其 GPIO 针脚提供一些输出。换句话说,使用本地网络上的浏览器,你可以访问树莓派的 Web 服务器,远程控制 GPIO 并打开某些设备。

Web 服务器可以提供静态动态内容。静态网站的内容除非你编辑其 HTML 文件,否则不会发生变化。项目 15 中构建的网站就是一个静态网站的例子。动态网站的内容会根据用户的交互发生变化。在这个项目中,你将创建一个动态网站,用于控制并显示连接到继电器的 12V 灯的当前状态,我们稍后会更详细地介绍。

注意

只有连接到与你的树莓派同一路由器的设备才能通过浏览器访问你树莓派上托管的网页。从 外部 网络访问你的 Web 服务器则更为困难。你可以通过使用一种叫做路由器端口转发的技术,使树莓派 Web 服务器可以从任何地方的计算机访问,但这一主题超出了本书的范围。

介绍继电器模块

继电器 是一种电动开关,可以打开或关闭,允许电流通过或阻止电流流动,并且可以通过低电压控制,例如树莓派提供的 3.3V。你将在本项目中使用的继电器模块有两个继电器——即图 16-2 中显示的两个蓝色方块。

image

图 16-2: 带有两个通道的继电器模块

继电器模块左侧的六个针脚连接高电压,而右侧的针脚连接需要低电压的组件——树莓派 GPIO。

继电器引脚分配

高压端有两个连接器,每个连接器有三个插孔:公共(COM)常闭(NC)常开(NO)。当你希望继电器默认闭合时使用常闭配置,意味着电流是流动的,除非你从树莓派向继电器模块发送信号来打开电路并停止电流。常开配置则相反:继电器始终保持打开状态,因此电路被切断,除非你从树莓派发送信号来闭合它。

低压端有一组四个针脚和一组三个针脚。第一组包括 VCC 和 GND,用于为模块供电,以及输入 1(IN1)和输入 2(IN2),分别用于控制底部和顶部的继电器。第二组针脚包括 GND、VCC 和 JD-VCC 针脚。JD-VCC 针脚为继电器的电磁铁供电。请注意,模块上有一个跳线帽将 VCC 和 JD-VCC 针脚连接在一起;这里显示的是蓝色的,但你的可能是其他颜色。

跳线帽允许你选择电路是否与树莓派电路物理连接,你可以选择是否将其打开。使用跳线帽时,VCC 和 JD-VCC 引脚会连接。这意味着继电器电磁铁直接由树莓派的电源引脚供电,因此继电器模块和树莓派电路在物理上是相互连接的。这就是我们将使用的配置。如果没有跳线帽,你需要提供独立的电源,通过 JD-VCC 引脚为继电器电磁铁供电。这种配置通过模块内置的光耦合器将继电器与树莓派物理隔离,从而在电气浪涌发生时保护树莓派不受损坏。

光耦合器

我们不会过多讨论光耦合器。它基本上是一个允许你在两个隔离电路之间发送信号的组件,这样你就可以用低电压控制高电压,而这两个电路之间实际上没有物理连接。光耦合器通过一个发光二极管(LED)发出光,再通过一个光敏晶体管接收光并激活或关闭继电器,从而在电路之间建立“连接”。

继电器使用

你将在这个项目中使用常开配置。你希望只有在你选择时才点亮灯泡,因此电路应该保持断开,直到你另行指示。为此,你将使用 COM 和 NO 插座。

在常开配置中,COM 和 NO 插座之间没有接触,除非你触发继电器。继电器在输入电压低于约 2V 时被触发。这意味着如果你从树莓派发送低电平信号,继电器会打开;如果发送高电平信号,继电器会关闭;这被称为反向逻辑。你只需控制一个继电器,因此你将 IN1 连接到树莓派的一个 GPIO 引脚。

警告

如果你不太熟悉处理像 12V 这样的高电压,但又想做这个项目,你可以用 LED 替代继电器模块。你还需要对 Python 脚本做一些小改动,因为继电器是使用反向逻辑,而 LED 则不是。

项目的硬件涉及连接一个可以提供 1A 电流的 AC 12V 电源适配器到继电器模块,用以控制 12V 的灯泡。我们将使用一个 DC 圆形电源插孔,以便更容易地将适配器与继电器连接。圆形插孔与电源适配器端子完美对接,如图 16-3 所示。

image

图 16-3: 电源适配器端子和 DC 圆形电源插孔

项目概览

在开始构建这个项目之前,让我们先快速浏览一下概览,以便你更好地理解每一步(见图 16-4)。你还应该掌握 HTML 和 CSS 的基础知识,如果你还没有完成项目 15,我们建议你先完成它,然后再继续。

image

图 16-4: 项目概览

你将使用 Flask——一个 Python 的 Web 框架——来创建你的网页服务器,方法是创建一个名为app.py的文件。当你访问树莓派的 IP 地址,端口 80 时,你的网页浏览器会请求存储在树莓派中的网页文件——index.htmlstyle.css——然后显示网页。你的网页将有 ON 和 OFF 按钮,分别触发开关灯的事件。

电路连接

出于安全考虑,你需要将继电器放置在一个塑料外壳内。你可能需要在塑料外壳上打几个孔——一个孔用于树莓派的电缆,一个孔用于灯具电缆,另一个孔用于电源适配器。图 16-5 显示了没有盖子的外壳样子。树莓派的电缆将穿过盖子上的孔。

image

图 16-5: 继电器放置在塑料外壳内的电路

注意

确保 JD-VCC 和 VCC 引脚上安装了跳线帽。

在树莓派关闭电源的情况下,按照以下说明搭建电路:

  1. 将一个灯座终端连接到直流圆筒电源插座的负(–)端。

  2. 将直流圆筒电源插座的正(+)端连接到继电器的 COM 插口。

  3. 将另一个灯座终端连接到继电器的 NO 插口。你需要使用螺丝刀将继电器插座连接得很紧。

  4. 按照表格连接树莓派与继电器,并确保你的完成设置与图 16-6 一致。

    继电器 树莓派
    VCC 5 V
    IN2 不连接
    IN1 GPIO 17
    GND GND

    image

    图 16-6: 使用继电器通过树莓派控制 12V 灯具

警告

在继电器或任何接通 12V 电源的电缆连接时,请勿触碰它们。如果出现问题并且你决定更改电路,务必先从插座中拔掉 12V 电源适配器的插头,然后再动手。

完成电路并仔细检查所有连接后,给树莓派供电,连接一个 12V 电源适配器到直流圆筒电源插座,并通过将 12V 电源适配器插入墙壁插座来供电。

如果你想控制一个 LED,请参考项目 1 原理图(见第 41 页),将 LED 连接到 GPIO 17。

为树莓派准备运行 Web 服务器

树莓派支持多种 Web 服务器,但我们将使用 Flask,一个 Python 的 Web 框架,把树莓派变成一个动态 Web 服务器。

安装 Flask

要安装 Flask,你需要使用 pip——一个用于从 Python 包索引安装库的工具。打开终端并运行以下命令来更新树莓派并安装 pip:

pi@raspberrypi:~ $ sudo apt update && sudo apt upgrade
pi@raspberrypi:~ $ sudo apt install python3-pip

然后使用 pip 安装 Flask 及其依赖项,输入以下命令:

pi@raspberrypi:~ $ sudo pip3 install flask

如果 Flask 安装成功,终端将显示成功安装 Flask的信息。

组织文件

在这个项目中保持文件有序非常重要,因为 Flask 需要你的文件以特定的方式进行结构化才能正确工作。为此项目创建一个名为Project_16的文件夹,放在Web_Applications文件夹内。然后按照图 16-7 中显示的结构创建所需的文件夹和文件。

image

图 16-7: 文件和文件夹结构

使用文本编辑器创建index.htmlstyle.css文件,并使用 Python 3(IDLE)创建app.py文件。static文件夹将存储静态文件,如 CSS 文件。templates文件夹将存储可更改的文件;例如,index.html文件是一个模板,根据用户输入动态更改继电器状态标签。

编写脚本

你需要为这个项目编写三个脚本:一个 Python 脚本用于创建 Pi web 服务器,一个 HTML 文件用于构建网页,另一个 CSS 文件用于设置网页样式。

创建 Web 服务器

要创建 web 服务器,打开你目前为空的app.py文件,并输入列表 16-1 中的代码。该代码会在 Raspberry Pi 的 80 端口创建 web 服务器,并生成一个网页,你可以在本地网络中的任何 web 浏览器上访问。你可以在www.nostarch.com/RaspberryPiProject/下载在整个项目中使用的所有代码。

列表 16-1: 使用 Flask 创建 web 服务器

  #import necessary libraries
➊ from gpiozero import LED
  from flask import Flask, render_template, request

  #create a Flask object
➋ app = Flask(__name__)

  #create an object that refers to a relay
➌ relay = LED(17)
  #set the relay off; remember the relay works with inverted logic
  relay.on()
  #save current relay state
  relay_state = 'Relay is off'

  #display the main web page
➍ @app.route('/')
  def main():
     global relay_state
     #pass the relay state to index.html and return it to the user
   ➎ return render_template('index.html', relay_state=relay_state)

  #execute control() when someone presses the on/off buttons
➏ @app.route('/<action>')
  def control(action):
     global relay_state
     #if the action part of the URL is 'on', turn the relay on
   ➐ if action == 'on':
        #set the relay on
        relay.off()
        #save the relay state
        relay_state = 'Relay is on'
     if action == 'off':
        relay.on()
        relay_state = 'Relay is off'

     #pass the relay state to index.html and return it to the user
     return render_template('index.html', relay_state=relay_state)
  #start the web server at localhost on port 80
  if __name__ == '__main__':
  ➑ app.run(host='0.0.0.0', port=80, debug=True)

首先导入所需的库 ➊,然后创建一个名为app的 Flask 对象 ➋。你在 GPIO 17 上初始化继电器 ➌,并默认将继电器设置为关闭状态。继电器使用反向逻辑,因此你需要使用relay.on()来关闭它。

@app.route('/')装饰器 ➍ 在有人访问根 URL(即服务器的 Pi 主 IP 地址)时运行main()函数。在 Python 中,装饰器@符号开始,位于函数定义的上方。装饰器基本上是一个接受另一个函数的函数,但你暂时不需要担心这个。

你将index.html文件渲染到 web 浏览器,并使用 HTML 文件中的relay_state变量值打印当前继电器状态 ➎。然后,你添加一个动态路由,将action作为变量 ➏。当有人访问这个动态路由时,control()函数会被触发。如果 URL 中的actionon,程序就会打开继电器并保存当前继电器状态 ➐。也就是说,当你访问 Raspberry Pi 的 IP 地址并后跟/on(例如http://192.168.1.112/on)时,继电器会打开。稍后你会获取到你自己的 IP 地址。

如果 URL 中的actionoff,程序会关闭继电器并保存当前继电器状态。服务器监听 80 端口,并将调试模式设置为True,以报告任何错误 ➑。

编写 HTML 文件

列表 16-2 是一个简单的 HTML 文档,用于构建网页结构。随意添加更多段落和标题,以使用你在项目 15 中学到的知识对其进行个性化设置。将此代码复制到你的 index.html 文件中,该文件应位于 templates 文件夹中,如前面的图 16-7 所示。

列表 16-2: 为你的网页编写 HTML 文件

  <!DOCTYPE html>
  <head>
  <title>RPi Web Server</title>
  <link rel="stylesheet" type="text/css" href="{{ url_for('static',
  filename='style.css') }}">
➊ <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>

  <body>
      <h2>RPi Web Server</h2>
   ➋ <p>{{relay_state}}</p>
   ➌ <a href="/on"><button>ON</button></a>
   ➍ <a href="/off"><button class="off">OFF</button></a>
  </body>
  </html>

这是一个简单的 HTML 结构,我们不会在这个 HTML 代码中深入探讨太多细节,因为你应该已经熟悉项目 15 中的大部分内容。你可以随时回顾该项目以进行复习。

<meta> 标签和给定的属性使你的网页在任何移动浏览器中都具有响应性 ➊。使用此标签,浏览器会将页面的宽度渲染为移动屏幕的宽度。在第 ➋ 行,在双花括号 {{ }} 之间,是 relay_state,这是我们在 app.py 中用于保存当前继电器状态的变量。此段落显示保存在 relay_state 变量中的状态。当你单击 ON 按钮时,你将被重定向到 /on 根目录 ➌,这将打开继电器,如 app.py 中所示。当你单击 OFF 按钮时,你将被重定向到 /off 根目录 ➍,这将关闭继电器。

编写 CSS 文件

列表 16-3 设置你的网页样式。这只是一个如何设置页面样式的示例;你可以根据需要编辑属性。你的 style.css 文件应位于 static 文件夹中,如你在前面的图 16-7 中看到的那样。

列表 16-3: 使用 CSS 设置网页样式

➊ h2 {
    font-size: 2.5rem;
  }
  p {
    font-size: 1.8rem;
  }
➋ body {
    display: inline-block;
    margin: 0px auto;
    text-align: center;
  }
➌ button {
    display: inline-block;
    margin: 0 auto;
    padding: 15px 25px;
    font-size: 3rem;
    border-radius: 4px;
    color: #fff;
    background-color: #009933;
    border: none;
  }
  .off {
    color: #fff;
    background-color: #604f43;
  }
  a {
    text-decoration: none;
  }

总而言之,我们已经为标题 2 和段落 ➊ 设置了字体大小。然后,我们对齐了所有页面主体 ➋。最后,我们编辑了 ON 和 OFF 按钮的外观 ➌。

启动你的 Web 服务器

完成 Python 脚本、HTML 文件和 CSS 文件后,就可以运行你的 Web 服务器了。打开终端窗口并通过输入以下命令导航到 Project_16 文件夹:

pi@raspberrypi:~ $ cd ~/Desktop/Projects/Web_Applications/Project_16

然后,使用以下行运行 app.py

pi@raspberrypi:~Desktop/Projects/Web_Applications/Project_16 $ sudo
python3 app.py

你的 Web 服务器现在正在运行。在本地网络上的任何浏览器中打开并输入你的 Raspberry Pi IP 地址。

要查找你的 Pi 的 IP 地址,请转到终端并输入以下内容:

pi@raspberrypi:~ $ hostname -I

这将打印 Pi 的 IP 地址。

在你的电路准备就绪并且服务器正在运行的情况下,打开浏览器并导航到你的 Raspberry Pi IP 地址。你的浏览器将显示 Web 服务器页面。现在点击按钮来远程控制灯!图 16-8 显示了智能手机浏览器中的网页。

image

图 16-8: 智能手机浏览器上的 Raspberry Pi Web 服务器页面

要停止 Web 服务器,只需按 CTRL-C。

进一步探索

在这个项目中,你学习了如何使用继电器以及如何设置 Web 服务器来提供网页,以及如何将你的 HTML 和 CSS 技能付诸实践。以下是一些进一步扩展这些概念的想法:

  • 编辑此项目以控制多个输出。

  • 编辑 CSS 和 HTML 文件以符合您的个人口味。

  • 控制其他电子设备。

  • 编辑以前的项目以控制连接到继电器的灯,而不是 LED。

使用 Node-RED 的物联网控制中心**

在这个项目中,您将创建一个物联网应用,通过一个网络服务器使用 Node-RED 来控制您最喜爱的家庭电子设备,Node-RED 是一个功能强大且易于使用的物联网应用工具。

image

所需零件

树莓派

面包板

DHT22 温湿度传感器

4.7 kΩ 电阻

两个 5 mm LED

两个 330 Ω 电阻

跳线

所需软件

Node-RED DHT 节点

Node-RED 仪表盘

您将创建一个 Node-RED 应用,控制本地网络中的输出并读取输入。您将用它来控制 LED,以及使用 DHT22 传感器远程读取和显示温湿度值——所有操作都通过网络服务器完成。

介绍 Node-RED

Node-RED 是一个开源的可视化接线工具,用于构建物联网应用,它已经预安装在您的树莓派操作系统中,并且与树莓派完美兼容。

Node-RED 使用视觉编程,具有称为 节点 的模块,您可以将它们连接起来以执行某项任务,从而大大简化了编程。Node-RED 允许您快速而简单地原型化一个复杂的家庭自动化系统,让您有更多时间去设计和制作酷炫的东西。

我们在这个项目中不会涵盖 Node-RED 的所有功能,但如果您想进一步探索,这里有一个简要概述:

  • 访问树莓派的 GPIO 引脚。

  • 与 Arduino 和 ESP8266 等其他板子建立连接。

  • 创建一个响应式图形用户界面。

  • 与第三方服务进行通信。

  • 从网页获取数据。

  • 创建时间触发事件。

  • 存储和检索数据库中的数据。

安装 DHT22 节点

虽然 Node-RED 软件已经预装在树莓派的操作系统中,但它没有提供能够读取 DHT22 传感器的节点。我们需要先安装它,方法是通过 npm(Node 包管理)来安装,如下所示:

pi@raspberrypi:~ $ sudo apt install npm

当提示时,键入 Y 并按 ENTER。安装过程可能需要几分钟。然后,输入以下命令以将 npm 升级到推荐用于 Node-RED 的最新 3.x 版本:

pi@raspberrypi:~ $ sudo npm install -g npm@3.x
pi@raspberrypi:~ $ hash -r

这些命令将输出警告消息,但不用担心——它是无害的,您可以忽略它。

接下来,您将下载并解压一个适用于树莓派的 C 库。您需要这个库来通过 Node-RED 控制 DHT22。本文撰写时,库的最新版本是 1.55。安装该库之前,请访问 www.airspayce.com/mikem/bcm2835/ 检查最新版本。然后,输入以下命令,将斜体显示的 1.55 替换为最新版本。

pi@raspberrypi:~ $ wget http://www.airspayce.com/mikem/bcm2835/
bcm2835-*1.55*.tar.gz
pi@raspberrypi:~ $ tar zxvf bcm2835-*1.55*.tar.gz

最后,输入以下命令列表以编译和安装控制 DHT22 传感器和添加仪表盘支持所需的节点:

pi@raspberrypi:~ $ cd bcm2835-*1.55*
pi@raspberrypi:~/bcm2835-1.55 $ ./configure
pi@raspberrypi:~/bcm2835-1.55 $ make
pi@raspberrypi:~/bcm2835-1.55 $ sudo make check
pi@raspberrypi:~/bcm2835-1.55 $ sudo make install
pi@raspberrypi:~/bcm2835-1.55 $ cd
pi@raspberrypi:~ $ sudo npm install -–unsafe-perm -g node-dht-sensor
pi@raspberrypi:~ $ sudo npm install --unsafe-perm -g node-red-
contrib-dht-sensor
pi@raspberrypi:~ $ sudo npm install --unsafe-perm -g node-red-
dashboard

完成所有安装后,重启你的 Pi。现在我们将简要介绍一下 Node-RED。

开始使用 Node-RED

要打开 Node-RED,请进入终端并输入以下命令:

pi@raspberrypi:~ $ sudo node-red start

你的终端窗口应该显示类似于 图 17-1 的内容。高亮的那一行显示的是本地主机的 Raspberry Pi IP 地址,后面跟着 Node-RED 服务器运行的端口号。本地主机 是一个主机名,意思是“这台计算机”,并解析为终端窗口中显示的 IP 地址:http://127.0.0.1\。使用这个 IP 地址你只能在 Raspberry Pi 浏览器中访问 Node-RED。要在本地网络的任何浏览器中访问 Node-RED,你需要找到 Pi 的 IP 地址。

注意

通常,你可以不通过终端,而是通过点击任务栏主菜单中的 编程 ▸ Node-RED 来打开 Node-RED。然而,在这个项目中,你需要从终端启动 Node-RED,因为 DHT22 节点需要管理员权限。

image

图 17-1: 从终端启动 Node-RED

要查找你的 Raspberry Pi 的 IP 地址,请在终端中输入以下命令:

pi@raspberrypi:~ $ hostname -I

打开 Chromium 浏览器并输入 http://<Pi IP 地址>:1880/,将 <Pi IP 地址> 替换为你的 Raspberry Pi 的 IP 地址。你的 Node-RED 页面服务器应该会如 图 17-2 所示打开。

image

图 17-2: 浏览器中的 Node-RED 编辑器

在左侧,你将看到一个块或节点的列表。节点根据它们的功能进行分组;向下滚动列表以查看你有哪些节点。在本项目中,我们将介绍少数现有节点,包括来自输入、Raspberry_Pi、功能和仪表盘部分的节点,如 图 17-3 所示。

image

图 17-3: 一些 Node-RED 节点

Node-RED 页面中的中央框是 流程 部分;这是你拖拽节点并将它们连接在一起以构建应用程序的地方。右侧有几个选项卡:信息 选项卡显示所选节点的信息,调试 选项卡用于调试,仪表盘 选项卡是你组织应用程序用户界面控件的地方。最后,部署 按钮保存对流程所做的更改并执行它。接下来,你将连接硬件,并在 Node-RED 中组装你的流程。

电路连接

你应该已经熟悉 LED 和 DHT22 传感器,但如果你需要复习 DHT22 传感器,请查看 项目 12。要连接电路,请按照以下说明操作:

  1. 将 Pi 的 5 V 和 GND 分别连接到面包板的红色和蓝色轨道上。

  2. 将两个 LED 插入面包板中。将每个 LED 的短引脚通过一个 330 Ω 电阻接到 GND 排,较长引脚分别连接到 GPIO 18 和 GPIO 17。

  3. 将 DHT22 插入面包板(带凸起的一面朝向你),并按照下表接线。完成的电路应与 图 17-4 相匹配。

DHT22 树莓派
1 3.3 V
2 GPIO 4 和 3.3 V(通过 4.7 kΩ 电阻)
3 不连接
4 GND

image

图 17-4: 将 DHT22 和两个 LED 连接到树莓派

创建流程

脚本在这里指的是你将在 Node-RED 中创建的流程。这个流程将允许你执行以下操作:

  • 用开关控制一个 LED。这颗 LED 还将具有时间敏感性,也就是说,它将在你设定的特定时间自动点亮并熄灭。

  • 使用滑块通过 PWM 控制另一个 LED,作为调光开关。

  • 从 DHT22 传感器读取温度,并将其显示在时间与温度的图表上。

  • 从 DHT22 传感器读取湿度并显示在仪表上。

首先,你需要创建一个用户界面,用于从服务器控制各个组件。

创建仪表板用户界面

来自仪表板部分的节点提供了在应用程序用户界面(UI)中显示的小部件,用于控制各个组件。你添加到流程中的每个小部件——如按钮、滑块或图表——都必须与一个 相关联,组会告知该小部件在 UI 中应该出现的位置。

你还需要选项卡,就像应用程序中的页面一样(类似于浏览器中的标签页)。组则是选项卡内的部分,你可以在其中将小部件分组。在任何项目中,你都需要在仪表板中创建选项卡和组,以便组织 UI 中的小部件。你将创建一个名为“Home”的选项卡,其中有一个名为“Dashboard”的组。

参考 图 17-5,点击右上角的 仪表板 选项卡 ➊,然后按 + 选项卡 按钮 ➋ 创建一个新选项卡。点击 编辑 来编辑选项卡 ➌,并输入名称“Home”。创建后,按 + 组 按钮 ➍ 创建一个新组;然后点击 编辑 ➎ 并输入名称“Dashboard”。

image

图 17-5: 在仪表板中创建选项卡和组

要查看当前仪表板的样子,打开浏览器并访问 http://<Pi IP 地址>:1880/ui,将 <Pi IP 地址> 替换为你之前获得的树莓派 IP 地址。如 图 17-6 所示,目前你的用户界面是空的,因为你还没有添加任何小部件,接下来我们将添加一些功能。

image

图 17-6: Node-RED 用户界面

连接节点

现在,你将向流程中添加节点。你的流程将控制连接到树莓派 GPIO 的 LED,并从 DHT22 传感器读取温度和湿度。

添加开关和调度事件

返回到 Node-RED 编辑器,拖动两个注入节点到流程中的输入部分。当你将它们放入流程中时,它们的名称会变为时间戳。从仪表盘部分添加一个开关,并从 Raspberry_Pi 节点部分添加一个 rpi gpio 输出节点(左侧有接头的那个)。按照 图 17-7 中所示的方式排列这些节点并将它们连接起来。

image

图 17-7: 控制 LED 1 的流程

这组节点控制连接到 GPIO 17 的 LED。该 LED 可以通过开关进行远程控制,且具有时间敏感性;时间戳 1 确定 LED 点亮的时间,而时间戳 2 确定 LED 关闭的时间。

如果你双击一个节点,新的窗口将打开,允许你编辑该节点的属性。双击第一个时间戳节点并按如下方式编辑其属性:将有效负载数字设置为1,这样当该节点被触发时,会向树莓派 GPIO 17 发送 1。在重复字段中,选择在特定时间并选择你希望 LED 点亮的时间和日期;你将在时间戳 2 中选择关闭时间。我们为一周的所有天选择了 19:00(晚上 7 点)。你可以选择任何你希望的时间和日期。名称字段允许你为节点命名,在我们的例子中,我们将其命名为晚上 7 点开。编辑完节点后,点击完成按钮。图 17-8 显示了我们为此节点设置的属性。

image

图 17-8: 编辑注入节点属性

编辑其他节点的属性,使其与下表中的内容一致。

节点 属性

| 时间戳 | 有效负载: 数字 1 重复: 在特定时间

时间:19:00

开:选择所有日期 |

| 时间戳 | 有效负载: 数字 0 重复: 在特定时间

时间:23:00

开:选择所有日期 |

| 开关 | 分组: 仪表盘 [首页] 标签: LED 1

开启有效负载:数字 1

关闭有效负载:数字 0

名称:LED 1 – 开关 |

| 引脚 | GPIO: GPIO17 – 11 类型: 数字输出

名称:LED 1 – GPIO 17 |

要运行你的 Node-RED 应用程序,点击右上角的部署按钮。这也将保存更改。要查看应用程序的效果,请在浏览器中访问http://<Pi IP 地址>:1880/ui。此时,应该与 图 17-9 类似。

image

图 17-9: 带有 LED 1 控制开关的用户界面

点击开关测试是否可以控制连接到 GPIO 17 的 LED。如果不能,请返回并确保你已正确接线并设置了正确的属性。

添加滑块

现在,你将添加节点来控制连接到 GPIO 18 的 LED 的亮度。将一个滑块和一个 rpi gpio 输出节点拖入流程,并按照 图 17-10 中所示的方式排列它们。

image

图 17-10: 控制 LED 1 和 LED 2 的流程

按照下表中的设置编辑新节点的属性。

节点 属性

| 滑块 | 分组:仪表盘 [主页] 标签:LED 2

范围:最小值:0;最大值:100;步长:1

名称:LED 2 – 滑块 |

| 引脚 | GPIO:12 – GPIO18 类型:PWM 输出

名称:LED 2 – GPIO 18 |

这一组新的节点通过 PWM 控制连接到 GPIO 18 的 LED。移动滑块将改变 LED 的亮度。点击 部署 按钮,进入 Node-RED UI,测试你的应用程序。它应该看起来像图 17-11。

image

图 17-11: 带有 LED 1 开关和 LED 2 滑块的用户界面

添加温度图表和湿度计

最后,要创建温度图表和湿度计,拖动一个注入节点、rpi dht22 节点、功能节点、图表和湿度计到流程中。图表和湿度计是仪表盘节点。排列这些节点,使你的流程像图 17-13 那样。

image

图 17-12: 完整的 Node-RED 流程

使用下表中的设置编辑新节点的属性。

节点 属性

| 时间戳 | 负载:布尔值 true 重复:间隔

每隔:1 分钟

名称:读取 DHT22 |

| rpi-dht22 | 传感器型号:DHT22 引脚编号:BCM GPIO

引脚编号:4

名称:DHT22 – GPIO 4 |

| 图表 | 分组:仪表盘 [主页] 标签:温度

类型:折线图

X 轴:1 小时

X 轴标签:HH:mm

名称:温度 – 图表 |

| f | 名称:获取湿度 功能:

msg.payload = msg.humidity;

return msg; |

| 湿度计 | 分组:仪表盘 [主页] 类型:湿度计

标签:湿度

值格式:{{value}}

单位:%

范围:最小值:0;最大值:100

名称:湿度 - 湿度计 |

点击 部署 按钮,再次测试你的应用程序。记住,如果遇到任何问题,确保你的属性与这里的表格匹配,并且仔细检查你的节点连接与图示的线缆连接。

运行你的应用程序

恭喜!你已经使用 Node-RED 构建了你的第一个物联网应用程序。前往 http://<Pi IP 地址>:1880/ui 查看你的用户界面效果。你可以使用任何本地网络中的浏览器访问这个网址,无论是在电脑还是智能手机上。图 17-13 展示了你可以用 Node-RED 应用控制的最终电路。

image

图 17-13: 使用 Node-RED 的物联网项目

你的应用程序有一个开关来控制 LED 1,它也是时效性的;一个滑块来控制 LED 2 的亮度;以及一个图表和湿度计来显示温度和湿度。

进一步探索

这个项目只是 Node-RED 能做的冰山一角。以下是一些将这个项目进一步扩展的想法:

  • 将时效性 LED 替换为继电器和灯泡(有关继电器模块的介绍,请参阅项目 16)。

  • 向你的应用程序添加更多的传感器读取,比如烟雾传感器和运动检测传感器。

第七章:游戏与玩具

数字鼓组**

在这个项目中,你将创建一个由面包板电路和几个按钮控制的数字鼓组。按下不同的按钮将产生不同的鼓声,包括两个鼓点循环。

image

所需部件

树莓派

面包板

八个按键

耳机,或通过 HDMI 连接到树莓派的显示器带扬声器

跳线

所需软件

avconv

本项目使用来自 Sonic Pi 样本库的样本,但你可以根据需要适配它,使用任何你喜欢的声音。

准备音频

首先,你需要正确配置树莓派的音频设置,并准备好使用 Sonic Pi 的音频样本。

配置音频

首先,将耳机或扬声器插入树莓派音频插孔。如果你的显示器内建扬声器,并且通过 HDMI 电缆连接到树莓派,则不需要连接任何东西到音频插孔——你可以通过显示器扬声器来听音频。

在桌面环境的右上角,右键单击音频符号并选择音频源,如图 18-1 所示。如果你使用耳机,选择模拟(Analog),如果你使用带扬声器的显示器,选择 HDMI。

image

图 18-1: 选择音频源

获取音频样本文件

注意

我们在本书中不会介绍 Sonic Pi,但如果你想自己探索它,可以通过任务栏主菜单中的编程Sonic Pi来打开软件并浏览。

Sonic Pi 软件已预装在树莓派的操作系统中,允许你使用代码创作数字音乐,但在这个项目中,你只会使用 Sonic Pi 的示例音频文件。

在终端中,输入以下命令,在Projects文件夹内创建一个名为Games_and_Toys的新文件夹,并进入该文件夹。你将在这里保存样本。

pi@raspberrypi:~ $ cd ~/Desktop/Projects
pi@raspberrypi:~/Desktop/Projects $ mkdir Games_and_Toys
pi@raspberrypi:~/Desktop/Projects $ cd Games_and_Toys

然后输入以下命令,将 Sonic Pi 的sample文件夹复制到Games_and_Toys文件夹中(注意,最后的/和句点之间有一个空格):

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys $ cp -r /opt/
sonic-pi/etc/samples/ .

接下来,输入以下命令列出sample文件夹中的内容,检查它们是否正确转移:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys $ cd samples
pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ ls

如果一切顺利,你应该看到如下的文件列表:

ambi_choir.flac        drum_cowbell.flac        elec_ping.flac
ambi_dark_woosh.flac   drum_cymbal_closed.flac  elec_plip.flac
...

你可能注意到这些文件具有不常见的扩展名.flac。这种格式在 Sonic Pi 中使用,但要在 Python 中使用它们,你需要将它们转换为.wav文件。为此,你将使用 avconv 软件。输入以下命令来安装 avconv:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ sudo apt
install libav-tools

然后输入以下命令,这将遍历sample文件夹中的所有文件,并将每个.flac文件转换为.wav文件:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ for f in
*.flac; do avconv -i "$f" "${f%.flac}.wav"; done

接下来,使用ls命令列出sample文件夹中的项目,并检查是否已准备好.wav文件:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ ls

每个样本应该同时拥有.wav.flac文件。要从sample文件夹中删除.flac文件,请输入以下命令:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ rm *.flac

如果你愿意,可以通过再次使用 ls 命令来核对文件是否正确。

你可以使用操作系统默认安装的 omxplayer 软件来播放这些声音。要播放名为 drum_snare_soft.wav 的示例音频,请在命令行中输入以下内容:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ omxplayer
drum_snare_soft.wav

如果你浏览示例文件,你会看到各种各样的声音,从吉他声到牛铃声和鼓声。选择八种不同的鼓声加入你的鼓组(或任何其他你喜欢的声音)。

我们选择的声音如下;最后两个是鼓点,而其他的是单个鼓声:

  • drum_cymbal_open.wav

  • drum_heavy_kick.wav

  • drum_snare_hard.wav

  • drum_cymbal_closed.wav

  • drum_roll.wav

  • perc_snap.wav

  • loop_amen_full.wav

  • loop_mika.wav

电路连接

本项目的电路仅涉及将八个按键连接到树莓派。每个按键都与不同的声音相关联。

要连接电路,请按照这些说明进行操作,并参考 图 18-2。

image

图 18-2: 数字鼓电路

  1. 将 GND 引脚连接到面包板的 GND 导轨。

  2. 将八个按键均匀地插入面包板,并沿着中间分隔线排列。

  3. 将底部左侧的按键引脚连接到 GND,将底部右侧的引脚依次连接到以下 GPIO 引脚之一:GPIO 2、3、14、15、17、18、22 和 27。

    PUSHBUTTON 树莓派
    1 GPIO 2
    2 GPIO 3
    3 GPIO 14
    4 GPIO 15
    5 GPIO 17
    6 GPIO 18
    7 GPIO 22
    8 GPIO 27

编写脚本

打开 Python 3 (IDLE),然后点击 文件新建文件 来创建一个新脚本。将以下代码复制到 Python 编辑器中,并将脚本保存为 digital_drum_set.py,存放在 Games_and_Toys 文件夹内(记得你可以从 www.nostarch.com/RaspberryPiProject/ 下载所有脚本):

  #import the necessary libraries
➊ import pygame.mixer
  from pygame.mixer import Sound
  from gpiozero import Button
  from signal import pause

  #create an object for the mixer module that loads and plays sounds
➋ pygame.mixer.init()

  #assign each button to a drum sound
➌ button_sounds = {
      Button(2): Sound("samples/drum_cymbal_open.wav"),
      Button(3): Sound("samples/drum_heavy_kick.wav"),
      Button(14): Sound("samples/drum_snare_hard.wav"),
      Button(15): Sound("samples/drum_cymbal_closed.wav"),
      Button(17): Sound("samples/drum_roll.wav"),
      Button(18): Sound("samples/perc_snap.wav"),
      Button(22): Sound("samples/loop_amen_full.wav"),
      Button(27): Sound("samples/loop_mika.wav"),
  }
  #the sound plays when the button is pressed
➍ for button, sound in button_sounds.items():
      button.when_pressed = sound.play
  #keep the program running to detect events
➎ pause()

和往常一样,你首先通过导入必要的库来开始你的脚本 ➊。这里的新库是 pygame.mixer,用于加载和播放声音。从 pygame.mixer 中,你还导入了 Sound 模块,用于创建声音对象。

然后初始化 Pygame mixer ➋ 并创建一个字典,用于存储声音 ➌。在 Python 中,字典 是一种用于存储项目间关系的数据结构。在本例中,你将每个按钮与特定的声音关联。字典的基本结构如下:

dictionary_name = {key_1: value_1, key_2: value_2}

字典用花括号 {} 括起来,并由键/值对组成。你使用冒号 (:) 将每个键与其对应的值关联,并使用逗号 (,) 分隔每个键/值对。

在这个项目中,键是按钮,值是声音。要创建一个声音对象,你需要将声音文件路径作为字符串传递给Sound()函数。在这种情况下,由于sample文件夹位于Games_and_Toys文件夹内,你不需要提供完整的路径,只需要提供文件夹名称,然后跟上声音文件名。你需要将此脚本中以粗体突出显示的声音文件名更改为你选择的声音文件。

接下来,将每个按钮分配到一个声音效果 ➍;这意味着,当按下按钮时,相应的声音会播放。最后,脚本末尾的pause()函数 ➎ 保持程序运行,以便能够检测事件。

要运行脚本,按F5或转到运行运行模块

恭喜你——你刚刚完成了自己的数字鼓组!现在,按下按钮,创作你自己的音乐片段。

进一步探索

这是一个很酷的项目,而且非常简单。我们鼓励你通过尝试以下内容来扩展这个项目:

  • 向你的数字鼓组添加其他声音

  • 录制你自己的声音或在网上搜索免费的声音

  • 建造一个数字钢琴、数字吉他或一个混合音效的混合音乐盒

在 Scratch 中制作一个游戏:饥饿的猴子**

在这个项目中,你将使用基于块的编程语言 Scratch 来创建一个可以通过两个按钮和你的 Pi 进行控制的游戏。

image

所需零件

树莓派

两个按钮

面包板

跳线

在这个项目中,你将创建自己的游戏《饥饿的猴子》。游戏的目标是让猴子在 30 秒内尽可能多地抓住成熟的香蕉,同时避免腐烂的香蕉。你可以通过两个按钮控制猴子左右移动,这些按钮将连接到你的树莓派。

介绍 Scratch 2

注意

要了解更多关于 Scratch 的信息,请访问官方 Scratch 网站 scratch.mit.edu/

Scratch 是一种可视化编程语言,你可以使用它通过拖拽代码块来创建动画、故事和游戏。虽然 Scratch 是为了教孩子如何编程而开发的,但它适合任何想要学习一些基本编程概念或只是想通过制作自己的游戏来娱乐的人。

Scratch 2 在 Raspbian 上默认安装。你可以通过桌面主菜单中的编程Scratch 2来打开它。当你打开 Scratch 2 时,你应该会看到像图 19-1 中的窗口。

image

图 19-1: Scratch 2 窗口

Scratch 窗口将屏幕分为四个主要部分。舞台 ➊ 是你的游戏或动画播放的地方。在右上角,你会看到一个绿色旗帜和一个停止标志;你可以使用这些图标分别开始和停止游戏。当你第一次打开 Scratch 时,默认情况下,你应该会在舞台上看到一只猫。

Sprite 列表➋显示了所有的精灵,这些精灵是你游戏中的角色或任何在项目中执行动作的物体。窗口中间是代码区➌,它有三个标签:脚本、服装和声音。脚本标签包含了你用来构建程序的编程块。每个块代表一种编程指令,你可以将它们拖放到程序中。你会看到按照功能分类的不同块,每个分类都有特定的颜色;例如,运动类别的块,用来控制精灵如何移动,颜色是深蓝色的。

服装标签➍显示了用于自定义和创建新服装的选项,声音标签➎允许你为精灵添加声音。脚本区➏是你拖动块并将它们组合在一起创建脚本的地方。

顶部的菜单栏➐显示了左侧的“文件”和“编辑”主菜单。中央的图标允许你复制、删除、放大和缩小精灵,也可以从 Scratch 获取帮助。

连接电路

本项目的电路由两个按钮和树莓派组成。按照这些指示连接它们,并使用图 19-2 作为参考。

  1. 将一个 GND 引脚连接到面包板的 GND 轨道。

  2. 将两个按钮插入面包板中。

  3. 将右下角按钮的引脚连接到 GND 轨道。

  4. 将一个按钮的左下角引脚连接到 GPIO 2,另一个连接到 GPIO 3。

image

图 19-2: 将两个按钮连接到树莓派

就这些!你已经准备好编写游戏代码了。

构建脚本

在创建游戏之前,列出你希望游戏具备的功能是有用的,这样你就能明确需要做什么。

在《饥饿的猴子》游戏中,玩家控制一只猴子,需要尽量抓住熟香蕉,同时避开坏香蕉。每抓到一个好香蕉,玩家得一分;如果抓到一个坏香蕉,游戏会扣一分。以下是构建《饥饿的猴子》游戏的主要步骤:

  1. 创建主要角色——猴子,并允许玩家使用两个按钮控制它的移动:一个按钮向右移动猴子,另一个向左移动猴子。也允许玩家使用键盘按键控制猴子的移动。

  2. 创建好香蕉和坏香蕉的精灵并让它们从天空中掉下来。

  3. 编写代码让猴子在接触到香蕉时抓住它们。

  4. 创建一个得分系统,当猴子抓到一个好香蕉时加一分,抓到一个坏香蕉时扣一分。

  5. 创建一个计时器,当计时器归零时结束游戏。

  6. 游戏结束时显示玩家的得分。

这个项目的 Scratch 文件可以在 www.nostarch.com/RaspberryPiProject/ 获取。要将保存的程序上传到 Scratch,请选择 文件 ▸ 加载项目。要构建脚本,请按照接下来的几个部分进行操作。

创建角色并选择舞台背景

注意

你也可以使用画笔图标从零创建角色,点击文件夹图标上传你自己的角色,或使用摄像头图标拍照创建你的角色。

在《饥饿猴子游戏》中,你将使用猴子角色和来自角色库的香蕉角色。你不需要使用默认出现在舞台上的猫角色,所以你可以通过右键点击该角色并选择 删除 来删除它。

转到角色列表,点击最左侧的图标(看起来像一个人物图标,见图 19-3),以打开角色库。

image

图 19-3: 创建新角色的图标

从动物类别中选择 Monkey2 角色,并点击 确定。然后,再次打开角色库,从物品类别中选择 Bananas 角色,点击 确定

你可以从角色列表的最左侧选择游戏的背景。在那里,你会找到一组背景图标。点击第一个图标——在图 19-4 中突出显示——从背景库中选择一个背景。我们选择了叫做蓝天的背景。

image

图 19-4: 从背景库中选择背景

现在,你的角色部分应该像图 19-5 所示。

image

图 19-5: 选定角色和背景的角色列表

编辑角色

Scratch 允许你增加或减少角色的大小,改变其颜色,或像在图像编辑程序中一样编辑它。Scratch 内置的图像编辑器叫做绘图编辑器。在这里,你将对角色的外观进行一些修改。

在角色列表中选择 Monkey2 角色;如果某个角色被选中,它会被蓝色轮廓框住,如图 19-5 所示。接下来,点击 Monkey2 角色的造型标签,并编辑第一个造型,名为 monkey2-a。使用鼠标指针拖动角色的一个角,直到它的大小为 98×138 像素,或者使用缩小工具调整到所需大小;角色的大小会显示在 monkey2-a 造型下方。同时,将 Bananas 角色的大小调整为 28×28 像素。

在调整角色大小时,确保它们位于画布的中心,以保持角色的参考点。

为猴子角色添加控制功能

现在,你将为猴子添加控制功能,这样你就可以通过按下按钮或键盘上的左右箭头让它向左或向右移动。

为了让树莓派 GPIO 与 Scratch 接口,这样程序在按下按钮时能够响应,你需要将一个扩展库添加到 Scratch 中。选择“脚本”标签下的 Monkey2 精灵,选择 更多积木,然后点击 添加扩展。接下来选择 Pi GPIO 图标,如 图 19-6 所示,然后点击 确定

image

图 19-6: 添加 Pi GPIO 扩展

扩展库添加了新的积木来控制 Pi GPIO,这些积木应出现在“更多积木”类别中。

在 Scratch 中,有许多不同的方法可以让你的精灵移动。你将使用 (x,y) 坐标系统,其中 (0,0) 位置是舞台的中央。增加 x 坐标会将精灵向右移动,减少 x 坐标会将精灵向左移动。增加 y 坐标会将精灵向上移动,减少 y 坐标会将精灵向下移动。控制移动的积木位于深蓝色的 动作 类别中。

为了控制猴子,选择 Monkey2 精灵并将 图 19-7 中的积木拖入脚本区域。然后修改积木中的设置,使其与 图 19-7 中的设置一致。

image

图 19-7: 控制猴子的积木

你首先将 Monkey2 精灵的 x 位置设置为 0,y 位置设置为 –110。将 x 设置为 0 会将精灵水平居中,而将 y 设置为 –110 会将精灵移到地面。这样,每次开始游戏时,精灵都会处于这个位置。

接下来的两个积木将 GPIO 2 和 GPIO 3 设置为输入,这样程序就能判断按钮是否被按下。

注意

找到这些积木很容易。记住,每个积木类别都有特定的颜色,每个类别中的积木也会相应地着色。

然后你需要添加一个永远循环,不断检查玩家是否按下按钮或左右箭头键。如果玩家按下连接到 GPIO 3 的按钮,或按下右箭头键,精灵的 x 位置将增加 30,向右移动;如果玩家按下连接到 GPIO 2 的按钮,或按下左箭头键,精灵的 x 位置将减少 30,向左移动。你可以增加这个数值来让猴子移动得更快,或者减少它让猴子移动得更慢。

一旦你添加了这些积木,双重检查它们是否与 图 19-7 相符,然后你就可以进行测试了。

测试你的脚本

在 Scratch 中启动脚本时,你使用绿色旗帜积木, image。这个积木会启动你的游戏并同步所有精灵中的脚本。当你点击舞台右上角的绿色旗帜图标时,Scratch 会启动所有位于这个积木下方的脚本。

现在点击舞台右上角的绿色旗帜图标!image。按下按钮和箭头键,测试精灵是否按预期移动。当一切正常时,继续进行计时器部分。

创建倒计时器

玩家需要知道他们剩下多少时间来抓住香蕉,因此接下来你将创建一个倒计时器。

要将计时器添加到游戏中,选择Monkey2精灵,然后将图 19-8 中的块添加到脚本区域。你可能会注意到找不到显示变量块。这是因为你需要创建一个变量来保存时间。要创建变量,请进入数据块类别,然后点击创建变量按钮。将新变量命名为 time,并通过勾选对所有精灵可用框,使其对所有精灵可用。现在将该块拖到脚本区域。

image

图 19-8: 创建倒计时器的块

要创建倒计时器,你将使用一个叫做计时器的块。这个块会计数从脚本开始到现在已经过去的时间。当你点击绿色旗帜图标时,脚本会重置计时器,因此每次开始游戏时,计时器都会从 0 开始计数。接下来,你将包括一个显示time变量的块,并可以通过将其拖动到舞台区域来定位它;将其移动到舞台的右上角。

接下来,永远循环将不断更新time变量,使其从 30 开始,每秒减少 1。你可以使用四舍五入块,这样倒计时的时间只会以整数显示。如果你想更改游戏的持续时间,可以调整四舍五入块中的数字。

请特别注意这里的嵌套结构(见图 19-9);你会注意到设置时间块首先出现,然后是四舍五入块。接着,在它之上,你需要放置一个绿色块,里面有两个空圆圈。在第一个空圆圈内输入 30,在第二个空圆圈内,从传感器类别中拖入计时器块。

image

图 19-9: 嵌套的 if 块

末尾的 if 块(见图 19-8)隐藏了time变量,直到time达到 0。现在试试看吧!

计数和显示得分

要创建得分系统,首先需要创建一个变量来跟踪得分。在数据块类别中,创建一个新变量,命名为score,并使其对所有精灵可用。选择Monkey2精灵,然后将图 19-10 中的块添加到脚本区域。

image

图 19-10: 显示得分并停止游戏的块

将设置得分块设为0,这样游戏开始时得分就会重置。然后添加显示变量块,将得分显示在舞台上。

在游戏结束时,当时间归零时,猴子应在对话框中说出得分,所有脚本将停止,游戏结束。为了让猴子说话,添加一些紫色的外观积木来显示对话框——你可以在此输入任何你希望猴子说的话。

再次提醒,注意积木的嵌套结构,并仔细查看图 19-10。

让香蕉从天而降

现在你已经创建了所有的Monkey2动画和控制,你需要让Bananas角色从天而降。以下是Bananas角色的待办事项:

  • 香蕉应该从天而降,从一个随机的 x 位置开始,随后 y 位置逐渐下降,直到它们碰到地面。

  • 当香蕉碰到地面时,它们应该消失。

  • 当香蕉碰到猴子时,应该播放一个声音,得分加一,香蕉应该消失。

  • 当时间归零时,所有香蕉应被删除,以防它们在游戏结束后继续掉落。

首先,你需要从声音库中添加一个声音到积木区。

从声音库添加声音

你将为Bananas角色添加一个"砰"的声音,当它与Monkey2角色碰撞时播放。为此,选择Bananas角色,在积木区选择声音选项卡。然后点击image图标,从声音库中选择一个声音并选择pop。选择脚本选项卡以添加你的动作积木。

让香蕉掉落

为了完成待办事项,选择Bananas角色,然后将图 19-11 中的积木添加到其脚本区。

image

图 19-11: 创建和控制Bananas角色的积木

在图 19-11 中,使用左上方的一组积木➊,每秒创建一个Bananas角色的克隆。换句话说,每秒都会让一个新的Bananas角色出现。

在右侧的积木组➌中,你初始化了Bananas克隆的值。show 积木确保香蕉出现在屏幕上。你将香蕉的 y 位置设置为 170,对应舞台的顶部,并将 x 位置设置为-230 到 230 之间的随机数,这是舞台的水平空间,从左到右。

然后,你初始化了一个repeat until积木,这就像一个while循环,直到time变量为 0 时才停止。在repeat until积木内部的change y by积木使香蕉的 y 位置减少,看起来像是从天而降。在这个例子中,我们将 y 位置减少了 5。如果你想让它们掉得更快,可以增加 y 值;如果你想让它们掉得更慢,可以减少 y 值。

repeat until块内的第一个if块会在香蕉到达舞台底部时(y < -160)让香蕉消失。第二个if块会在香蕉碰到猴子时给score变量加一分并播放pop声音,同时让香蕉消失。最后,当repeat until块结束时,Bananas克隆体会从舞台上消失。

图 19-11 中左下角的模块➋会在time变量为 0 时停止创建新的Bananas克隆体。

添加烂香蕉

现在你已经有了猴子、好的香蕉、计时器和分数。你只缺少烂香蕉。烂香蕉的脚本和图 19-11 中的脚本非常相似;你只需要做以下更改:

  • 每 2 秒生成一次烂香蕉,而不是每秒一次。

  • 当烂香蕉碰到猴子时,减少一分。

  • 当猴子碰到烂香蕉时播放不同的声音。我们选择了F elec bass的声音。

  • 修改烂香蕉的外观。

因为这个脚本和之前的非常相似,所以你将复制好的香蕉精灵并进行更改。右键点击Bananas精灵并选择复制。精灵及其脚本会被复制,并自动命名为Bananas2。右键点击Bananas2精灵并选择信息;将会出现一个菜单,允许你更改精灵的名称。将新名称输入为Rotten。你需要对脚本进行的更改在图 19-12 中已标出。

image

图 19-12: 控制烂香蕉的模块

将等待模块的值改为2 ➊,这样每隔 2 秒就会生成一个新的Rotten克隆体,而不是每秒生成一个。同时,将声音模块改为播放F elec bass ➋,并在设置分数的模块中将分数减少1 ➌。记住,你需要先在“声音”选项卡中从库中添加这个声音。

完成烂香蕉的脚本后,接下来你需要更改Rotten精灵的颜色,使香蕉看起来像是烂掉了。选择Rotten精灵并点击造型选项卡。应出现绘画编辑器屏幕(见图 19-13)。

在窗口的右侧,选择桶图标➊;然后,在底部选择不同的颜色➋来为每根香蕉填充不同的颜色。选择棕色、橄榄绿色和深绿色等颜色,表示它们已经烂了。

image

图 19-13: 编辑Rotten精灵颜色

玩游戏

恭喜!你的游戏已经准备好了。要在全屏模式下玩游戏,点击舞台左上角的全屏图标,然后点击绿色旗帜图标。在全屏模式下玩游戏会让它运行得更流畅、更快速。

记住,你可以使用推按钮或键盘键来玩游戏。当游戏结束时,只需点击绿色旗帜图标重新开始。

进一步发展

这个项目只是展示了你可以用 Scratch 做的一小部分内容。以下是一些改进这个游戏的想法:

  • 随着游戏的进行,增加香蕉掉落的速度。

  • 随着游戏的进行,增加腐烂香蕉的数量。

  • 通过创建另一个具有不同控制的角色精灵,使这个游戏支持多人游戏。(你需要添加一个score变量来保存玩家 2 的分数。)

  • 在你的电路中添加可以与 Scratch 接口的其他电子元件,例如按钮、蜂鸣器或传感器。

玩得开心,创造你自己的游戏!

Wi-Fi 远程控制机器人

在这个项目中,你将构建一个两轮电池供电的机器人,使用 Raspberry Pi Zero W 和 MotoZero 附加板。你可以通过 Wi-Fi 使用你用 Node-RED 制作的网页应用程序来控制它。

image

所需部件

Raspberry Pi Zero W(或其他 40 个 GPIO 的 Raspberry Pi)

智能机器人车底盘套件

MotoZero 附加板(或其他电机控制器附加板)

四个 AA 电池

便携充电器

跳线

所需软件

Node-RED 仪表板

项目概要

我们不会直接进入项目,而是会先介绍机器人的最重要部分,给你一个整体的了解。

Wi-Fi

你将使用 Node-RED 应用程序控制机器人,因此你的 Raspberry Pi 需要具备 Wi-Fi 功能。Raspberry Pi 3 和 Zero W 型号内置了 Wi-Fi,如果你的板子没有,你可以使用兼容 Pi 的 Wi-Fi 加密狗。

Raspberry Pi 主板

我们使用的是 Raspberry Pi Zero W,因为它的体积小,非常适合用于小型机器人底盘。但是,只要具有 Wi-Fi 功能,任何带有 40 个 GPIO 的 Raspberry Pi 版本都可以与本项目兼容。

机器人底盘套件

我们使用的是一个机器人底盘套件,里面包含了构建机器人所需的所有部件,包括车轮、电机和螺丝。你可以通过在亚马逊或 eBay 等在线市场上搜索智能车机器人底盘套件来找到这个套件。你需要的是带有两个直流电机的套件。

MotoZero 附加板

直流电机将使机器人移动,你将通过名为 MotoZero 的附加板来控制它们。一个可以找到这个附加板的地方是 The Pi Hut 网站 (thepihut.com/motozero/)。你也可以使用任何其他与 Raspberry Pi 兼容的电机驱动附加板,或者使用 LC293D 芯片构建电路。我们不会在这里详细介绍如何构建这个电路,但如果你想自己制作,可以在网上找到很多教程。

电源

我们不想将 Pi 机器人连接到墙壁插座,因为我们希望它具有便携性,因此需要使用便携充电器或移动电源为机器人供电。移动电源必须能够输出 5V 和 2A。我们使用一款容量为 2200mAh 的移动电源进行了测试,效果良好;如果使用容量更大的电源,机器人可以运行更长时间。

直流电动机需要独立于树莓派供电,这意味着你需要两个独立的电源。为了为电动机供电,我们使用了底盘套件中附带的电池盒和四个 AA 电池(套件中不包含电池)。

Node-RED 应用程序

你将用来控制机器人 Node-RED 应用程序应该能够让机器人前进和后退、左右移动和停止。由于你并没有将树莓派作为桌面计算机使用,树莓派需要在启动时自动启动 Node-RED。你还将为应用程序添加一个关机按钮,以便你可以远程关闭树莓派。

图 20-1 显示了机器人如何工作的高层概览。

image

图 20-1: 机器人结构

准备树莓派

我们使用的是树莓派 Zero W 板,如图 20-2 所示,这是一款带有内置无线局域网和蓝牙的树莓派 Zero 变体,但请记住,你可以使用其他兼容 Wi-Fi 的板子或 Wi-Fi 加密狗。树莓派 Zero W 的尺寸仅为 2.56 英寸 × 1.18 英寸 × 0.20 英寸(65 mm × 30 mm × 5 mm),价格约为 10 美元。

树莓派 Zero 具有 40 个 GPIO 引脚,排列与树莓派 3 相同。如图 20-2 所示,它配备了一个迷你 HDMI 接口和两个 Micro USB 接口,其中一个专用于电源。如果你打算将树莓派 Zero 作为桌面计算机使用,你需要一些额外的配件,例如 USB 集线器、USB 转 Micro USB 适配器和 HDMI 转迷你 HDMI 适配器来连接外设。为了节省一些费用,我们将在常规的树莓派 3 上准备好所有内容,然后将 Micro SD 卡切换到树莓派 Zero W 上。

image

图 20-2: 树莓派 Zero W

我们建议为此项目使用一张新的 Micro SD 卡。请参考“上传操作系统”章节,了解如何在新的 Micro SD 卡上安装最新版本的 Raspbian。

安装操作系统后,将 Micro SD 卡插入你的常规树莓派。启动树莓派,并等待几秒钟,直到系统启动。然后,从桌面右上角点击Wi-Fi配置 Wi-Fi。接下来,输入你的 Wi-Fi 密码,并等待几秒钟,直到 Wi-Fi 连接成功建立。

Node-RED 软件已经预装在树莓派的操作系统中,但你仍然需要安装 Node-RED 仪表板。为此,首先更新库源,然后通过在命令行中输入以下命令安装 npm(Node 包管理器):

pi@raspberrypi:~ $ sudo apt update
pi@raspberrypi:~ $ sudo apt install npm

当系统提示时,键入 Y 并按回车键。安装过程可能需要几分钟。然后输入以下命令,将 npm 升级到最新的 3.x 版本,这是推荐与 Node-RED 一起使用的版本:

pi@raspberrypi:~ $ sudo npm install -g npm@3.x
pi@raspberrypi:~ $ hash –r

最后,输入以下命令以安装 Node-RED 仪表板:

pi@raspberrypi:~ $ sudo npm install --unsafe-perm -g node-red-dashboard

同样,Node-RED 需要在 Pi 启动时自动启动。为此,请在终端中输入以下命令。

pi@raspberrypi:~ $ sudo systemctl enable nodered.service

完成后,关闭树莓派并将 micro SD 卡换到树莓派 Zero W 上。

接线电路

要构建机器人结构,您需要一个机器人底盘、两个带相应车轮的直流电机、MotoZero 附加板、跳线和您的树莓派(带 Wi-Fi)。使用 图 20-1 作为参考。我们将从将 MotoZero 安装在树莓派的顶部开始,然后再将电机接到 MotoZero 上。

将直流电机接线到 MotoZero

MotoZero 允许您独立控制四个电机,但您只需要控制两个直流电机。MotoZero 将以未组装的形式提供,因此您需要焊接它的零件。Pi Hut 在产品页面上提供了组装手册,因此请访问 thepihut.com/motozero/ 并在继续之前按照说明进行操作。组装完成后,您的 MotoZero 应该看起来像 图 20-3 一样。

image

图 20-3: 组装好的 MotoZero 附加板

图 20-3 显示了可以连接到 MotoZero 的接线方式:四个直流电机的正极 (+) 和负极 (–) 接口,以及电源的正极 (+) 和负极 (–) 接口。您需要一个外部电源来驱动电机。电机需要较大的电流跳跃才能运转,因此使用外部电源可以防止当这种电流跳跃发生时,树莓派突然失去电源。

按照这些说明并参考 图 20-1 来连接电机和电池座。

  1. 将右侧直流电机的红线连接到 MotoZero 上的电机 1 正极 (+) 引脚,黑线连接到电机 1 负极 (–) 引脚。您需要松开螺丝,将线放入引脚槽中,然后再重新拧紧螺丝。

  2. 对左侧电机执行相同操作,将电源线连接到 MotoZero 的电机 2 接口。

  3. 在没有插入电池的情况下,将电池座的红线连接到 MotoZero 电源连接器上的正极 (+) 引脚,黑线连接到负极 (–) 引脚,如 图 20-3 所示的电路底部。

注意

如果您发现机器人的车轮旋转方向与您预期的相反,您可能需要交换直流电机的红线和黑线,分别连接到电机 1 或电机 2 的正极 (+) 和负极 (–) 端子。当您在项目结束时测试应用程序时,您就会知道是否需要进行此更改。

使用 MotoZero 控制电机

每个直流电动机都有三个与之相关的 GPIO 引脚。一个引脚,称为使能引脚,用于启动电动机,类似于开关。另两个引脚控制正负电动机线的电源。将电源连接到一根线,GND 连接到另一根线时,电动机会向一个方向旋转;而将电源和 GND 接到相反的电动机线时,电动机会向相反方向旋转。

注意

有关电动机 3 和电动机 4 的 GPIO 信息,可以查阅 MotoZero 手册,访问 The Pi Hut 的产品页面 (thepihut.com/motozero/)。

当你将 MotoZero 安装在树莓派顶部时,我们只使用电动机 1 和电动机 2 的端子,它们由下表中显示的 GPIO 控制。

电动机 1 电动机 2
使能:GPIO 5 使能:GPIO 6
电动机 1 (+):GPIO 27 电动机 2 (+):GPIO 22
电动机 1 (–):GPIO 24 电动机 2 (–):GPIO 17

要使电动机旋转,使能引脚必须为 HIGH 才能启动电动机,且正负引脚之一(且仅有一个)应为 HIGH。例如,如果你想让电动机 1 向一个方向旋转,可以使用以下设置:

  • GPIO 5:HIGH

  • GPIO 27:HIGH

  • GPIO 24:LOW

要让同一个电动机向相反方向旋转,请使用以下设置:

  • GPIO 5:HIGH

  • GPIO 27:LOW

  • GPIO 24:HIGH

要关闭电动机,你需要向所有 GPIO 发送一个 LOW 信号。其他电动机也适用相同的逻辑。

编写应用程序

一旦你组装好硬件,就可以开始创建 Node-RED 应用程序了。由于树莓派已经安装在你的机器人底盘中,最实用的方法是使用你常用的台式机或笔记本电脑,从那里控制它。

首先,你需要找到树莓派 Zero W 的 IP 地址;你将使用它来访问树莓派的 Node-RED 应用程序仪表盘,在那里你可以创建机器人应用程序。

在继续之前,确保树莓派已开启,并且你的计算机和树莓派连接到同一网络。

查找树莓派的 IP 地址

警告

树莓派 Zero W 有两个迷你 USB 端口,其中一个标有 PWR IN,用于为树莓派供电。

通过将 5V 电源适配器连接到墙壁插座,为树莓派供电。在创建 Node-RED 应用程序时,你只使用这个电源;一旦完成,你应该切换到便携电源。

你可以使用 Angry IP Scanner 软件来查找树莓派的 IP 地址。免费从* angryip.org/download/*下载到你的常用台式机或笔记本电脑,并按照提示安装。

安装完成后,打开 Angry IP Scanner 并点击 开始 按钮。等待几秒钟,直到它显示可用的 IP 地址。你的 Pi IP 地址应该显示为 raspberrypi.lan 的主机名,因此记下对应的 IP 地址。图 20-4 高亮显示了我们的 Raspberry Pi IP 地址,即 192.168.1.122。

图片

图 20-4: 使用 Angry IP Scanner 软件查找 Raspberry Pi IP 地址

创建 Node-RED 流程

注意

关于 Node-RED 的介绍,请参见 项目 17。

在你的常规计算机上,确保它与 Raspberry Pi 在同一网络下,打开一个网页浏览器标签并访问 http://<Pi IP 地址>:1880,将 <Pi IP 地址> 替换为你之前记录的 Raspberry Pi IP 地址。在我们的例子中,我们访问了 http://192.168.1.122:1880。你的 Raspberry Pi Node-RED 网络服务器应该会打开。

在窗口的右上角,选择 仪表盘 标签,在 布局 标签内创建一个名为 机器人 的标签。接下来,在该机器人标签内创建两个组,分别为 关闭电源。主组是你将组织控制机器人按钮的地方,而关闭电源组是你将添加远程关闭 Raspberry Pi 按钮的地方。完成这些标签和组之后,你的布局应当与 图 20-5 相似。

图片

图 20-5: Node-RED 应用程序仪表盘布局

向流程中添加五个按钮、一个函数、六个 RPI GPIO 输出节点以及一个执行节点。连接节点并编辑它们的名称,使其与 图 20-6 中的名称一致。

图片

图 20-6: Node-RED 应用程序节点

编辑函数的属性,使其具有六个输出,并根据 表 20-1 显示的内容分配所有节点的属性。

表 20-1: 分配给每个节点的属性

节点 属性

| 前进 | 分组:主 [机器人] 大小:自动

图标:fa-arrow-up

标签:前进

有效载荷:前进 |

| 左 | 分组:主 [机器人] 大小:自动

图标:fa-arrow-left

标签:左

有效载荷:左 |

| 右 | 分组:主 [机器人] 大小:自动

图标:fa-arrow-right

标签:右

有效载荷:右 |

| 反向 | 分组:主 [机器人] 大小:自动

图标:fa-arrow-down

标签:反向

有效载荷:反向 |

| 停止 | 分组:主 [机器人] 大小:自动

图标:fa-hand-paper-o

标签:停止

有效载荷:停止 |

f 函数:在 清单 20-1 中输入代码 输出:6
启用 M1 GPIO: GPIO5 – 29 类型:数字输出
+ M1 GPIO: GPIO27 – 13 类型:数字输出
– M1 GPIO: 18 – GPIO24 类型:数字输出
启用 M2 GPIO: GPIO17 – 11 类型:数字输出
+ M2 GPIO: GPIO6 – 31 类型:数字输出
– M2 GPIO: GPIO22 – 15 类型:数字输出

| 关闭电源 | 分组:关闭电源 [机器人] 大小:自动

图标:fa-power-off

标签:关闭电源

背景:红色 |

| exec | 命令:/usr/bin/sudo + 附加:未选中

关机

名称:关机 |

图 20-7 展示了 exec 节点的设置方式。

image

图 20-7: exec 节点属性

所有节点应当在主组中,除了关机按钮,应该属于关机组。

输入脚本

将 JavaScript 代码插入到清单 20-1 中(也可以从 www.nostarch.com/RaspberryPiProject/ 下载):

清单 20-1: 遥控机器人脚本

➊ var msg1 = { payload: 0 };
  var msg2 = { payload: 0 };
  var msg3 = { payload: 0 };
  var msg4 = { payload: 0 };
  var msg5 = { payload: 0 };
  var msg6 = { payload: 0 };
➋ if (msg.payload === "forward") {
     msg1.payload = 1;
     msg2.payload = 1;
     msg4.payload = 1;
     msg5.payload = 1;
  }
  else if (msg.payload === "left") {
     msg1.payload = 1;
     msg2.payload = 1;
  }
  else if (msg.payload === "right") {
     msg4.payload = 1;
     msg5.payload = 1;
  }
  else if (msg.payload === "reverse") {
     msg1.payload = 1;
     msg3.payload = 1;
     msg4.payload = 1;
     msg6.payload = 1;
  }
➌ return [msg1, msg2, msg3, msg4, msg5, msg6];

这个函数会按照它们连接到函数节点的顺序,将消息发送到连接的 rpi gpio 输出节点。这意味着 msg1 会发送到启用 M1 节点,msg2 会发送到 + M1,msg3 会发送到 – M1,依此类推(参见 图 20-6)。

首先,你将所有负载消息变量值初始化为 0 ➊。然后,一系列的 ifelse if 语句检查按下了哪个按钮 ➋,根据函数收到的负载设置消息值,以决定机器人应执行的动作。例如,如果你按下前进按钮,函数节点接收到的负载是 forward,因此 ➋ 处的条件被满足,代码会将 msg1msg2msg4msg5 的负载值设置为 1,而 msg3msg6 保持为 0

然后,函数节点将msg.payload的值发送到相应的节点➌。为了让机器人前进,负载需要是:

  • 启用 M1: 1

    • M1: 1
  • – M2: 0

  • 启用 M2: 1

    • M2: 1
  • – M2: 0

这里,两个电机都已启用并朝相同方向转动——前进。下表显示了函数应发送给每个节点的消息,以实现每个动作。

动作 启用 M1 + M1 – M1 启用 M2 + M2 – M2
前进 1 1 0 1 1 0
1 1 0 0 0 0
0 0 0 1 1 0
反向 1 0 1 1 0 1
停止 0 0 0 0 0 0

当点击停止按钮时,代码中设置的条件没有一个被满足,函数会发送在开始时初始化的值➊。

在函数节点外部,当点击关机按钮时,exec 节点会执行poweroff命令以关闭 Pi。记住你已经在exec命令属性中填写了/usr/bin/sudo/poweroff——参见表 20-1。

一切就绪后,点击右上角的部署按钮,保存更改并运行流程。

运行应用

现在你的 Node-RED 应用已经准备好了。访问 http://<Pi IP 地址>:1880/ui (将 <Pi IP 地址> 替换为你自己的地址)来查看你的应用仪表盘。它应该像 图 20-8 那样。

测试控制,看看轮子是否朝正确方向转动,别忘了你需要将四节 AA 电池插入电池座,以便为电机提供电源。

如果一个或两个电机转动方向错误,请交换该电机终端上的黑色和红色电线,或者更改负载消息以匹配所需的方向。

image

图 20-8: Node-RED 应用程序远程控制机器人

启动机器人

现在应用程序已经准备好,点击 关机 按钮关闭树莓派。然后等待几秒钟,直到它关闭。

将树莓派的电源从墙壁插座切换到移动电源。等待几分钟,直到树莓派启动并自动启动 Node-RED。在与树莓派处于同一网络的智能手机或其他设备上,打开一个新的浏览器标签页,访问 http://:1880/ui。然后点击按钮来远程控制你的机器人。

恭喜——你现在拥有了一台 Wi-Fi 控制的机器人!

进一步操作

你的机器人有很多升级空间。以下是一些需要硬件和软件更改的升级思路。你需要在 Node-RED 中做一些实验,以使这些功能正常工作:

  • 获取一个四轮的机器人底盘,控制四个电机而不是两个。

  • 给机器人添加 LED 灯和蜂鸣器,使其更具互动性。

  • 添加传感器,例如超声波传感器,让机器人能够自主避开障碍物。

第八章:Raspberry Pi GPIO 引脚指南

本指南是所有现有 Raspberry Pi 板的 GPIO 引脚参考。使用这些表格查看引脚的位置、名称和功能。

以下表格显示了 Raspberry Pi 3 Model B、Raspberry Pi 2 Model B、Raspberry Pi 1 Model A+、Raspberry Pi 1 Model B+、Raspberry Pi Zero 和 Raspberry Pi Zero W 的 GPIO 引脚。

功能 名称 编号 编号 名称 功能
DC 电源 3.3 V 1 2 5 V DC 电源
SDA1, I²C GPIO 2 3 4 5 V DC 电源
SCL1, I²C GPIO 3 5 6 GND
GPIO_GCLK GPIO 4 7 8 GPIO 14 TXD0
GND 9 10 GPIO 15 RXD0
GPIO_GEN0 GPIO 17 11 12 GPIO 18 GPIO_GEN1
GPIO_GEN2 GPIO 27 13 14 GND
GPIO_GEN3 GPIO 22 15 16 GPIO 23 GPIO_GEN4
DC 电源 3.3 V 17 18 GPIO 24 GPIO_GEN5
SPI_MOSI GPIO 10 19 20 GND
SPI_MISO GPIO 9 21 22 GPIO 25 GPIO_GEN6
SPI_CLK GPIO 11 23 24 GPIO 8 SPI_CE0_N
GND 25 26 GPIO 7 SPI_CE1_N
I²C ID EEPROM DNC 27 28 DNC I²C ID EEPROM
GPIO 5 29 30 GND
GPIO 6 31 32 GPIO 12
GPIO 13 33 34 GND
GPIO 19 35 36 GPIO 16
GPIO 26 37 38 GPIO 20
GND 39 40 GPIO 21

Raspberry Pi 1 Model A 和 Raspberry Pi 1 Model B Rev. 2 具有相同的引脚排列,但仅包含前 26 个引脚。

Raspberry Pi 1 Model B Rev. 1 是发布的第一款 Raspberry Pi 板,具有不同于其他所有板的引脚排列。这些板现在已经不再供应,但如果你恰好有一块,下面是它的引脚排列。

功能 名称 编号 编号 名称 功能
DC 电源 3.3 V 1 2 5 V DC 电源
SDA0, I²C GPIO 0 3 4 5 V DC 电源
SCL0, I²C GPIO 1 5 6 GND
GPIO_GCLK GPIO 4 7 8 GPIO 14 TXD0
GND 9 10 GPIO 15 RXD0
GPIO_GEN0 GPIO 17 11 12 GPIO 18 GPIO_GEN1
GPIO_GEN2 GPIO 21 13 14 GND
GPIO_GEN3 GPIO 22 15 16 GPIO 23 GPIO_GEN4
DC 电源 3.3 V 17 18 GPIO 24 GPIO_GEN5
SPI_MOSI GPIO 10 19 20 GND
SPI_MISO GPIO 9 21 22 GPIO 25 GPIO_GEN6
SPI_CLK GPIO 11 23 24 GPIO 8 SPI_CE0_N
GND 25 26 GPIO 7 SPI_CE1_N

第九章:解码电阻值

本快速指南展示了如何识别不同的电阻器。电阻器是一种被动电子元件,用于对电流流动产生阻力,限制电流到达电路的某个部分。在本书中,你主要使用电阻器来限制电流到达 LED。

使用这个电阻颜色图表来帮助你根据电阻器上的色带计算电阻值。

颜色 第一条色带 第二条色带 第三条色带 乘数 公差
黑色 0 0 0 1 Ω
棕色 1 1 1 10 Ω +/–1%
红色 2 2 2 100 Ω +/–2%
橙色 3 3 3 1 KΩ
黄色 4 4 4 10 KΩ
绿色 5 5 5 100 KΩ +/–0.5%
蓝色 6 6 6 1 MΩ +/–0.25%
紫色 7 7 7 10 MΩ +/–0.10%
灰色 8 8 8 +/–0.05%
白色 9 9 9
金色 0.1 Ω +/–5%
银色 0.01 Ω +/–10%

电阻器通常有四条色带。前两位数字代表电阻值的前两位数字。第三条是乘数,表示前两位数字后面零的数量。第四条是电阻的公差。

例如,让我们计算图 B-1 中电阻器的电阻值。

图片

图 B-1: 四条色带电阻器

  • 第一条色带是绿色(5)= 5。

  • 第二条色带是蓝色(6)= 6。

  • 第三条色带是黄色(4)= 0000(表示零的数量)。

  • 第四条色带是金色,所以公差为+/– 5%。

电阻值为 56 × 10,000(10 kΩ),结果为 560 kΩ。公差为 5%,这意味着电阻值可能在 560 kΩ ± 5%之间,即在 532 kΩ和 588 kΩ之间。除非你使用非常敏感的元件,否则不必过于担心公差。对于本书,公差为 5%的电阻器完全可以使用。

更新

访问www.nostarch.com/RaspberryPiProject/获取更新、勘误和其他信息。

更多实用书籍来自 图片 NO STARCH PRESS

图片

ARDUINO 项目手册,第 1 卷

25 个实用项目让你快速上手

作者 马克·吉迪斯

2016 年 6 月,272 页,$24.95

ISBN 978-1-59327-690-4

全彩

图片

ARDUINO 发明者指南

通过制作 10 个酷炫项目来学习电子学

作者 布莱恩·黄 德里克·伦伯格

2017 年 6 月,336 页,$29.95

ISBN 978-1-59327-652-2

全彩

图片

ARDUINO 工作坊

通过 65 个项目进行实践入门

作者 约翰·博克索尔

2013 年 5 月,392 页,$29.95

ISBN 978-1-59327-448-1

图片

生还者指南:丧尸末日

用简单电路、Arduino 和树莓派保护你的基地

作者 西蒙·蒙克

2015 年 10 月,296 页,$24.95

ISBN 978-1-59327-667-6

Image

ARDUINO 操场

适合经验丰富的创客的极客项目

作者 WARREN ANDREWS

2017 年 3 月,344 页,$29.95

ISBN 978-1-59327-744-4

Image

LED 项目手册

编辑 JOHN BAICHTAL

2018 年春季,280 页,$24.95

ISBN 978-1-59327-825-0

全彩

电话:

1.800.420.7240 或 1.415.863.9900

电子邮件:

sales@nostarch.com

网站:

www.nostarch.com

Image

posted @ 2025-11-25 17:04  绝不原创的飞龙  阅读(28)  评论(0)    收藏  举报