引言
如果只需要让机械臂走点位,运动学加 PID 就够了。但一旦要求高速跟踪、柔顺接触、拖动示教、力控装配,就必须进入动力学。动力学回答的是「给定关节角、速度、加速度,需要多大力矩」,它的逆问题(给力矩求运动)就是仿真要做的事。
动力学的工程难点有三个。第一是模型的准确性:连杆质量、质心、惯量张量这些参数从 CAD 拿到的是名义值,与实物有偏差,减速器与电机转子的惯量折算、线缆的拖拽力都不在 CAD 里。第二是计算的实时性:完整的动力学方程包含 O(n²) 甚至 O(n³) 项,要在 1 kHz 下算完必须用递归算法并做符号优化。第三是摩擦与柔性:真实的关节有库仑摩擦、粘滞摩擦、齿隙,连杆也不是刚体,这些不在刚体动力学模型里,却是低速运动时误差的主要来源。
本文按「方程与性质 → 建模算法 → 参数辨识 → 关节空间控制 → 笛卡尔控制 → 力控 → 工程细节 → 实现与调参」的顺序展开。公式给出完整形式,代码给出可编译的骨架,参数给出典型取值。示例以六轴串联臂为主,兼顾移动平台。
读完本文你应当能够:写出并理解动力学方程的结构、选择合适的建模算法、判断什么时候需要参数辨识、实现计算力矩与阻抗控制器、以及知道低速抖动与力控超调该从哪里下手。
目录
- 动力学方程与结构性质
- 拉格朗日法与牛顿-欧拉法
- 惯量矩阵、科氏力与重力项
- 参数辨识与模型修正
- 关节空间控制:PID 与计算力矩
- 笛卡尔空间控制:阻抗与导纳
- 力控、柔顺与接触稳定
- 摩擦、重力补偿与柔性
- 控制器实现与增益整定
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:精度高、算力贵 |
通用原则:先补重力,再考虑惯量。重力补偿的收益最大、风险最低;惯量前馈的收益在高加速场景才明显,而它对模型误差敏感,模型不准时反而变差。
常见坑清单
- 不做重力补偿,松手机械臂直接掉落——位置控制必须叠加
g(q)前馈。 - PID 积分项不限幅,大误差后恢复时严重超调——积分项限幅并做条件积分。
- 微分项用误差微分,目标跳变时产生微分冲击——基于测量速度做微分。
- 采样率只有闭环带宽的 3~5 倍,离散化相位滞后导致振荡——采样率 ≥ 10 倍带宽。
- 姿态刚度与平移刚度混用同一数值,姿态剧烈抖动——注意 N/m 与 N·m/rad 的单位差异。
- 力控带宽超过 1/(5×总延迟),接触时高频振荡——先测延迟再定带宽。
- 接触瞬间刚度突变,产生冲击损坏工件——用 200 ms 渐变过渡。
- 摩擦只辨识库仑项,低速换向时出现死区——把粘滞与静摩擦一起辨识。
- 忽略关节柔性,结构共振(10~30 Hz)被激发——加陷波滤波器或降低带宽。
- 手写 RNEA 用于生产,性能与正确性都不可控——用 Pinocchio/RBDL,手写仅作教学。
小结
动力学的核心是那条方程与它的三条结构性质:对称正定的惯量矩阵、反对称的 Ṁ - 2C、以及参数线性性。前者保证数值稳定,中者支撑无源性与阻抗控制的稳定性证明,后者让参数辨识可以用最小二乘完成。控制律的复杂度可以按需选择,从重力补偿到计算力矩到阻抗控制,收益与代价都是清晰的。
下一步建议沿着两条线深入。一是规划线:读运动规划与轨迹优化 ,理解轨迹如何满足动力学约束(速度、加速度、力矩限幅)。二是执行线:读机械臂控制与抓取规划 ,看动力学如何进入力控装配与柔顺抓取。运动学正逆解 是本文的前置,若对雅可比与奇异处理还不熟悉,建议先回去补齐。
最后一句实操建议:任何新控制律先在机器人仿真 里用重力补偿做基线对比。如果新算法连「加重力补偿的 PID」都赢不了,就不值得上真机。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。