English
🤖 LateAI
首页做自己的机器人 › MIT Drake 真实动力学模拟 Annin AR4(详细分册)
🧬

MIT Drake 真实动力学模拟 Annin AR4 (MK5) 六轴机械臂(详细分册)

Real-Dynamics Simulation of Annin AR4 (MK5) with MIT Drake (pydrake)
📶 进阶 ⏱ 约 1 晚 💰 软件免费 🦾 DIY · 站内原创

📌 本分册目标

站内的 AR4 真实 3D 轨迹实验室运动学演示:它在浏览器里做 6 轴 IK 解算,算出「关节角要转到多少」,然后用 Three.js 把机械臂画出来。它会告诉你几何上到得了,但不会告诉你真机转不转得动 —— 因为那里没有重力、没有惯性、没有电机扭矩上限。

本分册补上这一层:用 MIT Drake 把同一台 AR4 放进真实动力学里跑。URDF 的关节原点与官方 ar_macro.xacro 逐字段对齐,Drake 会真实计算重力矩、惯性耦合、科氏力与各轴扭矩,于是你能亲眼看到:同样一条轨迹,真机需要付出的跟踪滞后扭矩饱和到底有多少。

你会亲手得到:一份末端轨迹 CSV、一组「最大跟踪误差 / 扭矩饱和占比」的量化结论,以及一段可直接离线播放的 3D 动画。这些结论直接决定真机该选多大的减速比、跑多快、末端能挂多重。

💡 先仿真、后花钱:AR4 真机按 BOM 采购通常要数千元,而 Drake 完全免费(BSD-3-Clause)。本页所有程序都可直接运行,URDF 参数全部有据可查。

🧰 环境准备

⚠️ 版本搭配是本教程唯一的「硬门槛」:Drake 的 wheel 与 Python 版本、CPU 架构强绑定。装不上时先确认 python3 --versionuname -m(x86_64 / arm64),再挑对应版本。

📦 第 1 步:四个文件,一个目录

四个文件放在同一个目录下(脚本按自身位置找 URDF,不需要改路径)。若只想跑数值,前两个文件的下一个就够了。

🔧 第 2 步:读懂 URDF —— AR4 的「身份证」

URDF 用一棵树描述机器人:节点是 link(连杆),边是 joint(关节)。下表就是 ar4_arm.urdf 的全部关节参数,逐字段取自官方 annin_ar4_description/urdf/ar_macro.xacro(MK5)—— Drake 就是靠这些数算真实的动力学。

关节父 → 子连杆轴向相对位置 (m)限位effort (N·m)子连杆质量 (kg)
world → base(固定)0 0 01.20(底座)
J1base → link10 0 1(绕 Z)0 0 0.092±170°200.90
J2link1 → link20 0 -10 0.06415 -0.07778-42° ~ 90°201.10(大臂)
J3link2 → link30 0 -10 -0.305 0-89° ~ 52°150.80(小臂)
J4link3 → link40 0 -10 0 0±180°80.50
J5link4 → link51 0 00 0 -0.22294±105°80.35
J6link5 → link60 0 1(绕 Z)0 0 0.041±180°50.20
link6 → tool_link(固定)0 0 0.030.05(末端)

全臂自重合计约 5.1 kg,J2→J3 臂长 0.305 m、J4→J5 臂长 0.223 m,末端法兰到 tool 参考点 0.03 m。

ar4_arm.urdf · 关键结构(节选,完整文件见上方下载)
<!-- ① 必须有一个固定关节把底座焊在世界原点,否则整条臂会自由落体 -->
<link name="world"/>
<joint name="world_joint" type="fixed">
  <parent link="world"/><child link="base"/>
  <origin xyz="0 0 0" rpy="0 0 0"/>
</joint>

<!-- ② 关节 = 父子关系 + 相对位置 + 轴向 + 限位 + 扭矩上限 + 阻尼 -->
<!--    注意 rpy="1.57079633 0 -1.57079633" 就是官方的 rpy="90 0 -90" -->
<joint name="J2" type="revolute">
  <parent link="link1"/><child link="link2"/>
  <origin xyz="0 0.06415 -0.07778" rpy="1.57079633 0 -1.57079633"/>
  <axis xyz="0 0 -1"/>
  <limit lower="-0.73303829" upper="1.57079633" effort="20" velocity="1.5"/>
  <dynamics damping="0.10"/>
