news 2026/9/13 21:04:26

四旋翼ADRC仿真:LADRC与ESO的Simulink实现与调参

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
四旋翼ADRC仿真:LADRC与ESO的Simulink实现与调参

简介:面向四旋翼无人机控制研究与开发者,这套工程包提供了完整的自抗扰控制(ADRC)实现方案,涵盖无人机动力学建模、扩展状态观测器设计、扰动抑制与稳定控制策略。压缩包共3个文件,体积仅113KB,包含两个Simulink模型文件和一个MATLAB脚本:前者分别对应四旋翼物理模型与线性自抗扰控制器(LADRC)模型,后者用于配置无人机质量、惯量等关键参数,便于直接开展仿真调试与参数整定。LADRC部分基于扩展状态观测器与自适应策略,能够动态补偿外部干扰,为从传统PID到自抗扰控制的进阶理解提供了可运行示例。目前已有180人学习下载,适合具备一定控制理论基础的无人机爱好者与相关专业学生,可借助该资源快速验证算法效果、掌握Simulink建模方法,并作为课程设计或科研实验的起步模板。

1. 四旋翼ADRC:把“总扰动”当成状态估计出来再消掉

调试过quadcopter悬停的人都有体会,PID参数在室内无风时好好的,拿到室外一阵侧风过来就开始低频晃动;换个载重,又要从头调。ADRC(Active Disturbance Rejection Control)之所以在无人机圈子里受欢迎,是因为它把模型失配、阵风、机体磨损这些不确定因素统一看成“总扰动”,用扩展状态观测器实时估出来并补偿掉。这个包里给的不是教学demo,而是一套能直接跑的四旋翼ADRC仿真工程:Quadparameters.m负责物理参数,PlantModel.slx是被控对象,LADRC_GAO.slx是线性自抗扰控制器,走的是高志强(Gao)的带宽参数化路线。适合刚把PID调明白、想往现代控制理论再走一步的飞控工程师。

2. ADRC与LADRC_GAO:从韩京清到带宽参数化

2.1 韩京清提出的ADRC核心构成:TD、ESO、NLSEF

韩京清先生早年指出PID的“误差取微分”在实际系统中容易放大噪声,于是重新设计了控制器结构。第一是跟踪微分器(Tracking Differentiator, TD),它给参考信号安排一个过渡过程,同时提取出近乎无超调的微分信号;第二是扩展状态观测器(Extended State Observer, ESO),这是整个ADRC的发动机,它把未建模动态和外部扰动的总和作为“扩张状态”估计出来;第三是非线性状态误差反馈(NLSEF),用非线性组合代替PID的线性加权,提高收敛速度。这三块组装起来,控制器不再依赖精确模型,而是靠观测器实时感知系统在多大程度上偏离了标称模型。

在四旋翼上,这个思想非常直接。悬停时重力与推力平衡,但桨叶老化、电池电压下降、突风都算作扰动。传统PID只能通过积分慢慢抵消稳态误差,而ADRC把扰动当作一个虚拟状态,在下一个控制周期就补偿掉大部分。这也是为什么ADRC在无人机抗风研究中经常被拿来和PID、LQR做对比。

但韩京清原始版本里的非线性函数,比如fal(e,α,δ),参数多,整定靠试凑。高志强在2003年前后把它简化成线性形式,让工程师只需要敲两个带宽数字就能用,这就是后面LADRC_GAO.slx的理论根源。

2.2 高志强的LADRC:ESO与控制律线性化,带宽参数化

LADRC做两点简化:第一,省掉跟踪微分器,参考信号直接进入误差计算;第二,把ESO和非线性反馈都线性化。以二阶系统为例,被控对象写成:

[ \ddot{y} = f(y,\dot{y},w,t) + bu ]

其中f是总扰动,b是控制增益的已知部分。LADRC把f也当作状态,构造三阶线性ESO:

[ \begin{aligned} \dot{z}_1 &= z_2 + \beta_1 (y-z_1) \ \dot{z}_2 &= z_3 + b u + \beta_2 (y-z_1) \ \dot{z}_3 &= \beta_3 (y-z_1) \end{aligned} ]

z3就是总扰动f的估计。控制律写成:

[ u = \frac{u_0 - z_3}{b} ]

