robot-notes — 機器人知識筆記 GitHub ↗
本章與本頁目次

送餐機器人基礎原理補充

這是舊版單檔整理,已被取代,不再更新。本檔的 28 節已拆分到 docs/10-core/docs/20-forms/ 各主題檔,逐節去處見 docs/section-map.md。本檔保留僅供歷史對照,內容可能與現行主題檔不一致——請以主題檔為準

配合 delivery-robot-architecture.md 閱讀,展開底盤裝置、FOC、感測器原理,以及 M1 階段需要掌握的知識清單。

目錄

章節 主題 層級
1 底盤裝置:差速、萬向輪、輪轂馬達、BLDC、encoder 機構/選型
2 FOC 磁場導向控制 控制理論
3 感測器:2D LiDAR、深度相機、IMU 感知
4 M1 底盤控制知識清單與驗收 開發路線
5 BLDC + 行星減速機 機構/選型
6 CAN 與 RS485:原理與 STM32F4 串接 通訊
7 運動學解算 vs PID 控制理論
8 x86 NUC 是什麼 運算平台
9 看懂馬達驅動晶片的行銷術語 功率電子
10 伺服馬達 vs 底盤輪馬達:解析度與精度 觀念釐清
11 霍爾編碼器:原理與 STM32F4 連接 感測/韌體
12 功率橋與閘極驅動器 功率電子
13 Open-drain 是什麼 數位電路
14 STM32 的 open-drain 程式控制 韌體
15 為什麼開漏是「留下管」而不是「留上管」 數位電路
16 定子與轉子:命名由來、定義、與 Park 變換的關係 馬達原理
17 有刷 vs 無刷直流馬達(附圖) 馬達原理
18 電壓法規與日台送餐機器人標準 法規/認證
19 Encoder 圖解:增量式 A/B 相如何運作 感測/韌體
20 Encoder 回授與下位機:STM32 程式實例 韌體
21 2D SLAM 建圖流程(圖解) 導航
22 AMCL 定位演算法(圖解) 導航
23 深度相機輸出什麼:RGB 圖與深度圖(圖解) 感知
24 採用深度相機的送餐機器人產品案例 市場/選型
25 急停控制如何達成 安全
26 加減速 ramp 與過流/堵轉保護 安全/韌體
27 Odometry 是什麼:定義、字源、家族與本質限制 導航
28 地標定位:用相機看目標物反推自己的位置 導航

1. 底盤裝置解釋

1.1 兩輪差速 (Differential Drive)

兩顆驅動輪裝在同一軸線上、各自獨立控制轉速,靠左右輪「速度差」轉彎——沒有方向盤、沒有轉向機構:

俯視圖:
        ┌─────────┐
        │  ○ 萬向輪 │   ← 被動,只支撐
        │          │
   驅動輪▐█        █▌驅動輪   ← 同軸線,各自獨立馬達
        │          │
        │  ○ 萬向輪 │
        └─────────┘
左右輪狀態 行為
同速同向 直行
左快右慢 向右弧線轉彎
左正右反(等速) 原地旋轉(餐廳窄道的關鍵能力)

運動學公式(M1 必背,b = 兩輪間距、r = 輪半徑):

正解(輪速 → 車體):  v = (vR + vL) / 2        ω = (vR − vL) / b
逆解(車體 → 輪速):  vR = v + ωb/2            vL = v − ωb/2

上位機只下發 (v, ω)(線速度 + 角速度,即 ROS 的 /cmd_vel),下位機用逆解算出左右輪各該轉多快。

1.2 萬向輪 (Caster Wheel)

就是辦公椅腳下那種輪子:完全被動,不驅動、不轉向,只負責支撐車體重量、維持平衡。輪叉可 360° 自由旋轉,車往哪走它就被拖著朝哪。兩輪差速車一定要配 1–2 顆萬向輪,否則只有兩個接地點會傾倒。

1.3 輪轂馬達 (Hub Motor)

馬達直接做進輪框裡——定子固定在輪軸上,轉子連著輪框外殼,輪子本身就是馬達:

1.4 BLDC(無刷直流馬達)與 24V / 100–200W

有刷 vs 無刷:傳統有刷馬達靠碳刷機械接觸換相,會磨損、有火花、噪音大。BLDC 把換相改成電子式——轉子是永久磁鐵,定子是三相線圈 (U/V/W),由驅動器依轉子位置輪流給三相通電,「推著」磁鐵轉。代價是必須有驅動器 + 轉子位置感測(霍爾感測器),馬達不能直接接電池就轉。

選型數字的由來:

1.5 Encoder(編碼器)為什麼必備

Encoder 量測輪子實際轉了多少角度 / 多快。沒有它,兩件事都做不到:

  1. 速度閉迴路:你命令輪子轉 2 rad/s,實際因為載重、地面摩擦、電池電壓下降,可能只轉 1.6 rad/s。沒有 encoder 回授就無法修正,左右輪誤差不一致時車會走歪。
  2. Odometry(里程推算):把左右輪轉角累積起來,推算「車從起點走了多遠、轉了多少角度」,這是上位機定位的基礎輸入。

常見形式(解析度由低到高):

類型 解析度 說明
霍爾感測器 每電氣圈 6 個狀態 BLDC 換相本來就需要,可兼當低解析度 encoder
磁編碼器 每圈數千–數萬 counts 輪轂馬達常見加裝選項
光學增量式 (A/B 相) 每圈數百–數千線,×4 解碼 配減速機方案常用;STM32 Timer 有硬體 encoder mode 直接解

2. FOC 是什麼

FOC = Field-Oriented Control,磁場導向控制,是 BLDC/PMSM 馬達目前最主流的控制演算法。

2.1 它解決什麼問題

最簡單的 BLDC 控制是「六步方波換相」:依霍爾訊號把三相電依 6 個區段切換通電。能轉,但每次切換都是電流硬跳變 → 扭矩脈動,表現為低速時車身一頓一頓、有電磁嘯叫。送餐機恰好最在意低速平順(湯不能灑)與安靜,所以用 FOC。

2.2 核心思想

馬達出力最大的條件是「定子磁場永遠垂直於轉子磁鐵方向」。FOC 做的事就是:即時知道轉子角度,然後把三相電流精確控制成永遠垂直推轉子的方向

實作流程(每秒執行 10k–20k 次):

① 量測兩相電流 (ADC)
② Clarke + Park 變換:把三相交流電流,用轉子角度「旋轉」到
   跟著轉子轉的 d-q 座標系 → 交流問題變直流問題
     iq = 垂直分量 → 產生扭矩(想要的)
     id = 平行分量 → 不產生扭矩(控制成 0)
③ 兩個 PI 控制器分別調節 iq、id
④ 反 Park 變換 + SVPWM → 換算回三相 PWM 占空比,驅動功率橋

直觀類比:推旋轉門,FOC 保證你的手永遠垂直推門板(全部力氣變成轉動),而不是斜著推(一部分力氣浪費在擠門軸上)。

2.3 串級控制架構

FOC 電流環是最內環,外面再包速度環:

上位機 (v,ω) → [運動學逆解] → 輪目標轉速 → [速度環 PI (~1kHz)]
             → 扭矩(iq)命令 → [FOC 電流環 (10–20kHz)] → 三相 PWM → 馬達
                  ↑ encoder 轉速回授          ↑ 電流感測 + 轉子角度

2.4 為什麼建議初期買現成驅動器

自研 FOC 需要:電流採樣電路設計、功率橋(MOSFET)設計、20kHz 中斷下的定點/浮點運算優化、各種保護(過流/過壓/堵轉)、參數整定。是一個獨立的專業領域。現成 FOC 驅動器(CAN/RS485 介面)把這層全部封裝好,STM32 只要下「目標轉速」指令——把研發資源留給系統整合,符合 deep module 原則:介面窄(一條 CAN 指令),複雜度藏在模組內。


3. 感測器如何運作

3.1 2D LiDAR

原理:一顆雷射測距儀裝在馬達上連續旋轉。每個角度發一束雷射,量「光飛出去碰到物體反射回來的時間」(ToF) 換算距離。轉一圈(10–15Hz)得到一幀「掃描」(scan):幾百~幾千個 (角度, 距離) 點,等於在安裝高度切一個水平面,描出周圍所有物體的輪廓線

三個用途:

用途 運作方式
SLAM 建圖 連續 scan 之間做匹配 (scan matching),邊走邊拼出 2D 平面地圖
定位 (AMCL) 拿當下 scan 跟已知地圖比對:「我看到的輪廓跟地圖哪個位置最像?」
避障 scan 點直接投進 costmap,當成障礙物

關鍵限制:它只看得到「安裝高度那一個平面」

側視圖:
                ╔══════════╗ ← 桌面(突出桌腳之外)
                ║          ║
   LiDAR ----●----------→  ║   雷射在 20cm 高度水平掃出
  (裝在20cm高)  │掃描平面    ║
                █桌腳      █桌腳  ← LiDAR 只看得到這兩根

LiDAR 裝在底盤(約 15–30cm 高)→ 只掃到桌腳,看不到比掃描平面高的桌面突沿、椅背、托盤架。機器人會以為桌腳之間是空的,直接撞上桌沿。這就是深度相機存在的理由。

3.2 深度相機

輸出不是一條輪廓線,而是一張每個像素都帶距離值的影像(常見原理:結構光投射紅外圖案看變形,或雙目視差,或面陣 ToF)。一幀深度圖可轉成視野內的 3D 點雲 → 涵蓋從地面到相機視野上緣的整個立體空間,正好補上 LiDAR 掃描平面以外的高度帶:桌沿、椅背、伸出的腳、地上的低矮物。

在 ROS2 裡的處理:點雲 → 濾掉地面與雜訊 → 壓進 Nav2 costmap 的 voxel layer,跟 LiDAR 障礙層疊加。代價是視野窄(水平 ~60–90°,只看前方)、有效距離短(~0.3–4m)、運算量大——所以它補避障,不負責建圖定位

3.3 IMU

慣性測量單元 = 陀螺儀(角速度)+ 加速度計(線加速度),六軸。底盤上最重要的是陀螺儀的 yaw(偏航角速度):積分得到「車轉了幾度」。

為什麼 encoder 有了還要 IMU——兩者誤差特性互補:

  輪式 odometry (encoder) IMU 陀螺儀
直線距離 不可用(加速度二次積分發散)
旋轉角度 打滑就錯(油污地、地毯、過坎) 不受打滑影響
長期 誤差累積但可標定 零偏漂移,長時間積分會飄

融合策略(robot_localization EKF):距離信 encoder、角度信陀螺儀,長期漂移再由 LiDAR 定位(AMCL)修正。三層互補,缺一層定位品質就明顯下降。


4. M1 底盤控制該懂哪些

M1 目標:下位機完成馬達閉迴路 + odometry + 安全保護,上位機可遙控。以下是知識清單,依「不懂就做不出來」的程度排序。

4.1 必懂理論(不多,就這些)

  1. 差速運動學正逆解(§1.1 的兩組公式)——下位機核心邏輯就是逆解,odometry 就是正解的積分。
  2. PID 速度閉迴路:誤差 = 目標轉速 − encoder 實測轉速 → PID 輸出扭矩/PWM 命令。要懂:P 太大震盪、I 消穩態誤差但會 windup(需限幅)、底盤速度環通常 PI 就夠(D 對噪聲敏感)。
  3. Odometry 積分(航位推算):

    Δs = (ΔsR + ΔsL)/2          Δθ = (ΔsR − ΔsL)/b
    x += Δs·cos(θ + Δθ/2)
    y += Δs·sin(θ + Δθ/2)
    θ += Δθ
    

    符號定義(每個控制週期計算一次,如 100Hz → 每 10ms):

    符號 意義 單位 來源
    ΔsL、ΔsR 這個週期內,左/右輪在地面滾過的弧長 m encoder 增量 counts × 每 count 對應弧長(= 2πr / CPR,r = 輪半徑)
    Δs 這個週期車體中心前進的距離(左右輪平均) m 計算值
    Δθ 這個週期車體朝向的變化量(逆時針為正) rad 計算值;直行時 ΔsR=ΔsL → Δθ=0
    b 輪距:左右兩驅動輪與地面接觸點的中心距 m 量測+標定(§1.1 的同一個 b)
    x, y 車體在世界座標系的位置(從開機點累積) m 積分累積值
    θ 車體在世界座標系的朝向角(開機時 = 0) rad 積分累積值
    θ + Δθ/2 用「這段位移的中點朝向」投影,比直接用 θ 少一階誤差 rad 數值積分技巧(中點法)

    以及它的標定:輪徑誤差 → 走 5m 量實際距離修正;輪距誤差 → 原地轉 10 圈量實際角度修正。這個標定品質直接決定 M2 之後的 SLAM/定位品質。

4.2 必懂 STM32 實作技能

技能 用在哪
Timer encoder mode 硬體解 A/B 相正交訊號,注意 counter 溢位處理
定時中斷 / RTOS 週期任務 控制環必須固定週期執行(如 100Hz),抖動會毀掉 PID
CAN 通訊 對現成 FOC 驅動器下轉速命令、收狀態回報
UART + DMA 對上位機協議收發,DMA 避免高頻收發吃滿 CPU
協議設計 幀頭 + 長度 + 命令 + payload + CRC16;粘包/斷包處理
Watchdog 思維 通訊逾時計數器:超過 300ms 沒收到速度指令 → 主動減速停車

