[教學] 以藍牙連接Raspberry Pi 3與LEGO Mindstorms EV3
安裝藍牙相關軟體
sudo apt-get install bluetooth bluez blueman
連接藍牙
因為我是透過SSH連接Raspberry Pi,便使用下面指令進入藍牙設定,如果有把Raspberry Pi用HDMI連接螢幕,應該可以透過GUI連接EV3。
sudo bluetoothctl
完成後會看到以 [bluetooth]#
起頭的狀態,此時便能輸入指令。
agent on
default-agent
scan on
接著打開EV3的藍牙,記得勾選 Visibility
。
回到Raspberry Pi,搜尋到EV3後,以指令 pair
做配對,這邊可以用 Tab
將MAC address自動補全,這組MAC address稍後也會用到。
pair AA:BB:CC:DD:EE:FF
EV3會出現配對設定畫面,點確認
用預設的PIN 1234即可
在Raspberry Pi輸入1234完成配對。
輸入 exit
離開藍牙設定,下圖是操作的截圖可供參考。
最後一步,利用 rfcomm
指令作 bind
,以利Python可用seiral package操作藍牙介面。
sudo rfcomm bind 0 AA:BB:CC:DD:EE:FF
EV3 藍牙封包
在進行測試之前,先了解EV3傳輸資料的格式,有助於之後編寫Raspberry Pi傳輸的程式。樂高官方提供的文件有講述藍牙的封包格式,這裡摘要需要的部分。
Byte # | 意義 | 設定值 |
---|---|---|
0 ~ 1 | 指令大小 Little Endian |
除去這 2 bytes 後剩餘資料總數 |
2 ~ 3 | 訊息計數器 Little Endian |
說明看起來是個正向的計數器,但實際上EV3完全沒用到 |
4 | 指令型態 | EV3是設定0x81,不須等待接收端回覆 |
5 | 系統指令 | EV3提供多種系統指令,這裡設定0x9E (WRITEMAILBOX) |
6 ~ n | 資料 | 根據系統指令的設定決定 |
下面直接介紹WRITEMAILBOX的格式,其他有興趣就去參考文件吧。
Byte # | 意義 | 設定值 |
---|---|---|
6 | 標題大小 | title字數,文件講不包含最後0x00 termination bit,但實際測試有算 |
7 ~ k-1 | 標題 | title,EV3藍牙相關block的參數 |
k ~ k+1 | 訊息大小 Little Endian |
訊息字數 |
k+2 ~ n | 訊息 | 傳遞的訊息,三種型態:字串、數字、邏輯 |
Raspberry Pi接收測試
EV3的藍牙傳輸方塊有三種型態可供選擇:字串、數字、邏輯,依序測試不同資料型態會接收到什麼。
Python 程式碼
程式碼就不多做解釋,這支程式會把接收到的藍牙資料以16進位全部輸出在螢幕上。
#!/usr/bin/python3
import serial
import time
EV3 = serial.Serial('/dev/rfcomm0')
print("Listening for EV3 Bluetooth messages, press CTRL+C to quit.")
try:
while 1:
n = EV3.inWaiting()
if n != 0:
s = EV3.read(n)
print("".join("{:02x} ".format(c) for c in s))
time.sleep(0.2)
except KeyboardInterrupt:
pass
EV3.close()
print("\nRX End")
EV3傳輸程式
EV3程式如下圖,傳遞藍牙訊息標題為 abc
,依序會測試字串、數字、邏輯
字串
在EV3程式中,傳遞訊息為 TEST
。下面是Raspberry Pi接收到的結果。
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
10 | 00 | 01 | 00 | 81 | 9e | 04 | 61 | 62 | 63 | 00 | 05 | 00 | 54 | 45 | 53 | 54 | 00 |
Byte # | 說明 |
---|---|
0 ~ 1 | Byte 02到Byte 17,總計16 bytes |
2 ~ 3 | 訊息計數器,EV3傳遞過來總是01 |
4 | 0x81,不須等待接收端回覆 |
5 | 0x9E,WRITEMAILBOX |
6 | 標題大小, abc 外加 0x00 的zero termination共4 bytes |
7 ~ 10 | 標題, a b c 0x00 |
11 ~ 12 | 訊息大小, TEST 外加 0x00 的zero termination共5 bytes |
13 ~ 17 | 訊息, T E S T 0x00 |
可以觀察到文字都是以ASCII code表示。
數字
在EV3程式中,依序傳遞 0
、 1
、 2
。下面是Raspberry Pi接收到的結果。
數字 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0f | 00 | 01 | 00 | 81 | 9e | 04 | 61 | 62 | 63 | 00 | 04 | 00 | 00 | 00 | 00 | 00 |
1 | 0f | 00 | 01 | 00 | 81 | 9e | 04 | 61 | 62 | 63 | 00 | 04 | 00 | 00 | 00 | 80 | 3f |
2 | 0f | 00 | 01 | 00 | 81 | 9e | 04 | 61 | 62 | 63 | 00 | 04 | 00 | 00 | 00 | 00 | 40 |
Byte # | 說明 |
---|---|
0 ~ 1 | Byte 02到Byte 17,總計16 bytes |
2 ~ 3 | 訊息計數器,EV3傳遞過來總是01 |
4 | 0x81,不須等待接收端回覆 |
5 | 0x9E,WRITEMAILBOX |
6 | 標題大小, abc 外加 0x00 的zero termination共4 bytes |
7 ~ 10 | 標題, a b c 0x00 |
11 ~ 12 | 訊息大小,4 bytes |
13 ~ 16 | 收到的數字 |
這邊數字實際上是float type,有興趣可以按按看轉換機。
邏輯
在EV3程式中,依序傳遞 True
、 False
。下面是Raspberry Pi接收到的結果。
數字 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
True | 0c | 00 | 01 | 00 | 81 | 9e | 04 | 61 | 62 | 63 | 00 | 01 | 00 | 01 |
False | 0c | 00 | 01 | 00 | 81 | 9e | 04 | 61 | 62 | 63 | 00 | 01 | 00 | 00 |
Byte # | 說明 |
---|---|
0 ~ 1 | Byte 02到Byte 17,總計16 bytes |
2 ~ 3 | 訊息計數器,EV3傳遞過來總是01 |
4 | 0x81,不須等待接收端回覆 |
5 | 0x9E,WRITEMAILBOX |
6 | 標題大小, abc 外加 0x00 的zero termination共4 bytes |
7 ~ 10 | 標題, a b c 0x00 |
11 ~ 12 | 訊息大小,1 byte |
13 | 邏輯值, True 為 1 , False 為 0 |
Raspberry Pi傳送測試
有了上一章節的基礎,我們可以知道該怎麼轉換資料讓EV3能看懂,這章節實作以Raspberry Pi傳送資料的測試。
EV3接收程式
這裡我只傳送字串和數字,邏輯就不測試了。如果接收到標題 motor
,在 Port D
上的馬達就會依收到的數字轉動,值的範圍是 -100 到 +100
。如果接收到標題是 abc
,在EV3螢幕上就會顯示收到的字串。
Python程式碼
程式碼傳送的資料就依照上一章節實驗結果設計。理論上輸入數字可以看到馬達轉動,輸入字串可以看到EV3螢幕顯示結果。
#!/usr/bin/python3
import serial
import struct
import time
def tx_num(name, msg):
msg_cnt = b'\x00\x00' # message counter (2 byte), but ev3 core tx don't update this counter
cmd_type = b'\x81' # command type (1 byte), no reply
cmd_sys = b'\x9e' # system command, WRITEMAILBOX: 0x9e
name_len = struct.pack('B', len(name)+1) # including null termination 0x00
msg_len = struct.pack('<H', 4) # float 4 bytes
cmd_data = msg_cnt + cmd_type + cmd_sys + \
name_len + str.encode(name+'\x00') + \
msg_len + struct.pack('f', msg)
cmd_len = struct.pack('<H', len(cmd_data))
tx_data = cmd_len + cmd_data
return tx_data
def tx_str(name, msg):
msg_cnt = b'\x00\x00' # message counter (2 byte), but ev3 core tx don't update this counter
cmd_type = b'\x81' # command type (1 byte), no reply
cmd_sys = b'\x9e' # system command, WRITEMAILBOX: 0x9e
name_len = struct.pack('B', len(name)+1) # including null termination 0x00
msg_len = struct.pack('<H', len(msg)+1) # including null termination 0x00
cmd_data = msg_cnt + cmd_type + cmd_sys + \
name_len + str.encode(name+'\x00') + \
msg_len + str.encode(msg+'\x00')
cmd_len = struct.pack('<H', len(cmd_data))
tx_data = cmd_len + cmd_data
return tx_data
EV3 = serial.Serial('/dev/rfcomm0')
print("Ready for transmitting EV3 Bluetooth messages, press CTRL+C to quit.")
try:
while True:
msg = input('Enter your message: ')
try: # float type
num = float(msg)
print(''.join('{:02x} '.format(c) for c in list(tx_num('motor', num))))
EV3.write(tx_num('motor', num))
except ValueError: # non-float type, treat as string
print(''.join('{:02x} '.format(c) for c in list(tx_str('abc', msg))))
EV3.write(tx_str('abc', msg))
except KeyboardInterrupt:
break
time.sleep(0.2)
except KeyboardInterrupt:
pass
EV3.close()
print("\nTX End")
疑難排解
- 操作python噴出
[Errno 5] Input/output error
怎麼辦?
- 重新連接一次藍牙
- 多試幾次有機會會好
- 重新開機Raspberry Pi
- 怎麼重新配對?
sudo bluetoothctl
進藍牙指令操作,輸入remove AA:BB:CC:DD:EE:FF
來移除原有的配對,然後再做一次配對。
留言
張貼留言