☰
Simulink迭代学习控制ILC:重复轨迹跟踪参数整定与收敛排查
2026/11/2 23:01:31 网站建设 项目流程

简介:ILC_Simulink.rar 是一份面向控制理论学习者、高校学生与工程实践者的 Simulink 迭代学习控制仿真资料,围绕周期性或重复性任务中的无静差跟踪问题,提供可直接运行的模型与配套数据。压缩包共 14 个文件,以 mat 数据、slx 仿真模型、m 脚本和 xml 配置为主,另有少量缓存与自动保存文件,整体约 66KB,体量轻便,便于在 MATLAB 环境中加载、复现和二次修改。资源核心包含一阶或二阶传递函数被控对象与 ILC 算法模块,覆盖误差计算、反馈校正、控制输入更新及迭代停止准则等环节,并借助 Stateflow 模型展示多周期迭代学习过程,可观察输出曲线并调整增益、限幅等参数并调试以优化跟踪性能。已有 3983 人学习,适合希望理解 ILC 基本原理、验证控制策略或搭建教学演示的读者参考。

1. 同一段轨迹跑一百遍:ILC 在 Simulink 里补的是哪一块

机械臂每两秒重复一次取放动作,第一遍末端超调 8%,跑到第一百遍还是 8%——PID 调到参数极限也就这样,因为它不“记住”上一遍错在哪个时刻。ILC(迭代学习控制)补的正是这一块:把每次迭代的逐点误差按时间索引存下来,下一遍在同样的时刻提前补偿,误差随迭代次数 k 单调压下去。Simulink ILC 这套仿真资源做的事,就是把这套“记错—改控制—再跑一遍”的闭环搭成能直接运行的模型,让人在不碰硬件的前提下先把学习律、学习增益、滤波器截止频率试出来,看清它到底收不收敛、收敛到什么量级、第几遍之后不再有收益。它适合做重复轨迹跟踪的人:机械臂、数控进给轴、批处理产线的往复运动、疲劳测试台架。下面按“模型怎么搭、参数怎么定、跑不出来怎么看”的顺序拆开讲。

2. 学习律先立住:P 型、PID 型与三条 Simulink 实现路线

动手搭模块之前,得先知道自己要搭的是哪个式子。ILC 的模型再花哨,核心永远是一条“上一遍的控制 + 增益 × 上一遍的误差 = 这一遍的控制”的递推。这条递推写成什么样,直接决定了 Simulink 里是用一个 MATLAB Function 块搞定,还是必须回到 MATLAB 外层写循环。

2.1 三种学习律的数学形式与收敛条件

最基础的是 P 型(也叫一阶学习律):

u_{k+1}(t) = u_k(t) + Γ · e_k(t) e_k(t) = y_d(t) - y_k(t)

其中 k 是迭代批次,t 是批次内的时间,Γ 是学习增益。它对线性时不变对象能收敛的充要条件,落在频域上就是|1 - Γ·G(jω)| < 1对所有频率成立。这个式子的几何含义很直白:把 Γ·G(jω) 画在复平面上,它必须整个落在“以 1 为圆心、半径 1”的圆盘里。低频段对象增益大、相位滞后小,Γ 取小一点很容易满足;高频段相位往 -180° 靠,Γ·G 就绕到圆盘外面去了,|1-ΓG|大于 1,那一小段频率的误差会一代代被放大。这就是为什么纯 P 型 ILC 在论文里能证收敛,在实际模型上一跑就抖——不是推导错了,是高频没管。

D 型把误差换成误差的导数:u_{k+1} = u_k + Γ·ė_k。它对相对阶为 1 的对象收敛更快,但对测量噪声极其敏感,误差上叠一点白噪声,微分之后就是一片尖刺,学进去的全是噪声。PID 型是三者的加权组合,积分项负责消化每一次都重复出现的常值扰动(比如恒定的摩擦力矩),微分项负责相位超前,比例项负责主收敛速度——代价是三个增益互相耦合,整定比单一 Γ 麻烦得多。

