EV3 直接命令 - 第 4 课 用两个驱动轮精确地移动小车
簡介
上一課,我們編寫了 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 = speedspeed_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 = speedspeed_left = speed * (1 - turn / 100)else:speed_right = speed * (1 + turn / 100)speed_left = speed速度和步數是成比例的,我們也可以寫為:
if turn > 0:step_right = math.copysign(1, speed) * stepstep_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 *= -1if self._port_left < self._port_right:turn *= -1ops = b''.join([ev3.opOutput_Step_Sync,ev3.LCX(0), # LAYERev3.LCX(self._port_left + self._port_right), # NOSev3.LCX(speed),ev3.LCX(turn),ev3.LCX(0), # STEPSev3.LCX(0), # BRAKEev3.opOutput_Start,ev3.LCX(0), # LAYERev3.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 的運動類型:
- 兩個電機具有相同的極性,
- 右輪連接到更低的端口,左邊的更高。
| 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_wheelOK,第一個參數計算出來了,但是我們仍然需要計算 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):
在 Python 中,這可以寫作:
rad_outer = radius_turn + 0.5 * self._treadrad_inner = radius_turn - 0.5 * self._treadstep = round(angle*rad_outer / self._radius_wheel)turn = round(100*(1 - rad_inner / rad_outer))if angle < 0:step *= -1turn *= -1if 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_wheelself._tread = treadself._polarity = 1self._port_left = ev3.PORT_Dself._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 *= -1if self._port_left < self._port_right:turn *= -1ops_ready = b''.join([ev3.opOutput_Ready,ev3.LCX(0), # LAYERev3.LCX(self._port_left + self._port_right) # NOS])ops_start = b''.join([ev3.opOutput_Step_Sync,ev3.LCX(0), # LAYERev3.LCX(self._port_left + self._port_right), # NOSev3.LCX(speed),ev3.LCX(turn),ev3.LCX(step),ev3.LCX(0), # BRAKEev3.opOutput_Start,ev3.LCX(0), # LAYERev3.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._treadrad_left = radius_turn - 0.5 * self._treadif 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 中:
- 構造函數:
- drive_straight(speed, distance)
- drive_turn(speed, radius_turn, angle)
完成類 TwoWheelVehicle
我們通過添加更多的功能來完成類 TwoWheelVehicle:
-
我們添加一個方法 rotate_to(speed: int, o: float),它做如下的事情:
- 計算實際的和新的方向的距離
- 以 radius_turn = 0 調用 drive_turn 旋轉小車,以使其獲得新方向。
-
我們添加一個方法 drive_to(self, speed: int, x: float, y: float),它做如下的事情:
- 計算實際的位置和新位置的距離:
我們需要坐標和絕對值:
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_ysign = math.copysign(1.0, fract)direction = sign * 90 - math.degrees(math.atan(fract))if diff_x < 0:direction += 180direction %= 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 python3import ev3, ev3_vehiclemy_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 python3import ev3, ev3_vehiclemy_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:NAMEev3_vehicle - EV3 vehicleCLASSESev3.EV3(builtins.object)TwoWheelVehicleclass 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 课 用两个驱动轮精确地移动小车的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EV3 直接命令 - 第 2 课 让你的
- 下一篇: EV3 直接命令 - 第 5 课 从 E