读到“PID期刊论文复现之多旋翼飞行器动力学建模与控制系统设计研究(Simulink仿真实现)”这个标题,懂行的人一眼就能看出来,这是一条典型的“论文方法复现+仿真验证”工作流。标题里最核心的三个要素是PID、动力学建模、Simulink仿真,合在一起解决的是一个很实际的问题:怎么让一台多旋翼飞行器在给定目标位置或期望轨迹下飞得稳、跟得准。这篇文章把我完整走通这条流程的过程写下来,模型怎么建、控制器怎么分、仿真环境怎么搭、参数怎么调、最后踩了哪些坑,全部整理成一份能直接照着做的参考。
这个题目表面上叫“复现”,但真正交付的东西其实是一套可运行的闭环仿真平台。论文里往往只给公式和几句话,工程细节全靠自己补。我做完之后把整个工作拆成三条线:刚体动力学建模、级联PID控制器设计、Simulink非线性仿真验证。这三条线是串行耦合的,模型错了,控制器再漂亮也是空中楼阁。这篇文章适合正在做类似方向的学生、刚入门多旋翼控制的新工程师,也适合需要用仿真验证自定义控制算法的人。下面按我实际操作的顺序,把整个项目从拆解到实现一步步讲清楚。
1. 标题拆解与项目整体思路
1.1 看似是“复现”,实际要交付的是三件事
“PID期刊论文复现”这几个字很容易让人理解成照着论文公式抄一遍的搬运工作。但实际动手就知道,论文原版公式往往省略了大量参数、坐标系和单位细节,真正要复现的是一套“能跑出收敛曲线的闭环仿真”。我做完后把工作拆成三个明确产物:
- 动力学模型:用牛顿-欧拉方法建立多旋翼的6自由度运动方程,这是仿真结果能否贴近真实飞行的基础。
- 控制器结构:采用内外环级联PID,解决位置、姿态、角速度之间的强耦合问题。
- Simulink仿真环境:把连续方程转成框图或MATLAB Function,配合求解器、数据记录和可视化完成闭环验证。
我参考的是一篇以四旋翼建模和控制为背景的期刊论文,结构上就是最经典的“刚体模型+内外环PID+非线性仿真验证”。但论文里很多参数不会直接给出,坐标变换也有可能被一笔带过,把这些细节补全到“能动手做”的程度,才是复现工作真正的价值所在。
1.2 整体结构:按数据流拆模块
为了防止自己陷进一坨公式里,我先把整个系统按数据流方向拆成几个独立模块:
| 模块 | 输入 | 输出 | 对应内容 |
|---|---|---|---|
| 参考输入生成器 | 分段阶跃/轨迹 | 期望位置、期望偏航 | 仿真测试信号 |
| 位置控制器(外环) | 位置误差、速度误差 | 期望滚转角、俯仰角、总拉力 | PID |
| 姿态控制器(内环) | 姿态角误差、角速度误差 | 三轴力矩指令 | PID/PD |
| 力矩分配 | 总推力、三轴力矩 | 四电机转速/推力 | 混控矩阵 |
| 执行器模型 | 期望转速 | 实际推力、力矩 | 一阶惯性+饱和 |
| 刚体动力学 | 总推力、力矩 | 位置、姿态、角速度导数 | 牛顿-欧拉方程 |
| 积分更新状态 | 加速度、角加速度 | 位置、速度、欧拉角、角速度 | 积分器 |
这个结构最大的好处是每一级都可以单独验证。我把模块边界定得非常明确:控制器只输出“力/力矩”这种物理量,电机模型单独处理。调试时甚至可以先用“直接给力矩”的方式验证控制器逻辑,再接执行器模型,排错范围一下缩小很多。
为什么采用这种结构?因为四旋翼本质上是一个多变量强耦合系统:滚转和俯仰不仅改变姿态,还会改变推力在水平方向的分量;偏航又和水平运动存在坐标变换耦合。如果不做分层,直接设计一个4输入4输出的PID矩阵,参数会变得极难整定。内外环级联是最直观、最易调,同时被工程验证过的主流做法。
2. 动力学建模:从牛顿第二定律到Simulink
2.1 刚体模型:为什么起点选牛顿-欧拉
多旋翼建模路线有很多种:牛顿-欧拉、欧拉-拉格朗日、四元数模型,甚至包含柔性效应的建模。在论文复现这个维度上,牛顿-欧拉是最省事的一条:方程数量少、物理意义直白、转成Simulink积分链时非常顺手。
我用的状态向量是12维:
- 位置 p = [x, y, z](世界坐标系)
- 速度 v = [u, v, w](世界坐标系)
- 欧拉角 [φ, θ, ψ](滚转、俯仰、偏航)
- 角速度 [p, q, r](机体坐标系)
在机体坐标系下,电机对机体产生的总推力沿z轴方向,重力沿世界坐标系z轴方向,所以位置动力学方程写成:
m * a_world = m * g * e3 - R * T * e3 - 空气阻力项这里的R是机体坐标系到世界坐标系的旋转矩阵,T是电机总推力。符号方向取决于你采用的坐标系朝向,Simulink模型里最容易出错的也是这一步。我建模时专门写了一个“旋转矩阵方向验证”的静态测试:给一个90°滚转输入,检查模型的x方向推力投影符号是否正确。这个测试能省掉后面一整周排查时间。
姿态动力学部分:
J * ω_dot = M - ω × (J * ω)J是转动惯量矩阵,M是滚转、俯仰、偏航力矩向量。在初始建模阶段,我不会一步到位把所有非线性项都加上,而是先忽略陀螺力矩等小项,让仿真先跑起来,后面再加扰动项。模型复杂度逐步递增,而不是一次性塞满全部物理效应,这个策略能极大降低排查难度。
2.2 电机的推力与力矩:别小看执行器模型
很多人只搭刚体部分,直接把PID输出的总推力和力矩丢给运动方程。这样也能跑,但复现不出论文里常见的响应延迟和油门突变现象。因为电机转速不是瞬间建立的,它有时间常数。
简化的执行器模型是一阶惯性环节加上饱和:
- 期望转速通过电机时间常数 τ 变成实际转速;
- 实际转速经过 Kf 和 Kt 映射为推力和反扭矩。
如果只做控制律验证,可以把时间常数设得非常小甚至设为1,强调“控制律正确性优先”。但要做更真实的响应分析,τ 就按真实电机的量级设置。我补一个很常用的基准参数表,数值量级是能直接放进仿真跑的:
| 参数 | 数值 | 说明 |
|---|---|---|
| 机体质量 m | 1.2 kg | 基准样机 |
| 机臂长度 l | 0.25 m | 轴距约0.5m |
| 转动惯量 Ixx | 0.012 kg·m² | 滚转轴 |
| 转动惯量 Iyy | 0.012 kg·m² | 俯仰轴 |
| 转动惯量 Izz | 0.02 kg·m² | 偏航轴 |
| 推力系数 Kf | 8e-6 N/(rad/s)² | 由悬停推力反推 |
| 反扭矩系数 Kt | 1.2e-7 N·m/(rad/s)² | 与Kf相匹配 |
| 电机时间常数 τ | 0.02 s | 一阶惯性 |
| 最大转速 | 8000 rad/s | 饱和限制 |
Kf的量级我建议按“总推力约等于两倍机体重量”来反推,这样悬停时电机转速不会落在饱和区边缘。所有PID参数都和这个量级绑定,更换机体参数后一定要重新整定,不能原样照搬。
2.3 小角度假设与非线性模型的取舍
建模时会遇到一个明显分歧:论文推导PID控制律时,往往使用小角度假设把模型线性化;但仿真验证阶段又必须回到非线性模型,否则结果没有说服力。
我的做法是“双轨制”:
- 控制器设计阶段:用小角度简化模型,用来分析结构、估算初始参数。
- 仿真验证阶段:用完整非线性模型,包含旋转矩阵、陀螺力矩、执行器饱和。
我见过不少犯这个错误的人:拿着小角度模型的PID参数直接扔进非线性模型,结果仿真发散,还以为是参数没调好。其实这是控制系统失稳,不是参数抖动。如果控制律在小角度模型里调得不错,但在非线性模型里直接发散,优先怀疑控制力矩方向符号或者欧拉角范围超限,而不是急着调增益。
3. PID控制器设计:分层控制与参数整定思路
3.1 内外环级联控制:为什么内环一定比外环快
最开始我也困惑过,四旋翼位置和姿态明明高度耦合,为什么论文里都喜欢画一串串的PID方框?实际跑一遍就明白了:四旋翼位置变化不能直接靠推力方向实现,必须先把姿态转一个角度,然后用推力的水平分量产生水平加速度。
所以位置环的输出不是油门,而是期望姿态角;姿态环才负责把飞行器翻转到那个角度。整个控制结构自然分成三层:
- 角速度环(内环):输入是期望角速度,输出三轴力矩。
- 姿态角环(中间环):输入是指令姿态角,输出期望角速度。
- 位置环(外环):输入是指令位置,输出期望加速度和期望姿态角。
级联结构有一个硬约束:内环响应带宽必须远高于外环,通常至少相差3到5倍。否则外环指令一变,内环还没跟上,整个闭环就容易出现耦合振荡。我常用一个开车类比来解释:位置环是导航,姿态环是方向盘,角速度环是转向执行机构。导航让你转弯,方向盘要立刻响应,如果转向机构慢半拍,车就会画龙。
3.2 各环PID控制律的具体形式
以期望位置 [x_d, y_d, z_d] 和期望偏航 ψ_d 为例,位置环控制律:
a_des = Kp_p*(p_d - p) + Kd_p*(v_d - v) + Ki_p*∫(p_d - p)dt + g*e3a_des 是期望的机体加速度,把它拆解为期望姿态角:
φ_d = k_phi*(a_des_x*sinψ_d - a_des_y*cosψ_d) θ_d = k_theta*(a_des_x*cosψ_d + a_des_y*sinψ_d) u1 = m*(g + a_des_z)k_phi 和 k_theta 在悬停附近取 1/g。原理很简单:悬停状态下,水平加速度主要靠倾角把推力投影到水平方向,tanφ 近似等于 a_x/g。这个映射关系是位置环和姿态环之间的桥梁,也是整个控制器设计里最容易“想当然”的地方。
姿态角环接着设计:
p_des = Kp_att*(φ_d - φ) q_des = Kp_att*(θ_d - θ) r_des = Kp_att*(ψ_d - ψ)角速度环最终输出力矩:
M_roll = Kp_w*(p_des - p) + Kd_w*(p_des_dot - p_dot) + Ki_w*∫(p_des - p)dt M_pitch = Kp_w*(q_des - q) + Kd_w*(q_des_dot - q_dot) + Ki_w*∫(q_des - q)dt M_yaw = Kp_w*(r_des - r) + Kd_w*(r_des_dot - r_dot) + Ki_w*∫(r_des - r)dt在Simulink里,这串逻辑就是一堆Gain、Sum、Integrator的组合,维护很方便。有一个细节我踩过坑:位置环输出的期望加速度是在世界坐标系里算的,但滚转和俯仰指令产生的是机体坐标系的旋转,所以需要先按偏航角做一次坐标变换。第一次实现时我直接忽略了这一步,结果y方向跟踪总是慢半拍。
3.3 PID参数整定的经验方法
PID参数是整个项目里最“玄学”的部分。我的整定顺序固定如下:
- 先整角速度环:固定期望角度为0,只给角速度阶跃指令,调P和D让响应不过冲,稳定时间约0.5到1秒。
- 再整姿态角环:把内环连起来,调姿态环P,让角度阶跃响应单调、无显著超调。
- 再整位置环:把内两级都连上,调位置环P和D。
- 最后加积分项处理稳态误差,积分环节前面必须加抗饱和处理。
整定经验可以浓缩成一句话:P给到抖,D给到稳,I最后补。实际操作先把P设得非常小,慢慢加直到出现持续等幅振荡,记录临界振荡增益,然后取临界增益的一半作为起始参数。这是齐格勒-尼科尔斯法的简化版,仿真相对于现场调试来说足够好用。
给出一个和第二节参数表匹配的初始参考值:角速度环 P=4.0、D=0.8、I=0.1;姿态角环 P=2.0、D=0.4;位置环 P=0.8、D=0.5、I=0.05。注意这些值只对应那组机体参数,照抄之前先理解量级和系统带宽。
4. Simulink仿真实现:从模型搭建到结果分析的完整流程
4.1 Simulink整体模型结构配置
我搭建Simulink时按“模块拆分成子系统”的原则:顶层放参考输入生成器、控制器子系统、执行器子系统、非线性动力学子系统、状态监视子系统。每个子系统都可以双击进去独立调试。
搭建要点如下:
- 动力学子系统使用MATLAB Function写状态微分方程,输入是总推力和三轴力矩,输出是12维状态向量。
- 控制器子系统使用Gain、Sum、Integrator组合实现级联PID,逻辑清晰且易于改参数。
- 执行器子系统用一阶惯性环节加饱和模块,输出实际推力和力矩反馈给动力学。
我第一次用纯基础模块搭动力学,结果连线连了七十多根,改参数极其痛苦。后来全部改成MATLAB Function,代码只有几十行,排查方便了不止一个量级。这里强烈建议用代码块替代复杂的Gain连线,只要把变量名和Simulink端口对应清楚,整体模型会清爽得多。
4.2 初始化脚本与参数设置
Simulink模型必须配一个初始化脚本,用模型自身的常量对话框手输参数既容易漏又难维护。我习惯写一个quad_init.m:
%% 机体参数 m = 1.2; l = 0.25; Ixx = 0.012; Iyy = 0.012; Izz = 0.02; Kf = 8e-6; Kt = 1.2e-7; tau = 0.02; g = 9.81; omega_max = 8000; %% 初始状态 % [x y z u v w phi theta psi p q r] x0 = zeros(12,1); x0(3) = 0; % 初始高度 %% PID 参数 Kp_p = 0.8; Kd_p = 0.5; Ki_p = 0.05; Kp_att = 2.0; Kd_att = 0.4; Kp_w = 4.0; Kd_w = 0.8; Ki_w = 0.1; %% 仿真参数 sim_time = 30;脚本是参数的“单一事实来源”,所有要改的数值集中在这里,比在Simulink界面里到处找模块参数靠谱。模型里凡是涉及m、I、Kf、PID增益的地方,全部直接用变量名引用,不要写死数值。
4.3 求解器与仿真步长的取舍
非线性动力学加高低频PID控制环,求解器选择直接影响结果可信度。我的方案分两个阶段:
- 初调阶段:变步长ode45,相对容差1e-6,先把逻辑跑通。
- 正式验证阶段:固定步长ode4(四阶龙格-库塔),步长1ms。如果内环带宽较高,再降到0.5ms。
电机时间常数 τ=0.02s 时,仿真步长至少要小于 τ 一个量级。我曾把步长设成5ms跑偏航响应,结果曲线毛刺非常明显,改成1ms后就平滑很多。数值积分步长决定了高频动态能否被正确描述,越短越接近连续模型,但仿真耗时也会增加。
4.4 仿真结果的分析与可视化
仿真跑完后,我固定看三条曲线:
- 位置阶跃响应(x、y、z对指令的跟踪):评价位置环和姿态环配合质量。
- 姿态角曲线(φ、θ、ψ):看姿态环有没有超调和振荡。
- 电机转速曲线:看油门有没有逼近饱和。
论文复现通常要把“位置阶跃响应的上升时间、调节时间、稳态误差”这些指标量化。我给自己定的标准是:设定指令在 t=2s 从零阶跃到 [1, -1, 1.5] 米,从悬停启动,3.5到4.5秒内到达目标并稳定。如果超过8秒还没达到,就要怀疑控制器带宽设计过低或者执行器饱和。
5. 常见问题与调参排查实录
5.1 问题速查表:从现象到根因的一次定位
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 仿真一开始就出现NaN或Inf | 初值过大、旋转矩阵符号错误、积分器缺初始条件 | 把初始姿态角降到0,逐步加角度 |
| 位置响应高频振荡 | 内环增益过大、仿真步长过大、执行器饱和形成极限环 | 降低角速度环P,缩小步长 |
| 悬停时有稳态偏移 | 位置环缺积分项、坐标变换有偏置 | 加积分项,检查坐标旋转 |
| 姿态出现“转圈” | 欧拉角接近90度奇异、旋转矩阵公式错误 | 限制飞行范围,或改用四元数 |
| 控制器输出持续饱和 | 期望指令太激进、混控矩阵增益错误 | 改用梯形加减速轨迹,检查混控 |
5.2 三个一定会遇到的排查案例
案例一:模型发散。最初建模时我不小心把旋转矩阵写成了转置版,导致推力在坐标系间的投影方向反了。现象是给定悬停油门,飞行器不是稳稳悬停,而是往下加速并翻转。排查方法是用一个静态测试:给一个固定推力等于mg,初始姿态全为0,观察z位置是否在短时间内基本不变。如果z在快速下坠,基本可以断定旋转矩阵方向反了。
案例二:位置响应慢半拍。x方向跟随指令总是有滞后,但姿态指令看起来正常。查下来发现是位置环输出的期望加速度没有先经过偏航坐标变换,直接当成机体坐标加速度传给姿态环。加一个世界坐标到机体坐标的变换模块后,滞后问题立刻消失。
案例三:油门频繁进入饱和。一开始我把参考指令设成1米快速阶跃,直接让期望姿态角超过30度,电机转速顶在最大值。解决方式是改成梯形加减速轨迹,或者放宽期望调节时间。仿真验证不一定非要追求最激进的响应,能复现论文结论的合理指标更重要。
5.3 写在最后的经验建议:先让模型走,再让控制动
有一段被自己坑得很惨的经历:连续一周都在调PID参数,怎么调都调不出论文里的效果,最后才发现是建模时把电机反扭矩系数符号搞反了,导致偏航环是正反馈。从那以后我养成了一个固定习惯:在接控制器之前,先给动力学模型做开环测试,验证它基本物理行为是否正确。给固定推力等于mg,初始姿态全0,看z方向能否悬停;给一个滚转力矩,看机身是否沿正确方向产生角加速度。这两步通过后,再接PID控制器才会事半功倍。
关于“复现论文”这件事,我最后想多说一句:纸面上的公式和能跑起来的仿真之间,差的往往是那些被作者视为常识省略掉的工程细节,比如坐标变换方向、单位换算、符号约定、初始状态。所以做这类项目不要急于一次搭成大闭环,先把每个环节拆开单独验证,再逐级连接。把模型、控制器、仿真环境三者之间的关系吃透了,后面换机型、换控制方法都是水到渠成的事。