学习律适用对象关键参数常见失败方式
P 型相对阶 1~2、带宽内相位滞后小Γ高频段发散,误差先降后升
D 型相对阶 1、误差信号干净Γ噪声被微分放大,控制量毛刺
PID 型有重复性常值扰动Kp、Ki、Kd三项互相打架,调一个坏两个
带 Q 滤波的 P 型模型有未建模高频动态Γ、Q 截止频率Q 直流增益不为 1,稳态误差消不掉

表格里最后一行值得单独说一句:Q 滤波器的直流增益必须严格等于 1,否则你等于在每一遍都给控制量打了个折扣,误差会收敛到一个非零的平台上下不来。这是我在一个位移跟踪项目上折腾了两天才发现的,当时一直以为是学习增益不够大。

2.2 路线 A:MATLAB 外层循环驱动 sim(),调试首选

把 ILC 放在 Simulink 模型内部,还是放在 MATLAB 脚本里,是第一个要做的选型。模型包里通常两种都有,但排错阶段我强烈建议先用外层循环这条路:每一次迭代都是一次干净的sim()调用,工作区的 u 序列、误差序列、迭代次数全都能直接看,哪一遍出问题就单步查哪一遍。

% ilc_run_p.m —— 路线 A:MATLAB 外层迭代 clear; clc; model = 'ilc_demo'; % 模型名,与 .slx 同名 Ts = 1e-3; % 采样周期,必须和模型固定步长一致 T = 1.0; % 单次任务时长 N = round(T/Ts) + 1; % 采样点数,注意 +1 Gamma = 0.35; % P 型学习增益,先从频域条件筛出来的 1/3 起 Kmax = 30; % 最大迭代次数 u = zeros(N,1); % 第 0 遍控制序列,全零起步 errNorm = zeros(Kmax,1); % 每遍误差的 2-范数 for k = 1:Kmax assignin('base', 'u_k', u); % 投给 Simulink 的输入序列 out = sim(model, 'StopTime', num2str(T)); y = out.logsout.get('y').Values.Data; % 取记录的输出 yd = out.logsout.get('y_d').Values.Data; % 取参考轨迹 e = yd(:) - y(:); % 逐点误差,强制列向量 errNorm(k) = norm(e); u = u + Gamma * e; % 学习律更新 u = min(max(u, -10), 10); % 执行器限幅,别省这一步 end semilogy(1:Kmax, errNorm, 'o-'); grid on; xlabel('迭代次数 k'); ylabel('||e_k||_2');

这段脚本有几个地方是刻意写死的。N = round(T/Ts) + 1里的 +1 不是笔误,采样点包含 t=0 和 t=T 两端,少算一个点,误差序列和控制序列就会差一位,后面所有索引全歪。e = yd(:) - y(:)强制转列向量,是因为 Simulink 记录出来的数据方向取决于模块设置,某一次运行是行向量、下一次是列向量,u + Gamma*e会静默地广播成矩阵而不是报错,这种错最难查。限幅那一行看起来多余,但如果被控对象有行程限制而模型里没建饱和模块,学出来的 u 会一路涨到几百,仿真里看着收敛得漂亮,换到实机上第一遍就撞限位。

提示:sim的返回值取信号有两种常见写法,out.logsout.get('y').Values.Data和out.y,取决于模型里信号是走 Signal Logging 还是 Outport。拿不准就在命令行敲一下out,看它下面挂了什么。

这条路线的代价是慢。每次sim()都要重新编译一次模型(开了加速模式会好一些),三十次迭代就是三十次启动开销。做参数扫描的时候,把set_param(model,'SimulationMode','accelerator')加上,能省掉一大半等待时间。

2.3 路线 B:MATLAB Function 块用 persistent 记住上一遍

真正要嵌到控制器里、或者要跑很长的迭代时,得把学习律塞进模型内部。Simulink 里最省事的做法是一个 MATLAB Function 块,用persistent变量存住整个控制序列,按当前时间算出索引,读出来用、写回去改。

