# 第一章 Cyber Pi 控制 Dobot 機械手臂操作指南 示意圖 ![142198_0](https://hackmd.io/_uploads/rkwZFI4-xx.jpg) 透過兩個Cyber Pi將其中一個Cyber Pi ## 目標 本教程旨在幫助老師和學生快速了解如何使用 Cyber Pi 控制 Dobot 機械手臂,包括移動指令、吸盤控制和回到原點等功能。 ## 接線 利用四條杜邦線將兩者進行連接,兩條電源線,一條寫入一條讀取,完成操作反饋 ![image](https://hackmd.io/_uploads/HyrTLF9Eee.png) ![image](https://hackmd.io/_uploads/HkURUtcEgl.png) ## Cyber Pi控制Dobot程式碼 ```python from machine import UART # 用來控制 UART import cyberpi, time, struct # 需要 struct 來轉換 float # 設定 UART 串口 uart1 = UART(1, baudrate=115200, tx=15, rx=21) # 如果你接的 TX/RX 沒變,則不需要更改 # 預設座標 Sx = 260 Sy = 0 Sz = -8.5 Sr = 0 # 主功能:傳送一個 PTP 點位移動指令 def MoveToPoint(x, y, z, r): UartCommand = [170, 170] # 固定開頭 Header,不用改 length = 19 # 資料長度固定為 19 bytes ID = 84 # 指令 ID = 84 表示 PTP 點位移動 Ctrl = 3 # 控制模式 3 表示立即執行 ptpmode = 1 # PTP 模式,1 為線性運動(Linear),可改為 0 看需求 # 將 x/y/z/r 轉成 4-byte IEEE754 格式,並反轉為小端序 Dx = list(struct.pack("!f", x)) Dy = list(struct.pack("!f", y)) Dz = list(struct.pack("!f", z)) Dr = list(struct.pack("!f", r)) payload = [] payload.append(ID) # 不用改 payload.append(Ctrl) # 不用改 payload.append(ptpmode) # 可改為其他移動模式 payload.extend(Dx[::-1]) # x 座標(4 bytes) payload.extend(Dy[::-1]) # y 座標(4 bytes) payload.extend(Dz[::-1]) # z 座標(4 bytes) payload.extend(Dr[::-1]) # r 角度(4 bytes) Checksum = 256 - sum(payload) & 0xff # 計算 Checksum UartCommand.append(length) # 將長度放進封包 UartCommand.extend(payload) # 將 payload 放進封包 UartCommand.append(Checksum) # 將 Checksum 放進封包 uart1.write(bytes(UartCommand)) # 傳送完整指令封包 # 吸盤控制 def suck_on(): # 吸盤打開 UartCommand = [170, 170, 4, 63, 3, 1, 1, 188] uart1.write(bytes(UartCommand)) def suck_off(): # 吸盤關閉 UartCommand = [170, 170, 4, 63, 3, 0, 0, 190] uart1.write(bytes(UartCommand)) # 夾爪控制 def gripper_open(): # 夾爪張開(值 = 500) UartCommand = [170, 170, 6, 62, 3, 1, 244, 1, 9] uart1.write(bytes(UartCommand)) def gripper_close(): # 夾爪閉合(值 = 0) UartCommand = [170, 170, 6, 62, 3, 1, 0, 0, 11] uart1.write(bytes(UartCommand)) # 回到原點 def home(): cyberpi.console.print('homing') # homing commend data1 = [170,170, 6, 31,3,0,0,0,0,222] uart1.write(bytes(data1)) Sx = 260 Sy = 0 Sz = -8.5 Sr = 0 time.sleep(1) # 主循環:控制按鈕事件 while True: if cyberpi.controller.is_press('up'): #按鈕按下 cyberpi.console.println("u") #顯示模式 time.sleep(.2) MoveToPoint(200, 50, -10, 0) # A 到 B time.sleep(2) # 停 2 秒 MoveToPoint(250, 0, -8.5, 0) # B 到 A time.sleep(2) # 停 2 秒 MoveToPoint(200, 50, -10, 0) # 再次 A 到 B if cyberpi.controller.is_press('a'): cyberpi.console.println("s1") suck_on() time.sleep(1) # 吸住 1 秒 suck_off() time.sleep(1) # 放開 1 秒 if cyberpi.controller.is_press('middle'): cyberpi.console.println("home_") home() ``` ## 加入廣播控制Dobot 之前廣播用積木的型態不好帶入python程式中 因此將其簡單整理成收與發 首先是發送訊息的部分相對簡單,用elif可以疊加訊息發送 ### 發送訊息 ```python import event, time, cyberpi @event.start def on_start(): # 嘗試連接 WIFI(請填入名稱與密碼) cyberpi.wifi.connect("", "") # 設定控制台字體大小,顯示提示文字 cyberpi.console.set_font(12) cyberpi.console.println("連線成功") while True: if cyberpi.controller.is_press('up'): cyberpi.console.println("up") cyberpi.mesh_broadcast.set("message", 1) elif cyberpi.controller.is_press('down'): cyberpi.console.println("down") cyberpi.mesh_broadcast.set("message", 2) elif cyberpi.controller.is_press('left'): cyberpi.console.println("left") cyberpi.mesh_broadcast.set("message", 3) elif cyberpi.controller.is_press('right'): cyberpi.console.println("right") cyberpi.mesh_broadcast.set("message", 4) else: cyberpi.mesh_broadcast.set("message", 0) # 沒有方向鍵按下 time.sleep(0.05) # 每 50 毫秒檢查一次 ``` 接下來 目前將dobot手臂動作加入,讓遠端將訊息傳入後就能夠實際做出相關動作了。 ### 接收訊息 ```python= import event, time, cyberpi,mbot2 # 啟動時執行的程式碼 @event.start def on_start(): cyberpi.wifi.connect("", "") # 請填入 Wi-Fi 名稱與密碼 cyberpi.console.set_font(12) cyberpi.console.println("等待遙控") # 當接收到 mesh 廣播訊息時執行的程式碼 @cyberpi.event.mesh_broadcast("message") def on_mesh_broadcast(): msg = cyberpi.mesh_broadcast.get("message") if msg == 1: # 收到訊息1時要執行的動作 mbot2.forward(50) pass elif msg == 2: # 收到訊息2時要執行的動作 mbot2.backward(50) pass elif msg == 3: # 收到訊息3時要執行的動作 mbot2.turn_left(50) pass elif msg == 4: # 收到訊息4時要執行的動作 mbot2.turn_right(50) pass else: mbot2.forward(0) # 收到其他訊息時要執行的動作 pass ``` 最後是合成疊加,將dobot訊息整合進入接收訊息端 因此需要將接收端進行小部分份修改 ## AI鏡頭結合dobot機械手臂實現遠端控制 ### AI鏡頭設定 1. 連接AI鏡頭 ![142197_0](https://hackmd.io/_uploads/Skb8FINbxx.jpg) 2. 選擇色塊辨識模式(左下角Blob) ![142194_0](https://hackmd.io/_uploads/r1t6KLNZlx.jpg) 3. 進入學習模式 ![142195_0](https://hackmd.io/_uploads/B1Xb58VZlg.jpg) 4.點選物件=>直到綠色框框完全選擇在要學習的物件由上繳的由上的筆按鈕,學習成功右下方會多一個辨識號碼,如圖就是將新的黃色標註為ID3,註記成第三個編號,程式要使用時,只需要使用3號標註,就是剛剛學習的顏色 ![142191_0](https://hackmd.io/_uploads/SkZ75LE-el.jpg) ### 從AI鏡頭 發送訊號 不過目前AI鏡頭只能用簡體版本的線上介面操控之後,如果可以應該會將繁體版如何使用一起加入 ```python= import event, time, cyberpi, mbuild # initialize variables flag = 0 @event.start def on_start(): global flag cyberpi.console.print("makeblock") cyberpi.wifi.connect("JY-Office", "80061719") cyberpi.led.on(208, 2, 27, "all") while not cyberpi.wifi.is_connect(): # DO SOMETHING pass cyberpi.led.on(108, 208, 1, "all") @event.is_press('a') def is_btn_press(): global flag # DO SOMETHING pass @event.is_press('b') def is_btn_press1(): global flag flag = 0 mbuild.ai_camera.ai_camera_set_func_switch(3, 1) while not False: cyberpi.table.add(1, 2, flag) if mbuild.ai_camera.ai_camera_color_spatial_attribute_get(1, 2, 1) < -300 or mbuild.ai_camera.ai_camera_color_color_get(1, 1) == -1: flag = 0 cyberpi.table.add(1, 1, "離開") cyberpi.mesh_broadcast.set("message", 0) if flag == 0: if mbuild.ai_camera.ai_camera_color_color_get(1, 1) == 1: cyberpi.table.add(1, 1, "紅色") flag = 1 cyberpi.mesh_broadcast.set("message", 1) else: if mbuild.ai_camera.ai_camera_color_color_get(1, 1) == 2: cyberpi.table.add(1, 1, "藍色") flag = 1 cyberpi.mesh_broadcast.set("message", 2) else: if mbuild.ai_camera.ai_camera_color_color_get(1, 1) == 3: cyberpi.table.add(1, 1, "黃色") flag = 1 cyberpi.mesh_broadcast.set("message", 3) else: if mbuild.ai_camera.ai_camera_color_color_get(1, 1) == 4: cyberpi.table.add(1, 1, "綠色") cyberpi.mesh_broadcast.set("message", 4) flag = 1 ``` ### 接收到訊息時的畫面 ```python= # 區域網路壞掉一定要連wifi from machine import UART import event, time, cyberpi,mbot2,struct uart1 = UART(1, baudrate=115200, tx=15, rx=21) # ✅ 不用改(如果你接的 TX/RX 沒變) # ✅ 預設座標:你可以改,也可以直接在 MoveToPoint 裡傳參數 # ✅ 主功能:傳送一個 PTP 點位移動指令 def MoveToPoint(x, y, z, r): UartCommand = [170, 170] # ✅ 固定開頭 Header,不用改 length = 19 # ✅ 資料長度固定為 19 bytes ID = 84 # ✅ 指令 ID = 84 表示 PTP 點位移動,不用改 Ctrl = 3 # ✅ 控制模式 3 表示立即執行 ptpmode = 1 # ✅ PTP 模式,1 為線性運動(Linear),可改為 0 看需求 # ✅ 將 x/y/z/r 轉成 4-byte IEEE754 格式,並反轉為小端序 Dx = list(struct.pack("!f", x)) # ✅ 轉 x Dy = list(struct.pack("!f", y)) # ✅ 轉 y Dz = list(struct.pack("!f", z)) # ✅ 轉 z Dr = list(struct.pack("!f", r)) # ✅ 轉 r # 🟡 除錯用,顯示轉換後的資料,可以刪除 print(Dx[::-1]) print(Dy[::-1]) print(Dz[::-1]) print(Dr[::-1]) payload = [] payload.append(ID) # ✅ 不用改 payload.append(Ctrl) # ✅ 不用改 payload.append(ptpmode) # ✅ 可改為其他移動模式 payload.extend(Dx[::-1]) # ✅ x 座標(4 bytes) payload.extend(Dy[::-1]) # ✅ y 座標(4 bytes) payload.extend(Dz[::-1]) # ✅ z 座標(4 bytes) payload.extend(Dr[::-1]) # ✅ r 角度(4 bytes) Checksum = 256 - sum(payload) & 0xff # ✅ 計算 Checksum,不用改 UartCommand.append(length) # ✅ 將長度放進封包 UartCommand.extend(payload) # ✅ 將 payload 放進封包 UartCommand.append(Checksum) # ✅ 將 Checksum 放進封包 uart1.write(bytes(UartCommand)) # ✅ 傳送完整指令封包 def suck_on(): # 吸盤打開 UartCommand = [170, 170, 4, 63, 3, 1, 1, 188] uart1.write(bytes(UartCommand)) def suck_off(): # 吸盤關閉 UartCommand = [170, 170, 4, 63, 3, 0, 0, 190] uart1.write(bytes(UartCommand)) def home(): cyberpi.console.print('homing') # homing commend data1 = [170,170, 6, 31,3,0,0,0,0,222] uart1.write(bytes(data1)) Sx=260;Sy=0;Sz=-8.5;Sr=0 time.sleep(1) # 啟動時執行的程式碼 @event.start def on_start(): cyberpi.wifi.connect("JY-Office", "80061719") # 請填入 Wi-Fi 名稱與密碼 cyberpi.console.set_font(12) # 按下A按鈕自動回歸原點 @event.is_press('a') def is_btn_press(): home() # 當接收到 mesh 廣播訊息時執行的程式碼 @cyberpi.event.mesh_broadcast("message") def on_mesh_broadcast(): msg = cyberpi.mesh_broadcast.get("message") if msg == 1: # 收到訊息1時要執行的動作 cyberpi.console.println("red") #顯示模式 time.sleep(.2) MoveToPoint(195.4, 28.7, 36.8, 0) time.sleep(.2) MoveToPoint(163.2, 21.9, 4.35, 0) time.sleep(.2) MoveToPoint(198.1, 31.7, -4.1, 0) time.sleep(.2) MoveToPoint(206.3, 32.8, 55.5, 0) # time.sleep(.2) MoveToPoint(67, -206, 20, 0) # 第五步移動到手臂右邊 time.sleep(.2) MoveToPoint(55.1, -215.9, -79, 0) time.sleep(.2) MoveToPoint(43.8, -180.8, -62.1, 0) time.sleep(.2) MoveToPoint(195.4, 28.7, 36.8, 0) time.sleep(.2) elif msg == 2: # 收到訊息2時要執行的動作 cyberpi.console.println("blue") time.sleep(.2) MoveToPoint(195.4, 28.7, 36.8, 0) time.sleep(.2) MoveToPoint(163.2, 21.9, 4.35, 0) time.sleep(.2) MoveToPoint(198.1, 31.7, -4.1, 0) time.sleep(.2) MoveToPoint(206.3, 32.8, 55.5, 0) time.sleep(.2) MoveToPoint( 119, 163, 38, 0) time.sleep(.2) MoveToPoint(25, 199, 37, 0) time.sleep(.2) MoveToPoint(21, 211, -74, 0) time.sleep(.2) MoveToPoint(23, 179, -60, 0) time.sleep(.2) MoveToPoint(23, 179, -60, 0) time.sleep(.2) elif msg == 3: # 收到訊息3時要執行的動作 cyberpi.console.println("yellow") elif msg == 4: # 收到訊息4時要執行的動作 cyberpi.console.println("green") else: # 收到其他訊息時要執行的動作 pass ``` 這樣只差最後一步,就是將發送端的部分將AI程式合成進來後,讓dobot進行遠端接收,就可以實現,遠端智慧物流的概念了 ## 最新整合 ### 手臂一程式 ```python from machine import UART import cyberpi,time,struct,gamepad,mbuild uart1 = UART(1, baudrate=115200, tx=15, rx=21) Sx=260;Sy=0;Sz=0;Sr=0 def MoveToPoint(x,y,z,r): UartCommand=[170,170] length=19 ID=84 Ctrl=3 ptpmode=1 # Home 260,0,-8.5,0 # x = 200 Dx=list(struct.pack("!f",x)) # 32float to 4 decimel list print(Dx[::-1]) # to reverse # y = 0 Dy=list(struct.pack("!f",y)) print(Dy[::-1]) # z = -8.5 Dz=list(struct.pack("!f",z)) print(Dz[::-1]) # r= 0 Dr=list(struct.pack("!f",r)) print(Dx[::-1]) payload=[] payload.append(ID) payload.append(Ctrl) payload.append(ptpmode) payload.extend(Dx[::-1]) payload.extend(Dy[::-1]) payload.extend(Dz[::-1]) payload.extend(Dr[::-1]) # print(payload) Checksum=256 - sum(payload) & 0xff # print(Checksum) UartCommand.append(length) UartCommand.extend(payload) UartCommand.append(Checksum) # print(UartCommand) uart1.write(bytes(UartCommand)) # c=uart1.read() # cyberpi.console.println(x,y,z,r) while True : suck=False if gamepad.is_key_pressed('Select'): cyberpi.console.print('homing') # homing commend data1 = [170,170, 6, 31,3,0,0,0,0,222] uart1.write(bytes(data1)) Sx=260;Sy=0;Sz=0;Sr=0 time.sleep(1) elif gamepad.get_joystick('Rx')==100: cyberpi.console.print('x') Sr-=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Rx')==-100: cyberpi.console.print('o') Sr+=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Ry')==100: cyberpi.console.print('-') Sz-=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Ry')==-100: cyberpi.console.print('+') Sz+=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Lx')==100: # 檢測是否按下按鍵 cyberpi.console.print('>') Sy-=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Lx')==-100: cyberpi.console.print('<') Sy+=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Ly')==100: cyberpi.console.print('^') Sx+=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.get_joystick('Ly')==-100: cyberpi.console.print('v') Sx-=5 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.is_key_pressed('L1'): UartCommand=[170, 170, 4, 63, 3, 0, 0, 190] # unsuck uart1.write(bytes(UartCommand)) suck=False cyberpi.console.print('unsuck') elif gamepad.is_key_pressed('R1'): UartCommand=[170, 170, 4, 63, 3, 1, 1, 188] # suck uart1.write(bytes(UartCommand)) suck=True cyberpi.console.print('suck') elif gamepad.is_key_pressed('Start'):#加號按鈕向左 uart1.write(bytes([170, 170, 8, 135, 3, 0, 1, 16, 39, 0, 0, 62])) time.sleep(7.2) uart1.write(bytes([170, 170, 8, 135, 3, 0, 1, 0, 0, 0, 0, 117])) # on輸送帶停下 elif gamepad.is_key_pressed('N1'):#原點1 Sx=233;Sy=-37;Sz=-17;Sr=0 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.is_key_pressed('N2'):#原點2 Sx=233;Sy=-18;Sz=-17;Sr=0 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.is_key_pressed('N3'):#原點3 Sx=233;Sy=1;Sz=-17;Sr=0 MoveToPoint(Sx,Sy,Sz,Sr) elif gamepad.is_key_pressed('N4'):#過去 Sx=22;Sy=247;Sz=14;Sr=0 MoveToPoint(Sx,Sy,Sz,Sr) ``` ### 手臂二程式 ```python= # 區域網路壞掉一定要連wifi from machine import UART import event, time, cyberpi,mbot2,struct uart1 = UART(1, baudrate=115200, tx=15, rx=21) # ✅ 不用改(如果你接的 TX/RX 沒變) # ✅ 預設座標:你可以改,也可以直接在 MoveToPoint 裡傳參數 # ✅ 主功能:傳送一個 PTP 點位移動指令 def MoveToPoint(x, y, z, r): UartCommand = [170, 170] # ✅ 固定開頭 Header,不用改 length = 19 # ✅ 資料長度固定為 19 bytes ID = 84 # ✅ 指令 ID = 84 表示 PTP 點位移動,不用改 Ctrl = 3 # ✅ 控制模式 3 表示立即執行 ptpmode = 1 # ✅ PTP 模式,1 為線性運動(Linear),可改為 0 看需求 # ✅ 將 x/y/z/r 轉成 4-byte IEEE754 格式,並反轉為小端序 Dx = list(struct.pack("!f", x)) # ✅ 轉 x Dy = list(struct.pack("!f", y)) # ✅ 轉 y Dz = list(struct.pack("!f", z)) # ✅ 轉 z Dr = list(struct.pack("!f", r)) # ✅ 轉 r # 🟡 除錯用,顯示轉換後的資料,可以刪除 print(Dx[::-1]) print(Dy[::-1]) print(Dz[::-1]) print(Dr[::-1]) payload = [] payload.append(ID) # ✅ 不用改 payload.append(Ctrl) # ✅ 不用改 payload.append(ptpmode) # ✅ 可改為其他移動模式 payload.extend(Dx[::-1]) # ✅ x 座標(4 bytes) payload.extend(Dy[::-1]) # ✅ y 座標(4 bytes) payload.extend(Dz[::-1]) # ✅ z 座標(4 bytes) payload.extend(Dr[::-1]) # ✅ r 角度(4 bytes) Checksum = 256 - sum(payload) & 0xff # ✅ 計算 Checksum,不用改 UartCommand.append(length) # ✅ 將長度放進封包 UartCommand.extend(payload) # ✅ 將 payload 放進封包 UartCommand.append(Checksum) # ✅ 將 Checksum 放進封包 uart1.write(bytes(UartCommand)) # ✅ 傳送完整指令封包 def suck_on(): # 吸盤打開 UartCommand = [170, 170, 4, 63, 3, 1, 1, 188] uart1.write(bytes(UartCommand)) def suck_off(): # 吸盤關閉 UartCommand = [170, 170, 4, 63, 3, 0, 0, 190] uart1.write(bytes(UartCommand)) def home(): cyberpi.console.print('homing') # homing commend data1 = [170,170, 6, 31,3,0,0,0,0,222] uart1.write(bytes(data1)) Sx=260;Sy=0;Sz=-8.5;Sr=0 time.sleep(1) # 啟動時執行的程式碼 @event.start def on_start(): cyberpi.wifi.connect("JY-SE", "80061719") # 請填入 Wi-Fi 名稱與密碼 cyberpi.console.set_font(12) cyberpi.console.println("press A go home") cyberpi.console.println("press B test") @event.is_press('a') def is_btn_press(): home() @event.is_press('b') def is_btn_press1(): cyberpi.console.println("test") MoveToPoint(222,-17,38,0)#原點 MoveToPoint(-6.3,-263,37.6,0)#輸送帶上方 MoveToPoint(-6.6,-262,3,0)#輸送帶 suck_on() time.sleep(2) MoveToPoint(-6.3,-263,37.6,0)#輸送帶上方 MoveToPoint(-3,241,76,0)#車子上方 MoveToPoint(-3,241,45,0)#車子 suck_off() MoveToPoint(-3,241,76,0)#車子上方 MoveToPoint(222,-17,38,0)#原點 time.sleep(5) # 當接收到 mesh 廣播訊息時執行的程式碼 @cyberpi.event.mesh_broadcast("message") def on_mesh_broadcast(): msg = cyberpi.mesh_broadcast.get("message") if msg == 1: # 收到訊息1時要執行的動作 cyberpi.console.println("go") #顯示模式 MoveToPoint(222,-17,38,0)#原點 MoveToPoint(-6.3,-263,37.6,0)#輸送帶上方 MoveToPoint(-6.6,-262,3,0)#輸送帶 suck_on() time.sleep(2) MoveToPoint(-6.3,-263,37.6,0)#輸送帶上方 MoveToPoint(-3,241,76,0)#車子上方 MoveToPoint(-3,241,45,0)#車子 suck_off() MoveToPoint(-3,241,76,0)#車子上方 MoveToPoint(222,-17,38,0)#原點 time.sleep(5) cyberpi.console.println('finish_get_basket') ``` ### 位置擺放 好的程式也必須搭配正確的座標位置,例如手臂與位置和每個物件 要做的事情都必須做出一些正確的搭配,先來看整體規劃 #### 整體位置圖 ![image](https://hackmd.io/_uploads/Bk-wV2Sfgl.png) #### 第一支手臂 對齊左下角1-B位置 ![hand1site](https://hackmd.io/_uploads/HkKofnSMlx.png) #### 貨物初始位置 對齊2-B左上角整齊擺放 ![cartsite](https://hackmd.io/_uploads/H1_eXhBzge.png) #### 輸送帶位置 左邊的腳對齊F2 G2之間的線 ![輸送帶位置](https://hackmd.io/_uploads/ByWmm2rfxg.png) #### 第二支手臂位置 手臂位在H-2位置 ![hand2site](https://hackmd.io/_uploads/Bygr73Szgx.png) #### 最後是mbot2擺放位置 車頭面向我們擺在黑線之前 ![mbot2site](https://hackmd.io/_uploads/ByTI73SGgg.png) 相關影片連結 https://www.youtube.com/watch?v=tdHXZUN7sKw 整體配置圖 ![image](https://hackmd.io/_uploads/By1T8ljzxx.png) ## 更多參考 第零章 認識dobot原廠設備 https://hackmd.io/-MpYdCViThiPUSD-bKzczw?view 第一章 Cyber Pi與dobot連結 https://hackmd.io/Ob3uHwJJT3-AXZA4BZwyIQ 第二章 Cyber Pi控制滑軌與區域網路 https://hackmd.io/J0WuY15yRDSiBRz32veFhA?view 第三章 搖桿控制Cyber Pi和dobot滑軌 https://hackmd.io/v_1uxa9WRxyGruGAwPw61g?view