</joint>

<!-- ③ 连杆 = 外观 + 碰撞 + 惯性(Drake 靠 mass 与 inertia 算真实受力) -->
<link name="link2">
  <visual><origin xyz="0 -0.15 0"/><geometry><box size="0.07 0.30 0.07"/></geometry></visual>
  <inertial>
    <origin xyz="0 -0.15 0"/>
    <mass value="1.10"/>
    <inertia ixx="0.008700" ixy="0" ixz="0" iyy="0.000898" iyz="0" izz="0.008700"/>
  </inertial>
</link>
💡 惯量怎么来:统一按长方体绕质心公式 I = m/12·(a² + b²),用 AR4 真实连杆尺度(铝件 + NEMA23 / NEMA17 电机)估算,总重 5.1 kg 与真机同量级。想要更精确,把每节实测质量与质心替换进去即可,控制器一行都不用改
⚠️ AR4 与常见教学臂有个关键差别:J2 / J3 的限位是不对称的(-42°~90°、-89°~52°),不是 ±180°。所以程序里摆动的目标角以限位中点为基准,而不是 0 —— 若照惯例绕 0° 摆动,J3 会在负半周直接撞限位。

▶️ 第 3 步:跑起来

1

先确认环境正常

python3 arm_wave_drake_ar4.py --duration 3

看到 [ar4] plant=6 dof · gravity=-9.81 m/s² 就说明 Drake 装好、URDF 也加载成功了。

2

正式跑一次摆动

python3 arm_wave_drake_ar4.py --duration 14 --freq 0.5 --amp-deg 25

终端每 0.5 s 打印一次实测关节角与末端 XYZ;结束时给出跟踪误差、扭矩饱和占比,并把末端轨迹写入 ar4_trail.csv

3

对照实验:重力补偿到底有多重要

--no-ff:关掉重力补偿前馈 —— 手臂会被自重拽着往下掉,位置环单独扛不住自重。

--no-gravity:关掉重力场 —— 跟踪误差会明显变小,这正说明剩下的误差来自惯性而非重力。

实测输出(Drake 1.34 · macOS · Python 3.12 · 14 s 摆动)
[ar4] plant=6 dof · gravity=-9.81 m/s²
[ar4] 扭矩上限(N·m) = [20. 20. 15.  8.  8.  5.]
[ar4] 有效惯量(kg·m²) = [9.64e-02 2.16e-01 7.46e-02 5.37e-04 2.03e-03 9.08e-05]
[ar4] 整定 kp = [400. 400. 300. 160. 160. 100.]
[ar4] 整定 kd = [ 9.94 14.89  7.57  0.27  0.91  0.05]
t=7.0s 实测(rad):   -0.153   -0.012   -0.600    0.153    0.430    0.278  末端XYZ(m): 0.052 -0.308 0.616
[summary] 最大跟踪误差(rad): 0.000 0.000 0.000 0.000 0.000 0.000
[summary] 扭矩饱和占比(%):  0 2 0 0 0 6
[summary] 末端轨迹范围(m): x[-0.144, 0.052] y[-0.517, -0.308] z[0.216, 0.616]
[done] 轨迹已保存 -> ar4_trail.csv
📊 末端 XYZ 是 tool_link 在世界系的坐标,与站内 3D 实验室同一约定,可直接对照。这里饱和只出现在 J2(承重最大)与 J6(启动瞬态),稳态跟踪误差在 1 kHz 下收敛到 0。注意有效惯量一列差了 三个量级(J2 的 0.216 对 J6 的 9e-5),这个悬殊正是下一节那个坑的源头。

PD 增益是怎么来的

不需要手调增益 —— 程序会先用动能法测出每个关节的有效转动惯量(令该关节速度为 1,则动能 T = ½I),再按下面的判据整定:

kp = min( effort / 0.05 ,  0.5 · (1.5/Δt)² · I )
     #  前项:0.05 rad 误差内电机不进入饱和(线性工作区)
     #  后项:1 kHz 数字控制的稳定带宽约束(ω·Δt < 1.5)
kd = min( 1.6 · sqrt( kp · I ) ,  I / (2Δt) )
     #  前项:略过阻尼,抑制摆动超调
     #  后项:微分时间常数 τ = I/kd 必须 ≥ 2Δt(腕部抖动的解药,见下节)