function u = ilc_block(t, e, N, Ts, Gamma) %#codegen % 每个采样步调用一次;persistent 保存跨迭代的控制序列 persistent uPrev k if isempty(uPrev) uPrev = zeros(N,1); % 第 1 遍:全零控制起步 k = 1; end idx = round(t/Ts) + 1; % 时间 -> 采样索引,+1 对齐 MATLAB 的 1 起始 if idx > N idx = N; % 浮点误差导致末尾越界时兜底 end u = uPrev(idx); % 本遍输出:读上一遍学到的值 uPrev(idx) = uPrev(idx) + Gamma * e; % 用当前误差更新"下一遍"的同一时刻 if idx == N k = k + 1; % 迭代计数,只用于观测,不参与控制 end

这个块的关键在于“先读后写”。u取的是uPrev(idx)更新之前的值,所以当前这一遍用的永远是上一遍学到的结果;写回操作只影响下一遍同一时刻的输出。索引 idx 随时间单调递增,不会出现同一遍里读写同一位置两次的情况。round(t/Ts)而不是floor或者直接除,是为了抵消仿真器在浮点累加步长时产生的微小漂移,否则跑到第 900 步左右 idx 会突然跳一位,误差曲线上出现一个孤立的尖峰。最后那个idx > N的兜底判断,纯粹是血泪经验:仿真时长设成 1.0 而 Ts 设成 1e-3 时,最后一步的 t 可能是 1.0000000001,round回来就是 1002,数组越界直接报错终止。

两种路线各有各的适用面:外层循环适合调参和验证,模型内嵌适合长时间跑批和代码生成。我一般的做法是先用路线 A 把 Γ、Q 和采样点数全定下来,确认误差曲线形状对了,再照着搬到路线 B 里,最后比对两条路线跑出来的误差范数序列是否重合——重合了说明内嵌实现没有索引错位,不重合就说明某个环节的时序对不上。

3. 参数整定:采样点数、学习增益与 Q 滤波器的取值依据

ILC 的参数比 PID 少,但每一个都更容易踩坑,因为它多了一个维度——迭代。PID 调错了顶多是这一遍响应不好,ILC 调错了是三十遍之后控制量炸到天上。这一章把四个必须定死的参数按依赖顺序捋一遍。

3.1 时间轴对齐:Ts、T、N 与求解器设置

所有参数里最先要钉死的是采样周期。它同时决定了三件事:模型固定步长、控制序列的数组长度、以及误差更新时的索引粒度。三者只要有一个不一致,误差曲线就会出现那种“看起来在收敛但永远差一截”的诡异形态。

参数示例取值由什么决定取错的典型后果
Ts1e-3 s参考轨迹最高频率成分的 10 倍以上索引与真实时间错位,末端误差降不下去
T1.0 s单次任务的物理时长与参考轨迹长度不匹配,末端突变
N1001N = T/Ts + 1少一个点,最后一个采样永远差一截
SolverFixed-step / ode4连续对象 + 离散控制器的常规组合变步长导致每遍步数不同,误差序列对不上
Γ0.2 ~ 0.6频域条件 sup|1-ΓG| < 0.85大了高频发散,小了看不出收敛

求解器这一项值得展开。默认的变步长求解器在每一遍仿真里走的步数可能不同,尤其当控制量 u 逐代变化、对象响应变陡时,步数会跟着变。误差序列是按采样点存的,步数一变,第 800 个采样点对应的物理时刻就漂了,迭代学习立刻失效——表现出来是误差降到第 5 遍左右开始原地打转,怎么调 Γ 都没用。所以只要用 ILC,就把求解器锁成固定步长。

% 固定步长求解器配置:迭代学习的前提条件 set_param(model, 'SolverType', 'Fixed-step'); set_param(model, 'Solver', 'ode4'); % 连续对象用 ode4,纯离散用 FixedStepDiscrete set_param(model, 'FixedStep', num2str(Ts)); % 必须等于控制周期 set_param(model, 'StopTime', num2str(T)); set_param(model, 'ReturnWorkspaceOutputs', 'on'); set_param(model, 'SignalLogging', 'on');

