精通-Arduino-全-

精通 Arduino(全)

原文:zh.annas-archive.org/md5/9ee23a5cc9c893935dd980cf1d94561b

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

精通 Arduino是充分利用 Arduino 的全面指南。这本实用、直截了当的指南教你所有需要的电子和编程技能,以创建高级 Arduino 项目。本书充满了你可以练习的真实世界项目,将书中的所有知识结合起来,并赋予你从本书中的示例构建自己的机器人的技能。最后两章讨论了无线技术及其在项目中的应用。

本书从电子学的基础知识开始,确保你在继续前进之前理解了组件、电路和原型制作。然后,它对代码执行同样的功能,带你进入 Arduino IDE,并展示如何将 Arduino 连接到计算机并运行简单的项目。

在基础知识介绍完毕后,本书接下来的 10 章将专注于围绕特定组件的小型项目,例如 LCD 显示屏、步进电机或语音合成器。每一章都会让你熟悉相关的技术,如何使用它进行构建、编程,以及如何在你的项目中使用它。

本书面向的对象

精通 Arduino适合任何想要实验 Arduino 板并构建简单项目的人。不需要任何先前的知识,因为本书涵盖了电子和编码的基础知识。

本书涵盖的内容

第一章,Arduino 入门,通过简要介绍 Arduino 的历史和不同版本,向读者介绍 Arduino。我们还探讨了引脚及其用途。

第二章,基础电子学,向读者介绍了电和电子学的基础知识。我们还向读者介绍了基本电子元件,并讨论了它们的用途。

第三章,电路图,向读者介绍了电路和电路设计。它还介绍了电路特性,如电压、电流和电阻,以及它们如何影响电路。我们还向读者介绍了 Fritzing 工具,该工具可用于电路设计。

第四章,基础原型制作,向读者介绍了原型制作,并展示了他们如何创建项目的基本原型。我们还讨论了使用 Arduino 创建原型所需的工具。

第五章,Arduino IDE,向读者介绍了 Arduino IDE 和 Arduino Web 编辑器。我们将向读者展示他们如何使用 IDE 和 Web 编辑器来编程 Arduino。

第六章,Arduino 编程基础,为读者介绍用于编程 Arduino 的语言以及文件布局。

第七章,Arduino 编程进阶,向读者展示他们如何与 Arduino 的引脚头交互。我们还讨论了更高级的主题,如结构、联合和类。

第八章,运动传感器,是第一个“项目”章节。我们展示了如何使用 HC-SR01 运动传感器与 Arduino 一起使用。

第九章,环境传感器,帮助读者使用温度和湿度传感器以及雨量传感器构建一个基本的气象站。

第十章,避障与碰撞检测,教导读者如何使用碰撞传感器、红外避障传感器和超声波测距仪来感知附近物体。

第十一章,灯光的乐趣,教导读者如何使用 Arduino 控制 RGB LED 和 NeoPixel。

第十二章,声音的乐趣,展示读者如何使用压电蜂鸣器和 8 欧姆扬声器以及 Arduino 的 tone 库来创建声音和音乐。它还教导读者如何使用 Arduino 播放 RTTTL(铃声文本传输语言)铃声。

第十三章,使用 LCD 显示屏,教导读者如何连接和使用 Nokia 5110 LCD 显示屏与 Arduino。

第十四章,语音识别和语音合成,教导读者如何使用 MOVI 语音识别和语音合成盾牌创建一个语音激活的温度设备,该设备会告诉他们温度。

第十五章,直流电机和电机控制器,教导读者如何使用 L298 电机控制器和 L293D h-bridge 电机驱动器使用直流电机。

第十六章,伺服电机,教导读者如何使用外部电源的伺服电机来创建一个机械爪。

第十七章,使用继电器,向读者展示如何使用继电器允许 Arduino 控制交流电源设备。

第十八章,远程控制 Arduino,向读者展示如何使用 RF(射频)和 IR(红外)遥控器来控制 Arduino。

第十九章,创建机器人,向读者展示如何将前一章中获得的知识应用于设计机器人。我们实际上并没有设计机器人,而是展示如何使用这些部件,以便读者可以设计自己的作品。

第二十章,蓝牙低功耗,向读者介绍蓝牙低功耗以及如何使用 Arduino 与 HM-10 蓝牙低功耗无线电模块。

第二十一章,蓝牙经典,向读者介绍蓝牙经典以及如何使用 Arduino 与 HC-05 蓝牙无线电模块。

要充分利用本书

  • 本书假设读者没有电子学、编程或 Arduino 的先验知识。本书涵盖了所需的所有内容。

下载示例代码文件

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

您可以通过以下步骤下载代码文件:

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

  2. 选择“支持”标签。

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

  4. 在搜索框中输入书名,并遵循屏幕上的说明。

文件下载后,请确保使用最新版本的以下软件解压缩或提取文件夹:

  • Windows 上的 WinRAR/7-Zip

  • Mac 上的 Zipeg/iZip/UnRarX

  • Linux 上的 7-Zip/PeaZip

本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Mastering-Arduino。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富的图书和视频目录的代码包可供选择,请访问github.com/PacktPublishing/。查看它们吧!

下载彩色图像

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

使用的约定

本书使用了多种文本约定。

CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件作为系统中的另一个磁盘挂载。”

代码块设置如下:

#define BUTTON_ONE 12
#define LED_ONE 11

void setup() {
  pinMode(BUTTON_ONE, INPUT);
  pinMode(LED_ONE, OUTPUT);
}

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

display.clearDisplay();
display.drawPixel(10, 10, BLACK);
display.display();

任何命令行输入或输出都应如下编写:

at+nameBuddy at+name?

粗体: 表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“从管理面板中选择系统信息。”

警告或重要注意事项看起来像这样。

小贴士和技巧看起来像这样。

联系我们

我们始终欢迎读者的反馈。

一般反馈: 请通过电子邮件 customercare@packtpub.com 发送,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请通过电子邮件 customercare@packtpub.com 联系我们。

勘误: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,我们将不胜感激。请访问 www.packt.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

盗版: 如果您在互联网上发现我们作品的任何非法副本,我们将不胜感激,如果您能提供位置地址或网站名称,我们将不胜感激。请通过电子邮件 copyright@packt.com 联系我们,并附上材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com.

评论

请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 公司可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

如需了解 Packt 的更多信息,请访问 packt.com.

第一章:什么是 Arduino

您是否曾看过一个小玩意儿并想知道它是如何工作的?您是否想创建自己的酷炫和令人兴奋的电子产品项目,但不知道如何开始?您决定开始阅读这本书是迈出的一个极好的第一步。

在这本书中,我们将教您所有开始使用 Arduino 所需的知识。从基本的电子和原型制作到设置 Arduino 开发环境以及编程,一切都会涵盖。这本书还有许多示例项目,向您展示如何使用这些知识进行实际应用。在我们到达所有这些有趣的内容之前,让我们先看看 Arduino 本身,并熟悉它。

在本章中,您将学习:

  • Arduino 板是什么

  • 如何给 Arduino 板供电

  • Arduino 扩展板是什么

  • Arduino 板上引脚的功能

  • 了解通用和兼容的 Arduino 板

Arduino 是一家公司,开发板,社区以及一种思维方式。正如您很快就会发现的,Arduino 也是意大利北部一家酒吧的名字。虽然我们可以从写几章关于 Arduino 这个名字代表的一切开始这本书,但这不是这本书的主题。这本书是关于教您如何使用 Arduino 开发板来构建有趣和令人兴奋的项目。除非另有说明,本书中任何地方提到 Arduino 时,我们都会指的是 Arduino 开发板。然而,我们确实认为要真正理解 Arduino 板,您至少应该对其历史有一个基本的了解;因此,我们将从向您简要介绍该板及其前身的历史开始。

Arduino 的历史

2003 年,Hernando Barragan 开始在意大利的互动设计学院 IvreaIDII)攻读硕士学位时,开始了一个名为Wiring的项目。当时的学生使用一块价值 100 美元的微控制器板,需要额外的硬件和软件才能使用。Massimo Banzi 和 Casey Reas,因其在 Processing 语言上的工作而知名,是他的论文导师。项目的名字是Wiring: Prototyping Physical Interaction Design

您可以在此处查看论文:people.interactionivrea.org/h.barragan/thesis/thesis_low_res.pdf

论文的目的在于创建一个低成本且易于使用的工具,以便非工程师能够创建数字项目。为了做到这一点,Hernando 希望抽象出电子的复杂细节,让用户专注于他们的项目。这意味着它必须通过简单地将其插入主机计算机来工作,并且有一个易于使用的界面来编程它。

第一版原型使用了Parallax Javelin Stamp微控制器,它使用 Java 编程语言的一个子集。这个解决方案需要 Parallax 专有工具来编译、链接并将项目上传到微控制器;因此,它不符合项目的需求,因为 Wiring 项目将是一个开源项目。

第二版原型使用了基于 Atmel ARM 的91R40008 微控制器。赫尔南多使用这个新微控制器获得了更好的结果;然而,他确定这个微控制器过于复杂,几乎不可能手工焊接在电路板上。

第三版原型使用了Atmel ATmega128微控制器和MAVRIC微控制器板。赫尔南多使用这个微控制器取得了巨大成功。他使用 Brian Dan 编写的一个工具Avrdude轻松地将新程序上传到板上。

Avrdude 至今仍在使用,可以在www.nongnu.org/avrdude/找到。

由于 FTDI 的硬件在 Linux、Windows 和 macOS 平台上易于获取驱动程序,因此选择了它用于 USB 到串行通信。这使得 Wiring 项目能够与所有三个主要平台兼容。

2004 年,IDII 订购并支付了 25 块 Wiring 电路板。这些电路板由 SERP 制造,包括 ATmega128 微控制器、FTDI USB 到串行硬件、连接到引脚的板上 LED 以及串行 RX/TX LED。使用这些电路板进行了可用性测试,结果非常好。

2004 年以优异成绩毕业后,赫尔南多回到了他的祖国哥伦比亚,在 Universidad de Los Andes 教书,并继续在 Wiring 项目上工作。2005 年 5 月,赫尔南多订购了 200 块电路板,开始在 IDII 之外组装第一块 Wiring 电路板。他大约以 60 美元的价格出售了这些电路板。到 2005 年底,Wiring 已经在世界各地的许多地方使用。

此外,2005 年,第一块 Arduino 板被创建。Arduino 板使用了更便宜的 ATmega128 微控制器以降低成本。Arduino 团队分叉了 Wiring 代码,并添加了对这块板的支持。

初始 Arduino 核心团队由 Massimo Banzi、David Cuartielles、Tom Igoe、Gianluca Martino 和 David Mellis 组成。赫尔南多没有被邀请参与这个项目。有多个不同参与者的说法解释了为什么他没有被邀请。

我对这些故事哪些是真的哪些是假的没有任何第一手资料;因此,对于这本书,我将只保留已知的真相,即赫尔南多没有被邀请参与 Arduino 项目

Arduino 团队坚信开源硬件和软件。他们认为,通过开放平台,更多的人将能够访问并参与其中。开放平台的原因之一是 IDII 已经用完了资金,即将关闭。通过开源平台,他们知道它将生存下来,不会被他人利用。

团队最初决定将板的价格定为 30 美元。他们认为这将使它对学生以及个人都容易获得。他们还决定将板做成蓝色,这与当时大多数其他绿色板不同。另一个有助于增加板受欢迎程度的设计决策是给它提供大量的输入和输出引脚。当时的大多数板都限制了 I/O 的数量以降低成本。

最初,团队订购了 300 块印刷电路板来进行可用性测试。他们把这些板分发给 IDII 的学生,并给出了三个简单的指示:在网上查找组装说明,组装你的板并使用它来创建一些东西。这次测试取得了巨大成功,因为学生们能够组装这些板,并使用它们创建了众多项目。

在这次测试不久之后,人们开始听说这块板,并想要一块属于自己的。项目开始起飞;然而,它仍然缺少一个名字。在讨论名字时,团队在当地一家由 Massimo Banzi 常去的酒吧喝酒。酒吧的名字是Bar Di Re Arduino,新的板因此被称为 Arduino。

什么是 Arduino?

Arduino 的核心是微控制器。微控制器是一个独立的、单芯片集成电路,其中包含 CPU、只读存储器、随机存取存储器和各种 I/O 总线。大多数 Arduino 板使用 Atmel 8 位 AVR 微控制器。

本书主要使用的 Arduino UNO R3 板使用 ATmega328 芯片。这个芯片是一个基于 8 位 RISC 的微控制器,具有 32KB 的读写能力闪存,1KB EEPROM,2KB SRAM,23 个通用 I/O 引脚和 32 个通用寄存器。如果您不理解所有这些规格,请不要过于担心,因为我们将通过 Arduino 板提供的接口与微控制器进行交互。当您开始开发更复杂的应用程序时,了解这些规格是有好处的,因为它们确实对我们能做什么有限制。

构成 Arduino 平台的全部硬件和软件都以开源形式分发,并受 GNU 较小通用公共许可证LGPL)或 GNU 通用公共许可证GPL)的许可。这允许任何人制造和分发 Arduino 板,并导致了众多通用、成本更低的 Arduino 兼容板的产生。

您可以在 Arduino 网站上找到有关许可证和 Arduino 板的更多信息:www.arduino.cc.

探索 Arduino UNO R3

Arduino 是一个开源的硬件和软件平台,它功能强大且易于使用。您可以从 GitHub 上的任何 Arduino 仓库查看并下载代码:github.com/arduino。这个平台吸引了全世界的电子爱好者和创客社区的想象力。它使人们能够以低廉的成本进行电子原型实验,并看到他们的项目变为现实。这些项目可以从简单地使 LED 闪烁或记录温度到控制 3D 打印机或制作机器人。

虽然 Arduino 有多种型号,但在这本书中,我们将主要使用非常流行的 Arduino UNO R3 板。以下照片显示了 Arduino Uno 的板布局,其中标明了主要连接器:

图片

在本书中,当我们提到 Arduino Uno 板或 Uno 板时,我们指的是前面照片中展示的 Arduino Uno R3 板。

如我们所见,今天的 Arduino Uno 仍然使用原始 Arduino 设计者选择的蓝色,以帮助他们的电路板脱颖而出。以下列出了 Arduino Uno 的主要组件:

  • 直流电源输入:直流电源输入可以使用交流转直流电源适配器或电池。电源可以通过 2.1 毫米中心正极插头连接。Arduino Uno 在 5 伏电压下运行,但最大输入电压为 20 伏;然而,建议不要使用超过 12V。

  • 电压调节器:Arduino 使用线性调节器来控制进入电路板的电压。

  • USB 端口:USB 端口可用于供电和编程电路板。

  • 重置按钮:按下此按钮将重置电路板。

  • USB 的 ICSP:电路板上的串行编程引脚用于在 USB 接口芯片上烧录固件。

  • ATmega328 的 ICSP:电路板上的串行编程引脚用于在 ATmega 微控制器上烧录固件。

  • 数字和 PWM 连接器:这些标记为 0 到 13 的引脚可以用作数字输入或输出引脚。带有波浪线(~)标记的引脚也可以用于脉冲宽度调制PWM)输出。

  • 模拟输入连接器:标记为 A0 到 A5 的引脚可用于模拟输入。这些引脚可以用来读取模拟传感器的输出。

  • 电源和外部重置:此引脚头中的引脚为外部设备和传感器提供来自 Arduino 的接地和电源。Arduino 也可以通过这些引脚供电。还有一个可以用来重置 Arduino 的重置引脚。

  • ATmega328:Arduino Uno 板的微控制器。

数字/PWM/模拟输入/电源/重置连接器统称为引脚头。这些头部的引脚允许 Arduino 与外部传感器和其他设备通信。让我们看看我们可以用哪些不同的方式为 Arduino 板供电。

为 Arduino 供电

Arduino 可以通过三种方式之一供电:通过 VIN/GND 引脚、直流电源输入端口或 USB 端口。

使用 VIN/GND 引脚为 Arduino 供电

电源和外部复位引脚中的 VIN 和 GND 引脚可以用来使用外部电池为 Arduino 供电。以这种方式为 Arduino 供电主要用于我们希望连接电池,串联,并通过开关来开启和关闭 Arduino 的电源。以下照片说明了这一点:

图片

不建议我们以这种方式为 Arduino 供电,除非我们正在寻找最昂贵且寿命最短的方式来为 Arduino 供电。我们可以使用串联的六节 AA 电池,这将提供与前面照片中 9V 电池相同的电压,但容量大约是四倍。仍然不建议我们以这种方式为 Arduino 供电,因为它会相当昂贵。

除非有特定需要使用电池为 Arduino 供电,否则我会避免使用它们。

使用直流电源输入为 Arduino 供电

直流电源输入连接器可以与交流转直流电源适配器或电池一起使用,为 Arduino 供电。该连接器有一个 2.1 毫米中心正极的母座。虽然 Arduino 在 5 伏时运行,但可以使用最大 20 伏的输入;然而,正如之前所述,建议不要使用超过 12V。

我们可以使用类似以下照片中所示的交流转直流可调电源适配器,通过直流电源输入连接器为 Arduino 供电:

图片

使用这个适配器,您可以调整输出电压到所需的电压。您可以在网上或大多数销售电子产品的商店找到类似的电源。

使用 USB 连接器为 Arduino 供电

使用 USB 连接器为 Arduino 供电是我通常使用的方法。这是迄今为止为 Arduino 供电最容易、最安全且最经济的方式。您可以直接从计算机的 USB 端口或从以下照片中所示的 USB 充电宝为 Arduino 供电:

图片

这是一种非常经济且简单的方法来为 Arduino 供电。它也可以用于需要移动性的机器人或类似项目;然而,当我们连接外壳或其他附件到 Arduino 时,我们需要小心确保 USB 连接器能够提供足够的电力。例如,在本书的后面部分,我们将探讨 MOVI 语音合成和语音识别外壳,该外壳消耗的电力过多,以至于在连接外壳时无法通过 USB 连接器为 Arduino 供电。

现在我们已经提到了 Arduino 外壳,让我们来看看它们是什么,以及它们可以提供哪些功能类型。

Arduino 外壳

Arduino 保护罩是一个模块化电路板,可以直接插入 Arduino 板的引脚头。这些保护罩将为 Arduino 板添加额外的功能。如果我们想连接到互联网、进行语音识别、控制直流电机或为 Arduino 添加其他功能,可能有一个保护罩可以帮助我们。虽然我们不是必须使用保护罩,但它们确实使向我们的 Arduino 板添加额外功能变得非常容易。

以下照片展示了几个保护罩的示例。我们将在本书后面的某些示例项目中使用保护罩:

图片

保护罩通过直接插入引脚头安装在 Arduino 之上。如果它们不使用相同的资源,我们还可以将一个保护罩堆叠在另一个保护罩之上。以下是 Arduino 上安装了两个保护罩的外观:

图片

Arduino 保护罩使向 Arduino Uno 添加功能变得极其简单。大多数保护罩通常都有很好的文档,这使得编程它们也非常容易。保护罩的缺点是它们通常比购买组件并将它们用面包板连接到 Arduino 的成本要高。

一些保护罩,例如 MOVI 语音合成和语音识别保护罩以及 Sparkfun Xbee 无线电模块保护罩,增加了无法简单作为一个单独组件添加的功能。对于这种功能,需要一个保护罩或外部电路板。

让我们更仔细地看看 Arduino Uno R3 的引脚头。

Arduino 引脚

Arduino Uno 引脚头总共有 31 个引脚。其中大部分可以配置为执行不同的功能。以下图表显示了各种引脚的用途:

图片

让我们看看不同的引脚有什么作用。

数字引脚

在连接外部传感器时,Arduino 的数字引脚是使用最多的。这些引脚可以配置为输入或输出。这些引脚默认为输入状态;因此,当我们使用引脚作为输入时,我们不需要明确声明它们为输入引脚;然而,这样做是一种良好的实践,因为它会使阅读我们代码的人更容易理解该引脚的用途。

数字引脚将有两个值之一:高(1),即 5V,或低(0),即 0V。一旦我们开始编程 Arduino,我们就会看到如何从这些引脚读取或写入。

模拟输入引脚

Arduino Uno 内置一个具有六个通道的模拟-数字ADC)转换器,这为我们提供了六个模拟输入引脚。ADC 将模拟信号转换为数字值。虽然数字引脚有两个值,要么是高要么是低,但模拟输入引脚的值从 0 到 1023,相对于 Arduino 的参考值。Arduino Uno 的参考值为 5V。

模拟输入引脚用于读取模拟传感器,如测距仪和温度传感器。如果我们的项目中数字引脚不足,六个模拟引脚也可以配置为数字引脚。

PWM 引脚

由于模拟输入引脚是为读取模拟传感器(输入)而设计的,PWM 引脚是为输出而设计的。PWM 是一种使用数字输出获得模拟结果的技术。

由于数字输出可以是开或关,为了获得模拟输出,数字输出会在高和低之间快速切换。信号处于高电平的时间百分比称为占空比。以下图表说明了这个概念:

我们有能力设置信号在高低之间切换的频率。这个频率以赫兹为单位,并设置信号每秒可以切换多少次。例如,如果我们设置频率为 500 Hz,这意味着信号每秒可以切换 500 次。

我们将在本书的几个示例中使用 PWM 引脚,并在我们学习如何编程 Arduino 时对它们进行更深入的探讨。

电源引脚

Arduino 有几个电源引脚。它们如下:

  • VIN:当我们使用外部电源为 Arduino 板供电时使用此引脚。这是本章“使用 VIN/GND 引脚为 Arduino 供电”部分中使用的引脚。

  • GND:这些是地引脚。

  • 5V:这是 5V 输出,用于为大多数传感器供电。

  • 3.3V:这是 3.3V 输出,可用于为与 3.3V 兼容的传感器供电。一些兼容 3.3V 传感器的列表可以在此找到:www.dfrobot.com/wiki/index.php/3.3V_Compatible_Device_List

  • 复位:此引脚可以通过外部源来重置 Arduino 板。

  • ioref:这是板的参考电压。对于 Arduino,这将是一个 5V。

串行引脚

这些引脚可用于串行通信。RX(数字引脚 0)用于接收,而 TX(数字引脚 1)用于传输。这些引脚直接连接到 USB 到 TTL 串行芯片。有一点需要注意,你不应该直接将这些引脚连接到 RS-232 串行端口,因为这会损坏你的板。

SPI 引脚

串行外设接口SPI)引脚用于微控制器与外围设备通信的同步串行数据协议。此协议始终有一个主设备和一个或多个从设备。引脚如下:

  • MISO主从输入输出引脚用于从从设备向主设备发送数据。

  • MOSI:该引脚的主从输出输入用于从主设备向从设备发送数据。

  • SCK串行时钟用于同步数据传输,并由主设备生成。

  • SS从机选择引脚告诉从机激活或进入睡眠状态。这用于选择哪个从设备应该接收主机的传输。

现在我们已经快速地看了 Arduino Uno R3 上的引脚,让我们来看看一些不同的 Arduino 板。

不同的 Arduino 板

有许多不同的官方 Arduino 板和模块,可用于各种目的。要查看所有不同的板,你可以访问 Arduino 产品页面(www.arduino.cc/en/Main/Products),那里列出了所有官方 Arduino 板。

虽然 Arduino Uno R3 在创客社区中是最受欢迎的 Arduino 板,以下列出了一些其他流行的板:

Arduino Micro

Arduino Micro 是 Arduino 家族中最小的板。它基于 ATmega32U4 微控制器。该板具有 20 个数字 I/O 引脚,其中 7 个可用于 PWM 输出,12 个可用于模拟输入。Micro 板和稍后我们将看到的 Nano 板可以用于 Arduino Uno 可能太大的项目。

Arduino Mega 2560

Arduino Mega 2560 是为最复杂的项目设计的。它具有 53 个数字 I/O 引脚、16 个模拟输入引脚和 15 个 PWM 输出引脚。它还拥有 4 个串行 UART 用于串行连接。如果你想创建一个像机器人这样的复杂项目,Mega 板是你想要开始的地方。

Lilypad

Arduino Lilypad 是为可穿戴项目设计的。它可以缝入织物中,并使用缝入织物中的电源和传感器。Lilypad 基于 ATmega168V 或 ATmega328V(低功耗版本)。该板具有 16 个数字 I/O、6 个模拟输入和 6 个 PWM 输出。

Arduino Nano

Nano 和 Micro 之间有很多相似之处。Micro 板是在 2012 年发布的,而 Nano 板是在 2008 年发布的。Nano 板具有 14 个数字 I/O 引脚、8 个模拟输入引脚和 6 个 PWM 输出引脚。根据这些规格,你可能认为你应该使用 Micro 板而不是 Nano 板,但是如果你查看像 Amazon 或 eBay 这样的大多数在线零售商,你可以以 Micro 板价格的一半左右购买到 Nano 板。

你会发现,Nano 板比 Micro 板更容易获得,因为市场上有很多通用的 Nano 板。我们在这本书的一些项目中也会使用 Nano 板。

通用的板

在本书的开头,我们提到 Arduino 是一个开源硬件和软件平台。所有原始硬件设计文件均在 Creative Commons Attribution Share-Alike 许可下发布。此许可允许个人和商业衍生品使用所有 Arduino 板,前提是它们要提及 Arduino 并在相同许可下发布其设计。这导致了许多价格较低的通用板的出现。

如果您在大多数在线零售商网站上搜索 Arduino 板,大多数板实际上并不是真正的 Arduino 板。如果您查看以下照片中的 Arduino Uno 板,您会注意到一个包含加号(+)和减号(-)的无限符号。那是官方 Arduino 标志,任何带有此标志的板都是正品 Arduino 板。

图片

在本书中,我们将主要使用通用 Arduino 板,因为它们更便宜,通常也更容易获得。以下照片显示了某些通用 Arduino 板的外观。第一张照片显示了两个通用 Arduino Uno 板:

图片

下一张照片显示了一个通用 Arduino Mega 2560 板:

图片

您会注意到这些通用板不包含 Arduino 标志;然而,它们仍然包含官方板的名称。虽然之前的通用板看起来与官方 Arduino 板非常相似,但这并不是必需的。一些制造商选择采用 Arduino 参考设计,并在其板上增加额外的功能。以下照片中的板就是这种类型的例子:

图片

DFRobot RoMeo BLE 板是一款兼容 Arduino 的机器人控制板,具有蓝牙 LE 4.0 功能。该板采用了 Arduino Uno 的设计,并增加了许多额外功能,例如内置蓝牙和集成双向直流电机驱动器。

无论您的项目是什么,您可能都能找到符合您需求的正品 Arduino 板或通用/兼容板。

摘要

在本章中,我们简要介绍了 Arduino 的历史,包括它从一篇硕士论文到完整商业项目的发展历程。这包括参观了一些不同的 Arduino 板。我们还展示了 Arduino 板的不同供电方式,并对各种引脚类型进行了简要说明。

在下一章中,我们将为您简要介绍电子和常用组件。

第二章:基本电子学

就像我所能记得的,我一直对电子学很着迷。当我七岁或八岁的时候,我记得拆开了一台手持式晶体管收音机,并挣扎了好几天才把它重新组装起来,而没有让我的父母知道我把它拆开了。尽管我会拆开小装置并重新组装它们,但我直到高中上了基础电子学课程才真正理解了基本的电子学。那门课给了我足够的知识,开始理解我一直在拆解的电子设备是如何工作的。它也为我后来所做的一切电子项目奠定了基础。

你在本章中将学到什么:

  • 电子电路的四个基本模块是什么

  • 什么是万用表

  • 一些更受欢迎的基本电子元件有哪些

  • 电的性质是什么

  • 如何使用欧姆定律

  • 如何计算功率

虽然 Arduino 板最初是为了抽象化电子的复杂细节,使用户能够专注于他们的项目而设计的,但我们仍然需要对电子和电有一个基本了解,以便将外部组件连接到 Arduino 板上,并确保我们不会因为那些组件而损坏板子。本章,连同第三章,电路图和第四章,基本原型制作,旨在为你提供一个关于电子、电和电路的基本理解,以便你可以构建自己的原型,并在此书后续章节的示例中工作。

本书涵盖的所有电子和电都是与直流DC)相关的,这与来自电源插座交流电AC)不同。直流电是电荷在一个方向上的流动,而交流电则周期性地改变方向。我们只在本书中涵盖直流电,因为它本质上比交流电更安全,Arduino 及其组件由直流电源供电。我们可以通过将电源适配器插入墙壁来为 Arduino 供电,正如我们在第一章,Arduino中看到的;然而,电源适配器将交流电源转换为直流电源。

让我们从查看任何电子项目的四个主要构建模块开始探索基本电子学。

电子构建模块

当我想构建一些东西时,无论是用 Arduino 进行原型设计,编写应用程序,还是建造通往后门的木阶,我通常会尝试将项目分解成单独的模块。这真的帮助我专注于设计的每个单独部分,而不是被整个设计所压倒。我们通常可以将电子项目分解成四个独立的模块。以下图表说明了这些模块是如何协同工作的:

让我们逐一查看这些块,从电源开始。

电源

电源是项目的电力来源。对于本书中的大多数实验,电源将是一些低压直流电源,例如在第一章电源 Arduino部分中讨论的电源,Arduino。我们将在本书的后续章节中展示如何使用 12V 电池通过电机控制器板为直流电机供电。

输入

输入块将包含向控制电路提供信息的电子组件。这些组件使控制电路能够感知外界。输入组件的例子包括:

  • 按钮或开关:按钮和开关可以用来开启或关闭电流的流动。当它们处于关闭状态时,电流不会通过电路;然而,当它们处于开启状态时,电流会通过电路。

  • 温度传感器:温度传感器根据温度调整电路中的电阻量,导致电压随温度变化。通过读取输入的电压量,控制电路可以计算出当前温度。

  • 测距仪:存在许多类型的测距传感器;然而,基本原理是传感器输出的电压根据物体与传感器的距离而变化。就像温度传感器一样,控制电路可以根据输入的电压来计算距离。

  • 接近传感器:这是一种当某个物体进入一定距离范围内时被触发的传感器。接近传感器通常是开启状态(全电压)或关闭状态(无电压)。

输出

输出块将包含执行某些操作的电子组件。这些组件使电路能够与外界交互或执行某些操作。输出组件的例子包括:

  • 电机:电机允许电路转动某些东西,如风扇或轮子。这种类型的组件将电能转化为做功的能源。

  • LED 灯:LED 可以用作指示器,向用户提供视觉反馈。这种类型的组件将电能转化为光。

  • 扬声器:扬声器可以用来向用户提供音频反馈。这种类型的组件将电能转化为声音。

控制电路

控制电路块是接收来自输入组件的信息、处理这些信息并控制输出组件的电子组件。为了本书的目的,控制电路将是 Arduino,但请注意,我们并不强制使用 Arduino 作为控制电路。我们可以创建自己的控制电路或使用其他类型的微控制器板,如树莓派贝格尔 Bone Black

此块图为我们提供了 Arduino 项目的整体视图。我们需要首先理解的是,这些块以及块中的组件是如何相互连接的。为此,我们需要对电子和电力有一个基本的了解。让我们从查看一些工具和常见电子元件开始。我们将从查看我们工具箱中可能最重要的工具开始,以帮助我们进行电子项目:万用表。

万用表

万用表,也称为伏欧姆表,是一种电子测量仪器,通常用于测量电压、电流和电阻。有模拟万用表通过移动指针来显示测量值,但如今大多数万用表都是数字的,并带有 LCD 显示屏,用于精确显示测量值。

万用表的价格可以从低至 10 美元,到一款非常棒的 Fluke 87V 高达 400 美元。除非你打算将万用表用于专业用途,否则我建议购买像以下照片中展示的低成本万用表:

数字万用表有三个主要部分,分别是显示屏、选择旋钮和探头端口。

显示屏会显示测量值。一些高端万用表具有背光显示屏,以便在低光环境下更容易阅读。

选择旋钮用于选择我们想要测量的内容。大多数万用表可以读取电阻(欧姆)、电压(伏特)和电流(安培)。一些高端万用表还具有测量额外项目的能力,并具有额外功能。

探头插入探头端口,这些端口通常位于仪表的前面。黑色探头应插入标有 COM 的端口,红色探头将插入其他端口之一,具体取决于要测量的内容。实际上,红色和黑色探头之间没有太大区别;然而,良好的做法是将黑色探头和电线连接到公共/接地,将红色探头和电线连接到电源,因为这是标准做法。

数字万用表非常易于使用。我们首先使用选择旋钮选择我们想要测量的内容。然后将黑色探头连接到电路的一端,红色探头连接到另一端,万用表将显示测量值。以下照片显示了如何测量 9V 电池剩余的电压:

你会注意到选择旋钮被设置为 20V,因此,万用表将测量高达 20V。以下照片显示了如何测量电阻器的电阻:

在这张照片中,我们正在测量一个 330K 欧姆的电阻。如果我们不小心将选择旋钮设置到低档,就会过载万用表,但几乎所有的万用表都有保护功能,因此,它们不会因为过载而损坏。当你过载一个万用表时,它们通常会在显示屏的最左边显示 1,如下面的照片所示:

开关图片

在前面的照片中,我们试图使用选择旋钮设置为最大 200K 欧姆来测量一个 330K 欧姆的电阻。如果你不习惯使用万用表,我建议你搜索“万用表视频教程”并观看一些关于如何使用它们的视频。

电子元件

我们将在项目中使用多种类型的电子元件。这些元件可以从许多在线零售商那里以相对较低的成本获得。熟悉一些更受欢迎的元件很重要,因为它们将用于我们创建的许多电路中。让我们从查看我们将几乎在所有电路中使用的元件开始,即电阻。

电阻

万用表图片

电阻将几乎用于我们创建的每一个电路。电阻是一种具有特定数量的电阻力且该阻力永远不会改变的电子元件。电阻限制电路中的电子流动。我们将在本章稍后看到电阻是如何工作的。

电位器

电子元件图片

电位器,也称为“电位计”,是一种可变电阻。电位器允许用户创建一个电路,其中可以改变电阻。电位器有多种形状、尺寸和值,但它们都有三个连接器。两个外接器之间的电阻是固定的,是电位器的最大电阻。当用户转动旋钮时,中心连接器和任一外接器之间的电阻会变化。

电位器有很多用途,包括音频控制、伺服电机的运动控制和调节灯光亮度。

开关

电阻图片

根据需要,可以使用几种类型的开关。前面的照片从左到右显示了按钮开关、微动开关和拨动开关。

按钮开关是按下按钮时被激活的开关。这些开关通常用于启动或停止某些动作。按钮开关的一些用途示例包括启动机器人运动、启动 LED 闪烁或从传感器捕获电流测量值。

微动开关是一种通过在开关的杠杆或按钮上施加光力来激活的开关。这些开关在许多家庭和工业应用中得到了广泛使用。微开关的一些用途示例包括 3D 打印机的限位开关、检测微波炉门是否关闭以及机器人的碰撞检测。

拨动开关通过移动杠杆到开或关位置来激活和去激活。拨动开关主要用于像开关灯一样打开和关闭物品。

晶体管

图片

晶体管是可以通过电信号触发的微小开关。晶体管是我们今天所生活的数字世界的构建块。集成电路由许多微小的晶体管组成。晶体管提供了所有数字设备所依赖的数字开/关信号。

晶体管有三个连接器,分别称为集电极、基极和发射极。对于 NPN 晶体管,集电极是左侧的引脚,接着是基极和发射极。对于 PNP 晶体管,左侧的引脚是发射极,接着是基极和集电极。集电极应连接到输入电源。发射极应连接到电路中的公共地。当施加一定量的电压时,基极会触发电流通过晶体管。基极充当开关,用于打开或关闭电流的流动。

对于直流电路,就像我们在这本书中将要使用的那样,所有组件都需要连接到公共地。例如,如果你有一个为 Arduino 供电的电池和一个为直流电机供电的电池,那么地(负)端将需要连接在一起,这样所有组件都连接到相同的公共地。这被称为电路中的公共地。

LED

图片

发光二极管或简称LED,是一种两引脚半导体器件,当通过一定量的电流时发出光。使用 LED 时,我们需要非常小心,不要施加过多的电流,因为它很容易烧毁。对于大多数 LED,推荐的电压将是 5 伏。

大多数 LED 有两个不同尺寸的连接器。长连接器是阳极,应连接到电源。短连接器是阴极,应连接到公共地。

电容器

图片

电容器有多种形状、尺寸和容量。前面的照片显示了两个小型电容器,它们可以用于低功耗电路,就像我们在这本书中将要使用的那样。

电容器存储和放电电能,类似于电池,但电容器的充电和放电速度很快,通常在几秒或几分之一秒内。电容器几乎用于所有电子设备,出于各种原因。电容器可用于平滑电流流动,过滤电流,只允许一定量的电流通过,或者用于像相机闪光灯那样的大电流快速爆发。

在处理电容器时,我们应该非常小心,确保在处理之前它们已经完全放电,以避免触电。

集成电路

图片

集成电路,简称IC,是一种包含数千或数百万个微小电阻器、晶体管和电容器的半导体晶圆,用于执行特定功能。有数百万种不同类型的 IC,例如 Arduino 中使用的 Atmel ATmega128 微控制器。几乎所有的电子产品都包含一些集成电路。

现在我们已经看到了将构成本书项目中的一些更流行的电子组件,让我们看看电是什么。

什么是电?

宇宙中的一切都是由原子组成的。原子由三个基本组成部分组成,即质子、中子和电子。质子和中子组成原子的核,而电子像月球绕地球运行一样绕核旋转。质子带正电,电子带负电。

电是由粒子带电而产生的。一些粒子带正电,而另一些粒子带负电。带相反电荷的粒子相互吸引,而带相同电荷的粒子相互排斥。

由于电子处于持续运动中,偶尔一个电子会从其原子中逃逸并加入另一个原子。电子逃逸的原子现在将带有净正电荷,而获得电子的原子将带有净负电荷。电就是这种电子的流动。

金属的导电性是通过电子从其原子中逃逸的难易程度来测量的。铜、银和金是一些最好的导电材料,因为它们只有一个价电子,或外层电子,它移动时阻力很小。

如果我们将一根铜线从电池的一端连接到另一端,如图所示,电子的流动将从负极流向正极。

实际上不要这样做,电线可能会变得非常热,电池也会很快耗尽。

图片

电流以这种方式流动的原因是相反电荷的粒子相互吸引,因此正电荷端吸引电子,使它们从负电荷端流向。想象电子流动的最简单方法之一是观察水通过管道的流动。现在让我们看看电的一些特性,从如何测量电子流动开始。

电流

在电子电路中,电流是带电粒子(电子)的流动。电流通过每秒通过电路被测点的带电粒子数量来测量。

电流以安培为单位测量,可以用大多数标准万用表测量。一安培等于每秒通过被测点的 6.241×10¹⁸个电子。

如果我们将电想象成通过管道流动的水,以下图表将帮助可视化电流:

图片

电压

电压是两点之间的势能差,其中一点比另一点有更多的带电粒子。这种电荷的差异称为电压。我们可以将电压视为电路内的压力,以推动带电粒子通过电路。电压以伏特为单位测量。

继续使用管道中的水类比,我们可以将电压视为管道内的水压,推动水通过。以下图表说明了这一点:

图片

电阻

电阻是电流流动被减少或阻碍的程度。所有电路都有一些来自构成电路的导线和组件的电阻;然而,大多数电路包括电阻器,这些电阻器向电路添加额外的电阻。这些电阻器使我们能够限制电路中带电粒子的流动。电阻以欧姆为单位测量。

再次使用管道中的水类比,电阻是阻碍水流的最小管道部分。

图片

现在让我们通过考察欧姆定律来了解这三个属性是如何共同工作的。

欧姆定律

欧姆定律指出,通过电路的电流与施加到电路上的电压成正比。这意味着如果电路的电压增加,那么如果电阻保持不变,电流也会增加。

欧姆定律还指出,通过电路的电流与电路的电阻成反比。这意味着如果电路的电阻增加,那么如果电压保持不变,电流流动将减少。

欧姆定律的标准公式表明电流等于电压除以电阻:

图片

虽然前面的公式是通常介绍欧姆定律的方式,但它实际上由三个公式组成:

I = V/R

R = V/I

V = I³R

在这本书中,我们将最常使用 V/I 来计算限制电路电流所需的电阻。我们通过向电路中添加电阻来限制电路中的电流。

在处理电子元件时,我们还应该知道另一个公式。这个公式用于计算功率。

什么是功率?

功率是每秒将电能转化为另一种类型能量(热能、光能或功)的数量。功率是一个重要的概念,因为它允许我们的电路做事情。例如,如果我们创建了一个由电池和电阻组成的电路,电阻将把电能转化为热能(或热能)。所有电阻都有一个它们可以处理的最大功率额定值,因此为了确保我们不损坏电路中的电阻,我们需要知道如何计算功率。功率是通过乘以电压和电流来计算的,并且以瓦特为单位测量。

图片

我们可以从在线零售商那里获得的低成本电阻大多数被评定为 1/4(或 0.25)瓦特,因此我们将假设本书项目中使用的电阻评定为那个 1/4 瓦特。现在假设在我们的项目中,我们正在一个 100 欧姆的电阻上施加 10 伏特的电压,你认为电阻能够承受吗?为了找出答案,我们首先需要做的是使用欧姆定律计算电流。我们将使用公式 V/R 来计算电流(= 10V/100 欧姆),得到 0.1 安培的电流。现在我们可以使用公式 VI 来计算功率(P*=0.1A * 10V),这将等于 1 瓦特,是电阻额定值的四倍,因此电阻会变得非常热,甚至可能损坏。

电阻色码

电阻的值是通过其身体上的色环标记的。大多数电阻包含四个色环,但也有一些电阻有五个和六个色环。以下图片展示了四个色环的电阻外观:

图片

对于四色环电阻,从左到右的前两个色环表示电阻的值。第三个色环是倍数,第四个色环是公差。具有五个色环的电阻使用前三个色环来表示电阻的值,然后第四个色环是倍数,第五个色环是公差。以下表格显示了颜色值:

颜色 倍数 公差
黑色 0 1
棕色 1 10
红色 2 100
橙色 3 1K
黄色 4 10K
绿色 5 100K
蓝色 6 1M
紫色 7 10M
灰色 8 100M
白色 9 1G
金色 0.1 5%
银色 0.01 10%

本节开头所示的电阻有四个颜色为黄-紫-橙-银的色带,这意味着该电阻是 47K 欧姆,公差为 10%。我们知道这一点是因为前两个色带(黄和紫)的值分别是 4 和 7。然后我们使用第三个色带(橙)作为乘数,其值为 1K。这给我们一个电阻值为 47 乘以 1K,即 47K。第四个色带(银)表示公差为 10%。

摘要

在本章中,我们简要介绍了电学以及本书后面将要使用的一些较受欢迎的电子元件。本章将为你提供足够的知识,帮助你开始构建非常基础的样机。建议一旦你开始进行更高级的项目,你应进一步阅读有关电学和电子元件的内容。

在下一章中,我们将探讨电路以及如何阅读电路图。

第三章:电路图

当我还是个孩子的时候,我越拆手持式晶体管收音机和游戏,就越容易识别电路板上的各种部件。然而,要弄清楚所有部件是如何一起工作的,却要困难得多。虽然在不了解电路的基本原理的情况下拆解和重新组装电子设备是可能的,但无法弄清楚设备实际上是如何工作的。一旦我了解了电路的工作原理,我不仅能够开始理解电子设备是如何工作的,而且我还能自己构建简单的电路。

在本章中,你将学习:

  • 什么是电路

  • 开路、闭路和短路是什么

  • 什么是 Fritzing 图

  • 什么是原理图

  • 串联电路和并联电路之间的区别是什么

在我们开始开发自己的电子项目之前,我们需要对电路是什么以及如何设计它们有一个基本了解。在本章中,我们将探讨我们开始设计和创建基本电路所需的知识。

什么是电路?

电路是电流流动的环形路径。在第二章《基础电子》中,我们描述电流为电子的流动。电路是这些电子流动的路径,为构成电路的组件提供动力。电子进入电路的点,电流开始的地方,被称为电源。电子离开电路的点被称为回路。在电源和回路之间,构成电路剩余部分的组件和电线被称为负载

在闭路中,电流从电源到回路的完整路径允许电流通过电路。以下图示展示了闭路的情况,其中开关是闭合的,允许电流通过电路。在这个电路中,我们正在为灯泡供电。这使得灯泡成为负载:

开路是指电路中存在断裂,阻止电流流动。以下图示展示了开路的情况,其中开关是打开的,从而阻止了电流流动:

最后两个例子展示了你家里的典型灯开关是如何工作的。当你打开开关时,电路闭合,灯就会亮;然而,当你关闭开关时,电路就开路,灯就会熄灭。

大多数万用表都有一个用于测试是否有开路的通断模式。如果你的万用表有通断模式,你应该看到一个类似这样的符号:

如果你将万用表设置为通断测试模式并将两个引脚接触在一起,万用表将发出声音。这个声音会让你知道你有一个闭合的电路。为了测试你的电路是闭合还是开路,将万用表置于通断测试模式,并将万用表的两个引脚放置在电路的不同位置,如果两点之间存在连接,万用表将发出声音。

最危险的电路是短路。短路是指电流几乎无阻力地流过电路。这通常是一个没有负载的电路。

你永远不应该创建这样的电路,因为这样有引发火灾的风险。

以下图表显示了短路的外观:

虽然前面的电路图看起来很漂亮,但用这样的电路图来展示复杂的电路几乎是不可能的。如果没有解释,其他人也很难理解这些电路图,因为这些电路图没有定义标准符号。表示电路的标准方式是使用原理图。

当提到电子电路时,原理图是一种包含电路中元素表示的图表。每种类型的电气元件在原理图上都有标准的符号。这些图表将显示每个组件是如何连接的,使阅读图表的任何人都能理解电路的工作原理。

本书中的所有电路图都将使用非常流行且免费的Fritzing应用程序创建,该应用程序可以在此处下载:fritzing.org/home/。在本书编写时,Fritzing 的最新版本是 0.9.3b,也是本书中所有电路图以及随下载代码一起提供的电路图的版本。在我们查看 Fritzing 可以生成哪些类型的电路图之前,让我们先看看 Fritzing 是什么。

Fritzing

Fritzing 是一个开源项目,旨在开发用于设计电子硬件的软件。这款软件的设计灵感与 Arduino 类似,它抽象掉了电路图绘制的复杂性,因此用户可以专注于设计本身。本书中所有项目都将包含一个Fritzing 草图,这些草图是 Fritzing 应用程序中的一个项目,代表正在创建的电路。如果项目包含如 Arduino 这样的微控制器,这些项目可以包含电路图和代码。本书中我们将不会使用 Fritzing 的编码部分,而是使用标准的 Arduino IDE,但了解它的存在是很好的。

包含在可下载代码中的 Fritzing 绘图将允许您更仔细地检查每个项目的电路设计,并允许您在需要时调整设计。我们还将为每个项目在书中包含一个 Fritzing 图表或原理图,以展示如何创建电路。这两种类型的图表都将从 Fritzing 绘图中生成。

Fritzing 是一个非常强大的工具,建议您花些时间学习如何使用它来创建自己的图表。本章主要介绍如何阅读本书中包含的 Fritzing 和原理图。如果您想学习如何使用 Fritzing 软件,他们的网站有一个非常好的教程,您可以在以下链接找到:fritzing.org/learning/。让我们从 Fritzing 图表开始。

Fritzing 图表

Fritzing 图表是电路外观的图片表示,类似于本章之前展示的开放、关闭和短路图表;然而,Fritzing 图表有标准符号来表示每个部分,并且设计紧凑,使得设计更复杂的电路更加容易。以下 Fritzing 图表显示了 Arduino 连接到面包板上的 LED 和电阻器:

图片

该图中的电路从 Arduino 的 GND 引脚开始,它连接到面包板的上轨。LED 的阴极连接器也连接到上轨,从而将其连接到 Arduino 的公共地。LED 的阳极连接器连接到一个 220 欧姆的电阻器,该电阻器连接到 Arduino 的数字 12 引脚。这是一个很容易理解的图表,因为图表看起来就像组件本身。Fritzing 图表在电子网站和博客上展示图表时使用得很多,因为它更容易让初学者理解如何连接电路。

现在我们来看一下原理图。

原理图

虽然 Fritzing 图表使用图像来表示电路,但原理图使用符号。这使得图表更加紧凑,更容易表示复杂的电路。以下图表显示了原理图中一些常见电子元件的符号:

图片

我们将使用这些符号来表示电路中的组件。为了看到原理图的样子,让我们创建一个包含电池、电阻器和 LED 的简单电路。这个电路的 Fritzing 图表将看起来像这样:

图片

在这个图中,很容易看出需要哪些组件以及它们的连接方式;然而,在更复杂的电路中,可能更难看出所有组件的连接方式。Fritzing 图中的图像也没有显示组件的值。原理图提供了对电路的更清晰视图,如果图作者选择的话,还可以显示每个组件的值。以下图显示了相同的电路在原理图中的样子:

图片

此图显示了组件的连接方式以及所有组件的值。在这本书中,我们将使用 Fritzing 图和原理图,这样我们就可以熟悉这两种类型。这两种类型的图都将从本书提供的可下载代码中的 Fritzing 草图生成。

并联和串联电路

我们可以创建两种电路类型。这些是并联和串联电路。串联电路是一种电流只有一条路径从电源流向回路的电路。并联电路是一种具有多个电流流动路径的电路。理解这两种电路类型很重要,因为它们的特性不同。让我们首先看看串联电路。

串联电路

下面的原理图显示了串联电路:

图片

上述图显示了电流只有一条路径从电源流向回路的串联电路。在这个电路中,负载由两个电阻组成。一个电阻的值为 330 欧姆,另一个电阻的值为 220 欧姆。现在让我们看看串联电路的几个特性。

电阻

串联电路的总电阻是负载中每个组件电阻的总和。在示例电路中,负载由两个电阻组成,阻值分别为 220 欧姆和 330 欧姆。如果我们把这两个值加起来,我们得到总电阻为 550 欧姆。

电压

串联电路的总电压是串联连接的每个电源电压的总和。在示例电路中,只有一个电源,即 9 伏电池,因此总电压为 9 伏。如果我们使用 4 个 AA 电池(每个 1.5 伏)串联,而不是 9 伏电池,那么总电压将是 6 伏,因为 1.5V + 1.5V + 1.5V + 1.5V 等于 6 伏。

电流

在串联电路中,相同的电流流经电路的每一部分。这意味着我们可以在电路的任何一点测量电流,它将与电路中的其他任何点相同。

在示例电路中,我们知道总电压(9 伏)和总电阻(550 欧姆),因此我们可以使用欧姆定律计算电路的电流,其中I=V/R。这个公式将给我们 9 伏/550 欧姆或 0.0164 安培(16.4 毫安)的电流。

现在,让我们看看并联电路的特性。

并联电路

下面的电路图显示了并联电路:

上述图示显示了一个并联电路,其中电流有多个返回路径。电流可以通过 220 欧姆电阻分支、330 欧姆电阻分支或两个分支。

在这个示例电路中,就像串联电路一样,负载由两个电阻组成,阻值分别为 330 欧姆和 220 欧姆,然而,这次电阻是并联连接而不是串联。并联电路的特性与串联电路的特性非常不同。让我们看看这些特性。

电阻

并联电路的总电阻总是小于电路中任何分支的总电阻,并且添加额外的分支总是会降低电路的总电阻。

要计算并联电路的总电阻,取电路中每个组件电阻的倒数之和,这等于总电阻的倒数。听起来很复杂?其实并不复杂。这里就是公式:

这个公式将适用于所需的任何电阻值。在示例电路中,有两个电阻,阻值分别为 220 欧姆和 330 欧姆。因此,为了计算电路的电阻,我们将取 1/220 + 1/330 的倒数,这将等于 132 欧姆。

而不是试图手动计算并联电路的电阻,有很多在线计算器可以使用。

电压

并联电路的每个分支都将具有相同的电压。如果我们测量样本电路中任一分支的电压,它将显示 9 伏特的电压。

电流

在并联电路中,每个分支的电流将不同。电路的总电流(来自电源的电流)将等于每个分支电流的总和。这意味着来自电源的电流将等于通过 220 欧姆电阻分支和 330 欧姆电阻分支的电流之和。

让我们用欧姆定律来看看这是如何工作的。根据欧姆定律计算电流,我们使用公式 I=V/R,这意味着电流等于电压除以电阻。在之前的电阻部分,我们计算出电路中的电阻为 132 欧姆,并且我们知道电压是 9 伏特,因此,总电流将等于 9 伏特/132 欧姆或 0.0682 安培(68.2 毫安)。

我们还可以使用欧姆定律来计算每个分支的电流,知道每个分支的电压将是相同的 9 伏特。包含 220 欧姆电阻的分支中的电流将是 9 伏特/220 欧姆或 0.0409 安培(40.9 毫安)。包含 330 欧姆电阻的分支中的电流将是 9 伏特/330 欧姆或 0.0273 安培(27.3 毫安)。

现在我们可以将包含 220 欧姆电阻的分支电流与包含 330 欧姆电阻的分支电流相加,以得到总电流 40.9 毫安+ 27.3 毫安,等于从电源输出的相同 68.2 毫安。

现在我们已经理解了并联和串联电路之间的区别,在我们开始构建东西之前,还有一个概念我们需要理解。这个概念就是电压降。

电压降

电压降是指电路中每个元件由于阻抗而损失的电压量。阻抗是电路和元件对电流流动的阻力量。我们将在下一节中看到电压降的重要性,那时我们将点亮一个 LED。

点亮 LED

现在我们已经讨论了电路的基本知识,让我们来构建我们的第一个电路。在这个电路中,我们将简单地点亮一个 LED。我们将从查看电路图开始,它将告诉我们如何构建电路:

在这个图中,我们可以看到电路由一个 9 伏电源(9 伏电池)、一个 LED 和一个电阻组成。对于电路新手来说,第一个问题之一就是如何计算电阻的值。为了计算这个值,我们需要知道 LED 的正向电压以及它能够承受的最大电流。正向电压是 LED 导电所需的电压量以及将发生的电压降。如果你从一家好的电子产品店购买 LED,他们应该有一份数据表,上面会告诉你 LED 的正向电压和它能够承受的最大电流。

我建议将这些项目写下来,这样你以后忘记从哪里购买 LED 时,还可以找到。

我们使用正向电压和最大电流在以下公式中,以确定我们需要多大尺寸的电阻:

在这个公式中,标记为S的电压是电压源,在这个例子中是 9V。标记为LED的电压和电流分别是 LED 的正向电压和最大电流。我在这例子中使用的 LED 是 3.4 伏和 20 毫安。这给出了(9 - 3.4 伏)/20 毫安的值,即 280 欧姆。由于我没有 280 欧姆的电阻,所以我将其四舍五入到下一个最高的值,即 330 欧姆,如电路图所示。

以下照片展示了我是如何连接一切的:

如果你正确连接了所有东西,当电池连接时,LED 将会点亮。

摘要

在本章中,我们介绍了电路是什么,并描述了闭合、开路和短路之间的区别。我们还展示了如何阅读 Fritzing 图和原理图。通过比较并联和串联的区别,我们完成了本章,并制作了我们第一个电路来点亮 LED。

在下一章中,我们将探讨如何原型化电路,以及我们需要哪些工具和技能。

第四章:基本原型制作

原型制作是我们所有想法得以实现的地方。当我开始使用微控制器时,我非常想设计和构建整个项目,并看到它神奇地工作。我意识到我只是在让自己感到不知所措,并很快学会了如何将大型项目分解成更小的项目。然后我可以为这些较小的项目创建原型,以验证它们在整合到更大的项目之前是否工作正常。

在本章中,我们将学习:

  • 在哪里设置工作区域

  • 关于面包板的全部内容

  • 杜邦(跳线)电缆是什么

  • 如何进行项目原型制作

原型用于验证工作概念或过程的规格,而不是理论规格。在我们这本书中提到的原型,是一个用于测试概念或过程的模型。原型制作是创建和测试原型的过程。

当与 Arduino 和其他微控制器一起工作时,了解如何进行原型制作尤其重要,尤其是在处理大型项目时。这些大型项目通常可以被分解成多个较小的项目。然后我们可以为这些较小的项目各自进行原型制作,以确保它们按预期工作,然后再将它们整合到更大的项目中。在我们探讨如何进行原型制作之前,我们需要设置一个良好的工作区域来进行原型制作。

设置工作区域

当你刚开始使用 Arduino 构建项目时,找到一个好的工作区域很重要,不仅用于构建这些项目,还用于存放你创建的零件和原型。当我刚开始使用 Arduino 等微控制器时,我在看电视棒球比赛的同时,把客厅的桌子当作我的工作区域,很快意识到客厅并不是理想的工作场所,因为所有构建这些项目所需的工具和元件很快就会占据整个房间。在本节中,我们将探讨什么是一个好的工作区域。

在决定工作区域时,我们需要考虑的第一件事是静电。虽然静电问题没有很多人说的那么严重,但在设置原型制作区域时,我们仍需要考虑这一点。我个人在过去十五到二十年中从未因为静电而丢失任何电子元件;然而,我也不会在工作时穿蓬松的毛衣,抚摸我的狗,或者在地毯上摩擦我的脚。一般来说,我们应该尽量避免在厚地毯等容易产生静电的区域工作。

一些避免在处理 Arduino 或其他电子元件时产生静电的技巧:

  • 在触摸电子元件之前,触摸金属物体以释放任何积累的静电。

  • 如果可能的话,避免在工作区域放置地毯或穿着橡胶底鞋。

  • 注意你的衣物材质。羊毛毛衣或袜子可能导致静电积累,因此选择棉质衣物。

  • 在工作时避免抚摸任何毛茸茸的宠物。

  • 如果你的工作区域干燥,可以使用加湿器向空气中增加湿度。

在选择工作区域时,另一个考虑因素是要有一个足够大的桌子或长凳,以便存放你的项目,并且最好是那种你可以长时间存放项目的。当我使用客厅桌子作为我的工作区域时,我很快意识到每晚都要清理是多么不方便。

你还想要确保你的工作区域有足够的光线。即使你的工作区域有足够的照明,我也建议投资一个可以夹在桌子边缘的夹式台灯,在你需要额外照明时使用。相信我,会有需要额外照明来使电子元件上的小字迹清晰可见的时候。

你还想要在你的工作区域中寻找充足的存储空间。你做的原型越多,你将获得的零件就越多,你将需要一个靠近的地方来存放它们。当我最初开始使用微控制器时,我把大部分小零件存放在几个小塑料工具箱里。一开始效果很好,但后来我长大了,工具箱不够用了,于是我为较小的元件买了几个小零件柜,为较大的元件买了大塑料储物箱。

在开始使用 Arduino 之前,花时间设置一个合适的工作区域是非常值得的。我一开始没有这样做,这使我的原型制作变得非常困难。现在让我们看看在原型设计时,除了微控制器之外,你将使用的两个最重要的物品。这两个物品是面包板和杜邦线。

使用无焊点面包板

避免将 LED、电阻和其他电子元件直接连接到 Arduino 上是个好主意,因为这样很容易损坏 Arduino 的引脚,电路也会很快变得杂乱无章。在原型设计时,使用无焊点面包板连接组件要容易得多。

无焊点面包板使我们能够不使用焊接将电子元件连接在一起。你可以以不到十美元的价格购买到好的无焊点 MB-102 面包板。它们也是可重复使用的,这使得它们非常适合创建原型和实验电路设计。

我建议不要在面包板上节省太多,因为你将非常频繁地使用它们。你可以找到一些非常便宜且小的面包板,就像下面照片中的那样;然而,使用这些较小的面包板组织电路设计要困难得多。我确实使用这些进行快速和小规模的原型设计,但我不建议将这些作为你的主要面包板。

图片

对于使用 Arduino 进行原型设计,也不必非得购买带有内置电源连接器的面包板,就像下面照片中展示的那样:

图片

我会推荐购买一个像以下照片中显示的 MB 102 面包板:

图片

MB-102 面包板包含两个正电源轨和两个负电源轨,这使得将 Arduino 的电源和地引脚连接到原型所需的电子组件变得容易。在以下照片中,我们突出显示了四个电源轨,其中用红色突出显示的轨是正轨,用黑色突出显示的轨是负轨。正轨通常在面包板上用加号标记,负轨用减号标记:

图片

电源轨在面包板上水平运行,其中所有沿水平轨的连接器通常都是连接在一起的。这意味着,例如,如果我们把 Arduino 上的一个电压引脚连接到正轨上的任何一个引脚,那么该轨上的所有连接器都必须连接到 Arduino 上的电压引脚。

板中间的引脚是垂直连接的;然而,连接并没有穿过面包板的中心。以下照片显示了板中间的引脚是如何连接的:

图片

虽然前面的照片只显示了六列垂直行是如何连接的,但所有垂直行都是以这种方式连接的。需要注意的是,我们描述的 MB-102 面包板是如何配置的,大多数都是这样配置的;然而,并不是所有的都是这样配置的。

当你购买像面包板这样的组件时,一定要阅读规格说明,以确保你知道你购买的产品是否有任何不同之处。这可以为你节省数小时的问题排除。

如果我们撕掉面包板的背面,我们可以更好地看到引脚是如何连接的。以下照片显示了如果我们撕掉填充物,面包板的背面看起来是什么样子:

图片

当与 Arduino 一起工作时,我们通常可以直接从 Arduino 将电源和地连接到面包板上的电源轨;然而,有时我们可能需要外部电源。在这种情况下,他们确实有外部电源适配器,可以直接连接到面包板,并使我们能够使用 USB、AC 适配器和其他电源为项目供电。以下照片显示了一个这样的适配器以及我们如何将这些电源适配器连接到面包板:

图片

现在的问题是你是如何使用面包板将组件连接在一起,答案是杜邦(跳线)电缆。

杜邦(跳线)电缆

杜邦线,也称为跳线,用于在无焊面包板上连接组件。这些线有三种类型:公对公、公对母和母对母。当我们使用这些线与无焊面包板和 Arduino 一起使用时,我们通常使用两端都有公接头的线;然而,有些组件本身已经带有公接头,所以拥有一些公对母线也是值得的。以下照片显示了杜邦线末端的公接头样子:

图片

购买预制的杜邦线相当便宜,但如果你想要定制长度,你可以通过购买一个剥线钳和一些端子自己制作它们。这些线并不难制作,但我建议先从购买预制的开始。

原型设计

原型用于验证工作概念或过程的规格,而不是理论规格。在我们这本书中提到的原型,是一个用于测试概念或过程的模型。对于简单概念或过程,我们可能会为整个系统创建一个原型,但对于我复杂的系统,我们希望将系统分解成单个组件,并为每个组件创建一个原型。

使用 Arduino 的原型通常由一个 Arduino 微控制器和一个面包板组成,用于将组件连接到 Arduino。对于这些原型,我使用了我用 3D 打印机设计和打印的支架。以下照片显示了支架中包含 Arduino Uno、Nano 和一个面包板:

图片

这个支架设计用来固定 MB-102 面包板、Arduino Nano 以及 Arduino Uno 或 Mega。如果你有 3D 打印机,我在这本书的可下载代码中包含了这个支架的 STL 文件,这样你可以自己打印一个。这种支架在我们需要移动原型时特别有用,因为它能将所有东西保持在一起并保持有序。

在我们开始构建原型之前,我们需要有一个关于我们想要构建什么的想法。这个想法可能像简单地闪烁一个 LED 灯那样简单,也可能像创建一个自主机器人那样复杂。在开始一个项目时,想法总是第一步。

第二步是将想法分解成第二章中讨论的不同构建块。在我们将项目分解成不同的块之后,我们就会绘制需要原型化的电路图。在电路图完成后,我们就可以开始构建原型了。让我们更详细地看看这些步骤,从电子电路的四个构建块开始。

电子项目的四个构建块

在第二章,“基础电子”中,我们讨论了电子项目的四个基本模块。以下图表显示了这四个模块:

图片

将一个简单的原型,比如一个用于开关 LED 的电路,分解成单独的模块是非常容易的,但对于更复杂的项目来说,由于不同的组件,这会变得更加困难。

通过将组件分离成不同的模块,更容易看到如何将更大的项目分解成单独的原型。例如,如果我们想要创建一个自主机器人,我们可以看到其中一个输入是一个声纳测距仪,它将检测机器人前面的障碍物,而受测距仪输入影响的输出将是驱动机器人移动的电机。在这个例子中,如果我们希望测距仪检测到前面的障碍物,我们可能需要改变电机的方向。然后我们可以创建一个原型来测试这个特定系统的工作情况。

在这一步中,我们定义了输入和输出是什么,以及基于这些输入触发的输出是什么。这也是我们定义项目大部分逻辑的地方,因此我们可以设置原型。

当你刚开始构建这些项目时,你将想要为每个项目创建一个图。一旦你积累了经验,对于大多数小型到中型项目,你在这个步骤中可能不需要写下任何东西。这一步将变成只是在脑海中分解项目并确定输入和输出是什么。对于更大的项目,我们可能需要创建流程图甚至展示我们希望所有东西如何协同工作的图表。一旦我们进入这本书中的项目,我们将看到不同的方法来将组件分离成各自的模块,以及如何定义输入和输出的逻辑。

在我们将项目分解成具有单独输入和输出电路的各个原型之后,我们接下来就会想要为这些原型中的每一个创建电路图。

创建图表

一旦我们定义了电路的输入和输出以及项目的逻辑,下一步就是绘制电路图。我们想要确保我们绘制了所有创建的电路,即使是最基本的电路。这将帮助我们确定所需的电阻,并可视化我们想要如何组织和连接组件。

在创建电路图时,我建议使用在第三章,“电路图”中描述的 Fritzing 软件。现在让我们看看最后一步,构建原型。

构建原型

最后一步实际上是构建一个工作原型。这是前三个步骤中所有艰苦工作的集中体现,你将能够看到原型是否按预期工作。

我们都希望跳过前几个步骤,直接进入构建原型;然而,如果你花时间逐一完成这些步骤,你将更成功,损坏的部件也会更少。

现在我们来看看如何进行这些步骤并构建我们的第一个原型。你将想要跟随并自己构建这个原型,因为在我们学习编程 Arduino 的下一章中,我们将使用它。

第一个原型

我们将要构建的第一个原型相当简单,设计用于在接下来的几章中学习如何编程 Arduino。这个原型将包含一个按钮,用于打开或关闭 LED 灯,另一个可以打开或关闭的 LED 灯,以及一个 TMP36 温度传感器。

在这个原型中,我们将有两个输入(一个按钮和温度传感器)和两个输出(两个 LED 灯)。我们将使用 Arduino 的 5V 输出为组件供电。以下是这个原型的 Fritzing 图:

图片

在这个图中,从左到右,我们有按钮、两个 LED 灯和 TMP36 温度传感器。每个 LED 都使用了与第三章中“电路图”相同的 330 欧姆电阻。由于 Arduino 的电源只有 5 伏,我们可以将电阻值降低到 100 欧姆,但 330 欧姆的电阻也可以正常工作。

我们用来连接按钮的电阻被称为下拉电阻,因为其一边连接到地线。数字逻辑电路实际上可以有三种状态:高、低和浮空。浮空状态发生在引脚既没有被拉高也没有被拉低,而是被留下浮空。在这种浮空状态下,微控制器可能会不可预测地将这种状态解释为高或低。为了解决这个问题,我们使用下拉电阻将浮空状态拉低。

如果电阻连接到电源而不是地线,它将被视为一个上拉电阻。上拉电阻的工作原理与下拉电阻相同,但上拉电阻将浮空状态拉高,而不是拉低。在这本书的几个项目中,我们将使用下拉和上拉电阻。

按钮连接到 Arduino 的 12 号引脚;因此,我们可以通过检查 12 号引脚的状态来读取按钮的状态。LED 灯连接到 11 号和 3 号引脚;因此,我们可以通过向这些引脚发送数字高电平来打开它们,或者通过发送数字低电平来关闭它们。最后,TMP36 温度传感器的输出引脚(中间引脚)连接到模拟 5 号引脚,因为传感器的输出是模拟的。TMP36 温度传感器的电压和地线引脚连接到面包板上的电压和地线轨。

Fritzing 图表让原型看起来非常整洁和有组织;然而,大多数原型并不像那样。以下是我为这本书构建原型时的样子:

图片

很好,让原型整洁且看起来不错,但只要你能工作,这并不是必要的。当你只有一两条杜邦(跳线)电缆时,让原型整洁尤其困难。

你也会注意到横跨电源轨的短电线。在这个面包板上,电源轨并没有完全水平连接。这是一个配置略有不同的面包板示例。你可以看出电源轨并没有完全横跨,因为面包板上的红色和蓝色线条在中途有一个间隙;因此,我们使用了小电缆将两侧连接起来。有时我们需要不同的电源,所以电源轨这样分开连接非常有帮助,但在这个原型中,因为我们为所有组件使用相同的电源,所以我们把电源轨连接在一起。

摘要

在本章中,我们探讨了原型制作的基础以及我们在创建原型时应采取的步骤。本章末尾我们做的示例原型相当基础,但我们仍然走过了每一个步骤,以确保一切正确。我们将在本书后面的项目章节中逐步讲解这些步骤。

现在我们已经创建了我们的第一个原型,我们需要学习如何编程 Arduino。接下来的三章将教授你如何编程 Arduino。我们将从向你展示如何下载和安装 Arduino IDE 开始。

第五章:Arduino IDE

我已经作为爱好或专业程序员编程计算机超过 37 年。在这段时间里,我使用了许多不同的集成开发环境IDE)和文本编辑器来编写代码。我在一个既没有 IDE 也没有文本编辑器的电传打字机上编写了我的第一个 Hello World 程序。当我买了我的第一台电脑,Commodore VIC-20 时,我使用编程模式中的 BASIC 编程语言来编写我的程序。在编程模式下,你逐行输入代码,每行在你输入时进入内存,没有好的编辑器或 IDE。直到我学会了在 IBM PCjr 上用 C 编程语言编程,我才使用了我的第一个真正的 IDE。现在我想知道我是如何用那个来写任何东西的。

在本章中,你将学习:

  • Arduino 草图的含义

  • Arduino IDE 的含义

  • Arduino Web 编辑器的含义

  • 如何编写你的第一个草图

在我们查看 Arduino IDE 和 Web 编辑器之前,让我们看看 Arduino 草图的含义,以帮助我们理解为什么我们需要这些工具。

Arduino 草图

当我们为 Arduino 编程时,代码被放入一个项目中。这些项目被称为草图,草图存储在草图簿中。草图的设计尽可能简单直接,通过使用预构建的函数来抽象出许多编程 Arduino 的技术方面。

用于编程 Arduino 的编程语言与 C 编程语言非常相似。我们将在第六章“编程 Arduino – 基础”和第七章“编程 Arduino – 超越基础”中探讨如何编程 Arduino。本章的目的是让你熟悉草图是什么,并查看我们可以使用的工具。

在草图上传到 Arduino 之前,Arduino IDE 或 Web 编辑器必须经过几个步骤来构建应用程序。构建草图的第一个步骤是进行一些预处理,这会将草图转换为一个 C++(见加加)程序,然后传递给编译器,将这个可读的 C++ 代码转换为机器可读的指令(目标文件)。然后,这些目标文件与提供 Arduino 基本功能的标准 Arduino 库进行链接。链接的结果是一个可以上传到 Arduino 并运行的单一十六进制文件。好事是,当我们将草图上传到 Arduino 板时,Arduino 工具会自动执行所有这些操作。

如果我们要对 Arduino 草图的定义进行说明,我们会说它是一个包含可构建并上传到 Arduino 的人可读代码的项目。现在让我们看看两个可以帮助我们编写和构建这些草图的工具,首先是 Arduino IDE。

Arduino IDE

Arduino IDE 是一个集成开发环境,可以安装在 Windows、macOS 和基于 Linux 的计算机上。IDE 可以从以下 URL 的 Arduino 软件页面下载:www.arduino.cc/en/Main/Software。在本书编写时,IDE 的最新稳定版本是 1.8.5。

以下图像显示了首次运行 IDE 时的样子:

图片

我们将在本章末尾开始使用 IDE,我们将构建我们的第一个草图。我们将在本书的其余部分广泛使用它以及 Web 编辑器。现在,让我们简要地探索 IDE,以便我们熟悉其一些基本功能。

探索 IDE

IDE 的主开发窗口由四个区域组成。以下截图显示了这四个区域:

图片

IDE 的菜单栏与其他应用程序中的菜单栏类似,您点击其中一个选项,就会出现一个包含更多选项的下拉菜单。随着我们阅读本书,我们将查看一些常用的菜单选项。

命令栏提供了对五个最常用命令的快速访问。这些命令从左到右依次是验证、上传、新建、打开和保存。验证命令将尝试编译草图以验证代码中没有任何错误。上传命令将尝试构建并将草图上传到连接的 Arduino。新建命令将创建一个新的草图。打开命令将打开一个草图。最后,保存命令将保存草图。

注意:为了上传草图,您必须将 Arduino 连接到您正在工作的计算机,并在 IDE 中配置它,否则您将收到错误。我们将在“在 IDE 中配置 Arduino”部分查看如何进行此操作。

编码区域是我们编写 Arduino 代码的地方。您会注意到,当我们开始一个新的草图时,主标签页会自动创建两个函数(setup 和 loop)。在这本书中,我们将大量使用这些函数。当我们在本章末尾创建我们的第一个草图时,我们将查看这两个函数的作用。

状态区域用于 IDE 让我们知道当 IDE 正在执行编译、上传或验证草图等活动时发生了什么。

为了将草图上传到 Arduino,我们需要使用 USB 线将 Arduino 连接到 IDE 运行的计算机,并在 IDE 中配置它。在 IDE 中配置 Arduino 需要我们告诉它我们使用的是哪种类型的 Arduino 以及它在哪个端口上。让我们看看如何操作。

在 IDE 中配置 Arduino

要将 Arduino 连接到 IDE,IDE 首先需要知道正在使用哪种类型的 Arduino。为此,我们点击菜单栏中的工具选项,然后选择板子选项,如图所示:

截图

当选择板子选项时,IDE 显示一系列兼容的板子。这个列表看起来可能类似于以下截图:

截图

从这个列表中,选择你为你的项目使用的板子。一旦选择了板子,IDE 接下来需要知道 Arduino 连接到哪个端口。要选择端口,点击菜单栏中的工具菜单选项,然后选择端口选项,如图所示:

截图

当从该菜单选择端口选项时,IDE 显示它所知道的端口的列表。这个列表应该看起来类似于以下截图:

截图

你可能有一个不同的端口列表;然而,通常很明显应该选择哪个端口,因为如果它看到了板子,它会显示连接到端口的板子的名称。大多数时候,IDE 会自动为你选择正确的端口。

一旦选择了板子和端口,IDE 就准备好将编译好的草图上传到板子。

现在我们已经看到了如何使用 Arduino IDE,让我们看看如何设置和使用 Arduino Web 编辑器。

Arduino web editor

Arduino Web 编辑器使我们能够在大多数网络浏览器中创建和上传草图。官方上,Web 编辑器通过安装插件支持 Chrome、Firefox、Safari 和 Edge 浏览器。

Web 编辑器是 Arduino Create 项目的一部分,可以从这里访问:create.arduino.cc

在你能够安装插件并使用 Web 编辑器之前,你需要创建一个免费的 Arduino 账户。一旦我们登录到我们的账户,网站将引导你安装插件。一旦插件安装完成,你应该会看到一个类似的页面:

截图

让我们探索 Web 编辑器,看看如何使用它。

探索

Web 编辑器的四个主要区域如图所示:

截图

Web 编辑器的菜单栏使我们能够快速访问某些项目,如示例、库和串行监视器。我们将在本章后面讨论这些项目。

命令栏提供了对常用命令的快速访问,并使我们能够选择我们正在使用的板子。带有勾选标记的图标将验证草图,带有箭头的图标将编译并将草图上传到 Arduino。带有三个点的图标打开一个菜单,使我们能够保存、重命名、下载和删除当前草图。

代码区域是我们编写 Arduino 代码的地方。与 Arduino IDE 一样,你会注意到当开始一个新的草图时,setup()loop()函数会自动创建。

状态区域被 IDE 用来告诉我们当 IDE 在进行诸如编译、上传或验证草图等操作时发生了什么。让我们看看如何在 Web 编辑器中配置 Arduino,以便我们可以将其草图上传到它。

在 IDE 中配置 Arduino

为了将草图上传到 Arduino,我们需要用 USB 线将 Arduino 连接到 Web 编辑器运行的计算机。Arduino 连接到计算机后,我们可以在 Web 编辑器中通过点击“选择板或端口”部分来选择 Arduino 和端口。如果 Web 编辑器识别到 Arduino 板,你应该在下拉菜单中看到 Arduino 板和端口的列表。列表看起来像以下截图:

如果你看到 Arduino,请选择它,然后你将能够上传编译后的草图。学习 Arduino IDE 或 Web 编辑器最好的方法是使用它们,我们将在整本书中广泛使用它们。

让我们来看看 Arduino IDE 和 Web 编辑器的一些功能,从包含的示例开始。

示例

Arduino IDE 和 Web 编辑器中包含了许多示例。这些示例是简单的草图,展示了 Arduino 命令及其使用方法。这些示例从演示如何读取和写入数字 I/O 的非常基础的草图到展示如何使用传感器的更高级的草图。虽然这些示例旨在演示 Arduino 命令,但它们也可以用作编写 Arduino 良好代码的示例。

要访问 Arduino IDE 中的示例,请点击菜单栏中的“文件”选项,然后转到“示例”选项。你会看到一个与以下截图类似的示例类别列表:

如果你进入任何一个类别,你会看到该类别的示例列表。如果你选择一个示例,比如在“基础”类别下的DigitalReadSerial示例,示例的代码将在新窗口中加载,草图看起来类似于以下截图:

使用 Web 编辑器,要加载示例,请从菜单栏中选择“示例”选项。与 Arduino IDE 一样,我们将看到相同的示例类别列表。这个列表看起来类似于以下截图:

然后,我们可以选择任何类别以查看示例列表。Web Editor 中的示例与 Arduino IDE 中的示例的不同之处在于,Web Editor 中的大多数示例还包括布局(Fritzing)和电路图,显示如何创建可以与示例一起使用的电路。例如,如果我们选择与 Arduino IDE 中相同的DigitalReadSerial示例,我们不仅会看到草图代码,还会看到以下截图所示的布局:

截图

Web Editor 中包含的图表非常适合初学者,因为它们显示了如何构建电路,而无需完全理解示例的代码。

有许多外部库可以与 Arduino 一起使用。让我们看看什么是库。

Arduino 库

与大多数开发平台一样,Arduino 环境可以通过库进行扩展。这些库提供了额外的功能,我们可以在我们的草图(sketch)中使用,例如提供对特定硬件的访问、操作数据和添加额外功能,如任务调度器(Arduino Cron Library)。IDE 和 Web Editor 中内置了许多库,但我们也可以下载其他库或构建自己的库。

要访问 Arduino IDE 中的库,我们从菜单栏中选择 Sketch 选项,然后选择 Include Library 选项。这将显示另一个菜单,允许您加载库或管理库。此菜单应类似于以下截图:

截图

如果您选择任何内置库,头文件将自动包含在您的草图(sketch)中。我们将在第六章“编程 Arduino – 基础”和第七章“编程 Arduino – 超越基础”中了解更多关于头文件的信息。

选择 Manage Libraries 选项使我们能够下载和安装 Arduino IDE 标准安装中未包含的其他库。当一个库被下载并安装后,它将出现在 Arduino 库的快速列表中,并且可以像内置库一样使用。这些库中的许多也安装了示例代码,可以从 IDE 的示例部分访问。

要访问 Web Editor 中的库,从菜单栏中选择 Library 选项,菜单栏右侧将出现一个带有搜索栏的可用库列表。界面将类似于以下截图:

截图

Web 编辑器中包含数百个库。这使得访问库比 Arduino IDE 更容易,因为我们不需要安装它们。Web 编辑器还使共享需要库的草图更容易。当共享使用 Arduino IDE 创建的草图时,接收草图的人需要安装所需库的正确版本。这有时可能会变得复杂和令人困惑。使用 Web 编辑器时,当我们共享草图时,Web 编辑器将确保在编译草图时使用正确的库。

要将库添加到草图,请在搜索栏中搜索库,当库出现在列表中时,将鼠标悬停在它上面,就会出现一个包含按钮,如下面的截图所示:

图片

点击包含按钮,所需的头文件将出现在代码中,库将与草图一起包含。

在我们创建第一个草图之前,让我们看看串行监视器是什么。

串行监视器

串行监视器发送和/或接收文本,通常通过 USB 电缆。这使得我们能够在 Web 编辑器或 Arduino IDE 中接收调试消息或从键盘发送文本。我们将在本章末尾创建第一个草图时看到如何进行这两者。

要使用 Arduino IDE 或 Web 编辑器中的串行监视器,您必须首先将 Arduino 连接到计算机,并在 Arduino 和 IDE 或编辑器之间建立通信。

要在 Arduino IDE 中使用串行监视器,请点击 IDE 右上角的串行监视器图标。以下截图突出了串行监视器图标:

图片

串行监视器将在单独的窗口中打开,如下面的截图所示:

图片

输入部分使我们能够将文本发送到 Arduino。为此,请在输入框中输入文本,然后点击发送按钮或按回车键发送。Arduino 的文本将出现在输出部分。

要使用 Web 编辑器中的串行监视器,请点击菜单栏中的监视器选项,串行监视器将出现在菜单栏的右侧。以下截图显示了 Web 编辑器中的串行监视器:

图片

就像 Arduino IDE 中的串行监视器一样,要将文本发送到 Arduino,请在输入部分内的输入框中输入文本,然后点击发送按钮或按回车键发送。Arduino 的输出将出现在输出部分。

现在我们已经对 Arduino IDE 和 Web 编辑器的工作原理有了基本的了解,让我们创建一些草图。

Hello World

对于我们的第一个 Sketch,我们将使用 Arduino 创建传统的Hello World应用程序。这个应用程序将把“Hello World”这几个字输出到串行监视器;然而,在我们创建这个应用程序之前,我们需要了解setup()loop()函数的作用。

setup()函数在应用程序第一次启动时只运行一次。这个函数使我们能够在应用程序第一次启动时初始化任何变量或硬件。setup()函数完成后,loop()函数将被第一次调用。当loop()函数完成后,它将被再次调用,并继续循环,直到 Arduino 断电。

让我们演示这些函数是如何工作的。我们需要首先在 Arduino IDE 或 Web Editor 中创建一个新的 Sketch。要使用 Arduino IDE 创建一个新的 Sketch,我们可以使用命令栏上的新建图标,或者从菜单栏中选择文件 | 新建。要使用 Web IDE 创建一个新的 Sketch,点击菜单栏上的 Sketchbook 选项,然后点击 NEW SKETCH 按钮。

一旦创建了一个新的 Sketch,将以下代码添加到setup()函数中:

Serial.begin(9600);
Serial.println("Hello World");

我们接下来需要将 Arduino 连接到计算机,并建立 Arduino 与 IDE 或 Web Editor 之间的连接,如本章前面所述。然后我们可以通过在 Arduino IDE 和 Web Editor 的命令栏上的上传按钮上传 Sketch 来运行 Sketch。一旦代码编译并上传到 Arduino,你应该会在串行监视器中看到一次输出Hello World

现在,让我们从setup()函数中移除Serial.println("Hello World");这一行,并将其放入loop()函数中,使我们的代码看起来像这样:

void setup() { 
 Serial.begin(9600);   
}
void loop() { 
  Serial.println("Hello World"); 
}

然后我们可以上传 Sketch,我们应该会看到Hello World这个词在串行监视器中反复打印。文本将持续打印,直到我们从计算机上拔掉 Arduino。

在最后两个示例中,我们使用了Serial.println()函数将文本输出到串行监视器。这个函数会在文本末尾添加一个换行符。我们也可以使用Serial.print()函数,它将输出文本,但不会在末尾添加换行符。

输出到串行监视器的输出应该类似于以下内容:

现在我们已经看到了如何从 Arduino 输出文本到串行控制台,让我们看看 Arduino 如何通过创建一个回显应用程序来接收来自串行监视器的文本。

回显

一个回显应用程序将读取来自串行监视器的文本,并将其回显出来。

文本将被输入到输入字段中,如下面的截图所示:

文本将被回显,如下面的截图所示:

我们将首先创建一个新的 Sketch,并向其中添加以下代码:

byte bytesIn;

void setup() { 
  Serial.begin(9600);   
}

void loop() { 
  if (Serial.available()) { 
    bytesIn = Serial.read(); 
    Serial.write(bytesIn); 
  }
}

在此代码中,我们首先定义一个名为 bytesIn 的字节类型变量。然后在 setup() 函数中,将串行数据传输的波特率设置为 9600 波特。

loop() 函数中,我们使用 Serial.available() 函数来查看串行队列中是否有任何数据存储。Serial.available() 函数返回串行接收缓冲区中可用于读取的字节数。如果有可读的字节,代码随后使用 read() 函数读取字节,然后使用 write() 函数将字节写回串行监视器。

在此代码中使用的 write() 函数与之前示例中使用的 println() 函数之间的区别是,println() 函数将数据以人类可读的 ASCII 文本形式打印,而 write() 函数将数据以字节形式写入。在此示例中,如果我们使用 println() 函数,我们将看到输入字符的 ASCII 等价字符,而不是字符本身。

摘要

在本章中,我们了解了如何设置 Arduino IDE 和 Web 编辑器,还学习了它们的基本功能。在本章末尾,我们看到了如何使用串行监视器向 Arduino 发送和接收数据。

在下一章中,我们将开始学习如何编程 Arduino。

第六章:编程 Arduino - 基础知识

我记得我编程的时间已经很长了,从电传打字机和大型机到个人电脑和嵌入式设备,我编写过游戏、商业应用、网站和移动应用,但我可以诚实地说我最喜欢编程微控制器板,如 Arduino。

原因是,对于微控制器,我的程序可以通过各种传感器和电机与外部世界交互,而不是为简单的用户交互编程。使用微控制器,我们只受限于我们的想象力和独创性;然而,在我们开始征服世界之前,我们必须首先学习 Arduino 编程语言的基础知识。

在本章中,你将学习:

  • 变量和常量的含义以及如何使用它们

  • Arduino 编程语言提供的数学函数

  • 如何给我们的代码添加注释

  • 如何使用 Arduino 编程语言进行决策

  • 如何创建循环以重复代码块

在第五章 Arduino IDE 中,我们学习了如何使用 Arduino IDE 和 Web 编辑器。我们还检查了setup()loop()函数,并学习了如何使用它们。在本章和第七章 编程 Arduino – 超越基础知识 中,我们将学习 Arduino 编程语言以及如何使用该语言为 Arduino 开发应用程序。让我们从查看花括号开始。

花括号

左花括号({)定义了代码块开始的位置,而右花括号(})定义了结束的位置。我们在查看setup()loop()函数时看到了这些括号;然而,花括号不仅限于定义函数内的代码,它们还用于定义其他代码块。我们将在本章的决策循环部分看到这方面的例子。

每当有左花括号时,也必须有一个右花括号。当我们左右花括号数量相等时,我们说花括号是平衡的。不平衡的花括号可能导致编译器错误。如果你收到非常复杂且难以理解的编译器错误,你可能想从验证花括号是否平衡开始进行故障排除。

现在让我们看看分号的使用。

分号

每个语句的末尾都使用分号来分隔一个语句与下一个语句。如果一个语句不以分号结束,将导致编译时错误。忘记分号的错误文本非常明显,并将包括缺少分号的语句的行号。

分号也用于for循环中,以分隔不同的元素。我们将在本章的循环部分查看for循环。现在让我们看看我们如何给我们的代码添加注释。

注释

在我们的 Arduino 代码中可以使用两种类型的注释。这些是块注释和行注释。块注释用于注释文本跨越多行的情况,通常用于函数调用之前,让读者知道函数的作用。行注释用于需要简短的单行注释的情况,通常用于函数块内,让读者知道特定代码行的作用。

块注释以 /* 开头,以 */ 结尾。以下代码展示了块注释的外观:

/* This is a block comment
   This comment can span multiple lines
   This type of comment is usually found outside function calls
*/

行注释以 // 开头,直到行尾。行注释可以位于行的开头,或者可以在语句结束后。以下示例展示了行注释的外观:

// This is a single line comment
Serial.println("Hello World"); // comment after statement

总是给代码添加注释,让读者知道某些代码块的作用是一个好主意。现在让我们看看什么是变量。

变量

变量用于在代码中存储可以引用或操作的信息。变量被赋予一个唯一的名称,然后可以使用该名称访问信息。变量的名称应该是描述变量内容的,这样任何查看代码的人都能理解变量用于什么。

当从多个单词或短语创建名称时,使用驼峰式命名法,其中名称的第一个字母小写,但每个剩余单词的开头字母大写。一些驼峰式命名的例子有 ledOne、myVariable 和 redLedOnRightSide。

当声明一个变量时,通常给它一个初始值是一个好主意。这有助于避免在初始化之前意外访问变量。要声明一个变量,我们定义变量的类型,然后是变量的名称,然后如果我们打算给它一个初始值,我们添加等号后跟初始值。以下代码展示了我们如何这样做:

int myInt = 0;

在前面的代码行中,我们声明了一个名为 myInt 的整型 (int) 变量,并赋予其初始值 0。让我们看看一些在 Arduino 语言中使用的一些更流行的内置数据类型。

数据类型

Arduino 编程语言中有许多内置的数据类型。在本节中,我们将查看最常用的类型。让我们首先看看布尔类型。

布尔

布尔数据类型可以包含两个可能的值之一,truefalse。以下示例展示了如何声明一个布尔类型的变量:

boolean myBool = true;

前面的代码声明了一个名为 myBool 的布尔类型变量,并设置了初始值 true。布尔类型在标准的 Arduino 程序中使用得很多,所有比较操作,正如我们将在本章后面看到的,都返回布尔值。

字节

byte 数据类型是一个 8 位数值,其范围从 0 到 255。以下展示了如何声明 byte 类型的变量:

byte myByte = 128;

前面的代码声明了一个名为 myBytebyte 类型变量,其初始值为 128

整数

整数类型是当不需要小数时存储数值数据的主要数据类型。整数类型的变量可以包含从 -32,768 到 32,768 的数字。整数使用 int 关键字定义。

我们可以使用 unsigned 关键字声明一个无符号整数。无符号整数可以从 0 到 65,535,而正常整数范围是 -32,768 到 32,768。以下代码展示了如何定义一个常规整数和一个无符号整数:

int mySignedInt = 25;
unsigned int myUnsignedInt = 15;

在前面的代码中,我们声明了一个名为 mySignedInt 的整数类型变量,其初始值为 25。我们还声明了一个名为 myUnsignedInt 的无符号整数类型变量,其初始值为 15

在一些 Arduino 板(如 Due 或 SAMD)上,整数可以存储大于 32,768 和小于 -32,768 的值。由于大多数板子的整数范围是 -32,768 到 32,768,我建议始终假设你可以使用这个范围。

long 数据类型可以存储从 -2,147,483,648 到 2,147,483,647 的整数。以下代码展示了如何定义 long 变量:

long myLong = 123,456,789; 

在前面的代码中,我们声明了一个名为 myLonglong 类型变量,并给它赋值为 123,456,789。如果需要存储比整数类型更大的数字,则应避免使用 long 数据类型,因为它比整数类型使用更多的内存。

doublefloat

doublefloat 数据类型是浮点数,这意味着它们可以包含小数点。doublefloat 类型都可以存储从 -3.4028235E+38 到 3.4028235E+38 的值。

在大多数平台上,float 数据类型的精度为六或七位小数,而 double 数据类型通常有十五位小数;然而,在 Arduino 平台上并非如此。在 Arduino 平台上,doublefloat 类型完全相同,因此它们都具有六或七位小数的精度。

有两个非常好的理由不使用 doublefloat 值,除非你绝对需要小数。第一个原因是精度不准确,例如,6.0 除以 3.0 可能并不总是等于 2。你可能会得到类似 1.9999999999 的结果。第二个原因是浮点数运算比整数运算慢得多。

以下代码展示了如何定义一个 doublefloat 变量:

double myDouble = 1.25; 
float myFloat = 1.5; 

在前面的代码中,我们声明了一个名为 myDoubledouble 类型变量,其值为 1.25。我们还声明了一个名为 myFloatfloat 类型变量,其值为 1.5

字符

char数据类型通常被描述为存储字符的数据类型,然而,这在技术上是不正确的。char数据类型将字符存储为基于 ASCII 表的数值。当定义一个char变量时,它可以定义为表示字符的数字或字符本身,如下面的代码所示:

char myChar = 'A';
char myChar = 65;

在前面的代码中,两行都声明了一个名为myCharchar类型变量,其值为大写字母A。能够只存储单个字符的类型是有用的,但如果可以存储整个单词或句子会更有用。在本章的后面部分,我们将看到如何通过使用字符数组来存储单词或句子。

数组

数组是有序变量的集合,这些变量类型相同。数组中的每个变量称为元素,这些元素可以通过数组中的位置(索引)来访问。当定义一个数组时,我们必须声明将要存储在其中的变量的类型。定义数组有多种方式。以下示例展示了定义数组的一些基本方法:

int myInts[10];
int myInts[] = {1, 2, 3, 4};
int myInts[8] = {2, 4, 6, 8, 10};

这些示例中的每一个都定义了一个整数数组。第一个示例定义了一个未初始化的包含十个整数的数组。在定义未初始化的数组时要小心,因为内存位置永远不会初始化,这可能导致非常意外的结果。

第二个示例定义了一个包含四个整数的数组,所有元素都初始化了值。此数组自动调整大小以匹配初始化数组中的元素数量。

最后一个示例定义了一个包含八个整数的数组,其中前五个元素初始化了值,但最后三个元素未初始化。再次建议不要定义这样的数组,因为最后三个元素未初始化。一会儿我们将看到当我们尝试访问一个未初始化值的数组元素时会发生什么,但首先我们需要看到我们如何访问数组中的元素。

我们通过索引访问数组中的元素。我们将要检索的元素的索引放在两个方括号之间,如下面的代码所示:

int myInts[] = {1, 2, 3, 4};
int myInt = myInts[1];

在前面的代码中,我们首先定义了一个包含四个整数的数组并初始化了所有四个值。在下一行中,我们检索索引1处的元素并将值放入myInt变量中。

认为变量myInt包含数字1是不正确的,因为数组是零索引的,这意味着第一个值将在索引0处,因此myInt变量包含数字2。以下代码展示了这是如何工作的:

int myInts[] = {1, 2, 3, 4};
int myInt0 = myInts[0]; // contains 1
int myInt1 = myInts[1]; // contains 2
int myInt2 = myInts[2]; // contains 3
int myInt3 = myInts[3]; // contains 4

这段代码显示的是当我们声明了一个包含四个整数的数组时,这个数组的有效索引从0开始,到3结束。现在我们知道了如何访问数组,让我们看看当我们访问未初始化的元素时会发生什么。在脚本的setup()函数中添加以下代码并运行它:

int myInts[5];
Serial.println(myInts[0]);
Serial.println(myInts[1]);
Serial.println(myInts[2]);
Serial.println(myInts[3]);
Serial.println(myInts[4]);

在串行监视器中,你会看到打印出五个值,但它们可以是任何有效的整数值,因为元素从未被初始化。将新值赋给数组中的元素就像将值赋给任何变量一样。以下代码显示了这一点:

int myInts[2];
myInts[0] = 0;
myInts[1] = 1;

在前面的代码中,我们定义了一个包含两个整数的数组,并将值0赋给第一个元素,将值1赋给第二个元素。

我们还可以创建多维数组,它们基本上是数组的数组。以下代码显示了两种定义 3×4 整数数组的方法:

int myInts[3][4];
int myInts[][] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} };

多维数组中的元素可以通过索引访问,就像单维数组一样。以下代码显示了如何做到这一点:

int myInt = myInts[1,2]; // The value would be 6

现在我们已经看到了如何使用数组,让我们看看我们如何使用字符数组来存储单词和句子。

字符数组

在本章前面,我们看到了我们可以使用字符(char)类型来存储单个字符;然而,如果我们想存储整个单词或句子呢?我们可以使用字符数组来做到这一点。字符数组可以像其他数组一样初始化,如下面的代码所示:

char myStr[10]; 
char myStr[8] = {'A', 'r', 'd', 'u', 'i', 'n', 'o', '\0'}; 

通常,字符数组被称为字符串。在前面代码中,我们定义了一个未初始化的字符串,它可以包含多达十个字符,还有一个包含单词Arduino的字符数组。

你可能会注意到,在Arduino字符串的末尾有一个\0字符。这个字符代表一个空字符。当我们定义一个字符串时,我们应该始终以空字符结束字符串,这被称为空终止。通过以空字符结束字符串,像serial.println()这样的函数就知道字符串在内存中的结束位置。如果没有空字符,这些函数将继续读取内存,直到遇到空字符,这将在控制台中产生大量垃圾。

有更简单的方式来声明一个字符串,如下面的代码所示:

char myStr[] = "Arduino";
char myStr[10] = "Arduino";

在前面的代码中,第一行初始化了一个包含单词 Arduino 的字符串,数组自动调整大小,并在末尾添加了空终止符。在第二行中,我们初始化了一个包含单词 Arduino 的字符串,并包含额外的空间。空终止符被添加到单词 Arduino 的末尾。

Arduino 语言确实包含一个单独的字符串对象;然而,你会发现字符数组在示例代码中用得很多。我们将在第七章中查看字符串对象,Arduino 编程——超越基础

现在我们已经看到了如何使用变量和数组,让我们看看如何定义一个常量。

常量

常量是一个永远不会改变的值。在 Arduino 编程语言中,我们有两种声明常量的方式。我们可以使用const关键字或#define组件。

#define组件使我们能够在应用程序编译之前给一个常量值命名。编译器将在应用程序编译之前替换所有对这些常量的引用,用分配的值替换。这意味着定义的常量在内存中不占用任何程序空间,如果您试图将大型程序压缩到 Arduino Nano 中,这可能是一个优点。

#define组件确实有一些缺点,其中最大的缺点是如果为常量定义的名称也包含在其他常量或变量名称中,则该名称将被#define组件中定义的值替换。因此,当我使用#define来定义常量时,我通常使用全部大写字母作为名称。

以下代码展示了如何使用#define组件。您将在以下代码中注意到,使用#define组件时,行尾没有分号。当使用像#define这样的指令时,您不需要使用分号:

#define LED_PIN 8

声明常量的第二种方式是使用const关键字。const关键字是一个变量限定符,它修改了变量的行为,使其变为只读。这将使我们能够像使用任何其他变量一样使用该变量,但我们无法更改变量的值。如果我们尝试更改值,我们将收到编译时错误。

以下代码展示了如何使用const关键字:

const float pi = 3.14; 

const关键字通常比#define组件更受欢迎;然而,对于内存有限的设备,可以使用#define。现在让我们看看如何在 Arduino 编程语言中执行数学函数。

算术函数

Arduino 编程语言包括运算符,使我们能够计算两个操作数的和、差、积和商。要使用这些运算符,两个操作数必须是同一类型。这意味着,例如,我们能够计算两个整型变量的和;然而,如果我们不将其中一个变量强制转换为同一类型,我们就无法计算浮点变量和整型变量的和。我们将在本章稍后讨论类型转换。

以下示例展示了我们如何计算两个变量的和、差、积和商:

z = x + y; // calculates the sum of x and y
z = x - y; // calculates the difference of x and y
z = x * y; // calculates the product of x and y
z = x / y; // calculates the quotient of x and y

当我们执行除法操作时,有时我们只需要余数。为此,我们可以使用取模运算符。如果我们用 5 除以 2,结果将是 2.5,因此使用取模运算符的结果将是 5,因为那是余数。以下代码示例展示了如何使用取模运算符:

z = x % y // z will contain the remainder of x divided by y

Arduino 编程语言还包括复合赋值运算符,使我们能够将算术和变量赋值操作结合起来。这使得我们能够在执行算术运算的同时将结果赋值给原始变量。以下代码展示了 Arduino 编程语言中的复合运算符:

x++; // increments x by 1 and assigns the result to x
x--; // decrements x by 1 and assigns the result to x
x += y; //increments x by y and assigns the result to x
x -= y; //decrement x by y and assigns the result to x
x *= y; //multiplies x and y and assigns the result to x
x /= y; //divides x and y and assigns the result to x

也有许多数学函数,使我们能够执行各种常见的数学运算。以下代码展示了其中一些更常见的函数:

abs(x) // returns the absolute value of x
max(x,y) // returns the larger of the two values
min(x,y) //returns the smaller of the two values
pow(x,y) // returns the value of x raised to the power of y
sq(x) // returns the value of x squared
sqrt(x) // returns the square root of the value

现在我们已经看到了 Arduino 编程语言提供的算术运算符和函数,让我们来看看比较运算符。

比较运算符

Arduino 编程语言包括比较运算符,使我们能够比较两个操作数的值。比较运算符返回一个布尔值,指示比较是否为真或假。以下代码展示了我们如何使用这些运算符:

x == y // returns true if x is equal to y
x != y // returns true if x is not equal to y
x > y // returns true if x is greater than y
x < y // returns true if x is less than y
x >= y // returns true if x is greater or equal to y
x <= y // returns true if x is less than or equal to y

现在我们已经看到了 Arduino 编程语言提供的比较运算符,让我们来看看逻辑运算符。

逻辑运算符

Arduino 编程语言中包含几个逻辑运算符。这些运算符是 AND、OR 和 NOT 运算符。NOT 运算符使我们能够反转比较操作。AND 和 OR 运算符使我们能够将多个比较运算符组合成一步。以下代码展示了如何使用逻辑运算符:

(x > 5 && x < 10) // true if x is greater than 5 and less than 10
(x > 5 || x < 1) // true if x is greater than 5 or less than 1
!(x == y) // returns true if x is not equal to y

现在让我们看看如何进行变量类型转换。

类型转换

类型转换运算符将变量类型转换为不同的类型。这将使我们能够在不同类型的变量上执行操作,例如算术运算。例如,如果我们想要将两个变量相加,其中一个为浮点类型,另一个为整型,那么我们需要将其中一个变量进行类型转换,以便两个变量具有相同的类型。

需要注意的一点是,当我们把浮点值转换为整型值时,值会被截断而不是四舍五入。这意味着如果浮点变量包含值为 2.9,而我们将其转换为整型,值将是 2。考虑到这一点,我们通常希望将整型值转换为浮点值,而不是将浮点值转换为整型值,即使这意味着操作会花费更长的时间。

以下代码展示了如何将整型变量转换为浮点变量以执行算术计算:

int x = 5;
float y = 3.14;
float z = (float)x + y;

我们可以编写的几乎所有有用的应用程序都包含某种逻辑。这种逻辑通常是通过根据某些输入来决定做什么来实现的。这要求我们的应用程序做出决策。让我们看看如何使用 Arduino 编程语言来实现这一点。

决策

在 Arduino 编程语言中,我们使用if语句来做出决策。if语句将检查条件是否为真,如果是,将执行花括号内的代码块。

以下展示了if语句的语法:

if (condition) {
  // Code to execute
}

我们可以在if语句之后使用else语句来执行一个代码块,如果条件不成立。

下面的代码显示了if/else语句的语法:

if (condition) {
  // Code to execute if condition is true
} else {
  // Code to execute if condition is false
}

if语句中的条件可以是任何布尔值或返回布尔结果的运算。你会发现你代码中的大多数if语句都将包含比较操作。让我们看看一些将说明这一点的代码:

if (varA > varB) {
  Serial.println("varA is greater than varB");
} else {
  Serial.println("varB is greater or equal to varA");
}

在前面的代码中,我们使用了大于(>)比较运算符来查看varA是否大于varB。如果比较操作返回true,则将varA is greater than varB消息发送到控制台。如果比较操作返回false,则将varB is greater or equal to varA消息发送到控制台。

我们也可以通过使用带有else语句的if语句将if语句串联起来。以下代码说明了这一点:

if (varA == varB) {
  Serial.println("varA is equal to varB");
} else if (varA > varB) {
  Serial.println("varA is greater than varB");
} else {
  Serial.println("varB is greater than varA");
}

在前面的代码中,我们使用了等于(==)比较运算符来查看varA是否等于varB,如果是这样,我们就将varA is equal to varB消息发送到控制台。如果不相等,我们随后使用大于(>)比较运算符来查看varA是否大于varB,如果是这样,我们就将varA is greater than varB消息发送到控制台。如果两个比较操作都不成功,我们就将varA is equal to varB消息发送到控制台。

当使用elseif语句一起时,代码将执行第一个返回true条件的代码块,然后忽略else语句的其余部分。

使用ifelse语句是在应用程序中执行逻辑的最常见方式;然而,如果我们需要检查两个或三个以上的条件,代码可能会变得非常混乱。只需想象一下,如果最后的if/else示例有十个不同的条件需要检查。如果需要检查两个或三个以上的条件,我们可以使用switch/case语句。

switch/case语句接受一个值,将其与几个可能的匹配项进行比较,并根据第一个成功的匹配执行相应的代码块。当存在多个可能的匹配项时,switch语句是使用多个else-if语句的替代方案。如果有三个或更多的可能匹配项,则首选switch语句而不是else-if语句。switch语句的格式如下:

switch (var) { 
  case match1:
    // Code to execute if condition matches case
  break;
  case match2:
   // Code to execute if condition matches case
  break;
  case match3:
   // Code to execute if condition matches case
  break;
  default:
   // Code to execute if condition matches case
}

前面的代码以switch语句开始,并在switch语句的括号内有一个名为var的变量。代码将尝试将var变量的值与每个从第一个开始的case语句进行匹配,一旦找到匹配项,它就会执行代码。

每个case语句中的代码应该以break语句结束。需要break语句是因为一旦switch语句匹配到某个case,它不仅会执行该case语句中的代码,还会执行每个后续case语句中的代码。这意味着如果我们没有包含break语句,并且var变量与match2中的值匹配,match2match3default中的代码都将执行。代码遇到break语句后立即退出switch语句,防止其他case语句中的代码执行。

现在我们已经看到了如何在 Arduino 编程语言中做出决策,让我们看看如何执行循环。

循环

Arduino 编程语言有三种循环语句,即for循环、while循环和do/while循环。我们将从查看for循环开始。

for循环用于重复执行代码块。for循环通常用于执行特定次数的代码块或访问数组中的元素。for语句有三个部分。这些部分是初始化、条件和增量。

for语句的初始化部分,我们初始化需要初始化的任何变量。可以有多个初始化,用逗号分隔,但我建议避免在此处进行与for循环不直接相关的任何初始化。

for语句的条件部分期望一个返回truefalse的语句,它通常包含一个条件语句。这个循环部分决定了循环何时结束。当条件语句返回true时,for循环将继续执行代码块。一旦条件语句返回false,循环将退出。

for语句的增量部分用于改变变量的值。这种改变在每次循环执行时都会进行。以下代码展示了for语句的语法:

for (initialization; condition; change) { }

要查看实际代码的运行效果,以下展示了如何创建一个将循环十次的for语句:

for (int i = 0; i < 10; i++) {
  // Code to execute
}

在前面的代码中,for语句在初始化部分将i变量初始化为零。在条件部分,for语句检查i变量是否小于十,如果是,代码将继续循环。在变化部分,每次循环执行时,for循环将i变量增加一。在示例中,for循环最初将0赋值给i变量,然后每次循环增加它,直到i变量等于9

我们接下来要查看的下一个循环是while循环。while循环将重复执行一个代码块,直到while语句中定义的条件返回false。这可能是一个危险的循环,因为如果条件永远不会返回false,则循环将无限继续。while语句的语法如下:

while (condition) {
  // code to execute
}

while语句中的条件应该返回truefalse。这个条件通常是一个比较语句。以下代码展示了while语句的示例:

int x = 0;
while (x < 200) {
  // code to execute
  x++;
}

在前面的代码中,代码块在x变量小于200时执行。在代码块结束时,x变量增加 1。如果我们忘记在代码块中放置增加x的行,那么while循环将无限循环。确保将更改语句放在代码块内非常重要,否则循环将永远不会退出。

使用while循环时,条件在执行代码块之前被检查。这意味着如果当while语句首次被调用时条件返回false,则代码块将永远不会被执行。如果我们需要确保代码块在条件检查之前至少执行一次,我们可以使用do/while循环。

do/while循环与while循环完全相同,只是条件是在执行代码块之后而不是之前被检查的。以下代码展示了这个循环的语法:

do {
  // code to execute
} while (condition);

while循环一样,while语句中的条件应该返回truefalse,通常是一个比较语句。以下代码展示了do/while语句的示例:

int x = 0;
do {
  // code to execute
  x++;
} while (x < 200);

前面的代码将执行代码块 200 次,这与上一个while循环中的代码完全一样。唯一的区别是,在while循环中,条件在执行代码块之前被检查,而在do/while循环中,条件在执行代码块之后被检查。

函数

函数是一个具有特定任务的命名代码块。当创建一个新的草图时,IDE 或 Web 编辑器会自动为我们创建两个函数,正如我们在上一章中看到的;然而,我们不仅限于只有这两个函数,我们还有能力自己声明自定义函数。以下代码展示了创建函数的语法:

type name (parameters) { } 

要声明一个函数,我们需要声明函数的类型。函数类型是函数返回的值。如果函数不打算返回值,就像setup()loop()函数一样,那么函数类型将是void

一旦声明了函数类型,我们就定义函数的名称。函数名称应该是描述函数功能的。例如,如果我们正在创建一个可以打开或关闭 LED 的草图,那么我们可能会有名为ledOff()ledOn()的函数。在命名函数时使用驼峰式命名法是一种良好的做法,就像变量一样。

在函数名称之后,我们在括号内放置函数的参数。参数是通过调用它的代码传递给函数的数据。函数通常依赖于这些数据来执行其所需的逻辑。您可以在括号内通过逗号分隔来放置多个参数。

我们使用花括号来定义函数代码块的开始和结束。左花括号表示函数的开始,而右花括号表示函数的结束。以下示例展示了不同类型的函数:

void myFunction() {
  // Function code
}    

void myFunction(int param) {
  // Function code
}

int myFunction() {
  // Function code
}

int myFunction(int param) {
  // Function Code
}

第一个函数的返回类型是void,这意味着它不返回任何值。它也没有任何参数。这种类型的函数会被用来执行不需要返回任何信息回调用它的代码且不需要任何额外信息来执行其所需任务的任务。

第二个函数也有void类型的返回值,但它接受一个参数。这种类型的函数会在函数需要从调用它的代码中获取一些信息以执行其任务时使用。参数的第一部分是参数类型。在这个例子中,类型是int,这意味着数据将是整数类型。参数的第二部分是参数的名称。这意味着在这个例子中,参数名为param,是整数类型。要声明多个参数,我们可以用逗号将它们分开,如下所示:(int param1, int param2, float param3)

第三个函数的返回类型是int,这意味着它必须返回一个整数;然而,它不接受任何参数。如果我们想要从函数传递信息回调用它的代码,这种类型的函数会被使用。

第四个函数返回一个整数并接受一个参数。如果我们想要将信息传递回调用它的代码,并且需要从该代码中获取信息以执行其任务,这种类型的函数会被使用。

我们使用return语句从函数返回一个值。以下代码展示了如何做到这一点:

int myFunction() {
  var x = 1;
  var y = 2;
  return x + y;
}

当在函数内部创建变量时,就像我们在上一个示例中看到的那样,该变量只能在那个函数内部访问。以下代码说明了这一点:

int g = 1;
void function myFunction1() {
  int x1 = 2;
}
void function myFunction2() {
 int x2 = 3;
}

在前面的代码中,g变量,因为它是在函数外部声明的,所以可以被任何函数访问。当你像这样在函数外部声明变量时,它被视为一个全局变量。x1变量只能在myFunction1()函数内部访问,而x2变量只能在myFunction2()函数内部访问。

摘要

在本章中,我们介绍了 Arduino 编程语言的基础知识。本章中的材料为本书中涵盖的所有其他内容奠定了基础,因此,理解这里所展示的项目非常重要。

在下一章中,我们将探讨 Arduino 编程语言和 Arduino 开发环境的一些更高级的功能。

第七章:编程 Arduino - 超越基础

在我的开发生涯早期,我学到的其中一件事是,即使我只了解我使用的编程语言的基础知识,我也能编写一些相当惊人的应用程序;然而,这通常会使代码难以维护和阅读,同时也会给项目增加显著的开发时间。我总是告诉正在学习语言的人,在为严肃的项目使用它之前,花时间了解他们正在学习的语言的更高级功能。

在本章中,我们将学习:

  • 如何设置 Arduino 数字引脚的引脚模式

  • 如何获取和设置 Arduino 数字引脚的值

  • 如何获取和设置 Arduino 模拟引脚的值

  • 如何使用结构和联合

  • 如何使用额外的标签页

  • 如何使用类和对象

在上一章中,我们探讨了 Arduino 编程语言的基础知识。在本章中,我们将超越语言本身的基础。我们将从查看我们如何与 Arduino 上的数字引脚交互开始。

对于本章的示例,我们将使用在 第四章,基本原型 的末尾创建的原型。

设置数字引脚模式

在 第一章,Arduino 中,我们看到了 Arduino 有几个数字引脚,我们可以将外部传感器和其他设备连接到这些引脚上。在我们使用这些引脚之前,我们应该根据我们使用它们的目的来配置它们为输入或输出。为此,我们使用内置在 Arduino 编程语言中的 pinMode() 函数。通常对于较小的草图,我们在 setup() 函数中调用 pinMode() 函数;然而,这并不是必需的。以下代码展示了 pinMode() 函数的语法:

pinMode(pin, mode);

这个函数使用两个参数调用。第一个是我们设置的引脚号,第二个是引脚的模式。引脚的模式可以是 INPUT,从引脚读取值(外部传感器将值写入引脚),或者 OUTPUT,为引脚设置值。以下代码展示了如何使用此命令设置两个引脚的引脚模式:

pinMode( 11 , INPUT);
pinMode( 12 , OUTPUT);

在前面的代码中,我们将引脚 11 设置为输入,引脚 12 设置为输出。因此,我们将值写入引脚 11,并从引脚 12 读取值。

好的做法是永远不要像在最后一个例子中那样使用引脚号本身来访问 Arduino 上的引脚。而不是像这样使用引脚号,我们应该设置一个变量或常数,包含引脚的数字,然后在访问引脚时使用该变量或常数。这将防止我们在代码中输入错误的数字。

我的个人偏好是当引脚号不会改变时,使用 #define 来定义我使用的引脚号。这允许我将我的引脚定义与其他常数分开。

如果你希望使用常量而不是#define,这是完全可以接受的,有些人甚至会说这是首选的。

以下代码显示了如何在草图中使用pinMode()函数:

#define BUTTON_ONE 12
#define LED_ONE 11

void setup() {
  pinMode(BUTTON_ONE, INPUT);
  pinMode(LED_ONE, OUTPUT);
}

在前面的代码中,我们定义了代表两个引脚的常量。第一行将BUTTON_ONE定义为数字(引脚)12,第二行将LED_ONE定义为数字(引脚)11。然后,我们在setup()函数中将BUTTON_ONE引脚设置为输入模式,将LED_ONE引脚设置为输出模式。

pinMode()函数也可以用来通过设置引脚的模式为INPUT_PULLUP来配置内部上拉电阻。这将改变引脚在输入模式下的行为。

这些数字引脚可能有两个值之一:HIGHLOW。让我们看看我们如何设置数字引脚的值。

数字写入

在 Arduino 编程语言中设置数字引脚的值时,我们使用digitalWrite()函数。此函数的语法如下:

digitalWrite(pin, value);

digitalWrite()函数接受两个参数,第一个是引脚号,第二个是要设置的值。在设置数字引脚的值时,我们应该使用HIGHLOW。以下代码显示了如何进行此操作:

digitalWrite(LED_ONE, HIGH);
delay(500);
digitalWrite(LED_ONE, LOW);  
delay(500);

在前面的代码中,我们将由LED_ONE常量定义的引脚设置为HIGH,然后暂停半秒钟。Arduino 编程语言中的delay()函数会暂停脚本的执行一段时间。这个函数的时间是以毫秒为单位的。在delay()函数之后,我们接着将LED_ONE常量定义的引脚设置为LOW,并在返回到开始之前再等待半秒钟。

之前的代码可以用在loop()函数中闪烁一个 LED;然而,在我们这样做之前,我们需要定义LED_ONE常量并设置引脚模式。让我们看看闪烁 LED 所需的完整草图。

#define LED_ONE 11

void setup() {
  pinMode(LED_ONE, OUTPUT);
}

void loop() {
  digitalWrite(LED_ONE, HIGH);
  delay(500);
  digitalWrite(LED_ONE, LOW);
  delay(500);
}

此代码首先定义了LED_ONE常量并将其设置为11。然后,在setup()函数中设置了LED_ONE引脚的引脚模式。最后,将使 LED 闪烁的代码添加到loop()函数中。如果你连接了我们在第四章,“基本原型”中开发的原型,并运行此代码,你应该会看到其中一个 LED 闪烁。

现在我们已经知道了如何向数字引脚写入,让我们看看我们如何读取一个引脚的值。

数字读取

在 Arduino 编程语言中读取数字引脚的值时,我们使用digitalRead()函数。此函数的语法如下:

digitalRead(pin);

digitalRead()函数接受一个参数,即要读取的数字引脚的编号,并将返回一个整数值。以下代码显示了如何使用digitalRead()函数读取 Arduino 上的一个数字引脚:

int val = digitalRead(BUTTON_ONE);

使用此代码,digitalRead()函数将返回由BUTTON_ONE常量定义的引脚的值,并将该值放入名为val的变量中。val变量被定义为整数类型。然而,digitalRead()函数只会返回 0 或 1。我们可以使用我们在数字写入部分看到的相同的HIGHLOW常量来查看引脚是高电平还是低电平。使用这些常量是首选的,并且可以使你的代码更易读。

现在我们来看看如何使用digitalRead()函数读取按钮的状态。以下代码将读取我们在第四章中构建的原型基本原型制作的按钮状态:

#define BUTTON_ONE 12

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_ONE, INPUT);
} 

void loop() {
  int val = digitalRead(BUTTON_ONE);
  if (val == HIGH) {
    Serial.println("Button HIGH");
  } else {
    Serial.println("Button LOW");
  }
}

此代码首先定义了BUTTON_ONE常量并将其设置为12。串行监视器和按钮连接的引脚模式都在setup()函数中配置。在循环按钮中,使用digitalRead()函数读取引脚,并使用if语句比较返回的值与HIGH常量。如果它们相等,则将消息Button HIGH发送到串行监视器,否则发送消息Button LOW

如果在第四章中创建的原理图上运行此代码,基本原型制作,那么你应该会看到两个消息之一被打印到串行监视器,具体取决于按钮是否被按下。

现在我们来看看如何向 Arduino 上的模拟引脚写入数据。

模拟写入

模拟值是通过 Arduino 的脉冲宽度调制PWM)引脚写入的。在第一章中,Arduino,我们探讨了 PWM 是什么以及它们是如何工作的。在大多数 Arduino 板上,PWM 引脚被配置为引脚 3、5、6、9、10 和 11;然而,Arduino Mega 有更多可用的引脚用于 PWM 功能。

要执行模拟写入,我们使用analogWrite()函数,其语法如下:

analogWrite(pin, value); 

analogWrite()函数接受两个参数,第一个是引脚号,第二个是要设置的值。analogWrite()函数的值可以从 0 到 255。

让我们看看一个示例草图,看看我们如何使用analogWrite()函数来使 LED 淡入淡出:

#define LED_ONE 11

int val = 0;
int change = 5;

void setup()
{
  pinMode(LED_ONE, OUTPUT);
}

void loop()
{
  val += change;
  if (val > 250 || val < 5) {
    change *= -1;
  }
  analogWrite(LED_ONE, val);
  delay(100);
}

此代码首先定义了一个值为11LED_ONE常量。这将是要连接 LED 的引脚。还定义了两个全局变量,都是整数类型,分别命名为valchangeval整数将存储模拟引脚的当前值,而change整数将存储每次循环val整数应更改的量。

setup()函数中,由LED_ONE常量定义的引脚被设置为输出模式。这将使我们能够向引脚写入数据并改变连接到该引脚的 LED 的亮度。

loop()函数首先将change变量加到val变量上,并将结果存储在val变量中。如果val变量的值大于 250 或小于 5,我们将change变量乘以-1。这导致change变量在 5 和-5 之间旋转,从而使val变量在每次循环中增加或减少。最后,将val变量的值写入由LED_ONE常量定义的引脚,然后在循环回之前有一个短暂的延迟。

如果在第四章中创建的原型上运行此代码,基本原型制作,你应该会看到 LED 灯闪烁。现在让我们看看如何读取模拟引脚。

模拟读取

我们使用analogRead()函数从模拟引脚读取值。此函数将返回介于 0 到 1023 之间的值。这意味着如果传感器返回满电压 5V,那么analogRead()函数将返回 1023 的值,这导致每单位值为 0.0049V(我们将在示例代码中使用这个数字)。以下代码显示了analogRead()函数的语法:

analogRead(pin);

analogRead()函数接受一个参数,即要读取的引脚。以下代码使用analogRead()函数和一个 tmp36 温度传感器来确定当前温度:

#define TEMP_PIN 5

void setup() {
  Serial.begin(9600);
}

void loop() {
  int pinValue = analogRead(TEMP_PIN);
  double voltage = pinValue * 0.0049;
  double tempC = (voltage - .5) * 100.0;
  double tempF = (tempC * 1.8) + 32;
  Serial.print(tempC);
  Serial.print(" -  ");
  Serial.println(tempF);
  delay(2000);
}

上述代码首先定义了温度传感器连接的引脚,即模拟引脚 5。setup()函数配置串行监视器,以便应用程序可以将其打印到监视器上。

loop()函数首先读取模拟引脚并将值存储在pinValue变量中。要将此值转换为实际电压,我们将其乘以本节之前看到的 0.0049V 值。如果我们查看 tmp36 温度传感器的数据表,我们将确定(电压 - .5) *100.0是计算摄氏温度的正确公式。然后我们可以使用标准公式(摄氏温度 *1.8) + 32来确定华氏温度。最后,我们将这些值打印到串行监视器,并在开始下一次循环之前延迟两秒钟。

在这本书中,我们将大量使用digitalRead()digitalWrite()analogRead()analogWrite()函数,因此你会熟悉它们。

现在让我们看看结构体。

结构体

结构体是一种用户定义的复合数据类型,用于将多个变量组合在一起。结构体中的变量可以是不同类型,使我们能够将不同类型的关联数据存储在一起。以下代码显示了定义结构体的语法:

struct name {
  variable list
  .
  .
};

当定义结构体时,使用struct关键字后跟结构体的名称。然后定义变量列表,位于花括号之间。

让我们看看如何通过将之前使用 analogRead() 函数读取 TMP36 温度的草图改为使用结构来创建和使用结构。首先,我们需要定义一个结构来存储传感器的温度信息。我们将把这个结构命名为 tmp36_reading,它将包含三个双精度类型的变量。以下代码显示了如何定义这个结构:

struct tmp36_reading {
  double voltage;
  double tempC;
  double tempF;
};

上述代码定义了一个名为 tmp36_reading 的结构,它包含三个双精度类型的变量。请注意,结构中的变量不必是同一类型,只是这个结构中的所有单个变量都是双精度类型。

以下代码显示了如何创建 tmp36_reading 类型的变量:

struct tmp36_reading temp;

上述代码创建了一个名为 temp 的变量,其类型为 tmp36_reading。然后我们可以使用点语法来赋值或检索值,如下所示:

temp.voltage = pinValue * 0.0049;
temp.tempC = (temp.voltage - .5) * 100.0;
temp.tempF = (temp.tempC * 1.8) + 32;

在前面的代码中,我们为 tmp36_reading 结构的 voltagetempCtempF 变量赋值。现在让我们看看如何将这段代码集成到一个读取 TMP36 温度传感器的草图中去。以下是新草图的完整代码:

#define TEMP_PIN 5

struct tmp36_reading {
  double voltage;
  double tempC;
  double tempF;
};

void setup() {
  Serial.begin(9600);
}

void loop() {
  struct tmp36_reading temp;
  int pinValue = analogRead(TEMP_PIN);
  temp.voltage = pinValue * 0.0049;
  temp.tempC = (temp.voltage - .5) * 100.0;
  temp.tempF = (temp.tempC * 1.8) + 32;

  showTemp(temp);
  delay(2000);
}

void showTemp(struct tmp36_reading temp) {
  Serial.print(temp.tempC);
  Serial.print("  -  ");
  Serial.println(temp.tempF);
}

这个草图的功能与之前读取 TMP36 温度传感器的草图完全相同,但现在我们使用结构来存储传感器的值,而不是变量。

如果你有多组可以像这样组合在一起的价值,建议我们使用结构而不是变量,因为所有的值都组合在一个结构中。

现在,让我们看看另一种可能看起来与结构相似的特殊数据类型;然而,其功能有显著不同。

联合

联合是一种特殊的数据类型,它允许我们在单个定义中存储不同的数据类型,类似于结构;然而,在任何时候只能有一个成员包含数据。以下是如何定义联合的语法:

union name {
  variable list
  .
  .
};

如果语法看起来很像结构的语法。实际上,它确实是相同的语法,只是 struct/union 关键字不同。

让我们看看如何使用联合。以下代码定义了一个新的联合:

union some_data {
  int i;
  double d;
  char s[20];
};

上述代码定义了一个名为 some_data 的联合,它可以包含整数、双精度或字符字符串。最后一句中的关键字是 。与可以存储多个不同值的结构不同,联合一次只能存储一个值。以下代码将演示这一点:

union some_data {
  int i;
  double d;
  char s[20];
};

void setup() {
  Serial.begin(9600);
  union some_data myData;
  myData.i = 42;
  myData.d = 3.14;
  strcpy( myData.s, "Arduino");
  Serial.println(myData.s);
  Serial.println(myData.d);
  Serial.println(myData.i);
}

在前面的代码中,我们定义了一个名为some_data的联合体。然后在setup()函数中,我们创建了一个名为myDatasome_data联合体类型的实例。然后我们为联合体类型的每个成员分配值。整数成员设置为42,双精度浮点数成员设置为3.14,字符字符串设置为Arduino。当运行此代码时,我们将看到Arduino字符字符串被正确地打印到串行监视器中;然而,当整数和双精度浮点数成员被打印到串行监视器时,信息是不正确的。

在前面的例子中,当some_data.i成员设置为42时,some_data联合体将包含整数 42。然后当我们设置some_data.d成员为3.14时,42 的整数值被覆盖,some_data联合体现在将包含 3.14。最后,当我们设置some_data.s成员为Arduino时,它覆盖了some_data.d成员,因此some_data联合体现在包含字符串Arduino

在我们查看 Arduino 编程语言的更多功能之前,让我们看看 Arduino IDE 和 Web 编辑器的另一个功能。

添加标签页

当你开始处理更大、更复杂的项目时,迅速将代码分成单独的工作空间变得非常重要,因为这使你的代码更容易管理。为了做到这一点,在 Arduino IDE 和 Web 编辑器中,我们可以在草图中添加新的标签页。

要在 Arduino IDE 中添加一个新标签页,请点击位于 IDE 窗口右上角的倒三角形按钮,如下面的截图所示:

图片

在弹出的窗口中,点击“新建标签页”选项,你将在 Arduino IDE 窗口的代码部分下方看到一个橙色条。在这个橙色条中,你可以命名新标签页,然后按“确定”按钮创建标签页。以下截图显示了如何命名新标签页:

图片

一旦点击“确定”,就会创建一个新标签页,其名称如以下截图所示:

图片

我们可以在 Web 编辑器中创建一个新标签页,就像在 Arduino IDE 中做的那样。在 Web 编辑器中,有一个类似的倒三角形按钮。当点击该按钮时,将出现一个菜单,你可以选择“新建标签页”选项。一旦你命名了新标签页,它就会出现在 Web 编辑器中。

在我们开始向我们的项目添加标签页之前,我们需要有一个计划,即我们想要如何分离代码。我发现对于大型项目来说,只将setup()loop()函数放在主标签页上是一种好的做法。然后我为项目的每个功能区域创建一个标签页。例如,如果我制作了一个包含温度和雨量传感器的气象站,那么我的主标签页将包含setup()loop()函数,然后会有两个额外的标签页;一个用于温度传感器功能,另一个用于雨量传感器功能。

除了使用额外的标签进行代码外,对于较大的项目和库,将定义需要在多个标签中使用的常量的标签也是良好的实践。这些常量通常放入头文件中。头文件应该以 .h 扩展名命名。现在让我们看看如何使用标签。

使用标签

在创建新标签时,我们需要决定标签将包含什么。例如,在本节中,我们将创建两个新的标签。一个将命名为 led.h,另一个 ledled.h 文件将包含常量定义,而 led 文件将包含代码。

当我们创建一个以 .h 扩展名结尾的标签时,我们正在创建 C 语言中所谓的头文件。头文件是一个包含声明和宏定义的文件。这些标签可以随后包含在正常的代码标签中。在下一节中,我们将看到另一种类型的标签,即 cpp 标签。

一旦创建了新的标签,请将以下代码添加到 led.h 标签中:

#ifndef LED_H
#define LED_H

#define LED_ONE 3
#define LED_TWO 11
#endif

此代码将定义两个常量,它们是我们构建在 第四章 中,基本原型上的两个 LED 的引脚编号。#ifndef#endif 确保头文件在任何标签中只导入一次。#ifndef 会检查 LED_H 常量是否已定义,如果没有,则包含 #ifndef#endif 之间的代码。

现在,在 led 标签中添加以下代码:

void blink_led(int led) {
  digitalWrite(led, HIGH);
  delay(500);
  digitalWrite(led, LOW);
  delay(500);
}

blink_led() 函数包含一个单一参数,它将是我们要闪烁的 LED 的引脚。该函数本身将 LED 打开 1/2 秒,然后关闭。

现在,在主标签中,我们将在标签顶部需要包含一个 #include 语句来包含 led.h 头文件。以下代码显示了如何做到这一点:

#include "led.h"

#include 语句将头文件包含在标签中,使我们能够在代码中使用这些定义。如果我们试图在我们的代码中使用某个常量,但忘记了包含头文件,我们会收到一个错误,表明常量在此作用域中未声明,这意味着编译器无法找到常量的声明。

如果我们从正在工作的草图添加头文件,头文件的名称将被双引号包围。如果我们从单独的库中包含头文件,名称将被小于号和大于号包围。我们将在本书后面使用第三方库时看到这一点。

loop() 函数中,我们将想要从 led 标签中调用 blink_led() 函数。这里需要注意的是,我们只需要包含头文件的 #include 语句,而不是包含代码的标签。以下显示了主标签的代码:

#include "led.h"
void setup() {
  // put your setup code here, to run once:
  pinMode(LED_ONE, OUTPUT);
  pinMode(LED_TWO, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  blink_led(LED_ONE);
  delay(1000);
  blink_led(LED_TWO);
}

现在如果您连接了我们在第四章,“基本原型”中创建的原型,应该会看到 LED 灯依次闪烁。

在处理大型项目时,将代码分布在单独的标签页中是一种很好的组织方式。这使得维护和组织代码变得容易得多。

在为 Arduino 创建库时通常使用类。虽然创建库超出了本书的范围,但了解类是什么以及如何使用它们是很好的,因为我们将在这本书的某些部分使用库。

面向对象编程

面向对象编程OOP)是一种编程范式,它帮助我们通过类和对象将代码划分为可重用的组件。对象是为了模拟某物而设计的。例如,我们可以创建一个 LED 对象,该对象将包含我们想要用于 LED 的属性和功能;然而,在我们能够创建对象之前,我们需要为其创建一个蓝图。这个蓝图被称为。让我们通过创建一个帮助我们控制 LED 的类来了解这是如何工作的。

我们将首先创建两个新的标签页,分别命名为led.cppled.hled.h文件将包含类的定义,而led.cpp文件将包含代码。让我们首先将以下代码添加到led.h文件中:

#ifndef LED_H
#define LED_H

#define LED_ONE 3
#define LED_TWO 11

class Led{
  int ledPin;
  long onTime;
  long offTime;
  public:
    Led(int pin, long on, long off);
    void blinkLed();
    void turnOn();
    void turnOff();
};

#endif

此代码与“使用标签页”部分中的led.h文件类似,除了添加了Led类定义。Led类定义定义了类的三个属性(变量):ledPinonTimeoffTime。在此示例之前,我们使用的所有变量要么是全局变量,要么是在函数中定义的。类属性是在类中定义的变量,通常定义了关于对象的某些内容。在这个例子中,ledPin属性定义了 LED 连接到的引脚;onTime属性定义了保持 LED 开启的时间;offTime属性定义了保持 LED 关闭的时间。

在属性之后,定义了一个类的构造函数。构造函数用于创建类的实例,我们将在本节后面看到如何使用它。在构造函数之后,有三个类方法(函数)。类方法简单来说就是属于类的一部分的函数,通常定义了对象的功能。

led.h标签页中包含Led类的定义,而led.cpp标签页包含类的代码。让我们将以下代码添加到led.cpp标签页中:

#include "led.h"
#include "Arduino.h"

Led::Led(int pin, long on, long off) {
  ledPin = pin;
  pinMode(ledPin, OUTPUT);

  onTime = on;
  offTime = off;
}

void Led::turnOn() {
  digitalWrite(ledPin, HIGH); 
}

void Led::turnOff(){
  digitalWrite(ledPin, LOW);
}

void Led::blinkLed() {
  this->turnOn();
  delay(onTime);              
  this->turnOff();
  delay(offTime);
}

此代码首先导入两个头文件。第一个头文件是我们刚刚创建的led.h文件,第二个是Arduino.h头文件。Arduino.h头文件包含了所有自定义 Arduino 函数的定义。它自动添加到主标签页中;然而,如果您希望在其他标签页中使用 Arduino 自定义函数,就像这里需要的那样,我们需要导入此文件。

在导入之后是led.h标签页中定义的Led类的构造函数的实现。当我们实现一个类的构造函数或方法时,我们使用类名作为前缀,后跟两个冒号(::)。类的构造函数的名称必须与类名相同。因此,构造函数的实现是Led::Led。在构造函数中,我们设置类的属性和 LED 连接的引脚的模式。

接下来的两个类方法Led::turnOnLed::turnOff使用digitalWrite()方法来打开或关闭 LED。注意这两个方法如何在digitalWrite()方法中使用ledPin属性。这个属性在创建类时在构造函数中设置。

最后,Led::blinkLed()方法的实现被定义。此方法使用之前定义的Led::turnOnLed::turnOff方法来闪烁 LED。当我们调用一个类的成员方法时,我们使用连字符/大于号(->)一起使用,如blinkLed()方法所示。this关键字用于引用当前实例。

现在,让我们看看如何使用Led类。在主标签页中,我们首先需要做的是包含led.h文件。将以下行添加到标签页的顶部:

#include "led.h"

接下来,我们需要创建一个全局的Led类实例,并给它命名为led。为此,我们使用为该类创建的构造函数。以下代码将创建一个Led类的实例:

Led led(LED_ONE, 1000, 500);

Led类中,构造函数定义如下:

Led::Led(int pin, long on, long off)

注意到Led类的定义有三个参数(pinonoff)。这三个参数与我们创建Led类实例时传递给构造函数的三个值相匹配。

我们现在可以使用这个类通过调用类的blinkLed()方法来使 LED 闪烁。以下代码展示了如何做到这一点:

led.blinkLed();

以下代码展示了主标签页中将要使用Led类来闪烁 LED 的代码:

#include "led.h"
Led led(LED_ONE, 1000, 500);
void setup() {
}
void loop() {
  led.blinkLed();
}

如果你在这第四章中创建的原型上运行此代码,基本原型制作,你会看到其中一个 LED 闪烁。

在本节中,我们只给出了一个非常简短的面向对象编程(OOP)介绍,使你能够理解大多数专业 Arduino 库是如何创建的以及如何使用它们。关于 OOP 有整本书的讨论,如果你希望为 Arduino 创建库,我建议阅读更多关于面向对象设计和 Arduino 的 OOP。

现在,让我们看看我们如何使用 Arduino 的内置 String 库。

String 库

Arduino 核心库的一部分 String 库使我们能够更容易地使用和操作文本,并且比字符数组更复杂。使用 String 库比使用字符数组需要更多的内存,但使用 String 库更容易。

创建 String 类型实例的方法有很多。让我们在这里看看几个例子:

String str1 = "Arduino";
String str2 = String("Arduino");
String str3 = String('B');
String str4 = String(str2 + " is Cool");

前两行创建了一个包含单词 Arduino 的简单字符串。在第三行中,从单个常量字符创建了一个新的 String 实例。在这一行中,请注意使用了单引号。最后一个例子将两个字符串连接起来。还有几个其他构造函数使我们能够从数字创建 String 类的实例。以下是一些例子:

String strNum1 = String(42);
String strNum2 = String(42, HEX);
String strNum3 = String(42, BIN);

在前面的代码中,strNum1 字符串实例将包含文本 42,这是数字 42 的十进制版本。strNum2 字符串实例将包含文本 2a,这是数字 42 的十六进制版本。strNum3 字符串实例将包含文本 101010,这是数字 42 的二进制版本。

也可以在 String 类的实例中使用许多方法。其中一些方法包括:

  • concat(string): 将一个字符串连接到原始字符串的末尾。

  • endsWith(string): 如果原始字符串以另一个字符串的字符结尾,则返回 true。

  • equals(): 将比较两个字符串,如果字符串包含相同的文本,则返回 true。在比较字符串时,此方法区分大小写。

  • equalsIgnoreCase(): 将比较两个字符串,如果字符串包含相同的文本,则返回 true。在比较字符串时,此方法不区分大小写。

  • length(): 返回字符串的长度。长度不包括尾随的空字符。

  • replace(substring1, substring2): 此方法将替换所有实例的一个子字符串为另一个子字符串。

  • startsWith(string): 如果原始字符串以另一个字符串的字符开头,则返回 true。

  • toLowerCase(): 返回原始字符串的小写版本。

  • toUpperCase(): 返回原始字符串的大写版本。

字符串库可以用作字符数组的替代品;然而,你会发现互联网上大多数示例代码主要使用字符数组,因为它们占用的内存更少,并且执行速度比字符串库更快。

摘要

这就结束了 Arduino 编程语言的介绍。您可以参考 Arduino 快速参考页面以获取有关 Arduino 编程语言的更多信息。

您可以在这里找到参考页面:www.arduino.cc/reference/en/。在这个页面上,您可以找到有关内置 Arduino 函数和变量的信息链接。还有关于运算符和其他 Arduino 语言元素的信息链接。

如果您现在感觉不舒服编写自己的 Arduino 程序,请不要担心,因为在这本书的剩余章节中我们将编写大量代码,到那时您应该能够轻松编写自己的 Arduino 应用程序。

第八章:运动传感器

在本章中,我们将探讨如何使用 HC-SR501 运动传感器。这是一个非常容易连接到 Arduino 并编程的传感器,这也是为什么当人们开始使用微控制器时,它通常是他们实验的第一批传感器之一。它也非常便宜,通常包含大多数入门套件中。

在本章中,您将学习:

  • 如何将 HC-SR501 运动传感器连接到 Arduino

  • 如何读取 HC-SR501 运动传感器的输出

  • 阅读运动传感器项目的 Fritzing 图

简介

PIR 传感器,也称为被动红外传感器,由微控制器用于检测运动,通常是人,但它们可以检测传感器范围内的任何运动。这些传感器体积小、价格低、功耗低且易于使用,这使得它们非常适合初学者进行实验,但工业版本的这些传感器也出现在许多消费和军事产品中。

PIR 传感器由热释电传感器组成,可以检测红外辐射水平。任何温度高于绝对零度的物体都会发出一些低水平红外辐射,热释电传感器可以检测到。名称中的“被动”部分意味着传感器不会产生或辐射其他设备可以检测到的能量。相反,它通过检测其他物体发出的红外辐射来工作。

运动传感器中的热释电传感器通常分为两侧,这使得运动传感器能够检测红外水平的改变。当传感器没有检测到任何运动时,两侧检测到相同数量的红外辐射并相互抵消。当传感器范围内有物体开始移动时,传感器的一半检测到的红外辐射比另一半多,导致传感器触发运动警报。

PIR 传感器有多种尺寸和强度。这些传感器用于众多商业产品中,如防盗报警器、自动灯光和节日装饰,当有人靠近时,它们会说话或发光。

在本章中,我们将使用 HC-SR501 运动传感器,如下照片所示,它配有一个我设计和打印的支架。本书提供的可下载代码包括一个STL文件,您可以使用它来打印自己的支架:

图片

以下图片显示了 HC-SR501 运动传感器底部的连接器和调整螺丝:

图片

灵敏度调整螺丝用于调整传感器的检测范围。检测范围可设置为 3 米至 7 米。顺时针旋转灵敏度螺丝会降低传感器的灵敏度。

输出时间调整螺钉设置检测到运动后输出保持高电平的时间。输出时间可以从 5 秒到 5 分钟不等。顺时针旋转输出时间调整螺钉将增加延迟时间。

引脚应连接到面包板上的地轨或直接连接到 Arduino 的地引脚。5V引脚应连接到面包板上的电源轨或直接连接到 Arduino 的 5V 输出。最后,中间引脚是传感器的输出引脚。如果传感器检测到运动,此引脚将根据输出时间调整螺钉定义的时间变高。

注意:一些兼容的传感器可能具有与本书中显示的不同引脚配置;在进行任何布线之前,请验证您的传感器引脚配置。

在您查看本章电路图部分之前,考虑一下您会如何将 HC-SR5012 运动传感器连接到 Arduino。一个提示,您不需要除了运动传感器、Arduino 和跳线之外的东西。在这个特定项目中使用面包板是可选的。

现在让我们看看本章项目所需的组件。

需要的组件

为了完成本章的项目,我们需要以下组件:

  • 一块 Arduino Uno 或兼容板

  • 一块 HC-SR501 运动传感器

  • 跳线

  • 对于挑战,您需要一个 LED

  • 一块面包板

面包板是可选的,因为您可以直接将 HC-SR501 运动传感器连接到 Arduino。要完成本章的挑战部分,您将需要一个面包板。

电路图

以下图显示了本项目 Fritzing 图:

通过图示,我们可以看到 HC-SR501 运动传感器的地引脚连接到面包板的地轨,运动传感器的 5V 输入连接到面包板的电源轨。面包板的电源和地轨连接到 Arduino 的 5V 电源和地引脚。

运动传感器的输出引脚是数字输出(要么是 HIGH,要么是 LOW),因此我们可以将其直接连接到 Arduino 的任何数字引脚上。在这种情况下,我们将传感器的输出引脚连接到 Arduino 的 3 号引脚。

这里是相同电路的原理图:

让我们看看本项目 Arduino 代码。

代码

要使用 HC-SR501 运动传感器,我们只需要读取传感器的数字输出。如果输出是 HIGH,则传感器检测到运动;如果是 LOW,则没有检测到运动。传感器的输出将保持 HIGH 状态,持续时间由输出时间调整螺钉定义。我通常将输出时间设置得较低,通常是一两秒钟。

对于这个项目,我们将把传感器的状态输出到串行控制台。在挑战部分,输出将变得更加复杂。

以下是如何读取 HC-SR501 运动传感器的代码:

#define MOTION_SENSOR 3

void setup() {
 pinMode(MOTION_SENSOR, INPUT);
  Serial.begin(9600);
}

void loop() {
  int sensorValue = digitalRead(MOTION_SENSOR);
  if (sensorValue == HIGH) {
    Serial.println("Motion Detected");
  }
  delay(500);
}

此代码首先使用 #define 指令创建 MOTION_SENSOR 宏并将其设置为 3。在 setup() 函数中,我们将 3 号引脚的引脚模式设置为输入,因为我们将从运动传感器读取数字输出引脚。我们还在 setup() 函数中初始化串行控制台。

loop() 函数首先调用 digitalRead() 函数来读取运动传感器的输出并将其赋值给 sensorValue 变量。如果 sensorValue 变量等于 HIGH,我们将消息 Motion Detected 发送到串行控制台。如果没有检测到运动,我们不打印任何内容。在 loop() 函数的末尾,在循环回之前有一个半秒的延迟。

现在我们来上传并运行代码。

运行项目

当我们运行代码时,需要打开串行控制台以查看输出。

一旦代码上传并运行,将手在传感器附近挥动,你应该会看到控制台打印出 Motion Detected 消息,如下面的截图所示:

图片

现在进入本章的挑战部分。

挑战

如果你选择接受这个挑战,就是给这个项目添加一个 LED 灯,当运动传感器检测到运动时,LED 灯会亮起。在前几章中,我们将提供代码或电路图,以便在项目初次构建时如果出现问题更容易进行故障排除。

对于这个挑战,我们将提供代码,并让你自己找出如何将 LED 连接到项目上。一个提示,不要忘记为 LED 添加电阻。

以下是在运动传感器检测到附近运动时,将连接到 Arduino 5 号引脚的 LED 灯点亮的代码:

#define MOTION_SENSOR 3
#define LED 5

void setup() {
  pinMode(MOTION_SENSOR, INPUT);
  pinMode(LED, OUTPUT);

  digitalWrite(LED, LOW);
  Serial.begin(9600);
}

void loop() {
  int sensorValue = digitalRead(MOTION_SENSOR);
  if (sensorValue == HIGH) {
    Serial.println("Motion Detected");
  }

  digitalWrite(LED, sensorValue);
  delay(500);
}

现在轮到你来完成这个挑战了。

概述

在本章中,我们学习了 HC-SR501 运动传感器及其工作原理。我们看到了如何将其连接到 Arduino,Arduino 为传感器提供电源并读取传感器的输出引脚。

在下一章中,我们将看到我们如何感知周围的天气。

第九章:环境传感器

在本章中,我们将探讨如何使用 DHT11 温度/湿度传感器和雨滴传感器构建一个真正简单的气象站。虽然上一章使用了基本的数字输入,但 DHT11 温度传感器将给我们提供使用第三方库的机会,而雨滴传感器将使用模拟引脚。我们还将介绍一些实用的函数,我们可以使用它们。

在本章中,你将学习:

  • 如何将第三方库添加到草图

  • 如何使用isnan()函数

  • 如何使用map()函数

  • 如何使用 DHT11 温度和湿度传感器

  • 如何使用雨传感器

简介

DHT11 是一种低成本的温度和湿度传感器。该传感器使用热敏电阻来测量温度。热敏电阻这个词是由热(温度)和电阻组合而成的,因为它是一种电阻,其电阻对温度的敏感性非常高,甚至比普通电阻还要高。可以根据热敏电阻的输出电压确定当前温度。

当与热敏电阻一起工作时,我们首先需要做的是确定如何根据输出电压计算温度。在第四章中我们使用的 TMP36 温度传感器原型中,我们可以通过一个基本的公式(电压 - 0.5)* 100.0轻松地根据传感器的输出电压计算温度,因为这个传感器使用固态技术来确定温度。热敏电阻的情况并非如此。虽然线性近似,类似于我们使用 TMP36 传感器计算温度的方法,可能适用于较小的温度范围,但要从热敏电阻中获得准确的温度测量,我们需要确定设备的电阻/温度曲线。

幸运的是,有几个 Arduino 库是为了帮助我们从 DHT11 温度和湿度传感器中获得准确的温度而编写的。在本章中,我们将使用Adafruit库。DHT11 传感器将看起来类似于以下照片:

图片

对于大多数 DHT11 传感器,引脚标记得很清楚,如前一张照片所示。VCC引脚将连接到面包板上的电源轨,它应该连接到 Arduino 的 5V 输出引脚。GND引脚将连接到面包板上的地轨,它应该连接到 Arduino 的地输出引脚。DATA引脚将连接到 Arduino 的一个数字引脚。

一些 DHT 温度传感器内置了上拉电阻,而另一些则需要外部上拉电阻。请查阅您的传感器文档,以验证是否需要添加外部上拉电阻。在本章的项目中,我们将展示外部上拉电阻。

对于本章的项目,我们还将使用一个通用的雨滴传感器。这个传感器有两个部分。第一部分是雨传感器板,当水完成板上的印刷引线电路时,它会检测到雨水。这个传感器板充当一个可变电阻,随着板变湿,电流的量增加。雨滴传感器的第二部分是电子印刷电路板,它将根据传感器板的电流确定水的量。

以下照片显示了雨滴传感器的样子:

印刷电路板上的 +/- 引脚连接到雨传感器板上的引脚。印刷电路板的另一侧有四个引脚。VCC 和 GND 引脚将分别连接到面包板的电源和地轨。对于本章的项目,我们将使用 A0 模拟输出引脚作为传感器的输出。A0 引脚将直接连接到 Arduino 上的一个模拟输入引脚。

需要的组件

我们需要以下组件来完成本章的项目:

  • 一个 Arduino Uno 或兼容板

  • 一个 DHT11 温湿度传感器

  • 一个 MH-RD 雨滴传感器

  • 一个 4.7K 电阻

  • 跳线

  • 一个面包板

电路图

以下图表显示了本项目的 Fritzing 图:

在这个图表中,我们可以看到两个传感器的 VCC 和地引脚都连接到面包板上的电源和地轨。面包板上的电源和地轨连接到 Arduino 的 5V 输出和地引脚。

我们在本章前面展示的 DHT11 传感器图像显示了一个带有三个引脚的 DHT11 传感器;然而,Fritzing 库中的传感器有四个引脚。在 Fritzing 图中忽略额外的引脚是安全的。

此图表显示 DHT11 传感器的数据引脚连接到 Arduino 的数字 3 引脚,并且它还有一个 4.7K 上拉电阻。如果您使用的 DHT11 传感器没有内置上拉电阻,您需要添加此图中所示的外部上拉电阻。雨传感器上的模拟输出连接到 Arduino 的模拟 2 输入引脚。

代码

在我们开始编写代码之前,我们需要加载我们将使用的 DHT11 Adafruit 库来读取温度和湿度读数。您可以在 Adafruit 的 GitHub 页面上找到此库的源代码:github.com/adafruit/DHT-sensor-library

注意:您需要参考此代码来完成本章的挑战部分。

要安装库,如果您使用 Arduino IDE,请从菜单栏中选择 Sketch 选项,然后选择 Include Library,然后选择 Manage Libraries,如下面的截图所示:

在打开的窗口中,在搜索栏中输入dht11,你应该会看到几个不同的 DHT11 传感器库。在本章中,我们将使用来自 Adafruit 的库(在以下列表中,第一个结果)。点击这个库,你会在最右边看到一个安装按钮,如下面的屏幕截图所示:

图片

对于 Arduino Web 编辑器,点击“库”选项,然后在搜索栏中输入dht11,如下面的屏幕截图所示:

图片

这可能不会返回任何结果;因此,我们需要点击“库管理器”链接,这将打开库管理器,并显示 DHT11 搜索的结果,如下面的图片所示:

图片

如此窗口顶部的说明所述,我们需要点击要包含的库的星号,然后点击“完成”按钮。不幸的是,Web 编辑器中的库管理器没有告诉我们是谁创建了库;然而,如果你注意到,三个库的名称与我们在 Arduino IDE 中看到的库名称相匹配。因此,我们可以通过标题来判断哪个是 Adafruit 库。

现在我们已经安装了库,是时候开始编写代码了。我们首先需要做的是包含 DHT 传感器库的头文件。我们可以通过在代码顶部添加以下include语句来实现:

#include "DHT.h"

接下来,我们需要定义一些宏。我们将从定义 DHT11 和雨滴传感器连接到的 Arduino 引脚开始:

#define DHT_PIN 3
#define RAIN_PIN A2

这段代码告诉我们 DHT11 传感器连接到数字 3 引脚,雨传感器连接到模拟 2 引脚。Adafruit DHT 传感器库可以读取 DHT11 和 DHT22 传感器。因此,我们需要告诉库我们使用的是哪种传感器类型,并且我们应该创建一个包含此类型的宏。以下代码定义了 DHT 传感器类型:

#define DHT_TYPE DHT11

最后,我们需要创建四个宏,这将帮助我们理解雨传感器的读数。如果你还记得,模拟输入引脚将输入电压映射到 0 到 1023 的整数值。当我们从雨传感器读取输入时,1023 的值表示没有雨,而 0 的值表示洪水。从纯电子角度来看,这是有意义的;然而,从逻辑角度来看,这似乎是相反的,因为雨传感器应该在雨更多时报告更高的值。

我们将使用 Arduino 的map()函数来帮我们转换。因此,我们需要定义模拟读数的最大/最小值以及我们想要将模拟值转换到的最大/最小值。我们将在查看map()函数的代码时进一步解释这一点;现在,这里有一些宏:

#define RAIN_SENSOR_MAX 1023
#define RAIN_SENSOR_MIN 0
#define RAIN_OUT_MAX 20
#define RAIN_OUT_MIN 0

现在我们将想要使用我们刚刚定义的 DHT_PINDHT_TYPE 宏来创建 DHT 类的一个实例。以下代码将创建 DHT 类的一个实例:

DHT dht(DHT_PIN, DHT_TYPE);

既然我们已经定义了所需的宏并创建了 DHT 类的全局实例,我们需要创建 setup() 函数。在 setup() 函数中,我们需要初始化串行监视器和 DHT 类。DHT 类的 begin() 方法用于初始化类的实例。以下代码显示了 setup() 函数:

void setup() {
  Serial.begin(9600);
 dht.begin();
}

现在我们有了 setup() 函数,让我们看看如何读取 DHT 和雨量传感器。本章剩余的代码将进入我们的草图中的 loop() 函数。以下代码将使用 Adafruit 库从 DHT 传感器读取湿度和温度:

float humidity = dht.readHumidity();
float celsius = dht.readTemperature();
float fahrenheit = dht.readTemperature(true);

if (isnan(humidity) || isnan(celsius) || isnan(fahreheit)) {
  Serial.println("Read Failed");
  return;
}

Serial.print("Humidity: ");
Serial.println(humidity);
Serial.print("Temperature: ");
Serial.print(celsius);
Serial.println(" *C ");
Serial.print(fahreheit);
Serial.println(" *F");

delay(3000);

此代码首先调用 DHT 类的 readHumidity() 方法来从 DHT 传感器读取湿度。然后调用 readTemperature() 方法两次,一次读取摄氏度温度,一次读取华氏度温度。请注意,当 readTemperature() 方法不带参数调用时,我们接收摄氏度温度;当我们传递布尔参数 true 时,我们接收华氏度温度。我们也可以传递布尔参数 false 来接收摄氏度温度。

在从传感器读取温度和湿度之后,验证读取是否成功是一个好习惯。为此,我们使用了 isnan() 函数。如果传入的值不是数字,isnan() 函数将返回 true,因此,行 if (isnan(humidity) || isnan(celsius) || isnan(Fahrenheit)) 读取为“如果湿度不是数字或摄氏度不是数字或华氏度不是数字,则执行代码块”。此代码块将打印错误消息到控制台,然后执行 return 语句以退出此循环。

如果所有变量都是数字,我们将湿度温度打印到串行控制台,然后等待 3 秒钟,退出此循环并再次开始循环函数。

现在我们来看看如何读取雨量传感器。将以下代码放在最终的 Serial.println() 语句和 DHT 传感器代码中的 delay() 函数调用之间:

int rain = analogRead(RAIN_PIN);
if (isnan(rain)) {
  Serial.println("Read Failed");
  return;
}
int range = map(rain, RAIN_SENSOR_MIN, RAIN_SENSOR_MAX, RAIN_OUT_MAX, RAIN_OUT_MIN);

Serial.print("Rain: ");
Serial.println(range);
Serial.println("-------------------------");

在此代码中,我们调用 analogRead() 函数来读取雨量传感器连接的模拟引脚,并使用 isnan() 函数来验证读取是否成功。在验证 analogRead() 函数成功执行后,我们调用 map() 函数。map() 函数将重新映射一个值从一个数字范围到新的数字范围。

此函数有五个参数,它们是:

  • value:要映射的值

  • fromLow:值当前范围的下限

  • fromHigh:值当前范围的上限

  • toLow:值的新范围的下限

  • toHigh:值新范围的上限

如果我们将前一段代码中的map()函数调用中的宏替换为实际值,map()函数将看起来像这样:

int range = map(rain, 0, 1023, 20, 0);

雨变量值来自analogRead()函数,我们知道它的值将在01023之间。因此,我们将当前范围的下限设置为0,上限设置为1023。如果你还记得本章前面的内容,1023 的值表示没有雨,而 0 的值表示洪水。我们希望在新范围内反转这个值,其中较高的值表示更多的雨,而较低的值表示较少的雨。因此,我们将新范围的下限设置为20,上限设置为0。这将把旧范围内的 1023 值映射到新范围内的 0,而旧范围内的 0 值映射到新范围内的20

使用这个新范围,20的高值表示洪水,而零的低位值表示没有雨。旧范围中的中间值(511 或 512)将映射到新范围中的中间值(10)。map()函数在我们想要改变比例和/或反转顺序时非常有用,就像在这个例子中看到的那样。

在调用map()函数后,我们将结果打印到串行控制台。现在让我们看看运行这个项目会发生什么。

运行项目

当我们运行这个项目时,我们应该看到以下截图类似的结果:

现在尝试用水喷洒雨传感器板(雨滴传感器感应雨的部分)并观察它如何改变雨输出。

注意:在电子项目周围使用水时总是要小心。如果你让你的 Arduino 或其他电子元件弄湿,你会损坏它们。当使用继电器处理交流电时,你也面临着触电的风险。

现在让我们看看挑战。

挑战

对于这个挑战,使用DHT库来计算热指数。热指数是温度和湿度结合引起的不适感。DHT类中有方法可以为你完成这个工作。

在本章的开头,我们提供了一个链接到包含 DHT 传感器库代码的 GitHub 仓库。查看DHT.h文件以了解 DHT 类中有哪些方法。

概述

在本章中,我们首次使用了第三方库。这是 Adafruit DHT 传感器库。我们还看到了两个之前未使用过的新函数。这些函数是isnan()map()函数。

在下一章中,我们将探讨范围和碰撞检测传感器。

第十章:避障和碰撞检测

如果你正在制作一个需要避障的自主机器人、一个需要检测碰撞的遥控车,甚至是一个需要知道打印头是否到达打印区域极限的 3D 打印机,你需要在你的项目中包含某种避障或碰撞检测系统。在本章中,我们将探讨可用于避障和碰撞检测系统的几个传感器。

在本章中,你将学习:

  • 如何使用碰撞传感器

  • 如何使用红外避障传感器

  • 如何使用超声波测距仪

简介

在本章中,我们将探讨三种我们可以用来为我们的项目添加避障和/或碰撞检测系统的传感器。这些传感器是:

  • 碰撞传感器:用于检测碰撞,也用作 3D 打印机的限位开关

  • 红外避障传感器:用于机器人的避障

  • 超声波测距仪:用于机器人的避障,并有许多其他商业和军事用途

碰撞传感器

碰撞传感器基本上是一个简单的开关,上面有一些某种类型的延长器,使其能够检测到较大的碰撞区域。以下照片展示了基本碰撞传感器的外观:

前一张照片中展示的碰撞传感器将一个简单的机械开关(如 3D 打印机上使用的端点开关)连接到电路板的一端。这使得将其安装在机器人底盘或其他表面上变得容易。碰撞传感器背后的概念是当开关被触发时,传感器已经撞到了某个物体。

碰撞传感器有三个引脚,分别清晰标记为GNDVCCOUT。GND 引脚连接到地轨,VCC 连接到面包板上的电源轨。OUT 引脚直接连接到 Arduino 的数字引脚,并带有 4.7K 的上拉电阻。

红外避障传感器由一个红外发射器、一个红外接收器和可调节传感器检测障碍物距离的电位器组成。以下照片展示了本章项目中使用的避障传感器。

避障传感器

红外避障传感器上的发射器发射红外辐射,如果传感器前方有障碍物,部分辐射会被反射并接收器接收。如果没有物体在传感器前方,辐射将消散,接收器将不会接收到任何东西。

传感器的引脚从左到右清晰标记为OUTGNDVCC。GND 引脚连接到接地轨,VCC 引脚连接到面包板的电源轨。OUT 引脚直接连接到 Arduino 的数字引脚。如果 OUT 引脚的信号为 LOW,则表示检测到了物体。如果输出为 HIGH,则表示未检测到物体。

距离调整器会调整传感器检测物体的距离。如果调整器逆时针旋转,则距离会减小;如果您顺时针旋转,则距离会增加。传感器将检测从 2 到 30 厘米的物体。

超声波测距仪

我们将要使用的第三个传感器是MaxSonar EZ1超声波测距仪。这是我最喜欢的传感器之一。我几乎在构建的每个自主机器人中都使用了它来确定附近物体的距离。以下是 EZ1 超声波测距仪的图片:

图片

对于本章的示例,我们将使用传感器的 3 号、6 号和 7 号引脚。3 号引脚用于模拟输出,6 号引脚用于 VCC,7 号引脚用于接地。4 号和 5 号引脚用于串行 RX/TX 连接,2 号引脚用于脉冲宽度输出,然而,在本章的示例中,我们不会使用这些输出。

超声波测距仪通过向特定方向发送超声波脉冲来工作。如果脉冲在反射回作为回声的形式时遇到物体,传感器通过测量回声返回所需的时间来确定到物体的距离。

EZ1 超声波传感器可以检测并测量从 0 到 6.45 米(254 英寸)的物体距离。该传感器几乎没有盲区,并将检测到传感器正面上的物体。

需要的组件

我们将需要以下组件来完成本章的项目:

  • 一个 Arduino Uno 或兼容板

  • 一个碰撞传感器

  • 一个避障传感器

  • 一个 EZ1 超声波传感器

  • 一个 4.7K 欧姆电阻

  • 跳线

  • 一个面包板

电路图

以下图显示了本项目的 Fritzing 图:

图片

图中显示的中间传感器代表碰撞传感器,因为 Fritzing 中没有碰撞传感器的部件。图中的开关具有与本章前面显示的碰撞传感器相同的引脚布局。

在图中,我们可以看到传感器上的所有接地引脚都连接到面包板的接地轨,而传感器上的所有 VCC 引脚都连接到面包板上的电源轨。

EZ1 超声波传感器上的模拟输出连接到 Arduino 的 A1 模拟引脚,碰撞传感器连接到数字引脚 3,红外传感器连接到数字引脚 2。碰撞传感器还带有一个 4.7K 的上拉电阻。现在我们已经将传感器连接到 Arduino,让我们看看这个项目的代码。

代码

我们将以三个宏开始代码,这些宏定义了三个传感器连接的引脚。这些宏将看起来像这样:

#define COLLISION_SWITCH 4
#define IR_SENSOR 3
#define RANGE_SENSOR A1

这些宏显示碰撞传感器连接到数字引脚 4,红外传感器连接到数字引脚 3,超声波测距仪连接到模拟引脚 1。现在我们需要设置我们使用的两个数字引脚的模式,并初始化串行监视器。我们可以通过在setup()函数中添加以下代码来完成此操作:

Serial.begin(9600);
pinMode(COLLISION_SWITCH, INPUT);
pinMode(IR_SENSOR, INPUT); 

这首先初始化串行监视器,然后配置碰撞和红外传感器引脚为输入,以便我们可以读取值。现在我们需要向loop()函数中添加代码以读取传感器。让我们首先看看如何读取和中断碰撞传感器:

int collisionValue = digitalRead(COLLISION_SWITCH); 
if (isnan(collisionValue)) { 
  Serial.println(" Failed to read collision sensor"); 
  return; 
}
if (collisionValue == LOW) { 
  Serial.println("Collision Detected"); 
}

此代码首先使用digitalRead()函数读取碰撞传感器连接的引脚,然后使用isnan()函数验证digitalRead()函数返回了正确的值。如果函数返回的值无效(不是一个数字),则将错误消息打印到串行控制台,并调用return语句退出此循环。

如果digitalRead()函数返回的值有效,那么我们会检查该值是否为LOW,如果是,则表示检测到了障碍物,并将消息打印到串行控制台。现在让我们添加红外传感器的代码:

int irValue = digitalRead(IR_SENSOR);
if (isnan(irValue)) {
 Serial.println(" Failed to read infrared sensor");
  return;
}
if (irValue == LOW) {
  Serial.println("IR Detected");
} 

此代码与碰撞传感器完全相同,只是我们读取了红外传感器引脚并检查了该值。现在让我们添加超声波测距仪的代码:

int anVolt = analogRead(RANGE_SENSOR);
if (isnan(anVolt)) {
  Serial.println(" Failed to read range sensor");
  return;
}
int mm = anVolt*5;
float inch = mm/25.4;
Serial.println(mm);
Serial.print("MM:   ");
Serial.println(mm);
Serial.print("Inches: ");
Serial.println(inch);
Serial.println("---------------------------");
delay(1000);

此代码首先使用analogRead()函数读取超声波测距仪连接的引脚。然后我们使用isnan()函数验证返回了正确的值。

然后以毫米和英寸为单位计算到物体的距离。计算中使用的数字取决于传感器的数据表,可能因所使用的型号而异。现在我们将在loop()函数的末尾添加一个短暂的延迟以暂停执行。

现在让我们运行这个项目。

运行项目

当我们运行此项目时,输出应类似于以下截图:

图片

这张截图显示了一个物体触发了红外传感器两次,其中IR Detected被打印到串行控制台,碰撞传感器一次,其中Collision Detected被打印到串行控制台。它还显示了测距仪返回的最近物体的距离。

挑战

这个挑战将与大多数挑战略有不同。实际上没有具体的项目要做;相反,这是一个思考挑战。挑战是思考这三个传感器如何协同工作以创建一个自主机器人。为此,思考这三个传感器的工作方式:

  1. 碰撞传感器:当传感器碰到物体时被触发的数字传感器

  2. 红外传感器:当物体靠近时被触发的数字传感器

  3. 超声波测距仪:用于检测物体距离传感器的模拟传感器

这里是答案:

超声波测距仪是最昂贵的,所以我通常只使用两个这样的传感器,它们面向机器人的前方。这些传感器被用来帮助机器人绕过障碍物。有了能够告诉机器人某个物体距离前方有多远的能力,我们可以给机器人提供所需的逻辑来决定何时转弯,而且,使用两个超声波传感器,我们还可以提供决定转向方向的逻辑。我们还可以使用超声波传感器来绘制房间的地图。

红外传感器非常便宜,可以安装在机器人的侧面和后面,以确保机器人在转弯或倒车时不会撞到任何东西。由于它们比超声波传感器便宜得多,我们可以使用多个红外传感器来确保机器人周围的所有区域都被覆盖。我们还可以使用朝下的红外传感器来确保机器人不会从边缘驶出。

碰撞传感器也非常便宜,可以安装在机器人的各个部位,用来检测机器人是否撞到了超声波或红外传感器未能检测到的物体。超声波和红外传感器最大的问题是它们在机器人上的高度。如果它们太高,那么它们可能会错过靠近地面的障碍物。可以使用碰撞传感器来检测这些障碍物。

摘要

在本章中,我们看到了如何使用三种传感器来进行障碍物避让和碰撞检测。碰撞传感器是一个数字传感器,可以用来判断传感器是否撞到物体。红外障碍物避让传感器也是一个数字传感器,可以判断传感器是否在障碍物的一定距离内。超声波测距仪是一个模拟传感器,可以用来判断障碍物距离传感器的距离。

在下一章中,我们将探讨一些不同类型的 LED,并看看我们如何在项目中使用它们。

第十一章:玩转灯光

我们创建的大部分大型项目将使用一个或多个 LED 作为指示器。这些 LED 可以指示诸如电源、接收数据、警告或任何我们需要视觉反馈的其他事物。我们已经看到了如何使用基本的单色 LED,但如果我们需要多个 LED 或甚至多色 LED 怎么办?在本章中,我们将探讨其他将 LED 添加到项目中的方法。

在本章中,你将学习:

  • 什么是 NeoPixel

  • RGB LED 的工作原理

  • 如何在项目中使用 NeoPixel

  • 如何在项目中使用 RGB LED

简介

在本章中,我们将探讨如何使用 RGB LED 和WS2812 40 RGB LED Pixel Arduino 屏蔽板。让我们先了解一下 RGB LED。

多色或 RGB LED 实际上并不是一个可以改变颜色的单个 LED,它实际上是三个 LED。RGB LED 包含三个 LED,分别是红色、绿色和蓝色。LED 产生的颜色是这三个 LED 产生的颜色的组合。

RGB LED 有两种类型。这些是公共阳极和公共阴极 LED。在公共阴极 RGB LED 中,三个 LED 共享一个公共接地源,而在公共阳极 RGB LED 中,三个 LED 共享一个公共电源源。

RGB LED 有四个引脚,每个颜色一个,第四个用于公共阴极或阳极连接。以下图示显示了公共阴极和公共阳极 RGB LED 的引脚:

图片

要产生各种颜色,我们可以通过 Arduino 上的 PWM 引脚调整三个不同 LED 的强度。由于 LED 非常靠近,光线会混合在一起,从而产生我们想要的颜色。现在让我们看看 WS2812 集成光源,或者,如它们在 Adafruit 网站上所知,称为NeoPixel。在本章的大部分内容中,我们将把 WS2812 集成光源称为 NeoPixel,因为它更简洁,听起来也更酷。

如你所想,如果我们想在项目中包含 10 个 RGB LED,每个 LED 需要三个输入引脚,项目会很快变成一团糟。更不用说 Arduino 上的引脚也会很快用完。我们可以解决这个问题的方法之一是使用 NeoPixel。NeoPixel 将红色、绿色和蓝色 LED 以及驱动芯片集成在一个微型表面贴装组件上。这个组件可以通过一根线控制,可以单独使用或作为一组使用。NeoPixel 有多种形式,包括条带、环形、Arduino 屏蔽板,甚至珠宝。

NeoPixel 的一个优点是,没有固有的限制可以连接多少个 NeoPixel。然而,根据你使用的控制器的 RAM 和电源限制,有一些实际限制。

在本章中,我们将使用 NeoPixel 屏蔽板。如果你使用单个 NeoPixel,有几件事你需要记住:

  • 在将 NeoPixels 连接到电源之前,您可能需要添加一个 1000 微法拉、6.3V 或更高电压的电容。

  • 您还希望在 Arduino 数据输出和第一个 NeoPixel 的输入线之间添加一个 470 欧姆电阻。

  • 如果可能的话,在电路通电时避免连接/断开 NeoPixels。如果必须将它们连接到通电电路,请始终先连接地线。如果必须从通电电路中断开它们,请始终先断开 5V 电源。

  • NeoPixels 应始终从 5V 电源供电。

在本章中,我们将使用Keyestudio 40 RGB LED 2812 像素矩阵盾。这个盾已经包含了电容器和电阻,所以我们只需要将盾放置在 Arduino Uno 的顶部,就可以开始了。Keyestudio 盾与 Arduino 的连接方式如图所示:

图片

当使用其他 NeoPixel 形式时,在将其连接到 Arduino 之前,请务必阅读制造商的数据表。损坏 NeoPixels 很容易,所以请确保遵循制造商的建议。

需要的组件

我们将需要以下组件来完成本章的项目:

  • 一块 Arduino Uno 或兼容板

  • 一个 RGB LED,要么是常见的阳极,要么是常见的阴极

  • 三个 330 欧姆电阻

  • 一块 Keyestudio 40 RGB LED 2812 像素矩阵盾

  • 跳线

  • 一块面包板

电路图

以下图示展示了我们如何将一个常见的阳极 RGB LED 连接到 Arduino:

图片

在此图中,我们展示了如何连接一个常见的阳极 RGB LED。我们可以看到这一点,因为公共引脚连接到面包板上的电源轨。如果您使用的 RGB LED 是常见的阴极 LED,那么请将 LED 上的公共引脚连接到地轨而不是电源轨。每个 RGB 引脚都通过一个 330 欧姆电阻连接到 Arduino 的 PWM 引脚。

我们没有展示 NeoPixel 盾的电路图,因为我们只需要将盾连接到 Arduino。现在让我们看看代码。

代码

让我们从查看 RGB LED 的代码开始。

RGB LED

我们将首先定义 Arduino 上的哪些引脚连接到 LED 的 RGB 引脚:

#define REDPIN 11
#define BLUEPIN 10
#define GREENPIN 9

此代码显示红色引脚连接到 Arduino 11 PWM 引脚,蓝色引脚连接到 Arduino 10 PWM 引脚,绿色引脚连接到 Arduino 9 PWM 引脚。我们将定义一个空宏,让应用程序代码知道我们是否有常见的阳极或阴极 RGB LED。以下代码将做到这一点:

#define COMMON_ANODE

如果您使用的是常见的阴极 RGB LED,那么请注释或删除此行代码。当我们查看设置 LED 颜色的函数时,我们将看到如何使用它。现在让我们看看setup()函数。

void setup() {
  pinMode(REDPIN, OUTPUT);
  pinMode(GREENPIN, OUTPUT);
  pinMode(BLUEPIN, OUTPUT);
}

setup() 函数将设置连接到 LED 上 RGB 引脚的引脚模式为输出。这将允许我们使用 PWM 引脚来设置组成 RGB LED 的三个颜色 LED 的光强度。接下来,我们需要创建一个设置这些颜色的函数。我们将把这个函数命名为 setColor(),它将接受三个参数,这些参数将定义每个 RGB LED 的强度,并包含以下代码:

void setColor(int red, int green, int blue) {
  #ifdef COMMON_ANODE
  red = 255 - red;
  green = 255 - green;
  blue = 255 - blue;
  #endif
  analogWrite(REDPIN, red);
  analogWrite(GREENPIN, green);
  analogWrite(BLUEPIN, blue);
}

这个函数中的代码从 #ifdef 语句开始。这个语句表示如果定义了 COMMON_ANODE 宏,则执行 #ifdef#endif 语句之间的代码;否则,跳过该代码。因此,如果我们定义 COMMON_ANODE 宏在代码的开头,那么我们将每个参数从 255 减去以获得正确的强度。然后我们使用 analogWrite() 函数将值写入 RGB 引脚。

在本章的开头,我们解释了 RGB LED 的工作原理是通过调整 RGB LED 内部三个 RGB LED 的强度。如果我们向一个共阴极 LED 写入 255 的值,那么 LED 将达到最亮。对于一个共阳极 LED,我们需要写入 0 的值来使 LED 最亮。这就是为什么如果定义了 COMMON_ANODE 宏,我们就从每个参数的值中减去 255

loop() 函数中,我们循环通过几种颜色来演示 LED 如何显示不同的颜色。以下显示了 loop() 函数的代码:

void loop() {
  setColor(255, 0, 0); // Red
  delay(1000);
  setColor(0, 255, 0); // Green
  delay(1000);
  setColor(0, 0, 255); // Blue
  delay(1000);
  setColor(255, 255, 255); // White
  delay(1000);
  setColor(255, 0, 255); // Purple
  delay(1000);
}

loop() 函数中,我们调用 setColor() 函数五次来改变 LED 的颜色。我们显示的颜色有红色、绿色、蓝色、白色和紫色。每次颜色改变后,在显示下一个颜色之前会有一个一秒的暂停。这个暂停是由 delay() 函数实现的。

我们在 RGB LED 中显示颜色的方式与点亮一个普通 LED 的方式非常相似,只是我们为三种颜色定义了光强度(亮度)。现在让我们看看 NeoPixel 面板的代码。

NeoPixel 面板

在我们开始编码之前,我们需要安装 Adafruit NeoPixel 库。以下截图显示了应该通过库管理器安装的库。如果你不记得安装库的步骤,请参阅第九章 环境传感器

图片

我们安装 DHT11 温湿传感器的库的传感器位置。

一旦安装了库,我们需要在代码顶部添加以下行来包含它:

#include <Adafruit_NeoPixel.h>

当我们使用 Adafruit NeoPixel 库时,我们需要告诉它 NeoPixel 连接到哪个引脚以及连接了多少个 NeoPixel。因此,我们将定义包含这些值的宏:

#define SHIELD_PIN 13
#define MAX_PIXELS 40 

根据 Keyestudio 盾牌的数据表,盾牌连接到 Arduino 的 13 号引脚,盾牌包含 40 个 NeoPixels;因此,我们在宏中定义这些值。现在我们将使用这些值来初始化Adafruit_NeoPixel类的一个实例,如下面的代码所示:

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(MAX_PIXELS, SHIELD_PIN, NEO_GRB + NEO_KHZ800);

第一个参数是盾牌上的像素数量,第二个参数是 NeoPixels 连接的引脚。最后一个参数是像素类型标志。本例中显示的值是最常见的。以下是一些可能的值:

  • NEO_KHZ800: 800 KHz 比特流(大多数 NeoPixel 产品带有 WS2812 LED)

  • NEO_KHZ400: 400 KHz (经典 v1(非 v2)FLORA 像素,WS2811 驱动器)

  • NEO_GRB: 像素以 GRB 比特流连接(大多数 NeoPixel 产品)

  • NEO_RGB: 像素以 RGB 比特流连接(v1 FLORA 像素,非 v2)

在这个例子中,我们将逐个将每个像素转换为特定的颜色。因此,我们需要一个全局变量来指向我们所在的像素,另一个全局变量来定义要使用的颜色。在这个例子中,我们将使用两种颜色并在两者之间切换。以下代码定义了这个全局变量:

int num = 0;
boolean color = 0;

setup()函数中,我们需要初始化 NeoPixels。以下代码展示了包含初始化 NeoPixels 代码的setup()函数:

void setup() {
  pixels.begin();
  pixels.show();
  pixels.setBrightness(50);
}

begin()函数准备 Arduino 上的数据引脚,以便输出到 NeoPixels。show()函数将数据推送到 NeoPixels,在这里并不是绝对必要的;我发现,无论何时我们向 NeoPixels 写入任何内容,都包括这个函数是一种好的做法。第三个函数控制像素的亮度。我通常将其设置为 50%,因为 NeoPixels 非常亮。

现在让我们看看将每个像素逐个设置为颜色的loop()函数。

void loop() { 
  num++; 
  if (num > (MAX_PIXELS -1)) { 
    num = 0; 
    color = !color; 
  } 
  if (color) { 
    pixels.setPixelColor(num, 170, 255, 10); 
  } else { 
    pixels.setPixelColor(num, 10, 255, 170); 
  } 
  pixels.show(); 
  delay(500); 
}

loop()函数中,我们首先将num变量增加一,然后检查是否到达了最后一个像素。如果我们到达了最后一个像素,我们将num变量重置为零,并交换color变量。在color = !color这一行中,!运算符是 NOT 运算符,它使得color变量在 true 和 false 之间切换。这是因为 NOT 运算符返回color变量当前值的相反数。因此,如果以color变量当前为 false 为例,那么!color操作将返回 true。

然后,我们使用setPixelColor()函数将当前像素设置为两种颜色之一,这取决于color变量是 true 还是 false。setPixelColor()函数有两种版本。我们在这里看到的版本使用第一个参数作为我们设置的像素编号,然后接下来的三个数字定义了组成我们想要的颜色的红色、绿色和蓝色强度的值。如果我们使用 RGBW NeoPixel,我们还需要定义白色颜色。因此,这个函数将添加一个额外的参数,如下所示:

 setPixelColor(n, red, green, blue, white);

调用setPixelColor()函数的第二种方式是传递两个参数,其中第一个参数是像素编号,第二个参数是一个 32 位数字,它结合了红色、绿色和蓝色值。这个版本的函数看起来像这样:

setPixelColor(n, color);

颜色值可以从 0 到 16,777,216。

在我们设置像素颜色后,我们接着调用show()函数将值推送到像素,然后使用延时函数在代码中插入半秒的暂停。

运行项目

如果我们运行 RGB LED 的草图,我们会看到 LED 缓慢地在五种颜色之间循环。NeoPixel 的代码将逐个翻转像素,在两种颜色之间切换。

挑战

这将是书中最难的挑战之一。Keyestudio NeoPixel 盾牌有八列像素,每列包含五个像素,像素的编号如下:

对于这个挑战,将每一列设置为不同的颜色,并让颜色从左到右在盾牌上旋转。以下是一些帮助你开始的提示。第一个是 Adafruit NeoPixel 库,它有一个名为Color()的函数,可以根据三个红色、绿色和蓝色值返回 32 位颜色。因此,你可以使用以下代码将 8 位数字转换为 32 位颜色。

uint32_t colorNum(int color) {
  colorPos = 255 - colorPos;
  if(colorPos < 85) {
    return pixels.Color(255 - colorPos * 3, 0, colorPos * 3);
  }
  if(colorPos < 170) {
    colorPos -= 85;
    return pixels.Color(0, colorPos * 3, 255 - colorPos * 3);
  }
  colorPos -= 170;
  return pixels.Color(colorPos * 3, 255 - colorPos * 3, 0);
}

然后,我们可以使用以下代码,该代码将列中的所有像素设置为它们的颜色:

for (int j=0; j<5; j++) {
  int pixNum = (j*8) + i;
  pixels.setPixelColor(pixNum, colorNum((tmpColorMode * 30) & 255));
}

tmpColorMode变量是一个从 1 到 8 的数字,将用于选择该列的颜色。这应该为你开始这个挑战提供了基础知识。答案可以在本书的可下载代码中找到。

摘要

在本章中,我们学习了 RGB LED 的工作原理,如何使用它们,并探讨了共阳极和共阴极 RGB LED 之间的区别。我们还学习了 WS2812(NeoPixel)的工作原理以及如何使用它。NeoPixel 有多种不同的形式,几乎可以用于需要大量 RGB LED 的任何地方。

在下一章中,我们将探讨如何使用 Arduino 和一个小蜂鸣器来产生声音。

第十二章:声音的乐趣

为你的机器人项目添加声音可能是好机器人与酷炫机器人之间的区别。想想看,如果电影《星球大战》中的 R2-D2 没有发出任何声音,它会有多可爱。我们可以使用声音不仅仅是机器人。例如,如果运动传感器检测到运动,我们可能想要添加一个响亮的警报,或者当外面的温度恰到好处时,我们可能只想播放一段旋律。

在本章中,你将学习:

  • 如何将压电蜂鸣器连接到 Arduino

  • 如何将扬声器连接到 Arduino

  • 如何使用tone()函数生成声音

  • 如何使用 Arduino 播放音乐

简介

在本章中,我们将进行几个可以使用压电蜂鸣器或小型 8 欧姆扬声器的项目。通过使用蜂鸣器和扬声器,你将能够听到两者之间的区别,以帮助确定哪个适合我们的项目。

压电蜂鸣器体积小、可靠且非常便宜。在大多数电子项目中,它们比普通扬声器更容易安装和使用。这些蜂鸣器可以发出从柔和的嗡嗡声到响亮警报的广泛声音。

压电蜂鸣器,有时也称为压电扬声器,其发声原理与普通扬声器略有不同。这些蜂鸣器的工作组件是一块薄的压电材料薄片,通常粘附在金属振膜上。当电压施加到压电材料上时,它会变形。这导致金属振膜向前或向后弯曲。这种变形发生得非常快,导致陶瓷/金属弯曲元件以施加电压的频率振动,从而产生可听的声音。

以下照片显示了压电蜂鸣器的样子:

图片

较短的引脚应连接到地,而较长的引脚应连接到电源。

8 欧姆扬声器是一种典型的扬声器,它包含一个电磁铁,这是一个金属线圈,当通电时会产生磁场。通过反转线圈的方向,磁铁的极性会反转。这个电磁铁放置在一个普通磁铁的前面,磁铁的极性不能反转。施加到电磁铁上的电流方向会迅速改变,导致磁铁相互吸引和排斥,从而从连接到电磁铁的圆锥体产生声音。

以下图显示了 8 欧姆扬声器的样子:

图片

现在让我们看看这个项目所需的组件。

需要的组件

我们将需要以下组件来完成本章的项目:

  • 一个 Arduino Uno 或兼容板

  • 一个压电蜂鸣器

  • 一个 8 欧姆扬声器

  • 跳线

  • 一个面包板

电路图

这里是我们将在本章的所有代码示例中使用的电路图:

图片

此图显示,扬声器上的接地引脚和压电蜂鸣器上的接地引脚都连接到面包板上的接地轨。压电蜂鸣器的电源引脚连接到 Arduino 的 8 号引脚,而扬声器的电源线连接到 Arduino 的 7 号引脚。

代码

让我们从使用tone()函数开始。

使用tone函数

在本章的前几个示例中,我们将使用 Arduino 的tone()函数。此函数有两种形式。第一种形式接受两个参数,第一个参数是蜂鸣器或扬声器连接的引脚号,第二个参数是在该频率(赫兹)下播放声音。函数看起来是这样的:

tone(pinNumber, frequency);

当此函数仅使用两个参数时,声音将无限期播放。以下代码显示了如何使用此函数通过之前的电路图播放音符:

#define PIEZOPIN 7
#define SPEAKERPIN 8

int soundPin = PIEZOPIN;

void setup() {
  tone(soundPin, 1000);
}

使用此代码,tone()函数在setup()函数中使用,以 1000 赫兹播放。我们可以将声音引脚设置为压电蜂鸣器或扬声器引脚,具体取决于您想播放哪种声音。如果我们想播放一个直到用户确认才停止的连续声音,我们会使用此版本的tone()函数。

此函数的第二种形式接受第三个参数,即播放声音的持续时间(毫秒)。此函数看起来是这样的:

tone(pinNumber, frequency, duration);

此版本的tone()函数可以这样使用:

#define PIEZOPIN 7
#define SPEAKERPIN 8

int soundPin = PIEZOPIN;

void setup() {
  tone(soundPin, 1000, 1000);
}

此代码与之前的代码完全相同,只是声音只播放一秒钟。如果我们想播放具有特定持续时间的短音符,我们会使用此版本的tone函数。例如,播放一首歌,我们将在下一个示例中看到。

在我们能够使用 Arduino 播放歌曲之前,我们需要定义不同音符的频率。频率列表相当长,可以与本书的可下载代码一起下载。包含频率的文件名为pitches.h,频率定义如下:

#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093

现在让我们看看如何使用这些频率来播放一首歌。首先,我们需要创建一个名为pitches.h的标题头文件,其中将包含频率,然后使用以下行将其包含在主标签中:

#include "pitches.h"

现在我们需要定义组成歌曲的音符或旋律。这些音符将被存储在一个名为melody的数组中:

int melody[] = {
  NOTE_E5, NOTE_E5, NOTE_E5, NOTE_E5, NOTE_E5, NOTE_E5,
  NOTE_E5,
  NOTE_G5,
  NOTE_C5,
  NOTE_D5,
  NOTE_E5,
  NOTE_F5, NOTE_F5, NOTE_F5, NOTE_F5, NOTE_F5,
  NOTE_E5, NOTE_E5, NOTE_E5, NOTE_E5, NOTE_E5,
  NOTE_D5, NOTE_D5,
  NOTE_E5,
  NOTE_D5,
  NOTE_G5
};

歌曲中的每个音符都应该播放一定的时间。我们可以创建另一个包含每个音符持续时间的数组,我们将称之为tempo

int tempo[] = {
  4, 4, 2, 4, 4, 2,
  4,
  4,
  4,
  4,
  1,
  4, 4, 4, 4, 4,
  4, 4, 8, 8, 4,
  4, 4,
  4,
  2,
  2
};

我们将使用tone()函数来创建音符。使用此函数,我们不需要在setup()函数中设置任何内容。以下代码可以放入loop()函数中,以播放由melodytempo数组定义的歌曲:

// Get the number of notes in the song
int songSize = sizeof(melody) / sizeof(melody[0]);

//Loop through each note
for (int note = 0; note < songSize; note++) {

  //Calculate how long to play the note
  int noteDuration = 1000 / tempo[note];

  //Play the note
  tone(soundPin, melody[note], noteDuration);

  //Calculate how long to pause before playing next note
  int pauseBetweenNotes = noteDuration * 1.20;
  delay(pauseBetweenNotes);
}
delay(3000);

此代码首先通过将melody数组的大小除以数组中第一个元素的大小来计算melody数组中的音符数量。我们使用这种逻辑来计算数组中的元素数量,因为sizeof(melody)代码返回数组占用的字节数,而sizeof(melody[0])代码返回数组中第一个元素占用的字节数。存储单个整数需要两个字节,melody 数组中有 26 个音符。因此,sizeof(melody)代码将返回52,而sizeof(melody[0])代码将返回2

使用for循环遍历melodytemp数组。在for循环内,通过将一秒除以音符类型(tempo数组中的元素)来计算音符时长,其中四分音符等于 1000 除以 4,八分音符等于 1000 除以 8。

使用tone函数来播放melody数组中的音符,并持续计算出的时长。tone函数在播放音符时不会使应用程序暂停。因此,我们需要自己创建暂停。我们还想暂停的时间略长于音符的时长,以便在音符之间有轻微的停顿。为此,我们将音符时长乘以 1.2,然后使用delay()函数。在for循环完成后,在重新开始之前会有一个三秒钟的延迟。

最后这个例子展示了我们可以如何使用tone()函数和两个数组来播放一首歌,一个数组用于音符,另一个数组用于节奏。现在让我们看看如何使用一个库,它将使我们能够播放RTTTL铃声文本传输语言)格式的音乐。RTTTL 格式是由诺基亚开发的,用于将铃声传输到手机。

在 RTTTL 格式中播放铃声

目前 Arduino 库管理器没有我们可以下载来播放 RTTTL 文件的库。因此,我们需要下载并手动安装一个库。我们将使用 Arduino-rtttl-player,可以从这里下载:github.com/ponty/arduino-rtttl-player。我们需要创建一个库的 ZIP 文件以将其加载到 IDE 中。如果您没有访问可以压缩文件的实用程序,本书提供的可下载代码中已经包含了已压缩的库。

当我们创建 ZIP 文件以加载到 Arduino IDE 时,我们不想将 GitHub 存储库中下载的所有内容都压缩起来,因为 Arduino IDE 不会将 ZIP 文件识别为库文件。我们只想压缩包含库代码的目录,在 Arduino-rtttl-player 库的情况下,就是rtttl文件夹。

在我们下载库并创建一个包含库中的rtttl文件夹的 ZIP 文件后,我们希望将库加载到 Arduino IDE 中。为此,我们需要从主菜单中选择 Sketch | Include Library | Add .ZIP Library...,如下面的截图所示:

代码截图

在你选择添加 ZIP 库选项后,你将看到一个文件选择器,你可以浏览到你创建的 ZIP 文件的位置并选择它。如果库成功导入,你将在消息栏中看到一条消息,如下面的截图所示:

截图

现在我们准备播放一个 RTTTL 旋律。我们首先需要做的是通过在草图中加入以下include语句来将库包含到项目中:

#include <rtttl.h>

我们将想要包含蜂鸣器和扬声器,就像我们在早期项目中做的那样,以下代码:

#define PIEZOPIN 7
#define SPEAKERPIN 8

int soundPin = PIEZOPIN; 

我们需要定义要播放的歌曲。互联网上有大量的 RTTTL 代码。要找到一些,搜索rtttl songs,你应该能看到大量各种歌曲的 RTTTL 代码。对于这个例子,我们将播放《星球大战》的主题。以下代码包含了这首歌曲的 RTTTL 代码:

char *song = "Star Wars:d=8,o=5,b=180:f5,f5,f5,2a#5.,2f.,d#,d,c,2a#.,4f.,d#,d,c,2a#.,4f.,d#,d,d#,2c,4p,f5,f5,f5,2a#5.,2f.,d#,d,c,2a#.,4f.,d#,d,c,2a#.,4f.,d#,d,d#,2c";

要播放这首歌曲,请将以下代码添加到setup()函数中:

Rtttl player;
player.begin(soundPin);
player.play(song, 0);

我们使用 Arduino-rtttl-player 库中的begin函数来初始化库,并定义扬声器连接的引脚,然后使用play函数来播放歌曲。play函数的第二个参数是音阶。音阶设置得越高,歌曲播放的音调就越高。我通常将其设置为 0。

当这段代码运行时,你应该能认出《星球大战》的主题。

挑战

对于这个挑战,我们将继续使用《星球大战》的主题。假设我们想要制作一个看起来像《星球大战》中的 R2-D2 的机器人。我们可能会加入的一个特性是让它发出像 R2-D2 的声音。你将如何让这个机器人发出像 R2-D2 的声音呢?

摘要

在本章中,我们学习了如何将扬声器和蜂鸣器连接到 Arduino。然后我们学习了如何使用tone()函数来创建声音并播放歌曲。我们还看到了如何安装和使用第三方库来播放 RTTTL 文件。

在下一章中,我们将探讨如何使用 LCD 显示屏来显示消息。

第十三章:使用 LCD 显示屏

有时候我们希望有能力将 Arduino 的数据显示给用户。为此,我们可以使用 LCD 显示屏。我们可以使用多种类型的 LCD 显示屏,其中最流行的是 1602 显示屏。由于它们非常流行,你可以在互联网上找到大量关于如何使用它们的教程。虽然这些显示屏易于使用,但你在使用它们时受到的限制。

在本章中,我们将探讨一个我们可以做更多事情的显示屏。这个显示屏是诺基亚 5110 LCD 显示屏

在本章中,你将学习:

  • 如何将诺基亚 5110 LCD 连接到 Arduino

  • 如何将文本打印到 LCD 上

  • 如何在 LCD 上绘制圆形

  • 如何在 LCD 上绘制矩形

  • 如何在 LCD 上绘制圆角矩形

简介

诺基亚 5110 LCD 显示屏是一种基本的单色图形 LCD 显示屏,可用于众多项目。诺基亚最初在 20 世纪 90 年代末为手机开发此显示屏。此显示屏使用PCD8544 LCD 控制器/驱动器

拥有 LCD 显示屏可以极大地提升任何项目的用户界面,因为我们有能力直接向用户显示消息,让他们知道正在发生什么,或者在出现问题时提供特定的错误消息。5110 LCD 允许我们显示文本和图形。

5110 LCD 的显示区域大约为 4.2 厘米,有 84 × 48 个单独的像素。该显示屏价格低廉,使用 Adafruit 5110 LCD 库(本章将使用)非常容易。本章中我们将使用的 5110 LCD 看起来如下:

5110 LCD 显示屏安装在 PCB 板上,有八个引脚用于为显示屏供电并与之接口。这些引脚从左到右分别是:

  1. RST: 重置 – 低电平有效

  2. CE: 芯片选择 – 低电平有效

  3. DC: 模式(数据/指令)选择 – 选择命令模式(低)或数据模式(高)

  4. DIN: 串行数据线

  5. CLK: 串行时钟线

  6. VCC: 电源输入 3.3V

  7. BL: 背光 LED 控制 – 3.3V

  8. GND: 地

重置引脚将重置 5110 LCD 模块,其为低电平有效,意味着当引脚降至 0V 时将触发重置。当连接多个 SPI 外围设备时使用芯片选择引脚。该引脚也是低电平有效。

DC 引脚用于在数据模式或命令模式之间选择。当引脚为高时使用数据模式,当引脚为低时使用命令模式。

DIN 引脚是串行指令发送的输入引脚。

CLK 引脚是 SPI 模块共用的时钟引脚。时钟源连接到该引脚。

BL 引脚为背光显示屏供电。此引脚的电压不应超过 3.3V。如果引脚为低,则背光将关闭。

VCC 和 GND 分别是电源和地。电源的电压不应超过 3.3V。

让我们看看本章项目需要哪些部件。

需要的组件

  • 一个 Arduino Uno 或兼容板

  • 一个诺基亚 5110 LCD

  • 四个 10K 欧姆电阻

  • 一个 1K 欧姆电阻

  • 跳线

  • 一个面包板

电路图

以下图表显示了本章项目的电路图:

图片

诺基亚 5110 LCD 应使用 Arduino 的 3.3V 电源输出,而不是我们在早期项目中使用的 5V。我们使用内联电阻来保护 LCD 上的 3.3V 输入线。CE 线使用 1K 欧姆电阻,其余使用 10K 欧姆电阻。

以下图表显示了 5110 LCD 模块上的哪些引脚连接到 Arduino 上的哪些引脚:

5110 Arduino
RST 3
CE 4
DC 5
DIN 11
CLK 13
VCC 3.3V out
BL GND
GND GND

背光设置为接地以关闭。如果您想使用背光,可以将引脚连接到用于 VCC 引脚的 3.3V 电源输出。

现在我们来看看如何在 LCD 上显示项目。

代码

我们需要首先安装两个 Adafruit 库。这些是Adafruit GFX 库Adafruit PCD8544 诺基亚 5110 LCD 库。这些库需要安装,因为我们需要包含它们和 SPI 库。我们可以通过在代码开头添加以下include语句来实现:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>

我们不希望使用以下代码来初始化Adafruit_PCD8544类型的一个实例:

Adafruit_PCD8544 display = Adafruit_PCD8544(13, 11, 5, 4, 3);

参数是 Arduino 引脚号,CLK、DIN、DC、CE 和 RST 引脚分别连接到这些引脚。

setup()函数中,我们希望添加以下代码来设置Adafruit_PCD8544实例:

Serial.begin(9600);

display.begin();
display.setContrast(40);

现在可以将其余的代码放入setup()函数中进行测试,或者放入loop()函数中。让我们首先看看如何点亮显示屏上的单个像素。这可以通过使用以下代码中的drawPixel()函数来实现:

display.clearDisplay();
display.drawPixel(10, 10, BLACK);
display.display();

在我们在屏幕上绘制任何内容之前,我们希望清除显示屏和缓冲区。我们使用clearDisplay()函数来完成此操作。接下来,我们使用drawPixel()函数来点亮位于X坐标 10 和Y坐标 10 的单个像素。在 LCD 上显示任何内容之前,我们需要运行display()函数,如前述代码所示。重要的是要记住在向 LCD 绘制任何内容之前运行clearDisplay()函数,并在屏幕上绘制所有内容后运行display()函数以显示它们。

绘制一条线

我们可以将几个drawPixel()函数调用组合起来来绘制一条线,但使用以下代码中的drawLine()函数会更容易:

// draw a line
display.drawLine(3,3,30,30, BLACK);
display.display();

drawLine() 函数接受五个参数。前两个参数是线的起始点的 X/Y 坐标。接下来的两个参数是线的结束点的 X/Y 坐标,最后一个参数是绘制线的颜色。由于诺基亚 5110 LCD 是单色显示屏,这里的选项只有 BLACKWHITE

如果我们运行此代码,显示屏上会出现一条线,如下照片所示:

图片

显示文本

Adafruit 库还使得在诺基亚 5110 LCD 上显示文本变得非常简单。以下代码展示了我们如何显示文本:

// Display text
display.setTextSize(1);
display.setTextColor(BLACK);
display.setCursor(0,0);
display.println("Hello, world!");

// Display Reverse Text
display.setTextColor(WHITE, BLACK);
display.println(3.14);

// Display Larger Text
display.setTextSize(2);
display.setTextColor(BLACK);
display.print("This is larger text");
display.display();

setTextSize() 函数设置文本的大小。在第一个例子中,文本大小设置为 1setTextColor() 函数将设置文本的颜色。同样,由于诺基亚 5110 LCD 是单色显示屏,这里的选项是 BLACKWHITEsetCursor() 函数设置光标的位置,以便在屏幕上写入文本。在这种情况下,光标被设置为屏幕的左上角。最后,使用 println() 函数将 Hello World! 消息打印到屏幕上。

在下一个例子中,我们使用 setTextColor() 函数将前景色设置为 WHITE,背景色设置为 BLACK 以反转文本,然后使用 println() 函数将 PI 的值打印到屏幕上。由于我们没有调用 setTextSize() 函数,文本保持之前定义的大小,即 1

在最后一个例子中,文本大小设置为 2,文本颜色恢复为黑色。以下图片展示了运行此代码后屏幕的显示效果:

图片

旋转文本

我们还可以旋转文本。以下代码展示了如何进行操作:

display.setRotation(1);
display.setTextSize(1);
display.setTextColor(BLACK);
display.setCursor(0,0);
display.println("Hello, world!");
display.display();

setRotation() 函数将文本逆时针旋转。值为 1 将文本逆时针旋转 90 度。值 2 和 3 也可以用来将文本旋转 180 度和 270 度。以下照片展示了运行此代码后文本的显示效果:

图片

注意,如果文本长度超过单行可显示的长度,文本将自动换行。

基本形状

Adafruit 库还允许我们在 LCD 上创建基本形状。这些包括圆形、矩形和圆角矩形。还有一些函数可以用来创建这些形状并填充它们。以下代码和截图展示了如何使用圆形函数:

display.drawCircle(display.width()/2, display.height()/2, 6, BLACK);

图片

填充形状

display.fillCircle(display.width()/2, display.height()/2, 6, BLACK);

图片

圆形函数需要四个参数。前两个参数是圆心的 X/Y 坐标。在这两个示例中,圆心的位置是屏幕的中心。第三个参数是圆的半径,最后一个参数是圆的颜色,以及在fillCircle()函数中填充圆的颜色。

矩形

下两个示例展示了如何绘制矩形和填充矩形:

display.drawRect(15,15,30,15,BLACK);

图片

填充矩形

display.fillRect(15,15,30,15,BLACK);

图片

圆角矩形

矩形函数需要五个参数。前两个参数是矩形左上角的 X/Y 坐标。接下来的两个参数是矩形右下角的 X/Y 坐标,最后一个参数是绘制矩形的颜色以及fillRect()函数填充矩形的颜色。

下两个示例展示了如何使用 Adafruit 库绘制圆角矩形:

display.drawRoundRect(15,15,30,15,4,BLACK);

图片

填充圆角矩形

display.fillRoundRect(15,15,30,15,8,BLACK);

图片

圆角矩形函数的前四个参数与常规矩形函数相同,即矩形左上角和右下角的坐标。下一个参数是圆角的大小,最后一个参数是绘制圆角矩形及其填充的颜色。

从本章的示例中我们可以看到,使用诺基亚 5110 LCD 显示屏,我们可以做很多不仅仅是文本的事情,Adafruit 库使得使用它变得非常简单。

挑战

对于挑战,选择本书中之前的任何项目,并添加诺基亚 5110 LCD 显示屏。然后,而不是将输出显示到串行控制台,将输出显示到 LCD 显示屏。一个例子是将 LCD 显示屏添加到第十章的测距仪项目,避障和碰撞检测,并使用 LCD 显示距离。

概述

在本章中,我们看到了如何将诺基亚 5110 LCD 单色显示屏添加到我们的项目中。这些显示屏可以极大地增强几乎所有项目的用户体验,因为我们能够告诉用户正在发生什么,如果出现问题,是什么问题。

在下一章中,我们将看到如何将语音合成器和语音识别添加到我们的项目中。

第十四章:语音识别和语音合成

任何使用过亚马逊的 Echo、谷歌的 Home 扬声器或甚至苹果的 Siri 的人都知道语音识别和语音合成是多么强大和方便。现在想象一下,如果我们能以更小的规模将这些功能添加到我们的智能设备中?如果我们能,我们将能够直接对我们咖啡壶说话,并告诉它在早上开始煮咖啡,或者命令我们构建的机器人。

在本章中,我们将探讨如何使用 MOVI 盾牌将语音识别和语音合成添加到任何 Arduino 项目中。在本章的整个过程中,我们将学习:

  • 如何使用 MOVI 盾牌进行语音识别

  • 如何使用 MOVI 盾牌进行语音合成

  • 如何创建一个语音激活的温度计

简介

MOVI这个名字代表My Own Voice Interface。Audeme 的 MOVI Arduino 盾牌是一个非常易于使用的语音识别和语音合成盾牌。这个盾牌可以直接与Uno R3DuemilanoveMega 2560Leonardo Arduinos一起使用。然而,当 MOVI 盾牌连接时,你不应该通过 USB 连接器为板子供电。MOVI 盾牌需要至少 7V 的电压。因此,如果你尝试从 USB 连接中为其供电,可能会损坏 MOVI 和/或 Arduino。

你可以在此处了解 MOVI 盾牌并下载用户手册:www.audeme.com

当使用 MOVI 连接到 Arduino 进行供电和编程时,你将想要通过直流电源输入连接器使用 9V 的输入为 Arduino 供电。一旦板子供电启动,你就可以连接 Arduino 和你的电脑之间的 USB 线进行编程。由于板子最初是通过直流电源输入连接器供电的,它将继续从这个来源而不是 USB 连接中获取电力。

非常重要:当 MOVI 盾牌连接时,不要从 USB 连接器为 Arduino 供电。

在编程 MOVI 时,建议连接一个外部麦克风,以获得更好的语音识别,以及耳机,这样你可以听到 MOVI 的响应。以下照片显示了连接到 Arduino 的 MOVI,以及连接到 MOVI 的外部麦克风和耳机:

图片

MOVI 盾牌上有一个内置麦克风,可以用来代替外部麦克风。然而,没有内置扬声器。为了接收音频反馈,包括错误和系统消息,你必须将耳机或外部扬声器连接到 MOVI 盾牌。扬声器的阻抗应该是 32 欧姆,这是耳机标准。你不应该将 4-或 8-欧姆的扬声器连接到外部扬声器插孔。

MOVI 可以用作按钮、遥控器或任何其他控制输入的替代品。正如我们在本章的示例项目中将看到的,我们可以使用 MOVI 发出 Arduino 可以响应的语音命令。

MOVI 盾板的一个最佳特性是不需要互联网连接。这减轻了与其他语音控制设备(如亚马逊 Echo 和谷歌 Home 扬声器)通常相关的任何隐私问题,因为这些设备不会将数据发送到外部服务器。

MOVI 盾板上有一个 LED 灯,它指示盾板的状态。以下列表显示了 MOVI 盾板可能处于的不同状态以及相应的 LED 状态:

  • LED 关闭:表示盾板已关闭或 MOVI 没有足够的电力运行

  • LED 闪烁越来越快:MOVI 正在启动

  • LED 随机闪烁:MOVI 正在向 SD 卡写入

  • LED 以恒定频率闪烁:可能有 SD 卡问题

  • LED 亮起:表示 MOVI 已开启并准备就绪

MOVI 盾板是你可以与 Arduino 一起使用的最有趣和最有意思的板之一。如果你对用它做的一些更高级的事情感兴趣,你应该查看 MOVI 库中提供的示例。

在本章中,我们将使用我们在第九章,“环境传感器”中使用的 DHT-11 温度传感器和 MOVI 盾板创建一个语音激活的温度计。为了将温度传感器和 MOVI 盾板连接到 Arduino,我们首先需要将 MOVI 盾板连接到 Arduino,然后将 DHT-11 温度传感器连接到 MOVI 盾板的引脚插座上。

让我们看看这个项目所需的组件。

需要的组件

在本章中,你需要以下组件。

  • 一个 Arduino Uno 或兼容板

  • 9V 电源,如电源插座适配器

  • 一个 MOVI 盾板

  • 一个 DHT-11 温度/湿度传感器

    • 一个 4.7K 欧姆电阻

    • 跳线

    • 一个面包板

电路图

以下图示显示了如何连接 DHT-11 温度传感器进行此项目。记住在连接温度传感器之前将 MOVI 盾板连接到 Arduino。以下图示中的电阻是一个 4.7K 欧姆电阻:

图片

现在,让我们看看我们的语音激活温度传感器的代码。

代码

你应该已经从第九章,“环境传感器”中的示例下载了 DHT-11 库,但你需要下载 MOVI 库。如果你进入库管理器并搜索Movi,你会找到几个与该术语匹配的库。寻找Audeme LLC 的 MOVI 语音对话盾板库并下载它。

我们将开始绘制草图,包括 MOVI 和 DHT 库。以下代码显示了如何包含这两个库:

#include <DHT.h>
#include <MOVIShield.h>

接下来,我们将定义 DHT 引脚/类型,并创建一个 DHT 类型的实例,就像我们在第九章,“环境传感器”中所做的那样:

#define DHT_PIN 3
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);

现在我们将想要创建一个 MOVI 类型的实例,如下面的代码行所示。布尔值false表示我们不希望开启串行调试:

MOVI movi(false);

最后,我们需要一个字符数组,用于创建包含当前温度的句子,这样 MOVI 盾就可以在我们请求时告诉我们温度。

char answer[21];

setup()函数中,我们需要初始化 DHT 温度传感器和 MOVI 盾。以下代码展示了setup()函数:

void setup() {
  dht.begin();
  movi.init();
  movi.callSign("buddy");
  movi.addSentence("temp");
  movi.train();
}

此函数首先通过调用dht类型的begin()函数初始化 DHT 温度传感器。接下来,我们通过调用init()函数初始化movi类型。此函数必须首先调用,以初始化movi类型。

大多数语音激活设备,如亚马逊 Echo,都是通过呼号激活的。对于亚马逊的设备,呼号是"Alexa"。在我们的例子中,MOVI 盾也将使用一个呼号来激活它。呼号可以通过callSign()方法设置,其中要使用的呼号作为字符串传入。在这个例子中,呼号将是"buddy"。

接下来,我们将想要添加 MOVI 可以匹配的句子或单词。我们使用addSentence()函数来完成这个操作。在这个例子中,我们将尝试匹配单词"temp."。我们确实有选择用完整的句子或单词来训练 MOVI 盾的选项。如果您希望 MOVI 盾能够识别一个句子,建议添加完整的句子,即使您需要添加相同句子的多个版本。通过添加完整的句子,MOVI 的算法可以用来识别句子,这提供了更高的准确性。还建议所有训练过的句子大小接近相同。如果说了很多单词,单个长句子将比很多更小的句子更受青睐。

最后,调用train()方法来告诉 MOVI 盾我们已经添加了所有句子和呼号。第一次添加句子或呼号时,MOVI 盾需要一些时间来训练,但如果呼号和句子在应用程序的构建之间保持不变,那么 MOVI 盾将启动得非常快。

现在已经完成了setup()函数,让我们看看loop()函数。以下代码展示了loop()函数:

void loop() {
  signed int res=movi.poll();
  if (res == 1) {
    float fahreheit = dht.readTemperature(true);
    int tmp = (int)fahreheit;
    sprintf(answer, "The temperature is %02d", tmp);
    movi.say(answer);
  }
}

在第一行,我们使用movi实例的poll()函数。如果没有任何事件发生,此函数将返回零(0),如果匹配到一个句子,则返回一个正数。返回的数字是匹配到的句子编号。在我们的例子中,我们只有一个句子。因此,唯一可能的匹配是句子编号1

如果找到与句子的匹配,则从 DHT-11 温度传感器读取当前温度,然后通过类型转换将其从浮点值转换为整数值。

为了构建我们希望 MOVI 盾牌说的字符串,我们使用了sprintf()函数。这个函数可以用来构建一个字符数组。在这个例子中,我们以句子The temperature is开始,然后使用%02d格式添加温度值。这告诉sprintf()函数向字符串添加一个两位整数。创建的字符数组存储在在草图开头创建的 answer 数组中。

我们使用movi实例中的say()函数,让 MOVI 盾牌通过连接的耳机或扬声器告诉我们当前的温度。

现在让我们运行这个项目。

运行项目

第一次运行项目时,需要一点时间来训练 MOVI 盾牌。等待盾牌说它已准备好,然后使用呼叫信号来激活它。一旦你说出呼叫信号,如果你 MOVI 盾牌识别到了,你会听到一个蜂鸣声。如果 MOVI 识别到了呼叫信号,说出句子"temp."。MOVI 盾牌应该通过告诉你通过 DHT-11 温度传感器读取的当前温度来回应。

这个例子仅触及了你可以用 MOVI 盾牌做什么的非常基础的部分,并为你提供了足够的开始。

挑战

有其他非常实用的函数可以与 MOVI 实例一起使用。以下是一些你可以尝试添加到项目中的附加函数:

  • isReady(): 如果 MOVI 准备就绪,将返回 true 的布尔值,如果不准备就绪,则返回 false。

  • setVolume(int volume): 将 MOVI 的输出音量设置为 0(静音)到 100(全音量)。

  • setVoiceGender(bool female): 将 MOVI 的声音性别设置为男或女。true 值将设置为女性声音,而 false 值将设置为男性声音。

  • setThreshold(int threshold): 设置语音识别器的噪声阈值。值范围从 2 到 95。在嘈杂的环境中,15 是一个好的值,而在非常嘈杂的环境中,30 是一个好的值。

  • welcomeMessage(bool on): 将 MOVI 的欢迎信息设置为开启或关闭。

  • beeps(bool on): 开启或关闭识别蜂鸣声。

  • ask() 和 ask(string question): 直接监听而不等待呼叫信号。如果传递了一个字符串,则 MOVI 将首先提出问题然后再进行监听。

挑战是尝试在示例项目中添加一些这些函数,并看看你还能用 MOVI 盾牌做什么。还要尝试添加 MOVI 可以监听的额外句子。

概述

在本章中,我们看到了如何使用 MOVI 盾牌进行语音识别和语音合成。我们使用语音识别来监听特定的命令,并使用语音合成来响应命令。

在下一章中,我们将探讨如何使用直流电机和电机控制器。

第十五章:直流电机和电机控制器

到目前为止,在这本书中,所有的项目都是固定项目。我所说的固定项目是指这些项目无法自行移动。在本章中,我们将探讨如何将直流电机添加到任何项目中,使其能够自行移动。在使用直流电机时,我建议使用电机控制器来控制它们。电机控制器使我们能够非常容易地将外部电源连接到电机,并控制电机的方向和速度。

在本章中,你将学习:

  • 刷式直流电机的工作原理

  • H 桥的工作原理

  • 如何使用 L298 和 L293D 电机控制器

简介

直流电机是一类将电能转换为物理运动的旋转电气设备。直流电机有多种类型;然而,在本章中,我们将探讨一种特定类型,即刷式直流电机

刷式直流电机广泛应用于各种应用中,从玩具和机器人到电动窗户和电动工具。刷式直流电机的一些优点是它们的初始成本低、控制简单和低速扭矩。这些电机的缺点是维护成本高和在高强度环境中的使用寿命短。对于我们通常使用 Arduino 进行的原型设计和机器人项目,刷式直流电机的缺点通常不是问题。

刷式直流电机的中心是一个旋转的转子,其中包含一个电磁铁。在旋转的转子外部是一个永久的、静止的磁铁。当转子中的电磁铁通电时,会产生一个磁场,吸引和排斥永久的静止磁铁。这导致转子开始旋转。

为了使转子持续旋转,电磁铁的极性需要反转。为此,使用了一个称为换向器的分段铜套,它位于电机的轴上。随着电机的转动,电刷在换向器上滑动,接触换向器的不同部分,导致磁铁的极性切换。

以下图表说明了刷式直流电机的部件:

图片

刷式直流电机有多种形状和尺寸。其中一些电机内置了变速箱,可以改变电机旋转时的扭矩和速度。以下照片展示了刷式直流电机的几个示例:

图片

直接从 Arduino 为直流电机供电(除了演示目的之外),通常不是一个好主意,因为从引脚头提供的电压和电流相当有限。我们可以使用电机控制器从 Arduino 控制直流电机的方向和速度,同时仍然提供外部电源为其供电。在本章中,我们将探讨如何使用以下照片中所示的 L298 双 H-桥电机驱动器,以及如何使用 L293D 芯片:

L298 电机驱动器使我们能够控制两个电机的方向和速度。此驱动器允许我们控制从 5V 到 35V 的电机,最大电流为 2A。如果供电电压为 12V 或更低,我们还可以使用 5V 输出为 Arduino 供电。L298 电机驱动器有几个标记的输入、输出和电源连接器。这些从左到右的输入如下:

  • ENA:启用电机 A 并控制电机速度

  • IN1 和 IN2:控制电机 A 的方向

  • IN3 和 IN4:控制电机 B 的方向

  • ENB:启用电机 B 并控制电机速度

ENA 和 ENB 通常在引脚上跨接跳线。为了控制刷式直流电机,我们需要移除这些跳线并将引脚连接到 PWM 端口。输出如下:

  • OUT1 和 OUT2:输出功率到电机 A

  • OUT3 和 OUT4:电机 B 的输出功率

从左到右,电源输入如下:

  • Vmotor:用于为电机供电的外部电源

  • GND:地

  • Vout:5V 输出,可用于为 Arduino 供电

如果我们正在构建一个空间有限的项目,而不是使用 L298 双 H-桥电机驱动器这样的电机控制器,我们可以使用如 L293D H-桥集成电路 这样的集成芯片。L293D 芯片可以驱动两个电机,类似于 L298 电机驱动器,并且可以用 600mA 的稳定电流为电机供电,最大可达 1.2A。以下图示显示了 L293D IC 的引脚排列:

L298 电机控制器和 L293D 芯片都是 H-桥。让我们快速了解一下 H-桥的工作原理。H-桥是一种电路,允许我们对电机施加电压,使其可以正向或反向运行。H-桥这个术语来自电路的典型图形表示,看起来像一个大写的 H。以下图示显示了 H-桥的工作原理:

H-桥通常由四个固态开关组成。正如我们在前面的图像中看到的那样,当开关 1 和 3(I1I3)打开,开关 2 和 4(I2I4)关闭时,电机的右侧连接到电源,左侧连接到地,使电机向一个方向旋转。如果开关 1 和 3(I1I3)关闭,开关 2 和 4(I2I4)打开,那么电机的左侧连接到电源,右侧连接到地,使电机向另一个方向旋转。

让我们看看本章项目中我们将需要的部件。

需要的组件

在本章中,你需要以下组件。

  • 一个 Arduino Uno 或兼容板

  • 一个 L298 电机驱动器

  • 一个 L293D H-桥芯片

  • 两个刷式直流电机

  • 一个外部 12V 电池(或其他外部直流电源,如 9V 电池)

  • 跳线

  • 一个面包板

电路图

在本章中,我们将创建两个项目。第一个项目将使用 L298 电机驱动器控制单个电机,第二个项目将使用 L293D 芯片控制单个电机。以下是 L298 电机驱动器项目的电路图:

在我们解释这个图之前,让我们也看看 L293D 芯片电路的电路图,因为这两个图之间有很多相似之处:

注意这两个图的第一点是电路包含一个公共地。这意味着 Arduino、电池和电机控制器(L298 和 L293D)上的地连接器都连接在一起。在这些包含多个电源的项目中,我们必须在所有设备和电源之间有一个公共地。

在这两个电路中,Arduino 上的 10 个引脚的 PWM 输出连接到电机控制器的使能引脚。同样,在这两个电路中,数字 2 和 3 引脚连接到电机控制器的 IN1 和 IN2 引脚。这使得我们可以为这两个项目使用相同的代码。

现在我们来看看这些项目的代码。

代码

控制电机的代码只需要使用 Arduino 标准库中的标准digitalWrite()analogWrite()函数,因此不需要为这段代码使用外部库。因此,我们的代码将从定义连接到电机控制器的 Arduino 引脚开始。以下代码就是这样做的:

#define MC_IN_1 3
#define MC_IN_2 2
#define MC_ENABLE 10

现在我们需要在setup()函数中配置输出引脚,如下代码所示:

void setup() {
  pinMode(MC_ENABLE, OUTPUT);
  pinMode(MC_IN_1, OUTPUT);
  pinMode(MC_IN_2, OUTPUT);
}

现在我们已经准备好给电机供电了。让我们将以下代码放入loop()函数中:

void loop() {
  digitalWrite(MC_IN_1, HIGH);
  digitalWrite(MC_IN_2, LOW);
  analogWrite(MC_ENABLE, 250);
  delay(2000);
  analogWrite(MC_ENABLE, 0);
  delay(1000);
  digitalWrite(MC_IN_1, LOW);
  digitalWrite(MC_IN_2, HIGH);
  analogWrite(MC_ENABLE, 125);
  delay(2000);
  analogWrite(MC_ENABLE, 0);
  delay(1000);
}

loop()函数一开始使用digitalWrite()函数将电机控制器上的输入1设置为HIGH,输入2设置为LOW。然后使用analogWrite()函数在电机控制器上的使能引脚上创建一个占空比为 250。记住 PWM 引脚的最大占空比为 255;因此,一旦执行analogWrite()函数,电机应该以接近全速开始旋转。

为了使直流有刷电机旋转,一个输入必须为HIGH,另一个必须为LOW。如果输入都是HIGH,都是LOW或者使能引脚上的占空比为 0,电机将不会旋转。以下图表显示了这一点:

IN1 IN2 Enable Duty Cycle Result
HIGH LOW >0 Motor spins in the same direction
LOW HIGH >0 The motor spins in the opposite direction
HIGH HIGH Motor stopped
LOW LOW Motor stopped
0 Motor stopped

在调用analogWrite()函数之后,使用delay()函数暂停应用程序的执行两秒钟,以便电机运行。然后再次调用analogWrite()函数将占空比设置为 0,这将停止电机旋转,并延迟一秒钟,给电机一个停止的机会。

然后使用digitalWrite()函数将一个引脚设置为LOW,另一个引脚设置为HIGH,这与它们最初设置的方式相反,这将使电机以相反方向旋转。然后调用analogWrite()函数将占空比设置为 125,这将使电机以半速开始旋转。然后使用delay()函数暂停应用程序的执行两秒钟,然后再次停止电机。

运行项目

当运行此代码时,电机应在一个方向上旋转两秒钟,停止一秒钟,然后在另一个方向上旋转两秒钟,停止一秒钟,然后重新开始。

挑战

对于本章的挑战,尝试给两个项目都添加第二个电机,然后修改代码,使两个电机同时旋转。你也可以尝试将电机接线,以便在将 IN1 和 IN3 引脚设置为HIGH值,IN2 和 IN4 引脚设置为LOW值时,它们会以相同方向旋转。

摘要

在本章中,我们学习了直流有刷电机的基本工作原理以及如何使用 L298 电机驱动器和 L293D 芯片来控制直流有刷电机。我们还学习了 H 桥的工作原理。

在下一章中,我们将探讨另一种类型的直流电机。这种电机被称为伺服电机,用于需要精确定位的项目中,例如在机械臂上。

第十六章:伺服电机

当给刷式直流电机供电时,它将开始连续旋转,直到切断电源。这使得刷式直流电机非常适合像机器人轮子或风扇叶片这样的物品。有时我们需要对电机旋转的精确度有更多的控制。例如,为了控制机器人臂,我们需要电机以精确的量旋转,以便将臂放置在所需的位置。对于此类应用,我们可以使用伺服电机。

在本章中,您将学习:

  • 如何控制伺服电机

  • 如何使用 Arduino 伺服库

  • 如何为伺服电机供电

简介

我们将与 Arduino 一起使用的伺服电机类型相当小巧,但大多数都具有相当高的扭矩,并且非常节能。这使得我们可以将这些电机用于工业级应用,如机器人臂、输送带、相机中的自动对焦镜头,甚至是太阳能板的太阳能跟踪系统。

伺服电机由一个直流电机组成,它执行实际工作;一个电位计,用于控制电机接收的功率;控制电路,用于控制电机的运动和齿轮。以下照片显示了一个连接到机器人爪子的伺服电机:

伺服电机包含三根线,用于控制信号、电源和地线。信号线通常是橙色或黄色。电源通常是红色,地线通常是棕色或黑色。

一些较小的伺服电机可以使用 Arduino 上的 5V 输出;然而,在本章中,我将使用MG996R 高扭矩电机,它可以处理高达 7.2V 的电压,并且运行电流为 500mA 至 900mA。因此,我们将将其连接到一个包含 4 节 AA 电池的外部 6V 电池组。我建议您查阅您的伺服电机的数据表,以确定伺服电机的正确电源输入。

注意:虽然一些较小的电机可以通过 5V 输出供电,但我建议在为电机供电时始终使用外部电源。

地线应连接到与电池组和 Arduino 共用的公共地线。信号线应连接到 Arduino 的 PWM 输出引脚。

PWM 引脚的占空比决定了伺服轴的位置。当伺服电机的轴处于所需位置时,会切断对电机的供电。电机将旋转的速度与实际位置和所需位置之间的差异成正比,这意味着所需位置离实际位置越远,电机旋转的速度就越快。这使得伺服电机非常高效,因为它只工作到所需的程度。

不同的伺服电机有不同的最大转动半径。大多数伺服电机的转动半径要么是 120 度(每个方向 60 度)要么是 180 度(每个方向 90 度)。在本章中我将使用的 MG996R 伺服电机的最大转动半径为 120 度。一旦伺服电机旋转到所需的位置,它将尝试保持该位置,并抵抗任何将其推出位置的尝试。

让我们看看本章项目所需的组件。

需要的组件

在本章中,你需要以下组件:

  • 一个 Arduino Uno 或兼容板

  • 一个伺服电机(代码已在 MG996R 伺服电机上测试过。然而,任何标准伺服电机都应适用)

  • 一个电位计

  • 一个 4 AA 电池盒和电池,为伺服电机供电

  • 跳线

  • 一个面包板

电路图

下面的图示显示了如何将伺服电机连接到 Arduino:

图片

在这个项目中,我们将使用电位计来控制伺服电机的位置。请注意,电位计使用 Arduino 的 5V 电源,而伺服电机使用来自电池的 6V(4 × 1.5V);然而,这两个电源共享一个公共地。现在让我们看看控制伺服电机的代码。

代码

Arduino IDE 和 Web 编辑器都附带了一个伺服库,我们可以通过包含头文件来简单地使用它。以下代码将执行此操作:

#include <Servo.h>

接下来,我们需要定义伺服电机和电位计连接到的引脚。以下代码将信号线连接到数字 3 引脚,将电位计连接到 Arduino 的模拟 0 引脚:

#define SERVO0_POT 0
#define SERVO0_OUT 3 

现在我们需要定义一个 Servo 类型的实例,如下面的行所示:

Servo servo0;

setup() 函数中,我们需要从 servo 实例调用 attach() 方法来初始化实例并告诉它伺服电机连接到哪个引脚。以下代码显示了这一点:

void setup() {
  servo0.attach(SERVO0_OUT);
}

我们将想要定义一个函数,该函数将读取电位计并根据电位计旋转的程度设置伺服电机的位置。

void setServo(int pot, Servo out) {
  int servo = analogRead(pot);   
  long int servo_val = map(servo, 0, 1023, 0, 120);
  out.write(servo_val);
}

此函数将接受一个整数,即电位计连接到的引脚。调用 analogRead() 函数来读取电位计连接到的引脚。我们使用 map() 函数将读取的模拟引脚值(0-1023 的值)映射到伺服电机可以移动的 120 度。然后使用伺服类型的 write() 函数将此值写入伺服电机,使伺服电机调整其位置。

然后从 loop() 函数中调用 setServo() 函数来读取电位计并设置伺服电机,如下面的代码所示:

setServo(SERVO0_POT, servo0);
delay(15); 

我们创建setServo()函数而不是直接将其代码放入loop()函数中的原因,是这样做可以更容易地添加多个伺服电机。例如,如果我们想创建一个带有五个伺服电机的机械臂,我们可以非常容易地通过像第一个一样设置伺服电机,然后在setup()函数中使用以下代码:

setServo(SERVO0_POT, servo0);
setServo(SERVO1_POT, servo1);
setServo(SERVO2_POT, servo2);
setServo(SERVO3_POT, servo3);
setServo(SERVO4_POT, servo4);
delay(15);

如果我们有像setServo()函数中的代码那样可能被多次使用的代码,将其放入一个单独的函数中总是一个好主意。

如果运行此项目,随着电位器的旋转,伺服电机的位置将发生变化。

挑战

在本章的挑战中,你需要一个 6 自由度(DOF)的机械臂。以下图片展示了 6 自由度机械臂的外观:

图片

对于这个挑战,你需要想出如何将剩余的伺服电机连接到 Arduino 以及正确的电源配置。你可以从亚马逊或 eBay 订购 6 自由度机械臂套件。访问他们的网站,并搜索6 自由度机械臂。这些套件的价格因机械臂/爪的大小和功率而大不相同。你可以购买预组装的机械臂,或者作为需要自己组装的套件。

概述

在本章中,我们学习了伺服电机的工作原理以及如何使用 Arduino 来控制它们。我们还看到了组成伺服电机的组件。在下一章中,我们将看到如何使用继电器板。

第十七章:使用继电器

有时候,我们想要控制高电压的物品,如灯、风扇或任何其他家用电器。然而,Arduino 以及本书中迄今为止的所有项目都使用直流电DC),而你的家用电器使用交流电AC)。交流电和直流电之间存在显著差异。在本章中,我们将探讨如何使用 Arduino 和继电器来控制使用交流电的灯。

在本章中,你将学习:

  • 什么是继电器

  • 如何使用继电器控制交流供电设备

  • 如何使用继电器控制直流供电设备

  • 如何使用继电器隔离电路

简介

警告:在本章中,我们将根据你所在的国家使用 120V 或 240V 交流电,这比本书中我们使用过的任何其他东西都要强大得多。

如果不当处理、使用不当或不正确使用继电器或电源线,可能会导致严重伤害,甚至死亡。在尝试本章中的项目之前,请确保你已经阅读并理解了你的继电器板的工作原理,它所标称的电压和电流,以及使用交流电的风险。

不要害怕寻求专业帮助,如果你对任何事情不确定。交流电比我们在本书中之前使用的直流电危险得多。

如果你在一个项目中使用交流电感到不舒服,或者你对它不够熟悉,那么本章将提供两个电路图。一个图将展示如何将交流供电的灯连接到继电器,另一个图将展示如何使用继电器连接电机和 9V 电源。本章的代码将适用于任何项目,并将使灯或电机开启和关闭。

在对交流供电设备进行操作时,始终确保在开始工作之前将设备拔掉插头。来自电源插座的电击可能导致严重伤害,甚至死亡。

继电器可以被视为一个电开关。许多继电器使用电磁铁来机械地操作开关。然而,还有其他控制继电器的方法。一个例子是固态继电器,它不使用任何机械部件。我们与 Arduino 一起使用的多数继电器使用电磁铁来操作开关。

当需要将两个或更多电路彼此隔离,同时仍能从其中一个电路(例如 Arduino)控制另一个电路中的组件时,会使用继电器。当你在项目中使用所有组件的直流电时,通常不需要隔离电路;然而,当你想要控制一个交流供电设备,如台灯时,就需要一个像继电器这样的设备。

如我们之前提到的,如果你不熟悉在交流供电设备上工作,那么请使用直流电机项目而不是灯项目。这两个项目使用相同的概念;然而,一个由交流电供电,另一个由直流电供电。

AC 供电设备的插头包含两根线。为了将此设备连接到继电器,我们需要剪断其中一根线,并将一端连接到继电器上的 COM 连接,另一端连接到继电器上的 NO 连接。另一根电缆保持完好。以下照片说明了这一点:

图片

电磁继电器上的NO标签代表常开,而NC标签代表常闭。COM 连接,通常位于 NO 和 NC 连接之间,是公共连接。当继电器关闭时,电源通过 NC 连接,这意味着如果继电器关闭时 AC 供电设备应该开启,那么它应该连接到 COM 和 NC 连接。如果 AC 供电设备仅在继电器开启时开启,那么它应该连接到 COM 和 NO 连接,就像我们在这里做的那样。

继电器通常标有最大电压为 230 VAC 至 250 VAC 或 30 VDC,电流为 10A。您需要验证项目中的继电器能否处理您正在运行的电压和电流。

以下图示展示了继电器的工作原理:

图片

在前面的图像中,我们看到当没有电压施加到继电器上时(左侧的图像),NC 引脚连接到 COM 端子,从而完成该电路。当施加电压时,电枢被拉到 NO 引脚,连接到 COM 端子,完成该电路。

在前面的照片中,使用了单个继电器的板。然而,还有包含多个继电器的板,这使我们能够控制多个电路中的组件。以下照片展示了一个包含四个继电器的板:

图片

本章中展示的继电器适用于交流和直流电路。因此,它们可以用于本章中的任何项目。如果您愿意,甚至可以尝试两个项目。

让我们看看本章项目所需的组件。

需要的组件

  • 一个 Arduino Uno 或兼容板

  • 一个继电器板

  • 跳线

  • 您希望用 Arduino 控制的 AC 供电设备,例如台灯,或者如果您希望使用直流电源设备而不是交流电源设备

  • 一个 9V 电池适配器,带有直流电机的电池

电路图

以下图示展示了我们如何将 AC 供电设备和 Arduino 连接到继电器:

图片

AC 供电设备按照简介部分所述连接到继电器。继电器上的VCC引脚连接到 Arduino 的5V输出,继电器上的GND引脚连接到 Arduino 的GND引脚。我们将 Arduino 的数字 3 引脚连接到继电器上标记为IN的引脚。数字 3 引脚将用于控制继电器。

以下图示展示了我们如何使用继电器通过 Arduino 控制直流电机和 9V 电源:

图片

在之前的电路中,我们强调了在各个组件之间需要有公共地线的重要性;然而,在这个电路中,你会注意到 Arduino 和电机/9V 电池电路之间没有公共地线。当使用继电器时,继电器两边的电路是相互隔离的;因此,我们不想在这两者之间有公共地线。如果你希望两个电路之间有公共地线,那么继电器就不再必要了,因为继电器是用来隔离两个电路的。

现在,让我们看看这些电路的代码。

代码

以下为本章项目中代码的示例:

#define RELAY 3 

void setup() {
  pinMode(RELAY, OUTPUT);
}

void loop() {
  digitalWrite(RELAY, HIGH);
  delay(3000);
  digitalWrite(RELAY, LOW);
  delay(3000);
}

现在代码看起来应该很熟悉了。此代码首先定义继电器连接到 Arduino 的数字 3 号引脚。在setup()函数中,我们启用继电器引脚以输出模式,因为我们想使用digitalWrite()函数来开启和关闭继电器。

loop()函数中,我们使用digitalWrite()函数将继电器引脚设置为高电平,暂停三秒钟,然后再次使用digitalWrite()函数将继电器引脚设置为低电平,并最终再次暂停三秒钟。这样,连接到继电器的组件每三秒钟就会开启和关闭一次。此代码适用于本章之前展示的交流电路或直流电路。

挑战

对于本章的挑战,使用带有四个继电器的板,并尝试将可以由 Arduino 控制的组件连接到每个继电器上。请注意,每个电路都需要其自身的独立电源。

概述

在本章中,我们看到了如何使用继电器控制交流电和直流电组件。我们还了解到,继电器两边的电路需要各自的独立电源。

在下一章中,我们将看到如何使用无线电频率远程控制 Arduino。

第十八章:远程控制 Arduino

当我还是个孩子的时候,我的父母经常用我和我的妹妹作为电视机的遥控器,因为那时候,电视机并没有配备遥控器。幸运的是,Zenith 的一名工程师 Eugene Polley 提出了用遥控器控制电视的想法,从而让数百万的孩子免于为父母换频道。遥控器极大地增强了我们与电视机的互动方式,也可以为你的 Arduino 项目做到同样的事情。

在本章中,你将学习:

  • 如何将射频遥控器连接到 Arduino

  • 如何确定射频遥控器上按下了哪个按钮

  • 如何将红外遥控器连接到 Arduino

  • 如何确定红外遥控器上按下了哪个按钮

简介

在本章中,我们将探讨几种我们可以远程控制 Arduino 项目的方法。对于第一个项目,我们将使用 Keyestudio 红外(红外)接收器,它使用HX1838 红外控制模块。HX1838 红外控制模块被用于许多 Arduino 可用的红外接收器。因此,你不需要特别购买我们这里使用的 Keyestudio 型号。

红外发射器有一个 LED,它发射红外辐射,被红外接收器接收。当在遥控器上按下按钮时,发射器上的 LED 会非常快速地闪烁一秒钟的几分之一,接收器将读取闪烁模式并对其进行解释。

我们在本章中将使用的 Keyestudio 红外接收器看起来如下照片所示:

标有S的引脚是信号引脚,应该连接到 Arduino 的一个数字引脚上。标有+号的引脚应该连接到 5V,标有-号的引脚应该连接到地线。

使用红外接收器作为项目遥控器的一个真正不错的地方是,你可以使用几乎任何红外遥控器作为发射器。例如,我可以使用与我其中一个红外接收器一起来的这个遥控器:

或者,我可以用我多余的任何红外电视遥控器,如下面照片所示:

然而,有些遥控器不使用红外技术,例如使用蓝牙 4.0 的 Apple TV 遥控器。因此,它们不能与红外接收器一起使用。

红外遥控器确实有几个缺点,最大的缺点是它们必须与接收器有视线才能进行通信。这意味着发射器必须直接指向接收器;否则,接收器将无法读取传输。红外遥控器的另一个缺点是,它们只有在 30 英尺(10 米)以内才真正有用。

而不是使用红外发射器/接收器,我们可以使用射频RF)发射器和接收器。在本章中,我们将探讨如何使用基本的四键遥控器 RF 发射器和接收器,如下面的照片所示:

使用 RF 发射器和接收器的两个最大优点是 RF 信号可以传播得更远,并且它们可以穿过普通墙壁,这意味着不需要视线对准。与几乎可以与任何红外发射器一起工作的红外接收器不同,如果 RF 发射器和接收器没有设计成一起工作,并且设置为相同的频率,它们将无法通信。如前照片所示,RF 发射器也比红外遥控器有更少的按钮。

让我们看看这些项目所需的部件。

需要的组件

对于这些项目,你需要以下物品:

  • 一个 Arduino Uno 或兼容板

  • 一个红外接收器

  • 一个或多个红外发射器

  • 一对 RF 发射器和接收器

  • 跳线

  • 面包板

电路图

以下图显示了我们将如何将红外接收器连接到 Arduino:

红外接收器的 5V 输入和地线引脚连接到面包板上的适当轨道。信号引脚连接到 Arduino 的数字 2 引脚。现在让我们看看我们将如何将射频接收器连接到 Arduino:

RF 接收器的 5V 输入和地线引脚连接到面包板上的适当轨道。接收器的四个输出引脚连接到 Arduino 的 8、9、10 和 11 数字引脚。当发射器上的按钮被按下时,接收器上的相应输出引脚会变为高电平。

现在让我们看看我们项目的代码。

代码

在我们开始编写读取红外接收器输入的代码之前,我们需要通过 shirriff 加载IRremote 库。以下截图显示了我们将要使用的库和版本:

一旦加载了库,我们首先需要导入 IRremote 库的头文件,并创建全局变量和指令。以下代码显示了如何进行:

#include <IRremote.h>

#define IR_PIN 2
IRrecv ir(IR_PIN);
decode_results irCode;

在前面的代码中,我们首先将IRremote.h头文件包含到我们的项目中。然后我们定义红外接收器连接到 Arduino 的 2 号引脚。接下来,我们创建一个IRrecv类型的实例,用于读取 IR 接收器的输入。最后,我们创建一个decode_results类型的实例,用于存储 IR 接收器的值。

现在我们需要在setup()函数中添加初始化代码。以下代码显示了此示例的setup()函数:

void setup()
{ 
  Serial.begin(9600);
  ir.enableIRIn();
}

在这个例子中,我们首先初始化串行监视器,以便我们可以打印出结果。然后,我们从IRrecv类型的实例中调用enableIRIn()函数,这将使 Arduino 准备好读取红外接收器的输入。

loop()函数中,我们寻找来自红外接收器的输入,并打印出遥控器上按下的按钮的代码。以下代码显示了loop()函数将看起来像什么:

void loop()
{
  if (ir.decode(&irCode))
  {
    Serial.println(irCode.value, HEX);
    ir.resume();
  }
  delay(100);
}

loop()函数中,我们使用decode()函数,传递decode_results类型的实例,来读取被按下的按钮的代码。一旦收到代码,就使用Serial.println()函数将代码打印到串行控制台。我们延迟应用程序的执行 100 毫秒,以便在发送重复代码之前给用户一个释放按钮的机会。最后,调用resume()函数以再次开始监听结果。

代码的结果应该看起来像以下截图:

在本章前面展示的汽车 MP3 遥控器被用来获取前面截图中的结果。"FF30CF"是 1 号按钮的代码,"FF18E7"代码是 2 号按钮,而"ff7A85"代码是 3 号按钮。FFFFFFFF的结果意味着按钮被按住;因此,应该再次使用最后一个有效的代码。

你可能想把这个项目保存到某个地方,因为它对于获取遥控器上按钮的有效代码非常有用。一旦你有了这些代码,你就可以在你的其他项目中使用 IRremote 库,并根据接收器返回的代码执行所需的任何活动。

由于发射器上的每个按钮都有一个引脚,因此 RF 接收器的读取稍微容易一些,我们不需要外部库来读取它。当用户按下按钮时,接收器上相应的引脚将被拉高。

我们将开始定义 Arduino 上的哪些引脚连接到 RF 接收器的引脚上。如果你按照 Fritzing 图连接 RF 接收器,按钮的定义将如下所示:

#define BUTTON_A 10
#define BUTTON_B 8
#define BUTTON_C 11
#define BUTTON_D 9

然后,我们需要在setup()函数中设置这些引脚为输入,并初始化串行监视器。以下代码显示了此项目的setup()函数:

void setup() {
  pinMode(BUTTON_A, INPUT);
  pinMode(BUTTON_B, INPUT);
  pinMode(BUTTON_C, INPUT);
  pinMode(BUTTON_D, INPUT);
  Serial.begin(9600);
}

loop()函数中,我们需要读取每个引脚,检查它是否为高电平,如果是,则在你的项目中执行所需的函数。对于这个项目,我们只是打印出该引脚被按下。以下显示了此项目的loop()函数:

void loop() {
  int valA = digitalRead(BUTTON_A);
  if (valA == HIGH) {
    Serial.println("Button A");
  }

  int valB = digitalRead(BUTTON_B);
  if (valB == HIGH) {
    Serial.println("Button B");
  }

  int valC = digitalRead(BUTTON_C);
  if (valC == HIGH) {
    Serial.println("Button C");
  }

  int valD = digitalRead(BUTTON_D);
  if (valD == HIGH) {
    Serial.println("Button D");
  }
  delay(100);
}

当运行此代码时,你将在串行监视器中看到哪些按钮被按下。给你的项目添加遥控器可能看起来只是“锦上添花;”然而,它实际上可以增强项目的可用性,并让你免于不断起身与之互动。现在让我们看看本章的挑战。

挑战

在本章中,我们看到了两种类型的遥控设备。第一种是红外控制,它需要与投影设备有视线,并且可以有很多不同的按钮。射频遥控器在遥控器需要从设备更远距离工作或甚至在不同的房间工作时表现良好。

使用无线信号创建遥控器的方法有很多,例如Zigbee无线电或甚至 Wi-Fi;然而,对于这个挑战,我们希望你能跳出思维定势,开始拓展自己的视野。本章的挑战是思考如何在不使用无线信号的情况下远程控制你的设备。

你现在可能正在摇头,想知道我们所说的在不使用无线信号的情况下远程控制项目是什么意思。一个例子就是拍手器。拍手器是一种声音激活的电气开关。你拍一次手,开关就会打开,你再次拍手,开关就会关闭。另一个例子是控制你户外灯光的运动传感器。如果运动传感器检测到运动,它就会打开灯光。现在尝试跳出思维定势,想出其他不使用无线信号控制设备的方法。

概述

在本章中,我们看到了如何使用红外遥控器和射频遥控器与 Arduino 一起工作。你也被挑战跳出思维定势,思考其他不使用无线信号远程控制项目的方法。这个挑战之所以放在最后一个项目章节中,是为了让你在设计项目时开始跳出思维定势,因为跳出思维定势和创造新的、改进的方法去做某事是让人们对这些类型的项目感到兴奋的原因。如果你能够垄断你的项目,这也可能让你赚很多钱。

在下一章中,我们将讨论如何使用从前几章项目中学到的知识来创建一个简单的机器人。我们不会为你编写代码或设计电路。相反,我们将向你展示如何将书中学到的部件组合起来,这样你就可以设计自己的机器人或创建其他项目。

第十九章:创建一个机器人

当我最初开始使用像 Arduino 这样的开发板时,我的最初目标是构建一个机器人。然而,我并不知道从哪里开始。我有许多问题,机器人的好外形是什么?我应该使用什么电机?我如何给机器人供电?我需要为电机单独供电吗?避障是如何工作的?这一章是为了帮助您回答这些问题,并展示在阅读这本书的前几章之后,您现在已经有足够的知识来设计和构建自己的机器人。

在本章中,我们将讨论:

  • 我们如何利用这本书中学到的知识来创建一个完全工作的机器人

  • 一系列额外的提示和技巧,将帮助您在项目中

  • 除了这本书之外,我们还能用 Arduino 构建哪些其他项目

我们将通过挑战你创建自己的项目并以它与我们分享来结束这一章。

简介

在本章中,与之前的项目章节不同,我们不会设计和构建一个特定的项目。相反,我们将向您展示如何利用您在前几章中获得的知识来设计自己的机器人。我们还将为您提供各种提示和技巧,以帮助您避免人们在最初开始构建机器人时犯的一些错误。

当我们开始设计我们的第一个机器人时,我们首先做的事情是决定机器人应该做什么,因为我们认为整个机器人的设计应该围绕机器人的目的展开。这是一个大错误,因为机器人要做的项目列表非常长。它将介于 R2-D2、Wall-E 和指挥官数据之间。它将成为有史以来最酷的机器人,然后,正如你可能猜到的,我们真的感到不知所措,不得不将一切缩减到最小。

在最初设计您的前几个机器人时,您应该从设计/购买机器人底盘(身体)或确定机器人应该如何移动开始。这里真的是一个“先有鸡还是先有蛋”的困境,因为您使用的底盘实际上定义了机器人将如何移动,但另一方面,机器人应该如何移动(甚至完全不移动)定义了您需要什么类型的底盘;因此,我通常在设计机器人时会考虑这两者。

底盘和移动

开始构建机器人的最简单方法之一是购买现成的坦克底盘。其中一些底盘甚至内置了电机和电机控制器,或者它们被设计成可以一起工作,这使得开始构建更加容易。我第一个构建的机器人使用了 Dagu Electronics 设计和制造的 Rover 5 坦克底盘和 Rover 5 电机控制器板。这是我第一个机器人的照片:

这个机器人的所有部件都购买了,包括连接电路板的顶板。购买所有这些部件会很快变得昂贵,尤其是当你开始向机器人添加额外的传感器和设备时。

如果你计划建造许多机器人,或者使用 Arduino 进行大量其他原型设计,购买 3D 打印机是值得的,因为与其购买底盘的预制部件,你还可以设计和打印自己的。例如,不久前我拿出了 Rover 5 底盘,并用它创建了一个名为 BuddyBot 的另一个机器人。它是使用我设计和打印的部件制作的,这使我能够为 BuddyBot 获得我想要的外观。以下照片展示了 BuddyBot 的前部:

图片

如果你想的话,甚至可以打印整个底盘。以下照片展示了两个我使用 3D 打印机设计和打印的机器人底盘。左侧的底盘是我打印了整个底盘,右侧的是我正在设计的实验性底盘。我还为右侧的底盘打印了万向轮:

图片

底盘有各种形状和大小。找到或设计一个完美的底盘通常是构建你的机器人最容易的部分之一。关键是确保你有一个可以扩展的底盘。如果你注意到,在前面的照片中,这些板上有长而窄的矩形凹槽。这些凹槽设计得可以很容易地移除和添加新部件。你也会注意到,在前面的照片中左侧的机器人上的小面包板可以很容易地拧下、移除或移动到机器人的其他部分。

通过以模块化方式设计机器人,我们可以非常快速地扩展其功能。你将想要避免那些扩展性有限的底盘,因为除非你正在为特定功能设计机器人,否则你总会想着要添加新的组件和功能。

我们已经讨论了很多关于底盘的内容,那么关于定义机器人如何移动的问题呢?我在本章前面展示的 Rover 5 底盘使用坦克履带移动。之前照片中的黄色机器人使用的是标准且非常便宜的 Arduino 汽车电机和轮子(如下照片所示),而实验性底盘使用的是我打印的万向轮。

图片

使用坦克履带移动你的机器人使你能够在几乎任何地形上移动。然而,它们比标准轮子和电机更贵,底盘也必须为它们而设计。你通常还需要扭矩更高的电机,但我们将在下一节讨论电机。

使用 Arduino 汽车电机和轮子比履带便宜得多,但除非你有一个像以下照片中展示的 Bogie Runt Rover 这样的高端底盘,否则你的机器人将仅限于室内使用。

图片

Bogie Runt Rover 是一个非常不错的底盘,但我会强烈建议你拥有一个 3D 打印机,这样你就可以为它打印自己的扩展部件。

全向轮是一种非常特殊的轮子,轮子周围有小的圆盘,这使得它们可以横向推动,因为小圆盘会旋转,从而减少摩擦。如果你询问使用过全向轮的人对它们的看法,你收到的答案将涵盖整个光谱,有些人绝对讨厌它们,认为它们毫无用处,而有些人则非常喜欢它们。

你也可以制作出会走路的机器人。这类机器人超出了像这样入门书籍的范围,我建议你的前几个机器人还是坚持使用履带或轮子的类型。

现在让我们来看看电机以及如何给它们供电。

电机和电源

决定使用哪种电机以及如何供电,在你开始搭建机器人时可能是你做出的最艰难的决定之一,因为可供选择太多。以下照片展示了我项目中用到的部分电机:

图片

购买二手电机可以节省很多钱,而且有几种方式可以获取它们。我购买了许多二手遥控汽车和其他电子产品,如旧 DVD 播放器,并从中拆下了电机。这就是前面照片中显示的两个最小电机来源;然而,如果你需要更强大的电机,可以考虑从电钻中拆下电机。前面照片中最大的电机来自一个旧的 Ryobi 12V 电钻。

当你刚开始搭建机器人时,我建议从 Arduino 汽车电机开始。它们非常容易使用,并且是为供电机器人汽车而设计的。它们可以由多种电源供电,因为它们可以处理低至 3V 高至 12V 的电压。建议的电压范围是 6V 到 8V。然而,我经常使用一个最大电流为 1.3A 的 12V 电池来供电这些电机。当你购买电机时,请仔细阅读规格,以确保电机适合你在项目中使用的电源。

为了控制电机,你需要使用电机控制器。我们在第十五章,直流电机和电机控制器中展示的 L298 电机控制器是一个完美的起始电机控制器。它处理从 5V 到 46V 的广泛电压和高达 2A 的电流。你可以参考第十五章,直流电机和电机控制器,了解如何使用这个控制器。

为电机供电一开始可能会让人感到困惑,尤其是在购买电池时。首先,你不想从 Arduino 的 5V 电源为电机供电。对于小型机器人项目,你可以使用 AA 电池或 9V 电池。对于能够容纳更大电池的底盘,我建议购买一个小型 12V 电池,如以下照片中展示的 Duracell Ultra 12V 1.3Ah 电池。

图片

选择电池时一个关键因素是电机的规格以及你将有多少个电机。当你通过电机控制器将电机连接到电池时,你将它们相互并联连接。如果你还记得第三章“电路图”中串联和并联电路的比较,你会记得在并联电路中,每个分支都将有电源输出的最大电压。然而,电流将在分支之间分配。这意味着如果我们的电池可以产生 12V 和 1.2A 的电流,而我们试图为 6 个电机供电,那么如果每个电机以相同的速度旋转,每个电机将会有 12V 电压,可以吸取 200mA(1.2A 除以 6)的电流。因此,之前展示的 Duracell Ultra 将足以为六个 Arduino 汽车电机供电,但如果你打算使用更大型的电机,你可能需要一个容量更大的电池。

你可能已经注意到 Duracell 电池的额定电压为 12V,容量为 1.3Ah。不要将 Ah 额定值与安培混淆。电池的额定值是根据容量来确定的,1.3Ah 意味着它可以连续供应 1.3A 电流 1 小时,然后你需要充电。

选择为你的机器人项目供电的电池时,一个好的规则是:拥有更多的电量总是比更少的电量好。除非你将这个规则推向极端,试图用一个 12V 电池为两个小型电机的小型遥控车供电,否则这个规则非常适用。

使用 L298 电机控制器,我们可以控制车轮的速度和方向,这使得我们能够操控带有车轮或履带的机器人。如果机器人一侧的车轮或履带旋转速度比另一侧快,机器人就会转向。两侧速度差越大,机器人转向越快。你还可以通过让机器人一侧的车轮或履带向前旋转,而另一侧的车轮或履带反向旋转,使机器人原地旋转。

自主机器人——避障和碰撞检测

如果你想要构建一个自主机器人,你需要有一种形式的障碍物避障和碰撞检测,以及告诉机器人如何绕过或避开检测到的物体的逻辑。我们在第十章障碍物避障和碰撞检测中展示了如何使用几种障碍物避障和碰撞检测传感器,但问题可能在于我们如何开发绕过或避开检测到的物体的逻辑。

在我们开始讨论障碍物避障逻辑之前,让我们再次看看 BuddyBot 机器人:

如果你仔细观察 BuddyBot 的“眼睛”,你可能认出它们是第十章障碍物避障和碰撞检测中讨论的 MaxSonar EZ1 超声波测距仪。为了刷新你的记忆,MaxSonar EZ1 通过向特定方向发送超声波脉冲来工作。如果脉冲路径上有物体,那么它将以回声的形式反射回来。传感器通过测量接收回声所需的时间来确定物体距离。以下照片显示了 MaxSonar EZ1 的外观:

BuddyBot 使用两个 MaxSonar 测距仪来帮助实现障碍物避障逻辑。让我们看看这是如何工作的。我们首先需要确定在什么距离开始障碍物避障逻辑。例如,如果我们正在构建一个在室内移动的小型机器人,我们可能不需要担心距离五英尺的障碍物。如果我们有一个在工厂内移动的大型机器人,那么五英尺可能是开始障碍物避障逻辑的合理距离。机器人将要移动的环境在设计障碍物避障逻辑中起着重要作用。

一旦确定了开始障碍物避障逻辑的正确距离,我们就可以开始构建这个逻辑。通过使用两个传感器,我们能够确定机器人以什么角度接近物体。接下来的图示显示了机器人以轻微的角度接近物体:

当机器人以这种角度接近物体时,我们可以比较两个测距仪报告的距离,并确定物体更靠近机器人的左侧。通过确定机器人的左侧更靠近物体,我们可以使用一些基本的逻辑来告诉机器人向右转,直到物体超出范围,如图所示:

一旦物体超出范围,我们就可以再次开始前进。这是一种非常基本的避障方法,确实需要不断轮询测距仪。作为一个更经济的选项,因为与其它传感器相比,MaxSonar 测距仪相当昂贵,我们可以使用在第十章,避障和碰撞检测中描述的红外避障传感器。我之所以更喜欢 MaxSonar 测距仪,是因为红外传感器发出的红外辐射束比 MaxSonar 测距仪发出的声波束要窄得多。有了测距仪的更宽的波束,我们不太可能错过稍微在传感器左/右或高/低位置的物体。

你还希望在机器人周围放置碰撞传感器;这些传感器可以用来检测机器人撞到东西的情况。这些传感器在机器人的后面工作得非常好,可以检测到机器人倒车时是否撞到东西。避障可能是一个非常复杂的话题,一个容易开始但可能变得非常复杂的话题,就像这里展示的那样。

现在我们来看看如何远程控制机器人。

远程控制机器人

我们在第十八章,远程控制 Arduino中看到的 RF 遥控器,相较于红外遥控器来说,是远程控制机器人的更好选择,因为 RF 遥控器不需要视线对准信号。RF 遥控器唯一的问题是通常没有足够的按钮来完成我们想让机器人做的所有事情。这种按钮不足的问题可以通过将机器人变成一个自主机器人来解决,这样它就可以通过避障来自己移动,但然后可以使用遥控器告诉机器人执行特定的任务。这些任务可以包括告诉机器人开始/停止移动,通过扬声器播放音乐,或者从冰箱里给你拿饮料。

在第十八章,远程控制 Arduino的结尾,你被挑战跳出思维定式,寻找除了无线信号之外远程控制项目的其他方法。思考你对这个挑战的回答,看看你是否可以用它们来控制机器人。

我最喜欢的控制机器人的一种方式是通过语音识别,使用我们在第十四章,语音识别和语音合成中看到的 MOVI 盾。有了 MOVI 盾,我们可以编程命令,如向右转向左转停止,或者你希望机器人做的任何其他事情。

我们还可以使用的一个传感器是声音传感器,我们可以在机器人周围以圆形或方形模式放置三个或四个,这样它就可以检测声音来自哪个方向,然后朝那个方向移动。我们也可以使用单个声音传感器,当我们拍手或制造其他响亮噪音时,来启动或停止机器人。

让我们看看我们如何向机器人用户提供反馈。

用户反馈

我们总是希望提供一种方式来提供反馈,以便我们知道机器人正在发生什么。当我们为了调试目的编程机器人时,这非常有用。如果你还记得本章前面提到的 BuddyBot 的图像,它的鼻子被多彩 LED 灯照亮。LED 灯的颜色表示机器人应该做什么,以及它是否检测到左右两侧的障碍物。通过观察 LED 灯的颜色,我知道机器人应该做什么,如果它没有这样做,我知道编程或硬件出了问题。

使用多彩 LED 灯是添加机器人反馈最容易和最快的方式之一。我们可以非常快速地设置不同的颜色来表示不同的活动。如果我们需要同时表示多个活动,我们可以添加多个 LED 灯,而不会花费太多。我更喜欢多彩 LED 灯而不是单色 LED 灯,因为我们可以用不同的颜色来表示不同的事物,而单色 LED 灯要么开要么关,因此它们只能表示一个项目。

提供反馈的另一种方式是使用声音。在第十二章,“声音的乐趣”中,我们看到了 Arduino 如何使用各种扬声器产生声音。如果你曾经使用过 Roomba 或另一个自主式机器人吸尘器,你会知道当吸尘器卡在某个地方时,它会发出声音来通知主人它卡住了。播放声音是向机器人添加反馈的另一种简单方法。

在第十二章,“使用 LCD 显示屏”中,我们说明了如何使用 LCD 显示屏来传达项目信息。添加 LCD 显示屏可以使你的项目向用户提供精确的信息。这些可以是文字或图像的形式。

用户反馈应该是你首先放入项目中的内容之一,因为它可以在你开发项目时帮助进行故障排除。现在让我们谈谈我们如何使事物旋转。

使事物旋转

在第十六章,伺服电机中,我们看到了如何使用伺服电机来打开和关闭一个机器人爪子;然而,伺服电机能做的远不止这些。正如我们在机器人爪子中看到的那样,我们可以将电机旋转到特定的角度。在过去,我将一个 MaxSonar 距离传感器连接到伺服电机上,并将传感器正对前方。然后当距离传感器检测到机器人前方有物体时,伺服电机就会将距离传感器转向不同的方向,以便确定最佳移动方向。这使得我能够仅使用一个距离传感器构建一个具有避障功能的自主机器人。

我们也可以将一个光源连接到伺服电机上,制作一个旋转的探照灯,这样你就能在黑暗中看到机器人所在的位置,或者它只是你机器人上一个非常酷的附加功能,实际上并没有什么功能性。

我推荐的一个基本建议是,当你刚开始制作机器人时,避免尝试将机器人臂连接到你的机器人上。机器人臂通常对大多数小型到中型机器人底盘来说太重了,而且需要大量的编程才能精确地移动到你想要的位置。我并不是说你不应该考虑在你的机器人上添加机器人臂。然而,这是一个非常高级的项目,需要花费大量时间来完善。

非机器人项目

如果你对机器人不感兴趣,还有很多其他的项目你可以用 Arduino 来做。让我们来看看其中的一些。

气象站

在第九章,环境传感器中,我们看到了如何使用多种环境传感器,如 DHT-11 温度/湿度传感器和雨传感器。我们可以使用这些传感器以及任何额外的传感器,比如风速传感器来开发一个气象站。只需记住,你需要将 Arduino 和其他电子部件放入一个防水容器中。

智能恒温器

在第九章,环境传感器中,我们看到了 Arduino 如何使用 DHT-11 温度/湿度传感器读取温度和湿度。如果我们把窗户或便携式空调或加湿器连接到继电器板上,如第十七章,使用继电器中所述,我们就可以自动调节空调和/或加湿器的开关。

实际上,使用继电器板,你可以根据各种传感器的读数几乎随时打开或关闭任何交流电源设备。例如,我们可以非常容易地通过将声音传感器连接到 Arduino,并使用 Arduino 在检测到大声响时打开或关闭继电器,来创建我们自己的拍手器克隆。

距离传感器

在第十章,避障与碰撞检测中,我们学习了如何使用 MaxSonar 测距仪。如果我们把测距仪连接到我们在第十六章,伺服电机中看到的伺服电机上,我们就可以创建一个可以旋转至 180 度以监控房间大部分区域的接近传感器。当接近传感器首次启动时,它需要运行一个初始周期来绘制物体位置图,然后开始监控初始运行后是否有任何变化。如果接近传感器检测到有物体比预期更近,它可以通过扬声器播放警报,正如在第十二章,声音的乐趣中描述的那样。

这些只是您可以用 Arduino 创建的一些项目。现在,让我们面对最后的挑战。

挑战

我开始学习如何使用原型板,如 Arduino,以便构建机器人。您可能想构建其他项目,比如使用温度、湿度和雨量传感器构建气象站,或者使用在第八章,运动传感器中描述的运动传感器构建安全系统。

无论您对什么感兴趣,我都挑战您用 Arduino 制作一些超级酷的项目。一旦完成,我很乐意看到您项目的图片和描述,我甚至会在我的博客上发布一些,并给予提交者项目认可。如果您在 YouTube 上有关于您项目的视频,我也很乐意看到。您可以发送您的图片和描述到:mastering.arduino@gmail.com

摘要

在本书的第一部分,我们学习了 Arduino 和基本电子学。这些章节旨在向您,读者,提供一个关于 Arduino 如何工作以及如何安全地将电子组件连接到 Arduino 而不会伤害到自己或电子组件的基本理解。

接下来,我们学习了我们可以与 Arduino 一起使用的开发工具以及如何编程 Arduino。这些章节为您提供了对开发工具和 Arduino 编程语言的基本理解。

在上一章中,我们结合了书中早期学到的知识,展示了如何将各种不同的组件连接到 Arduino 上。这些章节旨在向您展示一系列不同方式的 Arduino 接口组件。这希望给您提供足够的多样性,当您为自己的项目购买各种传感器时,即使它们没有在本书中明确介绍,您也能理解它们如何与 Arduino 接口。

在接下来的两章中,我们将探讨蓝牙无线电,看看我们如何在项目中实现双向通信。

第二十章:蓝牙 LE

在本书到目前为止的内容中,我们与 Arduino 项目之间的所有外部通信都是在封闭环境中进行的。所谓封闭环境,是指我们的项目仅仅接收来自遥控器的信息或指令,而没有从项目中传出任何信息。有许多用例需要将信息从我们的 Arduino 项目传输到外部设备,如智能手机或其他物联网设备。当有这种需求时,首先被提出的技术之一是 蓝牙低功耗,也称为 蓝牙 LE 或蓝牙智能。

在本章中,您将学习:

  • 什么是蓝牙低功耗(Bluetooth LE)

  • 蓝牙低功耗无线电的工作原理

  • 什么是 GAP 配置文件

  • 什么是 GATT 配置文件

  • 如何使用 HM-10 蓝牙低功耗无线电模块与 Arduino 配合使用

简介

对于不熟悉这项技术的人来说,蓝牙 LE 最常见的误解之一是蓝牙 LE 是蓝牙经典的一个轻量级子集。这并不正确,因为蓝牙经典和蓝牙 LE 是两种基本不同的协议,具有不同的设计目标。

大多数无线技术,如 Wi-Fi 和蓝牙经典,都是为了满足广泛的用例而设计的;然而,蓝牙低功耗的设计略有不同。最初由诺基亚创建,被称为 Wibree,蓝牙低功耗的主要设计重点是创建一个具有可能最低功耗的无线电标准,并针对低成本、低复杂度和低带宽进行优化。

蓝牙低功耗规范于 2010 年 6 月作为蓝牙 4.0 核心规范的一部分发布。蓝牙核心规范由蓝牙 特别兴趣小组SIG)监督和更新。

您可以从他们的网站 www.bluetooth.com 获取有关蓝牙的信息并下载规范;然而,由于超过 2500 页,我建议您阅读本章而不是蓝牙规范,除非您需要帮助治疗失眠。

蓝牙 LE 的采用率比大多数其他无线技术都要快。这其中的原因是移动行业采用了蓝牙 LE 标准,苹果和谷歌在 iOS 和 Android 操作系统中投入了大量精力,包括可靠的蓝牙 LE 堆栈,并为开发者开发了易于使用和理解的蓝牙 LE API。这使得开发者能够轻松创建和与具有蓝牙 LE 无线电的设备交互。

移动行业推动采用蓝牙 LE 的原因是,使用蓝牙 LE 连接的设备消耗的电量远低于其他无线技术,如蓝牙经典和 Wi-Fi,因此得名蓝牙低功耗。这导致他们的手机电池寿命更长,从而让客户更加满意。

蓝牙设备有三种类型,每种类型都支持蓝牙经典、蓝牙 LE 或两者兼而有之。以下图表显示了每种类型支持的内容:

设备类型 蓝牙经典支持 蓝牙 LE 支持
预 4.0 蓝牙
单模
双模

虽然蓝牙 5.0 规格于 2016 年 6 月发布,但在本书撰写时,支持此新规范的 Arduino 蓝牙模块非常少。实际上,此时对蓝牙 4.1 或 4.2 规格的支持也非常有限;因此,在本书中,我们将专注于蓝牙 4.0 规格,并知道蓝牙 5.0、4.2 和 4.1 都与该标准向后兼容。

为了设计使用蓝牙 LE 的物联网设备,我们确实需要了解这项技术,以便我们知道何时实际使用它。因此,我们将比本书中其他技术更深入地探讨这项技术。我们将从查看无线电规格开始。

蓝牙 LE 无线电

由于本书中的所有项目都将使用蓝牙 LE 4.0,以下规格适用于此标准:

范围 最多 100 米
无线电频率 2.402 - 2.481 GHz
无线电信道 40(37 个数据信道和 3 个广告信道)
最大 OTA 数据速率 1 Mbit/s
应用数据吞吐量 0.125 Mbit/s
网络拓扑 点对点
网络标准 IEEE 802.15.1

蓝牙 LE 的最大范围为 100 米,但这非常依赖于周围环境。当连接的设备在室内时,由于墙壁和其他障碍物,无线电信号需要穿过,范围将显著减小。通常,除非我们在开阔的田野中,否则我们不会看到接近 100 米的范围。即使如此,也很难达到 100 米的范围。

蓝牙 LE 无线电在超过 40 个信道上运行,频率范围从 2.402 GHz 到 2.481 GHz。在这些信道中,37 个用于数据,3 个用于广告。多个信道的原因是蓝牙 LE 使用跳频来减少干扰。三个广告信道用于设备的发现。一旦发现设备,就使用相同的信道来交换初始连接参数。一旦交换了连接参数,就使用常规数据信道进行通信。

下图显示了蓝牙 LE 使用的信道:

图片

蓝牙低功耗(Bluetooth LE)的设计宗旨是在低功耗下运行,而避免使用电力的最佳方法就是尽可能频繁且尽可能长时间地关闭无线电。对于蓝牙低功耗来说,这是通过在特定频率下发送短数据包脉冲来实现的,而在这些无线电脉冲之间,无线电处于关闭状态。这通常被称为空闲状态竞争,因为无线电实际上是以尽可能快的速度发送信息,然后关闭一段时间。

我们为了蓝牙低功耗的低功耗使用而做出的最大牺牲是应用数据吞吐量。在之前的图表中,我们看到蓝牙低功耗无线电的最大空中数据速率是 1 兆比特/秒。然而,我们也列出了应用数据吞吐量为仅 0.125 兆比特/秒。这意味着无线电理论上每秒可以传输 1 兆比特;然而,由于对无线电施加的限制以节省电力,我们只有每秒 0.125 兆比特的最大传输速率。在实际应用中,我们实际上永远不会看到接近这个数据传输速率的任何东西。

让我们分析空闲状态竞争和蓝牙低功耗标准施加的限制,以了解为什么数据吞吐量如此低。首先,蓝牙低功耗规范定义了连接间隔,即两个连续连接事件(当两个设备交换数据时)之间的时间间隔,应在 7.5 毫秒到 4 秒之间。这意味着如果我们将连接间隔设置为可能的最短时间(7.5 毫秒),我们每秒将有 133 个连接事件。

无线电在每个连接事件中可以传输多达六个数据包,其中每个数据包可以包含最多 20 字节的用户数据。这意味着每个连接事件最多有 120 字节。

如果我们将所有这些信息综合起来,我们得到以下公式:

网络拓扑

这表明最大数据吞吐量将是 0.125 兆比特/秒;然而,正如我们之前提到的,即使这个数字也永远不会达到,因为我们通常永远不会达到每包字节数的最大值或在一秒钟内有 133 个连接事件。设备本身还可以进一步限制连接间隔和数据包数量。在最佳情况下,我们通常每秒只能看到大约 5-10 千字节的数据吞吐量。这意味着我们通常只有在需要交换短数据脉冲时才使用蓝牙低功耗技术,而在需要交换大量数据或进行数据流传输时则避免使用。

现在我们来看看蓝牙低功耗连接的网络拓扑。

网络拓扑

蓝牙低功耗设备可以通过广播或已建立的连接与其他蓝牙低功耗设备进行通信。每种方法都有其自身的优缺点。我们将首先查看设备通过广播进行通信时的网络拓扑。对于本章的项目,我们将专注于通过已建立的连接交换数据,但了解如何通过广播交换数据也是有益的。因此,我们将在本介绍部分涵盖它。

蓝牙低功耗广播

下图展示了广播网络的网络拓扑:

图片

在广播时定义了两个角色:

  • 广播者:此设备在设定的时间间隔内向任何正在监听的设备发送非连接性广告数据包

  • 观察者:此设备扫描广播频率以接收广播设备发送的非连接性广告数据包

广播数据是设备向多个设备发送数据的唯一方式。标准的广播数据包可以携带 31 字节的数据负载,通常用于描述广播者和其能力。然而,它也可以包含我们希望向其他设备广播的任何自定义信息。蓝牙低功耗还支持一个可选的第二广告负载,称为扫描响应,它可以包含额外的 31 字节数据。

如果我们希望向多个设备传输少量数据,广播既快又简单;然而,数据没有安全性和隐私性。通常,安全性是避免使用广播数据包的最大原因。然而,避免使用广播数据包的另一个重要原因是观察者没有能力向广播者发送任何数据。

现在让我们来看看蓝牙低功耗(LE)连接

蓝牙低功耗连接

下图展示了蓝牙连接的工作原理:

图片

与蓝牙低功耗广播拓扑结构类似,连接拓扑也定义了两个角色:

  • 中心设备:中心设备通常是一个如笔记本电脑、平板电脑或手机等设备。这些设备将扫描广告频道并监听可连接的广告数据包。当发现设备时,中心设备可能会尝试与该设备建立连接。连接建立后,中心设备管理时间并启动数据交换。中心设备可以连接多个外围设备。

  • 外围设备:外围设备通常是一个如智能手表、气象站或医疗设备等设备。这些设备会定期发送可连接的广告数据包并接受传入的连接。一旦建立连接,外围设备通常会遵循中心设备的时间安排,并在中心设备请求时交换数据。外围设备只能连接到一个中心设备。

外设通常会在中央设备发现它并请求连接之前进行广播。一旦建立连接,外设将停止广播,然后两个设备可以交换数据。在这种拓扑结构中的数据交换是双向的,外设和中央设备都可以发送和接收数据。

当中央设备和外设建立连接时,传输和接收的数据是以称为服务和特性的单位组织的。我们将在本章稍后查看通用属性配置文件GATT)时进一步探讨这一点。现在要理解的是,蓝牙低功耗外设可以拥有多个特性,这些特性用于发送和接收数据。这些特性被组织或分组到服务中。

建立蓝牙低功耗连接的最大优势是可以有多个特性来组织你的数据,并且每个特性都可能有自己的访问权限和描述性元数据。另一个优势是能够建立安全的加密连接。

在蓝牙 4.0 中,设备可以作为中央设备或外设,但不能同时作为两者。从蓝牙 4.1 开始,这种限制被取消,并且在新版本的蓝牙低功耗中,设备可以作为外设、中央设备或两者兼具。

现在,让我们看看蓝牙低功耗配置文件。

蓝牙低功耗配置文件

蓝牙低功耗(Bluetooth LE)定义了两种类型的配置文件。这些配置文件包括定义所有蓝牙低功耗设备所需的基本操作模式的配置文件(通用访问配置文件和 GATT)或用于特定用例的配置文件(健康设备配置文件和邻近配置文件)。在本章中,我们不会深入探讨这些配置文件的具体用例;然而,我们确实想看看通用访问配置文件GAP)和通用属性配置文件(GATT)。我们将从查看 GAP 开始。

通用访问配置文件(GAP)

GAP 定义了设备如何相互交互以确保设备互操作性。它定义了蓝牙低功耗设备如何发现彼此、建立安全连接、终止连接、广播数据和设备配置。这是我们将在本章中涵盖的蓝牙低功耗堆栈的最低级别。

在本章的早期,我们看到了蓝牙低功耗设备可以处于两种状态之一。在广播拓扑中,设备可以是广播者(从设备)或观察者(主设备)。如果两个设备之间建立了连接,那么设备就变成了中央(主设备)或外设(从设备)。我们在这里引入了主设备和从设备这两个术语,以说明设备可能处于的状态。以下图表显示了不同的状态:

图片

这两种类型的设备都是从空闲或待机状态开始的。这是设备重置时的初始状态。从机设备随后将成为广播者,它正在广播特定数据,让任何主设备知道它是一个可连接的设备以及它提供的服务。在空闲状态之后,主设备将开始扫描正在广播的从设备。当主设备收到广告时,它将向广播者发送扫描请求,从设备将回以扫描响应。这就是设备发现过程。

在设备发现过程之后,如果主设备想要连接到广播设备,它将发起一个连接。在发起连接时,主设备将指定连接参数。一旦连接建立,设备将扮演主从角色。在蓝牙低功耗(BLE)4.0 中,从机设备只能有一个主设备。此外,在蓝牙低功耗(BLE)4.0 中,设备可以充当主设备或从设备,但不能同时充当两者。在蓝牙规范的后续版本中,这些限制已被取消。我知道我们在这本书中已经提到过几次,但在开发您的设备时,这一点很重要。

我们提到过,在发起连接时,主设备指定连接参数的数量。其中一些参数包括:

  • 连接间隔: 在蓝牙低功耗(BLE)中,使用跳频方案,通信的两个设备最清楚在哪个信道上传输/接收,何时切换信道以及何时建立连接。连接尝试之间的时间间隔称为连接间隔。

  • 从机延迟: 从机延迟允许从机设备选择跳过一定数量的连接事件。从机设备不得跳过超过此参数定义的连接事件数量。

  • 监督超时: 监督超时是两次成功连接事件之间的最大时间间隔。如果超过这个时间,设备将终止连接,从机设备将回到未连接状态。

在决定将这些参数设置为什么值时,有许多考虑因素。主要考虑因素是功耗和数据吞吐量。随着吞吐量的增加,设备将消耗更多电力。例如,如果我们降低连接间隔,从而增加每秒的连接尝试次数,设备的功耗将增加,因为无线电将在更多的时间内开启。通过减少从机延迟,无线电同样会开启更多。因此,它也会增加功耗。在处理蓝牙低功耗(BLE)无线电时,您需要平衡项目的功耗需求与数据吞吐量需求。没有适用于所有类型设备的神奇比例;这是您需要根据每个项目逐一考虑的事情。

在本章的示例项目中,我们将展示如何使用 AT 命令调整蓝牙低功耗模块的各种设置。现在让我们来看看 GATT 配置文件。

通用属性(GATT)配置文件

虽然 GAP 配置文件定义了蓝牙低功耗设备的低级(广播和连接)交互,但 GATT 配置文件定义了设备交换数据的细节。GATT 也是所有基于属性配置文件的参考框架,这些配置文件定义了特定的用例,例如心率血压配置文件。

与 GAP 配置文件一样,GATT 配置文件定义了两个角色。这些角色是客户端和服务器。当你查看这个工作原理的图示时,这些角色可能一开始看起来有点奇怪;然而,一旦我们了解了蓝牙低功耗设备如何交换数据,它就会更有意义。GATT 配置文件中的客户端角色对应于 GAP 配置文件中的主角色,而 GATT 配置文件中的服务器角色对应于从角色。以下图示说明了这一点:

图片

在这个图中,我们可以看到一个客户端可以有多个服务器;然而,每个服务器只能有一个客户端。在 GATT 配置文件中,客户端(GAP 中的中心角色)从服务器(GAP 中的外围设备)请求信息。当我们展示 GATT 和 GAP 角色之间的关系时,值得注意的是,GATT 和 GAP 角色实际上是相互独立的,并且在蓝牙低功耗规范的后续版本中,一个设备可以同时充当中心和外围设备。

GATT 配置文件定义的最小数据实体是属性。属性是位于服务器上的可寻址信息块,客户端可以访问并可能修改它。每个属性都由一个UUID全球唯一标识符)唯一标识,该标识符可以是 16 位或 128 位数字。这个标识符被称为句柄。

GATT 配置文件定义了一组与所有属性关联的权限。权限指定可以对每个属性执行哪些操作。这些权限包括:

  • 访问权限:访问权限指定可以对属性执行哪些操作。每个属性都将具有以下权限之一:

    • :客户端无法读取或写入属性

    • 可读:客户端可以读取属性

    • 可写:客户端可以写入属性

    • 可读和可写:客户端可以读取和写入属性

  • 加密:加密权限确定客户端访问属性所需的加密级别

    • 无加密(安全模式 1,级别 1):不需要加密

    • 未认证加密(安全模式 1,级别 2):连接必须加密;然而,加密密钥不需要进行认证

    • 认证加密(安全模式 2,级别 2):连接必须加密,加密密钥必须经过认证

  • 授权:授权权限决定用户是否需要授权才能访问属性

    • 无需授权:访问属性无需授权

    • 需要授权:访问属性需要授权

GATT 定义了一个严格的层次结构,用于组织属性。属性被分组到服务中,其中每个服务可以包含零个或多个特性。这些特性可以包括零个或多个描述符。服务、特性和描述符都是 GATT 服务器中的属性。

下图显示了层次结构:

图片

服务用于将相关属性分组到一个公共实体中。每个服务都有一个唯一的 UUID,可以是 16 位用于官方采用的服务类型,也可以是 128 位用于自定义服务类型。

您可以在蓝牙 SIG 网站上查看官方采用的服务列表,请点击此处:www.bluetooth.com/api/silentlogin/login?return=http%3a%2f%2fwww.bluetooth.com%2fspecifications%2fgatt%2fservices.

如果您查看心率服务,您会看到该服务包含三个特性。

特性是数据的容器,其中每个特性封装一个单一的数据点。与服务类似,特性通过 16 位或 128 位 UUID 进行标识。特性是蓝牙 LE 客户端与服务器交互的主要入口点。

您可以在蓝牙 SIG 网站上找到官方采用的特性列表,请点击此处:www.bluetooth.com/specifications/gatt/characteristics.

每个特性的访问权限应该是只读或只写。具有读写权限的特性非常罕见。例如,如果我们想为我们的蓝牙 LE 设备创建一个简单的串行接口,我们会创建一个具有只读权限的 TX 特性来传输数据,以及一个具有只写权限的 RX 特性来接收数据。我们不会创建一个同时具有读写权限的单个特性,因为当客户端向其写入数据时,服务器可能会覆盖它。

描述符用于向客户端设备提供有关特性和其值的附加信息。

您可以在蓝牙 SIG 网站上找到官方采用的描述符列表,请点击此处:www.bluetooth.com/specifications/gatt/descriptors.

通常,服务器只是简单地响应客户端对特性的数据请求;然而,服务器也可以通过使用服务器发起的更新来启动通信。有两种类型的服务器发起的更新,它们是:

  • 通知:当服务器配置为通知客户端特性值已更改,但不期望客户端确认通知时,使用特性值更改通知。在本章的所有项目中,通知都已被打开;然而,它仅在第一个和第三个项目中使用。

  • 指示:当服务器配置为向客户端指示特性值已更改并期望客户端确认它已收到指示时,使用特性值更改指示。

现在我们对蓝牙低功耗及其工作原理有了非常基本的了解,让我们看看本章我们将使用的 HM-10 蓝牙模块。

HM-10 蓝牙模块

HM-10 是一个基于 TI CC2530 或 CC2541 蓝牙 SOC片上系统)的蓝牙 4.0 模块。HM-10 是 Arduino 非常流行的蓝牙 4 模块,这主要归功于其低廉的成本和易用性。HM-10 为蓝牙层提供标准串行连接。这允许一个非常直接的接口;然而,它也隐藏了实际的蓝牙低功耗层。

在 第二十一章 的 蓝牙经典 中,当我们查看 HC-05 蓝牙模块时,你会注意到 HC-05 和 HM-10 之间的接口使用相同的串行接口;然而,了解蓝牙低功耗和蓝牙经典技术之间的区别将有助于你决定在你的项目中使用哪种技术。

我们可以使用 AT 命令来控制该模块,我们将在本章的项目部分查看如何操作。以下照片显示了 HM-10 蓝牙模块的外观:

图片

HM-10 模块有六个引脚。然而,我们只对中间的四个感兴趣,它们是:

  • VCC:连接到 Arduino 的 3.3V 电源输出

  • GND:连接到 Arduino 的地线

  • TX:发送引脚,连接到 Arduino 的一个数字引脚

  • RX:接收引脚,连接到 Arduino 的一个数字引脚

现在,让我们看看本章我们将要进行的项目的所有组件。

需要的组件

对于这些项目,你需要以下物品:

  • 一块 Arduino Uno 或兼容板

  • 一个 HM-10 蓝牙 4.0 模块

  • 一个 DHT-11 温度传感器

  • 一个 LED

  • 一个 4.7K 欧姆电阻

  • 一个 3.3K 欧姆电阻

  • 一个 1.1K 欧姆电阻

  • 一个 330k 欧姆电阻

  • 跳线

  • 面板

您需要在您的手机/平板电脑或电脑上安装一个蓝牙 LE 应用程序。我在手机上使用的是BTCommander – Serial port HM10应用程序(itunes.apple.com/us/app/btcommander-serial-port-hm10/id1312640906?mt=8)。还有许多其他应用程序,例如 Android 的nRF connect应用程序(play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en_US)和 iOS(itunes.apple.com/us/app/nrf-connect/id1054362403?mt=8)。

电路图

在本章中,我们将进行三个项目。第一个项目将是一个简单的串行通信项目,通过蓝牙无线电将文本从 Arduino 发送到另一个设备,并从另一个设备发送回 Arduino。我们还将展示如何在第一个项目中配置蓝牙无线电。对于第二个项目,我们将展示如何远程切换 LED 的开关。对于最后一个项目,我们将构建一个迷你气象站,通过蓝牙无线电远程读取温度。每个项目都将包含其自身的接线图;然而,如果您希望一次性连接所有硬件,以下图示显示了所有连接方式:

与之前的图示相比,这个图示可能一开始看起来比较复杂;然而,如果我们将其分解为三个部分,它实际上并不复杂。第一部分是 DHT-11 温度传感器,位于面包板的右侧。第二部分是 LED,位于面包板的中心。第三和最后一部分是 HM-10 蓝牙模块,位于面包板的左侧。

我们已经在第九章“环境传感器”中介绍了 DHT-11 温度传感器的接线,以及在第四章“基本原型”中介绍了 LED 的接线。因此,这里我们将重复解释。

要将 HM-10 蓝牙模块连接到 Arduino,VCC引脚连接到面包板上的电源轨,该电源轨连接到 Arduino 的 5V 电源输出。蓝牙模块上的GND引脚连接到面包板上的地轨,该地轨连接到 Arduino 的地线引脚。蓝牙模块上的RX引脚直接连接到 Arduino 的数字引脚 10

将蓝牙模块上的 TX 引脚连接到 Arduino 是有一点不同的。为此,我们想确保电压不超过 3.3V。因此,我们使用一个简单的分压器。分压器是一个简单的电路,可以将较大的电压转换为较小的电压。为此,我们使用两个电阻,一个 1.1K 和一个 3.3K。这两个电阻串联连接,其中 3.3K 电阻的一端连接到地,1.1K 电阻的一端连接到 Arduino 的数字 引脚 11TX 引脚连接在这两个电阻之间。

项目 1 – 串行通信

对于这个第一个项目,我们只将使用 HM-10 蓝牙模块和 Arduino。您需要按照前面的电路图将蓝牙模块连接到 Arduino。以下图示显示了这一点:

图片

现在我们需要编写代码来访问蓝牙模块。我们将使用 SoftwareSerial 库与 HM-10 蓝牙 LE 模块进行接口。这个库是为了允许在除了引脚 0 和 1 之外的其他数字引脚上进行串行通信而开发的。如果您使用的是 Uno 以外的板,这个库可能会有局限性。您可以参考文档(www.arduino.cc/en/Reference/softwareSerial)以查看您的板是否有任何限制。

代码需要从包含 SoftwareSerial 头文件开始,然后初始化 SoftwareSerial 类的一个实例。我们还想在从串行监视器发出新命令时添加新行。因此,我们将定义一个布尔变量,每当收到新命令时将其设置为 true(这将使在串行监视器中读取响应变得更容易)。以下代码将执行此操作:

#include <SoftwareSerial.h>
SoftwareSerial HM10(10, 11); // RX | TX
bool addNewLine = false;

在创建 SoftwareSerial 类的实例时,您需要定义用于接收(RX)和发送(TX)数据的引脚。第一个值是 RX 引脚,第二个值是 TX 引脚。

接下来,我们需要初始化串行监视器和 SoftwareSerial 实例。我们将在 setup() 函数中这样做。我们还想让用户知道应用程序何时准备好接受命令或连接。以下代码显示了 setup() 函数的代码:

void setup()
{
  Serial.begin(9600);
  HM10.begin(9600);
  Serial.println("Connected to HM-10\.  Try connecting from any device or 
  issue AT commands");
}

当我们启动串行监视器和 SoftwareSerial 接口时,我们需要定义波特率。HM-10 蓝牙模块和串行监视器都使用 9600 波特率进行通信。一旦一切启动,串行监视器上会显示一条消息,告知用户一切正常。

loop() 函数中,我们需要将用户输入到串行监视器中的任何内容写入蓝牙模块,并将从蓝牙模块接收到的任何内容写入串行监视器。以下代码显示了 loop() 函数:

void loop() 
{ 
  if (Serial.available()) { 
    HM10.write(Serial.read()); 
    addNewLine = true; 
  } 

  if (HM10.available()) { 
    if (addNewLine) { 
      Serial.write("\r\n"); 
      addNewLine = false; 
    } 
    Serial.write(HM10.read()); 
  }  
}

在这个函数中,我们使用串行监视器和SoftwareSerial类型的实例上的available()函数来检查任一设备是否有可读数据。如果有,我们读取数据并将其写入另一个设备。在读取串行监视器并写入SoftwareSerial实例的部分,我们将addNewLine布尔变量设置为 true,这样下次我们写入串行监视器时,我们将写入一个回车符和新行。在从蓝牙模块读取并写入串行监视器的部分,我们检查addNewLine布尔变量是否为true,如果是,我们在将addNewLine变量设置为false之前,向串行监视器写入一个回车符和新行。

我们可以使用此应用程序的两种方式。第一种是将AT注意)命令输入到串行监视器中,这使您能够获取/设置蓝牙模块的配置设置并控制模块。第二种是使用手机上的蓝牙 LE 应用程序读取和写入蓝牙模块的值。让我们首先看看 AT 命令。

要向蓝牙 LE 模块发送 AT 命令,运行前面的代码块,然后打开串行监视器,它是 Arduino IDE 的一部分。一旦一切启动,你将在监视器中看到已连接到 HM-10。尝试从任何设备连接或发出 AT 命令的消息。这表明模块已准备好,一切已启动。一旦看到消息,在输入框中输入at,然后点击发送按钮或按Enter键。你应该从蓝牙模块看到OK响应。输出应该如下所示:

要发送 AT 命令,您将使用以下格式:

Set item: AT+{command}{new setting}
Query item: AT+{command}?

要设置项目,你输入字母AT,然后是加号(+),命令和新的设置,中间没有空格。例如,要将蓝牙模块广播的名称设置为“Buddy”,我们会发出以下命令:

at+nameBuddy

注意:AT 命令不区分大小写。

要查询项目,我们输入字母AT,然后是加号(+),命令,然后是问号(?)。例如,要查询蓝牙模块正在广播的名称,我们会使用以下命令:

at+name?

我们可以使用我们刚刚编写的应用程序从串行监视器手动设置配置,或者我们可以通过使用SoftwareSerial库中的print()函数在应用程序内部设置配置,如下所示:

HM10.print("AT+Name?\r\n");

让我们看看一些常用的命令。这些命令可以从串行监视器或代码中像我们刚才展示的那样使用。

测试命令

命令 响应 参数 描述
AT OK 这是一个测试命令,可以用来测试与蓝牙模块的连接。

查询软件版本

命令 响应 参数 描述
AT+VERR 版本号 此命令将返回模块的固件版本号。

恢复出厂默认

命令 响应 参数 描述
AT+RENEW OK+RENEW 恢复出厂默认设置。

重启模块

命令 响应 参数 描述
AT+RESET OK+RESET 重启蓝牙模块。

查询 MAC(媒体访问控制)地址

命令 响应 参数 描述
AT+ADDR? OK+ADDR:{MAC 地址} 此命令可用于查询蓝牙无线电的 MAC 地址。

设置名称

命令 响应 参数 描述
AT+NAME{参数} OK+set{参数} 此命令将设置模块的名称。

查询名称

命令 响应 参数 描述
AT+NAME? OK+NAME{参数} 此命令将返回模块的名称。

设置广告间隔

命令 响应 参数 描述
AT+ADVI{参数} OK+Set:{参数} 0: 100ms1: 152.5 ms2: 211.25 ms3: 318.75 ms4: 417.5 ms5: 546.25 ms6: 760 ms7: 852.5 ms8: 1022.5 ms9: 1285 msA: 2000 msB: 3000 msC: 4000 msD: 5000 msE: 6000 msF: 7000 ms 此命令将设置蓝牙低功耗模块的广告间隔。参数应为 0-F。

查询广告间隔

命令 响应 参数 描述
AT+ADVI? OK+get:{参数} 此命令将检索当前广告间隔,并将返回 0-f 的参数。

设置广告类型

命令 响应 参数 描述

| AT+ADTY{参数} | OK+set:{参数} | 0: 广告扫描响应,可连接1: 仅允许在 1.28 秒内连接最后设备2: 仅允许广告和扫描响应3: 仅允许广告 | 此命令将设置广告类型。 |

广告类型。 |

查询广告类型

命令 响应 参数 描述
AT+ADTY? OK+get:{参数} 此命令将检索当前广告类型,并将返回 0-3 的参数。

设置波特率

命令 响应 参数 描述
AT+BAUD{参数} OK+set:{参数} 0: 96001: 192002: 384003: 576004: 1152005: 48006: 24007: 12008: 230400 此命令将设置蓝牙模块的串行接口波特率。

查询波特率

命令 响应 参数 描述
AT+BAUD? OK+get:{参数} 此命令将检索当前波特率,并将返回 0-8 的参数。

设置特征 ID

命令 响应 参数 描述
AT+CHAR{参数} OK+set:{参数} 0x0001 -> 0xFFFe 此命令将设置特征的 ID。

设置服务 ID

命令 响应 参数 描述
AT+UUID{参数} OK+set:{参数} 0x0001 -> 0xFFFe 此命令将设置服务的 ID。

查询服务 ID

命令 响应 参数 描述
AT+UUID? OK+get:{参数} None 此命令将检索当前服务 ID。

设置角色

命令 响应 参数 描述
AT+ROLE{参数} OK+set:{参数} 0: 边缘设备1: 中心设备 此命令将设置蓝牙模块的角色。

查询角色

命令 响应 参数 描述
AT+ROLE? OK+get:{参数} None 此命令将返回蓝牙模块的角色。

清除最后连接设备

命令 响应 参数 描述
AT+CLEAR OK+CLEAR None 清除最后连接设备的地址。

注意at+clear命令仅在设备处于中心模式时使用。

尝试连接到最后一次连接的设备

命令 响应 参数 描述
AT+CONNL OK+CONN{参数} L: 连接中E: 连接错误F: 连接失败N: 无地址 此命令将尝试连接到最后一次成功连接的设备。

注意at+connl命令仅在设备处于中心模式时使用。

尝试连接到地址

命令 响应 参数 描述
AT+CON{参数} OK+CONN{参数} A: 连接E: 连接错误F: 连接失败 此命令将尝试连接到指定地址的设备。

注意at+con命令仅在设备处于中心模式时使用。

设置 PIN 码

命令 响应 参数 描述
AT+PASS{参数} OK+set:{参数} 000000 -> 999999 设置连接的 PIN 码。

查询 PIN 码

命令 响应 参数 描述
AT+PASS? OK+get:{参数} None 此命令将返回当前的 PIN 码。

设置模块功率

命令 响应 参数 描述
AT+POWE{参数} OK+set:{参数} 0: -23db1: -6db2: 0db3: 6db 设置模块的功率。

查询模块功率

命令 响应 参数 描述
AT+POWE? OK+get:{参数} None 此命令将返回当前模块的功率。

设置配对模式

命令 响应 参数 描述
AT+TYPE{参数} OK+set:{参数} 0: 不需要 PIN 码1: 无 PIN 码认证2: 有 PIN 码认证3: 有 PIN 码配对 此命令设置连接到此设备所需的认证。

查询配对模式

命令 响应 参数 描述
AT+TYPE? OK+get:{parameter} None 此命令将返回连接到此设备所需的当前认证。

设置通知信息

命令 响应 参数 描述
AT+NOTI{parameter} OK+set:{parameter} 0: 不通知1: 通知 此命令启用或禁用设备连接或断开时的通知。

查询通知信息

命令 响应 参数 描述
AT+NOTI? OK+get:{parameter} None 此命令将返回设备在连接或断开时是否会发送通知。

当没有其他设备连接到蓝牙模块时,我们才能向蓝牙模块发出 AT 命令。一旦设备连接,我们编写的应用程序代码将接管,并且输入到串行控制台的数据将发送到连接的设备。让我们看看从另一个设备连接到蓝牙模块时会发生什么。我将使用BTCommander – Serial Port HM10应用程序来展示这是如何工作的。

在 Arduino 上运行应用程序后,在你的手机/平板电脑或电脑上启动蓝牙应用程序。BTCommander应用程序看起来如下所示:

要连接到设备,请按下位于应用程序右上角的蓝色连接按钮,它看起来像一个电源插座。按下按钮后,你应该会看到应用程序可以连接到的设备列表。此屏幕看起来如下所示:

此屏幕显示所有正在广播且足够接近以连接的设备。在本章早期,当我们运行AT+nameBuddy命令时,我们将我们的设备重命名为Buddy。因此,我们知道这是我们想要连接的设备。如果我们点击该设备,然后点击应用程序右上角的连接按钮,应用程序将尝试连接。如果连接尝试成功,并且蓝牙模块上的AT+NOTI设置启用了通知,我们应该在串行控制台中看到以下截图所示的OK+CONN

连接成功后,应用程序将返回主屏幕。现在让我们在屏幕底部的输入框中输入一条消息。例如,在这里我们将输入一个简单的hello消息,如下所示:

输入消息后,按下看起来像纸飞机的输入框旁边的按钮以发送消息。如果消息成功发送,我们将在以下截图所示的串行控制台中看到它:

这里发生的情况是,在应用程序中输入的消息正从手机应用程序传递到 HM-10 蓝牙模块上的特征,因此我们的应用程序可以读取它。消息是逐个字符发送的。

要发送回消息,请在串行控制台输入框中输入消息并按发送按钮。如果消息发送成功,我们应该在以下截图所示的应用程序中看到它:

在此截图中,我们可以看到从应用程序发送了 hello 消息,并且从连接的设备接收到了 Hello from Arduino。当从 HM-10 蓝牙模块向手机应用程序发送消息时,应用程序将消息写入特征(一次一个字符),蓝牙模块使用通知来通知客户端(手机应用程序)有新数据。

如果我们在 BTCommander 应用程序中再次按下连接按钮以断开连接,并且将 AT+NOTI 配置设置为通知,我们将在串行控制台中看到如下截图所示的 OK+LOST 消息:

现在,让我们看看我们如何使用蓝牙模块从我们的手机控制一个 LED。

项目 2 – 控制 LED

在这个项目中,我们将根据手机输入打开或关闭连接到 Arduino 的 LED。我们首先需要做的是将 LED 添加到我们的电路中。以下图示显示了新的电路:

LED 通过一个 330 欧姆电阻连接到 Arduino 的数字 5 引脚。现在我们需要编写代码来控制 LED。我们将首先为蓝牙模块设置 SoftwareSerial 库并定义 LED 连接的引脚。以下代码将执行此操作:

#include <SoftwareSerial.h>
#define LED_PIN 5
SoftwareSerial HM10(10, 11);

我们可以看到蓝牙模块连接到与上一个示例相同的引脚,LED 连接到 Arduino 的数字引脚 5。在 setup() 函数中,我们需要配置 SoftwareSerial 实例以及 LED 连接的引脚模式。以下代码显示了此示例的 setup() 函数:

void setup()
{
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  Serial.begin(9600);
  HM10.begin(9600);
  Serial.println("Connected to HM-10");
}

此代码首先定义了 LED 连接的引脚模式。然后配置了串行监视器的串行端口和 SoftweareSerial 实例。最后,我们在串行控制台中打印一条消息,让用户知道一切已配置并准备就绪。

在我们的 loop() 函数中,我们需要检查 SoftwareSerial 实例,如果从连接的设备接收到 1,则 LED 会打开,如果接收到 0,则 LED 会关闭。如果没有接收到 10,则忽略输入。以下是 loop() 函数的代码:

void loop()
{
  if (HM10.available()) {
    char val = HM10.read();
    if(val == '1') {
      digitalWrite(LED_PIN, HIGH);
    } else if(val == '0') {
      digitalWrite(LED_PIN, LOW);
    }
  }
}

在此代码中,我们检查蓝牙模块是否有可用值,如果有,我们读取设备以获取接收到的字符。如果字符等于1(字符 1,而不是数字 1),我们将连接 LED 的引脚拉高以打开 LED。如果字符等于0(字符 0,而不是数字 0),我们将连接 LED 的引脚拉低以关闭 LED。

现在让我们运行这个应用程序,并使用BTCommander应用程序来连接它。从BTCommander应用程序,如果我们发送1,LED 将点亮,如果我们发送0,LED 将熄灭。这种类型的示例可以用于我们希望手机应用程序控制连接到 Arduino 的某些设备,如 LED、直流电机或某些传感器。

现在让我们看看如何通过蓝牙低功耗从 DHT-11 传感器检索温度和湿度数据。

项目 3 - 环境传感器

在这个项目中,我们将从手机请求 Arduino 发送温度或湿度信息,具体取决于发送的字符。我们需要将 DHT-11 传感器添加到我们的电路中。以下图表显示了如何进行:

DHT-11 温度传感器连接到 Arduino 的方式与我们第九章中所述的环境传感器相同。如果您不确定如何将此传感器连接到 Arduino,请参阅该章节。现在我们需要编写代码,以便我们可以通过蓝牙低功耗服务访问传感器的数据。我们将首先为蓝牙模块和 DHT-11 温度传感器设置SoftwareSerial库。以下代码将执行此操作:

#include <DHT.h>
#include <SoftwareSerial.h>

#define DHT_PIN 3
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);

SoftwareSerial HM10(10, 11); // RX | TX

此代码包括 DHT 温度传感器的库以及SoftwareSerial设备。然后它定义了连接到温度传感器的引脚和传感器的类型。最后,它创建了DHTSoftwareSerial类型的实例。

setup()函数中,我们需要配置SoftwareSerial实例和连接到 LED 的引脚的模式。以下代码显示了我们的示例的setup()函数:

void setup()
{
  Serial.begin(9600);
  HM10.begin(9600);
  Serial.println("Connected to HM-10");
}

此函数配置SoftwareSerial实例,并在一切启动并准备就绪时向串行控制台打印一条消息。在loop()函数中,我们将想要从连接到服务的设备读取输入,然后使用适当的信息进行响应。

以下图表显示了输入以及应该返回的内容:

输入 返回属性
f 华氏度温度
c 摄氏度温度
h 湿度
F 华氏度热指数
C 摄氏度热指数

从图表中,我们可以看到我们将有五个输入,每个输入将返回不同的信息给远程设备。让我们看看读取输入并返回所需信息的代码:

void loop()
{
  if (HM10.available()) {
    char val = HM10.read();
    if(val == 'f') {
      float fahreheit = dht.readTemperature(true);
      HM10.println(fahreheit);
    } else if(val == 'c') {
      float celsius = dht.readTemperature();
      HM10.println(celsius);
    } else if(val == 'h') {
      float humidity = dht.readHumidity();
      HM10.println(humidity);
    } else if(val == 'F') {
      float fahreheit = dht.readTemperature(true);
      float humidity = dht.readHumidity();
      float hif = dht.computeHeatIndex(fahreheit, humidity);
      HM10.println(hif);
    } else if(val == 'C') {
      float celsius = dht.readTemperature();
      float humidity = dht.readHumidity();
      float hic = dht.computeHeatIndex(celsius, humidity, false);
      HM10.println(hic);
    }
  }
}

此代码首先检查蓝牙适配器,看是否有可用输入,如果有,则读取字符。如果输入字符是之前图表中列出的字符之一,代码将从 DHT-11 传感器检索适当的值,并将该值返回给连接的设备。

BTCommander iOS 应用中的代码输出将类似于以下内容:

图片

我们在这里展示的所有内容都与蓝牙 LE 4.0 及以上版本兼容。截至本书编写时,市场上真正与新的蓝牙 LE 4.2 和 5.0 标准兼容的低成本蓝牙 LE 模块并不多,这就是为什么我们坚持使用 4.0。好消息是所有新的标准都与 4.0 标准向后兼容,因此,随着与较新标准兼容的新蓝牙模块的发布,本章中讨论的所有内容都将正常工作。让我们看看较新蓝牙标准提供了哪些特性。

蓝牙 4.1、4.2 和 5.0 的新特性是什么?

在本节中,我们将探讨蓝牙 4.1、4.2 和 5.0 的新特性。虽然这些特性与我们在本章中使用的蓝牙 LE 4.0 模块不兼容,但最终将会有与这些标准兼容的 Arduino 蓝牙模块发布;因此,了解它们提供的特性是很有帮助的。

蓝牙 4.1

蓝牙 4.1 主要提供可用性更新。其中最重要的更新是允许蓝牙 LE 和 LTE 无线电更好地共存。此更新允许无线电协调传输以减少干扰的机会。它还使数据传输更高效,并在连接丢失后允许更好的重新连接。

蓝牙 4.1 的非可用性重大变化允许设备同时作为外围设备和中心设备。

蓝牙 4.2

蓝牙 4.2 为物联网、安全性、更快速度和更大容量提供了众多新特性。

对于物联网,蓝牙 4.2 增加了蓝牙智能互联网网关,这使得蓝牙 4.2 设备能够连接到互联网。通过互联网网关,蓝牙 4.2 还增加了 IPv6/6LoWPAN,这使得通过蓝牙连接支持 IPv6。

蓝牙 4.2 还通过 LE 隐私 1.2 增加了额外的安全性。蓝牙 4.2 的加密标准符合联邦信息处理标准FIPS),这是一项美国政府计算机安全标准。

传输数据包大小增加了十倍。这允许更快、更可靠的数据传输。

蓝牙 5.0

蓝牙提供了一系列增强功能,将范围扩大了四倍,速度提高了两倍。但问题是,如果设备制造商增加了设备的范围,速度就会下降;同样,如果速度提高了,范围也会下降。

蓝牙网状网络

在最新的蓝牙技术中,我认为最令人兴奋的是蓝牙网状网络。所有之前的蓝牙技术都依赖于一对一或一对多的连接,其中始终有一个主/中心设备。网状技术允许蓝牙设备建立多对多的连接,这可以实现不依赖于中心控制器的大规模设备网络。当我们撰写这本书时,蓝牙网状技术仍处于起步阶段。然而,我相信它是蓝牙的未来,并且是一项值得关注的科技。

需要注意的是,对于高于 4.0 的蓝牙标准,大多数新特性都是可选的,并不要求完全实现。例如,制造商可能会说他们的设备符合蓝牙 4.2 标准。然而,IPv6/6LoWPAN 可能并未在设备中实现。一个很好的例子是 iPhone。我的 iPhone X 支持蓝牙 5.0;然而,它无法运行蓝牙网状网络或 IPv6/6LoWPAN。

挑战

对于这个挑战,请使用我们在第十三章中使用的 Nokia 5110 液晶显示屏和本章第一个项目使用的串行通信代码,打印从手机应用发送到液晶显示屏的任何消息。这需要对本章中的代码进行一些修改,以便使用液晶屏幕而不是串行控制台。

摘要

在本章中,我们学习了关于蓝牙 LE 的很多知识,从对无线电工作原理和蓝牙 LE 连接的网络拓扑的简要介绍开始。我们学习了蓝牙 LE 设备如何使用 GAP 来发现和连接到其他设备。我们还看到了 GATT 如何使用属性(服务、特性和描述符)使两台蓝牙 LE 设备能够相互通信。最后,我们在本章末尾通过三个项目展示了蓝牙 LE 的工作原理。

当我们希望使用外部设备,如手机,来控制我们正在构建的设备时,蓝牙低功耗(LE)是最适合的技术,因为几乎所有的智能手机都内置了蓝牙 LE,并且该技术易于使用。它也是当我们希望从一台设备向另一台设备发送短数据包时使用的好技术。如果您希望构建一个独立的设备,如遥控器,来控制主设备或传输大量数据,那么我建议使用另一种名为蓝牙经典或遗留的蓝牙技术,我们将在下一章中看到。

第二十一章:蓝牙经典

我们在第二十章中看到的蓝牙低功耗(蓝牙低功耗)是我们需要两个设备在短数据突发中无线通信且关注功耗时的一个优秀选择。随着蓝牙低功耗的 4.2 和 5.0 版本的出现,它发生了变化,这使得它对需要传输大量数据或甚至流式传输数据的设备更具吸引力。然而,还有一种蓝牙技术已经非常成功地做了很多年,这种技术被称为蓝牙经典。虽然名字可能意味着这项技术已经过时,但不要被名字所迷惑,因为蓝牙经典被用于许多蓝牙设备中,并且直到有更多支持一些新功能的 Arduino 蓝牙 5.0 模块可用,蓝牙经典在需要两个设备之间传输大量数据时仍将是一个优秀的选择。

在本章中,您将学习:

  • 蓝牙经典版本号的意义

  • 蓝牙无线电的工作原理

  • 蓝牙网络的网络拓扑

  • 如何使用 HC-05 蓝牙模块

简介

蓝牙是一种无线技术标准,两个设备使用 2.4GHz 无线连接在短距离内传输或接收数据。虽然蓝牙低功耗的设计目标是创建一个低功耗的无线协议,但蓝牙经典有不同的设计目标。蓝牙经典是由瑞典吕德的爱立信移动工程师创建的,作为一种无线替代串行(RS232)电缆。这意味着这个新协议将需要传输大量数据,甚至流式传输数据到短距离。

蓝牙经典规范由蓝牙特殊兴趣集团蓝牙 SIG)管理,作为蓝牙核心规范的一部分。正如我们在第二十章中提到的蓝牙低功耗,您可以通过从蓝牙 SIG 网站www.bluetooth.com下载规范来找到有关蓝牙低功耗和蓝牙经典的信息。

起初,在介绍较新的技术(蓝牙低功耗 4.0)之前介绍较旧的技术(蓝牙经典)可能看起来有些奇怪。蓝牙低功耗首先被介绍的原因是您会发现它在您将使用 Arduino 创建的绝大多数项目中都是合适的,因为大多数项目都希望发送短数据突发,这正是蓝牙低功耗的设计目的。蓝牙低功耗也更容易与使用蓝牙低功耗的智能手机集成,因为每个智能手机操作系统都有一个易于使用且文档齐全的蓝牙低功耗 API,而蓝牙经典则不是这样。对于您想要在两个自定义设备之间流式传输数据或共享大量数据的情况,蓝牙经典可能更合适。

当你为项目购买蓝牙经典模块时,你将可以选择三种不同的蓝牙版本。这些版本是:

  • 蓝牙 2.0 + EDR:该版本的核心理念于 2004 年发布。这次对蓝牙核心规格的更新包含了对蓝牙标准的多项小改进。唯一的重大改进是 EDR增强数据速率),将数据传输速率从 1Mbits/sec 提高到 3Mbits/sec。该标准的名称为蓝牙 2.0 + EDR,这意味着 EDR 功能是可选的。我们将在本章中使用 HC-05 蓝牙模块,它是蓝牙 2.0 兼容的,这意味着它不包括 EDR 功能。对于你将用 Arduino 构建的绝大多数项目,蓝牙 2.0 兼容的模块将足够好,实际上更可取,因为我们能够避免蓝牙 2.1 中引入的安全配对功能。虽然新的配对功能可能被称为简单安全配对,但它通常需要人工交互来完成配对过程,而我们可能希望避免这种情况,因为许多 Arduino 项目没有进行这种输入的能力。

  • 蓝牙 2.1 + EDR:该版本的核心理念于 2007 年发布。蓝牙核心规格的这次版本也对其前一个版本进行了多项改进,其中特色改进是引入了 SSP简单安全配对)。SSP 对配对过程进行了彻底的改革,使其既简单又更安全。

  • 蓝牙 3.0 + HS:该版本的核心理念于 2009 年发布。规格名称中的 HS 代表高速。蓝牙 3.0 + HS 的理论数据传输速度可达 24 Mbits/sec,然而数据不是通过蓝牙连接传输的。在高速模式下,数据实际上是通过 801.11(Wi-Fi)连接传输的。蓝牙链路仅用于协商和建立 Wi-Fi 连接。与蓝牙 2.X + EDR 规范一样,HS 功能是可选的,你将看到仅符合蓝牙 3.0 标准的设备。

与蓝牙 LE 一样,要真正理解何时使用蓝牙经典,我们需要了解这项技术本身,所以让我们更深入地探讨一下。

蓝牙无线电

蓝牙无线电的传输范围取决于类别。以下图表显示了不同类别蓝牙无线电的传输范围:

类别 功率(毫瓦) 功率(dBm) 范围(米)
1 100 20 ~100
2 2.5 4 ~10
3 1 0 ~1

与任何无线电技术一样,无线电周围的环境对无线电的传输范围有重大影响。前一个图表中列出的范围是在理想条件下的理论最大范围。典型的范围通常小于这个理论最大范围。

在蓝牙 LE 无线电从 2,402 MHz 到 2,480 MHz 运行,每个信道间隔 2 MHz 的情况下,蓝牙经典无线电使用从 2,402 MHz 到 2,480 MHz 的 79 个信道,每个信道间隔 1 MHz。与蓝牙 LE 类似,蓝牙经典无线电使用跳频,无线电每秒改变信道 1,600 次,以减少干扰。

在蓝牙 LE 中,无线电持续关闭自身以减少功耗,蓝牙经典不这样做。这使得蓝牙 LE 无线电技术在低功耗的短数据突发方面表现更好,而蓝牙经典无线电在传输大量数据或数据流方面表现更好,因为无线电是持续开启的。

所有蓝牙设备都有一个独特的 48 位地址,由制造商分配给蓝牙无线电。地址的上半部分(最重要的 24 位)被称为组织唯一标识符,由两部分组成。这些部分是非重要地址NAP)和高地址部分UAP)。

NAP 是地址的前 16 位,用于跳频同步。UAP 是接下来的 8 位,由 IEEE 组织分配给无线电制造商。

地址的最后 24 位被称为低地址部分LAP)。LAP 由制造商分配,以唯一标识无线电。以下图表显示了蓝牙地址的构成:

图片

现在让我们看看蓝牙经典的网络拓扑。

网络拓扑

蓝牙经典 piconet 的拓扑结构与蓝牙 LE 网络的拓扑结构非常相似,其中一个设备充当主设备,其他设备充当主设备的从设备。在蓝牙经典 piconet 中,一个主设备可以有最多七个从设备,总共八个设备在 piconet 中。以下图表显示了蓝牙经典 piconet:

图片

Piconets 可以与其他 piconets 交互,形成所谓的散射网。在散射网中,一个 piconet 的主设备在另一个 piconet 中充当从设备。这确实允许一个 piconet 中的设备与其他 piconet 中的设备共享数据;然而,这需要复杂的同步和带宽共享,使得这些网络更加复杂且效率较低。了解我们可以创建散射网是好的,但根据我的经验,这很少被使用。

蓝牙的内容远不止这里所描述的;然而,对于绝大多数用例,您将希望使用第二十章中描述的蓝牙低功耗(Bluetooth LE)。使用 Arduino 时,当我们想要连接两个设备并在它们之间传输数据时,我们会使用蓝牙经典。让我们看看我们如何通过三个项目来实现这一点。对于第一个项目,我们将配置蓝牙模块;在第二个项目中,我们将学习如何从蓝牙模块发送和接收数据;在第三个项目中,我们将看到如何从一个无线电流式传输数据到另一个无线电。我们将从查看这些项目所需的组件开始。

需要的组件

对于这些项目,您需要以下物品:

  • 两个 Arduino Uno 或兼容板

  • 两个 HC-05 蓝牙模块

  • 一个用于 Arduino 的摇杆扩展模块

  • 跳线

  • 面包板

现在让我们看看我们项目的电路图。

电路图

在本章中,我们将为三个项目编写代码。在第一个项目中,我们将配置蓝牙模块;在第二个项目中,我们将创建一个应用程序,该应用程序将以字节格式从一台蓝牙无线电发送数据到另一台;在最后一个项目中,我们将把一个摇杆连接到一个 Arduino 上,并通过蓝牙连接将摇杆位置流式传输到另一个 Arduino。以下是我们项目的电路图:

图片

这两个 Arduino 电路彼此完全隔离,因此它们不需要公共地线。两个 HC-06 蓝牙模块以相同的方式连接到 Arduino,其中 HC-06 蓝牙模块的 VCC 引脚连接到 5V 输出,GND 引脚连接到 Arduino 的地线输出。蓝牙模块的关键引脚连接到 Arduino 的数字 9 引脚,RX 引脚连接到数字 10 引脚,TX 引脚连接到数字 11 引脚。

我们希望将一个摇杆扩展模块连接到其中一个 Arduino 上。为此,我们需要将扩展板上的 VCC 引脚连接到 Arduino 的 5V 输出,并将 GND 引脚连接到 Arduino 的地线输出。我们将根据您的摇杆模块连接 SEK 或 SW 引脚到 Arduino 的数字 2 引脚。最后,我们将扩展板上的 HOR 或x轴引脚连接到 Arduino 的模拟 0 引脚,并将 VER 或y轴引脚连接到模拟 1 引脚。

现在,让我们开始我们的项目。

项目 1 – 配置蓝牙模块

要与 HC-05 蓝牙模块通信,我们将使用与第二十章中相同的 SoftwareSerial 库,即 蓝牙低功耗。用于通信的代码在 HM-10(蓝牙低功耗)和 HC-05(蓝牙经典)之间非常相似。这两款无线电如何传输和接收数据有很大不同,因此理解无线电的工作原理以及它们应该用于什么将决定何时使用不同的技术。

对于这个第一个项目,我们将编写一个应用程序,允许我们配置蓝牙模块。此代码将与我们之前使用的蓝牙低功耗代码完全一样,通过包含 SoftwareSerial 库并创建 SoftwareSerial 类型的实例。以下代码展示了如何做到这一点:

#include <SoftwareSerial.h>
SoftwareSerial HC05(10, 11);
bool addNewLine = false;

第一行包含了 SoftwareSerial 库,第二行创建了这个类型的实例。最后一行的布尔变量将用于告诉应用程序何时在串行控制台中添加新行。

现在我们需要在 setup() 函数中添加代码来配置串行控制台和 SoftwareSerial 实例。以下代码展示了这个第一个项目的 setup() 函数:

void setup()
{
  Serial.begin(9600);
  pinMode(9,OUTPUT);
  digitalWrite(9,HIGH);
  HC05.begin(38400);
  Serial.println("Connected to HC-05\.  Try connecting from any device or issue AT commands");
}

此代码首先使用波特率 9600 设置串行控制台,然后定义数字 9 脚将是一个输出脚并将其设置为高电平。数字 9 脚连接到 HC-05 的按键引脚。我们将此引脚拉高以启用蓝牙模块。然后我们使用波特率 38400 配置 HC05 类型的 SoftwareSerial 实例,并向串行控制台打印一条消息,告知用户一切已配置并准备就绪。

你会注意到,在这个第一个项目中,我们将 SoftwareSerial 实例的波特率设置为 38400,因为我们正在配置蓝牙模块。在接下来的两个项目中,我们将波特率设置为 9600,因为我们将在蓝牙模块之间发送和接收数据。

loop() 函数中,就像在蓝牙低功耗(BLE)代码中一样,我们将从蓝牙模块接收任何输入并将其打印到串行控制台,同时任何来自串行控制台的输入将通过蓝牙模块发送出去。以下代码将执行此操作:

void loop()
{
  if (HC05.available())
  {
    if (addNewLine) {
      Serial.write("\r\n");
      addNewLine = false;
    }
    Serial.write(HC05.read());
  }

  if (Serial.available())
  {
    HC05.write(Serial.read());
    addNewLine = true;
  }
}

在这个函数中,我们首先使用 available() 函数检查是否有来自 HC05 SoftwareSerial 实例(蓝牙模块)的数据可用。如果有数据可用,我们会检查是否需要向串行控制台添加新行,通过检查 addNewLine 布尔变量。如果需要添加新行,我们将回车和换行符写入串行控制台,并将 addNewLine 布尔变量设置为 false。然后我们将从蓝牙模块接收到的数据写入串行控制台。

接下来,我们检查串行控制台是否有任何可用数据,也使用available()函数,如果有,我们将该数据写入蓝牙模块,然后将其传输到连接的设备。我们还设置addNewLine布尔变量为 true,这样下次从连接的设备接收数据时,我们将在串行控制台添加回车符和换行符。

在我们将 Arduino 插入并运行此代码之前,我们需要将 HC-05 蓝牙模块设置为配置模式。为此,我们需要按住蓝牙模块上的按钮,然后将 Arduino 插入电脑,为蓝牙模块供电。只需几秒钟,蓝牙模块上的灯就会开始非常缓慢地闪烁;灯亮两秒钟,然后熄灭两秒钟。一旦灯开始闪烁,我们可以松开按钮,蓝牙模块就准备好配置了。

要配置蓝牙模块,我们将发出与蓝牙低功耗模块类似的 AT 命令。要发送 AT 命令,您将使用以下格式:

Set item: AT+{command}{new setting}
Query item: AT+{command}?

要设置一个项目,您输入字母 AT,然后是加号,命令和新的设置,中间没有空格。例如,要将蓝牙模块的角色设置为从属角色,我们将发出以下命令:

at+role0

注意:AT 命令不区分大小写。

要查询项目,我们将输入字母at,然后是加号,命令,然后是问号。例如,要查询蓝牙模块的角色,我们将使用以下命令:

at+role?

要发出命令,我们将在串行控制台上的输入框中输入命令,然后按Enter键。我们需要将串行控制台设置为添加 NL(换行符)和 CR(回车符)。以下截图显示了如何发出 AT 命令:

在我们输入at+role?命令后,我们按Enter键或发送按钮来将命令发送到蓝牙模块。蓝牙模块将响应查询结果,如下面的截图所示:

在我们配置模块之前,让我们看看我们可以向 HC-05 蓝牙模块发出的某些命令。

测试命令

命令 响应 参数 描述
AT OK 这是一个测试命令,可以用来测试与蓝牙模块的连接。

重置命令

命令 响应 参数 描述
AT+RESET OK 此命令将重置蓝牙模块。

查询固件

命令 响应 参数 描述
AT+VERSION? +VERSION:<Param> 返回 HC-05 蓝牙模块上的固件版本。

恢复出厂设置

命令 响应 参数 描述
AT+ORGL OK 将 HC-05 蓝牙模块恢复到默认设置。

查询模块地址

命令 响应 参数 描述
AT+ADDR? +ADDR:<Param> 返回 HC-05 蓝牙模块的地址。

设置/查询模块模式

命令 响应 参数 描述
AT+ROLE? +ROLE:<Param> 0 从机1 主机 查询 HC-05 蓝牙模块的角色。
命令 响应 参数 描述
AT+ROLE=<Param> OK 0 从机1 主机 设置 HC-05 蓝牙模块的角色。

设置/查询 UART 参数

命令 响应 参数 描述
AT+UART? +UART:<Param1>, <Param2>, <Param3> Param1 = 波特率Param2 = 停止位Param3 = 奇偶校验 查询 UART 参数。
命令 响应 参数 描述
AT+UART=<Param1>, <Param2>, <Param3> OK Param1 = 波特率Param2 = 停止位Param3 = 奇偶校验 设置 UART 参数。

设置/查询连接模式

命令 响应 参数 描述
AT+CMODE? +UART:<Param> 0 连接到固定地址1 连接到任何地址2 从机环回 查询 HC-05 蓝牙模块的连接模式。
命令 响应 参数 描述
AT+CMODE=<Param> OK 0 连接到固定地址1 连接到任何地址2 从机环回 设置 HC-05 蓝牙模块的连接模式。

设置/查询绑定地址

命令 响应 参数 描述
AT+BIND? +BIND:<Param> 查询模块配置的绑定地址。
命令 响应 参数 描述
AT+BIND=<Param> OK 固定地址 设置要绑定的地址。

现在我们已经看到了大多数 AT 命令,让我们配置两个蓝牙模块。我们需要将一个蓝牙模块配置为主机,另一个配置为从机。为了下一两个项目的目的,我将连接到摇杆的同一 Arduino 的蓝牙模块配置为从机。然而,这并非必要,任何模块都可以是主机或从机。

让我们从配置从设备开始。为此,将一个 Arduino 连接到计算机,运行本节开头编写的应用程序,然后运行下一几段中将要概述的命令。

我们首先想要做的是向蓝牙模块发送测试 AT 命令。模块应该返回一个 OK 消息。如果你没有收到任何响应,请检查串行控制台是否已配置为发送 NL 和 CR。如果你收到错误响应,请再次尝试发送 AT 命令。

现在我们确信串行监视器和蓝牙模块正在通信,我们想要查看此模块当前的 UART 设置。为此,发送AT+UART?命令。在本章的示例中,我们将假设 UART 设置为 9600 波特率,0 停止位,0 奇偶校验。如果您的模块不是这样配置的,请发出以下命令:

AT+UART=9600,0,0

我们接下来想要做的是将设备的角色设置为从角色。为此,我们发出以下命令:

AT+ROLE=0

最后,我们想要检索这个蓝牙模块的地址。以下命令将检索地址:

AT+ADDR?

确保记下地址,因为我们将在配置主设备时使用它。

我们运行以配置从模块的命令如下:

命令 响应
AT OK
AT+UART? +UART:9600,0,0(如果不是,设置为这个值)
AT+ROLE=0 OK
AT+ADDR? +ADDR:{address}

现在让我们配置主设备。为此,将另一个 Arduino 连接到计算机(记住在给模块供电时按住按钮),运行本节开头编写的代码,并发出下几段中将要讨论的命令。

就像从设备一样,我们首先想要做的是向蓝牙模块发出AT命令。模块应该返回一个OK消息。如果你没有收到任何响应,请检查确保串行控制台已配置为发送 NL 和 CR。如果你收到错误响应,请再次发出AT命令。

现在,我们想要查看模块的 UART 设置。为此,发送AT+UART?命令。在本章的示例中,我们将假设 UART 设置为 9600 波特率,0 停止位,0 奇偶校验。如果您的模块不是这样配置的,请发出以下命令:

AT+UART=9600,0,0

我们接下来想要做的是将设备的角色设置为主角色。为此,我们发出以下命令:

AT+ROLE=1

现在,我们将想要将连接模式设置为连接到固定地址(模式 0)。为此,发出以下命令:

AT+CMODE=0

由于我们要告诉蓝牙模块连接到一个固定的地址,因此我们需要提供它需要连接的从设备地址。

要这样做,发出以下命令:

AT+BIND=????,??,??????

问号是从设备的地址。当我们查询从设备的地址时,地址以冒号分隔返回,例如98d3:31:300e42。在BIND命令中输入地址时,地址需要以逗号分隔,例如98d3,31,300e42

我们用来配置主设备的命令如下:

命令 响应
AT OK
AT+UART? +UART:9600,0,0(如果不是,设置为这个值)
AT+ROLE=1 OK
AT+CMODE=0 OK
AT+BIND=????,??,??????(问号是从设备的地址) OK

现在如果我们通过重新供电来重置两个设备,两个蓝牙模块应该会连接。首先,在从设备上重新供电,你会看到 LED 快速闪烁。然后,在主设备上重新供电,一旦两个设备连接,两个设备上的 LED 都会快速闪烁两次,然后关闭两秒钟,然后重复。这个灯光序列表明两个设备已经连接。

如果设备没有连接,最常见的一个错误就是在AT+BIND命令中输入了错误的地址。我会首先通过运行AT+BIND?命令来检查,并验证地址是否正确。如果地址正确,那么通过运行AT+CMODE?AT+ROLE?命令来验证AT+CMODEAT+ROLE命令是否正确执行。现在我们已经将两个蓝牙模块连接起来,让我们继续进行项目二。

项目 2 – 串行连接,发送数据

对于这个项目,为了看到数据从一个设备传输到另一个设备,你需要两台计算机。一台连接到主设备,另一台连接到从设备。如果你没有两台计算机,仍然值得阅读这一部分,以了解我们正在创建的协议,因为我们将在第三个项目中使用相同的协议。

当我们在传输数据或发送大量可变长度的数据时,我们需要一种方式来告诉接收设备新消息的开始和结束位置。幸运的是,我们有内置的 ASCII 代码可以实现这一点。0x01 SOH标题开始)和0x04 EOT传输结束)代码可以用来告诉接收设备消息的开始和结束。

在这个项目和下一个项目中,我们将定义的协议是:当接收设备接收到0x01 ASCII 字符时,它会知道一个新的消息已经开始。当它接收到0x04 ASCII 字符时,它会知道消息已经结束,并且0x010x04字符之间的所有内容都是消息本身。

如果你不太熟悉 ASCII 代码,以下图表显示了 ASCII 表:

图片

事实上,当我们通过两个蓝牙经典设备之间传输字符数据时,我们实际上是在发送 ASCII 代码。例如,如果我们发送单词“Dog”,我们实际上在发送三个字节数据,分别是0x44(D),0x111(o),和0x67(g)。根据我们定义的协议,如果我们发送单词 Dog,我们会发送五个字节数据,因为我们需要以0x01字符开始,以0x04字符结束。我们将发送的五个字节将是0x010x440x1110x670x04

现在我们来看一下在两个蓝牙模块之间发送和接收消息的代码。此代码将在主设备和从设备上运行。我们将首先在项目中包含SoftwareSerial库,并创建一个SoftwareSerial类型的实例。以下代码执行此操作:

#include <SoftwareSerial.h>
SoftwareSerial HC05(10, 11);// RX | TX
bool newMessage = true;

第一行在项目中包含SoftwareSerial库,下一行创建一个SoftwareSerial类型的实例。第三行创建一个全局变量,用于定义何时开始新消息。

现在我们需要在setup()方法中配置串行控制台和HC05 SoftwareSerial实例。以下代码将执行此操作:

void setup()
{
  Serial.begin(9600);
  pinMode(9,OUTPUT);
  digitalWrite(9,HIGH);
  HC05.begin(9600);
  Serial.println("Connected to HC-05\. ");
}

在此代码中,我们首先配置串行控制台,波特率为 9600。然后定义数字 9 脚将作为输出脚,并将其设置为高电平。数字 9 脚连接到 HC-05 蓝牙模块的按键引脚。我们将此引脚拉高以启用它。我们使用波特率为 9600 配置HC05类型的SoftwareSerial实例,并在串行控制台上打印一条消息,告知用户一切已配置就绪,准备开始。

loop()函数需要监控串行控制台和HC05 SoftwareSerial实例,以接收新数据。如果它从串行控制台接收到新数据,它需要通过蓝牙模块将其发送出去,如果它从蓝牙模块接收到新数据,它需要在串行控制台上显示这些数据。以下代码执行此操作:

void loop()
{
  if (HC05.available())
  {
    byte val = HC05.read();
    Serial.write(val);
    if (val == 0x04)
    {
      Serial.write("\r\n");
    }
  }
  if (Serial.available())
  {
    if (newMessage)
    {
      HC05.write(0x01);
      newMessage = false;
    }
    char val = Serial.read();
    if (val == '~')
    {
      HC05.write(0x04);
      newMessage = true;
    }
    else
    {
      HC05.write(val);
    }
  }
}

在此函数中,我们检查HC05 SoftwareSerial实例是否有任何可用数据,如果有,它将被读取到val变量中。然后将val变量写入串行控制台。然后我们检查val变量是否等于0x04,如果是,我们在串行控制台上写入一个回车符和换行符,因为这条特定的消息已经结束。

现在我们检查串行控制台是否有任何可用数据,如果有,我们检查是否开始了一个新消息,通过检查newMessage变量是否等于true。如果newMessage变量等于true,我们将一个0x01字符写入HC05 SoftwareSerial实例,并将newMessage变量设置为false。然后我们从串行控制台读取字符,并检查它是否等于波浪号(~)字符。我们将使用波浪号字符来指定消息已结束,因此当用户输入波浪号时,我们将写入0x04字符到HC05 SoftwareSerial实例,并将newMessage变量设置为true,因为这条特定的消息已经结束。如果字符不等于波浪号,我们将字符写入SoftwareSerial实例。

现在如果我们在这两个主从设备上运行此代码,无论我们在一个设备的串行控制台上输入什么,都会通过蓝牙模块传输到另一个设备。消息将继续打印在串行控制台的一行上,直到用户输入波浪号(~)表示消息的结束。

互相传输文本是好的,但正如我们在第二十章中看到的,我们可以使用蓝牙低功耗(Bluetooth LE)来做这件事,蓝牙低功耗。让我们通过将 Arduino 摇杆模块连接到其中一个设备并将摇杆位置流式传输到另一个设备来做一些更有用的事情。

项目 3 – 摇杆遥控

如果你还没有将摇杆扩展模块连接到 Arduino 之一,你需要在开始此项目之前完成连接。一旦摇杆扩展模块连接到 Arduino,我们将编写代码来读取摇杆的位置并将其通过 HC-05 蓝牙模块传输到另一个 Arduino;然而,在我们这样做之前,我们需要弄清楚我们将要使用的协议。

对于这个例子,我们将使用与上一个项目相同的协议,其中消息以0x01字节开始,以0x04字节结束,而中间的一切都是消息本身。消息本身将包含两个字节,一个表示摇杆的x位置,另一个表示y位置。因此,一个完整的传输将包含总共四个字节,如下所示:

0x01 - Start of header
0xDD - X position (221 decimal)
0xDD - Y position (221 decimal)
0x04 - End of transmission

现在我们有了将摇杆位置从一块 Arduino 传输到另一块的协议,让我们开始编写将在连接摇杆扩展模块的 Arduino 上运行的代码。摇杆的位置是通过连接到它的两个模拟引脚读取的。我们还需要将连接到数字 2 引脚的 SEL 引脚设置为高电平。

在代码中,我们首先需要包含蓝牙模块的SoftwareSerial库,创建一个SoftwareSerial类型的实例,并定义摇杆模块连接到的引脚。以下代码将完成这项工作:

#include <SoftwareSerial.h>
#define SW_PIN 2 // digital pin Joystick
#define BT_PIN 9 // digital pin Bluetooth
#define X_PIN 0  // analog pin
#define Y_PIN 1  // analog pin

SoftwareSerial HC05(10, 11);

在此代码中,我们定义摇杆的 SEL 引脚为数字 2 引脚,蓝牙模块上的按键引脚为数字 9 引脚,xy轴为模拟 0 和 1 引脚。

setup()函数中,我们需要将SW_PINBT_PIN都设置为高电平,并初始化串行控制台和SoftwareSerial实例。以下是setup()函数的代码:

void setup()
{
  pinMode(BT_PIN,OUTPUT);
  digitalWrite(BT_PIN,HIGH);
  pinMode(SW_PIN,OUTPUT);
  digitalWrite(SW_PIN,HIGH);
  HC05.begin(9600);
  Serial.begin(9600);
  Serial.println("Connected to HC05.");
}

现在代码应该看起来很熟悉了。前四行初始化数字引脚并将它们拉高。接下来的两行初始化SoftwareSerial实例和串行控制台,波特率为 9600。最后,在串行控制台上打印一条消息,告知用户一切准备就绪。

在我们的loop()函数中,我们需要读取操纵杆的位置,然后将消息写入蓝牙模块。以下代码将执行此操作:

void loop()
{
  int xpos = analogRead(X_PIN) / 4;
  int ypos = analogRead(Y_PIN) / 4;
  HC05.write(0x01);
  HC05.write(xpos);
  HC05.write(ypos);
  HC05.write(0x04);
  delay(500);
}

前两行读取操纵杆模块的x轴和y轴。当读取模拟引脚时,返回的值范围从 0 到 1024;然而,我们只想发送一个字节来表示操纵杆的位置。一个字节的范围可以从 0 到 255,因此我们将模拟读取的值除以4

在我们获取操纵杆的x轴和y轴的值之后,我们需要将这些值通过蓝牙模块发送消息。接下来的四行代码写入0x01(SOH),x轴的值,y轴的值,最后是0x04(EOT)。消息发送后,我们暂停 500 毫秒,然后循环返回。

现在我们有了将在连接操纵杆的 Arduino 上运行的代码,我们需要编写将在接收数据的 Arduino 上运行的代码。此代码需要首先包含蓝牙模块的SoftwareSerial库,并创建一个SoftwareSerial类型的实例。我们还需要定义一个缓冲区,用于存储通过蓝牙模块传入的数据。以下代码将执行此操作:

#include <SoftwareSerial.h>
#define MAXBUF 255
#define BT_PIN 9 // digital pin Bluetooth
SoftwareSerial HC05(10, 11);
byte buf[MAXBUF];

此代码首先包含SoftwareSerial库,然后定义输入缓冲区的最大大小,为255。虽然我们知道每条消息的大小为 4 个字节,但我们总是希望在缓冲区中留有额外空间,尤其是在无线通信中,以防消息在传输过程中出错。如果这是一个生产系统,我可能会将缓冲区的大小限制为 12 或 16 字节。

我们定义蓝牙模块上的按键引脚连接到 Arduino 的数字 9 号引脚。然后我们创建一个SoftwareSerial类型的实例和一个输入缓冲区的byte数组。

setup()函数中,我们将初始化串行控制台和SoftwareSerial实例。我们还需要将蓝牙模块的按键引脚拉高。以下代码执行此操作:

void setup()
{
  Serial.begin(9600);
  pinMode(BT_PIN,OUTPUT);
  digitalWrite(BT_PIN,HIGH);
  HC05.begin(9600);
  Serial.println("Connected to HC05");
}

现在在loop()函数中,我们希望持续读取蓝牙模块的输入,直到我们接收到 EOT(0x04)字节。当我们读取数据时,它将被存储在字节数组中,一旦读取到0x04字节,我们将打印出消息,然后循环返回。以下是loop()函数的代码:

void loop()
{
  memset(buf, 0, MAXBUF);
  int counter = 0;
  while (counter < MAXBUF)
  {
    if (HC05.available())
    {
      byte val = HC05.read();
      buf[counter] = val;
      counter++;
      if (val == 0x04)
      {
        break;
      }
    }
  }
  for(int i=0; i<counter; i++)
  {
    Serial.print(buf[i]);
    Serial.print(" ");
  }
  Serial.println(" ");
}

此函数首先使用memset()函数将缓冲区初始化为零。然后我们创建一个整数变量,该变量将计算读取的字节数。

while 循环用于持续循环,直到读取到最大字节数。在 while 循环中,我们使用 HC05 SoftwareSerial 实例的 available() 函数来查看蓝牙模块是否有可读取的值。如果有可读取的值,我们使用 read() 函数读取该值,将其存储在 buf 字节数组中,并增加计数器。然后我们检查读取的值是否等于 0x04,如果是,我们使用 break 语句跳出 while 循环。

最后,我们创建一个 for 循环,该循环将遍历缓冲区中的值并将它们打印到串行控制台。如果我们同时在两个 Arduino 上执行代码并移动摇杆,我们将看到类似于以下截图的输出:

如输出所示,每个消息都以 0x01 字节开始,以 0x04 字节结束。在这两个字节之间是摇杆在 x 轴和 y 轴上的位置。

我们知道数据包应该是四个字节长。在生产环境中,我们希望丢弃任何长度不是四个字节的消息,因为我们知道如果消息长度不是四个字节,那么消息在传输过程中可能已经损坏。

我们还可以使用校验和来确保消息被正确接收。校验和是使用发送的数据计算出的某个值。生成校验和的最简单方法之一是将所有数据字节相加,将值存储在一个字节中,当该值大于 255 时将导致值回绕。以下是一个生成校验和的函数示例:

byte checksum(byte *bytes, int buf_size)
{
  byte checksum = 0;
  for (int i=0; i< buf_size; i++)
  {
    checksum += bytes[i];
  }
  return checksum;
}

此函数接受一个指向 byte 数组的指针和数组的大小作为参数。然后它遍历数组,将每个字节添加到校验和中,然后返回值。一个字节的最大值为 255,因此一旦值超过 255,值将回绕。例如,如果校验和字节值为 252,向其添加一个值为 10 的值,则校验和值将变为 7。然后我们可以在 0x04 值之前发送校验和,接收消息的设备可以通过在接收端计算校验和并验证两个值是否匹配来验证消息的完整性。

摘要

在本章中,我们学习了有关蓝牙经典的大量知识,从对无线电工作原理和蓝牙经典连接的网络拓扑的简要介绍开始。我们演示了如何配置蓝牙 HC-05 蓝牙模块作为从机和主机。我们还看到了如何配置蓝牙模块在启动时自动连接到彼此。最后,我们看到了如何使用蓝牙经典从一台设备向另一台设备传输数据。

在第二十章,“蓝牙低功耗”以及本章中,我们探讨了两种不同的蓝牙技术,但问题可能仍然在于何时使用哪一种。当我们有一个用例定义了我们需要一个设备定期向另一个设备请求信息,比如气象站时,我们通常希望使用蓝牙低功耗。当我们想要从一台设备向另一台设备传输数据而不需要等待接收设备请求时,我们通常希望使用蓝牙经典。

在这本书的整个过程中,我们从微控制器到传感器,从电机到无线通信模块,考察了许多不同的项目。我们的想法是让你接触到许多不同的项目,希望这能给你自己的项目带来灵感。关于 Arduino 最好的事情是,你做的项目只受限于你的想象力,所以开始想象你可以做哪些超级酷炫的项目,然后去创造它们。

posted @ 2025-10-26 08:52  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报