4.3 必做安全機制(M1 驗收項)

  1. 硬體急停:實體開關直接切斷驅動器致能訊號,不經過任何軟體。
  2. 通訊逾時停車:模擬「上位機當機」(拔線)測試,車必須在限定時間內平滑停下。
  3. 加減速 ramp:速度指令變化率限幅(送餐機 ≤0.3–0.5 m/s²),急停除外。
  4. 過流/堵轉保護:輪子被卡住時驅動器電流飆高 → 限流 + 上報異常。

4.4 M1 驗收測試方法

測試 方法 合格參考
直線精度 命令直行 5m,量終點橫向偏移 <5cm(標定後)
旋轉精度 原地轉 360°×10 圈,量累積角度誤差 <10°/10 圈
速度追蹤 階躍速度命令,錄 encoder 實測曲線 無持續震盪、穩態誤差 <5%
逾時保護 行進中拔掉上位機通訊線 300ms 內開始減速,平滑停止
急停 全速行進中按急停 立即斷動力

4.5 M1 不需要懂的(常見的過早投入)


5. BLDC + 行星減速機

5.1 為什麼需要減速機

馬達的天性是高轉速、低扭矩(一般 BLDC 甜蜜點在 3000–5000 RPM),但輪子需要的是低轉速、高扭矩(輪徑 15cm、車速 1 m/s → 輪子只要 ~127 RPM)。減速機就是「轉速換扭矩」的齒輪變速箱:

減速比 N = 20 時:
馬達 3000 RPM、0.2 N·m  →  輸出 150 RPM、~3.6 N·m(扣除效率 ~90%)
轉速 ÷ N        扭矩 × N × 效率

5.2 行星減速機的構造

俯視剖面:
        ┌─── 外環齒輪(固定)───┐
        │   ◯       ◯        │
        │     ┌───┐           │   太陽齒輪:中心,接馬達軸(輸入)
        │   ◯│ ☀ │◯         │   行星齒輪:3–4 顆,繞著太陽轉
        │     └───┘           │   行星架:串起行星輪的軸(輸出)
        │   ◯       ◯        │
        └─────────────────────┘

馬達轉太陽齒輪 → 行星齒輪被帶動,沿著固定的外環齒輪「公轉」→ 行星架以較慢的速度輸出。命名就是來自「行星繞太陽」的結構。

為什麼選「行星式」而不是普通正齒輪減速:

特性 行星減速機 平行軸正齒輪箱
輸入/輸出軸 同軸心 → 可直接套在馬達前端,結構緊湊 軸偏移,體積大
扭矩承載 負載分散到 3–4 顆行星輪 → 同體積扭矩大 單一嚙合點受力
減速比 單級 3–10,可串級(兩級 9–100) 單級比小

5.3 與輪轂馬達的取捨

面向 輪轂馬達 BLDC + 行星減速機
結構 馬達=輪子,零傳動件 馬達 + 減速機 + 聯軸器/軸承座
噪音 安靜 有齒輪聲(精度等級越低越吵)
扭矩裕度 受輪內空間限制 換減速比即可,裕度大
背隙 (backlash) 有(齒輪間隙),低速換向時 odometry 有小死區
馬達選擇 特規多極馬達 通用高速 BLDC,便宜好買
適用 平地室內(送餐主流) 重載、爬坡、戶外 AGV

Encoder 安裝位置的細節:配減速機時 encoder 通常裝在馬達端(轉得快 → 等效解析度高 N 倍),但量到的是減速機「之前」的角度,背隙誤差量不到;對送餐機精度要求這不構成問題,但要知道有這回事。


6. CAN 與 RS485:原理與 STM32F4 串接

兩者都是差分訊號匯流排(用兩條線的「電壓差」傳資料,抗共模干擾,適合馬達旁邊這種電磁噪聲環境),但層級完全不同:CAN 是一套完整協議,RS485 只是電氣規格。

6.1 共同的前提:MCU 腳位不能直接上匯流排

STM32 腳位輸出的是 0/3.3V 單端數位訊號,匯流排上跑的是差分電平,中間必須加一顆收發器 (transceiver) 做轉換。這是接線的核心觀念:

STM32F4 ──(TX/RX 數位訊號)── 收發器 IC ──(差分線)── 匯流排

6.2 CAN 串接(STM32F4 內建 bxCAN 控制器 ×2)

 STM32F407                  CAN 收發器                匯流排(雙絞線)
┌──────────┐              ┌────────────┐
│ CAN1_TX  ├─ PA12/PB9 ──►│ TXD   CANH ├──┬───────────────┬── CANH
│ CAN1_RX  ├─ PA11/PB8 ◄──┤ RXD   CANL ├──┼─┐           ┌─┼── CANL
└──────────┘              │ (TJA1051/  │  │ │           │ │
                          │ SN65HVD230)│ [120Ω]       [120Ω]
                          └────────────┘  終端電阻(匯流排兩端各一顆)

同一對線上掛所有節點:STM32、左輪驅動器、右輪驅動器、BMS…

要點:

6.3 RS485 串接(本質是 UART + 差分收發器)

RS485 沒有自己的協議——它只是把 UART 訊號變成差分電平的電氣標準。STM32 端用的外設就是普通 USART:

 STM32F407                 RS485 收發器               匯流排
┌──────────┐              ┌────────────┐
│ USART_TX ├─────────────►│ DI       A ├──┬──────────┬── A
│ USART_RX │◄─────────────┤ RO       B ├──┼─┐      ┌─┼── B
│ DE 控制腳 ├─────────────►│ DE/RE      │  │ │      │ │
└──────────┘              │ (MAX3485)  │ [120Ω]  [120Ω]
                          └────────────┘

關鍵差異:半雙工——同一對線收發共用,任一時刻只能一個節點講話,所以收發器有 DE/RE 方向腳:發送前拉高(進入驅動模式)、發完拉低(回到接收模式)。STM32 USART 有硬體 Driver Enable 功能(UART_Init 開 DE mode 指定腳位),不必手動 GPIO 切換、不會有時序競態。

因為沒有內建仲裁與校驗,RS485 上一律採主從輪詢:STM32 當 master 逐一問、slave 答,協議自己定(業界最常見是 Modbus RTU)。

6.4 怎麼選

  CAN RS485
協議層 硬體內建(訊框/CRC/重傳/仲裁) 無,自定(常用 Modbus RTU)
拓撲 多主,任何節點隨時可發 主從,master 輪詢
錯誤處理 硬體自動 軟體自己做
即時性 好(ID 即優先級) 受輪詢週期限制
成本/複雜度 收發器同價,軟體初始化較繁 最簡單,等於寫 UART

底盤建議:馬達驅動器、BMS 走 CAN(多節點即時控制是它的主場);RS485 留給只支援 Modbus 的周邊。多數現成 FOC 驅動器兩種介面都提供。


7. 運動學解算需要哪些知識?跟 PID 是什麼關係?

7.1 先回答:不是,兩者是不同層次的東西

  運動學解算 PID
本質 幾何換算(純數學公式,代入就有答案) 回授控制(根據誤差持續修正)
回答的問題 「車要走 v、轉 ω,左右輪應該各轉多快?」 「輪子實際沒轉到目標速度,怎麼修?」
有沒有回授 沒有,開環計算 有,靠 encoder 回授
跟時間的關係 無關(瞬時換算) 有關(誤差隨時間的累積與變化)

兩者在控制鏈上是上下游關係,不是同一件事:

(v, ω) ──[運動學逆解:算出目標]──► 目標輪速 ──[PID:達成目標]──► 馬達
                                        ▲
                                   encoder 實測

運動學負責「算目標」,PID 負責「追目標」。運動學算錯,PID 會很努力地追一個錯的目標——車照樣走歪。

7.2 差速車的運動學需要哪些知識

清單其實很短,高中數學 + 大一微積分程度:

  1. 三角函數與基本幾何:cos/sin、弧長 = 半徑×角度。
  2. 剛體平面運動的一個概念:ICC(瞬時旋轉中心)——車身任一瞬間都繞某個點旋轉(直行時該點在無限遠),左右輪因為到 ICC 距離不同所以速度不同,差速公式就是從這推出來的。懂這個,公式就不用背。
  3. 單位換算鏈(實作上最容易出錯的地方):

    m/s(車體) ↔ rad/s(輪) ↔ RPM(馬達,有減速比要除) ↔ encoder counts/週期
    
  4. 座標系概念:車體座標系(v、ω 定義在這)vs 世界座標系(odometry 的 x、y、θ 在這),odometry 積分就是不斷把車體系的位移用 θ 旋轉到世界系。
  5. 一點微積分:odometry 是離散積分(每週期累加),理解取樣週期內「近似直線」的假設。

不需要:矩陣運算、雅可比矩陣、Lagrange 動力學——那些是機械手臂、全向輪(麥克納姆)或動力學控制的範圍,差速底盤用不到。

7.3 那 PID 需要哪些知識

P/I/D 三項的直覺(P 看現在、I 看過去、D 看未來)、積分 windup 與限幅、取樣週期一致性、基本調參法(先 P 後 I,底盤速度環通常 PI 就夠)。不需要傳遞函數、頻域分析也能把底盤調好——那些是進階優化時才回頭補的。


8. x86 NUC 是什麼

NUC(Next Unit of Computing)原是 Intel 定義的迷你電腦規格:約 10×10cm 的小盒子,裝著跟筆電同級的 CPU、RAM、SSD。Intel 已把這條產品線轉給 ASUS,現在「NUC」泛指這一類迷你 PC(ASUS NUC、Beelink、工控品牌的無風扇款等)。

x86 指的是 CPU 指令集架構——跟你的桌機、筆電、伺服器同一家族(Intel/AMD),相對於 Jetson、Raspberry Pi、RK3588 用的 ARM 架構。

放在機器人上的意義:

面向 x86 NUC ARM 板(Jetson/RK3588)
軟體相容性 與開發機完全一致,Ubuntu/ROS2 任何套件 apt install 直接裝 部分套件要自己編譯,偶有 ARM 相容性坑
開發流程 程式在開發機編好直接丟上去跑,不用交叉編譯 交叉編譯或板上編譯(慢)
CPU 效能 強(SLAM、Nav2 跑得寬裕) 中等
AI 加速 無 GPU/NPU(視覺 AI 弱) Jetson 有 GPU、RK3588 有 NPU
功耗 15–40W(電池容量要多留) 7–25W
量產成本 較高 較低

所以架構文件的建議是:開發期用 NUC——把「平台移植」這個變數從開發過程中拿掉,專心解決機器人本身的問題;等功能定型、算力需求明朗,再評估量產要不要換 Jetson(視覺重)或 RK3588(成本敏感)。選工控版本(寬溫、寬壓 DC 輸入、無風扇)可以直接吃電池的 DC-DC 輸出。


9. 看懂馬達驅動晶片的行銷術語

情境:晶片廠(TI、ST、Infineon…)官網的 BLDC 產品文案,例如: 「利用我們的 BLDC 馬達驅動器產品組合,實現最高的 3 相無刷馬達性能與永磁同步馬達 (PMSM) 性能。我們的產品具備如智慧型閘極驅動器、整合式馬達控制、整合式 FET 及功能安全設計封裝等關鍵功能…」

9.1 先建立座標:馬達驅動的硬體鏈

要看懂這些詞,先知道「讓 BLDC 轉起來」的完整硬體鏈長什麼樣:

MCU ──► 閘極驅動器 ──► 功率橋(6 顆 MOSFET)──► 三相線圈 ──► 馬達轉
(算 PWM)  (小訊號放大     (真正開關大電流,
           成能推 MOSFET   把電池 24V 切成
           的驅動訊號)     三相波形)

9.2 逐詞解讀那段文案

文案用詞 實際意思
馬達驅動器(此處) 驅動晶片 (IC),不是裝在機器人上的「驅動器整機」。整機 = 這顆 IC + MCU + 功率級 + 外殼做成的產品
3 相無刷馬達 (BLDC) vs PMSM 同族兄弟,都是「永磁轉子 + 三相定子」。嚴格區分:BLDC 反電動勢是梯形波(配六步方波控制)、PMSM 是弦波(配 FOC)。實務上常混用;文案並列只是說「兩種都支援」
智慧型閘極驅動器 (smart gate driver) gate driver 加上「智慧」= 內建保護(過流/過溫/欠壓)、可程式化開關速率 (slew rate,用來折衷 EMI 與發熱)、故障診斷回報。對比傳統 gate driver 需要一堆外部電阻電容調整
整合式馬達控制 (integrated motor control) 控制演算法燒在晶片裡——例如無感測 FOC 直接內建,MCU 只要下「目標轉速」,連 FOC 程式都不用寫(或根本不需要 MCU)。對比:傳統作法演算法跑在你的 STM32 上
整合式 FET (integrated FETs) 6 顆功率 MOSFET 直接做進同一顆晶片封裝,外面不用再放功率橋。代價是電流上限受封裝散熱限制(通常適合數十瓦小馬達);大功率仍要外置 MOSFET
功能安全設計封裝 (functional safety package) 不是物理封裝,是「文件包」:晶片依 ISO 26262(車用)/ IEC 61508(工業)功能安全標準開發,廠商提供 FMEA、安全手冊、診斷覆蓋率數據,讓你的產品過安規認證時可以引用
工業 / 個人電子 / 汽車應用 同一系列晶片分不同等級:車規 (AEC-Q100)、工規、消費級,溫度範圍與認證不同

9.3 整合程度光譜(這段文案真正想說的事)

晶片廠的產品線就是沿著「幫你整合多少」排開的:

整合少 ◄──────────────────────────────────────► 整合多

