机器人动力学与控制基础

动力学描述力矩与运动的关系,是高性能控制的地基。本文给出拉格朗日与牛顿-欧拉两条建模路线、惯量矩阵与科氏力的结构性质、参数辨识的工程方法、计算力矩与 PID 的实现差异、笛卡尔阻抗与导纳控制的公式、以及重力补偿、摩擦建模、增益整定的具体做法与数值示例。

引言

如果只需要让机械臂走点位,运动学加 PID 就够了。但一旦要求高速跟踪、柔顺接触、拖动示教、力控装配,就必须进入动力学。动力学回答的是「给定关节角、速度、加速度,需要多大力矩」,它的逆问题(给力矩求运动)就是仿真要做的事。

动力学的工程难点有三个。第一是模型的准确性:连杆质量、质心、惯量张量这些参数从 CAD 拿到的是名义值,与实物有偏差,减速器与电机转子的惯量折算、线缆的拖拽力都不在 CAD 里。第二是计算的实时性:完整的动力学方程包含 O(n²) 甚至 O(n³) 项,要在 1 kHz 下算完必须用递归算法并做符号优化。第三是摩擦与柔性:真实的关节有库仑摩擦、粘滞摩擦、齿隙,连杆也不是刚体,这些不在刚体动力学模型里,却是低速运动时误差的主要来源。

本文按「方程与性质 → 建模算法 → 参数辨识 → 关节空间控制 → 笛卡尔控制 → 力控 → 工程细节 → 实现与调参」的顺序展开。公式给出完整形式,代码给出可编译的骨架,参数给出典型取值。示例以六轴串联臂为主,兼顾移动平台。

读完本文你应当能够:写出并理解动力学方程的结构、选择合适的建模算法、判断什么时候需要参数辨识、实现计算力矩与阻抗控制器、以及知道低速抖动与力控超调该从哪里下手。

目录

  1. 动力学方程与结构性质
  2. 拉格朗日法与牛顿-欧拉法
  3. 惯量矩阵、科氏力与重力项
  4. 参数辨识与模型修正
  5. 关节空间控制:PID 与计算力矩
  6. 笛卡尔空间控制:阻抗与导纳
  7. 力控、柔顺与接触稳定
  8. 摩擦、重力补偿与柔性
  9. 控制器实现与增益整定

1. 动力学方程与结构性质

串联机器人的刚体动力学方程有统一形式:

M(q)·q̈ + C(q, q̇)·q̇ + g(q) + τ_f(q̇) = τ + J^T(q)·F_ext

M(q)   :n×n 对称正定惯量矩阵
C(q,q̇):n×n 科氏力与离心力矩阵
g(q)   :n×1 重力项
τ_f    :摩擦力矩(非刚体模型项)
τ      :关节驱动力矩
F_ext  :末端施加的外力,通过 J^T 映射到关节空间

这个方程有三条极其重要的结构性质,几乎所有的先进控制律都建立在它们之上:

性质 1:M(q) 对称正定
  保证 q̈ = M^-1(...) 总有解,是数值稳定的基础

性质 2:Ṁ(q) - 2C(q,q̇) 是反对称矩阵
  这意味着 q̇^T (Ṁ - 2C) q̇ = 0
  由此可得能量守恒:无外力时系统总能量不变
  这条性质是无源性(passivity)证明的基础,阻抗控制稳定性靠它

性质 3:方程对参数线性
  M(q)q̈ + C(q,q̇)q̇ + g(q) = Y(q, q̇, q̈) · π
  其中 Y 是回归矩阵(只与运动状态有关),π 是参数向量(质量、惯量等)
  这条性质是参数辨识的理论依据

性质 3 的工程价值极高:它意味着可以用最小二乘法辨识参数,而不需要非线性优化。

2. 拉格朗日法与牛顿-欧拉法

两种建模路线各有用途。

拉格朗日法(能量视角):
  L = K - P  (动能 - 势能)
  d/dt(∂L/∂q̇) - ∂L/∂q = τ
  优点:形式统一、便于理论分析、显式给出 M/C/g
  缺点:符号推导随自由度爆炸,6 轴臂手推几乎不可行

牛顿-欧拉法(递推视角):
  前向递推:从基座到末端,算每个连杆的速度、加速度、惯性力
  后向递推:从末端到基座,算关节力矩
  复杂度 O(n),是实际计算的唯一选择

牛顿-欧拉的前向递推公式(以转动关节为例):

