简介:面向无人机控制研究者、相关专业学生及入门工程师的 MATLAB 四旋翼模糊控制仿真资源,以模糊逻辑为核心,从四旋翼动力学建模、模糊控制器设计到仿真验证形成完整闭环,帮助理解模糊控制处理非线性、不确定因素的原理,并解决飞行姿态与位置控制的稳定性设计问题。压缩包共 13 个文件,以 .m 脚本为主,包含主仿真程序、模糊控制器、动力学方程、四旋翼绘图与轨迹生成等 MATLAB 源文件,同时附有 1 张示意图和 1 段 2D 仿真演示视频,整体大小约 1.77MB。目前已有 124 人学习下载,适合作为无人机控制、智能控制课程的仿真实验素材,也适合课题入门与二次开发。使用者可直接运行仿真,观察四旋翼跟踪二维轨迹的动态过程,也可修改模糊规则、隶属函数或参考轨迹,直观对比不同控制策略的飞行性能,从而为后续实物调试和算法优化提供可复现的实验基础。
1. 为什么用模糊控制来稳住四旋翼
四旋翼能悬停不是因为它本身稳定,而是因为控制回路每秒都在修正偏差。四个旋翼产生的拉力和力矩,只要任何一个通道出现几毫秒的延迟或者几十克的载荷变化,姿态就会肉眼可见地偏出去。传统PID在固定工作点附近效果不错,但四旋翼是一个强耦合、非线性、参数时变的对象,风速突变、电机老化、重心偏移都会让固定增益不再可靠。模糊控制的核心思路是把手动调PID时积累的经验翻译成“误差大就给猛力、误差小就温柔修正”这类规则,不需要精确的数学模型,天然对参数漂移不敏感。
这套用Matlab写的仿真包把动力学方程、轨迹生成和模糊控制器全部拆开了,能直接看到每个文件在做什么。适合做四旋翼控制算法验证、毕设课程设计,以及想对比模糊控制与传统PID效果差异的开发者。压缩包里的simulation_2d.m、runsim.m、controller.m和test_control_2d.avi构成了一条完整链路:从参考轨迹到状态更新,再到动画回放,都可以自己改参数重新跑。
2. 先把四旋翼模型立起来:sys_params.m 到 sys_eom.m
模糊控制器虽然不依赖精确模型,但仿真端必须有一个足够可信的被控对象,否则规则调得再漂亮也是自欺欺人。这个包里把模型拆成了参数文件、运动学变换、动力学函数和可视化四个部分,顺序读下来正好是一套建模流程。
2.1 状态量与坐标变换
四旋翼的完整状态一般用12维向量表示:位置、速度、姿态、角速度。包里常见的做法是用四元数表示姿态而不是欧拉角,因为四元数没有万向锁,数值上也不会因为角度接近90度而跳变。状态向量的组织顺序直接影响后续代码,建议先定下来再读后续文件。
simStateToQuadState.m负责把仿真状态提取成画图或控制需要的子集,QuatToRot.m则是把四元数转成旋转矩阵,作用是把机体坐标系下的升力投影到地面坐标系。旋转矩阵的列向量分别对应机体轴的朝向,在动力学里用于把推力方向从机体系换到惯性系。
| 状态量 | 含义 | 单位 |
|---|---|---|
| x, y, z | 地面系位置 | m |
| vx, vy, vz | 地面系速度 | m/s |
| qw, qx, qy, qz | 姿态四元数 | 1 |
| wx, wy, wz | 机体系角速度 | rad/s |
姿态四元数归一化很重要,仿真中每一步更新后如果模长偏离1,就会导致旋转矩阵不再正交,位置和速度的耦合会出现虚假漂移。所以sys_eom.m里即使不做显式归一化,控制器和绘图函数也应当假定状态是合法的。
2.2 质量、惯量和空气动力参数
sys_params.m把模型的物理常数集中放在一个结构体里,修改时不需要去翻动力学方程。默认参数大致对应一架轴距230mm左右的小型四旋翼,质量0.65kg,转动惯量在毫安级别。参数表的精度决定仿真结果能不能复现真实飞行,尤其是Ixx、Iyy、Izz之间的比例关系,如果调成一样会削弱姿态通道之间的差异感。
% sys_params.m p.m = 0.65; % 无人机总质量 kg p.g = 9.81; % 重力加速度 m/s^2 p.L = 0.23; % 机臂长度 m p.k = 2.98e-6; % 拉力系数 N/(rad/s)^2 p.kd = 0.08; % 平动阻尼系数 p.Ixx = 7.5e-3; % 绕x轴转动惯量 kg*m^2 p.Iyy = 7.5e-3; % 绕y轴转动惯量 kg*m^2 p.Izz = 1.3e-2; % 绕z轴转动惯量 kg*m^2这里的kd是平动阻尼项,模拟空气对平移速度的阻力。实际飞行器没有这个线性阻尼,但在仿真中加入它可以防止水平速度发散,也能让位置环更容易收敛。读代码时需要注意:p.k是旋翼拉力系数,而不是电机转速到升力的线性系数,它和旋翼转速的平方相乘才得到升力,因此控制输出量纲需要和模型匹配。
2.3 动力学函数 sys_eom.m
sys_eom.m是六自由度刚体动力学方程的载体,输入是当前状态和控制量,输出是状态的导数。控制量通常抽象为总升力和三个轴的力矩,而不是四个电机的实际转速。这样设计的好处是模糊控制器只需要关心力和力矩,下层电机混控矩阵再把它分配到四个旋翼。
function sdot = sys_eom(s, u, p) % 状态解析 pos = s(1:3); vel = s(4:6); q = s(7:10); omega = s(11:13); % 姿态四元数转旋转矩阵 R = QuatToRot(q); % 平动方程: 重力 + 机体升力投影 - 阻尼 acc = [0; 0; -p.g] + R * [0; 0; u(1)] / p.m - p.kd * vel; % 四元数运动学: q_dot = 0.5 * q * omega omega_quat = [0; omega]; qdot = 0.5 * quatmultiply(q', omega_quat')'; qdot = qdot / norm(qdot); % 保持单位四元数 % 转动方程: I * omega_dot + omega x (I*omega) = M I = diag([p.Ixx p.Iyy p.Izz]); omegadot = I \ (u(2:4) - cross(omega, I * omega)); sdot = [vel; acc; qdot; omegadot]; end平动方程里R * [0;0;u(1)]做的事情是把机体坐标系的z轴推力转换到地面坐标系,所以u(1)必须是总升力而不是单个电机油门。转动方程用的欧拉方程,cross(omega, I*omega)是陀螺力矩项,在快速旋转时不可忽略。四元数求导公式的乘法顺序如果写反,姿态会不断漂移,最直观的表现是悬停时无人机自己转起来。
2.4 轨迹定义和绘图辅助
trajectories文件夹里的traj_line.m和traj_sine.m生成参考轨迹,前者给阶跃或直线指令,后者给正弦曲线,用来检验控制器对时变目标的跟踪能力。轨迹函数返回的通常是一个行数等于时间步数、列数为状态维数的矩阵,方便主循环按列索引。
QuadPlot.m和quad_pos.m属于可视化层。quad_pos.m根据当前状态计算机身四个电机的位置坐标,QuadPlot.m负责把这些坐标画成线段和平面,动画效果就来自这两个文件。跑仿真的过程中如果发现动画卡顿,多半是绘图刷新频率和仿真步长不一致,可以先在循环里隔几帧更新一次图形对象。
3. 模糊控制器设计:误差量化、规则表和推理
模型只是被控对象,真正决定仿真效果的是controller.m里的模糊逻辑。模糊控制器不是凭空给控制量,而是把“误差多大”和“误差变化多快”映射成“该怎么修正”,映射关系就是工程经验。
3.1 控制器拿什么输入
四旋翼的位置控制通常拆成两个回路:外环把位置误差转换成期望姿态角,内环把姿态误差转换成力矩。纯模糊控制器如果同时接收位置误差和姿态误差,规则数量会爆炸,所以常见做法是外环用PD或者模糊PD生成期望角度,内环姿态也用一个模糊控制器。
controller.m的输入参数一般是参考状态、当前状态、时间以及参数结构体。输出是四个原始控制量,至少要保证第一项不小于悬停所需的推力m*g,否则高度环会一直往下掉。如果输出的是油门百分比而不是真实推力,还需要在控制器内部乘以p.k和转速上限做换算。
3.2 隶属函数与量化因子
模糊控制器先把连续输入变成模糊集合中的隶属度,这个过程叫模糊化。这里通常取七个模糊子集:NB、NM、NS、ZO、PS、PM、PB。三角形隶属函数实现最简单、计算量最小,也最容易手工调整。
量化因子把实际物理量映射到模糊论域,比如高度误差实际可能是-1.5到1.5米,而模糊论域只有-1到1,那么量化因子就是1/1.5。误差变化率同理。输出比例因子负责把模糊推理结果换算回控制量。整个调参手感其实就是在调这三个因子。
% 构建一个简单模糊推理器 fis = newfis('quad_hover'); fis = addvar(fis, 'input', 'e', [-1 1]); fis = addmf(fis, 'input', 1, 'NB', 'trimf', [-1.5 -1 -0.5]); fis = addmf(fis, 'input', 1, 'NS', 'trimf', [-1 -0.5 0]); fis = addmf(fis, 'input', 1, 'ZO', 'trimf', [-0.5 0 0.5]); fis = addmf(fis, 'input', 1, 'PS', 'trimf', [0 0.5 1]); fis = addmf(fis, 'input', 1, 'PB', 'trimf', [0.5 1 1.5]); fis = addvar(fis, 'output', 'u', [-1 1]); fis = addmf(fis, 'output', 1, 'ZO', 'trimf', [-0.5 0 0.5]);如果实际仿真中响应太慢,优先检查量化因子和输出比例因子,而不是急着改规则表。规则表决定趋势,因子决定幅度,两者要分开调。
3.3 7×7规则表
规则表是模糊控制器的“大脑”。对于二维输入单输出的系统,规则表可以写成矩阵:行对应误差e,列对应误差变化率ec,交叉点就是输出模糊子集。常用规则表按对角线对称,误差大时输出也大,误差小且变化率小时输出接近零。
| e \ ec | NB | NM | NS | ZO | PS | PM | PB |
|---|---|---|---|---|---|---|---|
| NB | NB | NB | NB | NM | NM | NS | ZO |
| NM | NB | NB | NM | NM | NS | ZO | PS |
| NS | NB | NM | NS | NS | ZO | PS | PM |
| ZO | NM | NM | NS | ZO | PS | PM | PM |
| PS | NM | NS | ZO | PS | PS | PM | PB |
| PM | NS | ZO | PS | PM | PM | PB | PB |
| PB | ZO | PS | PM | PM | PB | PB | PB |
这张表基本符合人的直觉:误差为正且还在增大,输出就要正大;误差为负且趋于零,输出就不要再推。如果需要抑制超调,可以让对角线附近的NS和PS区域输出再小一点,相当于模糊版的死区。
3.4 查表式模糊推理实现
在Matlab里反复调用evalfis会有一定开销,仿真步长很小时会拖慢速度。更高效的做法是把规则表预先量化成离散矩阵,运行时直接用查表加线性插值。误差和误差变化率先量化到整数等级,再根据规则表得到输出等级,最后乘以比例因子。
function u_out = fuzzy_lookup(e, ec) % 错误量化到 [-3, 3] idx_e = max(-3, min(3, round(e * 3))); idx_ec = max(-3, min(3, round(ec * 3))); ruleTable = [ ... -3 -3 -3 -2 -2 -1 0; ... -3 -3 -2 -2 -1 0 1; ... -3 -2 -1 -1 0 1 2; ... -2 -2 -1 0 1 2 2; ... -2 -1 0 1 1 2 3; ... -1 0 1 2 2 3 3; ... 0 1 2 2 3 3 3; ... ]; u_out = ruleTable(idx_e + 4, idx_ec + 4) / 3; end代码里idx_e + 4是因为Matlab索引从1开始,而量化等级从-3到3。规则表每个条目直接是输出等级,省去了模糊推理的合成运算。这种查表方式在嵌入式环境里也很好移植,本质上和真实飞控里的模糊查表是一回事。
4. 仿真主循环与调参:从 rk4 到不失控
有了模型和控制器,接下来要把它们装进主循环。runsim.m是启动入口,simulation_2d.m承载完整仿真流程。先跑通默认参数,再动规则表,才能分清楚发散原因到底是模型还是控制。
4.1 runsim.m 启动流程
启动脚本会按固定顺序执行:加载参数、生成参考轨迹、初始化状态、进入时间循环、在每个步长里计算控制量、积分状态、记录数据。时间步长dt一般取0.01秒,对应100Hz控制频率,这和大多数飞控的实际运行频率一致。如果dt太大,四元数积分的误差会累积,导致动画里无人机频繁抖动量发散。
4.2 主循环代码
把主循环写成函数而不是脚本的好处是方便批量跑参数扫描。每次保存所有历史状态用于绘制曲线和动画,最后用QuadPlot.m回放。test_control_2d.avi就是这种回放的成品。
% runsim.m p = sys_params(); dt = 0.01; tEnd = 10; t = 0:dt:tEnd; % 生成正弦轨迹 [traj, ref] = traj_sine(dt, tEnd); s = zeros(12, 1); s(9) = 1; % 初始四元数为 [0,0,0,1] history = zeros(12, length(t)); for k = 1:length(t) u = controller(ref(:, k), s, p, t(k)); s = rk4(@(s) sys_eom(s, u, p), s, dt); history(:, k) = s; end % 绘制离线曲线并生成动画 QuadPlot(t, history);这里controller的返回变量u不能直接是四个电机的PWM值,因为sys_eom期望的是总升力和三个力矩。如果控制器输出的是油门,就要在控制器内部完成到推力的换算:推力等于单个旋翼拉力系数乘以油门对应转速平方再乘以电机数量,力矩则根据机臂长度和布局矩阵解算。
4.3 常见发散和处理策略
仿真发散是最常见的问题,但发散方式不同,原因也不同。位置和速度快速飞向无穷大,先检查升力符号和重力方向是否一致;姿态在几秒内剧烈翻转,检查四元数导数符号;高频抖动发散,大多是时间步长太大或比例因子过大。
模糊控制系统还需要额外注意量化因子和规则表的配合。如果量化因子太大,误差会非常容易进入饱和区,控制器相当于只有正负两个档位,动作生硬;量化因子太小,误差变化率区分不出来,稳态附近会来回颤。我一般先把误差量化到[-0.2, 0.2]范围内观察曲线,再逐步扩大。
调试时建议先固定水平通道,只让z轴做高度阶跃,确认推力通道没有静差。然后再放开x通道跟踪直线,最后加正弦轨迹。这样逐步增加变量,出了发散能立刻定位是哪个回路的问题。
5. 把模糊控制器导出为.fis 并在 Simulink 中复用
纯脚本仿真适合验证算法,但如果想进一步做半实物仿真或者和复杂执行器模型结合,通常会转去Simulink。Matlab的Fuzzy Logic Toolbox支持把mamdani或sugeno模糊推理系统导出为.fis文件,Simulink里的Fuzzy Logic Controller模块可以直接读取这个文件,省掉手写查表函数。
这里的关键是把推理系统定义成变量后调用writefis,再在Simulink中指定文件名。.fis是文本格式,手动看也能定位规则和隶属函数参数,跨机器拷贝不会丢内容。
% 把第3章构建的fis导出 writefis(fis, 'quad_hover.fis'); % 验证可以重新读回 fis_check = readfis('quad_hover.fis'); evalfis([0.3 -0.1], fis_check)Simulink模块要求输入是一个向量,顺序必须和定义的input变量顺序一致。这里定义的是[e, ec],所以上游需要把误差和误差变化率打包成一个两元素的向量。如果传感器信号是标量,先用Demux或者Mux处理好维度再进模糊控制器。
实际使用中还有一个技巧:不要把模糊控制器直接接入最内环,而是在外环信号链上并联。比如位置误差经过PD计算得到期望角速度,模糊控制器只输出修正分量叠加到PD输出上。这样即使模糊规则不够精确,基础的PD仍然能兜底,不会出现规则表误激活导致的快速发散。叠加前要注意限幅,修正量加在期望角度上可能超过物理约束,所以要在控制器输出端加Saturation模块。
运行Simulink模型时,如果提示无法读取.fis,通常是因为工作区里没有对应变量或者路径包含中文。把.fis放到当前工程目录下,并在模型初始化回调里加上readfis赋值给基础工作区,就能稳定复用。验证方法也很直接:用同样的输入序列分别跑脚本仿真和Simulink模型,对比姿态曲线,偏差应只来自积分器的数值算法差异。
本文还有配套的精品资源,点击获取