Xbee-树莓派和-Arduino-传感器网络编程-三-

219 阅读1小时+

Xbee、树莓派和 Arduino 传感器网络编程(三)

原文:Beginning Sensor Networks with XBee, Raspberry Pi, and Arduino

协议:CC BY-NC-SA 4.0

六、基于 Arduino 的传感器节点

物理计算的最大进步之一是微控制器的激增。微控制器由一个处理器、一个小指令集、存储器和可编程输入/输出电路组成,包含在单个芯片上。微控制器通常与支持电路和连接一起封装在一个小的印刷电路板上。

微控制器用于嵌入式系统中,可以定制小型软件程序来控制和监控硬件设备,使其成为传感器网络的理想选择。Arduino 平台是最成功和最受欢迎的微控制器之一。

在本章中,您将探索 Arduino 平台,目标是使用 Arduino 管理传感器节点。您会看到一个关于 Arduino 的简短教程和几个帮助您开始使用 Arduino 的项目。

什么是 Arduino?

Arduino 是一个由开源软件环境支持的开源硬件原型平台。它于 2005 年首次推出,设计目标是使硬件和软件易于使用,并尽可能提供给最广泛的受众。因此,使用 Arduino 并不需要成为电子专家。

最初的目标受众包括艺术家和爱好者,他们需要一个微控制器来使他们的设计和创作更有趣。然而,由于其易用性和多功能性,Arduino 很快成为更广泛的受众和更广泛的项目的选择。

这意味着您可以将 Arduino 用于各种项目,从对环境条件做出反应到控制复杂的机器人功能。Arduino 还通过实际应用使学习电子学变得更加容易。

帮助 Arduino 平台快速采用的另一个方面是通过 Arduino 官方网站( http://arduino.cc/en/ )提供的丰富信息的贡献者社区不断增长。当您访问该网站时,您会发现一个优秀的“入门”教程,以及一个有用的项目想法列表和一个完整的类似 C 语言的参考指南,用于编写控制 Arduino 的代码(称为草图)。

Arduino 还提供了一个名为 Arduino IDE 的集成开发环境。IDE 在您的计算机(称为主机)上运行,在那里您可以编写和编译草图,然后通过 USB 连接将它们上传到 Arduino。IDE 可用于 Linux、Mac 和 Windows。它是围绕一个专门为编写代码而设计的文本编辑器和一组支持编译和加载草图的有限功能而设计的。

草图是以一种特殊的格式编写的,只包含两个必需的方法——一个在 Arduino 复位或通电时执行,另一个持续执行。因此,您的初始化代码放在setup()中,控制 Arduino 的代码放在loop()中。这种语言类似于 C 语言,你可以定义自己的变量和函数。关于写草图的完整指南,见 http://arduino.cc/en/Tutorial/Sketch

您可以通过编写封装了某些功能(如联网、使用存储卡、连接数据库、做数学等)的库来扩展草图的功能并提供重用。

Arduino 支持许多模拟和数字引脚,您可以使用这些引脚来连接各种设备和组件并与之交互。主流主板有特定的引脚布局或接头,允许使用称为屏蔽的扩展板。Shields 允许您为 Arduino 添加额外的硬件功能,如以太网、蓝牙和 XBee 支持。Arduino 和盾牌的物理布局允许你堆叠盾牌。因此,您可以拥有以太网屏蔽和 XBee 屏蔽,因为两者使用不同的 I/O 引脚。在探索 Arduino 在传感器网络中的应用时,您将学习插针和屏蔽的使用。

接下来的部分将研究各种 Arduino 板,并简要描述它们的功能。我按上市时间列出这些主板,从最新型号开始。有更多的电路板和变体可供使用,在本书出版时可能会有一些新的电路板问世,但这些都是传感器网络项目中通常使用的电路板。

Arduino 型号

越来越多的 Arduino 板可供使用。一些是为特殊应用而配置的,而另一些是为不同的处理器和内存配置而设计的。一些主板被认为是官方的 Arduino 主板,因为它们是由 Arduino.cc 标记和认可的。由于 Arduino 是使用知识共享署名共享许可进行许可的,任何人只要遵守许可,都可以构建 Arduino 兼容的主板。本节研究一些更受欢迎的 Arduino 品牌主板。

Arduino 板的基本布局包括一个 USB 连接、一个电源连接器、一个复位开关、用于电源和串行通信的 led 以及一组用于连接屏蔽的标准间隔接头。官方电路板采用独特的蓝色印刷电路板,上面印有白色字样。除了一个型号之外,所有官方主板都可以安装在一个机箱中(它们在 PCB 上有孔,用于安装螺钉)。例外情况是 Arduino 设计用于安装在试验板上。

优诺牌

Uno 板是大多数 Arduino 新手都会选择的标准 Arduino 板。它采用了 ATmega328P 处理器;14 个数字 I/O 引脚,其中 6 个可作为脉宽调制 1 输出;和 6 个模拟输入引脚。Uno 板有 32KB 的闪存和 2KB 的 SRAM。

Uno 有表面贴装器件(SMD)和标准 IC 插座两种形式。如果您希望使用外部 IC 编程器来构建定制解决方案,IC 插座版本允许您更换处理器。详情和完整的数据表可在 https://store.arduino.cc/usa/arduino-uno-rev3 获得。它有一个标准的 USB 型连接器,支持所有屏蔽。图 6-1 显示了 Arduino Uno 板卡。

img/313992_2_En_6_Fig1_HTML.jpg

图 6-1

Arduino Uno Rev3(由 Arduino.cc 提供)

还有一种版本的主板内置 Wi-Fi 芯片,可以用于传感器网络或使用 Wi-Fi 屏蔽有问题的情况(缺乏空间,与其他屏蔽冲突等)。).虽然名称相同,但它在几个方面与标准的 Uno 不同。除了 Wi-Fi 芯片,它还有一个不同的处理器和一个更少的 PWM 引脚。你可以在 https://store.arduino.cc/usa/arduino-uno-WiFi-rev2 了解更多关于 Uno Wi-Fi 板的信息。图 6-2 显示了 Arduino Uno Wi-Fi 板。

img/313992_2_En_6_Fig2_HTML.jpg

图 6-2

Arduino Uno Wi-Fi 版本 2(由 Arduino.cc 提供)

【男性名字】利奥纳多

Leonardo 板代表 Arduino 平台中的另一种标准板。略有不同的是,虽然它支持标准接头布局,但它还有一个 USB 控制器,允许电路板作为 USB 设备(如鼠标或键盘)出现在主机上。该板使用较新的 ATmega32u4 处理器,具有 20 个数字 I/O 引脚,其中 12 个可用作模拟引脚,7 个可用作脉宽调制(PWM)输出。它有 32KB 的闪存和 2.5KB 的 SRAM。

莱昂纳多比乌诺有更多的数字引脚,但继续支持大多数盾牌。USB 连接使用较小的 USB 连接器。该板有带接头和不带接头两种。图 6-3 描绘了一个官方的莱昂纳多董事会。详情和完整的数据表可在 https://store.arduino.cc/usa/leonardo 找到。

img/313992_2_En_6_Fig3_HTML.jpg

图 6-3

Arduino Leonardo(由 Arduino.cc 提供)

由于

Arduino Due 是一款基于 Atmel SAM3X8E ARM Cortex-M3 处理器的新型、更大、更快的主板。处理器为 32 位处理器,板卡支持海量的 54 个数字 I/O 端口,其中 14 个可用于 PWM 输出;12 路模拟输入;4 个 UART 芯片(串行端口)以及 2 个数模转换器(DAC)和 2 个双线接口(TWI)引脚。新处理器有几个优点:

  • 32 位寄存器

  • DMA 控制器(允许独立于 CPU 的内存任务)

  • 512KB 闪存

  • 96KB SRAM

  • 84 兆赫时钟

Due 具有更大的外形尺寸(称为 mega footprint ),但仍然支持使用标准屏蔽和 mega 格式屏蔽。这种新板有一个明显的限制:不像其他板在 I/O 引脚上可以接受高达 5V 的电压,在 I/O 引脚上的 Due 限制为 3.3V。详情和完整的数据表可在 https://store.arduino.cc/usa/due 找到。

Arduino Due 旨在用于需要更多处理能力、更多内存和更多 I/O 引脚的项目。尽管新板的功能非常强大,但它仍然是开源的,价格与以前的板相当。寻找您的项目需要最大硬件性能的原因。图 6-4 显示了一个 Arduino Due 板。

img/313992_2_En_6_Fig4_HTML.jpg

图 6-4

arduino Due(arduino . cc 提供)

Mega 2560

Arduino Mega 2560 是 Due 的旧版本。它基于 ATmega2560 处理器(因此得名)。与 Due 一样,该板支持大量的 54 个数字 I/O 端口,其中 14 个可用作 PWM 输出、16 个模拟输入和 4 个 UARTs(硬件串行端口)。它使用 16MHz 时钟和 256KB 闪存。详情和完整的数据表可在 https://store.arduino.cc/usa/mega-2560-r3 找到。

Mega 2560 本质上是标准 Arduino Uno 和 Leonardo 的更大形式,但支持标准盾牌(以及“Mega”盾牌)。图 6-5 显示了 Arduino Mega 2560 板。

img/313992_2_En_6_Fig5_HTML.jpg

图 6-5

Arduino Mega(由 Arduino.cc 提供)

有趣的是,Arduino Mega 256 是 Prusa Mendel 和类似 3D 打印机的首选主板,这些打印机需要使用名为 rep rap Arduino Mega polo Lu Shield(RAMPS)的控制板。

Tip

请注意 Due 比 Uno 大多少。如果您选择安装 Due、Mega 或类似的电路板,您可能需要留出更多空间来安装电路板。

微处理器

Arduino Micro 是 Leonardo 板的一种特殊形式,使用带有 20 个数字 I/O 引脚的相同处理器,其中 12 个可用作模拟引脚,7 个可用作 PWM 输出。它有 32KB 的闪存和 2.5KB 的 SRAM。详情和完整的数据表可在 https://store.arduino.cc/usa/arduino-micro 找到。

这款 Micro 与 Mini 一样,是为在试验板上使用而设计的,但形式更新颖、更新。但与 Mini 不同的是,Micro 是一个全功能的主板,配有 USB 连接器。和 Leonardo 一样,它有内置的 USB 通信,允许电路板作为鼠标或键盘连接到计算机。图 6-6 显示了 Arduino 微板。

img/313992_2_En_6_Fig6_HTML.jpg

图 6-6

Arduino Micro(由 Arduino.cc 提供)

虽然品牌是官方的 Arduino 板,但 Arduino Micro 是与 Adafruit 合作生产的。

NANOTECHNOLOGY 简称

Arduino Nano 是 Arduino Micro 的旧版本。在这种情况下,它基于 Duemilanove4 的功能,具有 ATmega328 处理器(旧型号使用 ATmega168)和 14 个数字 I/O 引脚,其中 6 个可用作 PWM 输出,8 个可用作模拟输入。mini 有 32KB 的闪存,使用 16MHz 时钟。详情和完整的数据表可在 https://store.arduino.cc/usa/arduino-nano 找到。

像 Micro 一样,它具有通过 USB 连接进行连接和编程所需的所有功能。图 6-7 显示了一个 Arduino 纳米板。

img/313992_2_En_6_Fig7_HTML.jpg

图 6-7

Arduino Nano(由 Arduino.cc 提供)

MKR 系列主板

Arduino 还有另一种形式,叫做 MKR(意为“制造者”)系列。MKR 系列包括基于(现已退役的 Zero)板的各种板,具有各种通信功能,如 Wi-Fi、LoRa、LoRaWAN 和 GSM。

它们基于 Atmel ATSAMW25 SoC(片上系统),专为物联网项目和设备而设计。它还支持加密认证。对于那些在需要电池端口的项目上工作的人来说,MKR 系列电路板包括一个 LiPo 充电电路,用于在使用外部电源运行时为 LiPo 电池充电。详情和完整的数据表可在 https://store.arduino.cc/usa/arduino-mkr1000 找到。

这些板不使用与 Uno 兼容的基于屏蔽的板相同的引脚布局(但是你可以得到一个适配器)。相反,它们的设计类似于 Nano 和 Mini(但稍大一点),以最小化电路板的尺寸,从而更容易集成到您的项目中。事实上,它们是物联网(IoT)项目的首选板之一,是传感器网络项目的绝佳选择。由于它们相对较新,并且有些具有专门的通信选项,大多数 Arduino 新手最好从支持 Uno 兼容屏蔽的 Arduino 板开始。

img/313992_2_En_6_Fig8_HTML.jpg

图 6-8

mkr 1000(arduino . cc 提供)

Caution

MKR 板采用 3.3V 电源供电,GPIO 引脚上的最大输入为 3.3V

arduino 翻制

越来越多的 Arduino 板可从大量来源获得。因为 Arduino 是开放的硬件,所以发现世界各地的厂商生产的 Arduino 板并不罕见,甚至有点不合法。

虽然有些人会坚持认为真正的 Arduinos 是那些品牌的,但事实是,只要构建质量良好,组件质量高,选择使用品牌还是副本,也就是克隆,是个人偏好之一。我已经从许多来源对 Arduino 板进行了采样,除了少数例外,它们都出色地执行了预期的功能。

除了 Arduino Mini,Arduino 克隆板的硬件配置种类更多。有些 Arduinos 是为嵌入式系统或试验板设计的,有些是为原型设计的。在接下来的几节中,我将研究一些更流行的克隆板。

迷你版 Arduino

Arduino Pro Mini 是 SparkFun 的另一款主板。它基于 ATmega168 处理器(旧型号使用 ATmega168 ),有 14 个数字 I/O 引脚,其中 6 个可用作 PWM 输出,8 个模拟输入。Pro Mini 有 16KB 的闪存和 1KB 的 SRAM,它使用 16MHz 的时钟。详情和完整的数据表可在 www.sparkfun.com/products/11113 找到。

Arduino Pro Mini 以 Arduino Mini 为模型,也用于试验板,但不带有接头。这使得 Arduino Pro Mini 成为半永久性安装的理想选择,在这种安装中,引脚可以焊接到组件或电路上,空间非常宝贵。图 6-9 显示了一个 Arduino Pro 迷你板。它真的那么小。

img/313992_2_En_6_Fig9_HTML.jpg

图 6-9

Arduino Pro Mini(由 SparkFun 提供)

此外,Pro Mini 不包括 USB 连接器,因此必须连接到 FTDI 电缆或类似的分线板并进行编程。它有两种型号,一种是带 8MHz 时钟的 3.3V 型号,另一种是带 16MHz 时钟的 5V 型号。

菲欧

Arduino Fio 是 SparkFun 制作的另一款主板。它是为无线项目设计的。它基于 ATmega32U4 处理器,具有 14 个数字 I/O 引脚,其中 6 个可用作 PWM 输出,8 个模拟引脚。详情和完整的数据表可在 www.sparkfun.com/products/11520 找到。

Fio 需要一个 3.3V 电源,允许使用锂聚合物(LiPo)电池,该电池可以通过板上的 USB 连接器充电。

在主板底部的 XBee 插座中可以看到它的无线血统。虽然 USB 连接可以让您给电池充电,但您必须使用 FTDI 电缆或分线适配器来连接和编程 Fio。与 Pro 型号类似,Fio 不带有接头,允许电路板用于半永久性安装,连接焊接到位。图 6-10 显示了一个 Arduino Fio 板。

img/313992_2_En_6_Fig10_HTML.jpg

图 6-10

Arduino Fio(由 SparkFun 提供)

西迪耶诺

Seeeduino 是 Seeed Studio ( www.seeedstudio.com )制作的 Arduino 克隆体。它基于 ATmega328P 处理器,具有 14 个数字 I/O 引脚,其中 6 个可用作 PWM 输出,8 个模拟引脚。它有 32KB 的闪存和 2KB 的 SRAM。详情和完整的数据表可在 www.seeedstudio.com/Seeeduino-V4-2-p-2517.html 找到。

该板的尺寸与 Arduino Uno 相似,支持所有标准接头。它支持许多增强功能,如 I2C 和串行 Grove 连接器和迷你 USB 连接器,并使用 SMD 组件。这也是一个醒目的红色黄色标题。图 6-11 显示了一个 Seeeduino 板。

img/313992_2_En_6_Fig11_HTML.jpg

图 6-11

Seeeduino(由 Seeed Studio 提供)

Seeed Studio 还制作了该板的“迷你”版本()。

SpikenzieLabs ( www.spikenzielabs.com )的 Sippino 设计用于无焊料试验板。它的成本更低,因为它的元件更少,占地面积更小。它是未组装的,如果你正在学习焊接,可以成为一个非常愉快的下午项目。

它基于 ATmega328 处理器,有 14 个数字 I/O 引脚,其中 6 个可用作 PWM 输出,6 个模拟输入引脚。Sippino 板有 32KB 的闪存和 2KB 的 SRAM。详情和完整的数据表可在 www.spikenzielabs.com/Catalog/arduino/sippino-prototino-8482/sippino-kit?cPath=1& 找到。

Sippino 没有 USB 连接,因此您必须使用 FTDI 电缆对其进行编程。好消息是,无论您的项目中有多少个 Sippinos,您都只需要一根电缆。我有许多 Sippinos,并在我的许多 Arduino 项目中使用它们,这些项目的空间非常宝贵。图 6-12 显示了安装在试验板上的 Sippino。

img/313992_2_En_6_Fig12_HTML.jpg

图 6-12

西皮诺(SpikenzieLabs 提供)

SpikenzieLabs 还做了一个带 USB 接口的版本( www.spikenzielabs.com/Catalog/arduino/sippino-prototino-8482/sippino8482-usb-kit?cPath=1& )。

虽然您不能在 si Pino 上使用普通屏蔽,但 SpikenzieLabs 还提供了一种称为屏蔽坞的特殊适配器,允许您将 si Pino 与标准 Arduino 屏蔽配合使用。shield dock 是一个令人惊叹的附件,它可以让您像使用标准的 Uno 或 Duemilanove 一样使用 Sippino。图 6-13 显示了安装在屏蔽坞上的西皮诺。详情和完整的数据表可在 www.spikenzielabs.com/Catalog/spikenzielabs/the-shield-dock 找到。

img/313992_2_En_6_Fig13_HTML.jpg

图 6-13

盾牌码头上的西皮诺(SpikenzieLabs 提供)

原型

Prototino 是 SpikenzieLabs 的另一个产品。它具有与 Sippino 相同的元件,但它不是一个友好的试验板布局,而是安装在一个包含完整原型制作区域的 PCB 上。与 Sippino 一样,它基于 ATmega328 处理器,具有 14 个数字 I/O 引脚,其中 6 个可用作 PWM 输出,6 个模拟输入引脚。Prototino 板有 32KB 的闪存和 2KB 的 SRAM。详情和完整的数据表可在 www.spikenzielabs.com/Catalog/arduino/sippino-prototino-8482/prototino?cPath=1& 找到。

Prototino 是构建具有支持组件和电路的解决方案的理想选择。在某些方面,它类似于 Nano、Mini 和类似的板,因为您可以将其用于永久安装。但与这些电路板(甚至 Arduino Pro)不同的是,Prototino 为您提供了一个直接将组件添加到电路板的空间。我在项目中使用了许多 Prototino 板,在这些项目中,我将组件添加到 Prototino 并将其安装在机箱中。这让我可以使用一块板创建一个解决方案,甚至可以快速轻松地构建几个副本。

像 Sippino 一样,Prototino 没有 USB 连接,所以你必须使用 FTDI 电缆对其进行编程。图 6-14 显示了一个 Prototino 板。

img/313992_2_En_6_Fig14_HTML.jpg

图 6-14

Prototino(由 SpikenzieLabs 提供)

阿达福罗地铁站

