要制造自动驾驶的机器人车辆,我们首先需要了解人类是如何驾驶车辆的。当我们开车时,我们不断地分析空间和与其他物体的距离。然后,我们决定是否可以通过它。这经常发生在我们的大脑-眼睛协调上。类似地,机器人也必须做同样的事情
在我们前面的章节中,您了解到我们可以通过传感器找到我们周围物体的接近程度。这些传感器可以告诉我们物体有多远,我们可以根据它做出决定。我们使用超声波传感器主要是因为它非常便宜。然而,正如您所记得的,连接超声波传感器和运行其代码有点麻烦。是时候我们用一个简单得多的传感器把它安装到汽车上了
本章将涵盖以下主题:
- 红外接近传感器
- 自动紧急制动
- 给它自我控制的能力
- 使其完全自治
下图描绘了一个红外接近传感器:
它由传感器和变送器两大部分组成。发射器发射红外波;这些红外(红外)波随后击中物体并返回传感器,如下图所示
现在,如上图所示,发射的红外波从与传感器不同距离的表面反弹回来,然后以角度接近传感器。现在,由于发射器和传感器之间的距离在所有时间点上都是固定的,因此反射的红外波所对应的角度将与它反弹之前所经过的距离成正比。红外接近传感器中有超精密传感器,能够感应红外波接近它的角度。通过这个角度,它给用户一个与其相对应的距离值。这种寻找距离的方法被称为三角测量,在行业中得到了广泛的应用。我们需要记住的另一件事是,正如我们在前面章节中提到的,我们都被红外辐射包围;任何高于绝对零度的物体都会发出相应的波。此外,我们周围的阳光具有充足的红外辐射。因此,这些传感器有一个内置的电路来补偿它;然而,它能做的只有这么多。这就是为什么,这个解决方案在处理阳光直射时可能会遇到一些问题。
现在,理论已经足够了,让我们看看这辆车到底是如何工作的。我们在本例中使用的红外接近传感器是夏普的模拟传感器,零件代码为 GP2D12。它的有效感应范围为 1000-800 毫米。该范围还取决于相关对象表面的反射率。对象越暗,范围越短。此传感器有三个针脚。正如您可能已经猜到的,有一个用于 VCC,另一个用于接地,最后一个用于信号。这是一个模拟传感器;因此,将根据电压给出距离读数。通常,对于大多数模拟传感器,您会得到一个图表,该图表将描述不同传感范围内的各种电压。输出基本上取决于传感器的内部硬件及其结构,因此可能会有很大的不同。下面是传感器及其输出的图表:
好吧,到目前为止还不错。正如我们所知,Raspberry Pi 不接受模拟输入;因此,我们将继续使用之前使用过的 ADC。我们将使用以前使用过的 ADC。
有一种新技术,新的汽车都配备了。称为自动紧急制动;无论我们开车时多么认真,我们都会分心,比如 Facebook 或 WhatsApp 通知,这会诱使我们把目光从路上移开,看向手机屏幕。这可能是道路事故的主要原因;因此,汽车制造商正在使用自动制动技术。这通常依赖于远程和短程雷达,它检测汽车周围其他物体的接近程度,并且在发生明显碰撞的情况下,它会自动对汽车应用制动器,防止它们与其他汽车或行人发生碰撞。这是一项非常酷的技术,但有趣的是,我们今天将用自己的双手来实现它。
为了做到这一点,我们将使用红外接近传感器来感知周围物体的接近程度。现在,拿起双面胶带,将红外距离传感器连接到汽车前部。完成此操作后,按如下所示连接电路:
好吧,那么,我们都准备好编码了。以下是代码,只需将其复制到 Pi 中即可:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
import Adafruit_ADS1x15
adc0 = Adafruit_ADS1x15.ADS1115()
GAIN = 1
adc0.start_adc(0, gain=GAIN)
Motor1a = 20
Motor1b = 21
Motor2b = 23
Motor2a = 24
GPIO.setup(Motor1a,GPIO.OUT)
GPIO.setup(Motor1b,GPIO.OUT)
GPIO.setup(Motor2a,GPIO.OUT)
GPIO.setup(Motor2b,GPIO.OUT)
def forward():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,1)
GPIO.output(Motor2a,0)
GPIO.output(Motor2b,1)
def stop():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,0)
GPIO.output(Motor2a,0)
GPIO.output(Motor2b,0)
while True:
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
forward()
min_dist = 20
if F < min_dist:
stop()
现在,让我们看看这段代码中实际发生了什么。一切都很简单;红外接近传感器感测前方物体的接近度,并以模拟信号的形式给出相应的距离值。这些信号随后由 ADC 采集,并转换为数字值。这些数字值最终通过 I2C 协议传输到 Raspberry Pi。
到目前为止,一切顺利。但你一定想知道这条线在干什么?
F = (1.0 / (F_value / 13.15)) - 0.35
我们在这里做的不多,我们只是简单地取 ADC 给出的数字值,使用这个公式,我们将数字值覆盖到以厘米为单位的可理解的距离值。这个计算是由制造商提供的,我们真的不必去想这个问题。大多数传感器都提供了这些计算。但是,如果您想了解我们使用此公式的方式和原因,我建议您仔细阅读传感器的数据表。数据表可通过以下链接在线获取:https://engineering.purdue.edu/ME588/SpecSheets/sharp_gp2d12.pdf 。
接下来,代码的主要部分如下:
min_dist = 20
If F < min_dist:
stop()
这同样非常简单。我们已经输入了一个距离值,在这个程序中,我们已经设置为20
。因此,每当F
(红外接近传感器产生的距离)的值小于20
时,就会调用stop()
函数。stop
功能只是让汽车停下来,防止与任何东西相撞
让我们上传代码,看看它是否真的有效!确保你在室内驾驶这辆车;否则,如果这辆车没有任何障碍物,你将很难阻止它。玩得高兴
我希望你能从这个活泼的小东西中得到乐趣。有趣的是,传感器的应用是多么简单,它能产生多大的影响。正如你已经学会的基本知识,现在是时候向前迈进,给汽车更多的权力。
在前面的代码中,我们只是让机器人停在障碍物前,为什么不让它绕着汽车转一圈呢?这将是一个超级简单但又超级有趣的过程。我们需要做的就是调整函数stop()
并使其能够转动。显然,为了清晰起见,我们还将函数的名称从stop()
更改为turn()
。有一件事要记住,你不必重写代码;我们需要做的只是一些小的调整。那么,让我们看一下代码,然后我会告诉你到底发生了什么变化以及为什么:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
import Adafruit_ADS1x15
adc0 = Adafruit_ADS1x15.ADS1115()
GAIN = 1
adc0.start_adc(0, gain=GAIN)
Motor1a = 20
Motor1b = 21
Motor2a = 23
Motor2b = 24
GPIO.setup(Motor1a,GPIO.OUT)
GPIO.setup(Motor1b,GPIO.OUT)
GPIO.setup(Motor2a,GPIO.OUT)
GPIO.setup(Motor2b,GPIO.OUT)
def forward():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,1)
GPIO.output(Motor2a,0)
GPIO.output(Motor2b,1)
def turn():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,1)
GPIO.output(Motor2a,1)
GPIO.output(Motor2b,0)
)
while True:
forward()
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
min_dist = 20
while F < min_dist:
turn()
正如您可能已经注意到的,除了以下几点之外,一切都几乎保持不变:
def turn():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,1)
GPIO.output(Motor2a,1)
GPIO.output(Motor2b,0)
代码的这一部分定义了turn()
功能,在该功能中,车辆的对侧车轮将朝相反方向旋转;因此,使汽车绕自己的轴转动:
min_dist = 20
while F < min_dist:
turn()
现在这是节目的主要部分,;在这一部分中,我们将定义汽车在遇到任何障碍物时会做什么。在我们之前的项目中,我们主要是告诉机器人一旦遇到任何障碍就立即停止;然而,现在我们将stop
函数与turn
函数链接起来,我们在程序中已经定义了turn
函数
我们只需提出如下条件:
min_dist = 20
If F < min_dist:
turn()
然后,它只会旋转几秒钟,因为微控制器会解析代码并执行它,然后脱离状态。要做到这一点,我们的 RespberryPi 几乎不需要几微秒。所以,我们甚至可能看不到发生了什么。因此,在我们的程序中,我们使用了一个while
循环。这基本上保持循环运行,直到满足时间条件。我们的条件是while F < min_dist:
,因此,在机器人检测到前方物体之前,它将继续执行其内部的功能,在我们的情况下,就是turn()
功能。因此,简单地说,直到它没有足够的转向来避开障碍物,车辆将继续转向,然后一旦执行循环,它将再次跳回主程序并继续直线行驶
很简单,不是吗?这就是编程的美妙之处!
现在,您必须已经了解了使用简单的接近传感器进行自动驾驶的基本知识。现在是我们让它完全自主的时候了。为了使其完全自主,我们必须了解并绘制周围环境,而不是在车辆遇到障碍物时才转动车辆。我们基本上需要将整个活动分为以下两个基本部分:
- 扫描环境
- 决定如何处理感知数据
现在,让我们先编写代码,然后看看需要做什么:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
import Adafruit_ADS1x15
adc0 = Adafruit_ADS1x15.ADS1115()
GAIN = 1
adc0.start_adc(0, gain=GAIN)
Motor1a = 20
Motor1b = 21
Motor2a = 23
Motor2b = 24
GPIO.setup(Motor1a,GPIO.OUT)
GPIO.setup(Motor1b,GPIO.OUT)
GPIO.setup(Motor2a,GPIO.OUT)
GPIO.setup(Motor2b,GPIO.OUT)
def forward():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,1)
GPIO.output(Motor2a,0)
GPIO.output(Motor2b,1)
def right():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,1)
GPIO.output(Motor2a,1)
GPIO.output(Motor2b,0)
def left():
GPIO.output(Motor1a,1)
GPIO.output(Motor1b,0)
GPIO.output(Motor2a,0)
GPIO.output(Motor2b,1)
def stop():
GPIO.output(Motor1a,0)
GPIO.output(Motor1b,0)
GPIO.output(Motor2a,0)
GPIO.output(Motor2b,0)
while True:
forward()
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
min_dist = 20
if F< min_dist:
stop()
right()
time.sleep(1)
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
R = F
left()
time.sleep(2)
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.3
L = F
if L < R:
right()
time.sleep(2)
else:
forward()
现在大部分节目都和我们以前的节目一样;在本程序中,我们定义了以下功能:
forward()
right()
left()
stop()
关于函数的定义,我不需要告诉您太多,所以让我们继续前进,看看我们还有什么存货。
主要动作在我们的无限循环while True:
中进行。让我们看看到底发生了什么:
while True:
forward()
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
min_dist = 20
if F< min_dist:
stop()
让我们看看这部分代码在做什么:
- 当我们的程序进入无限循环时,首先执行的是
forward()
函数;也就是说,一旦执行无限循环,车辆将开始前进 - 此后,
F_value = adc.get_last_result()
从 ADC 获取读数并将其存储在名为F_value
的变量中 F = (1.0/(F-value/13.15))-0.35
正在将距离计算为可理解的公制距离值min_dist = 20
,我们已经简单地定义了我们稍后将使用的最小距离
完成这部分代码后,if
语句将检查F < min_dist:
是否正确。如果是这样的话,if
语句下的代码将开始执行。第一行是stop()
功能。因此,每当车辆遇到前方的任何障碍物时,它会做的第一件事就是停车
现在,正如我提到的,我们的代码的第一部分是理解环境,所以让我们继续看看我们是如何做到的:
right()
time.sleep(1)
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
R = F
left()
time.sleep(2)
F_value = adc0.get_last_result()
F = (1.0 / (F_value / 13.15)) - 0.35
L = F
车辆停止后,将立即右转。如您所见,下一行代码为time.sleep(1)
,因此在另一1
秒内,车辆将继续右转。我们随机选取了1
秒的时间,您可以稍后调整
一旦右转,它将再次读取接近传感器的读数,在使用此代码R=F
时,我们将该值存储在名为R
的变量中。
完成此操作后,车辆将转向另一侧,即使用left()
功能向左,并将像我们的time.sleep(2)
一样继续左转2
秒。这将使汽车转向障碍物的左侧。左转后,它将再次接收接近传感器的值,并使用代码L = F
将该值存储在变量L
中。
所以我们所做的基本上就是扫描我们周围的区域。在中间,我们有一个障碍。首先右转,取右侧的距离值;此后,我们将左转并获取左侧的距离值。所以我们基本上知道障碍物周围的环境
现在我们要做一个决定,我们必须朝着哪个方向前进。让我们看看我们将如何做到这一点:
if L < R:
right()
time.sleep(2)
else:
forward()
使用if
语句,我们通过该代码if L < R:
比较障碍物左右两侧的接近传感器值。如果L
小于R
,则车辆将右转2
秒。如果条件不正确,则else:
语句将生效,从而使车辆前进
现在,如果我们从更大的角度来看代码,就会发生以下事情:
- 车辆将向前行驶,直到遇到障碍物
- 遇到障碍物时,机器人将停止
- 它将首先右转并测量到它前面物体的距离
- 然后,它将左转并测量到它前面的物体的距离
- 在此之后,它将比较左右两侧的距离,并选择它必须进入的方向
- 如果必须向右转,它将右转,然后继续前进
- 如果它必须向左走,那么它已经处于左转方向,所以它必须一直走
让我们上传代码,看看事情是否按计划进行。记住这一点,尽管每个环境和每辆车都不同,但您可能需要调整代码以使其顺利工作
现在我给你留个问题。如果在这两种情况下,传感器的读数都是无穷大或它能给出的最大可能值,该怎么办?机器人将做什么?
继续,做一些头脑风暴,看看我们能做些什么来解决这个问题!
在本章中,通过使用您迄今为止所学的所有基础知识,并通过引入红外接近传感器,我们能够采取先进的步骤开发我们的机器人汽车,以检测障碍物并相应地改变方向。在下一章中,我们将研究如何使我们自己的区域扫描器看到您!