← 回專案總覽與驗證系統

SG90 幾何量測

沒有照 datasheet 抄數字。step.parts 的 sg90_micro_servo.step(sha256 已驗證)下載後,用 OCC 的 BRepAdaptor_Surface 把每個圓柱面的真實軸線抽出來——因為 build123d 的 Face.center() 回傳的是面的 形心,對半圓柱面會給出偏移的位置,直接拿來當孔位會錯。

r= 1.00 axis_pt=(  0.00, -2.35, 11.00) dir=(+0,+0,+1)   ← 鎖孔 1
r= 1.00 axis_pt=(  0.00, 24.85, 11.00) dir=(+0,+0,+1)   ← 鎖孔 2  → 間距 27.2
r= 2.30 axis_pt=(  0.00, 16.60, 26.70) dir=(+0,+0,+1)   ← 輸出花鍵軸
r= 5.90 axis_pt=(  0.00, 16.60, 22.70) dir=(+0,+0,+1)   ← 齒輪箱圓凸台
量測項量測值datasheet 標稱備註
本體 寬×長×高11.8 × 22.5 × 22.712.2 × 22.8 × 22.5相符
含耳片全長32.432.2相符
耳片厚度2.52.5相符
鎖孔直徑2.02.0 (M2)相符
鎖孔間距27.227.8 差 0.6mm 本專案的座槽依模型實測值開孔; 若用實體 SG90 要重新量或把孔改成長孔
輸出軸離本體中心+5.35—datasheet 通常不標
軸面高於耳片頂4.3—決定連桿與座板的落差

定義:安裝座標系(全專案共用)

原點 = 輸出軸軸線 ∩ 本體頂面;+Z = 輸出軸指出方向。在此座標系下伺服佔 Z ∈ [−22.7, 0], 耳片在 Z ∈ [−6.8, −4.3],兩個 M2 孔在 Y = −18.95 與 +8.25。所有零件的座槽都由這組常數推導, 不重複輸入尺寸。

手臂設計

骨架

J1  底座偏航   軸 +Z   (0,   0.0,  33.0)   s=+1
J2  肩         軸 +X   (0,   0.0,  60.0)   s=+1     ← 上臂 70 mm
J3  肘         軸 +X   (0,   0.0, 130.0)   s=−1     ← 前臂 60 mm
J4  腕         軸 +X   (0,   0.0, 190.0)   s=+1
J5  夾爪       軸 +X   (0, −11.0, 232.0)   s=−1

J2–J4 三軸全部落在世界 X 軸上,所以運動學是嚴格平面的,沒有側向偏置要記帳。 底座繞 Z 偏航,手臂在 Y–Z 平面內伸展。

本專案最重要的一條結構規則:關節手性必須交替

對一顆軸線沿世界 X、軸面在 x = xs 的伺服:

s = +1 : 本體朝 −X,舵盤面在 xs + 6.0(連桿材料在 X > 此值)
         耳片座在 xs − 6.8(托架材料在 X < 此值)
s = −1 : 本體朝 +X,舵盤面在 xs − 6.0(連桿材料在 X < 此值)
         耳片座在 xs + 6.8(托架材料在 X > 此值)