ω_i   = R_i^T · ω_{i-1} + z · q̇_i
ω̇_i  = R_i^T · ω̇_{i-1} + R_i^T(ω_{i-1} × z) · q̇_i + z · q̈_i
v̇_i  = R_i^T(v̇_{i-1} + ω̇_{i-1} × r_{i-1} + ω_{i-1} × (ω_{i-1} × r_{i-1}))
a_c,i = v̇_i + ω̇_i × r_c,i + ω_i × (ω_i × r_c,i)
F_i   = m_i · a_c,i
N_i   = I_c,i · ω̇_i + ω_i × (I_c,i · ω_i)
// 递归牛顿-欧拉(RNEA)的核心循环,Pinocchio 的 rnea 即此实现
// 手写版仅用于理解;生产环境直接用库
void rnea(const Model & m, const VectorXd & q,
          const VectorXd & v, const VectorXd & a, VectorXd & tau) {
  // 前向:算速度与加速度
  for (int i = 1; i < m.njoints; ++i) {
    const auto & j = m.joints[i];
    const Matrix3d R = j.placement.rotation().toRotationMatrix();
    const Vector3d r = j.placement.translation();
    omega[i]  = R.transpose() * (omega[i-1] + r.cross(v[i-1])) + j.axis * v[i];
    alpha[i]  = R.transpose() * (alpha[i-1] + r.cross(a[i-1])
                + omega[i-1].cross(omega[i-1].cross(r))) + j.axis * a[i];
    a_c[i]    = alpha[i].cross(c[i]) + omega[i].cross(omega[i].cross(c[i]));
    F[i]      = mass[i] * a_c[i];
    N[i]      = inertia[i] * alpha[i] + omega[i].cross(inertia[i] * omega[i]);
  }
  // 后向:算关节力矩
  for (int i = m.njoints - 1; i >= 1; --i) {
    f[i] = R_next.transpose() * f[i+1] + F[i];
    n[i] = R_next.transpose() * n[i+1] + N[i]
         + c[i].cross(F[i]) + r_next.cross(R_next.transpose() * f[i+1]);
    tau[i-1] = j.axis.dot(n[i]);
  }
}

工程建议:永远不要手写 RNEA 用于生产,用 Pinocchio 的 rnea(约 5 µs 完成 7 自由度)、RBDL 或 KDL。手写只用于教学与极端嵌入式场景(比如 MCU 上跑固定构型)。Pinocchio 还能给出解析导数(computeRNEADerivatives),对 MPC 与优化控制是刚需。

3. 惯量矩阵、科氏力与重力项

三项各自的物理意义与工程重要性不同,理解它们能指导优化取舍。

重力项 g(q):
  只与位形有关,计算最便宜
  低速重载场景下是主要力矩来源(占 60%~90%)
  所有工业控制器都必须做重力补偿,否则松手就掉落

惯量矩阵 M(q):
  与位形有关,计算最贵(O(n²) 项)
  决定加速度响应,高速运动时不可忽略
  对角占优通常成立,非对角项代表关节耦合

科氏力 C(q,q̇)q̇:
  与速度平方成正比
  低速时几乎为零,高速时可达重力的 30%
  计算复杂度与 M 相当

一个实用的计算频率分级策略:重力项在位置控制回路里按 1 kHz 更新;完整的 M 与 C 只在需要加速度前馈或力控时按 500 Hz 更新;预测控制(MPC)里用简化模型(忽略科氏项)以换取更长的预测步长。这样能把平均计算负载降下来,而精度损失在多数场景可接受。

4. 参数辨识与模型修正

CAD 参数与实际参数的偏差通常在 10%~30%,主要来源是线缆、减速器、末端工具与未建模的质量。辨识的标准流程是「激励 → 采集 → 最小二乘 → 验证」。

步骤:
  1. 设计激励轨迹
     有限傅里叶级数(最常用):q_i(t) = q0_i + Σ (a_k sin(kωt) + b_k cos(kωt))
     频率选择要覆盖所有关节,且避免共振频率
     典型:基频 0.1 Hz,5 个谐波,单次 20 秒,做 5~10 次不同起始位形

  2. 采集数据
     每个采样点记录 q, q̇, q̈, τ(力矩由电流乘力矩常数得到)
     采样率 1 kHz,滤波(低通 10~20 Hz)后再微分或直接读编码器差分

  3. 构造回归矩阵并求解
     τ = Y(q,q̇,q̈) · π
     最小二乘:π̂ = (Y^T W Y)^-1 Y^T W τ
     加正则项避免病态:π̂ = (Y^T W Y + λI)^-1 Y^T W τ

  4. 验证
     用独立的一组轨迹对比预测力矩与实测力矩
     好的辨识结果:均方根误差 < 5% 峰值力矩
