树莓派项目手册-全-

树莓派项目手册(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

你是否曾想过你可以用 35 美元买到一台电脑?不是的,我们说的可不是从典当行买来的古董电脑。我们说的是 Raspberry Pi——一块大约与信用卡大小相当的计算机板。但是,不要被它看似脆弱的外观欺骗;它比看起来要强大得多。

Raspberry Pi 是由 Raspberry Pi 基金会创始人 Eben Upton 在英国开发的。Upton 和他的同事们注意到,申请剑桥大学计算机科学专业的学生技能逐渐下降,意识到这是因为新一代的学生成长在易于使用的电脑环境中。学生们从未真正需要了解计算机是如何工作的,也没有太多机会进行编程或动手操作。Upton 开发 Raspberry Pi 是为了推动学校中基础计算机科学的教学。这也是为什么 Pi 是一个裸板而不是装在机箱里的电脑的原因:让每个人都能看到计算机的各个组件。

自 2012 年首次推出以来,已有超过一千万块 Raspberry Pi 板被售出。Raspberry Pi 不仅在学生中广受欢迎,还吸引了电子爱好者、动手能力强的人、计算机科学家以及各个年龄段的“孩子”。

Raspberry Pi 可以作为一台普通计算机使用——你可以上网、发邮件、写文档、观看视频等等——但这并不是它的主要目的。Pi 是你可以进行实验、黑客攻击、玩弄的工具,用来构建你自己的程序和发明。Raspberry Pi 和类似的板子不仅让参与电子学和编程世界变得可能,而且变得容易,从而催生了全球范围内大量的创意发明。现在,该轮到你发明一些东西了。

本书适合谁?

本书适合任何想要充分利用 Raspberry Pi 的初学者,无论是希望探索电子学和编程世界的孩子,还是希望帮助孩子和学生学习的家长和教育工作者,亦或是希望使用 Raspberry Pi 实现创意的爱好者和创客。

我们不假设读者具备关于 Raspberry Pi、电路或编程的任何先验知识。如果你已经有一些基础技能,本书将帮助你进一步提升,并给你接下来的创意方向。

关于本书

20 个简单的 Raspberry Pi 项目是一本包含 20 个 Raspberry Pi 项目的合集。我们相信,发现新概念的最佳方式是通过实践,而项目书是入门的绝佳途径。

本书从 Raspberry Pi 和 Python 编程语言的简要介绍开始,然后跳入项目实践。全书采用“边做边学”的方式,这意味着你不需要在实际动手制作一些酷炫的项目之前花费无数小时学习理论概念。

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

如果你没有编程或制作电路的经验,不用担心。每个项目都会提供逐步的说明,帮助你搭建电路、绘制原理图,并编写用于编程 Pi 的代码。你可以从书中复制代码,或者访问* www.nostarch.com/RaspberryPiProject/ *下载代码。

你的 Pi 学习不应仅限于本书中的项目,这就是为什么每个项目都有一个“进一步拓展”部分,我们会提供一些建议,帮助你进一步开发项目,并将书中学到的概念结合起来,构建更加复杂和有趣的内容。我们希望在书本结束时,你能具备必要的技能,将自己的项目想法付诸实践。

你需要什么?

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

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

本书中的大多数项目涉及使用 Pi 与外界通过电路进行互动。这意味着除了树莓派,你还需要一些电子元件。在每个项目的开始,我们会提供所需零件的清单,以及零件的成本和预计的构建时间。带有一个 $ 符号的项目可能花费不到 10 美元的元件,而带有两个 $ 符号的项目可能在 10 至 50 美元之间(这个范围较宽,但零件的价格会因购买地点而异)。如果你想一次性购买所有零件,也可以参考 第 9 页 的“项目组件清单”,它列出了本书所需的所有组件。

本书结构

本书分为七个部分。《入门篇》是对树莓派的介绍,对于正确设置你的树莓派是必不可少的,后面的部分则包含了项目。以下是本书的结构安排:

入门篇

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

LED

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

显示屏

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

传感器

这些项目使用了几种类型的传感器。传感器非常棒,因为它们能够让你的项目与世界互动。几乎你能想到的任何东西都有传感器。你将学习如何使用温度传感器、PIR 运动传感器等。

相机

本部分的项目使用树莓派相机模块。你将学习如何使用相机拍照、如何进行视频流传输,然后利用你的技能构建酷炫的项目,比如入侵者检测器和家庭监控系统。

Web 应用

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

游戏和玩具

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

在书的最后,你还会找到一些参考信息,包括迄今为止所有可用的树莓派板子的 GPIO 指南,以及你可以用来识别不同电阻值的电阻色环图。

本书中的项目是独立的,这意味着没有特定的顺序,你可以随时选择任何你感兴趣的项目来制作。

我们希望你能享受本书中的项目制作过程,并享受学习的乐趣。

image

第一章:入门

在本入门中,你将为接下来的项目做好所有准备。我们将向你介绍树莓派板及其最重要和最有趣的部分。然后我们将带你一步步设置树莓派所需的所有硬件和软件。

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

完成本入门后,你将准备好开始进行项目。

开始使用树莓派

我们将从探索树莓派板的用途开始,收集使其运行所需的东西,并将操作系统加载到一张新格式化的 microSD 卡上,然后插入树莓派板中。

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

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

image

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

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

树莓派的用途

那么,如果树莓派像一台普通的计算机,但有一些限制,它的优势是什么呢?树莓派让你能够以一种你在普通计算机上无法做到或不敢做到的方式进行实验。计算机价格昂贵且修复难度大,所以你不愿意在没有完全了解自己的操作的情况下做过多的改动。然而,正如 Linux 创始人 Linus Torvalds 在接受 BBC 新闻采访时所说,树莓派让“承受失败”成为可能。你可以在树莓派上随意玩弄硬件和软件,而不用担心损坏昂贵的设备或丢失重要文件!

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

创建你自己的程序 Pi 附带一套免费的软件编写工具。如果你犯了错误,你只需擦除 Pi 并重新开始。

创建你自己的电子项目 Pi 板具有 GPIO 引脚,可以连接传感器和其他电子设备,从而与现实世界进行交互。你可以创建有用的物品,并且在不耗费大量电力的情况下让你的项目 24/7 全天候运行。

Raspberry Pi 的应用几乎没有限制。以下是全球各地的人们制作的一些项目示例:一台复古游戏机,用于玩老式街机游戏;一个超级计算机,通过将多个 Pi 板连接起来构建;一台云服务器,用于存储和访问数据;一个媒体中心,用于在电视上组织和播放媒体;一个家庭自动化系统,用于控制和监控家中的设备;一系列非常酷且有用的机器人,比如农业机器人和自动驾驶机器人车——这些只是其中的一部分。作为一个非常酷的 Pi 项目示例,看看 图 0-2 展示的 SeeMore 雕塑,它通过关联一个 256 节点 Raspberry Pi 集群的运动,展现了并行算法的美。

image

图 0-2: SeeMore 项目

区分各个板子

Raspberry Pi 板有多个版本,如 图 0-3 所示。

image

图 0-3: Raspberry Pi 板的时间轴

本书中我们将使用 Raspberry Pi 3 Model B,这是我们推荐购买的板子。不过,如果你已经有了旧版的 Raspberry Pi,它也能支持本书中的大部分项目。(如果你使用的是 26 个 GPIO 引脚的模型,而不是 40 个引脚,你可能需要更改一些项目电路,可能需要进行一些研究。你也可以参考 “Raspberry Pi GPIO 引脚指南” 中的内容,在 第 261 页 查找旧版板子的引脚分布。)

Raspberry Pi 板在多个方面有所不同,包括 GPIO 引脚的数量以及连接器的类型和数量。表 0-1 显示了 Model B 板的不同特性。(请注意,较新的板子具有更好的特性,例如更多的内存和更强的处理器。你还可以看到,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
GPIO 引脚 26 40 40 40
以太网端口
HDMI
存储 SD MicroSD MicroSD MicroSD
Wi-Fi - - -
蓝牙 - - -
价格 $35 | $25 $35 | $35
电源 MicroUSB MicroUSB MicroUSB MicroUSB

了解树莓派及其配件

让我们仔细看看树莓派主板。图 0-4 显示了一个标注的树莓派 3 型号 B 主板。

image

图 0-4: 树莓派 3 型号 B

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

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

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

音频插孔 连接音频设备。

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

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

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

DSI(显示串行接口)连接器 使您能够轻松连接兼容 DSI 的显示器,如 LCD 触摸屏,使用 15 针排线连接。

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(或更大)Class 10 的 microSD 卡。如果您使用的是树莓派 1 型号 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(可选)

面包板

5mm 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 Camera Module v2

继电器模块 HL-52S

塑料盒外壳

12V 灯泡及灯座

12V 电源适配器

公型 DC 插座电源接口

DHT22 温湿度传感器

MotoZero 扩展板

智能机器人车底盘套件

四节 AA 电池

便携式充电器

打火机

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

你还需要一把烙铁以及焊料、吸锡带和帮助手等配件。此外,针嘴钳、迷你剪钳和剥线钳也会很有帮助。

上传操作系统

Raspberry Pi 运行的是 Linux 操作系统,这是一款由来自全球的专家志愿者合作构建的开源软件。由于 Linux 是开源的,任何人都可以访问源代码。

注意

有 Android 和 Windows 版本适用于 Pi,但它们的性能不如 Linux 系统。最好使用 Linux,它是 Raspberry Pi 支持最好的操作系统。

Raspberry Pi 有多个 Linux 发行版,但推荐给初学者使用的是 Raspbian,因为它在 Raspberry Pi 社区中有最广泛的支持。Raspbian 也是免费的,可以下载。

由于 Raspberry Pi 没有硬盘,你需要将操作系统安装到 microSD 卡上,而为此你需要另一台普通的个人计算机。

使用 New Out Of the Box Software(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: 在树莓派上更改密码

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

添加新用户账户

要在你的树莓派上添加新用户账户,打开终端并输入以下命令,将“username”替换为你想给用户取的名字:

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: 树莓派终端

在终端中,你可以输入特定的命令来控制 Raspberry Pi。例如,你可以使用命令 ls foldername 来查看文件夹中的内容,或使用 mkdir foldername 来创建一个新文件夹。你可以像使用计算机一样做几乎所有事情,只不过不是点击图标,而是输入一些特定的文本命令。

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

将 Pi 连接到互联网

你可以通过 Wi-Fi 或使用以太网电缆连接 Pi 到互联网。通过 Wi-Fi 连接与普通计算机一样简单。

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

image

图 0-23: 连接到 Wi-Fi

如果你使用以太网电缆,只需将它插入 Pi 的以太网端口即可。

访问互联网并浏览网页

Raspberry Pi 浏览器是 Chromium。要打开 Chromium,请点击任务栏上的Chromium图标(一个蓝色的地球图标),或者在任务栏菜单中选择InternetChromium

图 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: 关机菜单选项

在移除电源适配器之前,等待绿色 LED 停止闪烁。

你还可以从关机菜单中重新启动或注销你的 Pi。如果你想重新登录,只需输入用户名和密码即可。注销选项对于需要切换帐户时非常有用。

你的 Pi 现在已准备好运行一些程序!但在此之前,让我们学习一些 Python 编程基础,包括用户输入、变量、数学运算符、条件语句和循环。

开始使用 Python

Python 是你在本书中大多数项目中使用的编程语言。它是最简单、最易学的编程语言之一,因此即使你从未编程过,它也是一个不错的入门选择。

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

注意

我们仅在 项目 12 中使用 Python 2,该项目使用了 Python 3 写作时无法使用的包。

Python 3 集成开发环境

要编写 Python 代码,你需要 Python 3 的 IDLE(Integrated DeveLopment Environment),安装 Raspbian 时会自动安装该环境。将你的 Pi 连接到显示器、鼠标和键盘后,通过任务栏主菜单启动 IDLE,然后选择 编程Python 3 (IDLE)。 图 0-27 显示了 Python Shell,它是打开的 IDLE 窗口的名称。

image

图 0-27: Python Shell

Python Shell 是你输入并运行 Python 指令的地方,用于告诉 Python 做什么。Python 使用 解释器 来运行你的指令,解释器实际理解并执行你的代码。

当 Shell 打开时,你应该会看到三个箭头 >>>,如 图 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 数据类型

数据类型 描述
整数 整数(完整的)数值
浮点数 带小数点的数字
字符串 由引号括起来的一组字符
布尔值 真或假

我们来看一下表中每个数据类型的示例:

>>> 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语句,但如果有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代码块的最后一行执行选定的数学运算并打印结果 ➒。

如果用户输入5running变量将被设置为Falsewhile循环停止 ➓。

运行脚本

将你的脚本保存为calculator.py。然后按F5或去运行运行模块。 图 0-31 展示了你应该看到的内容。

image

图 0-31: 运行calculator.py

进一步扩展

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

第二章:使 LED 闪烁**

在这个第一个项目中,你将把一个 LED 连接到你的树莓派,并通过一个 Python 脚本使它闪烁。学习如何通过 GPIO 引脚控制 LED 闪烁是你树莓派教育的重要步骤;一旦你学会了如何控制 LED,你就可以控制几乎任何输出设备,无论是马达、灯泡,还是甚至是烤面包机。

image

所需部件

树莓派

面包板

5 毫米 LED

330 Ω 电阻

跳线

介绍 GPIO 引脚

通用输入/输出(GPIO) 引脚允许你将电子硬件(如 LED 和传感器)连接到你的树莓派。它们可以用于读取和发送信息,使得你的树莓派能够与真实世界进行互动。

树莓派 3 型 B 主板有一排 40 个 GPIO 引脚,见图 1-1。这个布局与树莓派 2 型 B 和树莓派 1 型 B+相同,但与树莓派 1 型 A 和 B 略有不同,后者只有前 26 个引脚。如果你使用的是其他型号的树莓派,可以查阅“树莓派 GPIO 引脚指南”(见第 261 页)。

image

图 1-1: 树莓派 GPIO 布局

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

功能 名称 编号 编号 名称 功能
直流电源 3.3 V 1 2 5 V 直流电源
SDA1, I²C GPIO 2 3 4 5 V 直流电源
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
功能 名称 编号 编号 名称 功能
--- --- --- --- --- ---
直流电源 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.3V 工作,因此如果将它们连接到更高电压,会永久损坏你的树莓派。

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

介绍 LED

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

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

image

图 1-2: 一个 5 毫米红色 LED

选择合适的电阻器

LED 只能承受一定量的电流,超过该电流会导致 LED 过载并烧毁,这可能会损坏 LED 甚至损坏树莓派主板。为了防止这种情况发生,你必须始终将 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 引脚是阳极。

我们已经给你提供了电阻器放置的位置说明,但实际上它是否连接到阳极或阴极并不重要,只要它连接到其中一个即可。你可能会想,为什么我们不直接将 LED 的阴极连接到引脚 6(GND)呢,因为只需要那个 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),并进入文件新建文件来创建一个新的脚本。将以下代码复制到 Python 编辑器,并将脚本保存为blinking_led.py,保存在LEDs文件夹内(记住你可以在* 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 循环

在➎处,你开始一个while循环,条件是True,这意味着这个循环将永远运行,直到你自己停止程序。紧随循环声明之后的代码行会缩进,告诉 Python 这些是循环的内容,只要while条件满足就会执行。

设置数字输出

接下来,你需要为 LED 设置数字输出。你可以使用led.on()函数 ➏ 将 GPIO 25 设置为高电平,打开 LED,使用led.off()函数 ➑ 将 GPIO 25 设置为低电平,关闭 LED。每次 LED 状态变化时,使用sleep()函数 ➐ 暂停 1 秒,形成闪烁效果。代码在此停下,等待delay变量中指定的时间(以秒为单位) ➍ 后继续执行下一行代码。这样,你就可以让 LED 在给定的时间内保持开或关状态。

运行脚本

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

image

图 1-4: 完成的项目

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

进一步拓展

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

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

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

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

第三章:按钮 LED 手电筒**

这个 LED 手电筒项目将教你如何使用按钮开关,这是电子电路中的一项基础技能。你将学习如何在电路中使用按钮开关来触发事件——在这种情况下,当按钮被按下时点亮 LED,并在松开时停止事件。

image

所需部件

树莓派

面包板

5 毫米 LED

330 Ω 电阻

跳线

介绍开关和按钮

开关无处不在。你用它们来开灯、调节搅拌机的速度、点亮炉灶等。市场上有各种各样的开关,但你在家里常见的包括按钮开关、拨动开关、旋转开关和磁簧开关。Figure 2-1 展示了一些常见的电子开关。

image

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

开关可以作为 中断器,中断电流以允许或限制某个组件的电源,或者作为 分流器,将电流引导到电路的另一部分。这个项目将使用一个简单的按钮开关——一个中断器开关,它非常适合电子电路,因为它便宜、完美适配面包板,而且易于操作。

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

image

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

常闭型 按钮开关在按钮没有被按下时,电路是闭合的,允许电流流动,直到你按下按钮使电路打开并停止电流的流动。

按钮开关可以有两脚或四脚。四脚按钮开关在原型设计项目中更为常见,因为它们非常适合面包板。图 Figure 2-2 的右侧展示了一个典型的常开型四脚按钮开关的示意图。脚 1 和脚 2 始终连接,脚 3 和脚 4 也是如此。

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

接线电路

在这个项目中,你希望 LED 只有在按钮按下时才会点亮,因此你的树莓派需要能够识别按钮是被按下还是没有被按下。你的树莓派将通过 GPIO 引脚获取这个信息。

按照以下步骤创建你的电路,参考 Figure 2-3 进行操作:

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

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

  3. 将按钮插入面包板的中间位置,使得两个引脚位于分隔线两侧。你需要确保顶部的两个引脚和底部的两个引脚之间没有连接。(记住,面包板中央的这一条横向插槽是断开的。)

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

image

图 2-3: LED 手电筒电路

编写脚本

该脚本会根据按钮是否被按下或释放触发不同的事件。下面的 伪脚本——这是一种用英语描述代码的方式——应当帮助你理解 Python 脚本。编写伪脚本是概述程序的好方法。以下是程序的工作流程:

  • 当按钮被按下时,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 和按钮的 LED 和 Button 库;然后,你从 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 只能读取数字信号,这意味着它们只能读取高电平(3.3V)或低电平(0V),中间的值不能读取。然而,电位器是模拟输入,旋转旋钮会将其输出电压从 0V 变化到 3.3V。你希望树莓派能够读取所有中间的值——比如 1V、1.4V、1.8V 等——这样你就能实现渐变的亮度,而不仅仅是开和关。为此,你需要通过模拟到数字转换器芯片将模拟信号转换为数字信号,然后使用脉宽调制生成模拟信号。我们将在开始构建之前讲解这两个主题。

模拟到数字转换器

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

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 电源

脉宽调制(PWM)

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

如果你将 LED 的电压在 HIGH 和 LOW 之间快速切换,你的眼睛无法跟上 LED 开关的速度;你会看到亮度的渐变效果。这基本上就是 PWM 的工作原理——通过输出在 HIGH 和 LOW 之间快速变化的信号,达到非常高的频率占空比是指 LED 在一个周期内设置为 HIGH 的时间比例。图 3-5 展示了 PWM 是如何工作的。

image

图 3-5: PWM 工作原理

50% 的占空比意味着 LED 亮度为 50%,占空比为 0 时 LED 完全关闭,占空比为 100 时 LED 完全亮起。通过改变占空比,你可以调节 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 亮度的电路

编写脚本

树莓派通过 SPI 通信从 MCP3008 芯片读取模拟值,因此你需要先启用 SPI。

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

image

图 3-7: 启用 SPI 通信

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

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

  • 使用 PWM 控制 LED 的亮度。

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

输入脚本

打开Python 3 (IDLE),然后选择文件新建文件来创建一个新的脚本。将以下代码复制到 Python 编辑器,并将脚本保存为brightness_controller.py,保存在LEDs文件夹内(记得你可以在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(),但你也可以自己编写用户自定义函数,正如本脚本中所示。阅读“用户自定义函数”框来了解更多。

控制强度与关闭窗口

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 参数是你将小部件添加到的窗口,而每个选项参数允许你个性化你的滑块。在rgb_led_controller.py脚本中,你使用了以下选项:

  • from_定义了滑块范围的下限。

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

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

  • orient定义了滑块的方向,可能是HORIZONTAL(水平)或VERTICAL(垂直)。这个脚本将滑块设置为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或选择RunRun Module来运行脚本。你的界面窗口应该会弹出,如图 4-5 所示。

image

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

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

要关闭程序,只需点击关闭按钮。

更进一步

现在你知道如何制作图形用户界面,我们鼓励你编辑其参数,以定制界面的外观,符合你的个人喜好。这里有一些你可以尝试的其他项目想法:

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

  • 制作一个图形用户界面,用来控制 LED 的亮度。

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

第六章:彩虹灯带**

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

image

所需零件

Raspberry Pi

面包板

WS2812B 可寻址 RGB LED 灯带

逻辑电平转换器模块 BSS 138

两个 10 kΩ 电位器

MCP 3008 芯片

按键

三个头针

跳线

所需软件

WS2 81X 库

介绍 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 引脚。

注意

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

现在你需要确定你的电源。LED 灯带需要一个 5V 电源。你可以通过每个 LED 所需的功率来确定所需的电流。每个 LED 在全亮时会消耗最多 60 毫安(这时产生白光),但由于你很少需要所有 LED 长时间以最大亮度运行,你可以安全地估算每个 LED 需要 20 毫安。因此,如果你的灯带有 14 个 LED,你将需要一个大约为 20 × 14 = 280 毫安的 5V 电源。

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

介绍逻辑电平转换器

逻辑电平转换器 允许你将 3.3 V 信号转换为 5 V 信号。虽然有许多种逻辑电平转换器,但在本项目中,你将使用如图 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),并将插针焊接到 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)通信,以便条形灯带能够与树莓派进行通信。

安装 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并按 ENTER 键。重启你的 Pi 以使更改生效,然后继续进行库的安装。

打开Python 3(IDLE),点击文件新建文件来创建一个新脚本。将 Listing 5-1 中的代码复制到 Python 编辑器中,并将脚本保存为rainbow_effect.py,存放在LEDs文件夹内(记得你可以下载所有脚本,网址是www.nostarch.com/RaspberryPiProject/):

LISTING 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,你会看到 32 个矩形,每个矩形由 5×8 像素组成。开关像素的组合构成了字符的形状。

焊接头引脚

很可能你的 LCD 模块会配有分离的头引脚,如图 6-3 所示。你需要将引脚焊接到模块上,使其适合面包板。将引脚插入可用的孔中——应该有 16 个引脚对应 16 个孔——并确保长端朝下,然后将其焊接固定。

image

图 6-3: 带有分离头引脚的 LCD

LCD 模块引脚图

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

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

警告

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

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

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. 将面包板的电源轨连接到 5 V 和 GND。

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

  3. 添加一个电位器来调整对比度:将一个外部引脚连接到 GND,另一个外部引脚连接到 5 V,然后将中间引脚连接到 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 个字符的消息,屏幕会显示不全。所以,现在我们将向你展示如何编写一个脚本,显示一条更长的消息,并让它在屏幕上滚动,就像一个你不能错过的医生预约提醒。在第一行,你会显示消息的标题,比如“Reminder”或“Don’t forget”,在第二行,你的提醒消息将滚动显示。

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

  • 第一行显示一个静态标题。

  • 第二行显示一个滚动消息。

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

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

  • 字符应该从最左侧消失。

消息会一直滚动,直到停止。在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 ➍是字符停留在一个位置的时间,之后向左移动一个字符。在这个例子中,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,学习如何从传感器读取数据。

第八章:小型天气预报器**

在这个项目中,你将构建一个天气预报器,将你所在位置当天的天气显示在 OLED 显示屏上。你将学习如何发起 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 显示屏,OLED 显示屏的功耗更低。

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 请求你选择位置的当天天气预报。学会在 Raspberry Pi 上使用 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 终端中。用你自己的 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是你网址的单引号括起来的参数。

在➌位置,你创建了temp_max变量来存储你请求的特定数据。在这个案例中,你想要获取最大温度。

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

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

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

你需要通过speed的父节点wind来请求风速信息。

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

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

学会了如何在 Python 中进行 API 请求后,你就准备好开始这个项目了!

requests 库

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

接线电路

只需根据表格中显示的引脚图将 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 通信协议与 Pi 进行通信,因此你需要在 Pi 上启用 I²C 通信。进入桌面主菜单,选择首选项树莓派配置。在接口选项卡中,启用 I²C,如图 7-4 所示,然后点击确定

image

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

I²C 通信协议

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

输入脚本

打开Python 3 (IDLE),并选择文件新建文件以创建一个新脚本。将清单 7-4 中显示的代码复制到 Python 编辑器,并将脚本保存为weather_forecast.py,保存在Displays文件夹中(记住,你可以在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 图像库(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  要显示的文本

字体  文本将显示的字体

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 是一个附加板,可以为你的树莓派提供更多的功能,带有像 LED 矩阵、摇杆和多个传感器等额外特性,能够获取外部世界的信息。

image

所需配件

树莓派(带 40 个 GPIO 的版本)

Sense HAT

你将使用 Sense HAT 的 LED 矩阵来显示游戏,使用摇杆来进行游戏。如果你没有硬件设备,不用担心:你还将学习如何使用 Sense HAT 模拟器来创建相同的游戏而无需硬件。

介绍 Pong 游戏

Pong 是历史上最早的电子游戏之一,它是一款极受欢迎的 2D 乒乓球游戏,可以进行单人或双人模式游戏。你将创建单人版,因此更像是玩壁球:你用球拍将球反弹到墙上,当球回来时用球拍接住它。如果你错过了球,你就输了。

介绍树莓派 Sense HAT

树莓派 Sense HAT 配备了一个 8×8 RGB LED 矩阵、一个五按钮摇杆、一个陀螺仪、一个加速度计、一个磁力计、一个温度传感器、一个气压传感器和一个湿度传感器,所有这些功能集成在一个板子上,如图 8-1 所示。

image

图 8-1: 树莓派 Sense HAT

安装板子

注意

Sense HAT 不兼容树莓派 1 Model A 和 B,但如果你使用不兼容的板子,可以通过模拟器来构建这个项目。

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

将 Sense HAT 上的 40 个 GPIO 连接到树莓派上的 40 个 GPIO 接口;两个板子应当完美对齐。当你第一次成功将 Sense HAT 安装到通电的树莓派上时,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 的颜色。

显示文本

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

LISTING 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 之间的整数(如你在 Project 5 中所做的那样)。

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

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

控制特定的 LED

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

image

FIGURE 8-4: Sense HAT 坐标系统

要使用相应的颜色点亮 Figure 8-4 中的 LED,你可以使用 Listing 8-2 中的代码。

LISTING 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 的列表来确定每个 LED 的颜色。请查看 Listing 8-3 中的代码,它显示了一个悲伤的表情。

LISTING 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 不会在代码中显示为红色。我们只是高亮它们,以便更容易地可视化 LED 的显示效果。

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

image

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

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

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

从摇杆读取数据

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

  • 向上移动

  • 向下移动

  • 向右移动

  • 向左移动

  • 按下

你需要告诉你的程序每个控制选项应该让树莓派执行什么操作。清单 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()

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

其他的移动函数也是以相同的方式工作。

编写脚本

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

游戏的目标是:

  • 一只长度为 3 像素、宽度为 1 像素的拍子应该出现在第 0 列。

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

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

  • 当球击中物体——墙壁、天花板或拍子——时,它应该沿相反的对角线方向移动。

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

输入脚本

打开 Python 3 (IDLE) 并进入 文件新建文件 来创建一个新的脚本。然后将 清单 8-5 中的代码复制到新文件,并将脚本保存为 pong_game.pyDisplays 文件夹中(记得你可以在 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 像素长的条形,沿着最左边的列上下移动。在➌处,你通过y = 4定义了蝙蝠的起始位置,距离顶部 4 个像素。完整的蝙蝠在draw_bat()函数中绘制,位置为起始位置顶部再加一个像素(y - 1),底部再加一个像素(y + 1),使蝙蝠的长度为 3 个像素。

移动蝙蝠

蝙蝠只在 y 轴上移动,因此它的 x 坐标始终是0,但是它的 y 坐标需要根据玩家移动蝙蝠而变化。换句话说,玩家只能上下移动蝙蝠。move_up()move_down()这两个函数在➐处定义,控制这些移动。在➒处,您通过分别调用move_up()move_down()来告诉树莓派,当玩家上下移动摇杆时应该执行什么操作。

详细查看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参数允许你传递一些关于摇杆的信息给函数——例如,摇杆使用的时间;推动的方向;以及是按下、释放还是按住——这样树莓派就知道该如何反应。

提示

在 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或者去运行运行模块来运行脚本。

当你失败并且游戏结束时,LED 矩阵会显示一个如图 8-6 所示的难过表情。

image

图 8-6: 当游戏结束时,LED 矩阵显示一个难过的表情

更进一步

下面是一些升级你游戏的建议:

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

  • 添加一个得分系统,每当球击中棒球时就得 1 分,并在屏幕上显示得分。

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

第十章:一体化气象传感器站

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

image

所需部件

Raspberry Pi(带有 40 个 GPIO 接口的版本)

Sense HAT

Sense HAT 作为气象站

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

温度传感器

注意

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

顾名思义,温度传感器用于测量温度。默认情况下,Sense HAT 以摄氏度读取温度,因此如果你需要以华氏度显示温度,需将读取值进行转换。转换公式为:将摄氏度数值乘以 9,除以 5,再加上 32,具体公式如下所示。

image

你可以将此公式添加到代码中,这样程序就能为你自动转换。

湿度传感器

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

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

气压传感器

气压传感器读取大气压力,即在给定点的空气“重量”,单位是 hPa(百帕),相当于 mbar(毫巴)。为什么测量气压很有趣呢?因为大气压力的变化有助于预测天气。气压上升通常意味着好天气即将到来,而气压下降则意味着坏天气的预兆,比如降雨或风暴。

气压变化非常小。你需要仔细跟踪气压计的读数,才能注意到变化趋势。

读取温度、湿度和气压

现在让我们看看如何从传感器读取数据并将其输出到 Python shell。

将 Sense HAT 安装在你的树莓派上,就像在项目 8 中一样,并确保它连接良好。当首次连接时,Sense HAT 应显示一个彩虹背景,与你在启动树莓派时在屏幕上看到的彩虹匹配(见图 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
温度标题 文本:“Temperature”,字体:Helvetica,大小:18,位置:南
温度值 字体:Courier,大小:20,颜色:红色,位置:北
压力标题 文本:“Pressure”,字体: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_bar的变量,类型为double ➐,这是进度条所接受的变量类型。最后,第 ➑ 行的代码创建了湿度进度条并放置在画布上。

显示温度和压力的标题和值的过程与在 ➌、➍ 和 ➎ 中所述的步骤相同。

自动更新读数

在 ➒ 处,你定义了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() 中,并告诉 Pi 每 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

两个 5mm 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()的第一个参数,将端口作为整数传递给第二个参数。在这个脚本中,我们使用的是 Gmail 的 SMTP 服务器和端口。如果你使用其他电子邮件提供商,确保更改这些值。

server.starttls()函数对于使用 TLS 加密邮件的电子邮件提供商是必需的。如果你的电子邮件提供商不使用 TLS,你可以删除或注释掉这一行代码。

接下来,脚本登录到发送电子邮件的账户第➎行,发送电子邮件,并停止与服务器的通信。最后,脚本会向 Python shell 打印'Email sent'消息,以告知用户电子邮件已发送。

运行发送电子邮件的脚本

现在是时候看看你的脚本实际运行了!保存你的脚本并按F5或进入运行运行模块来运行脚本。然后检查你发送邮件的收件箱。你应该会收到一封新邮件。你可以在图 10-2 中看到我们通过此脚本收到的电子邮件。

image

图 10-2: 使用send_email.py发送的电子邮件

如果你没有收到电子邮件,检查send_email.py中的电子邮件和 SMTP 信息是否正确。同时,确保你已经在电子邮件帐户设置中允许较不安全的应用访问你的账户。

连接电路

现在让我们将 PIR 传感器连接到你的树莓派,这样当传感器检测到运动时,它就可以发送电子邮件给你。你还将把两个 LED 集成到系统中,一个用于指示报警是否启动,另一个用于指示报警是否触发,同时还需要一个按钮来启动和停止传感器。

按照以下步骤建立入侵报警电路,参考图 10-3 进行连接。

image

图 10-3: 入侵报警电路

  1. 将树莓派的 GND 连接到面包板的其中一条蓝色轨道。

  2. 在面包板上插入红色 LED 和绿色 LED。将绿色 LED 的正极引脚通过 330 Ω电阻连接到 GPIO 18,电阻位于 LED 引脚和 GPIO 引脚之间,并将负极引脚连接到 GND 轨道。将红色 LED 的正极引脚通过另一个 330 Ω电阻连接到 GPIO 17,并将负极引脚连接到 GND 轨道。

  3. 将按钮插入面包板中间,使其跨接在中心断开处,如图 10-3 所示。将右下角的引脚连接到 GND 轨道,左下角的引脚连接到 GPIO 2。

  4. 按照下表中的连接方式连接 PIR 运动传感器。

PIR 运动传感器 树莓派
GND GND
OUT GPIO 4
VCC 5 V

编写脚本

打开Python 3 (IDLE),然后转到文件新建文件,创建一个新的脚本。将清单 10-2 中的代码输入到新文件中,并将脚本保存在Sensors文件夹内,命名为intruder_alarm.py(记得你可以下载所有脚本,地址是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 会亮起。

你将读取模拟信号,这些信号提供了气体浓度的定量测量,使你能够更好地定义一个阈值,超过该阈值时,蜂鸣器会警告你气体浓度过高。请记住,Pi 只能读取数字信号,因此,为了用 Pi 读取模拟信号,你需要使用一个模拟到数字转换模块(MCP3008 芯片),它首次在项目 3 中引入。

介绍压电蜂鸣器

当接收到来自 Pi 的数字信号时,压电蜂鸣器会发出警报。你将使用的蜂鸣器,见 图 11-2,是最简单的一种。

image

图 11-2: 压电蜂鸣器

蜂鸣器的外壳包含一个在施加电压时会以特定频率振动的圆盘。接线压电蜂鸣器很简单。你只需要将一根线连接到 Pi 的 GND 引脚,另一根线连接到 GPIO 引脚。

连接电路

为了构建烟雾和气体检测报警电路,你需要将一个 LED 和一个按钮连接到 Pi;你应该已经知道如何接线,参考之前的项目。你还需要将压电蜂鸣器和 MQ-2 传感器连接到 Pi(后者将通过 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 是左侧顶部的引脚;请参阅 《模拟到数字转换器》 中的 第 55 页,以查看完整的 MCP3008 引脚图。

  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. 将 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) 并选择 FileNew File 来创建一个新脚本。将 清单 12-1 中的代码复制到 Python 编辑器中,并将脚本保存为 temperature_humidity_data_logger.py,保存在 Sensors 文件夹中(记住你可以从 www.nostarch.com/RaspberryPiProject/ 下载所有脚本):

清单 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 结果。位于 ➐ 的 if 语句确保只有在数据不为 null 时,树莓派才会保存数据。你还可以使用 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 停止脚本。然后,你应该会在传感器文件夹中找到一个包含所有数据的sensor_readings.txt文件。

进一步探讨

在这个项目中,你学到了一个非常实用的概念:数据记录。现在你可以在其他监控项目中使用数据记录。以下是一些想法:

  • 使用一个每次检测到运动时都会生成时间戳的 PIR 运动传感器。

  • 使用 Sense HAT 构建一个气象站数据记录器。

  • 寻找其他监控传感器应用程序——例如土壤湿度、降雨和光照传感器——来构建一个温室数据记录器。

第十四章:带有照片捕捉的防盗探测器

本项目将教您如何使用树莓派相机模块 v2,该模块与 PIR 运动传感器一起工作,用来检测并拍摄闯入者。当运动传感器检测到运动时,它会触发拍照事件,让您知道在您不在家时是谁进入了您的家。

image

所需部件

树莓派

面包板

树莓派相机模块 v2

PIR 运动传感器 HC-SR501**

按钮

跳线

介绍树莓派相机模块 v2

如图 13-1 所示,树莓派相机模块 v2 配备了 8 百万像素的索尼 IMX219 图像传感器和固定焦距镜头。它支持 3280×2464 像素的静态图像,并支持 1080p 30 帧、720p 60 帧和 640×480 90 帧的视频分辨率——这些都意味着它是一个相当不错的相机,尤其是它的体积!在这个项目中,您只会使用它的静态图像功能。

image

图 13-1: 树莓派相机模块 v2

这款相机与所有树莓派型号(1、2、3 和 Zero)兼容,配有 15 厘米的排线,使其能够轻松连接到树莓派的 CSI 端口,该端口专为连接相机设计。如果您希望相机距离树莓派超过 15 厘米,您应该能够找到并购买更长的电缆。

树莓派相机模块 v2 是最受欢迎的树莓派附加组件之一,因为它为用户提供了以实惠的价格拍摄静态照片和录制全高清视频的方式。一个有趣的树莓派相机 v2 项目示例来自 Naturebytes 社区,该社区提供远程捕捉野生动物照片的套件。图 13-2 展示了这款野生动物相机的工作场景。

image

图 13-2: 配备 PIR 运动传感器的树莓派相机对准鸟食器

Naturebytes 套件还配备了一个 PIR 运动传感器,因此如果一只鸟停在图 13-2 中的喂食器上,它将触发相机拍摄鸟的照片。您将在这个项目的防盗探测器中使用相同的原理。

构建防盗探测器

防盗探测器由 PIR 运动传感器、按钮和您将连接到树莓派的相机模块组成。您将使用内置的 picamera 库,它使得控制相机变得非常简单。

启用相机

在使用相机模块之前,您需要启用树莓派的相机软件。在桌面环境中,进入主菜单,选择首选项树莓派配置。您应该能看到类似于图 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. 将照片保存在 Desktop 文件夹中。

  4. 为照片命名时使用递增的方式,以便知道它们拍摄的顺序——例如 image_1.jpgimage_2.jpg,依此类推。

  5. 当按下按钮时停止相机。如果不包含这个功能,你将无法退出在屏幕上弹出的相机预览。

进入脚本

进入你的 Projects 文件夹,创建一个名为 Cameras 的新文件夹。然后打开 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()函数将使用这个变量来计数并编号图像,每拍一张照片,文件名中的数字会递增。

接着,你定义了stop_camera()函数,该函数使用camera.stop_preview()方法停止相机 ➎。在➏处,你定义了我们刚才提到的take_photo()函数,它用于拍照。在这个函数中,你使用了camera.capture()方法,并在括号内指定了你想要保存图像的目录。在此示例中,我们将图像保存到Desktop文件夹,并将图像命名为image_%s.jpg,其中%s会被先前在i中递增的数字替代。如果你想将文件保存到其他文件夹,只需将该目录替换为你选择的文件夹路径。

然后,你设置一个 10 秒的延迟 ➐,这意味着在 PIR 传感器检测到运动时,相机会以 10 秒的间隔拍摄照片。你可以随意增加或减少延迟时间,但要小心不要通过将延迟时间设置得太小,从而让 Pi 负担过重,生成大量图像。

在➑处,你定义了一个行为,当按下按钮时触发stop_camera()函数。此函数停止相机预览并退出程序。exit()函数会弹出一个窗口,询问你是否要关闭程序;只需点击OK即可关闭。最后,当检测到运动时,你通过触发take_photo()函数来让相机拍照 ➒。

运行脚本

按下F5键或前往运行运行模块来运行脚本。当脚本运行时,你应该能在屏幕上看到相机所看到的预览画面。要关闭相机预览,按下按钮并在弹出的窗口中点击OK

恭喜!你的防盗探测器已准备好捕捉小偷了。将防盗探测器放置在一个战略位置,稍后回来查看保存的照片。图 13-6 展示了一张由我们的防盗探测器拍摄的照片,捕捉到了有人从我们的实验室偷电脑。

image

图 13-6: 防盗探测器拍摄的照片

更进一步

正如你所见,带有相机的项目非常有趣!这里有一个改进你的安全系统的想法:重新设计你的项目,使得当传感器检测到运动时,Raspberry Pi 不仅拍照,还会给你发送电子邮件通知,并发出警报。你应该已经掌握了如何使用从项目 9 到 12 中学到的技能来完成这些操作。

第十五章:家庭监控摄像头**

在这个项目中,你将创建一个家庭监控摄像头系统,将实时视频流传输到一个网页,你可以通过任何连接到与你的树莓派相同网络的设备来访问。这意味着你可以在不离开沙发的情况下监控你家里的任何地方!

image

所需组件

树莓派

树莓派摄像头模块 v2

在这个项目中,你需要将摄像头连接到树莓派,正如我们在“连接摄像头”一节中在第 165 页所展示的那样。如果你还没有启用软件摄像头,请返回项目 13,按照说明设置摄像头,然后再继续。

将视频录制到文件

在构建家庭监控摄像头系统之前,你需要学习如何将视频录制到文件中。

以项目 13 为参考,通过 CSI 端口将树莓派摄像头模块 v2 连接到你的树莓派。创建一个名为record_file.py的新脚本,使用Python 3 (IDLE),将其保存在Cameras文件夹中,并输入列表 14-1 中的代码。

列表 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

这将打开一个新窗口,并以全屏方式播放整个视频。图 14-1 展示了我们的录制视频测试的截图。

image

图 14-1: 使用树莓派摄像头录制视频

编写脚本

现在是关键部分:你将构建一个托管在树莓派上的网页——也就是一个Web 服务器——它会进行实时视频流。(我们将在项目 15、16 和 17 中更详细地讲解 Web 服务器。)

这个项目的脚本比较复杂,所以我们不会逐行解释。以下是代码应该实现的功能概述:

  1. 初始化一个 Web 服务器和树莓派摄像头。

  2. 设置 Web 服务器,使其在树莓派 IP 地址的 8000 端口上显示一个可以用 HTML 自定义的网页。

  3. 设置网页以包含摄像头的视频流。

  4. 使 Web 服务器可以从任何连接到与你的树莓派同一网络的浏览器访问。

输入脚本

打开Python 3 (IDLE),然后转到文件新建以创建一个新脚本。输入清单 14-2 中的代码,并将其保存为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 地址,并输入 URL http://<Pi IP 地址>:8000,将<Pi IP 地址>替换为你的树莓派 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 自定义流媒体网页。

第十六章:创建你的第一个网站

在这个项目中,你将构建一个简单的网站,包含标题、段落、图片、链接和按钮。你将使用 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>是一级标题的标签;<h2></h2>是二级标题的标签,以此类推,直到六级标题,它是标题的最低层级。标题标签应该放在<body></body>标签之间。现在创建几个标题并将它们放入文件的主体中:

<body>
  <h1>MAX - THE DOG</h1>
  <h2>About Max</h2>
</body>

我们已向页面添加了两个标题:“MAX – THE DOG”作为顶级标题,以及“About 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 来为页面细节添加样式,使页面更加美观。

添加链接、图片和按钮

任何自尊的网页都不应该仅仅包含文字。我们将向你展示如何添加图片、按钮、更多页面以及指向其他页面的链接。

包含超链接

要为另一个页面添加超链接,您可以在<body></body>标签之间的任何位置添加<a>标签。例如,您可以像这样插入指向《侦探犬雷克斯》维基百科页面的超链接:

<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>标签在页面内容内创建一个部分(请参见清单 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 的网页上添加了一个图像、一个链接和一个按钮。清单 15-2 中的代码显示了此时 HTML 文档的样子。

清单 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 展示了 HTML 文件在列表 15-2 中的网页效果。

image

图 15-3: Max 的网页,包含一个链接、一张图片和一个按钮

用 CSS 为页面添加样式

现在你将添加CSS(层叠样式表),这是一种用于描述网页元素渲染时显示样式的语言。你可以将 CSS 直接添加到 HTML 文件中,或者在一个单独的文件中定义 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 属性

position属性可以具有以下值:

静态

一个具有static值的元素是根据页面的正常流进行定位的,不受topbottomleftright属性的影响。默认情况下,HTML 元素是static

相对定位

一个具有relative值的元素是相对于其默认位置进行定位的,使用topbottomleftright属性。

固定

一个具有fixed值的元素即使在页面滚动时也保持在相同位置。要在视口中定位元素,可以使用topbottomleftright属性。

绝对定位

一个具有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,分别用来将文本对齐到左侧、右侧、居中或两端对齐。

样式化标题、段落和链接

列表 15-6 样式化标题、段落和链接。将这些样式添加到你的 CSS 文件中。

列表 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 单位表示该属性的初始值。因此,1 rem 等于 HTML 元素的字体大小——在大多数浏览器中默认是 16 像素。

在 列表 15-6 中,我们为标题 1、标题 2 和段落分别定义了 42.51.3 rem 的字体大小,以使每个标题级别逐渐比上一级小,最低级别略大于默认文本。

main 部分格式化页面的主要内容 ➋。我们将内容的最大宽度设置为 500px。定义元素的宽度可以防止元素拉伸到容器的边界;在这种情况下,它可以防止文本在网页浏览器窗口中水平扩展。然后,我们使用 auto 设置外边距,以便水平居中元素。

超链接默认是带下划线的。将 text-decoration 设置为 none ➌ 可以去除下划线。

样式化按钮

要样式化按钮,请将 列表 15-7 中的样式复制到你的 style.css 文件中。

列表 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;
}

display设置为block可以确保按钮作为块级元素显示,就像段落一样;例如,按钮不会与文本元素在同一行上。我们使用padding属性在内容周围添加空间。我们为按钮的上下边距定义了10px的填充,为左右边距定义了20px的填充——这设置了按钮内容周围的空白区域。请注意,这里我们使用的是十六进制颜色代码来设置按钮文本和按钮背景颜色。其他按钮属性不言自明。可以通过调整这些属性,按自己的喜好样式化按钮。你还可以在网上搜索button properties CSS,了解更多属性和值。

保存你的style.css文件并刷新浏览器,查看你所做的更改。现在,你应该已经有了一个简单的网页,类似于本项目开始时展示的页面。

进一步拓展

这个项目只是一个如何使用 HTML 和 CSS 构建简单网页的快速介绍。你可以以无穷无尽的方式编辑和改进它。我们鼓励你尝试这里介绍的所有选项。如果需要灵感,你可以:

  • 使用超链接将多个页面添加到主页面并相互连接。

  • 创建一个网页,展示你的某个 Raspberry Pi 项目。

  • 在网上搜索更多 CSS 属性和值,并编辑页面的显示效果。

第十七章:将你的电子设备连接到互联网**

在这个项目中,你将创建自己的物联网 web 服务器,可以用它来通过手机远程控制灯。你将构建的简单 web 服务器之后可以添加到其他项目中,从而控制其他电子设备。

image

所需零件

树莓派

继电器模块 HL-52S

12 V 灯及其支架

12 V 电源适配器

公头 DC 条形电源插座

塑料盒外壳

跳线

所需软件

Flask 框架

在这个项目中,你将创建一个自己的 web 服务器,通过浏览器控制电子设备。你将使用动态网页和按钮来控制一个 12 V 灯。

介绍 web 服务器

web 服务器是一台提供网页的计算机。它存储网站的文件,包括所有的 HTML 文档及相关资源如图片、CSS 样式表、字体和视频。当用户请求服务器的 URL 时,它也将这些文件传送到用户设备的网页浏览器中。

当你在浏览器中访问一个网页时,实际上是在通过超文本传输协议(HTTP)向服务器发送请求。这仅仅是一个请求和返回互联网上信息的过程。服务器会通过 HTTP 将你请求的网页返回给你。

在这个项目中,你将使用树莓派在本地网络上托管 web 服务器,如图 16-1 所示。

image

图 16-1: 运行 web 服务器的树莓派

作为服务器,树莓派可以通过其 GPIO 引脚提供一些输出。换句话说,通过你本地网络上的浏览器,你可以访问树莓派的 web 服务器,远程控制 GPIO 引脚并打开某些设备。

web 服务器可以提供静态动态内容。静态网站的内容除非你编辑 HTML 文件,否则不会改变。项目 15 中构建的网站就是一个静态网站的例子。动态网站则根据用户的交互来改变内容。在这个项目中,你将创建一个动态网站,来控制并显示连接到继电器的 12 V 灯的当前状态,稍后我们会更详细地讨论。

注意

只有与树莓派连接到同一个路由器的设备,才能通过浏览器访问托管在树莓派上的网页。从外部访问你的 web 服务器更加困难。你可以通过使用一种叫做路由器端口转发的技术,让任何地方的计算机都能访问你的树莓派 web 服务器,但这个主题超出了本书的范围。

介绍继电器模块

继电器是一种电控开关,可以开关,允许电流通过或不通过,并且可以用低电压(例如树莓派提供的 3.3 V)来控制。你将在这个项目中使用的继电器模块有两个继电器——即图 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 灯具。我们将使用直流插头电源来简化适配器与继电器之间的连接。直流插头与电源适配器端子完美连接,如图 16-3 所示。

image

图 16-3: 电源适配器端子与直流插头电源

项目概览

在开始搭建该项目之前,让我们先浏览一下项目概览,以便更好地理解每个步骤(见图 16-4)。你还应该了解 HTML 和 CSS 的基础知识,如果你还没有完成项目 15,我们建议先完成它再继续进行。

image

图 16-4: 项目概览

你将使用 Flask,一个 Python 网页框架,通过创建名为app.py的文件来搭建你的 Web 服务器。当你访问树莓派的 IP 地址,端口 80 时,浏览器会请求存储在树莓派中的网页文件——index.htmlstyle.css——并显示网页。你的网页上将有“开”和“关”按钮,分别触发打开和关闭灯的事件。

电路接线

为了安全起见,你应该将继电器放入一个塑料盒内。你可能需要在塑料盒上钻一些孔,用来放置电线——一个孔用于树莓派的电线,另一个用于灯具的电线,还有一个孔用于电源适配器。图 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: 使用继电器控制树莓派的 12 V 灯

警告

在继电器或任何接通 12 V 的电线连接时,切勿触碰它们。如果出现故障并决定对电路进行更改,请在触摸任何物品之前先拔掉 12 V 电源适配器插头。

在完成电路并仔细检查所有连接后,为树莓派供电,将 12 V 电源适配器连接到直流电源接口,并通过插入墙面插座为适配器供电。

如果你想控制 LED,可以使用项目 1 原理图,参考第 41 页,将 LED 连接到 GPIO 17。

准备你的 PI 来运行 Web 服务器

树莓派支持多种 Web 服务器,但我们将使用 Flask,一个 Python Web 框架,把树莓派变成一个动态 Web 服务器。

安装 Flask

要安装 Flask,你需要先安装 pip,这是一个从 Python 包索引安装库的工具。打开终端,运行以下命令以更新你的 Pi 并安装 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 安装成功,终端会显示消息 successfully installed Flask

整理文件

在这个项目中保持文件的组织性非常重要,因为 Flask 需要按照特定的结构来管理文件。首先在 Web_Applications 文件夹内创建一个专门用于这个项目的文件夹,命名为 Project_16。然后根据图 16-7 所示的结构创建需要的文件夹和文件。

image

图 16-7: 文件和文件夹结构

使用文本编辑器创建 index.htmlstyle.css 文件,并使用 Python 3 (IDLE) 创建 app.py 文件。static 文件夹将存储静态文件,如 CSS 文件。templates 文件夹将存储可以更改的文件;例如,index.html 文件是一个模板,能根据用户输入动态更改继电器状态标签。

编写脚本

你需要为这个项目编写三个脚本:一个 Python 脚本,用来创建树莓派的 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 中的操作是 on,程序将打开继电器并保存当前继电器状态 ➐。这意味着当你访问 Raspberry Pi 的 IP 地址并跟上 /on(例如http://192.168.1.112/on)时,继电器将打开。稍后你将获取你自己的 IP 地址。

如果 URL 的操作是off,程序将关闭继电器并保存当前继电器的状态。服务器正在监听 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根路径➌,它会开启继电器,正如在app.py中所看到的。当你点击“关”按钮时,你会被重定向到/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 和段落➊设置了字体大小。然后,我们对页面主体进行了对齐➋。最后,我们编辑了开启和关闭按钮的外观➌。

启动你的网页服务器

完成了 Python 脚本、HTML 文件和 CSS 文件后,接下来是运行你的网页服务器。打开终端窗口,进入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

你的网页服务器现在正在运行。打开任何在本地网络上的浏览器,输入你的树莓派 IP 地址。

要找到树莓派的 IP 地址,请打开终端并输入以下命令:

pi@raspberrypi:~ $ hostname -I

这将打印树莓派的 IP 地址。

准备好电路并启动服务器后,打开浏览器并导航到树莓派的 IP 地址。浏览器将显示网页服务器页面。现在点击按钮来远程控制灯!图 16-8 展示了在智能手机浏览器中的网页。

image

图 16-8: 在智能手机浏览器中查看树莓派网页服务器页面

要停止网页服务器,只需按下 CTRL-C。

进一步扩展

在这个项目中,你学习了如何使用继电器以及如何设置网页服务器来提供网页,还将 HTML 和 CSS 技能付诸实践。以下是将这些概念进一步拓展的一些想法:

  • 编辑这个项目,以控制多个输出。

  • 编辑 CSS 和 HTML 文件,以适应你自己的喜好。

  • 控制其他电子设备。

  • 编辑以前的项目,以控制连接到继电器的灯,而不是 LED。

第十八章:使用 Node-RED 构建物联网控制中心**

在这个项目中,你将创建一个物联网应用程序,使用 Node-RED 控制你最喜欢的家电,通过 Web 服务器远程操控,它是一个功能强大且易于使用的物联网应用工具。

image

所需部件

树莓派

面包板

DHT22 温湿度传感器

4.7 kΩ 电阻

两个 5 毫米 LED

两个 330 Ω 电阻

跳线

所需软件

Node-RED DHT 节点

Node-RED 仪表盘

你将创建一个 Node-RED 应用程序,控制本地网络中的输出并读取输入。你将用它来控制 LED,并使用 DHT22 传感器读取并显示温湿度值——所有这些都通过 Web 服务器远程完成。

介绍 Node-RED

Node-RED 是一个开源的视觉连接工具,用于构建物联网应用程序,它已经预装在树莓派的操作系统中,且与树莓派完美兼容。

Node-RED 使用视觉编程,包含称为节点的模块,你可以将它们连接在一起以执行特定任务,从而显著简化了编程。Node-RED 允许你快速且简单地原型化一个复杂的家庭自动化系统,让你有更多时间专注于设计和制作酷炫的东西。

我们不会在这个项目中涵盖 Node-RED 的所有功能,但如果你想进一步探索,这里有一个简短的概述:

  • 访问树莓派的 GPIO。

  • 建立与其他板卡的连接,例如 Arduino 和 ESP8266。

  • 创建响应式图形用户界面。

  • 与第三方服务进行通信。

  • 从网上检索数据。

  • 创建时间触发事件。

  • 从数据库中存储和检索数据。

安装 DHT22 节点

虽然 Node-RED 软件已经随 Pi 的操作系统预装,但它并没有自带可以读取 DHT22 传感器的节点。我们需要先安装它,通过像下面这样安装 npm(Node 包管理):

pi@raspberrypi:~ $ sudo apt install npm

当系统提示时,输入 Y 并按下 ENTER 键。安装可能需要几分钟时间。然后,输入以下命令将 npm 升级到最新的 3.x 版本,这是与 Node-RED 一起使用时推荐的版本:

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 的内容。高亮显示的行显示了你本地主机的树莓派 IP 地址,后面是 Node-RED 服务器运行的端口号。本地主机是一个主机名,表示“这台计算机”,并解析为终端窗口中显示的 IP 地址:http://127.0.0.1。使用此 IP 地址,你只能在树莓派浏览器中访问 Node-RED。如果要在本地网络中的任何浏览器中访问 Node-RED,你需要找到树莓派的 IP 地址。

注意

通常,你可以通过进入任务栏主菜单并点击编程 ▸ Node-RED来打开 Node-RED,而不是直接进入终端。然而,在这个项目中,你需要从终端启动 Node-RED,因为 DHT22 节点需要管理员权限。

image

图 17-1: 从终端启动 Node-RED

要查找你的树莓派 IP 地址,请在终端输入以下命令:

pi@raspberrypi:~ $ hostname -I

打开 Chromium 并输入http://<Pi IP 地址>:1880/,将<Pi IP 地址>替换为你的树莓派的 IP 地址。你的 Node-RED 页面服务器应该会如图 17-2 所示打开。

image

图 17-2: 浏览器中的 Node-RED 编辑器

在左侧,你会看到一个块或节点的列表。节点根据其功能分组;向下滚动列表查看你所拥有的节点。我们将在这个项目中介绍少量现有节点,包括来自输入、树莓派、功能和仪表板部分的节点,详见图 17-3。

image

图 17-3: 一些 Node-RED 节点

你的 Node-RED 页面的中央框是流程区域;这是你拖动节点并将它们连接起来以构建应用程序的地方。右侧有几个标签:信息标签显示选定节点的信息,调试标签用于调试,仪表板标签是你组织应用程序用户界面小部件的地方。最后,部署按钮保存对流程所做的更改并执行它。接下来,你将接线硬件,然后在 Node-RED 中搭建流程。

电路接线

你应该已经熟悉 LED 和 DHT22 传感器,但如果需要复习 DHT22 传感器的内容,请查看项目 12。接线电路时,请按照以下说明操作:

  1. 将树莓派的 5V 和 GND 分别连接到面包板的红色和蓝色导轨。

  2. 将两个 LED 插入面包板。将每个 LED 的短脚通过 330Ω电阻连接到 GND 导轨。将一个 LED 的长脚连接到 GPIO 18,另一个 LED 的长脚连接到 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 也是时间敏感的,这意味着它将在你设置的特定时间自动亮起和熄灭。

  • 使用滑块控制另一个 LED,通过 PWM 来充当调光开关。

  • 从 DHT22 传感器读取温度,并在时间与温度的图表上显示它。

  • 从 DHT22 传感器读取湿度,并在仪表盘上显示它。

首先,你将创建一个用户界面,用于从服务器控制组件。

创建仪表板用户界面

来自仪表板部分的节点提供了在应用程序用户界面(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,当该节点触发时,它会将 1 发送到 Raspberry Pi GPIO 17。在重复字段中,选择 在特定时间,并选择你希望 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;

返回 msg; |

| 仪表盘 | 组:仪表板 [主页] 类型:仪表

标签:湿度

值格式:{{value}}

单位:%

范围:最小值:0;最大值:100

名称:湿度 - 仪表 |

点击 部署 按钮,再次测试你的应用程序。记得,如果遇到问题,确保你的属性与此处的表格一致,并再次检查节点的接线与图示是否匹配。

运行应用程序

恭喜!你已经用 Node-RED 构建了第一个物联网应用程序。前往 http://<Pi IP 地址>:1880/ui 查看用户界面效果。你可以在本地网络上的任何浏览器(无论是电脑还是智能手机)中访问这个 URL。图 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 所示。如果你使用耳机,选择模拟;如果你使用带扬声器的显示器,通过 HDMI 连接,则选择 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 的samples文件夹复制到Games_and_Toys文件夹中(请注意,最后一个斜杠与句号之间有一个空格):

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys $ cp -r /opt/
sonic-pi/etc/samples/ .

接下来,输入以下命令列出samples文件夹的内容,检查它们是否正确传输:

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

然后输入以下命令,它将遍历samples文件夹中的所有文件,并将每个.flac文件转换为.wav文件:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ for f in
*.flac; do avconv -i "$f" "${f%.flac}.wav"; done

接下来,使用ls命令列出samples文件夹中的项目,检查是否有.wav文件可以使用:

pi@raspberrypi:~/Desktop/Projects/Games_and_Toys/samples $ ls

对于每个样本,你应该同时拥有.wav.flac文件。要从samples文件夹中删除.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 RASPBERRY PI
    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 编辑器中,并将脚本保存在Games_and_Toys文件夹内,命名为digital_drum_set.py(记得你可以从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 混音器 ➋ 并创建一个字典来存储声音 ➌。在 Python 中,字典是一种用于存储项之间关系的数据结构。在这里,你将按钮与特定的声音关联起来。字典的基本结构如下:

dictionary_name = {key_1: value_1, key_2: value_2}

字典用大括号 {} 括起来,由键/值对组成。你使用冒号 (:) 将每个键与对应的值关联起来,使用逗号 (,) 来分隔每个键/值对。

在这个项目中,键是按钮,值是声音。要创建一个声音对象,你将声音文件路径作为字符串传递给Sound()函数。在这个例子中,由于sample文件夹位于Games_and_Toys文件夹内,因此你不需要提供完整路径,只需要提供文件夹名和声音文件名。你需要修改脚本中加粗的声音文件名,替换为你选择的声音文件。

接下来,将每个按钮分配一个音效 ➍;这意味着,当按下某个按钮时,对应的声音将会播放。最后,脚本结尾的pause()函数 ➎ 保持程序运行,以便能够检测事件。

要运行脚本,请按F5键或选择运行运行模块

恭喜你——你已经成功打造了自己的数字鼓组!现在,按下按钮并创作属于你自己的音乐片段。

进一步探索

这是一个很酷的项目,而且非常简单。我们鼓励你通过尝试以下方法来扩展这个项目:

  • 为你的数字鼓组添加其他声音

  • 录制你自己的声音或在网上搜索免费的声音素材

  • 构建一个数字钢琴、数字吉他或混合声音的音乐盒

第二十章:在 Scratch 中制作游戏:饥饿猴子**

在这个项目中,你将使用基于区块的编程语言 Scratch 来创建一个可以通过两个按键和树莓派控制的游戏。

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 窗口将屏幕分为四个主要部分。Stage ➊ 是你的游戏或动画播放的区域。右上角,你会看到一个绿色的旗帜和一个停止标志;你可以使用这些图标来启动和停止游戏。当你首次打开 Scratch 时,默认情况下,舞台上应该会看到一只猫。

Sprite List ➋ 显示了所有的精灵,这些精灵是你的游戏角色或在项目中执行某个动作的任何物体。窗口的中间是 Blocks 区域 ➌,它有三个标签:Scripts、Costumes 和 Sounds。Scripts 标签包含你用来构建程序的编程块。每个块都是一个不同的编程指令,你可以将其拖放到程序中。你会看到根据功能不同而分类的块,每个类别都有特定的颜色;例如,运动类别的块是深蓝色的,它们告诉精灵如何移动。

Costumes 标签 ➍ 显示了自定义和创建新角色服装的选项,Sounds 标签 ➎ 允许你为角色添加声音。Scripts 区域 ➏ 是你拖动并拼接代码块以创建脚本的地方。

顶部的菜单栏 ➐ 显示了左侧的文件和编辑主菜单。中间的图标允许你复制、删除、放大和缩小你的角色,还可以从 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精灵并点击OK。然后再次打开精灵库,从“物品”类别中选择Bananas精灵,点击OK

你可以从精灵列表的最左侧选择游戏背景。在那里,你会找到一组背景图标。点击第一个图标(在图 19-4 中高亮显示)以从背景库中选择一个背景。我们选择了名为蓝天的背景。

image

图 19-4: 从背景库中选择背景

现在你的精灵区应该像图 19-5 所示。

image

图 19-5: 选中的精灵和背景的精灵列表

编辑精灵

Scratch 允许你增加或减少精灵的大小,改变其颜色,或者像在图像编辑程序中一样编辑它。Scratch 内置的图像编辑器叫做Paint Editor。在这里你将对精灵的外观进行一些更改。

在精灵列表中选择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 坐标将使精灵向右移动,减少它会使精灵向左移动。增加 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: 创建倒计时器的积木

要创建倒计时器,你需要使用一个叫做 timer 的积木。这个积木会计算从脚本开始以来已经过去的时间。当你点击绿色旗帜图标时,脚本会重置定时器,每次开始游戏时它都会从 0 开始计时。接下来,添加一个积木,将time变量显示在舞台上。你可以通过拖动它来调整time变量的位置,将其移动到舞台的右上角。

接下来,永远循环会不断更新time变量,使其从 30 开始,每秒减少 1。你使用圆形积木,确保倒计时只显示整数。如果你想改变游戏时长,可以调整圆形积木中的数字。

请特别注意这里的嵌套(参见图 19-9);你会注意到,先有设定时间的模块,然后是回合模块。接着,在这个基础上,你需要放置带有两个空圆圈的绿色模块。在第一个空圆圈里输入 30,在第二个空圆圈里放入一个来自感知类别的计时器模块。

image

图 19-9: 嵌套的 if 语句块

最后的 if 模块(参见图 19-8)会在time归零时隐藏time变量在舞台上的显示。现在试试看吧!

计算并显示分数

为了创建分数系统,首先你需要创建一个变量来跟踪分数。在数据模块类别中,创建一个名为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 值。

第一个 if 块位于 repeat until 块内,当香蕉到达舞台底部(y < –160)时,它会让香蕉消失。第二个 if 块在香蕉碰到猴子时,会为 score 变量加 1 并播放 pop 音效,同时让香蕉消失。最后,当 repeat until 块结束时,Bananas 克隆体会从舞台上隐藏。

在 图 19-11 左下角的代码块 ➋ 停止在 time 变量为 0 时创建新的 Bananas 克隆体。

添加腐烂香蕉

现在你已经有了猴子、好的香蕉、计时器和分数。你只差腐烂香蕉了。腐烂香蕉的脚本与 图 19-11 中的脚本非常相似;你只需要做以下更改:

  • 每 2 秒钟生成腐烂香蕉,而不是每 1 秒钟生成。

  • 当腐烂香蕉碰到猴子时,减少分数 1。

  • 当猴子碰到腐烂香蕉时播放不同的音效。我们选择了名为 F elec bass 的音效。

  • 更改腐烂香蕉的外观。

由于这个脚本与之前的脚本非常相似,你将复制好的香蕉并进行更改。右键点击 Bananas 精灵并选择 duplicate。精灵及其脚本将被复制,并自动命名为 Bananas2。右键点击 Bananas2 精灵并选择 info;此时会弹出一个菜单,让你更改精灵的名称。输入 Rotten 作为新名称。你需要在脚本中进行的更改已在 图 19-12 中高亮显示。

image

图 19-12: 控制腐烂香蕉的代码块

wait 块的值更改为 2 ➊,这样每隔 2 秒就会生成一个新的 Rotten 克隆体,而不是每秒生成一个。另外,将音效块更改为播放 F elec bass ➋,并在 set score to 块中将分数减少 1 ➌。记住,你必须先在音效选项卡中从库中添加这个音效。

腐烂香蕉的脚本完成后,接下来您将改变Rotten角色的颜色,使香蕉看起来腐烂。选择Rotten角色,点击服装选项卡。应该会弹出绘图编辑器屏幕(参见图 19-13)。

在窗口的右侧,选择桶图标 ➊;然后,在底部,选择不同的颜色 ➋ 来填充每个香蕉。选择棕色、橄榄绿和深绿等颜色,显示它们腐烂了。

image

图 19-13: 编辑Rotten角色的颜色

玩游戏

恭喜!你的游戏已准备就绪。要在全屏模式下玩游戏,请点击舞台左上角的全屏图标,然后点击绿旗图标。在全屏模式下玩游戏能使其运行更加流畅和快速。

请记住,您可以使用按钮或键盘键来玩游戏。游戏结束时,只需点击绿旗图标即可重新开始。

进一步扩展

这个项目只是展示了您在 Scratch 中可以做的一小部分。以下是一些改进这个游戏的想法:

  • 随着游戏的进行,增加香蕉的下落速度。

  • 随着游戏的进行,增加腐烂香蕉的数量。

  • 通过创建另一个角色并使用不同的控制方式,使这个游戏支持多人模式。(您需要添加另一个score变量来存储玩家 2 的分数。)

  • 向您的电路中添加其他电子设备,您可以用 Scratch 接口连接,如按钮、蜂鸣器或传感器。

玩得开心,创造属于你自己的游戏吧!

第二十一章:Wi-Fi 遥控机器人**

在这个项目中,你将使用 Raspberry Pi Zero W 和 MotoZero 扩展板构建一个两轮电池驱动的机器人。你可以通过你用 Node-RED 制作的 web 应用程序通过 Wi-Fi 控制它。

image

所需部件

Raspberry Pi Zero W(或其他 40 个 GPIO 引脚的树莓派)

智能机器人车底盘套件

MotoZero 扩展板(或其他电机控制器扩展板)

四个 AA 电池

便携式充电器

跳线

所需软件

Node-RED 仪表板

项目概要

在正式进入项目之前,我们将突出介绍机器人的关键部件,帮助你理解它的工作原理。

Wi-Fi

你将使用 Node-RED 应用程序来控制机器人,因此你的树莓派需要具有 Wi-Fi 功能。树莓派 3 和 Zero W 型号内置 Wi-Fi,但如果你的板没有,你可以使用与树莓派兼容的 Wi-Fi USB 适配器。

树莓派板

我们使用的是 Raspberry Pi Zero W,因为它的小巧尺寸非常适合小型机器人底盘。但任何配有 40 个 GPIO 引脚并支持 Wi-Fi 的树莓派版本都可以与此项目兼容。

机器人底盘套件

我们使用的是一个机器人底盘套件,套件中包含了构建机器人所需的所有组件,包括轮子、电机和螺丝。你可以在像 Amazon 或 eBay 这样的在线市场上搜索 智能车机器人底盘套件 来找到该套件。你需要配备两个直流电机的套件。

MotoZero 扩展板

直流电机将驱动机器人移动,你将使用一个名为 MotoZero 的扩展板来控制它们。你可以在网上的 The Pi Hut 找到该扩展板 (thepihut.com/motozero/)。你也可以使用任何其他与树莓派兼容的电机驱动扩展板,或者用 LC293D IC 芯片制作电路。我们这里不会介绍如何制作该电路,但如果你想自己制作,可以在网上找到很多相关教程。

电源

我们不想将树莓派机器人连接到墙上的插座,因为我们希望它具有便携性,因此我们需要通过便携式充电器或 移动电源 为机器人供电。移动电源必须能够输出 5 V 和 2 A。我们使用了一款容量为 2200 毫安时的移动电源进行测试,运行良好;如果使用更大容量的移动电源,机器人将能运行更长时间。

这些直流电机需要独立于树莓派供电,这意味着你需要两个独立的电源。为了为电机供电,我们使用了底盘套件中附带的电池盒,并配有四个 AA 电池,电池不包括在套件中。

Node-RED 应用

你将用来控制机器人的 Node-RED 应用应该能够让机器人前进、后退、左右移动并停止。由于你并非将树莓派作为桌面电脑使用,因此树莓派需要在启动时自动启动 Node-RED。你还会在应用程序中添加一个关闭按钮,这样就能远程关闭树莓派。

图 20-1 展示了您的机器人如何工作的概览。

image

图 20-1: 机器人结构

准备 Raspberry Pi

我们使用的是 Raspberry Pi Zero W 开发板,如图 20-2 所示,它是 Raspberry Pi Zero 的一种变体,内置了无线 LAN 和蓝牙,但请记住,您也可以使用其他支持 Wi-Fi 的开发板或 Wi-Fi 加密狗。Raspberry Pi Zero W 的尺寸仅为 2.56 英寸 × 1.18 英寸 × 0.20 英寸(65 毫米 × 30 毫米 × 5 毫米),价格约为 10 美元。

Pi Zero 具有 40 个 GPIO 引脚,排针与 Pi 3 相同。如图 20-2 所示,它配有一个迷你 HDMI 接口和两个 micro USB 接口,其中一个专门用于供电。要将 Pi Zero 用作桌面计算机,您需要一些额外的配件,比如 USB 集线器、USB 转 micro-USB 适配器和 HDMI 转 mini-HDMI 适配器来连接外设。为了节省一些费用,我们将在常规的 Raspberry Pi 3 上准备好一切,然后将 micro SD 卡切换到 Pi Zero W 上。

image

图 20-2: Raspberry Pi Zero W

我们建议为这个项目使用一张新的 micro SD 卡。请参阅“上传操作系统”在第 10 页查看如何在新的 micro SD 卡上安装最新的 Raspbian 版本。

安装操作系统后,将 micro SD 卡插入常规的 Pi 中。打开 Pi 并等待几秒钟以便系统启动。然后通过点击桌面右上角的Wi-Fi来配置 Wi-Fi。接着,输入您的 Wi-Fi 密码,并等待几秒钟直到 Wi-Fi 连接成功建立。

Node-RED 软件已经预装在 Pi 的操作系统中,但您仍然需要安装 Node-RED 仪表盘。为此,首先更新库仓库,然后通过在命令行中输入以下内容来安装 npm(Node 包管理):

pi@raspberrypi:~ $ sudo apt update
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

最后,输入以下命令来安装 Node-RED 仪表盘:

pi@raspberrypi:~ $ sudo npm install --unsafe-perm -g node-red-dashboard

再次提醒,Node-RED 需要在 Pi 启动时自动启动。为此,在终端中输入以下命令。

pi@raspberrypi:~ $ sudo systemctl enable nodered.service

完成这些后,关闭 Pi 并将 micro SD 卡切换到 Raspberry Pi Zero W 上。

接线电路

要构建机器人结构,您需要一个机器人的底盘、两个带有相应车轮的直流电动机、MotoZero 附加板、跳线和您的 Pi(带 Wi-Fi)。使用图 20-1 作为参考。我们将首先将 MotoZero 安装在 Raspberry Pi 顶部,然后将电动机连接到 MotoZero。

将直流电动机接入 MotoZero

MotoZero 允许您独立控制四个电机,但您只需要控制两个直流电机。MotoZero 会是一个未组装的状态,因此您需要进行焊接。Pi Hut 在产品页面提供了组装手册,请访问 thepihut.com/motozero/ 并按照上面的说明进行操作,组装完成后,您的 MotoZero 应该看起来像 图 20-3 中所示。

image

图 20-3: 组装好的 MotoZero 附加模块

图 20-3 展示了您可以连接到 MotoZero 的接口:四个直流电机的正极 (+) 和负极 (–) 连接,以及电源的正极 (+) 和负极 (–) 连接。驱动电机需要外部电源。电机需要较大的电流变化来启动,因此使用单独的电源可以防止 Pi 在电流变化时突然断电。

请按照这些说明操作,并参考 图 20-1 连接电机和电池座。

  1. 将右侧直流电机的红线连接到 MotoZero 上电机 1 的正极 (+) 引脚,黑线连接到电机 1 的负极 (–) 引脚。您需要松开螺丝,将电线插入引脚插槽,然后重新拧紧螺丝。

  2. 对左侧电机重复前面的操作,将电源线连接到 MotoZero 电机 2 的接口。

  3. 在未插入电池的情况下,将电池座的红色线连接到 MotoZero 电源连接器的正极 (+) 引脚,黑色线连接到负极 (–) 引脚,如 图 20-3 所示的电路板底部。

注意

如果您发现机器人的车轮旋转方向与预期相反,您可能需要交换直流电机红线与黑线的位置,分别连接到电机 1 或电机 2 的正极 (+) 和负极 (–) 端子。您可以在项目结束时测试应用程序,看看是否需要这样操作。

使用 MotoZero 控制电机

每个直流电机都有三个与之相关的 GPIO 引脚。其中一个引脚,称为 启用 引脚,用于启用电机,就像一个开关。另两个引脚控制电机正负极线的电源。给一根线提供电源并将另一根线接地,电机就会朝一个方向转动;而将电源和接地反向接到电机线的另一端,则使电机朝相反方向转动。

注意

关于电机 3 和电机 4 的 GPIO 信息,您可以查看 Pi Hut 产品页面上的 MotoZero 手册 (thepihut.com/motozero/)。

我们只使用电机 1 和电机 2 的端子,这些端子由下表中显示的 GPIO 控制,当您将 MotoZero 安装到 Pi 顶部时。

电机 1 电机 2
启用:GPIO 5 启用:GPIO 6
电机 1 (+):GPIO 27 电机 2 (+):GPIO 22
电机 1 (–):GPIO 24 电机 2 (–):GPIO 17

要让电动机旋转,启用引脚必须为高电平才能开启电动机,并且正负引脚中必须有一个且仅有一个为高电平。例如,如果你希望电动机 1 以某个方向旋转,可以使用以下设置:

  • GPIO 5: 高

  • GPIO 27: 高

  • GPIO 24: 低

要让同一个电动机旋转到相反的方向,可以使用以下设置:

  • GPIO 5: 高

  • GPIO 27: 低

  • GPIO 24: 高

要关闭电动机,你需要向所有 GPIO 发送低电平信号。其他电动机也遵循相同的逻辑。

编写应用程序

一旦硬件搭建完成,就可以开始创建 Node-RED 应用程序。由于你的 Pi 已经安装在机器人底盘上,最实际的做法是使用常规的桌面或笔记本电脑,并从那里控制它来创建机器人 Node-RED 应用程序。

首先,你需要找到 Raspberry Pi Zero W 的 IP 地址;你将使用它来访问 Pi 的 Node-RED 应用程序仪表盘,在那里你可以创建机器人应用程序。

在继续之前,你需要确保 Raspberry Pi 已经开机,并且你的计算机和 Pi 连接到同一网络。

查找 Raspberry Pi IP 地址

警告

Raspberry Pi Zero W 有两个迷你 USB 端口,其中一个标有 PWR IN,专用于为 Pi 提供电源。

通过将 5V 电源适配器连接到墙面插座来启动 Raspberry Pi。在创建 Node-RED 应用程序时,你只需使用此电源;一旦完成,应该切换为便携电源。

你可以使用 Angry IP Scanner 软件找到 Pi 的 IP 地址。免费下载并安装到你的常规桌面或笔记本电脑上,网址为 angryip.org/download/,然后按照提示完成安装。

安装完成后,打开 Angry IP Scanner 并点击 开始 按钮。等待几秒钟,直到显示可用的 IP 地址。你的 Pi IP 地址的主机名应该是 raspberrypi.lan,所以记下对应的 IP 地址。图 20-4 标出了我们的 Raspberry Pi IP 地址,即 192.168.1.122。

image

图 20-4: 使用 Angry IP Scanner 软件查找 Raspberry Pi 的 IP 地址

创建 Node-RED 流程

注意

关于 Node-RED 的介绍,请参见 第 17 项目。

在你的常规计算机上,确保它与 Pi 处于同一网络,打开一个网页浏览器标签,访问 http://<Pi IP 地址>:1880,将 <Pi IP 地址> 替换为你之前记下的 Raspberry Pi IP 地址。在我们的例子中,我们访问了 http://192.168.1.122:1880。你的 Raspberry Pi Node-RED Web 服务器应该会打开。

在窗口的右上角,选择 dashboard 标签,在 Layout 标签内,创建一个名为 Robot 的标签。接下来,在该 Robot 标签中创建两个组,分别命名为 MainPoweroff。Main 组是你将组织控制机器人按钮的地方,Poweroff 组是你将添加远程关闭树莓派按钮的地方。完成这些标签和组后,你的布局应如 图 20-5 所示。

image

图 20-5: Node-RED 应用程序仪表板布局

添加五个按钮、一个功能、六个 rpi gpio 输出节点和一个 exec 节点到流程中。连接节点并编辑它们的名称,使其与 图 20-6 中的名称匹配。

image

图 20-6: Node-RED 应用程序节点

编辑功能的属性,使其具有六个输出,并按 表 20-1 中所示分配所有节点的属性。

表 20-1: 分配给每个节点的属性

节点 属性

| 前进 | 组:Main [Robot] 大小:自动

图标:fa-arrow-up

标签:前进

有效载荷:前进 |

| 左 | 组:Main [Robot] 大小:自动

图标:fa-arrow-left

标签:左

有效载荷:左 |

| 右 | 组:Main [Robot] 大小:自动

图标:fa-arrow-right

标签:右

有效载荷:右 |

| 反向 | 组:Main [Robot] 大小:自动

图标:fa-arrow-down

标签:反向

有效载荷:反向 |

| 停止 | 组:Main [Robot] 大小:自动

图标: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 类型:数字输出

| Poweroff | 组:Poweroff [Robot] 大小:自动

图标:fa-power-off

标签:Poweroff

背景:红色 |

| exec | 命令:/usr/bin/sudo + 附加:未选中

poweroff

名称:Poweroff |

图 20-7 显示了 exec 节点的设置。

image

图 20-7: exec 节点属性

所有节点应属于 Main 组,除了 Poweroff 按钮,它应属于 Poweroff 组。

输入脚本

将 清单 20-1 中的 JavaScript 代码(也可以从 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

当点击停止按钮时,代码中没有任何条件被满足,函数将发送初始化时的值 ➊。

在函数节点外部,当点击 Poweroff 按钮时,exec 节点执行 poweroff 命令来关闭 Pi。记得你已经在 exec 命令属性中填入了 /usr/bin/sudo/poweroff——参见 表 20-1。

一切就绪后,点击右上角的 部署 按钮以保存更改并运行流程。

运行应用程序

现在你的 Node-RED 应用程序已经准备好了。前往 http://<Pi IP 地址>:1880/ui(将 <Pi IP 地址> 替换为你自己的地址)查看应用程序仪表盘。它应该看起来像 图 20-8。

测试控制是否能够让车轮朝正确方向转动,别忘了你需要将四个 AA 电池插入电池座中,以便为电机供电。

如果一个或两个电机转动方向错误,交换 MotoZero 上该电机端口的黑色和红色线,或者更改有效载荷消息以匹配所需的方向。

图片

图 20-8: Node-RED 应用程序用于远程控制机器人

启动机器人

现在应用程序已经准备就绪,点击 Poweroff 按钮关闭 Pi。然后等待几秒钟,直到它关闭。

将 Pi 的电源从墙壁插座切换到电源银行。等待几分钟,直到 Pi 启动并自动启动 Node-RED。在与 Pi 同一网络上的智能手机或其他设备上,打开一个新的浏览器标签页并访问 http://<Pi IP 地址>: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 中电阻的电阻值。

image

图 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%的电阻是可以使用的。

第二十四章:LED 灯

第二十五章:显示

第二十六章:传感器

第二十七章:相机

第二十八章:网络应用

第二十九章:游戏和玩具

posted @ 2025-12-01 09:40  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报