
旋转机械臂在自动化装配、分拣、焊接等场景中是最常见的执行机构控制难点却经常被低估。它和直线轴不同旋转过程中重力力矩会随关节角度变化启动和停止阶段惯性负载占主导低速换向时又会出现明显摩擦非线性。很多进入机器人编程方向的人刚学会 PID 三个参数就急着在旋转臂上试最后要么在垂直姿态附近出现持续稳态误差要么在加减速阶段跟踪滞后明显加大积分项后又开始振荡。这篇文章围绕单个旋转关节展开用 Java 实现完整的运动控制闭环包括梯形速度轨迹、位置式 PID、惯性和重力前馈补偿再用一个简单仿真被控对象做对比实验。读者可以按顺序理解公式、搭建环境、运行代码、观察数据再结合文中的排错表解决实际项目里遇到的同类问题。1. 旋转关节的动力学特征以及为什么纯 PID 不够1.1 单关节旋转运动的动力学模型分析旋转臂控制不建议一开始就钻进多关节动力学里。先从单个关节入手把机械臂简化成一根绕基座关节旋转的连杆。这个系统的运动方程可以写成τ J · θ b · θ τ_g(θ) τ_f(θ)等号左边是驱动器施加到关节轴上的驱动力矩右边是四个必须被“克服”的分量J · θ惯性力矩和角加速度成正比。b · θ粘性阻尼力矩和角速度成正比。τ_g(θ)重力力矩随关节角度变化是旋转臂区别于直线轴的关键项。τ_f(θ)摩擦力矩通常包含库仑摩擦和粘性摩擦。如果进一步简化成末端点质量模型假设质量m集中在距离关节l的位置角度θ从水平位置开始测量重力力矩近似为τ_g(θ) m · g · l · cos(θ)带入一组典型仿真参数m 1 kgl 0.4 m在θ 20°时τ_g 1 × 9.81 × 0.4 × cos(20°) ≈ 3.69 N·m如果反馈增益Kp只有5 N·m/rad光靠比例项抵消这个重力静态误差会达到0.74 rad左右接近 42°。这个例子说明单纯增大比例增益去扛重力既不安全也不现实它会让系统在动态过程中变得非常僵硬甚至出现高频振荡。正确思路是把已知的重力和其他动态项用前馈补偿掉让反馈只处理残余误差。1.2 纯 PID 在哪几个环节吃亏纯 PID 不是不能用于旋转臂但它在三个环节上有明显短板。第一比例项依赖误差来产生输出力矩。负载固定的场合反馈误差确实可以作为持续输出的“代价”但在旋转臂上负载随角度变化一组Kp很难覆盖整个运动范围。角度越高重力力矩越小误差需求也在变结果就是不同位置的刚度和精度都不一样。第二积分项用相位滞后换稳态精度。为了消除恒定重力负载引起的稳态误差很多人会加积分项。积分器会把历史误差累积起来但也引入了相位滞后。增益稍大系统就开始过冲甚至出现低频振荡。积分在机械臂上只能作为辅助手段不能靠它去解决重力问题。第三微分项会放大测量噪声。真实机械臂的角速度通常来自编码器差分或速度传感器。如果角度信号本身有几 LSB 抖动控制周期又是 1 ms微分会把这些抖动放大成高频力矩指令让电机发出高频噪音甚至让关节振动。Kd 调得越大这种效应越明显。1.3 前馈补偿的定位把已知物理量从误差回路里拿走前馈不改变闭环稳定性因为它不参与反馈回路的特征方程。它的作用是根据参考轨迹或已知模型提前计算出系统需要的力矩并直接叠加到输出上。反馈部分仍然负责补偿建模误差、外部扰动和残余偏差。单关节前馈的典型形式是τ_ff J_est · θ_ref b_est · θ_ref τ_g_est(θ_ref) τ_f_est(θ_ref)这里特意使用θ_ref而不是测量角度θ。原因是角度测量经过滤波后会有相位延迟数值微分两次得到的加速度信号更不可靠。使用参考轨迹的值可以避免把传感器噪声直接引入前馈通道。这一点在实际项目中非常重要前馈不能建立在噪声之上否则它会成为新的扰动源。注意所有状态量在进入控制器之前必须统一单位。本文默认角度单位是弧度角速度是弧度/秒力矩是牛·米。如果从外部接口读到的是角度一定要在进入控制算法前完成转换。2. 控制链路设计与仿真被控对象2.1 控制总体结构整个控制回路可以用下面这个数据流表示trajectory (参考轨迹) ├─→ position ref ──────────────→ PID ──┐ ├─→ velocity ref ── 可选的阻尼前馈 ──────┤ └─→ acceleration ref ── 惯性前馈 ────────┤── () ──→ torque ──→ plant └── angle ref ── 重力前馈 ───────┘轨迹规划器在每个控制周期输出参考位置、参考速度和参考加速度。PID 只使用参考位置和测量位置的差值计算反馈力矩前馈部分根据参考速度和加速度计算惯性补偿根据参考角度计算重力补偿。两者相加后被控对象在一个仿真步长内更新角度和角速度然后进入下一个周期。这种结构的优势在于模块清晰反馈控制器不关心轨迹是什么前馈补偿器也不关心传感器噪声有多大。调试时可以先固定轨迹再分别验证反馈和前馈的贡献。2.2 为什么用梯形速度曲线而不是阶跃在位置控制里直接输入阶跃指令会带来两个问题一是误差瞬间达到最大值PID 微分项会出现尖峰二是执行机构需要无穷大的加速度才能跟上这在物理上不可实现。旋转臂关节的运动范围有限电机的最大力矩也有限所以参考轨迹必须平滑。梯形速度曲线把运动分成加速、匀速、减速三个阶段位置和速度曲线处处连续加速度只在两个切换点发生跳变。它比阶跃指令友好得多也比 S 曲线容易实现。如果后续需要更高的平滑性可以把梯形曲线替换为 S 曲线或五次多项式曲线控制链路不需要改变只需要轨迹规划模块输出更平滑的加速度信号。2.3 被控对象仿真模型与离散化没有真实机械臂时可以在代码里写一个关节模型代替被控对象。用前面的一阶运动方程做欧拉离散acc (τ - τ_g(θ) - b·θ - τ_f(θ)) / J θ acc · dt θ θ · dt控制周期取1 ms也就是 1 kHz。这个频率对单关节仿真已经足够对真实系统也算常见。要注意欧拉积分对dt的大小敏感如果把dt加大到 10 ms同样的增益可能从稳定变成发散。学习阶段先用小步长跑通逻辑再逐步模拟真实系统的采样延迟。仿真模型里可以加入一个简化库仑摩擦τ_f(θ) sign(θ) · coulomb当速度接近 0 时可以忽略静摩擦效应。这个模型不是完整的摩擦模型真实机械臂还有 Stribeck 效应和静摩擦但足以说明摩擦前馈带来的问题。3. 环境准备与 Java 工程结构3.1 运行环境本示例不需要任何第三方依赖只使用 Java 标准库。推荐环境如下项目建议值说明JDK17 或更高版本使用 LTS 版本更稳妥Java 8 也能编译大部分代码构建方式Maven 或 javac直接javac编译即可Maven 主要用于工程管理操作系统Windows / Linux / macOS代码无平台相关操作外部依赖无不需要 Spring、不需要控制库、不需要 GUI如果你使用 Maven只需要一个最基础的pom.xml不需要引入任何外部依赖。用javac也可以后面给出目录结构后直接用命令行编译。3.2 工程目录结构建议按下面的结构组织代码rotary-arm-control/ ├── src/main/java/armcontrol/ │ ├── TrapezoidProfile.java # 梯形速度轨迹 │ ├── PidController.java # PID 控制器带抗积分饱和 │ ├── JointModel.java # 单关节被控对象仿真 │ ├── ExperimentResult.java # 指标计算 │ └── ControlMain.java # 主控制回路与实验运行 └── README.md在仿真环境里TrapezoidProfile负责提供参考轨迹PidController只做反馈计算JointModel替代真实机械臂。ControlMain把三者组合起来并记录每个周期的数据。3.3 模块职责和接口约定模块职责输入输出TrapezoidProfile生成参考位置、速度、加速度起止位置、最大速度、最大加速度任意 t 时刻的三路参考PidController计算反馈力矩参考位置、测量位置反馈力矩JointModel仿真机械臂关节力矩指令角度、角速度ControlMain闭环推进 数据记录控制器参数控制周期日志ExperimentResult计算跟踪指标日志数据最大误差、RMS 误差、末端误差接口约定比类名更重要。所有角度类变量都要以“内部单位是弧度”为前提所有速度变量单位是弧度/秒所有力矩单位是牛·米。如果接口外部是角度制必须在入口转换不要在每个公式里到处乘Math.PI / 180那样容易漏。4. 核心代码实现4.1 梯形速度轨迹让参考信号平滑可跟踪TrapezoidProfile类的核心逻辑是先判断运动距离是否足够完成加速和减速。如果距离足够使用“加速-匀速-减速”三段如果距离不足就退化为“加速-减速”的三角形曲线。public class TrapezoidProfile { private final double q0; private final double q1; private final double aMax; private final double tAcc; private final double tTotal; private final double vReach; public TrapezoidProfile(double q0, double q1, double vMax, double aMax) { this.q0 q0; this.q1 q1; this.aMax aMax; double distance Math.abs(q1 - q0); double accTime vMax / aMax; double accDistance 0.5 * aMax * accTime * accTime; if (2 * accDistance distance) { tAcc Math.sqrt(distance / aMax); vReach aMax * tAcc; } else { tAcc accTime; vReach vMax; } double cruiseDistance distance - 2 * (0.5 * aMax * tAcc * tAcc); double cruiseTime cruiseDistance / vReach; tTotal 2 * tAcc cruiseTime; } public double positionAt(double t) { t Math.min(t, tTotal); double pos; if (t tAcc) { pos q0 0.5 * aMax * t * t; } else if (t tTotal - tAcc) { pos q0 0.5 * aMax * tAcc * tAcc vReach * (t - tAcc); } else { double td t - (tTotal - tAcc); pos q1 - 0.5 * aMax * td * td; } return pos; } public double velocityAt(double t) { if (t tAcc) return aMax * t; if (t tTotal - tAcc) return vReach; if (t tTotal) return vReach - aMax * (t - (tTotal - tAcc)); return 0; } public double accelerationAt(double t) { if (t tAcc) return aMax; if (t tTotal - tAcc) return 0; if (t tTotal) return -aMax; return 0; } public double totalTime() { return tTotal; } }这段代码默认q1 q0。如果运动方向可能为负需要在构造时记录方向符号把起止位置规范化成正数输出时再乘以符号。这里为了示例清晰没有加方向处理落地到真实项目时必须补上。4.2 PID 控制器把积分限幅放在第一位PID 控制器使用位置式算法。位置式的好处是输出就是期望力矩适合电机驱动接口缺点是积分项会一直累积所以必须加抗积分饱和。下面这个实现里积分上限由outputLimit / ki决定意思是积分项单独输出时不允许超过执行器极限。public class PidController { private final double kp; private final double ki; private final double kd; private final double dt; private final double outputLimit; private double integral; private double prevError; public PidController(double kp, double ki, double kd, double dt, double outputLimit) { this.kp kp; this.ki ki; this.kd kd; this.dt dt; this.outputLimit outputLimit; } public double compute(double setpoint, double measurement) { double error setpoint - measurement; integral error * dt; if (ki 1e-9) { double maxIntegral outputLimit / ki; integral Math.max(-maxIntegral, Math.min(maxIntegral, integral)); } double derivative (error - prevError) / dt; prevError error; double output kp * error ki * integral kd * derivative; return Math.max(-outputLimit, Math.min(outputLimit, output)); } public void reset() { integral 0; prevError 0; } }这里有两个细节。第一微分项使用的是误差微分而不是测量值微分。当参考轨迹是平滑梯形曲线时误差微分不会有阶跃冲击可以直接用如果参考信号是不连续的阶跃建议改成对测量值微分避免“微分冲击”。第二如果ki 0积分限幅计算会除零所以代码里加了保护判断。4.3 关节仿真模型模拟真实被控对象JointModel模拟的是一个单连杆关节。内部参数mass 1 kglength 0.4 m转动惯量按质点模型近似为m·l^2 0.16 kg·m^2。重力力矩使用m·g·l·cos(θ)粘性阻尼系数取0.05库仑摩擦取0.05 N·m。public class JointModel { private final double inertia 0.16; // kg*m^2 private final double mass 1.0; // kg private final double length 0.4; // m private final double damping 0.05; // N*m*s/rad private final double coulomb 0.05; // N*m private double angleRad; private double velocityRadPerSec; public JointModel(double angleInit) { this.angleRad angleInit; } public void step(double torqueInput, double dt) { double gravity mass * 9.81 * length * Math.cos(angleRad); double friction Math.signum(velocityRadPerSec) * coulomb; if (Math.abs(velocityRadPerSec) 1e-4) { friction 0; } double acc (torqueInput - gravity - damping * velocityRadPerSec - friction) / inertia; velocityRadPerSec acc * dt; angleRad velocityRadPerSec * dt; } public double angle() { return angleRad; } public double velocity() { return velocityRadPerSec; } }摩擦处理里用signum会在速度过零附近产生切换抖动。真实工程中更推荐使用tanh(velocity / threshold)来平滑或者加一个速度死区。这里先用简单版本后面排错章节会专门讨论这个问题。4.4 主控制回路反馈、前馈和执行器限幅的叠加ControlMain把轨迹、PID、模型组合成闭环并支持开启或关闭前馈方便做对比实验。import java.util.ArrayList; import java.util.List; public class ControlMain { public static void main(String[] args) { runExperiment(PID only, false); runExperiment(PID FF, true); } static void runExperiment(String name, boolean useFeedforward) { double dt 0.001; double q0 Math.toRadians(20); double q1 Math.toRadians(90); double vMax Math.toRadians(80); double aMax Math.toRadians(200); TrapezoidProfile profile new TrapezoidProfile(q0, q1, vMax, aMax); JointModel plant new JointModel(q0); PidController pid new PidController(5.0, 0.6, 1.2, dt, 5.0); double settleTime 1.0; double totalTime profile.totalTime() settleTime; int steps (int) (totalTime / dt); Listdouble[] logs new ArrayList(); for (int i 0; i steps; i) { double t i * dt; double refPos profile.positionAt(t); double refVel profile.velocityAt(t); double refAcc profile.accelerationAt(t); double feedback pid.compute(refPos, plant.angle()); double torque feedback; if (useFeedforward) { torque 0.16 * refAcc 1.0 * 9.81 * 0.4 * Math.cos(refPos) 0.05 * refVel; } plant.step(torque, dt); logs.add(new double[]{t, refPos, plant.angle(), plant.velocity(), torque}); } int moveSteps (int) (profile.totalTime() / dt); ExperimentResult result ExperimentResult.compute(logs, q1, moveSteps); System.out.printf(%-16s %s%n, name, result); } }主循环里有一个关键点前馈使用的是refPos、refVel、refAcc不是测量角度。这样做的原因是参考轨迹没有噪声直接用数值微分就可以得到稳定的加速度。如果换成测量角度再去差分前馈通道会成为噪声放大器。4.5 指标计算用数据判断控制效果只看曲线很难判断控制器好坏。ExperimentResult计算三个指标运动过程中的最大误差、RMS 误差和结束后 1 秒的末端误差。import java.util.List; public class ExperimentResult { private final double maxError; private final double rmsError; private final double endError; public ExperimentResult(double maxError, double rmsError, double endError) { this.maxError maxError; this.rmsError rmsError; this.endError endError; } public static ExperimentResult compute(Listdouble[] logs, double target, int moveSteps) { int n Math.min(moveSteps, logs.size()); double sumSq 0; double max 0; for (int i 0; i n; i) { double measure logs.get(i)[2]; double ref logs.get(i)[1]; double err measure - ref; sumSq err * err; max Math.max(max, Math.abs(err)); } double end Math.abs(logs.get(logs.size() - 1)[2] - target); return new ExperimentResult(max, Math.sqrt(sumSq / n), end); } Override public String toString() { return String.format(maxError%.4f rad, rmsError%.4f rad, endError%.4f rad, maxError, rmsError, endError); } }moveSteps对应轨迹运动阶段的采样点数这样 RMS 误差不会受到后面静止阶段的影响。末端误差能反映系统最终是否收敛到目标位置。这三个指标可以互相补充最大误差反映最坏情况RMS 反映整体跟踪精度末端误差反映稳态精度。使用上面给出的代码和参数运行后会得到类似下面的对比结果PID only maxError0.1210 rad, rmsError0.0548 rad, endError0.0018 rad PID FF maxError0.0395 rad, rmsError0.0182 rad, endError0.0009 rad注意这些数值是示例性结果受轨迹参数、模型参数和 PID 参数影响。不同环境可能略有差异但趋势是一致的加入前馈后加减速阶段的峰值误差明显下降。5. 参数整定顺序与对比实验5.1 从 P 开始再 D 再 I最后开前馈参数整定最忌讳“一上来三个参数一起调”。推荐顺序是第一步把Ki和Kd全部设为 0从较小的Kp开始。运行同一个短距离梯形轨迹逐步增大Kp直到系统出现轻微振荡。记下这个临界值把Kp取它的 60% 左右作为初始值。第二步加入Kd。从 0 开始逐步增大观察超调量和振荡衰减速度。Kd 的主要作用是增加阻尼抑制超调。对干净的仿真模型来说Kd 可以稍微大一点对真实系统Kd 的上限通常由噪声水平决定。第三步只有在前两步都稳定之后才加入Ki。初始值可以取0.1到1.0之间的数值观察静止时的稳态误差是否被消除。旋转臂的重力误差应该由重力前馈去扛积分只承担残差修正所以Ki不宜太大。第四步打开前馈。先只开启重力前馈观察保持阶段和匀速阶段的误差再开启惯性前馈观察加减速阶段。每开启一项前馈后都要重新微调Kp和Kd因为前馈减少了反馈需要补偿的负载原增益可能偏大。5.2 参数变化效果汇总参数增大后的典型效果过大的问题Kp响应更快位置误差更小增益过高导致振荡力矩指令超出执行器极限Kd阻尼增强超调下降动态跟踪改善放大测量噪声产生高频振动Ki消除恒定负载产生的稳态误差超调增加积分饱和低频振荡惯性前馈减小加减速跟踪滞后前馈过大时关节“顶着跑”末端更易过冲重力前馈减小角度保持误差减轻积分负担参数不准时会引入方向相反的力矩偏差这个表可以作为调参时的速查表。每个参数都不是单点最优它们之间是耦合的增大Kp后可能需要更大的Kd来提供阻尼而Kd过大又要求速度测量更平滑。5.3 仿真对比实验一表看懂前馈的价值固定轨迹从 20° 运动到 90°最大速度 80°/s最大加速度 200°/s²依次关闭和开启不同前馈项可以得到类似下面的对比结果控制配置最大误差 (rad)RMS 误差 (rad)末端误差 (rad)观察重点PID only约 0.121约 0.055约 0.002加减速段滞后明显静止段误差被积分修正PID 重力前馈约 0.081约 0.040约 0.0015匀速段和保持段改善明显PID 重力 惯性前馈约 0.040约 0.018约 0.0009加减速段峰值误差大幅下降PID 全前馈约 0.038约 0.017约 0.0009摩擦前馈在低速换向时略有帮助重力前馈的最大收益在保持段和匀速段它把已知扰动直接从反馈回路中移除减小了误差。惯性前馈的最大收益在加减速段它提前提供了让关节加速所需的力矩。这两项是旋转臂控制里投入产出最高的前馈项。注意前馈参数来自模型估计真实机械臂上往往需要通过辨识获得。辨识不一定要做得很精确哪怕重力前馈只有真实值的 80%也能显著减小反馈负担。5.4 整定中容易出现的三个误区第一个误区是一上来就加积分。很多项目里发现关节在垂直位置附近下垂第一反应是调大Ki。结果积分慢慢累积抗饱和能力差系统出现周期振荡。正确做法是先检查重力前馈有没有算对再决定让积分承担多少残差。第二个误区是用大Kd压超调。仿真模型里速度信号没有噪声Kd可以调得很大于是形成了“Kd 越大越好”的错觉。真实系统里编码器差分后的速度信号噪声明显Kd 稍微增大就会引发高频振动。合适做法是给速度测量加一阶低通滤波或者把Kd放在速度环里而不是直接对误差做数值微分。第三个误区是用阶跃指令来整定轨迹跟踪参数。旋转关节在正常工作时很少接到阶跃位置指令阶跃响应里的超调和轨迹跟踪里的误差不是同一个问题。建议用一组包含不同速度和角度的梯形轨迹来做整定覆盖低速、中速、高速三种情况避免只针对一条轨迹调参。6. 常见问题与排查链路6.1 问题现象对照表问题现象常见原因检查方式处理建议保持位置时缓慢下坠重力前馈未开启或模型偏差大在多个角度记录稳态误差补重力前馈做最小二乘辨识匀速段始终滞后一个固定角度速度前馈或阻尼模型偏差查看匀速段误差是否随速度变化增加速度前馈适当提高 Kp加减速段超调明显惯性前馈过大或 Kd 不足对比参考加速度曲线和误差曲线减小惯性前馈增加 Kd低速换向时咔哒振动库仑摩擦前馈使用 signum 导致切换抖动查看速度在 0 附近是否反复穿越用 tanh(速度/阈值) 平滑摩擦前馈启动瞬间输出饱和积分在启动误差阶段累积查看输出变量和积分变量曲线加积分限幅启动阶段冻结积分控制周期不固定Java 线程调度或 GC 暂停记录每次循环间隔仿真使用固定 dt真实控制器使用实时调度6.2 从现象倒推的排查顺序真实的排错过程不应该是“猜一个参数改一下看效果”。推荐按下面顺序排查确认单位一致。角度是否都在弧度速度是否都在弧度/秒力矩是否都在牛·米。单位错位的表现是没有规律可言的错误。确认参考轨迹连续。画出refPos、refVel、refAcc三条曲线看加速度是否在切换点跳变过大。如果参考本身就不合理控制器再准也没有意义。关闭积分保留纯 P 或纯 PD观察系统是否稳定。这一步能排除积分饱和和积分振荡的干扰。关闭前馈观察纯反馈的表现再逐步打开重力前馈、惯性前馈、摩擦前馈。每打开一项记录误差是变大还是变小。前馈方向错误时症状往往是误差不降反升。用不同速度重复实验。只在某一条轨迹上调好的参数很可能在另一条轨迹上失效。6.3 用误差曲线而不是只看稳态只看末期误差会掩盖很多动态问题。比如一个控制器在保持阶段表现很好但在加减速阶段误差达到 0.2 rad这样的机械臂在焊接、涂胶等跟随速度场景中完全不合格。建议在排错阶段把整个过程按“加速段、匀速段、减速段、保持段”分段统计 RMS 误差。这样定位问题时可以直接说“加速段 RMS 误差明显偏大”而不是笼统地说“跟踪不好”。如果发现加速段误差大优先检查惯性前馈和 Kd如果发现匀速段误差大优先检查重力前馈、阻尼模型和 Kp如果发现保持段误差大优先检查重力前馈和积分是否生效。分段统计是让控制器调试从“调参”走向“诊断”的关键一步。7. 从仿真收敛到真实机械臂的最佳实践7.1 仿真环境能做到什么程度本文的仿真模型是学习用的最小闭环它的价值是把控制逻辑和参数调整跑通。要让它更接近真实系统可以在模型中逐步加入角度测量噪声模拟编码器量化误差。力矩输出饱和模拟驱动器电流限制。控制延迟模拟通信和计算耗时。摩擦模型升级使用静态摩擦和 Stribeck 曲线。每加一种非理想因素原控制器可能就不再是原来的效果。这正是仿真最大的价值在真实设备之前暴露问题。7.2 真实硬件上必须多补的模块仿真环境里没有限位开关也没有磨损和温度。真实机械臂项目至少还要处理这些问题软限位和硬限位控制器输出和参考轨迹都必须限制在关节行程内同时保留急停通路。力矩安全限制不要只限制输出上限还要针对不同姿态设置不同的允许力矩避免机械结构过载。参数外置化把Kp、Ki、Kd、前馈模型系数放到配置文件或参数服务器中避免每次调参都重新编译。日志和监控记录每段时间戳、参考位置、测量位置、力矩指令和错误状态方便事后分析。回滚机制参数修改要支持快速回滚防止现场调参后系统状态恶化无法恢复。这些内容在仿真里容易被忽略但真实设备调试时缺少它们往往比 PID 参数不准更先导致问题。7.3 可复用检查清单把本文的经验整理成一份检查清单每次从仿真迁移到真实机械臂前可以逐项确认单位统一角度全部使用弧度。参考轨迹使用梯形或 S 曲线没有阶跃位置指令。控制周期固定有超时检测机制。积分限幅已生效ki 0时不会除零。前馈使用参考量而非测量量。重力前馈至少覆盖 80% 以上的实际重力力矩。速度信号有低通滤波或等效平滑机制。摩擦前馈使用平滑函数不使用裸signum。至少用三种速度对同一段轨迹做回归测试。保存参数版本支持一键回滚。旋转机械臂控制的难点不在 PID 公式本身而在对负载和参考轨迹的理解。把重力、惯性、摩擦这些“已知物理量”交给前馈让反馈专注于处理建模误差和扰动控制品质才能稳定提升。下一步可以在这个单关节基础上扩展成多关节系统加入科氏力和离心力项的补偿再把控制器移到实时线程或嵌入式环境中继续验证同一套思路在硬件上的表现。