去年给某自动化产线做设备改造时,遇到个棘手问题——传统PID控制的机械臂在跟踪变曲率轨迹时总是超调。当时尝试了各种参数整定方法,最后发现模型预测控制(MPC)才是解决这类轨迹跟踪问题的终极方案。今天要分享的这个二自由度MPC控制器,正是基于那次实战经验提炼出的精华版。
这个控制器的核心优势在于"双自由度"设计:一个自由度处理轨迹跟踪精度,另一个自由度应对路况扰动。就像赛车手既要注意行车线又要应对路面颠簸,我们的控制器也能在保持轨迹精度的同时,自动补偿外部干扰。最妙的是支持自定义参考轨迹,无论是S形弯道还是急转弯,只要给出数学描述,控制器就能自动生成最优控制量。
MPC的核心思想可以用"三步走"来理解:
在Matlab里用mpcmove函数实现时,计算耗时主要消耗在QP(二次规划)求解上。实测表明,预测时域(Prediction Horizon)设为20步时,单次求解约需2ms(i7-11800H处理器)。
传统MPC只有一个优化目标——跟踪误差最小化。我们的改进版增加了第二个优化维度:
matlab复制% 成本函数示例
J = 0.5*(x'-xref)'*Q*(x'-xref) + 0.5*u'*R*u + 0.5*(x'-xdist)'*S*(x'-xdist)
其中:
这种设计相当于给控制器装了两个"大脑":一个专注跟线,一个负责抗扰。在Simulink里测试时,对比单自由度MPC,在相同扰动下跟踪误差降低了63%。
以AGV小车为例,建立二自由度动力学模型:
matlab复制% 状态方程离散化
A = [1 Ts 0 0;
0 1 -Ts*Kf/m 0;
0 0 1 Ts;
0 0 -Ts*Kf/Iz 1];
B = [0 0;
Ts/m 0;
0 0;
0 Ts/Iz];
其中Kf是侧偏刚度,m为质量,Iz为转动惯量。注意采样时间Ts的选择很关键——太大会丢失动态细节,太小会增加计算负担。经验值是取系统响应时间的1/10~1/5。
权重矩阵Q、R、S的设定直接决定控制效果。经过多次实测,总结出这个调参口诀:
Q对角线元素 = [位置权重, 速度权重, 角度权重, 角速权重]
建议初始值设为[10, 1, 5, 0.5]
R对应控制量权重,通常取[0.1, 0.1]
S取Q的1/5~1/3
调试时建议先用正弦轨迹测试,观察超调量和收敛速度。有个小技巧:在Matlab命令行实时修改变量值时,用mpcobj.Weights.OutputVariables = [10,1,5,0.5]可以即时生效。
参考轨迹生成我推荐两种方式:
matlab复制% 8字形轨迹
t = 0:0.1:10;
xref = [3*sin(t/2); 2*cos(t)]';
matlab复制waypoints = [0 0; 1 2; 3 3; 5 1];
t = linspace(0,1,100);
xref = spline(linspace(0,1,size(waypoints,1)), waypoints', t)';
在Simulink中加入脉冲扰动测试时,注意三个关键点:
实测数据表明,当侧向风扰动力达到车重的15%时,我们的控制器仍能保持轨迹偏差<5cm(车速2m/s情况下)。
在xPC Target实时系统测试时,发现两个性能瓶颈:
优化方案:
mpcActiveSetSolver替代默认求解器症状:控制量持续正负跳变,轨迹偏差越来越大
可能原因:
解决方案:
matlab复制% 诊断脚本示例
figure;
subplot(2,1,1); plot(u_hist); title('控制量');
subplot(2,1,2); plot(xerr_hist); title('跟踪误差');
若发现控制量与误差同频振荡,应优先检查R矩阵权重是否过小。
症状:最终轨迹存在固定偏移
排查步骤:
有个取巧的办法:在成本函数中加入终端代价权重:
matlab复制mpcobj.PredictionHorizon = 20;
mpcobj.ControlHorizon = 3;
mpcobj.Weights.OutputVariables = [10 1 5 0.5];
mpcobj.Weights.ManipulatedVariablesRate = 0.1;
mpcobj.Weights.Terminal = 5; % 新增终端权重
对于复杂路况,可以预存多组MPC参数:
matlab复制% 根据路况切换控制器
if road_type == 1
load('mpc_highway.mat','mpcobj');
elseif road_type == 2
load('mpc_urban.mat','mpcobj');
end
实测在高速公路和城市道路场景切换时,过渡时间可控制在0.8秒内。
更高级的做法是在线更新模型参数:
matlab复制function mpcobj = updateModel(mpcobj, new_params)
mpcobj.Model.Plant.A(3,3) = new_params(1);
mpcobj.Model.Plant.B(4,2) = new_params(2);
mpcobj.Model.Nominal.U = new_params(3:4);
end
记得每次更新后要调用mpcobj=mpc(mpcobj)重新验证模型。我在某物流AGV项目上应用这种方法后,不同载重下的跟踪误差标准差降低了58%。