import numpy as np

def identify(Y_list, tau_list, lam=1e-6):
    """Y_list: 每个采样点的回归矩阵 (n x 10*nparams)
       tau_list: 对应的力矩向量"""
    Y = np.vstack(Y_list)
    tau = np.concatenate(tau_list)
    # 加权最小二乘 + 岭正则,抑制病态
    A = Y.T @ Y + lam * np.eye(Y.shape[1])
    b = Y.T @ tau
    pi = np.linalg.solve(A, b)
    # 验证:残差与相对误差
    resid = Y @ pi - tau
    rms = np.sqrt(np.mean(resid**2))
    return pi, rms

辨识的常见陷阱是「只辨识质量不辨识摩擦」,导致低速时误差大。正确做法是把摩擦项也纳入回归:τ_f = f_v·q̇ + f_c·sign(q̇),两者都是线性参数,可以一起辨识。更精细的模型还会加入 Stribeck 项与齿隙模型。

5. 关节空间控制:PID 与计算力矩

最基础的是独立关节 PID,忽略耦合,每个关节单独闭环:

τ_i = Kp_i · (q_d,i - q_i) + Ki_i · ∫(q_d,i - q_i)dt + Kd_i · (q̇_d,i - q̇_i)

工程要点:
  - 必须加重力前馈:τ_i += g_i(q),否则静差与下垂明显
  - 积分项必须限幅(anti-windup),否则大误差后恢复时超调严重
  - 微分项应基于测量速度而非误差微分,避免目标跳变时的微分冲击
struct JointPID {
  double kp, ki, kd;
  double i_term = 0.0, prev_err = 0.0;
  double i_limit = 0.5;      // 积分项限幅(N·m)

  double update(double err, double dt, double gravity_ff) {
    i_term += ki * err * dt;
    i_term = std::clamp(i_term, -i_limit, i_limit);   // anti-windup
    const double d_term = -kd * (err - prev_err) / dt;
    prev_err = err;
    return kp * err + i_term + d_term + gravity_ff;
  }
};

PID 的局限在高速高加速时暴露:关节耦合导致跟踪误差随速度增大。解决方案是计算力矩控制(computed torque,又称逆动力学控制):

τ = M(q)·(q̈_d + Kd·ė + Kp·e) + C(q,q̇)·q̇ + g(q)

代入动力学方程可得误差动力学:
  ë + Kd·ė + Kp·e = 0

即误差按指定的二阶系统收敛,Kp、Kd 直接对应自然频率与阻尼比:
  Kp = ω_n²,  Kd = 2ζ·ω_n
  典型:ω_n = 20~40 rad/s,ζ = 1.0(临界阻尼)

计算力矩的代价是必须实时算 M、C、g,且对模型误差敏感。工程折中是「部分补偿」:只补重力与科氏力,惯量矩阵用对角近似。

// 计算力矩控制(1 kHz 循环内)
Eigen::VectorXd q_d, dq_d, ddq_d, q, dq;
Eigen::VectorXd e    = q_d - q;
Eigen::VectorXd de   = dq_d - dq;
Eigen::VectorXd a_d  = ddq_d + Kd.asDiagonal() * de + Kp.asDiagonal() * e;

Eigen::VectorXd tau = pinocchio::rnea(model, data, q, dq, a_d);
// rnea 的第三个参数是期望加速度,直接得到 τ = M·a_d + C·dq + g

注意 rnea(model, data, q, dq, a_d) 这一调用把三项一次性算完,是 Pinocchio 最优雅的接口。它比手动拼 M*a + C*dq + g 快约 3 倍,因为避免了单独计算 M 与 C。

6. 笛卡尔空间控制:阻抗与导纳

接触任务必须用笛卡尔空间控制,因为要控制的不是位置而是「位置与力的关系」。两种基本形式:

阻抗控制(Impedance Control):
  输入:位置/速度(来自轨迹),输出:力
  F = K·(x_d - x) + D·(ẋ_d - ẋ) + M·(ẍ_d - ẍ)
  τ = J^T · F + g(q)

