内容简介:上一课,我们编写了上一课的主题是一个遥控小车。我们编写了无限移动的代码,它们由新命令中断。我们已经看到了这一设计理念的好处,它不阻塞 EV3 设备。精确移动的第一个版本(本节课的结果)将阻塞 EV3 设备,且它将耗费我们进一步的工作来找到一个不阻塞的方案。希望上一节课的车辆仍然存在。我们再次需要它。你还记得,右轮连接到端口 A,左边连接到端口 D。我们的遥控解决方案存在缺陷:移动越慢,转向的精度越差。我们需要的是一个操作,两个电机的速度可以被设置为定义的比率。如你可能期待的那样,这种操作是存在的。请给你的
上一课,我们编写了 TwoWheelVehicle
类,一个 EV3 的子类。它的方法是 move
和 stop
,但它不仅仅是围绕操作 opOutput_Speed
, opOutput_Start
和 opOutput_Stop
的薄薄的封装。本节课的最后,类 TwoWheelVehicle
将具有实质的内容。如大多数软件那样,它一步步增长,本节课不会是最后一次,我们将继续使用这个类。
上一课的主题是一个遥控小车。我们编写了无限移动的代码,它们由新命令中断。我们已经看到了这一设计理念的好处,它不阻塞 EV3 设备。精确移动的第一个版本(本节课的结果)将阻塞 EV3 设备,且它将耗费我们进一步的工作来找到一个不阻塞的方案。
同步的电机移动
希望上一节课的车辆仍然存在。我们再次需要它。你还记得,右轮连接到端口 A,左边连接到端口 D。我们的遥控解决方案存在缺陷:移动越慢,转向的精度越差。我们需要的是一个操作,两个电机的速度可以被设置为定义的比率。如你可能期待的那样,这种操作是存在的。请给你的小车发送如下的直接命令:
------------------------------------------------------------- \len \cnt \ty\hd \op\la\no\sp\tu \step \br\op\la\no\ ------------------------------------------------------------- 0x|12:00|2A:00|80|00:00|B0|00|09|3B|81:32|82:D0:02|00|A6|00|09| ------------------------------------------------------------- \18 \42 \no\0,0 \O \0 \A \-5\50 \720 \0 \O \0 \A \ \ \ \ \ \u \ \+ \ \ \ \ \u \ \+ \ \ \ \ \ \t \ \D \ \ \ \ \t \ \D \ \ \ \ \ \p \ \ \ \ \ \ \p \ \ \ \ \ \ \ \u \ \ \ \ \ \ \u \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \_ \ \ \ \ \ \ \ \S \ \ \ \ \ \ \S \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \e \ \ \ \ \ \ \a \ \ \ \ \ \ \ \p \ \ \ \ \ \ \r \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \t \ \ \ \ \ \ \ \S \ \ \ \ \ \ \ \ \ \ \ \ \ \ \y \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \c \ \ \ \ \ \ \ \ \ \ -------------------------------------------------------------
点击 A 旋转 720°,点击 D 移动 360°。小车左转,两个电机都以低速转动且良好的同步。新的操作是:
-
opOutput_Step_Sync = 0xB0,参数是:
- LAYER
- NOS:两个输出端口,命令不是对称的,它区别了低端口和高端口。在我们的情况中,低端口是 PORT_A,右轮,高端口是 PORT_D,左轮。
- SPEED
- TURN:转向比率,[-200 - 200]
- STEP:转速脉冲以度为单位,值 0 代表无限运动(如果TURN > 0,则 STEP 限制低端口,如果 TURN < 0,则限制较高端口)。正值,参数 SPEED 的符号确定方向。
- BRAKE
参数 TURN 需要一些解释。但你处在一个很好的位置,你已经知道了它的含义,因为我们在第 3 课的远程控制程序中使用了它。如你可能记得的那样,我们计算两轮的速度:
if turn > 0: speed_right = speed speed_left = round(speed * (1 - turn / 100)) else: speed_right = round(speed * (1 + turn / 100)) speed_left = speed
到整数值的舍入是低速下糟糕的精度的原因!但现在我们很高兴,操作 opOutput_Step_Sync
计算时不执行舍入:
if turn > 0: speed_right = speed speed_left = speed * (1 - turn / 100) else: speed_right = speed * (1 + turn / 100) speed_left = speed
速度和步数是成比例的,我们也可以写为:
if turn > 0: step_right = math.copysign(1, speed) * step step_left = math.copysign(1, speed) * step * (1 - turn / 100) else: step_right = math.copysign(1, speed) * step * (1 + turn / 100) step_left = math.copysign(1, speed) * step
操作 opOutput_Step_Sync
对于具有两个驱动轮的小车是完美的!就像是专门为它设计的一样。请通过把两个 opOutput_Speed
操作替换为一个 opOutput_Step_Sync
来提升你的远程控制程序。你将看到,它工作的更好,特别是低速下。我把我的 move
函数的代码修改为了:
def move(self, speed: int, turn: int)->None: assert self._sync_mode != ev3.SYNC, 'no unlimited operations allowed in sync_mode SYNC' assert isinstance(speed, int), "speed needs to be an integer value" assert -100 <= speed and speed <= 100, "speed needs to be in range [-100 - 100]" assert isinstance(turn, int), "turn needs to be an integer value" assert -200 <= turn and turn <= 200, "turn needs to be in range [-200 - 200]" if self._polarity == -1: speed *= -1 if self._port_left < self._port_right: turn *= -1 ops = b''.join([ ev3.opOutput_Step_Sync, ev3.LCX(0), # LAYER ev3.LCX(self._port_left + self._port_right), # NOS ev3.LCX(speed), ev3.LCX(turn), ev3.LCX(0), # STEPS ev3.LCX(0), # BRAKE ev3.opOutput_Start, ev3.LCX(0), # LAYER ev3.LCX(self._port_left + self._port_right) # NOS ]) self.send_direct_cmd(ops)
一些说明:
-
车辆遵从相同的转弯,与速度无关。这是
opOutput_Step_Sync
相对于使用两个opOutput_Speed
操作的主要提升。 -
如果你使用了
opOutput_Polarity
,你将发现 ,你无法把它和opOutput_Step_Sync
结合。你不得不手动完成。修改两个电机的极性很简单,只是反转参数 SPEED 即可。 - 如果你交换了电机的连接,以使你的左边电机连接在较低端口上,它也很简单,反转 TURN。
- 仅更改一个电机的极性非常棘手。
下表可能有助于理清你已经看到的移动。当给出以下条件时,它描述了依赖于 TURN 的运动类型:
- 两个电机具有相同的极性,
- 右轮连接到更低的端口,左边的更高。
TURN | 移动 | 描述 |
---|---|---|
0 | 直行 | 两个电机以相同的速度朝相同的方向移动 |
[0 - 100] | 左转 | 两个电机朝相同的方向旋转,左边的以更低的速度 |
100 | 绕左轮左转 | 只有右边的电机移动 |
[100 - 200] | 向左转弯 | 两个电机朝相反的方向旋转,左边的以更低的速度旋转 |
200 | 向左转圈 | 两个电机朝相同的方向旋转,但速度相同 |
[0 - -100] | 右转 | 两个电机朝相同的方向旋转,右边的以更低的速度 |
-100 | 绕右轮右转 | 只有左边的电机移动 |
[-100 - -200] | 向右转弯 | 两个电机朝相反的方向旋转,右边的以更低的速度旋转 |
-200 | 向右转圈 | 两个电机朝相同的方向旋转,但速度相同 |
当端口连接不同时,左轮为较低端口,右轮为较高端口时,请反映情况。
欢迎你进一步提升远程控制工程。你可以使用游戏杆而不是你的键盘的方向键。或者你可以使用智能手机的陀螺仪传感器。但这是你的项目,我们将远程控制留在了后面(至少目前为止)。
具有两个驱动轮的车辆的定义明确且可预测的运动
我们仍然专注于具有两个驱动轮的车辆及其运动的准确性。远程控制是一种非常特殊的情况,如果需要,人类的头脑会监督车辆的运动并立即进行一些修正。这种情况不需要像转弯半径那样的单位。这就像驾驶汽车一样,没有必要确切知道转弯半径是如何从方向盘的位置决定的。修正是相对和直观的。机器人在没有外部控制的情况下移动,它们的算法需要知道参数的确切依赖性。我们将编写控制车辆运动的程序。我们从最难的变体开始,不存在任何校正机制。这意味着,我们需要能够预测和精确描述车辆运动的函数。我想到以下几点:
-
drive_straight(speed:int, distance: float=None),其中
- speed 的符号描述了方向(向前或向后)
- speed 的绝对值描述了速度。我们更喜欢每秒 SI 单位计,但它是百分比。
- distance 为 None 或正的,它以 SI 单位 meter 给出。如果它是 None,则运动是无限的。
-
drive_turn(speed:int, radius_turn:float, angle:float=None, right_turn:bool=False),其中
- speed 的符号描述了方向(向前或向后)
- speed 的绝对值描述了速度。
-
radius_turn 是转弯的半径,单位为
meter
。我们取两个驱动轮的中间作为我们的参考点。 -
radius_turn
的符号描述了转弯的方向。正值表示左转,正转向,负值代表顺时针转动。 -
angle
为 None 或正的,它是以度为单位的圆弧段 (90° 为四分之一圆,720° 为两个完整的圆)。如果它是 None,则表示无限移动。 -
right_turn
是一个用于特殊情景的标记。如果我们打开 place,属性radius_turn
为零并且没有符号。在这种情况下,左转是默认值,属性right_turn
是相反方向的标志。
正如你可能想象的那样,我们并没有走太远。我们将使用操作 opOutput_Ready
, opOutput_Start
和 opOutput_Speed_Sync
。这是说我们将不使用中断。从这个角度来看,我们可以回到第二课的知识。
确定车辆的尺寸
们需要车辆的某些尺寸来将上述功能的 SI 单位转换为参数 turn
和操作 opOutput_Speed_Sync
的步骤。 车辆的尺寸为:
radius_wheel tread
使用尺子量出(对于我的车辆):
- radius_wheel = 0.021 m
- tread = 0.16 m
具有更高精度的替代方案是测量车辆的运动。要获得驱动轮的半径,可以使用以下直接命令:
---------------------------------------------------------- \len \cnt \ty\hd \op\la\no\sp\tu\step \br\op\la\no\ ---------------------------------------------------------- 0x|11:00|2A:00|80|00:00|B0|00|09|14|00|82:10:0E|01|A6|00|09| ---------------------------------------------------------- \17 \42 \no\0,0 \O \0 \A \20\0 \3600 \1 \O \0 \A \ \ \ \ \ \u \ \+ \ \ \ \ \u \ \+ \ \ \ \ \ \t \ \D \ \ \ \ \t \ \D \ \ \ \ \ \p \ \ \ \ \ \ \p \ \ \ \ \ \ \ \u \ \ \ \ \ \ \u \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \_ \ \ \ \ \ \ \ \S \ \ \ \ \ \ \S \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \e \ \ \ \ \ \ \a \ \ \ \ \ \ \ \p \ \ \ \ \ \ \r \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \t \ \ \ \ \ \ \ \S \ \ \ \ \ \ \ \ \ \ \ \ \ \ \y \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \c \ \ \ \ \ \ \ \ \ \ ----------------------------------------------------------
拿出尺子并测量你的小车移动的距离(如果你的小车没有直行,寻找轮子的最佳结合,而不是外观,这似乎是相同的尺寸,真的是)。轮子的一个完整旋转的距离计算公式为 2 pi
radius_wheel。3,600 度为 10 个完整旋转,因此下面的计算给出了你的轮子的 radius_wheel
:
radius_wheel = distance / (20 * pi)
这将我的轮子的半径修正为 radius_wheel = 0.02128 m。接下来,我们让车辆旋转,并计数 N,车辆转数。为此,我们发送直接命令:
---------------------------------------------------------------- \len \cnt \ty\hd \op\la\no\sp\tu \step \br\op\la\no\ ---------------------------------------------------------------- 0x|13:00|2A:00|80|00:00|B0|00|09|14|82:C8:00|82:50:46|01|A6|00|09| ---------------------------------------------------------------- \19 \42 \no\0,0 \O \0 \A \20\200 \18000 \1 \O \0 \A \ \ \ \ \ \u \ \+ \ \ \ \ \u \ \+ \ \ \ \ \ \t \ \D \ \ \ \ \t \ \D \ \ \ \ \ \p \ \ \ \ \ \ \p \ \ \ \ \ \ \ \u \ \ \ \ \ \ \u \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \_ \ \ \ \ \ \ \ \S \ \ \ \ \ \ \S \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \e \ \ \ \ \ \ \a \ \ \ \ \ \ \ \p \ \ \ \ \ \ \r \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \t \ \ \ \ \ \ \ \S \ \ \ \ \ \ \ \ \ \ \ \ \ \ \y \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \c \ \ \ \ \ \ \ \ \ \ ----------------------------------------------------------------
我计算了我的车辆 N = 15.2 完整旋转。两个轮子都旋转了 18, 000°,这是50 个完整的旋转,或者距离 50 2
pi
radius_wheel
。旋转的半径为 0.5
tread(我们定义轮子的中间为我们的参考点)。这就是说 N 2
pi 0.5
tread = 50 2
pi * radius_wheel 或者:
tread = radius_wheel * 100 / N
这修正了 tread
的尺寸(在我的情况中:tread = 0.1346 m)。稍后我们将执行一些额外的运动,也许这将再次修正 tread
的尺寸。
获取参数 STEP 和 TURN 的数学转换
OK,我们知道了车的尺寸。接下来我们需要把我们的方法 drive_straight
和 drive_turn
的参数转为操作 opOutput_Step_Sync
的参数 STEP 和 TURN。这需要一些数学的东西。如果你对细节不感兴趣,你可以只看本小节最后的结果。但是大多数人都想知道细节,细节在这里。
给定我们的小车需要旋转的 angle
和 radius_turn
。在转弯中,两个轮子移动不同的距离。外面的轮子的距离为:2 pi
radius_wheel STEP / 360。可以根据转弯的几何形状和车辆踏板的知识计算相同的距离:2
pi (radius_turn + 0.5
tread) * angle / 360。这两个相同距离的描述给出了以下等式:
STEP = angle * (radius_turn + 0.5 * tread) / radius_wheel
OK,第一个参数计算出来了,但是我们仍然需要计算 TURN。关键的方法来自转弯的几何学,是说,两个车轮之间的速度比 speed_right / speed_left 与外面和里面的车轮的距离比相同:
speed_right / speed_left = (radius_turn + 0.5 * tread) / (radius_turn - 0.5 * tread)
你可能还记得,我们已经知道了两个轮子的速度:speed_right = SPEED 和 speed_left = SPEED * (1 - TURN / 100)。这给出等式:
1/ (1 - TURN / 100) = (radius_turn + 0.5 * tread) / (radius_turn - 0.5 * tread)
转换这个等式得到:
TURN = 100 * (1 - (radius_turn - 0.5 * tread) / (radius_turn + 0.5 * tread))
就是这样,果给出转弯运动的尺寸 (angle, radius_turn) 和车辆的尺寸 (tread, radius_wheel):
-
变量 STEP 的计算方法为:
STEP = angle * (radius_turn + 0.5 * tread) / radius_wheel
-
变量 TURN 的计算方法为:
TURN = 100 * (1 - (radius_turn - 0.5 * tread) / (radius_turn + 0.5 * tread))
在 Python 中,这可以写作:
rad_outer = radius_turn + 0.5 * self._tread rad_inner = radius_turn - 0.5 * self._tread step = round(angle*rad_outer / self._radius_wheel) turn = round(100*(1 - rad_inner / rad_outer)) if angle < 0: step *= -1 turn *= -1 if self._polarity == -1: speed *= -1
数学转换的控制
我们做一些合理性检查:
- 以半径 radius_turn = 0.5 * tread 旋转得到 TURN = 100 或 TURN = -100,这是对的。
- 以半径 radius_turn = 0 旋转得到 TURN = 200 或 TURN = -200,这也是OK 的。
现在,我们回到真正的测试。以 angle = 90° 和 radius_turn = 0.5 m 旋转。在我的情况中,由上面的计算给出 STEP = 2358 和 TURN = 23,这将得到命令:
---------------------------------------------------------- \len \cnt \ty\hd \op\la\no\sp\tu\step \br\op\la\no\ ---------------------------------------------------------- 0x|11:00|2A:00|80|00:00|B0|00|09|14|17|82:36:09|01|A6|00|09| ---------------------------------------------------------- \17 \42 \no\0,0 \O \0 \A \20\23\2358 \1 \O \0 \A \ \ \ \ \ \u \ \+ \ \ \ \ \u \ \+ \ \ \ \ \ \t \ \D \ \ \ \ \t \ \D \ \ \ \ \ \p \ \ \ \ \ \ \p \ \ \ \ \ \ \ \u \ \ \ \ \ \ \u \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \_ \ \ \ \ \ \ \ \S \ \ \ \ \ \ \S \ \ \ \ \ \ \ \t \ \ \ \ \ \ \t \ \ \ \ \ \ \ \e \ \ \ \ \ \ \a \ \ \ \ \ \ \ \p \ \ \ \ \ \ \r \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \t \ \ \ \ \ \ \ \S \ \ \ \ \ \ \ \ \ \ \ \ \ \ \y \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \c \ \ \ \ \ \ \ \ \ \ ----------------------------------------------------------
确实,我的小车几乎以半径 0.5 m 移动了完美的四分之圆。
增强类 TwoWheelVehicle
数学已经足够了,至少在目前,让我们的编码吧!作为我们的第一个任务,我们修改类 TwoWheelVehicle
的构造函数。我们添加两个尺寸 radius_wheel
和 tread
,它们被确定为必要的:
def __init__( self, radius_wheel: float, tread: float, protocol: str=None, host: str=None, ev3_obj: ev3.EV3=None ): super().__init__(protocol=protocol, host=host, ev3_obj=ev3_obj) self._radius_wheel = radius_wheel self._tread = tread self._polarity = 1 self._port_left = ev3.PORT_D self._port_right = ev3.PORT_A
接下来我们编写方法 _drive
,它与方法 move
非常接近。调用者传入参数 speed
, turn
和 step
调用它。外部的世界用 radius_turn
和 angle
来思考,但它们必须被转为内部的参数 turn
和 step
。这是说,方法 drive_straight
和 drive_turn
执行转换,然后它们调用内部的方法 _drive
:
def _drive(self, speed: int, turn: int, step: int) -> bytes: assert isinstance(speed, int), "speed needs to be an integer value" assert -100 <= speed and speed <= 100, "speed needs to be in range [-100 - 100]" if self._polarity == -1: speed *= -1 if self._port_left < self._port_right: turn *= -1 ops_ready = b''.join([ ev3.opOutput_Ready, ev3.LCX(0), # LAYER ev3.LCX(self._port_left + self._port_right) # NOS ]) ops_start = b''.join([ ev3.opOutput_Step_Sync, ev3.LCX(0), # LAYER ev3.LCX(self._port_left + self._port_right), # NOS ev3.LCX(speed), ev3.LCX(turn), ev3.LCX(step), ev3.LCX(0), # BRAKE ev3.opOutput_Start, ev3.LCX(0), # LAYER ev3.LCX(self._port_left + self._port_right) # NOS ]) if self._sync_mode == ev3.SYNC: return self.send_direct_cmd(ops_start + ops_ready) else: return self.send_direct_cmd(ops_ready + ops_start)
我们区分 SYNC 和 ASYNC 或 STD。在 ASYNC 或 STD 的情况中,我们在移动开始前等待,在 SYNC 的情况中直到它结束。如果你从 ev3-python3
下载了模块 ev3_vehicle.py
,你将找不到方法 _drive
。这是一个提示,我们将回到类 TwoWheelVehicle
实现中断。我们编写方法 drive_turn
的代码:
def drive_turn( self, speed: int, radius_turn: float, angle: float=None, right_turn: bool=False ) -> None: assert isinstance(radius_turn, numbers.Number), "radius_turn needs to be a number" assert angle is None or isinstance(angle, numbers.Number), "angle needs to be a number" assert angle is None or angle > 0, "angle needs to be positive" assert isinstance(right_turn, bool), "right_turn needs to be a boolean" rad_right = radius_turn + 0.5 * self._tread rad_left = radius_turn - 0.5 * self._tread if radius_turn >= 0 and not right_turn: turn = round(100*(1 - rad_left / rad_right)) else: turn = - round(100*(1 - rad_right / rad_left)) if turn == 0: raise ValueError("radius_turn is too large") if angle is None: self.move(speed, turn) else: if turn > 0: step = round(angle*rad_right / self._radius_wheel) else: step = - round(angle*rad_left / self._radius_wheel) self._drive(speed, turn, step)
非常大的 radius_turn
值将导致一些问题。舍入到整数之后,它们导致直行。在这种情况下我们抛出一个错误。
方法 drive_straight
:
def drive_straight(self, speed: int, distance: float=None)->None: assert distance is None or isinstance(distance, numbers.Number), \ "distance needs to be a number" assert distance is None or distance > 0, \ "distance needs to be positive" if distance is None: self.move(speed, 0) else: step = round(distance * 360 / (2 * math.pi * self._radius_wheel)) self._drive(speed, 0, step)
放松,我们已经实现了一种可以预测车辆的工具。 请做一些测试!
了解车辆的位置和方向
抱歉,之前的一些内容我写了 足够的数学知识 ,但现在我来到了三角学。但我们处于一种真的值得努力的情境。想象一下,你驾驶你的车并经过了一系列的移动,你需要知道它的位置和方向。我们无需使用传感器就可以做到。相反,我们使用纯数学而不是魔法。
让我们做一些假设:
- 当你创建类 TwoWheelVehicle 时你的车放置的位置将是你的坐标系统的原点 (0, 0)。
- 此时指向正前方的方向是 x 轴的方向。
- y 轴指向你的车的左手边。
- 你的车的位置由 x 和 y 坐标描述。以米为单位。
-
你的车的
orientation
是车的原始方向和它的实际方向的差值。以度数为单位。左转增加orientation
,右转减小它。
这次,我不会介绍数学。把它当作原样,或者把它作为一个谜语,你必须解决。但是请把如下的逻辑添加到你的类 TwoWheelVehicle
中:
-
构造函数:
self._orientation= 0.0 self._pos_x= 0.0 self._pos_y= 0.0
-
drive_straight(speed, distance)
diff_x = distance * math.cos(math.radians(self._orientation)) diff_y = distance * math.sin(math.radians(self._orientation)) if speed > 0: self._pos_x += diff_x self._pos_y += diff_y else: self._pos_x -= diff_x self._pos_y -= diff_y
-
drive_turn(speed, radius_turn, angle)
angle += 180 angle %= 360 angle -= 180 fact = 2.0 * radius_turn * math.sin(math.radians(0.5 * angle)) self._orientation += 0.5 * angle self._pos_x += fact * math.cos(math.radians(self._orientation)) self._pos_y += fact * math.sin(math.radians(self._orientation)) self._orientation += 0.5 * angle self._orientation += 180 self._orientation %= 360 self._orientation -= 180
完成类 TwoWheelVehicle
我们通过添加更多的功能来完成类 TwoWheelVehicle
:
-
我们添加一个方法
rotate_to(speed: int, o: float)
,它做如下的事情:- 计算实际的和新的方向的距离
-
以
radius_turn
= 0 调用drive_turn
旋转小车,以使其获得新方向。
-
我们添加一个方法
drive_to(self, speed: int, x: float, y: float)
,它做如下的事情:-
计算实际的位置和新位置的距离:
diff_x = pos_x - self._pos_x diff_y = pos_y - self._pos_y
-
计算实际的位置和新位置的距离:
我们需要坐标和绝对值:
distance = math.sqrt(diff_x**2 + diff_y**2)
- 计算新位置的方向。这很棘手,你需要彻底了解三角学,因为你必须使用 `atan`,`tan` 的反函数。我给你一个提示:
if abs(diff_x) > abs(diff_y): direction = math.degrees(math.atan(diff_y/diff_x)) else: fract = diff_x / diff_y sign = math.copysign(1.0, fract) direction = sign * 90 - math.degrees(math.atan(fract)) if diff_x < 0: direction += 180 direction %= 360
- 调用 `rotate_to`,以使 `orientation` 指向 `direction`。 - 调用 `drive_straight` 移动小车到新的位置。
我们做一些测试:
-
我们把小车发送到一些循环行程中,并编码一系列
drive_to
,最终在 position = (0,0) 结束。我们最后添加一个以 orientation = 0 对rotate_to
的调用。请评估,小车是否真的回到了它最初的位置和方向。 -
我们给循环行程添加一些
drive_turn
并评估,drive_turn
的错误是大于还是小于drive_to
的。
以大 radius_turn
旋转将有一个糟糕的精度。这也是舍入的结果。在这种情况中,将 TURN 舍入为整数值创造了错误。
如果你从 ev3-python3
下载了 ev3_vehicle
模块,当你调用它的方法 drive_straight
, drive_turn
, rotate_to
或 drive_to
时,你最后需要添加一个对 stop
的方法调用!
异步和同步运动
让我们仔细看看我们的车辆驾驶情况。这是我的循环行程的程序:
#!/usr/bin/env python3 import ev3, ev3_vehicle my_vehicle = ev3_vehicle.TwoWheelVehicle(0.02128, 0.1346, protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') my_vehicle.verbosity = 1 speed = 25 my_vehicle.drive_straight(speed, 0.05) my_vehicle.drive_turn(speed, -0.07, 65) my_vehicle.drive_straight(speed, 0.35) my_vehicle.drive_turn(speed, 0.20, 140) my_vehicle.drive_straight(speed, 0.15) my_vehicle.drive_turn(speed, -1.10, 55) my_vehicle.drive_turn(speed, 0.35, 160) my_vehicle.drive_to(speed, 0.0, 0.0) my_vehicle.rotate_to(speed, 0.0)
这个程序的输出:
15:42:05.989592 Sent 0x|14:00|2A:00|80|00:00|AA:00:09:B0:00:09:19:00:82:87:00:00:A6:00:09| 15:42:05.990443 Sent 0x|15:00|2B:00|80|00:00|AA:00:09:B0:00:09:19:81:9E:82:A3:01:00:A6:00:09| 15:42:05.990925 Sent 0x|14:00|2C:00|80|00:00|AA:00:09:B0:00:09:19:00:82:AE:03:00:A6:00:09| 15:42:05.991453 Sent 0x|15:00|2D:00|80|00:00|AA:00:09:B0:00:09:19:81:32:82:DF:06:00:A6:00:09| 15:42:05.991882 Sent 0x|14:00|2E:00|80|00:00|AA:00:09:B0:00:09:19:00:82:94:01:00:A6:00:09| 15:42:05.992330 Sent 0x|14:00|2F:00|80|00:00|AA:00:09:B0:00:09:19:34:82:C9:0B:00:A6:00:09| 15:42:05.992766 Sent 0x|15:00|30:00|80|00:00|AA:00:09:B0:00:09:19:81:20:82:42:0C:00:A6:00:09| 15:42:05.993306 Sent 0x|16:00|31:00|80|00:00|AA:00:09:B0:00:09:19:82:38:FF:82:D0:01:00:A6:00:09| 15:42:05.993714 Sent 0x|14:00|32:00|80|00:00|AA:00:09:B0:00:09:19:00:82:3F:03:00:A6:00:09| 15:42:05.994202 Sent 0x|15:00|33:00|80|00:00|AA:00:09:B0:00:09:19:82:38:FF:81:69:00:A6:00:09|
在五毫秒内这个程序给 EV3 设备发送了所有的直接命令,它们被放入队列并等待执行。这个代码很简单,但是会阻塞 EV3 设备直到开始执行最后的命令。请遵循代码并反映出属性 pos_x,pos_y 和 orientation 的值,以及它们是否与我们车辆的实际位置和方向相对应。
这是异步行为! 程序和 EV3 设备在不同的时间尺度上运行。
现在我们改为同步模式并比较行为:
#!/usr/bin/env python3 import ev3, ev3_vehicle my_vehicle = ev3_vehicle.TwoWheelVehicle(0.02128, 0.1346, protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') my_vehicle.verbosity = 1 speed = 25 my_vehicle.sync_mode = ev3.SYNC my_vehicle.drive_straight(speed, 0.05) my_vehicle.drive_turn(speed, -0.07, 65) my_vehicle.drive_straight(speed, 0.35) my_vehicle.drive_turn(speed, 0.20, 140) my_vehicle.drive_straight(speed, 0.15) my_vehicle.drive_turn(speed, -1.10, 55) my_vehicle.drive_turn(speed, 0.35, 160) my_vehicle.drive_to(speed, 0.0, 0.0) my_vehicle.rotate_to(speed, 0.0)
此版本产生以下输出:
15:46:19.859532 Sent 0x|14:00|2A:00|00|00:00|B0:00:09:19:00:82:87:00:00:A6:00:09:AA:00:09| 15:46:20.307045 Recv 0x|03:00|2A:00|02| 15:46:20.307760 Sent 0x|15:00|2B:00|00|00:00|B0:00:09:19:81:9E:82:A3:01:00:A6:00:09:AA:00:09| 15:46:22.100007 Recv 0x|03:00|2B:00|02| 15:46:22.100612 Sent 0x|14:00|2C:00|00|00:00|B0:00:09:19:00:82:AE:03:00:A6:00:09:AA:00:09| 15:46:24.597018 Recv 0x|03:00|2C:00|02| 15:46:24.597646 Sent 0x|15:00|2D:00|00|00:00|B0:00:09:19:81:32:82:DF:06:00:A6:00:09:AA:00:09| 15:46:29.141999 Recv 0x|03:00|2D:00|02| 15:46:29.142609 Sent 0x|14:00|2E:00|00|00:00|B0:00:09:19:00:82:94:01:00:A6:00:09:AA:00:09| 15:46:30.221994 Recv 0x|03:00|2E:00|02| 15:46:30.222626 Sent 0x|14:00|2F:00|00|00:00|B0:00:09:19:34:82:C9:0B:00:A6:00:09:AA:00:09| 15:46:44.779922 Recv 0x|03:00|2F:00|02| 15:46:44.780364 Sent 0x|15:00|30:00|00|00:00|B0:00:09:19:81:20:82:42:0C:00:A6:00:09:AA:00:09| 15:46:46.938901 Recv 0x|03:00|30:00|02| 15:46:46.939701 Sent 0x|16:00|31:00|00|00:00|B0:00:09:19:82:38:FF:82:D0:01:00:A6:00:09:AA:00:09| 15:46:55.695901 Recv 0x|03:00|31:00|02| 15:46:55.696508 Sent 0x|14:00|32:00|00|00:00|B0:00:09:19:00:82:3F:03:00:A6:00:09:AA:00:09| 15:47:05.061856 Recv 0x|03:00|32:00|02| 15:47:05.062504 Sent 0x|15:00|33:00|00|00:00|B0:00:09:19:82:38:FF:81:69:00:A6:00:09:AA:00:09| 15:47:05.097692 Recv 0x|03:00|33:00|02|
车辆的运动是相同的,但现在程序发送一个直接命令并等待它完成,然后它发送下一个。sync_mode = SYNC 如它设计那样工作,它同步程序和EV3 设备的时间尺度。另一个好处是,它控制每个直接命令的成功并直接作出反应,如果出现意外情况。
这两个版本(异步和同步)都会阻塞 EV3 设备。
结论
类 TwoWheelVehicle
通过两个驱动轮控制小车的运动。如果我们知道课程的几何形状,我们就可以编写一个程序,通过它来驱动我们的车辆。
我们已经看到了同步和异步模式之间的区别。目前,我们更倾向于 SYNC。但我们的目标是一个解决方案,它可以同步驱动车辆并且不会阻塞EV3 设备。这将打开多任务的大门。
我的 EV3TwoWheelVehicle
类实际上有以下状态:
Help on module ev3_vehicle: NAME ev3_vehicle - EV3 vehicle CLASSES ev3.EV3(builtins.object) TwoWheelVehicle class TwoWheelVehicle(ev3.EV3) | ev3.EV3 vehicle with two drived Wheels | | Method resolution order: | TwoWheelVehicle | ev3.EV3 | builtins.object | | Methods defined here: | | __init__(self, radius_wheel:float, tread:float, protocol:str=None, host:str=None, ev3_obj:ev3.EV3=None) | Establish a connection to a LEGO EV3 device | | Arguments: | radius_wheel: radius of the wheels im meter | tread: the vehicles tread in meter | | Keyword Arguments (either protocol and host or ev3_obj): | protocol | BLUETOOTH == 'Bluetooth' | USB == 'Usb' | WIFI == 'Wifi' | host: mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99') | ev3_obj: an existing EV3 object (its connections will be used) | | drive_straight(self, speed:int, distance:float=None) -> None | Drive the vehicle straight forward or backward. | | Attributes: | speed: in percent [-100 - 100] (direction depends on its sign) | positive sign: forwards | negative sign: backwards | | Keyword Attributes: | distance: in meter, needs to be positive | if None, unlimited movement | | drive_to(self, speed:int, pos_x:float, pos_y:float) -> None | Drive the vehicle to the given position. | | Attributes: | speed: in percent [-100 - 100] (direction depends on its sign) | positive sign: forwards | negative sign: backwards | x: x-coordinate of target position | y: y-coordinate of target position | | drive_turn(self, speed:int, radius_turn:float, angle:float=None, right_turn:bool=False) -> None | Drive the vehicle a turn with given radius. | | Attributes: | speed: in percent [-100 - 100] (direction depends on its sign) | positive sign: forwards | negative sign: backwards | radius_turn: in meter | positive sign: turn to the left side | negative sign: turn to the right side | | Keyword Attributes: | angle: absolute angle (needs to be positive) | if None, unlimited movement | right_turn: flag of turn right (only in case of radius_turn == 0) | | move(self, speed:int, turn:int) -> None | Start unlimited movement of the vehicle | | Arguments: | speed: speed in percent [-100 - 100] | > 0: forward | < 0: backward | turn: type of turn [-200 - 200] | -200: circle right on place | -100: turn right with unmoved right wheel | 0 : straight | 100: turn left with unmoved left wheel | 200: circle left on place | | rotate_to(self, speed:int, orientation:float) -> None | Rotate the vehicle to the given orientation. | Chooses the direction with the smaller movement. | | Attributes: | speed: in percent [-100 - 100] (direction depends on its sign) | orientation: in degrees [-180 - 180] | | stop(self, brake:bool=False) -> None | Stop movement of the vehicle | | Arguments: | brake: flag if activating brake | | ---------------------------------------------------------------------- | Data descriptors defined here: | | orientation | actual orientation of the vehicle in degree, range [-180 - 180] | | polarity | polarity of motor rotation (values: -1, 1, default: 1) | | port_left | port of left wheel (default: PORT_D) | | port_right | port of right wheel (default: PORT_A) | | pos_x | actual x-component of the position in meter | | pos_y | actual y-component of the position in meter | | ---------------------------------------------------------------------- | Methods inherited from ev3.EV3: | | __del__(self) | closes the connection to the LEGO EV3 | | send_direct_cmd(self, ops:bytes, local_mem:int=0, global_mem:int=0) -> bytes | Send a direct command to the LEGO EV3 | | Arguments: | ops: holds netto data only (operations), the following fields are added: | length: 2 bytes, little endian | counter: 2 bytes, little endian | type: 1 byte, DIRECT_COMMAND_REPLY or DIRECT_COMMAND_NO_REPLY | header: 2 bytes, holds sizes of local and global memory | | Keyword Arguments: | local_mem: size of the local memory | global_mem: size of the global memory | | Returns: | sync_mode is STD: reply (if global_mem > 0) or message counter | sync_mode is ASYNC: message counter | sync_mode is SYNC: reply of the LEGO EV3 | | wait_for_reply(self, counter:bytes) -> bytes | Ask the LEGO EV3 for a reply and wait until it is received | | Arguments: | counter: is the message counter of the corresponding send_direct_cmd | | Returns: | reply to the direct command | | ---------------------------------------------------------------------- | Data descriptors inherited from ev3.EV3: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | sync_mode | sync mode (standard, asynchronous, synchronous) | | STD: Use DIRECT_COMMAND_REPLY if global_mem > 0, | wait for reply if there is one. | ASYNC: Use DIRECT_COMMAND_REPLY if global_mem > 0, | never wait for reply (it's the task of the calling program). | SYNC: Always use DIRECT_COMMAND_REPLY and wait for reply. | | The general idea is: | ASYNC: Interruption or EV3 device queues direct commands, | control directly comes back. | SYNC: EV3 device is blocked until direct command is finished, | control comes back, when direct command is finished. | STD: NO_REPLY like ASYNC with interruption or EV3 queuing, | REPLY like SYNC, synchronicity of program and EV3 device. | | verbosity | level of verbosity (prints on stdout).
我希望,你感觉到,我们现在正在做实际的事情。 对我来说,这是一个亮点。 只有在极少数情况下,人们才能用这么少的努力获得这么多。
以上所述就是小编给大家介绍的《EV3 直接命令 - 第 4 课 用两个驱动轮精确地移动小车》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
企业应用架构模式
Martin Fowler / 人民邮电出版社 / 2009 / 79.00元
随着信息技术的广泛应用,系统需要处理的数据量越来越大,企业级软件开发已经渐成主流,而开发人员面临的困难与挑战也是显而易见的。更糟糕的是,这一领域的资料一直非常缺乏。 本书是软件开发大师Martin Fowler的代表作,采用模式的形式系统总结了业界多年积累的经验,被称为“企业级应用开发领域的圣经”,出版以来一直畅销不衰,至今仍然无可替代。作 者在精彩地阐述了企业应用开发和设计中的核心原则基础......一起来看看 《企业应用架构模式》 这本书的介绍吧!