想手动覆盖,用 --kp / --kd;想看每次的力矩细节,加 --debug 会额外打印命令、误差、实际角速度与重力矩 —— 排查数值抖动靠的就是那一行角速度。

怎么读 summary

⚠️ 腕部抖动:饱和 74%,误差却几乎为零

这是本教程在实际调试中真实踩到的坑,值得单独讲。早期版本的 kd 只由阻尼比决定,跑 14 s 摆动的结果是:J6 饱和占比 74%、J4 饱和 30%,但两者跟踪误差分别只有 0.073 / 0.006 rad —— 乍看「误差这么小,应该没问题」。

--debug 把实际角速度打出来,才看见真相:

   err :  -0.000  -0.000  -0.000   0.005  -0.000   0.022
   qd  :   0.440   0.035  -0.257 -10.435   0.484  47.229   ← J4 / J6 疯了
   u   :  -2.288  -5.854   0.025   5.609  -0.767  -4.982

J6 的实际角速度是 ±47 rad/s,而目标速度只有 0.22 rad/s,相差 200 倍 —— 它在正负之间高频抖动,平均值相互抵消,所以位置上看不出异常,力矩却一直顶在上限

根因是微分时间常数 τ = I/kd。腕部三轴的惯量比 J2 小 2~3 个量级(J6 仅 9.08e-5 kg·m²),而 kd 按阻尼比公式算出 0.15,对应 τ = 0.6 ms —— 比控制周期 1 ms 还短,一步之内速度反馈就能把力矩打满,于是形成极限环。

关节有效惯量 (kg·m²)kd(修正前)τ = I/kd实际表现
J69.08e-050.150.61 ms < Δt±47 rad/s 抖动 · 饱和 74%
J45.37e-040.471.14 ms ≈ Δt±10 rad/s 抖动 · 饱和 30%
J52.03e-030.912.23 ms > Δt正常
J1~J37.46e-02 ~ 2.16e-017.57 ~ 14.899.7 ~ 14.5 ms正常

规则很清楚:τ 大于控制周期的关节都正常,τ 小于控制周期的都在抖。加上 kd ≤ I/(2Δt) 这条约束后(J6 的 kd 从 0.15 降到 0.05):

指标修正前修正后
qd(J4 / J6)-10.4 / +47.2 rad/s0.204 / -0.168 rad/s
跟踪误差(J6)0.073 rad0.000
饱和占比(J4 / J6)30% / 74%0% / 6%

修正后 qd 与目标速度 cmd_dot 逐位吻合(-0.204 = 0.218·cos 3.5),抖动消失、真正跟上了。

⚠️ 结论:仿真里出现「跟踪误差很小、力矩却长期饱和」,几乎总是数值问题而非物理问题。真机的电流环带宽、关节摩擦与减速箱柔性会天然滤掉这种高频抖动,而 URDF 里是理想力矩源,没有这些环节 —— 所以这种抖动只存在于仿真里,别据此判定真机做不到。

三组对照实验(实测数据)

场景最大跟踪误差 (rad)饱和占比说明
默认(PD + 重力前馈)0 0 0 0 0 0J2: 2%, J6: 6%前馈把重力矩完全抵消
--no-ff(关重力补偿)0 0.016 0.003 0 0.002 0J2: 2%, J6: 6%J2 出现 0.9° 稳态误差
--no-gravity(关重力场)0 0 0 0 0 0J2: 1%, J6: 6%J2 饱和减半

三行数据把「重力补偿前馈值不值」量化了:不加前馈,J2 会稳定偏差约 0.9°;而关掉重力场后连饱和都减半,说明 J2 的力矩份额主要来自扛自重。

🔁 第 4 步:回放网页导出的轨迹(本分册的重头戏)

这是把「网页运动学」和「真实动力学」接起来的关键一步。

1

先在 3D 实验室导出关节角

打开 AR4 真实 3D 轨迹实验室,选一种轨迹(正弦 / 圆形 / 三角形),点「导出」得到 q_traj.npy —— 它是页面上那条轨迹对应的 N×6 关节角序列(单位 rad)。

2

把它喂给 Drake

python3 arm_wave_drake_ar4.py --q-traj q_traj.npy

程序会把 N 帧线性重采样到 5 s(可用 --traj-period 改),然后让 PD 控制器去追这条轨迹。

3

读结论

