第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 工程落地的几点忠告
最后,我结合自己的经验,给大家几条实在的建议:
- 别一上来就搞全自适应。先搞清楚你的负载变化有多大、变化有多快。如果变化很慢,用增益调度就够了。
- 参数辨识一定要加保护。我习惯在代码里写死参数的上限和下限。比如惯量估计值不能小于空载惯量的50%,不能大于满载惯量的150%。
- 自适应和鲁棒性是一体两面。自适应能力越强,鲁棒性往往越差。你需要在这两者之间找平衡。
- 仿真跑通了再上真机。这个不用我多说吧?我在仿真里踩过的坑,比真机多十倍。
- 别忘了「退出机制」。如果系统检测到参数估计异常(比如方差过大),应该自动切回固定参数模式,并报警。
总结一下:自适应控制不是银弹,但它确实是处理负载变化、参数漂移等问题的利器。核心在于三点——参数在线辨识要准、自适应律设计要稳、鲁棒性保护要全。掌握了这三点,你就能让系统「自己照顾自己」了。
嗯,这一章就到这里。下一章我们聊聊更进阶的内容——迭代学习控制,专门对付重复性运动中的周期性误差。到时候见。