控制器#
机器人任务通常用任务空间中的目标末端轨迹来描述,而控制动作在关节空间中执行,因此形成了 关节空间 和 任务空间 (操作空间)两类控制方法。不过,运动控制要顺利完成交互任务,往往需要准确的机械臂模型和环境模型。机械臂模型可以足够精确,环境的详细模型却很难获得 [SSVO09] 。在交互中引入 柔顺性 ,可以克服模型不匹配造成的规划误差。
柔顺性可以通过机器人结构(如弹性执行器、软体机械臂)被动实现;这里主要讨论通过控制器实现主动交互控制的方法,大致分为以下两类:
阻抗控制: 一种间接控制方法,用刚度和阻尼可调的质量–弹簧–阻尼系统描述交互时运动偏差与接触力的关系。刚度控制 是其中一种特例,只考虑位置误差与接触力之间的静态关系。
力/运动混合控制: 一种主动控制方法,分别在任务中不受约束的方向控制运动、在受约束的方向控制力。力/运动混合控制有多种方案,这里提供的实现采用操作空间逆动力学控制 [Kha87] 。
备注
我们欢迎社区贡献更多运动生成器。有兴趣参与的话,请提交 issue 与我们讨论。
选择控制器#
控制器 |
输入指令 |
输出 |
所需状态 |
|---|---|---|---|
末端执行器的位置或位姿,可使用绝对值或相对增量 |
关节位置目标 |
关节位置,以及在同一坐标系下表示的末端执行器位姿和雅可比矩阵 |
|
末端执行器位姿和/或力旋量,也可包含刚度和阻尼比 |
关节力/力矩 |
雅可比矩阵;运动控制所需的位姿和速度;惯性解耦所需的质量矩阵;以及根据启用功能提供的重力向量、接触力和关节状态 |
基本的关节空间控制直接向执行器设置关节目标。关节空间阻抗控制可使用 JointImpedanceController ,见 关节空间控制器。
使用位置控制执行器进行到达目标或遥操作时,可以先选用微分 IK。需要输出力矩指令、实现任务空间柔顺性,或同时控制运动和接触力时,可选用 OSC。例如,擦拭桌面时,需要沿桌面运动,同时维持沿桌面法向的接触力。
IK 教程 和 OSC 教程 提供了完整场景、状态提取方法和可运行的演示。两个教程脚本均使用 Isaac Sim PhysX,需要安装 Isaac Sim。
控制循环#
IK 和 OSC 控制器根据调用者提供的状态计算目标值,不会自行读取仿真状态或向执行器发送指令。set_command() 保存目标,直到下一次设置将其替换。每个控制周期都需要读取最新状态、调用 compute()、写入目标,再推进仿真;IK 教程中的循环见 API 使用示例 。IK 输出关节位置目标,由配置的位置执行器跟踪。OSC 输出关节力/力矩目标,因此应将执行器刚度和阻尼设为零,避免执行器的反馈控制额外叠加力/力矩。
指令、末端执行器位姿和雅可比矩阵必须使用一致的坐标系。教程用后缀 _b 表示机器人基座坐标系,用 _w 表示世界坐标系;变量名称本身不会变换数据。绝对位姿由位置和四元数组成,四元数采用 (x, y, z, w) 顺序。OSC 的指令和控制轴选择可以使用单独的任务坐标系,但传入 compute() 的状态仍须在基座坐标系下表示。
关节空间控制器#
没有专门用于选择关节空间控制模式的配置类。控制模式取决于通过 articulation.actuators 设置哪些目标:驱动力目标对应力矩控制,速度目标对应速度控制,位置目标对应位置控制。运行时 API 见 运行时 API: articulation.actuators 。
力矩控制#
动作维度: "n" (关节数量)
在扭矩控制模式下,输入动作被直接设置为前馈关节扭矩命令,即在每个时间步,
速度控制#
动作维度: "n" (关节数量)
在速度控制模式下,需要一个比例控制律来减少当前与期望关节速度之间的误差。基于输入动作,关节扭矩命令计算如下:
在这里 \(k_d\) 是从配置中解析得到的增益。
注意
在执行速度控制时,在许多情况下,需要进行重力补偿以确保更好地跟踪命令。在这种情况下,我们建议在仿真中禁用关节中链节的重力。
位置控制与固定阻抗#
动作维度: "n" (关节数量)
在位置控制模式下,采用比例-阻尼 (PD) 控制律来跟踪期望的关节位置,并确保关节保持在期望位置(即期望的关节速度为零)。根据输入动作,关节扭矩命令计算如下:
其中 \(k_p\) 和 \(k_d\) 是从配置中解析出的增益。
考虑关节体动力学后,更完整的表达式为:
其中 \(M\) 是大小为 \(n \times n\) 的关节空间惯性矩阵, \(g\) 是关节空间重力向量。通过将阻抗模式设置为 "fixed" ,可以通过 JointImpedanceController 类来实现此功能。增益 \(k_p\) 从输入配置中解析, \(k_d\) 则在将系统视为解耦的点质量振荡器时计算得出,即,
其中 \(D\) 是系统的阻尼比。当 \(D = 1\) 时,系统达到临界阻尼;当 \(D > 1\) 时,系统为过阻尼;当 \(D < 1\) 时,系统为欠阻尼。
此外,可以通过将配置中的标志 inertial_compensation 和 gravity_compensation 分别设置为 False 来禁用控制器中的惯性补偿或重力补偿。
位置控制与可变刚度#
动作维度: "2n" (关节数)
在刚度控制中,采用与上述相同的公式,然而,增益 \(k_p\) 是输入命令的一部分。此实现可以通过 JointImpedanceController 类实现,方法是将阻抗模式设置为 "variable_kp" 。
位置控制与可变阻抗#
动作维度: "3n" (关节数)
在阻抗控制中,采用与上述相同的公式,然而, \(k_p\) 和 \(k_d\) 都是输入命令的一部分。通过将阻抗模式设置为 "variable" ,可以通过 JointImpedanceController 类来实现此功能。
任务空间控制器#
微分逆运动学 (IK)#
动作维度: "3" (相对/绝对位置), "6" (相对姿态),或 "7" (绝对姿态)
逆运动学将任务空间中的跟踪误差转换为关节空间误差。最常见的实现先计算任务空间位姿误差 \(\Delta \chi_e = (\Delta p_e, \Delta \phi_e)\) :位置误差为目标位置与当前位置之间的笛卡尔距离,姿态误差为目标姿态与当前姿态在 \(\mathbb{SO}(3)\) 中的最短距离。
使用几何雅可比矩阵 \(J_{eO} \in \mathbb{R}^{6 \times n}\) ,它将任务空间的速度与关节空间的速度关联起来,我们设计控制律以获得期望的关节位置,如下所示:
其中 \(\eta\) 是一个缩放参数,而 \(J_{eO}^{-}\) 是雅可比矩阵的伪逆。
可以使用不同的公式计算雅可比矩阵的伪逆:
Moore–Penrose 伪逆(
"pinv"):\(A^{-} = A^T(AA^T)^{-1}\) 。Levenberg–Marquardt 伪逆(阻尼最小二乘法,
"dls"):\(A^{-} = A^T (AA^T + \lambda^2 \mathbb{I})^{-1}\) 。转置法(
"trans"):\(A^{-} = A^T\) 。自适应奇异值分解(SVD)伪逆(
"svd"),见 Buss [Bus04] 。考虑可操作度的阻尼最小二乘法(
"adaptive_dls"):雅可比矩阵接近奇异状态时,增大阻尼 \(\lambda\) 。
这些实现通过 DifferentialIKController 提供。可运行示例见 使用任务空间控制器 。
阻抗控制器#
它利用任务空间位姿误差和雅可比矩阵,通过质量–弹簧–阻尼系统计算关节力矩,支持 a) 固定刚度、b) 可变刚度(刚度控制),以及 c) 可变刚度和阻尼(阻抗控制)。
操作空间控制器#
与任务空间阻抗控制类似,但使用运动方程(EoM)计算任务空间力。可运行示例见 使用操作空间控制器 。
闭环比例力控制器#
它根据末端执行器当前力旋量与目标力旋量的误差,通过比例项跟踪目标力/力矩指令。
混合力-运动控制器#
它结合闭环力控制和操作空间运动控制,计算末端执行器的目标力旋量,并用选择矩阵指定任务中不受约束和受约束的方向。
API 使用示例#
关节目标#
下面分别展示不同类型的指令,假定 robot 已初始化,且 arm_joint_ids 已解析。每个目标张量的形状均为 (num_envs, len(arm_joint_ids))。位置或速度反馈增益在执行器配置中设置;直接控制力/力矩时,应将执行器刚度和阻尼设为零。指令 API 见 运行时 API: articulation.actuators ,增益配置见 执行器 。
joint_pos_target = robot.data.default_joint_pos.torch[:, arm_joint_ids].clone()
robot.actuators.target_command.set_position_index(value=joint_pos_target, joint_ids=arm_joint_ids)
robot.write_data_to_sim()
joint_vel_target = robot.data.default_joint_vel.torch[:, arm_joint_ids].clone()
robot.actuators.target_command.set_velocity_index(value=joint_vel_target, joint_ids=arm_joint_ids)
robot.write_data_to_sim()
import torch
joint_effort_target = torch.zeros_like(robot.data.joint_pos.torch[:, arm_joint_ids])
robot.actuators.target_command.set_effort_index(value=joint_effort_target, joint_ids=arm_joint_ids)
robot.write_data_to_sim()
这些调用先写入待发送指令,再提交给仿真。每个周期还需要推进物理仿真并更新场景,见 控制循环 。
任务空间目标#
IK 教程 每隔 150 步重置机器人并更换目标。其余步读取最新状态并计算关节位置目标;每一步都会应用目标并推进仿真:
if count % 150 == 0:
# reset time
count = 0
# reset joint state
joint_pos = robot.data.default_joint_pos.torch.clone()
joint_vel = robot.data.default_joint_vel.torch.clone()
robot.write_joint_position_to_sim_index(position=joint_pos)
robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
robot.reset()
# reset actions
ik_commands[:] = ee_goals[current_goal_idx]
joint_pos_des = joint_pos[:, robot_entity_cfg.joint_ids].clone()
# reset controller
diff_ik_controller.reset()
diff_ik_controller.set_command(ik_commands)
# change goal
current_goal_idx = (current_goal_idx + 1) % len(ee_goals)
else:
# obtain quantities from simulation. The Jacobian DoF axis prepends
# ``num_base_dofs`` floating-base columns (0 for fixed-base, 6 for
# floating-base); shift the actuated-joint ids accordingly.
jacobi_joint_ids = [j + robot.num_base_dofs for j in robot_entity_cfg.joint_ids]
jacobian = robot.data.body_link_jacobian_w.torch[:, ee_jacobi_idx, :, jacobi_joint_ids]
ee_pose_w = robot.data.body_pose_w.torch[:, robot_entity_cfg.body_ids[0]]
root_pose_w = robot.data.root_pose_w.torch
joint_pos = robot.data.joint_pos.torch[:, robot_entity_cfg.joint_ids]
# compute frame in root frame
ee_pos_b, ee_quat_b = subtract_frame_transforms(
root_pose_w[:, 0:3], root_pose_w[:, 3:7], ee_pose_w[:, 0:3], ee_pose_w[:, 3:7]
)
# compute the joint commands
joint_pos_des = diff_ik_controller.compute(ee_pos_b, ee_quat_b, jacobian, joint_pos)
# apply actions
robot.set_joint_position_target_index(target=joint_pos_des, joint_ids=robot_entity_cfg.joint_ids)
scene.write_data_to_sim()
# perform step
sim.step()
# update sim-time
count += 1
# update buffers
scene.update(sim_dt)
使用相对指令时,DifferentialIKController.set_command() 还需要接收测得的位姿,以便在设置指令时解析相对增量。
OperationalSpaceController 实例 osc 同样通过 OperationalSpaceController.set_command() 保存运动/力目标,但 OperationalSpaceController.compute() 返回的是力/力矩,需要用 set_effort_index 写入。所需状态取决于启用的功能。控制器创建、状态提取和完整循环见 IK 教程 与 OSC 教程 。
反应式规划器#
典型的任务空间控制器并没有考虑诸如关节限制、自碰撞和环境碰撞等运动约束。相反,它们依赖于高级规划器(例如 RRT )来处理这些非欧几里得约束,并将关节/任务空间的路径点传递给控制器。然而,这些方法通常比较保守,并且在靠近物体时会出现不理想的减速。最近,不同的方法将约束直接整合到优化问题中,从而为运动生成和控制提供了一个整体解决方案。
反应式规划器的示例包括:
RMPFlow (lula): 一种基于加速度的策略,通过组合各种黎曼运动策略(RMPs)来解决任务层次结构 [CMI+21] 。它能够在将末端执行器导航到目标的同时执行动态避碰。
MPC(OCS2): 基于序贯线性二次(SLQ)规划的滚动时域控制策略。它通过软惩罚将各类约束纳入同一优化问题,并使用自动微分计算系统动力学、约束和代价的导数。 [MHF+22] 中用于固定机械臂和移动操作机器人末端轨迹跟踪的 MPC 采用运动学系统模型,考虑关节限位和自碰撞规避。外部工具包和示例见 OCS2 文档 。
警告
RmpFlowController 包装基于 CPU 的 Lula 绑定,因此大批量计算时可能限制吞吐量。它还需要 Lula 库和机器人描述文件;这些依赖与所选物理后端相互独立。