3. 执行器与驱动
好,到了这一章,我们终于要让机械臂「活」过来了。前面两章我们把模型搭好、参数设好,但如果没有执行器,它就是一具漂亮的骨架。执行器,说白了就是给关节装上的「肌肉」。
MuJoCo 里的执行器类型有好几种,每种对应不同的控制方式。我最早接触的时候,以为随便选一个就行,结果仿真出来的机械臂要么抖得像筛糠,要么根本推不动。后来才明白——选对类型、调好增益,才是仿真的核心。
3.1 执行器的四种基本类型
MuJoCo 支持四种最常用的执行器类型。我习惯把它们分成两类:力驱动和运动驱动。
| 类型 | 控制量 | 典型场景 | 我的经验 |
|---|---|---|---|
motor |
力/扭矩 | 关节直接力矩控制 | 最常用,适合做底层控制 |
position |
目标位置 | 位置伺服控制 | 需要配合高增益,否则有静差 |
velocity |
目标速度 | 速度伺服控制 | 适合做轨迹跟踪的中间层 |
intvelocity |
积分速度 | 无静差速度控制 | 我一般不用,除非有特殊需求 |
核心要点: motor 输出的是力/扭矩,其他三种输出的是目标位置或速度,内部通过 PID 计算力。选型时先想清楚——你要直接控制力,还是控制运动?
3.2 增益与时间常数——执行器的「脾气」
每种执行器都有两个关键参数:增益(gain)和时间常数(timeconst)。这两个参数决定了执行器的响应特性。
增益,你可以理解为「放大倍数」。增益越大,执行器对控制信号的响应越猛。但增益不是越大越好——我在一个六轴机械臂项目里,把增益调得太高,结果仿真直接炸了,关节像疯了一样乱甩。
时间常数,则是执行器的「反应速度」。时间常数越小,响应越快;越大,响应越慢,像泡在水里一样迟钝。
| 参数 | 含义 | 调大效果 | 调小效果 |
|---|---|---|---|
gain |
控制信号到力的放大倍数 | 响应更猛,容易震荡 | 响应更弱,可能推不动 |
timeconst |
一阶低通滤波的时间常数 | 响应变慢,更平滑 | 响应变快,可能抖动 |
调参小技巧: 我一般先设一个中等增益(比如 10-50),然后逐步增大,直到出现轻微震荡,再回调 20%。时间常数默认 0.01 通常够用,除非你发现关节有高频抖动。
3.3 在 XML 中定义执行器
定义执行器很简单,在 <actuator> 标签里写就行。下面是一个典型的例子:
<actuator>
<!-- 关节1:力矩电机 -->
<motor name="joint1_motor" joint="joint1" gear="100" ctrllimited="true" ctrlrange="-10 10"/>
<!-- 关节2:位置伺服 -->
<position name="joint2_pos" joint="joint2" kp="100" kv="10" ctrllimited="true" ctrlrange="-3.14 3.14"/>
<!-- 关节3:速度伺服 -->
<velocity name="joint3_vel" joint="joint3" kv="50" ctrllimited="true" ctrlrange="-5 5"/>
</actuator>
注意看区别:motor 用 gear 来设定减速比,position 和 velocity 用 kp、kv 来设定 PID 参数。嗯,这里要特别提醒——position 的 kp 是位置增益,kv 是速度阻尼,两者要配合好。
我曾经踩过的坑: 给 position 执行器设了很大的 kp 但忘了设 kv,结果机械臂在目标位置附近来回震荡,像得了帕金森。后来加上合适的 kv 才稳定下来。记住——kp 管刚度,kv 管阻尼,缺一不可。
3.4 让机械臂动起来——Python 控制代码
定义好执行器之后,我们就可以用 Python 来发送控制指令了。MuJoCo 的控制接口非常直接:
import mujoco
import mujoco.viewer
import numpy as np
# 加载模型
model = mujoco.MjModel.from_xml_path("my_robot.xml")
data = mujoco.MjData(model)
# 启动可视化
with mujoco.viewer.launch_passive(model, data) as viewer:
# 设置关节1的力矩指令
data.ctrl[0] = 5.0 # 对应 joint1_motor
# 设置关节2的目标位置(弧度)
data.ctrl[1] = 1.57 # 对应 joint2_pos,目标90度
# 设置关节3的目标速度
data.ctrl[2] = 2.0 # 对应 joint3_vel,目标2 rad/s
# 仿真步进
for _ in range(1000):
mujoco.mj_step(model, data)
viewer.sync()
你想想看,就这么几行代码,机械臂就能按照你的指令动起来了。但实际项目中,控制指令不会这么简单——你需要根据传感器反馈实时调整。
3.5 执行器选型决策流程
为了帮你快速决策,我画了一张流程图。每次做新项目,我都会先走一遍这个流程:
3.6 实战:让六轴机械臂画圆
光说不练假把式。我们用一个实际例子来收尾——让六轴机械臂的末端执行器画一个圆。
思路是这样的:先通过逆运动学算出每个关节的目标角度,然后用 position 执行器去跟踪。代码框架如下:
import numpy as np
import mujoco
# 加载模型
model = mujoco.MjModel.from_xml_path("six_axis_arm.xml")
data = mujoco.MjData(model)
# 圆的参数
radius = 0.1
center = np.array([0.3, 0.0, 0.2])
t = 0.0
# 仿真循环
for step in range(5000):
# 计算目标位置(圆轨迹)
target_pos = center + np.array([
radius * np.cos(t),
radius * np.sin(t),
0.0
])
# 设置末端执行器的目标位置
# 这里用 mocap 或者直接设置关节角度
# 实际项目中会用 IK 求解器
# 步进仿真
mujoco.mj_step(model, data)
t += 0.01
我的经验: 画圆看起来简单,但实际跑起来你会发现——如果执行器的增益没调好,末端轨迹会是一个「抖动的圆」。我一般先用低增益跑一遍,观察轨迹偏差,再逐步增大增益直到轨迹平滑。这个过程,说白了就是「调参的艺术」。