只有 gate driver     + 整合 FET          + 整合馬達控制
(MCU 跑 FOC,        (省掉外部功率橋,    (連控制演算法都內建,
 外置 MOSFET,         適合小功率)         給你一顆晶片馬達就會轉)
 功率彈性最大)

對送餐機器人的對應:輪轂馬達 100–200W 的電流等級,現成 FOC 驅動器整機內部通常就是「MCU + smart gate driver + 外置 MOSFET」的組合。這張光譜圖是做驅動器的人要面對的選型;你買整機走 CAN,這層複雜度已被封裝掉——這正是第 2.4 節「初期不自研 FOC」的理由。


10. 伺服馬達 vs 底盤輪馬達:解析度、精度與控制模式

情境:網路上常見「17 位伺服馬達,131072 脈衝轉一圈,最小精度 0.0027 度,定位誤差可達 0.001mm」這類描述。它講的是工業伺服定位的世界,跟送餐機器人底盤是兩種不同的控制問題,而且用詞有幾處不精確。

10.1 逐句拆解常見誤導

原文說法 精確的說法
「17 位伺服馬達」 17-bit 指的是馬達後面那顆編碼器的解析度:2¹⁷ = 131072 counts/圈。馬達本身沒有位元數
「每 131072 個脈衝轉一圈」 這是「脈衝列 (pulse/direction) 命令介面」:控制器發脈衝、驅動器收,每個脈衝轉一格。脈衝數對應幾度其實由驅動器的電子齒輪比設定,不一定等於編碼器解析度
「最小精度 0.0027 度」 這是解析度 (resolution),不是精度。解析度 = 能「分辨/命令」的最小刻度;精度 (accuracy) = 實際到達位置與目標的誤差,受齒隙、軸承、溫漂、編碼器本身誤差影響,永遠比解析度差
「定位誤差可達 0.001mm」 旋轉角度要換算成直線定位,必須經過機構(滾珠螺桿導程、皮帶輪徑)。脫離機構講 mm 級定位誤差沒有意義;0.001mm 是「高階螺桿 + 全閉環」整個系統的數字,不是馬達自己的

核心觀念一句話:解析度是「尺上有幾格刻度」,精度是「量出來準不準」——刻度可以無限細,尺本身歪了照樣不準。

10.2 兩個世界的對照

  工業伺服(引文的世界) 送餐機器人底盤
控制目標 位置(停在 0.001mm 內) 速度(輪子穩定追蹤目標轉速)
控制迴路 位置環 + 速度環 + 電流環 三環 速度環 + 電流環 兩環(沒有位置環)
命令介面 脈衝列 / EtherCAT / CANopen CAN 速度命令(或 PWM)
編碼器 17–23 bit 絕對值編碼器 霍爾 / 數千 counts 增量編碼器就夠
機構 螺桿 / 減速機,剛性連接,無打滑 輪胎與地面摩擦接觸,會打滑
典型應用 CNC、機械手臂、半導體設備 AMR、AGV

10.3 為什麼底盤不需要 17-bit 編碼器

送餐機器人的定位誤差來源排序:輪胎打滑 » 輪徑/輪距標定誤差 » 編碼器解析度

粗算:輪徑 15cm、4096 counts 編碼器,一個 count ≈ 0.115mm 地面距離——而一次輕微打滑就是毫米到公分級誤差。換 17-bit 編碼器把 0.115mm 變成 0.0036mm,對 odometry 品質幾乎沒有貢獻,因為瓶頸根本不在解析度。這也是為什麼架構上靠 IMU 融合(§3.3)與 LiDAR 定位修正,而不是堆編碼器位元數。

引文第一段(汽車輪轂馬達含動力、傳動、制動)補充一點:送餐機器人的輪轂馬達「制動」通常不是機械煞車,而是馬達自身電氣制動(能量回收減速 / 三相短路煞車),部分型號加電磁抱閘用於斷電駐車。坡道停車需求要在選型時確認有無抱閘。


11. 霍爾編碼器:原理與 STM32F4 連接

11.1 原理:三顆霍爾開關 + 轉子磁鐵

BLDC 定子上嵌三顆霍爾開關(HA、HB、HC),彼此相隔 120° 電氣角。轉子永磁體掃過時,每顆輸出 0 或 1(N 極靠近 = 1)。三個位元組合出 8 種狀態,其中 000111 物理上不會出現,有效狀態 6 個,轉子每轉過 60° 電氣角切換一次:

正轉狀態序列(典型):
  HA HB HC   狀態值
   1  0  1  → 5
   1  0  0  → 4         每個箭頭 = 60° 電氣角
   1  1  0  → 6         反轉就是同一序列倒著走
   0  1  0  → 2         → 看「下一個狀態」就知道方向
   0  1  1  → 3
   0  0  1  → 1
   (回到 5,循環)

解析度換算(關鍵):6 個狀態是「每電氣圈」;機械一圈 = 電氣圈數 × 極對數。輪轂馬達極對數多(常見 15 對),所以:

每機械圈狀態數 = 6 × 15 = 90 → 角解析度 = 360°/90 = 4°
輪徑 15cm → 一格 ≈ 5.2mm 地面距離

對比 §1.5:這就是「霍爾 = 低解析度編碼器」的由來——做速度閉迴路夠用(中高速時),做精細 odometry 偏粗,所以講究的方案會在輪轂馬達內加裝磁編碼器

它的雙重身分:同一組訊號,既給驅動器做換相(知道該給哪兩相通電),又可當編碼器(數狀態變化次數 = 角度;量狀態間隔時間 = 速度)。

11.2 接線:先弄清楚訊號從哪裡出來

輪轂馬達引出線通常是「3 粗 + 5 細」:

3 條粗線:U / V / W 三相動力線 → 接 FOC 驅動器功率端
5 條細線:VCC(常見 5V)、GND、HA、HB、HC → 訊號線

架構決定接到哪:

方案 霍爾線接到 STM32 怎麼拿到速度
買現成 FOC 驅動器(本專案建議) 接驅動器,它自己用來換相+測速 走 CAN 讀驅動器回報的轉速/位置——STM32 根本不直接接霍爾線
自研驅動 / 額外獨立測速 接 STM32 下述兩種讀法

以下 §11.3 適用於第二種情境,或單純理解原理。

11.3 STM32F4 硬體連接

輪轂馬達霍爾線              STM32F407
┌─────────┐                ┌──────────────┐
│ VCC ────┼── 5V           │              │
│ GND ────┼── GND ─────────┤ GND          │
│ HA  ────┼──┬─[10kΩ]─3.3V─┤ PB6 (TIM4_CH1)│
│ HB  ────┼──┼─[10kΩ]─3.3V─┤ PB7 (TIM4_CH2)│
│ HC  ────┼──┼─[10kΩ]─3.3V─┤ PB8 (TIM4_CH3)│
└─────────┘  └ 上拉電阻      └──────────────┘

接線要點:

  1. 上拉電阻必要:霍爾開關輸出幾乎都是 open-collector / open-drain(只能拉低,不能推高),不上拉就永遠讀到不定值。上拉到 3.3V(即使霍爾供電 5V,open-drain 輸出上拉到 3.3V 就安全;若該霍爾板是推挽 5V 輸出,則要確認所用腳位為 5V-tolerant)。
  2. 訊號線跟三相動力線分開走線/隔開,動力線的 PWM 開關噪聲很容易耦合進來;必要時霍爾線用遮蔽線。
  3. 三個訊號接到同一顆 Timer 的 CH1/CH2/CH3(如 TIM4 的 PB6/PB7/PB8),才能用下面的硬體霍爾介面模式。

11.4 軟體讀法(兩種)

方法 A:Timer 霍爾感測器介面模式(硬體支援,建議)

STM32 Timer 有專門的 Hall sensor interface:把 CH1/CH2/CH3 三路訊號硬體 XOR 成一路接到內部 TI1,任何一顆霍爾翻轉 → 產生一次 capture 事件 + 計數器自動歸零:

HA ─┐
HB ─┼─ XOR ──► TI1 ──► 每次任一訊號翻轉:
HC ─┘                  ① capture 寄存器 = 距離上次翻轉的時間 → 直接得到速度
                       ② counter 歸零,重新計時

方法 B:三支 GPIO + EXTI 中斷(簡單直觀)

三支腳都設「雙緣觸發 EXTI」,中斷裡讀出 3-bit 狀態,查表:

// 正轉序列 5→4→6→2→3→1,做成查找表
static const int8_t hall_dir[8][8] = { /* [prev][curr] = +1 / -1 / 0(非法跳變) */ };

void EXTI_IRQHandler(void) {
    uint8_t curr = (HA << 2) | (HB << 1) | HC;
    position += hall_dir[prev][curr];   // 累積 count → odometry
    prev = curr;                        // 非法跳變(跳 2 格)→ 記異常計數
}

取捨:方法 B 好懂、好移植,但高轉速時中斷頻率 = 6 × 極對數 × 轉速(15 對極、300 RPM → 每輪每秒 2700 次中斷),兩顆輪子加起來 CPU 負擔可觀;方法 A 把計時歸零全交給硬體,CPU 只在需要時讀結果。正式韌體用方法 A,快速驗證接線用方法 B。

11.5 與本專案架構的對應

再強調一次邊界:採用「現成 FOC 驅動器 + CAN」的建議架構下,霍爾線接驅動器,STM32 不碰這五條線——轉速與累積位置由驅動器經 CAN 回報,STM32 拿來做運動學正解與 odometry 即可。本節的價值在於:(1) 看懂驅動器規格書裡的霍爾參數(極對數、霍爾角度 120°/60°);(2) 現場除錯時能用三用電表/邏輯分析儀直接驗證霍爾訊號;(3) 若日後自研驅動或加裝獨立編碼器,知道從哪裡下手。


12. 功率橋與閘極驅動器:電池直流電如何變成推馬達的三相電

12.1 要解決的問題

電池給的是固定的直流 24V;但 BLDC 三相線圈需要的是輪流通電、方向會換、大小可調的電流(§2.2 FOC 就是在指揮這件事)。中間做轉換的硬體就是功率橋,指揮它的訊號放大器就是閘極驅動器。

12.2 MOSFET:電控的大電流開關

先理解單顆元件。MOSFET 有三支腳:

        D (Drain 汲極)
        │
  G ──┤├    G (Gate 閘極):控制端,加電壓 → D-S 之間導通
        │      像繼電器的線圈,但每秒可開關 2 萬次、無機械磨損
        S (Source 源極)

把它想成「電壓控制的繼電器」:Gate 對 Source 加約 +10V → D-S 導通(電阻僅數 mΩ,可流數十安培);Gate 歸零 → 斷開。它只工作在「全開/全關」兩態——半開會同時承受高電壓與大電流,瞬間燒毀,這也是後面所有設計約束的來源。

12.3 功率橋(三相橋 / 逆變器):6 顆 MOSFET 的排法

每相一對(上管 + 下管),共三組「半橋」,中點接出馬達三相線:

 電池 24V ──┬──────────┬──────────┬─────
            │          │          │
          [Q1 上管]   [Q3 上管]   [Q5 上管]
            │          │          │
            ├──► U 相  ├──► V 相  ├──► W 相 ──► 馬達三相線圈
            │          │          │
          [Q2 下管]   [Q4 下管]   [Q6 下管]
            │          │          │
 電池 GND ──┴──────────┴──────────┴─────

電流怎麼流(舉一例):開 Q1(U 上管)+ 開 Q4(V 下管),其餘全關:

電池+ → Q1 → U 相線圈 → 馬達內部 → V 相線圈 → Q4 → 電池−

電流從 U 進、V 出,定子產生一個方向的磁場。換開不同的管子組合,電流就走不同相、不同方向——六步換相就是依霍爾狀態(§11.1 的 6 個狀態)輪流切 6 種組合;FOC 則更進一步:6 顆管子都以 10–20kHz 的 PWM 高速開關,用「占空比」精細調出三相各自的等效電壓(SVPWM),配合線圈電感的天然濾波,流出近似弦波的平滑電流。

兩條鐵律:

  1. 同一半橋上下管絕不同時導通——否則電池正負極直接短路(shoot-through),瞬間炸管。所以上管關閉到下管開啟之間要插入「死區時間 (dead time)」,約數百 ns。
  2. PWM 開得越慢(切換過渡期越長),MOSFET 停留在「半開」的時間越久 → 切換損耗越大、越燙。所以要「開得快」——這正是閘極驅動器存在的理由。

12.4 閘極驅動器:為什麼 MCU 不能直接推 MOSFET

STM32 輸出的 PWM 是 3.3V、約 20mA 的小訊號,直接接 MOSFET 的 Gate 有三個過不去的坎:

坎 1:Gate 是一顆電容,快速開關需要安培級瞬間電流。 MOSFET 的 Gate 等效於數十 nC 的電容,要在 ~100ns 內充滿(快速開關),瞬間電流 I = Q/t ≈ 50nC/100ns = 0.5–2A。MCU 腳位的 20mA 只能慢慢充 → 切換過渡期拖長 → 切換損耗暴增 → MOSFET 過熱。閘極驅動器內部就是一對大電流推挽級,專門做這個「瞬間灌電流」。

坎 2:上管的 Gate 電壓必須比電池電壓還高。 N-MOSFET 導通條件是「Gate 比 Source 高 10V」。下管的 Source 接地,給 10V 就好;但上管的 Source 是相線中點,上管導通時中點電位 ≈ 24V,所以 Gate 要到 ~34V——比系統裡任何電源都高。閘極驅動器用「自舉電路 (bootstrap):下管導通時偷偷把一顆電容充到 10V,等上管要開時,把這顆電容『墊』在中點電位上面」產生這個懸浮的高壓。這是 MCU 物理上不可能直推上管的根本原因。

