Python 树莓派编程学习指南(三)
九、猫的玩具
大多数人都很熟悉“猫追小红点”的模式。它非常受欢迎,甚至在《怪物史莱克》的一部电影中有一个简短的场景。有些猫会追逐激光点,直到它们掉下来。有些人只会追逐一小会儿。无论如何,任何养猫的人都可能在某个时候和他们的猫朋友玩耍时使用了激光指示器。
但是如果你不在的时候可以逗逗你的猫不是很好吗?与你所想的相反,激光笔并不一定要由人来握持和控制。只需一点编程和机械工程魔法,你就可以拥有一个自主猫玩具。
然而,在我们将在本章构建的猫玩具项目中,我们不会就此罢休。我们就是不能。毕竟,不管你的猫在不在,让玩具一直动是没有意义的,不是吗?因此,我们将添加一些特殊的东西—红外传感器。这样,只有当你的猫在的时候它才会打开,当猫离开房间的时候它就会关闭。
你准备好了吗?让我们从获得制作猫玩具所需的零件开始。
零件购物清单
这只猫玩具的一个很好的特点是,除了简单和自制之外,零件实际上相当便宜。您将需要以下内容:
图 9-1
与猫玩具一起使用的普通激光笔
-
覆盆子馅饼
-
两个标准(不是连续)伺服系统——我推荐视差 900-00005 (
http://www.parallax.com/product/900-00005),但任何型号都可以 -
一个便宜的激光笔,你可以在宠物店花大约 10 美元买到(见图 9-1
-
PIR 运动传感器(
http://parallax.com/product/910-28027) -
胶水/环氧树脂
-
其他电线(红色、黑色等)
-
平头螺钉
-
电工胶带
-
冰棒棍
-
9V 电池
-
一个可以装下所有东西的容器——我用了一小段 PVC 管
玩具背后的概念
让这个玩具工作的关键是随机运动。如果你给玩具编程做出一系列同心圆,一个接一个,然后重复图案,用不了多久,你的猫就能认出图案,并感到厌烦。然而,通过使用 Python 的random或randint函数,你可以随机化模式并让你的猫(可能还有你的小孩)开心。
另一件要记住的事情是,你将在两个轴上随机化运动——x 轴和 y 轴——同时将运动保持在一定的范围内(这是随机化函数的参数和伺服系统的运动极限出现的地方)。你将使用两个伺服系统,但它们将被连接在一起,以便控制一个激光笔的运动。为了控制伺服系统,就像本书中的其他几个项目一样,我们将使用 Pi 的 GPIO 管脚和 Python 的 GPIO 库。这个项目的另一个好处是,伺服系统消耗的能量如此之少,以至于它们可以用一个简单的 9V 电池供电。你仍然需要独立于 Pi 为它们供电(总是一个好主意,不管是什么项目),但是你不需要像其他项目那样需要一个花哨的电池设置。
创建和使用随机数
你可能认为创建和使用随机数很简单。只需调用一个函数,获得一个随机整数,然后继续。虽然这可能是它在实践中的工作方式,也是我们大多数人所想到的,但随机化输出的实际过程非常有趣——这实际上是许多计算机科学家和数学家密集研究的主题。(参见侧栏“哦,随机性。”)要在 Python 中得到一个随机数,可以使用几个内置函数。第一个是random()函数。该函数返回一个浮点数(一个浮点数)。它没有参数,而是返回一个介于 0.0 和 1.0 之间的随机数。这通常非常有用,但出于我们的目的,我们需要更大的数字,最好是整数格式,这可以用randint()函数来完成。
哦,随机性
自古以来,人类就有许多产生“随机”数字的方法,从掷硬币、掷骰子到洗牌。对于大多数应用来说,这些都可以。如果需要决定谁先踢球,可以抛硬币;从 52 张牌中选择一张足够随机,当魔术师成功猜出它的身份时,它会给人留下非常深刻的印象。
然而,使用这些方法为任何真正的数学或统计工作生成随机数有两个主要问题。首先,它们都是基于物理系统——圆形硬币在空中的翻转,或多或少的方形骰子在不平坦的地面上的滚动。因为它们是物理系统,所以永远不可能是真正随机的。给定足够多的迭代,一个模式将最终基于系统中的缺陷开始出现。例如,一枚硬币,由于两面都刻有图案,所以在一面有轻微的重量偏差。如果你翻转的次数足够多,这个模式会在收集的结果中显示出来。它可能需要几百万或几十亿的翻转,但它会出现。同样,骰子永远不会是完美的正方形或完美的重量,在足够数量的滚动后,最终会偏向一侧。
这些生成随机数的方法的第二个问题是,它们花费太多时间。如果你需要一批一百万个随机数,你将会掷一枚硬币很长时间来得到所有这些数字。只是对于大批量数据来说不切实际。
但是计算机在处理大量的数字和数据方面表现出色,它们可以以令人难以置信的速度生成数据。一般的台式计算机理论上可以处理 70 亿次浮点运算。(也就是每秒 70 亿次浮点运算。)在这个速度下,生成一百万个随机数需要。。。让我想想。。。拿着这两个。。。除以黄色。。。嗯。。。大约 7 毫秒。比洗牌快多了。
然而,计算机也是物理系统。是的,你是从计算机中央处理器的“网络空间”中产生数字的,但是那个处理器是一个带有物理晶体管和导线的物理硅芯片。无论你用什么程序来生成这些随机数,它们最终都会显示出一种模式,表明它们不是真正的随机。因此,你可以看到数学家和科学家对随机数的兴趣。一个真正的随机数生成器在许多科学领域都非常有用,尤其是在密码学领域。大多数密码是基于随机散列码的;一个真正随机的代码将会更加难以破解,这也是人们如此感兴趣的原因之一。
当前的随机数生成器通过使用产生长串伪随机数的算法来工作,通常基于乘法和模数运算的组合。根据算法的质量,这样生成的数字可能是也可能不是加密的,尽管它们对于视频游戏这样的应用来说通常是足够随机的。换句话说,你用来生成随机数的算法可能不会阻止超级计算机破解代码的协同努力,但当你玩使命召唤:我们都死在街区六年级学生手中的那一天时,它足以产生对手。
这种生成过程就是为什么当您开始一个将使用随机数的程序时,您必须用另一个数“播种”随机数生成器,例如今天的日期或您计算机上的系统时间。随机种子只是一个用来初始化程序中随机数向量算法的数字。只要原始种子被忽略,后续的初始化应该提供足够的随机数。是的,最终会出现一种模式——这是不可避免的——但是随机种子发生器应该足够随机以满足大多数需求,尤其是随机运动的猫玩具。薛定谔的猫可能不会被愚弄,但你的猫应该被愚弄。(抱歉,这只是一个小小的物理幽默。)
根据 Python 文档,randint(a, b)返回一个随机数n,这样n就在a和b之间,包含这两个值。换句话说,下面的代码
>>> import random
>>> x = random.randint(1, 10)
>>> print x
应该返回1, 2, 3, 4, 5, 6, 7, 8, 9,或者10。我们将用它来为我们使用的伺服系统生成位置。
注意
Python 文档可以在 http://docs.python.org 找到。我强烈建议,当你学习语言的时候,养成查阅文档的习惯。您也可以在交互式 Python 提示符下键入help(function)来获得相同的阅读材料。
使用 GPIO 库
现在你已经知道我们如何生成随机数,你需要知道如何控制将连接到 Pi 的伺服系统。幸运的是,有一个专门为此目的设计的 Python 库,并且预装在 Pi 中。这个库使我们能够访问 Pi 的通用输入输出(GPIO)引脚,简称为RPi.GPIO。
如果您还没有安装 Python 开发库,那么您可能需要手动安装。首先,通过键入以下命令确保您的 Pi 是最新的
sudo apt-get update
然后通过键入以下命令来安装这些包
sudo apt-get install python-dev
我们现在可以开始键入这个项目的最终代码。记住,它可以从Apress. com作为cat-toy.py获得。现在,要访问 Pi,请在程序的第一行中调用以下内容:
import RPi.GPIO as GPIO
然后,通过键入以下命令进行配置
GPIO.setmode(GPIO.BOARD)
这使您可以根据标准引脚排列图上的标签来识别引脚,如图 9-2 所示。
图 9-2
GPIO 引脚的引脚排列
注意
请记住,对于GPIO.setmode(GPIO.BOARD),当您提到引脚 11 时,您实际上指的是物理引脚#11,它在图 9-2 )中转换为 GPIO17,不是 GPIO11,它转换为物理引脚#23)。
一旦设置好模式,就可以将每个引脚设置为输入或输出。Arduino 的用户可能会认识到这里的概念,但这是您为 Pi 键入的内容:
GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.IN)
等等。一旦将一个管脚设置为输出,就可以通过输入
GPIO.output (11, 1)
或者
GPIO.output (11, True)
随后,您可以通过输入以下命令将其关闭
GPIO.output (11, 0)
或者
GPIO.output (11, False)
我们将使用两个引脚进行伺服控制,一个引脚为激光笔供电,另一个引脚从红外传感器读取输入。
控制伺服系统
伺服系统是许多不同应用的重要组成部分,从无线电控制的车辆到高端机器人。从本质上讲,伺服系统只不过是一台 DC 发动机。然而,在软件的帮助下,你可以对电机的旋转进行非常精细的控制。例如,如果您需要它旋转 27.5 度,然后停止(假设伺服能够),您可以通过编程向它发送该命令。
那么,如何利用 Pi 上的 GPIO 引脚实现这一点呢?不幸的是,你不能只是将伺服的信号线(通常是白色的)插入 GPIO 输出引脚,给它一个正负电压,然后期望它工作。有可能,但也有可能没有。
答案在于如何控制伺服系统。作为硬件的模拟部分,它们早于今天的大多数数字硬件,包括圆周率。它们使用脉宽调制(PWM)信号工作。要控制它们,你必须能够通过你正在使用的任何机制发送 PWM 信号,无论是 Arduino 引脚、串行电缆还是 Raspberry Pi 的 GPIO 引脚。如果你想设置一个伺服系统的位置,你需要给它发送规则的电流脉冲——每秒 50 次是平均脉冲速度——而不是一个长脉冲。如果你想知道,每秒 50 次相当于每 20 毫秒(ms)一次脉冲。
此外,脉冲的长度决定了伺服系统的位置。例如,每 20 毫秒发送一次的 1.5 毫秒的 on 脉冲将把伺服系统发送到中心位置。较短的脉冲会使它转向一个方向,而较长的脉冲会使它转向另一个方向。因此,通过精确计时发送给伺服系统的脉冲长度,可以精确定位伺服磁头。
图 9-3 中的图表最好地说明了这一点。
图 9-3
伺服系统的占空比
如果你想把伺服发送到中间“零”位置,你每 20 毫秒发送一个 1.5 毫秒的 on 脉冲,这可以被认为是 7.5%的占空比。同样,如果你想用 0.5 毫秒的 on 脉冲逆时针转动它,它的占空比为 2.5%,较长的 2.5 毫秒 on 脉冲转换为 12.5%的占空比。换句话说,在 2.5%、7.5%或 12.5%的时间里,伺服系统被给予“高”脉冲。
这些方向适用于标准— 而非连续—伺服系统。不同之处在于,标准伺服系统使用脉冲的长度来确定它们的最终位置(以偏离中心的度数为单位),而连续伺服系统使用脉冲的长度来确定它们应该转动的速度。标准伺服将移动到其目的地位置,然后停止,直到新的命令被发送;连续伺服几乎总是在移动,移动的速度由脉冲长度决定。虽然你可以为猫玩具使用任何一种类型的伺服系统,但使用标准伺服系统更有意义,因此既有精确定位的能力,又有完全停止的能力,让猫至少暂时“抓住”小红点。
然而,这种移动伺服系统的方法的问题是,很难使用 Pi 和 Python 向 GPIO 引脚发送毫秒级脉冲。像 Pi 上运行的所有其他进程一样,Python 不断被系统级运行的进程中断,至少可以说,这使得 Python 程序精确计时脉冲是不切实际的。然而,GPIO 库又一次提供了我们需要的东西:您可以使用该库将 GPIO 引脚设置为 PWM 引脚,赋予它向该引脚发送正确长度脉冲所需的占空比。
因此,虽然理论上有可能编写这样的脚本:
while True:
GPIO.output (11, 1)
time.sleep (0.0015)
GPIO.output (11, 0)
time.sleep (0.0025)
如果成功的话,结果很可能完全出乎意料。相反,我们可以做的是使用RPi.GPIO库函数,通过输入以下命令,将伺服的信号引脚(此处示例中的引脚 11)设置为 PWM 输出引脚
p = GPIO.PWM(11, 50)
在这种情况下,50 将脉冲设置为 50Hz(每 20 毫秒一个脉冲),这是伺服工作所需的频率。然后,我们可以通过键入以下命令将引脚的占空比设置为 7.5%
p.start (7.5)
如果我们将p.start(7.5)放在一个while循环中,结果是伺服将移动到中心位置,然后停留在那里。用p.ChangeDutyCycle()改变占空比将允许我们在不同的方向上移动伺服系统,这就是我们对猫玩具的追求。因此,例如,要查看您的伺服来回移动,请尝试以下脚本:
import RPi.GPIO as GPIO
import time
GPIO.setmode (GPIO.BOARD)
GPIO.setup (11, GPIO.OUT)
p = GPIO.PWM (11, 50)
p.start (7.5)
while True:
p.ChangeDutyCycle (7.5)
time.sleep (1)
p.ChangeDutyCycle (12.5)
time.sleep (1)
p.ChangeDutyCycle (2.5)
time.sleep (1)
运行这个脚本应该使你的伺服来回扫描,在每次方向改变之间暂停一秒钟。
留给我们的猫玩具脚本的就是实现一些随机数。这些数字将决定哪个伺服移动,在哪个方向,以及多长时间。结果应该是一个相当随机的二维路径。
构建伺服机制
我们的猫玩具将在两个方向上扫描激光笔,这意味着我们需要一个能够做同样事情的伺服系统。虽然伺服系统通常不能进行二维运动,但我们可以使用两个相互连接的普通伺服系统轻松构建一个云台伺服机构。
注意
这个过程将永久地结合你的两个伺服系统,使它们不可分割,所以请确保你有其他人用于其他项目。但是,请记住,像您在这里制作的平移-倾斜设置对于需要在二维空间移动项目来说是一个方便的东西,您可能会再次使用它。所以,这并不是说你完全破坏了两个伺服系统。
你所需要做的就是把一个伺服机构的主体安装到另一个伺服机构的角上。为了建立安全的连接,您可能需要拆下固定底座伺服系统(为了简化任务,我们称之为 x 轴伺服系统)伺服角的螺钉,并将塑料锉平一点。您正在尝试尽可能展平伺服的顶部,使其与另一个(y 轴)伺服的主体紧密配合。
当它尽可能平整时,使用强力环氧树脂或粘合剂将 y 轴伺服机构粘合到 x 轴伺服机构的角上。我使用大猩猩胶水,得到了如图 9-4 所示的结果。
图 9-4
粘合的 x 和 y 伺服系统
激光笔现在可以(经过一些不那么小的修改)安装到顶部(y 轴)伺服。
构建激光机制
我们将使用标准的激光笔,但我们将在几个重要方面对其进行修改。最重要的方式是,我们将使用 Pi 的 GPIO 引脚供电,而不是使用电池。这很重要,因为它使我们能够通过编程来打开和关闭激光器,而不是通过摆弄按钮开关。
要改装激光笔,你需要一些绝缘胶带和一个大约两英寸长的平头螺钉,螺钉头比激光笔的内径稍小一点。用绝缘胶带包裹螺丝,使其紧贴指针内部。如果有必要,当你完成后,切断胶带的末端(如图 9-5 所示),这样螺钉的尖端就露出来了。
图 9-5
激光笔的螺旋机构
拆下激光笔的底座,取出电池。首先将螺钉头推入指针主体,使螺钉头向下推动通常由电池压住的内部弹簧。你可能不得不玩弄你用来包裹螺丝的胶带的数量;你希望它足够紧,能够压下弹簧并保持在原位,而不会有移动的危险。
我们还需要用胶带把激光笔的电源按钮粘住,这样它就会一直开着。如我所说,我们将负责从 Pi 给指针供电。用一条胶带包住指针,按住按钮。
此时,你应该有类似于图 9-6 中图像的东西。
图 9-6
完整的激光指示器机构
如果你想测试你的工作(不是一个坏主意),使用几个鳄鱼夹连接螺丝的点到引脚 6(接地引脚),然后连接指针的身体到引脚 1 (3.3V)。激光器应该亮起,表明您直接从 Pi 的电源引脚为其供电。如果什么都没发生,确保螺丝头压在弹簧上,电源按钮用胶带牢牢固定,所有连接都很牢固。一旦连接牢固,你就可以把激光安装到你的二维伺服装置上了。
将激光器连接到伺服系统
将激光器连接到伺服系统可能是这个项目中最简单的部分。如果你和我一样,你不会想把激光永久地连接到你的云台伺服系统上,因为这个系统可以在其他项目中派上用场。所以,我们需要找到一种方法来暂时把激光附加到伺服喇叭上。
我用一根冰棒棒把它拉下来。我们可以把激光粘在冰棒棍上,然后把棍拧到伺服喇叭上。这可以用伺服系统附带的螺丝来完成(你还有它们,不是吗?)或者你能在车间里找到的最小的螺丝钉。相信我——那些是伺服喇叭上非常小的孔。
使用强力胶(同样,我喜欢大猩猩胶),将激光组件粘贴到冰棒棍上。当该区域干燥时,使用小螺钉将冰棒棒连接到伺服喇叭上。当你完成后,你应该有一个看起来像图 9-7 的设备。
图 9-7
安装在伺服系统上的激光器
这里需要注意的是:花一些时间定位激光笔及其相关的伺服系统,这样无论两个伺服系统在循环中的哪个位置,激光都可以自由旋转。显然,最简单的方法是移除将伺服喇叭固定在伺服系统上的螺钉,并根据伺服系统的行程弧线重新定位喇叭。然后,运行两个伺服通过所有可能的位置,并确保您的机制没有绑定在其运动的任何一点。由于标准的伺服系统只能通过大约 180 度的圆弧,你应该能够为所有部件找到一个合适的位置。
这个项目的最后一个组成部分是连接运动传感器。
连接运动传感器
运动传感器(如图 9-8 所示)不仅能节省你的电池,还能极大地促进的酷因素:只有当你的猫(或狗,或室友,或大脚野人)靠近它时,你的玩具才会打开。
图 9-8
视差红外传感器
连接红外传感器很简单:正极和负极引脚连接到电源,第三个引脚(图 9-8 中最左边的引脚)连接到配置为输入的 Pi 的 GPIO 引脚之一。当传感器检测到运动时,它会在输出引脚上输出一个高电平信号,然后该信号会传输到 Pi 的输入引脚。通过将 GPIO 引脚配置为输入,我们可以读取该信号,并仅在该信号出现时执行控制玩具的 Python 脚本。
在我们继续之前,我需要讨论一下上拉电阻或下拉电阻的重要概念,我在前一章中也谈到过。每当电子设备中有输入时,如果该输入不直接读取任何内容,则称为浮动输入。这意味着从该输入中读取的值绝对可以是任何值。我们需要定义输入的“空”状态,以便我们知道输入何时改变。
为了定义输入的“空”状态,我们通常在输入和正引脚(从而产生一个上拉电阻)或地(从而产生一个下拉电阻)之间连接一个电阻(10K 或 100K 是常见值)。使用哪一种并不重要,重要的是输入被拉高或拉低。因此,如果引脚上没有读取任何内容,并且它通过一个下拉电阻接地,则读数为“0”当它不再显示“0”时,我们就知道它正在接收输入。
对于我们的 IR 传感器,我们需要将未检测到运动时引脚上的读数定义为“低”,因此我们将使用一个下拉电阻。幸运的是,为了使这个过程简单,GPIO 库允许我们在代码中将一个引脚定义为输入时这样做,就像这样:
GPIO.setup(11, GPIO.IN, pull_up_down=GPIO.PUD_UP)
如果我们将 IR 传感器的 OUT 引脚连接到 Pi 上的引脚 11,并用前面的代码行初始化该引脚,则在检测到移动之前,引脚 11 上读取的所有内容都将为“低”。在这一点上,引脚将读取“高”,我们可以调用打开激光器并移动它的功能。
为了测试传感器和我们的编码能力,我们将相应地设置 GPIO 引脚。我们可以使用一个简单的设置来测试我们的代码;当传感器跳闸时,试验板上的 LED 会亮起。在 Python 脚本中,输入并保存以下代码:
import RPi.GPIO as GPIO
import time
GPIO.setwarnings (False) #eliminates nagging from the library
GPIO.setmode (GPIO.BOARD)
GPIO.setup (11, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup (13, GPIO.OUT)
while True:
if GPIO.input (11):
GPIO.output (13, 1)
else:
GPIO.output (13, 0)
测试代码到此为止!要测试代码和传感器设置,首先将传感器上的(+)引脚连接到 Pi 上的#2 引脚。将 OUT 引脚连接到 Pi 上的引脚#11。将(–)引脚连接到试验板上的公共地线。最后,将 Pi 上的 13 号引脚连接到一个电阻,然后连接到 LED 的正极引脚,并将 LED 的负极引脚连接到公共接地线。您应该会得到类似于图 9-9 中所示的配置。
图 9-9
红外传感器和 LED 测试设置
注意
图 9-9 中的图像是用 Fritzing ( http://www.fritzing.org )创建的,这是一个伟大的开源试验板/设计工具。它是跨平台的,非常容易使用和学习,强烈推荐。
当您运行脚本时(记住以超级用户身份执行代码,或者 sudo ,因为您正在访问 GPIO 引脚),当您在传感器周围移动您的手时,LED 应该会亮起,然后在几秒钟不动后再次熄灭。如果不起作用,检查你的连接和你的零件;相信我,烧坏的 LED 会导致各种令人头疼的故障排除问题!
如果一切按计划进行,我们现在可以给玩具布线并完成所有连接。
连接所有的位
在您成功测试了红外传感器、操作它所需的代码以及用于操作伺服系统的代码之后,当所有东西都连接到其他东西时,就该将所有东西都连接起来并进行所有连接了。这就是小型试验板派上用场的地方——你可以将所有的地面连接在一起(绝对必要的),并根据需要为所有东西供电。我使用了 9V 电池的两个伺服,但你可以随意尝试不同的电池。在这种情况下,没有必要像我们在其他一些项目中那样使用可充电的 RC 电池,因为重量不是一个真正的问题,而且伺服系统不会太快耗尽电池,因为它们不是一直在运行——这要归功于传感器。然而,你确实需要用一个独立的电源给伺服系统供电,而不是你用来给 Pi 供电的电源;否则,你可能会得到不断冻结和崩溃。对于具有两个电源通道的试验板,您可以将+9V 沿一个正极通道向下,将 Pi 上的 2 号引脚连接到另一个正极通道,然后将两个负极连接在一起。然后,您可以将激光电源和红外传感器电源连接到 Pi 的电源通道,并将两个伺服系统连接到 9V 电源通道,这样您的所有地线就会连在一起。参见图 9-10 ,图中显示了使用试验板连接在一起的各个部件。
图 9-10
最终组件连接
图 9-10 并不完全准确,因为激光笔是物理连接到伺服系统的,但你可以看到电气连接。指针由引脚#11 供电,伺服系统由引脚#13 和#15 供电,引脚#19 是传感器输入。然后,每样东西都被赋予各自的电压使其工作。
有几个机械工程任务涉及到这个玩具的建设,至少是最好的方法来永久连接电源和地线的激光笔。虽然鳄鱼夹很适合测试,但一旦伺服系统开始将指针急拉成圈,它们就要被取消了。
最好的解决办法是将电线焊接到指针部分。如果可以,用砂纸打磨螺丝和指针外壳的尖端。如果有空间,在指针外壳上钻一个小孔来固定正极导线。然后,附上你的电线和焊接一切。您的结果可能会有所不同,这取决于您的焊接能力和您正在使用的材料。你甚至可以使用胶水,只要你不要让电线和指针部件的金属触点之间沾上胶水。所有的电线都必须连接牢固,这一点很重要。
最后一步是将整个设备安装在某种容器中,以保持其位置,并保护机器的内脏免受猫科动物的研究。你可以保留所有的部分,只要它不被最终用户(你的猫)发现。我倾向于在这种情况下使用 PVC 管,因为标准的伺服系统几乎完全适合两英寸内径的 PVC 管。在这种情况下,您可以将较低的伺服安装到管道的边缘,大多数电线和内脏可以安全地存放在里面,并在侧面钻一个孔用于红外传感器。Pi 不适合,但可以安全地存放在一个单独的盒子里,通过长跳线连接到管道组件。如果您决定使用 Pi Zero(它完全能够运行这段代码),它也将适合管道内部,因此这可能是需要考虑的事情。
希望你最终得到一个看起来像图 9-11 的猫玩具。
图 9-11
成品猫玩具
不好看,但是你的猫不会在意。当然,它可以用管子末端的盖子和一层油漆来装饰。可能要记住的最重要的细节(这里没有显示)是隐藏内脏和电线,以免被窥探(和爪子)。我在这里使用的 PVC 管太窄了,但是一个具有较大横截面的管可以很容易地与 Pi 和它内部的所有其他内容相配合,从而形成一个独立的单元。然后,你可以给外面加个电源开关,就万事俱备了。
应该就是这样!执行cat_toy.py脚本现在应该可以让你的猫朋友(很可能是你的人类朋友)开心几个小时。值得注意的是,你现在有知识和能力用你的树莓派瞄准并发射激光 ??。是的,它是一个小得可怜的激光笔,但这个概念可以很容易地应用于任何激光器,无论大小或功率。任何激光。
玩得开心!
最终代码
这段代码可以在Apress.com网站上以cat-toy.py的名字获得,它设置 GPIO 输出引脚,为随机数发生器播种,然后旋转伺服系统,以随机运动的方式点亮激光器。
import RPi.GPIO as GPIO
import time
import random
random.seed()
#set pins
GPIO.setmode (GPIO.BOARD)
GPIO.setwarnings (False)
GPIO.setup (11, GPIO.OUT) #laser power
GPIO.setup (13, GPIO.OUT) #X-servo
GPIO.setup (15, GPIO.OUT) #Y-servo
GPIO.setup (19, GPIO.IN, pull_up_down=GPIO.PUD_UP) #in from IR
#setup servo pwm
p = GPIO.PWM (13, 50)
q = GPIO.PWM (15, 50)
#set both servos to center to start
p.start (7.5)
q.start (7.5)
def moveServos():
"Turns on laser and moves X- and Y-servos randomly"
lightLaser ()
p.ChangeDutyCycle (random.randint (8, 12))
time.sleep (random.random())
q.ChangeDutyCycle (random.randint (8, 12))
time.sleep (random.random())
p.ChangeDutyCycle (random.randint (3, 5))
time.sleep (random.random())
q.ChangeDutyCycle (random.randint (3, 5))
time.sleep (random.random())
dimLaser ()
def lightLaser():
GPIO.output (11, 1)
def dimLaser():
GPIO.output (11, 0)
#main loop
while True:
#check for input from sensor
if GPIO.input (19):
moveServos()
time.sleep (0.5) #wait a half sec before polling sensor
else:
dimLaser()
time.sleep (0.5)
摘要
在这一章中,你成功地构建了一个两轴伺服机构,黑了一个要被树莓派发射的激光笔,并利用你的编程技能随机指向并发射激光来娱乐你的猫。
在下一章,我们将把你的圆周率拿出房子,用无线电控制的飞机把它送上天空。
十、无线电控制的飞机
我们许多人长久以来都梦想着飞翔,梦想着在空中翱翔,像鸟儿一样自由。或者,就像飞行员小约翰·马吉所说的那样,挣脱“地球粗暴的束缚”,用“镀银的翅膀”在天空中舞蹈,轻松优雅地登上“狂风呼啸的高峰”
不幸的是,摆脱地球的束缚通常需要我们没有的时间和金钱,这可能部分解释了无线电控制(RC)飞机的出现。虽然我们可能负担不起地面或飞行学校的费用,但 1:12 比例的 Piper Cub 可以让我们感觉不那么踏实,我们有机会驾驶真正的飞机,而不必离开陆地。
然而,问题是,虽然我们可以从地面控制飞机,但这并不完全像真的在那里。虽然有复杂、昂贵的方法将小型摄像机连接到你的遥控飞机或无人机上,但如果你可以用你的 Pi 做类似的事情,那就太好了。如果你能跟踪你的飞行,然后将坐标加载到谷歌地球上,看看你的飞行是什么样子,那将会非常酷。
在这个项目中,你可以做到这一点。这个项目的计划是将 Pi、摄像机和 GPS 接收器放在一架遥控飞机上,然后驾驶它。Pi 的摄像头将在飞行过程中拍照,GPS 将记录位置数据。然后,当你回家时,你将使用一个 Python 脚本将位置数据解析成一个可以上传到 Google Earth 的 KML 文件。
注意
这一章包含了一些高级的编程概念——可能比你到目前为止遇到的任何东西都要高级,比如线程,甚至一点点面向对象编程(OOP)。但是它们并不复杂,我会在概念出现时解释它们。
零件购物清单
虽然这个项目不需要很多零件,但它实际上可能是本书中最昂贵的项目,因为它需要一架中型无线电控制(RC)飞机。以下是您需要的一些附加零件:
-
树莓派(带摄像头)
-
GPS 接收器(
https://www.adafruit.com/products/746) -
接收机天线(可选)(
https://www.adafruit.com/products/851和https://www.adafruit.com/products/960) -
中型遥控飞机
-
RC 电池和 5V 调节器为 Pi 供电
如果你碰巧已经是一个遥控爱好者,你很可能有一架飞机可以使用,但如果你是这项运动的新手,你需要购买一架好的入门飞机。作为一个业余爱好者,我可以推荐一个不错的初学者飞机 Switch,由 Flyzone(如图 10-1 )提供。
图 10-1
开关(图像飞行区飞机在 http://www.flyzoneplanes.com )
这架飞机足够坚固,足以承受你学习驾驶时的几次碰撞,足够稳定,足以让一个完全的初学者驾驶,最重要的是,足够强大,可以承载 Pi、GPS 接收器和电池的额外重量。它的名字来源于这样一个事实,当你越来越擅长飞行时,你可以将机翼从稳定的“顶部”配置移开,并将其切换到更低的“中间”配置,以进行更多的特技飞行。正如你将看到的,“顶部”配置是完美的,不仅因为它对初学者来说很容易,而且因为 Pi 和 GPS 可以舒适地位于机翼顶部。
准备好了吗?让我们首先为我们的 plane 程序创建一个目录:
mkdir plane
然后通过键入cd plane导航到其中。
现在,让 Pi 与我们的 GPS 设备通信。
将 GPS 接收器连接到 Pi
要让您的 Pi 与 GPS 接收器对话,您首先需要连接两者。为此,我们将使用名为gpsd的 Python 库和 Pi 的 UART(通用异步接收器/发送器)接口(引脚 7 和 8)。Python gpsd模块是一个更大的代码库的一部分,旨在允许 Pi 等设备监控连接的 GPS 和 AIS 接收器,并具有 C、C++、Java 和 Python 的端口。它允许您“读取”由大多数 GPS 接收器传输的国家海洋电子协会(NMEA)格式的数据。
UART 接口是一个老接口。它基本上是一个串行(RS-232)连接,但对于我们的目的来说,这就是我们所需要的。它由电源的正极(+)和负极(-)连接以及发送和接收引脚组成。首先输入下面一行,安装读取 GPS、gpsd及其相关程序所需的软件:
sudo apt-get install gpsd gpsd-clients python-gps
接下来,我们需要禁用默认的gpsd systemd服务,因为我们安装的服务应该会覆盖它。用...做这件事
sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket
现在,我们需要禁用串行 getty 服务:
sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl disable serial-getty@ttyS0.service
我们还需要强制 Pi 的 CPU 使用固定频率,并启用 UART 接口。通常,CPU 的频率会根据负载而变化,但不幸的是,这可能会影响 GPS 模块等敏感项目。这将稍微影响您的 Pi 的性能,但您不太可能会注意到很大的差异。为此,编辑/boot/config.txt文件:
sudo nano /boot/config.txt
并将最后一行从
enable_uart=0
到
enable_uart=1
现在,通过键入以下命令重新启动
sudo shutdown –r now.
当您重新启动并运行时,将 GPS 接收器连接到 Pi,如下所示:
-
将接收器的 VIN 连接到 Pi 的 5V(引脚#2)。
-
将 GND 连接到 Pi 引脚 6。
-
将 Rx 连接到 Pi Tx(引脚#8)。
-
将 Tx 连接到 Pi Rx(引脚#10)。
当接收器的 LED 开始闪烁时,你就知道你有电了。我们使用的 GPS 接收器有两种闪烁速率。当它通电但没有 GPS 定位时,它每秒钟闪烁一次。当它被定位时,它每十五秒闪烁一次。
当你有了一个补丁,你可以测试你的gpsd程序。进入
sudo killall gpsd
(终止任何正在运行的实例)然后
sudo gpsd /dev/ttyS0 -f /var/run/gpsd.sock
然后,通过键入以下命令启动通用 GPS 客户端
cgps -s
客户端是一个普通的查看器;它只是获取gpsd程序正在接收的数据并显示给用户。
数据开始流动可能需要一段时间,但当它开始流动时,你应该会看到如图 10-2 所示的屏幕。
图 10-2
cgps流
如果你只看到零,就意味着 GPS 找不到卫星定位。你可能要等几分钟,甚至给 GPS 一个清晰的天空视野。我的经验是,这种特殊的 GPS 板,即使没有可选天线,也非常敏感。当我添加天线时,即使在我的房子里,接收 GPS 信号也没有问题。(按“Q”停止流并返回到终端提示符。)
一旦我们知道 GPS 单元正在工作并与 Pi 通信,我们就需要将该信息转换成可以在日志文件中使用的格式。虽然我们在这里使用的通用客户端cgps对于查看坐标和测试连接很有用,但不幸的是很难从中获得有用的信息。出于这个原因,我们将使用 Python gps模块与接收者进行交互。
注意
gps模块允许你与许多不同的 GPS 接收器通信,而不仅仅是我们在这个项目中使用的那个。有一些接收器产生专有数据流,但大多数接收器输出的是与我们这里使用的芯片相同的 NMEA 格式数据。
设置日志文件
当我们从 GPS 获得流时,我们需要有一个地方来存储它以备后用,因为如果我们只是在飞行中将其打印到(非连接的)屏幕上,它不会给我们带来太多好处。我们可以做的是使用 Python 的日志模块建立一个日志文件,然后,当 Pi 返回地面时,我们可以解析该文件并将其转换成我们可以在 Google Earth 中使用的格式。
设置日志文件非常简单。从输入开始
import logging
logging.basicConfig(filename='locations.log', level=logging.DEBUG, format='%(message)s')
这两行导入模块,声明日志的文件名和记录的内容,并给出每行的格式。我们将把每个 GPS 呼叫保存为三个字符串:经度、纬度和高度 Google Earth 使用的三个坐标。(它们实际上是作为浮点数保存的,而不是字符串,这意味着当我们将它们写入日志文件时,我们必须将它们转换成字符串。)向日志文件中写入一行,格式很简单:
logging.info("logged message or string or what-have-you")
没有必要使用换行符(\n),因为每次调用logging.info()函数时,它都从新一行开始。
如果您想知道,是的,我们可以简单地将 GPS 数据写入一个常规文件,但是日志是一个重要的、有用的概念,许多程序员要么没有完全理解,要么完全跳过。对于 Python 的日志模块,您可以根据被跟踪事件的严重程度设置要输入的日志条目。可能有五种严重性:调试、信息、警告、错误和严重。
五个严重程度(级别)
虽然我使用术语严重性来描述日志条目,但也许级别可能是一个更好的术语。当一个程序执行时(不管它是用什么语言编写的),它通常会生成可以被日志模块记录的事件。调试事件是详细的,通常仅用于诊断问题。信息事件是事情正常工作的确认。警告事件就是这样做的——它们警告说,虽然事情还在运行,但在不久的将来可能会出现问题。错误和关键事件只有在某些东西中断时才会发生,而关键通常意味着程序无法继续工作。默认级别是警告,这意味着除非您以不同的方式设置您的日志记录功能,否则被赋予调试或信息严重性的事件(因为它们低于警告的*)将不会被记录。*
要查看运行中的日志模块,键入python启动 Python 提示符并输入以下内容:
>>> import logging
>>> logging.warning("I am a warning.")
>>> logging.info("I am an info.")
第二行将输出
WARNING:root:I am a warning
而分类为信息级别的第三行将不会被发送到控制台。另一方面,如果你进入
>>> logging.basicConfig(level=logging.DEBUG)
它将默认级别设置为 DEBUG,这意味着无论严重性如何,每个事件都将被记录或输出。输入文件名和格式,就像我们前面所做的那样,设置日志文件以及如何将事件写入其中。
注意
记录事件对于任何程序员来说都是一项重要的技能;如果你想更深入地了解 Python 的日志模块,我强烈推荐你阅读位于 http://docs.python.org/2/howto/logging.html 的 Python 文档。
格式化 KML 文件
KML 文件是一种特殊的 XML(可扩展标记语言), Google Earth 使用它来描绘地标、对象甚至路径。它看起来类似于一个 HTML 文件,有不同级别信息的开始和结束标签,如<Document>和</Document>和<coordinates>和</coordinates>。一旦我们有了来自 GPS 的日志文件,我们需要将包含的坐标格式化成 Google Earth 可以识别的 KML 文件。幸运的是,这非常容易,因为我们将日志文件的格式设置为只有经度、纬度和海拔高度,用空格分隔——用format='%(message)s'和logging.info()行。现在,我们可以解析日志文件中的每一行,用空格和string.split()分隔,并将其写入一个预格式化的。kml 文件。通过使用write()函数,我们可以将每一行写入新文件,在脚本中称为kml,如下所示:
kml.write('<Document>blah blah blah</Document>\n')
既然我们知道最终的 KML 文件需要如何寻找谷歌地球来使用它,我们实际上可以编写一个程序,在我们的飞机离开地面之前解析该文件。这样,我们需要做的就是从实际的日志文件中获取数据输入,我们将在飞机着陆时获取这些数据。文件中不需要实际坐标的其他部分可以提前格式化。
例如,每一个兼容谷歌地球的 KML 文件都以这样一行开头
<?xml version="1.0" encoding="UTF-8" ?>
接下来是
<kml xmlns:="http://www.opengis.net/kml/2.2">
<Document>
<name>
等等。因此,我们可以编写脚本,将这些行添加到最终的plane.kml文件中。
我们将编写飞机上的代码,每 30 秒左右拍一张照片并记录当前的 GPS 位置。因为我们在特定的时间沿着特定的路线获取数据点,所以我们可以使用 KML 的path功能来创建我们的飞机到底做了什么的视觉记录。该路径将最终看起来像你在图 10-3 中看到的那样。
图 10-3
谷歌地球中的 KML 文件
请记住,因为我们每 30 秒才轮询一次 GPS 单元,所以我们不会有一条漂亮的曲线。相反,该路径将连接飞机在这些间隔的位置,并且连接将是直线。正如你在图 10-3 中看到的,我在试飞时使用了一个停车场。我给新手的建议是,如果可以的话,使用草地,因为在草地上迫降对你的飞机来说更容易!在我试飞的时候,阿拉斯加的一切都被雪覆盖着,所以我在哪里测试并不重要。
使用线程和对象
我们将在这个程序中使用的一个重要编程特性是线程。你可能以前见过他们;我甚至在本书的一两个其他项目中使用它们。线程很重要,因为它们允许你的程序和处理器同时执行几个任务,而且它们不会占用所有的内存和处理能力来完成一个简单的任务。一个简单的调用import threading给你线程的全部能力和它们能为你做的一切。
线程实际上是做什么的?
线程允许你的计算机(看起来)同时执行几个任务。我说“表面上”是因为处理器仍然一次只能执行一个进程,但线程允许它在进程之间来回切换,速度之快就好像同时执行它们一样。举个例子,假设你正在电脑上工作,一个窗口打开了文字处理器,另一个窗口打开了互联网浏览器。当文字处理器在一个线程中运行时,另一个线程(在你击键之间执行)保持你的浏览器更新,还有一个线程检查你的电子邮件客户端的新消息,等等。
在本节目中,我们将对 GPS 接收器进行轮询。通过使用线程,当我们继续获取数据时,我们的主缓冲区不会被数据填满,但是我们仍然可以将数据记录在日志文件中以备后用。为了尽可能高效地使用线程,我们将创建一个名为轮询器的对象,它将使用gps模块不时地(比如说每三秒钟)从 GPS 接收器请求信息。每次我们得到一个位置读数,我们都会更新日志并拍照。
对象、类和函数,天啊!
现在,你可能开始有点害怕了:“物品?班级?他在说什么?”引用道格拉斯·亚当斯的话,“不要惊慌。”把这看作是对面向对象编程(OOP)的一个简单、无压力的介绍。
把一个类想象成一组共享某些特征的相似对象。例如,正方形、三角形和五边形都是shape类的成员——它们有边、可计算的周长和可计算的面积。一个对象是该类的一个特定成员,或实例:例如,myRectangle是shape类的某个实例。
当你定义一个类时,你定义了它的特征,比如一个形状有边并且是一个封闭的对象。您还可以定义该类特有的函数。举例来说,shape类的每个成员都可以定义一个函数来指定如何计算其周长。该计算可能因单个 shape 对象而异,因此它对于该对象是唯一的,但每个 shape 对象都有一个defineArea()函数。
我们在最终程序中创建的线程将包含一个对象——Thread类的一个成员——以及一组独特的变量和函数。因此,当我们启动线程时,它将有一个相关的 GPS 轮询功能,该功能将处理位置检索和图片拍摄。
我们的线程对象将这样定义:
class myObject(threading.Thread):
def __init__(self):
#function used to initiate the class and thread
threading.Thread.__init__(self) #necessary to start the thread
def run(self):
#function performed while thread is running
从程序的主要部分,我们可以通过声明一个新的myObject对象(一个新线程)来启动线程:
newObject = myObject()
然后开始的时候
newObject.start()
线程现在用自己的myObject实例运行,称为newObject。我们的线程(如本章最后的代码所示)将从threading.Thread.__init__(self)开始。一旦它被启动,它将继续执行它的功能(在我们的例子中,收集 GPS 数据和拍照),直到我们退出程序。
设置自动启动
因为在将 Pi 连接到飞机上之前,当我们给 Pi 加电时,很可能没有插入显示器或键盘,所以我们需要确保我们的 GPS 日志脚本自动启动。最简单的方法是向/etc/rc.local文件添加一个条目(如侧栏“什么是 rc.local 文件?”中所解释的)).在我们的例子中,如果我们的 GPS 日志代码叫做getGPS.py,并且它存储在我们的Documents/plane文件夹中,我们可以添加这一行
/home/pi/Documents/plane/getGPS.py
到rc.local文件。打开它
sudo nano /etc/rc.local
并添加一行
python /home/pi/Documents/plane/getGPS.py
到文件中最后一个exit 0行之前的文件。
rc.local 文件是什么?
rc.local文件是 Linux 内核的标准部分。它是系统启动文件rc之一,驻留在/etc/目录中。内核在启动时初始化所有设备后,会逐个检查rc文件,运行每个文件中包含的脚本。rc.local文件是最后一个运行的文件,它包含不适合任何其他文件的脚本。因此,该文件可由系统管理员编辑,并经常用于(就像这里一样)保存需要在计算机启动时运行的脚本。
在这个文件中添加脚本需要记住的一个重要细节是,因为它不是以任何特定用户的身份执行的,所以您必须给出脚本的完整的路径,例如,不仅仅是~/Documents/myscript.py,而是/home/pi/Documents/myscript.py。
然而,这并不是我们需要做的全部。在 GPS 程序开始工作之前,我们需要再次打开 GPS feed,就像我们在测试通用 GPS 客户端时所做的那样(在前面的“将 GPS 接收器连接到 Pi”)。因此,我们还需要将那行代码放入/etc/rc.local:
sudo gpsd /dev/ttyS0 -F /var/run/gpsd.sock
最后,在我们开始记录之前,我们需要等待 GPS 单元对一些卫星进行定位;否则,我们将记录大量的0.0,0.0,nan坐标。(nan代表而不是数字。)我的经验是,该单元需要大约 30 秒来获得修复并开始返回真实数据,因此在启动脚本之前等待 45 秒可能是安全的。为此,只需将
sleep 45
在您刚刚添加的sudo gpsd行之后,系统将等待 45 秒,然后启动下一行中的 Python 脚本。完成后,您的/etc/rc.local文件应该像这样结束:
sudo gpsd /dev/ttyS0 –F /var/run/gpsd.sock
sleep 45
python /home/pi/Documents/plane/gpstest.py
保存并退出,脚本将在启动时运行。
连接比特
一旦你有了飞机,建设这个项目就相对容易了。你需要一个电池和一个调节器来确保你不会给它太多的能量。我特别喜欢 RC 爱好者使用的 Li-Po 电池(如图 10-4 所示),因为它们很轻,并且在一个小包装中包含大量的电力。我用的那种给我 1.3A 一小时——比我需要的时间长得多。
图 10-4
锂聚合物(脂)电池
对于电压调节器,你可以从 Adafruit 或 Sparkfun 等经销商处购买 5V 调节器,也可以像我一样,黑一个 USB 车载充电器,如图 10-5 所示。
图 10-5
被黑的车载充电器
中间的端子连接到电池的正极,一个外部的端子连接到 GND。然后,一根简单的 USB 线插入 Pi,你就有电了。
当谈到把所有东西都放在飞机上时,这有点像大杂烩。要记住的重要细节是保持飞机平衡,不要扰乱机翼上的气流。我把 GPS 放在机头上,把 Pi 粘在机翼上。在图 10-6 的图片中有点看不出来,但是相机被贴在左舷机翼上,指向地面。就在机翼后面,您可以看到将插入 Pi 的 USB 插头。整个设置看起来笨拙,但它飞得很好。
图 10-6
设置概述
图 10-7 显示了我如何将 GPS 装置连接到飞机的机头。
图 10-7
飞机机头 GPS 单元特写
图 10-8 显示了我如何将 Pi 连接到机翼上。
图 10-8
飞机机翼上的 Pi 特写
当你准备好出发并完成所有飞行前检查时,插入 Pi 并等待 45 秒钟,等待plane.py脚本启动,等待 GPS 单元获取一些卫星。然后,起飞,拍一些很棒的照片!
当您带着 Pi 回到家中时,登录到它并运行。kml 转换脚本,这里叫做kml.py。该脚本将打开由plane.py脚本创建的locations.log文件,解析其文本,并将所有包含的位置写入一个有效的。kml 文件,名为plane.kml。
然后,您可以将该文件传输到任何安装了 Google Earth 的计算机上。当它被加载到你的计算机上时,右击该文件并打开“打开方式”菜单。在程序选项中找到“谷歌地球”,点击“打开”(如图 10-9 )。
图 10-9
在 Mac 上使用 Google Earth Pro 打开plane.kml
当文件被加载时,你会得到一个类似本章前面图 10-3 中停车场的图像。与此同时,相机拍摄的照片将与您的gpstest.py文件放在同一个文件夹中,或者您在脚本中指定的任何位置。(有关示例,请参见本章末尾的最终代码。)
这里有一个最后的提示:因为您将gps脚本放在了您的/etc/rc.local文件中,它将在您每次启动时继续启动,直到您从文件中删除那一行。如果您想终止gps脚本,使它不在后台运行,也不占用处理器资源,但是您还没有从rc.local中删除它的代码行,请键入
top
变成一个终端。这个命令显示了 Pi 上当前运行的所有进程。要停止 python 脚本,请查找名为“Python”的进程,并注意第一列中的 PID(进程 ID)。按“Q”退出top,然后输入
sudo kill xxxx
其中 xxxx 是您之前提到的 PID。这将杀死 Python 脚本,直到您从rc.local中删除它的行并重新启动。
最终代码
最终代码由两部分组成:平面程序和 KML 转换程序。
飞机程序
这部分程序是当飞机在空中时运行的,拍摄照片并记录 GPS 坐标。从Apress.com开始,可作为plane.py使用:
import os
from gps import *
from time import *
import time
import threading
import logging
from picamera import PiCamera
#set up logfile
logging.basicConfig(filename='locations.log', level=logging.DEBUG,
format='%(message)s')
camera = PiCamera()
picnum = 0
gpsd = None
class GpsPoller(threading.Thread):
def __init__(self): #initializes thread
threading.Thread.__init__(self)
global gpsd
gpsd = gps(mode=WATCH_ENABLE)
self.current_value = None
self.running = True
def run(self): #actions taken by thread
global gpsd
while gpsp.running:
gpsd.next()
if __name__ == '__main__': #if in the main program section,
gpsp = GpsPoller() #start a thread and start logging
try: #and taking pictures
gpsp.start()
while True:
#log location from GPS
logging.info(str(gpsd.fix.longitude) + " " + str(gpsd.fix.latitude) + " " + str(gpsd.fix.altitude))
#save numbered image in correct directory
camera.capture("/home/pi/Documents/plane/image" + str(picnum) + ".jpg")
picnum = picnum + 1 #increment picture number
time.sleep(3)
except (KeyboardInterrupt, SystemExit):
gpsp.running = False
gpsp.join()
KML 转换计划
这个程序在 Pi 返回地面时运行。它获取 GPS 日志文件并将其转换为 KML 文件。从Apress.com开始,可作为kml.py使用:
import string
#open files for reading and writing
gps = open('locations.log', 'r')
kml = open('plane.kml', 'w')
kml.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
kml.write('<kml xmlns:="http://www.opengis.net/kml/2.2">\n')
kml.write('<Document>\n')
kml.write('<name>Plane Path</name>\n')
kml.write('<description>Path taken by plane</description>\n')
kml.write('<Style id="yellowLineGreenPoly">\n')
kml.write('<LineStyle<color>7f00ffff</color><width>4</width></LineStyle>\n')
kml.write('<PolyStyle><color>7f00ff00</color></PolyStyle>\n')
kml.write('</Style>\n')
kml.write('Placemark><name>Plane Path</name>\n')
kml.write('<styleUrl>#yellowLineGreenPoly</styleUrl>\n')
kml.write('<LineString>\n')
kml.write('<extrude>1</extrude><tesselate>1</tesselate>\n')
kml.write('<altitudeMode>relative</altitudeMode>\n')
kml.write('<coordinates>\n')
for line in gps:
#separate string by spaces
coordinate = string.split(line)
longitude = coordinate[0]
latitude = coordinate[1]
altitude = coordinate[2]
kml.write(longitude + "," + latitude + "," + altitude + "\n")
kml.write('<\coordinates>\n')
kml.write('</LineString>\n')
kml.write('</Placemark>\n')
kml.write('</Document>\n')
kml.write('</kml>\n')
摘要
在本章中,我们将 GPS 连接到 Pi,并通过 Pi 的 UART 连接读取其输入。然后,我们将这些信息放入 Python 日志文件中。我们把 Pi 和 GPS 绑在一架无线电控制的飞机上,记录我们的飞行,在飞行中每隔几秒钟拍一次照片。然后,飞机着陆后,我们将 GPS 日志文件转换成 KML 文件,并将该文件放入谷歌地球,以查看我们最终飞行路径的卫星显示。这一章展示了 Raspberry Pi 真正的可移植性。
在下一章中,我们将把圆周率放在气象气球里送到高层大气中,从而得到更高的圆周率。
十一、气象气球
你可能对气象气球很熟悉。有时直径可达 20 英尺,它们可以充满氦,给定一个小的科学有效载荷,并上升到大气层的上限,在它们前进的过程中记录各种传感器读数。然后,当外部压力明显小于气球内部压力时,它们就会破裂,有效载荷在一个小降落伞的帮助下落回地球。发射气球的小组追踪下落的包裹并取回数据。通过这种方式,科学家和业余爱好者可以对大气层的上层了解很多。
虽然在前一章中用树莓派操作无线电控制的飞机很酷,但它也有些乏味,因为我们记录了它的位置并将它的路径数据上传到 Google Earth。这个项目背后的概念是建造一个气象气球,非常简单,但更先进。我们将充气并发射一个小型气象气球,能够将树莓派送到至少 30,000 英尺的高空。然后,我们将对 Pi 进行编程,使其经常拍照,为我们的飞行提供图像记录,我们还将使用一个小型 GPS 设备来记录 Pi 的行程。
但是我们不会就此止步,因为那有点无聊,而且已经被许多不同的爱好者和专业人士做过了。此外,我们将通过编程 Pi 来记录自己每隔 15 秒左右说出其坐标,从而实时更新气球正在做什么——它的纬度、经度,甚至高度。然后,我们将通过地面无线电广播这段录音。我们所需要做的就是把我们的小型调频收音机调到预先指定的频率,这样我们就能听到我们的气球在和我们说话,给我们实时更新它的状况。
我们去买零件吧!
警告
在美国,FAA 法规要求您在发射前 6 至 24 小时通知该机构相关信息,如发射时间和地点、预测高度、气球描述和着陆位置预测。联邦航空局还要求你跟踪气球的位置,并有能力更新联邦航空局的信息,如果需要的话。系留气球和自由气球的规则不同;更多信息,请参见 http://www.gpo.gov/fdsys/pkg/CFR-2012-title14-vol2/pdf/CFR-2012-title14-vol2-part101.pdf 的完整规定。
零件购物清单
像遥控飞机一样,这是书中最昂贵的项目之一,因为气象气球可能有点贵,你需要从聚会用品商店或焊接用品商店购买或租赁一罐氦气。这是你需要的:
-
带摄像板的树莓皮
-
LiPo 电池和 5V 调节器为 Pi 供电
-
直径 6-7 英尺的乳胶气象气球
-
GPS 接收器(
https://www.adafruit.com/products/746) -
GPS 天线(可选)(
https://www.adafruit.com/products/851) -
手持式调幅/调频收音机
-
10 英尺长的电线
-
小型泡沫聚苯乙烯冷却器
-
模型火箭降落伞
-
暖手宝
-
钓鱼线——至少 5000 码
-
1 英尺长的手术导管,内径约为 1 英寸
-
氦——大约 250 立方英尺
-
管道胶带、电工胶带、橡皮筋、拉链
设置 GPS 接收器
就像第十章中的遥控飞机项目一样,这个项目的一个重要组成部分就是让 GPS 设备和你的 Pi 一起运行。为此,您将安装 Python gpsd模块,并使用 Pi 的通用异步接收器/发送器(UART)引脚#7 和#8。Python gpsd模块是一个更大的代码库的一部分,旨在允许 Pi 等设备监控连接的 GPS 和自动识别系统(AIS)接收器,并具有 C、C++、Java 和 Python 的端口。它允许您“读取”由大多数 GPS 接收器传输的国家海洋电子协会格式的数据。
UART 接口是旧的;它基本上是一个串行(RS-232)连接,但对于我们的目的来说,这就是我们所需要的。它由电源的正极(+)和负极(–)连接以及发送和接收引脚组成。首先输入下面一行,安装读取 GPS、gpsd及其相关程序所需的软件:
sudo apt-get install gpsd gpsd-clients python-gps
接下来,我们需要禁用默认的gpsd systemd服务,因为我们安装的服务应该会覆盖它。用...做这件事
sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket
现在,我们需要禁用串行 getty 服务:
sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl disable serial-getty@ttyS0.service
我们还需要强制 Pi 的 CPU 使用固定频率,并启用 UART 接口。通常,CPU 的频率会根据负载而变化,但不幸的是,这可能会影响 GPS 模块等敏感项目。这将稍微影响您的 Pi 的性能,但您不太可能会注意到很大的差异。为此,编辑/boot/config.txt文件:
sudo nano /boot/config.txt
并将最后一行从
enable_uart=0
到
enable_uart=1
现在,通过键入以下命令重新启动
sudo shutdown –r now.
当您重新启动并运行时,将 GPS 接收器连接到 Pi,如下所示:
-
将接收器的 VIN 连接到 Pi 的 5V(引脚#2)。
-
将 GND 连接到 Pi 引脚 6。
-
将 Rx 连接到 Pi Tx(引脚#8)。
-
将 Tx 连接到 Pi Rx(引脚#10)。
当接收器的 LED 开始闪烁时,你就知道你有电了。我们使用的 GPS 接收器有两种闪烁速率。当它通电但没有 GPS 定位时,它每秒钟闪烁一次。当它被定位时,它每十五秒闪烁一次。
当你有了一个补丁,你可以测试你的gpsd程序。进入
sudo killall gpsd
(终止任何正在运行的实例)然后
sudo gpsd /dev/ttyS0 -f /var/run/gpsd.sock
然后,通过键入以下命令启动通用 GPS 客户端
cgps -s
客户端是一个普通的查看器;它只是获取gpsd程序正在接收的数据并显示给用户。
数据开始流动可能需要一段时间,但当它开始流动时,你应该会看到如图 11-1 所示的屏幕。
图 11-1
cgps流
我们不会使用cgps显示屏——这只是一种确保 GPS 单元正确连接并正常工作的简便方法。我们将使用 Python 的gps模块与 GPS 板通信。
存储 GPS 数据
在这个项目中,您将把 GPS 数据写入一个文件,以便以后读取、记录和传输。我们可以为此使用日志文件,就像我们对 RC plane 项目所做的那样,但是学习使用 Python 读写普通文件也很重要。在您的终端中,启动 Python 提示符并键入
f = open('testfile.txt', 'w')
这将打开一个文件—在本例中是testfile.txt。第二个参数可以是以下四个值之一:
-
文件是只读的。
-
**‘w’**文件只用于写入。(每次打开文件时,以前的数据将被清除。)
-
'a' 文件将被追加到。
-
该文件以可读写方式打开。
要继续,请键入
f.write('This is a test of my file-writing')
f.close()
如果您现在通过按下Ctrl+d退出 Python 提示符并列出您的目录内容,您将看到testfile.txt被列出,查看它的内容将显示您刚刚输入的行。现在,再试一次:启动另一个 Python 提示符并键入
f = open('testfile.txt', 'w')
f.write('This text should overwrite the first text')
f.close()
并退出 Python 提示符。因为您使用'w'参数打开了文件,所以所有原始文本都被覆盖。这就是你要在我们的 GPS 定位文件中做的事情。我们对像以前一样在飞机日志文件中保存位置不感兴趣;相反,每个位置将被记录,然后传输,然后我们可以通过用'w'标志打开它,用下一个位置覆盖它。
安装 PiFM
要让你的 Pi 通过无线电和你说话,你需要使用一个由伦敦帝国学院机器人协会成员开发的方便的小工具。该模块名为PiFM,使用 Pi 的现有硬件将其变成一个漂亮的小型调频发射机。
要使用该模块,您首先需要下载它。在您的/balloon目录中,打开一个终端并键入
wget http://omattos.com/pifm.tar.gz
下载完成后,通过键入
tar -xvzf pifm.tar.gz
这将把编译后的二进制文件、源代码和一些声音文件放在您的目录中。您现在已经准备好使用PiFM包了。要进行测试,请将一根一英尺长的电线连接到您的 Pi 的 7 号引脚(GPIO 号引脚)。现在,在您的终端中,键入
sudo ./pifm sound.wav 100.0
把你的收音机调到 100.0 调频。你应该被奖励一首熟悉的曲子。如果您偶然听不到任何声音,请尝试在命令末尾添加**‘22050’**,因为额外的参数(声音文件的采样率)可能需要明确说明,这取决于您拥有的软件版本。
恭喜你!你把你的 Pi 变成了无线电发射机。现在,让我们把它变成一个 DJ。
安装节
我敢肯定,如果我们的电脑能和我们说话,我们大多数人都会喜欢的。幸运的是,由于我们使用的是 Linux,当谈到一个好的语音合成器程序时,我们有几个选择。我们将要使用的是 Festival,它是免费的,非常容易使用。它还附带了一个我们将使用的方便的文本到语音的录音功能。
Festival 可以从您的标准 Pi 存储库中获得,这意味着要使用它,您只需要输入
sudo apt-get install festival
它会下载并安装。一旦下载和安装过程完成,尝试一下。将一对耳机插入 Pi 的 3.5 毫米音频输出插孔。然后,在您的终端中,键入以下内容:
echo "I'm sorry Dave, I'm afraid I can't do that." | festival --tts
你的私人侦探会和你说话。(如果你不知道的话,这句话是经典电影 2001:太空 奥德赛中的计算机哈尔说的。如果你没看过,那就把这本书放下去看吧。现在。我会等的。)
好了,现在你可以让你的 Pi 说出你在命令行中告诉它的任何一行。虽然这很酷,但我们需要让它从文本文件中读取一行:类似于“当前高度 10,000 英尺,当前位置纬度 92 度,经度 164 度。”最简单的方法是使用 Festival 非常方便的text2wave功能。这个函数读取一个文件,例如position.txt,并记录下来。它的语法是
text2wave position.txt -o position.wav
现在,知道我们将每 15 秒更新一次position.txt文件,我们可以在使用PiFM广播该记录之前使用text2wave功能重新记录其内容。
然而,有一个小问题:text2wave使用 44100 kHz 的采样率对其录音进行编码,而 PiFM 需要 22050 kHz 的采样率。这需要我们工具包的另一个程序—FFMPEG。
安装 FFMPEG
在音频和视频编码器、解码器和代码转换器的世界中,FFMPEG 无疑是最流行和最强大的程序之一。它可以将电影文件从 MPG 格式转换为 AVI 格式,将 AVI 文件分离成单独的帧,甚至可以将音频从电影中分离出来,通过连续的过滤器,然后重新附加到视频中。
然而,尽管所有这些壮举都令人印象深刻,但我们需要使用它来将我们的音频从 44100 kHz 更改为 22050 kHz。首先用以下命令获取源文件
wget https://ffmpeg.org/releases/ffmpeg-snapshot-git.tar.bz2
下载完成后,用
tar -vxjf ffmpeg-snapshot-git.tar.bz2
完成提取后,用cd进入结果目录
cd ffmpeg
然后键入
./configure
配置安装。当完成后,输入
make
然后
sudo make install
安装ffmpeg库。这不是一个小图书馆,所以在它编译的时候,你可以随意喝杯咖啡或者小睡一会儿。
替代 avconv
如果从源代码安装ffmpeg让你紧张,还有另一个选择:在 Pi 上使用ffmpeg的替代品,avconv。这个库应该已经存在于您的 Pi 上,所以不需要安装。
现在,为了转换我们的position.wav文件,我们使用以下语法:
ffmpeg -i "position.wav" -y -ar 22050 "position.wav"
或者
avconv -i "position.wav" -y -ar 22050 "position.wav"
就是这样— position.wav现在已经以 22050 kHz 的采样率重新编码,并准备广播。如果我们想广播position.wav的内容,我们可以输入一个终端
sudo ./pifm position.wav 103.5 22050
(103.5 FM 是它将被广播到的频率。当然,要根据你当地的电台进行调整。)
准备 Pi
如果你在 RC 飞机章节之后阅读这一章,你可能认识这里的大部分设置,因为这些项目的许多部分是非常相似的。您需要做的第一件事是确保每次启动 Pi 时gpsd模块都会运行。为此,通过键入以下命令打开您的rc.local文件
sudo nano /etc/rc.local
并将下面一行添加到末尾:
sudo gpsd /dev/ttyS0 -F /var/run/gpsd.sock
注意
有关rc.local文件的更多信息,请参见第十章中的侧栏。
现在,每次我们启动 Pi 时,gpsd模块都会运行。
然而,我们还没有完成rc.local文件。很可能当您发射气球时,您只想给 Pi 加电并发射,而不必担心登录和启动您编写的程序。幸运的是,您也可以用rc.local文件做到这一点。在启动时,在gpsd模块发射后,在它开始记录数据之前,你会想要给你的 GPS 板几秒钟来定位一些卫星。为此,在刚刚显示的gpsd行之后,添加该行
sleep(45)
让 Pi 暂停 45 秒,然后添加一行
sudo python /home/pi/Documents/balloon/balloon.py
(当然,要确保这条线与你存放气球程序的地方相匹配。)您的气球程序现在将自动启动,在您的 GPS 模块开始从其卫星定位中读取数据 45 秒后。
使用线程和对象
我们将在这个项目中使用的一个重要编程特性是线程。如果你已经在第十章的中建造了遥控飞机项目,这将是你的旧帽子。然而,如果你没有,这里有一个快速的纲要。
线程很重要,因为它们允许你的程序和处理器同时执行几个任务,而且它们不会占用所有的内存和处理能力来完成一个简单的任务。一个简单的导入线程的调用就可以让您充分利用线程的全部功能以及它们能为您做的一切。
线程实际上是做什么的?线程允许你的计算机(看起来)同时执行几个任务。我说“表面上”是因为处理器仍然一次只能执行一个进程,但线程允许它在进程之间来回切换,速度之快就好像同时执行它们一样。有关线程的更多介绍,请参见第十章的侧栏。
我们将用它们来查询 GPS 接收器。通过使用线程,当我们继续获取数据时,我们的主缓冲区不会被数据填满,我们仍然可以将数据记录在我们的position.txt文件中以备后用。为了尽可能高效地使用线程,我们将创建一个名为轮询器的对象,它将使用gps模块每隔 15 秒向 GPS 接收器请求信息。每当我们得到一个位置读数,我们将更新文本文件并拍照。
注意
关于对象、类和面向对象编程的复习,请参见第十章的侧栏。
我们的线程对象将这样定义:
class myObject(threading.Thread):
def __init__(self):
#function used to initiate the class and thread
threading.Thread.__init__(self) #necessary to start the thread
def run(self):
#function performed while thread is running
从程序的主要部分,我们可以通过声明一个新的myObject对象(一个新线程)来启动线程:
newObject = myObject()
然后开始
newObject.start()
线程现在用自己的myObject实例运行,称为newObject。我们的线程(如本章最后的代码所示)将从
threading.Thread.__init__(self)
一旦它被启动,它将继续执行它的功能(在我们的例子中,收集 GPS 数据,传输它,并拍照),直到我们退出程序或关闭 Pi。
连接比特
这个气象气球项目的建设可能相当复杂,所以留出几个小时来做好一切准备。
首先要做的是安装氦气罐。当你租油箱时,它应该配有一个调节器和一个用来给气球充气的奶嘴。这可以用来填充你的气球,只需一个小小的改动。将外科手术导管套在调节器上,并用胶带牢牢固定。然后可以将管子插入气球的颈部进行填充(如图 11-2 所示),并用拉链固定。
图 11-2
调节器至气球颈部设置
调节器与气球的连接安全后,你就可以把你的有效载荷组装起来了。在你的聚苯乙烯泡沫冷却器的底部切一个足够大的小洞,以适合 Pi 的相机板。将相机放入孔中,必要时用胶带固定。(见图 11-3 。)在冷却器的底部,安装 Pi 及其电池组,准备在任务启动时连接。
图 11-3
放置在冷却器中的摄像机
在冷却器底部戳一个小洞。这是 GPS 天线、FM 天线和气球系绳的位置。将它们穿过孔,并将 FM 天线连接到 Pi 的 7 号引脚。如果尚未将 GPS 板连接到 Pi(参见本章前面的“设置 GPS 接收器”一节),则将天线连接到 GPS。两个天线应该自由悬挂在底部,系绳应该连接到您的渔线线轴上。将系绳的自由端固定在你的冷却器上,不要撕裂冷却器;我切了一段聚氯乙烯管的长度冷却器和系绳。(见图 11-4 。)
图 11-4
冷却器内部显示了连接到 PVC 管的系绳
现在,打开暖手宝,通过摇晃和混合里面的东西来激活它们。把它们放在冷却器的底部,把你的 Pi 和电池组放在上面。暖手宝很重要,因为高层大气会变得相当冷——冷到足以让你的电子设备停止工作。加温器应该使冷却器内部保持足够的温度,以使您的 Pi 在飞行的顶点继续工作。
你需要把降落伞系在冷藏箱的盖子上,以防气球爆炸,从而保护你的 Pi。我发现最好的方法是在盖子上戳一个洞,将降落伞绳穿过这个洞,然后用热胶水将它们固定在盖子上。然后,在降落伞上松松地系一根绳子——系得足够紧,以便在上升时保持降落伞关闭,但又足够松,以便在冷却器开始自由下落时让降落伞挣脱并打开。
*最后要做的事情是给 Pi 加电,把盖子紧紧地贴在冷却器上,然后把它贴在装满的气球上。当气球达到所需的体积时(这将随着你的有效载荷的重量而变化,所以有必要做一些实验),将它从油箱中取出,并用拉链或橡皮筋系住末端。然后,用拉链将气球的颈部系在你的冷却器上,然后松开。气球将飘入大气层,一边飘一边拍照。与此同时,将你的收音机调到 103.5 FM(或者你在最终代码中设置的任何一个电台),当你的气球准确地告诉你它有多高时,你可以收听实时更新。除非它爆裂(总是一种明显的可能性),否则气球应该行进到所附钓鱼线的末端,这是一个尽可能获得更多线的好理由,以便允许你的气球尽可能高地上升。当要取回你的气球时,用附带的钓鱼线把它卷回来。为了保护你的手臂肌肉,你可能想把钓鱼线线轴连接到电钻上。
注意
在放飞气球之前,请咨询当地的 FAA 分支机构或同等机构,以确定最佳的发射时间和地点。
查看照片结果
根据您的发射结果,您可以希望从您的 Pi 获得一些良好的高空拍摄,如图 11-5 、 11-6 、 11-7 、 11-8 和 11-9 所示。您的结果可能会有所不同,但如果您的第一次启动不顺利,您随时可以再试一次!
图 11-9
高空图片
图 11-8
高空图片
图 11-7
高空图片
图 11-6
高空图片
图 11-5
高空图片
最终代码
该代码(从Apress.com开始以balloon.py的形式提供)将查询 GPS 板,记录并传输其坐标,并定期用车载摄像头拍照:
import os
from gps import *
import time
import threading
import subprocess
#set up variables
picnum = 0
gpsd = None
class GpsPoller(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
global gpsd
global picnum
gpsd = gps(mode=WATCH_ENABLE)
self.current_value = None
self.running = True
def run(self):
global gpsd
while gpsp.running:
gpsd.next()
if __name__ == '__main__':
gpsp = GpsPoller()
try:
gpsp.start()
while True:
f = open('position.txt', 'w')
curAlt = gpsd.fix.altitude
curLong = gpsd.fix.longitude
curLat = gpsd.fix.latitude
f.write( str(curAlt) + "feet altitude," + str(curLong) + "degrees longitude," + str(curLat) +
" degrees latitude")
f.close()
subprocess.call(["text2wave position.txt -o position.wav"], shell = True)
subprocess.call(['ffmpeg -i "position.wav" -y -ar 22050 "position.wav"'], shell = True)
subprocess.call(["sudo ./pifm position.wav 103.5 22050"], shell = True)
subprocess.call(["raspistill -o /home/pi/Documents/balloon/image" + str(picnum) + ".jpg"], shell=True)
picnum = picnum + 1
time.sleep(15)
except (KeyboardInterrupt, SystemExit):
gpsp.running = False
gpsp.join()
摘要
在本章中,您对您的 Pi 进行了编程,以从 GPS 模块获取其位置,并将其位置传输给您,以便您可以停留在地面上,并将它发送到高层大气中进行拍照。您再次使用了线程,并且更多地使用了面向对象的代码,我希望您从高层获得了一些好的图片。
在下一章中,我们将反其道而行之,将圆周率发送到水下。*
十二、潜水艇
几十年来,潜水器,无论是遥控潜水器还是自主潜水器,都被用于科学研究和私营企业。他们研究了贫瘠的洲际平原上的生命,探索了大西洋中部的火山口,去了人类不可能去的地方。在商业领域,他们在 2010 年深水地平线石油灾难中成为焦点。一队潜水器被用来覆盖油井,并在 5000 英尺的深度阻止泄漏,这远远低于人类潜水员所能达到的深度。它们通常用于深海石油钻塔和近海波浪农场的维护。
潜水器基本上有两种:ROV 和 AUV。 ROV 代表遥控 运载工具,描述了一种通过系绳从船上远程控制的潜艇——一种为潜艇提供动力并与其船上系统进行双向通信的电缆。通常情况下,船上的摄像机通过绳索将视频信号发送到控制室的监视器上,在控制室,受过专门训练的操作员使用视频信号通过高科技版本的 Xbox 控制器控制潜艇。遥控操作者控制潜艇的推进、转向和深度,如果装备了样本收集器,他也能操作潜水器的夹子和样本收集器。
另一方面,AUV 代表自主水下 运载工具,描述了一种无需人工干预或控制的潜水器。它可能使用也可能不使用机载相机;如果是的话,摄像头的视频通常只用于数据收集目的,而不是用于导航。AUV 具有一系列机载传感器和相对复杂的机载计算机,该计算机被编程为基于来自传感器的信息执行各种任务和运动。
使用 Raspberry Pi,你实际上可以建造一个 ROV 和一个全尺寸的 AUV,但是为了我们的目的,我们将制作一个(更简单的)遥控潜水器。我们可以使用 Pi 的机载相机来拍摄我们的水下探索,我们可以用黑掉的 Wii 双截棍来控制潜艇。你将无法通过视频导航,因为通过电缆将视频信号发送到外部显示器稍微超出了本书的范围,但只要你不进入太深,你应该能够用木筏或小船跟随潜艇,并从水面指挥它。同样,为了将项目限制在单个章节的范围内,我们也要避免深度控制;相反,我们要让潜艇保持中性浮力。
以防万一你炸了你的 pi
这个建筑包括深水和电子设备——这两样东西在历史上就像水和猫一样密切相关。如果您创建的外壳不是完全防水的,那么您的 Pi 和/或其相关部分极有可能被烤焦。考虑到这一点,您应该执行以下一项或两项操作:
-
买一个额外的 Pi 在你的潜水器里使用。它相对便宜(特别是当你使用 Pi Zero 或 Zero W 时),如果你碰巧炒了它,你仍然可以得到你原来的 Pi,包括所有的调整和增加的模块。如果你复制你的 SD 卡(见下一个要点),潜水式 Pi 将和你的普通 Pi 完全一样。
-
定期备份 SD 卡,就像备份电脑硬盘一样。如果你边走边备份,如果卡出了问题,你也不会损失太多辛苦。
零件购物清单
对于此版本,您将需要以下内容:
-
树莓皮
-
Raspberry Pi 相机套件—可从 Adafruit、Amazon 和 Sparkfun 获得
-
电机驱动器—双 L298 H 桥(
http://www.sparkfun.com/products/9670) -
任天堂 Wii 双节棍控制器
-
1 Wiichuck 配接卡(
https://www.dfrobot.com/product-91.html -
头球
-
2 DC 汽车公司
-
模型螺旋桨
-
2 个电池组(RC 爱好者使用的锂聚合物电池组是一个不错的选择)
-
1 防水外壳——想想特百惠或类似的东西
-
1 管 5200 船用防水密封胶
-
PVC 管和弯管接头
-
各种拉链
-
铁丝网或塑料网
-
电线—红色和黑色,18 号
-
以太网电缆——25-50 英尺(如果可能的话,批量购买,因为你不需要塑料端子,只需要电缆)
-
蜡
-
填塞
访问 Raspberry Pi 的 GPIO 引脚
使 Raspberry Pi 如此有用的特性之一是它的 GPIO(通用输入/输出)引脚。使用预先安装在 Pi 上的特定 Python 模块,可以直接控制 GPIO 引脚,将电压发送到外部设备并读取输入,然后在程序中使用。
GPIO:有什么大不了的?
GPIO 引脚就像旧计算机上的一些端口。一种与外部世界连接的简单方式曾经是串行端口或打印机(并行)端口。两者都可以通过正确的编程库访问,直接向每个引脚发送信号。但随着技术的进步,这两个端口都消失了,取而代之的是 USB 和以太网连接。因此,从编程的角度来看,控制外部设备变得更加困难,这就是为什么许多人对 Pi 的 GPIO 引脚提供的可能性感到兴奋。
要配置您的 Pi 以编程方式访问 GPIO 引脚,您可能需要安装正确的开发工具包和工具。只要进入
sudo apt-get install python-dev
完成后,键入
sudo apt-get install python.rpi-gpio
注意
python-rpi.gpio可能已经安装,取决于您的 Raspian 版本。根据您阅读本章的日期,它也可能返回“无法定位包”的错误没什么大不了的,很可能已经安装了。
您现在可以访问 pin 了。用于访问它们的 Python 模块是RPi.GPIO模块。它通常在程序的第一行通过键入
import RPi.GPIO as GPIO
然后通过键入以下命令进行配置
GPIO.setmode(GPIO.BOARD)
这使您可以根据标准引脚排列图上的标签来识别引脚,如图 12-1 所示。
图 12-1
树莓 Pi 3 引脚排列
注意
请记住,对于GPIO.setmode(GPIO.BOARD),当您提到 11 号引脚时,您实际上是指物理引脚#11(在图 12-1 的图中转换为 GPIO17),不是 GPIO11,它转换为物理引脚#23。
一旦设置好模式,就可以将每个引脚设置为输入或输出。Arduino 的用户可能会认识到这里的概念。键入以下内容
GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.IN)
等等。一旦你设置了一个管脚作为输出,你就可以通过输入
GPIO.output (11, 1)
或者
GPIO.output (11, True)
然后通过键入以下命令将其关闭
GPIO.output (11, 0)
或者
GPIO.output (11, False)
如果 pin 已被配置为输入,您可以查询它以查看与其连接的按钮或开关是否已被按下。然而,这里需要注意的一点是,如果该引脚仅被声明为输入,则它被定义为“浮动”且没有定义的电压电平。在这种情况下,我们需要将引脚接地,使其在我们按下按钮之前一直为低电平(0)。这可以通过在引脚和公共地之间放置一个电阻来实现。幸运的是,RPi.GPIO模块允许我们通过键入以下命令在软件中实现这一点:
GPIO.setup (11, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
此时,您可以通过键入以下命令随时“轮询”pin
if GPIO.input(11):
print "Input was HIGH"
else:
print "Input was LOW"
将这段简单的代码插入脚本的循环中;每次循环运行时,脚本都会检查 pin 的状态。我们将在最终的潜水器程序中使用这一功能来触发相机拍照。
安装 Pi 摄像机板
我们将要建造的潜水器将配备有拍照设备,可以按预设的时间间隔拍照,也可以按下按钮拍照。显然,这意味着我们需要安装与 Pi 接口的相机模块。
当您收到相机板时,它很可能会放在一个普通的白色盒子里,盒子里面是一个装有相机板的防静电灰色袋子。通过接触良好的地面,确保您已经释放了身体上的任何静电(因为相机对静电非常敏感);然后,从包里取出相机。
摄像头连接到 HDMI 输出和以太网端口之间板上的条形连接器。通过拉起侧面来打开连接器,它会“弹出”几毫米,这正是您所需要的。定位相机带状电缆的末端,使连接器(银色侧,而不是蓝色侧)面向 HDMI 端口。将电缆滑入连接器,直到其到底,以确保连接良好。然后,在保持电缆不动的同时,向下按压连接器的两侧,直到它们卡入到位(参见图 12-2 )。
图 12-2
连接 Pi 的摄像机电缆
当您在第三章设置 Pi 时,您应该已经启用了摄像机支持;不过,如果你没有,这并不难。在终端中,键入
sudo apt-get update
和
sudo apt-get upgrade
以确保您的 Pi 运行所有最新的内核补丁和软件更新。当这两个都完成时,键入
sudo raspi-config
启动配置程序。导航到“摄像机”选项,然后单击“启用”然后,选择“完成”并重启 Pi。
当 Pi 重新启动时,您可以使用其内置的相机功能raspistill和raspivid来试验相机。要查看完整的命令列表,只需输入
raspivid
或者
raspistill
在命令提示符下获取说明。例如,要以“卡通”格式拍摄静态图片,只需键入
raspistill -o image.jpg -ifx cartoon
图像将保存在您所在的目录中。您可以更改图像分辨率、高度、宽度、效果和延迟,甚至可以使用-tl标志(我们稍后可能会用到)设置延时情况。
控制潜艇
为了控制潜艇,我们将使用两个 DC 电机、一个电机驱动芯片(已经放置在印刷电路板上)和一个 Wii 双节棍(如图 12-3 所示)。使用特殊的适配器,您可以访问双节棍的电线,并将它们直接连接到 Raspberry Pi,而不必从控制器的末端切断连接器。
图 12-3
Wii 双截棍
双截棍通过一种被称为 I2C 的协议进行通信,或 I 2 C,或 IIC,代表内部集成电路。如果你按顺序完成了这些项目,你可能还记得《I2C 协议》第六章“气象站”I2C 是由飞利浦在 20 世纪 80 年代早期创建的,作为不同设备在单条总线(通信线)上进行通信的一种方式。此后,它经历了几次修订,但基本概念保持不变。在 I2C 总线上,一台机器充当“主机”,可以连接到各种不同的“从机”每台机器都可以在同一组电线上进行通信,使用主机发送的时钟信号来同步通信。幸运的是,就我们的目的而言,Raspberry Pi 可以通过它的一些 GPIO 引脚利用 I2C 协议,并且通信相对简单,因为只有两个设备在通信:作为主机的 Pi 和单独的从机 Wii nunchuk。
连接 Wiichuck 适配器
你可能要做的第一件事是将一组四个接头焊接到你的 Wiichuck 适配器上,如果它没有预连接它们的话。仅使用少量焊料,将接头连接到适配器(如图 12-4 所示)。与许多市售电路板一样,该适配器覆盖有一层疏焊料涂层,可防止焊料在连接之间流动并使其短路。这使得将接头焊接到连接器上成为一个简单的过程,即使对于没有经验的焊工来说也是如此。
图 12-4
焊接到 Wiichuk 适配器的接头
您将与 Wiichuck 建立四个连接—正极、负极、SDA(I2C 数据线)和 SCL(I2C 时钟线)。将正极线连接到 GPIO 引脚 1,负极线连接到 GPIO 引脚 6。将 SDA 线连接到引脚 3,将 SCL 线连接到引脚 5。在图 12-5 中,您可以看到 Wiichuk 已正确插入控制器。
图 12-5
Wiichuk 适配器的正确定位
警告
您应该将双截棍连接到 Pi 的#1 引脚(3.3V),而不是的#2 引脚(5V)。Wii 的双截棍设计为 3.3V 运行,而不是 5V。虽然它可以在 5V 电压下工作,但会严重缩短控制器的寿命。
激活 Pi 的 I2C
Raspberry Pi 有两个引脚#3 和#5,分别预配置为 I2C 协议的 SDA(数据)和 SCL(时钟)线路,因此它可以轻松地与 I2C 设备通信。Pi 还有一个 I2C 实用程序,可以查看当前连接的设备。要安装它,请键入
sudo apt-get install python-smbus
sudo apt-get install i2c-tools
如果你使用的是 Raspbian 的最新版本,比如 Wheezy 或 Stretch,这些应该已经安装了,在这种情况下,你只会得到一个提示,告诉你它们已经是最新版本了。
现在,您可以运行名为 i2cdetect 的 I2C 实用工具来确保一切正常,并查看连接了哪些设备。键入以下行:
sudo i2cdetect -y 1
这将显示如图 12-6 所示的屏幕。
图 12-6
I2C 检测工具
在图中,没有设备出现,这是有意义的,因为我们还没有插入任何设备。但是你知道你的工具工作正常。如果您插入了 Wiichuk 适配器,您应该在#52 处看到一个条目,这是该工具的 I2C 地址。
小费
如果你很难得到结果,并且你确信你正确地连接了所有的电线和设备,检查你所有的电线是否完好无损。我花了很多时间对一个构建进行故障排除,却发现我使用的一根或多根廉价跳线在绝缘层内断裂,这意味着我在应该的地方收不到信号。这比你想象的要经常发生!
读双节棍
你现在可以开始读双节棍了。当然,最终我们会将双节棍发出的信号转化为马达的指令,但看看我们从什么信号开始可能会有指导意义。为此,让我们创建一个 Python 脚本,它导入正确的模块,监听来自双节棍的信号,并将它们输出到屏幕上。有七个信号通过导线发送:操纵杆的 x 和 y 位置,前面的“Z”和“C”按钮的状态,以及双节棍内置加速度计的 x,y 和 Z 状态。我们不会把所有这些都用在潜艇上,但是我们仍然可以看看它们。
这是你要输入的完整脚本:
#import necessary modules
import smbus
import time
bus = smbus.SMBus(1)
#initiate I2C communication by writing to the nunchuk
bus.write_byte_data(0x52,0x40,0x00)
time.sleep(0.1)
while True:
try:
bus.write_byte(0x52,0x00)
time.sleep(0.1)
data0 = bus.read_byte(0x52)
data1 = bus.read_byte(0x52)
data2 = bus.read_byte(0x52)
data3 = bus.read_byte(0x52)
data4 = bus.read_byte(0x52)
data5 = bus.read_byte(0x52)
joy_x = data0
joy_y = data1
accel_x = (data2 << 2) + ((data5 & 0x0c) >> 2)
accel_y = (data3 << 2) + ((data5 & 0x30) >> 4)
accel_z = (data4 << 2) + ((data5 & 0xc0) >> 6)
buttons = data5 & 0x03
button_c = (buttons == 1) #button_c is True if buttons = 1
button_z = (buttons == 2) #button_z is True if buttons = 2
print 'Jx: %s Jy: %s Ax: %s Ay: %s Az: %s Bc: %s Bz: %s' % (joy_x, joy_y, accel_x, accel_y, accel_z, button_c, button_z)
except IOError as e:
print e
如果你还没有,为你的潜水程序创建一个文件夹,在那个文件夹中保存这个脚本为nunchuktest. py,然后运行它。导入必要的模块后,脚本创建了一个“总线”,通过它可以与双节棍进行所有的通信。然后它通过写信给双截棍的 I2C 地址(bus.write_byte_data())开始通信。然后,它进入一个循环,连续读取来自双节棍(data0、data1等)的 5 字节字符串,并按顺序将它们分类为操纵杆方向、加速度计读数和按钮按压。然后,它将这些值打印到屏幕上,并重复这个过程。
因为它涉及到对 I2C 总线的读写,所以您必须以 root 用户身份运行它,所以键入以下命令:
sudo python nunchuktest.py
脚本一启动,就会显示所有双节棍传感器的运行状态报告,实时更新,格式如下:
Jx: 130 Jy: 131 Ax: 519 Ay: 558 Az: 713 Bc: False Bz: False
在脚本运行时,尝试移动操纵杆、按下按钮并摇动双节棍来观察值的变化。您现在知道如何从双节棍读取值,我们将用它来驱动马达。
双截棍和 LED 试验方项目
作为一个小项目(也是为了测试我的双节棍阅读能力),我在一个小试验板上连接了六个 led 和一些 GPIO 引脚,这样它们就会根据我移动操纵杆或按下按钮的方式而点亮。这可能是一个值得进行的测试,以确保您不仅阅读了这些值,而且能够用这些值做一些事情。在这种情况下,选择四个 GPIO 引脚并将其设置为输出。将这些引脚连接到一个电阻器和并联连接的 led 的正极引脚(如图 12-7 所示),将所有接地连接在一起,并运行以下脚本:
图 12-7
LED 测试设置
import smbus
import time
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
#set pins
GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.OUT)
GPIO.setup (15, GPIO.OUT)
GPIO.setup (19, GPIO.OUT)
GPIO.setup (21, GPIO.OUT)
GPIO.setup (23, GPIO.OUT)
bus = smbus.SMBus(0)
bus.write_byte_data (0x52, 0x40, 0x00)
time.sleep (0.1)
while True:
try:
bus.write_byte (0x52, 0x00)
time.sleep (0.1)
data0 = bus.read_byte (0x52)
data1 = bus.read_byte (0x52)
data2 = bus.read_byte (0x52)
data3 = bus.read_byte (0x52)
data4 = bus.read_byte (0x52)
data5 = bus.read_byte (0x52)
joy_x = data0
joy_y = data1
# the following lines add the necessary values to make the received 5-byte
# strings easier to decode and print
accel_x = (data2 << 2) + ((data5 & 0x0c) >> 2)
accel_y = (data3 << 2) + ((data5 & 0x30) >> 4)
accel_z = (data4 << 2) + ((data5 & 0xc0) >> 6)
buttons = data5 & 0x03
button_c = (buttons == 1)
button_z = (buttons == 2)
print 'Jx: %s Jy: %s Ax: %s Ay: %s Az: %s Bc: %s Bz:
%s' % (joy_x, joy_y, accel_x, accel_y, accel_z, button_c, button_z)
if joy_x > 200:
GPIO.output (11, 1)
GPIO.output (13, 0)
GPIO.output (15, 0)
GPIO.output (19, 0)
GPIO.output (21, 0)
GPIO.output (23, 0)
elif joy_x < 35:
GPIO.output (11, 0)
GPIO.output (13, 1)
GPIO.output (15, 0)
GPIO.output (19, 0)
GPIO.output (21, 0)
GPIO.output (23, 0)
elif joy_y > 200:
GPIO.output (11, 0)
GPIO.output (13, 0)
GPIO.output (15, 1)
GPIO.output (19, 0)
GPIO.output (21, 0)
GPIO.output (23, 0)
elif joy_y < 35:
GPIO.output (11, 0)
GPIO.output (13, 0)
GPIO.output (15, 0)
GPIO.output (19, 1)
GPIO.output (21, 0)
GPIO.output (23, 0)
elif button_c == True:
GPIO.output (11, 0)
GPIO.output (13, 0)
GPIO.output (15, 0)
GPIO.output (19, 0)
GPIO.output (21, 1)
GPIO.output (23, 0)
elif button_z == True:
GPIO.output (11, 0)
GPIO.output (13, 0)
GPIO.output (15, 0)
GPIO.output (19, 0)
GPIO.output (21, 0)
GPIO.output (23, 1)
else:
GPIO.output (11, 0)
GPIO.output (13, 0)
GPIO.output (15, 0)
GPIO.output (19, 0)
GPIO.output (21, 0)
GPIO.output (23, 0)
except IOError as e:
print e
如前所述,代码首先创建了一个“总线”,所有的通信都通过它与双节棍进行。然后,它通过写信给双截棍的 I2C 地址(bus.write_byte_data())开始通信。然后,它进入一个循环,连续读取来自双截棍的 5 字节字符串(data0、data1等等),并按顺序将它们分类为操纵杆方向、加速度计读数和按钮按压。这些字符串的值指示双截棍的每个组件在做什么:例如,如果buttonZ是True,按钮被按下。同样,操纵杆的 y 方向值表示操纵杆是向前推还是向后拉。一长串if-elif语句简单地遍历接收到的值并点亮相应的 LED。
运行它(再次使用sudo)会根据你用双节棍做什么产生不同的 led 灯。
如您所见,并联 led 意味着它们共享一个公共地和一条公共高压线。相比之下,如果将它们串联,每个 LED 的正极引脚将与下一个 LED 的负极引脚相连,最后一个 LED 与 Pi 的正极引脚或负极引脚之间有一个电阻。
用双截棍控制子马达和相机
现在我们已经让双节棍工作了,我们将使用它来控制潜艇的马达,这涉及到使用 L298 马达控制器芯片。通常,我们不能用 Pi 驱动非常强大的电机或伺服系统,因为 Pi 无法提供足够的电流来驱动它们。为了绕过这一限制,我们使用电机控制器芯片,如 L298。像这样的芯片被称为 H 桥,允许你将外部电源连接到你的电机,并根据需要使用 Pi 来打开和关闭电机。L298 芯片成本仅几美元,可用于驱动难以置信的电流和电压——高达 4 安培和 46 伏。它通常用于业余爱好机器人,非常适合这种类型的应用。
然而,尽管芯片很便宜,但将其连接到 Pi 和电机可能会很复杂,因为需要使用几个 10nF 电容和一些反激二极管来保护 Pi 免受电机电压尖峰的影响。出于这个原因,我强烈推荐从 Sparkfun 购买 L298 电机驱动板,如“零件购物清单”一节所述。它使连接更简单:你插入外部电源,输入来自 Pi 的信号,输出电线到电机,你就完成了。在本章的其余部分,我将假设您使用的是 Sparkfun 板。如果你决定自己试验芯片,可以在网上找到一些不错的原理图。
为了控制潜艇和马达,我们可以修改我在前面“双节棍和 LED 测试”一节中介绍的 LED 驱动脚本,但我们不是打开和关闭 LED,而是打开和关闭马达并激活摄像机。控制将基本如下:
-
操纵杆向前=两个电机旋转
-
操纵杆左=右电机旋转
-
操纵杆右=左马达旋转
-
按下 c 按钮=用相机拍摄静态照片
-
按下 z 按钮=用相机拍摄视频
要使用 L298 板为电机供电,需用七根电线将 Pi 连接到板上——每台连接的电机用三根,一根接地。电机 A 由 IN1、IN2 和 ENA 控制(“启用 A”)。电机 B 由 IN3、IN4 和 ENB 控制。为了控制电机 A,你设置 ENA 为“高”,然后在 IN1 或 IN2 发送电压(或者都不发送,以制动电机)。电机 B 的控制方式相同。电机的电源连接到电路板,完全绕过 Pi。要查看图示,请看表 12-1 和图 12-8 。
图 12-8
连接到 Pi 的电机和电机控制器
表 12-1
电机值和设置
| ENA 价值 | ENA = 1 | ENA = 1 | ENA = 1 | ENA = 0 | | **IN1 值** | IN1 = 1 | IN1 = 0 | IN1 = 0 | - | | **IN2 值** | IN2 = 0 | IN2 = 1 | IN2 = 0 | - | | **结果** | 电机 A 顺时针旋转 | 电机 A 逆时针旋转 | 马达 A 制动器 | 电机 A 停止 |显然,我们需要做的就是为每个电机设置三个 GPIO 引脚。同时,我们读取设置为 I2C 输入的 GPIO 引脚,并根据我们从双节棍获得的信号将电机引脚设置为高或低。我们还将检查按钮按下,以激活相机。(图 12-7 显示了一个电机的连接,而不是两个。)
我们可以为摄像机设置每个命令(take_picture、take_video等)。)并在双节棍上按下适当的按钮时调用该函数。类似地,我们可以设置运行电机的函数,并根据操纵杆的位置调用这些函数。所有这些都发生在一个while循环中,一直持续到我们终止程序或者电池耗尽。
远程启动程序
一旦给潜水器加电,有几种方法可以让 Python 程序运行,因为您没有连接到 Pi 的键盘、鼠标或监视器。最简单的方法似乎是设置一个静态 IP 地址,从笔记本电脑远程登录到 Pi,然后从那里启动程序。然而,这只有在你登录到无线网络的情况下才能工作,而且在湖中央(或海洋,或任何你碰巧使用潜水器的地方)可能没有任何可用的网络。
您可以设置一个临时网络,在这个网络中,Pi 和您的笔记本电脑创建一个小型的专用网络,然后从您的笔记本电脑登录到 Pi。然而,建立一个自组织网络可能会有问题,如果它由于某种原因不工作,你将无法访问你的 Pi,使你的潜水器毫无用处。
经过一番思考,我决定最好的方法是在启动 Pi 时自动启动子控制程序。这样,你就可以打开它,给马达供电,然后继续和你的潜艇一起工作。
为此,我们需要做的就是编辑一个文件——您的cron调度程序。cron调度程序是一个您可以编辑的作业调度程序。它使您能够在指定的时间和间隔安排任务和脚本。要编辑它,您可以通过键入以下命令打开名为crontab的文件
sudo crontab -e
每个用户都有自己的crontab,但是通过使用sudo,我们将编辑根用户的cron,这是我们运行 Python 脚本所需的用户,因为我们正在访问 GPIO 引脚。你会看到类似图 12-9 的东西。
图 12-9
用户的crontab文件
向下滚动到文件末尾,并输入以下行:
@reboot python /home/pi/Desktop/submersible/sub.py &
这是您的 Python 脚本的具体路径(假设您已经将sub.py保存在桌面上的submersible文件夹中),并且&告诉cron在后台运行作业,以便不干扰正常的启动例程。保存文件。下次您重新启动 Pi 时,sub.py将会运行——如果您愿意,可以测试一下!
最终代码
将下面的代码保存在您的 Pi 上,最好保存在它自己的文件夹中。它也可以在 Apress 网站上以sub.py的名称获得。
import time
import smbus
from picamera import PiCamera
import RPi.GPIO as GPIO
GPIO.setwarnings (False)
GPIO.setmode (GPIO.BOARD)
camera = PiCamera()
def take_stillpic(num):
camera.capture("image" + str(num) + "jpg")
def go_forward():
GPIO.output (19, 1) #IN1 on
GPIO.output (23, 0) #IN2 off
GPIO.output (11, 1) #IN3 on
GPIO.output (15, 0) #IN4 off
def go_backward():
GPIO.output (19, 0) #IN1 off
GPIO.output (23, 1) #IN2 on
GPIO.output (11, 0) #IN3 off
GPIO.output (15, 1) #IN4 on
def go_right():
GPIO.output (19, 1) #IN1 on
GPIO.output (23, 0) #IN2 off
GPIO.output (11, 0) #IN3 off
GPIO.output (15, 1) #IN4 on
def go_left():
GPIO.output (19, 0) #IN1 off
GPIO.output (23, 1) #IN2 on
GPIO.output (11, 1) #IN3 on
GPIO.output (15, 0) #IN4 off
#set motor control pins
#left motor
# 11 = IN3
# 13 = enableB
# 15 = IN4
GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.OUT)
GPIO.setup (15, GPIO.OUT)
#right motor
# 19 = IN1
# 21 = enableA
# 23 = IN2
GPIO.setup (19, GPIO.OUT)
GPIO.setup (21, GPIO.OUT)
GPIO.setup (23, GPIO.OUT)
#enable both motors
GPIO.output (13, 1)
GPIO.output (21, 1)
#setup nunchuk read
bus = smbus.SMBus(0) # or a (1) if you needed used y -1 in the i2cdetect command
bus.write_byte_data (0x52, 0x40, 0x00)
time.sleep (0.5)
x = 1
while True:
try:
bus.write_byte (0x52, 0x00)
time.sleep (0.1)
data0 = bus.read_byte (0x52)
data1 = bus.read_byte (0x52)
data2 = bus.read_byte (0x52)
data3 = bus.read_byte (0x52)
data4 = bus.read_byte (0x52)
data5 = bus.read_byte (0x52)
joy_x = data0
joy_y = data1
accel_x = (data2 << 2) + ((data5 & 0x0c) >> 2)
accel_y = (data3 << 2) + ((data5 & 0x30) >> 4)
accel_z = (data4 << 2) + ((data5 & 0xc0) >> 6)
buttons = data5 & 0x03
button_c = (buttons == 1) or (buttons == 2)
button_z = (buttons == 0) or (buttons == 2)
if joy_x > 200: #joystick right
go_right()
elif joy_x < 35: #joystick left
go_left()
elif joy_y > 200: #joystick forward
go_forward()
elif joy_y < 35: #joystick back
go_backward()
elif button_c == True:
x = x+1
take_stillpic(x)
elif button_z == True:
print "button z! \n"
else: #joystick at neutral, no buttons
GPIO.output (19, 0)
GPIO.output (23, 0)
GPIO.output (11, 0)
GPIO.output (15, 0)
except IOError as e:
print e
建造潜艇
在这一点上,我们准备开始建造真正的潜水器。把你的零件收集在一起:PVC 管和弯管、防水容器、胶水、螺丝和其他东西。请记住,我在接下来的章节中展示的设计仅仅是一个说明,而不是一个逐步说明的表格。只要你最终有一个框架,你可以在上面安装防水的 Pi 外壳和防水的电机和螺旋桨,你就成功地完成了这项任务。
注意
建筑计划,特别是电机防水程序,受到了“海鲈鱼计划”( http://www.seaperch.org )的严重影响,该计划旨在教授所有年龄段的学生建造遥控潜水器,并鼓励他们参与工程和数学领域。这是一个有价值的项目,我非常赞同它的目标。
构建框架
使用 PVC 管和 90°弯头,构建一个大致方形的框架,足够容纳您的 Pi 外壳。使用 PVC 胶水或螺丝将所有东西固定到位后,切割塑料网以适合正方形,并使用拉链将它固定在框架上。你最终会得到一个塑料“托盘”,如图 12-10 所示。
图 12-10
子平台
这是你的潜艇的尸体。我把它留得足够大,如果我需要改变浮力的话,可以在边上添加泡沫聚苯乙烯条,尽管我不应该这样做,因为码头的围栏充满了空气。
创建 Pi 的外壳
你需要一个足够大的透明塑料容器来装下你所有的电子产品。我指定“清晰”,因为你的相机将通过它看,并需要能够看到。一旦你选定了你的容器,就在上面钻三个小洞——两个用于连接马达的电线,一个用于连接双节棍的电线,如图 12-11 所示。
图 12-11
防水码头围栏
电机外壳防水
也许这个项目最难的部分是电机的防水,因为它们将在外壳之外。我用处方药瓶来装我的。首先,用绝缘胶带将电机完全包裹起来,以密封外壳上的大孔(参见图 12-12 )。
图 12-12
电机用绝缘胶带包裹
然后,剥去一小段以太网电缆的两端,将其中两根电线焊接到电机的电线上。当它完全包裹好并且电线连接好后,在药瓶上钻两个孔——一个在盖子上用于推进器,一个在底座上用于控制线。如图 12-13 和图 12-14 所示,将电线滑过瓶子,确保所有东西都紧贴在瓶子里。
图 12-14
电机紧贴安装在药瓶中
图 12-13
一种可插入药瓶的电机
现在(这是防水钥匙),把你的药瓶直立起来,在马达和电线周围装满蜡,如图 12-15 所示。石蜡,在你超市卖罐头的过道里可以买到,很适合这种情况。如果你加入一小层蜡,让它变硬,然后继续直到瓶子满了,你可能会有最好的运气。
图 12-15
电机被蜡包围
当它被蜡牢牢包围时,确保你的电机轴仍然可以转动,然后在盖子上滑动,通过你预先钻好的孔安装电机轴。然后,将你的螺旋桨滑到暴露的轴上,你应该有一个类似图 12-16 的防水电机。对潜艇另一侧的另一个马达重复上述步骤,然后用拉链将两个马达固定在潜艇的框架上。
图 12-16
防水电机,准备安装
连接双节棍
因为我们要从潜艇到你的双节棍(将保存在你的船上)运行以太网电缆,你需要使用另一段以太网电缆。剥去两端,抓住四根电线,将它们焊接到 Wiichuck 控制器上。将电缆的另一端穿过潜艇顶部的孔,然后将这四根电线的另一端连接到 Pi 的 GPIO 引脚上。
组装最终产品
一旦你的马达防水,你就可以组装最终产品了。将每台电机的电线穿过容器上的孔,并按照测试时的方式将它们连接到电机控制板。当所有电线都穿过孔后,使用船用环氧树脂密封孔。
警告
5200 密封胶极其黏糊糊的,脏兮兮的,弄在皮肤上就脱不下来。戴上手套,尽可能在室外工作。此外,不要吝啬密封胶——你要保护所有的电子设备,所以要确保水无法进入你的外壳。
当孔被密封时,确保所有的电子连接都完好无损,并将 Pi 和电机控制器放入外壳中。使用一小块胶带或海报油灰(这是我使用的)将相机压在外壳的前“墙”上,并将各种板固定到位。使用一个小试验板连接您的地,并添加您的两个电池——一个用于 Pi,一个用于电机控制器。
当你用电池给你的 Pi 供电时,你必须使用 5V 电源,因为 Pi 没有板载电压调节器。您可以尝试不同的电池组合,或者使用电压调节器来完成这项工作。在我所有的便携式 Pi 工作中,我曾破解过一个 USB 车载充电器,如图 12-17 所示,因为它能完美地提供 5V 和 2 安培的电流。
图 12-17
USB 汽车充电
打开外壳,使用 USB 转 micro-USB 线将 USB 电源输出连接到您的 Pi 电源输入。然后,将你的电池连接到充电器的电源输入端,瞧!你的 Pi 有 5V 电源!
一旦您解决了电源问题,您就可以将所有东西都放入机箱中,打开 Pi 电源,并将其密封起来。希望你已经得到了类似于图 12-18 的东西。
图 12-18
完整的潜水器
显然,图 12-18 所示的产品版本尚未完成——我还没有连接双截棍——但你可以看到电机和外壳本身的位置。作为最终的施工照片,图 12-19 显示了 Pi 的摄像机靠着外壳前墙的位置,用海报油灰固定到位。
图 12-19
Pi 摄像机在外壳中的放置
如果你已经仔细遵循了所有的说明,你现在应该有一个 Pi 驱动的潜水器,你可以从岸上或你的船上使用 Wii 双截棍进行控制。按下双节棍上的按钮就可以拍照,当你把 Pi 带上飞机时,你可以把这些照片传输到你的家用电脑上。
你能拍什么样的照片?嗯,如果你住在澳大利亚,你可以拿一些像你在图 12-20 中看到的东西。
图 12-20
水下摄影
(此处完全披露:这张照片是用 Pi 接头拍摄的而不是。但有可能是。)
然而,如果你住在阿拉斯加,你得到的照片可能最终看起来更像图 12-21 和图 12-22 。
图 12-22
更多阿拉斯加水下摄影
图 12-21
阿拉斯加水下摄影
如果你住在南加州,你可能会看到类似图 12-23 、 12-24 和 12-25 的图片。
图 12-25
水下照片
图 12-24
水下照片
图 12-23
水下照片
享受你的潜艇,我真的很想看看你拍的照片!
摘要
在本章中,您学习了如何使用 I2C 协议将 Wii 双节棍连接到您的 Pi,并使用它来控制 Pi 上的一些不同功能。然后,你为你的码头建造了一个防水的移动外壳,连接了一些马达和你的相机,并能够远程驾驶你的潜艇,拍摄一些(希望)令人印象深刻的水下照片。
在下一章中,您将学习如何将微控制器(Arduino)连接到您的 Pi 以增强其功能。