简介:模型预测控制(MPC)算法实现资料包,面向自动控制、机器人、过程控制等领域的学生与工程师,解决从理论推导到MATLAB仿真验证的落地问题。压缩包内共2个文件,一个MPC.m源码文件,一个MPC算法实现.docx说明文档,总计约156KB,轻量便于快速上手。已有2201人学习/下载,是广受关注的入门实战实例。文档系统讲解MPC的核心概念,包括系统建模、预测与控制视界、约束条件与性能指标设置,并梳理从构建优化问题到实时控制的完整实现步骤;配套MATLAB代码则覆盖传递函数模型构建、优化问题求解与控制序列应用的关键环节,与文档案例相互印证。读者可结合文档逐行调试,灵活调整模型参数、预测步长与约束范围,并借助仿真结果理解MPC的滚动优化机制,为后续自定义控制系统开发打下基础;整体结构紧凑,既适合系统学习,也可作为课题设计的参考实现。
1. 模型预测控制算法:从一个可跑的 MATLAB 包里看 MPC 的实用价值
模型预测控制算法这几年几乎成了先进控制的代名词,但很多人对着教材啃完原理,打开 MATLAB 还是一头雾水。这份资源不是那种只放几个 m 文件让你自己猜的散包,它把预测传递函数建模、控制器参数整定、闭环仿真串成了完整链路,照着改就能看到控制效果变化。如果你正在做温度、液位、流量这类单回路对象的控制器设计,或者刚接手一个需要从 PID 升级到 MPC 的项目,这份东西能帮你把理论快速落成能跑的仿真程序,避免从零写代码时才意识到约束处理和预测时域选择有多容易翻车。
2. MPC 核心选型与建模:为什么用传递函数做预测模型
2.1 预测模型选型:传递函数比状态空间更适合单回路工程
模型预测控制里最容易被低估的一步是预测模型怎么选型。很多新手一上来就搭状态空间方程,因为有现成的工具函数可以用,但对于温度、液位、流量这类工业上最常见的单输入单输出对象,预测传递函数模型往往是更务实的选择。一阶惯性加纯滞后(FOPDT)结构,也就是 $G(s)=\frac{K}{Ts+1}e^{-\tau s}$,是绝大多数过程控制对象的合理近似,参数 K、T、τ 物理意义清楚,通过一次阶跃响应实验就能辨识出来,而这正是这份资源里“预测传递函数”部分的核心价值。
用传递函数做预测模型有很实在的理由:一是辨识成本低,现场做一次阶跃测试就能拿到三个参数;二是模型结构简单,数值上不容易出现状态空间模型因矩阵病态导致的预测发散;三是后续做模型失配分析时,直接改 K 或 T 就能模拟参数漂移,而不需要重新推导矩阵。状态空间模型当然有它的优势,多变量耦合系统、串级回路、带不可测扰动时它更强,但那是 MIMO 场景才需要的东西,不是这份资源的主战场。
2.2 滚动优化与反馈校正:MPC 相对 PID 的底层逻辑
一旦预测模型确定,MPC 的控制逻辑就围绕着两个核心动作转:滚动优化和反馈校正。滚动优化是每个采样周期重新求解一个有限时域的最优控制问题,目标函数一般写成误差平方加权加上控制增量加权,也就是 $J=\sum_{i=1}^{P} w_y (y_{r,i}-y_{p,i})^2 + \sum_{i=1}^{M} w_{\Delta u} \Delta u_i^2$,其中 P 是预测时域,M 是控制时域。每一拍算出一串未来控制量,但只执行第一个,下一拍重新计算,这个“只取第一步”的机制才是 MPC 应对模型失配和扰动的关键。
反馈校正解决的是模型不完美的问题。预测模型算出来的输出和实际对象输出必然有偏差,MPC 的做法是用当前的实测输出减去预测输出得到误差项,再去修正后续预测。这份资源的程序里,反馈校正是接在实际对象输出和预测模型输出之间的一环。如果你跑过仿真就会发现,把这一环节去掉后系统还能稳,但抗干扰能力明显下降,阶跃扰动后恢复时间会拉长很多,这就是反馈校正存在的必要性。
注意:滚动优化和反馈校正是 MPC 不可拆分的上下半场,仅做优化不做校正的 MPC 本质上就是个高质量前馈控制器,遇到模型失配很容易翻车。
2.3 预测模型离散化:采样时间和离散方法的选择
传递函数建模之后,下一步是把它离散化,因为 MPC 的在线优化是在离散时间上求解的。MATLAB 里这一步用 c2d 函数完成,离散方法通常选零阶保持器,也就是 zoh,它对应的是实际数字控制器“保持输出直到下一拍”的行为。采样时间 Ts 的选择直接影响整个 MPC 的表现,这一点很容易成为软件包实际运行时的瓶颈,我会在第四章展开讲。
离散化得到的预测模型一般会转成状态空间形式存入 MPC 控制器对象里,这里有个工程习惯:保留传递函数作为“原型模型”,因为后续模型失配测试、参数灵敏度分析全靠它。
% 建立被控对象连续传递函数:增益K=2,时间常数T=5,纯滞后tau=2秒 K = 2; T = 5; tau = 2; G_cont = tf(K, [T 1]); % 一阶惯性 G_cont.InputDelay = tau; % 附加纯滞后 % 离散化:采样周期1秒,零阶保持器 Ts = 1; G_disc = c2d(G_cont, Ts, 'zoh'); % 查看离散模型 disp(G_disc);这段代码有三个参数值得注意。K 和 T 决定动态响应的速度和稳态增益,InputDelay 设置的纯滞后决定了预测补偿的难度,是控制器性能和稳定性的最大约束来源。Ts 是这里最需要谨慎选择的参数,后面会有专项避坑。
3. MATLAB 实例拆解:从预测传递函数到闭环仿真
3.1 控制器对象搭建:mpc 函数与三要素设置
拿到离散模型后,构建 MPC 控制器对象用 mpc 函数一步完成。这一步有三个隐藏的“雷”:一是初始模型必须是离散的,传连续传递函数进去会报模型转换警告甚至直接错误;二是 mpc 函数会自动把输入输出变量名命名为 MV 和 MO,但这些不会显式显示,别去找;三是创建对象后默认的预测时域是 10,控制时域是 2,权重默认各为 1,这些默认值只对特定对象有效,实际项目里都要改。
设置控制器参数我用 set 方法而不是直接赋值,因为 MPC 对象是封装类,直接 obj.PredictionHorizon = 10 这种方式在部分版本里不生效,set 方法则各版本兼容。下面这段代码展示的是完整控制器搭建流程。
% 创建基于离散模型的 MPC 控制器 MPCobj = mpc(G_disc, Ts); % 设置预测时域与控制时域 MPCobj.PredictionHorizon = 10; % 预测未来10步 MPCobj.ControlHorizon = 2; % 控制自由度:2步 % 设置被控量与操纵量约束 MPCobj.ManipulatedVariables(1).Min = -5; MPCobj.ManipulatedVariables(1).Max = 5; MPCobj.ManipulatedVariables(1).RateMin = -1; MPCobj.ManipulatedVariables(1).RateMax = 1; MPCobj.OutputVariables(1).Min = -10; MPCobj.OutputVariables(1).Max = 10; % 设置权重:输出误差权重1,控制增量权重0.1 MPCobj.Weights.Output = 1; MPCobj.Weights.MVRate = 0.1; % 查看控制器参数汇总 get(MPCobj);PredictionHorizon 和 ControlHorizon 是一对需要相互配合的参数。预测时域 P 要覆盖对象从当前状态过渡到稳态的主要部分,一般取开环响应上升时间的 1.2 到 1.5 倍;控制时域 M 则对计算量影响很大,每增加一步,优化问题的决策变量就多一个,但控制精度的提升不是线性的。我通常先定 P,再试着把 M 从 1 加到 3,观察性能变化,如果提升不明显就维持小的 M,这样还能减少在线计算压力。
3.2 闭环仿真:sim 函数与 mpcmove 手动循环
仿真有两种常见方式。一是直接用 sim 函数,它能快速得到闭环响应曲线,适合验证参数;二是用 mpcmove 手动写仿真循环,这更接近 MPC 实际运行机制,因为可以看到每一拍控制量怎么被算出来、约束怎么被激活。第二种方式在调试阶段价值更大,因为可以把每拍的预测误差和控制增量打印出来。
% 手动 MPC 闭环仿真 rng(0); % 固定随机种子 simSteps = 60; % 仿真60步 ref = ones(simSteps, 1) * 2; % 给定值:第1步起跳至2 % 初始化输出与状态 x0 = zeros(size(G_disc)); y = zeros(simSteps, 1); u = zeros(simSteps, 1); % 使用当前 MPC 对象的状态 state = mpcstate(MPCobj); for k = 1:simSteps % 第k步:根据当前输出和给定值计算控制量 [u(k), info] = mpcmove(MPCobj, state, y(max(k-1,1)), ref(k), []); % 被控对象实际响应:使用离散模型递推 y(k) = 2 / 5 * (2 - 0) + 0; % 示例逻辑,实际用 lsim 或 state-space 递推 % 这里真实工程中应调用对象模型的差分方程或 lsim end % 绘图 figure; subplot(2,1,1); plot(0:simSteps-1, y, 'b', 'LineWidth', 1.5); grid on; ylabel('输出'); title('MPC 闭环响应'); subplot(2,1,2); plot(0:simSteps-1, u, 'r', 'LineWidth', 1.5); grid on; ylabel('控制量'); xlabel('时间/采样周期');这段代码中间有一行我故意留了一个简化的替代逻辑,因为 MIMO 或黑箱对象在手动递推时容易出错。我的习惯是仿真循环里直接用 lsim 或 dlsim 更新对象输出,不要自己手写差分方程,尤其是对象带纯滞后时,手写递推几乎必然出现索引错位。
mpcmove 的输入参数里,当前实测输出和给定值是一对需要对齐的量:给定值 ref(k) 是当前拍的目标,实测输出 y(max(k-1,1)) 是上一拍的反馈值。很多初学仿真跑出来曲线乱跳,不是控制器参数问题,而是把实测输出传成了未来值,导致控制器在“追逐”还没有发生的误差。
3.3 结果可视化与性能评估指标
仿真跑完,直接用 plot 看响应曲线只是第一步。更有效的评估是计算上升时间、超调量、调节时间和控制量变化总量。我一般会用一段脚本来做量化评估,这样不同参数组合之间才能公平对比,而不是靠眼睛看曲线高度。
% 性能指标计算 overshoot = max(y) - ref(1); rise_time = find(y >= ref(1)*0.9, 1, 'first') * Ts; settle_idx = find(abs(y - ref(1)) > 0.05 * ref(1), 1, 'last'); if isempty(settle_idx), settle_time = NaN; else, settle_time = (settle_idx) * Ts; end mv_total = sum(abs(diff(u))); fprintf('超调量: %.2f\n', overshoot); fprintf('上升时间: %.2f s\n', rise_time); fprintf('调节时间: %.2f s\n', settle_time); fprintf('控制量总变化: %.2f\n', mv_total);上面这段代码是资源包里最有价值的工具脚本之一。超调量和控制量总变化往往是一对矛盾,比如调高输出权重能让输出更快跟上去,但控制量总变化也跟着涨,做工程时要让指标说话而不是凭感觉。
4. 参数调试与避坑:调参顺序与四个典型翻车现场
4.1 调参顺序:采样时间、预测时域、控制时域、权重矩阵
MPC 调试最忌讳想到哪个参数就改哪个。我推荐的顺序是:先定采样时间 Ts,再定预测时域 P,然后定控制时域 M,最后调权重。这个顺序的内在逻辑是:Ts 决定整个系统的可用频带,P 决定预测的视野范围,M 决定自由度分配,权重是精细调节。
采样时间 Ts 的选择依据是对象开环响应时间,一般取纯滞后时间的五分之一到十分之一,或者时间常数 T 的十分之一到二十分之一。Ts 取大了,离散模型丢失动态信息,控制器动作粗放,甚至闭环不稳定;Ts 取小了,仿真步数剧增,计算量上升,但性能提升有限。这个平衡点需要通过二分法找。
提示:如果你的仿真里控制器计算时间超过一个采样周期的 40%,优先考虑增大 Ts 或减小 P,而不是优化代码。MPC 在线求解是实时任务,算不完就是失效控制。
预测时域 P 的物理含义是控制器“向前看多少步”。P 太小,控制器视野不足,无法预见纯滞后的影响,控制效果像近视眼走路;P 太大,预测误差累加,远处的预测值已经没有意义,反而拖慢求解。一般做法是把 P 设为对象从阶跃响应开始到进入稳态所需步数的 1.2 倍左右。控制时域 M 设为 2 到 5 即可满足大多数单回路需求,尽量不要超过 5。
权重矩阵的调试逻辑稍后单独说,因为这里有一个教科书没写清楚的点:输出权重和输入约束之间存在耦合关系,不是独立的。
4.2 常见问题一:闭环仿真发散
现象:仿真的前几步输出还算正常,从某一步开始大幅振荡,最后直接发散到无穷大。
原因:最常见的原因是采样时间 Ts 设置过大,导致离散模型丢掉了被控对象的高频特性。另一个可能是预测时域 P 太小,控制器“看”不到纯滞后之后的响应,导致每一拍都在过度控制。
解决:先把 Ts 缩小为原来的二分之一,重新仿真看是否稳定;再逐步增大 P,直到响应曲线稳定。如果两者都已调整仍然发散,检查被控对象模型是否包含正零点或不稳定极点,这类对象需要先用内模控制或者其他前馈手段处理,MPC 不是万能的。
4.3 常见问题二:控制量动作剧烈甚至抖振
现象:输出曲线看起来跟住了给定值,但控制量来回大幅波动,像振荡。
原因:控制器试图用激烈的控制动作抵消每一个小的预测误差。这实质上是输出权重相对控制增量权重来说太高了,也就是 Weights.Output 远大于 Weights.MVRate。
解决:增大控制增量权重,比如从 0.1 调到 1,或者同时限制控制量的变化速率,也就是前面代码里的 RateMin 和 RateMax。另外,减小控制时域 M 也能抑制抖振,因为控制自由度减少,控制器无法做出高频决策。
4.4 常见问题三:约束形同虚设
现象:在仿真里明明设置了操纵量 Min 和 Max,但控制量输出一旦到达边界就能看到明显的超限。
原因:这里要分两种情况,一是控制器对象里设置的约束值根本没有生效,原因是把 mpc 对象的约束值改了但忘了调用 get/set 刷新;二是对象的线性模型权重让约束求解器认为超出约束造成的代价低于偏离目标造成的代价。
解决:第一种情况用 get(MPCobj) 检查当前生效的参数;第二种情况,在设置约束后配合增大控制增量权重,让控制器“宁可慢一点,不想越界”。实际做项目时,约束边界超出一点点有时是可以接受的,但必须是你主动决策,而不是控制器求解器放水。
4.5 常见问题四:给定值变化后输出响应迟钝
现象:给定值阶跃变化后,输出经过很长延迟才开始跟踪,而且跟踪斜率很慢。
原因:预测时域 P 设置的覆盖范围不够,导致控制器在纯滞后阶段没有提前规划控制动作。本质上,控制器“预见”到给定值变化的能力来自于参考轨迹在预测时域内的展开。
解决:把 P 增大到能覆盖至少一个完整的滞后加上升过程,同时检查给定值变化前控制器是否处于大的稳态控制量附近,如果是,调大输出权重,让控制器更有动力去跟目标。
5. 验证方法与进阶技巧:从仿真闭环到抗扰动与模型失配评估
5.1 抗扰动验证方法
基础的阶跃响应测试通过之后,下一步要做抗扰动测试。做法是在仿真循环中第 30 步给被控对象输出端添加一个幅值为 0.5 的阶跃扰动,观察输出能否在若干个采样周期内回到给定值附近。这里关键的评估指标是最大偏差和恢复时间,最大偏差越小、恢复时间越短,说明反馈校正环节越有效。
% 在第30步加入输出阶跃扰动 disturbance = zeros(simSteps, 1); disturbance(30:end) = 0.5; for k = 1:simSteps y_measure = y(k) + disturbance(k); % 实际测量值包含扰动 [u(k)] = mpcmove(MPCobj, state, y_measure, ref(k), []); end这种验证在资源包的原程序里是缺失的,需要自己补充。加扰动后如果恢复时间超过原来的 1.5 倍,我的经验是先检查反馈校正相关的权重设置,再看预测模型是否和对象模型存在明显偏差。
5.2 模型失配测试:最真实的一课
把 MPC 控制器里的模型参数 K 从 2 改成 1.6,或者把纯滞后从 2 秒改成 3 秒,再看看闭环响应会怎样,这是检验 MPC 鲁棒性的直接方法。
G_mismatch = tf(1.6, [5 1]); G_mismatch.InputDelay = 3; G_mismatch_d = c2d(G_mismatch, Ts, 'zoh');运行后通常会发现,小幅失配时系统仍能稳定,但调节时间变长;纯滞后被低估时最容易出现稳定性问题,因为预测模型认为对象响应更快,控制器会提前动作,实际对象滞后导致过度驱动。这也解释了为什么做 MPC 项目时要把滞后时间的辨识放得保守一点,宁可把滞后时间估算长一些,也不要低估它。
5.3 进阶:把 MPC 导出或固化到 Simulink
调试完成后如果要用到实时环境,代码生成的路径价值最大。用 Simulink 的 MPC Controller 模块可以省掉手写调用接口的成本,配置好之后用 Embedded Coder 生成 C 代码,不太建议把整个 mpc 对象直接导出到嵌入式环境,因为求解器依赖不是总被支持。
我给一个相对通用的做法:仿真在 MATLAB 里完成,然后通过 getCodegenData 和 buildMEX 或 Simulink 代码生成导出,具体 API 随版本差异会有出入,但思路是控制器逻辑和对象逻辑分离,控制器对象生成独立代码块,对象模型留在仿真环境里做测试桩。如果遇到求解器不支持的情况,换用 mpcactive 或自己实现 QP 求解算法,这是另一个深度话题,以上思路足够了。
5.4 我的收尾习惯
每次调完 MPC 参数,我都会强制走一遍这样的流程:先扫描 Ts 从 0.2 到 2 的稳定范围,再固定 Ts 做 P 和 M 的网格扫描,最后只微调权重。这套流程帮我避开了多数仿真发散和现场抖振的尴尬。这件事看起来繁琐,但在 MPC 上它是少有的“后悔药”,因为很多问题在参数扫描阶段就会露出原形,不要等到仿真看起来差不多就收工,模型失配测试少一步,到了现场就得多熬几个夜。希望这份资源能让你少走一点我走过的弯路。
本文还有配套的精品资源,点击获取