网页里这条轨迹的「跟随误差」是 IK 解算误差(纯几何,通常亚毫米级);Drake 里的「最大跟踪误差」才是 真机代价(控制滞后 + 惯性 + 扭矩上限)。两者一对比,你就知道图纸上漂亮的轨迹,真机跑起来要打多少折扣。

💡 换模型不用改代码:想让真实的 18 个零件网格也参与动力学,加 --urdf ar4_real.urdf 即可(网格只影响外观与惯量分布,控制逻辑完全一致)。

🎬 第 5 步:真实外观 3D 与离线动画

⚠️ Meshcat 的 3D 视野是在浏览器里打开的(终端会打印一个 http://localhost:7000 之类的地址)。若在远程服务器上跑,需要 SSH 端口转发才能看到画面。
🌐 不想装环境?直接在线看。本站已用上面的命令把三种形状导出成单文件动画(每个约 7.5 MB,内含 18 个官方零件网格与整段仿真数据),点开即播,浏览器直接跑,无需安装 Drake / Python: 正弦 sine · 圆形 circle · 三角形 tri · 静态文件只负责回放,真正的动力学计算发生在导出那一刻(也就是上面那条命令里)。

⚠️ 两个必踩的坑(脚本已内建处理,这里说明原因)

坑一:官方 URDF 的碰撞体会把腕部关节「锁死」。AR4 官方 URDF 给每个连杆只配了一个粗略的长方体 collision,相邻连杆的盒子互相穿插 —— link4 的盒子覆盖 z∈[-0.22, 0],link5 的盒子又落在同一区间。Drake 的接触求解器于是施加巨大的穿透修正力:实测 J4 饱和占比 89%、跟踪误差 0.430 rad(25°)完全跟不上;把 collision 剥离后立刻恢复到 0% / 0.007 rad。脚本默认自动剥离(源 URDF 不动,另存一份临时文件),要保留用 --keep-collisions

坑二:连续积分器跑不动这个模型。腕部 J6 的有效惯量只有 9.08e-5 kg·m²,系统刚性极强,连续时间积分器被迫把步长压到极小 —— 实测跑了 4 分钟连 1 秒仿真都没到。脚本默认改用 0.001 s 离散半隐欧拉(正好等于 1 kHz 控制周期),8 秒仿真约 40 秒跑完。

配置J4 跟踪误差J4 饱和占比8 s 仿真耗时
连续积分 + 带碰撞> 4 min(1 s 都跑不完)
离散积分 + 带碰撞0.430 rad89%约 45 s
离散积分 + 剥离碰撞(默认)0.007 rad0%约 45 s
📐 这两个坑有个共同点:它们都不是控制问题,而是建模问题。碰撞体本来是给碰撞检测用的,却污染了动力学;积分器本是数值细节,却能让仿真慢到不可用。拿到一个新模型先跑一次「能不能动起来」的最小实验,比直接调增益有效得多。

🎯 第 6 步:在画板上画 正弦 / 圆形 / 三角形(运动轨迹可视化)

站内 3D 轨迹实验室 里的三种末端轨迹,现在可以直接在 Drake 里跑 —— 用的是同一块画板、同一组目标点,差别只在于:网页是瞬时的运动学解,这里是 1 kHz 扭矩控制下的真实动力学响应。

一条命令选一种轨迹(--shape sine | circle | tri)
# 三种形状同时看:各起一个进程、端口分开,浏览器分别打开对照。
# 场景里会自动画出:蓝线 = 参考轨迹   黄线 = 末端实际轨迹   红球 = 当前目标点
python3 arm_meshcat_ar4.py --shape circle --port 7000 --hold --html none   # → localhost:7000
python3 arm_meshcat_ar4.py --shape sine   --port 7001 --hold --html none   # → localhost:7001
python3 arm_meshcat_ar4.py --shape tri    --port 7002 --hold --html none   # → localhost:7002

# 只跑一种:端口默认就是 7000,不用写 --port
python3 arm_meshcat_ar4.py --shape sine --hold --html none

# 导出可离线播放 / 转发分享的单文件动画
python3 arm_meshcat_ar4.py --shape sine --html arm_sine_3d.html

# 只算数值,并把解出的关节轨迹存下来,喂给数值脚本做定量分析
python3 arm_meshcat_ar4.py --shape tri --no-meshcat --save-q q_tri.npy
python3 arm_wave_drake_ar4.py --q-traj q_tri.npy
🎬 三种形状的在线动画(零安装,点开即播)正弦 · 圆形 · 三角形 —— 与上面 --html 命令的产物完全一致,可直接分享或存档。
💻 本机实时 3D 入口(仅限本机):先在本机把上面的进程跑起来,再访问终端打印的地址,三种形状分别对应 localhost:7000(圆形)/ 7001(正弦)/ 7002(三角形)。这些地址指向你自己的电脑,线上访客点开无法访问,所以这里只做说明、不做成链接。

画板在 Drake 世界系里的位置

网页用 Y-up 显示,Drake 的世界系是 Z-up,中间差一次 Rx(-90°)。换算后网页坐标 (x, YC, ZP) 对应 Drake 的 (x, -ZP, YC):画板竖直立在正前方 y = −0.174 m、板中心高 z = 0.40 m,板面 0.30 × 0.22 m。三种形状与网页的 targetAt() 逐字段一致,都是 5 s 一个周期的闭合路径,可以一直循环。

--shape末端怎么走板面范围(x 横向 / z 高度)一个周期
sine左右往返一次,板面留下正弦曲线x ∈ ±0.075 m;z = 0.40 + 0.045·sin(62x)5 s(去程 2.5 s + 回程 2.5 s)
circle逆时针一整圈x ∈ ±0.055 m;z = 0.40 ± 0.055 m5 s
tri从正上方顶点起逆时针一圈x ∈ ±0.048 m;z ∈ [0.373, 0.455] m5 s

末端轨迹怎么变成关节角:用 Drake 的逆运动学

和网页一样,只解 J1 / J2 / J3 / J5,J4 与 J6 全程锁 0(末端姿态不变,笔尖始终垂直板面)。区别是这里用 Drake 自带的 InverseKinematics,约束直接写在 URDF 的真实运动学上。采样点数用 --shape-points 调(默认 240 点/圈)。

⚠️ 又一个真实的坑:IK 会跳分支。沿路径稀疏取点、或每点都拿同一个固定初值去解,Drake 会中途跳到另一组完全不同的解 —— 实测 J5 从 0.19 rad 突跳到 1.62 rad,关节轨迹直接断裂,这种轨迹真机执行不了。算法是密集采样 + 逐点续解:每点以上一点的解作初值,再加一个很轻的正则项把解往初值附近拉。修正后步间最大变化只有 0.008 rad。
arm_meshcat_ar4.py · 轨迹生成与逐点续解 IK(节选,完整文件见下载)
def shape_points(shape, n=240):
    """画板上的末端目标点,返回 n×3(Drake 世界系 Z-up)。
    与网页 arm_traj_3d.html 的 targetAt() 完全一致,且都是闭合周期路径。"""
    ts = np.linspace(0.0, 1.0, n, endpoint=False)
    ...
    pts[:, 1] = -BOARD_ZP            # 全部落在画板平面内
    return pts


def solve_ik_path(plant, frame, pts, seed=None):
    q = Q_SEED.copy()                # 与网页同一个初值
    for k, p in enumerate(pts):
        ik = InverseKinematics(plant)
        qv, prog = ik.q(), ik.prog()
        ik.AddPositionConstraint(frame, [0, 0, 0], plant.world_frame(),
                                 p - 2e-4, p + 2e-4)
        prog.AddQuadraticErrorCost(1e-3 * (W.T @ W), q, qv)   # 贴着上一个解
        prog.AddBoundingBoxConstraint(q[3], q[3], qv[3:4])    # J4 锁 0
        prog.AddBoundingBoxConstraint(q[5], q[5], qv[5:6])    # J6 锁 0
        prog.SetInitialGuess(qv, q)
        res = Solve(prog)
        if res.is_success():
            q = np.array(res.GetSolution())
        Q[k] = q
    return Q

把运动轨迹画进 3D 场景

arm_meshcat_ar4.py · 参考轨迹 / 实际轨迹 / 当前目标点
# 开跑前:画板里放上参考轨迹(蓝线)与当前目标点(红球)
meshcat.SetLine("/board/ref", np.asfortranarray(ref_pts.T), line_width=3.0,
                rgba=Rgba(0.42, 0.55, 1.0, 0.85))
meshcat.SetObject("/board/target", Sphere(0.005), rgba=Rgba(1.0, 0.36, 0.45, 1.0))

# 主循环里:每 0.02 s 采一次 tool_link 的世界坐标,连成黄线
if t >= next_trail:
    next_trail += args.publish_period
    trail.append(plant.EvalBodyPoseInWorld(ctx, tool).translation())
    meshcat.SetLine("/trail", np.asfortranarray(np.array(trail).T),
                    line_width=3.0, rgba=Rgba(1.0, 0.84, 0.3, 0.95))
    meshcat.SetTransform("/board/target", RigidTransform(ref_now))
💡 红球(当前目标点)与末端之间的空隙就是真实动力学的跟踪滞后,黄线与蓝线的间距则是整条轨迹上的累计偏差。网页上那条「完美贴合」的曲线,在真机上到底偏多少,一眼就能看出来。不想要轨迹线可以加 --no-trail

实测结果(macOS · Python 3.12 · Drake 1.34 · 8 s)

--shape末端实际范围 x / z (m)最大跟踪误差 (rad)扭矩饱和末端偏离参考路径
sine±0.075 / 0.355 ~ 0.4450.0000%最大 1.8 mm,平均 0.7 mm
circle±0.055 / 0.347 ~ 0.4530.011(J6)0%最大 0.8 mm,平均 0.4 mm
tri±0.043 / 0.372 ~ 0.4550.0000%最大 0.7 mm,平均 0.4 mm

末端范围与画板设计逐位吻合(y 恒为 −0.174 m,说明末端始终贴着板面),IK 几何误差 ≤ 0.35 mm。三种轨迹的扭矩饱和都是 0%,这与第 3 步里关节空间摆动时 J2 会到 2~4% 形成鲜明对照 —— 原因很直接:沿画板走时末端速度只有约 0.07 m/s,比关节空间摆动温和得多。换句话说,同样一台臂,「画图」这种任务对电机是最友好的。

❓ 常见问题

pip install drake 装不上 / 导入报错?

Drake 的 wheel 与 Python 版本、CPU 架构严格绑定,这是最常见的坑。三步排查:

python3 --version 是否在支持区间(3.9 ~ 3.11,越新版本要求越高);② uname -m 判断是 x86_64 还是 arm64;③ Intel 版 macOS 用 pip install drake==1.34.0

另外务必在虚拟环境里装,避免与系统 Python 打架。

为什么网页和 Drake 用的关节角一样,误差却完全不同?

因为两者算的根本不是一回事。网页做的是运动学:给定末端目标点,解出关节角,再检查用这些角做正解能否回到目标点 —— 误差只有几何意义(几毫米),且机械臂是「瞬时到位」的。

Drake 做的是动力学:关节角是「命令」,电机通过有限扭矩去追,于是必然有滞后;再加上重力、惯性耦合与扭矩上限,误差自然大一个量级。前者告诉你到得了吗,后者告诉你追得上吗。

跟踪误差多大算正常?

取决于轨迹速度与幅值,没有绝对标准。判断方法是对照实验:

--no-ff(关重力补偿)后误差显著变大 → 说明模型的重力矩是合理量级;若 --no-gravity 后误差大幅下降 → 说明主要误差来自重力而非控制带宽。两条都符合预期,就说明模型可信。

反过来,若误差达到几十度、或某个关节饱和占比接近 100%,那多半不是「真机就这样」,而是增益或惯量设置有问题。

扭矩饱和占比很高,能说明真机一定跑不动吗?

方向对了,但要结合 URDF 里的 effort 值一起看 —— 那些扭矩上限是本站按 AR4 量级估算的教学值,不是铭牌数据。真要评估选型,请把官方 BOM 里的电机 + 减速箱实际输出扭矩(含减速比)填进 URDF 的 effort,结论才可用。

但它作为相对比较工具非常有效:同一套参数下,哪种轨迹的饱和最严重、哪个轴最先顶到上限,这些排序结论不会因为绝对值不准而失效。

能把末端负载(夹爪)加进去评估吗?

可以,而且这是本模型最实用的用法之一。改 ar4_arm.urdftool_linkmassinertia(比如把 0.05 kg 换成 0.5 kg 的夹爪),重跑同样的轨迹,比较饱和占比与跟踪误差的变化 —— 这就直接回答了「末端挂多重还能跑得动」。控制器一行都不用改。

🧭 与站内其他内容的关系

💡 提示与避坑