其中u0可以只是PD控制:k_p(r-z1) + k_d(-\dot{z}_1)。这样扰动被前馈抵消,系统被拉回到纯积分的标称结构。高志强把观测器增益设计成带宽形式:

[ \beta_1=3\omega_o,\quad \beta_2=3\omega_o^2,\quad \beta_3=\omega_o^3 ]

控制器增益同理,k_p=ω_c²,k_d=2ω_c。这样一来,整个控制器只有两个参数:ω_o(观测器带宽,越快则状态估计收敛越快,但太高会把测量噪声放大)和ω_c(控制器带宽,决定闭环响应速度)。LADRC_GAO.slx这个文件名里的GAO就是高志强的姓,模型里用的正是这套带宽参数化。

对比一下传统PID和LADRC的调参工作量。PID至少需要调P、I、D三个参数,而且对于姿态内环和位置外环两套PID,耦合时互相牵制。LADRC每个通道只需要两个带宽,而且物理意义明确:ω_c针对参考跟踪速度,ω_o针对抗扰能力。用这个仿真工程做实验时,绝大多数时间是在来回调整这两个带宽,而不是面对六七个互相影响的PID系数。

2.3 Quadparameters.m参数与控制器参数的关系

Quadparameters.m负责把四旋翼的物理参数灌进MATLAB工作区。典型的参数包括:机体质量m,旋翼力臂l,转动惯量Ixx、Iyy、Izz,以及螺旋桨的升力系数、扭矩系数等。下面是这个脚本里常见的一段:

%% Quadcopter physical parameters m = 1.2; % mass kg g = 9.81; % gravity m/s^2 l = 0.25; % arm length m Ixx = 0.018; % inertia around X axis kg.m^2 Iyy = 0.018; Izz = 0.034; Kf = 1.2e-5; % motor force coefficient Km = 2.0e-7; % motor torque coefficient

这里m和I决定被控对象的响应快慢,Kf和Km影响控制输入到实际力的换算。在仿真设计中,这些参数被Simulink模型里的增益模块或MATLAB Function块引用。用LADRC调参时,ω_c的初始值通常取开环穿越频率的2~3倍,而ω_o要设计成ω_c的5倍左右;如果Quadparameters.m里把Ixx改小,转动惯量下降,同样的ω_c会让角速度响应变快甚至振荡,这时就需要适当降低ω_c。

还有一点容易被忽略:Quadparameters.m里如果定义了电机响应时间常数T_motor,那么LADRC的设计必须把它算进总扰动里。ESO虽然能估计掉未建模动态,但电机延迟太大时,观测器的补偿会出现相位滞后。所以在仿真里,我一般会把电机一阶惯性环节的带宽写进PlantModel.slx,然后根据它的时间常数把ω_o限制在电机带宽的1/3以内,否则高频段抖得厉害。

3. 在Simulink里搭PlantModel.slx:动力与测量回路

3.1 四旋翼动力学模型:位置环与姿态环的耦合

四旋翼是一个典型的内外环结构。外环是水平位置,状态是[x, y, z, vx, vy, vz];内环是姿态角,状态是[φ, θ, ψ, p, q, r]。水平位置的变化由机体倾斜产生的水平推力分量决定:

[ m\ddot{x} = (c_{\psi}s_{\theta}c_{\phi} + s_{\psi}s_{\phi}) \cdot T ]

[ m\ddot{y} = (s_{\psi}s_{\theta}c_{\phi} - c_{\psi}s_{\phi}) \cdot T ]

[ m\ddot{z} = c_{\theta}c_{\phi} \cdot T - mg ]

姿态角速度方程则跟转动惯量和力矩直接相关:

[ \dot{p} = \frac{I_{yy}-I_{zz}}{I_{xx}} q r + \frac{\tau_\phi}{I_{xx}} ]

PlantModel.slx通常把这些公式用积分器和乘法器搭出来,而不是用一个大的MATLAB Function块。这样做的好处是能直观看到每个积分环节的初始值设置。比如仿真开始时要让四旋翼稳定悬停在一个点上,那么姿态角的初始值要和初始油门匹配,否则一启动就会先冲出去一段。常见做法是把高度通道的期望值设成1m,初始z从0开始,控制器内部会通过积分把自己拉起来。