一個連桿若要同時鎖上關節 j 的舵盤、又承載關節 j+1 的伺服, 就必須滿足 horn_side(s_j) == seat_side(s_{j+1}),而這只有在符號交替時成立。 搞錯的話,鎖孔會切在空氣中——幾何仍然輸出成一個合法的單一實體,完全沒有錯誤訊息。 我第一版就是在 J5 上犯了這個錯(見失敗 #3)。

正視圖
正視圖驗證交替配置:由下而上 J2 伺服朝左、J3 朝右、J4 朝左、J5 朝右。 這正是上述手性規則的視覺結果。

真實安裝特徵(不是簡化方塊)

夾爪

雙指四連桿:主動指直接鎖在 J5 舵盤上,惰指裝在腕部延伸柱的 Ø3 銷上,兩者以連桿條耦合成鏡像動作。 開度 2 mm(閉合)至 46.6 mm(全開),實測映射:

J5 關節角 (rad)0.000.100.200.300.450.70
夾爪開度 (mm)2.08.314.721.231.046.6

零件清單與驗證

驗證的原則來自這台機器上先前的 text-to-CAD 壓測結論 (~/3d/ttc-test/RESULTS.md):CAD 工具鏈不檢查任何幾何意圖。 孔切在錯的面上、零件飄浮沒接合,它照樣產出合法 STEP。所以每個零件都要驗 實體數 + 圓柱直徑普查 + 邊界框;沒切到材料的孔在 build123d 裡是完全靜默的。

零件體積關鍵特徵稽核狀態
p1_base 底座76.83 cm³2ר1.7 J1鎖孔、4ר4.5 腳孔、Ø44 環孔✓
p2_turntable_shoulder 轉盤+肩座15.80 cm³6ר1.7(2鎖孔+4舵盤)、Ø21.4 沉孔✓
p3_upper_arm 上臂10.65 cm³6ר1.7、Ø21.4、Ø24 舵盤墊✓
p4_forearm 前臂9.44 cm³6ר1.7、Ø21.4、Ø24✓
p5_wrist_base 腕座11.07 cm³6ר1.7、Ø21.4、Ø3 惰爪銷孔✓
p6_jaw_driven 主動指2.42 cm³4ר1.7 舵盤螺孔、Ø21.4 盲沉孔、Ø3.2 曲柄銷✓
p7_jaw_idler 惰指1.34 cm³Ø3 樞紐、Ø3.2 曲柄銷✓
p8_coupler 連桿條0.34 cm³2ר3.2✓
$ python check_parts.py
...
PARTS_VERIFIED

$ python assembly.py
printed parts : 8   total printed volume 127.9 cm3
envelope      : X[-50.0,50.0] Y[-50.0,50.0] Z[0.0,267.0]
servos        : 5 x SG90
CLASH_CHECK_CLEAN  (no pair overlaps by more than 1 mm3)

連桿質量慣性(由實體算,非估算)

每個連桿 = 列印件(PLA 1.24 g/cm³)+ 它承載的那顆 SG90(9 g)。伺服約佔連桿質量的 40% 且遠離關節軸,用方塊近似會明顯失真,所以用 OCC 的 BRepGProp 取完整慣性張量。

連桿質量質心 (mm)IxxIyyIzz
base_link104.27 g(0.0, −0.2, 12.0)8.35e−58.33e−51.39e−4
turntable28.59 g(−4.9, −2.3, 17.1)7.05e−67.00e−68.64e−6
upper_arm22.20 g(9.6, −2.6, 50.7)1.71e−51.60e−52.06e−6
forearm20.70 g(−9.6, −2.8, 44.5)1.25e−51.15e−51.98e−6
wrist22.72 g(7.4, −5.7, 32.5)1.01e−56.63e−65.73e−6
jaw_driven3.00 g(−8.7, 0.9, 6.5)5.81e−74.72e−71.20e−7
jaw_idler1.66 g(−7.5, −2.3, 12.3)2.91e−72.71e−72.21e−8
合計203.1 g單位 kg·m²,對各連桿質心

伺服模型(訊號鏈)

直接餵 target_radians 的模擬,會顯示真實手臂做不到的動作。真實訊號鏈上每一段都會扭曲指令:

sketch.write(deg)   → Arduino map()  整數運算,0–180° → 544–2400 µs(不是 1000–2000)   → 50 Hz 取樣  伺服每 20 ms 才看一次脈寬,跑更快的控制迴圈是在對自己講話   → 脈寬→角度  SG90 全行程對應 500–2400 µs,與函式庫範圍不對齊                     → 所以 write(0)/write(180) 到不了機械端點   → 迴轉率上限  0.1 s/60° ⇒ 10.47 rad/s,與指令步階大小無關   → 死區  約 5 µs(≈0.5°),以下完全不動   → 關節設定點 (rad)   → MuJoCo position 致動器  kp = 1.011 N·m/rad(10° 比例帶)   → 力矩箝制於 ±0.1765 N·m  ← 這一段是重點   → 剛體動力學

最後那道箝制是整個模型的價值所在:伺服撐不住的東西,會表現成追蹤誤差而不是動作。 這正是你希望在列印零件之前就看到的失效模式。

刻意不放進伺服模型的:內部 PD 迴路與扭矩極限。那兩者會和負載交互作用, 屬於物理引擎的職責(MuJoCo 的 kp/forcerange、PyBullet 的 force)。

一個容易被忽略的細節

Servo::read() 回傳的是最後一次寫入的指令,不是軸的實際位置。 它不是位置感測器。模型裡刻意保留了這個語意。

模擬結果

任務:取放一個 15 mm/4 g 的方塊,從 (0, −100, 7.5) mm 搬到 (−70, −70) mm 附近。 程式為 10 個位姿的序列,位姿角度由數值 IK 求得後量化成整數 Servo.write() 參數。

MuJoCo 完整取放(14.3 s,434 影格)。綠點為 IK 的目標夾持點。
模擬關鍵影格
關鍵影格:接近 → 下降 → 夾取 → 舉起 → 旋轉搬運 → 放下。
伺服追蹤與扭矩
上:虛線為經過 PWM 量化與迴轉率限制後的伺服設定點,實線為關節實際角度, 兩者之差就是伺服的追蹤誤差。下:致動器力矩,紅色虛線為 SG90 堵轉扭矩。 只有肩關節在快速動作時短暫觸頂。

MuJoCo

關節峰值誤差RMS 誤差峰值力矩佔堵轉飽和時間
j1_base_yaw16.11°1.40°0.1318 N·m74.6%0.0%
j2_shoulder18.59°2.81°0.1765 N·m100%0.8%
j3_elbow14.35°1.64°0.1110 N·m62.9%0.0%
j4_wrist12.96°1.21°0.0469 N·m26.5%0.0%
j5_gripper10.27°2.02°0.0773 N·m43.8%0.0%

酬載:最大舉高 +50.0 mm,水平搬運 73.9 mm → GRASP_SUCCEEDED

後續更正(見 #54): 這個舉高是零關節摩擦下的上界,不是值。MJCF 的 frictionloss 未設=0,而塑膠齒輪舵機有實際的靜摩擦。 把它掃過 0–25% 堵轉扭矩後舉高是 43.5–49.6 mm; 任務成敗與搬運距離不受影響。

PyBullet 交叉驗證

同一支程式、同一個伺服模型、不同物理引擎。只跑一個引擎只能告訴你那個引擎怎麼想。

關節峰值誤差RMS 誤差峰值力矩
j1_base_yaw7.70°0.75°0.1765 N·m
j2_shoulder22.45°2.29°0.1765 N·m
j3_elbow2.35°0.18°0.1765 N·m
j4_wrist1.40°0.15°0.1684 N·m
j5_gripper4.77°2.98°0.1765 N·m

酬載:最大舉高 +55.3 mm,水平搬運 75.7 mm → GRASP_SUCCEEDED

兩引擎的差異怎麼讀

任務結果一致(舉高 50.0 vs 55.3 mm、搬運 73.9 vs 75.7 mm),這是重點。 但 PyBullet 的關節追蹤誤差普遍較小、峰值力矩卻幾乎都貼在堵轉值——因為 POSITION_CONTROL 的 maxVelocity 是硬性速度箝制,內部會用滿可用力矩去達成, 與 MuJoCo 的顯式 kp/kv 比例控制不同。兩者都不是「錯」,是不同的伺服近似; 要比較絕對力矩數字時必須知道這件事。

扭矩包絡分析

掃描肩/肘關節全行程 61×61 個姿態,讀 MuJoCo 在零速度下的 qfrc_bias—— 那就是各關節必須提供的重力保持力矩。

扭矩包絡圖
各姿態下的峰值關節力矩佔 SG90 堵轉扭矩的比例。綠線為 50%、紅線為 100% (圖中不存在,因為在這張圖的腕部慣例下最大只到 38.4%)。注意這張圖不是可達範圍的最壞值——見下方更正。
0.0789 N·m可達範圍最壞(肩 +90°、肘 0°、腕 −6°)
44.7%佔堵轉扭矩
100%可保持的姿態比例
~36 g中位姿態下、維持在 50% 堵轉內的額外酬載餘裕

#34 原本發布的「最壞 38%」不是可達範圍的最壞值 靜默 已更正

做第二支手臂的扭矩預算時,用 link_properties.json 的實測質量 手算完全伸直姿態的肩關節力矩,得到 44.8%,與本頁原本寫的 38% 差 6.8 個百分點。 追下去發現不是「兩個定義都對」:

torque_map.py 掃 j2×j3 時,把腕部釘在
    qpos[j4] = clip(π − a2 − a3)        # 讓夾爪保持朝下

那是合理的操作慣例,但受限集合的最大值不是可達集合的最大值。 用同一個模型、同一個 qfrc_bias,只改腕部慣例:

腕部慣例力矩佔堵轉最壞姿態
朝下(原本的圖)0.0678 N·m38.4%j2 +90° j3 0° j4 +90°
打直 j4=00.0788 N·m44.7%j2 −90° j3 −4°
腕部也一起掃0.0789 N·m44.7%j2 +90° j3 0° j4 −6°

手臂做得到那個姿態(j2、j3、j4 都在行程內),使用者程式隨時可以下。 所以原本的 38% 低估需求 6.3 個百分點、相對 17%。

連帶更正一句錯誤描述:原本寫最壞姿態是「肩 +90°、肘 0°,即完全水平伸展」—— 但那個姿態的腕部被釘在 +90°、是折起來的,並不是完全水平伸展。 這句錯誤描述正是掩蓋低估的地方:它讓人以為已經涵蓋了最極端的姿態。

為什麼守門有效:預算腳本一開始就因為這 6.8 點差距而拒絕繼續 (BUDGET_BLOCKED_PENDING_ANCHOR)。我第一版把容許值寫成 8 點、剛好讓 6.8 通過, 發現後改成 3 點——挑一個恰好能過的門檻,就是把錯誤藏起來。 重現:sim/torque_worst_case.py。

後續更正(見 #48):下面這個「50% 堵轉」的標準, 在拿到 FT5425BL 同時標示堵轉與額定的資料之後才看得出來——它本身就在連續工況之外 (44.7% 堵轉 ≈ 134% 連續額定)。第一支手臂撐得住是因為動件只有 70 g、酬載 4 g, 不是因為這個標準保守。第三、四支手臂改以連續額定為基準。

用 50% 堵轉當設計上限而不是 100%,是因為便宜的伺服長時間貼在堵轉會過熱、嗡叫並漂移—— 100% 不是可用的工作點。以這個標準看,這支手臂的機構尺寸對 SG90 是合適的,不是勉強。

對應的 Arduino 韌體

sketch.ino 由 make_ino.py 從同一個位姿表產生, 所以 Uno 寫給 5 顆 SG90 的數字,和模擬餵進伺服模型的數字逐位元相同。

const uint8_t POSE[10][N_JOINTS] PROGMEM = {
  { 92,  96,  36, 149, 113},   // home
  { 92, 116,  27, 146, 113},   // hover over part
  { 92, 140,  24, 133, 113},   // descend
  { 92, 140,  24, 133,  97},   // close gripper
  { 92, 115,  26, 146,  97},   // lift
  { 46, 115,  26, 146,  97},   // traverse
  { 46, 140,  23, 133,  97},   // place
  { 46, 140,  23, 133, 113},   // release
  { 46, 115,  25, 146, 113},   // retreat
  { 92,  96,  35, 148, 113},   // park
};

接線注意(已寫進 .ino 的註解)

5 顆 SG90 必須用獨立的 5 V 電源(≥2 A)並與 Arduino 共地。 五顆同時堵轉的電流遠超過板載穩壓器,典型症狀是動作做到一半板子 brown-out 重開。

v2 機構重繪

v1 精神上是一支 MeArm:平板、直角、每個關節只靠舵盤支撐。它驗證得過, 但外觀與行為都像切割板材。v2 保持運動學完全不變(關節位置、連桿長度都一樣), 只重做機構,對照 cad/design_rubric.py 的 12 條規則。

v1 vs v2 對照
左 v1、右 v2。v2 的連桿有明顯束腰漸變,底座外擴帶圓角,表面連續而非板材堆疊。

參考來源

EEZYbotARM Mk2ABB IRB460 運動學 1:7 縮放、主臂內部走線、垂直軸用滾珠支撐、 夾爪快拆接頭。「一支看起來被設計過的列印手臂」的基準
MeArm3mm 平板雷切、廉價伺服、全 M3。v1 基本上就是板材版的 MeArm
LittleArm V34 個零件、0.3mm 層高可印。零件數紀律的參考
列印機器人通則中空連桿 8–10mm 內部走線;每關節雙軸承(只靠舵盤會撓曲磨損); 螺孔周圍壁厚 ≥2mm;伺服座要有出線槽

改了什麼

雙剪關節每個俯仰關節一側是舵盤、對側是 623ZZ 軸承跑在 Ø3 銷上。 這是最大的功能升級——只靠舵盤的關節會撓曲並磨損
手性交替規則消失v1 需要 horn_side(s_j) == seat_side(s_{j+1}), 只有符號交替時成立。那是單剪的產物:連桿只有一側有材料。 叉臂跨過伺服後兩側都有板,下一顆伺服的座可以放任一側。 統一手性後手臂從 59mm 寬降到 47mm,每節形狀也一致
錐形 loft 連桿斷面是從根部往端部收縮的圓角矩形,受力路徑上不再有等寬平板。 實測根/腰比 1.72
其他Ø8 內部走線通道、不切斷受力路徑的減重開口、每顆伺服的出線槽、 轉盤下 8 顆 Ø6 滾珠溝槽(取代塑膠對塑膠滑動)、燕尾快拆夾爪介面

關鍵的建構方式改變

叉臂的縫要「挖」出來,不是「拼」出來

v2 第一版我用 union 疊很多板與腹板,結果比 v1 更亂——它讀起來像一堆盒子鎖在一起。 無論加多少圓角,一堆方塊的聯集看起來就是方塊。

改成:每節連桿是一整塊斷面漸變的 loft,然後把伺服的掃掠體積減掉, 叉臂的縫因此是挖出來的。外表面保持連續,這才是「被設計過的零件」與「一堆邊角料」的差別。

規則評分(可量化的 7 條)

規則量測結果
R1/R2 雙剪 + Ø10.1 軸承座3/3 個俯仰連桿通過
R3 錐形斷面(根/腰 ≥1.3)42.2/24.5 = 1.72通過
R4 圓角化(凸面種類數)v1 的 10 → v2 的 15通過
R5 內部走線通道 Ø82/2 個長連桿通過
R8 連桿實體率 <60%23.5%通過
R9 底座滾珠溝槽8 個 Ø6.5 珠座通過
R12 寬度/連桿長 ≤0.750.8/70 = 0.73規則作廢

R12 不是沒做到,是規則本身跟 R1 互斥

0.7 這條線是我從 MeArm / LittleArm 這類單剪參考設計抄來的。 雙剪跨過一顆 SG90 的寬度有硬下限:舵盤凸台 +11.6 到銷座 −36.4 共 48 mm, 對 70 mm 連桿就已經是 0.69,再加任何壁厚必然破線。

保留 R1(結構)、放寬 R12(外觀)——關節撓曲是會壞的,寬一點只是不好看。 這種衝突記在 design_rubric.py 的 RULE_CONFLICTS 裡,而不是默默調數字讓它通過。

重繪過程中抓到的 6 個問題

問題怎麼發現的
出線槽開在鎖孔上孔數普查:每個零件都少一顆 Ø1.7。槽位 Y∈[−24.3,−15.3] 正好蓋住 Y=−18.95 的鎖孔
夾爪整組留在 −X干涉 738mm³。v1 的 J5 是 s=−1,v2 統一成 s=+1 後舵盤移到 +X,夾爪忘了跟著翻
托架腹板穿過叉臂走廊干涉 924mm³。銷座在叉臂外、伺服座在叉臂內,直連必然穿過
伺服耳片沒有讓位干涉 69mm³。窗口只清本體 22.5mm,耳片有 32.4mm
讓位切口把零件切成兩塊實體數 = 2。干涉檢查是乾淨的,只有實體數看得出來
舵盤沉孔後方沒有材料孔數普查:4 顆舵盤螺孔只剩 2 顆。連桿收窄後 Ø21.4 沉孔吃掉剩餘厚度

#53 「數圓柱面」的普查法在 v2 失效 已修

v1 沒有圓角,所以每個圓柱面都是孔,數面等於數孔。v2 到處都是 RectangleRounded, 一個半徑 1.5 的圓角和一個 Ø3 銷孔在「半徑」上完全一樣。

而且 v2 的多壁結構讓一個孔穿過兩道壁會產生兩個圓柱面, 讓正確的零件看起來多了一顆螺孔。

修法:feature_census.py 改為(一)只數凹面(孔是凹的、圓角是凸的), (二)依軸線去重而不是數面。順帶發現 mirror() 會翻轉面方位讓凹凸判斷反過來, 所以惰爪改成直接用負 Y 建構,不用鏡射。

還沒做:v2 尚未接進模擬與驗證系統

目前 v2 只到 CAD。URDF、MuJoCo 模型、沙盒幾何、以及那 205 項驗證套件 全部仍跑在 v1 上。要接上去需要:重算 link_properties.json、 重匯連桿網格、重生 MJCF、重訂黃金基準,並且更新沙盒與對照頁的示範角度 ——因為 j3 的 SERVO_SIGN 從 −1 變成 +1,所有 write() 數值都會變。

這不是小工程,而且會讓已驗證的數字全部重來,所以我沒有擅自動它。

全螺絲組裝與工程圖

把所有緊固件都畫出來、組裝、再出三視圖與爆炸圖,原本的目的是讓 VLM 判讀。 結論是看圖不夠——真正的組裝缺陷全部是「數字」而不是「輪廓」, 所以這批圖之後才有了 T8 可組裝性那一層(見驗證章節)。 圖仍然保留,因為它們是人工複核最快的入口。

SG90-Arm v2 三視圖與等角視圖
三視圖 + 等角。8 個列印件 + 5 顆 SG90 + 43 件緊固件 + 8 顆鋼珠。 螺絲長度不是統一的:由每個孔沿孔軸的實際材料厚度從標準階梯自動挑選 (M2×6 / ×8 / ×10),所以 BOM 是幾何的讀數而不是對幾何的主張。
SG90-Arm v2 爆炸圖
爆炸圖:整機徑向展開,右側另附 J3 肘關節的分解—— 舵盤側鎖 4 顆 M2、對側 623ZZ 軸承配 Ø3 銷,這就是 R1 雙剪的實體。
零件圖示列表與硬體清單
零件圖示列表:8 個列印件各自的縮圖與體積,加上由幾何推導的硬體清單。 這張圖上的每個數量都不是手寫的,而是從實體的孔軸數出來的: 一顆沒有孔可去的螺絲根本放不進來,一個沒人鎖的孔會以「未使用孔」出現。
項目數量來源
M2 自攻螺絲305 伺服 ×(2 耳片 + 4 舵盤);長度混合 ×6 / ×8 / ×10
M3 樞紐螺絲2夾爪曲柄,×6 與 ×8 各一
M4 內六角4底座鎖工作檯
Ø3 定位銷43 個俯仰關節的軸承銷 + 夾爪惰動側
623ZZ 軸承3每個俯仰關節一顆(R1 的量化條件)
Ø6 鋼珠8底座偏航推力環(R9)
SG90 伺服馬達5step.parts 下載並經 sha256 驗證的真實 STEP

重生方式:cad/make_drawings.py、cad/joint_explode.py、 cad/make_partsheet.py。三者都直接讀當下的 CAD,所以幾何一改圖就跟著改。

動手製作前的調查:兩個 build blocker 與一份先做清單

要真的把它做出來,先查那些我在 CAD 裡填了具體數字、而填錯就做不出來的地方。 查完發現兩個真正的 blocker,其中一個是設計本身的錯,不是製造難度。

左:耳片壁厚預算表;右:舵盤花鍵整齒索引與 j4 行程
左:壁厚的預算表。標稱 1.10 mm 已是 SG90 的幾何下限, 再扣掉同型異廠的本體浮動就剩 0.80 mm。右:舵盤只能按整齒裝, 模型原本要求的 30° 落在齒與齒之間(已改為 2 齒 = 34.29°)。

Blocker 1:j4 的舵盤索引角在花鍵上裝不出來 靜默 已修正

SG90 的輸出花鍵是 21 齒 → 17.14°/齒,舵盤只能離散地按整齒安裝。 而模型裡 HORN_INDEX_DEG["j4_wrist"] = 30.0,= 1.75 齒,物理上裝不出來。

影響是實質的,不是四捨五入的問題。j4 行程寬度固定 180°,索引角只是平移它, 而腕部這支手臂需要到 +118°:

安裝j4 行程能否到 +118°
1 齒(17.14°)[−72.9°, +107.1°]✗ 出界 10.9°
2 齒(34.29°)[−55.7°, +124.3°]✓ 還有 6.3° 餘裕
原模型值(30°)[−60.0°, +120.0°]數字可行但裝不出來

已修正:索引角不再是可以手寫的實數,改成由齒數推導—— HORN_TEETH = {"j4_wrist": 2},HORN_INDEX_DEG 由它乘上 360/21 算出。 這樣它再也不可能落在齒與齒之間,而不是靠一條檢查事後抓。 原始碼註解裡那句「~34 deg, rounded to 30 for the model」就是錯誤來源: 那次四捨五入沒留下任何痕跡,因為 30° 仍涵蓋 IK 要的 +118°, 於是模擬與韌體一致地跑在一個沒人裝得出來的組態上。 連帶重新基準化 T7,並因此發現黃金基準本身也有問題(見 #40)。

為什麼原本的測試沒抓到:測試檢查的是「行程寬度=180°」與「索引角與極限一致」, 兩者在 30° 下都成立。當時沒有任何一項知道花鍵是離散的。

已經補上了:T2 新增「舵盤索引角必須是整齒」,常數 SPLINE_TEETH = 21 放在 HORN_INDEX_DEG 旁邊。這條檢查曾刻意留成失敗, 讓套件回報 204/205 而不是全過——把已知缺陷寫成散文而不寫成測試, 正是這個專案要避免的事。現在索引角改成 2 齒、T7 重新基準化,它通過了。

Blocker 2:耳片壁厚已在下限,而 SG90 是被大量翻製的零件

T10 那節算出耳片螺孔的壁厚上限是 1.10 mm,是 SG90 的幾何決定的。 問題是 SG90 沒有單一製造商:它由中國數百家小廠翻製, 「尺寸、速度、扭矩、軸的大小會依來源甚至批次大幅變動」, 資料表寫 22.5 mm 的寬度實測有約 0.3 mm 的浮動, 而且一批的舵盤常常裝不上另一批的軸。

對這個設計的意義很直接:0.3 mm 的本體浮動會把 1.10 mm 的壁吃到 0.80 mm, 而 0.4 mm 的裝配間隙也幾乎被吃光。最脆弱的位置正好是 T10 已經標記為「無法再改善」的那一處。

可行的做法:

  1. 五顆伺服買同一批,到手先量:本體長寬、耳片孔距、舵盤花鍵
  2. 量到的值填回 cad/sg90_params.py —— 它是單一來源, 改一個常數整組零件與整套驗證都會跟著重算(包含那條推導出來的壁厚下限)
  3. 材料用 PETG 不要用 PLA。理由不是偏好:自攻螺絲在脆性材料上會直接裂, PETG 韌性夠;而 0.80–1.10 mm 的壁正是最會裂的地方

尺寸選擇的驗算

我填的值查證結果依據
M2 攻牙孔 Ø1.7正確 自攻螺絲的引孔應為外徑的 85–90%;M2 → 1.7–1.8 mm。1.7 是 85%,偏緊端, 對 PETG 合理
軸承座 Ø10.1(裝 Ø10 的 623ZZ)需實印驗證 壓配的建議是負間隙(PLA 每側 0.05、PETG 可到 0.10)。 Ø10.1 是正間隙 +0.1 —— 但 FDM 的孔實際會比模型小 0.1–0.3 mm, 所以印出來大概落在 9.8–10.0,剛好變成壓配。 這是碰巧不是設計,而且高度依賴印表機。必須印試片實測
裝配間隙 0.4 mm/側偏緊 校準良好的 FDM 在 XY 約 ±0.2 mm,加上伺服本體 0.3 mm 浮動就吃光了。 建議先印一個伺服座試片試裝

電源:不能從 Arduino 供電

五顆 SG90,每顆堵轉電流約 0.5–0.7 A,同時動作時合計 2.5–3.5 A。 Arduino 的穩壓器承受不了,會 brownout 或直接燒。需要:

注意這裡的 0.5–0.7 A 是社群與資料表的引用值。 依前一節的邏輯,這正是本專案會堅持實測的數字 —— 而量它需要的 INA219 也正好是補 validation 缺口要用的那一顆。

組裝順序:伺服要先歸中再上機

這一條所有教學都強調,而且是不可逆的:舵盤裝上去之後角度就固定了。

  1. 先通電把每顆伺服轉到 90°(中位),保持通電再裝舵盤 —— 否則關節的行程會有一半被機構極限吃掉
  2. 裝舵盤時若卡不進去、或裝進去孔不在正交方向,把它拔起來轉約 45° 再試
  3. j4 的舵盤裝在 +2 齒(34.29°),不是中位(見 Blocker 1)
  4. 依 T8 推導出的裝配順序組裝:先把伺服鎖上托架,再套上一節連桿的叉臂。 T8 就是依這個順序判定起子路徑的,所以順序反了會有 9 根螺絲鎖不到

建議的「先做什麼」清單

#動作為什麼放在這個順序
1買五顆同批 SG90 + 3 顆 623ZZ,到手先量 所有下游數字都由 sg90_params.py 推導。量完才知道要不要改
2印公差試片:伺服座 + 軸承座 + 兩個 Ø1.7 孔 幾分鐘的事,但決定 Ø10.1 與 0.4 mm 間隙對不對。 Printables 上有現成的孔公差量規與軸承校正件
3量到的值填回 sg90_params.py,重跑 tests/run_all.py 整套檢查會重新推導,包含壁厚下限。這步是整個驗證系統真正兌現價值的地方
4決定 j4 索引角(建議 34.2857),重新基準化 T7 Blocker 1,已修正為 2 齒(34.29°)
5PETG 印全部 8 件,孔周 100% 填充、多加牆 0.80–1.10 mm 的壁需要韌性材料與足夠牆數
6接獨立 5 V/3 A 電源,共地,先單顆歸中 組裝前的最後一步;歸中之後才碰舵盤
7依 T8 順序組裝 先鎖伺服再套叉臂

可用的開源資源

資源用途
Hole Tolerance Gauge 孔徑量規,每欄遞增 0.0–0.3 mm。用來定 Ø1.7 與 Ø10.1 要不要補償
Printer Tolerance Test · Diameter Tolerance Test 直接量出自己印表機在 XY 的實際偏差
Hole Tolerance Calculator · Tolerance & Fit Calculator 壓配/滑配/間隙的建議值,含軸承與螺絲
RobotGeek 101: Servo Centering 伺服歸中的標準流程
MeArm IK 校正 同樣是 SG90 手臂,記錄了實機角度與模型角度對不上時怎麼校
Arduinobot 韌體與 ROS 2 介面的參考切法(見ROS 節)

一句話總結這節

設計本身沒有製造難度上的問題——8 件都放得進標準床、都有貼床面、最差擺法只有 11% 需支撐。 真正的風險是兩個尺寸決策:一個是模型裡填了物理上裝不出來的舵盤角度(可修,但會動到已發表數字), 另一個是壁厚已在 SG90 的幾何下限、而 SG90 本身是尺寸會浮動的翻製零件(要靠實測與換材料吸收)。 兩者都不是印出來才會發現的問題,但都是不查資料就不會發現的問題。