← 回專案總覽與驗證系統
第四支手臂:STS3215 匯流排舵機(換取閉環回授)
前三支手臂都是開環——韌體只知道自己下了什麼指令。
所以這個專案的每一層檢查,都是拿一個模型去比另一個模型,
整套可以自洽地描述一台不存在的機器。
#54 的 frictionloss=0 就是活生生的例子:
零摩擦是自洽的,只是不真實,12 層測試沒有一條抓得到。
為什麼換掉 FT5425BL
| 項目 | FT5425BL | STS3215 |
|---|---|---|
| 回授 | 無(3 線 PWM,read() 是指令的回音) |
位置/速度/負載/電壓/電流/溫度 |
| 額定扭矩 | 9.5 kg·cm,單一經銷商推得 | 10 kg·cm,原廠公布 |
| 編碼器 | — | 12-bit 磁編碼,0.0879°/計數 |
| 本體 | 40.6×20.0×30.9 mm(含包絡 62.6×34.9×20.0) | 45.2×37.8×24.7 mm |
| 質量 | 67.6 g | 55 g |
| 花鍵 | 25T | 25T(相同,舵盤介面沿用) |
| 伸距(100 g、70% 額定) | 190 mm | 212 mm |
更輕、扭矩略高、依據更硬、還多了回授。 FT5425BL 那支的紀錄保留為被取代的設計—— 「同級舵機,一顆有回授有公布額定、一顆沒有」本身就是這個專案要比較的東西。
CAD 沒有複製一份
arm_ft5425bl.py 改成可以在執行前注入致動器與伸距,
所以同一份 395 行的推導直接長出第二支手臂。
結果是這支手臂第一次跑 T0/T8/T10 就全過——
那支付出過的幾何教訓(叉臂的掃掠對角、耳片要包過關節軸、
Cylinder 吃的是半徑)全部繼承,不必重學。
而且這麼一改就暴露了一個原本就存在的潛在錯誤:
REACH、L_UPPER、L_FORE 是三個獨立常數,
剛好滿足 105 + 85 = 190。注入 212 mm 之後 REACH 變了、
連桿還是 190——手臂按一個數字造、文件按另一個數字寫,
而零件在兩種長度下都是合法的,沒有任何檢查會發現。已改為由 REACH 推導。
閉環回授層:兩種誤差,兩個不同的界限
這支舵機的回授路徑有兩個確定性的誤差源,而它們不是同一件事:
| 誤差 | 來源 | 界限 |
|---|---|---|
| 感測誤差 | 12-bit 量化,與機構無關 | 半個計數 = 43.9 毫度 |
| 停止誤差 | 韌體死區 10 計數,與感測器無關 | 0.879° |
死區是感測解析度的 10 倍——這支舵機的精度上限是韌體, 不是那顆 12-bit 編碼器。拿「12-bit」當精度規格是把兩件事搞混。
#57 我把停止誤差的界限寫成死區本身,漏了兩個誤差源會疊加 已修正
驗證層第一次跑,「停止誤差不超過死區」失敗:最壞 0.9218°, 界限 0.8789°。查下去不是模型錯,是我的界限錯:
韌體用「量化後的讀值」判斷是否到位
→ 真實角可以再多偏半個計數
→ 界限 = 死區 + 半計數 = 0.8789 + 0.0439 = 0.9228°
實測最壞 0.9218°
兩個誤差源會疊加,不是二選一。這也正是為什麼它們要分開記錄—— 混成一個「精度」數字,就分不出該修韌體還是換感測器。
唯一一處「模型」對上「實體量測」
第三方(robonine.com)實測一台 STS3215:
| 量測 | 值 | 對照 |
|---|---|---|
| 虛隙 | 0.87° | 規格書寫 ≤0.5° |
| 韌體死區 | 10 計數 | = 10 × 0.0879 = 0.879° |
| 重複性(空載) | ±0.17° | ≈2 個計數 |
| 位置偏差(1.5 kg × 100 mm) | 2.6° | 那是順應性,不是虛隙 |
0.87 與 0.879 幾乎相等。這支持一個具體結論: 這顆舵機的「虛隙」主要是韌體死區而不是齒隙—— 所以它可以被精確建模,不必像 #55 那樣 因為沒有實測參數而留成缺口。
但這仍然不是 validation:樣本數 1,量的不是我們的手臂、
不在我們的台子上。它比另外三支手臂高一階,如此而已。
這句話寫在 tests/suite_bus_servo.py 的檔頭,不是寫在這裡就算。
驗證結果
| 層 | 結果 |
|---|---|
| T0 幾何完整性 | 8/8(2 跳過:尚未產出網格與連桿屬性) |
| T8 可組裝性 | 5/5 |
| T10 可列印性 | 4/4 |
| B'4 閉環回授層 | 7/7 |
全套 445/445。接下來是 B'5 韌體(半雙工序列匯流排協定) 與四支手臂的對照報告。
B'6:連桿網格與慣性,以及它揭穿的一件事
這支手臂之前只有 CAD,沒有連桿網格與慣性張量,所以 T0 的兩項檢查一直顯示 「這支手臂還沒產出網格」。那個跳過訊息讀起來像「不適用」,其實是「沒做」。
補的方式不是複製 FT5425BL 的產生器。這支手臂本來就是
同一個設計模組換一顆舵機——arm_sts3215.py 是把致動器注入
arm_ft5425bl.py,而不是抄一份幾何程式碼。質量特性的產生器如果用抄的,
等於把同一個錯誤往下搬一層,兩份實作會在任何一支手臂改動時分岔。
所以產生器也改成可注入:同一張 LINKS 表、同一組
_gprops/_parallel_axis、同一個從尺寸預算讀出的夾爪假設值,
只換舵機質量與輸出路徑。
| 連桿 | STS3215(212 mm/55 g 舵機) | FT5425BL(190 mm/67.6 g 舵機) |
|---|---|---|
| base_link | 170.4 g | 211.0 g |
| turntable | 193.7 g | 184.2 g |
| upper_arm | 151.1 g | 145.1 g |
| forearm | 182.4 g | 180.6 g |
| 合計 | 697.7 g | 720.9 g |
連桿更長卻更輕,不是同一支手臂的小擾動——舵機輕了 12.6 g、連桿長了 22 mm, 對肩關節的慣量一個往下一個往上。
失敗 #66:FT5425BL 的連桿慣性過期了十天,而 T0 一路回報 32/32
把產生器改成可注入之後,重跑 FT5425BL 應該得到逐位元組相同的輸出——
畢竟只是換了取值來源。結果 git diff 顯示質量與慣性都變了,
upper_arm.stl 甚至變大了 1200 位元組。
不是重構壞掉。是磁碟上的產物本來就過期:
cad/arm_ft5425bl.py 最後修改 2026-08-11(#62 緊固件修正)
urdf/meshes_ft5425bl/ 最後產生 2026-08-01
URDF 描述的是 #62 修正之前的幾何,整個 C 系列都是。 upper_arm 差 0.178 mg——M4 鑽進 M3 孔位的那 45 mm³ 干涉、 以及螺栓圓轉 45° 的修正,都沒有進到模擬裡。
T0 在這段期間一直回報 32/32:網格 watertight、慣性張量對稱、正定、 主慣性滿足三角不等式。每一項都通過,而且每一項都在驗證一個十天前的物件。 那些檢查問的是「這個 JSON 是不是一個合法的剛體」, 從來沒有問過「這個 JSON 是不是現在這支手臂」。
修法是加一項會把答案重算出來的檢查:
fresh = {q["name"]: q for q in arm.link_gen()} # 從現在的 cad/ 重算
ok = worst_m < 1e-12 and worst_i < 1e-15 # 容差是 JSON 的往返精度,
# 不是工程餘裕
容差刻意取得極緊:檔案存的是完整雙精度,所以「沒過期」的意思是逐位元相同, 任何高於這個的差異都是真的幾何差異。並且照 #65 學到的規矩, 這項檢查必須被證明會失敗——把 STS3215 的 upper_arm 質量改掉 0.178 mg (正好是真實過期造成的差),它抓到了:
FAIL 連桿慣性與現行 CAD 一致(非過期產物)
最大質量差 178.000 mg(upper_arm)
四支手臂全部接上這項檢查後,重建 FT5425BL 的網格、URDF 與 MJCF, 全套由 538 項成長到 567 項全過、4 項跳過, 而且四項跳過都是真的不適用,不再有任何一項是「還沒做」。
這是這個專案裡反覆出現的同一個形狀第四次:#59 的 .pyc 快取、#61 的 0/0、 #65 的固定設定點,現在是過期的產物。共同點不是檢查寫錯了, 是檢查通過的對象不是它宣稱的對象。