位置环和姿态环的耦合在这里体现得很明显。外环控制器给出期望横滚角和俯仰角,内环再把角度跟踪到给定值。如果内外环都用LADRC来设计,两个ESO会同时工作。外环估计的是风、推力衰减等慢变扰动,内环估计的是力矩不平衡、重心偏移等快变扰动。如果两个观测器带宽设置得太接近,内外环之间可能出现频率打架,具体表现是位置响应缓慢但姿态高频抖动。我在调参时通常保持外环ω_c = 1~2 Hz,内环ω_c = 8~12 Hz,差距拉开到5倍以上。

3.2 从Quadparameters.m自动加载模型参数到工作区

打开Quadparameters.m直接运行,它会把参数写入base workspace。需要注意的是,Simulink模型里的变量名必须和脚本里完全一致,否则模型里引用的变量会显示为undefined。为了保证仿真可复现,我习惯在脚本末尾加上一段参数校验:

%% sanity check assert(Ixx > 0 && Iyy > 0 && Izz > 0, 'Inertia must be positive'); assert(kf > 0 && km > 0, 'Force/torque coefficients must be positive'); disp('Quadcopter parameters loaded successfully');

运行这个脚本后,在命令行输入who就能看到所有变量。这一招在调试PlantModel.slx时特别有用。当你双击模型里某个Gain模块,看到它的参数是一个变量名,比如l,但脚本没定义,模型就会在仿真启动时报错。提前做校验能少踩很多坑。

如果用的是R2019b之后的版本,建议在模型初始化回调里直接调用Quadparameters脚本。做法是在Simulink模型窗口中,进入Model Properties,在InitFcn回调中写run('Quadparameters.m')。这样每次点“运行”按钮都会自动刷新参数,不会出现改了脚本但工作区还是旧参数的尴尬情况。

3.3 将PlantModel.slx与LADRC_GAO.slx组成闭环:几个关键连线

这个压缩包里的两个Simulink模型是分离的,需要自己组成闭环。一般我会新建一个顶层模型,把PlantModel.slx和LADRC_GAO.slx各用子系统引进来。连线时需要明确三组端口:

  • 参考输入:位置和偏航角的期望值,通常是一个4维向量:xe, ye, ze, psi_e
  • 测量反馈:PlantModel输出的实际位置、速度和姿态角
  • 控制输出:四个电机的PWM等效输入,或者直接是推力T和力矩τφ、τθ、τψ的合成

如果PlantModel.slx的输出端是姿态角和角速度,LADRC_GAO.slx的输入端需要匹配。两边的端口宽度不一致时,Simulink会在仿真开始时提示端口维度错误。这时可以去Signal Attributes里查看到底是哪里多了个信号。最常见的错误是把[p q r]角速度写成[phi theta psi]角度,导致控制器拿到的是角度而不是角速度,整个系统直接振荡。

我建议在顶层模型加两个Scope或To Workspace模块,分别记录控制量和姿态角。这样跑完仿真后,用out.logsout就能把数据拖到工作区里继续分析。如果不想在每个通道上加Selector模块,可以用向量拼接技巧:

% 从simout取数绘图 t = simout.time; phi = simout.signals(1).values(:,1); theta = simout.signals(1).values(:,2); psi = simout.signals(1).values(:,3); plot(t, phi*180/pi, t, theta*180/pi); grid on;

这段代码把姿态角的弧度转成角度并绘图。为什么我总强调要转成角度?因为Simulink里默认单位是弧度,直接看数据会觉得角度变化很小,误以为响应不足,其实已经超调了。

4. LADRC_GAO.slx内部实现:ESO观测器与扰动补偿的仿真逻辑

4.1 ESO的方程与Simulink实现要点

打开LADRC_GAO.slx,你会看到核心是一个ESO子系统。它以被控对象的输出y和控制输入u为输入,输出三个估计状态:z1是输出估计,z2是速度估计,z3是扰动估计。在Simulink里,ESO一般用积分器搭出来,对应前面的微分方程组。为了在仿真里抑制代数环,需要把观测器增益β1、β2、β3放到积分器之后、反馈相加点之前。

一个典型的ESO实现排列方式是:从上到下依次是z1、z2、z3的积分器,每个积分器的输入来自前一状态的反馈和误差加权。如果直接在MATLAB Function块里写ESO,代码会很短:

function [z1, z2, z3] = eso(y, u, b0, wo) persistent x1 x2 x3; if isempty(x1) x1 = 0; x2 = 0; x3 = 0; % initial state end dt = 0.001; % need actual solver step e = y - x1; % state update x1 = x1 + dt*(x2 + 3*wo*e); x2 = x2 + dt*(x3 + b0*u + 3*wo^2*e); x3 = x3 + dt*(wo^3*e); z1 = x1; z2 = x2; z3 = x3;

注意这段代码里硬编码了dt=0.001,这在固定步长仿真里没问题,但如果换成变步长求解器,会引入额外误差。实际工程里我推荐在Simulink里用积分器模块,让求解器自己控制步长,观测器精度会高得多。这也是为什么给你的这个包里LADRC_GAO.slx用图形化积分器而不是MATLAB Function的原因。

ESO初值设置很关键。如果z3初始值设成0,而实际作用在quadcopter上的重力或其他恒定力不为0,那么在仿真最初几毫秒内z3会从0快速爬升。若此时控制器已经输出大油门,容易造成初始尖峰。因此我一般把z3的初始值设成悬停时的等效推力除以b0,这样扰动估计从仿真开始就贴近实际值,起飞阶段不会猛点头。

4.2 控制器带宽ωc与观测器带宽ωo的整定原则

LADRC的参数只有ωo和ωc,但整定并不是随便填两个数就行。下面这张表是我在四旋翼仿真里经常采用的起点,按不同响应速度需求划分:

应用场景ω_c (rad/s)ω_o (rad/s)说明
高度外环2~415~25响应慢,抗风依赖ESO提供前馈
姿态角环10~1560~100需要快速跟随杆量
角速度内环20~30100~200噪声放大风险高

ω_o和ω_c的比值一般取5~15。比值太小,扰动估计跟不上,系统会有稳态误差;比值太大,高频噪声通过ESO进入控制量,表现为电机指令颤抖。实际调试时,先把ωc调到一个使系统不振荡的值,再逐步增大ωo,直到扰动响应开始出现噪声尾巴为止,然后退回一点。这个“退回一点”就是工程上的安全余量。

控制输入b0也是一个隐藏参数。b0是被控对象中控制增益的估计值。对姿态通道,b0是角加速度与力矩的比值,比如b0≈1/Ixx。如果b0比真实值小,ESO会努力把差的这部分也算进扰动里,虽然最终可能也能稳定,但会消耗更多控制能量。所以在建好的PlantModel.slx里,我习惯先用一次开环阶跃响应,直接测出角加速度与输入力矩的比值,把这个实测值填回LADRC_GAO.slx里的b0,效果立竿见影。

4.3 仿真发散、抖动与数值积分器的坑

很多人在这个项目里遇到的第一个问题是:把LADRC_GAO.slx连上PlantModel.slx后,仿真开始不到0.1秒就发散,屏幕上全是红色错误。原因多半有三个。

第一个是采样时间不一致。ESO内部积分器如果用连续时间,而控制器里某些模块用了离散控制,但离散周期设得太大,比如0.1s,那么观测器根本无法稳定响应。解决办法是把LADRC_GAO.slx里的所有离散模块统一到一个采样率,通常取1ms或更小。

第二个是代数环。如果你把ESO的输出z3直接送回控制器并参与ESO自身的输入计算,Simulink会检测到代数环。比如,有的模型里把控制量u也作为ESO输入,而u又依赖z3,这就在一个步长里形成了互相依赖。解决办法是在u进ESO之前加一个Memory模块或者Unit Delay,打破代数环。代价是会引入一个步长的滞后,但对于1ms步长来说完全可接受。

第三个是数值求导。有些自己搭的模型里为了得到角加速度,会对角速度信号做Derivative。Derivative模块对噪声极度敏感,和ESO里大量的高增益反馈叠加后,容易在积分时产生跌代振荡。正确做法是从PlantModel.slx的积分器后端直接引出状态,而不要用Derivative模块。

下面这段代码用脚本方式对LADRC_GAO.slx做参数批量扫描,找出临界稳定的ωc值:

wc_vec = 5:1:20; res = zeros(size(wc_vec)); for i = 1:length(wc_vec) wc = wc_vec(i); wo = wc * 8; sim('LADRC_GAO.slx'); y = tout; % actually use logsout % simple stability check: max |phi| < 30 deg res(i) = max(abs(logsout.get('phi').Values.Data)); end plot(wc_vec, res);

