简介:这套MRAC算法控制MATLAB代码包,专为自动化、计算机、电子信息工程及数学等专业学生打造,可广泛应用于课程设计、期末大作业或本科毕业设计,兼容MATLAB 2014/2019a/2021a三个版本。压缩包共212个文件,大小仅3.93MB;核心为60个.m脚本,另有4个Simulink模型、5个图形文件、若干C代码及说明文档,工程结构清晰,方便按模块查阅。目前已有107人浏览学习。代码采用参数化编程,参数集中于文件头部,修改便捷;注释明细、思路清晰,并附赠可直接运行的案例数据。借助这些材料,读者可深入理解MRAC算法的参考模型选取、自适应律推导与参数收敛过程,还能快速修改被控对象或控制器参数进行二次扩展,有效缩短算法验证与仿真的时间。
1. MRAC 算法控制代码为什么值得手动推一遍
跑 MRAC 的次数多了会得到一个反直觉的结论:模型参数越准,MRAC 的优势越不明显;模型参数偏差越大,自适应律才真正开始起作用。MRAC(模型参考自适应控制)不是 PID 的替代品,它先定义一个可跟踪的参考模型,再让实际被控对象去“贴近”参考模型,控制增益按 Lyapunov 稳定性条件在线更新。这套方法适合被控对象存在恒值或慢变不确定性的场合,比如电机负载突变、气动参数漂移、机械臂末端负载变化。这份资源的主体是 MATLAB 仿真代码,同时附带了 TI Tiva TM4C123GH6PM 的 CCS 工程骨架,正好覆盖“先用 MATLAB 验证算法,再往 MCU 移植”的完整路径。适合课程设计、毕业设计,也适合已经在写固定增益控制器、准备给系统加自适应能力的工程师。
2. MRAC 参数自适应律推导:Lyapunov 函数与一阶对象落点
直接说结论:MRAC 控制律可以写成u = kx * x + kr * r的形式,难点不是控制律本身,而是kx、kr怎么在线更新。网上大部分推导都从一阶对象展开,这里也沿用这个路径,因为后面的 MATLAB 和 C 代码都建立在这组公式上。
2.1 直接 MRAC 与间接 MRAC 怎么选
直接 MRAC 不建立对象内部参数的显式估计,直接调整控制器增益,结构简单,控制周期和自适应周期可以用同一个任务驱动。间接 MRAC 会先在线辨识对象参数,再把辨识值送到控制器里,结构上多一个辨识回路,收敛分析和稳定性分析都更繁琐。zip 里的FeedbackController.asv从命名看是直接 MRAC 的反馈控制器原型,.asv是 MATLAB 自动保存文件,不是正式的.m源文件,打开之前最好先重命名,避免误改。
实际工程里,直接 MRAC 更好落地,因为控制器结构不随对象模型阶次变化太大,参数辨识部分被隐式地塞进了自适应律。下面全部围绕直接 MRAC 写。
2.2 一阶对象的 MRAC 完整推导
假设被控对象是一阶线性定常系统,参数a、b未知但符号已知,取b > 0:
dx/dt = a * x + b * u期望参考模型是稳定的:
dxm/dt = am * xm + bm * r,其中 am < 0控制器选为:
u = kx * x + kr * r定义跟踪误差:
e = x - xm如果存在理想控制器增益kx*、kr*满足匹配条件:
a + b * kx* = am b * kr* = bm那么误差动力学为:
de/dt = am * e + b * [(kx - kx*) * x + (kr - kr*) * r]现在选 Lyapunov 函数:
V = e^2 + b / γx * (kx - kx*)^2 + b / γr * (kr - kr*)^2因为b > 0,只要γx > 0、γr > 0,V就是正定函数。对时间求导后,把误差动力学代进去:
dV/dt = 2 * am * e^2 + 2 * b * (kx - kx*) * (e * x + dkx/dt / γx) + 2 * b * (kr - kr*) * (e * r + dkr/dt / γr)想让交叉项抵消,自适应律必须取:
dkx/dt = -γx * e * x dkr/dt = -γr * e * r代入后得到:
dV/dt = 2 * am * e^2 ≤ 0因为am < 0,所以能量函数不增。这里要提醒一句:dV/dt ≤ 0只能说明误差有界,不能直接说明误差收敛到零,通常还要用 Barbalat 引理,从误差函数的连续性推出e → 0。这也是 MRAC 和经典极点配置的本质区别:MRAC 保证的是渐近收敛,不是指数收敛。
2.3 用 MATLAB 符号工具验证推导过程
如果你要把上面的推导落到代码里,建议先用 MATLAB 的符号计算快速验证一遍,避免系数写错。这里给出一个可直接运行的符号脚本:
% mrac_symbolic_check.m syms a b am bm kx kr kxs krs gamx gamr e x r % 定义参数误差 deltax = kx - kxs; deltar = kr - krs; % 误差动力学 de = am*e + b*(deltax*x + deltar*r); % 自适应律 dkx = -gamx*e*x; dkr = -gamr*e*r; % Lyapunov 函数 V = e^2 + (b/gamx)*deltax^2 + (b/gamr)*deltar^2; % 对时间求导 dV = 2*e*de + 2*(b/gamx)*deltax*dkx + 2*(b/gamr)*deltar*dkr; % 化简结果,R2014/R2019a 用 simple 或 simplify 均可 simplify(dV)代码里deltax、deltar是控制器增益与理想增益的差,gamx、gamr是自适应增益。simplify(dV)化简后只应该看到2*am*e^2,如果看到多余项,说明要么de写错,要么dkx、dkr的符号反了。日常调 MRAC 时,符号错误是最容易出问题的点,尤其是从一阶推广到二阶系统时,P B项会带入额外的符号信息,不能照抄。
2.4 从一阶到状态空间的参数映射
实际对象很少真是一阶,但很多状态反馈 MRAC 的最后落脚都是通过 Lyapunov 方程计算P矩阵,自适应律里会把e^T * P * B当作广义误差乘子。参考模型通常写成:
dxm/dt = Am * xm + Bm * rAm必须是 Hurwitz 矩阵,Bm的选择要满足参考模型的直流增益要求。用 MATLAB 求P矩阵的标准方法:
% 参考模型系数 wn = 2.0; zeta = 0.7; Am = [0 1; -wn^2 -2*zeta*wn]; Bm = [0; wn^2]; % 解 Lyapunov 方程 Q = eye(2); P = lyap(Am', Q);lyap(Am', Q)求解的是Am'*P + P*Am = -Q,注意这里用的是Am',不是Am。求解P矩阵之后,自适应律里所有e^T * P * B都需要在每次控制周期实时计算,这会带来不小的乘法量,但 TM4C123 这类 Cortex-M4F 运行单精度浮点足够。
3. Matlab 代码里把 MRAC 跑起来:M 脚本、激励信号与调参
理论推导只是第一步,真正能复现的还是 M 代码。这一章给一个可以直接运行的一阶 MRAC 脚本,并说明参数怎么改、曲线怎么看。
3.1 完整的一阶 MRAC M 脚本
下面的脚本使用显式欧拉积分,采样周期Ts足够小时精度满足工程需求,这段代码针对的是dx/dt = a*x + b*u的对象:
% mrac_first_order.m % 用于验证源码包内 MRAC 算法主逻辑 clear; clc; % 被控对象参数,实际调试时改这里 a = 1.5; b = 2.0; % 参考模型参数:一阶惯性,直流增益 1 am = -1.2; bm = 1.2; % 自适应增益 gamma_x = 3.0; gamma_r = 1.0; % 仿真时间与采样周期 Ts = 0.001; T = 6.0; N = round(T / Ts); t = (0:N-1)' * Ts; % 激励信号,前半段指令为 0,后半段阶跃 r = zeros(N, 1); r(N/2+1:end) = 1.0; % 状态与增益序列 x = zeros(N, 1); xm = zeros(N, 1); kx = zeros(N, 1); kr = zeros(N, 1); u = zeros(N, 1); for k = 1:N-1 e = x(k) - xm(k); u(k) = kx(k) * x(k) + kr(k) * r(k); % 对象模型,实际使用时用传感器反馈替换 x(k+1) = x(k) + (a * x(k) + b * u(k)) * Ts; % 参考模型前推进 xm(k+1) = xm(k) + (am * xm(k) + bm * r(k)) * Ts; % 自适应律,注意符号与 2.2 节一致 kx(k+1) = kx(k) - gamma_x * e * x(k) * Ts; kr(k+1) = kr(k) - gamma_r * e * r(k) * Ts; end % 绘图:上图为跟踪,下图为增益 figure subplot(2,1,1) plot(t, x, t, xm, '--') legend('对象状态 x', '参考模型 xm') ylabel('x') subplot(2,1,2) plot(t, kx, t, kr) legend('kx', 'kr') xlabel('t/s')这里把自适应律放在对象更新之后,实际上是拿上一拍的误差在更新增益,工程上这样做没问题。重点看两个地方:gamma_x和gamma_r决定参数调整的步长;kx、kr初值设 0,会让前 0.2 秒内u偏小,系统响应会比参考模型慢半拍。
3.2 不同激励信号下的自适应收敛
MRAC 的参数收敛依赖参考输入的充分激励。r只给一次阶跃时,参数在稳态附近会松弛,但kr往往不容易收敛到理论值。想要验证参数辨识能力,可以把指令改成方波或正弦:
% 方波指令 r = 1.0 * (mod(t, 2) < 1); % 带偏置的正弦指令 r = 0.5 + 0.5 * sin(2 * pi * 0.5 * t);方波能同时激励多个频率,正弦的跟踪误差曲线更直观。改完激励之后,kx、kr的收敛轨迹会明显不同,这是正常现象。如果最终系统输出能跟上参考模型,而自适应律参数在持续小幅度震荡,说明gamma偏大,需要下调。
3.3 调参顺序与关键参数表
我习惯的调参顺序是:先固定参考模型,再调gamma_x和gamma_r,最后改激励信号。参数关系如下表:
| 参数 | 含义 | 建议范围 | 调大后的表现 |
|---|---|---|---|
am | 参考模型极点 | 小于对象开环极点 | 收敛变快,但控制量峰值增大 |
bm | 参考模型增益 | bm/am取期望直流增益 | 影响参考模型稳态输出 |
gamma_x | 反馈增益自适应速率 | 0.1 ~ 10 | 收敛快,但高频段噪声被放大 |
gamma_r | 前馈增益自适应速率 | 0.1 ~ 10 | 影响阶跃指令跟随速度 |
Ts | 采样周期 | 比系统最短时间常数小 5~10 倍 | 过大时欧拉积分误差明显 |
如果gamma太大,kx和kr会在稳态附近高频抖动,抖动的频率接近奈奎斯特频率。这一点在纯 MATLAB 仿真里不容易暴露,因为仿真测量没有噪声;一旦接上真实 ADC 或者电流采样,噪声会通过e * x进入自适应律,参数漂移非常快。
3.4 直接修改包内案例数据的方法
如果你是从课程设计资源里拿到的代码,案例数据通常以.mat文件存放。把外部数据接到 MRAC 脚本里的常见做法是:
% 载入案例数据,只读取输入输出列 load('case_data.mat'); r = data(:, 1); % 指令列 y = data(:, 2); % 测量输出列载入后把第 3.1 节循环里的x(k+1) = x(k) + (a * x(k) + b * u(k)) * Ts换成:
x(k) = y(k);这样就能把仿真对象替换成真实采集到的数据。注意必须先把y和r对齐到同一个时间栅格,否则相位差会被 MRAC 当成对象动态,导致自适应律乱调。
4. 把 MRAC 搬到 TM4C123 C 工程:CCS 工程结构与控制循环
源码包里包含了main.c、PLL.c、tm4c123gh6pm_startup_ccs.c、.ccsproject等文件,这是一套标准 Code Composer Studio 工程。MRAC 算法在 MATLAB 里验证完成后,要往 TM4C123 上移植,不是把.m文件翻译成.c就能完事的,还需要考虑任务调度、浮点类型和限幅。
4.1 工程文件结构与各自作用
先把文件作用说清楚:
| 文件 | 作用 | 移植时注意事项 |
|---|---|---|
.ccsproject | CCS 工程描述文件 | 双击打开工程,不能直接烧录 |
tm4c123gh6pm_startup_ccs.c | 中断向量表定义 | 改中断优先级时不要动向量表顺序 |
main.c | 主循环与控制任务入口 | 重点修改对象 |
PLL.c | 系统时钟配置 | 决定定时器的定时周期 |
FeedbackController.asv | MATLAB 临时自动保存文件 | 不是源码,需另存为.m |
.asv文件是 MATLAB 编辑器在崩溃前自动保存的旧版本,内容可能比正式.m还新,但命名不规范。拿到资源后先把.asv改名,再把里面和 2.2 节推导核对一遍。
4.2 定时中断里的 MRAC 步进函数
TM4C123GH6PM 没有内置硬件浮点加速指令集吗?实际上它带 Cortex-M4F 的 FPU,单精度 float 性能够用。控制任务建议放在定时器中断里,周期固定为 1 kHz 或 2 kHz。参考模型和自适应律必须严格按同一个TS推进,否则参考模型状态和对象状态之间会有相位误差。
下面是一段可直接放进main.c的 MRAC 步进函数:
// mrac_control.c #define TS 0.001f #define GX 3.0f #define GR 1.0f #define AM (-1.2f) #define BM 1.2f // 外部提供:当前对象状态的采样值,比如编码器位置 extern float get_plant_state(void); static float x_meas; static float x_ref; static float kx = 0.0f; static float kr = 0.0f; static float u_out = 0.0f; void MRAC_ControlTask(void) { float e; float ref_cmd; // 每个周期读一次反馈量 x_meas = get_plant_state(); ref_cmd = get_setpoint(); // 误差计算 e = x_meas - x_ref; // 控制量:反馈增益 + 前馈增益 u_out = kx * x_meas + kr * ref_cmd; // 限幅,防止 MCU 的 PWM 模块出现异常占空比 if (u_out > 0.95f) { u_out = 0.95f; } if (u_out < 0.05f) { u_out = 0.05f; } // 参考模型前推进 x_ref = x_ref + (AM * x_ref + BM * ref_cmd) * TS; // 自适应律,注意和 MATLAB 代码保持一致 kx = kx - GX * e * x_meas * TS; kr = kr - GR * e * ref_cmd * TS; }这段 C 代码和 MATLAB 脚本的对应关系是:x_meas对应x(k),x_ref对应xm(k),u_out对应u(k)。参考模型更新仍然在对象反馈之后执行,顺序不能反。get_plant_state()如果是从 ADC 读取,要先换算成物理量,不能直接用原始寄存器值,否则e的量纲和kx * x_meas不一致。
4.3 手写 C 代码与 Simulink Embedded Coder 自动生成怎么选
Matlab 版本从 2014 到 2021a 都能在 TM4C123 上做验证,比较稳的路线有两种。第一种是直接在 CCS 里手写 C,代码量小、逻辑透明,适合毕业设计和课程设计,缺点是参考模型改动后要同步改 C。第二种是在 Simulink 里搭出上面 3.1 节的框图,用 Embedded Coder 生成 CCS 工程,再和源码包里的.ccsproject合并,优点是 MATLAB 和 MCU 行为一致,缺点是生成代码体积大,版本兼容性麻烦。
如果你的目标是快速出结果,我建议用 4.2 节的手写方式。MRAC 的核心状态只有kx、kr、x_ref三个,远没有到必须上代码生成工具的程度。工程里真正要花时间的是把 ADC 采集、PWM 输出和定时器中断调通。
5. 实际调试时最后要加的三个保护
MRAC 算法在仿真里跑得通,不代表电机台架或机械臂上也能直接跑。最后这一步主要讲验证方法,建议把下面的保护加进代码里再测试。
5.1 参考模型和真实状态不能有量纲差
很多败局发生在量纲上。参考模型用的是角度单位,编码器反馈的是 raw 计数值,两者做差e会带着巨大的偏置,自适应律会把kx、kr推离合理区间。加一个简单的线性校准,编码器每圈 2000 线,就换算成圈数或角度,保证x_ref和x_meas的物理单位一致:
#define PULSE_PER_REV 2000.0f x_meas = encoder_cnt / PULSE_PER_REV;如果没有这一行,前 0.5 秒的kx增长量会大到让 PWM 直接饱和。
5.2 自适应律加死区
当跟踪误差小于量化噪声幅值时,e * x不再是真实误差,只是噪声。此时继续更新kx、kr只会引入参数漂移。在 MATLAB 和 C 里都要加死区:
float DEAD_BAND = 0.001f; if (fabsf(e) > DEAD_BAND) { kx = kx - GX * e * x_meas * TS; kr = kr - GR * e * ref_cmd * TS; }死区阈值一般取传感器分辨率的 2~3 倍。加了死区之后,系统会从“持续自适应”变成“误差足够大时才自适应”,稳态抖动明显减小。注意死区会破坏 Lyapunov 推导里的收敛性证明,所以死区阈值不能太大,否则小误差段会出现跟踪残差。
5.3 用阶跃指令做边界测试
验证 MRAC 到最后,我习惯用三组数据:小阶跃、大阶跃、方波连续扰动。小阶跃看稳态误差是否进入死区,大阶跃看控制量是否饱和以及kx、kr是否发散,方波扰动看自适应律能否在几次切换后快速修正参数。运行结束后画出u_out曲线,如果出现高频毛刺,优先调小GX和GR,而不是换滤波器。把参考模型极点从am = -1.2往左推到-3.0,跟踪会变快,但控制量的超调也会变大,这是 MRAC 里最直接的调参手感。
本文还有配套的精品资源,点击获取