Adafruit 的 Metro 是一组 Arduino 兼容板,支持多种格式,包括几种支持 Arduino 屏蔽的格式。我喜欢的版本是 Metro 328 ( www.adafruit.com/product/2488 )。图 6-15 显示了 Adafruit 的 Metro 328 板。该板使用 16MHz 的 ATmega328P 和许多小的改进,使 Metro 成为 Arduino Uno 的优秀替代产品。查看产品页面了解更多详情。

img/313992_2_En_6_Fig15_HTML.jpg

图 6-15

地铁 328 号线(由 Adafruit 提供)

Note

Adafruit 也有几个 CircuitPython 模型。有些更小,因此如果您需要最小化节点的占用空间,这可能是一个选项。详见 www.adafruit.com/category/966

There is Even One Made of Paper

时不时地,你会遇到一些平凡的事情,因为媒介的改变而变得非常有趣。古伊列梅·马丁斯创立的 PAPERduino 就是这样一个例子。PAPERduino 是一个最小的 Arduino,它使用纸质模板代替 PCB。您只需下载并打印模板,购买一份常用分立元件清单,然后按照模板上打印的连接图将元件焊接到短导线上。您可以访问以下网站了解更多信息: http://lab.guilhermemartins.net/2009/05/06/paperduino-prints/

那么,我该买哪个呢?

如果你想知道该买哪个 Arduino,答案取决于你想做什么。对于本书中的大多数项目,任何 Arduino Uno 或支持标准 shield headers 的类似克隆都可以。您不需要购买更大的 Due 或其前身,因为不需要增加内存和 I/O 引脚。

我使用 Arduino Uno、Uno Wi-Fi 或 Leonardo 来完成本书中的所有项目。虽然您可以使用旧的板没有问题,但使用莱昂纳多板有一些问题。当你遇到这些问题时,我会指出来。大多数问题都与列奥纳多板上重新定位的图钉有关。例如,在 Leonardo 上,SPI 接头引脚(在图 6-3 的左上角)已经被移动。

对于未来的项目,在选择 Arduino 之前,您应该考虑一些事情。例如,如果您的项目主要基于试验板,或者您想要将项目的物理大小保持在最小,并且您不打算使用任何屏蔽,Arduino Mini 可能是更好的选择。相反,如果您计划进行大量的编程来实现复杂的数据操作或分析算法,您可能需要考虑它所增加的处理能力和内存。

底线是,大多数情况下,您的选择将基于物理特性(大小、屏蔽支持等),很少基于处理能力或内存。SparkFun 有一个优秀的买家指南,在其中你可以看到每种选择的利弊。详见 www.sparkfun.com/pages/arduino_guide

去哪里买

由于 Arduino 平台的流行,许多供应商出售 Arduino 和 Arduino 克隆板、屏蔽和附件。Arduino.cc 网站( https://store.arduino.cc/usa )也有一个专门介绍授权经销商的页面。如果这里列出的资源都不适合你,你可以在这个页面上找到你附近的零售商。

在线零售商

有越来越多的在线零售商,你可以在那里购买 Arduino 板和配件。下面列出了一些比较受欢迎的网站:

  • SparkFun :从分立元件到公司自有品牌的 Arduino 克隆和盾牌,SparkFun 几乎拥有你可能想要的任何 Arduino 平台( www.sparkfun.com/ ))。

  • 阿达果:携带越来越多的组件、小工具等等。它为电子爱好者提供越来越多的产品,包括 Arduino 产品的完整系列。Adafruit 还拥有出色的文档库和 wiki 来支持其销售的所有产品( www.adafruit.com/ ))。

你也可以拜访一些克隆板的制造商。以下是领先的克隆制造商及其店面链接:

零售店(美国)

也有实体商店销售 Arduino 产品。虽然没有在线零售商那么多,而且他们的库存通常也有限,但是如果你很快需要一个新的 Arduino 板,你可以在以下零售商那里找到。你可能会在你所在的地区找到更多的零售商。寻找受欢迎的业余电子商店:

  • Fry's :一家电子超市,有一个巨大的仓库,里面存放着电子产品、组件、微控制器、电脑零件等,可供订购。Fry's 出售 Arduino 品牌的电路板、盾牌和配件,以及 Parallax、SparkFun 和更多( http://frys.com/ ).的产品

  • 微中心:微中心类似于 Fry 的,提供大量的产品库存。然而,大多数微型中心商店的电子元件库存比弗莱氏( www.microcenter.com/ ).)要少

现在,您已经对硬件细节和各种可用的 Arduino 板有了更好的了解,让我们深入了解如何使用和编程 Arduino。下一节提供安装 Arduino 编程环境和 Arduino 编程的教程。后面的章节将介绍一些项目,以培养你开发传感器网络的技能。

Arduino 教程

本节是一个简短的教程,介绍如何开始使用 Arduino。它涵盖了获取和安装 IDE 以及编写示例草图。我没有重复本书之前的优秀作品,而是涵盖了重点,并向不太熟悉 Arduino 的读者推荐在线资源和其他提供更深入介绍的书籍。此外,Arduino IDE 有许多示例草图,您可以使用它们自己探索 Arduino。大多数在 Arduino.cc 网站上都有相应的教程。

学习资源

有很多关于 Arduino 平台的信息。如果您刚刚开始使用 Arduino,Apress 提供了一系列令人印象深刻的书籍,涵盖了与 Arduino 相关的各种主题,从开始使用微控制器到学习其设计和实现的细节。以下是一些比较受欢迎的书籍。有些比你想象的要老一些,但是仍然非常有用。

  • 迈克尔·麦克罗伯茨的《Arduino 入门》

  • *实用 Arduino:开源硬件的酷项目(技术在行动)*作者 Jonathan Oxer 和 Hugh bl mings(a press,2009)

  • 戴尔·麦特(2011 年出版)的《Arduino 内部原理》

还有一些优秀的在线资源,可以帮助您了解更多关于 Arduino、Arduino 库和示例项目的信息。以下是一些最好的:

Arduino IDE

Arduino IDE 可供 Mac、Linux (32 位和 64 位版本)和 Windows 平台下载。你可以从 http://arduino.cc/en/Main/Software 下载 IDE。每个平台都有链接,如果需要为不同的平台编译 IDE,还可以链接到源代码。

Tip

有趣的是,有一个网页版的 IDE,你可以不用安装在电脑上就可以使用。如果您想在不想(或不能)安装 IDE 的 PC 上使用它,这可能会有所帮助。

安装 IDE 非常简单。为了简洁起见,我省略了安装 IDE 的实际步骤,但是如果您需要安装 IDE 的演示,您可以在下载页面上看到入门链接,或者阅读 Michael McRoberts 的开始 Arduino 中的更多内容(Apress,2010)。

IDE 启动后,您会看到一个简单的界面,其中包含一个文本编辑器区域(默认为白色背景),编辑器下方的一个消息区域(默认为黑色背景),以及顶部的一个简单的按钮栏。按钮(从左至右)编译编译上传新建打开保存。右边还有一个打开串行监视器的按钮。您可以使用串行监视器查看通过串行库发送(或打印)的 Arduino 消息。你可以在你的第一个项目中看到这一点。图 6-16 显示了 Arduino IDE。

img/313992_2_En_6_Fig16_HTML.jpg

图 6-16

Arduino IDE

注意,在图 6-16 中,你可以看到一个样本草图(称为blink)和一个成功编译操作的结果。我通过点击文件例子基本眨眼来加载这个草图。注意底部,它告诉你你正在一个特定的串行端口上编程一个 Arduino Leonardo 板。

由于处理器和支持架构的不同,编译器构建程序的方式(以及 IDE 上传程序的方式)也有所不同。因此,当您启动 IDE 时,首先要做的事情之一就是从工具电路板菜单中选择您的电路板。图 6-17 显示了在 Mac 上选择板卡的示例。

img/313992_2_En_6_Fig17_HTML.jpg

图 6-17

选择 Arduino 板

请注意可用的电路板数量。请务必选择与您的主板相匹配的产品。如果您使用的是克隆板,请查看制造商网站,了解推荐使用的设置。如果你选择了错误的板,你通常会在上传时得到一个错误,但是你选择了错误的板可能并不明显。因为我有很多不同的主板,所以我养成了每次启动 IDE 时都选择主板的习惯。

接下来您需要做的是选择 Arduino 板连接的串行端口。要连接到板上,使用工具端口菜单选项。图 6-18 显示了 Mac 上的一个例子。在这种情况下,没有列出串行端口。如果您没有将 Arduino 插入电脑的 USB 端口(或集线器),您已经将它插入但在某个时候将其断开,或者您没有加载 Arduino 的驱动程序(Windows ),就会发生这种情况。通常,这可以通过简单地拔出 Arduino 并将其插回,然后等待直到计算机识别该端口来解决。

Note

如果你用的是 Mac,选择哪个端口并不重要:无论是以tty开头的端口还是以cu开头的端口都可以。

img/313992_2_En_6_Fig18_HTML.jpg

图 6-18

选择串行端口

Tip

如果您需要在 Windows 上安装驱动程序的帮助,请参见 www.arduino.cc/en/Guide/HomePage?from=Guide.Howto

好了,现在你已经安装了你的 Arduino IDE,你可以连接你的 Arduino 并设置板和串行端口。您会看到 Arduino 上的 led 亮起。这是因为 Arduino 从 USB 获得电源。因此,当 Arduino 连接到您的电脑时,您不需要提供外部电源。接下来,您将深入一个简单的项目来演示 Arduino IDE,并学习如何构建、编译和上传基本草图。

项目:硬件“你好,世界!”

无处不在的“你好,世界!”Arduino 的项目是闪烁的灯。该项目使用一个 LED,一个试验板,和一些跳线。Arduino 在loop()迭代过程中打开和关闭。这是一个很好的开始项目,但它与如何使用传感器无关。

因此,在本节中,您将通过添加传感器来扩展闪光灯项目。在这种情况下,您仍然可以通过使用可以说是最基本的传感器来简化事情:按钮。目标是每当按钮被按下时,LED 就会亮起。

硬件连接

让我们从组装 Arduino 开始。确保首先断开(关闭)Arduino。您可以使用任何带有 I/O 引脚的 Arduino 变体。在试验板上放置一个 LED 和一个按钮。将 5V 引脚连接到试验板电源轨,将接地引脚连接到接地轨,并将按钮放在试验板的中心。将 LED 放在试验板的一侧,如图 6-19 所示。

img/313992_2_En_6_Fig19_HTML.jpg

图 6-19

带按钮的 LED 图示

你就快到了。现在,将一根跳线从电源轨连接到按钮的一侧,并将按钮的另一侧连接到 Arduino 上的(DIGITAL)引脚 2(位于带有 USB 连接器的一侧)。接下来,将 LED 连接到试验板上的地和一个 150 欧姆的电阻(颜色:棕色、绿色、棕色、金色)。电阻的另一端应连接到 Arduino 上的第 13 号针脚。你还需要一个电阻,在按钮没按下的时候把按钮拉低。将一个 10K 欧姆电阻器(颜色:棕色、黑色、橙色、金色)放在按钮的侧面,导线连接到引脚 2 和接地。

LED 最长的一边是正极。正极应该是连接到电阻的一侧。电阻接哪个方向都没关系;它用于限制 LED 的电流。再次检查图纸,以确保您有一个类似的设置。

Note

大多数 Arduino 板都有一个连接到引脚 13 的 LED。您将重复使用该引脚来演示如何使用模拟输出。因此,您可能会看到引脚 13 附近的一个小 LED 与试验板上的 LED 同时亮起。

Cool Gadget

使用 Arduino 的最酷的小工具之一是 Adafruit ( www.adafruit.com/products/275 )的 Arduino 安装板。

这个小小的丙烯酸板有足够的空间放半块面包板和一个 Arduino。它甚至有安装孔,用于将 Arduino 固定在平板上,还有小橡胶脚,用于保持平板远离工作表面。下图(由 Adafruit 提供)显示了安装板的作用。

img/313992_2_En_6_Figa_HTML.jpg

虽然你可以用 Lexan 或 Plexiglas(我有)制作自己的 Arduino 安装板,但 Adafruit 产品比你自己制作的产品好一点。只需大约 5.00 美元,您就可以将 Arduino 和试验板放在一起,避免在桌面上留下划痕(来自 Arduino 底部的尖头)——更好的是,避免不小心将通电的 Arduino 放在导电表面上带来的严重副作用(这绝不是一个好主意)。

写素描

这个项目需要的草图在 Arduino 上使用了两个 I/O 引脚:一个输出和一个输入。输出引脚将用于点亮 LED,输入引脚将检测按钮接合。将正电压连接到按钮的一侧,另一侧连接到输入引脚。当您检测到输入引脚上的电压时,您告诉 Arduino 处理器向输出引脚发送正电压。这种情况下,LED 的正极连接到输出引脚。

如图 6-18 所示,输入引脚为 2 号引脚,输出引脚为 13 号引脚。让我们使用一个变量来存储这些数字,这样您就不必担心重复硬编码的数字(并冒着出错的风险)。使用pinMode()方法设置每个引脚的模式(INPUTOUTPUT)。您将变量语句放在setup()方法之前,并在setup()方法中设置pinMode()调用,如下所示:

int led = 13;     // LED on pin 13
int button = 2;   // button on pin 2

void setup() {
  pinMode(led, OUTPUT);
  pinMode(button, INPUT);
}