FixedStep必须严格等于Ts,不能设成Ts/2或者auto。设成 auto 的时候 Simulink 会按模型里最快的采样率自动选,一旦某个模块的采样率比你预期的快,索引就全乱了。SignalLogging打开是为了后面能取到logsout里的信号,不然out.logsout是空的,脚本第一行取数据就报错。

3.2 学习增益:先用频域条件筛一遍,再动手试

绝大多数人调 Γ 的方式是“从 0.1 开始试,抖了就减半”,试到第五次失去耐心。其实这个参数可以在仿真之前就框出一个量级:既然收敛条件是|1 - Γ·G(jω)| < 1,那把对象的频率响应算出来,扫一遍 Γ,看哪个区间能满足条件就行了。

% 用频域条件先筛 Gamma,避免盲目试凑 G = tf(1, [0.01 0.1 1]); % 被控对象,换成你自己的传递函数 w = logspace(-1, 3, 800); % 关心的频段 Gj = squeeze(freqresp(G, w)); % 频响,复数向量 GammaList = 0.05:0.05:1.5; supNorm = zeros(size(GammaList)); for i = 1:numel(GammaList) supNorm(i) = max(abs(1 - GammaList(i) * Gj)); % 全频段最大值 end idx = find(supNorm < 0.85, 1, 'last'); % 留 15% 裕度给模型误差 fprintf('满足条件的最大 Gamma ≈ %.2f,建议起调值 %.2f\n', ... GammaList(idx), GammaList(idx)/3);

这里 0.85 的门限不是随便定的。sup|1-ΓG|等于 0.99 时理论上也能收敛,但收敛速度慢到你在三十遍里看不出来,而且模型里任何一点未建模动态都会把它推到 1 以上。留 15% 裕度、再从筛出来的最大值的三分之一起调,是我试下来最省迭代次数的组合:起调值基本不会发散,往上加还有空间。反过来说,如果扫完发现没有任何 Γ 能让sup小于 1,说明这个对象在当前频段上相位滞后太大,纯 P 型压不住,得直接上 Q 滤波或者换 D 型。

3.3 Q 滤波器:截止频率与直流增益两个约束

Q 滤波器的作用是把学习律在高频段的增益压下去,形式是u_{k+1} = Q(z) · (u_k + Γ·e_k)。它本质上是一个低通,但和普通低通有两个区别:第一,直流增益必须严格等于 1;第二,如果做在外层循环里,可以用零相位滤波避免引入额外滞后。

% Q 滤波器设计:低通 + 直流增益强制为 1 fc = 20; % 截止频率 Hz,取参考轨迹带宽的 3~5 倍 Wn = fc * 2 * Ts; % 归一化到 Nyquist [b, a] = butter(2, Wn, 'low'); b = b * sum(a) / sum(b); % 关键:把 Q(1) 拉回 1 uNew = filtfilt(b, a, u + Gamma * e); % 零相位滤波,需要整段数据

b = b * sum(a) / sum(b)这一行是整个设计的核心。butter出来的系数,直流增益sum(b)/sum(a)通常略小于 1,误差在千分之几的量级,单看滤波器指标完全合格。但在 ILC 里这个千分之几会被迭代放大成一个固定的稳态偏差:每一遍控制量都被打一次折,学到最后 u 稳定在一个比正确值低一点点的水平,误差曲线收敛到某个非零值就再也不动了。我第一次遇到这个现象时以为是对象有死区,查了两天才发现是滤波器系数。

filtfilt做的是前向-反向两次滤波,相位完全抵消,代价是需要拿到整段数据、不能在线跑。所以它只能用在路线 A 的外层循环里。如果学习律是嵌在模型内部(路线 B),就得退而求其次用一个因果低通,此时相位滞后会吃掉一部分相位裕度,Γ 要相应再降一点。截止频率取参考轨迹带宽的 3~5 倍是个经验值:取太低跟踪变钝,阶跃附近学不上去;取太高等于没滤,高频该发散还是发散。判断方法很直接——把每一遍误差做 FFT,看高频段能量是不是在随迭代增长,涨了就说明截止频率压得不够。

