简介:倒立摆自适应动态规划(ADP)MATLAB仿真资料包,面向控制理论、强化学习与MATLAB编程学习者,重点演示如何用ADP算法实现单级倒立摆的稳定控制,适合作为自动化、机器人或电气相关专业的课程设计与毕业设计参考。压缩包共4个文件,约1.72MB:两个m脚本分别承担主仿真流程与倒立摆动力学模型,一个CAJ文档提供基于近似动态规划的倒立摆控制参考论文,另有JPG图片展示随机起始状态下的控制效果。资源已有1635人学习下载,说明该实例在相关学习者中有较高热度。源码以完整可运行的MATLAB仿真为主体,包含系统建模、ADP迭代求解、仿真循环与结果可视化,使用者可直接修改模型参数,观察控制器在不同初始条件下逐步将摆杆稳定至垂直平衡的过程;CAJ文献进一步补充算法推导与结果分析,帮助读者将仿真代码与理论方法对照理解,快速复现ADP倒立摆控制实验。
1. 倒立摆 MATLAB 仿真:一条从动力学到闭环控制的完整链路
倒立摆是最典型的欠驱动非线性系统:小车加摆杆两个自由度,却只有一个控制输入。教材讲它,是为了引出状态空间和 LQR;工程上做自平衡小车、机器人运控的人,也先用倒立摆仿真验证算法,再碰真实硬件。
用 MATLAB 写倒立摆仿真程序,本质是把「建模—线性化—设计控制律—验证」这条链路完整跑通。程序的价值不在复现课本公式,而在每一环都变成可调、可看、可出错的代码。曲线发散、符号写反、权重设错,每个问题都会逼你把背后的理论再想一遍,这正是它作为入门项目至今没被替代的原因。
2. 建立倒立摆动力学模型,用 ode45 跑通开环 MATLAB 仿真
2.1 小车-摆杆模型的运动方程怎么来
常见做法取「小车 + 摆杆」二自由度模型,这也是自平衡小车、双轮机器人的通用等价模型。小车质量 M,摆杆质量 m,摆杆长度 l,重力加速度 g;状态取四个:小车位移 p、速度 ṗ、摆杆角度 θ、角速度 θ̇。θ 从直立位置算起,θ = 0 是竖直向上,θ = π 是自然下垂,外力 F 沿水平方向作用在小车质心。
用拉格朗日方程推出两个耦合的二阶方程:
(M+m) p̈ + m l θ̈ cosθ − m l θ̇² sinθ = F m l² θ̈ + m l p̈ cosθ − m g l sinθ = 0
θ̇² 项是摆杆旋转带来的向心加速度贡献,是非线性的主要来源之一,线性化时会被直接忽略,但它决定了开环大角度扰动下系统行为的真实性。θ 定义在直立点这一点很关键:教材从下垂点建模时符号会差一个负号,后面线性化、算 LQR 增益都会联动出错,建议在代码注释里固定这个约定。
2.2 把方程写成 ode45 能直接计算的函数
ode45 要求把方程改写成 dstate/dt = f(t, state) 的形式。上面的方程对 p̈ 和 θ̈ 是耦合的,手解显式表达式容易抄错,我一般用质量矩阵法:写成 M(q)·acc = rhs,在函数里用左除解加速度。
% cart_pole_dynamics.m function dstate = cart_pole_dynamics(t, state, params, F) % state = [p; p_dot; theta; theta_dot] M = params(1); m = params(2); l = params(3); g = params(4); p = state(1); p_dot = state(2); theta = state(3); theta_dot = state(4); Mq = [M + m, m * l * cos(theta); m * l * cos(theta), m * l^2]; rhs = [F - m * l * theta_dot^2 * sin(theta); m * g * l * sin(theta)]; acc = Mq \ rhs; % 解出 [p_ddot; theta_ddot] dstate = [p_dot; acc(1); theta_dot; acc(2)]; end逻辑说明:Mq \ rhs是求解线性方程组Mq * acc = rhs的标准写法,比inv(Mq) * rhs数值上更稳,尤其摆杆接近水平、矩阵条件数变大的区间,左除几乎不放大误差。刻意不展开 p̈ 和 θ̈ 的显式表达式,也是为了避免手写长公式时符号出错。
参数说明:params顺序固定为[M, m, l, g],调用处不要调换;F是外力,开环仿真先给 0。角度单位统一用弧度,绘图时再用rad2deg转。如果看到角度曲线一开始就往负方向走,说明初始状态符号与模型约定不一致,先检查约定,不要急着调控制器。
2.3 开环仿真:先让摆杆倒下,验证模型物理行为
初始状态我习惯取[0; 0; 0.1; 0],摆杆偏离直立约 5.7°,小车静止。零外力仿真两秒,摆杆应在重力作用下倒下,小车被反作用力推向另一侧。这一步是检验模型符号正确性的最快路径:摆杆往正方向倒,小车应该往负方向移动,才符合动量守恒。
% run_open_loop.m params = [1.0, 0.2, 0.5, 9.81]; % M=1kg, m=0.2kg, l=0.5m tspan = [0, 2]; state0 = [0; 0; 0.1; 0]; % 轻微偏离直立位 [t, states] = ode45(@(t, s) cart_pole_dynamics(t, s, params, 0), ... tspan, state0); figure; plot(t, rad2deg(states(:,3)), 'LineWidth', 1.2); xlabel('时间 (s)'); ylabel('摆杆角度 (deg)'); grid on;参数说明:tspan只给起点和终点时,MATLAB 自动决定输出点密度;后面做动画再改成linspace(0, 2, 2000)强制均匀输出。states(:,3)取角度整列,rad2deg只用于显示。跑完应看到角度从 0.1 rad 增长到接近 π;如果曲线变平或出现台阶,多半是误差容限下没采够点,用odeset('RelTol', 1e-6)作为第三个参数传入ode45再跑。
后续仿真统一用这张物理参数表,M、m、l、g 固定后,剩下的变量只有 Q、R 和初始角度:
| 参数 | 含义 | 数值 | 单位 |
|---|---|---|---|
| M | 小车质量 | 1.0 | kg |
| m | 摆杆质量 | 0.2 | kg |
| l | 摆杆长度 | 0.5 | m |
| g | 重力加速度 | 9.81 | m/s² |
| θ0 | 初始摆角(相对直立) | 0.1 | rad |
3. 倒立摆状态空间线性化与 LQR 控制器设计:Q、R 决定控制风格
3.1 在直立平衡点做线性化,写出 A、B 矩阵
闭环控制的目标是把摆杆维持在 θ = 0 附近。在这个平衡点做小角度近似:cosθ ≈ 1,sinθ ≈ θ,θ̇² 作为高阶小量忽略。代入上面的非线性方程并整理成 ẋ = Ax + Bu 标准形式:
A = [0 1 0 0; 0 0 -m*g/M 0; 0 0 0 1; 0 0 (M+m)g/(Ml) 0]
B = [0; 1/M; 0; -1/(M*l)]
x = [p; ṗ; θ; θ̇],输出取小车位移和摆杆角度时 C = [1 0 0 0; 0 0 1 0],D = 0。注意 A(4,3) = (M+m)g/(Ml),这一项随 l 增大而减小,特征值越靠近虚轴,系统越接近临界状态,这也解释了为什么实物摆杆越长越难控制。线性化模型只负责设计控制器;仿真验证必须回到非线性模型,两者不要混用。
3.2 用 lqr() 算反馈增益,Q、R 按这个顺序调
LQR 极小化 J = ∫(xᵀQx + uᵀRu)dt,Q 是状态权重对角阵,对应 [p, ṗ, θ, θ̇];R 是控制力惩罚标量。我一般先定 θ 的权重(控制核心目标),再定 p 的权重(限制小车行程),速度项初始都给 1,最后微调 R。
| 权重 | 对应状态 | 典型初值 | 增大后的效果 |
|---|---|---|---|
| Q(1,1) | 小车位移 p | 10 | 小车更快回中,行程变小 |
| Q(2,2) | 小车速度 ṗ | 1 | 运动更平滑,阻尼感增强 |
| Q(3,3) | 摆杆角度 θ | 100 | 立得更直,但控制力峰值变大 |
| Q(4,4) | 角速度 θ̇ | 1 | 抑制摆杆高频抖动 |
| R | 控制力 F | 0.1 | 越大越省力,响应越慢 |
代码直接用 Control System Toolbox 的lqr()函数:
% design_lqr.m M = 1.0; m = 0.2; l = 0.5; g = 9.81; A = [0 1 0 0; 0 0 -m*g/M 0; 0 0 0 1; 0 0 (M+m)*g/(M*l) 0]; B = [0; 1/M; 0; -1/(M*l)]; Q = diag([10, 1, 100, 1]); R = 0.1; K = lqr(A, B, Q, R); fprintf('反馈增益 K = [%.4f, %.4f, %.4f, %.4f]\n', K);参数说明:diag把四个权重排成对角阵,顺序必须与状态定义一致,否则相当于对错误的物理量加权。lqr默认求解连续时间代数黎卡提方程,返回 1×4 行向量。第一次运行建议打印出来看符号:K(3) 必须为正,摆角才能被拉回零;如果为负,优先检查 A 矩阵里 θ 的符号约定,而不是调权重。
提示:没有 Control System Toolbox 时,可以用
care()求解黎卡提方程得到 P,再按 K = R⁻¹BᵀP 手算反馈增益,结果与lqr()一致。
3.3 把 u = −Kx 接回非线性模型,跑出真正的闭环响应
控制器基于线性模型设计,但必须作用在cart_pole_dynamics这个非线性函数上,否则验证毫无意义。闭环脚本只用一个匿名函数包住反馈律,就能复用第 2 章的动力学函数:
% run_closed_loop.m params = [1.0, 0.2, 0.5, 9.81]; K = [-3.1623, -3.1769, 41.0955, 8.8022]; % 占位增益,以 lqr 输出为准 tspan = [0, 5]; state0 = [0; 0; 0.1; 0]; [t, states] = ode45(@(t, s) cart_pole_cl(t, s, params, K), tspan, state0); plot(t, rad2deg(states(:,3))); xlabel('时间 (s)'); ylabel('摆杆角度 (deg)'); grid on; function dstate = cart_pole_cl(t, state, params, K) u = -K * state; % 状态反馈,u 是标量控制力 dstate = cart_pole_dynamics(t, state, params, u); end逻辑说明:匿名函数把时间、状态、参数一次性传入闭环函数,内部先算u = -K * state,再调用非线性动力学。K 的第 3 列直接作用于摆角,所以它的符号和数值决定了控制方向与力度。运行后角度应在 1~2 秒内收敛到 0 附近并保持,小车位移同时被拉回原点附近。
参数说明:示例 K 只为了让脚本能直接跑,实际请用design_lqr.m的输出替换。把state0的第三行改成 0.5 再跑,会发现收敛明显变慢甚至失败——这不是代码问题,而是线性控制律在大角度区间的固有局限,也是后续进阶到滑模或能量控制(swing-up)的出发点。
4. 把倒立摆仿真搬进 Simulink,并输出一个摆杆动画
4.1 用 S-Function 在 Simulink 里复用同一个动力学函数
脚本仿真适合调参和跑批量数据,但演示控制逻辑时 Simulink 更直观。常见做法是搭一个环形结构:S-Function 封装非线性动力学,Mux 把状态汇总,Gain 矩阵乘状态得到控制力,Scope 看曲线。很多人纠结要不要在 Simulink 里重写一遍动力学,其实不用:直接复用cart_pole_dynamics.m,注册成 Level-2 S-Function,模型逻辑和脚本完全一致,避免两套模型对不上。
| Simulink 模块 | 关键设置 | 作用 |
|---|---|---|
| S-Function | 名称cart_pole_sfun,参数params | 计算四个连续状态的导数 |
| Mux | 4 输入 1 输出 | 汇总状态向量 |
| Gain | 矩阵-K,乘法模式 | 计算控制力 u = −Kx |
| Scope | 无 | 观察角度、位移曲线 |
Level-2 S-Function 有固定模板,核心是三个回调:InitializeConditions里把初值写入连续状态,Derivatives里调用动力学函数,Outputs里把状态透传出去。完整模板用edit sfuntmpl生成,按下面这个骨架填:
% cart_pole_sfun.m (Level-2 S-Function 回调核心) function cart_pole_sfun(block) setup(block); function setup(block) block.NumInputPorts = 0; block.NumOutputPorts = 1; block.NumContStates = 4; block.NumDialogPrms = 2; % 初值向量、物理参数 block.SetPreCompOutPortParamToDynamic; block.SampleTimes = [0 0]; % 连续采样 block.RegBlockMethod('InitializeConditions', @Init); block.RegBlockMethod('Derivatives', @Deriv); block.RegBlockMethod('Outputs', @Output); function Init(block) block.ContStates.Data = block.DialogPrm(1).Data; % 初值向量 function Output(block) block.OutputPort(1).Data = block.ContStates.Data; function Deriv(block) params = block.DialogPrm(2).Data; % [M m l g] F = 0; % 开环测试 block.Derivatives.Data = cart_pole_dynamics(0, ... block.ContStates.Data, params, F);参数说明:DialogPrm是 S-Function 的 mask 参数,这里约定第一个存初值[0;0;0.1;0],第二个存[M,m,l,g],顺序不要和动力学函数里的params混。SampleTimes = [0 0]声明连续系统,Simulink 求解器才能用变步长 ode45 驱动它。上面骨架是开环版本;做闭环时把NumInputPorts改为 1,在Derivatives里读block.InputPort(1).Data作为 F 即可。
4.2 用 patch + line 写一个可复用的小车摆杆动画
动画是报告和答辩里最能说清楚问题的输出。思路很简单:每画一帧,删除上一帧的小车矩形和摆杆线段,循环滚动。MATLAB 的patch适合画矩形,line画摆杆,delete做擦除。
% animate_cart_pole.m figure('Color', 'w'); hold on; axis([-3 3 -0.6 1.8]); axis equal; grid on; car_w = 0.4; car_h = 0.1; step = max(1, floor(length(t) / 500)); % 把总帧数压到 500 左右 % 这里的 states 来自 run_closed_loop.m 的输出 for k = 1:step:length(t) p = states(k,1); th = states(k,3); car = patch([p-car_w/2 p+car_w/2 p+car_w/2 p-car_w/2], ... [0 0 car_h car_h], [0.55 0.75 1.0]); pole = line([p, p + l*sin(th)], ... [car_h, car_h + l*cos(th)], ... 'LineWidth', 4, 'Color', 'k'); drawnow; pause(0.02); delete(car); delete(pole); end逻辑说明:patch用四个顶点画小车矩形,line从车顶中心画摆杆,摆杆末端由l*sin(th)和l*cos(th)从角度换算得到;drawnow强制刷新画布,不加它动画会攒到最后一次性显示。delete删掉旧帧,等效于擦除重画,比clf整体清屏快得多,画面也不闪烁。
参数说明:step控制抽帧密度,总帧数压到 500 左右,避免状态点太多导致动画卡顿;pause(0.02)控制帧间间隔,改小就是加速播放。脚本里l用params(3)取出,不要硬编码;角度和位移的单位保持一致,绘制前统一换算好。
提示:动画速度不等于仿真速度。
tspan的物理时间、pause的墙钟时间、drawnow的实际刷新率三者独立,验收只看动画行为,不要试图严格对齐时间轴。
5. 倒立摆仿真排错三招:先看发散,再看符号,最后验极点
5.1 三个最常踩的坑
仿真发散、曲线不收敛,绝大多数时候不是控制器不行,而是模型层的问题。下面三个坑按排查优先级排好,照表检查比反复调 Q、R 更快:
| 现象 | 原因 | 处理 |
|---|---|---|
| 角度直接变成 NaN | 求解器发散,步长过大或初始角过大 | 缩小RelTol,初值降到 0.3 rad 内 |
| 小车朝一个方向匀速漂移 | 有稳态力残留,θ 符号大概率反了 | 检查 A 矩阵第三行,确认 K(3) > 0 |
| 闭环能保持直立但摆动幅度大 | 权重不匹配,Q(3,3) 过小或 R 过大 | 按 3.2 表格增大角度权重、减小 R |
5.2 用闭环极点位置和扰动峰值做验证
验证 LQR 设计是否合理,有两个不依赖肉眼观察的办法。一是直接读闭环极点,eig(A - B*K)应全部落在左半平面,主导极点实部对应时间常数 τ ≈ 1/|Re(λ)|,收敛时间约为 3~5τ,可以反过来检验 Q、R 是否过保守。二是给小车加一个持续 0.2 秒的阶跃力扰动,记录摆角最大偏差和控制力峰值,和物理执行器的极限做对比。
% verify.m eig(A - B*K) % 所有特征值负实部即收敛 [max_dev, idx] = max(abs(states(:,3))); % 扰动峰值 fprintf('最大摆角偏差 = %.3f rad, 时间 %.2f s\n', max_dev, t(idx));参数说明:eig的结果若出现共轭复根,虚部对应振荡频率,实部对应衰减速率;max返回峰值和位置,峰值超过 0.35 rad 说明增益偏弱,回到第 3 章的表加大 Q(3,3)。固定 M、m、l、g 四个物理参数,只动 Q、R,是保证发散时可定位问题的前提。仿真收敛后,这套 K 可以直接搬到实物控制器上,换掉ode45为周期采样循环即可,算法本体不用改。
本文还有配套的精品资源,点击获取