控制器#

机器人任务通常用任务空间中的目标末端轨迹来描述,而控制动作在关节空间中执行,因此形成了 关节空间 和 任务空间 (操作空间)两类控制方法。不过,运动控制要顺利完成交互任务,往往需要准确的机械臂模型和环境模型。机械臂模型可以足够精确,环境的详细模型却很难获得 [SSVO09] 。在交互中引入 柔顺性 ,可以克服模型不匹配造成的规划误差。

柔顺性可以通过机器人结构(如弹性执行器、软体机械臂)被动实现;这里主要讨论通过控制器实现主动交互控制的方法,大致分为以下两类:

  1. 阻抗控制: 一种间接控制方法,用刚度和阻尼可调的质量–弹簧–阻尼系统描述交互时运动偏差与接触力的关系。刚度控制 是其中一种特例,只考虑位置误差与接触力之间的静态关系。

  2. 力/运动混合控制: 一种主动控制方法,分别在任务中不受约束的方向控制运动、在受约束的方向控制力。力/运动混合控制有多种方案,这里提供的实现采用操作空间逆动力学控制 [Kha87] 。

备注

我们欢迎社区贡献更多运动生成器。有兴趣参与的话,请提交 issue 与我们讨论。

选择控制器#

控制器

输入指令

输出

所需状态

DifferentialIKController

末端执行器的位置或位姿,可使用绝对值或相对增量

关节位置目标

关节位置,以及在同一坐标系下表示的末端执行器位姿和雅可比矩阵

OperationalSpaceController

末端执行器位姿和/或力旋量,也可包含刚度和阻尼比

关节力/力矩

雅可比矩阵;运动控制所需的位姿和速度;惯性解耦所需的质量矩阵;以及根据启用功能提供的重力向量、接触力和关节状态

基本的关节空间控制直接向执行器设置关节目标。关节空间阻抗控制可使用 JointImpedanceController ,见 关节空间控制器。

使用位置控制执行器进行到达目标或遥操作时,可以先选用微分 IK。需要输出力矩指令、实现任务空间柔顺性,或同时控制运动和接触力时,可选用 OSC。例如,擦拭桌面时,需要沿桌面运动,同时维持沿桌面法向的接触力。

IK 教程 和 OSC 教程 提供了完整场景、状态提取方法和可运行的演示。两个教程脚本均使用 Isaac Sim PhysX,需要安装 Isaac Sim。

安装在支架上的 Franka 机械臂,末端执行器处显示坐标系标记。

IK:到达目标位姿。 每条机械臂通过关节位置目标跟踪末端执行器的目标位姿。运行 IK 演示 ,可以观察实际位姿与目标位姿的坐标系标记逐渐重合。

Franka 机械臂用指尖接触半透明的红色倾斜墙面。

OSC:控制运动和接触力。 倾斜墙面示例沿墙面法向施加 10 N 的目标力,同时在其他方向上跟踪目标位姿。详见 OSC 演示 。

控制循环#

IK 和 OSC 控制器根据调用者提供的状态计算目标值,不会自行读取仿真状态或向执行器发送指令。set_command() 保存目标,直到下一次设置将其替换。每个控制周期都需要读取最新状态、调用 compute()、写入目标,再推进仿真;IK 教程中的循环见 API 使用示例 。IK 输出关节位置目标,由配置的位置执行器跟踪。OSC 输出关节力/力矩目标,因此应将执行器刚度和阻尼设为零,避免执行器的反馈控制额外叠加力/力矩。

指令、末端执行器位姿和雅可比矩阵必须使用一致的坐标系。教程用后缀 _b 表示机器人基座坐标系,用 _w 表示世界坐标系;变量名称本身不会变换数据。绝对位姿由位置和四元数组成,四元数采用 (x, y, z, w) 顺序。OSC 的指令和控制轴选择可以使用单独的任务坐标系,但传入 compute() 的状态仍须在基座坐标系下表示。

关节空间控制器#

没有专门用于选择关节空间控制模式的配置类。控制模式取决于通过 articulation.actuators 设置哪些目标:驱动力目标对应力矩控制,速度目标对应速度控制,位置目标对应位置控制。运行时 API 见 运行时 API: articulation.actuators 。

力矩控制#

动作维度: "n" (关节数量)

在扭矩控制模式下,输入动作被直接设置为前馈关节扭矩命令,即在每个时间步,

\[\tau = \tau_{des}\]

速度控制#

动作维度: "n" (关节数量)

在速度控制模式下,需要一个比例控制律来减少当前与期望关节速度之间的误差。基于输入动作,关节扭矩命令计算如下:

\[\tau = k_d (\dot{q}_{des} - \dot{q})\]

在这里 \(k_d\) 是从配置中解析得到的增益。

注意

在执行速度控制时,在许多情况下,需要进行重力补偿以确保更好地跟踪命令。在这种情况下,我们建议在仿真中禁用关节中链节的重力。

位置控制与固定阻抗#

动作维度: "n" (关节数量)

在位置控制模式下,采用比例-阻尼 (PD) 控制律来跟踪期望的关节位置,并确保关节保持在期望位置(即期望的关节速度为零)。根据输入动作,关节扭矩命令计算如下:

\[\tau = k_p (q_{des} - q) - k_d \dot{q}\]

其中 \(k_p\) 和 \(k_d\) 是从配置中解析出的增益。

考虑关节体动力学后,更完整的表达式为:

\[\tau = M \left( k_p (q_{des} - q) - k_d \dot{q} \right) + g\]

其中 \(M\) 是大小为 \(n \times n\) 的关节空间惯性矩阵, \(g\) 是关节空间重力向量。通过将阻抗模式设置为 "fixed" ,可以通过 JointImpedanceController 类来实现此功能。增益 \(k_p\) 从输入配置中解析, \(k_d\) 则在将系统视为解耦的点质量振荡器时计算得出,即,

\[k_d = 2 \sqrt{k_p} \times 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}\) ,它将任务空间的速度与关节空间的速度关联起来,我们设计控制律以获得期望的关节位置,如下所示:

\[q_{des} = q + \eta J_{eO}^{-} \Delta \chi_e\]

其中 \(\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 步重置机器人并更换目标。其余步读取最新状态并计算关节位置目标;每一步都会应用目标并推进仿真:

scripts/tutorials/05_controllers/run_diff_ik.py#
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 库和机器人描述文件;这些依赖与所选物理后端相互独立。