loop(方法中,您放置代码来检测按钮按压。使用digitalRead()方法读取管脚的状态(LOWHIGH),其中LOW表示管脚上没有电压,HIGH表示管脚上检测到正电压。

您还可以在loop()方法中放置当输入引脚状态为HIGH时打开 LED 的代码。在这种情况下,当输入引脚状态为HIGH时,使用digitalWrite()方法将输出引脚设置为HIGH,同样,当输入引脚状态为LOW时,将输出引脚设置为LOW。以下代码显示了所需的语句:

void loop() {
  int state = digitalRead(button);
  if (state == HIGH) {
    digitalWrite(led, HIGH);
  }
  else {
    digitalWrite(led, LOW);
  }
}

现在让我们看看完整的草图,并附上适当的文档。清单 6-1 显示了完成的草图。

/*
  Simple Sensor - Beginning Sensor Networks Second Edition
  For this sketch, we explore a simple sensor (a pushbutton) and a simple response to sensor input (a LED). When the sensor
  is activated (the button is pushed), the LED is illuminated.
*/
int led = 13; // LED on pin 13
int button = 2;   // button on pin 2
// the setup routine runs once when you press reset:
void setup() {
  // initialize pin 13 as an output.
  pinMode(led, OUTPUT);
  pinMode(button, INPUT);
}
// the loop routine runs over and over again forever:
void loop() {
  // read the state of the sensor
  int state = digitalRead(button);
  // if sensor engaged (button is pressed), turn on LED
  if (state == HIGH) {
    digitalWrite(led, HIGH);
  }
  // else turn off LED
  else {
    digitalWrite(led, LOW);
  }
}

Listing 6-1Simple Sensor Sketch

当你已经输入了草图,你就可以编译和运行它了。将草图命名为basic_sensor.ino

Tip

想要避免手动输入所有这些内容吗?你可以在 Apress 网站上找到这本书的源代码。

编译和上传

写好草图后,使用 IDE 左上角的编译按钮测试编译。修复消息窗口中出现的任何编译错误。典型的错误包括变量或方法的拼写错误或大小写改变(编译器区分大小写)。

修复任何编译错误后,点击上传按钮。IDE 编译草图,并将编译后的草图上传到 Arduino 板上。您可以通过消息窗口上方右下角的进度条来跟踪进度。当编译好的草图上传后,进度条会消失。

测试传感器

上传完成后,你会在 Arduino 上看到什么?如果你做对了每一件事,答案是什么都没有。它只是用一个黑暗的 LED 回望着你——几乎是嘲弄地。现在,按下按钮。LED 是否点亮?如果是这样,那么恭喜你:你是一名 Arduino 程序员!

如果 LED 不亮,按住按钮一两秒钟。如果不起作用,请检查您的所有连接,以确保您插入了试验板上的正确走线,并且您的 LED 正确就位,长边连接到电阻器,电阻器连接到引脚 13。

另一方面,如果 LED 灯一直亮着,尝试将按钮重新调整 90 度。您可能将按钮设置在了错误的方向。

尝试几次这个项目,直到兴奋过去。如果你是 Arduino 的老手,那可能是很短的一段时间。如果这一切对你来说都是新的,那就按下那个按钮,享受建造第一个传感器节点的荣耀吧!

下一节研究一个更复杂的传感器节点,使用一个发送数字数据的温度和湿度传感器。正如您将看到的,还有很多事情要做。

使用 Arduino 托管传感器

Arduino 的数字和模拟引脚使其成为托管传感器的理想平台。由于大多数传感器只需要很少的支持组件,所以您通常可以在一个 Arduino 上安装多个传感器。例如,可以安装一个温度传感器,甚至多个温度传感器、气压传感器、湿度传感器等等,用于从给定的地点采集天气状况。

SparkFun 和 Adafruit 有很好的网站,提供大量关于他们销售的产品的信息。通常,传感器产品页面包括示例链接和更多关于使用传感器的信息。如果你是电子产品的新手,你应该坚持使用能提供使用示例的传感器。这听起来可能像作弊,但除非你有很好的电子知识,否则不正确地使用传感器会付出很高的代价,因为你在正确使用之前已经烧坏了几个组件。

然而,当您想使用另一种传感器时,您应该查看其数据手册。大多数制造商和供应商通过产品页面上的链接提供数据手册。数据手册提供了使用传感器所需的所有信息,但可能没有实际的使用示例。如果你熟悉电子学,这是你可能需要的全部。

如果你是电子产品的业余爱好者或新手,可以查看 Arduino.cc、SparkFun 和 Adafruit 上的维基和论坛。这些站点有丰富的信息和大量的示例,并附有完整的示例代码。如果你找不到任何例子,你可以试着谷歌一下。使用类似“ Arduino <传感器名称>示例的术语。如果你找不到任何例子,并且不是一个有经验的电子技术人员,你可能要重新考虑使用传感器。

另一件需要考虑的事情是如何将传感器连接到 Arduino。回想一下,根据您选择的 Arduino,有许多不同的物理布局。因此,在规划 Arduino 托管的传感器节点时,您应该熟悉 Arduino 的引脚布局。如果您使用 Arduino 托管单个传感器,这可能不是问题。举例来说,图 6-20 显示了一个 Arduino Leonardo 板,突出显示了 I/O 引脚。如果你仔细观察你的 Arduino 板,你会在每个管脚旁边看到缩写的文字来表示它的用途。一些小尺寸的 Arduino 板可能没有放置标签的空间。在这种情况下,请查阅供应商的产品页面,并将其打印出来以供将来参考。

img/313992_2_En_6_Fig20_HTML.jpg

图 6-20

识别 Arduino 板上的 I/O 引脚

现在,让我们将您从学习 Arduino 中学到的知识用于构建一个带有 Arduino 和传感器的传感器节点。

项目:构建 Arduino 温度传感器

在这个项目中,您将构建一个更加复杂的 Arduino 托管的传感器节点。这个项目不仅演示了如何用 Arduino 托管传感器,还提供了一个例子,说明为什么需要微控制器来托管某些类型的传感器。在这种情况下,DHT22 传感器是一个数字传感器,它有自己的协议,这需要一点逻辑来正确解释,从而使它与 XBee 一起使用更加复杂。 2 稍后,您将看到一个简单模拟传感器的示例,您可以将它直接连接到 XBee 模块。

该项目使用 DHT22 温度和湿度传感器,通过试验板连接到 Arduino。DHT22 是一种产生数字信号的简单数字传感器。它需要一个电阻将数据引脚上拉至电压。这种情况下的上拉确保数据值被“上拉”到电压电平,以确保线路上的逻辑电平有效。

让我们直接进入并连接硬件。

Note

这个例子改编自 Adafruit 网站上的一个例子( http://learn.adafruit.com/dht )。

硬件设置

本项目所需的硬件包括 Arduino、DHT22 湿度和温度传感器、试验板、4.7K 欧姆电阻(颜色:黄色、紫色、红色、金色)和试验板跳线。

Tip

如果你被卡住了或者想要更多的信息,在 Adafruit 的网站上有一个极好的教程。

首先将 Arduino 放在试验板旁边。将 DHT22 传感器插入试验板的一侧,如图 6-21 所示。请经常参考此图,并在打开 Arduino 电源(或将其连接到笔记本电脑)之前仔细检查您的连接。你要避免电混沌理论中的意外实验。

img/313992_2_En_6_Fig21_HTML.jpg

图 6-21

DHT22 接线

接下来,将 Arduino 的电源连接到试验板。用一根跳线将 Arduino 上的 5V 引脚连接到试验板电源轨,用另一根跳线将 Arduino 上的接地(GND)引脚连接到试验板的接地轨。这些电线就位后,您就可以为传感器布线了。您使用四个引脚中的三个,如表 6-1 所示。

表 6-1

DHT22 连接

|

别针

|

连接到

| | --- | --- | | one | 电源和数据引脚之间的+5V 4.7K 电阻(强上拉电阻) | | Two | Arduino 上的引脚 7,4.7K 电阻 | | three | 不连接 | | four | 地面 |

接下来,将传感器的地和电源连接到试验板电源和接地轨。然后将一根电线从传感器上的数据引脚连接到 Arduino 的引脚 7。还有最后一个连接:使用一个 4.7K 欧姆的上拉电阻连接到数据线和试验板的电源轨。

软件设置

要将 DHT22 与 Arduino 配合使用,您需要拥有最新的 DHT22 库。您可以通过搜索库管理器直接从 Arduino IDE 安装库。打开 Arduino IDE,然后打开一个新的草图,从菜单中选择草图包含库管理库……。图 6-22 显示了库管理器。

img/313992_2_En_6_Fig22_HTML.jpg

图 6-22

图书馆经理

库管理器可能需要一些时间来连接到服务器并下载最新的目录。完成后,您可以在右上角的文本框中键入DHT22并按下ENTER。这将在库目录中搜索所有匹配的库。

从 Adafruit 选择 DHT 传感器库,点击Install。如果系统提示您安装支持库,请点击Install all以确保所有必备软件都已安装,如图 6-23 所示。

img/313992_2_En_6_Fig23_HTML.jpg

图 6-23

安装所有库

现在您已经配置好了硬件并设置好了 DHT22 库,让我们写一些代码吧!

写素描

像任何草图一样,我们需要包含一些库,定义一些常量,并在序言中实例化 DHT 对象。这里包含了DHT库头,将传感器的数据引脚定义为 Arduino 上的引脚 7,添加 5 秒的延迟常数,并实例化 DHT 类的一个实例。由于该库支持多种类型的 DHT 传感器,我们还必须使用库中声明的特殊类型。例如,为了告诉库使用 DHT22 传感器,我们相应地将类型设置为DHT22。以下是草图的序言:

#include "DHT.h"
#define DHTPIN 7          // DHT2 data is on pin 7
#define read_delay 5000   // 5 seconds
#define DHTTYPE DHT22     // DHT 22 (AM2302)

接下来,我们需要实例化 DHT 类。我们通过传递想要使用的 pin 和 DHT 类型来实现这一点。

DHT dht(DHTPIN, DHTTYPE);

DHT22 有自己的数据通信协议。幸运的是,Adafruit 的库使得从传感器读取变得容易。要读取数据,您只需调用适当的方法,如表 6-2 所示,该方法将返回值保存在变量中。

表 6-2

DHT 库中的数据方法

|

方法

|

描述

| | --- | --- | | dht.readHumidity() | 读取湿度 | | dht.readTemperature() | 读取摄氏温度 | | dht.readTemperature(true) | 读取华氏温度 | | dht.computeHeatIndex(temp_c, humidity, false) | 获取以摄氏度为单位的热量指数 | | dht.computeHeatIndex(temp_c, humidity, true) | 获取华氏温度的热量指数 |

知道了这些,我们需要做的就是调用这些方法来读取数据,将数据保存到变量中,然后打印出来。为了使草图简单而整洁,我们可以将这个逻辑放在一个名为read_data()的方法中。清单 6-2 显示了完整的read_data()方法。

void read_data() {
  // Read humidity
  float humidity = dht.readHumidity();
  // Read temperature as Celsius
  float temp_c = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float temp_f = dht.readTemperature(true);

  // Check for errors and return if any variable has no value
  if (isnan(temp_c) || isnan(temp_f) || isnan(humidity)) {
    Serial.println("ERROR: Cannot read all data from DHT-22.");
    return;
  }
  // Calculate heat index for Celsius
  float hi_c = dht.computeHeatIndex(temp_c, humidity, false);
  // Calculate heat index for temperature in Fahrenheit
  float hi_f = dht.computeHeatIndex(temp_f, humidity, true);
  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.print("%, ");
  Serial.print(temp_c);
  Serial.print("C, ");
  Serial.print(temp_f);
  Serial.println("F ");
  Serial.print("      Heat Index: ");
  Serial.print(hi_c);
  Serial.print("C, ");
  Serial.print(hi_f);
  Serial.println("F ");
}

Listing 6-2The read_data() Method

请注意,代码非常简单。我们只是读取这些值,然后使用Serial.print()Serial.println()方法将数据写入串行监视器。

唯一剩下的就是setup()loop()方法。setup()方法简单地初始化了Serialdht类。loop()方法使用延迟并调用read_data()方法。清单 6-3 显示了完成的草图。

/*
  Beginning Sensor Networks Second Edition
  Sensor Networks Example Arduino Hosted Sensor Node
  This sensor node uses a DHT22 sensor to read temperature and humidity printing the results in the serial monitor.
*/
#include "DHT.h"

#define DHTPIN 7          // DHT2 data is on pin 7
#define read_delay 5000   // 5 seconds
#define DHTTYPE DHT22     // DHT 22 (AM2302)

DHT dht(DHTPIN, DHTTYPE);

void read_data() {
  // Read humidity
  float humidity = dht.readHumidity();
  // Read temperature as Celsius
  float temp_c = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float temp_f = dht.readTemperature(true);

  // Check for errors and return if any variable has no value
  if (isnan(temp_c) || isnan(temp_f) || isnan(humidity)) {
    Serial.println("ERROR: Cannot read all data from DHT-22.");
    return;
  }
  // Calculate heat index for Celsius
  float hi_c = dht.computeHeatIndex(temp_c, humidity, false);
  // Calculate heat index for temperature in Fahrenheit
  float hi_f = dht.computeHeatIndex(temp_f, humidity, true);
  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.print("%, ");
  Serial.print(temp_c);
  Serial.print("C, ");
  Serial.print(temp_f);
  Serial.println("F ");
  Serial.print("      Heat Index: ");
  Serial.print(hi_c);
  Serial.print("C, ");
  Serial.print(hi_f);
  Serial.println("F ")

;
}

void setup() {
  Serial.begin(115200);  // Set the serial port speed
  dht.begin();
  delay(1000);
  Serial.println("Welcome to the DHT-22 Arduino example!\n");
}

void loop() {
  delay(read_delay);
  read_data();
}

Listing 6-3Completed Sketch: Reading a DHT-22 Sensor

如果您尚未这样做,请通过单击新菜单按钮或选择文件来打开一个新的 Arduino 草图。现在您可以编译、上传和测试项目了。你可以给它起任何你喜欢的名字,比如dht22_example.ino

测试执行

执行草图意味着将它上传到你的 Arduino 并观看它运行。如果您尚未连接 Arduino,现在就可以连接。

我喜欢从画草图开始。单击 Arduino 应用程序左侧的复选标记,观察底部信息屏幕中的输出。如果您看到错误,请修复它们并重试编译。常见错误包括丢失 DHT22 库(这可能需要重新启动 Arduino 应用程序)、键入错误、语法错误等。一旦一切编译正确,你就可以点击工具栏上的上传按钮来上传你的草图了。

上传完成后,单击工具栏右侧的按钮打开串行监视器。观察 Arduino 消息。清单 6-4 显示了您应该看到的典型输出。

Welcome to the DHT-22 Arduino example!

Humidity: 48.00%, 18.20C, 64.76F
      Heat Index: 17.33C, 63.19F
Humidity: 50.00%, 18.30C, 64.94F
      Heat Index: 17.49C, 63.48F
Humidity: 51.80%, 19.10C, 66.38F
      Heat Index: 18.42C, 65.15F
Humidity: 53.60%, 20.20C, 68.36F
      Heat Index: 19.67C, 67.42F
Humidity: 53.20%, 21.40C, 70.52F
      Heat Index: 20.98C, 69.77F
Humidity: 51.50%, 22.10C, 71.78F
      Heat Index: 21.71C, 71.08F
Humidity: 50.00%, 22.50C, 72.50F
      Heat Index: 22.11C, 71.80F
Humidity: 48.50%, 22.60C, 72.68F
      Heat Index: 22.18C, 71.93F

Listing 6-4Output of the DHT22 Sensor Sketch

如果你看到类似的输出,恭喜你!您刚刚构建了第一个 Arduino 托管的传感器节点。这是构建传感器网络的重要一步,因为您现在已经拥有了开始构建更复杂的无线传感器节点和用于记录传感器数据的聚合节点所需的工具。

让我们进一步体验 Arduino 传感器,添加 XBee 模块,使传感器能够远离 Arduino 放置。这有效地展示了 Arduino 如何远程托管多个传感器节点,从而成为传感器网络中的聚合节点。

项目:使用 Arduino 作为 XBee 传感器节点的数据收集器

这个项目结合了你在本章学到的 Arduino 和在第 2 和 4 章学到的 XBee。更具体地说,您使用一个 Arduino 和一个使用 XBee 模块将传感器与 Arduino 连接起来的远程传感器。我们将重用第四章中的 XBee 传感器节点,并使用 Arduino 读取数据。

XBee 传感器节点

按照第四章的内容创建 XBee 传感器节点。提醒一下,该节点的构造如图 6-24 所示。

img/313992_2_En_6_Fig24_HTML.jpg

图 6-24

XBee 传感器节点

如果您尚未配置第四章中的传感器节点,或者如果您需要重置模块,您应该首先确保加载了最新的固件,并使用表 6-3 中所示的设置。请注意,您不需要第四章中的 IR 设置,但是如果您想要重用您在该章中使用的模块,这是可以的。

表 6-3

XBee 传感器节点选项和值

|

密码

|

设置名称

|

描述

|

价值

| | --- | --- | --- | --- | | D3 | a3/上帝 3 | 触发模拟或数字数据记录 | 2—ADC | | 身份 | 平移 ID | 网络的 Id | Eight thousand and eighty-eight | | 红外的 | 输入输出采样率 | 等待发送数据的时间 | 3a 98—15000 毫秒 | | 镍 | 节点标识符 | 节点的名称 | TMP36 | | V+ | 电源电压阈值 | 电源电压 | FFFF(总是发送) |

注意,与第五章中的项目不同,我们必须设置 I/O 采样率。这是因为我们将使用的库不具备在 ZigBee 网络中搜索远程节点的能力。相反,在这个项目中,Arduino 将进行轮询,直到 IO 样本被发送到协调器节点。因此,我们已经看到了从 ZigBee 网络获取数据的两种不同方式——直接从节点请求数据(第五章)和轮询从节点发送的数据(本章)。

协调节点

协调器节点应使用最新加载的固件和表 6-4 中所示的设置进行类似配置。

表 6-4

XBee 协调器选项和值

|

密码

|

设置名称

|

描述

|

价值

| | --- | --- | --- | --- | | 身份 | 平移 ID | 网络的 Id | Eight thousand and eighty-eight | | 镍 | 节点标识符 | 节点的名称 | 协调者 |

现在还不需要安装 XBee 模块。您需要配置其设置。您将在下一节中执行该操作。

现在,让我们设置 Arduino 和 XBee。

带 XBee 盾的 Arduino

您可以使用 Arduino 从 XBee 传感器节点读取数据。这是一个使用 Arduino 作为来自 XBee 传感器节点的传感器数据的数据聚合器(收集器)的例子。让我们用 XBee 设置一个 Arduino。这个项目演示了使用 Arduino 通过 XBee 接收数据,但是你也可以通过 XBee 发送数据。

硬件设置

本节中的示例设置使用典型的 Arduino (Uno、Leonardo 等。)支持标准屏蔽。虽然没有明确要求必须使用设计用于接受 XBee 模块的屏蔽,但是大多数 XBee 屏蔽都是为了使 XBee 的使用更容易。换句话说,你不必担心如何将 XBee 连接到 Arduino。图 6-25 显示了一个来自 SparkFun 的带有 XBee 盾的 Arduino。

img/313992_2_En_6_Fig25_HTML.jpg

图 6-25

Arduino XBee 盾(SparkFun 提供)

我使用这个屏蔽来演示如何与 XBee 模块通信。如果您决定使用另一个 shield,请务必查看该 shield 的文档,查看如何使用它的示例,并将其与本项目中的代码进行比较。进行适当的修改(硬件连接和对草图的更改),以便您的项目可以正确地与您的盾一起工作。

shield 允许您选择通过数字引脚 0 和 1 与 Arduino 的板载串行电路(UART 3 )进行通信。但这些也是从 Arduino IDE 通过 USB 与 Arduino 通信时使用的引脚。幸运的是,SparkFun XBee shield 有一个小开关,允许你选择使用引脚 2 和 3 来代替。使用此选项,您可以编写一个脚本,通过 XBee 从 shield 读取数据,同时仍然连接到 Arduino IDE 并使用串行监视器。但是有一个问题:只有一个 UART 可用。您必须使用软件串行库来模拟第二个串行连接。Arduino IDE 中包含软件串行库。您可以在“软件设置”一节中看到如何操作。

Tip

如果您使用不同的 XBee 屏蔽,您应该参考屏蔽上的文档,并按照说明使用引脚。有些屏蔽是硬连线的。

如果您不想使用屏蔽,您可以像前面一样将 XBee 连接到 Arduino。在本例中,您使用 SparkFun 的 XBee 分线板来连接试验板。图 6-26 显示了将 XBee Explorer 调节分线板连接到 Arduino 的接线图。请注意,您使用的是 Arduino 的 5V 引脚。如果您使用的是非调节分线板,您应该使用 3.3V 引脚。在给项目通电之前,一定要仔细检查您使用的任何组件的最大电压。

img/313992_2_En_6_Fig26_HTML.jpg

图 6-26

通过 SparkFun XBee 分线板将 XBee 连接到 Arduino

Note

这两种方法都适用于这个项目。

无论您选择哪种方法,请将 XBee 协调器模块从 USB 适配器上取下,并将其插入 XBee shield 或 XBee Explorer 调节分线板。现在硬件已经准备好了,让我们设置您的 Arduino 环境,并编写一个草图来从 XBee 传感器节点读取数据。

软件设置

像大多数传感器一样,我们需要一个库来连接和读取数据。幸运的是,Arduino 有一个很好的 XBee 串行库。我们像安装 DHT22 库一样,打开一个新的草图,从菜单中选择草图包含库管理库… ,然后搜索 XBee,从 Andrew Rapp 安装库。图 6-27 显示了选择了正确库的库管理器。点击安装进行安装。

img/313992_2_En_6_Fig27_HTML.jpg

图 6-27

加载 XBee Arduino 库

一旦安装了库并且重启了 Arduino IDE,就可以编写脚本从 XBee 中读取数据。该库为每个流行的 XBee 数据包提供了类,用于向 XBee 发送数据或从 XBee 接收数据。这个项目使用 IO sample 类,因为您知道这是我们在这个项目中唯一感兴趣的包。

你需要创建草图的几个部分。使用 XBee 库比编写自己的通信方法更容易,但是这个库有一些设置步骤和方法,您需要使用它们来读取数据包。

首先,让我们包括 XBee 和软件串行库的库头。回想一下,软件串行库是 Arduino IDE 的一部分:

#include <XBee.h>
#include <SoftwareSerial.h>

现在,您必须定义用于与 XBee 模块通信的引脚。您将串行监视器用作输出设备,因此需要使用替代引脚。在这种情况下,使用引脚 2 和 3 进行接收和发射连接。您需要定义这些并初始化软件串行库,并使用它与 XBee 通信。下面显示了所需的定义:

uint8_t recv = 8;
uint8_t trans = 9;
SoftwareSerial soft_serial(recv, trans);

接下来,您必须实例化 XBee 库和助手类。在这种情况下,您需要 I/O 数据示例包的 helper 类:

XBee xbee = XBee();
ZBRxIoSampleResponse ioSample = ZBRxIoSampleResponse();

现在我们准备写启动代码。对于这个项目,您必须启动软件串行库,并将其传递给 XBee 库,以便在与 XBee 模块通信时使用。您还需要初始化默认的 serial 类,这样您就可以使用print()语句在代码的后面部分显示读取的数据。下面显示了完整的setup()方法:

void setup() {
  Serial.begin(9600);
  while (!Serial);   // Leonardo boards need to wait for Serial to start
  soft_serial.begin(9600);
  xbee.setSerial(soft_serial);
}

注意带有 while 循环的那一行。你需要把这个加到达芬奇的画板上。如果你忽略了这一点,并在莱昂纳多板上运行草图,XBee 可能无法工作。添加此循环以允许 Leonardo 有时间启动串行实例。

现在,让我们编写用于从数据包中读取数据的方法。稍后您将学习如何从 XBee 读取数据包。首先,让我们看看如何获得数据包的源地址。下面显示了这样做的代码:

void get_address(ZBRxIoSampleResponse *ioSample) {
  Serial.print("Received data from address: ");
  Serial.print(ioSample->getRemoteAddress64().getMsb(), HEX);
  Serial.print(ioSample->getRemoteAddress64().getLsb(), HEX);
  Serial.println("");
}

注意,您只需使用ioSample类实例并调用方法getRemoteAddress64().getMsb()。实际上,这是对一个子类(RemoteAddress64)及其方法getMsb()的调用。这将返回 64 位地址的最高有效字节(高 16 位)。对于getRemoteAddress64().getLsb()调用,您对最低有效位做同样的操作。然后打印这些值,指定要以十六进制打印它们。如果您从多个 XBee 节点读取数据,为每个地址应用一个名称会很方便,比如“卧室”或“起居室”。我把这个留给你做练习。

接下来,您想要读取数据有效负载。在这种情况下,您想要读取从 XBee 传感器节点发送到 XBee 协调器的温度数据。下面显示了执行此操作所需的代码。您可以使用前面讨论的公式将传感器读取的毫伏值转换为摄氏温度,然后再转换为华氏温度。

void get_temperature(ZBRxIoSampleResponse *ioSample) {
  float adc_data = ioSample->getAnalog(3);
  Serial.print("Temperature is ");
  float temperatureC = ((adc_data * 1200.0 / 1024.0) - 500.0) / 10.0;
  Serial.print(temperatureC);
  Serial.print("c, ");
  float temperatureF = ((temperatureC * 9.0)/5.0) + 32.0;
  Serial.print(temperatureF);
  Serial.println("f");
}

最后,您需要从数据包中读取电源电压。这种情况下,电源电压出现在数据采样之后。因为您知道只有一个数据样本(通过模拟样本掩码),所以您知道模拟电压正好出现在校验和之前。遗憾是,目前还没有从 XBee 库中的 I/O sample 包获取信息的方法。但是,并没有全部丢失,因为库的作者将数据存储在一个数组中,并提供了一个子类供您用来获取原始数据。在这种情况下,您需要数据中的字节 17(最高有效字节)和 18(最低有效字节)。你知道这些是从零开始从帧类型之后的字节计数所需的索引。详见表 6-5 。

与温度数据一样,您必须使用前面讨论的公式将读数转换为伏特。下面显示了读取、转换和显示 XBee 传感器节点的电源电压所需的代码。请注意,您将最高有效字节移动了 8 位,以便可以保留 16 字节的浮点值。

void get_supply_voltage() {
  Serial.print("Supply voltage is ");
  int ref = xbee.getResponse().getFrameData()[17] << 8;
  ref += xbee.getResponse().getFrameData()[18];
  float volts = (float(ref) * float(1200.0 / 1024.0))/1000.0;
  Serial.print(" = ");
  Serial.print(volts);
  Serial.println(" volts.");
}

花些时间检查一下计算结果。在本例中,您将 XBee 传感器节点读取和发送的电压转换为摄氏度,然后再转换为华氏度。为了便于阅读,您还将电源电压转换为伏特。测试期间,所有这些值都被发送到串行监视器进行反馈。

一旦实现了这些方法,就可以将从 XBee 读取数据的代码放在loop()方法中,调用这些方法来解密数据并将其打印到串行监视器上。

因为这个loop()方法被重复调用,所以您使用 XBee 类方法来读取包,然后确定包是否是 I/O 数据样本包。如果是,就从包中读取数据。如果不是,您添加一些简单的错误处理,以便 Arduino 可以继续读取数据而不是停止。下图显示了完成的loop()方法:

void loop() {
  xbee.readPacket();
  if (xbee.getResponse().isAvailable()) {
    if (xbee.getResponse().getApiId() == ZB_IO_SAMPLE_RESPONSE) {
      xbee.getResponse().getZBRxIoSampleResponse(ioSample);
      // Read and display data
      get_address(&ioSample);
      get_temperature(&ioSample);
      get_supply_voltage();
    }
    else {
      Serial.print("Expected I/O Sample, but got ");
      Serial.print(xbee.getResponse().getApiId(), HEX);
    }
  } else if (xbee.getResponse().isError()) {
    Serial.print("Error reading packet.  Error code: ");
    Serial.println(xbee.getResponse().getErrorCode());
  }
}

请注意,在代码中,您检查数据包是否可用;如果是的话,你读一下。如果读取的数据包是正确的帧类型,在本例中为ZB_IO_SAMPLE_RESPONSE,则从数据包中读取数据并显示。如果它不是正确的包,你把收到的包的帧类型打印到串行监视器上。如果读取数据包时出现错误,您可以在最后一个 else 中捕获该错误,并在串行监视器上显示该错误。

注意ZB_IO_SAMPLE_RESPONSE条件代码块的内容。首先用读取的数据初始化 I/O 数据样本类,然后读取发送数据包的 XBee 的地址,然后计算温度和参考电压。

到目前为止,一旦你理解了代码,创建一个新的文件,并在新的草图窗口中输入信息。清单 6-5 显示了 Arduino XBee 接收器项目的完整草图。该代码也可以在本书的源代码链接中的 Apress 站点上获得。

/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Example Arduino Receiver Node

  This project demonstrates how to receive sensor data from
  an XBee sensor node. It uses an Arduino with an XBee shield
  with an XBee coordinator installed.

  Note: This sketch was adapted from the examples in the XBee
  library created by Andrew Rapp.
*/

#include <XBee.h>
#include <SoftwareSerial.h>

// Setup pin definitions for XBee shield
uint8_t recv = 2;
uint8_t trans = 3;
SoftwareSerial soft_serial(recv, trans);

// Instantiate an instance of the XBee library
XBee xbee = XBee();

// Instantiate an instance of the IO sample class
ZBRxIoSampleResponse ioSample = ZBRxIoSampleResponse();

void setup() {
  Serial.begin(9600);
  while (!Serial);   // Leonardo boards need to wait for Serial to start
  soft_serial.begin(9600);
  xbee.setSerial(soft_serial);
  Serial.println("Hello. Welcome to the Arduino XBee Data Aggregator.");
}

// Get address and print it

void get_address(ZBRxIoSampleResponse *ioSample) {
  Serial.print("Received data from address: ");
  Serial.print(ioSample->getRemoteAddress64().getMsb(), HEX);
  Serial.print(ioSample->getRemoteAddress64().getLsb(), HEX);
  Serial.println("");
}

// Get temperature and print it
void get_temperature(ZBRxIoSampleResponse *ioSample) {
  float adc_data = ioSample->getAnalog(3);

  Serial.print("Temperature is ");
  float temperatureC = ((adc_data * 1200.0 / 1024.0) - 500.0) / 10.0;
  Serial.print(temperatureC);
  Serial.print("c, ");
  float temperatureF = ((temperatureC * 9.0)/5.0) + 32.0;
  Serial.print(temperatureF);
  Serial.println("f");
}

// Get supply voltage and print it
void get_supply_voltage() {
  Serial.print("Supply voltage is ");
  int ref = xbee.getResponse().getFrameData()[17] << 8;
  ref += xbee.getResponse().getFrameData()[18];
  float volts = (float(ref) * float(1200.0 / 1024.0))/1000.0;
  Serial.print(" = ");
  Serial.print(volts);
  Serial.println(" volts.");
}

void loop() {
  //attempt to read a packet
  xbee.readPacket();

  if (xbee.getResponse().isAvailable()) {
    // got something

    if (xbee.getResponse().getApiId() == ZB_IO_SAMPLE_RESPONSE) {

      // Get the packet
      xbee.getResponse().getZBRxIoSampleResponse(ioSample);

      // Read and display data
      get_address(&ioSample);
      get_temperature(&ioSample);
      get_supply_voltage();
    }
    else {
      Serial.print("Expected I/O Sample, but got ");
      Serial.print(xbee.getResponse().getApiId(), HEX);
    }
  } else if (xbee.getResponse().isError()) {
    Serial.print("Error reading packet.  Error code: ");
    Serial.println(xbee.getResponse().getErrorCode());
  }
}

Listing 6-5Arduino XBee Receiver

在将草图上传到 Arduino 之前,请花些时间确保它已经编译好。记住,草图一旦上传,就开始运行了。另存为xbee_sensor.ino

测试最终项目

要测试该项目,请确保首先启动 Arduino,然后启动 XBee 传感器节点。启动 Arduino,上传草图,然后打开串行监视器。当 XBee 节点被 Arduino 上的协调器接受并添加到网络中时,您应该会观察到 XBee 调节分线板上的链路灯闪烁。在大约 5 秒钟内,XBee 传感器节点开始发送数据。发生这种情况时,Arduino 草图应该开始将语句打印到您的串行监视器。清单 6-6 显示了您应该在串行监视器中看到的输出示例。

Hello. Welcome to the Arduino XBee Data Aggregator.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.83 volts.
Received data from address: 13A2004192DB79
Temperature is 11.76c, 53.16f
Supply voltage is  = 3.83 volts.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.82 volts.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.83 volts.
Received data from address: 13A2004192DB79
Temperature is 12.34c, 54.22f
Supply voltage is  = 3.83 volts.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.82 volts.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.82 volts.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.82 volts.
Received data from address: 13A2004192DB79
Temperature is 12.46c, 54.43f
Supply voltage is  = 3.82 volts.

Listing 6-6Sample Output of the XBee Arduino Sketch

你看到类似的东西了吗?如果是这样,那么您已经做了大量的工作,现在已经拥有了构建传感器节点和基于 Arduino 的传感器数据聚合器的基本组件。

如果您在串行监视器中看不到任何输出,请不要惊慌。相反,请仔细检查 Arduino 上的 XBee 是否正确插入,以及您是否使用了草图中与您正在使用的 XBee shield 如何连接到 Arduino 相对应的正确引脚(并非所有 shield 都像 SparkFun shield 一样使用引脚 2 和 3)。提示:查看你的盾的文档。

如果所有这些都是正确的,请确保在连接到 Arduino 的 XBee 上使用协调器 API 固件,在 XBee 传感器节点上使用路由器 API 固件。如果仍有问题,请返回到上一个项目,以确保传感器节点仍在工作。

您也可以尝试关闭 Arduino 和 XBee 传感器节点;然后打开 Arduino,等待大约 10 秒钟,并再次打开 XBee 传感器节点。有时握手过程和网络加入可能会停止,一段时间内什么都不会发生。按此顺序关闭再打开 XBee 可确保它会重新尝试配置。

另一方面,也许你正在获得数据,但它是不正确的——温度读数对于实际环境来说太低了。我曾经遇到过这种情况,当时我用来连接 TMP36 上数据引脚的电线被意外拔下。底线是总是检查,再检查你的线路。

为了更多乐趣

如果您想要扩展项目,您可以添加第二个 XBee 传感器节点,并修改 Arduino 草图以提供每个节点的位置。例如,您可以将一个节点标记为“办公室”,将另一个节点标记为“厨房”。草图应记录(写入串行监视器)传感器的位置以及来自 XBee 的传感器数据。

部件购物清单

完成本章中的项目需要一些组件。它们在表 6-5 中列出。

表 6-5

所需组件

|

项目

|

供应商

|

是吗?成本美元

|

所需数量

| | --- | --- | --- | --- | | 发光二极管(任何颜色) | www.sparkfun.com/products/9592 | 0.35one按钮(试验板安装)[www.sparkfun.com/products/97](http://www.sparkfun.com/products/97)0.35 | one | | 按钮(试验板安装) | [`www.sparkfun.com/products/97`](http://www.sparkfun.com/products/97) | 0.35 | one | | 试验板(非迷你) | www.sparkfun.com/products/9567 | 5.95one试验板跳线[www.sparkfun.com/products/8431](http://www.sparkfun.com/products/8431)5.95 | one | | 试验板跳线 | [`www.sparkfun.com/products/8431`](http://www.sparkfun.com/products/8431) | 3.95 | one | | www.adafruit.com/product/758 | | DHT22 | www.sparkfun.com/products/10167 | 9.95one[www.adafruit.com/products/385](http://www.adafruit.com/products/385)150欧姆电阻器大多数在线和零售商店变化one4.7K欧姆电阻器大多数在线和零售商店变化one10K欧姆电阻器大多数在线和零售商店变化oneArduinoXBee神盾局[www.sparkfun.com/products/10854](http://www.sparkfun.com/products/10854)9.95 | one | | [`www.adafruit.com/products/385`](http://www.adafruit.com/products/385) | | 150 欧姆电阻器 | 大多数在线和零售商店 | 变化 | one | | 4.7K 欧姆电阻器 | 大多数在线和零售商店 | 变化 | one | | 10K 欧姆电阻器 | 大多数在线和零售商店 | 变化 | one | | Arduino XBee 神盾局 | [`www.sparkfun.com/products/10854`](http://www.sparkfun.com/products/10854) | 24.95 | one | | XBee-ZB (ZB)系列 2、2.5 或 3 | www.sparkfun.com | 25.00+Two[www.adafruit.com](http://www.adafruit.com)[www.makershed.com](http://www.makershed.com)TMP36传感器[www.sparkfun.com/products/10988](http://www.sparkfun.com/products/10988)25.00+ | Two | | [`www.adafruit.com`](http://www.adafruit.com) | | [`www.makershed.com`](http://www.makershed.com) | | TMP36 传感器 | [`www.sparkfun.com/products/10988`](http://www.sparkfun.com/products/10988) | 1.50 | one | | www.adafruit.com/products/165 | | 试验板电源 | www.sparkfun.com/products/10804 | 14.95one壁式电源(6V12V)[www.sparkfun.com/products/15314](http://www.sparkfun.com/products/15314)14.95 | one | | 壁式电源(6V–12V) | [`www.sparkfun.com/products/15314`](http://www.sparkfun.com/products/15314) | 5.95 | one | | 0.10 毫伏电容 | www.sparkfun.com/products/8375 | 0.25oneXBeeExplorer由集管调节[www.sparkfun.com/products/11373](http://www.sparkfun.com/products/11373)0.25 | one | | XBee Explorer 由集管调节 | [`www.sparkfun.com/products/11373`](http://www.sparkfun.com/products/11373) | 9.95 | one | | 分离式公接头(可选) | www.adafruit.com/products/392 | 4.95oneArduinoUno(任何支持盾牌的)各种各样的25美元及以上one斯帕克芬XBee[www.sparkfun.com/products/10854](http://www.sparkfun.com/products/10854)4.95 | one | | Arduino Uno(任何支持盾牌的) | 各种各样的 | 25 美元及以上 | one | | 斯帕克芬 XBee 盾 | [`www.sparkfun.com/products/10854`](http://www.sparkfun.com/products/10854) | 24.95 | one | | 烙铁和焊料(可选) | 大多数在线和零售商店 | 变化 | one |

摘要

这一章涵盖了很多内容。您探索了 Arduino 平台,包括许多可用的表单以及如何编写草图(程序)来控制 Arduino。我还向您展示了如何使用温度和湿度传感器在 Arduino 中托管传感器。

您应用了在第 2 和 4 章中学到的关于 XBee 的信息,创建了一个 XBee 传感器节点来读取温度数据。然后,使用 XBee 协调器设置一个 Arduino,从 XBee 传感器节点接收传感器数据,并将其显示在串行监视器中。

在下一章中,您将发现各种用于存储传感器数据的机制,无论是板载的还是云中的。

Footnotes 1

https://en.wikipedia.org/wiki/Pulse-width_modulation

  2

至少,我还没有发现有人成功做到了这一点。

  3

http://en.wikipedia.org/wiki/Universal_asynchronous_receiver/transmitter

 

七、存储传感器数据的方法

如果你已经成功完成了本书中的项目,那么你已经有了几种形式的传感器和数据聚合节点。本质上,您已经具备了构建传感器网络来监控和记录温度数据的基本构建模块。为湿度或气压等其他环境传感器添加节点并不需要太多工作。事实上,您构建的基本传感器节点可以托管各种传感器。

如果您已经运行了示例项目并尝试了这些挑战,毫无疑问,您已经注意到生成了大量数据。你用这些数据做什么?它是仅在生成时有意义,还是您认为更有可能希望存储数据并在以后检查它?例如,如果你想知道你的车间全年每月的温度范围,逻辑上你需要一整年的数据 1 来制表和平均。

Arduino 主板一般没有内置存储设备(但一些专门的变体有)。Raspberry Pi 板配有一个安全数字(SD)驱动器,可以接受基于 USB 的存储设备来存储数据,但如何处理来自基于 Arduino 的节点的数据呢?

本章分析了可用的存储方法,并举例说明了如何使用这些方法存储数据。提供了示例项目来说明机制和代码,但是为了简洁起见,我省略了特定于传感器的代码。

存储方法

传感器数据可以有几种形式。传感器可以产生由浮点数或整数组成的数字数据。一些传感器产生更复杂的信息,这些信息被组合在一起,可能包含多种形式的数据。知道如何解释读取的值通常是使用传感器最困难的部分。事实上,您可以在许多传感器节点示例中看到这一点。例如,温度传感器产生的值必须转换成刻度才有意义。

虽然可以将所有数据存储为文本,但如果您希望在另一个应用程序中使用数据,或者在电子表格或统计应用程序中使用数据,您可能需要考虑以二进制形式或易于转换的文本形式存储数据。例如,大多数电子表格应用程序可以轻松地将文本字符串(如"123.45)转换为浮点型,但它们可能无法将"12E236"转换为浮点型。另一方面,如果您计划为 Arduino 草图或 Raspberry Pi Python 脚本编写额外的代码来处理数据,您可能希望以二进制形式存储数据,以避免必须编写成本高昂(并且可能很慢)的转换例程。

但这只是问题的一部分。你在哪里存储数据是一个更大的问题。您希望以您需要的形式存储数据,但也希望将数据存储在一个位置(在设备上),您可以从该位置检索数据,并且在主机重新启动时不会被擦除。例如,将数据存储在 Arduino 的主内存中并不是一个好主意。它不仅消耗了宝贵的程序空间,而且是易失性的,并且在 Arduino 断电时会丢失。

树莓派提供了更好的选择。您可以轻松地创建一个文件,并将数据存储在根分区或 SD 卡上的主目录中。这是非易失性的,不会影响 Raspberry Pi 操作系统的运行。唯一的缺点是,如果数据显著增长,它可能会导致磁盘空间过少。但是在威胁到操作系统的稳定性之前,数据必须增长到近 2GB(对于 2GB 的 SD 卡而言)(尽管这是可能发生的)。

那么,你有哪些用 Arduino 存储数据的选择呢?树莓派还有其他的可能性吗?有两种类型的存储需要考虑:本地和远程。本地存储包括导致数据与节点一起存储的任何方法,例如,将数据存储在 Raspberry Pi 上的 SD 卡上。远程存储包括将数据存储在不直接连接到节点的设备或介质上的任何方法,例如,将数据存储在不同的节点上,甚至存储在连接到互联网的服务器上。

Storing Date And Time With Samples

Arduino 和 Raspberry Pi 都没有内置实时时钟(RTC)。如果您想在本地存储传感器数据,您必须存储带有大致日期和时间戳的数据,或者使用 RTC 模块读取准确的日期/时间值。

幸运的是,有 RTC 模块可以与 Arduino 或 Raspberry Pi 一起使用。如果您的 Raspberry Pi 连接到互联网,并且您已启用网络时间同步功能,则不需要 RTC 模块。但是,如果您的 Raspberry Pi 没有连接到互联网,并且您希望存储准确的时间数据,您应该考虑使用 RTC 模块。

以下部分介绍了 Arduino 和 Raspberry Pi 可用的各种本地和远程存储选项。

Arduino 的本地存储选项

虽然 Arduino 确实没有板载存储设备,但有两种方法可以在本地为 Arduino 存储数据。您可以将数据存储在一种特殊形式的非易失性存储器中,或者通过特殊的 SD 卡盾或以太网盾(大多数以太网盾都有内置的 SD 卡驱动器)托管在 SD 卡上。

如果你真的很有创造力(或者无法抗拒挑战),你可以使用一些通信协议向其他设备发送数据。例如,您可以使用串行接口将数据写入串行设备。

以下部分将更详细地讨论每个选项。后面的小节介绍了一些小项目,您可以用它们来学习如何使用这些设备来存储数据。

非易失存储器

Arduino 最常见的非易失性存储器是电可擦除可编程只读存储器(EEPROM,读作“e-e-prom”或“double-e prom”)。EEPROMs 被封装成芯片(集成电路)。顾名思义,数据可以写入芯片,即使上电后也是可读的,但可以被擦除或覆盖。

大多数 Arduino 板都有一个小 EEPROM,用于存储草图,并在上电时读取。如果你曾经想知道 Arduino 是如何做到这一点的,现在你知道了。如果愿意,您可以写入该内存中未使用的部分,但是可用的内存量很小(某些主板为 512KB)。您也可以使用 EEPROM,并通过 I2C 协议将其直接连接到 Arduino,以克服这一限制。

Arduino IDE 中包含一个特殊的库,支持对 EEPROM 的读写。由于可用存储器的数量有限,将数据存储在 EEPROM 存储器中对于大多数传感器节点来说并不理想。如果存储的数据很大或者每个样本有许多数据项,则可能会超出可用的内存。

您还会遇到从 EEPROM 获取数据以用于其它应用的问题。在这种情况下,您不仅要构建写入数据的方法,还要构建读取数据并将其导出到其他介质(本地或远程)的方法。

这并不是说你不应该使用 EEPROM 来存储数据。几个可能的原因证明了在 EEPROM 中存储数据的合理性。例如,如果您的传感器节点可能被隔离,或者与其他节点的连接受到限制,您可能希望在节点离线时使用 EEPROM 临时存储数据。事实上,您可以构建草图来检测节点何时离线,并在那时切换到 EEPROM。这样,基于 Arduino 的传感器节点可以继续记录传感器数据。一旦节点重新联机,您可以编写草图将 EEPROM 的内容转储到另一个节点(远程存储)。

sdcard

您还可以在 SD 卡上存储(和检索)数据。Arduino IDE 有一个用于与 SD 驱动器交互的库。在这种情况下,您可以使用库通过 SD shield 或以太网 shield 访问 SD 驱动器。

在 SD 卡上存储数据是通过文件完成的。您打开一个文件,以最适合下一阶段数据分析的格式将数据写入其中。Arduino IDE 和其他地方的示例演示了如何为 Arduino 创建 web 服务器界面,以显示 SD 卡上可用文件的列表。

与 EEPROMs 相比,SD 卡存储的数据要多很多倍。可以购买超过 128GB 存储空间的高密度 SD 卡。这是大量的传感器数据!

在传感器节点被设计为不与其他节点连接的远程传感器的情况下,您可以选择将数据存储到 SD 卡,或者在传感器节点断开连接或数据聚合器节点关闭的情况下,您可以将它用作备份日志记录设备。由于该卡可在其他设备上移动和读取,当您想要使用数据时,可以在另一个设备上读取它。

使用 SD 卡意味着您可以将数据从传感器节点移动到计算机,只需从 Arduino 中拔出 SD 卡,然后将其插入计算机中的 SD 读卡器即可。

项目:在非易失性存储器中保存数据

回想一下,您可以使用 Arduino 上的本地 EEPROM 来存储数据。Arduino IDE 中有一些优秀的例子,我鼓励您在闲暇时尝试一下。它们位于 EEPROM 子菜单下的示例菜单下。您只需要一台 Arduino 和一台笔记本电脑,就可以在 Arduino 上尝试读写 EEPROM。

本节不再重复使用内置 EEPROM 的示例草图,而是概述一个使用外部 EEPROM 存储数据的项目。与使用专用库进行交互的本地 EEPROM 不同,外部 EEPROM 使用 I2C 通信协议。

硬件设置

这个项目的硬件包括一个 24LC256 或 24LC512 EEPROM 芯片,如 SparkFun ( www.sparkfun.com/products/525 )、一个按钮、跳线和一个 Arduino。图 7-1 显示了一个典型的 24LC256 引脚安装 EEPROM 芯片。

img/313992_2_En_7_Fig1_HTML.jpg

图 7-1

I2C EEPROM 芯片(SparkFun 提供)

该按钮将允许您重置芯片上的存储器。这样做会擦除存储的数据值,重新设置内存配置以备再次使用。当第一次使用草图、调试问题以及在存储器被读取并存储在另一个介质上后重用芯片时,您会发现该功能特别方便。

该芯片通过 I2C 总线进行通信。如图 7-2 所示,您可以通过将接地或电源连接到引脚 A0–A2 来设置芯片的地址。您可以将此视为一个二进制数,其中将地连接到所有三个引脚是可用的最低地址(0x50),将电源连接到所有三个引脚是可用的最高地址(0x57)。表 7-1 显示了所需的可能地址和连接。通过将地连接到所有三个引脚,可以使用最低地址(0x50)。

表 7-1

设置 I2C EEPROM 的地址

|

地址

|

A0

|

一流的

|

主动脉第二声

| | --- | --- | --- | --- | | 0x50 | 地面 | 地面 | 地面 | | 0x51 | 地面 | 地面 | +5V | | 0x52 | 地面 | +5V | 地面 | | 0x53 | 地面 | +5V | +5V | | 0x54 | +5V | 地面 | 地面 | | 0x55 | +5V | 地面 | +5V | | 0x56 | +5V | +5V | 地面 | | 0x57 | +5V | +5V | +5V |

img/313992_2_En_7_Fig2_HTML.jpg

图 7-2

I2C EEPROM 的引脚排列

现在您已经了解了如何寻址芯片,让我们将它连接到您的 Arduino。首先将芯片放在试验板上,半圆指向左侧。这将引脚 1 设置为右上引脚。将接地线连接到芯片顶部的所有四个引脚。这些是引脚 1–4,如图 7-2 所示。

接下来,将引脚 5 (SDA)连接到 Arduino 上的引脚 4,将引脚 6 (SCL)连接到 Arduino 上的引脚 5。将接地线连接到针脚 7。然后将正电压(+5V)连接到针脚 8。我们还在 I2C 线上使用 4.7K 欧姆的电阻来降低噪声。最后,将按钮一端连接到引脚 2,另一端接通电源。使用 10K 欧姆电阻器将按钮拉高(将其连接到正电压),就像您在之前的项目中所做的那样。详细接线图见图 7-3 。一定要仔细检查你的连接。

img/313992_2_En_7_Fig3_HTML.jpg

图 7-3

将 EEPROM 连接到 Arduino

Tip

如果您使用的是莱昂纳多板,您需要使用 USB 端口附近的 SDC 和 SCL 引脚。对于 Uno 板,它们位于 A4 和 A5,在 Mega 2560 上,它们位于 20 号和 21 号引脚。检查主板的硬件引脚排列,确保使用正确的 I2C 接口连接。

软件设置

布线就绪后,您就可以开始写草图来读写数据了。在本例中,您不是编写一个脚本来简单地存储数据,而是编写一个草图来允许您将数据写入芯片和从芯片中读取数据。您还包括一个复位操作,允许您覆盖任何内存。

添加读取方法,以便您可以创建额外的草图来读取数据,如果您希望查看数据,将芯片(数据)移动到另一个 Arduino,或者使用另一个草图来处理数据。

我们开始吧。您使用 I2C 库(称为Wire)与 EEPROM 交互。打开新草图,并输入以下内容:

#include <Wire.h>

#define FIRST_SAMPLE 0x02  // First position of first sample
#define MEM_ADDR 0x50      // EEPROM address
#define BUTTON_PIN 0x02    // Button pin
#define EEPROM_SIZE 32768  // Size of 24LC256
#define SAMPLE_BYTES 2     // Size of sample in bytes

int next_index = 0;        // Address of first sample

这些语句包括导线库,并定义了一些在草图中使用的常数。请注意,您有第一个样本的地址(芯片内存中的位置)、芯片地址、按钮引脚、最大尺寸(256 芯片)以及每个样本的字节数。

你需要很多方法。您需要将单个字节写入内存、存储样本、读取字节和读取样本的能力。让我们看看这些方法的最简单的形式——读字节方法。在下面的代码中,address 指的是 EEPROM 芯片的地址,index 指的是要访问的内存位置:

byte read_byte(int address, unsigned int index)  {
  byte data = 0xFF;

  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();

  Wire.requestFrom(address,1);

  if (Wire.available()) {
    data = Wire.read();
  }
  return data;
}

请注意与芯片通信的过程。首先,你用芯片开始一次传输,发送你想要读取的地址,然后结束传输。地址是一个两字节的值,这些语句向您展示了如何操作这些字节来形成一个字(两个字节)。下一个方法requestFrom(),告诉芯片你想读取一个字节。如果芯片准备好了,你就可以读取数据。最后,将值返回给调用者。

您可以对芯片的每个操作使用相同的格式。让我们看看将单个字节写入芯片的写入方法:

void write_byte(int address, unsigned int index, byte data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.write(data);
  Wire.endTransmission();

  delay(5);
}

请注意,您有相同的设置—您开始传输并在指定的索引处设置值。不同的是,您在结束传输之前发送数据(写入数据)。

但是你怎么知道什么被写到哪个地址(或者索引)?让我们使用索引 0 处的第一个字节来存储数据样本(或行)的数量,使用第二个字节来存储每个样本消耗的字节(或列)的数量,而不是随意地或以某种不合逻辑的顺序写入数据。这样,您可以使数据更容易阅读,因为它是统一的,并且在重新启动时更容易管理。

实际上,让我们添加一个名为sample_data()的新方法来写入一些数据,并在启动时显示 EEPROM 中数据的内容。回想一下 Arduino,如果您想在启动时执行一次方法,您可以将它放在setup()方法中。下面显示了如何使用现有的读取方法从 EEPROM 读取数据,并在串行监视器中显示信息:

void sample_data(void) {
  int bytes_per_sample = SAMPLE_BYTES;
  byte buffer[SAMPLE_BYTES];

  next_index = read_byte(MEM_ADDR, 0);
  bytes_per_sample = read_byte(MEM_ADDR, 1);
  Serial.print("Byte pointer: ");
  Serial.println(next_index, DEC);
  Serial.print("Bytes per sample: ");
  Serial.println(bytes_per_sample, DEC);
  Serial.print("Number of samples:");
  Serial.println((next_index/bytes_per_sample)-1, DEC);

  // Add some sample data
  record_sample(MEM_ADDR, 6011);
  record_sample(MEM_ADDR, 8088);

  // Example of how to read sample data - read last 2 values
  read_sample(MEM_ADDR, next_index-(SAMPLE_BYTES * 2), buffer);
  Serial.print("First value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
  read_sample(MEM_ADDR, next_index-SAMPLE_BYTES, buffer);
  Serial.print("Second value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
}

这种技术使得通过在启动时运行 dump 方法来验证代码正在工作变得容易,如下所示。本质上,您创建了一个简单的自我诊断机制,可以用来检查数据的状态。如果您在启动时看到有效数据之外的任何东西,您就知道出了问题:

void setup(void) {
  Serial.begin(115200);
  while (!Serial);
  Wire.begin();
  Serial.println("Welcome to the Arduino external EEPROM project.");
  initialize(MEM_ADDR);
  sample_data();
}

但是等等!如果遇到未初始化的 EEPROM,这段代码会做什么?在这种情况下,您可以创建一种特殊的方法来初始化 EEPROM。下面的代码显示了initialize()方法:

void initialize(int address) {
  // Clear memory
  // NOTE: replace '10' with EEPROM_SIZE to erase all data
  for (int i = 0; i < 10; i++) {
    write_byte(address, i, 0xFF);
  }
  write_byte(address, 0, FIRST_SAMPLE);
  write_byte(address, 1, SAMPLE_BYTES);
  Serial.print("EEPROM at address 0x");
  Serial.print(address, HEX);
  Serial.println(" has been initialized.");
}

使用write_byte()方法为字节数写 0,为每个样本的字节数写前面定义的常数。该方法首先将 0xff 写入前 10 个字节,以确保没有存储任何数据;然后将字节数写入索引 0,将每个样本的字节数写入索引 1。您添加了一些反馈的打印报表。

但是这个方法是如何被调用的呢?一种方法是将它放在 setup()方法中,作为初始化 Wire 库的调用之后的第一个调用,但这意味着您必须注释掉其他方法,加载草图,执行它,删除该方法,然后重新加载。那似乎是许多额外的工作。一个更好的方法是用按钮触发这个方法。完成这项工作的代码放在loop()方法中,如下所示:

if (digitalRead(BUTTON_PIN) == LOW) {
  initialize(MEM_ADDR);
  delay(500); // debounce
}

现在,您可以读写一个字节并初始化芯片,您还需要能够读取一个样本,以防您想在另一个草图中使用芯片来处理数据。以下代码显示了读取样本的方法:

void read_sample(int address, unsigned int index, byte *buffer) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();
  Wire.requestFrom(address, SAMPLE_BYTES);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    if (Wire.available()) {
      buffer[i] = Wire.read();
    }
  }
}

请注意,您形成了一个类似于read_byte()的事件序列。但是,不是读取一个字节,而是使用一个循环来读取样本的字节数。您还需要一种将样本存储(写入)到芯片的方法:

void write_sample(int address, unsigned int index, byte *data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Serial.print("START: ");
  Serial.println(index);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    Wire.write(data[i]);
  }
  Wire.endTransmission();
  delay(5); // wait for chip to write data
}

同样,该方法类似于write_byte()方法,但是您使用一个循环来写入样本的字节。请注意,您包含了一个 debug 语句来显示所使用的起始索引。这样做是为了在多次运行草图时可以看到值的增加。

Note

您可能已经注意到,我在*_ byte()和*_sample()方法中复制了代码。我这样做是为了代码的清晰,但这并不是绝对必要的。例如,如果您更改了*_sample()方法,使用一个额外的参数来指示读/写多少字节,那么您可以合并代码。我把这个优化留给你作为练习。

还有一个方法可以考虑。回想一下,您使用存储在索引 0 中的计数器来记录写入的样本数。write_sample()方法只是在特定的索引处写一个样本。您需要的是一个管理样本计数器和存储样本的方法。因此,您创建了一个record_sample()方法来处理更高级别的操作:

void record_sample(int address, int data) {
  byte sample[SAMPLE_BYTES];
  sample[0] = data >> 8;
  sample[1] = (byte)data;
  write_sample(address, next_index, sample);
  next_index += SAMPLE_BYTES;
  write_byte(address, 0, next_index);
}

请注意您是如何跟踪样本数量和下一个样本的下一个索引的。您使用之前创建的变量,并按照示例中的字节数对其进行递增。这样,您总是知道下一个地址是什么,而无需先读取样本数并计算索引。最后一个方法更新样本数值。

现在你已经有了所有的构建模块,清单 7-1 显示了这个草图的完整代码。将草图另存为external_eeprom.ino。请注意,在草图中,您没有包括任何要从传感器读取的代码。为了简洁起见,我省略了这一点,而是在setup()方法中包含了一些调试语句(以粗体显示),以展示如何记录样本。在修改草图以供传感器使用时,请务必删除这些陈述。

/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Example Arduino External EEPROM data store

  This project demonstrates how to save and retrieve sensor data
  to/from an external EEPROM chip.
*/

#include <Wire.h>

#define FIRST_SAMPLE 0x02 // First position of fist sample
#define MEM_ADDR 0x50     // EEPROM address
#define BUTTON_PIN 0x02   // Button pin
#define EEPROM_SIZE 32768 // Size of 24LC256
#define SAMPLE_BYTES 2    // Size of sample in bytes

int next_index = 0;       // Address of first sample

/* Initialize the chip erasing data */
void initialize(int address) {
  // Clear memory
  // NOTE: replace '100' with EEPROM_SIZE to erase all data
  for (int i = 0; i < 100; i++) {
    write_byte(address, i, 0x00);
  }
  write_byte(address, 0, FIRST_SAMPLE);
  write_byte(address, 1, SAMPLE_BYTES);
  Serial.print("EEPROM at address 0x");
  Serial.print(address, HEX);
  Serial.println(" has been initialized.");
}

/* Write a sample to the chip. */
void write_sample(int address, unsigned int index, byte *data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Serial.print("START: ");
  Serial.println(index);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    Wire.write(data[i]);
  }
  Wire.endTransmission();

  delay(5); // wait for chip to write data
}

/* Write a byte to the chip at specific index (offset). */
void write_byte(int address, unsigned int index, byte data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.write(data);
  Wire.endTransmission();

  delay(5);
}

/* Read a sample from an index (offset). */
void read_sample(int address, unsigned int index, byte *buffer) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();

  Wire.requestFrom(address, SAMPLE_BYTES);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    if (Wire.available()) {
      buffer[i] = Wire.read();
    }
  }
}

