简介:针对四旋翼无人机姿态控制中模型不完整、参数不确定和外界扰动等难题,这份 PDF 论文提出了一种基于 RBF 神经网络的无人机姿态自适应控制方法。内容从四旋翼动力学建模入手,详细阐述了利用 RBF 神经网络在线学习模型不确定项与外界扰动的原理,并基于反步法设计包含反馈控制和神经网络控制的自适应控制器;同时给出了网络权值自适应律,利用李雅普诺夫方法对闭环系统稳定性进行了严格证明。文中还提供了仿真结果分析,展示了在较大扰动情况下误差快速收敛、系统具备较好鲁棒性和自适应性的控制效果,可供相关课题直接参考。资源为单文件 PDF,大小约 3.65MB,适合自动化、控制工程及无人机方向的研究生和工程师学习使用,已有 269 人学习下载。 无人机飞得好不好,说到底就是一个字:调。但调PID这件事,做过飞控的人都知道有多折磨。尤其到了姿态环,P值给大了抖,D值给小了飘,悬停刚稳住,风一吹又开始荡。我前前后后调过好几架四旋翼,从纯手调到自动调参工具都试过,始终绕不开一个问题:控制器参数是固定的,但无人机在空中遇到的环境、载重、重心变化,却是随时在变的。所以当我决定把神经网络引入姿态控制时,思路很简单——与其花几个晚上反复试凑增益,不如让控制器自己学会补偿那些说不清、算不准的非线性扰动。这篇文章就记录了我用MATLAB/Simulink做“基于神经网络的无人机姿态自适应控制仿真”的完整过程,包括数学模型、控制器结构、代码实现和踩坑实录。不管是正在做毕业设计的自动化学生,还是想给飞控加点智能算法的爱好者,都可以直接参考这套框架。
1. 为什么传统PID在无人机姿态控制上"不够用"
1.1 四旋翼姿态控制到底难在哪
先说清楚被控对象。四旋翼是一个典型的欠驱动、强耦合、非线性系统:四个电机的转速变化,同时影响滚转、俯仰、偏航和总升力,任何一个通道的动作都会牵扯到另外几个通道。实际飞行中,气动阻尼、陀螺力矩、旋翼尾流扰动都在实时变化,而且变化的规律很难精确建模。
更麻烦的是不确定性。比如我用的机架是仿F450结构,装上电池、相机、云台之后,质量和转动惯量已经和出厂参数不一样了。换一块更重的电池,重心哪怕偏移几毫米,悬停时就需要一个恒定的姿态偏置来补偿。这种情况下,一组固定增益的姿态PID是难以同时满足“动态响应快”和“稳态不震荡”这两个要求的。
1.2 传统控制的三个死穴
第一个死穴是模型依赖。LQR、反步法、滑模控制都需要相对精确的模型参数,但这些参数在实际飞行中变化很大。第二个死穴是固定增益。PID三个增益一整定好就不再变,遇到大风、挂载投掷这类突变工况,要么反应太慢,要么超调振荡。第三个死穴是难以处理强非线性。四旋翼的动力学本身包含三角函数、耦合项、执行器饱和,传统的线性化方法在小角度假设下勉强可用,一旦做敏捷机动就原形毕露。
1.3 神经网络在这里扮演什么角色
神经网络在姿态控制里的定位,不是完全替代PID,而是做一个“在线补偿器”。核心思路是:基础控制仍由PID或PD完成,保证系统稳定;神经网络则负责估计系统的未建模动态、参数摄动和外部扰动,并把补偿量叠加上去。这样既保留经典控制的可靠性,又获得自适应能力。
我选用的是RBF(径向基函数)网络,原因是它结构简单、局部逼近能力强、在线计算量小,非常适合部署在飞控这种算力受限的嵌入式环境里。相比需要大量数据离线训练的深度网络,RBF网络可以在仿真过程中实时更新权值,真正做到“边飞边学”。
2. 被控对象建模:四旋翼的数学模型和参数设置
2.1 坐标系和欧拉角约定
仿真第一步是建立无人机的数学模型。我采用最常见的“机体坐标系B + 地面坐标系E”双坐标系方案:地面坐标系用来描述位置和姿态参考方向,机体坐标系固定在机身上,随无人机一起运动。两者之间的转换通过ZYX顺序的欧拉角实现——先偏航(yaw,绕Z轴),再俯仰(pitch,绕Y轴),最后滚转(roll,绕X轴)。
姿态角的角速度关系和很多人理解的不太一样:欧拉角的导数并不直接等于机体角速度。真正的运动学方程是:
roll_dot = p + sin(roll)*tan(pitch)*q + cos(roll)*tan(pitch)*r pitch_dot = cos(roll)*q - sin(roll)*r yaw_dot = sin(roll)/cos(pitch)*q + cos(roll)/cos(pitch)*r这里 p、q、r 是机体坐标系下的三轴角速度。我在仿真里没有做小角度近似,而是用完整的非线性方程,为的就是让控制器在大姿态角下也能被验证。
2.2 刚体动力学方程
姿态动力学方程用牛顿-欧拉法推导,核心是机体坐标系下的力矩平衡:
I * omega_dot = -omega × (I * omega) + M_prop + M_dist展开后就是三个轴的角加速度方程:
p_dot = ( (Iy-Iz)*q*r + Mx ) / Ix q_dot = ( (Iz-Ix)*p*r + My ) / Iy r_dot = ( (Ix-Iy)*p*q + Mz ) / Iz其中 Mx、My、Mz 是作用在机体上的总力矩,包括电机转速差产生的控制力矩和气动阻力矩。-omega × (I*omega) 这一项就是陀螺力矩,很多初学者会忽略它,但在快速偏航或翻滚时,它的影响非常明显——如果控制器没有足够的鲁棒性,这个交叉耦合项会让姿态响应出现肉眼可见的“串扰”。
2.3 模型参数怎么设置
我仿真里用的参数参考了一台1.5kg级四旋翼的实测值:
| 参数 | 数值 | 说明 |
|---|---|---|
| 质量 m | 1.5 kg | 含电池负载 |
| 滚转惯量 Ix | 0.012 kg·m² | 绕X轴 |
| 俯仰惯量 Iy | 0.012 kg·m² | 绕Y轴 |
| 偏航惯量 Iz | 0.022 kg·m² | 绕Z轴 |
| 力臂长度 l | 0.225 m | 机臂长度 |
| 升力系数 cT | 1.5e-5 N/(rad/s)² | 螺旋桨 |
| 阻力系数 cQ | 2.0e-7 N·m/(rad/s)² | 螺旋桨 |
电机到升力和力矩的关系按标准十字布局计算:
F_total = cT * (w1² + w2² + w3² + w4²) Mx = l*cT*(w1² - w3²) My = l*cT*(w2² - w4²) Mz = cQ*(w1² - w2² + w3² - w4²)通过这套模型,我就能在Simulink里搭建一个“虚拟无人机”,用它来验证控制器在不同扰动下的表现。
3. 神经网络自适应控制器设计
3.1 控制器总体架构
整个姿态控制采用的是内外环串级结构:内环是角速度环(快回路),外环是姿态角环(慢回路)。外环PID生成期望角速度,内环负责实际跟踪。神经网络不单独做控制器,而是并联在角速度环PID的输出端,叠加一个补偿力矩。这个架构最大的好处是:就算神经网络输出异常,底层的PID仍然能维持系统基本稳定,安全性有兜底。
这是我当时在笔记里画的信号流向,按这个理解就不会乱:
姿态角给定 ——> 姿态角PID ——> 角速度给定 ——> 角速度PID ——> [叠加神经网络补偿] ——> 电机混控 ——> 四旋翼动力学 ——> 姿态角输出 ↑ 角速度误差、误差导数注入RBF网络 —— 网络输出补偿力矩3.2 RBF网络结构
我用的RBF网络是最简洁的一种结构。输入层取两个关键状态量——角速度误差 e 和角速度误差变化率 ec,隐藏层取5个高斯径向基节点,输出层是1个补偿力矩。高斯径向基函数定义为:
phi_j(x) = exp( -||x - c_j||² / (2*sigma_j²) )其中 c_j 是第j个基函数的中心,sigma_j 是宽度。这一步有讲究:基函数中心和宽度的选取,直接决定网络能在哪个输入范围产生有效响应。我把中心均匀设置在 [-2, 2] × [-2, 2] 的网格上,宽度取0.8,这样覆盖了大部分实际飞行的角速度误差范围。中心太密会增加计算量,太疏则局部逼近性能下降,5个节点对单通道补偿来说是够用的。
网络输出就是简单的加权和:
M_nn = sum( w_j * phi_j(x) )w_j 是输出层权值,需要通过自适应律实时更新。
3.3 自适应律推导:为什么权值这样更新
这里如果不解释清楚,整个设计和“玄学调参”没区别。核心思想是构造一个李雅普诺夫函数,让权值更新律保证系统状态收敛。
定义角速度跟踪误差为 e = omega_d - omega,并构造滑模面 s = e + c*integral(e)。控制目标是让 s 趋于零。对滑模面求导,并把四旋翼动力学方程代入,整理后可以得到一个形如:
s_dot = -k*s + (f_uncertain - W*phi(x))的形式,其中 f_uncertain 是模型不确定项,Wphi(x) 是RBF网络对它的估计。如果能做到 f_uncertain ≈ Wphi(x),那么误差项就被抵消了,系统退化为线性稳定的形式 s_dot = -k*s。
我构造李雅普诺夫候选函数:
V = 0.5*s² + 0.5/gamma * sum(w_tilde_j²)其中 w_tilde_j = w_j* - w_j 是权值估计误差,gamma 是学习率。对 V 求导,选择合适的权值更新律使 V_dot ≤ 0,就能证明跟踪误差和权值误差是有界的。最终的权值更新律非常简单:
w_j_dot = gamma * s * phi_j(x)这就是经典的梯度下降形式的在线修正规则。在Simulink里实现时,我用一个积分器对 w_j_dot 做积分,就得到每一时刻的权值。学习率 gamma 我取0.05,太大权值震荡剧烈,太小自适应速度跟不上。
3.4 为什么不用标准BP网络和深度网络
这个坑我替大家踩过了。最开始我用的是标准BP神经网络(vanilla RNN的思路),把角速度误差序列作为输入,用时间反向传播更新权值。结果在纯仿真里表现尚可,但仔细分析后发现问题:BP反向传播每一层都要计算梯度,在线运行时每一控制周期都要做一遍矩阵运算,计算量是RBF的几十倍;更重要的是,BP网络是全局逼近,任何输入的微小变化都会导致所有神经元权值一起调整,在线学习的稳定性很差。
深度网络就更不用说了。姿态控制是一个毫秒级的实时任务,每个控制周期一般只有1~10毫秒,深度网络的前向推理和反向传播根本跑不满这个时间约束。RBF网络虽然结构简单,但足够逼近连续非线性函数,而且是局部响应——输入在某个基函数中心附近时,只有对应的少数节点被激活,权值更新互不干扰,在线学习的稳定性和收敛速度都好得多。
同理我也不推荐在姿态内环用LSTM这类时序网络。虽然它们能建模时间相关性,但计算开销和实现复杂度都是这个场景无法接受的。把时序记忆放在外环规划层面可以,放在内环控制层面就是给飞控增加不必要的负担。
4. 仿真搭建与核心代码实现
4.1 仿真环境选型的取舍
仿真平台我选的是MATLAB/Simulink,主要原因是控制算法验证方便、自带丰富的积分器和信号可视化工具。Simulink的S-Function块可以嵌入自定义的C语言或MATLAB代码,用来描述神经网络的在线更新特别顺手。为了仿真,我装的是MATLAB R2021a,这个版本对S-Function的支持已经很成熟。
如果不方便用MATLAB,也可以用Python替代:用numpy自己写四旋翼动力学,用scipy.integrate求解微分方程,控制器部分直接面向对象实现。我下文会给出两个版本的核心代码。
4.2 MATLAB脚本:四旋翼动力学与RBF控制器
先贴一段最核心的MATLAB脚本结构。动力学部分用一个函数文件描述,返回姿态角速度和角加速度:
function [dot_state] = quad_dynamics(state, M, params) % state: [roll; pitch; yaw; p; q; r] % M: 控制力矩向量 [Mx; My; Mz] % params: 结构体,包含惯性参数 phi = state(1); theta = state(2); psi = state(3); p = state(4); q = state(5); r = state(6); % 运动学方程(欧拉角变化率) dot_phi = p + sin(phi)*tan(theta)*q + cos(phi)*tan(theta)*r; dot_theta = cos(phi)*q - sin(phi)*r; dot_psi = sin(phi)/cos(theta)*q + cos(phi)/cos(theta)*r; % 动力学方程(角加速度) Ix = params.Ix; Iy = params.Iy; Iz = params.Iz; dot_p = ((Iy-Iz)*q*r + M(1)) / Ix; dot_q = ((Iz-Ix)*p*r + M(2)) / Iy; dot_r = ((Ix-Iy)*p*q + M(3)) / Iz; dot_state = [dot_phi; dot_theta; dot_psi; dot_p; dot_q; dot_r]; endRBF神经网络控制器的核心是权值更新。这段代码放在Simulink的S-Function里,每个采样周期调用一次:
function [M_nn, w_new] = rbf_controller(e, ec, w, x) % 输入:e角速度误差, ec误差变化率, 上一时刻权值, 基函数参数 % 输出:神经网络补偿力矩, 更新后的权值 % 基函数中心和宽度 c = [-2 -1 0 1 2; -2 -1 0 1 2]; % 二维中心网格 sigma = 0.8; % 计算高斯径向基输出 phi = zeros(1, 5); for j = 1:5 phi(j) = exp(-((e-c(1,j))^2 + (ec-c(2,j))^2) / (2*sigma^2)); end % 权值更新律 w_j_dot = gamma * s * phi_j gamma = 0.05; s = e + 0.5 * integral_ec; % 滑模面 w_dot = gamma * s * phi; w_new = w + w_dot * dt; % 输出补偿力矩 M_nn = w_new * phi'; end4.3 Simulink集成要点
在Simulink里搭建时,模块连线并不复杂。我用三个积分器串联表示姿态角和角速度状态:第一个积分器输出姿态角,输入是角速度;第二个积分器输出角速度,输入是角加速度;第三个积分器用来计算误差积分项。S-Function块接收角速度误差和误差变化率,输出补偿力矩给混控模块。
比较值得注意的两个细节:一是基本采样时间,我设的是0.002秒,即500Hz控制频率,模拟真实飞控(Pixhawk默认姿态环就是这个频率)。二是积分器初值,第一次跑仿真时我初始姿态角设置了[5°, -3°, 0°],相当于给控制器一个初始偏差,测试它能不能快速拉回水平。这里的响应曲线最能说明问题:初始偏差归零越快,控制器的瞬态性能越好。
4.4 Python版本:如果不用MATLAB
在Gazebo里跑过仿真的人都知道,ROS和Gazebo的环境配置有时候比写控制算法还折磨人。这里我分享一个轻量级的Python仿真方案,更利于理解控制算法的本质,对于只关注算法原理验证的场景是完全足够的。核心代码如下:
import numpy as np from scipy.integrate import solve_ivp class RBFController: def __init__(self, n_centers=5, gamma=0.05, dim=2): self.gamma = gamma self.sigma = 0.8 centers = np.linspace(-2, 2, n_centers) self.c = np.array(np.meshgrid(centers, centers)).T.reshape(-1, dim) self.w = np.zeros(len(self.c)) def phi(self, x): # 高斯径向基函数 d = np.linalg.norm(self.c - x, axis=1) return np.exp(-d**2 / (2 * self.sigma**2)) def update(self, e, ec, s): phis = self.phi(np.array([e, ec])) self.w += self.gamma * s * phis return np.dot(self.w, phis) # 补偿力矩 # 主仿真循环 def simulate(): params = {'Ix': 0.012, 'Iy': 0.012, 'Iz': 0.022} state = np.array([0.05, -0.02, 0, 0, 0, 0]) # 初始小角度偏移 rbf = RBFController() dt = 0.002 for t in range(10000): # 这里根据姿态角和角速度误差,调用rbf.update(),再叠加PID输出 # 用欧拉积分更新state passPython方案的好处是能直接在脚本里打日志、画曲线、做批量参数扫描,特别适合论文里的参数对比实验。缺点是没有Simulink那种可视化搭模型的方式,被控对象和控制器之间的连接需要自己在代码里捏合。最开始跑的时候,我建议先用Simulink搭一遍,对信号流有整体感觉,再迁移到Python做大规模实验。
5. 结果分析:自适应效果与踩坑实录
5.1 测试工况怎么设计才有说服力
仿真只设定悬停一个工况是不够的,控制器到底自适应在哪里,需要用多种工况来验证。我设计了四组实验:
- 阶跃给定:给一个30°的俯仰角阶跃,考察跟踪速度和超调量。
- 风扰力矩:在2秒时注入一个8 m/s的持续阵风扰动力矩,看看控制器能否顶住。
- 参数突变:在第5秒把质量从1.5kg突加到1.8kg,模拟挂载载荷的变化。
- 重心偏移:给机体添加一个恒定的偏置力矩,模拟重心偏离几何中心的情况。
每一组都有对照组,即不带神经网络、只有PID的版本。同一套PID参数跑两组对照,差异一目了然。
5.2 实测结果:神经网络补偿到底带来什么
先说结论:效果最惊艳的不是阶跃响应(PID本来就能跟踪得不错),而是在抗扰和参数突变这两项。
参数突变实验里,不带神经网络的PID重新爬升到稳态需要约3秒,期间俯仰角出现了约8°的偏差。带RBF补偿的网络则在0.2秒内就补偿掉了大部分偏置,稳态误差从2.1°降到了0.3°以内。原因是神经网络通过在线权值更新,快速辨识出了额外的重力力矩需要,并在输出端加了一个恒定的前馈补偿量。这个补偿量本质上是“学会”了新的挂载重量,不需要重新整定PID增益。
重心偏移实验更有意思。给无人机的滚转轴叠加强制扰动力矩后,PID控制器的反应是“对抗”——它会维持一个恒定的静差,或者说需要积分项慢慢吃掉偏差。RBF网络则直接把重心偏移的等效力矩学了出来,权值稳定后,控制器输出里多了一个恒定的偏置项,恰好等于重心偏移产生的力矩。这就是“补偿”和“对抗”的本质区别:神经网络学会的是“原因”,PID只能在“结果”层面纠偏。
5.3 仿真过程中踩过的坑
排在首位的坑是基函数中心范围设置不当。我第一次设中心范围是[-1, 1],结果大角度机动时输入超出覆盖范围,径向基输出近乎为零,网络直接“失明”了,补偿量消失,PID独自扛下所有。改成[-2, 2]之后问题消失。这是RBF网络特有的问题:网络的表达能力只在中心覆盖的区间内有效,输入一旦超出覆盖范围,输出就截断为零。基函数覆盖范围必须大于最大误差范围,没有捷径。
第二个坑是学习率过大导致的权值振荡。我把 gamma 设成0.3时,前200步网络权值剧烈震荡,补偿力矩像噪声一样叠加在控制输出上,差点以为控制器发散。后来把学习率降到0.05,并用滑模面s代替纯误差e做驱动项,震荡立刻缓解。这里的原理不复杂:学习率大,权值对误差的响应快,但如果误差本身还在振荡,权值就会跟着振荡,形成正反馈恶性循环。
第三个坑比较隐蔽:Simulink代数环。S-Function的当前输出依赖于当前输入,而输入又由输出决定时,Simulink会报代数环错误。我的解决办法是在S-Function输出后加一个单位延迟块(unit delay),虽然理论分析时差了一个采样周期,但实际控制频率500Hz下,一个周期的延迟对系统稳定性的影响可以忽略。这个坑在纯代码仿真里不存在,只在Simulink里遇到。
第四个坑关于代码生成部署。仿真跑通后,我曾想过把控制器迁移到Pixhawk真机上。但MATLAB Coder生成的C代码默认只用单精度浮点,RBF高斯计算里的exp函数在Cortex-M4上开销不小,一个控制周期根本算不完。后来我用查表法替代实时的exp计算才勉强压缩到1ms以内。如果打算最终上真机,建议从一开始就把计算量控制住,比如固定基函数中心后预先计算好高斯函数查找表。
6. 关于这个系统的设计和扩展方向
6.1 整体设计复盘
回顾整个仿真项目,值得肯定的是分层控制架构和“PID保底 + RBF补偿”的组合方式。这种设计的好处不言而喻:神经网络部分即使失效,系统也不至于完全失控,这在工程上是极其重要的保险机制。整个系统的设计逻辑是:先确定控制目标,然后建立被控对象的非线性模型,再选择能够在线学习不确定项的网络结构,最后用仿真验证各种工况下的表现。每一步都有清晰的动机,而不是拍脑袋堆砌算法。
我最初的方案里也考虑过直接套用完全神经网络控制器——即让网络直接输出全部控制力矩。第一版仿真就发现,网络初始权值为零时,无人机根本飞不起来,需要很长的预训练时间。而“PID + 自适应补偿”的方案,初始阶段PID保证基本飞行能力,网络可以在飞行过程中从零开始逐渐学习,两者配合默契,几乎没有冷启动问题。
6.2 后续在飞控上的扩展思路
在整个项目的收尾阶段,我仔细思考了这套算法的扩展空间。
目前RBF网络只能补偿每个轴独立的力矩偏差,轴间耦合还没有建模。复杂机动时滚转和俯仰之间的交叉耦合依然明显,下一步可以用多维输入把p*q耦合项一起送入网络学习,理论上能进一步减小动态误差。
从控制角度,现在神经网络补偿的是“控制力矩”层面的不确定项,还没有触及“参考模型”层面的自适应。如果改用模型参考自适应控制结构,让网络直接调整到期望闭环动态的误差,那就能针对不同任务的敏捷性需求在线调整控制带宽,这是目前PID加固定补偿方案做不到的。
在部署方面,如果要做真机验证,把Simulink模型通过Embedded Coder生成C代码、部署到Pixhawk飞行控制器是通用的路线。需要注意的是,一个控制周期内RBF网络的5个径向基函数求值再加上PID计算,在STM32F4这类主控上的耗时已经比较可观,要么降低控制频率到250Hz,要么减少基函数的数量。
6.3 给想复现这个项目的人的建议
根据我做这个仿真的全程经验,给准备复现的读者三点建议。第一,先跑通纯PID对照版本,观测记录最基本的姿态响应,再叠加RBF模块。直接上完整系统会分不清问题出在控制器设计还是网络调参。第二,把所有参数写到一个配置文件中,参数扫描时批量跑比手动改Simulink块参数高效得多。仿真中我每次改一组参数就要另存一份模型文件,管理起来非常混乱,后来干脆用脚本统一修改m文件中的参数并调sim函数,才从重复劳动里解脱出来。第三,记录每一组实验的完整数据,包括权值轨迹、控制力矩、姿态误差曲线,这些数据对写论文和答辩非常重要。仿真完随手导出成mat文件或csv,比事后补做实验省时间得多。
我至今还记得第一次看到权值轨迹稳定收敛时的场景:俯仰角被强风拉偏6°,BP补偿力矩迅速顶上去,姿态在不到一秒钟内回到水平,而PID对照组还在0.5°误差附近挣扎。那一瞬间你会直观感受到自适应控制的真正价值——它不是调出一个更好的参数,而是让系统有了自我纠正的能力。这种能力在纯仿真中已经足够令人兴奋,如果有一天跑在真机上,飞行器的适应性和鲁棒性又会上一个台阶。希望这篇文章能帮你绕过我踩过的坑,在仿真的基础上走得更远。
本文还有配套的精品资源,点击获取