4. 收敛判据:三条曲线读出“学进去了”还是“在抖”

仿真跑完三十遍,最危险的情况不是报错,是误差范数确实在降、曲线看着漂亮,但控制量已经在往限幅上顶,或者高频分量正在悄悄积累。光看一条||e_k||曲线不足以判断 ILC 是否真的收敛,至少要看三个量。

4.1 误差范数、逐点误差包络与控制量增量

第一条曲线是||e_k||_2随 k 的变化。健康形态是前几遍快速下降,之后进入缓慢下降的尾部,全程单调。如果出现“降—升—降”的锯齿,那是高频发散被低频收敛暂时掩盖了,不能算收敛。

第二条是逐点误差包络:把每个采样点上、所有迭代批次的 |e_k(t)| 取出来,画成一条随 t 变化的带子。收敛良好的时候这条带子在整个时间段上均匀变窄;如果只在 t 的前半段变窄、后半段纹丝不动,通常是 N 少算了一个点,或者参考轨迹末端有跳变。

第三条最容易被忽略但最有诊断价值:控制量增量||u_{k+1} - u_k||。ILC 收敛的严格定义是控制序列本身收敛,不是误差变小。误差变小有可能是因为对象被推到了某个碰巧对的位置,而控制增量趋近于零才说明学习过程真的停下来了。