导纳控制(Admittance Control):
  输入:力(来自力传感器),输出:位置修正
  M·ẍ_c + D·ẋ_c + K·x_c = F_ext
  x_d' = x_d + x_c,再交给位置控制器

选择规则很明确:机器人本身刚性大、要表现出柔顺(如拖动示教、装配),用导纳控制;机器人本身柔性大(如带弹性关节、软体),或用位置源伺服无外部力传感,用阻抗控制。工业臂通常用导纳(因为已有高刚度位置环),协作臂常用阻抗(因为有力矩传感)。

// 笛卡尔阻抗控制(6 维,位置 + 姿态)
Eigen::Matrix<double, 6, 1> x_err;          // 位置误差 + 姿态误差(so(3) 对数)
Eigen::Matrix<double, 6, 1> dx_err;
Eigen::Matrix<double, 6, 1> F =
    K.cwiseProduct(x_err) + D.cwiseProduct(dx_err);

// 映射到关节力矩
Eigen::MatrixXd J(6, n);
pinocchio::getJointJacobian(model, data, ee_id, pinocchio::LOCAL, J);
Eigen::VectorXd tau = J.transpose() * F + gravity_compensation(q);

刚度矩阵 K 的取值直接决定「多软」:拖动示教通常 K 设为 0 到 200 N/m 的极小值;精密装配用 5002000 N/m;刚性定位用 5000 N/m 以上。阻尼按 D = 2ζ√(K·M) 计算,ζ 取 0.71.0。K 与 D 的单位与坐标系必须一致,姿态部分的刚度单位是 N·m/rad,常被误用成平移刚度导致姿态抖动。

7. 力控、柔顺与接触稳定

接触瞬间的不稳定是力控最大的工程问题,表现为高频振荡或力值失控。

不稳定的三个来源:
  1. 环境刚度估计错误
     环境比预期硬,闭环增益过高 → 振荡
     对策:在线估计环境刚度,或用自适应阻抗

  2. 延迟
     力传感器采样、滤波、控制周期累加延迟
     闭环带宽上限 ≈ 1/(5~10 × 总延迟)
     若总延迟 10 ms,力控带宽不应超过 10~20 Hz

  3. 接触切换
     从自由空间到接触是模型跳变
     对策:接近阶段用位置控制并降低速度,接触后用渐变的刚度过渡

力控的稳定实现要控制「接触过渡」:接近时用较低速度(< 20 mm/s)并监测力阈值;接触后用 200 ms 时间常数把刚度从 0 渐变到目标值;退出时反向渐变。突变的刚度切换会产生冲击,可能损坏工件或触发安全停机。

8. 摩擦、重力补偿与柔性

低速运动的精度瓶颈几乎总是摩擦。三类摩擦模型按复杂度递增:

模型 1(最简):τ_f = f_v · q̇ + f_c · sign(q̇)
  库仑摩擦 + 粘滞摩擦,两个参数,覆盖 80% 场景

模型 2(Stribeck):τ_f = f_v·q̇ + f_c·sign(q̇) + (f_s - f_c)·exp(-|q̇/v_s|^δ)·sign(q̇)
  加入静摩擦与 Stribeck 效应,低速更准,参数 5 个

模型 3(LuGre):内部状态变量 z,dz/dt = q̇ - σ0·|q̇|·z/g(q̇)
  动态摩擦模型,能捕捉预滑移,参数 6 个,计算量最大

重力补偿的实现有两种层次:静态补偿(τ_ff = g(q),只与位形有关)与动态补偿(把重力、科氏、惯量一起前馈)。静态补偿已能消除 80% 以上的跟踪误差,实现简单,是首选。

柔性(joint flexibility)在轻量化臂与带谐波减速器的臂上不可忽略。关节不再是刚性连接,模型变成 M(q)q̈ + C·q̇ + g + K·(q - θ) = 0,其中 θ 是电机侧角度、K 是关节刚度。带柔性的臂需要更高阶的控制器,且容易激发结构共振(典型 10~30 Hz)。工程对策是在控制器输出加陷波滤波器,把共振频率附近的增益压下去。

9. 控制器实现与增益整定

控制器的代码结构应当把「状态更新」与「控制律」分离,便于测试与切换算法。

