简介:这是一份面向控制理论与自动化初学者、以及正在学习MATLAB/Simulink仿真的学生的倒立摆仿真资源,完整覆盖从非线性动力学建模、控制器设计到Simulink仿真验证的典型流程,可用于课程设计、实验报告或科研入门。资源包内共9个文件,压缩后仅372KB,包含4个MATLAB脚本(.m)用于编写状态方程与控制律,1个Simulink模型文件(.slx)及对应的r2014a版本、1个.mdl文件,另有PPT和Word文档用于讲解原理与记录实验。平台上已有2356人学习浏览,说明其可参考价值得到一定验证。内容不仅给出具体模型文件,还详细阐述倒立摆的数学模型(摆杆角度与角速度)、PID/LQR等控制策略、Simulink子系统搭建以及仿真参数设置要点,并附带参考文献,便于对照学习与二次开发。对于希望在仿真环境中快速上手倒立摆控制的读者,按文档步骤即可从搭模型、跑仿真到分析结果一气呵成,是理论与实践结合的不错案例。
1. 倒立摆仿真为什么总在第一步翻车:建模约定比PID参数更先决
倒立摆常被当成自动控制课的“hello world”,但真要放到MATLAB/Simulink里仿真,劝退率并不低:许多人花一下午拖模块,一按运行就发散,最后只能把锅甩给“PID没调好”。我做过的项目里,这类翻车多半不在参数,而在建模这一层:坐标系约定、单位、初始条件、反馈极性,任何一个不对,后面调三天增益都是白费。
这篇文章讲一条不绕弯的路线:在Simulink库下先用动力学方程搭出非线性倒立摆模型,再用串级PID把杆立住、把小车拉回原点,最后用状态反馈和动画回放做一次交叉验证。适合正在做课程设计的新手,也适合需要快速验证控制算法、又不想被模型黑匣子坑到的从业者。
读完你会得到一整套能复现、能排查、能说服人的仿真流程,而不是一个“碰巧能站稳一次”的模型文件。
2. 先把物理模型立住:从非线性方程到Simulink的建模选型
2.1 坐标系与自由度:先定义θ=0意味着“杆竖在天上”
这里讨论的直线倒立摆,由一节小车和一根摆杆组成:小车在水平导轨上移动,摆杆铰接在小车顶端,控制输入是作用在小车上的水平力F。系统有两个自由度,即小车位移x和摆杆角θ。第一步要做的不是拖模块,而是把所有约定写死在注释里:θ=0必须定义为摆杆严格竖直向上、杆头朝天,逆时针为正。很多教材把θ=0定义在竖直向下,两套约定下仿真结果看起来方向完全相反,反馈极性、初始值、控制器符号都得跟着翻,这是新手第一个隐蔽坑。
单位也要在初始脚本里统一。角度全程用弧度,长度用米,质量用千克,时间用秒,力用牛顿。我见过有人在PID里角度用度、积分时间用分钟,结果增益调了一个通宵都没找到规律。单位不统一时,仿真里的数值就是无意义的数字,任何“经验参数”都套不上去。
| 符号 | 含义 | 初版取值 | 单位 |
|---|---|---|---|
| M | 小车质量 | 0.5 | kg |
| m | 摆杆质量(集中质心) | 0.2 | kg |
| l | 摆杆质心到铰链距离 | 0.3 | m |
| g | 重力加速度 | 9.81 | m/s² |
| b | 车体摩擦系数 | 0(首版) | N·s/m |
| d | 关节阻尼系数 | 0(首版) | N·m·s/rad |
| F | 控制力(输入) | 由控制器给出 | N |
| x | 小车位移 | 初始0 | m |
| θ | 杆角,竖直向上为0 | 初始0.1 | rad |
2.2 从牛顿方程到Simulink可用形式:非线性方程的整理思路
摆杆按集中质量处理,质心距铰链l,转动惯量写为ml²。对课程设计和快速原型,这个近似足够。小车水平方向的受力方程为:
(M+m)ẍ + ml·θ̈·cosθ − ml·θ̇²·sinθ = F
摆杆绕铰链的转动方程为(暂不计摩擦阻尼):
ml²·θ̈ + ml·ẍ·cosθ − mgl·sinθ = 0
第一式里,ml·θ̈·cosθ是摆杆质心水平加速度带来的惯性耦合,ml·θ̇²·sinθ是向心加速度项;第二式三项分别对应角加速度、平动与转动耦合、重力项。首版把b和d都置0,是想让模型只剩一个控制输入和两个二阶状态,出问题时能一眼定位;等控制器能立住杆,再往两式右侧补−bẋ和−dθ̇。做仿真模型的优先序是:先让结构对,再谈参数复杂度。
把两式联立消去中间变量,得到适合在Simulink里直接计算的两条表达式:
θ̈ = [−F·cosθ − ml·θ̇²·sinθ·cosθ + (M+m)·g·sinθ] / [l·(M + m·sin²θ)]
ẍ = [F − ml·θ̈·cosθ + ml·θ̇²·sinθ] / (M+m)
我习惯先算θ̈,再把θ̈代回去求ẍ,而不是做二次消元直接得ẍ的闭式。原因是Simulink里信号是“流”,两级计算都有独立输出,Scope一接就能确认哪一级算错。如果只用一个长表达式写完ẍ,后期排查时根本分不清是符号错、单位错还是连错线。
2.3 建模路线选型:手算方程 vs Simscape物理建模
Simulink库下搭倒立摆有两条常见路线。路线A是手推方程,用Integrator、Mux、Gain、MATLAB Function这些基础模块搭出来;路线B是用Simscape Multibody物理库,直接拖一个小车、一个旋转关节、一根摆杆,物理库自动解算。两条路线我都跑过,倾向很明确:第一版用A,验证阶段用B。
路线A的每个状态都看得见,PID反馈接在哪、哪个信号进了控制器,出任何现象都能一步步看Scope定位。路线B确实更接近真实机构,可视化也漂亮,但模型内部的坐标变换和解算对新手几乎是个黑匣子,解算器更慢,代数环排查也更难。两条路线的参数必须统一,比如杆长:Simscape里可能填整根杆的长度,手算公式里l是质心到铰链的距离,两边如果混用,曲线就会硬差一个系数。
| 建模路线 | 主要优势 | 主要代价 | 适用场景 |
|---|---|---|---|
| 基础库手算(路线A) | 信号完全可见、接线直观 | 公式要自己推,模型是理想化近似 | 课程设计、PID/LQR设计验证 |
| Simscape Multibody(路线B) | 物理细节真实、自带三维可视化 | 解算慢、内部状态不透明 | 机构对标、方案演示、与A互验 |
2.4 线性化与“模型够用”的判断
在跑仿真之前,建议把线性化模型也准备好。在θ=0邻域取sinθ≈θ、cosθ≈1、θ̇²项直接舍去,非线性方程变成状态空间形式。取状态向量X=[x; ẋ; θ; θ̇],控制量为F,得到:
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)]
这个线性模型有两个用途。第一,第6章的LQR增益就是由这组A、B算出来的;第二,它可以充当校验基准:在同样的初始角度θ0=0.1下,线性模型和非线性模型前一两秒曲线应几乎重合,超过±30°后才明显分叉。如果一上来就分叉,线性化大概率写错了。
把A、B填进Simulink库的State-Space模块,C取单位阵、D取零矩阵,就能跑一个线性对照模型。这个“模型够用”的判断标准很实用:控制器设计在线性模型上做,最终可信度必须在非线性模型上验证。
3. 在Simulink里搭出非线性倒立摆:模块清单与初始化脚本
3.1 最小可运行模型:模块列表与信号路线
新建一个空白模型,建议按“从右到左”的风格搭:最左边是控制器反馈,中间是动力学计算,最右边是积分器链和可视化。最小集合只需要这些模块:
| 模块 | 数量 | 作用 |
|---|---|---|
| MATLAB Function | 1 | 计算θ̈和ẍ两条二阶导 |
| Integrator | 2 | 把θ̈、ẍ分别积出速度和位置 |
| Mux | 1 | 把F、ẋ、θ、θ̇打包成四路输入向量 |
| PID Controller | 2 | 角度内环与位置外环 |
| Saturation | 1 | 限制控制力输出,首版设±20N |
| Scope | 2 | 观察角度与位移曲线 |
信号主干是:F → Mux → 动力学块 → θ̈、ẍ → 各自积分链 → 反馈。位移链的中间速度ẋ要从第一个积分器输出端引出,角度系统的θ̇同样从积分器链中间引出,不要用位置信号差分去求速度,差分会把数值噪声放大。Simulink里同一信号允许并联多个输出,控制器反馈线和示波器线可以同源,不会互相干扰。
3.2 参数初始化脚本:单位与初值都写清楚
所有物理参数不要直接写在模块参数里,而是放到基础工作区用脚本定义。模块参数一多,漏改一个就会变成“不知道角度为什么总是发散的玄学问题”。把下面脚本存成prob_init.m,每次改完参数先运行它再启动仿真。
%% 倒立摆物理参数(国际单位) M = 0.5; % 小车质量 kg m = 0.2; % 摆杆质量 kg l = 0.3; % 摆杆质心到铰链距离 m g = 9.81; % 重力加速度 m/s^2 b = 0; % 车体摩擦,首版先置0 d = 0; % 关节阻尼,首版先置0 %% 仿真与初始条件 theta0 = 0.1; % 初始杆角 rad,竖直向上为0,0.1约5.7° x0 = 0.0; % 初始小车位置 m xd0 = 0.0; % 初始小车速度 m/s Tsim = 5.0; % 仿真时长 stheta0取0.1rad而不是0,是有意的:初始完全静止在θ=0、θ̇=0时,系统处在数学上的不动点,控制器没有误差信号,仿真再久也看不出控制器好坏。取0.1让反馈先“干活”,又不会偏离线性区太远。b和d先置0的道理在前面讲过,等基础结构调稳再加摩擦。
3.3 用MATLAB Function封装动力学计算
双击Simulink库里的MATLAB Function模块,输入下面的函数体。模块对外显示四个输入端和两个输出端,对应关系是:输入1为控制力F,输入2为当前车速xdot,输入3为当前杆角theta,输入4为当前角速度thetadot。
function [xddot, thetaddot] = pendulum_dyn(F, xdot, theta, thetadot) % 小车-摆杆非线性动力学 % 输入: F(N), xdot(m/s), theta(rad), thetadot(rad/s) % 输出: 位移二阶导xddot, 角度二阶导thetaddot M = 0.5; m = 0.2; l = 0.3; g = 9.81; % 与prob_init.m保持一致 den = l * (M + m * sin(theta)^2); thetaddot = (-F*cos(theta) - m*l*thetadot^2*sin(theta)*cos(theta) ... + (M+m)*g*sin(theta)) / den; xddot = (F - m*l*thetaddot*cos(theta) + m*l*thetadot^2*sin(theta)) / (M+m); end代码逻辑是把2.2的两条表达式逐段翻译。den先算分母,避免同一个分母在长表达式里重复出现,也方便检查量纲。特别提醒一句:MATLAB Function里的物理参数最好不要依赖外部工作区,直接把常量写死在函数体内,与prob_init.m手动保持一致。有人会把M、m、l也做成输入端口,那样更工程化,但首版接口多了容易接错,我宁可简洁优先。
3.4 双积分器链与示波器:把状态接进控制器
接线顺序建议这样走:动力学的第一个输出θ̈接第一节Integrator得到θ̇,再接第二节得到θ;第二个输出xddot同样接两条积分链得到xdot和x。角度环的反馈接到内环PID的测量端口,位置环反馈接到外环PID的测量端口,示波器接最终θ和x。
一个特别值得养成的习惯:跑第一次仿真前,把PID增益全部置0,让倒立摆在θ0=0.1rad下自然倒下去。如果θ在零点几秒内单调加速倒向水平,说明动力学块、符号、积分链大概率正确;如果一开始就乱跳或反向,说明有根线接错,或者坐标系约定没统一。这是动力学模块最便宜的验收方式,不要跳过。
提示:开环让杆子倒下是正常物理,不倒反而说明模型有隐藏约束或初始条件填错。
4. 串级PID整定与求解器参数:让杆立住、让车回中
4.1 控制结构先定:串级PD-PID双环,为什么不能只控角度
只把θ反馈回去、输出力F,仿真三秒内θ可能稳住,但小车位置会匀速向一个方向漂。原因是单角度环的控制目标是θ=0,系统对小车位移没有观测:作用在小车上的力既要让杆不倒,又会推动小车本身,力里的任何直流分量都会让小车加速。物理上就是杆立住了、小车撞到导轨尽头,照样失败。
所以需要串级结构:位置外环把x_ref与实际x的误差过PID_x,输出θ_ref作为角度环的期望值;角度内环把θ_ref与θ之差过PID_θ,输出力F。内环必须比外环快一个数量级:内环用来快速抵抗重力扰动,外环只是慢慢把小车拉回原点。我一般让内环调节时间在0.3s内,外环允许2到5s。如果外环输出的θ_ref超过±0.5rad,说明位置环增益过大或饱和设置太松,模型很容易翻。
| 回路 | Kp | Ki | Kd | 起始说明 |
|---|---|---|---|---|
| 角度内环 | 80 | 0 | 6 | 先从PD起,积分留给后续稳态误差处理 |
| 位置外环 | 4 | 0 | 1.5 | 外环要慢,增益不要追着内环跑 |
| 控制力饱和 | ±20 | — | — | 防止极端误差下力过大导致步长塌陷 |
4.2 增益起步值与整定顺序
上表的数值不是最优解,是“起步值”,按2.1的物理参数量级估算。换一组参数,要从系统特征频率重新估算:角度方程线性化后θ̈≈(M+m)g/(Ml)·θ,括号里的量就是开环不稳定极点量级,Kp要大到能把这个极点拉到左半平面,Kd再加阻尼。
整定顺序按三步走:
- 先断外环:把θ_ref直接给常数0,只调角度环。Kp_θ从20开始,每次×1.5往上加,直到θ能在0.3s内回正且无持续振荡;如果高频抖,加大Kd_θ。
- 再接外环:位置环Kp_x从1起步,逐步加到小车5s内回中。若发现θ_ref高频摆动,优先减小Kd_x或降Kp_x。
- 最后看控制力饱和:Scope里观察F曲线,如果频繁撞±20N限幅,说明控制器用力过猛,要降增益或检查模型参数。
这条顺序的核心是“先让内环可信,再让外环工作”。位置环没接时,角度环其实是在一个不稳定的零极点结构上工作,只要角度能回正,就说明内环已经压住了主线动力学。
4.3 求解器与步长设置:不同表现下的选择
Simulink默认的变步长ode45对大多数连续模型够用,但倒立摆有个典型问题:角速度可能到每秒数弧度,位移却常以厘米每秒量级变化,跨轴数值尺度差异大,容易触发自适应步长剧烈收缩。求解器这块建议按表配置:
| 配置项 | 推荐值 | 适用场景 |
|---|---|---|
| 求解器类型 | 变步长 | 连续模型 + 连续PID |
| 求解器名称 | ode45(默认) | 常规验证 |
| 最大步长 | 1e-3 | 防止大步长跨过振荡峰值 |
| 相对误差 | 1e-4 | 精度与速度的折中 |
| 固定步长 | 1e-3 | 把PID改成离散模块之后使用 |
如果仿真越跑越慢,先从求解器入手,把ode45换成ode15s或ode23t试试。另外,当PID Controller里开启离散时间积分器,或者直接把PID模块换成离散版本时,模型就混入了固定采样,此时建议把整个求解器改成固定步长ode4、步长1e-3,否则会报采样时间冲突,或者曲线出现奇怪的台阶。
4.4 判定“立得住”的验收指标
仿真跑完,别只看“好像稳了”就收工。给一组我常用的验收指标:角度稳态误差在±0.01rad以内;位置稳态误差在±0.02m以内;从θ0=0.1rad起步,调节时间不超过5s;控制力不长期停留在饱和限幅;角度曲线没有高频毛刺。毛刺明显时,优先怀疑仿真输出采样间隔太大,或者Kd过大会放大测量噪声。
把这组指标写成一个判断表,每次改完参数对照打钩。倒立摆仿真最大的错觉就是“曲线没散架=控制成功”,没有量化指标,后面换一组参数或换台机器跑,结果可能完全不同。
5. 五个高频倒立摆仿真翻车点与排查顺序
5.1 代数环报警:一运行就报“Algebraic loop”
现象:点击运行后Simulink提示检测到代数环,模型要么卡在第一步,要么仿真进度条几乎不动。
原因:控制器输出的力F经过动力学块和反馈,又在某个没有积分器的闭合回路里影响同一个F。最常见的情况是动力学计算里直接引用了刚算出的加速度,又用这个加速度参与计算力,形成直通回路。
解决:按“F → 动力学 → 积分速度 → 积分位移 → 反馈 → 控制器”的顺序检查每一段,确认所有影响力的路径都经过至少一个Integrator。确实需要直通的场景,在反馈支路插入Memory或Unit Delay,用一个小延迟打断闭环。代数环有时不报错也能跑,但它会让变步长求解器每一步反复迭代,表现就是“卡”。
5.2 角度初始值设反:θ=0躺平不动,一碰就爆
现象:把初始角度设为0,仿真却“什么都没发生”;稍给一个扰动,模型立刻发散。θ=0看起来就是竖直,怎么都不该错。
原因:很多教材把θ=0定义为竖直向下,或Y轴方向取反。坐标系约定不一致,导致初始状态、重力方向、控制器反馈极性全部错位。
解决:在prob_init.m注释里显式写清楚“θ=0=竖直向上,逆时针为正”。跑前先画一个初始位形图,把小车、摆杆按初值画出来,亲眼确认杆子立着再启动仿真。想最快验证反馈极性,把θ0设成0.1rad,看前0.2s内角度是否回中;如果角度被“推离”零点,把PID输出取反即可。
5.3 角度在±180°附近跳变:控制器输出瞬间反向
现象:角度曲线整体正常,偶尔竖着跳一下,控制力同时反向,模型随后发散。
原因:matlab里的atan2或wrapToPi返回值都落在(−π, π],角度跨过±π时,θ从+π直接跳到−π,连续变化被显示成阶跃。控制器把这个阶跃当成真实误差,输出瞬间反向。
解决:进PID之前,对反馈角度做unwrap,或把角度差用wrapToPi包回(−π, π]再计算。如果你的初始θ0超过π/2,摆杆已经越过水平线,普通PID很难救回来,先把θ0约束在±30°以内。这个坑在“摆起控制”里尤其常见,做摆动起摆时必须单独处理角度跳变。
5.4 步长塌陷:仿真越跑越慢,像被卡住
现象:前1秒跑得飞快,越往后进度条越慢,CPU单核占用拉满,仿真时长却几乎不前进。
原因:控制器饱和或角度接近水平时,方程出现大梯度和高频振荡,ode45自适应步长被压到极小;也可能是5.1的代数环没清干净,迭代开销随状态变化暴增。
解决:先在控制器输出串一个Saturation限幅(比如±20N),从物理上防止力爆炸;再把求解器换成ode15s或ode23t,并设置最大步长1e-3。如果仍然慢,打开模型设置里的求解器统计信息,看平均步长是否小到1e-6量级,是就说明某个信号存在高频抖动,优先查Kd和控制力饱和,而不是继续调求解器。
5.5 角度稳住但小车一直漂:位置环没接
现象:杆子立住了,小车却整体匀速或缓慢漂离原点,直到撞上导轨边界。
原因:只做了角度反馈,系统对x没有闭环控制,x对应的极点是积分模式,任何直流分量都会让位置持续累积。
解决:把x_ref=0与x做差,经过位置环PID后输出θ_ref。位置环增益从Kp_x=1起步,观察小车是否回中。如果位置环和角度环开始“互相打架”,出现缓慢振荡,把位置环Kp_x降到1以下,让外环比内环慢一个数量级。这个坑在课程设计里出现频率极高,但根因不是控制器不行,而是控制目标没定义全。
6. 用状态反馈与动画回放收尾:仿真结果怎么才叫可信
6.1 用lqr算增益:状态反馈一次到位
PID调稳只代表“这组增益在这个模型上有效”,不代表你理解了系统能控性。把线性模型交给lqr,是让结果可复现、可解释的兜底方案。在prob_init.m的参数基础上,构造线性状态空间模型并直接求增益:
% 状态向量 X = [x; xdot; theta; thetadot] 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)]; C = eye(4); D = zeros(4,1); Q = diag([10 1 100 1]); % 角度权重远大于位移 R = 0.01; % 控制力成本 K = lqr(A,B,Q,R);Q和R的调法有规律可循:Q里的角度对应分量给到100左右,位移给10左右,速度类状态给1,让控制器优先保杆;R越小,控制越“用力”,增益越大。求出的K是一个1×4行向量,四个分量分别对应x、xdot、theta、thetadot。
6.2 把K带入模型:用Gain模块实现状态反馈
把状态向量用Mux合成四路信号,接一个Gain模块,增益设为K,输出取负后作为F。这样就不需要PID双环了,结构更干净。建议在同一个Scope里画PID方案和LQR方案的θ曲线对比:权重拉开后,LQR的超调和调节时间会更有规律性。再把θ0改到0.3rad重跑一次,看哪套方案还能稳住,这是检验控制器鲁棒性最省钱的办法。
6.3 动画验证与数据导出:仿真结果可复现
Scope截图不能算验证,把数据导出成t、theta、x三个向量,再用动画回放,才算闭环。导出时注意把仿真数据设为离散输出,否则变步长求解器给出的时间点不均匀,动画会忽快忽慢。下面这个脚本按固定帧数回放:
figure; for k = 1:20:length(t) xb = x(k); th = theta(k); xtip = xb + l*sin(th); ytip = l*cos(th); clf; hold on; plot([xb-0.4 xb+0.4], [0 0], 'k', 'LineWidth', 3); plot([xb xtip], [0 ytip], 'r', 'LineWidth', 2); plot(xtip, ytip, 'ro', 'MarkerSize', 6); axis equal; xlim([xb-1.5 xb+1.5]); ylim([-0.2 1.2]); title(sprintf('t=%.2fs, theta=%.3f rad', t(k), th)); drawnow; endxlim跟随小车移动,是为了防止杆子飞出视野;每20个点取一帧,减少刷新负担。我自己的习惯是,无论PID调得多顺,交出去之前一定用LQR加动画核一遍,确认不是只能在一个初始角度下成立的脆弱模型。这个习惯救过我很多次,让我少走不少冤枉路——仿真能复现、能解释,才敢说这步做完了。希望帮到你。
本文还有配套的精品资源,点击获取