這台麥克納姆輪車之前是 Arduino UNO/Mega2560+ESP32-CAM 的取物機器人。這次把 Arduino 拿掉,改成 Raspberry Pi 5 直接用 GPIO 驅動兩片 L298N,加上 Logitech C310 攝影機、HC-SR04 超聲波(裝在 MG90S 伺服上可以轉頭)、MPU6050 陀螺儀,做成一台用手機網頁遙控、會自己避障的自走車。這篇記錄硬體接法、網頁介面,以及實車調校時一個一個踩到的坑。
硬體與接線
| 零件 | 用途 | 接到 Pi 5 |
|---|---|---|
| L298N ×2 | 四顆 TT 馬達(每片管一邊兩顆) | EN 用 GPIO12/13/18/19(PWM 調速,板上 ENA/ENB 跳帽要拔),IN 用 8 支 GPIO |
| HC-SR04 | 前方距離 | Trig GPIO17;Echo 是 5V,經 1kΩ/2kΩ 分壓才接 GPIO27 |
| MG90S | 超聲波轉頭(±80°) | GPIO22,電源吃 L298N 的 5V(不吃 Pi 的 5V) |
| MPU6050(GY-521) | 陀螺儀:車頭方向、撞擊偵測 | I2C:SDA 3 腳、SCL 5 腳、VCC 3.3V |
| Logitech C310 | 影像+YOLO 物件偵測 | USB(640×480 YUYV,MJPG 會一直噴壞 JPEG) |
| 18650 ×3 串聯 | 馬達電源(12.6V 滿) | → 保護板 → 開關 → 兩片 L298N,GND 跟 Pi 共地 |
| 行動電源 | Pi 5 電源 | USB-C,跟馬達電源分開 |
網頁操作
程式是 Python(aiohttp+lgpio+OpenCV+onnxruntime),開機自動跑成 systemd 服務,手機連 http://pi5-2:8080/:左邊搖桿前後+左右平移,右邊橢圓搖桿原地旋轉,影像上疊距離,YOLO(yolo26n,CPU 每張約 32 ms、限每秒 10 次)把看到的東西框出來。網頁每 0.1 秒送一次搖桿,超過 0.5 秒沒收到就停車(斷線、手機鎖螢幕)。
實車調校一:輪子方向與超聲波
- 輪子方向:網頁有「單輪測試」,每顆按「正轉」都應該讓車往前推。實車 RL(左後)和 FR(右前)是反的,不用改線,在 config.yaml 把那兩顆的
invert改成 true。 - 超聲波第一次完全沒回音:Pi 送了 10 次 Trig,GPIO27 一次都沒變高——是接線問題(實體腳號和 GPIO 編號很容易搞混,第 13 腳是 GPIO27,GPIO13 是第 33 腳),重接後馬上量到 40 cm。
- 轉頭後距離不會變:原本程式只在伺服朝正前方時才量,轉到 -10° 就凍結在最後一個值。改成任何角度都量,網頁標出方向(例如「63 cm 右 10°」);「太近不准往前」只看 ±15° 內的讀值。
實車調校二:陀螺儀讓車走直線
TT 馬達左右快慢不一樣,速度 50% 直走 3 秒,車頭就往左偏了 10°。加上 MPU6050 之後,搖桿沒有旋轉時程式會鎖住起步那一刻的車頭方向,偏了就讓兩邊輪子一快一慢拉回來(PD 控制),另外再學一個「這台車直走本來就要往右補多少」的固定量(積分,前/後/左/右分開學)。
| 版本 | 3 秒直走的車頭偏移 |
|---|---|
| 沒修正 | 10° |
| kp 0.03 | 7° |
| kp 0.1、ki 0.05、上限 0.5 | 最多 2°,之後自己拉回,停下時 1.3°;實際走 115 cm,眼睛看是直的 |
兩個坑:
- 兩個網頁互相蓋掉:我從遠端送「前進」做測試時,使用者手機的網頁還開著、每 0.1 秒送一次「搖桿放著=0」,車子其實是走走停停,第一次量到的「3 秒 80 cm」是錯的(真的是 115 cm)。改成每個網頁的搖桿分開記,有人在推就用最新推的那個,全部放開才停。
- 積分飽和:自動模式卡在櫃子前推不動時,修正量一路累積到上限 -0.5,右輪只剩 14% 出力,車子一直往右繞。改成偏差小於 10° 才學、修正量上限 0.2、偏超過 20°(被撞歪、手轉、卡住)就改用現在的方向當目標。
距離圖與路線圖
影像下面多了兩張圖:
- 距離圖(車身座標,車頭朝上):超聲波每 5° 一格、15 秒內的讀值畫成綠點連成輪廓;按「掃描一圈」伺服從右 80° 轉到左 80°,每 10° 量一次。黃色扇形是攝影機視角,YOLO 認得的東西畫成黃點並估距離。
- 路線圖(俯視,出發時車頭朝上):沒有編碼器,位置是「送給四輪的速度 → 麥克納姆反運算 → 乘實測 77 cm/s(全速)」推算,方向用陀螺儀;超聲波打到的點換成世界座標畫成紅點,跑久了房間的輪廓就出來了。
攝影機估距離用「地面法」:鏡頭離地 8 cm,東西放在地上時,它的底邊在畫面越下面就越近,距離=鏡頭高度 ÷ tan(底邊在水平線下的角度);底邊碰到畫面下緣(看不到落地點)時,改用人、椅子這類已知高度的東西用框的高度估。一開始櫃子被估成 28 cm,因為鏡頭被夾子夾得往上仰——用一個放在 100 cm 的箱子校正:箱子底邊在畫面第 350 列、遠處地面的水平線在第 290 列左右,算出鏡頭往上仰約 6°,校正後估成 87 cm(從車頭算),跟超聲波的 86 cm 對得上。
自動避障:一路改了好幾版
最初的邏輯很簡單:前方 25 cm 內有東西就停、伺服左右各看 60°、往比較空的一邊轉。實車跑起來的問題和改法:
| 實車問題 | 原因 | 改法 |
|---|---|---|
| 斜面的路障偵測不到 | 聲波被斜面彈走,收不到回音,原本當成「前方沒東西」 | 前進時伺服在 0/左 20°/0/右 20° 輪流量,取擋在車寬內最近的;±25° 都沒回音超過 1 秒就停下來看;沒回音或接近時半速 |
| 撞到東西還一直頂 | 沒有感測 | MPU6050 的加速度:水平加速度突然超過 0.9 g 當撞擊,退一點再找路 |
| 一直誤判撞到(前方還有 124 cm) | 起步、急停的加速度也超過門檻 | 穩定前進 0.8 秒之後才算撞擊 |
| 跑不出房間 | 只看左右兩個角度、固定轉一個角度;轉完還用轉之前的前方距離 | 看 5 個角度(右 80、右 40、正前、左 40、左 80),往最空的方向轉、每次最多 30°;轉完清掉舊讀值 |
| 左右來回換、轉不出去 | 每次重新選邊 | 記住閃避方向:沒走出 50 cm 又被擋就繼續往同一邊;那邊累積轉 120° 還不行就記住、改另一邊;兩邊都不行就後退重來 |
| 頂著抽屜櫃推 45 秒 | 超聲波裝得低,從櫃子下面看出去一直量到 90 cm | 卡住偵測:輪子推算走了 25 cm 以上,正前方距離卻變不到 1/4 → 當成卡住,後退換方向 |
| 太快 | 自動模式不吃網頁的「速度上限」 | 自動前進、轉彎、後退都乘上速度上限;轉彎改用陀螺儀轉到角度就停 |
最嚴重的坑:重開服務時暴衝
有一次車子突然全速衝進躺椅腳靠底下。查了才發現兩件事疊在一起:
- lgpio 的
tx_pwm(…, 0)不會把腳位拉低:它只是停掉 PWM,腳位停在最後的電位。用pinctrl get一看,四支 L298N 的 EN 一直都是 high。 - 我用 SIGKILL 重開服務(程式收到 SIGTERM 不會自己結束,之前都用強制結束),當時車子正在自動模式轉動:方向腳停在「轉」、EN 又是 high,新程式起來前那 2~3 秒就是 100% 全速。
修法:停車時一定 gpio_write(EN, 0);systemd 服務加 ExecStartPre/ExecStopPost 用 pinctrl set … op dl 把 12 支馬達腳全部拉低、TimeoutStopSec=3;收到關閉訊號第一件事先停車。之後就算程式當掉或被砍,馬達也不會停在轉動狀態。接馬達的專案,「程式死掉時腳位會停在哪」一定要先想好。
還沒解決的
- 超聲波看不到比它高或低的東西(躺椅腳靠、櫃子下緣、門檻),打算車頂加一顆 VL53L1X 雷射測距,跟 MPU6050 共用 I2C。
- Pi 5 的行動電源電壓偏低(4.75~4.89V),要換 PD 5V 3A。
- 路線圖的平移效率(假設直走的 80%)還沒實測;跑久了會飄。









沒有留言:
張貼留言