坎 3:保護與時序。 死區時間插入、欠壓鎖定(驅動電壓不足時拒絕半開 MOSFET)、過流偵測(去飽和偵測)、故障回報——§9.2 的「智慧型閘極驅動器」就是把這些保護全部做進晶片。

12.5 整條鏈合起來看

STM32/控制晶片        閘極驅動器              功率橋              馬達
 3.3V PWM ×6  ──►  電平轉換+放大+自舉  ──►  6 顆 MOSFET    ──►  三相線圈
 (決定「何時開      (把指令變成能真正    (把 24V 電池切成
  哪顆、開多久」)     推動 Gate 的訊號)     三相 PWM 波形)
   「大腦」            「神經+肌腱」           「肌肉」

回扣 §9 的整合光譜:這條鏈的每一段都可以「買整合的」——smart gate driver = 中段整合保護;integrated FET = 中段+右段做進同一顆晶片;FOC 驅動器整機 = 整條鏈裝盒、對外只剩 CAN 介面。本專案買整機,但看懂這條鏈,規格書上的「MOSFET 內阻」「死區時間」「最大相電流」「過流保護閾值」就都有了著落。


13. Open-drain 是什麼:數位輸出的兩種電路形式

13.1 先看「正常」的輸出:推挽 (push-pull)

數位晶片要輸出 0 或 1,腳位內部其實是兩顆電晶體,一顆負責「推高」、一顆負責「拉低」:

推挽輸出 (push-pull):
        VCC
         │
       [上管] ──┐
                ├──► 輸出腳:輸出 1 → 上管導通,主動推到 VCC
       [下管] ──┘            輸出 0 → 下管導通,主動拉到 GND
         │
        GND

兩種狀態都「有力氣」——高就是實實在在的 VCC,低就是實實在在的 GND。STM32 GPIO 預設就是這種。

13.2 Open-drain:把上管拿掉

Open-drain(開漏;BJT 版本叫 open-collector 開集)= 只有下管,沒有上管:

Open-drain 輸出:
        (沒有上管!)
                ┌──► 輸出腳:輸出 0 → 下管導通,拉到 GND(有力氣)
       [下管] ──┘            輸出 1 → 下管關閉 → 腳位「浮空」(沒力氣)
         │                              = 不接任何東西,電位不定
        GND

「drain」就是那顆 MOSFET 下管的汲極,「open」= 它沒接負載、開放著,等外部電路決定。所以:

13.3 上拉電阻:幫它補上「高」的力氣

解法就是外接一顆電阻到電源(上拉電阻 pull-up):

            3.3V
             │
           [10kΩ] 上拉電阻
             │
  輸出腳 ────┴────► 接收端 (STM32 GPIO)

  下管導通(輸出 0):腳位被拉到 GND(下管力氣 >> 10kΩ) → 讀到 0
  下管關閉(輸出 1):沒人拉低,電阻把腳位「拎」到 3.3V    → 讀到 1

這就是 §11.3「霍爾線必須上拉,否則永遠讀到不定值」的完整原因:霍爾開關內部只有那顆下管。

電阻值的取捨:太大(100kΩ)→ 拉高速度慢(跟線路電容形成 RC 延遲)、抗噪差;太小(1kΩ)→ 每次拉低都流較大電流、耗電。4.7k–10kΩ 是常用甜蜜點

13.4 為什麼要「故意」設計成 open-drain?三個經典理由

  1. 電平轉換免費送:輸出高電平由「上拉接到哪」決定,跟晶片自己的供電無關。5V 供電的霍爾開關,上拉到 3.3V,輸出擺幅就是 0–3.3V——直接安全接 STM32,不用電平轉換晶片。這正是 §11.3 接線圖把上拉接 3.3V 的原因。
  2. 多裝置共享一條線不打架 (wired-AND):推挽輸出不能並聯——一顆推高、一顆拉低 = 短路。open-drain 並聯沒事:誰都只能拉低,「任一裝置拉低 → 線就是低」。I²C 匯流排、多裝置共享的中斷線/故障線都靠這個特性。
  3. 故障安全語意:斷線、裝置斷電時,線被上拉電阻維持在「高」——所以慣例把「高 = 正常、低 = 警報」,斷線會自然表現為可偵測的異常而不是假正常。

13.5 STM32 端的對應設定

情境 GPIO 模式
讀霍爾(外部已加上拉電阻) Input(浮空即可)
讀霍爾(板上沒空間放電阻) Input + 內部上拉(STM32 內建 ~40kΩ 弱上拉,訊號乾淨時可頂用;馬達旁噪聲環境建議仍用外部 10kΩ 強上拉)
STM32 自己當 open-drain 輸出(如 I²C) Output open-drain + 外部上拉

延伸:§6 的 CAN 匯流排「顯性/隱性」電平機制,本質上就是同一個 wired-AND 思想的差分版——任何節點都能把匯流排打成顯性(0),沒人發送時回到隱性(1),這是 CAN 硬體仲裁(ID 小者勝出)的物理基礎。


14. STM32 的 open-drain 程式控制

14.1 硬體背景:每支 GPIO 都內建兩種輸出電路

STM32 每支 GPIO 腳位內部同時做了推挽和開漏電路,用暫存器選擇用哪種。由三個暫存器決定行為:

暫存器 作用 對應 HAL 欄位
MODER 模式:輸入 / 輸出 / 複用功能(AF) / 類比 .Mode 的 INPUT/OUTPUT/AF
OTYPER 輸出型式:0 = 推挽 (PP),1 = 開漏 (OD) .Mode_PP / _OD 後綴
PUPDR 內部上拉 / 下拉 / 無 .Pull

HAL 把前兩者合併成一個 .Mode 常數,命名規則一看就懂:

GPIO_MODE_OUTPUT_PP   一般輸出,推挽
GPIO_MODE_OUTPUT_OD   一般輸出,開漏      ← 軟體直接控制的 open-drain
GPIO_MODE_AF_PP       交給外設,推挽      (如 TIM PWM、USART)
GPIO_MODE_AF_OD       交給外設,開漏      (如 I²C 的 SCL/SDA)
GPIO_MODE_INPUT       輸入(讀霍爾用這個)

14.2 場景一:讀霍爾訊號(輸入 + 上拉)

open-drain 是「對方」(霍爾開關),STM32 只是讀;要控制的是上拉:

GPIO_InitTypeDef g = {0};
g.Pin  = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8;   // PB6/7/8 = HA/HB/HC
g.Mode = GPIO_MODE_INPUT;
g.Pull = GPIO_PULLUP;        // 內部 ~40kΩ 弱上拉;板上有外部 10kΩ 時設 GPIO_NOPULL
HAL_GPIO_Init(GPIOB, &g);

uint8_t hall = (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) << 2)
             | (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) << 1)
             |  HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8);   // 組成 §11 的 3-bit 狀態

14.3 場景二:STM32 自己當 open-drain 輸出

GPIO_InitTypeDef g = {0};
g.Pin   = GPIO_PIN_5;
g.Mode  = GPIO_MODE_OUTPUT_OD;     // 開漏輸出
g.Pull  = GPIO_NOPULL;             // 通常靠外部上拉電阻
g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &g);

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 寫 0 → 下管導通,拉低(有力氣)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);   // 寫 1 → 下管關閉,「放手」

關鍵語意差異:寫的 API 跟推挽一模一樣,但「寫 1」的意思變了——

  推挽寫 1 開漏寫 1
實際動作 主動推到 VCC 只是放手 (release),電平由外部上拉決定
腳位電壓 一定是 3.3V 上拉接 5V 就是 5V,沒上拉就浮空

兩個實用推論:

  1. 電平轉換:F4 多數腳位是 5V-tolerant(查 datasheet 的 FT 標記),設成 OD + 外部上拉到 5V,就能輸出 0–5V 訊號給 5V 裝置——軟體一行不用改。
  2. 讀回驗證:開漏腳位寫 1 後讀 IDR(輸入暫存器),若讀到 0 表示線上有別人在拉低——I²C 仲裁與匯流排卡死偵測就用這招。

14.4 場景三:交給外設的 open-drain(I²C 接 IMU)

I²C 協議規定 SCL/SDA 必須開漏(§13.4 的 wired-AND:多裝置共享、slave 可以 clock-stretching 拉住時脈)。腳位設 GPIO_MODE_AF_OD 後,控制權交給 I2C 外設,程式裡操作的是 I²C API 而不是 GPIO:

g.Pin       = GPIO_PIN_8 | GPIO_PIN_9;   // PB8=SCL, PB9=SDA
g.Mode      = GPIO_MODE_AF_OD;           // 複用功能 + 開漏(I²C 硬性要求)
g.Pull      = GPIO_NOPULL;               // I²C 一定用外部上拉(2.2k–4.7kΩ)
g.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &g);
// 之後用 HAL_I2C_Mem_Read() 等 API 讀 IMU,GPIO 層不再碰

常見錯誤:把 I²C 腳位誤設成 AF_PP → 兩個裝置對推短路,或 slave 無法拉低 → 表現為「I²C 永遠 timeout / NACK」,硬體看起來都接對了。除錯 I²C 先檢查 OD + 上拉電阻。

14.5 暫存器層(理解 HAL 在做什麼)

GPIOA->MODER  |=  (0b01 << (5*2));   // PA5 設為輸出
GPIOA->OTYPER |=  (1 << 5);          // 1 = open-drain(0 = 推挽)
GPIOA->BSRR    =  (1 << 5);          // 寫 1(放手)
GPIOA->BSRR    =  (1 << (5+16));     // 寫 0(拉低);BSRR 高 16 位是 reset 位

OTYPER 一個 bit 就是推挽/開漏的全部差別——印證 §14.1:兩種電路都在腳位裡,程式只是選用哪一種。


15. 為什麼開漏是「留下管」而不是「留上管」

§13 說 open-drain 是把推高的上管拿掉。鏡像的設計——留上管、拿掉下管、外部接下拉電阻——叫 open-source(開源極),物理上完全成立,但邏輯訊號幾乎沒人用。原因有三層。

15.1 電晶體物理:拉低的 NMOS 天生比推高的好做

MOSFET 分 N 通道 (NMOS) 和 P 通道 (PMOS) 兩種:

  NMOS(適合放下面拉低) PMOS(適合放上面推高)
載子 電子,遷移率高 電洞,遷移率約只有電子的 1/2–1/3
同樣導通電阻的晶片面積 大 2–3 倍(= 貴)
閘極驅動 Source 接 GND,給 +V 就開,最簡單 需要相對 VCC 的負向驅動

想用 NMOS 放上面推高?那就回到 §12.4 的坎 2:Gate 要比 Source 高,需要自舉電路——為一支邏輯腳位加 bootstrap 完全不划算。所以「一顆接地的 NMOS 拉低」是最小、最便宜、驅動最簡單、導通電阻最低的開關,開漏設計剛好只需要這一顆。

15.2 系統觀點:GND 是大家共有的,VCC 不是

這是最關鍵的理由。一塊板子上 3.3V、5V、12V 各種電源域並存,但 GND 全系統共用:

多裝置共線的邏輯也一樣:開漏是 wired-AND(任一裝置拉低 → 線為低),配「低 = 事件/警報」的慣例;開源變成 wired-OR 且高電平互推,不同 VCC 時誰推贏都是問題。

15.3 歷史慣性:open-collector 時代就定了

MOSFET 之前的 BJT 時代同樣不對稱:NPN(對應拉低)比 PNP(對應推高)增益高、速度快、便宜,所以 TTL 時代的共享匯流排就是 open-collector(NPN 拉低)+ 上拉。後來的 I²C、SMBus、中斷線、各種感測器輸出全部沿用這個慣例,生態系(上拉電阻的預設、「低有效」的訊號命名 nFAULT/nINT)都建立在它上面。

open-source 並非不存在:汽車電子的高邊開關(high-side switch,負載一端固定接地、由上面開關供電)就是這個結構——但那是「給負載供電」的功率場景,不是邏輯訊號。邏輯訊號的世界裡,開漏 + 上拉是唯一主流。

一句話總結:下管便宜好驅動(物理),GND 全系統共用而 VCC 各自為政(系統),五十年生態沿用(歷史)——三個理由都指向同一個設計。


16. 定子與轉子:命名由來、定義、與 Park 變換的關係

16.1 命名由來:就是字面意思

中文 英文 字源 定義
定子 stator 拉丁文 stare(站立不動),同字根:static、stationary 馬達裡固定不動的部分,鎖在外殼/機座上
轉子 rotor 拉丁文 rotare(旋轉),同字根:rotate、rotation 馬達裡跟著軸一起轉的部分

命名只描述「動不動」,不規定上面裝什麼——這是最容易混淆的地方:

馬達類型 定子上是什麼 轉子上是什麼
有刷直流馬達 永久磁鐵 線圈(靠碳刷+換向器供電)
BLDC / PMSM 三相線圈 (U/V/W) 永久磁鐵
感應馬達(工業 AC) 三相線圈 鼠籠導體(無磁鐵)

BLDC 的設計邏輯:把需要供電的線圈放在不動的定子上(導線直接拉出來,不需要碳刷),讓不用接線的磁鐵去轉——這正是「無刷」的由來(對照 §1.4)。