/* Read a byte from an index (offset). */
byte read_byte(int address, unsigned int index)  {
  byte data = 0xFF;

  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();

  Wire.requestFrom(address,1);

  if (Wire.available()) {
    data = Wire.read();
  }
  return data;
}

/* Save a sample to the data chip and increment next address counter. */
void record_sample(int address, int data) {
  byte sample[SAMPLE_BYTES];

  sample[0] = data >> 8;
  sample[1] = (byte)data;
  write_sample(address, next_index, sample);
  next_index += SAMPLE_BYTES;
  write_byte(address, 0, next_index);
}

/* Example write data sample */
void sample_data(void) {
  int bytes_per_sample = SAMPLE_BYTES;
  byte buffer[SAMPLE_BYTES];

  next_index = read_byte(MEM_ADDR, 0);
  bytes_per_sample = read_byte(MEM_ADDR, 1);
  Serial.print("Byte pointer: ");
  Serial.println(next_index, DEC);
  Serial.print("Bytes per sample: ");
  Serial.println(bytes_per_sample, DEC);
  Serial.print("Number of samples:");
  Serial.println((next_index/bytes_per_sample)-1, DEC);

  // Add some sample data
  record_sample(MEM_ADDR, 6011);
  record_sample(MEM_ADDR, 8088);

  // Example of how to read sample data - read last 2 values
  read_sample(MEM_ADDR, next_index-(SAMPLE_BYTES * 2), buffer);
  Serial.print("First value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
  read_sample(MEM_ADDR, next_index-SAMPLE_BYTES, buffer);
  Serial.print("Second value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
}

void setup(void) {
  Serial.begin(115200);
  while (!Serial);
  Wire.begin();
  Serial.println("Welcome to the Arduino external EEPROM project.");
  initialize(MEM_ADDR);
  sample_data();
}

void loop() {
  delay(2000);
  if (digitalRead(BUTTON_PIN) == LOW) {
    initialize(MEM_ADDR);
    delay(500); // debounce
  }
  //
  // Read sensor data and record sample here
  //
  sample_data();
}

Listing 7-1Storing and Retrieving Data on an External EEPROM

请注意,您包括了一些通过串行监视器传达草图进度的附加语句。花一些时间来检查这些,这样你就可以熟悉草图运行时会发生什么。

Tip

如果你想写保护芯片,断开 WP 引脚。这样做使芯片成为只读的。

测试草图

要测试草图,请确保代码可以编译,并且您的硬件设置正确。当你有了一个可以编译的草图,把它上传到你的 Arduino 上,然后启动一个串口监视器。

第一次加载草图时,需要按下按钮来初始化 EEPROM。这是因为芯片上的值对于新芯片来说是未初始化的。您只需在第一次运行草图时执行此操作。完成后,您应该会看到类似于清单 7-2 中的输出。

Welcome to the Arduino external EEPROM project.
EEPROM at address 0x50 has been initialized.
Byte pointer: 2
Bytes per sample: 2
Number of samples: 0
START: 2
START: 4
First value: 6011
Second value: 8088
Byte pointer: 6
Bytes per sample: 2
Number of samples: 2
START: 6
START: 8
First value: 6011
Second value: 8088
Byte pointer: 10
Bytes per sample: 2
Number of samples: 4
START: 10
START: 12
First value: 6011
Second value: 8088
EEPROM at address 0x50 has been initialized.
Byte pointer: 2
Bytes per sample: 2
Number of samples: 0
START: 2
START: 4
First value: 6011
Second value: 8088
Byte pointer: 6
Bytes per sample: 2
Number of samples: 2
START: 6
START: 8
First value: 6011
Second value: 8088

Listing 7-2Serial Monitor Output for EEPROM Example

你看到类似的东西了吗?如果再次运行草图(例如,按下重置按钮),您应该看到开始索引值(从write_sample()方法)增加。去试一试吧。

当你在非易失性存储器中保存数据几次后,按下按钮,注意会发生什么。正如您在清单 7-2 中看到的,开始索引被重置,接下来的样本被存储在内存的开始处。

为了更多乐趣

这个项目的草图很有前途。毫无疑问,您可以用这段代码做很多事情。以下是改进代码和尝试使用外部 EEPROM 的一些建议:

  • 添加一些在嵌入式项目中使用的视觉帮助(没有串行监视器功能的情况)。您可以添加一个 LED,当芯片上有数据时它就会发光。您还可以添加一组七段 led 来显示存储的数据样本数。

  • 改进代码以便重用。首先删除前面在 read 和 write 方法中描述的冗余,然后将代码移到一个类中,以便更容易在其他草图中使用 EEPROM。

  • 添加第二个 EEPROM 芯片以扩展可用存储量。提示:您需要将每个芯片设置为不同的地址,但使用的方法是相同的。

  • 也许更简单、更符合 Arduino 硬件入侵原理的方法是将 EEPROM 转移到另一个 Arduino 并读取所有存储的值。这证明了 EEPROM 芯片的非易失性。

Caution

使用适当的接地来避免静电放电(ESD)对芯片的损坏。

项目:将数据写入 SD 卡

除了 EEPROM 芯片,您还可以通过将数据写入 SD 驱动器,在 Arduino 上本地存储数据。SD 驱动器是存储数据的好选择,因为数据存储在文件中,其他设备可以读取(和写入)。

例如,虽然将数据写入 EEPROM 芯片并不困难,但在个人电脑上读取该芯片需要为 Arduino 编写一个草图来传输数据。但是,SD 卡可以从 Arduino 中取出(一旦文件关闭),并插入连接到个人电脑的 SD 驱动器中,这样您就可以直接读取文件。因此,SD 卡是传感器网络的更好选择,在传感器网络中,传感器节点不通过网络或其他无线连接进行连接。

为 Arduino 添加 SD 读卡器有多种选择。最受欢迎的两个是来自 SparkFun ( www.sparkfun.com/categories/240 )的 Arduino Ethernet shield 和 microSD shield。如果您使用 Arduino Ethernet shield,您可以同时使用网络功能和 SD 卡。许多类似的设备可以从不同的供应商处获得。

Adafruit 还为 Arduino 提供了一个数据记录屏蔽,内置 SD 驱动器( www.adafruit.com/product/1141 )。数据记录屏蔽还包括一个 RTC,可以存储样品的日期和时间。我将讨论在下一个项目中使用 RTC。

Tip

microSD shield 和 Data Logging shield 都提供了一个原型制作区域,您可以使用它来安装传感器组件甚至 XBee 模块。

SD 驱动器允许您创建一个混合节点,在本地存储数据,并将其传输到网络中的另一个节点。这种冗余是您可以在传感器网络中构建耐用性的方法之一。例如,如果一个节点失去了通过网络与另一个节点的连接,它仍然可以在本地记录其数据。尽管这是一个手动恢复数据的过程(您必须去拿 SD 卡),但数据完全可以恢复的事实意味着网络可以在网络故障中生存而不会丢失数据。

可以使用 EEPROM 作为本地存储备份选项,但 EEPROM 更难使用,不如 SD 卡耐用,不具有相同的存储容量,也不容易在其他设备中使用。

关于构建耐用的传感器节点,还有一件非常重要的事情需要考虑。如果您不知道数据是何时存储的,拥有数据的本地备份可能没有帮助。除了有限的精确周期时间,Arduino 没有任何计时功能。因此,如果您在本地存储数据,而没有可以与其他数据关联的任何类型的时间戳,则所获取的样本可能没有超出序列本身(值的顺序)的意义。

为了减轻这种情况,您可以向 Arduino 添加一个 RTC 模块。RTC 允许您存储取样的日期和时间。如果您试图绘制一段时间内的值,或者想知道虚假或有趣的事件何时发生,此信息可能非常重要。

硬件设置

这个项目的硬件使用 Arduino Ethernet shield、SparkFun 的 microSD shield(安装了 SD 卡)或 Adafruit 的数据记录 shield。为了简单起见,我使用了 Arduino 以太网屏蔽,并展示了使用 microSD 屏蔽或数据记录屏蔽所需的代码更改(通过#define语句)。

您还需要 RTC 模块。Adafruit 有一款出色的产品,性能非常好,包括一个板载电池,即使在 Arduino 断电时也能为时钟供电。Adafruit 的 DS1307 实时时钟分线板套件( www.adafruit.com/product/3296 )是添加到您的项目中的优秀模块。图 7-4 显示了 Adafruit RTC 模块。

img/313992_2_En_7_Fig4_HTML.jpg

图 7-4

DS1307 实时时钟分线板(Adafruit 提供)

SparkFun 还有一个名为实时时钟模块( www.sparkfun.com/products/99 )的产品,它使用与 Adafruit 产品相同的 DS1307 芯片和接口。你可以在这个项目中使用任何一个。

Note

Adafruit RTC 模块需要装配。SparkFun 的 RTC 模块没有。

RTC 模块使用易于连接到 Arduino 的 I2C 接口。只需将 5V 电源连接到 5V 引脚,将地连接到 GND 引脚,将 SDA 引脚连接到 Arduino 的引脚 4,将 SCL 引脚连接到 Arduino 的引脚 5。图 7-5 显示了连接 RTC 模块的接线图。

img/313992_2_En_7_Fig5_HTML.jpg

图 7-5

带以太网屏蔽和 RTC 模块的 Arduino

请注意,以太网屏蔽安装在 Arduino 上。如果您使用的是 SparkFun microSD shield,接线也是一样的。

对于这个项目,我们将使用 Adafruit 数据记录屏蔽和 Arduino Uno Wi-Fi,以尽量减少布线。事实上,你所需要做的就是把盾牌插到你的 Uno 上,然后你就可以出发了!图 7-6 显示了数据记录屏蔽的样子。

img/313992_2_En_7_Fig6_HTML.jpg

图 7-6

Adafruit 数据记录屏蔽(Adafruit 提供)

Note

如果您没有数据记录屏蔽,您可以使用前面描述的 RTC 模块。

软件设置

有了数据记录屏蔽,您就可以开始写草图,将数据写入 SD 卡。打开主板电源之前,请务必插入已格式化的 SD 卡。但是首先,你必须从 Adafruit ( https://github.com/adafruit/RTClib )下载并安装 RTC 库。

回想一下,要安装一个库,我们打开草图包含库库管理器,一旦它加载了所有的头文件,键入 RTCLib 并从 Adafruit 中选择库,然后单击安装来安装它。图 7-7 显示了用于本项目的库。

img/313992_2_En_7_Fig7_HTML.jpg

图 7-7

在库管理器中安装 Adafruit RTCLib

一旦下载并安装了这个库(并且重启了 Arduino IDE),就可以开始一个名为sd_file_example.ino的新草图了。输入以下代码,指定您需要在草图中使用的模块。您需要 Wire、RTC、SD 和 String 库:

#include <Wire.h>
#include "RTClib.h"
#include <SD.h>
#include <String.h>

接下来,您需要定义用于与 SD 驱动器通信的 pin。以下是前面描述的所有三个 SD 驱动器选项的定义。我在这个例子中使用了以太网屏蔽;但是如果您没有使用以太网屏蔽,您可以注释掉该行,并取消注释掉与您正在使用的屏蔽相对应的行。还包括用于存储样本的文件名的定义。请注意,在使用 FAT 格式的 microSD 时,我们必须使用 8.3 文件名格式。 2

// Pin assignment for Arduino Ethernet shield
// #define SD_PIN 4
// Pin assignment for SparkFun microSD shield
//#define SD_PIN 8
// Pin assignment for Adafruit Data Logging shield
#define SD_PIN 10
// Sensor data file - require 8.3 file name
#define SENSOR_DATA "sensdata.txt"

现在你声明一些变量。您需要一个用于 RTC 模块,一个用于您在 SD 驱动器上使用的文件。请注意,我使用了 RTC_PCF8523 模块,因为数据记录屏蔽具有该 RTC 模块。确保使用与您的 RTC 芯片匹配的 RTC 模块。

RTC_PCF8523 rtc;
File sensor_data;

准备工作完成后,您需要一种将传感器样本保存到 SD 卡的方法。该方法必须从 RTC 模块读取日期和时间,接受样本作为参数,并存储数据。在本例中,首先放置日期和时间,然后放置样本值。将此方法命名为record_sample()

使用 RTC 库可以轻松读取 RTC 模块。您只需使用这个库,通过 now()方法获取当前日期和时间。从那里,您可以调用方法来获取月、日、年、小时等等。可以通过多种方式形成要写入文件的字符串。我使用 string 类来构造字符串。请随意使用您喜欢的任何其他方法:

// Capture the date and time
DateTime now = rtc.now();

写入文件非常容易。您只需以写模式(FILE_WRITE)打开文件,这将自动允许任何写操作被写到文件的末尾(追加)。这很好,因为您不必担心寻找或找出文件指针在文件中的位置。打开文件会返回一个 file 对象实例,您可以使用它来写入数据。写入文件(一旦打开)只需要一次方法调用。以下代码显示了使用 SD 库打开文件和写入数据的一组简化调用。我把 record_sample()方法的细节留给您在清单 7-2 中探索:

// Open the file
sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
// Save the data
sensor_data.write(1234);
sensor_data.write("\n");
// Close the file
sensor_data.close();

当然,您需要一些东西来正确地设置组件和库。setup()方法至少应包含串行、有线和 RTC 库的初始化(通过调用它们的begin()方法)以及对 SD 库的调用,以启动与 SD 驱动器的通信。以下是这些步骤所需代码的摘录。请注意,您还可以根据草图的最后编译日期和时间(实际上是上传的日期和时间)来初始化 RTC 的日期和时间:

void setup () {
  Serial.begin(115200);
  while(!Serial);
  Wire.begin();
  rtc.begin();

  if (!rtc.initialized()) {
    Serial.println("RTC is NOT running!");
    // Set time to date and time of compilation
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }

  // disable w5100 SPI (if needed)
  // pinMode(10,OUTPUT);
  // digitalWrite(10,HIGH);

  // Initialize the SD card.
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_PIN)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
...
}

请注意,您还有关闭以太网 W5100 SPI 接口的代码。这仅在以太网屏蔽时需要,并且仅在您不打算使用网络功能时才需要。

还有一件事你可能想补充。你可能需要检查一下是否能读取 SD 卡上的文件。仅仅初始化 SD 库是不够的。SD 驱动器可能会正常通信,但您无法在卡上打开或创建文件。将以下代码添加到 setup()方法中,作为额外的检查。在这种情况下,您检查文件是否存在,如果不存在,则尝试创建该文件。如果在打开呼叫时出现错误,您将打印一条消息:

// Check for file. Create if not present
if (!SD.exists(SENSOR_DATA)) {
  Serial.print("Sensor data file does not exit. Creating file...");
  sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
  if (!sensor_data) {
    Serial.println("ERROR: Cannot create file.");
  }
  else {
    sensor_data.close();
    Serial.println("done.");
  }
}

loop()方法是调用record_sample()方法的地方。在这种情况下,为了简洁起见,将loop()方法留空。您可以在这里随意添加自己的代码来读取传感器,并为每个传感器调用record_sample()方法。

清单 7-3 显示了这个项目的完整代码。虽然到目前为止的解释都是关于草图的关键部分,但是请注意,清单中添加了额外的错误处理代码,以确保 SD 驱动器正确初始化,并且文件存在并且可以写入。

/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Example Arduino SD card data store

  This project demonstrates how to save sensor data to a
  microSD card.
*/

#include <Wire.h>
#include "RTClib.h"
#include <SD.h>
#include <String.h>

// Pin assignment for Arduino Ethernet shield
//#define SD_PIN 4
// Pin assignment for SparkFun microSD shield
//#define SD_PIN 8
// Pin assignment for Adafruit Data Logging shield
#define SD_PIN 10

// Sensor data file - require 8.3 file name
#define SENSOR_DATA "sensdata.txt"

RTC_PCF8523 rtc;
File sensor_data;

void record_sample(int data) {
  // Open the file
  sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
  if (!sensor_data) {
    Serial.println("ERROR: Cannot open file. Data not saved!");
    return;
  }

  // Capture the date and time

  DateTime now = rtc.now();

  String timestamp(now.month(), DEC);
  timestamp += ("/");
  timestamp += now.day();
  timestamp += ("/");
  timestamp += now.year();
  timestamp += (" ");
  timestamp += now.hour();
  timestamp += (":");
  timestamp += now.minute();
  timestamp += (":");
  timestamp += now.second();
  timestamp += (" ");

  // Save the sensor data
  sensor_data.write(&timestamp[0]);

  String sample(data, DEC);
  sensor_data.write(&sample[0]);
  sensor_data.write("\n");

  // Echo the data
  Serial.print("Sample: ");
  Serial.print(timestamp);
  Serial.print(data, DEC);
  Serial.println();

  // Close the file

  sensor_data.close();
}

void setup () {
  Serial.begin(115200);
  while(!Serial);
  Wire.begin();
  rtc.begin();

  if (!rtc.initialized()) {
    Serial.println("RTC is NOT running!");
    // Set time to date and time of compilation
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }

  // disable w5100 SPI
  // pinMode(10,OUTPUT);
  // digitalWrite(10,HIGH);

  // Initialize the SD card.
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_PIN)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  // Check for file. Create if not present
  if (!SD.exists(SENSOR_DATA)) {
    Serial.print("Sensor data file does not exit. Creating file...");
    sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
    if (!sensor_data) {
      Serial.println("ERROR: Cannot create file.");
    }
    else {
      sensor_data.close();
      Serial.println("done.");
    }
  }

  // Record some test samples

.
  record_sample(1);
  record_sample(2);
  record_sample(3);
}

void loop () {
  // Read sensor data here and record with record_sample()
}

Listing 7-3Storing Data on an SD Card

出于演示的目的,我在setup()方法中添加了调试语句,以确保草图正常工作。在setup()方法中放置这些调用允许您加载草图(或重启 Arduino)并检查 SD 卡的内容,看看代码是否工作。如果您将语句放在loop()方法中,那么根据您关闭 Arduino 的时间(拔掉插头),您可能不知道添加了多少行,甚至不知道文件是否正确关闭。将record_sample()语句放在setup()方法中意味着您希望检查输出。

Tip

如果您收到 SD 驱动器初始化错误,请检查定义部分中使用的 pin 分配,以确保您对 SD 驱动器/屏蔽使用了正确的 pin。

如果遇到文件写入或打开错误,请确保 SD 卡被格式化为 FAT 分区,SD 卡没有写保护,并且您可以使用个人电脑在驱动器上创建和读取文件。

测试草图

要测试草图,请确保代码可以编译,并且您的硬件设置正确。一旦你有了一个可以编译的草图,把它上传到你的 Arduino 并启动一个串行监视器。以下代码显示了串行监视器中的预期输出:

Initializing SD card...initialization done.
Sample: 2/29/2020 16:34:27 1
Sample: 2/29/2020 16:34:27 2
Sample: 2/29/2020 16:34:28 3

Note

第一次运行草图时,您可能会看到一条关于初始化 SD 卡和创建文件的消息。这很正常。后续运行(Arduino 重新启动)可能不会显示消息。

如果在写入草图时多次运行草图,那么每次初始化草图时,都会在文件末尾插入三行。这是因为出于调试目的,您在setup()方法中放置了对record_sample()的示例调用。在您读取传感器之后,这些调用自然会放在 loop()方法中。以下代码显示了运行草图(启动 Arduino)四次后的文件内容示例:

2/29/2020 16:31:8 1
2/29/2020 16:31:8 2
2/29/2020 16:31:8 3
2/29/2020 16:34:27 1
2/29/2020 16:34:27 2
2/29/2020 16:34:28 3

如果您检查文件并发现比预期更多的条目集,请尝试从文件中删除数据,启动 Arduino,然后按两次重置按钮。当您查看内容时,您应该正好看到三组条目(一组用于初始启动,因为草图一开始就在内存中,另一组用于每次重新启动 Arduino)。

如果您只看到部分集合(每个集合少于三行),请检查以确保您在关闭 Arduino 之前允许它启动。最好在关闭 Arduino 之前,使用串行监视器,并等待所有三个语句都在监视器上出现。

如果您的草图编译成功,串行监视器中没有显示错误,但数据文件为空,请检查以确保卡可用且未损坏。尝试用 FAT 文件格式重新格式化该卡。

Handle With Care

MicroSD 卡非常易碎。如果处理不当或受到 ESD 或磁场的影响,它们很容易损坏。如果您的卡不能正常工作,并且您不能重新格式化它,它可能已经损坏而无法使用。您可以尝试使用 sdcard.org 的格式化程序,但如果失败,您的卡将不再有效。到目前为止,这种情况只在我身上发生过一次。

既然您已经研究了在 Arduino 上本地存储数据的两种主要方法,那么让我们看看 Raspberry Pi 的可用选项。

树莓酱的本地存储选项

因为 Raspberry Pi 是一台个人电脑,它具有创建、读取和写入文件的能力。虽然可以使用通过 GPIO 头连接的 EEPROM,但为什么要这样做呢?鉴于编程的简易性和使用文件的便利性,很少需要另一种形式的存储。

你也知道 Raspberry Pi 可以用多种方式编程,并且是用最流行的语言之一 Python。 3 在 Python 中处理文件非常容易,并且是默认库的原生功能。这意味着您不需要添加任何东西来使用文件。

以下项目演示了使用 Python 处理文件的简易性。在线 Python 文档详细解释了读写文件( https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files )。

你会注意到,文件放在哪里并不重要——是放在 SD 卡上还是连接的 USB 驱动器上。您只需要知道您想要存储数据的位置(文件夹)的路径,并将其传递给 open()方法。

精明的 Python 程序员 4 知道 Python 库包含了额外的库和类,用于操作文件夹、导航路径等等。有关更多信息,请查看 OS 和 Sys 库的 Python 文档。比如找normpath()Path 5 类。

项目:将数据写入文件

这个项目展示了使用 Python 在 Raspberry Pi 上使用文件是多么容易。因为不需要额外的硬件或软件库,所以我可以跳过这些部分,直接进入代码。

启动您的 Raspberry Pi,并登录。使用以下命令(或类似命令)打开一个新文件:

nano file_io_example.py

使用. py 扩展名命名该文件,以表明它是一个 Python 脚本。在文件中输入以下代码:

import datetime
with open("/home/pi/sample_data.txt", "a+") as my_file:
    my_file.write("{0} {1}\n".format(datetime.datetime.now(), 101))

在本例中,首先导入datetime库。您使用datetime来获取当前的日期和时间。接下来,使用较新的with子句打开文件(注意,您使用的是 Pi 用户的主目录),并向文件中写入一行(您不需要关闭文件——当 execute 离开with子句的范围时,已经为您完成了)。如果你觉得使用显式打开和关闭更好,请随意这样做。

注意open()方法。它需要两个参数——文件路径和名称以及打开文件的模式。您使用“a+”添加到文件中(a),如果文件不存在(+),则创建该文件。其他值包括 r 表示读取,w 表示写入。其中一些可以结合起来:例如,“rw+”创建一个不存在的文件,并允许读写数据。

Note

使用写入模式会截断文件。对于大多数需要存储传感器样本的情况,可以使用追加模式。

对于每次执行,您应该会看到一行时间值略有不同,对应于脚本运行的时间。要执行该文件,请使用以下命令:

python ./file_io_example.py

继续并尝试运行脚本。如果出现错误,请检查代码并更正任何语法错误。如果打开文件时遇到问题(运行脚本时出现 I/O 错误),请尝试检查您正在使用的文件夹的权限。尝试多次运行该脚本,然后显示文件的内容。以下代码显示了该项目的完整命令序列:

$ nano file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ more sample_data.txt
2020-02-29 16:50:34.076657 101
2020-02-29 16:53:23.252384 101
2020-02-29 16:53:24.078429 101
2020-02-29 16:53:24.680599 101
2020-02-29 16:53:25.676225 101
2020-02-29 16:53:26.324482 101

有没有得到类似的结果?如果没有,请更正任何错误,然后重试,直到成功为止。从这个简单的例子中可以看出,在 Raspberry Pi 上使用 Python 将数据写入文件非常容易。

远程存储选项

远程存储意味着数据被发送到另一个节点或系统进行记录。这通常需要与远程系统进行某种形式的通信或网络连接。传感器网络本质上是相互连接的,因此可以利用远程存储。

为了让您了解我正在讨论的内容,请考虑一个 Arduino 传感器节点,它带有一个连接到基于 Raspberry Pi 的节点的 XBee 模块。假设您想将样本数据写入文件。您可以将数据发送到基于 Raspberry Pi 的节点并将数据存储在那里的文件中,而不是在 Arduino 节点上使用 SD 卡来存储数据。主要动机是在 Raspberry Pi 上通过 Python 使用文件要容易得多。如果您还考虑到使用 XBee 模块的多个 Arduino 传感器节点的可能性,您可以使用基于 Raspberry Pi 的节点作为数据集合,将所有数据存储在一个文件中。

Single File or Multiple Files?

在讨论存储聚合数据时,我有时会遇到这个问题。如果您的数据相似(例如温度),您可以考虑将来自相似传感器的数据存储到同一个文件中。但是,如果数据不同(例如一个节点的温度和另一个节点的湿度),您应该考虑使用不同的文件。这使得读取文件更容易,因为您不必编写代码(或使用工具)来分离数据。

但是,您真的只是在谈论将数据存储在文件中吗?答案是否定的。有许多远程存储数据的机制。虽然将数据存储在文件中是最简单的形式,但是您也可以将数据存储在云中,甚至存储在远程数据库服务器上。

如果您有使用数据库存储和检索数据的经验,这种方法会对您有吸引力——特别是如果您计划以后使用其他工具来处理数据。例如,您可能希望执行统计分析或创建图表来跟踪一段时间内的样本。因为使用数据库是一个复杂的主题,所以我将在接下来的几章中研究这种形式的远程存储。

您已经看到了使用文件是多么容易,但是如何在云中存储数据呢?这是怎么回事?简而言之,在云中存储数据涉及使用基于云的数据存储服务来接收数据并以某种方式托管数据。最流行的形式是将数据呈现给互联网上的其他人,供他们自己查看或消费。

下一节讨论如何使用 MathWorks 提供的一种流行的、易于使用的、基于云的物联网数据托管服务 ThingSpeak ( www.thingspeak.com )将样本数据存储在云中。您将看到在 Arduino 和 Raspberry Pi 上使用 ThingSpeak 的示例项目。

在云中存储数据

除非你住在一个非常偏僻的地方,否则你很可能已经被关于云和物联网的讨论轰炸了。也许你在杂志和电视上看到过广告,或者在其他书上读到过,或者参加过研讨会或会议。除非你花时间了解云的含义,否则你可能会想知道这一切有什么大惊小怪的。

简单来说, 6 云是一个通过互联网提供的服务的名称。这些可以是您可以访问的服务器(在更大的服务器上作为虚拟机运行),提供对特定软件或环境的访问的系统,或者是您可以附加到其他资源的资源,如磁盘或 IP 地址。云背后的技术包括网格计算(分布式处理)、虚拟化和网络。正确的科学术语是云计算。虽然对云计算的深入探讨超出了本书的范围,但是理解您可以使用云计算服务来存储您的传感器数据就足够了。

有许多物联网云供应商提供各种各样的产品、容量和功能,以满足您对物联网项目的任何需求。有这么多供应商提供物联网解决方案,很难选择一个。以下是云行业顶级供应商提供的更受欢迎的物联网产品的简短列表:

大多数供应商提供商业产品,但少数如谷歌,Azure,Arduino 和 ThingSpeak 提供有限的免费帐户。正如你所猜测的,有些产品是复杂的解决方案,需要很高的学习曲线,但是 Arduino 和 ThingSpeak 产品简单易用。因为我们想要一个支持 Arduino 和 Raspberry Pi(以及其他平台)的解决方案,所以我们将在本章中使用 ThingSpeak 作为在云中存储数据的例子。

Tip

如果您想要或需要使用其他供应商的产品,请确保在开始编写代码之前通读所有教程。

ThingSpeak 为每年产生少于 300 万条消息(或数据元素)或每天大约 8000 条消息的非商业项目提供免费账户。免费账户也限定为 7 个通道(一个通道相当于一个项目,最多可以保存 8 个数据项)。如果您需要存储或处理更多的数据,您可以购买四个类别之一的商业许可证,每个类别都有特定的产品、功能和限制:标准、学术、学生和家庭。请参见 https://thingspeak.com/prices 并单击每个许可选项,了解有关功能和定价的更多信息。

Note

除非你使用工作或学校账户,否则你可能需要付费使用一些产品,如 MatLab。

ThingSpeak 的工作原理是从包含您想要保存或绘制的数据的设备接收消息。有一些库可以用于某些平台或编程语言,如 Arduino 或 Python。这是迄今为止连接到 ThingSpeak 和传输数据最简单的方式。

但是,您也可以使用机器对机器(M2M)连接协议(称为 MQTT 7 )或表述性状态传输(REST 8 ) API,该 API 被设计为通过 HTTP 进行通信的请求-响应模型,用于向 ThingSpeak 发送数据或从中读取数据。是的,你甚至可以从其他设备读取数据。

Tip

参见 www.mathworks.com/help/thingspeak/channels-and-charts-api.html 了解更多关于 ThingSpeak MQTT 和 REST API 的细节。

当您想要读取或编写 ThingSpeak 通道时,您可以发布 MQTT 消息,通过 HTTP 向 REST API 发送请求,或者使用一个特定于平台的库来为您封装这些机制。一个通道最多可以有八个表示为字符串或数字数据的数据字段。您还可以使用一些复杂的过程来处理数字数据,如求和、平均、舍入等。

我们不会深入讨论这些协议的细节;相反,我们将看到如何使用 ThingSpeak 作为快速入门指南。MathWorks 提供了一整套教程、文档和示例。因此,如果你需要更多关于 ThingSpeak 如何工作的信息,请查看位于 www.mathworks.com/help/thingspeak/ 的文档。

The Cloud: Isn’t That Just Marketing Hype?

不要相信任何名称中包含“云”的产品的宣传或销售。云计算服务和资源应该可以通过互联网从任何地方访问,通过订阅(收费或免费)提供给你,并允许你消费或生产和分享所涉及的数据。此外,请考虑这样一个事实,即您必须能够访问云才能访问您的数据。因此,如果服务不可达(或关闭),您别无选择。

开始使用 ThingSpeak

要使用 ThingSpeak,您必须先注册一个帐户。幸运的是,他们提供了免费账户的选择。事实上,您可以先获得一个免费帐户,然后再添加(购买)许可证。要创建一个免费帐户,请访问 https://thingspeak.com/users/sign_up ,填写您的电子邮件地址、位置(通用地理信息)以及姓名,然后点击继续。然后,您将收到一封确认电子邮件。打开并按照说明验证您的电子邮件,并通过选择密码和完成一个简短的问卷来完成您的免费帐户。

创建频道

登录 ThingSpeak 后,您可以创建一个通道来保存您的数据。回想一下,每个通道最多可以有八个数据项(字段)。在你的登录首页,点击新频道,如图 7-8 所示。

img/313992_2_En_7_Fig8_HTML.jpg

图 7-8

在 ThingSpeak 中创建频道

你会看到一个很长的表格,里面有很多你可以填写的字段。图 7-9 显示了表格的示例。

img/313992_2_En_7_Fig9_HTML.jpg

图 7-9

新渠道形式

至少,您只需要命名频道,输入描述(不严格要求,但建议如此),然后选择(打勾)一个或多个字段来命名每个频道。就这样。点击保存频道完成该过程。

那么,这些频道设置是什么?下面给出了每种方法的简要概述。当您使用 ThingSpeak 时,您可能想要开始使用以下一些字段:

  • 完成百分比:根据频道中名称、描述、位置、URL、视频和标签的完成情况计算的字段。

  • 通道名称:通道的唯一名称。

  • 描述:通道的描述。

  • 字段# :勾选各框,启用该字段。

  • 元数据:JSON、XML 或 CSV 格式的频道附加数据。

  • 标签:逗号分隔的搜索关键字列表。

  • 链接到外部网站:如果你有一个关于你的项目的网站,你可以在这里提供网址,在频道上发布。

  • 显示频道位置:勾选此框,包括以下字段:

    • 纬度:项目或数据源传感器的纬度

    • 经度:项目或数据源传感器的经度

    • 标高:标高,以米为单位,用于受标高影响的项目

  • 视频 URL :如果您有一个与您的项目相关的视频,您可以在此处提供 URL 以在频道上发布。

  • 链接到 GitHub :如果你的项目托管在 GitHub,你可以提供要在频道上发布的 URL。

哇,这么多东西都是免费的!正如你将看到的,这不是一个简单的玩具或严重受限的产品。有了这些设置,你可以完成很多事情。请注意,有些地方可以放置视频、网站和 GitHub 的链接。这是因为通道可以是私有的(只有你的登录名或 API 密匙可以访问),也可以是公共的。公开频道允许您与任何人共享数据,因此这些 URL 字段可以方便地记录您的项目。酷毙了。

一旦你创建了你的通道,是时候写一些数据了。对于大多数项目来说,有两条信息是您需要的:通道的 API 键和一些库的通道号(显示在通道页面上的整数值)。许多平台都有可用的库,在某些平台上,可能有几种方法(库或技术)将数据写入 ThingSpeak 通道。

您可以通过点击 API 密钥选项卡在渠道页面上找到 API 密钥。当您创建一个新通道时,您将拥有一个 write 和一个 read API 密钥。如果需要,您可以添加更多密钥,以便每个设备、位置、客户等可以使用一个密钥。图 7-10 显示了之前在图 7-9 中创建的通道的 API 键选项卡。

img/313992_2_En_7_Fig10_HTML.jpg

图 7-10

ThingSpeak 通道的 API 键

注意,我遮住了钥匙。如果您公开您的频道,请不要与您不想允许写入您的频道的任何人共享写入密钥。您可以通过点击生成新的写 API 密钥添加新的读 API 密钥按钮来创建新的密钥。点击删除 API 键按钮可以删除读取的键。

我们在代码中使用密钥来允许设备连接到通道并向通道写入数据。因此,我们通常从频道页面复制这个字符串,并将其作为字符串粘贴到我们的代码中。回想一下,我们可能使用封装了 HTTP 或 MQTT 机制的库,或者在 Raspberry Pi Python 库的情况下,我们使用 Python 库和 HTTP 协议。我们将在即将到来的 Arduino 和 Raspberry Pi 示例项目中看到这两者。

既然你已经理解了向 ThingSpeak 写入数据的基础,那么让我们来看看如何为 Arduino 更详细地做这件事。接下来是树莓派的例子。

项目:用 Arduino 向 ThingSpeak 写入数据

这个项目演示了如何将传感器数据写入 ThingSpeak 通道。与本章前面的项目不同,您将使用一个传感器并生成一些样本数据。在这种情况下,您在 Arduino MKR1000 上监控温度,并将摄氏和华氏温度值保存到您的 ThingSpeak 中。如果您还没有为 Arduino 创建 ThingSpeak 通道,现在就创建,并记录通道 ID 和生成的 API 密钥。使用以下通道数据,并将其命名为MKR1000_TMP36,如图 7-11 所示。

img/313992_2_En_7_Fig11_HTML.jpg

图 7-11

为 MKR1000 和 TMP36 传感器设置通道

点击保存频道按钮创建频道。然后,在 API Key 选项卡上,复制 write key 并粘贴到一个新文件中以备后用。

现在我们已经创建了一个通道,让我们设置硬件。

硬件设置

本项目的硬件是 Arduino MKR1000、试验板、试验板电线、TMP36 温度传感器和 0.10uF 电容。如图 7-12 所示连接传感器和电容器。将传感器的第 1 针连接到 Arduino 上的 5V 针,将传感器的第 2 针连接到 MKR1000 上的 A1 针,将第 3 针连接到 MKR1000 上的地。电容器也连接到传感器的引脚 1 和 3(方向无关紧要)。

Tip

你可以使用较新的 MKR 型号,只要它们是 Wi-Fi 类型的。

img/313992_2_En_7_Fig12_HTML.jpg

图 7-12

Arduino 的 ThingSpeak 温度馈送的布线设置

若要使用“连接到互联网”,您还需要 Wi-Fi 接入点或路由器来连接。您将需要连接的 SSID 和密码。

现在,让我们看看如何设置软件和项目草图。

配置 Arduino IDE

为了创建草图,我们需要在 Arduino IDE 中设置一些东西。我们需要确保 MKR 板得到支持,ThingSpeak 和 Wi-Fi 101 库得到安装。让我们从 MKR 董事会的支持开始。

在 Arduino IDE 中,打开一个新草图,点击工具板卡 XXXX板卡管理器……(其中 XXXX 代表你最后使用的板卡)。当表单加载时,在搜索框中键入 Arduino & MKR1000,并安装 Arduino SAMD 支持,如图 7-13 所示。

img/313992_2_En_7_Fig13_HTML.jpg

图 7-13

安装 SAMD 板支架

点击安装按钮,安装板卡支撑模块。在某些 PC 平台上,当您第一次使用连接到 PC 的主板启动 IDE 时,Arduino IDE 可能会提示您安装主板支持。

现在,让我们安装我们需要的两个库。你可以按任何顺序做。在 Arduino IDE 中,选择草图包含库……管理库。在搜索框中输入 ThingSpeak,然后点击安装按钮,如图 7-14 所示。再次点击安装安装库。

img/313992_2_En_7_Fig14_HTML.jpg

图 7-14

安装 ThingSpeak 库

我们还需要 Wi-Fi 101 图书馆,以允许使用互联网通信。在 Arduino IDE 中,选择草图包含库……管理库。在搜索框中输入 Wi-Fi 101,然后点击安装按钮,如图 7-15 所示。再次点击安装安装库。

img/313992_2_En_7_Fig15_HTML.jpg

图 7-15

安装 WiFi 101 库

写草图

现在您已经安装了必要的库和板模块,打开一个新的 Arduino 项目并将其命名为arduino_thingspeak.ino。回想一下,我们将使用 TMP36 并从 MKR1000 上的引脚 A1 读取值。我们已经在第六章中看到了读取 TMP36 的代码,所以我们将跳过解释,直接进入如何与 ThingSpeak 交互。

在这个例子中,我们将看到如何在一个单独的头文件(.h)中存储我们的 API 密钥和其他关键数据,该文件将是草图的一部分,并保存在同一个文件夹中。要添加一个头文件,点击草图右侧的小向下箭头按钮,选择新建标签,如图 7-16 所示。在提示中,输入secrets.h并按键输入。这将打开一个新的标签。单击该选项卡打开文件。

img/313992_2_En_7_Fig22_HTML.jpg

图 7-22

树莓派的示例 ThingSpeak 饲料

img/313992_2_En_7_Fig21_HTML.jpg

图 7-21

验证 ADC 模块

img/313992_2_En_7_Fig20_HTML.jpg

图 7-20

将 TMP36 和 ADC 连接到 Raspberry Pi

img/313992_2_En_7_Fig19_HTML.jpg

图 7-19

12 位 ADC 模块(Adafruit 提供)

img/313992_2_En_7_Fig18_HTML.jpg

图 7-18

为 Raspberry Pi 和 TMP36 传感器设置通道

img/313992_2_En_7_Fig17_HTML.jpg

图 7-17

通道数据示例(MKR1000_TMP36)

img/313992_2_En_7_Fig16_HTML.jpg

图 7-16

添加新选项卡

我们将把 Wi-Fi 和我们的 ThingSpeak 频道数据放在这个文件中。使用#define指令创建我们将在主草图中使用的新字符串。以下代码显示了文件所需的行和数据。输入这些并保存文件。

#define SECRET_SSID "YOUR_SSID"                 // SSID
#define SECRET_PASS "SSID_PASS"                 // WiFi Password
#define SECRET_CH_ID 0000000000                 // Channel number
#define SECRET_WRITE_APIKEY "ABCDEFGHIJKLMNOP"  // Write API Key

现在,返回到主草图选项卡。从以下内容开始草图。您需要我们刚刚创建的ThingSpeak.hWiFi101.hsecrets.h文件。

#include "ThingSpeak.h"
#include <WiFi101.h>
#include "secrets.h"

接下来,我们需要声明几个变量,并实例化 Wi-Fi 客户机,如下面的代码所示。我们还添加了一个变量来存储传感器的 pin 号。注意,我们使用存储在secrets.h文件中的那些#defines

char ssid[] = SECRET_SSID;   // your network SSID (name)
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;

unsigned long myChannelNumber = SECRET_CH_ID;
const char * myWriteAPIKey = SECRET_WRITE_APIKEY;

int SENSOR_PIN = 1;

要使用以太网屏蔽,您还必须声明一个 MAC 地址。以太网屏蔽的 IP 地址是通过 DHCP 请求的。您将 MAC 地址定义为数组。这可以是一组随机值,只要它们在 0x 00–0x ff 范围内。您可以使用此处显示的内容:

byte mac_addr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

接下来,定义 ThingSpeak API 键和提要 ID。您还可以定义 TMP36 传感器的引脚编号。本例使用引脚 0。您可以选择想要使用的 pin——只需更改这个定义,剩下的代码就会指向正确的 pin:

char ThingSpeakKey[] = "<YOUR_KEY_HERE>";
#define FEED_NUMBER <YOUR_FEED_HERE>
#define SENSOR_PIN 0

现在我们准备编写setup()方法。您必须初始化串行类(这样您就可以使用串行监视器)并初始化 Wi-Fi 和 ThingSpeak 客户端。您对这些操作中的每一个都使用了begin()方法。对于 Wi-Fi 类,您传入前面定义的 SSID 和密码。完整的setup()方法如下:

void setup() {
  Serial.begin(115200);      // Initialize serial
  while (!Serial);
  // Connect to WiFi
  Serial.println("Welcome to the MKR1000 + TMP36 ThingSpeak Example!");
  while(WiFi.status() != WL_CONNECTED){
    WiFi.begin(ssid, pass);
    delay(5000);
  }
  Serial.println(" Connected.");
  ThingSpeak.begin(client);  // Initialize ThingSpeak
}

最后,loop()方法包含读取传感器的代码,计算摄氏和华氏温度,并将这些数据发送到我们的 ThingSpeak 通道。为此,我们首先调用 ThingSpeak 库的setField()方法来设置我们想要更新的每个字段(字段编号从 1 开始)。然后我们使用writeFields()方法将数据发送给 ThingSpeak。我们可以检查该调用的结果,以确保返回的代码是200,这意味着成功(OK)。这里显示了loop()方法的简化版本:

void loop() {
  // Read TMP36 here

  // Set the fields with the values
  ThingSpeak.setField(1, temperatureC);
  ThingSpeak.setField(2, temperatureF);

  // Write to the ThingSpeak channel
  int res = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  if (res == 200) {
    Serial.println("Channel update successful.");
  } else {
    Serial.print("Problem updating channel. HTTP error code ");
    Serial.println(res);
  }
  Serial.println("sleeping...");
  delay(20000); // Wait 20 seconds to update the channel again
}

请注意,如果没有返回代码 200,我们将显示实际结果。还要注意,我们在末尾添加了一个 sleep ( delay())来休眠 20 秒。我们这样做是因为 ThingSpeak 免费帐户仅限于每 15 秒更新一次。

现在您已经理解了草图的流程和内容,您可以完成缺失的部分并开始测试。清单 7-4 显示了该项目的完整草图。

/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Arduino ThingSpeak Write Example

  This project demonstrates how to write data to a ThingSpeak channel.
*/

#include "ThingSpeak.h"
#include <WiFi101.h>
#include "secrets.h"

char ssid[] = SECRET_SSID;   // your network SSID (name)
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;

unsigned long myChannelNumber = SECRET_CH_ID;
const char * myWriteAPIKey = SECRET_WRITE_APIKEY;

int SENSOR_PIN = 1;

void setup() {
  Serial.begin(115200);      // Initialize serial
  while (!Serial);
  // Connect to WiFi
  Serial.println("Welcome to the MKR1000 + TMP36 ThingSpeak Example!");
  Serial.print("Attempting to connect to SSID: ");
  Serial.print(SECRET_SSID);
  Serial.print(" ");
  while(WiFi.status() != WL_CONNECTED){
    WiFi.begin(ssid, pass);
    Serial.print(".");
    delay(5000);
  }
  Serial.println(" Connected.");
  ThingSpeak.begin(client);  // Initialize ThingSpeak

}

void loop() {
  // Read TMP36
  float adc_data = analogRead(A1);
  float voltage = adc_data *  (3.3 / 1024.0);
  Serial.print("Temperature is ");
  float temperatureC = (voltage - 0.525) / 0.01;
  Serial.print(temperatureC);
  Serial.print("C, ");
  float temperatureF = ((temperatureC * 9.0)/5.0) + 32.0;
  Serial.print(temperatureF);
  Serial.println("F");

  // Set the fields with the values
  ThingSpeak.setField(1, temperatureC);
  ThingSpeak.setField(2, temperatureF);

  // Write to the ThingSpeak channel
  int res = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  if (res == 200) {
    Serial.println("Channel update successful.");
  } else {
    Serial.print("Problem updating channel. HTTP error code ");
    Serial.println(res);
  }
  Serial.println("sleeping...");
  delay(20000); // Wait 20 seconds to update the channel again
}

Listing 7-4Arduino-Based ThingSpeak Channel Write

Note

确保在secrets.h文件中替换您的 API 密钥和通道号。否则会导致编译错误。

花一些时间来确保所有的代码都输入正确,并且草图编译无误。一旦到了这个阶段,就可以上传草图,进行试用了。

测试草图

要测试草图,请确保代码可以编译,并且您的硬件设置正确。一旦你有了一个可以编译的草图,把它上传到你的 Arduino MKR1000 并启动一个串行监视器。以下代码显示了您应该看到的输出示例:

Attempting to connect to SSID: ATT-WiFi-0059 . Connected.
Temperature is 15.50C, 59.90F
Channel update successful.
sleeping...
Temperature is 16.14C, 61.06F
Channel update successful.
sleeping...
Temperature is 15.82C, 60.48F
Channel update successful.
sleeping...
Temperature is 16.14C, 61.06F
Channel update successful.
sleeping...
Temperature is 16.46C, 61.64F
Channel update successful.
sleeping...

你看到类似的输出了吗?如果没有,请检查串行监视器中显示的返回代码。您应该会看到返回代码 200(表示成功)。如果返回代码是一位数(1、2、3 等等),您可能会遇到连接到 ThingSpeak 的问题。如果出现这种情况,请将您的笔记本电脑连接到同一根网线,并尝试访问 ThingSpeak。

如果连接速度非常慢,您可能会遇到每隔一次或每 N 次尝试都得到 200 以外的错误代码的情况。如果是这种情况,您可以在loop()方法中增加超时来进一步延迟处理。这可能有助于一些非常慢的连接,但它不是一个坏的或间歇性的连接治愈。

在访问 ThingSpeak 之前,让草图运行大约 3 分钟。草图运行一段时间后,导航到 ThingSpeak,登录并点击您的渠道页面。您应该会看到类似于图 7-17 所示的结果。

请注意图表开头和结尾附近的峰值。我通过在 TMP36(我的手指)上按压一个温暖的物体设备来模拟数据中的尖峰。如果你尝试这样做,小心不要碰到任何电线!

为了更多乐趣

您可以从这个脚本中获得很多乐趣。尝试连接其他传感器,并在 ThingSpeak 中创建其他通道。你也可以尝试阅读你保存在 ThingSpeak 中的数据。

既然您已经知道如何在 Arduino 上将数据保存到 ThingSpeak,那么让我们来探索如何在 Raspberry Pi 上做同样的事情。

项目:用 Raspberry Pi 向 ThingSpeak 写入数据

这个项目演示了在 Raspberry Pi 上通过 HTTP 使用 ThingSpeak REST API 将传感器数据写入 ThingSpeak 通道的简单性。回想一下,要读取模拟温度传感器(TMP36),您需要使用一个提供 12 位读数精度的 I2C 模块。

Tip

这个例子演示了如何使用 HTTP 接口向 ThingSpeak 写数据。然而,这也是一个 ThingSpeak Python 库,如果你愿意,你可以使用它。你可以用pip3 install thingspeak命令安装它。ThingSpeak Python 库的文档可以在 https://thingspeak.readthedocs.io/en/latest/ 找到。

如果您还没有为 Raspberry Pi 创建 ThingSpeak 通道,现在就创建,并记录通道 ID 和生成的 API 键。使用以下通道数据并命名为RASPI_TMP36,如图 7-18 所示。

点击保存频道按钮创建频道。然后,在 API Key 选项卡上,复制 write key 并粘贴到一个新文件中以备后用。

现在我们已经创建了一个通道,让我们设置硬件。

硬件设置

本项目的硬件包括一个 Raspberry Pi、一个 Raspberry Pi Cobbler+(可选)、一个试验板、TMP36 传感器、跳线和一个 ADC 模块。

我提到过,Raspberry Pi 不包含任何 ADC,因此不能使用模拟传感器。在本项目中,您将探索如何将多通道 ADC 与 Raspberry Pi 配合使用,以便使用 TMP36 模拟温度传感器。图 7-19 显示了 Adafruit ( www.adafruit.com/products/1083 )的 12 位 ADC。该模块最多支持四个传感器(通道)。在图中,您可以看到引脚 A0–A3;这些是用于支持的每个通道的引脚。

Tip

您正在探索将 ADC 模块与 Raspberry Pi 一起使用,因为它支持 I2C 协议,但是您也可以将该模块与 Arduino 一起使用。详见 http://learn.adafruit.com/adafruit-4-channel-adc-breakouts

您还需要通过 Raspberry Pi 上的网络连接连接到互联网。互联网连接可以通过有线以太网连接或无线连接。像 Arduino 一样,对连接没有具体要求。

图 7-20 显示了您需要进行的连接。如果你已经完成了前几章中的项目,这些对你来说应该很熟悉。对于 TMP36,将引脚 1 连接到与 ADC 模块相同的 5V 连接,将引脚 3 连接到 ADC 模块的 GND 连接。传感器上的引脚 2 连接到 ADC 模块上的 A0 引脚。

按如下方式连接 TMP36 传感器(再次参见图 7-20 )。

Caution

务必仔细检查您的连接,并将其与图 7-20 进行比较。树莓派上的东西连接不当会导致电路板损坏。

完成这些连接后,打开 Raspberry Pi 并发出以下命令:

$ sudo i2cdetect –y 1

您应该会看到 ADC 模块在输出中显示为地址 0x48,如图 7-21 所示。

写代码

现在您已经有了所需的库,是时候编写一个脚本来从 TMP36 传感器读取样本(通过 ADC 模块)并将数据保存到您的 ThingSpeak 通道了。由于我们已经编写了读取 TMP36 传感器的代码,因此我们将专注于向 ThingSpeak 写入数据的代码。

首先在树莓 Pi 上打开一个名为raspi_tmp36.py的新文件。您可以使用 Thonny IDE 或文本编辑器或终端中的nano来创建文件。

让我们从进口开始。我们需要为 ADC 模块导入 http.client、time、urllib、board、busio 和 Adafruit 库,如下所示:

import http.client
import time
import urllib

# import the Raspberry Pi libraries
import board
import busio

# Import the ADC Adafruit libraries
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

接下来,我们需要为 API 键声明一个变量,并实例化 I2C 接口,如下所示:

# API KEY
THINGSPEAK_APIKEY = 'YOUR_API_KEY'

# Instantiate (start/configure the I2C protocol)
i2c = busio.I2C(board.SCL, board.SDA)
# Instantiate the ADS1115 ADC board
ads = ADS.ADS1115(i2c)
# Setup the channel from Pin 0 on the ADS1115
channel0 = AnalogIn(ads, ADS.P0)

接下来是脚本的核心代码。我们将使用一个try…except块来捕获键盘中断( Ctrl+C )。在其中,我们准备了一个特殊的 URL 来编码我们通道的字段数据,然后打开这个 URL。

更具体地说,我们使用urllib.parseurlencode()方法以包含字段数据的字典的形式对数据进行编码。这确保了创建的字符串在 URL 中有效使用。接下来,我们创建一个标题字典,并将其传递给http.clientHttpConnection()方法,以打开到 ThingSpeak 的连接。最后,我们以对 update REST API 端点的POST命令的形式将数据发送给 ThingSpeak。哇哦!下面的代码显示了这些步骤。花点时间通读它们。它们应该容易理解。记住,你可以。

params = urllib.parse.urlencode(
    {
        'field1': temp_c,
        'field2': temp_f,
        'key': THINGSPEAK_APIKEY,
    }
)
# Create the header
headers = {
    "Content-type": "application/x-www-form-urlencoded",
    'Accept': "text/plain"
}
# Create a connection over HTTP
conn = http.client.HTTPConnection("api.thingspeak.com:80")
# Execute the post (or update) request to upload the data
conn.request("POST", "/update", params, headers)

清单 7-5 显示了该项目的完整代码。你会注意到我们跳过了print()语句和错误处理代码,但是这些都是我们在之前的项目中看到的。在运行代码之前,请务必通读一遍,这样您就可以看到它是如何工作的。此外,您可以从图书网站下载这些代码,而不是键入所有代码。

#
# Beginning Sensor Networks Second Edition
#
# IoT Example - Publish temperature data from a Raspberry Pi
# with TMP36 and ADC.
#
# Dr. Charles A. Bell
# March 2020
#
from __future__ import print_function

# Python imports
import http.client
import time
import urllib

# import the Raspberry Pi libraries
import board
import busio

# Import the ADC Adafruit libraries
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

# API KEY
THINGSPEAK_APIKEY = 'YOUR_API_KEY'

# Instantiate (start/configure the I2C protocol)
i2c = busio.I2C(board.SCL, board.SDA)
# Instantiate the ADS1115 ADC board
ads = ADS.ADS1115(i2c)
# Setup the channel from Pin 0 on the ADS1115
channel0 = AnalogIn(ads, ADS.P0)

# Run the program to upload temperature data to ThingSpeak

print("Welcome to the ThingSpeak Raspberry Pi temperature sensor! Press CTRL+C to stop.")
try:
    while 1:
        # Get temperature in Celsius
        temp_c = ((channel0.voltage * 3.30) - 0.5) * 10
        # Calculate temperature in Fahrenheit
        temp_f = (temp_c * 9.0 / 5.0) + 32.0
        # Display the results for diagnostics
        print("Uploading {0:.2f} C, {1:.2f} F"
              "".format(temp_c, temp_f), end=' ... ')
        # Setup the data to send in a JSON (dictionary)
        params = urllib.parse.urlencode(
            {
                'field1': temp_c,
                'field2': temp_f,
                'key': THINGSPEAK_APIKEY,
            }
        )
        # Create the header
        headers = {
            "Content-type": "application/x-www-form-urlencoded",
            'Accept': "text/plain"
        }
        # Create a connection over HTTP
        conn = http.client.HTTPConnection("api.thingspeak.com:80")
        try:
            # Execute the post (or update) request to upload the data
            conn.request("POST", "/update", params, headers)
            # Check response from server (200 is success)
            response = conn.getresponse()
            # Display response (should be 200)
            print("Response: {0} {1}".format(response.status,
                                             response.reason))
            # Read the data for diagnostics
            data = response.read()
            conn.close()
        except Exception as err:
            print("WARNING: ThingSpeak connection failed: {0}, "
                  "data: {1}".format(err, data))

        # Sleep for 20 seconds

        time.sleep(20)
except KeyboardInterrupt:
    print("Thanks, bye!")
exit(0)

Listing 7-5Complete Code for the raspi_thingspeak.py Script

Note

确保在标记的位置替换您的 API 密钥。否则将导致运行时错误。

现在您已经输入了所有的代码,让我们测试脚本,看看它是否工作。

测试脚本

Python 脚本是解释程序。尽管在脚本开始时有大量的语法检查,但是直到执行语句时才发现逻辑错误。因此,如果没有正确输入脚本,您可能会遇到错误或异常(例如,如果您拼错了方法或变量名称)。如果您未能替换 API 键和提要编号的占位符,也可能会发生这种情况。

要运行该脚本,请输入以下命令。在使用 Ctrl+C 中断主循环之前,让脚本运行几次迭代。

$ python3 ./raspi_thingspeak.py

以下代码显示了您应该看到的输出示例:

Welcome to the ThingSpeak Raspberry Pi temperature sensor! Press CTRL+C to stop.
Uploading 18.46 C, 65.23 F ... Response: 200 OK
Uploading 18.49 C, 65.28 F ... Response: 200 OK
Uploading 19.20 C, 66.56 F ... Response: 200 OK
Uploading 18.41 C, 65.13 F ... Response: 200 OK
Uploading 18.24 C, 64.83 F ... Response: 200 OK
Uploading 18.25 C, 64.85 F ... Response: 200 OK
Uploading 18.31 C, 64.96 F ... Response: 200 OK
Uploading 18.32 C, 64.97 F ... Response: 200 OK
Uploading 18.29 C, 64.93 F ... Response: 200 OK
Uploading 18.35 C, 65.03 F ... Response: 200 OK
Uploading 18.24 C, 64.83 F ... Response: 200 OK
Uploading 18.39 C, 65.09 F ... Response: 200 OK
Uploading 18.25 C, 64.84 F ... Response: 200 OK
Thanks, bye!

让脚本运行 3 分钟左右,然后在 ThingSpeak 上导航到你的 Raspberry Pi 频道。您应该会看到您的传感器数据显示出来,类似于图 7-22 所示。

如果您没有看到类似的数据,请返回并检查上一个项目中讨论的返回代码。您应该会看到返回代码 200(成功)。检查并更正网络连接中的任何错误或脚本中的语法或逻辑错误,直到它成功运行几次(存储的所有样本都返回代码 200)。

如果你看到类似的数据,恭喜你!您现在知道了如何使用两个不同的平台生成数据并将其保存到云中。

为了更多乐趣

您可以从这个脚本中获得很多乐趣。尝试连接其他传感器,并在 ThingSpeak 中为它们创建其他通道。你也可以尝试阅读你保存在 ThingSpeak 中的数据。

将传感器数据存储在数据库中

正如您可能已经猜到的,可以将传感器数据存储到 Raspberry Pi 上的数据库中。您可以使用 MySQL 作为您的数据库服务器,并使用 Connector/Python 库来编写 Python 脚本,该脚本读取传感器数据并将数据存储在表中以供以后处理。因为涉及的内容比几十行代码要多得多(比如在 Raspberry Pi 上设置 MySQL),所以您将在第 8 和 9 章中更详细地探讨这个主题。

部件购物清单

完成本章中的项目需要一些组件,如表 7-2 中所列。其中一些,如 XBee 模块和支持硬件,也包含在其他章节的购物清单中。这些如表 7-3 所示。

表 7-3

以前章节中重复使用的组件

|

项目

|

供应商

|

是吗?成本美元

|

所需数量

| | --- | --- | --- | --- | | 按钮 | www.sparkfun.com/products/97 | 0.35one试验板(非迷你)[www.sparkfun.com/products/9567](http://www.sparkfun.com/products/9567)0.35 | one | | 试验板(非迷你) | [`www.sparkfun.com/products/9567`](http://www.sparkfun.com/products/9567) | 4.95 | one | | 试验板跳线 | www.sparkfun.com/products/8431 | 3.95oneTMP36传感器[www.sparkfun.com/products/10988](http://www.sparkfun.com/products/10988)3.95 | one | | TMP36 传感器 | [`www.sparkfun.com/products/10988`](http://www.sparkfun.com/products/10988) | 1.50 | one | | www.adafruit.com/products/165 | | 0.10uF 电容 | www.sparkfun.com/products/8375 | 0.25one树莓派3B3B+4B大多数在线和零售商店35美元及以上oneHDMIHDMIDVI电缆大多数在线和零售商店变化oneHDMIDVI监视器大多数在线和零售商店变化oneUSB键盘大多数在线和零售商店变化oneUSB电源大多数在线和零售商店变化oneUSBA型至USB微型插头大多数在线和零售商店变化oneSD卡,2GB或更大大多数在线和零售商店变化one鞋匠+[www.adafruit.com/products/914](http://www.adafruit.com/products/914)0.25 | one | | 树莓派 3B,3B+或 4B | 大多数在线和零售商店 | 35 美元及以上 | one | | HDMI 或 HDMI 转 DVI 电缆 | 大多数在线和零售商店 | 变化 | one | | HDMI 或 DVI 监视器 | 大多数在线和零售商店 | 变化 | one | | USB 键盘 | 大多数在线和零售商店 | 变化 | one | | USB 电源 | 大多数在线和零售商店 | 变化 | one | | USB A 型至 USB 微型插头 | 大多数在线和零售商店 | 变化 | one | | SD 卡,2GB 或更大 | 大多数在线和零售商店 | 变化 | one | | 鞋匠+ | [`www.adafruit.com/products/914`](http://www.adafruit.com/products/914) | 7.95 | one | | 10K 欧姆电阻器 | 大多数在线和零售商店 | 变化 | one | | 4.7K 欧姆电阻器 | 大多数在线和零售商店 | 变化 | Two |

表 7-2

所需组件

|

项目

|

供应商

|

是吗?成本美元

|

所需数量

| | --- | --- | --- | --- | | I2C EEPROM | www.sparkfun.com/products/525 | 1.95oneArduino以太网盾[www.sparkfun.com/products/9026](http://www.sparkfun.com/products/9026)1.95 | one | | Arduino 以太网盾 | [`www.sparkfun.com/products/9026`](http://www.sparkfun.com/products/9026) | 24.95 | 1 | | 微型防护罩 | www.sparkfun.com/products/9802 | 14.95<sup></sup>Arduino的数据记录屏蔽[www.adafruit.com/products/1141](http://www.adafruit.com/products/1141)14.95 | <sup>*</sup> | | Arduino 的数据记录屏蔽 | [`www.adafruit.com/products/1141`](http://www.adafruit.com/products/1141) | 19.95 | | | DS1307 实时时钟分线板 | www.adafruit.com/product/3296 | 7.501<sup></sup>实时时钟模块[www.sparkfun.com/products/99](http://www.sparkfun.com/products/99)7.50 | 1 <sup>**</sup> | | 实时时钟模块 | [`www.sparkfun.com/products/99`](http://www.sparkfun.com/products/99) | 14.95 | ** | | 12 位 ADC 模块 | www.adafruit.com/products/1083 | $9.95 | one |

* 你只需要这些选项中的一个。

这两种方法都可以。

摘要

本章探讨了 Arduino 和 Raspberry Pi 的本地存储选项。您完成了许多小型项目,展示了每种可能的储物方案。我还讨论了使用 MathWorks 的 ThingSpeak 物联网网站将传感器数据存储在云中。在那里,我们学习了如何创建通道和向通道发送数据。

在接下来的两章中,我将暂停对传感器项目的探索,开始讨论另一种形式的远程存储:数据库服务器。第八章着重于设置 MySQL 服务器,第九章着重于通过为 Arduino 编写的特殊数据库连接器(库)将 MySQL 服务器与 Arduino 一起使用。

Footnotes 1

或者至少是所述时间段的数据。

  2

这是指旧的 FAT 文件系统对文件命名的要求,其中文件名最多可以有八个字符,扩展名最多可以有三个字符( http://en.wikipedia.org/wiki/8.3_filename )。你还记得那些日子吗?

  3

倪!(向巨蟒小组道歉。)

  4

叫做皮托尼斯塔斯。

  5

一条路!一条路!(更多对巨蟒的歉意。)

  6

经验丰富的云研究人员会告诉你,关于云还有很多东西需要学习。

  7

http://mqtt.org/

  8

https://en.wikipedia.org/wiki/Representational_state_transfer