
简介这份资源面向控制理论学习者与工程实践人员围绕迭代学习控制ILC在Simulink中的仿真实现解决周期性任务下无静差跟踪与迭代律调试的问题适合具备自动控制基础、希望动手验证ILC算法的中高级读者。压缩包共14个文件、约66KB以mat数据、xml配置、m脚本和slx模型为主另有Stateflow模型缓存与自动保存文件便于直接打开运行并对照参数修改。已有3981人学习下载。模型以简单传递函数代表被控对象将误差计算、增益反馈、限幅与迭代更新规则用Sum、Gain、Saturation等模块搭建可观察多周期迭代后输出逼近期望值的曲线并自行调整参数比较跟踪效果。借助这套模型读者能完整走通从建模、算法集成到性能验证的流程也可作为课程实验、论文复现与控制器设计的参考起点。1. 先别急着调参数Simulink ILC 资源包到底解决哪类重复轨迹问题Simulink ILC 资源包最容易让人误判的地方是把它当成一套现成的 PID 参数模板。实际拆开看它围绕的是重复轨迹跟踪同一段期望轨迹跑几十遍用上一遍的误差去修正下一遍的控制输入。适合机械臂重复抓取、伺服往返定位、批次过程这类“轨迹不变、只改批次”的场景。如果你做的是非重复轨迹或者每次初始状态都不同ILC 的收益会迅速变差。这份资源的价值不在于给一个万能增益而在于把迭代更新、误差存储和收敛观察串成可跑的 Simulink 闭环让新手先看到收敛曲线再谈调参也让熟手能在一个干净模型上验证自己的学习增益和滤波策略。2. 迭代学习控制在 Simulink 里的最小闭环从误差记忆到控制更新2.1 ILC 与常规 PID 的边界为什么重复轨迹才配得上迭代常规 PID 在 Simulink 里跑一遍误差反馈当下就修正下一批仿真重新从零开始。ILC 不一样它把第 k 次迭代的误差 e_k 存下来算成第 k1 次的控制输入 u_{k1}。离散 P 型更新最常见u_{k1}(t)u_k(t)L e_k(t)。这里的 t 是批内时间k 是批次。收敛条件不是看单次闭环稳定而是看迭代映射的谱半径||I - L G|| 1。G 是从控制输入到输出的算子。实际调参时不用真去算谱半径但要知道 L 的符号和量级G 低频增益接近 1 时L 取 0.1~0.5 比较常见L 大于 2 很容易把高频噪声放大。这个边界决定了很多翻车场景。某开发者把 ILC 直接套到一条每批次都变化的 S 形轨迹上前 3 次误差确实降了第 4 次开始反弹最后怪模型“不收敛”。问题不在模型而在轨迹不重复上一批学到的控制输入对下一批没有复用价值。ILC 的隐含假设是期望轨迹重复、系统动态在批次间变化不大、初始状态可重置。三个条件缺一个就要回到反馈控制或者增益调度。资源包里的模型之所以能跑出收敛曲线是因为它把这三件事固定住了同一个 yd、同一个初始状态、同一套采样时间。先确认自己的场景满足这些条件再打开模型比一上来改 L 更省时间。2.2 资源包里的三类模型P 型、PD 型与开闭环结构P 型是入门首选更新律只依赖误差本身结构简单对相位不敏感。它的缺点是收敛慢遇到系统相位滞后较大时L 只能往小取迭代次数会拉长。PD 型在误差之外再加误差微分相当于给更新律一个超前相位能改善收敛速度但微分项对噪声和采样抖动很敏感。实际用的时候微分不是对原始误差直接差分常见做法是先对误差做低通再差分或者用带滤波的微分器。开闭环结构则是在 ILC 更新之外保留一个反馈控制器u_{k1}u_kL e_kK_fb。它适合第一次迭代误差很大、纯 ILC 前几批控制输入太激进的情况反馈项负责兜底ILC 项负责逐批消掉重复性误差。三类模型对应三种调试顺序。我一般会先跑 P 型把 u0 置零看误差范数是不是单调下降如果下降太慢再把 L 从 0.1 往上加找到不发散的边界边界找到后再换 PD 型看能不能在同样 L 下把收敛速度提一截。开闭环模型放在最后因为反馈增益和 ILC 增益会互相影响两个参数一起调容易把问题搅在一起。资源包里如果同时给了这三类结构不要一上来就跑最复杂的那个先把 P 型的收敛曲线跑出来后面才有对照基准。2.3 用 MATLAB 脚本把模型参数外置一次改参、批量跑批次直接在 Simulink 模型里双击模块改参数跑两三次还能记住跑到二十次就很容易漏改。更稳的做法是把 Ts、T、yd、L、N 这些变量统一放到初始化脚本里模型只引用变量名。下面这段脚本可以直接放在解压后的工作路径下先跑它再打开模型。% ilc_params.m Ts 0.001; % 采样周期单位 s T 2.0; % 单批仿真时长单位 s t 0:Ts:T; % 批内时间向量 yd sin(2*pi*0.5*t); % 期望轨迹重复周期 2 s L 0.35; % P 型学习增益先小后大 N 30; % 迭代次数 u0 zeros(size(t)); % 初始控制输入第一次迭代从零开始这段脚本的关键不是公式而是变量名要和模型里的 From Workspace、To Workspace 对齐。yd是行向量模型里 From Workspace 的数据格式要设成与时间无关的数组或者用带时间列的矩阵u0长度要和t一致否则第一次sim就会报维度不匹配。L先取 0.35 是一个保守起点不是最优值。N30足够看出误差范数有没有进入平台期如果 30 次还在明显下降再加到 50 次不要一次设 200 次否则单次调试时间太长。把参数外置以后批量扫参只需要改脚本里的L不用动模型这也是后面做收敛性验证的基础。3. 跑通第一个 ILC 仿真三个信号路由决定收敛曲线真假3.1 解压后的文件怎么认.slx、.m 与数据字典解压后先不要双击 .slx 就开跑。先看根目录有没有 .m 初始化脚本里面通常是 Ts、T、yd、L 这些变量再看 .slx 模型里有没有 From Workspace、To Workspace、MATLAB Function 这三类模块。From Workspace 负责把u_k和yd送进模型To Workspace 负责把输出y和误差e送回 base workspaceMATLAB Function 负责迭代更新。数据字典或者 .mat 文件一般存的是期望轨迹和上一次迭代结果如果模型引用了 .mat 里的变量要先load再sim。路径问题比想象中常见。压缩包解压到带中文或空格的目录addpath有时会把路径截断模型里如果用了相对路径引用 .mat双击打开时当前文件夹不对就会报变量不存在。稳妥的做法是先把整个目录放到纯英文路径下再用脚本把子目录一次性加进来。% 在解压后的根目录执行 addpath(genpath(pwd)); % 把当前目录及子目录加入搜索路径 which ilc_params % 确认初始化脚本可见 ilc_params; % 把 Ts、T、yd、L 等变量送进 base workspacegenpath会把所有子目录加进来适合资源包里模型和脚本分目录存放的情况。which是确认脚本真的在路径上而不是在另一个同名目录里。ilc_params执行后用whos看一眼yd、u0、L的维度和类型别让一个列向量混进行向量里后面误差相减会得到一个矩阵而不是向量。3.2 用 sim 命令驱动单次迭代To Workspace 与误差对齐第一次跑不要直接上循环先手动跑一次单批仿真确认模型输出能拿到、维度对得上。下面这段代码用sim命令驱动模型模型名按实际 .slx 文件名改To Workspace 的变量名按模型里实际设置改。model ilc_p_type; % 按实际模型文件名修改 load_system(model); % 加载模型但不打开界面 assignin(base,yd,yd); % 期望轨迹送入 base workspace assignin(base,u_k,u0); % 第一次迭代的控制输入 set_param(model,StopTime,num2str(T)); simOut sim(model,ReturnWorkspaceOutputs,on); y simOut.get(y); % 变量名要和 To Workspace 一致 e yd(:) - y(:); % 统一成列向量再相减load_system比open_system适合脚本批量跑不会每次弹模型窗口。ReturnWorkspaceOutputs打开后simOut.get(y)才能拿到 To Workspace 写出的变量如果模型里 To Workspace 的变量名不是y这里要改成实际名字。yd(:)和y(:)是为了避免行向量、列向量混用导致e变成矩阵。实际模型输出可能比yd多一个采样点sim在 StopTime 处会多算一步这时候不要直接resample先看length(y)和length(yd)差多少差一个点就截断到相同长度差很多就是采样时间设置不一致。误差对齐这一步没做对后面画出来的收敛曲线就是假的看起来在收敛其实是两条时间轴错位后相减出来的低频包络。提示如果 To Workspace 的 Save format 选的是 Structure with timey会是结构体需要用y.signals.values取数据选 Array 则直接是数组。两种格式不要混着用。3.3 画收敛曲线用误差范数判断第几次迭代开始“贴轨迹”单次跑通后把迭代循环补上。下面这段代码把每次迭代的误差存进E把下一次控制输入存进U最后用 RMS 误差范数画半对数曲线。U zeros(N1, numel(t)); E zeros(N, numel(t)); U(1,:) u0; for k 1:N assignin(base,u_k,U(k,:)); simOut sim(model,ReturnWorkspaceOutputs,on); y simOut.get(y); y y(:).; n min(numel(y), numel(yd)); e yd(1:n) - y(1:n); E(k,1:n) e; U(k1,1:n) U(k,1:n) L * e; end err_norm sqrt(mean(E.^2,2)); figure; semilogy(1:N, err_norm, o-); xlabel(迭代次数 k); ylabel(RMS 跟踪误差); grid on;U存 N1 行因为第 N 次迭代后还要算出第 N1 次的控制输入E存 N 行对应每次迭代的误差。每次sim前用assignin把当前控制输入送进 base workspace模型里的 From Workspace 读的就是这个变量。min截断是为了防止模型输出多一个点导致维度不一致但截断之后要确认时间轴仍然对齐不能一个从 0 开始、一个从 Ts 开始。err_norm用 RMS 而不是最大误差是因为最大误差容易被单个尖峰带偏RMS 更能看出整体收敛趋势。半对数坐标下如果曲线在前几次快速下降、后面走平说明 L 和迭代次数基本匹配如果曲线下降后又翘起来先别急着加迭代次数那通常是发散前兆回到第 4 章检查增益和滤波。4. 参数整定学习增益 L 的上限怎么从收敛条件反推4.1 学习增益 L 不是越大越好从收敛条件反推上限P 型 ILC 的收敛条件可以粗略理解成每次迭代后误差被乘以一个小于 1 的因子。对于低频主导的系统如果从控制输入到输出的低频增益接近 1L 取 0.1~0.5 通常能收敛L 接近 1 时收敛很快但对相位滞后和噪声更敏感L 大于 1 时很多模型会在第 3 到第 5 次迭代后开始振荡。这不是精确判据但足够用来定扫参范围。更稳妥的做法是先跑一个小增益确认误差范数单调下降再逐步加大记录第一次出现非单调的 L 值把工作点放在它的一半左右。L_list [0.05 0.10 0.20 0.35 0.50 0.80]; result zeros(numel(L_list), N); for i 1:numel(L_list) L L_list(i); U zeros(N1, numel(t)); E zeros(N, numel(t)); U(1,:) u0; for k 1:N assignin(base,u_k,U(k,:)); simOut sim(model,ReturnWorkspaceOutputs,on); y simOut.get(y); y y(:).; n min(numel(y), numel(yd)); e yd(1:n) - y(1:n); E(k,1:n) e; U(k1,1:n) U(k,1:n) L * e; end result(i,:) sqrt(mean(E.^2,2)); end figure; semilogy(1:N, result.); legend(string(L_list), Location,southwest); xlabel(迭代次数 k); ylabel(RMS 误差); grid on;这段扫参脚本把每个 L 的误差范数存成一行最后一张图能直接看出哪个增益在前 10 次下降最快、哪个在第 6 次以后翘头。L_list不要设得太密先粗扫 5 到 6 个点找到大致边界后再在边界附近细扫。每次内层循环都重新初始化U和E否则上一次 L 的结果会污染下一次。扫参时间主要花在sim上模型越复杂越要控制N先用 15 次粗筛再对候选 L 跑 50 次确认。参数含义典型起点调整方向Ts采样周期1 ms减小会更接近连续但仿真和存储成本上升LP 型学习增益0.1~0.5过大发散过小收敛慢N迭代次数20~50看误差范数是否进入平台alpha一阶低通系数0.1~0.3越小滤波越强相位滞后越大4.2 低通滤波与 Q 滤波器抑制高频抖动的常见做法误差里混进高频噪声时P 型更新会把噪声逐批放大表现是误差范数前几次下降后面开始出现毛刺控制输入u_k越来越抖。常见做法是在更新前对误差做低通相当于给迭代映射加一个 Q 滤波器。下面是一阶低通的离线写法适合在 MATLAB 脚本里批量处理。alpha 0.2; % 0~1越小滤波越强 e_f filter(alpha, [1, -(1-alpha)], e); % 一阶低通 U(k1,1:n) U(k,1:n) L * e_f;filter的传递函数是 alpha/(1-(1-alpha)z^-1)alpha0.2时截止频率较低能压住高频毛刺但会带来相位滞后。ILC 对相位很敏感滤波太强会让收敛速度变慢甚至把原本能收敛的增益拖到发散。所以加滤波以后要重新扫一遍 L不能沿用滤波前的增益。离线批量可以用filtfilt做零相位滤波避免相位滞后但filtfilt需要整段误差实时迭代里用不了Simulink 模型里对应的是 Discrete Filter 模块初始条件要设成和上一批末尾一致否则每批开头会有跳变。4.3 迭代次数与停止准则别把发散当“还没收敛”迭代次数不是越多越好。误差范数进入平台期后再跑除了增加仿真时间没有太多收益如果误差范数开始上升继续跑只会让控制输入越走越偏。一个简单的停止准则可以写成连续 3 次迭代的误差范数比值小于 0.95认为收敛连续 2 次比值大于 1.05认为发散立即停止并回看 L 和滤波。ratio err_norm(2:end) ./ err_norm(1:end-1); converged false; diverged false; for k 1:numel(ratio)-2 if all(ratio(k:k2) 0.95) converged true; break; end if all(ratio(k:k1) 1.05) diverged true; break; end endratio是相邻两次误差范数的比值小于 1 表示在下降。连续 3 次小于 0.95 是一个偏保守的收敛判据适合先确认趋势如果 L 很小收敛比可能一直在 0.98 附近这时候不要把“没到 0.95”当成失败可以放宽到 0.99 或者直接看误差范数是否低于设定阈值。反过来连续 2 次大于 1.05 基本可以判定发散不要再加迭代次数。血泪经验是很多人看到第 5 次误差翘头第一反应是“还没收敛再跑 50 次”结果控制输入饱和模型报积分错误回头还得从第 1 次重跑。5. 避坑与排查ILC 仿真里最容易翻车的五个细节5.1 现象第一次迭代误差就爆掉第一次sim出来Scope 里的输出直接打到很大的量级误差范数比开环还大。常见原因有三个u0不是零而是上一批的残留值和当前初始状态不匹配学习增益的符号写反了更新律变成u_k - L e_k模型里积分器或传递函数的初始条件不是零第一批响应本身就有跳变。解决顺序是先把u0置零、L降到 0.05跑一次开环确认输出对输入有正常响应再检查更新律的加减号最后看模型里有没有非零初始状态。不要一上来就怀疑模型文件损坏多数情况是初始条件和符号问题。5.2 现象误差曲线每次迭代都平移每次迭代的误差形状几乎一样但在时间轴上左右错开RMS 范数降不下去。原因通常是 To Workspace 的采样时间与yd的时间向量不一致或者模型里有传输延迟、零阶保持器带来的固定滞后没有补偿。解决方法是不要自己造时间向量直接用模型输出里的时间轴和yd对齐如果存在固定延迟在误差相减前把yd延迟相同拍数或者在 Simulink 里加延迟补偿模块。注意sim在 StopTime 处可能多输出一个点截断时要从同一端截不能一个掐头一个去尾。5.3 现象前几次收敛后突然发散前 5 次误差范数下降得很漂亮第 6 次开始抬头控制输入出现高频抖动。最常见的原因是 L 取得太接近稳定边界前几批低频误差占主导看起来收敛高频分量累积到一定程度后开始放大。另一个原因是执行器饱和控制输入被限幅后ILC 仍然按未限幅的误差继续积分导致 windup。解决方法是加一阶低通或者 Q 滤波器把 L 降到原来的一半再检查饱和模块的限幅值是否合理。不要因为前几次曲线好看就继续加大 L那个“好看”往往只是高频还没长出来。5.4 现象Scope 显示收敛但数据里没有Scope 上肉眼看着输出已经贴住期望轨迹但E矩阵算出来的误差范数没降甚至变大。原因可能是 To Workspace 的Limit data points被打开只存了最后若干个点或者模型里有两个同名变量simOut.get拿到的是旧数据也可能是 Scope 的显示采样和实际数据采样不是同一套。解决方法是关掉数据点限制把 To Workspace 的变量名改得足够独特每次sim前清掉临时变量跑完用whos和size确认拿到的数组维度。Scope 只能用来快速看一眼波形收敛判断必须回到误差范数。5.5 现象换轨迹后完全失效同一套模型换一条幅值或频率不同的期望轨迹误差不降反升。ILC 只对重复轨迹有效新轨迹意味着上一批学到的u_k没有复用价值如果还沿用旧轨迹的u0第一批控制输入就是错的。另一个容易忽略的点是初始状态没有重置上一批结束时系统停在一个非零状态下一批从那里开始迭代之间就不满足“同初始条件”的前提。解决方法是新轨迹从u00重新开始确认模型里所有积分器和延迟模块在每批仿真前都复位如果轨迹只是参数变化可以把轨迹参数也纳入初始化脚本每次换参数后重新跑前 3 次低增益迭代看方向。6. 进阶验证把 ILC 从“能跑”推到“可信”的三个检查6.1 用单调收敛性检查代替肉眼观察误差曲线在半对数坐标下看着下降不代表迭代映射真的在收敛。更硬的检查是看相邻误差范数的比值是否稳定小于 1。下面这段代码在每次迭代后直接打印比值连续出现大于 1 的段就停下来。ratio err_norm(2:end) ./ err_norm(1:end-1); bad find(ratio 1.02, 1, first); if ~isempty(bad) fprintf(第 %d 次迭代后比值 %.3f检查 L 或饱和\n, bad, ratio(bad)); end1.02是留给数值波动的容差不是收敛判据。真正要看的是比值序列有没有持续下降的趋势而不是单次小于 1。如果比值在 0.98 附近来回跳说明 L 偏小或者误差里噪声占比高收敛速度已经到底了。6.2 换初始误差与扰动看鲁棒边界收敛曲线只能证明这一组初始条件下能跑。更可信的验证是给初始状态加一点偏移或者在控制输入上叠一个幅值 1% 的扰动再跑同样的迭代次数。如果误差范数仍然下降只是平台略高说明方法对这批扰动不敏感如果扰动后直接发散说明 L 没有余量工作点太靠近边界。我一般会做三组原条件、初始状态加 5% 偏移、控制输入加 1% 扰动三组都过再谈参数可用。6.3 把迭代过程录成表格定位第一次发散不要只存最后一条误差曲线。把每次的err_norm、最大绝对误差、控制输入峰值存成表格发散时直接看哪一行开始异常。控制输入峰值突然跳变通常是饱和或增益过大最大误差和 RMS 误差同时上升通常是相位错位RMS 上升但最大误差不变通常是高频噪声累积。这个表比 Scope 截图有用得多。从那以后我每次跑 ILC 都先跑 3 次低增益看方向再扫参不再一上来就调大 L模型里的 To Workspace 变量名也固定加前缀避免和脚本变量撞车。希望帮到你。本文还有配套的精品资源点击获取