第26章 自适应控制:负载变化自适应、参数在线辨识、鲁棒性设计

各位工程师朋友,大家好。今天我们来聊聊自适应控制。说实话,这个主题在运动控制里属于「高阶玩法」。很多做了一辈子运动控制的工程师,可能都没真正碰过它。但我个人觉得,如果你想从「调参工」进阶到「算法工程师」,这一章是绕不开的。

为什么?因为现实世界太「脏」了。你想想看,一个机械臂今天抓个1kg的零件,明天换个5kg的夹具,后天末端再装个气动夹爪。负载一直在变,你总不能每次都手动调PID吧?自适应控制,就是让系统自己学会「适应」这些变化。

核心思想一句话:当系统参数(比如负载惯量、摩擦力)发生变化时,控制器能在线调整自身参数,维持良好的动态性能。

26.1 负载变化自适应:为什么PID搞不定?

先讲个我自己的经历。几年前我调试一个六轴工业机器人,空载时位置跟踪误差在0.1mm以内,效果很好。结果一装上20kg的工件,末端抖动得跟筛子似的。我当时第一反应是「加大增益」,结果高频噪声又出来了。

为什么会这样?因为负载变化改变了系统的惯量。惯量一变,系统的谐振频率、阻尼比全变了。固定增益的PID,说白了就是「刻舟求剑」。

负载变化自适应的思路其实很朴素:

  • 前馈补偿:把负载变化当作已知扰动,用前馈模型直接补偿
  • 增益调度:预先标定不同负载下的最优参数,在线查表切换
  • 模型参考自适应:让实际系统去跟踪一个理想模型的行为

我个人比较推荐第三种。它不需要你事先知道负载到底多重,系统自己会「摸索」出来。

26.2 参数在线辨识:让系统「长眼睛」

自适应控制的核心,就是参数在线辨识。说白了,就是让控制器一边干活,一边「偷看」系统的状态,然后悄悄更新自己的内部模型。

常用的方法有几种:

方法 原理 适用场景 我踩过的坑
递推最小二乘(RLS) 用历史数据递归更新参数估计 慢变参数系统 数据饱和导致参数漂移
梯度下降法 沿误差梯度方向修正参数 实时性要求高 学习率选不好容易发散
卡尔曼滤波 状态与参数联合估计 噪声较大的系统 计算量大,嵌入式吃力
模型参考自适应(MRAC) 用参考模型输出与实际输出之差驱动参数更新 伺服系统、机器人 参考模型设计不当会震荡

这里我重点说一下RLS。我曾经在一个焊接机器人项目里用过它。焊接过程中,焊丝不断消耗,末端负载在缓慢变化。RLS能很好地跟踪这个变化。但有个坑——数据饱和。当系统运行时间长了,旧数据权重太大,新数据对参数更新的贡献越来越小。解决办法是加一个遗忘因子,让旧数据慢慢「失效」。

我的小技巧:遗忘因子一般取0.95~0.99。取值越大,系统越「健忘」,跟踪越快,但噪声敏感;取值越小,系统越「固执」,抗噪好,但响应慢。我一般从0.98开始试。

26.3 鲁棒性设计:别让自适应「玩脱了」

说到这,我得泼盆冷水。自适应控制不是万能的。搞不好,它会比固定参数更糟糕。我见过一个案例,某团队在机器人上跑自适应算法,结果负载突变时参数估计瞬间发散,电机直接飞车了。

这就是鲁棒性问题。自适应控制本质上是一个「带反馈的反馈」——参数更新本身也是一个动态系统。如果这个外环设计不好,整个系统可能失稳。

鲁棒性设计有几个关键点:

  • 参数投影:给参数估计值加个「笼子」,不让它跑到物理上不可能的范围。比如惯量估计值不能为负。
  • 死区处理:当误差很小时,停止参数更新。避免噪声导致参数「乱跳」。
  • σ-修正:在参数更新律中加入一个阻尼项,防止参数漂移。
  • 持久激励条件:系统必须有足够的激励,参数才能正确收敛。如果一直匀速运动,你永远辨识不出惯量。

警告:千万不要在系统没有充分激励的情况下开启自适应。我曾经在调试时,让机器人在空载下跑了个简单的点位运动,然后开启参数辨识。结果惯量估计值直接收敛到了错误值。后来一查,运动轨迹太单调,不满足「持续激励条件」。

26.4 一个简单的MRAC实现示例

光说不练假把式。我给大家看一个简化的模型参考自适应控制代码。这里假设被控对象是一个一阶系统,参考模型是一个理想的一阶系统。

// 简化版MRAC实现(伪代码风格)
// 被控对象: dx/dt = -a*x + b*u
// 参考模型: dxm/dt = -am*xm + bm*r
// 控制器:   u = θ1*r + θ2*x
// 自适应律: dθ1/dt = -γ*(x - xm)*r
//            dθ2/dt = -γ*(x - xm)*x

float x, xm;          // 实际输出和参考模型输出
float r;              // 参考输入
float theta1, theta2; // 自适应参数
float gamma = 0.5;    // 自适应增益
float am = 10.0;      // 参考模型参数
float bm = 10.0;

void MRAC_Update(float dt) {
    // 1. 更新参考模型
    float dxm = -am * xm + bm * r;
    xm += dxm * dt;
    
    // 2. 计算跟踪误差
    float e = x - xm;
    
    // 3. 更新自适应参数(带投影)
    float dtheta1 = -gamma * e * r;
    float dtheta2 = -gamma * e * x;
    
    // 参数投影:防止参数跑飞
    if (theta1 + dtheta1*dt < 0) dtheta1 = -theta1/dt;
    if (theta2 + dtheta2*dt < 0) dtheta2 = -theta2/dt;
    
    theta1 += dtheta1 * dt;
    theta2 += dtheta2 * dt;
    
    // 4. 计算控制量
    u = theta1 * r + theta2 * x;
}

这段代码看着简单,但有几个细节要注意:

  • 自适应增益γ不能太大,否则系统会震荡。我一般从0.1开始试。
  • 参数投影是保命用的。没有它,参数可能收敛到负值,导致正反馈。
  • 参考模型的时间常数要比实际系统快,但也不能太快,否则控制器会「够不着」。

26.5 工程落地的几点忠告

最后,我结合自己的经验,给大家几条实在的建议:

  1. 别一上来就搞全自适应。先搞清楚你的负载变化有多大、变化有多快。如果变化很慢,用增益调度就够了。
  2. 参数辨识一定要加保护。我习惯在代码里写死参数的上限和下限。比如惯量估计值不能小于空载惯量的50%,不能大于满载惯量的150%。
  3. 自适应和鲁棒性是一体两面。自适应能力越强,鲁棒性往往越差。你需要在这两者之间找平衡。
  4. 仿真跑通了再上真机。这个不用我多说吧?我在仿真里踩过的坑,比真机多十倍。
  5. 别忘了「退出机制」。如果系统检测到参数估计异常(比如方差过大),应该自动切回固定参数模式,并报警。

总结一下:自适应控制不是银弹,但它确实是处理负载变化、参数漂移等问题的利器。核心在于三点——参数在线辨识要准、自适应律设计要稳、鲁棒性保护要全。掌握了这三点,你就能让系统「自己照顾自己」了。

嗯,这一章就到这里。下一章我们聊聊更进阶的内容——迭代学习控制,专门对付重复性运动中的周期性误差。到时候见。