輪轂馬達是「外轉子」結構,跟直覺相反:定子(線圈)固定在輪軸中心,轉子(磁鐵)做在外殼上、連著輪框——轉的是外面那圈,§1.3 說「輪子本身就是馬達」就是這個意思。內轉子(轉軸在中心轉)反而是一般馬達的長相。

16.2 定子的具體構造(BLDC)

定子(不動,固定在輪軸上):
   矽鋼片疊成的鐵芯,開槽,繞上三組線圈 U/V/W
   三組線圈在圓周上每隔 120°(電氣角)分布
   §11 的三顆霍爾開關也嵌在定子上
   §12 功率橋的三條輸出線,接的就是定子的 U/V/W

轉子(轉動,= 輪框):
   一圈永久磁鐵(N/S 交替排列,§11.1 的「極對數」就是數它)

通電的因果鏈:功率橋給定子三相線圈輪流通電 → 定子產生旋轉的磁場 → 轉子磁鐵被這個磁場拖著追 → 輪子轉。注意:磁場在轉,但產生磁場的線圈本身不動——「不動的東西產生會轉的場」,這個圖像是理解下一節的鑰匙。

16.3 Park 變換裡的「定子」:指的是座標系

§2.2 的 FOC 流程裡,「定子」「轉子」指的是兩個參考座標系:

定子座標系(不動)                轉子座標系(跟著轉)
  a-b-c:三相線圈的三個軸          d-q:釘在轉子磁鐵上
  (物理上釘在定子上,永遠不動)      d 軸 = 磁鐵 N 極方向
        │                          q 軸 = 垂直 d 軸(出扭矩的方向)
        │ Clarke 變換:3 軸 → 2 軸(α-β,仍在定子系,仍不動)
        ▼
       α-β ── Park 變換:用轉子當下的角度 θ,把座標系「轉過去」──► d-q

為什麼要轉過去:站在定子(不動的觀察者)看,線圈電流是交流弦波——因為磁場在你面前轉,你量到的東西永遠在振盪,PI 控制器很難追交流目標。Park 變換等於跳上轉子一起轉:磁場相對你靜止了,原本的交流量變成兩個直流量(iq = 扭矩分量、id = 無用分量),控制問題瞬間變簡單。

直觀類比:站在月台看旋轉木馬,每匹馬的位置都在週期變化(交流);跳上轉盤,馬就停在你旁邊不動了(直流)。Park 變換就是「跳上轉盤」這個動作,需要的唯一資訊是轉盤當下轉到哪(轉子角度 θ)——這就是 §2.2 說 FOC 必須即時知道轉子角度的原因,也是霍爾/編碼器(§11)在 FOC 裡的另一重身分。

16.4 串起來:一張對照表

概念 物理意義 在 FOC 數學裡
定子 不動的線圈 + 鐵芯 a-b-c / α-β 座標系(靜止參考系)
轉子 轉動的永久磁鐵 d-q 座標系(旋轉參考系),d 軸 = 磁鐵方向
轉子角度 θ 磁鐵當下指向 Park 變換的旋轉角,來自霍爾/編碼器
定子電流 功率橋灌進 U/V/W 的電流 被變換的對象;在 d-q 系裡分解成 id、iq

17. 有刷 vs 無刷直流馬達(附圖)

17.1 兩個共同前提

所有直流馬達轉動的原理相同:線圈通電產生磁場 → 與永久磁鐵互相吸斥 → 產生扭矩。 但有個物理難題:線圈轉到磁鐵正前方(對齊)後,吸力就變成「定住它」的力——想持續轉,就必須在對的時機把線圈電流換向,讓「追逐」永遠進行。這個動作叫換相 (commutation)

有刷和無刷的全部差別,就是換相用什麼做:機械接觸,還是電子開關。

17.2 有刷馬達:機械換相(結構剖面)

            外殼 = 定子(永久磁鐵固定在殼上)
        ┌───────── N ─────────┐
        │                     │
        │      ┌───────┐      │     轉子:線圈繞在鐵芯上,
        │      │ 線圈   │      │           跟著軸一起轉
        │      │ +鐵芯  │═══╗  │
        │      └───┬───┘   ║軸 │
        │      ┌───┴───┐   ║   │
        │      │ 換向器 │═══╝  │     換向器:銅片做的圓筒,
        │      └─┬───┬─┘      │           分成數瓣,跟著轉
        │   碳刷▕█   █▏碳刷    │     碳刷:固定不動,用彈簧
        │        │   │        │           壓在換向器上滑動接觸
        └────────┼─S─┼────────┘
                 │   │
              電池+  電池−

電流路徑:電池+ → 碳刷 → 換向器銅片 → 轉子線圈 → 另一瓣銅片 → 另一支碳刷 → 電池−。

換相怎麼發生:換向器跟著轉子轉,每轉半圈,碳刷接觸到的銅片就換成另一瓣——線圈電流方向自動反轉。換向器本質是一顆「跟著轉子連動的機械式換向開關」,時機由幾何結構保證,完全不需要任何電子電路。

所以有刷馬達接上直流電就會轉——這是它最大的優點,也是「直流馬達」這名字的原始由來。

17.3 無刷馬達 (BLDC):電子換相(結構剖面)

            外殼(內轉子型)
        ┌─────────────────────┐
        │   U線圈              │     定子:三相線圈 U/V/W
        │  ┌──────────┐  ↑    │           固定不動(§16)
        │  │ 轉子:     │ W線圈 │
        │  │ 永久磁鐵  │═══╗   │     轉子:永久磁鐵,
        │  │  N / S   │   ║軸 │           沒有任何接線!
        │  └──────────┘═══╝   │
        │   V線圈    ▪霍爾×3   │     霍爾:嵌在定子上
        └──────┬──┬──┬────────┘           回報磁鐵位置(§11)
               U  V  W
               │  │  │
        ┌──────┴──┴──┴──────┐
        │  功率橋(6 MOSFET)  │  ← 換相在這裡發生(§12)
        │  + 控制晶片/MCU     │     依霍爾狀態決定哪兩相通電
        └────────┬───────────┘
               電池 DC

線圈和磁鐵的位置對調了(§16.1):要供電的線圈放到不動的定子上(導線直接拉出,不再需要滑動接觸),磁鐵去轉。但這樣一來換向器也沒了——換相改由控制電路做:霍爾感測器回報「磁鐵現在轉到哪」,MCU/驅動器據此切換功率橋,給對的相通電。

對應關係一句話:碳刷+換向器 被 霍爾+MOSFET+控制程式 取代——機械零件換成電子零件,磨損消失,代價是必須多一套驅動電路,馬達不能直接接電池。

17.4 差異總表

  有刷 DC 無刷 BLDC
換相方式 機械(碳刷+換向器) 電子(霍爾+功率橋)
線圈位置 轉子(轉) 定子(不動)
磁鐵位置 定子(不動) 轉子(轉)
直接接電池 ✓ 就會轉 ✗ 必須有驅動器
壽命瓶頸 碳刷磨損(數百~數千小時) 只剩軸承(數萬小時)
火花/EMI 碳刷跳火,粉塵+干擾 無火花(但 PWM 有開關噪聲)
噪音 機械摩擦聲 安靜(配 FOC 更安靜)
效率 中(碳刷壓降+摩擦損耗) 高 5–15%
維護 定期換碳刷 免維護
成本結構 馬達便宜,零驅動成本 馬達+驅動器,系統較貴
控制精細度 調電壓調速,粗 電流/速度/位置閉迴路,細
典型應用 玩具、電動工具、汽車雨刷 無人機、AMR、電動車、風扇硬碟

17.5 為什麼送餐機器人必然選 BLDC

對照表逐項映射到場景:免維護(餐廳不會排程換碳刷)、安靜(用餐環境)、無火花粉塵(食品場域)、壽命(每天 8–12 小時連續運轉)、控制精細(低速平穩不灑湯,§2.1)。有刷的唯一優勢「便宜+不用驅動器」,在這些需求前不構成選項——多出來的驅動器成本,正是 §9/§12 那整條功率電子鏈的價值。


18. 電壓法規與日台送餐機器人標準

本節為方向性整理,標準版本與強制範圍會更新;實際出貨認證以檢測機構與主管機關當下公告為準

18.1 「24V 免高壓法規」的精確說法

之前 §1.4 說 24V 是「安全特低電壓」,精確的層次是:

層次 分界 24V 機器人的位置
SELV / ES1(產品安全標準,IEC 62368-1 / 60950-1) ≤60V DC(穩態)、42.4V AC peak ✓ 24V(含 48V)落在內:單一故障下不構成觸電危險,絕緣/防護設計要求大幅簡化
日本「低圧/高圧」(電気設備技術基準) 低圧:AC ≤600V、DC ≤750V;高圧:其上至 7kV 24V 遠低於任何分界
台灣「低壓/高壓」(用戶用電設備裝置規則體系) 600V 以下為低壓;台電高壓供電 3.3kV 起 同上

所以正確理解是:24V 系統屬 SELV 範圍,觸電風險面的法規負擔極輕;但「免高壓法規」≠ 免一切——以下項目照樣要做:

18.2 日本:服務機器人標準體系(較成熟)

18.3 台灣:無專屬強制標準,走「零組件強制 + 整機自願」

18.4 對專案的實際意義

  1. 設計階段就鎖 24V/48V SELV,整機觸電面的合規成本幾乎歸零;升到 60V 以上是法規等級跳變,不要為了馬力輕易跨過。
  2. BOM 上充電器選已有 BSMI/PSE 認證的現成品、無線模組選已有 NCC/技適的模組(模組認證可繼承),自己只剩整機 EMC 要過。
  3. 若目標含日本市場,從機構設計初期就對照 JIS B 8445/8446 的要求(防夾、速度限制、緊急停止、穩定性)比事後補便宜一個數量級。

Sources: JQA ISO 13482 服務JIS B 8445:2016JET ロボット認証ISO 13482 (Wikipedia JA)SELV 定義日本電壓種別BSMI 認證概要NCC 認證概要


19. Encoder 圖解:增量式 A/B 相如何運作

19.1 光學增量式編碼器的構造

側視:
                    碼盤(跟著馬達軸轉)
   LED 光源 ──────→ ░█░█░█░█░ ──────→ 光電接收器 ×2(A、B)
                    │
            圓盤邊緣刻滿等距的「透光縫 / 遮光齒」
            轉動時:透光→遮光→透光… 接收器輸出方波

正視碼盤:
        ╱▔▔▔╲          █ = 遮光
      ▕ █░█░█ ▏        ░ = 透光縫(例:1024 縫/圈)
      ▕░█ ⊙ █░▏        ⊙ = 軸心
      ▕ █░█░█ ▏        外圈還可有一條獨立的 Z 相縫:
        ╲▁▁▁╱           每圈只觸發一次,當「歸零記號」

磁編碼器原理相同,只是把「光+碼盤」換成「磁鐵+磁感應晶片」,抗油污粉塵(輪轂馬達內建的就是這種);霍爾(§11)則可視為極粗的磁編碼器。

19.2 為什麼要兩個訊號(A/B 相)?——方向

A、B 兩個接收器在空間上錯開 1/4 個縫距,所以輸出方波相位差 90°:

正轉(A 領先 B):                 反轉(B 領先 A):
A: ──┐  ┌──┐  ┌──┐  ┌──        A: ──┐  ┌──┐  ┌──┐  ┌──
     └──┘  └──┘  └──┘               └──┘  └──┘  └──┘
B: ────┐  ┌──┐  ┌──┐  ┌─       B: ┐  ┌──┐  ┌──┐  ┌────
       └──┘  └──┘  └──┘            └──┘  └──┘  └──┘
   在 A 的上升緣看 B:B=0           在 A 的上升緣看 B:B=1
        → 判定正轉                      → 判定反轉

只有一相只能數「轉了多少」,不知道往哪轉;90° 相位差就是方向資訊

19.3 ×4 解碼:解析度免費變四倍

一個縫距內,A/B 共有 4 個邊緣(A↑、B↑、A↓、B↓),每個邊緣都計一次數:

A/B 組合循環:  00 → 10 → 11 → 01 → 00 …(正轉)
                00 → 01 → 11 → 10 → 00 …(反轉)
每換一格 = 1 count → 1024 縫/圈 × 4 = 4096 counts/圈

規格書寫「1024 PPR (pulses per revolution)」,經 ×4 解碼後實際可用 4096 CPR (counts per revolution)——§10.3 粗算用的 4096 就是這麼來的。

19.4 增量式 vs 絕對式(知道差別即可)

  增量式(A/B/Z) 絕對式(§10 伺服那種 17-bit)
開機時知道角度嗎 ✗ 只知道「之後轉了多少」 ✓ 每個角度有唯一編碼
訊號 兩路方波 數位介面(SSI/BiSS)
價格
底盤適用 ✓ 速度環+odometry 都只需要「增量」 不必要

底盤只關心轉速與位移增量,開機絕對角度無所謂——所以增量式就是正解。


20. Encoder 回授與下位機:STM32 程式實例

20.1 回授迴路全貌(資料在下位機裡怎麼流)

上位機 ──UART──► 目標速度 (v, ω)
                    │ 運動學逆解 (§7)
                    ▼
              目標輪速 ωL*, ωR*
                    │
              ┌─── PID ───┐ 100Hz 控制任務
              │     ▲      │
              ▼     │      ▼
        輸出命令    │   encoder 實測輪速 ◄── TIM encoder mode 硬體計數
        (CAN→驅動器)│                              ▲
              │     └──────────────────────────────┤
              ▼                                    │
            馬達 ──轉動──► 編碼器 ──A/B 方波────────┘
                    │
                    └─► odometry 積分 (x, y, θ) ──UART──► 上位機