class JointController {
 public:
  void updateState(const Eigen::VectorXd & q, const Eigen::VectorXd & dq,
                   double t) {
    q_ = q; dq_ = dq; t_ = t;
  }
  Eigen::VectorXd computeTorque() {
    switch (mode_) {
      case Mode::PID:      return pidTorque();
      case Mode::ComputedTorque: return computedTorque();
      case Mode::Impedance:      return impedanceTorque();
      default:             return Eigen::VectorXd::Zero(n_);   // 安全:零力矩
    }
  }
 private:
  Mode mode_ = Mode::PID;
};

整定的顺序建议固定为:先补偿重力(确认松手时臂不下垂);再调 Kp 到临界振荡后减半;再加 Kd 抑制超调(D 通常是 Kp 的 1/10~1/20 量级,具体取决于采样率);最后加积分项并限幅。若用计算力矩,先按 ω_n 与 ζ 设定理论增益,再实测微调。

增益整定经验值(1 kHz 控制周期、中等尺寸六轴臂):
  PID:Kp = 50~200 N·m/rad, Kd = 1~5 N·m·s/rad, Ki = 0~20
  计算力矩:ω_n = 20~40 rad/s, ζ = 0.9~1.1
  阻抗:平移 K = 500~2000 N/m, D = 2ζ√(KM)
  姿态 K = 5~50 N·m/rad, D = 0.5~5 N·m·s/rad

采样率与增益的关系必须遵守:采样率至少是闭环带宽的 10 倍。1 kHz 采样对应最大约 100 Hz 闭环带宽。想提高带宽必须先提高采样率,否则会因离散化引入的相位滞后导致振荡。

权衡取舍

决策选 A选 B
控制律PID + 重力补偿:简单、够用、好调计算力矩:高速高精度、需完整模型
笛卡尔控制阻抗:无力传感器、软体导纳:高刚度位置环、有力传感器
摩擦模型库仑+粘滞:两参数、覆盖多数LuGre:预滑移、高精度
动力学库Pinocchio:快、支持导数RBDL/KDL:轻量、ROS 集成
参数来源CAD 名义值:快速起步实验辨识:误差 < 5%
补偿范围只补重力:便宜、稳补 M/C/g:精度高、算力贵

通用原则:先补重力,再考虑惯量。重力补偿的收益最大、风险最低;惯量前馈的收益在高加速场景才明显,而它对模型误差敏感,模型不准时反而变差。

常见坑清单

  1. 不做重力补偿,松手机械臂直接掉落——位置控制必须叠加 g(q) 前馈。
  2. PID 积分项不限幅,大误差后恢复时严重超调——积分项限幅并做条件积分。
  3. 微分项用误差微分,目标跳变时产生微分冲击——基于测量速度做微分。
  4. 采样率只有闭环带宽的 3~5 倍,离散化相位滞后导致振荡——采样率 ≥ 10 倍带宽。
  5. 姿态刚度与平移刚度混用同一数值,姿态剧烈抖动——注意 N/m 与 N·m/rad 的单位差异。
  6. 力控带宽超过 1/(5×总延迟),接触时高频振荡——先测延迟再定带宽。
  7. 接触瞬间刚度突变,产生冲击损坏工件——用 200 ms 渐变过渡。
  8. 摩擦只辨识库仑项,低速换向时出现死区——把粘滞与静摩擦一起辨识。
  9. 忽略关节柔性,结构共振(10~30 Hz)被激发——加陷波滤波器或降低带宽。
  10. 手写 RNEA 用于生产,性能与正确性都不可控——用 Pinocchio/RBDL,手写仅作教学。

小结

动力学的核心是那条方程与它的三条结构性质:对称正定的惯量矩阵、反对称的 Ṁ - 2C、以及参数线性性。前者保证数值稳定,中者支撑无源性与阻抗控制的稳定性证明,后者让参数辨识可以用最小二乘完成。控制律的复杂度可以按需选择,从重力补偿到计算力矩到阻抗控制,收益与代价都是清晰的。

下一步建议沿着两条线深入。一是规划线:读运动规划与轨迹优化 ,理解轨迹如何满足动力学约束(速度、加速度、力矩限幅)。二是执行线:读机械臂控制与抓取规划 ,看动力学如何进入力控装配与柔顺抓取。运动学正逆解 是本文的前置,若对雅可比与奇异处理还不熟悉,建议先回去补齐。

最后一句实操建议:任何新控制律先在机器人仿真 里用重力补偿做基线对比。如果新算法连「加重力补偿的 PID」都赢不了,就不值得上真机。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「机器人」更多文章

  1. 机器人实时控制与嵌入式
  2. ROS 2 通信与 QoS
  3. 足式与人形机器人运动控制