% 三联图:判断收敛是否"真"收敛 figure('Position', [100 100 900 700]); subplot(3,1,1); semilogy(1:Kmax, errNorm, 'o-'); grid on; ylabel('||e_k||_2'); title('误差范数'); subplot(3,1,2); imagesc(1:Kmax, (0:N-1)*Ts, abs(errHistAll)'); % errHistAll: N×Kmax axis xy; colorbar; ylabel('t (s)'); title('逐点误差包络'); subplot(3,1,3); duNorm = vecnorm(diff(uHist, 1, 2)); % uHist: N×Kmax semilogy(2:Kmax, duNorm, 's-'); grid on; ylabel('||u_{k+1}-u_k||'); xlabel('迭代次数 k');

errHistAll和uHist需要在迭代循环里逐列存下来,内存开销是 N×Kmax 个 double,N 取一千、Kmax 取三十也就 240 KB,完全不用省。第三张图的判读标准是:它应该和第一张图同趋势下降,如果误差范数在降而控制增量不降,说明每一遍还在大幅修改控制量,只是恰好抵消了,这种状态再多跑几十遍就可能突然发散。

4.2 频域复核:把发散提前看出来

时域曲线只能告诉你“有没有问题”,频域能告诉你“问题在哪一段频率”。收敛末期把最后一遍的误差做一次 FFT,看能量分布,比盯着时域波形猜要快得多。

% 发散前兆排查:误差的高频成分是不是在长 E = fft(eLast); % eLast 是最后一遍的逐点误差 f = (0:N-1)' / (N*Ts); mag = abs(E) / N; half = 1:floor(N/2); plot(f(half), mag(half)); grid on; xlabel('频率 (Hz)'); ylabel('|E(f)|'); xlim([0 1/(2*Ts)]);

判读方式是这样:低频段(参考轨迹的主要频率成分附近)的能量应该在迭代过程中持续下降,高频段(接近 Nyquist 的那一段)的能量应该被 Q 滤波器压住、基本不动。如果出现低频降、高频涨的交叉形态,那就是典型的“学习增益相对高频偏大”,处理办法只有两个——降 Γ,或者把 Q 的截止频率往下压。这时候再去调 PID 或者改参考轨迹都是白费力气,方向错了。

还有一种情况是误差频谱整体右移,低频收敛得很好但中频段(参考轨迹的三到五次谐波位置)有一个鼓包。这通常是参考轨迹本身不够光滑,比如用分段线性拼出来的轨迹在拐点处一阶导不连续,D 型或者高阶学习律会把这个不连续当成需要学习的特征,反复在那里加大控制量。

4.3 停止条件与重复性校验

工程上不会无限迭代下去,需要一个停止条件。常见的三种:误差范数低于阈值、连续若干遍的相对改善小于 1%、迭代次数到上限。第一种最直观但阈值不好定,第二种更贴近实际——当(|e_k| - |e_{k+1}|)/|e_k| < 0.01连续出现三遍,说明继续跑收益已经很小了。

停止之后还有一步不能省:把同一组参数、同一个模型再完整跑三次,看三次的误差范数序列是否落在同一条曲线上。仿真里没有测量噪声、没有批次间差异,理论上三次应该完全重合。如果有明显分散,说明模型里某个环节带了随机性(比如某个模块里的白噪声源没关),或者迭代之间的状态复位不彻底——这两种情况都会让你在实机上得到完全不可复现的结果。

5. 避坑与排查:五类跑不出收敛的翻车现场

下面这五条都是我在不同项目上真实撞过的,按“现象—原因—解决”记下来,对着查能省掉大量时间。

5.1 三类“越跑越差”:增益、相位与饱和

第一类:误差前五遍降得很快,第六遍开始反弹,控制量出现高频毛刺。原因是学习增益在高频段不满足收敛条件。低频段|1-ΓG|远小于 1,收敛快;高频段相位滞后接近 -180°,ΓG绕到圆盘外,那一段频率的误差被逐代放大。等高频分量涨到和低频同量级,整体误差范数就反弹了。解决办法是先按 3.2 的脚本把 Γ 的上界扫出来,再从三分之一处起调;如果扫出来根本没有可行的 Γ,就加 Q 滤波器,把 20 Hz 以上的学习增益压下去。判断有没有效果,看 4.2 的误差频谱,高频段能量不再增长就对了。

第二类:误差降到某个值就完全不动,加大 Γ 也没用,反而开始抖。这是相位滞后型平台期,多发于相对阶为 2 的对象用纯 P 型学习律。P 型在每一遍只能修正幅值,修正不了相位,对象在某个频段的相位滞后超过 90° 之后,学习律的修正方向就偏了,越学越偏,最后停在一个平衡点上。解决方向有三个:降 Γ 换取收敛(代价是慢)、改用带微分项的 D 型或 PID 型补相位、或者直接上范数最优 ILC(第 6 章讲)。我一般先试第二个,加一个很小的 Kd(比 Kp 小一个量级)就能把平台期推下去不少。

第三类:误差范数一直在降,但降到最后还有一个明显的稳态偏差,怎么都消不掉。先查执行器限幅。如果模型里没有饱和模块而学出来的 u 已经超过了实机的行程或力矩上限,那仿真里的“收敛”是假的。加上饱和模块之后,误差往往会停在一个非零值上,这才是真实情况。可以接受就接受,不能接受就得改机构或者改轨迹,靠调参数是调不出来的。查完限幅再查 Q 滤波器的直流增益,就是 3.3 里那个sum(b)/sum(a)的问题。

5.2 两类“数据不对”:索引错位与状态残留

第四类:每一遍的第一个采样点误差都是一个孤立的大尖峰,其余点都正常。这是典型的索引 off-by-one。MATLAB 数组从 1 开始,而仿真时间从 0 开始,round(t/Ts)在 t=0 时返回 0,直接索引就报错,很多人为了不报错写成max(round(t/Ts),1),结果 t=0 和 t=Ts 两个时刻读到了同一个位置,第一点永远用的是错的初值。正确写法是round(t/Ts) + 1,然后在末尾加一个越界兜底。另一个变体是floor和round混用:更新时用floor、读取时用round,中间那一步的更新就永远读不到,误差曲线上表现为每 2 个点有一个没被学习。

第五类:明明改了参数重新跑,结果和上一次一模一样,或者第一遍就带着上一轮的误差。这是persistent变量残留。MATLAB Function 块里的persistent变量在整个 MATLAB 会话期间是活的,你重新点一次运行、甚至改了模型参数,它都还在,isempty(uPrev)判断为假,直接接着上一轮的控制序列往下跑。解决办法是在每次跑批之前清一次,clear ilc_block或者更彻底地clear functions。用外层循环路线(路线 A)时同样有这个坑——对象里如果有persistent状态或者 Integrator 模块没有设初值,sim()之间状态会继承,表现出来就是“每遍初始误差都一样大”,看着像学习律失效,其实是初始条件没有复位。

排查这一类问题的通用手法是:在循环里打印第一遍和最后一遍的u(1:5),如果第一遍的初值不是全零,那一定是状态残留,不用往下查了。

6. 升级到范数最优 ILC:卷积矩阵、权阵与一套复核习惯

P 型学习律的局限在于它对所有时刻用同一个 Γ,而实际上误差在不同时间段的重要程度、控制量在不同时刻的敏感程度都不一样。范数最优 ILC 把这个“一刀切”换成了一个带权的最小化问题:让下一遍的加权误差、控制能量、以及控制量的迭代变化三者之和最小。解得出来的更新律是u_{k+1} = u_k + L·e_k,其中L = (GᵀQG + R + S)⁻¹ GᵀQ,G 是系统的卷积矩阵。

% 范数最优 ILC:用脉冲响应搭 N×N 卷积算子 g = impulse(G, (0:N-1)*Ts); % 离散脉冲响应序列 Gm = tril(toeplitz(g)); % 下三角 Toeplitz 矩阵 = 系统算子 Q = eye(N); % 跟踪误差权重,可改成对角时变权重 R = 1e-4 * eye(N); % 控制能量惩罚 S = 1e-3 * eye(N); % 迭代间控制变化惩罚 L = (Gm'*Q*Gm + R + S) \ (Gm'*Q); u = u + L * e; % 一次拿到全时段更新量,非因果

三个权阵各管一摊。Q 决定误差在时间轴上的权重分布,想让末端定位更准,就把 Q 的最后几十个对角元素调大;R 惩罚控制能量,调大之后学出来的 u 幅值更小、更平滑,代价是收敛变慢;S 惩罚的是u_{k+1} - u_k,也就是每一次迭代的改动量,它才是真正压住高频发散的那一项——把它调到 1e-3 量级,配合 Q=I,效果基本等价于一个设计得当的 Q 滤波器,但不用额外挑截止频率。R + S严格正定保证了矩阵可逆,这一点比自己拼一个滤波器要省心。

权阵调大的效果典型量级什么时候动它
Q该时段跟踪误差被压得更狠1(基准)末端精度不够时,只加最后一段
R控制量幅值变小、变平滑1e-5 ~ 1e-3学出来的 u 顶到限幅时
S迭代间改动变小,高频更收敛1e-4 ~ 1e-2误差范数出现反弹时优先加它

这个更新律的 L 是一个稠密矩阵,意味着第 t 时刻的控制修正要用到全时段的误差——它是非因果的,只能在外层循环里跑,没法直接嵌进 Simulink 的在线模块。想嵌进去就得做截断或者滚动时域近似,那是另一个话题了。实操上我一般用外层循环跑范数最优拿到一条“理想收敛曲线”作为标杆,再用内嵌的 P 型 + Q 滤波去逼近它,两者的误差范数序列能重合到什么程度,就说明内嵌实现做得多扎实。

最后说一个我现在的固定习惯。以前改完学习律就直接看跟踪效果,觉得曲线贴着参考走就完事了,结果有一次在台架上跑到第七遍突然抖起来,回查仿真才发现当时的误差频谱已经在高频段涨了三代,只是被低频的下降掩盖住了。从那以后我每次动学习增益、滤波器或者采样点数,都强制走一遍三件事:误差范数单调性、控制量增量是否同步趋零、末遍误差的 FFT 高频段有没有长起来。这三步加起来不到两分钟,比事后在硬件上找原因便宜太多。这套 Simulink 模型的价值也在这儿——它让你能在两分钟里把这三件事跑完几十次。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询