encoder 在這個迴路裡身兼兩職:對內當 PID 的回授(快迴路,留在下位機),對外供 odometry(慢資料,上報上位機)。這就是「encoder 回授跟 STM32 的關聯」的全貌。

20.2 第一步:讓硬體自己數,CPU 不介入

STM32 Timer 的 encoder mode 把 §19.3 的 ×4 解碼做在硬體裡:A/B 接同一顆 Timer 的 CH1/CH2,計數器 CNT 自動加減(正轉加、反轉減),CPU 完全不用處理每個脈衝:

// 左輪 encoder:TIM3,A 相 = PA6 (CH1),B 相 = PA7 (CH2)
void Encoder_Init(void)
{
    TIM_Encoder_InitTypeDef enc = {0};
    htim3.Instance        = TIM3;
    htim3.Init.Period     = 0xFFFF;            // 16-bit 計數器讓它自由迴繞
    enc.EncoderMode       = TIM_ENCODERMODE_TI12; // A/B 雙緣 ×4 解碼
    enc.IC1Filter         = 6;                 // 數位濾波去抖(馬達旁必加)
    enc.IC2Filter         = 6;
    enc.IC1Polarity       = TIM_ICPOLARITY_RISING;
    enc.IC2Polarity       = TIM_ICPOLARITY_RISING;
    HAL_TIM_Encoder_Init(&htim3, &enc);
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}

20.3 第二步:100Hz 控制任務(回授真正發生的地方)

#define DT            0.01f                    // 100Hz 控制週期
#define CPR           4096.0f                  // counts/圈(§19.3)
#define RAD_PER_CNT   (2.0f * PI / CPR)

static uint16_t last_cnt;
static float    integ;                         // PID 積分項

void ControlTask_100Hz(void)                   // 由 FreeRTOS 週期任務或定時中斷呼叫
{
    /* ① 讀增量:int16_t 強制轉型自動處理 0xFFFF→0 的迴繞 */
    uint16_t now   = __HAL_TIM_GET_COUNTER(&htim3);
    int16_t  delta = (int16_t)(now - last_cnt); // 兩次讀數之差 = 這 10ms 轉的 counts
    last_cnt = now;

    /* ② counts → 實測輪速 (rad/s):這就是「回授」本身 */
    float w_meas = delta * RAD_PER_CNT / DT;

    /* ③ PID:讓實測追上目標(目標來自上位機指令的運動學逆解) */
    float err = w_target - w_meas;
    integ += err * DT;
    integ  = CLAMP(integ, -INTEG_MAX, INTEG_MAX);   // 抗 windup(§7.3)
    float u = KP * err + KI * integ;                // 底盤 PI 就夠

    /* ④ 輸出:經 CAN 下給 FOC 驅動器(或自驅時換算 PWM)*/
    Driver_SetTorque(LEFT, u);

    /* ⑤ 同一份 delta 順便餵 odometry(§4.1 公式)*/
    odom_accumulate(delta /*left*/, delta_right);
}

逐行對應前面的概念:① 是 §19 的硬體計數收割;② 把「格數」換成物理量(§7.2 的單位換算鏈);③ 是 §7 說的「PID 追目標」;⑤ 是同一顆 encoder 的第二重身分。

20.4 兩個容易踩的坑

  1. 迴繞處理:(int16_t)(now - last_cnt) 這個慣用法,靠 16-bit 無號減法的模運算特性,即使 CNT 從 65535 跳回 0 也算出正確增量——前提是兩次讀數間轉動不超過 ±32767 counts(100Hz 下绝對夠)。寫成 if (now < last) ... 的手工判斷反而容易錯。
  2. 週期一致性:DT 必須真的是 10ms。若控制任務被高優先級中斷拖延,實測速度會抖、PID 跟著抖——這就是 §4.2 說「控制環必須固定週期」的具體原因。用硬體定時器觸發、或 FreeRTOS vTaskDelayUntil()(不是 vTaskDelay())保證節拍。

注意邊界:本專案建議架構(現成 FOC 驅動器)下,「速度環在驅動器裡、encoder 接驅動器」,STM32 收到的是 CAN 回報的轉速/位置——上面 ②③ 發生在驅動器內部,STM32 只做 ①⑤ 的 odometry 部分與更外層的邏輯。若自驅(§11.2 第二種情境),這段程式就完整跑在 STM32 上。理解這段程式,兩種架構都看得懂。


21. 2D SLAM 建圖流程(圖解)

21.1 問題本質:雞生蛋

兩者互相依賴,只能同時解——這就是 SLAM(Simultaneous Localization and Mapping)的字面意思。輸入是 LiDAR scan + odometry,輸出是占據柵格地圖 (occupancy grid):

占據柵格:把世界切成 5cm 格子,每格存「有障礙物的機率」

   ░░░░░░░░░░░░░    █ = 占據 (occupied):牆、桌腳
   ░█████████░░░    · = 空閒 (free):確認走得過
   ░█·······█░░░    ░ = 未知 (unknown):還沒看過
   ░█···◉───█░░░
   ░█···│···█░░░    ◉ = 機器人,─ = 雷射光束
   ░█████·███░░░         (門口缺口 = 還沒掃到)
   ░░░░░░·░░░░░░

這就是建圖完存下來的 map.pgm(影像)+ map.yaml(解析度、原點)。

21.2 建圖主迴圈:預測 → 對齊 → 刻畫

每收到一幀 LiDAR scan(約 10Hz)做三件事:

 ① odometry 預測          ② scan matching 修正        ③ 更新地圖
 「encoder 說我前進了      「把這幀 scan 跟已建好的     「沿著每條雷射光束:
   0.31m、右轉 2°」         地圖滑動/旋轉比對,          穿過的格子 → 標 free
        │                   找到最契合的位置」           端點那格   → 標 occupied」
        ▼                        │                          │
   位姿初猜(有漂移)──────────────┴──► 修正後位姿 ───────────┘

② 的圖像:                          ③ 的圖像(ray casting):
   地圖既有的牆: ████████              ◉────────────█
   新 scan 的牆:   ████████              └─光束路徑─┘└─ 端點:occupied
        ↑ 差半格?把 scan 平移過去        全標 free
          對齊 → 同時修正了位姿

關鍵理解:scan matching 是在「用地圖反過來修定位」——odometry 每步都有小誤差(§3.3 打滑),靠「這幀 scan 跟地圖對不上就挪到對得上」持續拉回來。這也解釋了為什麼 §4.1 說 odometry 標定品質決定 SLAM 上限:初猜偏太多,scan matching 會對到錯的位置上。

21.3 迴圈閉合 (loop closure):SLAM 真正的難點

scan matching 只能修小誤差,長走廊繞一大圈回來,累積誤差可能已經半公尺——這時地圖會「裂開」:

閉合前(誤差累積):                  閉合後(圖優化攤平誤差):
   ┌──────────┐                       ┌──────────┐
   │  起點█    │                      │  起點█    │
   │       ╲   │ 繞一圈回來,           │      │    │
   │        ╲  │ 算出的位置卻          │      │    │
   │   回來時█ │ 偏了 → 同一面牆       │  █←重合   │
   │   (偏移)  │ 畫成兩道平行牆!       │           │
   └──────────┘                       └──────────┘

graph-based SLAM(slam_toolbox 的做法)的處理:

pose graph:節點 = 歷史位姿,邊 = 相鄰位姿間的相對約束(odometry/scan matching)

  P1 ──► P2 ──► P3 ──► P4 ──► P5
   ▲                           │
   └────── loop closure 邊 ────┘
   「P5 的 scan 跟 P1 附近超像 → 你們其實在同一個地方」

加入這條邊後做全圖優化 (graph optimization):
把「P5 應該≈P1」造成的矛盾,按比例攤回 P2~P4 每一步 → 整圈軌跡微調 → 地圖拉直

21.4 實際建圖操作與品質要訣

ros2 launch slam_toolbox online_async_launch.py   # 邊遙控邊建圖
# 用 RViz 即時看地圖長出來,繞完場域後:
ros2 run nav2_map_server map_saver_cli -f restaurant_map
要訣 原因
慢速行駛(<0.5 m/s)、轉彎更慢 scan 間重疊多,matching 穩
刻意繞回起點/交叉路徑 製造 loop closure 機會,攤平誤差
挑離峰時段建圖(沒有人群) 行人會被刻進地圖變成假牆
桌椅就定位後再建圖 地圖反映「平常的樣子」,AMCL 比對才像
建完用 RViz 檢查牆是否筆直、有無雙重牆 雙重牆 = loop closure 失敗,重建

建圖是部署時做一次的離線工作(§3.3 的分工);日常營運跑的是下一節的 AMCL。


22. AMCL 定位演算法(圖解)

22.1 問題:已知地圖,我在哪?

AMCL = Adaptive Monte Carlo Localization。Monte Carlo = 用大量隨機樣本逼近答案;這裡的樣本叫粒子 (particle):

一個粒子 = 一個「機器人可能在這」的假設 = (x, y, θ, 權重w)
AMCL 同時養 500~2000 個粒子,合起來表示「位置的機率分布」

22.2 核心循環:三步驟不斷重複

        ┌─────────────────────────────────────────────┐
        ▼                                             │
 ① 預測 (motion update)                               │
    每個粒子按 odometry 移動,並各自加上隨機噪聲          │
    「車說它前進 0.3m → 每個粒子前進 0.3m ± 一點亂數」    │
        │                                             │
        ▼                                             │
 ② 量測更新 (measurement update)                       │
    每個粒子回答:「假如我真的在這,LiDAR 應該看到什麼?」  │
    跟實際 scan 比對 → 像 = 權重調高;不像 = 權重調低     │
        │                                             │
        ▼                                             │
 ③ 重採樣 (resampling)                                 │
    按權重抽籤複製下一代:高權重粒子被複製多份,           │
    低權重粒子被淘汰 → 粒子雲向「真實位置」收斂 ──────────┘

②的圖像——兩個粒子的比對:

實際 LiDAR 看到:左 1.0m 有牆、前 3.2m 有牆

粒子 A(假設在走道中間):          粒子 B(假設在角落):
 按地圖計算應看到:                 按地圖計算應看到:
 左 1.1m 牆、前 3.0m 牆            左 0.3m 牆、前 0.5m 牆
 → 跟實測很像 → 權重 ↑↑           → 完全不像 → 權重 ↓↓ (下輪淘汰)

整體收斂過程:

 t=0 不確定位置:          t=1 走了幾步後:        t=2 持續比對後:
 粒子撒滿全場              不像的被淘汰            粒子雲聚成一團
 ░·░·░·░·░·░              ░░░░░░░░░░░            ░░░░░░░░░░░
 ·░·░·░·░·░·              ░░··░░░··░░            ░░░░░▓░░░░░
 ░·░·░·░·░·░              ░░·░░░░·░░░            ░░░░▓◉▓░░░░  ← 加權平均
 ·░·░·░·░·░·              ░░░░·░░░░░░            ░░░░░▓░░░░░     = 定位輸出

22.3 「Adaptive」與幾個關鍵行為

機制 內容
自適應粒子數 (KLD sampling) 不確定時自動增加粒子(撒網),收斂後減少(省 CPU)——這就是 A 的由來
初始位姿 部署時通常給初始位姿(RViz 點一下 / API 設定),粒子只撒在附近,幾秒收斂;不給就是全域定位,要走一段路才收斂
綁架問題 (kidnapped robot) 機器人被人抬走再放下 → 粒子雲全在錯的地方;AMCL 靠「注入少量隨機粒子」有機會恢復,但慢——實務上提供「重設初始位姿」操作比較實際
與 odometry 的關係 ①步直接消費 §20 上報的 odometry;odometry 越準,粒子噪聲可調越小,定位越穩——又一次回扣 §4.1

22.4 餐廳場景的調參重點

人群是最大干擾:行人擋住 LiDAR 時,實測 scan 跟地圖必然不像,所有粒子權重一起被拉低。AMCL 的量測模型有對應參數:

z_hit  :量到的點跟地圖吻合的權重(主成分)
z_rand :「隨機雜訊」成分 → 調高一點,讓被行人擋住的光束
         被解釋成雜訊,而不是懲罰所有粒子
z_short:量到比地圖近(= 有東西擋在前面)的寬容度

實務組合:離峰建一張乾淨地圖(§21.4)+ 調高 z_rand/z_short 容忍動態遮擋 + odometry 標定紮實 → 人群中定位依然穩。這三件事分別對應 §21、本節、§4.1——導航品質從來不是單一模組的事。


23. 深度相機輸出什麼:RGB 圖與深度圖(圖解)

23.1 直覺接近正確,但要修正一個觀念

「輸出兩張圖,一張 RGB、一張灰階」——結構對,本質要修正:RGB-D 相機確實輸出兩路影像,但第二路不是灰階「照片」,而是深度圖 (depth image):每個像素存的不是亮度,是該點到相機的距離(通常 16-bit 整數,單位 mm)。看起來像灰階圖,是因為檢視工具把距離「畫成」深淺而已。

同一個場景(前方有一張桌子):

 RGB 圖(像素 = 顏色):              深度圖(像素 = 距離 mm):
┌────────────────┐                ┌────────────────┐
│  棕色  棕色  白牆 │                │ 1200 1210 3500 │ ← 桌面近、牆遠
│  棕色  棕色  白牆 │                │ 1230 1245 3500 │
│  灰地  灰地  灰地 │                │  800 1500 2900 │ ← 地板由近到遠漸變
└────────────────┘                └────────────────┘
 人眼看內容                          視覺化成灰階:近 = 亮、遠 = 暗
                                    (或彩虹色:近紅遠藍)
