二级倒立摆MATLAB仿真:从LQR建模到动画录制全流程
2026/9/15 17:40:30 网站建设 项目流程

简介:面向控制理论与自动化方向学习者的一份二级倒立摆Matlab仿真资源,基于MATLAB 2021a环境开发,用于演示二级倒立摆这一经典非线性系统的建模与动态模拟。资源共5个文件,压缩包仅2.39MB,包含2个m脚本、1张示意图、1个动画gif和1段操作录像avi;两个m脚本分别承担主程序入口与双摆动力学建模,便于直接运行和二次修改,动画gif可随时预览摆动效果,示意图有助于比对模型结构,操作录像则记录了完整运行流程,可直接用Windows Media Player播放。运行时通过主程序即可输出倒立摆摆动动画与实时角度变化曲线,直观观察系统运动状态与实时角度变化规律,为深入理解倒立摆平衡控制原理提供实验支撑。录像还特别说明了MATLAB左侧当前文件夹必须指向程序所在目录等关键细节,能有效避免新手常见路径错误。已有183人学习使用,适合正在学习倒立摆控制、机器人平衡或需要搭建仿真演示环境的本科高年级及研究生参考。

1. 二级倒立摆的MATLAB仿真到底仿真什么

二级倒立摆看起来比一级倒立摆只多了一根杆,但实际仿真工作的重心完全不同:单摆的动力学方程在教科书写清楚后,剩下就是解微分方程;二级摆则需要处理两杆之间的角度耦合,以及小车加速度同时施加到两个摆杆上的传递路径。用MATLAB做这套模拟仿真,最有价值的产物不是那几张状态曲线,而是把摆杆的运动过程变成动画,并同步输出实时角度变化,让控制效果变得“看得见”。这个场景最适合课程设计、毕业设计和快速验证LQR等状态反馈算法的工程师。我下面给出的路径是:先建立可复现的线性化模型,再设计LQR,然后用ode45驱动动画,最后把整个仿真过程录成操作录像。按这条路径走,新手能一步步跟上,有经验的人也能从中看到参数选择的具体门道。

2. 二级倒立摆建模:从运动方程到状态空间矩阵

在进入MATLAB代码之前,先把物理模型限制在一个可行的范围内。这里把两根摆杆简化为末端质点,忽略转轴阻尼和小车摩擦。这样做的理由很简单:LQR设计只需要在平衡点附近有效的线性模型,摩擦和非线性项最终会由反馈的鲁棒性吃掉;如果你要把仿真结果搬到实际小车,再用实验数据把摩擦项补回来也不迟。

2.1 物理模型与参数定义

状态向量的定义如下:x是小车位移,theta1是下摆相对竖直向上的角度,theta2是上摆相对下摆延长线的角度。控制输入F是小车水平方向的牵引力。参数取一组接近小型实验台架的数值,单位统一为kg、m、s:

M = 0.5; % 小车质量 m1 = 0.2; % 下摆末端质量 m2 = 0.1; % 上摆末端质量 l1 = 0.2; % 下摆长度 l2 = 0.1; % 上摆长度 g = 9.8;

这组参数的特点是上摆比下摆轻,转动力矩更容易失控,适合用来检验控制器的鲁棒性。如果只做演示,可以把m2放大到0.15,稳定性会变好,但动画的真实感下降。各参数含义见下表:

参数数值说明
M0.5 kg小车质量
m10.2 kg下摆末端质量
m20.1 kg上摆末端质量
l10.2 m下摆长度
l20.1 m上摆长度

2.2 质量矩阵和势能Hessian的数值化

对末端质点模型,广义坐标q=[x; theta1; theta2]的动能矩阵为:

M_mass = [0.8, 0.06, 0.01; 0.06, 0.012, 0.002; 0.01, 0.002, 0.001]

这个矩阵的来源是拉格朗日方程中的二阶导数项,直接代入上面的参数得到。势能V的Hessian矩阵H在平衡点处的值为:

H = [0, 0, 0; 0, -0.686, -0.098; 0, -0.098, -0.098]

这两个矩阵可以直接在MATLAB里声明:

% 质量矩阵和势能Hessian矩阵 M_mass = [0.8, 0.06, 0.01; 0.06, 0.012, 0.002; 0.01, 0.002, 0.001]; H = [0, 0, 0; 0, -0.686, -0.098; 0, -0.098, -0.098]; % 检查M_mass条件数 cond(M_mass)

这里的关键是H中theta1和theta2对应的位置都是负值,说明势能在平衡点是局部极大值,系统在不加控制时必然发散。cond(M_mass)返回很小,说明这个参数下没有接近奇异,后面求逆是安全的。若把m2设成0,H第二行会退化,状态空间可控性会显著下降,这就印证了二级摆比一级摆更容易控制直觉。

2.3 构建状态空间模型并检查可控性

取状态向量X=[x; theta1; theta2; xdot; theta1dot; theta2dot],则连续时间系统的状态矩阵A和输入矩阵B由下面代码给出:

% 构建状态空间 A = [zeros(3), eye(3); -M_mass\H, zeros(3)]; B = [zeros(3,1); M_mass\[1;0;0]]; C = eye(6); D = zeros(6,1); sys = ss(A,B,C,D); % 可控性检查 Co = ctrb(sys); fprintf('可控性矩阵秩 = %d\n', rank(Co));

A左下块的-M_mass\H是3x3,表示角度与位移到加速度的耦合。B的第二行是M_mass[1;0;0],它表示牵引力F首先推动小车,再通过质量矩阵耦合到两个摆角。MATLAB左除\比inv(M)*H在数值上更稳定,当矩阵维度变大时这个差异会更明显。运行后如果秩等于6,说明系统可控,LQR设计的前提成立。如果秩小于6,回到参数列表检查m1、m2是否取了相同值等,常见原因是l2=0导致上摆不可控。

3. 用LQR让二级倒立摆“立得住”

3.1 为什么选LQR而不是PID

单级倒立摆还可以用PD串级勉强维持,二级摆的角度耦合让PID的每个回路都互相牵制:你调theta1的PD,结果theta2的响应跟着出现相位滞后,再回头调theta2,又会激起theta1谐振。LQR直接对全状态做线性反馈,控制器能同时看到小车位置、两个摆角以及三个速度量,耦合问题在代数上是统一的。我一般先试用LQR,原因很简单:设计参数只有Q和R两个矩阵,调参路径比PID清晰得多。

3.2 权重矩阵Q和R的工程设定

LQR的目标函数是积分(x'Qx + u'Ru),其中u=F。Q矩阵的物理意义是对各状态偏离平衡点的惩罚,R是对控制力大小的惩罚。实际调试时采用对角线矩阵:

% LQR权重 Q = diag([100, 100, 500, 1, 1, 1]); % 位置、下摆角、上摆角、速度、角速度 R = 1; K = lqr(A, B, Q, R); eig(A - B*K) % 观察闭环极点分布

Q的第一个对角元是100,对应小车位置,要求它快速回中;第三个对角元是500,对应上摆角度,因为它离控制力最远,需要更强的惩罚。速度相关的权重只设1,避免控制器过度微分放大噪声。R=1表示把牵引力约束在合理范围内,如果R过小,K会变得很大,实际电机容易饱和。

这里的调参经验可以做一个简单对照:

Q取值建议现象调整方向
上摆权重500上摆收敛快但小车来回摆动降低上摆权重到200
上摆权重50上摆修正慢、系统反复晃动提高上摆权重到500-800
R=0.1控制力峰值过大,仿真超调明显增大R到1-5
R=10响应慢,摆杆迟迟不立直减小R到0.5-1

这个表格实际是调试时最常看的四个信号。注意每次调完权重后重新执行lqr,然后观察eig(A-B*K)是否都在左半平面。

3.3 闭环极点与K矩阵的计算

闭环极点的位置直接对应控制品质。上例运行后,六个极点中会有两对主导共轭复根。虚部过大会导致动画里摆杆来回晃动多次才收住,实部绝对值过大则会让响应速度太快而超出线性化假设。我一般在仿真前先检查最大负实部是否小于8,如果大于8,就适当降低Q中的角度权重,避免在起步阶段就触发大角度脱离线性区。

K矩阵算出后,控制力F由-F = -KX产生。在后面的仿真里,要把这个反馈写成Acl = A - BK,用ode45直接对线性闭环系统积分。这里不推荐在求解器内部再调用lqr,因为K已经固定,每个时间步重复计算纯属浪费。

提示:换一组Q/R后,必须先重新计算K,再重新计算A-B*K。直接在原闭环系统上修改Q不会更新K,动画里看到的就是上一次的控制律。

4. 仿真与动画:用ode45驱动摆杆运动

4.1 设置仿真时长和输出步长

仿真的一大半“真实感”来自数值积分设置。ode45是自适应步长的Runge-Kutta求解器,但我们需要等间隔的输出,否则动画时间轴会乱。常见做法是给ode45一个指定tspan向量,并在options里设置RelTol和AbsTol:

% 仿真参数 Tspan = 0:0.01:10; % 10秒,每10毫秒一个输出点 y0 = [0; 0.1; -0.05; 0; 0; 0]; % 下摆偏10度,上摆相对偏-5度 opts = odeset('RelTol', 1e-6, 'AbsTol', 1e-8); [t, y] = ode45(@(t, y) (A - B*K)*y, Tspan, y0, opts);

这里Tspan的第一个元素不能等于后续步长,不能写成0:0.01:10以外,因为ode45只在tspan指定的点输出,内部积分步长是自适应的。RelTol设为1e-6,AbsTol设为1e-8,对数值稳定性足够;再小会造成积分步数爆炸,再大会出现摆杆缓慢发散的假象。y0中theta1=0.1rad,theta2=-0.05rad,这个初始状态在平衡点附近,LQR完全能拉回来,动画看起来又比较明显。

4.2 实时绘制摆杆动画与角度曲线

动画的核心是用line对象更新坐标而不是反复plot。先创建两个figure子图,左边画小车和摆杆,右边画theta1、theta2随时间的变化曲线。在循环里用set函数更新摆杆的XData/YData,然后对角度曲线追加新点:

% 建立画布 figure('Position',[100 100 900 450]); subplot(1,2,1); hold on; axis equal; xlim([-0.8 0.8]); ylim([-0.05 0.4]); plot([-0.5 0.5], [0 0], 'k', 'LineWidth', 2); % 导轨 cart = rectangle('Position',[-0.05 -0.03 0.1 0.03]); line1 = line([0 0],[0 0], 'Color','b', 'LineWidth', 2); line2 = line([0 0],[0 0], 'Color','r', 'LineWidth', 2); subplot(1,2,2); hold on; xlim([0 10]); ylim([-0.4 0.4]); plot_t1 = line([0],[0], 'Color','b'); plot_t2 = line([0],[0], 'Color','r'); % 动画循环 for i = 1:length(t) x = y(i,1); th1 = y(i,3); th2 = y(i,5); x1 = x + l1*sin(th1); % 下摆末端x y1 = l1*cos(th1); x2 = x1 + l2*sin(th1+th2); % 上摆末端x y2 = y1 + l2*cos(th1+th2); set(line1, 'XData', [x x1], 'YData', [0 y1]); set(line2, 'XData', [x1 x2], 'YData', [y1 y2]); set(cart, 'Position', [x-0.05 -0.03 0.1 0.03]); set(plot_t1, 'XData', t(1:i), 'YData', y(1:i,3)); set(plot_t2, 'XData', t(1:i), 'YData', y(1:i,5)); drawnow('limitrate') % 限制刷新率,避免占用过多CPU end

这段代码里,line1和line2是两个“杆”图形的句柄,循环里set改变了它们的端点坐标。用line而不是plot是因为set可以直接更新,不需要清理旧图形。右下角的plot_t1和plot_t2是用line([0],[0])生成的空曲线,循环里把历史时间与角度值整体传入,实现了实时角度变化曲线的滚动绘制。drawnow('limitrate')比普通drawnow更高频限制,10秒仿真在普通笔记本上大约2-3秒就能播完。

4.3 动画卡顿的常见归因

如果画面卡成PPT,先查三件事:仿真输出点数过多、每条曲线都新增图形对象、以及subplot内坐标轴未固定。输出点数在8000以下基本流畅;超过20000就明显掉帧。固定坐标轴用xlim/ylim而不是靠数据自动缩放。如果你用了hold on但又每次plot,内存很快会被图形对象拖垮。

症状常见原因解决办法
动画后期越来越慢每次循环增加新line对象用set更新现有句柄
角度曲线跳变输出步长过大Tspan步长改小到0.005
摆杆端点不在轨道上忘了把theta2作为相对角上摆末端x2要加th1+th2
动画一帧一帧卡电脑分辨率太高figure缩小,或用drawnow limitrate

这个表格是我在调试多摆动画时最常用的排查清单。其中第二项和第三项是新手最容易踩的坑:theta2是相对下摆的角度,不能直接当成绝对角来画上摆。

5. 生成仿真操作录像的录制技巧

5.1 VideoWriter录制figure窗口

把上面的动画录成视频,最不需要额外工具的方式是MATLAB自带的VideoWriter。在动画循环开始前创建VideoWriter对象,每个循环末尾用getframe抓取当前figure窗口,然后写进视频文件:

v = VideoWriter('double_inverted_pendulum.avi'); v.FrameRate = 30; open(v); for i = 1:length(t) % ... 更新动画 ... writeVideo(v, getframe(gcf)); end close(v);

这里FrameRate设30表示视频每秒30帧,但仿真每10毫秒输出一个点,10秒就有1000帧,视频时长变为1000/30约33秒。如果希望视频与仿真时间一致,应该把FrameRate设为100(1/0.01)。通常我更喜欢30帧率,因为视觉更平滑,缺点是不再严格对应仿真时间,这条需要根据用途选。

5.2 录像的控制与后处理

VideoWriter支持MPEG-4格式,把文件名后缀换成.mp4并指定'MPEG-4'即可:

v = VideoWriter('invp.mp4', 'MPEG-4'); v.Quality = 90; % 数值0-100,越小体积越小

Quality在90时清晰度足够,体积比avi小一半。录制过程中不要让其他窗口盖住figure,getframe抓的是屏幕上的渲染结果,一旦遮住就会录成空白。如果嫌手动操作录像重复,可以把整个录制脚本封装成一个函数,输入仿真参数,输出视频文件,这样后续改参数时重新跑一次就行。

5.3 增加录制动感的时间戳技巧

一个小技巧:在figure右上角放一个text对象,每个循环用sprintf显示当前仿真时间,这样录出来的视频能直观看到“实时”过程。在角度曲线子图里,还可以用scatter高亮当前时刻的点,让观看者注意力落在变化趋势上。这个普通set更新即可,不会对性能造成多少影响。最后,如果整个仿真循环里同时做了动画更新和writeVideo,建议先将仿真提前算好,不要在录制过程中做ode45积分。离线计算后,录制循环里只做插值取数据,视频即使中途卡顿也不会导致数据错位。

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

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

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

立即咨询