24 — 把「會瞬移的底盤」換成「真的有輪子的車」
現地場域的 Isaac Sim 6.0 調校,2026-08-15/16。一台自動堆高機原本用三個位置關節 (x / y / yaw)移動,要換成真輪系(轉向 + 驅動 + 輪胎摩擦)。
這一篇不談某個參數,談的是這個轉換會依序踩到哪些坑,以及每一個坑 為什麼都長得像別的問題。裡面每個數字都是實測的,失敗的做法也留著。
一句話
位置控制的底盤與輪系底盤不是同一個系統的兩種設定,是兩個不同的系統。 中間有一條必須按順序滿足的前提鏈,而每一環沒滿足時的症狀 都不會指向那一環。
0. 前提鏈:順序不能顛倒
① 車重壓在輪子上 → 沒有它,μ·N 的 N 是 0,後面全部無意義
② 輪胎抓地力夠 → 牽引力上限 = μ·N,要與馬達扭力比較
③ 轉向指令的**符號**正確 → 錯號時控制器會越修越歪,看起來像「增益不夠」
④ 路徑追蹤的**參考點**正確 → 錯點時半徑會系統性偏大,看起來像「軸距錯」
⑤ 路徑追蹤的**演算法前提** → 引導點必須在前方且距離 ≥ 前視距離
⑥ 到位判定用**弧長** → 歐氏距離對非完整約束系統不可能收斂(Brockett)
每一環都可以獨立驗證,而且應該獨立驗證。 跳過任何一環去調後面的, 得到的都是雜訊 —— 我們在 ③ 之前花了十幾輪調 ⑤ 的參數,全部作廢。
1. ① 車重到不了輪子:最底層,也最容易漏
症狀
輪子轉了,車幾乎不動。實測滑移率 85.7%(輪子轉 6.394 m,車只走 0.914 m)。 把輪胎摩擦係數從 0.02 掃到 5.00(250 倍),14 秒直行位移 0.922 / 0.954 / 0.978 m —— 完全平坦。
為什麼
F = μ·N,而 N ≈ 0。三個獨立的原因,任何一個成立就夠:
| 原因 | 怎麼查 |
|---|---|
| 地面沒有碰撞體 | 實查地板 prim 的 collisionEnabled —— 我們的 24 片全是 False |
| 剛體被關掉重力 | 查 physxRigidBody:disableGravity。⚠ 文件寫 3 個,實查是 8 個,含四個承載輪體與 2050 kg 的車體 |
| 底盤沒有垂直自由度 | 車被錨死在某個高度,重力只是載進錨點。查關節鏈裡有沒有沿世界 Z 的自由度 |
🔴 這三項是同一件事的三個半,分開做每一個都是 no-op —— 所以也分不出誰有效。要一起改,不要當成三個變因。
判別「N 是真的了沒」——靜態值不行,要看回彈
車靜止時垂直關節讀到 0.0,同時相容於「重力有開、車坐在輪上」與
「重力沒開、車浮著」。決定性的觀察是車動起來之後它會不會回彈:
world_z min +0.000000 max +0.000800 最後 +0.000000 非零樣本 157 / 1980
被壓下 0.8 mm 然後回到 0。重力關著時,接觸衝量會把一個自由的垂直關節 往上推而不會回來(沒有東西把它拉下去)。
⚠ 「有正向力」不等於「那是車重」
修好之前,我們量到「輪胎抵抗 15 kN」,一度以為抓地力沒問題。 實際上輪底穿透是 0.0000,那 15 kN 是剛體被硬推進地面時求解器的 穿透回復力 —— 由 authored 的高度與求解器決定,與 3470 kg 無關。
🔑 判摩擦有沒有生效,唯一可信的是量物理行為(滑動發生在什麼加速度), 不是回讀屬性、也不是看某個力的數字大不大。
2. ② 抓地力:算清楚誰才是限制
馬達給得出的力 5 000 N → 1.44 m/s² (drive maxForce 575 N·m ÷ 輪半徑 0.115 m)
輪胎抓得住的上限 27 233 N → 7.85 m/s² (μ 0.8 × 3470 kg × g)
餘裕 5.4 倍
實測 p99 線加速度 1.54 m/s²(≈ 馬達上限) 只用掉約 20% 抓地力
限制是馬達扭力,不是輪胎打滑。 這個判斷要在調任何控制參數之前做完 —— 否則會把「馬達出力不足」當成「摩擦不夠」去調 μ。
⚠ 驅動輪用速度控制時,牽引力上限要用「持續」而非「峰值」。 峰值 15 kN 超過驅動輪的靜態附著力(1726 kg × g × 0.8 = 13.5 kN), 速度控制會全力追指令轉速 → 輪子空轉,實測滑移率 81.5%。
3. ③ 轉向符號:一個沒驗過的旗標,燒掉十幾輪
症狀
控制器一路把轉向打死(角度中位數 65.1°、上限 75.3°),車繞著目標轉,
improvement 是負的,輪子來回換向。看起來完全像「增益調不好」。
檢定式(不需要改任何程式,純資料)
同一則 /joint_states 裡就有轉向角、輪子轉角、車體偏航 —— 天然對齊。
自行車模型:yaw_rate = v·tan(δ)/L,所以
預期偏航方向 = sign(Δ輪角 · tan δ) ← Δ輪角帶號,所以已含行進方向
拿它跟實測的 Δyaw 比:
| 結果 | |
|---|---|
| 符號一致性 | 73% 相反 |
| |實測 Δyaw| ÷ |模型 Δyaw| | 中位 0.94 |
量級對、符號反 ⇒ 自行車模型與軸距是對的,錯的只有符號。 翻號之後轉向角中位數 65.1° → 2.3°。
🔴 這個符號會隨行進方向再翻一次
同一個檢定分開統計:
輪子正轉 相反 9% (n=34)
輪子反轉 相反 97% (n=68)
推導:實際要送到關節的角度 δ_joint = −atan(L·κ) 只由路徑曲率決定,
與行進方向無關;而 pure pursuit 算出的轉向角內含 ·forward
(倒車時轉向要反向)。兩者不抵銷,倒車就反號。
前進 steer=+atan(Lκ) × sign(v)=+1 × polarity=−1 → −atan(Lκ) ✅
倒車 steer=−atan(Lκ) × sign(v)=−1 × polarity=−1 → −atan(Lκ) ✅
⇒ 送出去的關節角要再乘 sign(speed),同一個極性才能涵蓋兩個方向。
🔑 可複用的規則
任何「先留一個旗標,之後用正對照定號」的註解,都要當成未爆彈。 我們那行註解寫著「尚未實測…第一輪用極端值正對照定號」,而那個第一輪 從來沒發生過,預設值一路用到現在。接手一個模組時,grep 這類註解。
4. ④ 追蹤參考點:錯了會系統性地偏,而且長得像軸距錯
症狀
轉向關節精準跟隨指令(誤差 0.3~2°),但車做出的迴轉半徑比 R = L/tan δ 大,
控制器越要越多(44.5° → 56.8°)而橫向誤差照樣累積。
兩個候選,用幾何實測分辨(比跑實驗便宜)
量各輪體與車體原點在車體縱向的位置(⚠ 一律從世界變換投影, 不要讀 local 分量 —— 根 prim 的 orient 會把軸名轉掉):
承載輪軸(front_left/right 中點) 縱向 −0.8487 m
轉向/驅動輪 縱向 +0.5628 m
⇒ L_eff = 1.4115 m ← 與控制器用的完全相同,軸距**沒錯**
⇒ 車體原點離承載輪軸 = 0.8487 m ← 這才是問題
自行車模型的「固定軸」是承載輪軸,它沒有側滑,速度方向就是車頭方向。
車體原點不在那條線上,離 d 的點走的半徑是 sqrt(R² + d²):
δ=45° 固定軸 1.412 m → 車體原點 1.647 m(大 16.7%),車體偏航還差 31°
要讓車體原點走 R=1.41 的彎,固定軸半徑得 1.128 → **δ = 51.4°**
而控制器只要 44.5° —— 差 6.9°,正是它一路加到 56.8° 的原因
🔴 修法是在固定軸的座標裡規劃,不是後處理路徑
後處理的三種做法都試過,全錯:
| 做法 | 為什麼錯 |
|---|---|
沿末端切線把路徑延長 d |
只對直線成立;彎道差 d²/(2R)(R=1.41、d=0.85 時 0.255 m,實測 0.2355) |
| 用路徑點宣告的 yaw 逐點平移 | enforce_tangent=false 時宣告 yaw 不是路徑切線,常數 yaw 會把整條路徑整體搬移 |
| 沿切線逐點平移 | 半徑變成 sqrt(R²+d²),而固定軸該是 sqrt(R²−d²) —— 方向剛好相反 |
正解:把起點與目標各自沿自身航向推 d,規劃出來的直接就是固定軸的軌跡。
不需要後處理,也不必在折線上估局部曲率(那是噪聲來源)。
終點推 d 之後,「固定軸到達推移後的目標」=「車體原點到達原目標」。
驗證:轉彎段起點的轉向需求從 66.3° 變成 −44.5°,正好是 atan(L/R) 的理論值。
5. ⑤ 路徑追蹤:pure pursuit 的三個前提
前提一:引導點必須離車至少一個前視距離
規劃出來的路徑常是高頻重取樣的(30 Hz × 速度 → 732 點散在幾公尺), 相鄰點只有幾公分。而
κ = 2·sin(方位角) / Ld , sin(方位角) = y_local / distance
distance 幾公分時 sin 逼近 ±1,5 cm 的橫向誤差就打死方向盤。
實測:車在 50 個點內從貼著路徑跑到 12 m 外。
⇒ 引導點要按弧長取(s + Ld 處插值),不是取「下一個路徑點」。
前提二:引導點必須在前方
用「從當前索引往前找第一個距離 ≥ Ld 的點」也不夠 —— 車超過該索引之後, 第一個夠遠的點在後面,pure pursuit 會倒車,然後閘門滿足、索引前進、 再翻回來 → 沿線來回震盪。
⇒ 必須先做最近點投影取弧長 s(且 s 單調不減),再從 s 往前取。
前提三:Ld 不能大於路徑的曲率半徑
Ld 大於曲率半徑時,車到引導點的弦會整個切過弧,轉向立刻飽和。
自適應的判準用弦到路徑的最大偏離(sagitta),不要估曲率 (折線上數值微分會被取樣雜訊主導):
chord_tol 0.08 → 允許 Ld ≤ 0.937 ← 對 R=1.41 等於不限制(實際偏離 0.074)
chord_tol 0.04 → 允許 Ld ≤ 0.667
⚠ 容差設太鬆時自適應機制等於沒作用,而 log 上看不出來 ——
把實際用的 Ld 印出來。
🔴 規劃時要留轉向餘裕
Dubins 用 δ_max 算 R_min。若規劃直接用車的 δ_max(70° → R=0.514 m),
路徑正好貼在轉向極限上,控制器一點修正權限都不剩。
改用 δ_plan = 45°(R=1.41 m),留 25° 給修正。
⚠ 「弧上轉向飽和」不一定是 bug:R=0.5 的弧上 δ = atan(L/R) = 70.5° 就是正解。
我第一版把「不飽和」寫成自測的通過條件 —— 那個斷言本身是錯的。
🔴 行進方向要整段鎖定,不可逐步重判
從引導點方位角逐步判前進/倒車時,車偏掉之後引導點會跑到「前方」,
於是倒車路徑被自己切成前進 → Ld 跳回、轉向翻號 → 迅速發散。
規劃出來的方向不該由控制器中途改掉,那個自由度不該存在。
實測(同一條轉彎路徑,逐項疊加):
| 組態 | 完成度 |
|---|---|
| 前進 | 25% |
| 倒車 + 轉向反號 | 2% |
| 倒車 + 正號 | 31% |
| 倒車 + 正號 + 方向鎖定 | 50% |
6. ⑥ 到位判定:歐氏距離對這類系統不可能收斂
非完整約束系統(瞬時橫向速度恆為 0)不可能用連續的靜態狀態回授 穩定到一個點 —— 這是 Brockett 條件,不是增益調不好。
把目標點反覆餵給控制器的後果會隨控制器變好而惡化:
轉向符號錯時 → 車繞著目標轉,誤差卡在 0.58 m
轉向符號對時 → 積分項(overdrive)把目標往外推,而車跟得上
→ **追著一個一直後退的點跑出場景(y = 11.28 m)**
⇒ 到位判定要用沿路徑的弧長,橫向與航向誤差分開記錄當品質指標, 不拿來當控制目標。
靠泊(最後對準)可以另外做:引導點放在進場軸線上、比車當前投影 前進一個前視距離,且永不越過目標。點在線上 → pure pursuit 自然把車拉回線上, 橫向不需要第二個迴路;點不越過目標 → 不會追著後退的點跑掉。
實測結果:橫向 0.0001 m、航向誤差 0.03°。
7. 工程紀律:這一輪真正省時間的三件事
7.1 幾何全部做成離線可測的純函式
投影、取引導點、自適應前視、方向鎖定 —— 全部不依賴模擬器,寫成純函式, 配 30+ 項自測。這一輪的五個 bug 全是純幾何錯誤,離線測試當場就抓到, 而在模擬器裡每驗一次要 5~10 分鐘機時。
自測要釘住踩過的坑,不是只測 happy path:
✅ 引導點在車前方 ← 曾經跑到後面 → 沿線震盪
✅ 尾端引導點仍在前方 ← 夾在終點會落到車後面
✅ 側方逼近不誤判 done ← 歐氏距離會
✅ s 單調不減 ← 投影抖動會把車拉回上一段
✅ 負對照:偏離超限 abort / 點不足 raise
7.2 每個結論配一個行為指紋,而且要問「它什麼時候會騙我」
ros2 param get 只證明宣告值,證明不了攔截點吃到哪個值 —— 要在程式裡
印一行。而指標本身也會有盲區,這一輪踩了三次:
| 指標 | 盲區 |
|---|---|
橫向誤差 cross |
對航向不連續結構上看不見 —— 起點 cross=0 卻要 66° 轉向 |
ARRIVED 計數 |
依賴引導點距離,不反映控制品質 |
| log 印的轉向角 | 只乘了極性沒乘 sign(v),倒車時顯示成反號(致動是對的,顯示在騙人) |
7.3 事後讀狀態常常讀不到真相
中止時控制器會把致動歸零,所以事後去讀 /joint_states 一定讀到 0。
要分辨「指令對」還是「關節做得到」,必須在跑的當下把兩者一起記進 log。
8. 檢查清單(換底盤之前逐項確認)
□ 地面有碰撞體?(查 collisionEnabled,不要假設)
□ 相關剛體的重力沒被關掉?(查 disableGravity,**逐一列出**不要信文件)
□ 底盤有垂直自由度?(關節鏈裡有沿世界 Z 的自由度)
□ 上面三項一起改、一起驗(分開做都是 no-op)
□ 車動起來時垂直自由度會**回彈**?(靜態值分不出兩個世界)
□ 算過馬達扭力上限 vs 抓地力上限,知道誰是限制?
□ 轉向符號用資料驗過?(sign 一致性 + 量級比,量級對才是只有符號錯)
□ 轉向符號在**兩個行進方向**都驗過?
□ 追蹤參考點是固定軸(不側滑的那一軸)?量過它離車體原點多遠?
□ 規劃座標與追蹤點一致?(起點與目標都推過偏移)
□ 引導點按**弧長**取、保證在前方、`Ld` ≤ 曲率半徑?
□ 規劃的 R_min 留了轉向餘裕?(不要用車的 δ_max 去規劃)
□ 行進方向整段鎖定?
□ 到位判定用弧長不用歐氏距離?
□ 幾何寫成離線可測的純函式,自測釘住踩過的坑?
□ 每個變因有行為指紋,而且想過那個指紋什麼時候會騙人?
9. 還沒解決的(誠實紀錄)
轉彎段目前推進到 50%(5.753 / 11.464 m)。卡點是轉向飽和:
joint_act 貼在 72°、s 停住不再前進而橫向誤差繼續長 ——
規劃的 45° 加上要修正的量已經吃掉全部權限。
下一步是把規劃的 δ_plan 再降(例如 35° → R=2.0 m)看飽和是否消失。
⚠ 判準要看 joint_act 有沒有貼在上限,不是看走多遠 ——
「走得更遠」在這條線上出現過三次,每次都是延後發散而不是收斂。
證據等級
本篇的實測全部來自 2026-08-15/16 的一次底盤轉換(位置關節 → 真輪系),
單一車型、單一場景。⚠ 關鍵數字的樣本量都很小:符號翻轉那一組是行為統計
(73% / 9% / 97%),而 §5 那條「走得更遠」的觀察只有三次。這些足以支持
判準的方向(看 joint_act 有沒有貼上限、用弧長不用歐氏距離),
不足以支持任何定量關係。
可公開查證的是理論那一層:歐氏距離對非完整約束系統不可能收斂,是 Brockett 條件的直接後果(Brockett 1983,平滑狀態回饋鎮定的必要條件), 與這個場景無關,任何非完整約束系統都成立。μ、扭力、抓地力的量級比較用的是 基本力學,代自己的數字即可。
內部回溯用(未公開):該專案 docs/202 的逐輪紀錄。