┌────────────────┐                ┌────────────────┐
│ ▒▒▒ ▒▒▒ ░░░    │                │ ███ ███ ░░░    │
│ ▒▒▒ ▒▒▒ ░░░    │                │ ███ ██▓ ░░░    │
│ ▓▓▓ ▓▓▓ ▓▓▓    │                │ ███ ▓▓▓ ▒▒▒    │
└────────────────┘                └────────────────┘

關鍵差異一句話:灰階照片量的是「反射多少光」,深度圖量的是「離我多遠」——前者是外觀,後者是幾何。

23.2 內部其實不只兩路:以結構光/主動雙目為例

相機模組正面:
 ┌──────────────────────────────────┐
 │  [IR 投射器]  [IR 相機L] [IR 相機R]  [RGB 相機] │
 └──────────────────────────────────┘
      │             │        │           │
      │ 打出紅外     └────┬───┘           └──► ① RGB 影像(給人看/AI 辨識)
      │ 點陣圖案          │
      │             比對左右視差(或圖案變形)
      │                  │
      └──────────────────┴──► 深度運算 ASIC ──► ② 深度圖(給避障)
                                              (③ 有些型號也輸出原始 IR 圖)

所以 ROS2 driver 起來後通常看到多個 topic:

/camera/color/image_raw            ① RGB
/camera/depth/image_rect_raw       ② 深度圖(16UC1,單位 mm)
/camera/depth/color/points         ④ 點雲(由②+內參計算而來)
/camera/aligned_depth_to_color/…   ⑤ 對齊到 RGB 視角的深度圖

23.3 兩個實用衍生觀念

(1) 深度圖 → 點雲:知道相機內參(焦距、光心)後,每個像素 (u, v, depth) 可反投影成 3D 座標 (X, Y, Z)——§3.2 說壓進 costmap 的點雲就是這樣來的。深度圖是「壓縮的 3D」,點雲是攤開後的形式。

(2) 對齊 (alignment/registration):RGB 鏡頭和深度鏡頭物理位置不同、視角略異,同一個像素座標在兩張圖裡不是同一個點。要做「這個像素是什麼顏色+多遠」(如行人偵測框出人再取距離)就需要對齊後的影像(上面的⑤)。純避障只用深度圖,不需要對齊。

23.4 無效值:深度圖特有的「破洞」

深度圖上會有量不到的像素(值 = 0),成因:玻璃/鏡面(IR 穿透或鏡射)、黑色吸光物、超出量程、左右相機只有一邊看到的遮擋邊緣:

深度圖實際長相(有破洞):     避障處理原則:
┌────────────────┐         0 ≠ 「沒障礙物」!
│ ███ ▓▓▓ 000 ░░ │         0 = 「不知道」→ 寧可當未知處理,
│ ███ 000 ▒▒▒ ░░ │              也不可當 free 直接衝
└────────────────┘         (玻璃門就是經典事故場景,§2.4 超音波補盲)

24. 採用深度相機的送餐機器人產品案例

截圖存於 img/(2026-06 擷取自官網/代理商頁面);規格以原廠最新公告為準。

產品 深度感知配置(公開資訊) 印證的架構觀念
Pudu BellaBot(普渡,貓型機) 3 顆 RGBD 深度相機 + LiDAR;支援 Laser SLAM 與 Visual SLAM 雙方案 多顆深度相機補視野窄的限制(§23);LiDAR+深度互補(§3.2)
Keenon DINERBOT T10(擎朗) 4 顆立體視覺相機(270° 3D 偵測)+ 2 顆 LiDAR(360° 2D) 感測冗餘與覆蓋角設計;雙 LiDAR 消盲區
Bear Robotics Servi 深度相機 + LiDAR 融合(早期型號公開資訊採 Intel RealSense) 與本文件建議的「LiDAR 定位 + 深度避障」同構(§3)

BellaBot 官網頁面 Bear Robotics Servi 頁面

三家頭部產品全部是「2D LiDAR(定位主力)+ 多顆深度/立體相機(立體避障)」的組合——印證 §2.4/§3 的選型建議不是理論偏好,而是市場收斂後的共同答案。差異只在相機顆數與擺位(前向 vs 環繞),那是視野覆蓋率 vs 成本的取捨。

來源:Pudu BellaBot 官網Generation Robots BellaBot 規格Keenon DINERBOT T10Automated Warehouse 報導Bear Robotics Servi


25. 急停控制如何達成

25.1 核心原則:急停是「電路」,不是「程式」

§3.1 說過:安全功能即使上位機當機也要有效。急停再進一步——即使下位機(STM32)當機也要有效。所以真正的急停不經過任何 CPU:

急停鏈 (safety chain):全部是「硬接線」串聯,無軟體參與

  急停按鈕(常閉 NC)──┬──► 驅動器 STO / Enable 腳 ──► 閘極驅動器停止輸出(§12)
                      │         → 馬達瞬間失去動力
                      └──► STM32 GPIO(僅「知會」用,讓韌體同步進入急停狀態、上報上位機)

兩個設計細節都呼應 §13.4 的故障安全 (fail-safe) 思想:

  1. 常閉 (Normally Closed) 接點:按鈕沒被按時迴路導通 = 允許運轉;按下或斷線都會斷開 = 停車。線被夾斷、接頭鬆脫,表現為「停下來」而不是「急停失效」。
  2. 鎖定機制 (latching):工業急停按鈕按下後機械卡住,必須旋轉釋放;釋放後系統不得自動恢復運轉,要求操作者另行確認(re-enable)——防止「按鈕彈回車就暴衝」。

25.2 STO:現代驅動器的標準急停入口

STO(Safe Torque Off,安全轉矩關斷)是 FOC 驅動器普遍內建的安全輸入腳:拉斷它,驅動器直接在閘極驅動層封鎖 PWM(§12.4 的欠壓鎖定同層),馬達無法產生扭矩——但驅動器的邏輯電源、通訊都還活著,可以回報狀態。比「用繼電器硬切 24V 動力線」優雅:無大電流接點火花、恢復快、狀態可觀測。選驅動器時把「有無 STO 輸入」列入規格檢查項。

25.3 「斷動力」≠「煞車」:急停後車會滑行

切斷馬達動力後,車靠慣性繼續滑行(freewheel)。對策分層:

機制 原理 適用
三相短路煞車 (dynamic/short braking) 把三相繞組短路,轉動產生的反電動勢變成煞車力矩 多數驅動器內建,急停時自動切入
電磁抱閘 (electromagnetic brake) 斷電時彈簧夾緊、通電才釋放(fail-safe 方向) 有坡道的場域必備(§17.5 選型確認項)

25.4 完整的停止層級(對照 IEC 60204-1 停止類別)

最快/最硬 ──────────────────────────────────► 最慢/最軟
 硬體急停            軟體急停              受控減速停止
 (Stop Cat. 0)      (韌體層)              (Stop Cat. 1/2)
 按鈕→STO 斷扭矩     防撞條觸發/通訊逾時      上位機取消任務、
 不經 CPU            → STM32 命令速度=0      Nav2 規劃減速
                     + 經 CAN 下 disable     (§26 的 ramp 路徑)

設計準則:每一層失效,都有更硬的一層兜底;M1 驗收(§4.4)要分層測試——按鈕測 Cat 0、拔通訊線測韌體層、UI 取消任務測軟體層。


26. 加減速 ramp 與過流/堵轉保護

26.1 加減速 ramp(speed ramping / slew rate limiting)

問題:上位機指令是步階的——「現在 0.8 m/s」。直接照做,加速度衝擊會讓湯灑出、輪子打滑(odometry 毀)、電流暴衝。Ramp = 在目標與輸出之間插一層「變化率限幅器」:

指令(步階):  0 ──┐0.8 m/s          ramp 後(斜坡):      ╱──── 0.8
                  └──────                            ╱
                                              0 ────╱  斜率 = a_max

實作就是控制週期裡的幾行(接在 §20.3 的 ① 之前):

#define A_MAX   0.4f                 // 最大加速度 m/s²(送餐機 0.3–0.5)
#define D_MAX   0.6f                 // 最大減速度(可比加速大,煞車優先)

float ramp(float target, float current, float dt)
{
    float max_step = (target > current ? A_MAX : D_MAX) * dt;
    float diff     = target - current;
    if      (diff >  max_step) return current + max_step;  // 限幅:每週期最多變這麼多
    else if (diff < -max_step) return current - max_step;
    else                       return target;              // 已夠近,直接到位
}
// 每週期:v_cmd = ramp(v_target, v_cmd, DT); 再進運動學逆解

三個設計點:

  1. 加減速分開設:減速限值放寬(煞車比平順優先),急停(§25)則完全繞過 ramp。
  2. ω 也要 ramp:角加速度不限,原地起轉的甩動一樣灑湯;v 與 ω 的 ramp 要同步縮放,否則轉彎半徑會在加速過程中漂移。
  3. 放在下位機,不能只信上位機:Nav2 controller 雖有 acc limits 參數,但通訊抖動、手動遙控、異常指令都可能繞過——下位機 ramp 是最後一道平滑保證(§3.1)。進階版是 S-curve(梯形加速度,連 jerk 加加速度也限制),湯類配送值得做。

26.2 過流保護(overcurrent protection)

為什麼電流要管:馬達扭矩 ∝ 電流(§2.3 的 iq);電流同時也是發熱來源(P = I²R)。異常大電流 = 撞牆頂著推、爬不上的坡、MOSFET 直通故障——不處理會燒繞組或炸功率橋(§12)。

偵測與反應分三層、三種時間尺度:

層級 英文 偵測方式 反應時間 動作
硬體瞬時過流 hardware overcurrent / cycle-by-cycle current limit 功率橋低邊的分流電阻 (shunt resistor) 電壓 → 比較器,超過閾值直接封鎖當拍 PWM μs 級 逐週期限流,硬體自動
軟體限流 software current limiting FOC 電流環的 ADC 採樣值,韌體比較 ms 級 把 iq 命令夾在上限內(= 扭矩限制 torque limit)
熱保護 thermal / I²t protection I²t 積分:電流平方×時間累積(模擬繞組溫升),或 NTC 溫度感測器實測 秒級 持續過載 → 降額 (derating) 或停機
I²t 直覺:容許「短時間大電流」(起步、過坎),不容許「長時間中電流」
   電流
   3×額定 ┤█ 容許 0.5s        ← 起步瞬間 OK
   2×額定 ┤████ 容許 5s
   1×額定 ┤████████████ 連續  ← 額定就是「永遠可以」的線
          └──────────────► 時間

26.3 堵轉偵測(stall detection / locked-rotor detection)

堵轉 = 馬達被卡住(輪子頂住門檻、夾到異物):此時轉速 ≈ 0,反電動勢 ≈ 0,電流飆到最大、且全部變成熱——是過流的最危險形態。偵測邏輯抓它的特徵組合:

// 100Hz 任務內,概念示意
bool stalled = (fabsf(current) > I_STALL_THRESH)    // 電流很大(在用力)
            && (fabsf(w_meas)  < W_STALL_THRESH)    // 轉速幾乎為零(§20 encoder 回授)
            && (fabsf(u_cmd)   > U_CMD_THRESH);     // 而且確實有在命令它動

stall_timer = stalled ? stall_timer + DT : 0;
if (stall_timer > 0.5f) {                           // 持續 0.5s 才判定,濾掉過坎瞬間
    Driver_Disable();                                // 斷輸出
    Report_Error(ERR_STALL);                         // 上報上位機 → 任務層決定重試/求援
}

關鍵就是「大電流 + 零轉速」同時成立——單看電流會誤殺起步瞬間,單看轉速會誤殺正常停車;再加持續時間窗避免過門檻的誤觸發。現成 FOC 驅動器通常內建(規格書找 stall protection / locked-rotor protection),STM32 端要做的是訂閱它的故障碼並接到任務層的異常處理。

26.4 名詞速查(英文)

中文 英文
加減速斜坡 speed ramp / slew rate limiting / trapezoidal velocity profile
S 曲線加減速 S-curve profile(jerk-limited)
過流保護 overcurrent protection (OCP)
逐週期限流 cycle-by-cycle current limit
分流電阻 shunt resistor
扭矩限制 torque limit
熱過載保護 thermal overload / I²t protection
降額 derating
堵轉 stall / locked rotor
堵轉偵測 stall detection / locked-rotor protection

27. Odometry 是什麼:定義、字源、家族與本質限制

27.1 定義與字源

Odometry(里程推算)= 靠累積「自己的運動量測」來推算「我從起點移動到了哪裡」。

字源:希臘文 hodos(路)+ metron(量測)——跟汽車儀表板的 odometer(里程表)同字。差別是里程表只累積「走了多遠」(一個數字),機器人的 odometry 累積完整位姿 (x, y, θ):走了多遠、往哪走、現在朝哪。

它回答的問題鏈:

encoder 說:這 10ms 左輪滾 3.1mm、右輪滾 3.3mm     (§19/§20 量測)
   → 運動學正解:車體前進 3.2mm、左轉 0.04°        (§7 幾何)
   → 累積積分:從開機點算起,我在 (x=4.21m, y=1.37m, θ=87°)  (§4.1 公式)

航海時代叫 dead reckoning(航位推算):沒有 GPS 的船,靠「航速 × 時間 + 航向」逐段累加推算船位——odometry 就是它的機器人版,encoder 取代了測速繩與羅盤。

