EV3 直接命令 - 第 2 课 让你的 EV3 做点什么
介紹
上一課我們編寫了類 EV3,它可以用于與 LEGO EV3 設備通信。我們通過什么也不做的 opNop 操作測試它。這一課是關于帶有參數的真實指令的。這將使你的 EV3 設備成為你程序的活動部分。目前,我們不從我們的 EV3 接收數據。這個主題需要等稍后的一些課程。我們選取了如下這些種類的操作:
- 設置 EV3 的名稱
- 播放聲音和音調
- 控制它的LED
- 顯示圖像
- 定時器
- 啟動程序
- 模擬按鈕動作
請拿出 LEGO EV3 操作集的官方文檔 EV3 Firmware Developer Kit,并閱讀它。LEGO 的官方文檔 EV3 Communication Developer Kit 還包含一些直接命令的例子。
如果你沒有編寫 EV3 類,但想要運行本課的程序,你可以自由地從 ev3-python3 下載模塊 ev3。程序中僅有的需要你修改的地方是 MAC 地址。把 00:16:53:42:2B:99 替換為你的 EV3 設備的值。
設置 EV3 的名字
編程藝術的一個重要部分是選擇好的名字。我們思考事物的方式強烈依賴于我們為它使用的名字。因此我們從設置名字開始。為了把你的 EV3 的名字修改為 myEV3,你需要發送如下的直接命令:
------------------------------------------------- \ len \ cnt \ty\ hd \op\cd\ Name \ ------------------------------------------------- 0x|0E:00|2A:00|00|00:00|D4|08|84:6D:79:45:56:33:00| ------------------------------------------------- \ 14 \ 42 \Re\ 0,0 \C \S \ "myEV3" \ \ \ \ \ \o \E \ \ \ \ \ \ \m \T \ \ \ \ \ \ \_ \_ \ \ \ \ \ \ \S \B \ \ \ \ \ \ \e \R \ \ \ \ \ \ \t \I \ \ \ \ \ \ \ \C \ \ \ \ \ \ \ \K \ \ \ \ \ \ \ \N \ \ \ \ \ \ \ \A \ \ \ \ \ \ \ \M \ \ \ \ \ \ \ \E \ \ -------------------------------------------------響應是:
---------------- \ len \ cnt \rs\ ---------------- 0x|03:00|2A:00|02| ---------------- \ 3 \ 42 \ok\ ----------------這說明,直接命令被成功執行了。你可以通過查看 brick 顯示器來檢查操作的結果,在它的第一行應該顯示新名稱。此外,如果某些藍牙設備搜索設備并找到了你的 EV3,它將以新的名字顯示。
幾點備注:
- 我們使用了一個新操作,它執行一些設置:opCom_Set = 0x|D4|
- 由于操作 opCom_Set 用于執行不同的設置,因而它的后面總是跟一個指定操作內容的 CMD。CMD 告訴我們,具體是哪一個。你可以把它們想成是一個兩字節操作。但在 EV3 的術語中,它是一個操作(即 指令)和它的 CMD。
- opCom_Set 的 CMD SET_BRICKNAME = 0x|08| 需要一個參數:NAME。在 LEGO 的操作描述中,你可以讀到:(DATA8) NAME – First character in character string。但事實上,我們發送 0x|84:6D:79:45:56:33:00| 作為參數 NAME 的值。這需要一些解釋:
- 0x|6D:79:45:56:33| 是字符串 myEV3 的 ASCII 碼,其中 0x|6D| = “m”,0x|79| = “y” 等等。
- 0x|00| 終止字符串(這被稱為零終止字符串)。
- 0x|84| 是 LCS 字符串的前導標識字節(以二進制表示是:0b 1000 0100)。
結論是,你發送給你的 EV3 作為操作的常量參數的每個字符串,必須包含前導 0x|84| 和后綴 0x|00|。在我的情況中,連接的結果是LCS("myEV3") = 0x|84:6D:79:45:56:33:00|,作為參數 NAME 的值。
請給你的類 EV3 添加一個靜態類方法(在 Python 中,是一個模塊級的函數):
- LCS(value: str) 以 LCS 格式返回一個表示字符串值的字節數組。
然后添加兩個常量 opCom_Set = 0x|D4| 和 SET_BRICKNAME = 0x|08|。
可以編寫一個小程序來修改名稱。我通過如下的代碼來完成:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') my_ev3.verbosity = 1 ops = b''.join([ev3.opCom_Set,ev3.SET_BRICKNAME,ev3.LCS("myEV3") ]) my_ev3.send_direct_cmd(ops)它的輸出是:
09:12:31.011558 Sent 0x|0E:00|2A:00|80|00:00|D4:08:84:6D:79:45:56:33:00|請看一下你 EV3 的顯示器,它的名字改變了。
常量整數參數
字符串是一種參數類型,但還有其它的。共同的是,參數的類型由前導字節,標識字節 標識。在這一課,我們集中于常量參數和局部變量。術語 常量參數 并不是很精確,但它意味著參數具有如下兩個特性:
- 它們是操作的參數。
- 它們總是保存值而永遠沒有地址。
EV3 直接命令支持如下格式的常量參數:
- 字符串(你已經學習了它們中的一個)
- 整數值
字符串的長度可變,整數是有符號的,且可以包含 5 位,8 位,16 位和 32 位。也許你沒有看到浮點數,但是沒有操作需要以浮點數作為參數。事實上,只有 5 種常量參數。
你應該特別專注于第一個字節,即標識字節,它定義了變量的類型和長度。標識字節的位 0 (最高有效位) 代表長或短格式:
- 0b 0... .... 短格式(只有一個字節,標識字節包含值)
- 0b 1... .... 長格式(標識字節不包含任何值的位)
位 5 (在長格式的情況下)代表長度類型
- 0b .... .0.. 意味著固定長度。
- 0b .... .1.. 意味著以零結束的字符串。
位 6 和 7 (僅長格式)代表后續的整數的長度
- 0b .... ..00 意味著可變長度,
- 0b .... ..01 意味著后面有一個字節,
- 0b .... ..10 是說,后面有兩個字節,
- 0b .... ..11 是說,后面有四個字節。
現在我們寫 5 個常量作為二進制掩碼,其中 S 代表符號(0 是正的,1 是負的),V 代表值的一位。
- LC0:0b 00SV VVVV,5 位整數值,范圍:-32 - 31,長度:1 字節,由 2 個前導位 00 標識。整數值其實是 6 位的。
- LC1:0b 1000 0001 SVVV VVVV,8 位整數值,范圍:-127 - 127,長度:2 字節,由前導字節 0x|81| 標識。值 0x|80| 是 NaN。
- LC2:0b 1000 0010 VVVV VVVV SVVV VVVV,16 位整數值,范圍:-32,767 – 32,767,長度:3 字節,由前導字節 0x|82| 標識。值 0x|80:00| 是 NaN。
- LC4:0b 1000 0011 VVVV VVVV VVVV VVVV VVVV VVVV SVVV VVVV,32 位整數值,范圍:-2,147,483,647 – 2,147,483,647,長度:5 字節,由前導字節 0x|83| 標識。值 0x|80:00:00:00| 是 NaN。
- LCS:0b 1000 0100 VVVV VVVV ... 0000 0000,以零結尾的字符串,長度:可變,由前導字節 0x|84| 標識。
LC2 和 LC4 的字節序列是小尾端的。這意味著,正如你從第 1 課學到的那樣,標識字節是頭部,后面的字節與你習慣的順序相反。如果操作以整數常量作為參數,則可以在 LC0,LC1,LC2 或 LC4 之間進行選擇。對于小值(范圍在 -32 到 31 之間),用 LC0,對于非常大的值,用 LC4。直接命令從左到右讀取。當解釋一個參數的第一個字節時,哪個附加字節屬于它以及在哪里找到該值是清楚的。總是使用最短的可能變體以消除通信流量,并因此加速直接命令的操作,但這種影響很小。更多關于參數的標識字節的細節可以在 LEGO 的 EV3 Firmware Developer Kit 的 3.4 節找到。
請給你的 EV3 類添加另外的靜態類方法(或模塊方法):
- LCX(value: int) 以格式 LC0,LC1,LC2 或 LC4 返回一個依賴于值的范圍的字節數組。
播放聲音文件
我們想要我們的 EV3 brick 播放聲音文件 /home/root/lms2012/sys/ui/DownloadSucces.rsf,這通過如下操作完成:
- opSound = 0x|94| 的 CMD PLAY = 0x|02|,且參數為:
- VOLUME:百分比 [0 - 100]
- NAME:聲音文件的絕對路徑,或相對于 /home/root/lms2012/sys/ (不包含擴展名 “.rsf”)
程序:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') my_ev3.verbosity = 1ops = b''.join([ev3.opSound,ev3.PLAY,ev3.LCX(100), # VOLUMEev3.LCS('./ui/DownloadSucces') # NAME ]) my_ev3.send_direct_cmd(ops)輸出:
09:42:03.575103 Sent 0x|1E:00|2A:00|80|00:00|94:02:81:64:84:2E:2F:75:69:2F:44:6F:77:6E:6C:6F:61:64:53:75:63:63:65:73:00|EV3 brick 的文件系統不是本節課的主題。更多信息請參考?Folder Structure。
重復播放聲音文件
操作 opSound 具有一個 CMD REPEAT,它以無限循環播放聲音文件,這可以由操作 opSound 的 CMD BREAK 中斷。有兩個額外的操作:
- opSound = 0x|94| 的 CMD REPEAT = 0x|03|,且具有參數:
- VOLUME:百分比 [0 - 100]
- NAME:聲音文件的絕對路徑,或相對于 /home/root/lms2012/sys/ 的相對路徑 (不包含擴展名 “.rsf”)
- opSound = 0x|94| 的 CMD BREAK = 0x|00|,沒有參數。
我們用如下的程序來測試它:
#!/usr/bin/env python3import ev3, timemy_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') my_ev3.verbosity = 1ops = b''.join([ev3.opSound,ev3.REPEAT,ev3.LCX(100), # VOLUMEev3.LCS('./ui/DownloadSucces') # NAME ]) my_ev3.send_direct_cmd(ops) time.sleep(5) ops = b''.join([ev3.opSound,ev3.BREAK ]) my_ev3.send_direct_cmd(ops)它播放聲音文件 5 秒,然后停止播放。輸出為:
09:55:28.814320 Sent 0x|1E:00|2A:00|80|00:00|94:03:81:64:84:2E:2F:75:69:2F:44:6F:77:6E:6C:6F:61:64:53:75:63:63:65:73:00| 09:55:33.822352 Sent 0x|07:00|2B:00|80|00:00|94:00|播放音調
我們想要我們的 EV3 brick 播放音調,這通過如下操作完成:
- opSound = 0x|94| 的 CMD TONE = 0x|01|,且參數為:
- VOLUME:百分比 [0 - 100]
- FREQUENCY:單位為 Hz,[250 - 10000]
- DURATION:單位為毫秒(0 表示無限制)
播放一個 a’ 一秒的直接命令:
------------------------------------------------- \ len \ cnt \ty\ hd \op\cd\vo\ fr \ du \ ------------------------------------------------- 0x|0E:00|2A:00|80|00:00|94|01|01|82:B8:01|82:E8:03| ------------------------------------------------- \ 14 \ 42 \no\ 0,0 \S \T \1 \ 440 \ 1000 \ \ \ \ \ \o \O \ \ \ \ \ \ \ \ \u \N \ \ \ \ \ \ \ \ \n \E \ \ \ \ \ \ \ \ \d \ \ \ \ \ -------------------------------------------------程序發送它:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') ops = b''.join([ev3.opSound,ev3.TONE,ev3.LCX(1), # VOLUMEev3.LCX(440), # FREQUENCYev3.LCX(1000), # DURATION ]) my_ev3.send_direct_cmd(ops)盡管我們很自豪,但我們希望我們的 EV3 以 c’ 播放三和弦:
- c’ (262 Hz)
- e’ (330 Hz)
- g’ (392 Hz)
- c’’ (523 Hz)
我們把我們的程序修改為:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') ops = b''.join([ev3.opSound,ev3.TONE,ev3.LCX(1),ev3.LCX(262),ev3.LCX(500),ev3.opSound,ev3.TONE,ev3.LCX(1),ev3.LCX(330),ev3.LCX(500),ev3.opSound,ev3.TONE,ev3.LCX(1),ev3.LCX(392),ev3.LCX(500),ev3.opSound,ev3.TONE,ev3.LCX(2),ev3.LCX(523),ev3.LCX(1000) ]) my_ev3.send_direct_cmd(ops)但我們只聽到了一個音調,即最后的那個 (c’’)。為什么?
這是因為操作彼此中斷。你必須將操作視為不耐煩且表現不佳的角色。中斷是他們的標準。如果想要避免中斷,則必須明確告訴它。在播放聲音的場景中,可以通過如下操作完成:
- opSound_Ready = 0x|96|
它將一直等到聲音結束。我們再次修改程序:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') ops = b''.join([opSound,cmdSound_PlayTone,LCX(1),LCX(262),LCX(500),opSound_Ready,opSound,cmdSound_PlayTone,LCX(1),LCX(330),LCX(500),opSound_Ready,opSound,cmdSound_PlayTone,LCX(1),LCX(392),LCX(500),opSound_Ready,opSound,cmdSound_PlayTone,LCX(2),LCX(523),LCX(1000), ]) my_ev3.send_direct_cmd(ops)現在我們聽到了我們期望的!
修改 LEDs 的顏色
我們的 EV3 永遠不會達到真正的自動點唱機的質量,但為什么不添加一些燈光效果?這需要一個新操作:
- opUI_Write = 0x|82| 的 CMD LED = 0x|1B|,且參數為:
- PATTERN:GREEN = 0x|01|,RED = 0x|02|,等等。
LED Patterns 可以取的值如下:
- 0x00 : Led off
- 0x01 : Led green
- 0x02 : Led red
- 0x03 : Led orange
- 0x04 : Led green flashing
- 0x05 : Led red flashing
- 0x06 : Led orange flashing
- 0x07 : Led green pulse
- 0x08 : Led red pulse
- 0x09 : Led orange pulse
我們再次給我們的程序添加一些代碼:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') my_ev3.verbosity = 1 ops = b''.join([ev3.opUI_Write,ev3.LED,ev3.LED_RED,ev3.opSound,ev3.TONE,ev3.LCX(1),ev3.LCX(262),ev3.LCX(500),ev3.opSound_Ready,ev3.opUI_Write,ev3.LED,ev3.LED_GREEN,ev3.opSound,ev3.TONE,ev3.LCX(1),ev3.LCX(330),ev3.LCX(500),ev3.opSound_Ready,ev3.opUI_Write,ev3.LED,ev3.LED_RED,ev3.opSound,ev3.TONE,ev3.LCX(1),ev3.LCX(392),ev3.LCX(500),ev3.opSound_Ready,ev3.opUI_Write,ev3.LED,ev3.LED_RED_FLASH,ev3.opSound,ev3.TONE,ev3.LCX(2),ev3.LCX(523),ev3.LCX(2000),ev3.opSound_Ready,ev3.opUI_Write,ev3.LED,ev3.LED_GREEN ]) my_ev3.send_direct_cmd(ops)我們發送的是一個 60 字節長的直接命令:
11:39:49.039902 Sent 0x|3C:00|2A:00|80|00:00|82:1B:02:94:01:01:82:06:01:82:F4:01:96:82:1B:01:94:01:01:82:4A:01:82:F4:01:96:82:1B:02:...這小于它最大長度的 6%。
顯示圖像
EV3 的顯示屏是單色的,分辨率為 180 x 128 像素。這聽起來有點過時,但允許顯示圖標和表情符號或繪制圖片。操作 opUI_Draw 具有大量不同的操作顯示器的 CMD。這里我們使用其中四個:
- opUI_Draw = 0x|84| 的 CMD UPDATE = 0x|00|,沒有參數。
- opUI_Draw = 0x|84| 的 CMD TOPLINE = 0x|12|,具有參數:
- (Data8) ENABLE:啟用或禁用頂部狀態行, [0:禁用,1:啟用]
- opUI_Draw = 0x|84| 的 CMD FILLWINDOW = 0x|13|,具有參數:
- (Data8) COLOR:指定黑色或白色,[0:白色,1:黑色]
- (Data16) Y0:指定 Y 起始點,[0 - 127]
- (Data16) Y1:指定 Y 大小
- opUI_Draw = 0x|84| 的 CMD BMPFILE = 0x|1C|,具有參數:
- (Data8) COLOR:指定黑色或白色,[0:白色,1:黑色]
- (Data16) X0:指定 X 起始點,[0 - 127]
- (Data16) Y0:指定 Y 起始點,[0 - 127]
- NAME:圖像文件的絕對路徑,或相對于 /home/root/lms2012/sys/(具有擴展名 “.rgf”)。該命令的名稱具有誤導性。該文件的擴展名必須是 .rgf(代表 機器人圖形格式(robot graphic format))而不是 bmp 圖形的文件。
我們運行這個程序:
#!/usr/bin/env python3import ev3, timemy_ev3 = ev3.EV3(protocol=ev3.USB,host='00:16:53:42:2B:99' ) my_ev3.verbosity = 1ops = b''.join([ev3.opUI_Draw,ev3.TOPLINE,ev3.LCX(0), # ENABLEev3.opUI_Draw,ev3.BMPFILE,ev3.LCX(1), # COLORev3.LCX(0), # X0ev3.LCX(0), # Y0ev3.LCS("../apps/Motor Control/MotorCtlAD.rgf"), # NAMEev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops) time.sleep(5) ops = b''.join([ev3.opUI_Draw,ev3.TOPLINE,ev3.LCX(1), # ENABLEev3.opUI_Draw,ev3.FILLWINDOW,ev3.LCX(0), # COLORev3.LCX(0), # Y0ev3.LCX(0), # Y1ev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops)輸出為:
12:01:00.253855 Sent 0x|35:00|2A:00|80|00:00|84:12:00:84:1C:01:00:00:84:2E:2E:2F:61:70:70:... 12:01:05.265584 Sent 0x|0F:00|2B:00|80|00:00|84:12:01:84:13:00:00:00:84:00|顯示屏顯示圖像 MotorCtlAD.rgf 五秒鐘,然后顯示屏變為空,除了頂線。 一些注釋:
- 繪制需要畫布。這是顯示的實際圖像。我們添加一些元素,然后調用 UPDATE 使畫布可見。如果你更喜歡以空白畫布開始,則必須明確清除畫布的內容。
- CMD TOPLINE 允許打開或關閉頂線。
- CMD FILLWINDOW 允許填充或擦除窗口的一部分。如果參數 Y0 和 Y1 都為零,則表示整個顯示屏。
- 將 CMD BMPFILE 的參數 COLOR 設置為值 0 會反轉圖像的顏色。
- 操作 opUI_Draw 允許存儲和恢復圖像(CMD STORE 和 RESTORE)。 但是當實際的直接命令執行結束時,存儲的圖像將丟失。
歡迎你測試更多操作 opUI_Draw 的 CMD。
局部內存
在第 1 課我們讀到,本地內存是保存中間信息的地址空間。現在我們學習如何使用它,我們再次討論標識字節,它定義變量的類型和長度。我們將編寫另一個函數 LVX,它返回本地內存的地址。如你所知,標識字節的第 0 位代表短格式或長格式:
- 0b 0... .... 短格式(只有一個字節,標識字節包含值)
- 0b 1... .... 長格式(標識字節不包含任何值的位)
如果位 1 和 2 是 0b .10. ....,,它們代表局部變量,它們是局部內存的地址。
位 6 和 7 代表后續的值的長度
- 0b .... ..00 意味著可變長度,
- 0b .... ..01 意味著后面有一個字節,
- 0b .... ..10 是說,后面有兩個字節,
- 0b .... ..11 是說,后面有四個字節。
這允許將 4 個局部變量寫為二進制掩碼,我們不需要符號,因為地址總是正數。 V 代表地址(值)的一位。
- LV0: 0b 010V VVVV,5 位地址,范圍:0 - 31,長度:1 字節,由 3 個前導位 010 標識。
- LV1:0b 1100 0001 VVVV VVVV,8 位地址,范圍:0 - 255,長度:2 字節,由前導字節 0x|C1| 標識。
- LV2:0b 1100 0010 VVVV VVVV VVVV VVVV,16 位地址,范圍:0 – 65, 535,長度:3 字節,由前導字節 0x|C2| 標識。
- LV4:0b 1100 0011 VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV,32 位地址,范圍:0 – 4,294,967,296,長度:5 字節,由前導字節 0x|C3| 標識。
一些說明:
- 在直接命令中,不需要 LV2 和 LV4!你記得局部內存最多有 63 個字節。
- 必須正確放置局部內存的地址。 如果將 4 字節值寫入局部內存,則其地址必須為0,4,8,…(4的倍數)。對于 2 字節值也是一樣,它們的地址必須是 2 的倍數。
- 你需要將局部內存拆分為所需長度的段,然后使用每個段的第一個字節的地址。
- 頭字節包含局部內存的總長度(有關詳細內容,請參閱第 1 課)。 不要忘記正確發送頭字節!
一個新的模塊函數:LVX
請將函數 LVX(value) 添加到模塊 ev3 中,它取決于實際值,返回 LV0,LV1,LV2 或 LV4 類型中最短的。 我已經完成了,現在我的 ev3 模塊的文檔如下:
FUNCTIONSLCS(value:str) -> bytespack a string into a LCSLCX(value:int) -> bytescreate a LC0, LC1, LC2, LC4, dependent from the valueLVX(value:int) -> bytescreate a LV0, LV1, LV2, LV4, dependent from the value計時器
控制時間是實時程序的一個重要方面。我們已經看到如何等待音調結束,我們在本地程序中等待,直到我們停止重復播放的聲音文件。EV3 的操作集包含計時器操作,它們允許在直接命令的執行中等待。我們使用以下兩個操作:
-
opTimer_Wait = 0x|85|,具有參數:
- (Data32) TIME:等待的時間(單位為毫秒)
- (Data32) TIMER:用于計時的變量
這個操作向局部或全局內存中寫入 4 個字節的時間戳
-
opTimer_Ready = 0x|86|,具有參數:
- (Data32) TIMER:用于計時的變量
這個操作讀取時間戳并等待直到實際時間到達這個時間戳的值。
- (Data32) TIMER:用于計時的變量
我們用一個繪制三角形的程序測試計時器操作。這需要操作 opUI_Draw 的另一個 CMD:
- opUI_Draw = 0x|84| 的 CMD LINE = 0x|03|,具有參數:
- (Data8) COLOR:指定黑色或白色,[0:白色,1:黑色]
- (Data16) X0:指定 X 起始點,[0 - 177]
- (Data16) Y0:指定 Y 起始點,[0 - 127]
- (Data16) X1:指定 X 結束點
- (Data16) Y1:指定 Y 結束點
程序:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') ops = b''.join([ev3.opUI_Draw,ev3.TOPLINE,ev3.LCX(0), # ENABLEev3.opUI_Draw,ev3.FILLWINDOW,ev3.LCX(0), # COLORev3.LCX(0), # Y0ev3.LCX(0), # Y1ev3.opUI_Draw,ev3.UPDATE,ev3.opTimer_Wait,ev3.LCX(1000),ev3.LVX(0),ev3.opTimer_Ready,ev3.LVX(0),ev3.opUI_Draw,ev3.LINE,ev3.LCX(1), # COLORev3.LCX(2), # X0ev3.LCX(125), # Y0ev3.LCX(88), # X1ev3.LCX(2), # Y1ev3.opUI_Draw,ev3.UPDATE,ev3.opTimer_Wait,ev3.LCX(500),ev3.LVX(0),ev3.opTimer_Ready,ev3.LVX(0),ev3.opUI_Draw,ev3.LINE,ev3.LCX(1), # COLORev3.LCX(88), # X0ev3.LCX(2), # Y0ev3.LCX(175), # X1ev3.LCX(125), # Y1ev3.opUI_Draw,ev3.UPDATE,ev3.opTimer_Wait,ev3.LCX(500),ev3.LVX(0),ev3.opTimer_Ready,ev3.LVX(0),ev3.opUI_Draw,ev3.LINE,ev3.LCX(1), # COLORev3.LCX(175), # X0ev3.LCX(125), # Y0ev3.LCX(2), # X1ev3.LCX(125), # Y1ev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops, local_mem=4)這個程序清除顯示屏,然后等待一秒,繪制一條線,等待半秒,繪制第二條線,等待并最終繪制第三條線。它需要 4 個字節的本地內存,可以多次寫入和讀出。
顯然,計時可以在本地程序或直接命令中完成。 我們修改程序:
#!/usr/bin/env python3import ev3, timemy_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') ops = b''.join([ev3.opUI_Draw,ev3.TOPLINE,ev3.LCX(0), # ENABLEev3.opUI_Draw,ev3.FILLWINDOW,ev3.LCX(0), # COLORev3.LCX(0), # Y0ev3.LCX(0), # Y1ev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops) time.sleep(1) ops = b''.join([ev3.opUI_Draw,ev3.LINE,ev3.LCX(1), # COLORev3.LCX(2), # X0ev3.LCX(125), # Y0ev3.LCX(88), # X1ev3.LCX(2), # Y1ev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops) time.sleep(0.5) ops = b''.join([ev3.opUI_Draw,ev3.LINE,ev3.LCX(1), # COLORev3.LCX(88), # X0ev3.LCX(2), # Y0ev3.LCX(175), # X1ev3.LCX(125), # Y1ev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops) time.sleep(0.5) ops = b''.join([ev3.opUI_Draw,ev3.LINE,ev3.LCX(1), # COLORev3.LCX(175), # X0ev3.LCX(125), # Y0ev3.LCX(2), # X1ev3.LCX(125), # Y1ev3.opUI_Draw,ev3.UPDATE ]) my_ev3.send_direct_cmd(ops)兩種方案下,顯示器具有相同的行為,但又有些不同。 第一個版本所需的通信量較少,但它會阻塞 EV3,直到直接命令執行結束。第二個版本需要四個直接命令,但允許在繪圖休眠時發送其它直接命令。
啟動程序
直接命令可以啟動程序。通常你通過按下 EV3 設備的按鈕完成。程序是一個擴展名為 “.rbf” 的文件,它存放在 EV3 的文件系統上。我們將啟動程序 /home/root/lms2012/apps/Motor Control/Motor Control.rbf。這需要兩個新操作:
-
opFile = 0x|C0| 的 CMD LOAD_IMAGE = 0x|08|,具有參數:
- (Data16) PRGID:程序運行的 Slot。值 0x|01| 用于執行用戶工程,apps 和工具。
- (Data8) NAME:可執行文件的完整路徑,或相對于 /home/root/lms2012/sys/(具有擴展名 “.rbf”)的路徑
返回: - (Data32) SIZE:鏡像的字節大小
- (Data32) *IP:鏡像的地址
這個操作是?加載器。它把程序加載進內存中,并準備執行。
-
opProgram_Start = 0x|C0|,具有參數:
- (Data16) PRGID:程序運行的 Slot。
- (Data32) SIZE:鏡像的字節大小
- (Data32) *IP:鏡像的地址
- (Data8) DEBUG:調試模式,值 0 代表普通模式
程序:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.USB, host='00:16:53:42:2B:99') my_ev3.verbosity = 1ops = b''.join([ev3.opFile,ev3.LOAD_IMAGE,ev3.LCX(1), # SLOTev3.LCS('../apps/Motor Control/Motor Control.rbf'), # NAMEev3.LVX(0), # SIZEev3.LVX(4), # IP*ev3.opProgram_Start,ev3.LCX(1), # SLOTev3.LVX(0), # SIZEev3.LVX(4), # IP*ev3.LCX(0) # DEBUG ]) my_ev3.send_direct_cmd(ops, local_mem=8)第一個操作的返回值是 SIZE 和 IP*。我們把它們寫入局部內存的地址 0 和 4。第二個操作從局部內存讀取它的參數 SIZE 和 IP*。它的參數 SLOT 和 DEBUG 是給定的常量值。程序的輸出是:
12:50:45.332826 Sent 0x|38:00|2A:00|80|00:20|C0:08:01:84:2E:2E:2F:61:70:70:73:2F:4D:6F:74:6F:...它真的啟動了程序 /home/root/lms2012/apps/Motor Control/Motor Control.rbf。
譯者注:
- 關于操作命令的參數和返回值。要傳遞參數時,將參數值直接附加到操作命令的后面,并進行適當的編碼即可。對于操作命令的返回值,EV3 的直接命令虛擬機的處理方式,非常類似與傳出參數。即需要針對每一個返回值,傳入一個指針,告訴操作命令把返回值放在指針所指向的位置。EV3 中有兩種類型的指針,分別是全局內存指針和局部內存指針。兩者的主要差異在于,全局內存中的數據,在命令執行之后,我們的程序可以全部通過讀操作,從 EV3 設備中讀取出來,而局部內存則不會。全局內存和局部內存的分配,則是通過直接命令的頭部,告訴 EV3 中的直接命令虛擬機各為它們分配多少字節。保存返回值內容的內存的指針的具體值,需要開發者自己根據傳出參數的類型長度進行手動計算。因此,EV3 的直接命令的操作命令,沒有我們寫代碼時一般意義上的那種函數或方法,或者可以說,EV3 的操作命令的原型都是下面這樣的:
如上面,這里的小程序,指定通過局部內存 LVX(0) / LVX(4) 接收操作命令 opFile/LOAD_IMAGE 的返回值。上面那段程序,也可以寫為通過全局內存來接收操作命令 opFile/LOAD_IMAGE 的返回值,如:
def start_program(self, exe_file_path: str):ops = b''.join([opFile,cmdFile_LoadImage,PRGID_USER,LCS(exe_file_path),GVX(0),GVX(4)])ret = self.send_direct_cmd(ops=ops, global_mem=8)ops = b''.join([opProgram_Start,PRGID_USER,GVX(0),GVX(4),debugMode_Normal])ret = self.send_direct_cmd(ops = ops)上面這段代碼使用全局內存接收操作命令 opFile/LOAD_IMAGE 的返回值。第一個 send_direct_cmd() 調用的返回值 ret 中包含了操作命令 opFile/LOAD_IMAGE 的返回值。但注意操作 opProgram_Start 的說明。直接把操作命令 opFile/LOAD_IMAGE 的返回值傳給 opProgram_Start 是不行的,如下面這樣:
def start_program(self, exe_file_path: str):ops = b''.join([opFile,cmdFile_LoadImage,PRGID_USER,LCS(exe_file_path),GVX(0),GVX(4)])ret = self.send_direct_cmd(ops=ops, global_mem=8)ops = b''.join([opProgram_Start,PRGID_USER,ret[5:],debugMode_Normal])ret = self.send_direct_cmd(ops = ops)以這種直接傳值的方式,程序無法如預期執行。
模擬按鈕按下
在這個例子中,我們通過模擬如下的按鈕按下事件關閉 EV3 brick:
- BACK_BUTTON = 0x|06|
- RIGHT_BUTTON = 0x|04|
- ENTER_BUTTON = 0x|02|
我們需要等待直到初始化操作完成。這可以通過操作 opUI_Button 的 CMD WAIT_FOR_PRESS 完成,這再次預防了中斷。使用下面的新操作:
- opUI_Button = 0x|83| 的 CMD PRESS = 0x|05|,具有參數:
- BUTTON:Up Button = 0x|01|,Enter Button = 0x|02|,等等。
- opUI_Button = 0x|83| 的 CMD WAIT_FOR_PRESS = 0x|03|
直接命令具有如下的結構:
------------------------------------------------------------- \ len \ cnt \ty\ hd \op\cd\bu\op\cd\op\cd\bu\op\cd\op\cd\bu\ ------------------------------------------------------------- 0x|12:00|2A:00|80|00:00|83|05|06|83|03|83|05|04|83|03|83|05|02| ------------------------------------------------------------- \ 18 \ 42 \no\ 0,0 \U \P \B \U \W \U \P \R \U \W \U \P \E \ \ \ \ \ \I \R \A \I \A \I \R \I \I \A \I \R \N \ \ \ \ \ \_ \E \C \_ \I \_ \E \G \_ \I \_ \E \T \ \ \ \ \ \B \S \K \B \T \B \S \H \B \T \B \S \E \ \ \ \ \ \U \S \_ \U \_ \U \S \T \U \_ \U \S \R \ \ \ \ \ \T \ \B \T \F \T \ \_ \T \F \T \ \_ \ \ \ \ \ \T \ \U \T \O \T \ \B \T \O \T \ \B \ \ \ \ \ \O \ \T \O \R \O \ \U \O \R \O \ \U \ \ \ \ \ \N \ \T \N \_ \N \ \T \N \_ \N \ \T \ \ \ \ \ \ \ \O \ \P \ \ \T \ \P \ \ \T \ \ \ \ \ \ \ \N \ \R \ \ \O \ \R \ \ \O \ \ \ \ \ \ \ \ \ \E \ \ \N \ \E \ \ \N \ \ \ \ \ \ \ \ \ \S \ \ \ \ \S \ \ \ \ \ \ \ \ \ \ \ \ \S \ \ \ \ \S \ \ \ \ -------------------------------------------------------------我的對應的程序是:
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') ops = b''.join([ev3.opUI_Button,ev3.PRESS,ev3.BACK_BUTTON,ev3.opUI_Button,ev3.WAIT_FOR_PRESS,ev3.opUI_Button,ev3.PRESS,ev3.RIGHT_BUTTON,ev3.opUI_Button,ev3.WAIT_FOR_PRESS,ev3.opUI_Button,ev3.PRESS,ev3.ENTER_BUTTON ]) my_ev3.send_direct_cmd(ops)這真的關閉了EV3設備!
沒有必要回復,但我是一個好奇的人。我的問題是:EV3會在它關閉之前回復還是不回復?
#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') my_ev3.verbosity = 1 my_ev3.sync_mode = ev3.SYNC ops = b''.join([ev3.opUI_Button,ev3.PRESS,ev3.BACK_BUTTON,ev3.opUI_Button,ev3.WAIT_FOR_PRESS,ev3.opUI_Button,ev3.PRESS,ev3.RIGHT_BUTTON,ev3.opUI_Button,ev3.WAIT_FOR_PRESS,ev3.opUI_Button,ev3.PRESS,ev3.ENTER_BUTTON ]) my_ev3.send_direct_cmd(ops)在我按下另一個按鈕之前沒有任何反應,然后它回復并關閉。這并不令人驚訝,這是不一致的。 關機和回復不合適一起,EV3 設備無法完成命令然后發送回復!
我們學到了什么
- 直接命令由操作序列組成。當我們給 brick 發送直接命令時,一個操作接一個操作的執行。但它們彼此互相打斷,如果想要它們等待的話需要特殊的操作。
- 大多數操作需要參數,它們可以以格式 LC0,LC1,LC2 和 LC4 發送,這些都包含有符號整數,但具有不同的范圍。另一種格式是 LCS,用于字符串。它以0x|84| 開頭,然后是零終止的 ASCII 碼串。
- 局部變量(LV0,LV1,LV2 和 LV4)允許尋址保存中間數據的局部存儲器。
- 一些操作具有許多 CMD,它們使用不同的參數集定義不同的任務。
- 我們已經看過很多操作并知道他們的參數的含義,但這只是 EV3 操作集的一小部分。
結論
我們關于直接命令的知識增長了,我們的類 EV3 也是。添加我們需要的所有常量需要一些耐心。隨著操作數量的增加,直接命令的參考文檔 EV3 Firmware Developer Kit 需要更加仔細地閱讀。
這是我的函數和數據的實際狀態:
Help on module ev3:NAMEev3 - LEGO EV3 direct commandsCLASSESbuiltins.objectEV3class EV3(builtins.object)...FUNCTIONSLCS(value:str) -> bytespack a string into a LCSLCX(value:int) -> bytescreate a LC0, LC1, LC2, LC4, dependent from the valueLVX(value:int) -> bytescreate a LV0, LV1, LV2, LV4, dependent from the valueDATAASYNC = 'ASYNC'BACK_BUTTON = b'\x06'BLUETOOTH = 'Bluetooth'BMPFILE = b'\x1c'BREAK = b'\x00'ENTER_BUTTON = b'\x02'FILLWINDOW = b'\x13'LED = b'\x1b'LED_OFF = b'\x00'LED_GREEN = b'\x01'LED_GREEN_FLASH = b'\x04'LED_GREEN_PULSE = b'\x07'LED_ORANGE = b'\x03'LED_ORANGE_FLASH = b'\x06'LED_ORANGE_PULSE = b'\t'LED_RED = b'\x02'LED_RED_FLASH = b'\x05'LED_RED_PULSE = b'\x08'LINE = b'\x03'LOAD_IMAGE = b'\x08'PLAY = b'\x02'PRESS = b'\x53'REPEAT = b'\x02'RIGHT_BUTTON = b'\x04'SET_BRICKNAME = b'\x08'STD = 'STD'SYNC = 'SYNC'TONE = b'\x01'TOPLINE = b'\x12'USB = 'Usb'UPDATE = b'\x00'WAIT_FOR_PRESS = b'\x03'WIFI = 'Wifi'opCom_Set = b'\xd4'opFile = b'\xc0'opNop = b'\x01'opProgram_Start = b'\x03'opSound = b'\x94'opSound_Ready = b'\x96'opTimer_Wait = b'\x85'opTimer_Ready = b'\x86'opUI_Button = b'\x83'opUI_Draw = b'\x84'opUI_Write = b'\x82'真正的機器人從其傳感器讀取數據并通過其電機進行運動。目前我們的 EV3 設備都沒有。我也知道,存在更酷的聲音或光效的電子設備。現在,你可以測試在 EV3 Firmware Developer Kit 中找到的其他一些操作了。
保持聯系,下一課將是關于電機的。我希望,我們將更接近你真正感興趣的話題。
原文
總結
以上是生活随笔為你收集整理的EV3 直接命令 - 第 2 课 让你的 EV3 做点什么的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EV3 直接命令 - 第一课 无为的艺术
- 下一篇: EV3 直接命令 - 第 4 课 用两个