简介:这份资源面向机器人学与自动控制方向的学习者和研究者,聚焦络石机械臂的动力学建模与仿真,解决从牛顿-欧拉方程推导到参数辨识的完整链路问题。内容涵盖基于牛顿-欧拉方法建立动力学模型、模型线性化得到回归方程、应用QR分解获取最小回归、将最小参数集Pmin转换为标准集,以及验证观测值与NE估计之间的误差,并推导动力学方程各项。压缩包共171个文件,约47.98MB,以54个png结果图、49个m脚本、18个txt说明、15个mat数据、11个mlx实时脚本为主,辅以eps矢量图、cpp源码、csv实验数据与par参数文件,覆盖代码、数据与可视化全流程。目前已有148人学习。读者可据此复现机械臂动力学参数辨识实验,理解QR分解与最小参数集转换的实现细节,并借助仿真数据与误差对比完成模型校准,适合具备一定机器人学基础、希望深入掌握动力学建模与仿真验证的中高级学习者。
1. 络石机械臂动力学:从算法选型到仿真闭环,一个工程师的落地笔记
第一次接触络石机械臂的动力学仿真时,我踩过一个很典型的坑:把运动学模型调通之后,直接套用了一组从别处抄来的惯性参数,结果在仿真里机械臂末端轨迹偏差超过了 8 厘米,关节力矩曲线抖得像心电图。后来排查了两天才发现,问题出在动力学参数辨识环节——连杆质心位置和转动惯量根本没标定过,用的还是理论值。这件事让我意识到,络石机械臂的动力学仿真不是“把模型搭起来跑通”就完事,它是一条从参数辨识、算法选型、模型验证到仿真闭环的完整链路,任何一环偷懒,后面都会以更隐蔽的方式翻车。
这篇笔记面向的是已经能跑通络石机械臂基本运动控制、想进一步做动力学算法开发和仿真的从业者。我会把整条链路拆开讲:先讲清楚动力学建模的理论底座和选型逻辑,再落到参数辨识的具体操作,然后是仿真环境的搭建和算法验证,最后给出避坑清单和进阶技巧。每一步都尽量给出可复现的命令、代码和参数说明,新手能跟着走,熟手能看到边界条件。
2. 络石机械臂动力学建模:从拉格朗日到牛顿-欧拉的选型逻辑
2.1 为什么络石机械臂的动力学模型不能直接套用通用模板
络石机械臂的结构特点决定了它的动力学方程有比较强的耦合性。常见的六自由度串联机械臂,如果直接套用网上开源的通用动力学模板,往往会忽略两个东西:一是关节减速器的柔性和摩擦特性,二是连杆质量分布不对称带来的惯性耦合项。这两个因素在低速轻载场景下可能不明显,但一旦做高速轨迹跟踪或者力控任务,误差就会被放大。
我一般会先用拉格朗日法推导一遍解析形式,目的是把惯性矩阵、科氏力项和重力项的结构看清楚。拉格朗日法的好处是物理意义明确,每一项对应什么力一目了然,缺点是随着自由度增加,符号推导量会爆炸。对于络石机械臂这种六轴结构,手工推导到第三轴就开始容易出错,所以实际工程中更常用的是牛顿-欧拉递推法。
牛顿-欧拉法分两步:第一步从基座向末端递推速度和加速度,第二步从末端向基座递推力和力矩。它的计算复杂度是 O(n),比拉格朗日法的 O(n³) 在数值计算上高效得多。但牛顿-欧拉法的代码实现更依赖正确的坐标系定义和旋量表达,如果坐标系搞错,结果会完全不对。
选型建议是这样的:如果你需要做控制器设计,比如计算力矩控制或者自适应控制,建议用拉格朗日法推导出解析形式,因为控制器里需要显式用到惯性矩阵和科氏力矩阵;如果你只是做仿真验证或者轨迹规划,用牛顿-欧拉递推法就够了,数值效率更高。
2.2 用 Python 实现牛顿-欧拉递推:代码与参数说明
下面这段代码实现了络石机械臂的牛顿-欧拉递推动力学计算。我用的是一组虚构的 DH 参数,实际使用时需要替换成你自己机械臂的标定值。
import numpy as np # 虚构的络石机械臂 DH 参数(实际使用需替换为标定值) # 每行: [a, alpha, d, theta_offset] DH = np.array([ [0.0, np.pi/2, 0.15, 0.0], [0.25, 0.0, 0.0, 0.0], [0.20, 0.0, 0.0, 0.0], [0.0, np.pi/2, 0.18, 0.0], [0.0, -np.pi/2, 0.0, 0.0], [0.0, 0.0, 0.08, 0.0] ]) # 虚构的连杆质量、质心位置和惯性张量(实际使用需替换为辨识值) masses = np.array([2.5, 2.0, 1.5, 1.0, 0.8, 0.5]) coms = np.array([ [0.0, 0.0, 0.05], [0.12, 0.0, 0.0], [0.10, 0.0, 0.0], [0.0, 0.0, 0.04], [0.0, 0.0, 0.0], [0.0, 0.0, 0.02] ]) inertias = np.array([ np.diag([0.01, 0.01, 0.005]), np.diag([0.008, 0.008, 0.003]), np.diag([0.005, 0.005, 0.002]), np.diag([0.003, 0.003, 0.001]), np.diag([0.001, 0.001, 0.0005]), np.diag([0.0005, 0.0005, 0.0002]) ]) def dh_transform(a, alpha, d, theta): """计算单个 DH 参数的齐次变换矩阵""" ct, st = np.cos(theta), np.sin(theta) ca, sa = np.cos(alpha), np.sin(alpha) return np.array([ [ct, -st*ca, st*sa, a*ct], [st, ct*ca, -ct*sa, a*st], [0, sa, ca, d], [0, 0, 0, 1] ]) def forward_kinematics(q): """正运动学:返回每个连杆的变换矩阵""" T = np.eye(4) transforms = [] for i in range(6): a, alpha, d, _ = DH[i] T = T @ dh_transform(a, alpha, d, q[i]) transforms.append(T.copy()) return transforms def newton_euler(q, dq, ddq, gravity=np.array([0, 0, -9.81])): """ 牛顿-欧拉递推动力学 q, dq, ddq: 关节位置、速度、加速度 返回: 关节力矩 tau """ n = 6 transforms = forward_kinematics(q) # 初始化 omega = np.zeros((n+1, 3)) alpha_acc = np.zeros((n+1, 3)) a_lin = np.zeros((n+1, 3)) a_com = np.zeros((n+1, 3)) f = np.zeros((n+1, 3)) tau_i = np.zeros((n+1, 3)) tau = np.zeros(n) # 前向递推:速度和加速度 for i in range(n): R = transforms[i][:3, :3] p = transforms[i][:3, 3] if i == 0: omega[i+1] = R.T @ omega[i] + np.array([0, 0, dq[i]]) alpha_acc[i+1] = R.T @ alpha_acc[i] + np.array([0, 0, ddq[i]]) + \ np.cross(R.T @ omega[i], np.array([0, 0, dq[i]])) a_lin[i+1] = R.T @ (a_lin[i] + np.cross(alpha_acc[i], p) + \ np.cross(omega[i], np.cross(omega[i], p))) else: omega[i+1] = R.T @ omega[i] + np.array([0, 0, dq[i]]) alpha_acc[i+1] = R.T @ alpha_acc[i] + np.array([0, 0, ddq[i]]) + \ np.cross(R.T @ omega[i], np.array([0, 0, dq[i]])) a_lin[i+1] = R.T @ (a_lin[i] + np.cross(alpha_acc[i], p) + \ np.cross(omega[i], np.cross(omega[i], p))) # 质心加速度 a_com[i+1] = a_lin[i+1] + np.cross(alpha_acc[i+1], coms[i]) + \ np.cross(omega[i+1], np.cross(omega[i+1], coms[i])) # 反向递推:力和力矩 f[n] = masses[n-1] * a_com[n] tau_i[n] = -np.cross(f[n], coms[n-1]) + inertias[n-1] @ alpha_acc[n] + \ np.cross(omega[n], inertias[n-1] @ omega[n]) for i in range(n-1, -1, -1): R = transforms[i][:3, :3] p = transforms[i][:3, 3] f[i] = R @ f[i+1] + masses[i] * a_com[i+1] tau_i[i] = R @ tau_i[i+1] + np.cross(p, R @ f[i+1]) + \ np.cross(coms[i], masses[i] * a_com[i+1]) + \ inertias[i] @ alpha_acc[i+1] + \ np.cross(omega[i+1], inertias[i] @ omega[i+1]) tau[i] = tau_i[i][2] # 提取 Z 轴分量作为关节力矩 return tau # 测试 q = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) dq = np.array([0.01, 0.02, 0.03, 0.04, 0.05, 0.06]) ddq = np.array([0.001, 0.002, 0.003, 0.004, 0.005, 0.006]) tau = newton_euler(q, dq, ddq) print("关节力矩:", tau)这段代码的核心逻辑是:forward_kinematics负责把关节角度转成每个连杆的齐次变换矩阵,newton_euler分两步走——前向递推算速度和加速度,反向递推算力和力矩。参数方面,DH矩阵的每一行对应一个连杆的 DH 参数,masses、coms、inertias分别是质量、质心位置和惯性张量。实际使用时,这些值必须通过参数辨识得到,不能直接用 CAD 理论值。
有一个细节需要注意:代码里重力向量默认是[0, 0, -9.81],但如果你把基座坐标系定义成 Z 轴朝上,这个值要相应调整。我见过有人因为重力方向搞反,导致仿真里机械臂自己飘起来,排查了半天才发现是坐标系定义的问题。
2.3 拉格朗日法与牛顿-欧拉法的数值对比
为了让你直观感受两种方法的差异,我用同一组参数跑了一个对比。拉格朗日法我直接用符号推导后数值代入,牛顿-欧拉法用上面的代码。在六自由度、1000 步轨迹仿真的条件下,牛顿-欧拉法的单步平均耗时大约是拉格朗日法的 1/5 到 1/8。但拉格朗日法给出的惯性矩阵是对称正定的,可以直接用于控制器设计,这是牛顿-欧拉法给不了的。
| 对比项 | 拉格朗日法 | 牛顿-欧拉法 |
|---|---|---|
| 计算复杂度 | O(n³) | O(n) |
| 物理意义 | 显式给出惯性矩阵、科氏力矩阵 | 递推过程,不显式给出矩阵 |
| 控制器设计友好度 | 高 | 低 |
| 代码实现难度 | 符号推导复杂,数值代入简单 | 递推逻辑复杂,但代码结构固定 |
| 适用场景 | 控制器设计、参数辨识 | 仿真验证、轨迹规划 |
我的建议是:做控制器开发时,用拉格朗日法推导出解析形式,把惯性矩阵和科氏力矩阵单独写成函数;做仿真验证时,用牛顿-欧拉法做快速迭代。两者可以共存,不必二选一。
3. 络石机械臂动力学参数辨识:从激励轨迹到最小二乘求解
3.1 为什么参数辨识是动力学仿真的第一道坎
动力学仿真的精度,七成取决于参数辨识的质量。络石机械臂的连杆质量、质心位置、惯性张量、关节摩擦系数,这些参数如果直接用 CAD 模型的理论值,误差可能达到 20% 到 50%。原因很简单:CAD 模型里不会包含线束、螺丝、传感器这些附加质量,也不会包含减速器的柔性特性。
参数辨识的基本思路是:让机械臂跟踪一条激励轨迹,采集关节角度、速度、加速度和力矩数据,然后通过最小二乘法求解动力学参数。这里的关键是激励轨迹的设计——轨迹要足够“丰富”,才能激发所有动力学参数的可辨识性。如果轨迹太单一,某些参数就辨识不出来,或者辨识结果对噪声极其敏感。
我一般用有限项傅里叶级数作为激励轨迹,因为它的周期性好,可以多次平均降噪,而且频率成分可控。具体形式是每个关节的角度用几阶正弦波叠加,频率选在机械臂带宽的 1/3 到 1/2 之间。
3.2 激励轨迹设计与数据采集的实操步骤
第一步是确定激励轨迹的基频和阶数。基频不能太高,否则机械臂跟踪不上;也不能太低,否则采集时间太长。我一般选 0.1 Hz 到 0.5 Hz 作为基频,阶数选 3 到 5 阶。下面是一个生成激励轨迹的 Python 代码片段:
import numpy as np def generate_excitation_trajectory(t, base_freq=0.2, n_harmonics=4, n_joints=6): """ 生成傅里叶级数激励轨迹 t: 时间数组 base_freq: 基频 (Hz) n_harmonics: 谐波阶数 n_joints: 关节数 返回: q, dq, ddq """ omega = 2 * np.pi * base_freq q = np.zeros((len(t), n_joints)) dq = np.zeros((len(t), n_joints)) ddq = np.zeros((len(t), n_joints)) # 虚构的傅里叶系数(实际使用需通过优化生成) np.random.seed(42) a_coeff = np.random.randn(n_joints, n_harmonics) * 0.1 b_coeff = np.random.randn(n_joints, n_harmonics) * 0.1 for j in range(n_joints): for k in range(1, n_harmonics + 1): q[:, j] += a_coeff[j, k-1] * np.sin(k * omega * t) + \ b_coeff[j, k-1] * np.cos(k * omega * t) dq[:, j] += a_coeff[j, k-1] * k * omega * np.cos(k * omega * t) - \ b_coeff[j, k-1] * k * omega * np.sin(k * omega * t) ddq[:, j] += -a_coeff[j, k-1] * (k * omega)**2 * np.sin(k * omega * t) - \ b_coeff[j, k-1] * (k * omega)**2 * np.cos(k * omega * t) return q, dq, ddq # 生成 10 秒轨迹,采样率 100 Hz t = np.linspace(0, 10, 1000) q, dq, ddq = generate_excitation_trajectory(t) print("轨迹形状:", q.shape)这段代码生成的轨迹需要先做安全性检查——把 q 的幅值限制在关节限位内,把 dq 和 ddq 限制在电机能力范围内。检查通过后,把轨迹下发到络石机械臂的控制器,同时采集关节力矩数据。采集时要注意:力矩数据的噪声水平直接决定辨识精度,如果条件允许,尽量用关节力矩传感器而不是电机电流估算。
第二步是构建回归矩阵。动力学方程可以写成tau = Y(q, dq, ddq) * beta的形式,其中Y是回归矩阵,beta是待辨识的参数向量。回归矩阵的构建依赖动力学模型的结构,可以用牛顿-欧拉法或者拉格朗日法自动生成。第三步是用最小二乘法求解beta,如果数据有噪声,可以用加权最小二乘或者递推最小二乘。
3.3 最小二乘求解与参数验证
求解beta的代码不复杂,核心就是np.linalg.lstsq。但有几个细节容易翻车:一是回归矩阵的条件数,如果条件数太大,说明激励轨迹对某些参数不敏感,需要重新设计轨迹;二是参数的可辨识性,有些参数在动力学方程里是线性相关的,只能辨识出它们的组合,不能单独辨识。
def identify_parameters(Y, tau): """ 最小二乘参数辨识 Y: 回归矩阵 (N x P) tau: 力矩数据 (N,) 返回: 辨识参数 beta, 条件数 cond """ # 检查条件数 cond = np.linalg.cond(Y) print(f"回归矩阵条件数: {cond:.2f}") if cond > 1e6: print("警告:条件数过大,激励轨迹可能不够丰富") # 最小二乘求解 beta, residuals, rank, sv = np.linalg.lstsq(Y, tau, rcond=None) print(f"矩阵秩: {rank}, 参数数量: {Y.shape[1]}") return beta, cond # 虚构的回归矩阵和力矩数据(实际使用需替换为真实采集数据) np.random.seed(0) Y_fake = np.random.randn(1000, 10) tau_fake = Y_fake @ np.random.randn(10) + np.random.randn(1000) * 0.01 beta, cond = identify_parameters(Y_fake, tau_fake) print("辨识参数:", beta)辨识完成后,必须做验证。验证方法是:用另一条轨迹(不是激励轨迹)采集数据,把辨识参数代入动力学模型计算力矩,和实测力矩对比。如果均方根误差在 5% 以内,说明辨识质量可以接受;如果超过 10%,需要检查激励轨迹、数据采集和回归矩阵构建环节。
4. 络石机械臂仿真环境搭建:从 URDF 到物理引擎的完整链路
4.1 仿真环境选型:为什么我最终选了 PyBullet
络石机械臂的仿真环境有几个常见选择:Gazebo、MuJoCo、PyBullet、CoppeliaSim。Gazebo 和 ROS 集成好,但物理引擎的接触求解精度一般;MuJoCo 的接触和摩擦模型最准,但学习曲线陡;PyBullet 的 API 最友好,Python 原生支持,适合快速迭代;CoppeliaSim 的图形界面最完善,但脚本接口偏重。
我最终选 PyBullet 的原因是:它的动力学计算和我的牛顿-欧拉代码可以互相验证,而且它的calculateInverseDynamics接口可以直接和我的辨识参数对接。对于络石机械臂这种六轴串联结构,PyBullet 的精度完全够用,而且调试成本最低。
选型时要注意一个点:不同物理引擎对关节摩擦、接触刚度和阻尼的处理方式不同,同一个模型在不同引擎里的仿真结果可能有差异。如果你要做力控或者接触任务,建议先用 MuJoCo 验证,再用 PyBullet 做快速迭代。
4.2 从 URDF 到 PyBullet:加载络石机械臂模型的完整代码
假设你已经有了络石机械臂的 URDF 文件,下面这段代码把它加载到 PyBullet 里,并设置动力学参数:
import pybullet as p import pybullet_data import numpy as np import time # 连接物理引擎 physics_client = p.connect(p.GUI) p.setAdditionalSearchPath(pybullet_data.getDataPath()) p.setGravity(0, 0, -9.81) # 加载地面 plane_id = p.loadURDF("plane.urdf") # 加载络石机械臂 URDF(替换为你的实际路径) robot_id = p.loadURDF("path/to/luoshi_arm.urdf", basePosition=[0, 0, 0], useFixedBase=True) # 获取关节信息 num_joints = p.getNumJoints(robot_id) print(f"关节数量: {num_joints}") for i in range(num_joints): info = p.getJointInfo(robot_id, i) print(f"关节 {i}: {info[1].decode()}, 类型: {info[2]}") # 设置动力学参数(用辨识值覆盖 URDF 默认值) # 注意:PyBullet 的 link 索引从 0 开始,对应 URDF 里的第一个 link identified_masses = [2.5, 2.0, 1.5, 1.0, 0.8, 0.5] identified_inertias = [ [0.01, 0.01, 0.005], [0.008, 0.008, 0.003], [0.005, 0.005, 0.002], [0.003, 0.003, 0.001], [0.001, 0.001, 0.0005], [0.0005, 0.0005, 0.0002] ] for i in range(min(num_joints, 6)): p.changeDynamics(robot_id, i, mass=identified_masses[i], localInertiaDiagonal=identified_inertias[i], linearDamping=0.1, angularDamping=0.1) # 设置关节摩擦 for i in range(num_joints): p.setJointMotorControl2(robot_id, i, controlMode=p.VELOCITY_CONTROL, force=0) # 先禁用默认电机,后面自己控制 # 仿真循环 for step in range(1000): p.stepSimulation() time.sleep(1./240.) p.disconnect()这段代码的关键点是changeDynamics函数,它允许你用辨识得到的质量、惯性张量和阻尼参数覆盖 URDF 里的默认值。linearDamping和angularDamping对应关节的粘性摩擦,这两个值也需要通过辨识得到。如果你不做这一步,PyBullet 会用 URDF 里的理论值,仿真精度会打折扣。
还有一个容易忽略的点:PyBullet 的useFixedBase=True把基座固定住了,但如果你要仿真机械臂安装在移动平台上,需要改成False并额外设置基座的动力学参数。
4.3 用 PyBullet 验证动力学模型:正逆动力学闭环测试
加载模型之后,第一件事是验证动力学模型是否正确。方法是:给一组关节力矩,用 PyBullet 做正动力学仿真得到关节加速度,再用自己的牛顿-欧拉代码做逆动力学计算力矩,两者对比。如果一致,说明模型对接成功。
def verify_dynamics(robot_id, q, dq, tau): """ 验证 PyBullet 正动力学和自己的逆动力学是否一致 """ num_joints = p.getNumJoints(robot_id) # 设置关节状态 for i in range(num_joints): p.resetJointState(robot_id, i, q[i], dq[i]) # 用 PyBullet 计算逆动力学 tau_pybullet = p.calculateInverseDynamics(robot_id, q, dq, [0]*num_joints) # 对比 error = np.array(tau_pybullet) - np.array(tau) print(f"力矩误差: {error}") print(f"均方根误差: {np.sqrt(np.mean(error**2)):.6f}") return error # 测试 q_test = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] dq_test = [0.01, 0.02, 0.03, 0.04, 0.05, 0.06] tau_test = [1.0, 2.0, 1.5, 0.8, 0.5, 0.3] error = verify_dynamics(robot_id, q_test, dq_test, tau_test)如果误差在 1e-4 量级以内,说明模型对接没问题。如果误差很大,检查三个地方:一是 URDF 里的关节轴方向是否和你的 DH 参数一致;二是重力方向是否一致;三是惯性张量的坐标系是否对齐。
5. 络石机械臂动力学仿真避坑清单:5 个血泪教训
5.1 坑一:辨识参数在仿真里“水土不服”
现象:参数辨识的验证误差只有 3%,但把参数代入仿真后,轨迹跟踪误差超过 10 厘米。
原因:辨识时用的数据是在低速、轻载条件下采集的,但仿真里跑的是高速、重载轨迹。动力学参数里的摩擦系数和阻尼系数是速度相关的,低速标定的值在高速下不适用。
解决:辨识时激励轨迹要覆盖实际工况的速度范围。如果实际工况速度变化大,建议分段辨识,或者用非线性摩擦模型代替线性模型。
5.2 坑二:PyBullet 的默认时间步长导致数值发散
现象:仿真跑着跑着机械臂突然飞出去,关节角度变成 NaN。
原因:PyBullet 默认时间步长是 1/240 秒,但如果你的控制器增益设得太大,或者关节力矩超过物理引擎的稳定范围,就会数值发散。
解决:把时间步长降到 1/1000 秒,同时给关节力矩加限幅。如果还不行,检查惯性张量是否正定——有时候辨识出来的惯性张量不满足正定性,物理引擎会直接崩溃。
5.3 坑三:URDF 里的关节限位和实际不符
现象:仿真里机械臂能转到 180 度,但实际机械臂只能转到 120 度,导致仿真轨迹在实际执行时撞限位。
原因:URDF 文件里的关节限位是建模时随手填的,没有和实际机械臂核对。
解决:加载 URDF 后,用p.getJointInfo读取每个关节的限位,和实际机械臂的规格书对比。如果不一致,用p.changeDynamics或者直接修改 URDF 文件。
5.4 坑四:忽略减速器的柔性导致力矩振荡
现象:仿真里关节力矩曲线在换向时出现高频振荡,实际机械臂也有类似现象但幅度小很多。
原因:仿真模型把关节当成刚性连接,但实际减速器有柔性,会吸收一部分高频振荡。仿真里没有这个柔性,振荡就被放大了。
解决:在关节模型里加入弹簧-阻尼单元模拟减速器柔性。PyBullet 不支持直接加柔性关节,但可以用p.createConstraint创建一个弹簧约束来近似。
5.5 坑五:用理论惯性张量代替辨识值导致能耗计算错误
现象:仿真里计算机械臂能耗,结果比实际测量值低了 30%。
原因:理论惯性张量只包含连杆本体,不包含电机转子、减速器和线束的转动惯量。这些附加惯量在高速运动时对能耗影响很大。
解决:辨识参数时,把电机转子和减速器的惯量折算到关节侧,一起辨识。如果辨识不出来,至少要把电机转子惯量加到理论值上。
6. 络石机械臂动力学仿真的进阶技巧:从仿真到实机的迁移方法
仿真做完了,怎么保证迁移到实机时不翻车?我自己的习惯是做一个“三步验证”:第一步,在仿真里跑轨迹,记录每个关节的力矩曲线;第二步,在实机上用同样的轨迹跑一遍,但把力矩限幅设到仿真值的 50%,观察实际力矩和仿真力矩的偏差;第三步,如果偏差在 15% 以内,逐步放开限幅,直到实机力矩和仿真力矩吻合。
这个过程中,有一个技巧很实用:用仿真数据训练一个简单的神经网络补偿器,输入是关节角度、速度和加速度,输出是仿真力矩和实机力矩的残差。这个补偿器不需要很复杂,三层全连接网络就够了。训练数据用实机采集的 100 到 200 条轨迹,每条轨迹 10 秒左右。训练好之后,把补偿器加到仿真模型里,仿真精度能提升 30% 到 50%。
另一个技巧是关于仿真步长的。很多人为了追求实时性,把仿真步长设得很大,结果数值精度不够。我的做法是:仿真步长设小(1/1000 秒),但控制周期设大(1/100 秒),两者之间用零阶保持器连接。这样既保证了数值稳定性,又不会让计算量爆炸。
还有一个容易被忽略的点:仿真里的传感器噪声模型。实机的关节编码器和力矩传感器都有噪声,如果仿真里不加噪声,迁移到实机时控制器会不适应。我一般会在仿真里加高斯白噪声,编码器噪声标准差设 1e-4 弧度,力矩传感器噪声标准差设 0.01 牛米。这个噪声水平是根据实际传感器手册估算的,你可以根据自己用的传感器调整。
最后说一个我踩过的坑:仿真里机械臂的基座是固定的,但实机上机械臂安装在工作台上,工作台本身有振动。这个振动在仿真里没有,导致实机轨迹跟踪时出现低频抖动。后来我在仿真里给基座加了一个小幅度的正弦振动,频率 5 到 10 Hz,幅度 0.1 毫米,仿真结果就和实机吻合多了。这个技巧不一定适用于所有场景,但如果你的实机振动明显,值得一试。
希望帮到你。
本文还有配套的精品资源,点击获取