在这本书中,你将学习使用 Raspberry Pi 作为我们的硬件和 Python 作为我们的编程语言来掌握机器人基础知识。我们承担的项目将帮助读者理解我们所使用的语言和硬件的基本知识。本书中探索的技术是大多数我们使用的机器人所基于的基准技术。通过阅读本书,读者不仅将理解如何制作本书中提到的项目,而且可以更进一步,将自己的想象力转化为现实。在这本书中,我们将使用非常便宜且开源的硬件和软件,使其易于负担。在这本书中,我们将从 Python 开始,所以即使是 12 岁的孩子也能理解和实现它。除此之外,我们还将探索不同的硬件及其工作原理,这样你不仅能够连接硬件,还能了解其最细微的细节。对于那些愿意使用最新技术和语言探索机器人的人,这可能是一个完美的起点。
本书面向那些愿意学习机器人并使用最新的编程语言熟悉最新技术的人。年龄不是限制,无论你是 12 岁还是 60 岁,你都可以阅读这本书,并将你的想法转化为现实。
第一章,机器人 101,将帮助你理解我们的硬件和 Python 的基础知识。使用简单的 LED,我们将开始编写 Python 的简单程序。
第二章,使用 GPIO 作为输入,将讨论如何连接各种传感器,从通过超声波测距仪连接开关开始,最终使用模数转换器连接到一个光传感器(LDR)。
第三章,制作园艺机器人,将使用各种传感器,例如土壤湿度传感器和温度传感器来感知气候,并使用由继电器控制的电磁阀,我们将制作一个在需要时自动浇灌花园的机器人。
第四章,电机基础知识,将讨论电机的工作原理以及如何通过电机驱动器驱动电机,全 H 桥电机驱动器的工作原理,以及电机驱动器中的速度控制机制是如何工作的。在完成所有这些的同时,我们将控制电机,使其以不同的速度向不同的方向移动。
第五章,制作宠物喂食机器人,将教您如何制作一个机器人,该机器人将使用电机和其他传感器在您编程时为您的宠物喂食。它还会感应宠物是否在场,并且直到宠物吃完食物,蜂鸣器将一直开启以确保宠物理解食物已经被分发。此外,使用力传感器,我们将能够判断吃掉的食物量。在消耗了足够数量的食物后,蜂鸣器将被关闭。
第六章,蓝牙控制的机器人车,将更深入地介绍转向和控制机器人车辆,并将实现滑移转向机制的概念。您还将学习如何使用树莓派上的蓝牙并将其连接到您的手机。最后,使用应用程序,我们将通过手机控制我们的机器人车辆。
第七章,障碍物避障传感器接口,将揭示我们如何使用红外接近传感器来确定距离。此外,我们将制作智能算法来感知各个方向上的距离,然后向距离最大的方向移动。
第八章,制作自己的区域扫描仪,将向您介绍伺服电机的基础知识以及如何控制它们。使用伺服电机,我们将制作一个区域扫描仪,换句话说,一个自制的激光雷达。使用这个自建的传感器,我们将制作一个自动驾驶汽车。
第九章,视觉处理,将教您如何准备树莓派使用摄像头捕获图像以及处理是如何进行的。
第十章,制作守卫机器人,将教您如何制作一个由视觉处理驱动的系统,该系统能够利用上一章的见解来识别面部。然后,它将被训练来识别特定的面部,并在识别到熟悉的面孔时做出不同的反应。最后,我们将制作一个守卫机器人来保护您的房子;当它识别到您的面孔时,它会让您进入。如果它识别到一个未知的面孔,那么它会发出警报。
第十一章,基本开关,将使用简单的逻辑来控制您家的设备。最后,我们将制作一个通过灯光以自然方式唤醒您的警报器。这将具有智能自动延时的功能。
第十二章,使用 Jarvis 识别人类,将教会您如何使用我们将在家里使用红外接近传感器构建的房间占用传感器来控制家里的设备。然后我们将使这个占用传感器变得智能,并准备好计数房间内的人数,并且只有当房间里没有人时才关闭灯光或其他设备。
第十三章,使 Jarvis 物联网化,将向您介绍物联网和 MQTT 服务器的概念,通过这些概念我们可以根据事件来监控我们的家庭。此外,您还将学习如何在世界任何地方坐着就能控制我们家里的设备。
第十四章,赋予 Jarvis 声音,将教会您如何使系统能够合成语音。此外,您还将学习如何使系统能够识别我们的语音,并根据它控制家里的所有设备。
第十五章,手势识别,将使您能够通过电波识别在板上做出的手势,并根据这些手势,智能家居将被控制。
第十六章,机器学习,将使您理解机器学习的概念,特别是 k-最近邻算法。使用这个算法,您将了解如何将数据提供给系统,并基于它做出预测。最后,您将执行一个程序,随着时间的推移,通过用户的输入生成其自己的数据,然后根据这些数据,它将自动开始控制家庭,无需任何人为干预。
第十七章,手势控制机器人车辆,将教会您如何构建一个根据您手的倾斜来控制的机器人。您将理解加速度计和陀螺仪的概念,并将它们实现。
第十八章,制作机器人手臂,将帮助您制作一个机器人手。您将了解如何为保护目的设置伺服电机的物理限制,然后我们将编写一个程序,您将根据不同的框架控制机器人。最后,您将了解如何控制机器人运动的速度。
虽然有先前的经验会加速您的学习曲线,但不需要先前的 Python 知识。读者将需要下载并安装 RASPBIAN STRETCH WITH DESKTOP;这将为我们提供 Raspberry Pi 的 GUI 界面。
您可以通过以下步骤下载代码文件:
选择“支持”选项卡。
点击“代码下载与勘误”。
在搜索框中输入书名,并遵循屏幕上的说明。
文件下载完成后,请确保使用最新版本解压缩或提取文件夹:
Windows 上的 WinRAR/7-Zip
Mac 上的 Zipeg/iZip/UnRarX
Linux 上的 7-Zip/PeaZip
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“我们必须将2和3相乘,我们只需简单地写Multiply(2,3)。”
代码块按以下方式设置:
任何命令行输入或输出都按以下方式编写:
粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“然后选择 I2C 以启用它。然后选择是。”
警告或重要提示显示如下。
小贴士和技巧显示如下。
我们始终欢迎读者的反馈。
请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 的我们能够了解您对我们产品的看法,而我们的作者可以查看他们对书籍的反馈。谢谢!
你好,世界!一提到机器人这个词,科幻小说的思绪就会围绕着我们。我们可能会回想起卡通系列《杰森一家》或者想到电影《终结者》。但事实上,作为物种,机器人已经不再属于科幻小说了。它们是如此真实。看看你周围,指出任何物体;它可能没有机器人的参与就不会被制造出来。现代时代是由机器人塑造的。
但然后,你也可以退一步思考,等等,他所说的东西不是机器而不是机器人吗?嗯,是的,你非常正确,但同时也非常错误。是卡通和科幻小说赋予了类似人类的机器人一个被称为机器人的形象。但机器人远不止如此。
不幸的是,我们没有一个具体、普遍认同的机器人定义,但正如我经常说的,任何能够执行物理和智力任务的机器都可以被称为机器人。
现在,你可能会说,根据我的定义,即使是自动洗衣机也可以被称为机器人。从技术上讲,是的,那么我们为什么不称它为机器人呢?想想它为你做了什么,以及这些年来采用了什么样的自动化。在你输入了布料类型后,它会自动洗涤、漂洗和烘干,就像你在 19 世纪会做的那样。我试图说明的是,我们可以想到的机器人种类繁多,这可能会彻底改变我们的生活方式。我们需要以更广阔的视角来思考——不仅仅将机器人限制在类人形机器人的形式上。
我们正处于机器人自动化领域的黄金时代,新产品的开发变得前所未有的简单。十年前可能需要一支工程师团队完成的工作,现在一个人坐在卧室里几分钟就能完成,这要归功于开源世界。与此同时,你能够获得硬件算力,你实际上可以用几百美元在家构建一台超级计算机。我们周围充满了问题,有些简单,有些复杂,都在等待被解决。整个过程中唯一缺失的环节就是你:一个有能力利用这些技术来解决世界问题的创新思维。
为了使你的思维能够做到这一点,我们将从理解机器人的根源和基础知识开始。这本书的目标不仅是制作书中提到的项目,还要让你了解如何利用资源来构建你的梦想项目。
第一是科学家
第二是研究员
第三是工程师
第四是技术人员
最后是机械师
这意味着你越早进入任何领域,你就能在等级上爬得越高。来得越晚,爬到顶部的难度就越大。
足够说了——现在让我们直接进入正题!在本章中,我们将涵盖以下主题:
硬件装备
设置树莓派
编程
玩转电压
谈到机器人,它们由一些基本的有形组件组成,以下是一些:
计算单元
传感器
执行器
底盘
电源
首先,我们将讨论微控制器,在本书的其余部分,我们将根据需要详细讨论其他有形组件。
每次你去买笔记本电脑或电脑时,你一定听说过微处理器这个词。这是必须做出所有决策的主要单元。我称它为“国王”,但国王没有帝国又算什么?为了让国王工作,他需要一些下属来为他做事,就像微处理器需要一些下属,如 RAM、存储、I/O 设备等。现在的问题是,当我们把这些东西放在一起时,整体单元会变得昂贵且庞大。但正如我们所知,重量和尺寸在机器人领域是非常重要的因素,所以我们不能承受一个庞大的系统来运行机器人。
因此,我们制造了一种叫做 SoC 的东西。现在,这是一个一个人的表演,因为这个小小的芯片本身就有所有必要的系统来在它的小芯片组内工作。所以,现在你不需要添加 RAM、存储或其他任何东西来让它工作。这些小型的微控制器可以非常强大,但缺点是,一旦制造商制造了 SoC,之后就不能对其进行任何更改。存储器的大小、RAM 或 I/O 无法更改。但我们在编程机器人时通常可以接受这些限制,因为在你运行一些严肃的人工智能或机器学习代码之前,你可能不会使用微控制器的全部功能。
树莓派就是这样一件出色的硬件。是的,它听起来非常美味,但它的用途远不止于此。这是一个超级小巧但功能极其强大的微控制器。它通常被称为原型板,因为它被世界各地的机器人学家用来实现他们的想法,并在短时间内将它们变为现实。它在全球范围内都有售,而且非常便宜。你可以在仅 10 美元的设备上播放高清电影、上网,还能做更多的事情。我想不出有什么比这更荒谬的事情了。它使用起来相当简单,你可以用 Python 来编程它。
所以,基本上,它满足了我们的所有要求。这将是我们在整本书中使用的首要武器。
因此,让我来向您介绍树莓派!这就是它的样子:
市场上有多种树莓派型号可供选择。但我们将使用树莓派 Zero W;这大概需要花费您 10 美元,而且比巨无霸汉堡更容易购买。请确保您购买的是带有 W 的树莓派 Zero,W 代表无线功能,如 Wi-Fi 和蓝牙。还有一些其他事情您需要订购或安排以使其工作。以下是需要准备的项目列表:
Micro USB 转标准 USB 适配器
键盘
鼠标
Micro SD 存储卡,16 或 32 GB
Micro SD 卡读卡器
微型 USB 电源适配器(2 安培或以上)
Micro HDMI 转 HDMI 端口
面包板
一堆跳线(公对公、公对母和母对母)
3V LED 灯
从图片中您会立刻看出,板上有微型 HDMI 端口,您可以通过它连接高清显示器或电视屏幕。其次是微型 SD 卡槽。这将作为该电脑的主要存储设备。除此之外,您还会找到两个 USB 端口和一个摄像头总线。您可能会认为这就结束了,但最好的还在后面。树莓派有一种叫做GPIO的东西,代表通用输入/输出。这些隐藏在树莓派一角的小型 40 针通孔端口上;这就是它特别的地方。
现在,按照惯例,您会将与电脑兼容的设备连接到电脑上。因此,连接鼠标、键盘或游戏手柄就像插入 USB 端口一样简单,但您需要将电脑连接到灯泡或空调怎么办?没错,您不能。这就是 GPIO 发挥作用的时候。这些引脚在机器人技术中非常有用,因为它们可以用来连接各种组件,如传感器/电机。这些引脚的美丽之处在于,根据我们为其编程的方式,它们可以用作输入或输出。因此,正如我们稍后将会看到的,每个引脚都可以根据我们的需求在程序中定义为输入或输出。
现在,在这 40 个引脚中,有 26 个是 GPIO。其余的引脚是通用的电源或地端口。还有两个称为ID EEPROM的端口,在这个阶段我们不需要它们:
如您所见,树莓派可以为我们提供两种类型的电源供应:3.3V 和 5V。这些基本上满足了我们的大部分需求。
您首先需要确保树莓派的操作系统已准备就绪。我假设您正在使用 Windows PC,但如果您在其他操作系统上操作,那么差别不会很大。
要安装操作系统,启动您的 PC 并按照以下步骤操作:
现在点击 RASPBIAN,你会看到以下两个选项:
RASPBIAN STRETCH WITH DESKTOP
RASPBIAN STRETCH LITE
我们将下载带有桌面的 RASPBIAN STRETCH;这将为我们提供树莓派的图形用户界面。
下载后,将包解压到一个文件夹中。
完成后,你必须将镜像复制到 SD 卡上。最简单的方法是使用 WinDisk Imager。你可以在网上无任何问题地下载它。然后只需选择镜像和 SD 卡上的位置,然后开始复制镜像。
这可能需要几分钟。完成后,你的 SD 卡就准备好了。将其插入树莓派,我们就可以准备供电了。但在供电之前,请使用 Micro HDMI 到 HDMI 线连接显示器,使用 Micro USB 线将键盘和鼠标连接到树莓派,并使用 Micro USB 到标准 USB 适配器供电。现在,使用树莓派的另一个 USB 端口,通过 micro USB 供电适配器供电。
一旦启动,你将看到启动屏幕,几秒钟后你将能够看到桌面。所以,最终,我们的树莓派已经启动并运行。
好吧,探索一些选项,上网冲浪,在 YouTube 上看看一些猫咪视频,让自己熟悉这个强大的设备。
到现在为止,你一定已经欣赏到了树莓派的强大功能。它可能比你的普通电脑稍慢一些。但是,看在它只需 10 美元的份上,不是吗!
在本章中,我们将让你熟悉 Python 以及如何使用此设备上的 GPIO。为此,请点击左上角的树莓派图标。你会看到 Python 控制台 3.0。可能还有更旧的 Python 版本。在这本书中,我们将使用较新版本。
窗口打开后,你会看到编码的游乐场。所以现在我们准备好编写 Python 机器人的第一段代码了。现在让我们看看它是如何完成的。
我们将要写的第一件事是:
几乎每次我们开始编写程序时,我们都会从编写前面的行开始。现在,在我们理解它做什么之前,我们需要了解库。在编写代码时,我们经常需要在多个地方重复编写相同的代码。这花费了很多时间,当然也不酷!
因此,为了解决这个问题,我们创建了函数。一个函数可能是一个我们可能会反复使用的迷你程序。在这个迷你程序本身中,我们也提到了它的调用方式。
假设有一个代码,我们需要反复乘以两个数字。所以我们做的是,我们只编写一次代码,并将其制作成一个函数。我们还把这个函数命名为Multiply。
现在,每当我们需要乘以两个数字时,我们不必再次编写它的代码;相反,我们只需调用函数来为我们执行,而不是编写乘法的代码。问题是,我们如何知道哪个数字需要相乘?
对于这个问题也有解决方案。正如你可能会看到的,每次调用函数时,我们都会在它后面放一对开括号和闭括号,例如multiply()。
如果括号是空的,这意味着没有提供用户输入。例如,如果我们需要乘以2和3,我们只需编写Multiply(2,3)。
我们将输入设置为2和3。输入在括号中的位置也很重要,因为括号中的位置将定义它在程序中的位置。
现在,假设你创建了以下函数:
假设你把它们堆叠在一起。那么这些组合在一起的函数堆栈将被称为库。这些库可以有数百个函数。有些函数已经包含在 Python 语言中,这样就可以简化程序员的任务。其他可以定义为开源或根据您的方便开发。
下一行代码如下:
在我们理解它做什么之前,让我们先了解一下 GPIOs。这些引脚根据它们在树莓派中的物理位置进行硬编码。然而,我们可以为了我们的理解和方便在软件中更改引脚的编号。但在本代码中,我们不会玩弄这个,并将它设置为 Broadcom 默认设置,Broadcom 是树莓派微控制器的制造商。
现在,我们可以使用基本的引脚配置。此外,GPIO 引脚的一个特点是它们既可以作为输入使用,也可以作为输出使用。但唯一条件是我们必须在程序中指定它是要作为输入还是输出使用。它不能同时执行这两个功能。下面是如何实现的:
下一条代码将是以下内容:
再次,我们正在使用库GPIO中的一个名为output的函数。这个函数的作用是设置板上的特定引脚到我们想要的状态。所以,在这里我们提到引脚号23需要设置为高电平。为了清晰起见,高电平表示开启,低电平表示关闭。
下一条代码将是以下内容:
在这一行,我们使用了来自time库的函数。sleep函数基本上冻结了所有 GPIO 引脚的状态。例如,如果引脚23处于高电平,那么它将保持高电平状态,直到sleep函数执行。在sleep函数中,我们定义的值是3秒。
因此,在 3 秒钟内,树莓派的引脚状态将保持在这条代码之前的状态。
最后,代码的最后一行将是:
这将是每个程序之后的常见场景。GPIO 库的这个函数将重置程序中使用的每个引脚的状态——所有引脚的状态都将变为低电平。记住,它只会影响程序中使用的引脚,而不会影响其他引脚。例如,我们在程序中使用了引脚23,所以它只会影响引脚23,而不会影响树莓派上的其他任何引脚。
最后,你的程序看起来可能像这样:
现在,基于同样的概念,它将以以下方式执行程序:
现在,为了检查程序是否正常工作,让我们连接一些硬件来检查我们所写的是否真的发生了。
我假设读者已经了解面包板的使用方法。如果你不熟悉它,只需继续前进并谷歌一下。理解它只需要 5 分钟,非常简单,而且会很有用。
现在继续连接面包板上的 LED,然后将 LED 的接地连接到树莓派的接地引脚,并将正极/VCC 连接到引脚号23(参考引脚图)。
你还可以参考以下图表:
完成后,继续运行代码并看看会发生什么!
LED 将亮 3 秒,然后再次关闭,正如我们预期的那样。现在让我们只是稍微修改一下代码。这次,我们将添加一些用粗体标记的行:
在理解代码内部的内容之前,你会注意到并不是每一行都对齐,它们是有意为之的。这意味着什么?
与其他代码行缩进在一起的行称为代码块。例如,如果你有一个如下所示的语句
现在让我们看看这一行代码是如何运行的。
上述代码可以参考一个位于for循环内部的代码块。可以通过缩进代码来创建代码块。
现在,让我们看看它会做什么。While True是一个循环,它将反复运行它内部的for循环,直到条件不为假。我们在这里使用的条件是:
最大范围是3,每次执行该语句时,都会将i的值增加+1。所以它基本上充当一个计数器。让我们看看程序实际上会做什么。
它将检查i的值,并将其增加1。随着代码的执行,它将 LED 高亮 0.5 秒,然后关闭 0.5 秒。然后它将等待 1 秒。这将重复,直到 while 循环为假,即i的值大于3,此时它会退出程序并终止。运行程序并看看是否真的发生了。
到现在为止,你应该已经理解了在树莓派上编程是多么简单。为了更进一步,我们将创建另一个程序并对硬件进行一些更改。
我们将从 7 号引脚连接到 12 号引脚的五个更多 LED。我们将使它们按照一定模式开关。
连接完成后,我们将按照以下方式编写代码:
现在代码相当简单。让我们看看它的含义:
在我告诉你更多关于代码的信息之前,让我们先运行它。
当你运行它时,你会理解按照语句,它正在逐个处理引脚,并在每 1 秒后将它们切换到高电平。
到目前为止一切顺利!但你注意到一件事吗?我们一直在使用树莓派作为开关——简单地打开和关闭各种组件。但如果我们需要调整我们刚刚编程的 LED 的亮度呢?这是可能的吗?答案是:不可能。但我们仍然可以设法做到!
让我们看看这是如何实现的。计算机以二进制方式工作,这意味着它们可以表示0或1。这是因为任何系统中的主要计算单元都是基于晶体管,晶体管可以是开启的或关闭的,分别代表0或1。所以,如果我们从技术上看待这个问题,计算机只能因为二进制架构而切换。然而,有一个技巧。这个技巧被称为脉冲宽度调制(PWM)。
现在,在我详细解释任何内容之前,我们先在18号引脚上插入一个 LED,然后将此代码复制到树莓派上并运行:
你注意到了什么?LED 现在每秒闪烁一次。现在让我们稍作调整,将PWM(18,1)改为PWM(18,5)。让我们运行并看看会发生什么。
你会注意到现在它每秒闪烁五次。所以数字5基本上代表了频率,因为 LED 现在每秒闪烁五次。现在,再次重写代码并将5增加到50。一旦增加到50,LED 每秒开关 50 次,即 50 赫兹。所以,它看起来像是始终开启的。
现在是有趣的部分。回到你的代码,将duty_cycle = 50改为duty_cycle = 10。
你注意到了什么?你必须已经看到 LED 现在发光的强度大大降低。实际上,它将是原来的二分之一。
让我们看看实际上发生了什么:
当我们将频率增加到超过 50 赫兹时,人眼很难分辨出它实际上是开启还是关闭。理论上,50%的时间引脚将保持高电平,其余时间将保持低电平。因此,如果我们取平均值,我们就可以轻松地说整体电压将是原始电压的一半。使用这种方法,我们可以根据我们的需求调节任何引脚的电压输出。
现在你应该已经理解了如何将 GPIO 用作输出,以及通过应用条件如何改变它们的行为。
在下一章中,我们将了解这些引脚也可以用作输入。所以请回来,我们那里见!
在上一章中,我们了解了 GPIO 是如何用于输出的。但是,正如其名称所暗示的,GPIO 可以用于输入和输出两种目的。在本章中,我们将看到如何使用这些引脚将数据输入到树莓派。
本章我们将涵盖的主题是:
深入了解 GPIO
PIR 传感器的接口
超声波接近传感器的接口
通过 I2C 进行接口
我相信你记得上一章中的这一行代码:
如前所述,这基本上告诉我们 GPIO 引脚在某个程序中的行为。到现在,你可能已经猜到了,通过更改这一行代码,我们可以改变引脚的行为,将其从输出转换为输入。这就是你应该如何做:
一旦你在程序中写下这一行代码,微控制器就会知道在程序运行期间,引脚号18将仅用于输入目的。
为了理解这实际上是如何工作的,让我们回到我们的硬件,看看它是如何实现的。首先,你需要将一个 LED 连接到任何一个引脚上;在这个程序中,我们将使用引脚号23。其次,你需要将一个开关连接到引脚号24。你可以参考下面的图示来制作连接:
一旦连接好,就可以继续编写这个程序:
程序上传后,一旦按下按钮,LED 就会自动点亮。
让我们了解究竟发生了什么。while True:基本上是一个无限循环;一旦应用了这个循环,循环内部的代码会一直重复执行,直到某个中断使其停止并退出。现在,理想情况下,我们通过按下Ctrl + C来退出程序。
在上述行中,程序理解了它需要查找的位置;在这个程序中。在这一行中,我们告诉程序我们在寻找 GPIO 24,它是一个输入:
如果按钮处于高电平状态,换句话说,当按钮被按下且电流达到引脚号24时,GPIO 引脚号23将被设置为高电平:
如果引脚号24不是真的,它将遵循这一行代码,并将引脚号23保持低电平,换句话说,关闭。
因此,这就是你使用 GPIO 进行输入目的的第一个程序。
到目前为止,一切顺利!在本单元中,我们将继续接口我们的第一个传感器,这是一个被动红外传感器,通常称为 PIR 传感器。这个传感器是一个非常特殊的传感器,在自动化项目中非常常用。它低能耗的特性使其成为物联网项目的绝佳候选者。那么,让我们看看它是如何工作的。
你一定注意到了,当我们把金属加热到高温时,它会逐渐变成暗红色,当我们进一步加热时,它会变得更亮,并逐渐从红色变为黄色,如以下所示,该图显示了红热的钢片。现在,随着温度的升高,发出的辐射波长减小;这就是为什么随着温度的升高,颜色从红色变为黄色,因为黄色的波长比红色短。
但有趣的部分是,即使物体没有足够加热,它们也会发出辐射;事实上,任何高于绝对零度的物体都会以某种形式发出辐射。有些我们可以用肉眼看到,而有些则不能。因此,在室温下,物体发出红外辐射,其波长比可见光长。因此,我们用眼睛看不到它。尽管如此,它仍然存在。
这个 PIR 传感器所做的是,它感知周围物体的红外光,每当物体移动时,它都能感知其模式的整体变化,并根据这个变化检测其附近是否发生了任何运动。
我们假设只要房间里有人,就会有一些固有的运动发生,因此这种传感器非常常用作占用传感器。现在,让我们连接这个传感器,看看我们如何使用它:
一旦按照前面的图连接好,就可以上传代码了:
现在,让我们看看发生了什么。逻辑非常简单。一旦 PIR 传感器检测到运动,它就会将其输出引脚转为高电平。我们只需要监控那个引脚,基本上就是这样。
逻辑与按钮开关完全相同,它将以类似的方式工作。所以不需要太多解释。
首先,让我们从基础知识开始。接近传感器是一种能够感知物体与其之间距离的传感器。为了完成这项任务,有一个充满传感器的宇宙可供选择,还有许多技术使我们能够做到这一点。正如其名称所示,超声波接近传感器是基于超声波原理工作的。其工作原理非常容易理解。超声波传感器发送一束超声波;这些波对人类耳朵来说是听不到的,但无论如何,它仍然是一种声波,并且它也像声波一样表现。
现在,正如我们所知,声音会在不同的表面上反弹并形成回波。当你在一个空房间里说话时,你必须已经体验过这种回声。你可以听到自己的声音,但会有轻微的延迟。这种延迟是由声音的属性造成的。声音是一种波,因此它有速度。声波有一个固定的传播速度。因此,为了覆盖特定的距离,它们需要一些时间。通过计算这个时间,我们可以推导出声波在从表面反弹之前走了多远。
类似地,在这个传感器中,我们向特定方向发射超声波,然后感应回波。自然地,接收回波会有延迟;延迟与物体从传感器到距离成正比,基于这个延迟,我们可以轻松计算出距离。
现在,要使用接近传感器,我们需要了解传感器的物理结构,以便正确布线。传感器有四个引脚,它们是:
VCC(正)
触发
回波
GND(地)
显然,我没有必要解释 VCC 和地的作用。那么,让我们直接进入触发部分。每当引脚处于高电平 10 微秒时,超声波传感器将向目标发送 8 个周期的 40 kHz 声波。一旦触发周期完成,ECHO被设置为高电平。一旦接收到回波信号,ECHO引脚被设置回低电平。以下是实际发生过程的示意图:
目前我们只需要知道这些。随后,随着我们的学习深入,我们将了解更多内容。现在,为了使它生效,请按照以下图示进行连接:
一旦建立连接,以下是需要运行的代码:
现在,一旦运行此程序,屏幕上的输出将每 0.2 秒显示一次物体的距离。现在,你可能想知道这些读数是如何通信的:
我们将引脚23分配给传感器,以便在需要时向TRIGGER引脚发送脉冲:
我们将引脚24分配来接收逻辑,以确认回波信号的接收:
我们将使用前面的变量,并且每次循环开始时,我们都会给它们赋一个值,这个值是0;这是为了清除程序执行过程中存储的先前读数:
我们将触发引脚23保持高电平 0.000010 秒,以便超声波传感器可以发送一个短暂的超声波脉冲:
这个 while 语句将记录pulse_start变量的时间,直到时间引脚24变低。最终的时间读数将存储在pulse_start变量中,就像记录脉冲发送的时间一样:
在这个循环中的while语句将开始记录当引脚24上的输入为高电平时的时间,并且它将一直记录时间,直到引脚24保持高电平。最终的时间读数将存储在pulse_stop变量中,就像记录脉冲接收时的时间一样:
让我简要介绍一下。用基础物理学,我们会记住这个简单的方程:Speed = Distance / Time。
现在你可能也记得声音的速度是每秒 343 米。现在 1 米有 100 厘米,因此为了将这个速度转换为每秒厘米,我们必须将速度乘以 100,因此速度将是每秒 34,300 厘米。
现在我们知道了方程的一个元素,即速度。所以让我们把速度的值放入方程中。现在方程看起来可能像这样:34,300 = Distance / Time。
现在我们知道了一个事实,即声音传播的距离是实际距离的两倍。为什么?因为声音首先从传感器传到物体上。然后它从那个表面反弹回来并到达传感器。所以本质上它覆盖了双倍的距离。因此,为了适应这个方程,我们必须进行一个小小的改动:34,300 / 2 = Distance / Time
现在我们从这个方程中想要得到的是距离,所以让我们把其他所有部分移到另一边。现在公式看起来可能像这样:17,150 * Time = Distance
所以这里就是我们得到距离的公式。
由于超声波传播的距离是实际距离的两倍(一次是向物体移动,第二次是反弹回传感器),我们将其除以二以得到实际距离:
最后,我们将通过以下语句打印出测量的距离。任何在引号'...'中的内容都将按照原样写入。然而,distance没有引号,并且distance是一个变量。因此,存储在distance中的变量将在屏幕上的最终输出中写入:
代码将在这一行暂停 0.2 秒。如果我们没有这个暂停,那么值将以难以置信的速度出现,这将很难阅读或理解。如果你在尝试,我建议移除这个语句并运行代码看看会发生什么。
到目前为止,一切顺利。电子电路可以非常有趣,虽然它们看起来非常复杂,但往往我们发现它们的工作可以非常简单。在上一个部分中,我们一次接口一个传感器。我们可以继续接口多个传感器,但我们受限于现有的 GPIO 数量。我们也看到,一些传感器,如超声波传感器,可能需要多个 GPIO 引脚来工作。这进一步减少了我们可以与微控制器接口的传感器数量。一旦我们转向更复杂的电路,我们也会意识到布线可能真的会很混乱,如果出现问题,找到问题所在将是一项繁琐的任务。
现在,我们在设计机器人系统时面临的一个更大的问题是定时问题——系统中的所有工作都必须同步。大多数系统目前都是顺序性的,就像一个单元的输出成为另一个单元的输入:
现在,为了完成任务,处理单元 1必须在需要时向处理单元 2提供输入,对处理单元 3也是如此。如果数据没有完美地定时,那么处理单元 2将一直等待来自处理单元 1的输入,或者更糟糕的是,处理单元 1将在不需要时将数据发送给处理单元 2。在这种情况下,数据将丢失,过程将出现一些错误。
因此,为了解决这个问题,早期的计算机科学家发明了一种脉冲系统。时钟脉冲是一个非常简单的占空比为 50%的方波(回忆脉冲宽度调制(PWM))。电路被设计成在时钟脉冲的上升沿或下降沿执行一个操作。由于这种同步,电路的每个部分都知道何时工作。以下是时钟脉冲的外观:
现在,回到要点,我们有两个问题:
机器人可以连接的设备/传感器的数量有一个物理限制
如何同步传感器和互联电路以协同工作
为了解决这些问题,我们使用一个非常常用的协议,称为I2C,代表内部集成电路。当我们需要在相同的 GPIO 组上连接多个设备时,这个协议非常有用,例如当我们只有一组 GPIO 引脚,而多个传感器可以通过这些引脚连接时。这是由于每个硬件都分配了独特的地址而成为可能的。这个地址用于识别传感器,然后相应地与之通信。现在,为了实现 I2C 协议,我们需要两条线;这些线如下所示:
数据
时钟
如你所猜,时钟线用于向连接到它的设备发送时钟脉冲,而数据则是数据在其中的来回流动的总线。
现在,整个 I2C 架构都是基于主从配置工作的,其中主设备始终为从设备生成时钟信号,而从设备必须不断寻找主设备发送的时钟脉冲和数据包。让我们看看它是如何实现的。
如前所述,有两根线:数据线,被称为串行数据(SDA),和时钟线,被称为串行时钟(SCL)。从现在起,我们将使用 SCL 和 SDA 这两个术语:
让我们看看图中显示的主要指针:
启动条件:为了开始通信,会创建一个启动条件,表示即将发生通信。这个条件是通过主设备在 SCL 之前保持 SDA 线低电平来表示的。这表示所有从设备都准备好通信。
地址帧:一旦通信开始,主设备会发送需要通信的设备的地址。这是一个 7 位地址。在每一个时钟脉冲中,发送一个比特,因此需要七个时钟脉冲来发送 7 位地址。之后,7 位地址后面是一个读写位。这表示主设备在这个操作中是想写入还是想读取一些数据。因此,整个地址帧是 8 位,需要八个时钟脉冲来发送。在这八个脉冲之后,在第九个时钟脉冲期间,主设备等待设备的确认。这个确认是由被寻址的从设备通过拉低 SDA 线来发送的。通过这种策略,主设备知道它发送的地址已经被接收,从设备现在已准备好通信。如果确认没有发送回来,那么主设备需要决定接下来要做什么。
数据帧:一旦发送了确认,根据是读操作还是写操作,数据要么由主设备写入从设备,要么在读取操作中,数据由从设备发送到主设备。这个数据帧的长度可以是任意的。
停止帧:一旦数据传输完成,主设备会设置停止条件来指示通信必须停止。这个条件是在 SCL 线从低电平变为高电平之后,SDA 线也从低电平变为高电平时设置的。
这基本上就是 I2C 通信的工作原理。对于每个设备,我们有一个 7 位地址,因此我们可以在单个总线上连接多达 128 个设备。这有很多设备。物理极限用完的可能性几乎可以忽略不计。现在让我们看看我们如何通过此协议连接传感器。通常,不需要为 I2C 进行核心编程,因为这既长又繁琐。这就是开源的魔力所在。全球有大量的开发者正在研究这些传感器,其中大多数人都足够慷慨,制作库并与他人分享,以便于编程。这些库在网上可用,并且大多数库都处理了通信的复杂过程。
现在是我们连接第一个 I2C 设备的时候了,这是一个模拟到数字转换器。你可能想知道为什么我们首先使用这个转换器。回想一下我们刚开始理解 GPIO 引脚的时候。这些神奇引脚既可以作为输入也可以作为输出;你可能也记得这些引脚可以是开或关——这些都是数字引脚,不仅对于输出,对于输入也是如此。但是,有大量的传感器是通过模拟通信工作的。由于树莓派的数字架构,直接连接这些传感器是困难的。因此,我们使用一个模拟到数字转换器(ADC),这个转换器将传感器的模拟值转换为树莓派可理解的数字位。
我们将连接一个光敏电阻,电阻的阻值将根据照射到它上的光量而变化。因此,电压将取决于光敏电阻上的光量。
现在我们来看看它是如何实际操作的。拿起你的 Pi,让我们开始吧。首先,我们需要在树莓派上启用 I2C;按照以下步骤操作:
打开终端(Ctrl + Shift + T)
输入sudo raspi-config
选择接口选项:
现在安装adafruit库以连接 ADC1115:
此命令将库和依赖项下载到树莓派上
此命令将库和依赖项安装到树莓派上。
现在软件已经设置好了,让我们准备好硬件。按照以下图示将树莓派连接到 ADS1115:
一旦准备好,请上传此代码到 Pi:
注意,有时此代码可能无法正常工作,在这种情况下,请尝试调整阈值值:
你可能已经注意到,每当光敏电阻(LDR)面向光源时,LED 也会打开,而每当它远离光源时,LED 就会关闭。
所以现在你已经连接了一个 I2C 设备。让我们了解这段代码是如何实际工作的:
前面的代码行在代码中导入了Adafruit_ADS1x15库,这样我们就可以在程序中使用它的所有功能。
前面的代码行创建了库的实例Adafruit_ADS1x115。.ADS1115()是创建实例为adc的函数。你理解了吗?让我用英语解释一下。
现在,我们不再需要每次都写Adafruit_ADS1x15,我们可以简单地写adc来调用库函数。此外,你可以用任何词来代替adc;它可以是你猫的名字,也可以是你邻居的名字,它仍然会工作:
这是感应将进行的值。1表示感应将在全范围内进行。对于我们的 ADC 来说,这是从 0V 到 +/-4.096V 的电压范围。现在改变增益将导致感应范围的变化。也就是说,如果我们把增益的值改为2,那么感应发生的范围将是原始范围的一半,即 0 到 +/- 2.048 伏特。
现在你可能想知道电压范围以及为什么我们要改变增益?
原因很简单。有不同类型的模拟传感器,它们在广泛的电压范围内提供输出。一些传感器可以在 0.5 伏特到 4 伏特的范围内提供输出,而其他传感器可以从 0.1 伏特到 0.98 伏特提供输出。现在如果我们把增益设置为1,那么所有这些传感器都可以轻松连接。因为它们都位于 0 到 4.098 伏特的感应范围内。然而,由于它是一个 16 位 ADC,因此 ADC 可以提供的离散值的总数将在 2¹⁶或 65,536 次读取之间。因此,在增益为1时,ADC 能够检测到的最小电压变化将是:*4.096 */ 65536 = 0.000062。
但如果你将增益增加到4,那么感应范围将减少到仅仅0到 +/- 1.0245。因此,它将能够在 0.1 伏特到 0.98 伏特的输出范围内工作。但现在让我们看看它能够检测到的最小电压变化:1.0245 / 65536 = 0.00001563。
现在你可以看到,可以检测到的最小电压非常低,这对于与传感器的兼容性来说是个好事。
现在,选择你想要的增益值取决于你。LDR 在 5V 下工作,因此我们最好使用整个增益读取1:
当你仔细观察 ADC 硬件时,你会注意到有各种引脚,包括A0、A1、A2和A4。这是一个四通道 ADC——它可以将四个模拟输入转换为数字数据。由于我们只使用一个单一的数据流,我们将让 Pi 知道它连接在哪个引脚上。通过以下行,我们告诉 Pi 开始转换数据的过程:
在下一行,我们正在指示 ADC 停止转换,这就是代码的结束之处。
本章主要讲述了如何将传感器与 GPIO 接口连接,以便通过传感器获取数据。向前推进,在下一章中,借助这些已学到的主题,我们将学习如何制作一个宠物喂食机器人。
好朋友们,你们已经理解了一些输入和输出的基础知识;现在是我们动手做一些可以移交我们日常责任的事情的时候了。这个机器人可能看起来并不像机器人,但请相信我,它会让你生活得更轻松。最重要的是,你花园里的植物会因为它的存在而感激你。
我们将涵盖以下主题:
与电磁铁一起工作
制作机器人
使其更加智能化
使其真正智能化
我们将要制作的是一个自动化系统,它将在植物需要时自动浇水。所以从技术上讲,一旦设置好,你就不必再担心给你的绿色生物浇水了。无论你是在家、在办公室还是度假,它都会继续完成它的任务。
现在,你可能想知道它是如何浇水的,让我告诉你,在这个世界上,每个问题都有一个解决方案。在我们的案例中,这个解决方案就是电磁阀。它本质上做的是切换液体的流动。市场上有很多种电磁阀;以下是一些识别特征:
尺寸:它们有各种尺寸,如半英寸、3/4 英寸、1 英寸等。这基本上将决定电磁阀的流量。
介质:无论是用于流体、气体、蒸汽等。
正常状态:
常开状态:此阀门在关闭状态下允许液体流动——当没有电源供应到阀门时
常闭状态:此阀门在关闭状态下阻止液体流动——当没有电源供应到阀门时
方式数量:一个简单的阀门将有一个进口和一个出口。所以当它打开时,它将允许液体从进口流向出口。然而,可能还有其他类型的阀门,如三通阀,可能有两个出口和一个进口。它将调节液体的流动方向。
关于阀门,还有一些更具体的信息,但就目前而言,我们只需要知道这些。关于电磁阀需要注意的一点是,这些阀门可以是开启的或关闭的。通过这些阀门实现任何中间状态或控制流量是不可能的。为此,我们可以使用伺服阀或电机阀。但到目前为止,我们不需要它。
在本章中我们将使用一个半英寸的水/流体阀门,它处于常闭状态。当你仔细观察这个阀门时,你会看到它的工作电压为 12 伏,电流消耗接近 1 安培。这对于 Raspberry Pi 来说电流很大。Raspberry Pi 每个引脚可以提供的电流上限约为 50 毫安。所以如果我们把这个阀门连接到 Raspberry Pi 上,那么它肯定不会工作。
我们现在该做什么?这个问题的答案是继电器。继电器的基本作用是重新布线电路。基本上,它是一个电子控制的开关。继电器的基本作用是开关那些消耗电流/电压高于控制单元所能提供的设备。这是一个相当简单的设备,如你在图中所见。有两个电路。一个是蓝色的,代表低压和低电流电路。这个电路正在为线圈供电。另一个电路是红色和黑色的。这个电路是高压和高电流电路。
在初始阶段,如你所见,高压高电流电路是不完整的,烤箱将不会工作:
现在,在这第二个图中,你可以看到蓝色电路连接到 5 伏电源,线圈被激活。每当线圈被激活时,它就会形成一个电磁铁,吸引高压电路的金属叶片,使电路完整,从而为烤箱供电:
这就是电磁阀的工作原理。线圈的消耗几乎不到几毫安,因此通过微控制器很容易激活线圈。这反过来又会在最终电路中形成接触。
市场上有多种类型的继电器可供选择;以下是一些识别特征:
最大输出电压:它能处理的最大电压
最大输出电流:它能承受的最大电流,适用于连接到其上的任何输出设备
信号电压:它所需的电压以切换组件的开或关
正常状态:
正常关闭:在没有接收到信号之前,它不会允许任何电流流动
正常开启:它将允许电流流动,直到接收到信号为止
现在,回到我们的园艺机器人,附在其上的电磁阀将在 1 安培和 12 伏的电压下工作,因此任何能够提供等于或超过 1 安培和 12 伏的继电器都可以使用。
通常,市场上可用的继电器是 120 伏和 12 安培直流电。需要记住的一个重要事项是,对于交流和直流电压和电流将有两个不同的额定值。由于我们的电磁阀将在 12 伏下工作,我们只考虑直流上限。
现在,让我们开始制作机器人。首先,你需要从水龙头连接到电磁阀,再从电磁阀连接到喷头。你还需要按照以下方式建立连接:
现在,让我们开始编程。在这个机器人中,我们将连接一个土壤湿度传感器。这个传感器的任务是确定土壤中的水分量。通过确定这一点,我们可以了解花园是否需要浇水。这个土壤湿度传感器是一个模拟传感器,因此我们将使用 ADC 将模拟读数转换为 Pi 可理解的数字值。那么,让我们开始吧:
在你运行此代码之前,让我们先了解一下它实际上在做什么:
moisture_percentage = 20 是作为阈值的百分比;如果土壤中的水分低于 20%,那么你的花园就需要浇水。机器人会持续寻找这个条件;一旦这个条件满足,就可以采取适当的行动。这个百分比也可以根据你花园的需要改为 30、40 或其他任何值:
ADC 是一个 16 位设备——有 16 个二进制位可以表示一个值。因此,值可以在 0 和 2¹⁵ 之间,换句话说,在 0 和 32768 之间。现在,这是一个简单的数学问题,对于每个水分百分比,ADC 会给出以下读数:32768/100,或者说 327.68。因此,为了找出土壤中的水分百分比,我们需要将 ADC 给出的实际值除以 327.68。
其余的代码相当简单,一旦你通过了它,理解起来就不会太难。
恭喜你制作了你的第一个机器人!但你注意到一个问题吗?我们制作的机器人持续寻找水分值,一旦它注意到水分值低,就突然浇水,确保土壤的湿度总是超过 20%。然而,这并不是必要的。一般来说,我们每天给花园浇水一两次。如果我们浇得太多,可能对植物不利。
让我们继续上传以下代码,然后看看究竟发生了什么:
这段代码可能看起来有点陌生,但请相信我,它尽可能简单。让我们一步一步地看看发生了什么:
有几次我们不得不一遍又一遍地做同样的事情。这些代码集可能只是一些重复的行,也可能是多页的代码。因此,重写这些代码完全没有意义。我们可以创建一个函数。在这个函数中,我们可以定义每次调用它时会发生什么。在这里的这一行,我们创建了一个名为check_moisture()的函数;现在,每当这个函数在程序中被调用时,将执行一系列活动。要执行的活动集由用户定义。所以,每当我们写def时,这意味着我们正在定义一个函数;之后,我们写需要定义的函数的名称。
一旦完成,那么在它之后的缩进中写下的任何内容都将在函数被调用时执行。一定要记住,每当我们调用或定义一个函数时,它都由函数名称末尾的开放和闭合括号()表示:
获取日期:('%d')
获取月份:('%m')
获取年份:('%Y')
在前面的条件中,我们正在检查时间是否是 7 点钟;进一步,我们还在检查时间是否小于或等于 10 分钟。因此,这段代码只有在时间是7小时且在0到10分钟之间时,才会运行if语句中的语句。
特别要注意的一点是,我们在两个条件之间使用了and,因此只有当两个语句都绝对为真时,它才会运行其中的代码。我们还可以在它里面使用一些其他语句,例如or,在这种情况下,如果任一语句为真,它就会运行代码。
如果我们在这个if语句中将and替换为or,那么它将每小时从 0 到 10 分钟运行代码,并且在整个 7:00 a.m.到 7:59 a.m.的时间段内连续运行代码:
如你所记,我们之前定义了一个名为check_moisture()的函数。在定义该函数时,我们还定义了每次调用此函数时都会发生的一系列活动。
现在是调用该函数的时候了。一旦程序到达代码的这部分,它将执行之前在函数中定义的一系列活动。
太棒了!我们已经开始自己构建比我们更智能的东西了。但现在我们想要更进一步,让它比我们更智能——这就是机器人的作用所在。不仅要做我们做的事情,而且要以更好的方式完成所有这些事情。
那么,我们可以改进什么?嗯,在寒冷的冬天,我们不需要很多水,但到了夏天,我们需要的水比冬天我们喝的水多得多。植物也是一样。
在冬天,它们需要的水量要少得多。此外,土壤中水分的蒸发速率也较慢。因此,在这两种情况下,我们需要为花园提供不同数量的水。问题是,我们如何做到这一点?
好吧,首先,要知道外面是热还是冷,我们需要一个传感器。我们将使用一个名为 DHT11 的传感器。它是一个既便宜又坚固的传感器,可以为我们提供温度和湿度的读数。最好的部分是,它的价格非常便宜,大约 2 美元。
它有四个引脚。但如果你认为它会使用 I2C 协议,那么你就错了。它有自己的数据传输方法。对于所有传感器来说,有一个单一的协议是好的,但通常你也会发现有许多传感器或设备使用不同的或全新的协议。DHT11 就是这样一种传感器。在这种情况下,我们有两种选择:要么理解整个通信方法,要么简单地从制造商那里获取库并在我们手中使用它。目前我们将选择后者。
现在我们来看看 DHT11 的引脚是什么样的:
你在这里可以看到,只有一个信号引脚,它将进行所有的数字通信。有两个电源引脚,其中一个未使用。也就是说,这个引脚没有显著的作用。它可能只是为了焊接或未来使用。这个传感器在 5V 供电下工作,只需要几毫安,所以我们可以直接通过树莓派给它供电。现在,对于数据通信,我们将信号引脚连接到 GPIO 引脚编号4。
在我们开始编写代码之前,让我们首先安装 DHT11 和树莓派之间通信的库。我们之前已经用 ADS1115 库做过这件事,但在这个例子中,有一些小技巧需要我们注意。所以,让我们开始吧。
首先,我们需要确保你的树莓派的操作系统是最新的。所以将树莓派连接到互联网,在树莓派上打开命令提示符,并输入以下命令:
这个命令将自动更新你的树莓派 Raspbian 操作系统。然后继续输入以下内容:
在这个命令中,我们正在安装以下包:
build-essential
python-dev
python-openssl
你可能想知道为什么我们要安装所有这些。简而言之,这些都是我们即将安装的用于 DHT11 通信的库的依赖项。如果这些包没有安装在树莓派上,我们将无法使用这个库。
最后,我们必须安装库;这是一个通用的库,其中也包含了与 DHT11 传感器通信的功能。这应该能满足我们简单通信的需求。以下是安装它的命令:
好了,我们可以开始了。我们的系统已经准备好与 DHT11 通信。让我们首先看看到目前为止我们所做的是否按预期工作。要做到这一点,按照以下方式连接 DHT11;你可以让其他组件,如电磁阀和土壤湿度传感器保持连接,它们不应该干扰。现在,将以下代码上传到 Pi:
一旦上传此代码,你将在屏幕上看到传感器的读数。这个代码只是简单地为你提供传感器的原始读数。这个代码非常简单,这里写的所有内容你都会理解,除了几行代码,它们是:
在这一行代码中,我们正在将Adafruit_DHT库导入到我们的代码中。这是我们将用来与 DHT11 传感器通信的相同库:
DHT 有不同版本,如 DHT11、DHT22 等。我们需要告诉程序我们使用的是哪个传感器。因此,我们给变量 sensor 分配了一个值。稍后,你会看到我们将如何使用它:
在这一行,我们将值4赋给一个名为pin的变量。这个变量将被用来告诉程序我们在树莓派上连接了 DHT11 的哪个引脚:
一旦这段代码完成,我们就可以确信传感器正在按照我们的预期工作。最后,整合所有传感器并制作一个完全智能的机器人的时候到了。由于电磁阀、湿度传感器和温度传感器已经连接,我们所需做的就是将代码上传到树莓派,见证魔法:
代码看起来很长,对吧?虽然看起来是这样,但一旦你一行一行地写出来,你肯定会理解,尽管它可能比我们迄今为止写的所有代码都要长,但它并不复杂。你可能已经理解了程序的大部分内容,然而,让我解释一下我们在这里使用的一些新事物:
在这一行,我们定义了一个名为check_moisture()的函数。如果你记得,当我们创建check_moisture函数时,我们基本上是在检查湿度值是否大于或小于 20%。如果我们需要检查 30%、40%和 50%的湿度呢?我们会为那个单独创建一个函数吗?
显然不是!我们做的是向函数传递一个参数,参数基本上是放在函数括号内的一个变量。现在我们可以为这个变量赋值,例如,check_moisture(30)——在函数执行期间,m的值将是30。然后,如果你调用它为check_moisture(40),那么那个m的值将是40。
现在,正如你所看到的,我们在整个函数中比较m的值:
if语句将检查在调用函数时分配的m的值。这使得我们的工作变得非常简单和容易。
让我们看看程序的其他部分在做什么:
每当达到期望的时间时,它将检查温度。如果温度低于15,它将使用参数值为20调用check_moisture函数。因此,如果湿度低于 20%,那么水将被输送到花园:
在if语句之后使用elif或else if语句。用通俗的话说,这意味着如果之前的if语句不成立,那么它将检查这个if语句。所以,在前面的行中,它会检查温度是否在15到28摄氏度之间。如果是真的,那么它会检查土壤的湿度。这一行函数的参数是30。因此,它会检查湿度是否小于30。如果是这样,那么它将给花园供水:
同样地,在这行代码中我们正在检查温度,如果它等于或超过28摄氏度,那么它将把值40作为参数传递给函数check_moisture。因此这次它将检查如果温度是28摄氏度或更高的话,是否需要检查湿度。
如您所见,现在系统将检查环境温度,并根据这个温度来调节植物所需的水量。最好的部分是,它是一致的,并且会提供植物所需的确切水量。
本章中提到的所有数值仅仅是假设值。我强烈建议您根据自己的居住地以及您花园中种植的植物类型进行调整,以获得系统最佳效果。
在本章中,我们涵盖了某些主题,例如螺线管集成和土壤湿度传感器,以构建一个能够自动灌溉您后院花园的机器人。接下来,我们将介绍电机的基础知识。
好吧!我们已经制作了一个照顾你花园的机器人,我希望它运行得很好。现在是时候将事情提升到另一个层次了。
我们一直认为机器人就像 WALL-E 一样,四处移动为我们做事。嗯,我的朋友,现在这个梦想并不遥远。实际上,在本章中,我们将继续前进,制作一个。让我们看看它是如何完成的。
我们将涵盖以下主题:
基础知识
开始行动
改变速度
每当我们谈论从一个地方移动到另一个地方时,我们会想到轮子,同样地,每当我们想到移动机器人的轮子时,我们会想到马达。存在各种不同类型的马达。所以首先让我们看看最基本的马达类型,它被称为刷式直流马达。正如其名所示,它使用直流电。你可能会发现这样的马达:
相信我,这些事情无处不在,从你为邻居买的圣诞礼物到最强大最糟糕的机器——你都会发现这些马达隐藏在引擎盖下。这些马达之所以常见,是因为它们非常非常简单。简单到只需要一个电池和两根电线就能启动它们。只需将正极连接到一个端子,负极连接到另一个端子,马达就会开始旋转。交换这些连接,旋转的方向就会改变。取两个电池,电压加倍,马达就会转得更快。就这么简单。
现在你可能认为我们只需将这个马达连接到树莓派,然后我们就可以出发了。但不幸的是,情况并非如此。正如你可能从前面的章节中记得的那样,树莓派只能提供大约 50 毫安的电流,但马达的消耗可能要高得多。因此,为了运行它,我们需要一个中间设备。
第一个出现在你脑海中的想法可能是使用继电器,为什么不呢?它们可以引导大量的电流,可以处理高电压。这应该是理想的选择。如果你这样想,你是对的,但只限于某种程度上,这是因为继电器只是一个我们可以用来打开或关闭马达的开关。我们无法控制马达的转速或旋转方向。现在,你可能认为这个问题并不新鲜,我们可以非常容易地通过使用脉冲宽度调制(PWM)来解决它,对吧?嗯,答案是不了!因为这些继电器是机械装置,由于它们的机械性质,在每秒开关的次数上存在一些最大限制。因此,它无法应对 PWM 的频率。最后,我们仍然面临改变马达方向和速度的问题。那么我们现在该怎么办呢?
正如我经常说的,问题的美在于它总是有解决方案,这里的解决方案被称为电机驱动器。电机驱动器主要是电子继电器——一种可以允许高电流但不是机械的开关。因此,我们可以每秒切换它数百次。这些电子继电器可以是简单的晶体管,或者在高压应用中,甚至可以使用 MOSFET 进行切换。我们可以简单地给这些电子开关 PWM 信号,并确保电路中有足够的电流。此外,正如我之前提到的,电机驱动器由一组这些电子继电器组成。它们最常见的、最实用的排列方式被称为全桥或 H 桥。在我进一步解释之前,让我们看看这究竟是什么:
在全桥电路中,我们连接到电机的有四个开关电路;根据需要,这些电路可以独立地开启或关闭。在关闭状态下,所有这些开关电路都处于开启状态,因此保持电机关闭。现在,无论何时我们想要启动电机,我们都需要以某种方式开启两个开关,使得电路完整,电机开始工作。那么,让我们看看它将是什么样子:
在这里,我们开启了开关电路S2和S3;这反过来完成了电路,使得电流可以在电机中流动。现在,为了控制速度,这些相同的开关电路可以在非常高的频率下以不同的占空比开启和关闭,以实现特定的平均电压。现在我们可以通过改变这两个开关电路的电压来达到特定的电机速度,那么我们来看看我们是如何改变电机旋转方向的:
在这个电路中,我们关闭了之前连接的S2和S3,取而代之的是开启了S1和S4,因此电机的极性被反转。正如我们之前讨论的,每当直流刷式电机的极性改变时,方向也会随之改变。市场上你可以找到各种类型的电机驱动器。我们在这里理解的是刷式直流 H 桥电机驱动器;还有其他类型的电机驱动器用于控制其他类型的电机,但当前我们将仅关注刷式电机。在选择电机驱动器时,你应该非常仔细地检查电机驱动器的规格表。以下是一些将被提到的关键规格:
电压等级:电机驱动器可以处理和调节的电压将有一个最小和最大限制。确保你的电机位于这个特定的电压范围内。
电流等级:电机驱动器可以承受的绝对最大电流;超过这个电流将烧毁或损坏电机驱动器。这可能有点误导。让我们看看原因。除了绝对最大值外,还可能有其他许多电流等级可能被指定。这些可能包括:
重复最大电流:这是电机驱动器可以承受的最大电流,但不是连续的。这个等级给出是因为有时电机的负载可能会增加,可能会有一个短暂的更高电流需求。电机驱动器将重复提供足够的电流而不会损坏。但是,这种电流需求不应是连续的。
爆发最大电流:这是电机驱动器可以承受的绝对最大电流;超过这个电流将损坏电机驱动器。直流电机在从静止状态启动时可能会有非常高的电流需求。因此,电机驱动器被设计来处理这些电流。但是,这种电流的激增不应是重复的,否则可能会发生加热和随后的损坏。通常,爆发最大电流被制造商称为最大电流。
连续最大电流:这是真正的关键;连续最大电流是电机驱动器可以连续管理的最大连续电流。
供电电压:这是电机驱动器的运行电压——必须给电机驱动器提供这个电压以供其内部工作。
逻辑供电电压:这是给电机驱动器的控制信号,可以是 5V、3.3V 和 12V 等电压。因此,电机驱动器将指定它可以接受的信号线路上的最大逻辑电压。
现在,让我们看看我们有什么。在这本书的过程中,我们将使用 L298N 电机驱动器模块,目前这是市场上最常见的电机驱动器模块之一。它有两个通道——您有两个 H 桥,因此您可以将两个电机连接到它。此外,这个电机驱动器的规格对于价格来说也是相当不错的。以下是规格:
电压等级:2.5V 至 46V
重复最大电流:2.5 安培
爆发最大电流:3 安培
连续最大电流:2 安培
供电电压:4.5V 至 7V
逻辑供电电压:4.5V 至 7V
当您拥有物理电机驱动器时,您会注意到以下引脚:
电机 A:这是电机驱动器的 1 通道。您可以将第一个电机连接到这个端口。
电机 B:这是电机驱动器的 2 通道。您可以将第二个电机连接到这个端口。如果您只有一个电机,您可以简单地不连接这个端口。
GND:这是您将连接到电机的电源的接地。您不仅要连接电源的接地,还要将树莓派的接地连接到这个端口,以确保树莓派和电机驱动器之间的电路完整。
VCC: 这是电机驱动器的正极端口。这是您电池或电源适配器的正极将连接的地方。
IN 1 和 IN 2:这是我们为电机 A 需要从微控制器提供的两个逻辑输入。每当 IN 1 接收到信号时,H 桥的一部分被激活——电机开始朝一个方向旋转。每当 IN 2 接收到信号时,H 桥的另一部分被激活,使电机朝相反方向旋转。
IN 3 和 IN 4:这是电机 B 的逻辑输入,它将按照与 IN 1 和 IN 2 完全相同的方式工作。
EN A 和 EN B:这是两个通道的使能引脚。如果这些引脚不是高电平,尽管您在输入端口上给出任何信号,相应的通道也不会工作。您可能会注意到 EN 端口上有一个小电容。这被称为分流器。它的作用是在它连接的两个引脚之间建立接触。当这个电容连接到 EN 引脚上时,这意味着只要这个分流器连接,它就会永久保持高电平。
好的,理论讲得够多了,现在让我们通过树莓派启动一个电机。为此,请按照以下方式连接电机和电机驱动器:
现在,一旦你完成了它,让我们上传代码并看看会发生什么:
现在,让我们稍微了解一下代码:
引脚编号20连接到电机驱动器的 IN 1。为了方便,我们将电机 1 右侧改为Motor1R;实际上,电机可以朝任何方向旋转,但我们只是这样写以方便理解和说明。同样,我们也对Motor1L做了这样的处理。它连接到 IN 2,因此这将导致电机朝相反方向旋转:
在这里,我们将Motor1R或引脚编号20设置为高电平,这意味着输入电机驱动器接收到的信号是:
现在,在延迟 5 秒后,以下代码将运行,它将改变引脚的状态,如下表所示:
现在,让我们看看运行后会发生什么。电机首先会朝一个方向旋转,然后它会转向另一个方向。代码非常直接,我认为没有必要进行解释。我们在这里所做的只是简单地打开或关闭连接到电机驱动器的两个 GPIO 之一。一旦电机驱动器的输入 IN 1 被激活,H 桥的一部分就会接通,导致电机朝一个方向旋转。每当电机驱动器的 IN 2 为高电平时,H 桥的另一部分就会接通,导致电机驱动器输出端的极性改变,因此电机就会朝相反方向旋转。
现在我们已经了解了如何使用电机驱动器改变电机的方向,是时候更进一步,使用电机驱动器控制电机的速度了。要做到这一点,我们实际上不需要做太多。电机驱动器被设计成能够理解 PWM 信号。一旦向电机驱动器提供了 PWM 信号,电机驱动器就会相应地调整电机的输出电压,从而改变电机驱动器的速度。PWM 必须提供在电机 A 的 IN 1 和 IN 2 输入端口,以及电机 B 的 IN 3 和 IN 4 输入端口。很明显,提供 PWM 信号的引脚将决定电机移动的方向,而 PWM 的占空比将决定电机旋转的速度。
现在我们已经了解了电机驱动器中的速度控制是如何工作的。是时候我们自己动手尝试了。为此,我们不需要对连接进行任何更改;我们只需要上传以下代码:
你运行这段代码后发生了什么?我敢肯定电机开始时速度较慢,然后逐渐加速,达到最高速度后最终停止——这正是我们想要它做的。如果你还记得,这段代码看起来非常熟悉。记得第一章中改变 LED 亮度吗?几乎是一样的;尽管有一些不同,所以我们来看看它们是什么:
在这一行中,我们只是定义了必须提供 PWM 的引脚——即在Motor1R上,这对应于引脚号20。同时,我们定义了 PWM 的频率为100赫兹,即每秒 100 次:
由于我们正在以一个特定的方向运行电机,即1R,因此 H 桥的另一半应该关闭。这可以通过上面的行通过将1L设置为低电平来完成。如果我们不这样做,引脚21可能处于任意状态,因此它可以是开或关。这可能会与电机的移动方向冲突,硬件将无法正常工作:
现在来点真的;这一行,for i in range(0,101):,将会持续运行它包含的程序,直到i的值在0到101之间。每次这个循环运行时,它也会增加i的值。在这里,每次,值将增加一:
因此,每次代码通过for循环时,值或占空比将增加一。很简单,不是吗?
如果你做对了,机器人技术中的所有事情都非常简单。思路是将你的问题分解成小块,逐一解决;相信我,一旦你那样做了,没有什么会看起来很难。
在本章中,我们研究了电机的各个方面。接下来,通过使用所有基础知识,我们将研究蓝牙与移动设备的交互,并构建一个蓝牙控制的机器人车。
在这一章中,我们将进一步整合传感器,制作一个机器人,在你编程它时,它会随时喂你的宠物。这相当简单,你可能需要一些 DIY 技能和一些旧纸箱来准备这个项目。准备好剪刀和胶水,因为这里可能需要它们。
有时候你一整天都不在家,你的宠物一直在等你喂它。对于这种情况,这个机器人会非常有帮助;它会在特定时间喂你的宠物,并确保你的宠物每次都能得到正确的食物量。这甚至可以在日常生活中有所帮助。因为它永远不会忘记喂你的宠物,无论发生什么。
力是作用于物体上的基本单位之一,无论是由于重力还是某些外部因素。力的测量可以给我们关于环境或物体的很多洞察。如果你有一个电子秤,那么每次你踏上秤时,你得到的重量是因为力传感器。这是因为你的身体有质量。由于质量,重力将物体拉向地球的中心。重力对任何物理物体施加的力被称为物体的重量。因此,通过力传感器,我们基本上是在感知重力对物体施加的力有多大。
现在我们来谈谈力测量,它可以采用多种方式。有各种类型的复杂负载传感器,可以精确地告诉我们重量变化了多少毫克。也有更简单的传感器,可以简单地给我们一个粗略的估计,即施加了多少力。从这些数据中,我们可以计算出物体的相对重量。你可能想知道为什么我们不使用负载传感器。原因是它对于当前场景可能稍微复杂一些,而且从基础开始总是好的。
那么,让我们看看我们有什么。我们正在谈论的负载传感器是一个电阻式力传感器。它的工作方式非常简单。它由聚合物组成,其电阻随着施加的力的变化而变化。一般来说,你施加的力越大,电阻就越低。因此,由于这种电阻的变化,我们可以简单地计算出结果电压。这个结果电压将与施加在力传感器上的重量成正比。
现在,为了制作这个机器人,我们需要一些纸箱。我们需要制作它的两部分:
食物分配器
一个带有力传感器的收集碗
首先,让我们看看分配器应该如何制作。你需要遵循以下步骤:
取一个中等大小的纸箱,可以携带大约四磅半的宠物食品。
然后,继续制作一个小开口。
这个开口应该足够大,以便分配食物,但不能太大,以至于一次有太多食物从里面出来。
现在,一旦完成这个步骤,你需要制作一个覆盖通孔的盖子。
这个盖子应该比本身略大。
将盖子安装在电机的轴上。
按照以下图示将电机固定在纸板上。确保电机的位置要使得盖子能够覆盖纸板上的整个通孔。
最后,安装限位器。这些是简单的纸板片,将限制盖子向任一方向的移动。
第一个限位器应该位于一个位置,使得盖子正好覆盖整个通孔时停止。
第二个应该位于盖子完全打开的位置,当食物从容器中落下时没有阻碍。
为了帮助您进行构造,请参考以下图示;如果您想的话,也可以设计其他控制开启或关闭的方法:
现在,第二部分是碗。这部分相当直接。你只需要将力传感器使用温和的粘合剂粘贴在碗底,使其与地面接触。完成这个步骤后,添加另一层粘合剂并将其精确地粘贴在分配器下方。
一旦完成这个构造,请按照以下图示进行布线:
完美!现在我们准备好上传代码并让这个装置工作。所以,请上传以下代码;然后我会告诉你具体发生了什么:
现在,一旦你上传了这个代码,你将开始得到力传感器的原始读数。如何?让我们看看:
在这里,我们使用命令import Adafruit_ADS1x115来使用ADS1x15库;这将帮助我们读取 ADC 的值:
你应该知道这一行的作用;然而,如果你不确定,请参考第二章,使用 GPIO 作为输入:
在这一行,将显示 ADC 接收到的原始读数。
你可能想知道我们为什么要这样做。到目前为止,你可能一直根据视觉数量而不是具体重量来喂养你的宠物。因此,我们在这里做的是打印力传感器的值。一旦这个值被打印出来,然后你可以调整容器中食物的数量并测量读数。这个读数将作为阈值。也就是说,这是将要分配的数量。
现在,一旦你记下了这个读数,我们将稍微修改一下代码。让我们看看是什么:
现在我们来看看我们做了什么!
在这里,我们声明了连接到电机、蜂鸣器和力敏电阻(FSR)的引脚。我们还为名为THRESHOLD的变量赋值;这将确定将要分配的食物量。在这里,我们随意地使用了1000。在你的代码中,你必须放入之前代码中计算出的值。
现在大多数代码都很容易理解,让我们跳到主要表演的部分:
前面的代码是好的,我相信它会在设定的时间分发食物。然而,可能会有问题,因为如果宠物不知道食物是否已经被取走,那么机器人将不会有效。因此,我们需要有一个警报,通知宠物食物已经准备好可以吃了。
即使在之前的程序中,我们也使用了一个蜂鸣器来通知宠物食物正在分发,但那只是很短的时间。然而,我们现在讨论的是一个警报系统,它将持续响直到宠物来吃食物。为此,按照以下方式连接系统,并将超声波传感器以记录宠物吃食物时的距离的方式安装。
现在,为了做到这一点,你需要上传以下代码:
如您所见,大部分代码几乎完全相同;然而,增加了一个功能,它将一直让蜂鸣器响,直到宠物来吃食物。为此,我们输入以下内容:
我们定义了一个记录超声波传感器距离的函数。您可能还记得之前的章节中的这段代码。所以现在,每次调用这个函数时,都会记录下距离:
正如你们所看到的,蜂鸣器在 while 循环中就像上次一样被打开;然而,在之前的代码中,在等待了 5 秒之后蜂鸣器被关闭。然而,在这段代码中,我们并没有这样做。因此,蜂鸣器将会保持活跃,直到我们的代码中某个部分将其关闭。现在,为了打开蜂鸣器,我们在代码的末尾计算距离:
这段代码正在检查距离是否小于2厘米,以及食物容器的重量值是否小于50。这意味着宠物接近食物容器并至少吃掉了一半的食物。如果他没有正确地吃食物,那么蜂鸣器将会持续鸣响。
所以,读者们,我想你们已经理解了使用时间和力传感器逻辑进行电机集成的基础知识,以此来制作一个能帮你日常做些工作的机器人。这类机器人在市场上只需几百美元就能买到,但看看你们是如何如此容易且低成本地为自己制作了一个。向前看,在下一章中,我们将构建一个蓝牙控制的机器人汽车。
我们已经走了很长的路;现在是时候继续前进,制造出更好的东西了。全世界都在疯狂地追捧自动驾驶汽车的出现,在接下来的十年里,这将成为新常态。在这些车辆中发生了很多事情。多个传感器、GPS 和遥测数据都在实时计算,以确保汽车在正确的航向上行驶,并且由系统安全地驾驶在道路上,因此制造机器人车辆证明是学习机器人技术和未来技术的理想方式。在这本书中,我们将始终努力制造出不仅与现有技术一样好,而且在某些方面甚至更好的技术。那么,让我们一步一步地开始制造这辆自动驾驶汽车吧。
本章将涵盖以下主题:
车辆的基本知识
准备车辆
通过蓝牙控制车辆
你可能正在想:我们还能从车辆中学到什么我们不知道的东西呢?这可能是对的,但在我们开始这一章之前,我们必须确保理解以下几点。那么,让我们开始吧。
首先是底盘,我们将使用它:它是一个四轮驱动底盘,所有的四个轮子都由一个专门的电机独立控制。因此,我们可以根据我们的需求改变每个轮子的速度。我们选择四轮驱动传动系统,因为它在地毯和凹凸不平的表面上更容易卡住。如果你想这样做,你也可以选择两轮驱动传动系统,这不会造成太大的差异。
现在,一旦你组装了底盘,你可能会发现它没有转向机构。这意味着汽车只能直行吗?当然不是。有许多方法可以在制造小型车辆的同时控制汽车的方向。最好的方法是叫做差速转向。
在传统汽车中,有一个发动机,这个发动机为车轮提供动力;因此,原则上所有车轮都以相同的速度转动。当我们直行时,这没问题,但每当汽车想要转弯时,就会出现一个新的问题。参考以下图表:
你会看到,位于内曲线上的轮子直径较小,而位于外缘的轮子直径较大。你可能记得小学的一个事实:直径越大,周长越大,反之亦然。因此,靠近内缘的轮子与外缘的轮子相比,将覆盖更短的距离,或者简单地说,内轮将旋转得更慢,而外轮将旋转得更快。
这个问题导致了汽车中微分器的发现,它位于汽车车轴中心的圆形凸起。它的作用是根据转弯半径改变车轮的转速。天才,不是吗?现在,你可能正在想:这很好,但你为什么要告诉我这些呢?嗯,因为我们将做完全相反的事情来控制机器人。如果我们改变转向圆周内外的电机速度,那么汽车将试图向内转弯,同样地,如果我们对另一端做同样的操作,它将试图向相反方向转弯。在制作轮式机器人时,这种策略根本不是什么新东西。转向机制很复杂,在小型机器人上实现它们简直是一个挑战。因此,这是一种简单且容易的方法来使你的车辆转向。
这种方法不仅简单,而且非常高效,需要的组件最少。而且,由于车辆的转弯半径也减小了,所以它更好。实际上,如果我们以相同的速度旋转车轮的相对两侧以相反的方向,车辆将完全在其自身轴上转向,使转弯半径完全为零。这种配置被称为滑移转向驱动。对于在室内工作的轮式机器人,这是一个杀手级特性。
现在是时候将这个机器人车辆变为现实了。所以让我们打开车辆底盘,将每个部件组装在一起。组装手册通常与套件一起提供,所以你很快就能完成它。
一旦你完成了套件的组装,就继续为每个电机分离电线。这将制作车辆准备过程中的一个非常重要的部分。所以,一旦所有的电线都从车辆中出来,拿一个电池并为每个轮子供电。注意连接的正极性,其中车轮以正向旋转。你所要做的就是拿一支永久性记号笔或者可能是指甲油,标记当电机正向旋转时连接到正极的电线。由于所有这些电机完全依赖于极性来确定方向,这一步是确保我们每次供电时它们总是以相同方向旋转的关键。相信我,这将节省你很多麻烦。
现在,一旦所有这些都完成了,按照以下图示将电线连接到电机驱动器(红色标记的电线是你之前标记的电线):
完美!现在似乎一切都井然有序,除了电机驱动器与电源和 Raspberry Pi 的连接。那么让我们看看我们将如何做到这一点:
好了,那么现在是时候处理真正的任务了!我们首先想要确保所有连接都按照我们计划的方式正常工作。为此,我们将从一个虚拟代码开始,这个代码将简单地切换所有电机以正向运行。所以,以下是代码:
程序不可能比这更简单了;我们在这里所做的只是给电机驱动器发送一个命令,让电机单向旋转。可能会有这样的情况,一组电机将反向旋转,在这种情况下,您应该在电机驱动器上改变连接的极性。这应该会解决问题。有些人可能会认为我们可以修改代码来实现这一点,但根据我的经验,从那里开始事情会变得复杂,如果您选择了另一条路径,这可能会给您带来麻烦。
好了,一切都已经设置好了,并且一切运行正常。继续尝试其他输出排列和组合,看看汽车会发生什么。不用担心,无论您做什么,除非汽车从屋顶上掉下来,否则您不会损坏汽车!
尝试那些组合很有趣吗?现在是我们将这次旅程再向前迈进一步,看看还有什么是可能的。我们都玩过遥控车,我相信每个人都会喜欢那些快速的小玩具。我们将做类似的事情,但方式要复杂得多。
我们都知道蓝牙:这是与近距离设备通信的最佳方式之一。蓝牙通信是一种中速、低功耗的通信方法。它在移动设备中几乎无处不在,因此它是一个理想的起点。在本章中,我们将通过蓝牙使用您的手机来控制汽车。现在让我们看看我们如何做到这一点。
我们首先想要做的是将智能手机与机器人车辆配对,为此我们需要打开树莓派的终端并执行以下步骤:
输入命令 ~ $ bluetoothctl;这是一个蓝牙代理,它允许两个蓝牙设备进行通信。如果没有蓝牙代理,这两个设备将无法相互通信。
[蓝牙] # power on 命令简单地启动了树莓派上的蓝牙。
[蓝牙] # agent on 命令启动代理,然后它可以为我们发起连接。
[蓝牙] # discoverable on 命令使树莓派的蓝牙可被发现。蓝牙可能已经开启,但我们必须将其设置为可被发现,以确保其他设备能够找到它并与之连接。
[蓝牙] # pairable on 命令使设备可配对。如果蓝牙已开启,这并不意味着您的设备能够连接,因此我们需要将其设置为可配对,而这个命令正是这样做的。
[蓝牙] # scan on 这个命令开始扫描附近的蓝牙设备。这个命令的输出将是一对 MAC 地址和蓝牙名称。MAC 地址是设备的物理地址;这是一个唯一的地址,因此它永远不会为两个设备相同。
[蓝牙] # pair 94:65:2D:94:9B:D3 这个命令帮助你与想要连接的设备配对。你只需要输入提到的命令和 MAC 地址即可。
为了更清楚,这是你的屏幕应该看起来像的样子:
一旦完成这个过程,你应该能够将树莓派连接到你的移动设备上。现在你已经连接上了,是时候继续编写代码了,通过这段代码我们可以仅使用我们的移动设备来控制蓝牙汽车。所以,这就是代码。继续看,然后我们将进行解释:
现在我们来看看这段代码实际上在做什么:
在这个程序中,我们将使用一些蓝牙的通用功能,因此我们调用库bluetooth,这样我们就能调用那些方法:
现在,每次我们连接两个蓝牙设备时,我们都有多种通信方式;其中最简单的是射频通信,在这里被称为RFCOMM。现在,在这一行中,我们使用bluetooth库的BluetoothSocket方法来定义我们在程序中使用的通信协议,到现在你应该知道是RFCOMM。我们进一步将这个数据存储在一个名为server_socket的变量中,这样我们就不需要反复重复这个步骤。相反,每次我们需要这些数据时,它已经存储在名为server_socket的变量中:
现在,蓝牙有多个端口;这是一个非常有用的概念,因为通过一个单一的蓝牙连接,我们可以将各种数据流传输到不同的设备和程序。这避免了数据冲突,并确保数据被安全地传达给确切的接收者。我们现在使用的程序非常简单,我们不需要多个端口进行数据通信。因此,我们可以使用从1到60的任何端口进行通信。在这个程序的部分,你可以写入任何端口,你的程序将正常运行:
如您所见,参数内的第一个参数是空的。在这里,我们通常写入需要绑定的 MAC 地址。然而,由于我们将其设置为空,它将自动绑定到我们已配对的 MAC 地址。我们拥有的第二个参数是它需要连接的端口。正如我们所知,port变量的值被设置为1。因此,它将自动连接到端口号1:
这是一行非常有趣的代码。正如我们所知,我们可能不是唯一一个试图连接到树莓派蓝牙设备的人,那么当树莓派收到另一个连接请求时,它应该怎么做呢?
在这一行,我们只是定义了这一点:我们调用一个名为listen(1)的方法。在这个函数中,我们将参数的值定义为1。这意味着它将只连接到一个设备。任何其他试图连接的设备都不会通过。如果我们把这个参数改为2,那么它将连接到两个设备,但是它将保持在队列中,因此它被称为队列连接:
在这一行,我们只是告诉用户连接已经成功建立,我们使用str(address)函数打印出连接到的地址的值。这样我们就可以确保连接已经建立到了正确的设备。
在此之后,程序的其余部分相当简单。我们只需要比较从移动设备接收到的值,让汽车执行我们想要的任何操作。在这里,我们让汽车向四个方向移动,即向前、向后、向右和向左。您也可以根据您的需求添加特定的条件:
在这一行,我们正在关闭客户端套接字的连接,以便客户端可以被断开,数据传输可以被终止:
在上一行,我们正在关闭服务器套接字的连接,以便服务器连接可以被断开。
本章教导我们如何使用蓝牙接口通过数据抓取和共享来自动化和控制一辆汽车。接下来,我们将把迄今为止所学的内容应用于接口红外传感器以实现障碍物避让和路径规划。
制作一个能够自动驾驶的机器人车辆,我们首先需要了解人类是如何驾驶车辆的。当我们开车时,我们不断分析空间和其他物体的距离。然后,我们决定是否可以通过。这种事情是通过我们的脑眼协调不断发生的。同样,机器人也必须做同样的事情。
在我们之前的章节中,你了解到我们可以通过传感器找到我们周围物体的接近距离。这些传感器可以告诉我们物体有多远,基于这个信息,我们可以做出决策。我们主要使用超声波传感器来做这件事,因为它极其便宜。然而,正如你记得的那样,安装超声波传感器和运行其代码稍微有些繁琐。现在是时候我们使用一个更简单的传感器并将其安装到车上。
本章将涵盖以下主题:
红外接近传感器
自动紧急制动
赋予其自动转向能力
使其完全自动化
以下照片展示了红外接近传感器:
它由两个主要部分组成——传感器和发射器。发射器发射红外波;这些红外(IR)波然后击中物体并返回传感器,如图所示。
现在,正如你可以在前面的图中看到的那样,发射的红外波从距离传感器不同距离的表面反弹回来,然后它们以一个角度接近传感器。现在,因为发射器和传感器之间的距离在所有时间点都是固定的,所以反射的红外波对应的角度将与它反弹前的距离成正比。红外接近传感器中有超精确的传感器,能够感应红外波接近的角度。通过这个角度,它给用户一个与它对应的距离值。这种寻找距离的方法被称为三角测量法,并且在工业中得到了广泛的应用。我们还需要记住的是,正如我们在前面的章节中提到的,我们周围充满了红外辐射;任何高于绝对零度的物体都会发出相应的波。此外,我们周围的阳光也含有大量的红外辐射。因此,这些传感器内置电路来补偿这一点;然而,它只能做到这么多。这就是为什么,当处理直射阳光时,这个解决方案可能会有一些麻烦。
现在,理论部分就到这里吧,让我们看看汽车实际上是如何工作的。在这个例子中,我们使用的红外接近传感器是由夏普公司生产的模拟传感器,型号为 GP2D12。它的有效感应范围是 1000-800 毫米。这个范围还取决于所讨论物体表面的反射率。物体越暗,范围越短。这个传感器有三个引脚。正如你可能猜到的,一个用于 VCC,另一个用于地线,最后一个用于信号。这是一个模拟传感器;因此,距离读数将基于电压。通常,对于大多数模拟传感器,你都会得到一个图表,它将描绘出各种感应范围内的各种电压。输出基本上取决于传感器的内部硬件和构造,因此可能会有很大的不同。下面是我们传感器及其输出的图表:
好吧,到目前为止一切顺利。我们知道 Raspberry Pi 不接受模拟输入;因此,我们将继续使用我们之前也使用过的 ADC。我们将使用之前使用的相同 ADC。
有一种新技术,新车型都配备了。它被称为自动紧急制动;无论我们开车有多专注,我们都会分心,比如 Facebook 或 WhatsApp 的通知,这会诱使我们从路上看向手机的屏幕。这可能是交通事故的一个主要原因;因此,汽车制造商正在使用自动制动技术。这通常依赖于长距离和短距离雷达,它检测汽车周围其他物体的接近程度,在即将发生碰撞的情况下,它会自动施加制动,防止汽车与其他车辆或行人相撞。这是一个非常酷的技术,但有趣的是,我们今天将用自己的双手来制作它。
为了制作这个,我们将使用红外接近传感器来感应其周围物体的接近程度。现在,拿起双面胶带,将红外距离传感器贴在汽车的前端。一旦完成,按照以下连接电路:
好了,我们已经准备好编写代码了。以下就是代码,只需将其复制到你的 Pi 上:
现在,让我们看看这段代码中实际发生了什么。一切都非常基础;红外接近传感器正在感应其前方物体的接近程度,并以模拟信号的形式提供相应的距离值。然后这些信号被 ADC 读取,并转换为数字值。这些数字值最终通过 I2C 协议传输到 Raspberry Pi。
到目前为止,一切顺利。但你可能想知道这一行代码的作用是什么?
接下来,代码的主要部分如下:
这同样非常简单。我们输入了一个距离值,在这个程序中,我们将其设置为20。所以,每当红外接近传感器的距离值F小于20时,就会调用stop()函数。stop函数只是让汽车停止,防止其与任何物体相撞。
让我们上传代码,看看它是否真的能工作!确保你在室内运行这辆汽车;否则,如果它没有遇到任何障碍物,你将很难尝试停止这辆汽车。祝您玩得开心!
我希望你在玩这个小玩意儿时玩得开心。传感器应用得如此简单,以及它能带来多大的差异,这很有趣。既然你已经学会了基础知识,现在是时候继续前进,给汽车赋予更多的能力了。
在之前的代码中,我们只是让机器人停在障碍物前,为什么不让它绕过汽车呢?这将会非常简单而又非常有趣。我们只需要调整stop()函数,使其能够转向。显然,我们还将把函数名从stop()改为turn(),只是为了更清晰。有一点要记住,你不需要重写代码;我们只需要做一些小的调整。所以,让我们看看代码,然后我会告诉你具体有什么变化以及为什么:
正如你所注意到的,除了以下内容外,一切几乎保持不变:
这部分代码定义了turn()函数,其中车辆的对面轮子会向相反方向旋转;因此,使汽车绕其自身轴线转动:
现在这是程序的主要部分;在这个部分,我们定义了如果汽车遇到任何前方的障碍物,它会做什么。在我们之前的程序中,我们主要是告诉机器人一旦遇到任何障碍物就立即停止;然而,现在我们将stop函数与之前在程序中定义的turn函数链接起来。
我们简单地添加了一个如下条件:
然后,它将仅仅转动几秒钟,因为微控制器将解析代码并执行它,然后退出条件。为了做到这一点,我们的 Raspberry Pi 几乎只需要几微秒。所以,我们可能甚至看不到发生了什么。因此,在我们的程序中,我们使用了 while 循环。这本质上是在条件满足之前保持循环运行。我们的条件是 while F < min_dist:,所以直到机器人检测到前方有物体,它将一直执行其内部的函数,在我们的例子中是 turn() 函数。简单来说,直到它没有足够地转向以避开障碍物,车辆将一直转向,然后一旦循环执行完毕,它将再次跳回主程序并继续直线行驶。
简单吗?这就是编程的美丽之处!
现在,你们应该已经理解了使用简单接近传感器进行自动驾驶的基本原理。现在是时候让它完全自主了。为了实现完全自主,我们必须理解和映射我们的周围环境,而不仅仅是将车辆转向直到遇到障碍物。我们基本上需要将整个活动分为以下两个基本部分:
扫描环境
决定如何处理感知到的数据
现在,让我们先编写代码,然后再看看我们需要做什么:
现在程序的大部分内容都像我们之前的程序一样;在这个程序中,我们定义了以下函数:
forward()
right()
left()
stop()
关于定义函数,我没有什么需要告诉你们的,所以让我们继续看看我们还有哪些内容。
主要动作发生在我们的无限循环 while True: 中。让我们看看具体发生了什么:
让我们看看这段代码的这部分在做什么:
当我们的程序进入无限循环时,首先执行的是 forward() 函数;也就是说,一旦无限循环执行,车辆将开始向前行驶
F = (1.0/(F-value/13.15))-0.35 正在将距离计算成可理解的公制距离值
min_dist = 20,我们只是简单地定义了稍后将要使用的最小距离
一旦这段代码完成,if 语句将检查是否 F < min_dist:。如果是这样,那么位于 if 语句下的代码将开始执行。这将是第一行,即 stop() 函数。所以,当车辆遇到前方任何障碍物时,它首先会做的就是停止。
正如我之前提到的,我们代码的第一部分是理解环境,所以让我们继续看看我们是如何做到这一点的:
一旦它向右转,它将再次从接近传感器读取值,并且在使用此代码 R=F 时,我们将该值存储在名为 R 的变量中。
所以本质上我们做的是扫描了我们周围区域。在中心,我们有一个障碍物。它将首先向右转,并获取右侧的距离值;然后,我们将向左转,并获取左侧的距离值。所以本质上我们知道障碍物周围的环境。
现在我们来到了必须做出决定的部分,即我们必须向哪个方向前进。让我们看看我们将如何做到:
使用 if 语句,我们通过此代码 if L < R: 比较障碍物左右两侧接近传感器的值。如果 L 小于 R,那么车辆将向右转 2 秒。如果条件不成立,那么 else: 语句将生效,这将使车辆前进。
现在如果我们从更大的角度来看代码,以下事情正在发生:
车辆将一直向前行驶,直到遇到障碍物
遇到障碍物时,机器人将停止
它将首先向右转,并测量它前方物体的距离
然后,它将向左转,并测量它前方物体的距离
然后,它将比较左右两侧的距离,并选择它必须前往的方向
如果它必须向右转,那么它将向右转然后前进
如果它必须向左转,那么它已经处于向左转的方向,所以它只需直行即可
让我们上传代码,看看事情是否按照计划进行。记住,尽管每个环境都不同,每辆车也不同,所以你可能需要调整代码以使其顺利运行。
现在我将给你留下一个问题。如果在两种情况下传感器的读数都是无穷大或它能给出的最大可能值?机器人会怎么做?
好吧,进行一些头脑风暴,看看我们能做些什么来解决这个问题!
在本章中,我们利用到目前为止所学的所有基础知识,并引入红外接近传感器,使得我们能够将我们的机器人汽车的开发推进到一个更高级的阶段,以便检测障碍物并相应地改变方向。在下一章中,我们将研究如何制作我们自己的区域扫描仪——那里见!
电机是令人惊叹的东西;它们有各种各样的形状和大小。首先,它们可以被认为是大多数机器人的骨架。然而,在这个世界上,没有什么是不完美的。这些电机也一定有一些缺点。到现在,你可能已经自己发现了一些。在上一章中,当我们让汽车转向时,你可能已经注意到转向的角度从未真正相同。同样,当车辆被下达直行的命令时,它实际上并不会这样做。相反,它会试图轻微地偏向一侧。
问候第一个问题——精度。电机控制起来非常简单,但问题在于当我们只需要将电机旋转到特定角度时。如果你需要只旋转你的机器人车辆的电机 90 度,你将如何做?首先可能出现在你脑海中的事情可能是调整电机的定时。你可能在这里是正确的。但仍然,确保每次都是精确的 90 度是不可能的。
但是当我们谈到机器人时,即使是 1 度的精度可能也不够。现在的机器人学家正在期待达到两位数的精度。所以,我们所说的精度接近 0.01 度。你现在怎么想?我们如何通过电机达到这种精度?
本章将通过以下主题回答所有这些问题:
伺服电机
列表
激光雷达
那么,让我来向你介绍伺服电机。伺服电机基本上是一种带有一些附加组件的电机。现在,为了了解这些附加组件是什么,让我们先来看一个例子。假设你想要去伦敦。现在,为了了解你如何到达那里以及到达伦敦的路线,你需要知道的第一件事是,你现在确切的位置在哪里。如果你不知道你现在在哪里,就无法计算路线。同样,如果我们想要到达电机的一个特定位置,我们需要知道电机轴现在在哪里。为此,我们使用电位计。电位计基本上是一个可变电阻器,它本质上有一个轴,当旋转时,会改变电阻的值。可变电阻器看起来是这样的:
当电阻器的值改变时,电阻器的输出电压也会改变。有趣的是,如果电位器的输入电压是已知的,那么可以从它的输出电压推断出轴的位置。让我们看看如何:
现在,让我们假设在 0 度的位置,电位计的输出电压为 4.8V;当我们将其移动到 90 度时,值变为大约 3.2V,当完全旋转 180 度时,由于电阻的变化,电压降低到仅有 2V。
不必真正查看电位计的轴,我们可以轻松地推导出,如果电阻的电压输出为 4.8V,那么轴必须位于 0 度的位置。同样,如果我们说电压为 3.2V 时它位于 90 度,当电压为 2V 时它位于 180 度。
在这里,我们只画了三个点,但对于电位计上的任何给定点,都会有一个非常特定的电阻与之对应。通过这个我们可以精确地计算出电位计的轴将位于何处。现在,让我们将其放入一个有趣的组合中:
现在我们所拥有的是一个通过多个减速齿轮与电位计耦合的电机,这将降低电机的速度并增加扭矩。进一步地,在最终齿轮上,一个轴向外安装到机身并与电位计耦合。
所以正如你所学的,电位计能够感知输出轴指向的角度。然后电位计连接到一个控制电路,该电路读取电位计的读数并进一步指导电机移动多少以达到目标位置。由于这种闭环安排,控制电路知道轴的位置,它可以计算出需要移动电机多少才能达到目标位置。因此,这种安排能够精确地将输出轴转到任何给定位置。
这种安排通常被称为伺服电机。在整个机器人行业中,它是控制精确运动最广泛使用的硬件之一。本质上,有三个电线进入控制电路——VCC、地线和信号线。信号线将接收来自我们的 Raspberry Pi 的数据,并在接收后执行必要的电机运动,使轴达到期望的位置。以下是伺服电机的一张图片:
这些可以从极其便宜的价格开始,大约 4 到 5 美元,但它们可以高达数千美元。但真正决定这些伺服电机价格的是什么?在选择伺服电机时,我们需要考虑几个因素,但其中最重要的是扭矩。
扭矩基本上是电机通过它来转动输出轴的旋转力。这通常以 kg·cm 或 N·m 来衡量。那么这实际上意味着什么呢?让我们看看以下图示:
假设在前面的图中,我们有一个扭矩为 10 kg·cm 的电机,与之连接的转子直径为 1 cm。因此,它应该能够垂直地从地面拉起 10 kg 的重物。然而,当我们改变转子的半径到 2 cm 时,可以提升的重物重量减半。同样,如果半径增加到 10 cm,那么可以提升的重物重量将减少到 1 kg。所以基本上,可以提升的重物重量将是扭矩/半径。
但对于我们大多数目的来说,我们不会使用之前展示的机制,所以让我们看看下一张图,看看如何进行计算:
现在,假设我们有一个长度为L的轴和一个位于轴端部的载荷。为了便于计算,我们会认为轴的重量可以忽略不计。现在,如果伺服电机的扭矩为 100 kg·cm,轴的长度(L)为 10 cm,那么通过简单的计算,我们可以提起的载荷将是 100/10 = 10 kg。同样,如果长度增加到 100 cm,可以提起的载荷将减少到仅仅 1 kg。
好吧,我们已经对伺服电机有了一定的了解。现在的问题是,我们如何控制伺服电机?正如我提到的,有不同类型的伺服电机,可以通过不同的方式来控制。然而,最常用于业余目的的是数字伺服电机。这些伺服电机需要PWM,根据 PWM 的占空比,轴的角度会发生变化。那么,让我们看看这是如何发生的。
通常,这些伺服电机大多数的频率为 50 Hz。所以,基本上每个脉冲的长度将是 1/50 = 0.02 秒,换句话说,20 毫秒。此外,可以提供给这些伺服电机的占空比可以是 2.5%到 12.5%,这意味着脉冲宽度为 0.5 毫秒到 2.5 毫秒。现在让我们看看它是如何工作的:
如你所见,当给定 2.5%的占空比时,轴下降到 0 度的最小位置,当占空比增加到 7.5%时,轴移动到中间位置 90 度。最后,当占空比增加到 12.5%时,轴移动到最大位置 180 度。如果你想要任何介于两者之间的位置,那么你可以简单地选择相应的 PWM,它将改变伺服电机的位置到期望的角度。
但你可能想知道,如果我们想将其超过 180 度呢?嗯,这是个好问题,但大多数数字伺服电机只提供 180 度的旋转范围。有些伺服电机可以完全旋转其轴,即 360 度;然而,它们的地址方式略有不同。在本章之后,你可以查看任何数字伺服电机的数据表,并按照你想要的方式控制它。
好了,理论就到这里;是时候做一些有趣的事情了。所以,让我们动手设置硬件,用手直接控制伺服电机!按照以下方式将伺服电机连接到树莓派:
线的颜色编码如下:
接下来,我们需要上传以下代码并看看会发生什么:
一旦运行这个程序,你会看到伺服电机的轴从左到右移动,以 0 度、45 度、90 度、135 度和最终 180 度的步长进行移动。
让我们看看在程序中我们做了什么来实现这一点:
同样的循环正在使用不同的 PWM 值 5%重复,这将使轴旋转到 45 度,7.5%对应 90 度,10%对应 135 度,12.5%对应 180 度。这是一个非常简单的程序,将帮助我们了解伺服电机的基础知识。
到现在为止,你已经学会了如何控制伺服电机,并使其按照我们想要的方向移动。现在,让我们更进一步,稍微修改一下代码,使伺服电机运行得更平滑:
当你在你的 Pi 上上传这段代码时,你会注意到伺服电机从左到右非常平滑地滑动,然后又从右到左。我们做了一个非常简单的技巧;让我们看看它是什么:
在这里,我们正在运行一个循环,它将一直运行到i<=12.5的值,正如我们在程序中之前定义的那样,i的值在程序开始时被设置为默认的2.5。之后,每次代码运行时,占空比被设置为I的值,程序暂停 0.1 秒,然后i的值增加 0.1。这是增加 PWM 的占空比。一旦值达到 12.5,循环就会退出。
我们拥有的整个 PWM 范围是 2.5%到 12.5%,因此我们有 10%的空间可以操作。现在如果我们将其映射到伺服电机的角旋转,那么每个百分比的 PWM 对应于 180/10 = 18 度的变化。同样,每 0.1%的变化将导致 1.8 度的变化。因此,每 0.1 秒,我们通过增加 0.1%的占空比,或者说,我们通过增加 1.8 度来增加角度。因此,我们发现这个动作非常平滑。
我们在程序的下一部分做了类似的事情;然而,我们是在做反向运动。
好吧,我们非常确定如何使用伺服电机,并按照我们的需求进行有控制的运动。现在,是时候向前迈进,了解我们将大量使用的另一个概念了。它被称为数组。如果你在其他任何语言中编程过,你一定很熟悉它。但我们需要了解它的一些基本概念,这将使我们的生活变得更加容易。所以,让我们开始吧。
首先,最重要的是。在 Python 中,数组并不被称为数组,而是被称为列表。列表基本上是一种可以同时存储多个元素的数据结构。唯一的限制是元素必须是相同的数据类型。例如,如果你存储整数,那么所有的值都应该为int。同样,如果你存储字符,那么列表中的每个元素都应该为char。要定义一个列表,你所需要做的就是命名列表,就像我们通过myList所做的那样;列表的名称可以是任何名称,接下来我们需要告诉编译器它实际上是一个列表。要做到这一点,我们需要在方括号内放置值。它看起来会是这样:
有一个需要注意的事情是,每个值都应该用逗号分隔。每当我们想要通过调用它们的索引号来引用列表中的任何单个元素时,我们都可以简单地这样做。这是基于元素在列表中的位置。Python 列表中的索引值从 0 开始。所以根据前面的声明,索引 0 的值将是14,索引 4 的值将是9。现在,当我们需要在程序中打印这些元素时,我们需要编写以下代码:
一旦我们写下这段代码,程序将打印列表中第二个元素的值。在我们的例子中,它将是35。
现在,这是访问列表元素的一种方法;我们也可以按反向顺序访问它。所以,假设你想访问数组的最后一个元素。然后,我们可以编写以下代码:
这段代码将返回数组中最后一个元素的值。现在,每当我们使用列表中的负值时,它将按反向顺序开始索引。所以,如果我们输入print myList[-2],这将给出数组中倒数第二个元素的值。在这个整个方案中,有一点需要记住的是,编号将从 0 开始,而当我们从反向开始时,编号将从-1 开始。
如果你了解正确的工具,Python 真的很有趣,也很简单。Python 的开发者包括一些非常有用的函数,这些函数可以用于列表。所以,让我们去探索它们吧。
第一种方法是向数组中添加元素。为此,我们使用一个名为append()的函数。append()函数的作用是在数组的末尾添加值。所以,写下以下代码:
这将会在myList的末尾添加元素45。所以现在列表将如下所示:
很简单,不是吗?但是,如果你想在列表中间添加一个元素怎么办?显然,开发者不会让你干瞪眼。他们已经包括了一个用于此目的的函数;它被命名为insert(index, element)。现在,每当你使用这个函数时,你需要确保你提到你想放置这个元素的位置的索引,其次,你想放置的元素。所以它看起来像这样:
当你使用这个函数时,数组将看起来如下:
显然,每当开发者提供了一个添加元素的功能时,他们肯定也提供了一个删除元素的功能。但技巧在于你可以有两种方式来做这件事。首先,是常见的方式。我们简单地选择索引号并删除它。我们现在就要这么做:
现在这将做的是删除数组的第二个元素,所以执行这个操作后,数组将看起来像这样:
但现在这里有一个真正的技巧;你也可以通过简单地指定元素来删除元素。这就是它的做法:
现在你这么做的时候,它会找到列表中元素9的位置并将其删除。所以你不必关心元素在哪里;这个函数会说,我会找到你,然后我会消灭你!
好了,那么就不再引用电影台词了。我们可以谈论许多其他我们可以在列表上使用的函数,但我们现在所做的是足够的。随着需要,我们将看到其余的。但现在让我们在机器人领域更进一步。你可能已经看到许多自动驾驶汽车顶部的旋转物体。量产汽车通常不太可能配备它,主要是因为它的价格高昂,但研究目的的汽车总是装备有它。
那这个设备是什么呢?它被称为LIDAR;它是光探测与测距的缩写。我知道这是一个糟糕的缩写。LIDAR 之所以非常普遍,是有原因的。它以非常精确的方式为其周围区域提供距离读数。然而,如果我们为项目购买它,可能会有些过度,因为一个好的 LIDAR 可能要花费你接近 500 到 10000 美元。如果你仍然认为它在你的预算范围内,那么你将会非常幸运!但对于那些不想购买的人来说,我有一个好消息要告诉你。今天,我们将要自己制作一个 LIDAR 扫描仪。因此,为了制作一个区域扫描仪,我们需要一个伺服电机,我们将在这个伺服电机制作我们的红外接近传感器。现在为了做到这一点,我们需要一个稍微临时性的安排。你可以拿一张纸板,像我们在图片中展示的那样固定它,或者你也可以使用一个直角铝材并钻孔来固定组件,如果你想做得更专业的话。有一点要记住的是,传感器必须正好平行于地面,不能向上或向下。
一旦完成安装,那么就是连接其余硬件的时候了。所以按照以下图示连接硬件:
好的,那么让我们看看这个设备能做什么,准备好并上传这段代码:
代码做了什么?如果它运行正常,那么它应该返回整个 180 度的扫描读数,分为 10 个均匀的步骤。试试看,然后回来看看实际上发生了什么。
现在大部分代码都是基础的,你也应该已经对这段代码的实际功能有了了解。然而,让我们更深入地探讨一下,看看具体细节:
我们声明了两个列表:distLR,用于伺服电机的从左到右滑动的距离,以及distRL,用于伺服电机的从右到左滑动的距离。你可能想知道为什么这些括号中没有内容。声明一个空数组是完全正常的。它们最初不需要有值:
在这一行,我们只是简单地重置了i = 0和k = 0的值,以便下一次循环:
这可能对你来说有点新。当我们在一个括号内使用冒号时,这基本上意味着整个数组的所有元素都将被删除:
在这段代码中,发生的事情与我们在从左到右滑动时所做的相同;唯一的区别是我们将其保存到一个名为distRL的新列表中,滑动从 12.5%占空比开始,到 2.5%结束:
当我们打印完所有值后,我们再次重置i = 1、k = 2.5和j = 12.5的值,这样我们的第一个循环就可以无缝地继续下去。同时,我们也确保distRL列表中没有留下任何东西。
所以我们的代码就是这样工作的,简单直接!
记得我们上次制作的自动驾驶汽车吗?那很酷,而且肯定是你可以向朋友炫耀的东西。然而,我们现在要做的肯定比我们迄今为止所做的一切都要酷。
我们将把这个区域扫描仪安装到我们的机器人车辆上。但是等等,我们之前不是已经用同样的传感器扫描过区域,并将车转向其他方向了吗?我们确实这样做了,而且效果很好,几乎是完美的。我敢打赌,有时候它可能没有你想象的那样准确。但这不是真正的问题。主要问题是它不够流畅。它必须在检查空间时停下来,然后向任意方向移动。我们现在要做的是更进一步的事情。所以在做更多解释之前,让我们先制作这个新的机器人车辆,然后由你来评判它是否更酷。
因此,为了制作它,你需要将区域扫描仪安装到车辆上。建议你在车辆的前端设置它,并确保伺服电机的臂能够旋转 180 度。你可以使用我们之前固定红外传感器的类似方法来固定它。在你做所有这些的时候,尝试使用电缆绑带来确保电缆不会乱糟糟的,并确保为轴和其上的传感器的移动留出一些余量。这些电缆绑带可以让你的生活变得非常简单。一旦我们全部设置好,你应该使用 ADS1115 将红外接近传感器连接到树莓派,然后按照以下图示连接电机驱动器:
完成后,请上传以下代码:
呼!这可不短啊,不是吗?但请相信我,虽然可能很长,但并不难。那么,让我们看看这段代码在做什么:
这些东西可能对你来说看起来相当新颖。尽管不是。我们正在做的是定义哪个引脚编号将以什么 PWM 频率运行。此外,我们还为所有用于电机控制的 GPIO 引脚命名。那么,既然我们在做所有这些,为什么我们突然开始给电机驱动器提供 PWM 信号呢?我们不是对简单地提供高脉冲感到满意吗?
答案非常直接。通过使用 PWM,我们在前面的章节中能够改变 LED 的亮度。同样,通过改变电机驱动器的控制引脚的 PWM 输出,我们不仅可以定义旋转的方向,还可以定义旋转的速度。这一切都是通过 PWM 完成的。所以,假设引脚编号20正在以 50%的占空比接收 PWM 信号。这意味着连接到它的电机将获得电机驱动器接收到的输入电压的一半。因此,现在我们不仅可以控制电机旋转的方向,还可以控制旋转的速度:
在这个语句中,我们定义了一个名为 direction(index) 的函数。这个函数的作用是比较索引的值,并根据它来给电机供电。比如说,索引是 0。在这种情况下,左侧的轮子会向相反方向移动,而右侧的轮子也会向相反方向移动,这将使机器人围绕其轴旋转。
在下一个语句中,我们编写了一个 elif 语句,所以如果 else 语句不成立,它将检查主体中的其余 else if 语句。在 direction(index) 的整个定义中,有四个 elif 语句,这意味着它将检查每一个,并根据参数的值执行相应的活动。在这种情况下,它是索引。此外,还有一个最终的 else 语句,如果所有情况都不成立,则会执行。因此,根据该语句,它将调用一个停止函数。这将使车辆停止:
这一行非常有趣,因为我们使用了列表的另一个有趣的部分。所以,使用 max() 方法,我们可以在列表中找到最大的值。因此,在这一行中,我们只是简单地找到最大值并将其放入名为 max_dist1 的变量中:
列表的美妙之处似乎永远不会结束。在这一行中,我们使用了另一种名为 index() 的方法;此方法为我们提供了列表中值的索引。因此,我们可以知道值在列表中的位置。因此,在这一行中,我们证明了 max_dist1 的价值。index() 方法搜索索引号并将该值存储到名为 max_dist1_index 的变量中:
既然我们已经定义了函数 Direction(),现在我们所做的就是调用该函数以决定向哪个方向前进。然后,启动你的车辆并看看它们的驾驶表现如何,别忘了拍摄视频并上传到网上。
玩得开心!
专业激光扫描仪非常昂贵,因此,在本章中,我们决定自己构建一个替代品并将其安装到我们的车辆上。在下一章中,我们将涵盖诸如视觉处理、目标检测、目标跟踪等内容,这将使我们能够进行基本的视觉处理,并使汽车朝着特定物体(如球)的方向移动。
真是令人惊叹,我们能够通过简单的传感器和执行器组合做到的事情。毕竟,这些就是让机器人成为我们所知的样子的事物。如今市场上可供我们使用的传感器数量庞大。我们可以在机器人上安装数百个传感器,但有一件事能够超越所有其他传感器的力量。那就是相机。传感器为我们提供了有关物理环境的数据,而相机则让我们能够看到它。因此,这是一个相当重要的传感器,值得我们考虑。在本章中,我们将继续探讨如何将相机连接到我们的树莓派,并开始使用我们获取到的图像数据进行计算。
本章将涵盖以下主题:
图像的基本知识
为视觉处理应用准备树莓派
安装 OpenCV
图像识别
当我们提到相机时,我们并不一定将其视为传感器,尽管它确实是一种。然而,它与到目前为止我们所研究的所有传感器都略有不同。大多数传感器都由一个感应元件组成,例如接近感应、温度感应或被动红外感应。而使用相机,我们所做的是捕捉光线;现在,我们不再只有一个光传感器,而是有多个,实际上是在单个芯片上组成了一个百万级别的传感器阵列。但故事还没有结束,这些传感器不仅捕捉光线,还能捕捉不同强度的光线;它们可以感知颜色光谱及其相对亮度。执行这种功能的每个感应元件都被称为像素。大多数这些像素生成 RGB 值,这不过是红色、绿色和蓝色颜色的强度。为什么是 RGB 呢?这些是原色,以正确的比例混合这些颜色会产生不同的颜色阴影。
因此,如果我们有这些颜色的值,我们可以重新创建我们想要的颜色。正如我们之前所理解的,任何相机中都有数百万个这样的像素,构成了图像感应芯片的长度和宽度。这些图像感应元件的数据构成了图像。到目前为止,一切都很顺利。但我们为什么要谈论这个?原因在于,这就是相机看待世界的方式,而不仅仅是我们的视角。生物视觉完全是另一回事。因此,我们需要了解相机的工作原理以及它提供的原始数据,以便进行视觉处理。
现在,让我们回到像素及其数据。由于有数百万个这样的像素,因此必然有数百万个像素的读取值,这些读取值将以 RGB 值的形式出现。现在,这些数据必须以特定的形式排列,以便理解我们正在处理哪些像素数据。如果它们没有排列好,那么这些数据将没有任何意义。
如你之前所学,数组/列表是存储大量结构化数据的最佳方式,如果你看图像数据,那么它不过是单个结构化像素的 RGB 值。太棒了!所以,现在我们知道我们正在处理什么以及我们如何处理它。或者,我们真的知道吗?
如你所回忆,我们在这里讨论的图像是由数百万个像素组成的。因此,像素数据也将达到数百万级别。这些数据极其庞大,因为我们不仅谈论数百万个像素,还包括每个 RGB 值。换句话说,每个单独的像素都会给我们三个值,即红色、绿色和蓝色的值。换句话说,这是一大批数据。普通的列表无法处理这么多数据;因此,我们必须安装一个名为NumPy的特殊库来处理这些庞大的数组。在本章中,我们将需要安装和更新多个依赖项,以便 Raspberry Pi 能够进行视觉处理。我们将看到如何安装它。
现在继续前进,我们以非常结构化的方式拥有每个单个像素的数据,但我们究竟要做什么呢?好吧,正如你在前面的章节中学到的,当我们有结构化的数据时,我们可以进行多项操作,就像我们在上一章的列表中做的那样。我们正在处理的数据在某种程度上属于特定领域。也就是说,我们知道我们将接收到的数据将是图像数据。因此,有一些非常特定的库被制作出来以轻松完成这些任务。最常用的视觉处理算法之一是 OpenCV。它被广泛使用,并且这个库中有多个非常具体的功能,专门用于处理图像数据本身。所以,通过一个命令,你应该能够捕捉图像,通过另一个命令,我们将能够在图像上应用过滤器。不是 Instagram 的过滤器,而是将图像中的特定类型对象从其他对象中分离出来的过滤器。
最后,我们还将能够以所需的图像格式输出数据。因此,OpenCV 能做什么确实非常有趣。但请记住,这只是一个入门课程。OpenCV 本身可以是一个整本书的主题,但在这里,我试图给你们所有人一个关于视觉处理的预览。你们将学习基本命令以及如何使用它来制作项目。
现在是时候停止谈论并开始动手了。那么,让我们看看我们都需要做什么。
你首先需要确保你有足够的时间,也许还需要一些爆米花来打发时间。因为我们即将开始的过程可能会稍微长一些,可能需要几个小时。如果你有慢速的互联网连接,你甚至可能需要一整天。还有一点需要注意,随着时间的推移,软件和硬件两端可能会有各种新的更新。这可能会意味着一些命令可能无法工作或者可能需要一些修改。在这种情况下,搜索一下 Google 可能会有所帮助。所以,如果第一次尝试没有成功,不要担心。说了这么多,让我们开始准备你的 Raspberry Pi,确保我们为视觉处理准备好了所有先决条件。首先,我们需要为视觉处理应用准备我们的 Raspberry Pi。为了开始,确保你始终连接到互联网,一旦这个选项被勾选,然后继续在你的 Raspberry Pi 上打开终端。一旦你打开了终端,你需要输入以下命令:
因此,第一条命令会下载你的 Raspberry Pi 操作系统的更新,而第二条命令将会继续下载你的 Raspberry Pi 操作系统的升级。你明白我的意思了吗?不明白?让我再详细解释一下。
你的 Raspberry Pi 操作系统有多个用于执行不同活动的软件包。随着时间的推移,这些软件包会添加新的功能,所以当你更新操作系统时,你实际上是在更新你操作系统上软件包的功能。这是其中之一,有时也会向你的 Raspberry Pi 操作系统添加新的软件包。它们可能是为了各种附加功能。所以升级会在你的操作系统上添加新的软件包。
你可能想知道为什么这一切都是必要的?好问题。我们将使用多个库来使我们的代码工作。现在,制作这些库的开发者希望他的库比以往任何时候都要好,为了做到这一点,他使用了 Raspberry Pi 的最新软件包。所以现在当你尝试使用开发者的库时,有很大可能会需要最新的软件包和更新才能运行。因此,每隔几周更新和升级你的操作系统总是一个好主意。
到目前为止,我们只下载了那些更新和升级。我们实际上并没有安装它们。所以为了安装它们,我们需要输入以下命令:
完成这些后,你可能想重启你的 Raspberry Pi,以确保一切都能顺畅运行。为此,你可以手动重启它,或者你可以通过以下命令来采取专业的方法:
完美,所以现在我们的系统已经是最新的。现在我们需要安装一些特定于图像处理的包。没有这些包,进行任何视觉处理都会非常困难。这些单独的命令可能需要一些时间来执行,但请耐心等待,下载它们,然后我会解释这些都在做什么。所以,不要耽搁,让我们继续一个接一个地安装这些包:
完成后,你的系统将准备好所有由操作系统要求的进行视觉处理的前置条件。现在,让我们了解每个工具的作用:
sudo apt-get install build-essential git cmake pkg-config:这一行正在安装你可能需要的所有开发工具。
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev 和 sudo apt-get install libxvidcore-dev libx264-dev:在这一行中,我们正在安装视频包和编解码器。这将确保系统准备好 I/O,以便我们可能稍后向其投递的视频。
sudo apt-get install libjpeg8-dev libtiff5-dev libjasper-dev libpng12-dev:正如我们为视频安装了包,我们也为图像做了同样的事情。
sudo apt-get install libgtk2.0-dev:我们正在安装一个名为gkt的开发库子包。很明显,当我们处理一些图像时,我们也可能希望将其输出,可能是以图像和简单 GUI 的形式,这个库告诉我们如何做到这一点。
sudo apt-get install libatlas-base-dev gfortran:视觉处理是一个 CPU 密集型任务,当我们处理的东西不像超级计算机那么强大时,我们必须确保我们没有浪费任何 CPU 性能。为了做到这一点,我们需要优化整个过程,而这个库帮助我们做到这一点。
sudo apt-get install python2.7-dev python3-dev:现在,在这一行中,我们正在安装 Python 2.7 和 Python 3 头文件。这是必要的,因为没有它们,我们就无法编译我们的代码。
现在,在所有前面的命令中,我们已经安装了视觉处理或换句话说,我们将要使用的 OpenCV 的依赖项。现在,我们需要准备 Python 的环境,以便我们可以运行 OpenCV。继续,打开终端,并输入以下命令:
现在,让我们看看我们做了什么:
cd~: cd 代表 更改目录,而 ~ 符号代表主目录。所以基本上,我们正在切换到主目录,并且从现在开始要做的事情将在主目录本身完成。
完美!我知道这需要很长时间,这确实是一项任务。但这是不可否认的必要事情,没有它,我们就无法继续在我们的系统上进行视觉处理。所以现在我们已经完成了所有前面的任务,我们需要设置 Python。让我们继续做,然后我们会看到为什么这是必要的:
现在,让我们看看这是关于什么的:
sudo pip install virtualenv virtualenvwrapper: 这个命令非常有趣。这个命令正在安装一个名为 virtualenvwrapper 的东西。现在,这是一个扩展,允许我们在虚拟环境中使用程序。为什么这是必要的?好吧,当我们谈论运行具有各种先决条件的各种程序时,这是一个救命稻草。所以,这通过在已经准备好所有先决条件的虚拟环境中运行所有程序,帮助我们让所有程序运行得很好。
sudo rm -rf ~/.cache/pip: 我们只是使用这个命令来清除缓存。
现在,在我们完成这些之后。我们不得不打开 .profile 文件。为了做到这一点,我们需要输入 sudo nano ~/.profile 命令。这个命令将打开配置文件,然后一旦打开,就在文件的末尾复制以下命令:
为什么我们要这样做?基本上,终端需要知道虚拟环境在哪里,通过这样做,终端就会知道它。现在,再次启动终端;我们还需要加载一些其他的东西。所以,这就是我们需要做的:
现在你完成了所有这些命令后,让我们看看它们意味着什么:
source ~/.profile:这是为了确保一切设置就绪。
mkvirtualenv cv:这是创建一个名为cv的新环境,所有程序都将在这里运行。
source ~/.profile:现在,因为我们创建了一个新的虚拟环境,我们需要再次确保一切设置就绪,为此,我们需要再次输入这一行。
workon cv:这个命令将告诉系统我们正在使用我们刚刚通过名称cv创建的虚拟环境。
pip install numpy:这是一个非常重要的步骤;numpy是一个帮助我们管理大型多维数组的库。
在执行了所有前面的命令后,安装 OpenCV 的时刻终于到来了,我们需要使用以下命令:
好的,那么我们在这里做了什么?让我们看看:
workon cv命令很简单;我们告诉 Python 我们必须在一个名为cv的虚拟环境中工作。
使用cd ~/opencv-3.4.0/命令,我们更改目录并移动到已安装的 OpenCV 文件夹中。
在mkdir build命令中,我们创建了一个名为build的新文件夹。
在cd build命令中,我们再次使用了基本的cd命令。这是将目录更改为我们刚刚通过名称build创建的目录。
下一个命令是cmake。它所做的就是编译它被告诉编译的内容。所以,在接下来的整个命令中,我们正在将各种设置安装到我们的 Python 中。
我们已经开始编译各种设置,现在下一个命令相当重要。让我们看看它是什么:
现在,这个命令告诉树莓派在视觉处理时需要使用多少核心。本质上,树莓派总共有四个核心,所以通过输入命令-j4,我们告诉 Pi 我们将使用所有四个核心。显然,你可能会想,总是使用我们所有的马力是个好主意。但那绝对不是事实。因为如果我们使用所有核心,那么它可能无法执行任何其他任务。甚至无法接收用户的命令。所以,如果你想做一些同时任务,比如说将视觉处理数据应用到其他事情上,那么可能就不可能,因为你已经在处理过程中耗尽了所有的 CPU 计算能力。然而,现在并不是这种情况。因此,为了节省时间,我们将使用所有核心。
现在终于到了安装 OpenCV 的时候了。那么让我们看看我们该如何做:
如你所猜,sudo make install正在编译 OpenCV,而sudo ldconfig创建缓存并链接到重要的和最近共享的库。
好吧,你认为我们已经完成了。不,我们需要完成安装过程,但还有一些额外的步骤。这是最后一步,所以请再耐心一点,我们应该没有问题。以下是你需要执行的步骤:
由于你已经安装了所有前面的步骤,现在 OpenCV 应该安装在这个目录部分。我们使用ls命令,该命令用于列出特定位置的目录。一旦完成,系统将返回以下截图所示的输出:
最后,一旦完成这一步,我们需要验证到目前为止我们所做的一切是否都已完成成功。为此,我们只需运行以下命令:
每次我们需要在 OpenCV 上工作时,都会使用workon cv命令,所以一旦这个命令执行完毕,然后python命令将打开 Python 库。如果在任何情况下 Python 库没有正确安装,它将返回一些错误,这意味着步骤没有成功,在这种情况下,您将需要重新安装 OpenCV。如果一切正常,它将返回以下截图所示的详细信息:
哇哦!最后,我们完成了安装。现在我们一切准备就绪,可以开始在树莓派上进行视觉处理。
现在,为了继续进行视觉处理,让我们将摄像头连接到树莓派。一旦完成,你需要编写以下代码:
在你实际编译此代码之前,让我告诉你我们到底在做什么:
在上一行中,我们导入库numpy为np,换句话说,我们已经导入了库,每次需要调用时我们只需写np:
在上一行中,我们告诉树莓派从特定端口捕获视频。现在你可能想知道我们如何知道它连接到哪个端口?
关于端口,与 GPIO 不同,它们不是硬件依赖的,而是软件分配的。所以如果你的摄像头是第一个连接到 USB 端口的设备,那么它很可能连接到端口0。
在这个例子中,我们只添加了一个 USB 摄像头作为硬件;因此,我们可以非常确信它只会在端口0。
现在命令不仅用于端口选择。它的主要工作是捕获摄像头视频。现在,每次我们需要通过我们给定的名称cap而不是整个函数本身来调用它:
在这一行,我们使用库 cv2 和该库的一个名为 imshow() 的函数。这样做的作用是显示由相机捕获的图像。现在继续前进,我们有两个参数正在传递,那就是,“帧”和 image。帧是显示捕获图像的窗口的名称。进一步来说,我们有第二个参数 image。现在,正如我们记得的,我们已经将图像存储在名为 image 的变量中。所以,它将直接显示我们在上一行中已经存储的内容:
在这一行,我们正在做一件令人惊叹的事情。cvtColour() 函数是一个转换器。它转换什么?众所周知,图像是由二维像素数组组成的。这个函数所做的就是将我们的相机给出的值转换成用户期望的值。
让我更详细地解释一下。我们使用的相机给我们提供 RGB 图像。所以我们可以看到的是三种颜色的亮度混合。现在,为了我们的检测,我们希望将其转换为色调、饱和度和亮度。你可能会问,为什么我们要这样做,首先,这使我们的任务变得非常简单。所以为了理解这一点,让我们看看色调、饱和度和亮度是什么。
色调基本上代表我们正在谈论的颜色。每个色调值代表一种特定的颜色,饱和度是颜色的强度。所以饱和度越高,颜色越深,饱和度越低,颜色越淡。最后,亮度——这个术语可能会让人困惑;这基本上意味着图像中有多少黑色。所以让我给你一个粗略的例子:
现在第一张图像显示了色调:100,饱和度:100,和亮度:100。因此,黑色是零,颜色是绿色,饱和度是 100%。在随后的图片中,你可以看到当饱和度保持在较低百分比时,颜色已经变淡。最后,在下一张图像中,当亮度降低时,颜色变得非常暗。
所以现在回到要点,为什么要使用色调、饱和度和亮度?现在为了检测任何颜色,我们只需要一个单位,而不是形成该颜色的三个不同单位,这使得工作变得更简单。还有其他各种原因要做这件事,但在这个时候,这并不是我们关心的。
在这一行,我们正在提供需要检测的上限和下限值。如您所见,我们正在检测绿色;因此,我们将为两端提供上限和下限值。如果您想更改要检测的颜色,那么您只需更改此值,工作就会完成:
现在,我们正在将落在该颜色范围内的对象分离出来,并将值赋予一个数组。这是通过一个名为inRange()的函数部分完成的。因此,我们需要传递三个参数。首先,它需要处理哪张图像,它需要检测的下限值是什么,以及我们提供的上限值hsv、lowerGreen、upperGreen。这个结果将是一个数组,它将具有所有颜色变为黑色的值,而特定颜色范围内的颜色将以纯白色显示:
bitwise_and是cv2库中的一个函数;它所做的只是对数组中的两个值进行逻辑与操作。我们传递的参数是image和带有mask的图像,或者换句话说,我们传递了两个图像——一个是原始图像,另一个是mask。使用这个函数,我们结束了这两张图像。这个结果将是一个背景全黑的图像,而位于特定颜色范围内的对象将以适当的颜色显示在一张彩色图像中:
现在我们正在使用一个简单的if语句将键的值与ord('q')进行比较。ord函数的作用是接收参数并将其转换为 ASCII 值。所以,在这个函数中,如果按下q键,那么循环就会中断,程序就会退出:
使用这个函数,我们正在释放相机。因此,使这个资源空闲并准备好被其他程序使用。
在本章中,你学习了如何计算从将图像与树莓派耦合中获取的图像数据。在下一章中,你将学习如何制作一个可以进行面部识别的守护机器人。
我相信你一定看过电影《我,机器人》或《超能陆战队》。看完电影后,很多人会对制作一个能够保护你的机器人产生兴趣。然而,最先进的安全系统几乎不能被归类为机器人。在本章中,我们将进一步探索视觉处理领域,制作一个守卫机器人。它的目的是守卫你的大门,如果陌生人来到门口,它会开始触发警报。然而,有趣的是,如果熟悉的人回家,机器人不会触发任何警报。更重要的是,它会清除道路,从门口区域退出,让你进入。一旦你进入,它将自动回到其位置,继续守卫并再次开始工作。
那会多么酷啊?所以让我们开始行动,把这个机器人变成现实。
现在,在我们继续进行人脸检测之前,我们需要告诉机器人什么是人脸以及它看起来是什么样子。树莓派不知道如何从南瓜中精确地分类人脸。因此,首先,我们将使用一个数据集来告诉机器人我们的脸看起来是什么样子;然后,我们将开始识别人脸。所以,让我们继续看看如何做到这一点。
首先,你需要安装一个名为 Haar-cascade 的依赖项。这是一个用于快速检测对象的级联依赖算法。为此,请在你的终端上运行以下语法:
这将把haarcascades文件保存到你的树莓派上,你将准备好使用它。完成之后,查看以下代码,但请在逐行查看以下解释之后,只在你的树莓派上写入:
现在,这可能会看起来像是来自我们世界之外的东西,几乎所有东西都是新的,所以让我们理解我们在这里做了什么:
这将捕获来自端口号为0的摄像头的视频。所以,每当需要捕获数据时,可以使用变量cap而不是编写整个程序。
我们已经在上一章中理解了这一行。它只是从摄像头捕获图像并将其保存在名为img的变量中,然后ret将返回 true 表示捕获成功或返回 false 表示有错误。
在这一行代码中,我们简单地使用imshow()函数给出最终输出,这个输出将包含我们刚刚绘制的矩形覆盖的图像。这将表明我们已经成功识别了图像。'img'参数将告诉窗口的名称,第二个img将告诉函数需要显示哪张图片。
这一行代码只是在等待用户按下q键。当用户按下q键时,if语句就会变为真,从而打破无限循环。
现在,运行代码并查看它是否能够检测到你的脸。祝你好运!
好的,我们已经通过几行代码检测到了人脸,但我不会认为这是一个很大的胜利,因为我们是在用其他开发者制作的剑在战斗。导入的学习集是一个通用的面部学习集。然而,在本章中,我们将继续创建我们自己的学习集来识别特定的人脸。这真的非常酷,我相信你会喜欢做这件事的。
那么,让我们开始吧。就像你之前做的那样,先通过解释来理解,然后再编写代码,这样你就能很好地理解它。
首先,我们使用程序来捕捉需要检测的物体的图像。在我们的案例中,这个物体将是一个人和他的脸。那么,让我们看看我们需要做什么:
这里是解释:
如你之前所见,我们使用前面的两行代码来导入学习数据集并启动摄像头。
由于我们将训练系统学习特定的面孔,程序知道它检测的是谁(无论是通过名字还是 ID)非常重要。这将帮助我们明确我们检测到的是谁。因此,为了继续通过人脸检测一个人,我们需要提供他的 ID,这在以下代码中完成:
这与我们在代码的上一部分所做的是完全相同的。如果需要解释,请参考它。
现在,这一行代码可能听起来也像是重复的;然而,它有所增加。在这个函数中,已经传递了两个参数而不是一个。第一个是grey,第二个是可以检测到的对象的最低和最高尺寸。这很重要,以确保检测到的对象足够大,以便学习过程发生。
在这里,我们使用相同的for循环来执行以下条件。因此,当检测到人脸时,循环才会为真。每次检测到人脸时,sampleNum变量会通过计算检测到的人脸数量增加1。此外,为了将图像捕获到我们的系统中,我们需要以下代码行:
它所做的是,简单地将图像保存到名为dataSet/User的文件夹中。能够自己创建这样一个文件夹非常重要。如果你不这样做,当它找不到应该保存的文件夹时,就会出问题。"+str(id)"将通过人的 ID 保存名称,并通过使用+str(sampleNum)来增加样本计数。此外,我们已经提到图像将以.jpg格式保存,最后gray[y:y+h, x:x+w]是选择包含人脸的图像部分。
从这一点开始,程序的其他部分是自我解释的,我怀疑你可以自己理解。用非常简单的英语来说,这将在一个文件夹中保存图像,并且会一直这样做,直到达到 20 张图像。
现在我们已经捕获了图像,是时候让系统学习这些图像并理解如何识别它们了。为此,我们需要安装一个名为pillow的库。安装它很容易。你只需要在终端中写下以下行:
这个pillow库将帮助我们读取数据集。我们稍后会了解更多。一旦安装了它,让我们继续看看我们是如何进行学习部分的。所以,请继续理解以下代码,然后我们就可以开始了:
看到这段代码后,你可能会想到“外星人”这个词,但当你看过这个解释后,它肯定不会是外星的了。所以让我们看看:
这一行说明了捕获的数据在 Raspberry Pi 中的存储位置。我们已经用这个名字创建了一个文件夹,它包含我们之前存储的图像。
在这里,我们正在定义一个名为getImageID(path)的函数。
在这一行,我们使用一个变量来调用int()函数,该函数将分割正在捕获的图像的路径名。我们为什么要这样做?这是为了从实际文件名中提取 ID 号码。因此,我们使用以下函数来完成这项工作:
所以到目前为止的整个解释都是定义一个名为getImageId(Path)的函数。
现在这行将返回面部的IDs值,这些值将被进一步用于训练数据集。
在这里的第一行,getImageID(path)函数将接受任何图像的路径并返回图像的Ids。然后,faces将包含图像的数组数据。
这可能有点复杂,有时也可能令人困惑。然而,一旦你做了,它就会变得简单。现在,是你继续前进并让系统学习你的面部及其数据的时候了。
现在我们已经学会了如何让我们的系统学习,现在是时候使用这些学习数据来识别面部了。所以,不多说,让我们了解这将如何完成:
在这段代码中,没有太多你可能遇到的新东西。它与我们在本章开始时使用的第一个代码非常相似。本质上,它也在做相同的工作。唯一的区别是它通过 ID 识别人。所以,让我们看看有什么不同,以及它的表现如何。
现在,大部分代码都是重复的。所以,我只会涉及到新的部分。下面是:
就像上次一样,我们在一个已识别的图像上画了一个矩形。然而,这次还需要在某个地方用文本进行叠加。所以,我们在这里选择这个程序中需要使用的字体:
这一行,预测是通过识别器的predict()函数进行的。它预测图像并返回检测到的图像的id。
现在,最后,如果id等于1,那么id的值将更改为BEN。
putText() 函数将在检测到的物体上放置文本。每个参数的定义如下:
img: 这是需要放置文本的图像。
str(id): 这是需要打印的字符串,在我们的例子中,它将打印人的 ID。
(x, y+h): 这是文本将被打印的位置。
font: 这是打印文本的字体。在我们的例子中,它将是之前定义的字体值。
2: 这是字体大小,也就是说字体的大小。这可以类似于放大。
(255,0,0): 这是字体的颜色。
1: 这是字体厚度。
使用这个程序,我们可以找出学习集是否按照我们的要求工作。一旦你写好了代码,试着用它来识别人,看看它是否准确。如果准确性不满意,那么你可能需要为学习选择超过 20 个样本。进行几次试验,我确信你很快就能达到完美。
现在我们已经了解了学习是如何工作的,以及如何使用学习数据来识别人,现在是时候将其付诸实践了。正如章节的名称所暗示的,我们将使用这项技术制作一个守卫机器人。现在,让我们看看下面的程序。在你开始编程之前,拿出你之前有的机器人车辆,并像我们在第六章中做的那样建立连接,蓝牙控制机器人车。连接电机、电机驱动器和树莓派。一旦你完成了这些,然后编写以下代码。这段代码利用了本章前面程序的所有学习内容,我们能够根据视觉处理区分入侵者和房屋居民。所以,让我们开始吧:
就像回顾一样,我们定义了两个函数,分别是backwards、reverse和stop。这些函数将帮助我们将车辆移动到我们想要的方向。
在这段代码中,我们正在识别面部并根据它做出决策。正如我们之前所做的那样,如果检测到面部,程序会给出相应的 ID。然而,如果面部没有被先前学习的数据集检测到,那么就不会给出任何 ID,因为这个数据集可以检测到任何面部。因此,根据程序,如果id == 1,那么机器人车辆会向前移动,偏离路径,然后它会停止5秒并回到原来的位置。如果生成的 ID 不是1,那么蜂鸣器会开启5秒,提醒用户。
通过使用这个系统,任何被识别的人都可以被允许进入 premises;然而,如果人员没有被识别,那么就会触发警报。
在这一章中,我们学习了如何使用预学习的数据集检测对象。我们还学习了如何为特定对象创建我们自己的学习数据集。最后,我们利用所有这些学习来制作一个守卫机器人,它将利用视觉处理的力量来守护我们的家园。
到目前为止,这一定是一次史诗般的旅程!回想一下你开始阅读这本书的时候,你是否曾想过事情可以这么简单?值得注意的是,一切都是从非常简单开始的,随着对更复杂系统的需求逐渐增加,技术的复杂性也在稳步上升。回到个人计算机还不是主流的时代。那时,个人计算机仅用于商业,像 IBM 这样的公司只为商业客户提供服务。在那个时代,想要个人计算机的人只有一个选择,那就是从头开始自己组装。说实话,至少从我的角度来看,这并不难。但是,与那个时代相比,想想现在它们变成了什么样子。你有没有想过在家里组装一台电脑?这里的“组装”是指设计一切,而不仅仅是 CPU 的组装。这并不容易。
我在这里试图告诉你的是,曾经计算机是稀有的;它们并不常见,而且功能非常有限。然而,随着时间的推移和像史蒂夫·乔布斯、比尔·盖茨、惠普的休利特和帕卡德这样的人的智慧,计算机变得更加用户友好,更容易获得,成为一种受欢迎的商品。想想机器人也是一样。它们很贵;对于大多数人来说,他们用不上它们,而且在公共场合也很少见。但是,正如你所学的,为个人使用组装一台机器人并不难,再加上一些调整和像你这样的创新思维,事情可以朝着完全不同的方向发展。你可能会成为下一个史蒂夫·乔布斯或比尔·盖茨。我们需要的只是热情、激情和跳出思维定势。你可能会因为你的愿景而受到嘲笑。但请记住,每个发明家在某个时候都被认为是疯狂的。所以下次有人说你疯狂时,你可以非常确信你在进步!
好吧,我相当确信,如果你是一个机器人爱好者,那么你一定看过电影《钢铁侠》。如果你还没有看过,那么请从阅读这本书中休息一下,打开 Netflix 看看这部电影。
看过那部电影后,我有两个主要的东西想要建造:一个是钢铁侠的战衣,另一个是他的个人助理贾维斯,他会照顾他所有的需求。虽然战衣似乎是我可能需要一段时间才能完成的工作,但到那时,你可以继续为自己建造个人助理。
想象一下你的家能自己做事。那会多么酷啊?它会知道你喜欢什么,你什么时候起床,你什么时候回家,然后根据这些信息,它会自动为你做事。最好的是,这不会是你在货架上买来的东西,而是你亲手制作的。
在你做任何这些之前,我必须告诉你,你将处理高压和相当大的电流。电不是玩笑,你必须时刻小心,并穿戴所有安全装备。如果你对此不确定,那么找电工帮忙是个好主意。在你触摸或打开任何电气板之前,确保你穿着非导电鞋;同时检查螺丝刀、钳子、鼻钳、剪刀和其他工具是否绝缘良好且状况良好。戴上手套以增加安全性是个好主意。如果你未满 18 岁,那么你必须有成年人在场帮助你。
现在既然已经说到了这一点,让我们开始并看看我们有什么。
现在,这个话题非常有趣,因为你们都知道,我们的身体是以某种方式编程的。因此,我们对不同的刺激以非常已知的方式做出反应。比如当变暗时,我们的大脑会产生激素引发睡眠。一旦阳光照到我们的眼睛,我们就会醒来。至少这应该是这种情况!近年来,我们的生活方式发生了巨大的变化,这已经开始挑战这个周期。这就是为什么我们看到了越来越多的失眠病例。被闹钟叫醒当然不是自然的。因此,你永远不会喜欢早上听到闹钟的声音,即使它的音调是你的最爱。我们的睡眠周期本应与阳光同步,但如今几乎没有人通过这种方法醒来。所以,在这一章中,让我们首先制作一个智能闹钟,它将模仿我们自然醒来的方式。
由于我们处理的是高压和更高的电流,我们将使用继电器。为此,按照以下方式连接电线:
一旦你连接好,上传以下代码,让我们看看会发生什么:
好的,那么这是一个相当简单的代码,不需要太多解释。我们之前也做过一个非常类似的代码。你还记得是哪次吗?那是在前几章,我们制作一个园艺机器人时,我们需要在特定时间给植物浇水。现在它所做的一切就是检查时间,看是否是06小时且分钟数小于20。也就是说,灯光会在 07:00 到 07:19 之间打开。之后,它会关闭。
但这里有一个问题。问题是灯光会被打开,无论你是否起床,灯光都会在 20 分钟内自动关闭。这有点问题,因为并不是每次你醒来都是在 20 分钟内。所以,在这种情况下,我们应该怎么做?我们首先需要做的是检测你是否醒了。这很简单,这里不需要多说什么。如果你早上醒来,你一定会离开床。一旦你这样做,我们就可以检测到运动,这可以告诉我们自动系统你是否真的醒了。
现在,我们在这里可以做的事情非常简单。我们可以检测你的运动,并根据这个检测,我们可以决定你是否真的醒了。这似乎不是一个很大的任务。我们唯一需要做的就是添加一个运动检测传感器。为此,我们可以使用 PIR 传感器,它可以告诉我们是否检测到了运动。所以,让我们继续,在我们的系统上添加另一层传感器,看看会发生什么。
因此,首先,按照以下方式连接电路。在安装 PIR 传感器时,一定要确保它面向床,并检测其上和周围的任何运动。一旦 PIR 设置好,按照以下图示连接传感器,看看会发生什么:
完成后,继续编写以下代码:
好的,让我们看看我们做了什么。代码非常简单,但其中有一个小转折,那就是Irritation_Flag:
现在这个变量就像一个闹钟的“再睡五分钟”按钮。众所周知,当我们醒来时,有时,实际上,大多数时候,我们又会回到床上,然后醒来得晚些时候才意识到自己迟到了。为了防止这种情况,我们有了这个Irritation_flag,这个基本上会被用来检测你停止闹钟所执行动作的次数。我们稍后会看到它是如何被使用的:
我们已经准备好了整个基于灯光的闹钟,每天早上叫我们起床。然而,有一个问题。一旦它关闭,Irritation_Flag的值就会变成0。一旦变成0,无论时间是什么,灯都不会启动。因此,为了确保闹钟每天在相同的时间始终可用,我们需要将标志的值设置为任何大于0的数字。
现在在上一行,如果H != '07',那么Irritation_flag的值会是3。也就是说,当时间不是07小时时,Irritation_Flag的值会是3。
这很简单,不是吗?但我确信这会很好地确保你按时醒来。
你可以完全依赖前面的系统吗?如果你真的能控制你早上不想起床的情绪,那么,当然可以。但对于那些喜欢在按下闹钟按钮后再次回到床上睡觉的人来说,我确信你一定能找到一种方法在不完全醒来时关闭灯光。所以,在代码中,灯光会在检测到三次运动时关闭。但运动可以是任何东西。你可以在床上挥手,系统会将其检测为运动,这会违背整个目的。那么我们现在应该怎么做呢?
我们有解决这个问题的方案!我们可以使用一种方法来确保你必须起床。为此,我们将使用我们的红外接近传感器,这是我们之前在项目中使用过的,根据传感器的距离读数,我们可以检测你是否经过任何特定区域。这真的很有趣,因为你可以将这个传感器指向远离床的位置,或者也许放在浴室的门上,直到你不过那个特定的线。系统不会关闭闹钟。那么,让我们看看我们将如何做到这一点。首先,连接硬件,如下面的图所示:
一旦你完成了图示,继续上传以下代码:
头脑风暴?这段代码看起来相当复杂,有条件嵌套在条件中,还有一些更多的条件。向机器人技术问好!这些条件构成了许多机器人编程的基础。机器人必须持续观察周围发生的事情,并根据它做出决策。这也是人类工作的方式,不是吗?
那么,既然已经说到了这里,让我们看看我们实际上在这里做了什么。大部分代码与上一个版本几乎相同。主要区别出现在编程部分的中间:
在下一行,我们正在取存储在M_snooze中的那个分钟的值,并给它加上另一个5分钟。所以M_snooze的值现在增加了5:
现在,在之前使用的相同if条件中,我们放置了一个for循环,看起来是这样的:for M <= M_snooze。但这意味着什么呢?在这里,我们所做的是相当简单的。for循环内的程序将一直运行,并保持在循环中,直到我们声明的条件为真。现在,这里的条件表明,直到M小于或等于M_snooze时,条件将保持为真。正如你之前学到的,M是当前分钟值,而M_snooze是循环开始时M的值,它增加了5。因此,从开始时起,循环将保持为真5分钟:
好吧,正如你所记得的,我们已经将红外接近传感器放置在了浴室门的前面。现在,如果没有人经过它前面,数值将保持相对稳定。但是,每当有人经过时,距离就会发生变化。所以,如果从第一次读到最后的整体距离发生变化,那么我们就可以说有人穿过了红外传感器。
这听起来很酷,但为什么我们不简单地像之前那样设置一个阈值值呢?这个答案很简单。那是因为如果你需要改变传感器的位置,那么你还需要根据位置重新校准传感器。所以这是一个简单而稳健的解决方案,可以在任何地方使用:
现在我们已经得到了可以告诉我们是否有人经过它的读数。但除非我们将这些数据放在某个地方,否则这些数据将没有用处。
因此,在这里的条件if F_final > 10中,每当距离变化超过10厘米时,条件就会为真,并将Irritation_flag设置为1。
如果你回顾前面的行,你会发现灯光只有在时间在 07:00 到 07:15 之间,并且Irritation_flag必须为0时才会亮。正如这个条件一样,我们通过将Irritation_flag设置为1来使条件的一部分变为假;因此,打开灯光的程序将不会工作。
现在,让我们回顾一下到目前为止我们做了什么:
如果检测到运动,那么灯光将被关闭
另一个条件将在五分钟内为真,它将等待通过红外接近传感器检测到人体运动
如果有人在五分钟内穿过那里,那么警报将被解除,否则警报将再次开始切换灯光
真的很酷,对吧?话虽如此,让我们添加另一个来自上一个程序的功能:
你知道这是做什么的。如果你在前15分钟内没有移动,也就是从 07:00 到 07:15,那么它将每五秒闪烁灯光,迫使你醒来:
最后,我们使用条件if H != '07':。所以,每当H的值不是07时,条件就为真,这将重置Irritation_flag为0。到现在为止,你知道将Irritation_flag设置为0会做什么。
因此,最终,我们制作了我们的第一个迷你 Jarvis,它会在早上叫醒你,如果你没有按时醒来,甚至会让你感到烦躁。我希望你通过学习两个运动传感器及其在自动化家用电器中的应用,真正享受了这一章节。所以,继续前进,在家尝试一个,根据你的需求修改代码,并创造出一些真正酷的东西。接下来,我们将让我们的 Jarvis 做更多酷的事情,我们还将涵盖更多关于人体检测的激动人心的内容。
到目前为止,我们在上一章中已经了解到如何将多个条件层叠在一起以获得所需的功能。我们刚刚完成了让 Jarvis 为你工作的第一步。现在,是时候让它变得更加强大。
在本章中,我们将让它控制你家里的更多电子设备,这些设备可以在不告诉你任何信息的情况下自主控制。所以,不要拖延,让我们直接进入主题,看看我们的桶里有什么。
智能家居的一个基本功能是在你周围时为你打开灯光。这是任何系统可以为你做的最基本的事情之一。我们将从一进入房间就打开灯光开始,然后我们将使系统变得越来越智能。
所以,我们首先需要做的是识别你是否在房间里。有多种方法可以做到这一点。生命的一个重要特征是运动的存。你可能会说植物不动,但它们确实会动;它们会生长,不是吗?所以检测运动可以是检测是否有人在场的关键步骤!
这一步对你来说不会太难,因为我们之前已经接口了这个传感器。我们说的是那个古老的 PIR 传感器。所以,传感器会检测到该区域内的任何运动。如果有任何运动,Jarvis 将会打开灯光。我相信你现在可以自己完成这个任务。你仍然可以参考这里的代码和电路图:
现在请上传以下代码:
在前面的代码中,我们只是在检测到运动时立即打开灯光,但问题是它只会打开灯光,直到运动停止。这意味着什么?简单来说,只要有运动,就会保持灯光开启,一旦运动停止,就会关闭灯光。
这可能是一个想要减肥的人的好代码,但对于我们大多数人来说,这会让人烦恼。所以,让我们加入一个小循环,这是我们之前章节中使用过的,让这个功能变得更好:
因此,在这个程序中,我们所做的一切就是添加了一个 for 循环,它会打开灯光一段时间。这个时间可以通过改变变量 TIME 的值来切换。
在那个循环中还有一个更有趣的部分,如下所示:
你可能会想知道我们为什么要这样做?每当灯光打开时,它会持续 5 分钟。然后,它会关闭并等待运动发生。所以,本质上,这个代码的问题在于如果你在房间里,灯光打开后,它会持续 5 分钟检查是否有运动检测到。有可能你在 5 分钟后搜索运动时正在移动。但大多数时间,情况并非如此。因此,我们使用 PIR 传感器来检测运动。一旦检测到运动,M_final的值就会通过M_final = M_final + 1这一行增加,从而增加灯光开启的时间。
到现在为止,你必须已经意识到 PIR 传感器并不是我们用来开关灯的最理想的传感器。主要是因为,尽管运动是存在的一个最好的指标,但有时你可能根本不会移动,例如,在休息、读书、看电影等等。
我们现在该做什么呢?嗯,我们可以玩一个小技巧。记得在上一个章节中,我们使用我们的接近传感器来检测一个人是否穿过了特定的区域吗?我们在这里将植入类似的逻辑;但不是简单地复制粘贴代码,我们将改进它,让它变得更好。
因此,我们不会使用单个红外接近传感器,而是将使用两个这样的设备。安装方式如下所示:
现在很明显,当一个人从门口走到房间那边时,传感器 1在检测到人体时会显示较低的读数。然后,当他朝房间方向走时,传感器 2将显示类似的读数。
如果首先传感器 1被触发,然后传感器 2被触发,那么我们可以安全地假设这个人是从门口走到房间那边。同样,如果情况相反,那么可以理解这个人是从房间里走出来的。
现在这一点相当简单。但如何在现实生活中实现它呢?首先,我们需要按照以下方式连接电路:
完成这些后,上传以下代码:
现在,让我们看看我们在做什么。像往常一样,大多数语法都非常简单直接。最重要的是逻辑。所以,让我们按照适当的步骤来理解我们在做什么。
一旦获得了F1和F0的值,我们将计算差值以确定是否有人通过它。如果没有人在通过,那么读数几乎会相同,差异不会很大。然而,如果有人通过,那么读数将相当明显,并且该值将存储在一个名为F0_final的变量中。
现在,我们将对传感器 2执行完全相同的步骤。这里没有什么新的内容要说明;一切都是不言自明的。
一切都完成后,我们比较if Time1 > Time0。我们为什么要比较它?因为Time0是传感器 1记录的时间。如果有人进入房间,那么传感器 1将是第一个被触发的,然后是传感器 2。因此,记录的时间对于传感器 2会更大,而对于传感器 1则相对较早。如果发生这种情况,那么我们可以假设有人正在进入房间。嗯,如果有人进入房间,那么我们只需要打开灯光,这正是我们在这里所做的事情。
同样,当有人离开时,首先被触发的传感器将是传感器 2,然后传感器 1将被触发。这使得记录的Time1早于Time2;因此,每当这个条件为真时,我们就知道有人正在离开房间,可以关闭灯光。
好吧,把它安装在门附近,看看它的反应。我相信这会比我们通过 PIR 所做的好得多。享受这个过程,并尝试找出它可能存在的任何缺陷。
你是否在之前的代码中发现了任何缺陷?它们并不难找;当房间里只有一个人时,代码工作得非常出色。如果这个系统安装在一个有很多人进出的地方,那么可能会有些挑战。这是因为每当有人离开房间时,灯光就会熄灭。
既然问题已经很明显,是时候让代码变得更加完美了。为此,硬件将保持完全不变;我们只需要让代码更智能。让我们看看我们如何做到这一点:
我们所做的是一些非常基础的事情。我们声明了一个名为 PCount 的变量。这个变量是用来计算在一个房间或家中的人数。正如您可以在代码的前几行中看到的那样,我们已将 PCount 的值声明为 0。我们假设一旦开始这个操作,室内的人数将是 0。
每当满足条件 if Time1 > Time0: 时,PCount 值会增加 1。众所周知,只有当有人在室内行走时,这个条件才会为真。
同样,当一个人在室外行走时,条件 if Time1 < Time0: 为真;每当这种情况发生时,PCount 的值会减少 1。
现在我们已经开始计算房间内的人数,我们现在应用的条件是,如果 PCount 的值大于 0,则该条件会被触发。因此,当室内的人数大于 0 时,灯光将会亮起。
以非常相似的方式,如果 PCount 的值或室内的人数达到 0,灯光将会关闭。
因此,大功告成!
我们已经控制了很多灯光。现在是时候控制我们的风扇和其他空气循环系统了。当我们谈论风扇或任何其他空气循环设备时,本质上我们是在谈论电机。正如我们之前所学,电机是简单的设备,可以很容易地使用电机驱动器来控制。但是,正如您所知,当时我们是在控制直流电机。直流电机是极其简单的设备。但是当我们谈论我们的家用电器时,那么这些设备中的大多数都将使用交流电或交流电流。我假设您必须知道这是什么以及它与直流电的不同之处。
现在您知道了我们家庭中使用的电机是使用交流电工作的,您也必须考虑这样一个事实,它们的控制机制将与直流电机大不相同。您想得没错。然而,电子学的好处是,没有什么真的是困难的或复杂的。基础原理基本上是相同的。那么,让我们看看我们如何控制交流电源中电机的速度。
如我们之前所见,我们可以简单地给直流电机一个 PWM 信号,电机就会以 PWM 信号的平均电压速度运行。现在,您可能正在想这也可以应用于交流电。事实上,如果想要控制灯光或类似设备,这是可以做到的,因为这些设备在波形失真时没有太大的特性变化。然而,当我们谈论其他任何组件时,我们就会遇到一个大问题。交流波形看起来是这样的:
这基本上意味着电位在周期性地变化。在大多数家庭中,这是每秒 50 次。现在,想象一下,如果我们有一个 PWM 控制的设备,它只允许在特定间隔内让电源通过电路。那么,正弦波的不同部分就会被传输到最终的输出。
正如你在前面的 PWM 中看到的,幸运的是,PWM 信号与交流电的相位匹配;然而,由于这个原因,只有相位的正端被传输到最终的输出,而负端没有。这将给我们的负载带来严重问题,而且有很大可能性连接的设备将无法工作。
我们还有一个例子,其中 PWM 是随机的,它让波的随机部分通过。在这个例子中,我们可以清楚地看到波的任何部分都在被传输,正负端电压没有同步,这又是一个大问题。因此,我们不是使用 PWM,而是使用一些真正有趣的东西。
最常用的方法是称为相位触发控制。有时它也被称为相位角控制或相位斩波。它本质上是在相位的某些部分进行斩波,让其余的波通过。困惑吗?让我在这里展示给你:
现在,正如你所见,交流波后半部分的相位正在被斩波,并且没有通过到最终的输出中。这使得最终的输出只有整体输入的 50%。这种技术的作用是,在保持电源的交流特性的同时,还能降低整体输出电压。同样,正如你在下一张图中可以看到的,波在 75%已经通过后开始斩波。这导致输出相对较低:
现在你可能想知道,我们是如何实际进行这一操作的?这是通过一个相对复杂的电路来完成的,该电路检测波的相位角,然后打开或控制一个晶闸管,这是一个高功率的双向半导体。这导致电源在特定相位通过或停止。我们将具体电路的工作原理留到下次讨论,因为它相当复杂,并且与这本书的内容不太相关。
现在回到基本点,我们知道什么是相位斩波,我们也知道晶闸管是我们做这件事的基本设备。但问题是,我们如何使用 Raspberry Pi 来实现它。
首先,我们需要一个交流调光模块。这个模块已经包含了相位检测和斩波的所有组件。所以我们只需要简单地通过简单的 PWM 来控制它。
虽然我可能不需要演示如何连接电路或代码应该是什么,但为了理解,让我们使用这个模块将灯泡连接到我们的 Arduino 上,然后控制灯泡。现在,首先要记住的是,负载应该是灯泡,而不是其他任何东西,比如 LED 灯。所以,按照以下图示连接电路:
完成这些后,继续上传以下代码:
如预期的那样,附带的灯光将首先开始微弱地发光,并逐渐增加强度,直到达到 100%。这就是控制如此复杂过程如此简单的原因。
现在基础知识已经完成,让我们继续使用这个系统构建一些有意义的东西。调整空调到完美的温度是不是很难?无论你做什么,你最终都会感到不舒服。这是由于一天中体温的生理变化所导致的。
当你醒来时,你的体温相对较低。它低至 1°F,低于正常体温。随着一天的发展,体温升高,直到你上床睡觉的时间。一旦你入睡,你的体温又开始下降,在早上 4:00-6:00 达到最低点。这就是为什么当你上床睡觉时可能感觉温暖,但醒来时可能会非常冷的原因。现代空调有一种叫做睡眠模式的功能。这样做就是,它简单地通过整夜提高温度。这样你就不在任何时候感到冷。但话又说回来,它工作得有多好也是一个问题。
因此,既然我们已经非常了解机器人技术,我们将继续前进,制作我们自己的系统,该系统将负责一切。
在这部分,我们将空调和你的风扇连接在一起,这样它们就可以协同工作,让你睡得更好。现在,在我们直接进入之前,我想让你看看继电器上提到的评级。正如你所见,继电器只能处理 250 伏和 5 安培。现在,如果你查看你的空调手册,你很容易理解为什么我要向你展示所有这些。空调的功耗将远高于继电器可以处理的功耗。所以,如果你尝试使用普通继电器来运行你的空调,你肯定会烧毁继电器。可能你的电器电流额定值低于你的继电器。但是,对于任何内部有电机的设备,请记住,该设备的初始功耗远高于标称功耗。因此,如果你的空调标称需要 10 安培,那么启动负载可能高达 15 安培。你可能正在想,这不是问题,我们为什么不去购买一个更高额定值的继电器呢?好吧,正确!这正是我们将要做的。但是,电子设备的命名有时可能会很棘手。处理高功率、高电压机电开关的设备通常被称为接触器,而不是继电器。技术上,它们有相同的工作原理;然而,在这一点上,这不会是我们的关注点。因此,我们将使用接触器来切换空调,使用调光器来控制风扇速度。现在,这个问题已经澄清,让我们继续按照以下图示安装硬件:
这里使用的逻辑相当基础。让我们看看它在做什么:
在这里,我们正在获取湿度和温度的值。到目前为止,一切顺利,但我们能否更进一步,让它变得更加智能?之前的逻辑可能已经帮助你睡得更好,但我们能否让它对你来说更加完美?
我们的身体中有多个指标,可以让我们了解身体的状况。例如,如果你感到疲倦,你可能不会走得很快或说话很大声。相反,你可能会做相反的事情!同样,也有多个因素可以指示我们的睡眠周期如何进行。
其中一些因素包括:体温、呼吸速率、REM 睡眠和身体运动。精确测量体温或呼吸速率以及 REM 睡眠是一项挑战。但是当我们谈到身体运动时,我认为我们已经做得很好了。因此,基于身体运动,我们将感知我们睡眠得如何以及需要什么样的温度调整。
如果你注意的话,当有人睡觉并开始感到冷时,身体会采取胎儿姿势,并且动作会减少很多。这是自动发生的。然而,当一个人感到舒适时,会有一些不可避免的动作,比如翻身和手臂或腿部的移动。当一个人感到冷时,这种情况不会发生。所以通过这些动作,我们可以判断一个人是否感到冷。既然我们已经了解了身体的生理变化,那么让我们尝试围绕它构建一个程序,看看我们能实现什么。
要做到这一点,首先,我们需要按照以下方式连接电路:
一旦完成这些,就继续编写以下代码:
让我们来看看内部发生了什么:
你首先会看到我们有一个条件:if H,= 6 && H<= 22:。这个条件只有在时间在上午 10 点和晚上 6 点之间时才会为真。这是因为这是我们通常睡觉的时间。因此,这个标题下的逻辑只有在睡觉时间才会起作用。
第二个条件是if M <= 58,只有当时间在0到58分钟之间时,这个条件才会为真。所以当时间是M = 59时,这个条件将不起作用。我们将看到这个逻辑的原因。
此后,我们计算时间并将值存储在名为M的变量中。我们还计算湿度和温度值,并将它们存储在名为temperature和humidity的变量中:
现在我们有另一个条件,即if temperature < 28。当条件为真时不需要太多解释。所以每当条件为真,并且计数的Movement数量超过5时,Duty的值将增加10。因此,我们将 PWM 发送到 AC 调光器,这反过来会增加风扇的速度。最后,我们将Movement的值重置为0。
所以本质上,我们只是在计算动作的数量。只有当温度低于 28° C 时,这个动作才会被计算。如果动作超过5次,那么我们将风扇的速度提高 10%。
在上一节中,逻辑只有在时间在0到58之间时才会起作用,即计数发生的时间。当M的值为59时,将检查条件if Movement = 0,如果为真,则Duty的值将减少10。这反过来又会将风扇速度降低 10%。此外,一旦执行了这个条件,Movement的值将被重置为0。因此,新的周期可以开始,为下一个小时做准备。
现在它基本上意味着计数将按小时进行。如果Movement超过5,那么Duty的值将立即增加。然而,如果不是这种情况,程序将等待直到分钟接近59的值,每当这种情况发生时,它将检查是否有任何移动,在这种情况下,风扇速度将降低。
所有这些代码都非常简单易懂。如果温度低于22,那么空调将被关闭。此外,如果温度等于或高于24,并且时间在晚上 10:00 到早上 6:00 之间,那么空调将被打开。最后,如果温度高于27,那么风扇将切换到 100%的速度。
最后,我们通过使用条件for H > 7 && H <20确保在这个时间段内空调始终关闭。此外,如果H = 20,那么空调应该打开,以便在你准备睡觉之前房间冷却下来。
如你现在所理解的,我们可以根据我们的需求控制任何交流电器。我们已经理解了开关,并且也完善了我们可以调节灯光强度和风扇速度的方法。但是你注意到一件事了吗?迟早,随着我们的系统变得越来越复杂,所需的 GPIO 数量将会增加。会有这样一个时刻,你希望有越来越多的设备连接到你的 Raspberry Pi;然而,由于物理端口不足,你将无法做到这一点。
这在电子学中是一个非常常见的情况。就像往常一样,这个问题也有解决方案。这个解决方案被称为多路复用器。多路复用器的基本任务是增加任何计算机系统的端口数量。现在你可能想知道,它是如何做到这一点的?
这个概念极其简单。让我们首先看看多路复用器的图示:
在前面的图中,你可以看到多路复用器有两个端点——一个是有信号输出线,另一个与之相对。我们需要首先理解的是,多路复用器是一个双向设备,即它从多路复用器向连接的设备发送数据,同时也相反。
现在,首先,我们有电源线,这相当基础。它的作用是为多路复用器本身供电。然后,我们有信号线,它有两个端口,即Sig和EN。EN代表启用,这意味着在EN不是高电平之前,数据通信不会发生。然后我们有称为Sig的东西。这是连接到树莓派 GPIO 进行数据通信的端口。接下来是选择线。正如你所见,我们有四个端口,分别是S0、S1、S2和S3。选择线的目的是选择需要选择的特定端口。以下是一个表格,将阐明究竟发生了什么:
在前面的表格中,你可以看到通过在选择线上使用各种逻辑组合,可以寻址不同的线路。比如说,如果我们有如下选择引脚上的序列——S0 = 1,S1 = 0,S2 = 1,S3 = 1。如果这是从树莓派选择引脚上的输入,那么将选择 C13 引脚。这基本上意味着现在 C13 可以与多路复用器的Sig端口进行数据通信。此外,我们必须记住,为了数据传输发生,启用引脚必须是高电平。
以类似的方式,我们可以继续寻址多路复用器的所有 16 个引脚。因此,如果我们从逻辑上看待它,那么通过使用树莓派的六个引脚,我们可以利用 16 个 GPIO。现在我们已经了解了多路复用的基础知识,让我们继续尝试使用其中之一。
一旦硬件连接好,让我们上传以下代码:
在这里,我们实际上正在逐个触发选择线,以寻址连接 LED 的每个端口。每当发生这种情况时,相应的 LED 就会发光。此外,它发光的原因是信号端口Sig连接到树莓派的 3.3V。因此,向它连接的任何端口发送逻辑高电平。
这是一种多路复用器工作的基本方式之一。当我们需要使用多个设备和传感器时,这可以非常有用。
在本章中,我们使 Jarvis 能够在不同条件下自动化你的家用电器,并给系统应用各种属性。所以继续尝试许多其他场景,在这些场景下你可以增强你的智能家居系统。
在下一章中,我们将启用 Jarvis 物联网功能,从而可以通过 Wi-Fi 和互联网使用您的手机控制家电。
曾经,我们曾想象用指尖控制世界。现在,这个想象已经变成了现实。随着智能手机的出现,我们一直在做十年前只能想象的事情。随着手机变得智能,行业和企业也尽力跟上颠覆性的变革。然而,还有一部分仍然落后。那是哪一部分呢? 你的家!
想想你能用智能手机控制家里哪些东西?没多少吧!有一些设备可以打开或关闭一串设备,比如你的空调。然而,这个列表是详尽的。所以,在前面章节中获得的所有知识和我们手中的强大硬件面前,为什么我们不成为潮流的引领者和颠覆者,创造出仍然只是我们想象中的一部分的东西呢。
本章将涵盖以下主题:
物联网(IoT)基础
消息队列遥测传输(MQTT)协议
设置 MQTT 代理
制作基于物联网的入侵检测器
控制家庭
在本章中,我们将使用智能手机控制家里的设备,但在这样做之前,我们应该了解这项技术的基础。本章的第一个主题是物联网——现代世界中被过度使用的术语。它是每个人都想了解但没有人真正了解的东西。物联网可以与一种技术相关联,比如你的冰箱会告诉你哪些物品供应不足,并自动为你订购。可怜的家伙!这项技术需要一段时间才能侵入我们的家庭。但物联网不仅仅意味着这一点。物联网是一个非常广泛的概念,几乎可以应用于所有地方进行优化。那么,物联网究竟是什么呢?
让我们来分解这个缩写词,物联网有时也被称为网络物理系统。现在,什么是事物?任何能够无需人工干预收集或接收数据的电子设备都可以称为事物。所以这个事物可以是你的手机、起搏器、健康监测设备等等。唯一的条件是它应该连接到互联网,并且具有收集和/或接收数据的能力。第二个术语是互联网;互联网指的是互联网,嗯!现在,所有这些物联网设备都从云或中央计算机发送和接收数据。它之所以这样做的原因是,任何物联网设备,无论大小,都被视为资源受限的环境。也就是说,资源,如计算能力,要少得多。这是因为物联网设备必须简单且便宜。想象一下,你必须把物联网传感器安装在所有的路灯上以监控交通。如果设备成本为 500 美元,那么安装这种设备就不切实际了。然而,如果它可以用 5-10 美元的价格制造出来,那么没有人会眨一下眼。这就是物联网设备的特点;它们极其便宜。然而,这个故事的反面是,它们没有太多的计算能力。因此,为了平衡这个方程,它们不是在自己的处理器上计算原始数据,而是简单地发送这些数据到云计算设备或服务器,在那里这些数据被计算,并提取出有意义的结论。所以,这解决了我们所有的问题。嗯,不!这些设备的第二个问题是,它们可以是电池供电的,一次性使用设备。例如,在森林中安装的所有温度传感器;在这种情况下,没有人,绝对没有人会每周去更换电池。因此,这些设备被设计成消耗很少甚至没有电力,这使得编程变得非常棘手。
现在我们已经了解了物联网的概念,在本章中,我们将使我们的家庭物联网化。这意味着,我们将能够从我们的家庭传感器接收和收集数据,并在我们的移动设备上查看,如果需要,我们还可以通过智能手机控制这些设备。不过有一件事,我们不会在云上计算,而是将所有数据上传到云上,仅访问这些数据或从云上发送我们的数据,以便可以访问。我们将另开一册书来讨论云计算方面,因为这可以是一个全新的维度,并且超出了本书的范围。
MQTT 是一个 ISO 认证的协议,并且被广泛使用。这个协议有趣的地方在于,它是由 Andy Stanford 和 Arlen Nipper 在 1999 年开发的,用于通过沙漠监控石油管道。正如你可以想象的那样,在沙漠的中间,他们开发的协议必须既节能又带宽高效。
这种协议是如何工作的非常有趣。它有一个发布-订阅架构。这意味着,它有一个中央服务器,我们通常称之为代理。任何设备都可以注册到这个代理并发布任何有意义的到它。现在,正在发布的数据应该有一个主题,例如,空气温度。
这些主题特别重要。为什么你会问?对于代理来说,可以连接一个或多个设备。随着连接的建立,它们还需要订阅一个主题。假设它们订阅了主题Air-Temperature。现在,每当有新的数据到来时,它将被发布到订阅的设备。
有一个重要的事情需要了解,那就是从代理获取数据不需要像 HTTP 中那样发出请求。相反,每当接收到数据时,它将被推送到订阅了该主题的设备。很明显,在整个过程中,TCP 协议也将处于运行状态,并且与代理相关的端口将始终保持连接,以便实现无缝的数据传输。然而,如果数据出现中断,代理将缓冲所有数据,并在连接恢复时将数据发送给订阅者。
如您所见,运动传感器和温度传感器通过特定的主题将数据发送给 MQTT 服务器,这些主题分别是温度和运动。订阅了这些主题的用户将从这个设备获取读数。因此,实际传感器和移动设备之间不需要直接通信。
整个架构的好处是,可以连接无限数量的设备,并且不需要考虑可扩展性问题。此外,该协议相对简单,易于处理,即使处理大量数据也是如此。因此,它成为物联网的首选协议,因为它提供了一个简单、可扩展且无缝的数据生产者和数据接收者之间的链接。
记得那个老套的视觉处理更新过程吗?你怎么能忘记?我们在这里也要做同样的事情。但幸运的是,这次它并不长。那么,让我们看看我们为了设置这个服务器需要做什么。打开你的命令行,输入以下这些行:
你知道这一行的作用。如果你对它的记忆不是很清楚,那么请参考第九章,视觉处理。一旦更新和升级过程完成,继续安装以下包:
这将在你的 Raspberry Pi 上安装 Mosquitto 代理。这个代理将负责所有的数据传输:
现在,这一行将安装客户端包。正如你所想象的那样,Raspberry Pi 本身将是代理的一个客户端。因此,它将负责所需的一切。
我们现在已经安装了包;是的,就是那么简单。现在,我们只需要配置 Mosquitto 代理。为此,你需要输入以下命令:
现在,这个命令将打开保存 Mosquitto 文件配置的文件。为了配置它,你需要到达文件末尾,在那里你会看到以下内容:
现在,你可以通过在行前添加 # 来注释掉前面的代码行。完成后,继续添加以下行:
让我们看看我们在这里做了什么。allow_anonymous false 这一行告诉代理不是每个人都可以访问数据。下一行,password_file /etc/mosquitto/pwfile 告诉代理密码文件的存放位置,该文件位于 /etc/mosquitto/pwfile。最后,我们将使用 listener 1883 命令定义这个代理的端口,该端口是 1883。
因此,最终,我们在我们的 Raspberry Pi 上完成了 MQTT 客户端的设置。现在我们准备好继续使用它来为物联网化的家庭服务。
现在,Raspberry Pi 已经设置好,我们准备将其变为物联网设备,让我们看看我们将如何将系统连接到互联网并使其工作。首先,我们需要将 Raspberry Pi 连接到我们想要使用物联网技术控制的设备。所以,请继续使用以下图表来建立连接:
一旦设置好所有组件,让我们继续上传以下代码:
与我们之前看到的其他代码块不同,这段代码对你来说将相当新颖。所以,我将解释它的每一部分,除了几个显而易见的部分。那么,让我们看看这里有什么:
我们使用 mqtt 库的客户端方法定义了一个客户端。这可以通过 client 变量来调用。
现在我们已经像之前做的那样,将再次定义协议将工作的端口,在我们的例子中是 1883。
在这里,我们正在定义一个名为 pub_topic 的变量的值,它是 IntruderDetector_Home。这将是在代码运行后可以订阅的最终主题。
在这里,我们定义了一个名为 SendData() 的函数,将数据 Warning : SOMEONE DETECTED AT YOUR PLACE 发布到我们之前声明的主题的代理。
在这一行,我们正在定义on_connect()函数,该函数将打印出connection returned行,后跟rc的值。rc代表返回码。因此,每当消息送达时,都会生成一个代码,即使没有错误发生,也会返回特定的代码来通知错误。所以,这可以被视为一个确认。完成此操作后,我们将使用之前定义的SendData()函数将数据发送到代理。
当 PIR 传感器检测到高电平或检测到运动时,sendData()函数会被调用,将带有警告信息“有人在你这里被检测到”的消息发送到代理。
完成这些操作后,你将看到类似于以下屏幕的屏幕:
简单地添加你需要订阅的名称,即IntruderDetector_Home。完成这些操作后,你将看到魔法!
你也可以应用我们在第十章中使用的相同逻辑,即制作一个守卫机器人。所以,请继续尝试;我会在这里等你。
在下一节中,我们将根据物联网来控制事物;到时候见。
最后,使用以下图表,建立连接并上传以下代码:
现在,在这段代码中,我没有什么要告诉你的;它相当直接。我们发送数据的方式和上次一样。然而,这次我们使用了一个新的函数。那么,让我们看看这段代码是关于什么的:
在这里,我们定义了on_message()函数的作用。该函数有三个参数,消息将在这三个参数上工作。这包括我们之前已经声明的client;userdata,我们现在没有使用;最后是message,我们将通过智能手机通过互联网发送它。
这份数据将由我们的条件进行评估。如果数据保持on,那么 GPIO 端口号3将被设置为HIGH,如果字符串是off,那么 GPIO 端口号3将被设置为LOW——简单来说,就是打开或关闭你的设备。
要使用它,只需根据主题发送数据,它就会被你的树莓派接收。
一旦接收到,决策函数on_message()将决定 MyMQTT 应用接收到的数据是什么。如果接收到的数据是on,那么灯就会打开。如果接收到的数据是off,那么灯就会关闭。就这么简单。
在本章中,我们了解了物联网的基本知识以及 MQTT 服务器的工作原理。我们还创建了一个入侵检测系统,无论你在世界上的任何地方,只要有人进入你的家,它就会发出警报。最后,我们还创建了一个系统,可以通过简单的手机命令在家中的设备上打开。在下一章中,我们将让 Jarvis 能够让你根据声音与系统交互。
你是否想过使用机器人来完成我们的工作是否可能?嗯,是的!当然在一些高科技小说、漫威电影甚至漫画书中是可能的。所以,系好你的安全带,准备好进入这个令人惊叹的章节,你将实际实施我刚才提到的事情。
本章将涵盖以下主题:
基本安装
自动应答机
制作一个交互式门应答机器人
让 Jarvis 理解我们的声音
有许多方法和途径可以控制我们的智能家居 Jarvis,其中一些我们在之前已经探索过,例如通过控制它。因此,为了开始,我们需要准备我们的系统以便能够进行语音合成;为了做到这一点,让我们执行以下过程。
首先,转到终端并输入以下命令:
这将安装依赖项alsa-utils。alsa-utils包包含各种有用的工具,可用于控制你的声音驱动程序。
一旦完成,你需要编辑文件。为了做到这一点,我们需要打开文件。使用以下命令:
一旦完成,一个文件将打开;在该文件的底部,你需要添加以下行:
你不需要太深入地了解我们为什么要做这件事。它只是用来设置一些基础。我可以给你一个解释;然而,我不希望在这么激动人心的时刻让你感到无聊。
此外,如果你很幸运的话,有时你可能会发现这一行已经存在。如果是这样,那就让它在那里,不要去动它。
现在,为了播放 Jarvis 需要说的声音,我们需要一个音频播放器。不,不是你家里有的那个。我们说的是能够播放它的软件。
要安装播放器,我们需要运行以下命令:
好的,我们已经完成了音频播放器的设置;让我们看看接下来是什么。现在,再次,我们需要编辑媒体播放器的文件。我们将使用相同的步骤来打开和编辑文件:
这将打开文件。和之前一样,只需添加以下行:
最后,我们需要给它一些声音,所以运行以下命令:
这将为 Jarvis 安装一个 16 kHz、英国男性声音。我们喜欢英国口音,不是吗?
完美。一旦我们完成了之前提到的所有步骤,我们就可以开始了。为了测试声音,只需将 USB 扬声器连接到树莓派,并运行以下代码:
好吧,让我们看看我们实际上已经做了什么:
如你所想,我们正在导入名为os的库。这个库提供了一种使用操作系统依赖功能的方法:
在这里,我们使用了一个名为 system() 的方法;这个方法的作用是执行一个 shell 命令。你可能想知道这是什么。shell 命令是用户用来访问系统功能并与系统交互的命令。所以现在,我们想要将我们的文本转换为语音,我们将向这个函数提供两个参数。首先,文本是什么?在我们的例子中,它是 Hello from the other side;这里的第二个参数是 festival --tts。现在 festival 是一个库,而 tts 代表文本到语音转换。所以当我们传递给参数时,系统将知道传递给参数的文本需要从文本转换为语音。
就这样!是的,就是这样。这就是我们让树莓派说话所需要做的全部。
这些天,我们都在网上订购东西。然而,无论亚马逊的过程多么自动化,当我们谈到 2018 年时,我们仍然有人将包裹送到我们的家门口。有时,你希望他们知道一些关于在哪里留下包裹的事情。现在我们变得越来越自动化,那些可能在你门口留下便条的日子已经过去了。是时候用我们的技术做一些真正有趣的事情了。为了做到这一点,我们几乎不需要做什么严肃的事情。我们只需要按照以下图示连接组件:
PIR 传感器必须放置在门周围有移动时给出逻辑高电平的位置。
一旦完成,请上传以下代码:
现在我们所做的是非常简单的。一旦 PIR 传感器给出逻辑高电平,就会发出一定的指令。这里不需要解释。如果你需要任何澄清,可以参考之前的代码。
在上一章中,我们使用 PIR 传感器来感应任何人类活动,然而,这个传感器的问题在于,无论谁来或离开,它都会传达相同的信息。这基本上意味着,即使你在漫长的一天后回到家,它也会提出同样的问题。不是很愚蠢吗?
因此,在本章中,我们将使用之前的仓库,将视觉和语音集成在一起,以创造一个惊人的组合。在这里,摄像头将识别门上是谁,并识别是否是人或陌生人,如果是的话,它将传达你想要传达的信息。另一方面,如果你是那个人,它将简单地用一个简单的问候让你通过。然而,如果检测到人脸但没有识别出来,它将向站在摄像头前的人给出一系列指令。
要实现这一切,你只需要在门上设置一个带有 PIR(被动红外探测器)的摄像头。PIR 基本上是用来激活摄像头的。换句话说,只有在检测到没有运动时,摄像头才会被激活。这个设置非常简单,不需要使用任何 GPIO。只需固定摄像头和 PIR,然后上传以下代码:
在前面的代码中,我们正在使用CascadeClassifier方法创建一个级联分类器,以便摄像头可以检测到人脸。
在前面的代码中,我们正在使用cv2的VideoCapture(0)方法从摄像头读取帧。同时,正在创建人脸识别器以识别特定的人脸。
当检测到人脸时,包含人脸的图像部分将被转换为灰色并传递给预测函数。这种方法将告诉人脸是否为人所知,如果人脸被识别,它还会返回 ID。假设这个人是BEN,那么 Jarvis 会说“你好,欢迎来到我的家,BEN”。现在BEN可以告诉 Jarvis 打开灯光,当唤醒词 Jarvis 被激活时,Jarvis 会做出响应。如果人脸没有被识别,那么可能是一个快递员。然后,以下命令将被执行:
声音是沟通的精髓。它帮助我们非常快速地在短时间内传输大量数据。它当然比打字快且容易。因此,越来越多的公司正在努力开发能够理解人类声音和语言并据此工作的系统。这当然不容易,因为语言中存在巨大的变化;然而,我们已经取得了相当大的进步。所以,让我们在不浪费太多时间的情况下,让我们的系统准备好识别我们的声音。
因此,这里我们将使用 Google Voice 的 API。正如你可能知道的,谷歌在理解你说的话方面真的很擅长。比如,非常字面地。所以使用他们的 API 是有意义的。现在,它的工作方式非常简单。我们捕捉声音,并将其转换为文本。然后,我们比较文本是否与我们配置文件中定义的内容相似。如果匹配,则执行与之关联的 bash 命令。
首先,我们需要检查麦克风是否已连接。为此,运行以下命令:
此命令将显示连接到 USB 的设备列表。如果你在列表中看到了你的设备,那么恭喜你,你走对了路。否则,尝试通过连接找到它,或者也许尝试使用其他硬件。
我们还需要将录音音量设置为高。为此,请在串行上输入以下命令:
现在,一旦 GUI 出现在屏幕上,就可以使用箭头键切换音量。
最好是亲自听一下录制的声音,而不是直接将其传给树莓派。为此,我们首先需要录制我们的声音,所以我们需要运行以下命令:
这将检查摄像头是否在列表中。然后,输入以下命令进行录制:
现在我们还想听听我们刚才录制的声音。最简单的方法是输入以下命令:
检查声音是否正确。如果不正确,那么你可以自由地对系统进行任何调整。
一旦我们检查完声音和麦克风,就是时候安装实际的工作软件了。有一些简单的方法可以做到这一点。以下是你需要运行的命令列表:
现在当你运行这个程序时,一些非常有趣的事情将会开始发生。它将开始问你各种问题。其中一些将是直接的。你可以用你的理智以是或否的形式回答它。其他问题可能非常技术性。由于这些问题可能会随时间而变化,似乎没有必要明确指出需要填写的答案,但作为一个一般性的规则——除非你真的想说不,否则就给它一个肯定回答。
完美,我们已经安装了软件。现在在你进一步使用该软件之前,让我们先写下以下程序:
好的!当这一切都完成之后,然后输入以下命令来检查软件是否正确安装:
树莓派对唤醒词pi做出响应;让我们将其更改为jarvis。所有这些更改都可以在通过以下命令打开配置文件后进行:
在那个文件中,输入你自己的命令。这里,让我们添加以下代码:
现在对于每个命令,定义动作。动作将是运行包含切换灯和风扇开关代码的 Python 文件。代码是基本且易于理解的。将以下内容添加到文件中:
现在,让我们看看我们完成了什么。每当你说 <q>Jarvis,开灯</q>,它就会将你的速度转换为文本,将其与它必须运行的相应程序进行比较,并执行程序中的任何操作。因此,在这个程序中,每当我说 <q>开灯</q>,灯光就会打开,其余的命令也是如此。记得让它倾听你说的话。你将不得不说出单词 <q>Jarvis</q>,这将让它对命令保持警觉并准备好倾听。
在本章中,我们了解了如何交互并让 Jarvis 根据我们的需求工作。如果本章是关于口头交流,那么下一章就是关于手势识别,在那里,通过使用先进的电容技术,你只需挥动手臂就能控制你的自动化系统。
从时间的开始,人类就通过手势相互交流,甚至在还没有正式语言之前。手势是主要的交流方式,这在全世界发现的古代雕塑中也是显而易见的,这些符号已经是一种非常高效地传输大量数据的方式,有时甚至比语言本身还要高效。
手势是自然的,它们可以作为一种对特定情况的反射而出现。它也可能在我们不知道的情况下无意识地发生。因此,它成为与各种设备进行交流的理想方式。然而,问题仍然存在,那就是如何?
我们在前几章中使用了视觉处理,我们可以确信,如果我们谈论手势,那么我们肯定需要在视频中识别手势进行大量的编程;此外,它还需要巨大的处理能力来实现。因此,这是不可能的。我们可以使用一组接近传感器构建一些基本的 gesture-recognition 系统。然而,可识别的手势范围将非常有限,并且使用的端口数量将是多倍的。
因此,我们需要找到一个易于操作且成本不超过其提供价值的解决方案。
本章将涵盖以下主题:
电场感应
使用 Flick HAT
基于手势识别的自动化
近场感应是一个非常有趣的感应领域。准备好迎接一些有趣的内容。如果你感到有点困倦,或者注意力不集中,那么喝点咖啡吧,因为这个系统的原理可能会有些新颖。
对于我们使用的手势识别板,它周围的感应场只有大约几厘米,所以超出这个范围的任何东西都可以忽略不计。如果附近没有东西,那么我们可以安全地假设感应到的电场模式将保持不变。然而,每当有像我们的手这样的物体进入附近,这些波就会发生畸变。这种畸变直接与物体的位置及其位置有关。通过这种畸变,我们可以感知手指的位置,并通过持续感应,我们看到正在执行的运动类型。所讨论的板看起来像这样:
板上的中心交叉区域是发射器,而在极端两侧有四个矩形结构。这些是传感元件。它们能够感知空间中波形的模式。基于此,它们可以推导出物体的 x、y 和 z 坐标。这是由名为 MGC 3130 的芯片供电的。它执行所有计算并将原始读数交付给用户,关于坐标。
Flick HAT 以盾牌的形式出现,你可以简单地将其插入 Raspberry Pi 并开始使用。然而,一旦你这样做,你将不会剩下任何 GPIO 引脚。因此,为了避免这个问题,我们将使用公对母线将其连接。这将使我们能够访问其他 GPIO 引脚,然后我们可以尽情玩耍。
所以,按照以下步骤进行连接。以下是 Flick 板的引脚图:
此后,按照以下步骤进行连接:
连接完成后,只需上传此代码并查看会发生什么:
现在你已经上传了代码,让我们来了解一下这段代码实际上在做什么。
我们使用一个名为 import flicklib 的库,这是该板的制造商提供的。这个库中的函数将在本章中用于与 Flick 板通信并获取数据。
在这里,我们定义了一个名为 message(value) 的函数,它的作用是简单地打印传递给函数的任何值:
它有一个特殊的工作:动态定义程序中的任何函数。用简单的话说,使用这种方法定义的函数可以根据用户如何定义它而有所不同。
move() 函数将进一步由其后定义的函数补充。这类函数被称为嵌套函数。也就是说,函数在函数内部:
在这里,我们定义了一个名为flick(start, finish)的函数。它有两个参数:start和finish。使用flicktxt = 'FLICK-' + start[0].upper() + finish[0].upper()这一行,这是根据手势板识别的字符进行切片。如果检测到南-北滑动,那么start将得到南,而finish是北。现在我们只使用单词的第一个字符:
我们再次在全局范围内定义了名为xyztxt和flicktxt的变量。之前,我们是在函数中定义它们的。因此,在主程序中定义它们对我们来说非常重要:
当检测到手势时,flicktxt变量将获得与手势对应的值。如果没有检测到手势,则flicktxt将被留空。一个名为flickcount的变量将计算其被滑动的次数。如果值超出了指定的范围,则使用flicktxt = ''这一行将flicktxt清空为空字符串,并将flickcount设置为 0。
最终的输出将是一个提供给用户的文本,说明手是从哪个方向滑动的。
现在我们已经根据以下图示进行了连接:
让我们继续上传以下代码:
这个程序是在之前程序的基础上增加的,因为我们总是有一些使用通过手势板接收到的数据来打开或关闭灯光的附加功能。
与之前的程序类似,我们正在以滑动方向的形式接收板上的手势,并使用简单的条件来关闭或打开灯光。那么,让我们看看有哪些新增内容:
第一个条件很简单。我们正在将flicktxt的值与一个给定变量进行比较,在我们的例子中是FLICK-WE,其中WE代表从西到东。所以当我们从西向东滑动,或者说,当我们从左向右滑动时,灯光将会关闭:
与之前一样,我们再次接收一个名为FLICK-EW的变量,它代表从东向西的手势。它的作用是,每当我们将手从东向西或从右向左滑动时,灯光将会打开:
现在我们已经将调光器与风扇一起放置,以控制风扇的速度;因此,我们需要给出一个与所需驱动速度相对应的 PWM 信号。现在,每当用户将手从南向北或从下向上滑动时,条件if dc_inc <100将检查dc_inc的值是否小于或等于100。如果是,那么它将dc_inc的值增加20个单位。通过使用ChangeDutyCycle()函数,我们向调光器提供不同的占空比,从而改变风扇的整体速度。每次向上滑动风扇值时,它将增加 20%。
在本章中,我们能够理解通过电场检测来识别手势的概念。我们还了解到使用手势控制板控制家居用品是多么简单。我们将在下一章介绍机器学习部分。
从其原始时期到如今,机器人和计算机都在被编程执行一系列活动。这些活动可能非常庞大。因此,为了开发复杂的程序,需要大量的软件工程师日夜工作以实现特定的功能。当问题定义得很好时,这是可行的。但是,当问题也非常复杂时怎么办呢?
学习是我们人类之所以成为我们的原因。我们的经验塑造了我们以更好地、更有效地适应情况。每次我们做某事,我们就知道得更多。这使得我们在一段时间内更好地完成这项任务。俗话说,熟能生巧,而通过反复做事情来学习使我们变得更好。
然而,让我们退一步来定义什么是学习?我想引用谷歌的定义,它是通过学习、经验或被教导获得的知识。因此,学习基本上是一种从我们的周围环境中获取信息以理解过程及其本质的方式。
现在,你可能正在想,等等,我们不是在制作守卫机器人的前几章中使我们的系统学习了很多视觉数据吗?你的想法完全正确。然而,学习可以通过不同的方式进行。可能适用于一种类型的问题的方法可能对其他类型的问题毫无用处。因此,存在各种类型的机器学习算法及其原理。在本章中,我们将重点关注一个名为k-最近邻的算法。它被称为懒惰算法。我个人非常喜欢这个算法用于分类。为什么?因为从技术上讲,没有训练阶段。怎么呢?
k-最近邻实际上是一个智能算法。它不是对提供的数据进行回归计算并进行大量的数学计算,而是简单地从提供的数据集中获取结构化数据。每当有新的数据用于预测时,它就会根据用户提供的数据的分类,在数据库中简单地搜索与用户提供的数据最接近的k个匹配项。因此,在本章中,我们将学习这个算法是如何工作的,以及我们如何使用它来使我们的家庭智能化。
在本章中,我们将涵盖以下主题:
制作数据集
使用数据集进行预测
使你的家庭学会学习
家庭学习和自动化
就像在第十章制作守卫机器人中一样,我们使用了多张图片来训练模型,以确定图像中的物体是人还是其他东西。以非常相似的方式,我们不得不制作一个虚拟数据集,这样机器学习算法就可以根据这些数据预测应该做什么。
要创建一个数据集,我们需要了解正在考虑哪些数据。在本章中,我们将基于时间和温度创建一个机器学习算法,以预测风扇应该开启还是关闭。因此,至少有两件事应该由我们提供给系统,一个是Temperature,另一个是Time,以便进行预测。但有一点要记住,我们正在谈论一个监督学习算法,因此为了训练模型,我们还需要提供Temperature和Time的输出到风扇的状态。在这里,风扇的状态将是开启或关闭。因此,我们可以用0或1来表示它。现在让我们自己创建一个数据集。
现在,要创建一个数据集,你只需打开 Microsoft Excel 并按照以下方式开始编写数据集:
拥有一个包含超过 20 组数据的数据集总是更好的。此外,数据必须具有独特的特征,而不是随机数据。例如,在前面的例子中,你可以看到当温度为28时,在12.44时风扇是开启的;然而,在相同的时间,当时间是12.13且温度为21时,风扇是关闭的。
一旦你创建了一个数据集,你必须以dataset的名称将其保存为 CSV 格式。可能会有一些用户不会使用 Microsoft Excel,在这种情况下,你可以在文本编辑器中以相同的格式写入数据,最后以 CSV 格式保存。
不多说了,让我们看看下面的代码:
那么,让我们看看我们在做什么:
我们将numpy导入到我们的程序中;这有助于我们处理列表和矩阵:
这里,我们导入了一个名为pandas的库;这有助于我们读取以逗号分隔的值文件,换句话说,CSV 文件。我们将使用 CSV 文件来存储我们的数据,并在学习过程中访问它:
这里,我们从sklearn库中导入KneighborsClassifier。sklearn本身是一个庞大的库;因此,我们只导入其中的一部分,因为我们不会在这个程序中使用它:
这里,我们给变量knn赋值,其值将是KNeighborsClassifer(n_neighbors =5);这意味着我们正在使用带有参数n_neighbors=5的KneighborsClassifer()函数。这个参数告诉KneighborsClassifer函数,在算法中我们将有五个邻居。进一步来说,使用这个声明,整个函数可以通过knn来调用:
就像之前一样,我们使用numpy库的.ravel()函数将State存储在一个数组中。这样做是为了使数学函数可以在两个数组——x和y之间进行操作:
在这短短的一行中,我们使用knn库中的fit()函数,它的作用是使用x作为主要数据,y作为输出结果数据来拟合模型:
在这一行中,我们请求用户的数据。在第一行,我们将打印Enter time,然后等待用户输入时间。用户输入时间后,它将被存储在名为time的变量中。一旦完成,然后就会进入下一行;代码将打印Enter temp。一旦提示用户,它将等待收集数据。一旦用户收集到数据,它将把数据存储在名为temp的变量中:
在这里,我们创建了一个名为data的空列表;这个列表将用于计算输出结果的状态。因为所有的机器学习算法都是在列表数据类型上工作的。因此,输入必须以列表的形式给出,以便进行决策:
在这里,我们将数据添加到我们刚刚创建的名为data的列表中。首先,添加time,然后是temp:
一旦完成,将使用名为predict的knn算法中的函数来根据名为data的列表预测输出。预测算法的输出被检索到一个名为a的变量中:
最后,一旦完成预测,我们就会读取a的值,并记住所有数据输入/输出都是以列表的形式发生的。因此,预测算法给出的数据输出也将以列表格式。因此,我们正在打印列表的第一个元素。
这个输出将预测根据用户提供的数据集,风扇将处于哪种状态。所以,请继续提供一个温度和时间,让系统为您预测结果。看看它是否工作正常。如果不正常,那么尝试向 CSV 文件中添加更多的数据集,或者看看数据集中的值是否有意义。我相信你最终会得到一个出色的预测系统。
一旦完成这一宪法,就可以按照下面的图示进行接线:
一旦设置好,就轮到我们将在我们的树莓派上编写以下代码:
现在,让我们看看我们在这里做了什么:
我们使用这一行来读取 DHT 11 传感器的湿度和温度读数,并将这些值传递给变量humidity和temperature:
在这里,我们定义了不同类型的与开关组合相对应的状态。相应的表格如下:
因此,通过状态值,我们可以理解哪个开关会被打开,哪个会被关闭:
现在,使用write()函数,我们将数据的值写入我们之前通过值f定义的文件。
在这里,我们正在将状态与prev_state进行比较,正如你在我们的程序中看到的那样。上一个状态是在我们程序开始时计算的。所以,如果系统状态有任何变化,那么prev_state和state的值就会不同。这将导致if语句为真。当这种情况发生时,数据将通过write()函数写入我们的文件。传递的参数是需要写入的值。最后,计数器的值增加1。
既然在前一节我们已经了解了学习是如何工作的,现在是时候使用这个概念来制作一个能够自动理解我们如何运作和做决定的机器人了。基于我们的决定,系统将判断应该做什么。但这次,我们不是通过用户提供一组数据,而是让这个程序自己生成数据。一旦数据看起来足够它自己运作,那么,不多做解释,让我们直接进入正题:
现在我们来看看我们在这里做了什么。在这个程序中,while count < 200:条件下的程序的第一部分与我们之前所做的代码完全相同。所以它只是在按照用户的要求做事,同时,它正在从用户那里获取值以了解他们的工作行为:
此后,当计数器超过200时,将开始执行代码的第二部分,这部分代码位于前面的循环中:
在这里,我们将时间和温度的值添加到名为test_set的列表中,如果你查看程序,你会看到程序中间声明了一个空集合。所以,现在这个test_set包含了time和temp的值,这些值可以被预测算法进一步用于预测状态:
使用knn函数中的简单函数predict(),我们可以预测状态值。我们所需做的只是将数据或test_set列表传递给预测函数。该函数的输出将是一个列表,将被存储在一个名为a的变量中:
Out的值将被设置为列表a的第一个元素:
使用前面的代码块,我们能够根据算法预测的状态来选择性地打开灯和风扇。因此,使用这种方法,程序能够在没有你的干预下自动预测并打开或关闭灯和风扇。
在本章中,我们了解了即使不学习也能如何进行机器学习。我们了解了如何提供数据集,以及我们可以如何使用现有系统创建一个新的数据集。最后,我们了解了系统如何无缝地收集数据、从数据中学习,并最终提供输入。想要构建一个轮式自平衡机器人?那么,下一章见!
在前面的几章中,你已经学到了很多关于传感器的内容——它们的作用以及它们是如何工作的。这个星球上的传感器数量简直无法计数。它一直在增加,当你阅读这本书的时候,可能某个国家正在推出一些新的传感器。这可能会让你想,你是否需要每天学习市场上出现的所有这些传感器?
幸运的是,正如你所学的,这些传感器在某种程度上是相似的,并且基于有限的协议集。这使得与新的传感器合作变得容易。你可能认为我们已经涵盖了大部分的基本传感器。然而,这并不准确——因为我们还没有学习到与机器人平衡最重要的传感器。这些是加速度计和陀螺仪。这两个传感器不仅存在于所有与平衡有关的机器人中,而且它们也进入了我们的移动设备。所以,现在让我们来了解这些传感器的基本知识。
让我们先从加速度计开始。正如其名所示,这些设备是用来测量任何物体上的加速度的。因此,每当任何物体上有加速度时,加速度计就有工作来感知这种加速度并提供相应的读数。
陀螺仪是一种利用地球重力帮助确定机器人方向的设备。加速度计是一种设计用来测量非重力加速度的设备。每当有加速度时,加速度计中的晶体就会激发并提供与加速度相对应的电压。
这些设备之间的主要区别很简单:陀螺仪可以感知旋转,而加速度计则不能。从某种意义上说,加速度计可以测量静止物体相对于地球表面的方向。当向特定方向加速时,加速度计无法区分这是由于地球的重力提供的加速度还是由于加速本身。
在本章中,我们将使用一个名为 MPU 6050 的传感器,这是具有加速度计和陀螺仪的常见传感器之一,它们集成在一个单一包中。
上图是它的照片。正如你所见,它有SCL和SDA线。因此,你可以确信这个传感器是基于 I2C 协议工作的。这很酷,因为我们已经与 I2C 类型的传感器有了良好的合作经验。
MPU 6050 是一个三轴加速度计和三轴陀螺仪。它基于 I2C 协议工作,但这个传感器有趣的地方在于它为不同的传感器有不同的地址。你刚才说什么?不同传感器的不同地址。难道这不是一个单一的传感器吗?
嗯,是的,但是如果你看到传感器的内部结构,你会发现对于每个不同的轴,数据都存储在不同的寄存器中,我们可以从那里获取所需的数据。因此,如果你只需要读取加速度计的 x 轴,你可以这样做,而不是获取一长串不同的值。现在连接这个传感器非常简单。你所要做的就是使用 Raspberry Pi 给它供电,并简单地连接 I2C 引脚,如图所示:
一旦完成接线,继续上传以下代码。一旦这样做,运行并查看你得到的结果:
现在是时候看看我们做了什么:
在这些行中,我们定义了一个名为MPU_Init()的函数。这个函数将被用来初始化惯性测量单元(IMU)传感器。在这个函数中,我们使用来自bus库的write_byte_data()方法。它的作用是将数据写入在参数中声明的特定寄存器。使用此函数写入的数据大小为字节。现在我们声明了Device_Address。也就是说,写入的数据将位于连接的设备地址上。第二个参数SMPLRT_DIV将告诉寄存器的地址是十六进制的。因此,SMPLRT_DIV的值将用作地址。最后一个参数是需要写入的数据,在我们的例子中是7。同样,以下是将传递给设备的值:
PWR_MGMT_1: 1
CONFIG: 0
GYRO_CONFIG: 24
INT_ENABLE: 1
这些值是设置传感器所必需的。这些值的作用对我们目前来说并不重要。然而,如果你想知道的话,你可以使用 IMU MPU 6050 的数据表,以详细了解所有这些寄存器都在做什么:
在这里,我们定义了一个名为read_raw_data(addr)的函数,它从设备读取地址,然后我们读取地址的寄存器是addr用于high变量,addr+1用于low变量。
一旦获取了数据,它就会通过以下语句value = ((high << 8) | low)进行处理,这个操作是将high变量的值左移,因此如果high变量的二进制数据是10111011,(high << 8)会将这个值转换为1011101100000000。完成这个操作后,与操作符会将八个数字的值添加到新计算出的high变量的值上。使得输出成为一个 16 位整数。现在你可能想知道我们为什么要这样做?
MPU 6050 为任何读数提供的值是 16 位的。然而,MPU 6050 的寄存器大小是 8 位的。现在 MPU 6050 所做的是根据位的位置将任何传感器的值存储在寄存器中。也就是说,最重要的 8 位将存储在第一个地址,其余的最不重要的 8 位将存储在下一个地址,即addr+1:
正如我们在上一行所理解的,传感器的输出数据将以 16 位格式。当传感器处于正常位置时,这个读数将位于中间某个位置。如果值超过中间读数,我们将通过65535减去该值,因此为每个超过中间读数的值提供一个负值。这将给我们一个方向感,而不需要做太多的脑力劳动。如果值是正的,倾斜方向是向一个方向,如果是负的,则是向另一个方向。
现在我们已经定义了名为read_raw_data()的函数,我们可以开始从不同的传感器读取数据。这些传感器会将数据保存到不同的地址。我们之前提到的就是这些地址。因此,通过将这些数据传递给函数,我们可以了解每个传感器的读数。所有这些读数都在被计算,并且数据被存储在一个变量中。
在这一行,我们只是简单地打印出每个传感器对应的每个变量的值。
现在你运行这段代码,你会看到每个传感器的值都会显示出来,并且当你改变方向或加速度时,这些值会发生变化。探索这些读数以及它们如何随着运动而变化。我相信这会给你关于传感器的很多想法。所以,继续前进,享受吧!
我们已经看到了如何从 IMU 中提取数据。现在是时候将数据投入使用。在本章中,我们将通过手的倾斜来控制我们的机器人车辆。所以本质上,它将是一个手势控制的机器人车辆。现在为了做到这一点,让我们继续并按照以下方式连接 Raspberry Pi:
确保为传感器连接足够长的线,在任何地方都不要超过 1 米,并将其用作你车辆的遥控器。一旦连接,上传以下代码:
正如你所看到的,代码在到达while True循环之前几乎是一样的。之后我们做了一点小技巧。那么让我们看看它是什么。
当加速度计处于中间或水平放置的位置时,其值将接近32768,这将是一个中心读取值。因此,为了找出我们向前或向后倾斜了多少百分比,我们使用这一行。为了做到这一点,我们将其除以327。它所做的就是,在0至100之间给出一个读取值。例如,如果原始读取值是gryo_x = 21000,则gyro_x/327 = 64.22。现在64.22将是倾斜的百分比读取值。这对于我们来说是一个重要的步骤,因为这个读取值将帮助我们确定必须提供给电机驱动器的DutyCycle。
这一步非常简单,我们所做的是为读取值设置一个阈值,超过这个阈值就会向电机提供 PWM 信号。提供给电机的 PWM 的DutyCycle将与倾斜角度成正比。
就像我们之前做的那样,如果倾斜方向相反,那么对应后向方向的电机驱动引脚将被置为高电平,使其向后移动。速度与之前一样,将成正比于倾斜角度。
在这一行,我们使机器人车辆向一个方向转弯。和之前一样,由于倾斜的百分比值,DutyCycle会改变,从而改变转弯速度。正如你所看到的,机器人的车轮会相对转动,因此转弯将在其轴上完成。
有时在使用各种库时,原始输出可能会变化。为了确保你的代码运行良好,首先查看你得到的原始读取值。一旦你得到读取值,就在一张纸上写下这些值:平稳线读取值、最大读取值和最小读取值。这些读取值可能会根据传感器的材质而变化。(有许多假冒产品不会给出相同的读取值。)一旦你看到了整个读取范围,你就可以对算法进行所需的调整,使其工作。
试试看,看看你如何仅通过手势来控制它。
在上一章中,我们看到了我们如何仅基于我们的手势使汽车向任何方向转弯,然而之前的代码存在一个问题。首先,汽车一次只能向一个方向移动,即它要么向前或向后移动,要么向左或向右转弯。
它本身无法根据手势进行转弯。为了使其能够这样做,我们需要使代码更加智能。与机器人的整体连接将完全相同。但是代码会有所不同,让我们看看具体是什么:
现在我们来看看我们对代码所做的所有更改。整个传感机制保持不变,但是数据处理进行了重大改进。那么,让我们看看具体是什么:
现在我们正在比较Ax的值。如果Ax > 20,则下面的代码将运行。我们之所以这样做,是因为加速度计极其敏感,可以感知到最微小的振动。因此,可能会出现错误输出。所以为了过滤它,我们有一个20的阈值。也就是说,直到加速度计倾斜 20%,这个代码都不会生效。同样,我们也在 y 轴上这样做。一旦完成,这个百分比值就给了dc1 = Ax - Ay这一行。这个操作是在 x 轴的倾斜度上,也就是前进轴,减去Y的移动。在第二行,我们对电机的另一侧做同样的操作,然而,我们不是减去Y的值,而是加上它。因此,它将产生电机两端速度的差异。这个速度差异将直接与Y轴的角倾斜度相关。因此,倾斜度越大,速度变化越大,转弯角度也越大。
在下一行,我们为加速度计另一侧的倾斜度设置了一个条件,通过检测Ay < -20。如果值小于-20,则以下算法将生效。
在这里,这些行完全相同。然而,数学运算符已经被反转。因此,对于第一个电机,我们不再减去Y的值,而是现在加上它。另一方面,对于第二个电机,我们不是加值,而是减去它。
最后,如果Ax的值大于20,但Ay的值在-20到+20之间,那么我们将假设汽车需要直行。因此,Ax的值将直接传递给两个电机,使其完全直行。
在本章中,我们已经了解了加速度计和陀螺仪的工作原理。我们使用了陀螺仪和加速度计的原始数据,并将其用于制作一个手势控制机器人。最后,进入书的下一章和最后一章,我们将继续我们的旅程,这一章是你最期待的精彩章节。
最后,我们来到了大多数人在这本书开始时就希望达到的地方。制作一个机械臂!在本章中,我们将学习机械臂工作原理背后的概念。毫无疑问,我们也将制作一个供个人使用的机械臂,它可以为我们做无数的事情。
如果你看到人体,那么使我们能够与其他大多数物种不同的最显著部分之一就是手臂。它是我们用来做大部分工作的身体部位。
人类手臂是一个非常复杂的关节和肌肉机制,这些关节和肌肉协同工作,赋予了我们所熟知的灵活性。以我们的肩关节为例。如果你仔细观察,你会发现它能够上下移动,左右移动,甚至可以绕着自己的轴线旋转,而这一切都只靠一个单一的关节,我们称之为球关节。
当我们谈论机器人上的机械臂时,我们无疑是在谈论一个复杂的执行器排列,与身体(即底盘)一起,以在三维空间中获得所需的运动。
现在,让我们了解任何机械臂的基本组成部分。首先是执行器。我们可以使用电机来控制机械臂;然而,正如我们之前所研究的,使用我们之前使用的电机并不是理想的选择,因为它无法保持其位置,也没有反馈机制。因此,我们只剩下一个选择,那就是使用伺服电机。正如我们所知,它们具有相当大的扭矩,并且能够知道自己的位置,并且可以保持我们想要的任何位置。
机器人的第二部分是底盘,即那个将所有电机连接在一起并提供机器人结构支撑的部分。它必须以这种方式制作,以便为任何给定的关节提供所有期望的轴运动。这一点很重要,因为单个伺服电机只能在一个轴上提供运动。然而,有多个地方可以使用复杂的排列来使机器人在多个轴上移动。此外,底盘应该是刚性的,这一点非常重要。众所周知,地球上所有的材料都有一定的柔韧性。此外,材料的构造取决于材料的非顺应性。这服务于一个非常重要的重复性目的。
那么,什么是重复性?正如你可能已经在工业或任何制造单位中看到的那样,机器人被安装并重复执行相同的任务。这是可能的,因为机器人被编程在特定情况下执行一组特定的功能。现在,让我们假设机器人的底盘不是刚性的。在这种情况下,即使伺服系统 100%精确并且每次都能到达完全相同的位置,机器人实际上可能与其目标位置有所不同。这是因为底盘可能存在一些灵活性,这就是为什么最终位置可能不同。因此,一个合适的底盘是必不可少的。当我们谈论大型机器人时,这一点变得更加重要,因为即使是微小的变形也可能导致手臂最终位置发生很大的变化。
在谈论机械臂时,我们经常使用的一个术语是末端执行器。这基本上是机械臂的末端,它将为我们完成所有最终工作。在真实的人臂的情况下,末端执行器可以被认为是手。这是手臂的顶部,手臂的所有运动基本上是为了在三维空间中定位手的位置。此外,正是手拿起物体或执行必要的物理动作。因此,我们称之为末端执行器。
现在,随着机械臂在三维空间中移动,确定运动发生的轴线成为一个真正的大问题。因此,我们通常不是使用轴线来定义运动,而是使用正在执行的运动类型,这给我们一个关于运动是什么以及它可能在哪个轴上的现实概念。为了分析运动,我们使用偏航、俯仰和滚转(YPR)的概念。
上述图示将消除关于 YPR 的大部分疑问。这个概念通常用于飞机;然而,它也是机械手的一个基本组成部分。所以,正如你从上述图中可以看到的,当飞机的鼻部向上或向下移动时,这将被认为是俯仰运动。同样,如果飞机改变航向,那么偏航可以被认为是相应地改变——偏航就是飞机在y轴上的运动。最后,我们还有被称为滚转的东西。它用于理解旋转角度。正如你所看到的,这三个实体彼此独立,追逐任何一个都不会对其他产生影响。这个概念也很有用,因为无论飞机的朝向如何,YPR 都会保持不变,并且非常容易理解。因此,我们将这个概念直接从飞机应用到我们的机器人上。
最后,我们怎能忘记处理单元呢?它是控制所有执行器并进行协调和决策的单元。在我们的案例中,这个处理单元是树莓派,它将指挥所有执行器。所有这些先前的组件共同构成了一个机械臂。
并非每个机械臂都是相同的。它们有不同的负载能力,即末端执行器可以承受的最大负载,速度和范围,即末端执行器可以达到多远。然而,机械臂的一个非常重要的部分是它拥有的电机数量。因此,对于每个轴,你需要至少一个电机来使机器人沿该轴移动。例如,人类的肩关节具有三维自由度。因此,为了模仿该关节,你需要每个轴上的一个电机,也就是说,手臂要在所有三个轴上独立移动,至少需要三个电机。同样,当我们谈论我们手部的肘关节时,它只能在两个维度上移动。那就是手臂的闭合和打开,以及最后手臂的旋转——肘关节在第三维度上不移动。因此,为了复制其运动,我们需要至少两个电机,这样我们才能在w轴上移动机器人。
从我们目前所理解的来看,我们可以安全地假设,电机数量越多,机器人也会越灵活。这通常是这种情况;然而,你可能使用多个电机使机器人本身在一个轴上转动。在这种情况下,通过计算执行器的数量来确定机器人灵活性的基本概念将不再适用。那么,我们如何确定机器人的灵活性呢?
我们有一个称为自由度(DOF)的概念。如果按照标准定义来说,那么我可以非常确信你会对它实际意味着什么感到困惑。如果你还不信服,那么你自己尝试在谷歌上查找。用非常简单和直白的话来说,一个自由度是一个可以在任何给定轴上独立移动的关节。例如,如果我们谈论肩关节,那么它在三个轴上都有运动。因此,自由度是三个。现在,让我们考虑我们手臂的肘关节。因为它只能在俯仰和滚转方向上移动,所以我们最终得到两个自由度。如果我们把肩关节和肘关节连接起来,那么自由度将相加,整个系统将被称为具有六个自由度。请记住,这个定义是一个非常简化的定义。如果你选择深入研究,你会遇到多个复杂性。
现在,你将遇到的大多数机械臂都会有接近六个自由度。尽管你可能认为这比人类的臂少,但实际上,它完成了大部分工作。显然,自由度越少,意味着需要的电机数量越少,从而成本更低,编程的复杂性也显然更低。因此,我们尽量使用尽可能少的自由度。
现在,在先前的图中,你可以看到一个典型的六自由度(DOFs)机械臂。标记为数字1的底座执行器提供了滚动和改变俯仰的自由度。标记为数字2的肘部执行器只为机器人增加了一个俯仰的自由度。此外,编号为3的关节具有在俯仰和滚动中移动的能力。最后,我们这里有一个末端执行器作为夹爪;夹爪本身有一个自由度。因此,总的来说,我们可以称这个机器人为一个六自由度机器人。
我们在所有项目中都使用了一个单位,但我想在本章中强调它。这个单位是功率单位。我们之所以谈论它,是因为在本章中我们将控制多个伺服电机。当我们谈论多个伺服电机时,自然会谈到大量的功耗。在机械臂中,我们有六个伺服电机。现在,根据电机的品牌和型号,功耗会有所不同。但为了安全起见,假设每个伺服电机的功耗约为 1 安培是个不错的想法。你使用的多数电源可能无法提供这么大的瞬时电流。那么我们应该怎么办?
我们可以采取简单的方法,选择更高的功率输出。但相反,我们可以走一条非传统的路线。我们可以有一个在需要时可以提供这么多功率的电池。但问题是,任何电池都能满足我们的需求吗?显然,答案是否定的。
存在多种类型的电池。这些电池可以根据以下参数进行区分:
电压
容量
功率重量比
最大充放电速率
化学成分
这些内容将在接下来的小节中详细说明。
电压是电池可以产生的整体电位差。每个电池都有其特定的电压输出。有一点需要记住的是,这个电压会根据电池的充电状态略有变化。也就是说,当 12V 电池完全充电时,它可能输出 12.6V。然而,当它完全放电时,它可能达到 11.4V。因此,电池电压的含义是电池将提供的标称电压。
现在的第二个参数是容量。通常,当你购买一个电池时,你会看到它的容量以毫安时(mAh)或安培时(Ah)表示。这是一个非常简单的术语。让我用一个例子来解释这个术语。假设你有一个 5 Ah 容量的电池。现在,如果我连续 1 小时抽取 5 安培的电流,那么电池将完全放电。相反,如果我连续抽取 10 安培的电流,那么电池将在半小时内放电。通过这个,我们也可以使用以下简单的公式推导出电池的总功率:总功率(瓦)= 电池标称电压 x 电池总容量(安培)。
因此,如果你有一个 12V、容量为 10 Ah 的电池,那么总容量将是 120 瓦。
在上一章中,我们已经了解到重量在机器人技术中起着非常重要的作用,如果我们增加机器人的重量,那么移动它所需的力可以指数级增加。因此,功率与重量比的概念就应运而生。我们总是偏好那种极轻且相对于重量能提供大量功率的电池。功率与重量比的方程可以定义为以下:功率与重量比(瓦时/公斤)= 最大功率(瓦)/ 电池总重量(公斤)。
现在,假设一个电池提供 500 瓦的功率,重量为 5 公斤,那么功率与重量比将是 100 Wh/kg。功率与重量比越高,电池越好。
这可能是电池最重要的部分之一。通常,电池能够使机器人运行 1 小时。然而,机器人的功耗并不恒定。假设 90%的时间里,我们的机械臂消耗 2 安培的功率,那么电池容量是 2 Ah。然而,在操作过程中的一些时刻,机器人需要所有电机以最大功率工作。机器人的峰值功耗约为 6 安培。现在的问题是,2 Ah 的电池能否为机器人提供 6 安培的功率?
这是一个非常实际的挑战。你可能会说,使用比 2 Ah 电池更大的电池会更好。但是,正如你所知,这将极大地增加重量。那么解决方案是什么呢?
有一种叫做峰值放电电流的东西。这用C值表示。所以,如果我们的电池是 1C 的充电率,那么 2Ah 的电池在任何时候只能提供最大 2Ah 的电源。然而,如果电池是 10C 的充电率,那么它应该能够提供高达 20 安培的瞬间电源。如今,您可以找到能够提供高达 100C 甚至更多瞬间电源的电池。我们之所以有这个,是因为机器人的峰值功率消耗可能比它们的恒定功率消耗指数级更高。如果在任何时候电池无法提供足够的电力,那么机器人可能会出现错误行为,甚至可能关闭。
这个故事的第二部分是充电率。这是您可以提供给电池的最大充电电流。它也用相同的 C 值表示。所以,如果 C 值是 0.5,那么您可以向 2Ah 的电池提供最大 1 安培的充电电流。
换句话说,您最快可以在 2 小时内给电池充电。
市场上可以找到不同类型的电池,这些电池主要根据它们的化学成分进行分类。所有这些电池都有它们自己的优缺点。因此,我们不能说哪一个比另一个更好。这总是在各种因素之间的权衡。以下是在市场上可以找到的电池列表,以及它们的优缺点:
如您从这张表中可以看到,峰值功率输出是我们非常想要的,同样好的功率重量比也是;因此,在锂聚合物电池上投入大量资金是有意义的。
这些电池至少有 20C 的充电率,功率重量比比普通湿电池高约五倍。然而,它们的成本可能是普通湿电池的 10 倍以上。
现在我们知道了哪些电池适合那些更高的电流需求。一款 11.1V、2200mAh 的锂聚合物电池不会让您花费超过 20 美元,并且会为您提供您可能永远不需要的巨大功率。因此,我们已经解决了电源问题。现在是时候继续前进,使机器人手变得可用。
机器人手臂套件在 eBay 或 Amazon 上购买相当容易。组装起来并不困难,需要几个小时来准备。有些机器人手臂套件可能不包含伺服电机,在这种情况下,您可能需要单独订购。我会建议选择包含伺服电机的套件,因为如果您选择单独订购伺服电机,可能会出现兼容性问题。
如你所知,这些伺服将使用 PWM 工作,控制它们也不难。所以,让我们直接进入它,看看我们能做什么。一旦你组装好机器人手臂套件,按照以下方式连接伺服的电线:
现在,首先,我们需要知道我们机器人上连接的每个伺服的最大物理限制是什么。有各种技术可以做到这一点。最基本的方法是进行物理测量。这种方法可能很好,但你将无法充分利用伺服电机的全部潜力,因为你会有一定程度的测量误差。因此,你放入伺服的值将略小于你认为它可以达到的值。第二种方法是手动输入数据并找出确切的角度。所以,让我们继续使用第二种方法,并上传以下代码:
现在,让我们看看这段代码在做什么。这段代码看起来可能相当复杂,但它所做的是极其简单的。
使用前面的代码行,我们向用户打印了从 1-6 选择伺服的语句。当用户输入伺服的值时,这个值将被存储在变量j中:
你可能会问,我们为什么要这样做?这是出于一个非常重要的原因。假设你给它一个超出其物理限制的命令。如果是这样,那么伺服将不断尝试朝那个方向移动,不管发生什么。然而,由于物理限制,它将无法继续前进。一旦发生这种情况,然后,在几秒钟内,你将看到从伺服中冒出蓝色烟雾,这表明它已经死亡。问题是,犯这种错误很容易,损失也很明显。因此,为了防止这种情况,我们迅速将其恢复到中央位置,这样它就没有燃烧的可能性了。
现在,根据前面的代码,通过机器人对伺服 1-6 进行了相同的操作。既然你已经知道了发生了什么,是时候拿起笔和纸,开始为伺服器提供角度值了。请记住,这个代码的最终目标是找出最大限制。所以,让我们从 90 度开始做起来。在两侧给出一个值,直到你能够取到的值。在纸上列一个清单,因为我们需要它来编写下一部分的代码。
在本章的前一部分,通过我们的多次尝试,我们已经能够找到每个伺服器的最大位置。现在是我们使用这些值的时候了。在这一章中,我们将为伺服器的绝对最大值编程。在这个程序中,我们将确保伺服器不会超出定义参数的任何一侧,甚至一度。如果用户给出的值超出这个范围,它将简单地选择忽略用户输入,而不是造成自我损害。
那么,让我们看看如何完成它。在这个程序的一些部分,数值被加粗了。这些是需要用我们在本章前一部分记录的值来替换的值。例如,对于伺服器 1,记录的值是23和170作为两侧的最大值。因此,代码的变化将从if a[0] < 160 and a[0] > 30变为if a[0] < 170 and a[0] > 23。同样,对于每个伺服器,都必须遵循相同的程序:
现在,在这段代码中,我们做了一些非常基础的工作。你可以安全地说,我们所做的只是将ChangeDutyCycle()函数放入一个if语句中。这个if语句将决定伺服器是移动还是保持在它自己的位置。对一些人来说,这个程序在特殊部分中可能看起来非常天真。但是,相信我,它不是。这个语句现在将被用作从现在开始每个程序的一部分。所有为伺服器运动编写的代码都必须通过这个if语句检查要发送到伺服器的最终值;因此,代码的基本可视化是极其必要的。
现在解释已经完成,是时候给你不同的命令,看看它们是否在安全工作范围内工作。
在上一章中,我们学习了如何确保机器人在安全限制下工作的基础知识。在这一章中,我们将探讨如何通过点击按钮让机器人执行不同的活动,而不是逐个输入值。
要做到这一点,我们需要理解一些运动的高级概念。每当你观看任何视频或玩游戏时,你肯定遇到过“每秒帧数”(FPS)这个术语。如果你没有听说过这个术语,那么让我为你解释一下。现在制作的所有视频实际上都是由静态图像组成的。这些静态图像是由每秒点击 25-30 次的相机捕捉的。当这些图像以与捕捉时相同的速率在屏幕上播放时,就形成了一个平滑的视频。
类似地,在机器人中,我们确实有框架的概念。然而,这些框架并不是图像,而是机器人为了实现特定运动必须遵循的多个步骤。在一个简单的机器人程序中,可能只有两个框架,即初始框架和最终框架。这两个框架将对应初始位置或最终位置。
然而,在现实世界中,这并不总是可能的,因为每当机器人直接从初始位置移动到最终位置时,它通常会遵循一条具有特定曲率的特定路径。然而,这条路径上可能会有障碍,或者这条路径可能不是所期望的,因为需要遵循的路径可能是不同的。因此,我们需要框架。这些框架不仅定义了机器人从初始位置到最终位置的运动,还将这两个位置之间的过渡分解成多个步骤,使机器人遵循所需的路径。
这可以被称为帧编程,我们将在本章中介绍。需要注意的是,帧数越多,机器人的运行越平滑。你还记得我们看到的监控录像吗?我们可以说是不够平滑,有很多颠簸。这是由于监控摄像机的帧率低。他们不是在 30 FPS 下工作,而是在 15 FPS 下工作。这是为了减少视频的存储空间。然而,如果你看最新的视频,有些游戏和视频的帧率比正常情况下要高得多。我们最新的相机工作在 60 FPS,使视频更加平滑,观看起来更加愉快。机器人也是如此。帧数越多,运动越平滑、越受控。但是,请确保不要过度追求。
现在,要从一个位置移动到另一个位置,我们将在一开始就设置每个伺服电机的角度值。一旦获取,它将自动依次执行这些值。要这样做,请继续编写以下代码:
在这个程序中,你可以看到我们复制了之前的程序,只是做了一些非常小的改动。那么,让我们看看这些改动是什么:
在这里,我们正在为每个伺服系统获取输入值并将其存储在不同的列表中。对于伺服 1,将使用列表a;同样,b将用于伺服 2,以此类推,直到f。在代码的前几行中,机器人将提示用户填写motor 1的六个帧值。然后,它将询问motor 2的六个值,以此类推,直到motor 6:
给伺服系统提供 PWM 的整个程序都集中在这个 for 循环中。这个循环将检查i的值,并在每次迭代时增加它。i的值将从1开始,循环将运行并增加i的值,直到它达到6。
对于从伺服 1 到伺服 6 的其余伺服系统,也制作了类似的程序。因此,所有这些都将逐个读取它们对应列表的值,并按照用户编程的方式改变伺服的角度。此外,随着循环的执行,i的值将增加,从而使程序读取列表中提取的不同值。列表中伺服的每个值都对应于不同的帧,因此机器人通过它进行解析。
所以,继续前进,享受制作机器人做一些惊人的动作吧。只是要注意要温柔对待它!
制作一个机器人手臂竟然如此简单,而且只需一点代码,我们现在就能按照我们想要的方式控制它。然而,你可能已经注意到一个问题,那就是机器人移动的方式是我们想要的,但不是我们想要的速度。在使用基于数字 PWM 的伺服系统时,这是一个非常常见的问题。
这些伺服系统没有内置的速度控制。它们的控制系统被编程为尽可能快地移动伺服系统以到达目标位置。因此,为了控制速度,我们不得不自己调整程序,并给它一个平滑的线性进展。
速度控制可以通过几种不同的技术来完成。所以,不多说,让我们看看代码。在你写之前,先读一读,然后看一遍代码,然后看看下面的解释。这样,你将更好地了解我们在做什么。这将使编写代码更快、更容易。所以,让我们看看它:
在这个程序中,有很多东西。我们应该逐一了解它们,以便理解它。所以,让我们看看我们在做什么:
在这里,我们定义了六个新的变量,名称为prev0到prev5,并且它们都被赋予了90的值。这里的prev代表之前,所以这将用来指示前一个值。
在代码行for i in range 6之后,我们有前一行代码,它基本上是在检查a[i]的值是否与prev0相等。同样,它也在检查b[i]的值是否与prev1相等,依此类推。直到所有这些都不成立时,while循环才会成立,并且会循环程序内的代码,直到条件不成立。也就是说,所有的prev值都恰好等于列表中相应值的值。
再次,这对你来说可能有点奇怪,但请相信我,它将会非常有用,我们很快就会看到:
现在,这里才是真正的关键。这是控制伺服器速度的主要程序。在这个程序中,第一行很简单;它会检查给它提供的值是否有效,即是否在安全范围内。一旦完成这个检查,它将检查a[Ii]的值是否小于或大于前一个值。如果它大于a[i],那么它将取前一个值并减去用户指定的速度。如果它小于a[i]的值,那么它将以前一个值为基础增加用户指定的速度。
因此,如果你看的话,代码只是在while循环运行时简单地增加或减少前一个值。现在,while循环将一直运行,直到prev的值等于对应列表的值。也就是说,循环将一直增加值,直到达到指定的位置。
因此,速度值越低,每次增加的值就越低,从而整体降低速度。
这也是所有其他伺服器将要经历的过程。这听起来可能非常复杂,但事实并非如此!编程很简单,每次你将其分解成小块并逐一理解时,它都会保持简单!
在这一章中,我们了解了机器人的基本知识,它的电源和编程。通过一个非常简单的程序,我们能够找出伺服器的限制,并将这些限制应用到确保伺服器不会损坏自己的程序中。我们对框架有了基本的了解,并基于框架进行了一些编程。最后,我们还通过自己的程序在基本层面上控制了伺服器的速度。