这段代码把ωc从5扫到20,每个点跑一次仿真,记录最大姿态角偏差。如果发现某个ωc下姿态角超过30度,说明已经接近失稳边界。用这个曲线选ωc,比瞎试快很多。

5. 把LADRC调成能扛风突:增益调度与验证技巧

当你在Simulink里把LADRC跑通了,下一步就是验证它是否真的扛得住风。常见做法是在PlantModel.slx里加一个风扰信号,比如在水平速度方程上叠加一个随时间变化的加速度干扰,模拟突然来一阵侧风。我通常用Signal Builder加一个阶跃加上短暂脉冲的组合,这样既能测稳态抗扰,又能测瞬态恢复。

风扰加到系统里后,如果发现姿态角恢复太慢,第一反应应该是增大ωo,而不是盲目增大ωc。因为ωc决定的是参考跟踪速度,对阶跃风扰的抑制主要靠ESO的扰动估计速度。有个验证技巧:把ESO的z3信号通过Scope拉出来,和真实扰动信号对比。如果两者曲线形状一致,只是滞后一小段,说明ωo够用;如果z3明显跟不上真实扰动,加大ωo直到滞后缩短到可接受范围。

另一个实用技巧是做“假设扰动不可测”的实验。在仿真中把LADRC的控制输入强制设置为u=(u0-z3)/b0,然后把z3手动置零,相当于关掉扰动补偿,看系统会变成什么样子。你会发现系统变成纯PD控制,风扰下出现大的稳态偏差。这个对比实验能直观证明ESO的补偿有效,也比空口说“ADRC抗扰强”有说服力得多。做完这组对比,把两组仿真数据保存成不同的结构体,用一段脚本批量绘制抗扰性能指标:

% 比较有ESO补偿和没有ESO补偿时的最大偏差 with_eso = logsout_with.get('phi').Values.Data; without_eso = logsout_without.get('phi').Values.Data; max_dev_comp = max(abs(with_eso) - abs(phi_ref)); max_dev_uncomp = max(abs(without_eso) - abs(phi_ref)); fprintf('Compensated max dev: %.3f deg\n', max_dev_comp*180/pi); fprintf('Uncompensated max dev: %.3f deg\n', max_dev_uncomp*180/pi);

最后你可以尝试把ωc和ωo做成随时间缓慢变化的斜坡参数,模拟飞行中电池电压下降导致的执行器增益变化。运行同一组仿真脚本,观察LADRC在参数漂移下是否仍然保持稳定。如果出现轻微振荡,把ESO带宽向后调小10%再跑一次。经过这几轮对抗性测试,你对这个quadcopter ADRC仿真工程的理解就从“能跑通”升级为“能把控边界”了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 21:04:16

从51到DSP:五套单片机示波器方案与设计要点全解析

简介&#xff1a;基于51、STM32、TMS320F28033和Arduino四种平台的示波器设计资料包&#xff0c;面向电子爱好者、单片机学习者以及需要完成课程设计或电子竞赛的开发者。资料提供五套完整方案&#xff0c;从OLED显示的51简易示波器&#xff0c;到STM32数字示波器、20MHz手持式…

作者头像 李华
网站建设 2026/9/13 21:02:47

8款高性价比一键生成论文工具横向实测,本硕博避坑全流程指南

前言&#xff1a;AI 写论文乱象频发&#xff0c;实测 8 款工具理清适配边界 每到毕业季&#xff0c;本科生、硕博生都会集中寻找 AI 论文辅助工具&#xff0c;市面各类写作软件层出不穷。然而&#xff0c;许多工具存在明显短板&#xff1a;虚假参考文献、无法匹配本校格式、不支…

作者头像 李华
网站建设 2026/9/13 20:58:28

ODOO采购七大核心流程落地实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 20:57:44

OpenAI Agents SDK 流式运行中途怎么取消并拿到已完成结果?

OpenAI Agents SDK 流式运行中途怎么取消并拿到已完成结果&#xff1f; 【免费下载链接】openai-agents-python A lightweight, powerful framework for multi-agent workflows 项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python 用 OpenAI Agent…

作者头像 李华
网站建设 2026/9/13 20:56:13

8款小众桌面蓝牙音箱实测:选型逻辑与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华