27.2 Odometry 是一個家族

「靠量自己的運動來推位置」的思路,不限於輪子:

類型 量測來源 特性
Wheel odometry(本文件的主角) 輪式 encoder 便宜、高頻、直線準;打滑就錯(§10.3)
IMU(慣性推算) 陀螺儀/加速度計積分 角度好;位置二次積分發散,不能單用(§3.3)
Visual odometry 相機連續影格特徵追蹤 不怕打滑;怕快速移動、無紋理牆面
LiDAR odometry 連續 scan 之間的 matching 即 §21.2 的 scan matching 用在「相鄰兩幀」

實務上 /odom 常是 wheel + IMU 的 EKF 融合結果(§3.3 的「距離信 encoder、角度信陀螺儀」)。

27.3 本質限制:相對定位,誤差只增不減

odometry 是相對定位——一切從開機點累積,每一步的小誤差(量化、打滑、標定偏差)永久留在總和裡:

誤差成長示意:
 走 1m   → 偏 1cm          odometry 自己永遠不知道自己偏了,
 走 10m  → 偏 12cm          也沒有任何機制能把誤差消掉——
 走 100m → 偏 1.5m + 方向歪  它只會越走越錯(漂移 drift)

所以導航架構永遠是「odometry(高頻、平滑、短期準)+ 絕對定位修正(低頻、長期準)」的搭配:AMCL 對地圖比對(§22)負責把累積誤差週期性拉回。兩者關係像手錶與電台報時——手錶走時靠自己(會漂),每天對一次報時(歸正)。

一句話總結:odometry = 用自己的運動量測做積分的相對定位;快而平滑但必漂移,所以永遠搭配一個絕對定位來源使用。


28. 地標定位:用相機看目標物反推自己的位置

問題:可以用深度相機偵測「已知位置的目標物」,反推並強制修正自己的位置嗎? 答案:可以,這叫地標定位 (landmark-based localization),是成熟且廣泛部署的手法——它就是 §27 說的「絕對定位來源」的另一種實現,跟 AMCL 並列。

28.1 原理:已知地標在地圖哪裡 + 量到地標在我的哪裡 → 反推我在哪

地圖(部署時登錄):   地標 M 固定在世界座標 (10.0, 5.0),朝向已知
相機當下量到:        M 在我前方 2.0m、偏右 15°,且我看它的角度是 30°
反推(座標變換):     我必然站在世界座標的 (8.1, 4.6),朝向 75°
                              │
                              ▼
            用這個結果「強制定位」:重設 AMCL 粒子雲 / 餵給 EKF

跟 AMCL 的本質差異:AMCL 是「拿整幀 scan 跟整張地圖機率比對」,地標定位是「認出一個唯一已知的東西,幾何反算」——後者沒有多義性,一眼就是絕對位置,所以特別適合「強制」場景。

28.2 實作光譜:從貼碼到自然地標

方案 做法 精度/成本
人工標記 (fiducial marker):AprilTag / ArUco 場域貼黑白方塊碼,每張碼 ID 唯一、尺寸已知;單目相機偵測四角 → PnP 解算 → 相對位姿;深度相機可再用深度值強化距離估計 cm 級;成本 = 貼紙,演算法現成(OpenCV / apriltag_ros)
天花板碼(歷史主流) 碼貼天花板、相機朝上——天花板永遠不被人群遮擋。早期送餐機器人(Pudu/Keenon 初代)正是用這招,LiDAR SLAM 成熟後才退居輔助 部署工程較大,但餐廳人群中極穩
自然地標 / 視覺重定位 不貼碼,預先建視覺特徵地圖,比對當下影像特徵反推位姿(visual place recognition / relocalization) 免施工,但受光照、視角、場景變動影響大
反光柱(LiDAR 版地標) 高反射率柱子,LiDAR 強度值直接認出 工業 AGV 經典做法(對應 §18.3 的 ISO 3691-4 世界)

28.3 「強制定位」的正確姿勢:離散重設,不要連續打架

量到地標位姿後,有兩種用法,選一種,不要混:

  1. 離散重設 (re-seed):把結果發到 AMCL 的 /initialpose(或同等 API),粒子雲整團搬過去重新收斂。適合:開機初始化、§22.3 的綁架恢復、進出電梯/無特徵長廊後的歸位。
  2. 連續融合 (fusion):把地標觀測當成一路絕對量測,跟 odometry 一起進 EKF(robot_localization 支援 pose 輸入)。適合:地標夠密、希望平滑修正的場合。

反模式:AMCL 正常運作中,粗暴地high頻覆寫位姿 → 兩個定位源互相拉扯,路徑規劃跟著抖。地標是「校正點」,不是第二套常開定位(除非整套就設計成地標主定位,如天花板碼方案)。

28.4 送餐機器人實際用在哪

場景 為什麼用地標而不是 AMCL
回充對接(§2.5 的二期項目) 對接要 ±1cm/±1°,AMCL 給不了;充電樁上的標記/IR 信標做最後 50cm 的精確伺服
開機初始位姿 停機位貼一張碼,開機看一眼即完成初始化,免人工在 UI 點位置
電梯內/跨樓層 電梯轎廂無地圖特徵且樓層切換,出電梯看碼歸位
長走廊/玻璃帷幕區 LiDAR 特徵貧乏(§22 的多義性、§23.4 玻璃問題),走廊中段補一張碼

設計建議:本專案以「LiDAR + AMCL 為主、AprilTag 為輔(回充對接 + 開機初始化 + 特徵貧乏區補強)」——跟頭部產品(§24)的演進路線一致:標記從「主定位」退役後,正是在這些角落繼續服役。

28.5 常見疑問:看一張碼,能知道車的「朝向歪了多少」嗎?

能——方形標記給的是完整 6 自由度位姿(位置 + 姿態),不只位置。 機制有兩層:

(1) 透視變形編碼了相對姿態。 標記的實體尺寸與正方形形狀已知;相機看到的四個角點若不是正正方方,變形量就唯一對應「相機從什麼角度看它」:

正對著看:        從右側斜看:           碼在畫面中的梯形變形,
 ┌──────┐         ┌────┐               配合已知實體尺寸與相機內參,
 │ ▓▓▓▓ │         │▓▓▓ │╲              用 PnP 解算 → 相機相對標記的
 │ ▓▓▓▓ │         │▓▓▓ │ ╲             完整旋轉 R + 平移 t(6-DOF)
 └──────┘         └────┘  ╲
4 個角點對稱      4 個角點呈梯形 → 歪多少角度,算得出來

(2) 圖案不對稱消除了 90° 模糊。 純正方形轉 90° 看起來一樣;所以 QR code 有三個角的回字定位點(finder patterns)、AprilTag 的 ID 圖案本身不對稱——「碼的上下左右」唯一可辨,於是「車相對碼轉了幾度」也唯一。倉儲 AGV(Kiva/Geek+ 型)地面貼 QR 正是同時讀「位置 + 自身航向」,每經過一張碼就把 odometry 的角度漂移歸零。

直覺哪裡來的、又哪裡對:這個疑問對「點狀地標」完全成立——一根反光柱、一個 RFID 標籤是一個點,單次觀測只能給方位距離,給不了車的朝向;磁條導引也只知道「偏離帶子多少」。朝向資訊來自「地標本身有形狀和方向性」,方形碼恰好兩者都有。

要注意的真實限制是精度退化:碼離得遠、在畫面裡很小時,透視變形微弱,解出的姿態角會抖、甚至出現正反兩解跳變(pose ambiguity)——所以 AprilTag(角點定位精度與抗模糊性為位姿估計優化)比 QR code(為資料儲存設計)更適合定位用途;QR 適合「掃碼取資訊」,AprilTag 適合「量位姿」。回充對接把碼貼在 50cm 內的近距離用,正是讓透視變形夠明顯、姿態解夠穩。

28.6 方法詳解:從一張影像到車輛位姿的五步

以 AprilTag + 單目相機為例;深度相機的 RGB 流程相同,深度值作為第 ③ 步的額外約束。

事前準備(各做一次)

線上五步(每幀影像)

① 偵測角點          ② 解碼 ID           ③ PnP 解算
 影像 → 二值化 →     讀內部圖案 →        4 組「3D↔2D」對應
 找四邊形輪廓 →      這是 23 號碼 →      → 解出 R, t
 角點亞像素精修      查表:23 號在地圖     (相機看碼的位姿)
                    (10.0, 5.0) 朝南
        │                │                   │
        ▼                ▼                   ▼
                ④ 座標鏈串接:T(map→robot) =
                T(map→marker) · T(marker→camera) · T(camera→base_link)
                  查表(事前)      ③的逆          手眼標定(事前)
                        │
                        ▼
                ⑤ 投用:發 /initialpose 重設 AMCL,或進 EKF 融合(§28.3)

③ PnP 的原理(不需要解,需要懂它在解什麼)

PnP = Perspective-n-Point:已知 n 個點的 3D 座標與它們的 2D 像素投影,反求相機位姿。對方形碼,n = 4:

3D(標記自己的座標系,邊長 s 已知):     2D(影像中偵測到的像素):
  P1 = (-s/2, +s/2, 0)  左上              p1 = (412, 233)
  P2 = (+s/2, +s/2, 0)  右上              p2 = (561, 241)
  P3 = (+s/2, -s/2, 0)  右下              p3 = (553, 388)
  P4 = (-s/2, -s/2, 0)  左下              p4 = (405, 379)

求:旋轉 R 與平移 t,使得「P_i 經 R,t 變換再投影」最貼合 p_i:
     p_i ≈ K · (R · P_i + t)        K = 內參矩陣(①事前標定那個)

四點共面有封閉解(平面 PnP,如 IPPE 演算法),cv2.solvePnP(..., SOLVEPNP_IPPE_SQUARE) 一行呼叫。直覺:「多大」決定多遠(t),「多歪的梯形」決定角度(R)——§28.5 的透視變形在這裡變成方程式。

工程要點

要點 原因
角點要做亞像素精修 (sub-pixel refinement) 1 px 的角點誤差在 2m 外會放大成數 cm 的位姿誤差
ID 解碼放在 PnP 前 先確認「這真的是 23 號碼」再算,避免誤認海報、地磚花紋
連續多幀取中位數/濾波後再投用 單幀姿態抖(§28.5 的 ambiguity);重設 AMCL 這種「強制」動作要用穩定值
整套用現成包 apriltag_ros 直接輸出 TF,①~③ 全包;自己要寫的只有 ④⑤ 的投用邏輯

28.7 AprilTag 是 QR code 嗎?——不是,設計目的相反

兩者同屬「黑白方塊視覺標記 (fiducial marker)」家族,但一個為存資料設計、一個為量位姿設計:

QR code(存資料):                AprilTag(量位姿):
 ┌─────────────┐                 ┌─────────────┐
 │▛▜ ▖▗▘▙ ▛▜  │                 │█████████████│
 │▙▟ ▘▖▗▝▖ ▙▟ │← 模組極小極密    │█ ▓▓  ▓▓   █│← 格子極大極粗
 │ ▗▘▙▗▖▛▗▘▖  │  (21×21 起跳,   │█   ▓▓  ▓▓ █│  (例 6×6 資料格)
 │▖▝▗▘▖▙▗▘▝▗▖ │   可達 177×177)  │█ ▓▓   ▓▓  █│
 │▛▜ ▘▗▖▝▗▘▙  │                 │█  ▓▓ ▓▓ ▓▓█│
 │▙▟ ▗▘▝▖▗▘▖  │← 三個回字       │█████████████│← 一圈粗黑邊框:
 └─────────────┘   定位點         └─────────────┘   專為角點偵測設計
 容量:數百~3KB(網址、文字)      容量:只有一個 ID(數百~數萬種)
 用途:掃碼付款、取連結            用途:機器人位姿估計、AR、相機標定
  QR code AprilTag
設計目的 儲存資料 位姿估計(密西根大學 APRIL 實驗室為機器人開發)
資料量 大(KB 級) 極小(一個 ID 編號)
模組密度 密 → 必須近距離、高解析度才讀得到 粗 → 遠距離、低解析、輕微失焦都偵測得到
角點品質 為解碼設計,角點精度普通 粗黑邊框專為亞像素角點偵測設計(§28.6 ①)
抗模糊/運動 好(機器人在動,這很關鍵)
誤偵測防護 checksum 保資料 編碼族設計保「不會把雜物認成 tag」(如 tag36h11 漢明距離 11)

真實的 AprilTag 長這樣(官方 tag36h11 族,左 ID=0、右 ID=1;圖檔取自 AprilRobotics/apriltag-imgs):

AprilTag tag36h11 ID 0 與 ID 1

讀圖重點:(1) 外圈完整一圈粗黑邊框——PnP 用的四個角點就來自它;(2) 內部 6×6 格只編一個 ID,圖案不對稱——這就是 §28.5「消除 90° 模糊」的具體長相;(3) 兩個 ID 的圖案差異很大(漢明距離設計)——拍糊了也不會把 0 認成 1。實際部署列印時注意:邊長要量「黑框外緣」填進設定檔(PnP 的 s,§28.6),且紙面要平整——皺褶直接破壞透視幾何。

互補使用的實務:定位用 AprilTag,人機介面用 QR(顧客掃碼點餐、機台掃碼綁定)。倉儲 AGV 地面碼很多用 DataMatrix/QR 變體,是因為他們同時要在碼裡存「這是哪一格」的資料且距離極近(相機朝下 30cm)——送餐機器人的牆面/充電樁標記沒有這個需求,AprilTag 是正解。