简介:面向无人机控制与智能控制研究的一套Matlab四旋翼无人机模糊控制仿真资源包,可帮助学习者与工程师完成飞行姿态、位置、速度等参数的控制器设计、仿真测试与算法验证。资源共包含13个文件,以m脚本为主(11个),辅以1张示意/截图和1段avi仿真视频,整体大小仅1.77MB。m文件覆盖四旋翼动力学建模、模糊控制器实现、仿真启动与轨迹生成等关键模块,清晰展示了从系统建模、模糊规则制定到解模糊化的完整流程,适合在Matlab/Simulink环境中快速复现和调参。由于四旋翼在真实飞行中常受风力、载荷变化等干扰,其中实现的模糊控制方案具有较强的鲁棒性,可通过修改隶属函数与规则库直观观察不同控制策略对飞行稳定性的影响。对于正在学习智能控制、准备无人机相关课程设计或毕业设计的学生而言,这也是一份低成本的实验参考,无需真实飞行即可验证控制算法。目前已有124人学习浏览,适合具备一定Matlab基础、正在研究无人机智能控制算法的读者。
1. 四旋翼无人机模糊控制仿真:先把姿态拿稳,再谈飞得好
四旋翼无人机的飞控里,姿态环几乎决定了整机能不能飞。很多团队第一版都用PID,调起来也快,但一旦换了机架、改了电机和桨叶,PID参数经常要重新整定一轮。模糊控制的价值在于,它把“误差大时用力打、误差小时轻轻修”的操作经验写进规则表,对模型参数变化不敏感。这个题目对应的完整链条是:建立四旋翼姿态模型,在MATLAB里设计模糊控制器,再通过Simulink闭环仿真验证控制效果。适合刚接触飞控算法、想把模糊控制落到仿真里的工程师,也适合做课程设计时需要一个能跑通、能改参数、能解释清楚的仿真框架的人。接下来的内容按建模、控制器设计、仿真接线、发散排查的顺序展开,最后给一个不依赖模糊工具箱的纯M语言验证方案。
2. 四旋翼动力学建模:仿真先要有被控对象
模糊控制器再聪明,也要有个对象来“推”。建模型的粗细程度取决于你要验证什么:如果只看姿态控制逻辑,完全可以把四旋翼处理成力矩驱动的刚体,不需要涉及复杂的空气动力学细节。
2.1 从电机转速到升力和反扭矩:X构型的力与力矩
四旋翼最常见的布局是X构型,四个电机分布在机体对角线方向。每个电机旋转产生的升力与转速平方成正比,反扭矩也与转速平方成正比:
T_i = b * Ω_i^2 Q_i = d * Ω_i^2其中b是升力系数,d是反扭矩系数,Ω_i是第i个电机的转速。X构型下,滚转力矩由左右电机差速产生,俯仰力矩由前后电机差速产生,偏航力矩则来自对角线电机反扭矩的差。写成控制分配关系就是:
% 电机顺序:1前左,2前右,3后左,4后右 % U1: 总升力, U2: 滚转力矩, U3: 俯仰力矩, U4: 偏航力矩 % l: 电机到机体中心的力臂长度 U1 = b * (w1^2 + w2^2 + w3^2 + w4^2); U2 = l * b * (w2^2 - w4^2); % 左右差速产生滚转 U3 = l * b * (w1^2 - w3^2); % 前后差速产生俯仰 U4 = d * (-w1^2 + w2^2 - w3^2 + w4^2); % 对角差速产生偏航这段代码的作用是把四个电机转速映射成四个控制量。仿真里通常反过来用:控制器算出U1到U4,再通过这个矩阵把U解算到每个电机的转速,或者直接把U作为模型的力矩输入。力臂l和系数b、d是模型参数,后面调仿真发散时,这三个值的数量级对不对很关键。
2.2 姿态角加速度从哪来:欧拉方程与简化模型
有了力矩,姿态角加速度由转动惯量决定。忽略陀螺力矩和空气阻尼,姿态动力学可以简化成三个独立的二阶积分环节:
d(phi_dot)/dt = U2 / Jxx d(theta_dot)/dt = U3 / Jyy d(psi_dot)/dt = U4 / Jzz这里Jxx、Jyy、Jzz是三轴转动惯量。简化模型丢掉了一些耦合项,但对验证模糊控制逻辑完全够用。真正飞起来时,陀螺力矩会带来耦合,不过那是后续加入角速度环补偿的事,不要在第一步仿真里把模型搞得太重。实际项目中我一般先跑这个简化模型,确认控制器没问题,再逐步加回耦合项。
2.3 在MATLAB脚本里建一个可以直接仿真的姿态模型函数
这个模型可以写成MATLAB函数,方便Simulink用Interpreted MATLAB Function或MATLAB Function模块调用,也可以在脚本里用ode45直接跑开环验证:
function dydt = quad_attitude(t, y, U, J) % 状态 y = [phi, theta, psi, phi_dot, theta_dot, psi_dot] % 输入 U = [U1, U2, U3, U4],本例只使用 U2~U4 phi = y(1); theta = y(2); psi = y(3); p = y(4); % 滚转角速度 q = y(5); % 俯仰角速度 r = y(6); % 偏航角速度 dydt = zeros(6,1); dydt(1) = p + (q*sin(phi) + r*cos(phi))*tan(theta); dydt(2) = q*cos(phi) - r*sin(phi); dydt(3) = (q*sin(phi) + r*cos(phi))/cos(theta); dydt(4) = U(2)/J(1); dydt(5) = U(3)/J(2); dydt(6) = U(4)/J(3); enddydt(1)~dydt(3)是欧拉角运动学关系,dydt(4)~dydt(6)是角加速度方程。注意当俯仰角接近90度时tan(theta)会发散,仿真中如果出现角度跑到奇异点附近,要先检查是不是控制器输出过大,而不是急着改求解器。用ode45(@quad_attitude, [0 5], [0;0;0;0;0;0], [], [0,1,0,0], J)加一个滚转力矩阶跃,就可以验证模型对不对。
3. 模糊控制器设计:误差、误差变化率与7×7规则表
模型准备好之后,下一步是把模糊控制器设计出来。模糊控制器的设计自由度看起来很高——隶属度函数形状、论域范围、规则表、推理方法都可以改,但真正决定控制品质的其实是两件事:输入输出变量的定义和规则表的语义是否正确。
3.1 为什么姿态环普遍选二维模糊控制器
姿态控制是典型的快速随动问题,控制器需要同时响应“当前偏差”和“偏差变化的趋势”。二维模糊控制器以误差e和误差变化率ec作为输入,结构上等价于一个非线性PD控制器。它的优势在于,增益不再是一个固定数,而是根据工作点在线变化:误差大时输出接近饱和,误差小时输出变得平缓。
为什么不直接把积分项也做进模糊规则里?积分的作用是消除稳态误差,但在姿态控制中,稳态误差主要来自重力力矩和电机静差,这些可以被内回路的积分作用或者模糊PID混合结构吃掉。纯二维模糊控制器的稳态性能偏弱,这一点在仿真里很容易看到:阶跃响应可能有持续的残差。解决办法在第5章讲模糊PID混合时展开,先按二维结构把规则表建好。
3.2 输入输出定义:先约定误差符号,别让规则表自相矛盾
规则表里填的语言值符号,取决于你对误差的定义方式。我这里统一采用:
e = ref - measured % 期望角度减去实际角度 ec = d(e)/dt % 误差变化率输出du是控制量的修正值,与电机油门增量同号。采用这个定义时,e为负表示“当前角度低于目标”,控制器需要输出正的修正力矩把飞机拉回来。规则表里的所有符号都按负反馈逻辑填充,抄作业时不要随意改符号,否则飞机会越调越发散。
3.3 隶属度函数和7×7规则表怎么填
输入变量e和ec的论域都设为[-6, 6],每个变量分成7个语言值:负大(NB)、负中(NM)、负小(NS)、零(ZO)、正小(PS)、正中(PM)、正大(PB)。隶属度函数用三角形,相邻函数之间重叠0.5左右,这是最常用的配置,既保证平滑过渡,又不至于让隶属度饱和。输出du的论域也是[-6, 6],同样7个语言值。
规则表是模糊控制器的核心,按“同号增强、异号减弱”的原则填写:
| e \ ec | NB | NM | NS | ZO | PS | PM | PB |
|---|---|---|---|---|---|---|---|
| NB | NB | NB | NB | NB | NM | ZO | ZO |
| NM | NB | NB | NB | NM | NS | ZO | ZO |
| NS | NM | NM | NM | NS | ZO | PS | PS |
| ZO | NM | NS | NS | ZO | PS | PS | PM |
| PS | NS | NS | ZO | PS | PM | PM | PM |
| PM | ZO | ZO | PS | PM | PB | PB | PB |
| PB | ZO | ZO | PM | PB | PB | PB | PB |
规律的物理含义是:当e和ec同号时,误差正在增大,输出要加大力度;当e和ec异号时,误差在回落,输出要适当减小,避免过头。表格的反对角线附近输出接近ZO,这个区域对应系统已经接近稳态,控制动作要轻。
3.4 用Fuzzy Logic Designer建FIS:图形界面和脚本两套操作
在MATLAB命令窗口输入fuzzyLogicDesigner可以打开图形界面,按变量、隶属度函数、规则三个面板依次配置。这种方式直观,适合第一次建控制器时看隶属度函数的形状和规则覆盖情况。但如果是批量实验或者要复现结果,我倾向于用脚本建:
fis = newfis('attitude_fuzzy', 'mamdani'); % 输入1:误差e,论域[-6 6] fis = addvar(fis, 'input', 'e', [-6 6]); fis = addmf(fis, 'input', 1, 'NB', 'trimf', [-8 -6 -4]); fis = addmf(fis, 'input', 1, 'NM', 'trimf', [-6 -4 -2]); fis = addmf(fis, 'input', 1, 'NS', 'trimf', [-4 -2 0]); fis = addmf(fis, 'input', 1, 'ZO', 'trimf', [-2 0 2]); fis = addmf(fis, 'input', 1, 'PS', 'trimf', [0 2 4]); fis = addmf(fis, 'input', 1, 'PM', 'trimf', [2 4 6]); fis = addmf(fis, 'input', 1, 'PB', 'trimf', [4 6 8]); % 输入2:误差变化率ec,论域[-6 6] fis = addvar(fis, 'input', 'ec', [-6 6]); % 按同样方式添加ec各隶属度函数,命名与e一致 % 输出:du fis = addvar(fis, 'output', 'du', [-6 6]); % 按同样方式添加du各隶属度函数 ruleList = [1 1 1 1 1; 4 4 4 1 1; 7 7 7 1 1]; % 每行格式:[e的MF索引, ec的MF索引, 输出的MF索引, 权重, 连接词] % 连接词取1表示AND,2表示OR fis = addrule(fis, ruleList); writefis(fis, 'attitude_fuzzy');这段代码里的ruleList只是3条示例规则,用于说明格式。完整规则表有49条,用上面的7×7表逐行填进去即可。writefis生成的.fis文件可以保存,也可以直接在Simulink的Fuzzy Logic Controller模块里读取。注意trimf的三个参数是三角形左端点、顶点、右端点,相邻函数重叠关系的调整就在这里做。
3.5 量化因子和比例因子:模糊控制器真正要调的参数
FIS内部论域固定是[-6,6],但实际角度误差的单位是弧度或度,误差变化率的数值范围也完全不同。量化因子把真实量纲映射到模糊论域,比例因子把模糊输出映射回真实控制量:
e_scaled = e * Ke % Ke = 6 / e_max ec_scaled = ec * Kec % Kec = 6 / ec_max u_real = du * Ku % Ku = u_max / 6初值怎么给,取决于你的模型量级。角度以弧度为单位时,最大误差0.5 rad,那么Ke取12左右;角速度误差变化率最大约2 rad/s,Kec取3左右;电机油门修正量最大0.2,Ku取0.033。这三个参数是仿真发散时最先排查的对象,第5章会具体展开。
4. Simulink仿真搭建:把FIS接进闭环的最小结构
控制器设计完成后,进入仿真验证环节。Simulink里搭一个最小闭环只需要六个模块,重点是理解信号流向,而不是堆模块数量。
4.1 最小闭环模块清单与连接顺序
| 模块 | 参数设置 | 作用 |
|---|---|---|
| Step | Final value设为0.5,Step time设为0 | 给滚转角一个0.5 rad的阶跃目标 |
| Sum | 符号设为+ - | 计算误差 e = ref - measured |
| Fuzzy Logic Controller | 调用工作区内的fis对象 | 计算模糊输出 |
| Gain | 设为Ku | 比例因子,把论域值还原成真实控制量 |
| Saturation | 上下限设为实际油门限幅 | 防止控制量超出执行机构能力 |
| Scope / To Workspace | 记录时间与角度响应 | 用于性能分析 |
连接顺序是:Step输出接到Sum正端,模型输出接到Sum负端,Sum输出同时进入Fuzzy Logic Controller模块和一路微分环节(误差变化率)。模糊控制器模块默认只接受一个向量输入,所以需要把e和ec用Mux合成一个二维向量,模块内部会自动按FIS定义的输入顺序读取。
4.2 FIS怎么装进Simulink:readfis与工作区变量
Fuzzy Logic Controller模块运行时从MATLAB工作区读取变量。最稳妥的做法是在模型初始化脚本或命令窗口里先执行:
fis = readfis('attitude_fuzzy');然后在模块参数对话框里把“FIS file or name”填成工作区变量名fis。这里有个常见坑:如果改了FIS文件但没有重新执行readfis,Simulink里跑的仍然是旧的控制器。参数扫描前务必先刷新工作区变量。另一种做法是直接在模块参数里填.fis文件路径,Simulink每次仿真开始都会重新加载,代价是启动稍慢。
4.3 求解器与仿真步长:避开仿真发散的第一道门槛
模糊控制器本质上是非线性环节,包含查表、取极值、重心法计算,这些运算不会改变连续性,但会让系统对步长敏感。姿态模型的时间常数通常在0.1到0.5秒量级,角速度环更快,因此仿真步长要压到1毫秒以下才保险。
| 配置 | 现象 | 建议 |
|---|---|---|
| 变步长ode45 | 触发时刻附近步长骤减,仿真变慢 | 只用于开环模型检查 |
| 固定步长ode4,步长0.01s | 高频动态被步长滤掉,可能表现虚假稳定 | 偏大,不推荐 |
| 固定步长ode4,步长0.001s | 结果平滑,速度和精度平衡 | 推荐起点 |
| 固定步长ode3,步长0.0005s | 更稳但更慢 | 发散排查时使用 |
求解器设置路径是“仿真”→“模型配置参数”→“求解器”,类型选“固定步长”,求解器选ode4(四阶龙格库塔)。步长先设0.001,跑通后再尝试放大到0.002,观察响应是否仍然一致。模糊控制器运行时间很短,真正的计算瓶颈在模型的高频动态,所以步长不是越大越好。
4.4 阶跃响应与性能指标提取
闭环跑通后,用To Workspace模块把角度和误差存到工作区,然后写脚本提取性能指标:
% 假设 out.phi 是滚转角序列,out.t 是时间序列 idx = find(out.t >= 5, 1); % 稳态段从5秒开始 steady = out.phi(end); % 超调量 overshoot = (max(out.phi) - steady) / steady * 100; % 上升时间:从10%到90%稳态值 y10 = steady * 0.1; y90 = steady * 0.9; t10 = out.t(find(out.phi >= y10, 1)); t90 = out.t(find(out.phi >= y90, 1)); rise_time = t90 - t10; fprintf('超调量: %.2f%%, 上升时间: %.3f s\n', overshoot, rise_time);这段脚本的作用是把Scope里肉眼看的曲线变成数值。find函数找到第一次越过阈值的时间点,t90-t10就是上升时间。超调量大于30%时,优先减小Ku,而不是改规则表;上升时间过慢时,优先增大Ke和Kec。下一章会讲这些调整的顺序和边界。
5. 发散与震荡排错:先查模型,再调Ke,最后改规则
仿真发散是模糊控制调试里出现频率最高的问题。现象通常有两种:曲线直接飞到NaN,或者输出剧烈震荡发散。很多人第一反应是改规则表,实际上大多数发散与规则无关。
5.1 仿真发散的高频原因与排查顺序
| 优先级 | 原因 | 现象 | 验证方法 | 修正 |
|---|---|---|---|---|
| 1 | 模型本身开环不稳定或参数量级错误 | 不加控制器就发散 | 去掉Fuzzy模块,给模型一个固定力矩输入 | 检查J、b、d的单位和数值 |
| 2 | 量化因子过大 | 角度小幅震荡且越来越剧烈 | 把Ke、Kec缩小到1/10再跑 | 按e_max、ec_max重新计算 |
| 3 | 控制量未限幅 | 输出饱和后角度快速翻转 | 查看控制量曲线是否超限 | 加Saturation,设真实油门范围 |
| 4 | 步长过大 | 曲线在特定相位跳变 | 把步长从0.001改到0.0001 | 换ode4固定步长 |
| 5 | 单位不一致 | 同一套参数换模型后完全失控 | 检查角度单位是弧度还是度 | 统一到弧度制 |
这里要特别强调第2条。模糊控制器的规则表只在论域内有意义,当e超出最大论域时,最外侧隶属度函数饱和,控制器处于“开环”状态——无论误差怎么加大,输出都不再变化。此时整个闭环相当于一个固定增益系统,很容易发散。量化因子的取值以“让e和ec的峰值落在论域的60%到90%区间”为基准,不是越大越好。
排查顺序我个人习惯是:先在Simulink里禁用Fuzzy模块,改成直接给模型一个常数力矩,确认模型本身响应正常;再恢复模糊控制器,把Ke、Kec设成很小值跑一次,确认闭环稳定;最后逐步加大量化因子到正常范围。如果小增益下发散,问题一定在模型或接线,不是规则表。
5.2 从纯模糊到模糊PID混合
纯二维模糊控制的稳态误差问题,在仿真里表现为阶跃响应最终停在目标角度旁边一个固定偏移上。消除它最直接的做法是模糊PID混合:外层模糊控制器负责输出PID增益修正量,内层PID负责实际控制执行。
具体接线方式是:误差e同时进入模糊控制器和PID控制器,模糊控制器的输出不再是控制量,而是Kp、Ki、Kd三个增益的修正系数。模糊输出论域设为[0,1],乘以基准增益后得到最终PID参数。这样保留模糊控制的非线性适应能力,又借助PID的积分项消除稳态误差。Simulink里用三个Fuzzy Logic Controller模块分别输出三个增益修正系数,或者用一个三输出FIS。
5.3 用sim()脚本批量扫描Ke和Kec
手动在Simulink里改参数再点运行,效率太低。把控制器参数放到工作区变量,用脚本循环调用sim函数,可以实现批量参数扫描:
Ke_list = [8 12 16 20]; Kec_list = [2 3 4 5]; results = []; for i = 1:length(Ke_list) for j = 1:length(Kec_list) Ke = Ke_list(i); Kec = Kec_list(j); % Simulink模型名称为quad_sim,输入输出用工作区变量传递 simOut = sim('quad_sim', 'StopTime', '10'); phi = simOut.phi.Data; t = simOut.phi.Time; steady = phi(end); overshoot = (max(phi) - steady) / steady * 100; results(end+1, :) = [Ke, Kec, overshoot]; %#ok<SAGROW> end endsim函数第一个参数是模型名,模型内所有Gain模块的增益都直接引用工作区变量Ke、Kec,每次循环赋值后重新仿真即可。结果矩阵的第三列是超调量,按超调量最小、上升时间次优的顺序筛选,就是一组可用的起始参数。这套脚本也可以扩展到扫描Ku和规则表缩放系数。
6. 把FIS固化成纯M函数:不依赖模糊工具箱也能跑仿真
有的MATLAB许可证不带Fuzzy Logic Toolbox,或者需要在其他语言环境复现仿真结果,这时把FIS逻辑改写成纯M语言函数就很有用。下面是一个最小可用的Mamdani推理实现。
6.1 手写Mamdani推理的最小实现
function u = fuzzy_pd(e, ec, Ke, Kec, Ku) % 简化版二维模糊控制器,使用3个语言值:N, ZO, P % 输入偏差e与偏差变化率ec经过量化后进入规则表 x1 = max(0, 1 - abs(e*Ke - (-3))/3); % 三角形隶属度 N x2 = max(0, 1 - abs(e*Ke - 0)/3); % ZO x3 = max(0, 1 - abs(e*Ke - 3)/3); % P % ec同理得到 y1,y2,y3,共3个隶属度 % 规则表:e(N,ZO,P) x ec(N,ZO,P) -> 输出(N,ZO,P) % 规则含义:误差负且误差变化率负 -> 输出负,反向校正 rule_out = [1 1 2; 1 2 2; 2 2 3; 2 3 3; 3 3 3]; % 1=N, 2=ZO, 3=P u_fuzzy = 0; w_sum = 0; for i = 1:3 for j = 1:3 w = min( [x1 x2 x3](i), [y1 y2 y3](j) ); % AND out_val = rule_out(i,j); u_fuzzy = u_fuzzy + w * (out_val - 2); % 映射到[-1,1] w_sum = w_sum + w; end end if w_sum > 0 u = u_fuzzy / w_sum * Ku; % 重心法去模糊化 else u = 0; end end这个函数省去了隶属度函数库的通用计算,直接在循环里完成推理和去模糊化。rule_out是3×3规则表,数值1、2、3分别对应负、零、正三个语言值。min函数实现模糊AND运算,重心法用加权平均替代积分,对小规模规则表精度足够。实际使用前,用同一组e和ec分别调用evalfis和fuzzy_pd,对比输出曲线形状是否一致。
6.2 手写函数与Simulink模型联调
把fuzzy_pd函数放进MATLAB Function模块,就可以在不加载FIS文件的情况下运行闭环仿真。验证通过后,这段纯M函数可以直接翻译成C代码,嵌入到实际的飞控固件里。至此,从模型到控制器再到仿真验证的完整链路已经打通,剩下的工作就是按第5章的参数扫描脚本,把Ke、Kec、Ku三个系数针对具体机型的模型参数再扫一遍。
本文还有配套的精品资源,点击获取