四旋翼的ADRC姿态控制,总给人一种“玄学调参”的错觉。我最早接触自抗扰控制时,也被满屏的扩张状态观测器、总扰动、带宽法这些名词劝退过,直到真正从标题里那条滚转通道的角加速度方程往下一层层拆,才觉得这事其实比想象中有意思得多。这篇文章就从一个贯穿始终的核心公式聊起,把ADRC从物理模型、控制器结构、离散化代码到真机调参整条链路完整走一遍。适合正在做四旋翼姿态控制、想从经典PID转向ADRC,或者被“总扰动”“带宽法”绕晕的开发者。读完你会发现,所谓玄学,大部分只是没把模型到代码之间的因果链条理顺而已。1. 先把核心公式看明白:滚转通道的角加速度方程1.1 方程里每一项的物理含义四旋翼滚转通道的姿态动力学,写到最后其实就是一条二阶方程:$$J_x \ddot{\phi} = \tau_\phi + d_\phi$$其中 $J_x$ 是整机绕机体X轴(机头方向)的转动惯量,$\ddot{\phi}$ 是滚转角加速度,$\tau_\phi$ 是四个电机产生的滚转力矩,$d_\phi$ 则是一个“集所有不确定因素之大成”的扰动项。很多人在这一步就犯迷糊:既然是滚转,为什么方程里没出现陀螺力矩、气动阻尼、重心偏移、电机延迟?答案是它们全都被塞进了 $d_\phi$ 里。ADRC的整个思想内核,就是承认你很难精确写出一个完美的飞机模型,那就把模型误差、外部风扰、交叉耦合、执行器非线性全部打包成一个总扰动,用观测器把它实时估计出来,再在控制量里反向补偿掉。这条方程本身也说明了滚转力矩从哪来:四旋翼产生滚转,靠的是左右两侧电机推力之差。假设力臂为 $L$,右侧电机推力 $F_R$ 与左侧 $F_L$ 的差为 $\Delta F$,那么 $\tau_\phi \approx L \cdot (F_R - F_L)$。螺旋桨产生的推力又与油门或转速近似成平方关系。也就是说,你的控制指令从混控器出来,最终会映射成某个差动油门,再变成角加速度。这个链条上每个环节的增益都不是绝对线性的,但ADRC允许你只用一个粗糙的 $b_0$ 去代表整条链路的增益,剩下的偏差全交给总扰动项处理。注意:这条方程里的 $J_x$ 不是恒定不变的值。电池、载重、机架配件的更换都会改变整机转动惯量,这也是为什么同一套参数换一架飞机后必须重新调。ADRC的好处在于,你不需要把 $J_x$ 测到实验室精度,只要有一个量级准确的估算值就行。1.2 “总扰动”到底是什么把 $d_\phi$ 单独拎出来说,因为它是理解ADRC的门槛。它不是某一个具体的物理量,而是“被控对象真实动态与控制模型之间的全部差异”。包括但不限于:未建模动态:电机和螺旋桨的滞后、电调响应延迟、机架柔性形变。耦合项:四旋翼滚转时会连带引起偏航和俯仰方向的陀螺耦合。外部扰动:突风、载荷变化、推力线不经过质心产生的附加力矩。模型参数误差:$J_x$ 估算不准、混控器增益漂移。如果你用经典PID去处理这些扰动,思路是靠误差反馈硬扛:扰动来了,姿态偏了,控制器看到偏差再使劲拉回来。ADRC的思路则是先把扰动“看”出来,然后直接在控制量里减去一个等量反作用,让系统表现得像个完全的积分器型对象。打个比方。开一辆车走一条坑洼路段,PID是那种眼睛盯着仪表盘,发现车速掉下来了就踩油门、发现车速快了就收油的司机,反应永远滞后一拍。ADRC则是一个经验丰富的老司机,他感觉方向盘和车身反馈不对,反推出现在路面给了多大的阻力,提前一脚油门把阻力抵消掉。这个“反推阻力的过程”,就是扩张状态观测器在做的事。1.3 控制目标在方程里怎么表达有了这条角加速度方程,滚转通道的控制目标就很清晰了:给定期望姿态角 $\phi_{ref}$,你要设计一个控制力矩 $\tau_\phi$,让实际的 $\phi$ 尽可能快而无超调地跟上去,同时抵消掉 $d_\phi$ 的影响。写成误差形式的话,如果定义姿态角误差 $e_\phi = \phi_{ref} - \phi$,角速度误差 $e_{\dot{\phi}} = \dot{\phi}_{ref} - \dot{\phi}$,理想的控制率形式是让角加速度等于某种比例微分组合:$$\ddot{\phi}{des} = k_p e\phi + k_d e_{\dot{\phi}}$$然后反解出需要的力矩:$$\tau_\phi = J_x \ddot{\phi}{des} - d\phi$$这里 $J_x$ 是模型增益,$d_\phi$ 是扰动。问题来了:$d_\phi$ 不可能直接测量,$J_x$ 也未必准。ADRC的做法,是用一个扩张状态观测器(ESO)把 $\phi$、$\dot{\phi}$、$d_\phi$ 三个量全部在线估出来,然后用估计值去实现上面这条控制律。这就是整个算法能落地的底层逻辑。一旦想通了这一点,后面所有代码和调参步骤都是在回答一个问题:怎么把 $d_\phi$ 估准并且补偿掉。2. 自抗扰控制器内部结构与设计逻辑2.1 三个关键模块各司其职一个完整的ADRC姿态控制器,通常包含三个模块:跟踪微分器(TD)、扩张状态观测器(ESO)、以及误差反馈控制律。跟踪微分器的作用是给参考输入“整形”。四旋翼飞控里,遥控器打杆给的是阶跃式的期望姿态角,如果直接让姿态环去追一个阶跃,必然带来超调和振荡。TD会把阶跃参考安排成一条平滑的过渡轨迹,把“麋鹿跳悬崖”变成“盘山公路”,控制器跟着这条平滑轨迹走,大幅降低超调。它的实现形式并不复杂,常见的安排过渡过程可以用二阶最速控制或简单的低通滤波来完成,飞控里不发烧的话用一阶惯性滤波也够用。扩张状态观测器是整个ADRC的心脏。常规状态观测器只能估计系统的已知状态,ESO多出来一阶状态专门用来观测总扰动,这也是“扩张”二字的来源。对于滚转通道这个二阶被控对象,ESO一般设计成三阶结构:第一阶状态估计滚转角速度,第二阶估计滚转角加速度,第三阶估计总扰动。误差反馈控制律则负责把状态误差转换成控制量。它不需要复杂的积分项,因为扰动已经被ESO补偿掉了,剩下的是一个近似纯积分特性的被控对象,用一个PD组合就能获得很好的性能。这也是ADRC对比PID一个直观的差异:PID里积分项在扛扰动,ADRC里观测器在扛扰动,PD组合反而干干净净。2.2 ESO是怎么“看到”扰动的ESO的估计原理值得多说两句。假设你有一个二阶被控对象:$$\ddot{y} = b_0 u + d$$其中 $y$ 是输出(滚转角或角速度),$b_0$ 是控制增益的估计值,$u$ 是控制量,$d$ 是总扰动。把扰动 $d$ 新定义为一个状态变量 $x_3 = d$,并假设它的导数是某个有限值 $\dot{d}$,系统就变成了三阶线性系统:$$\dot{x}_1 = x_2$$ $$\dot{x}_2 = x_3 + b_0 u$$ $$\dot{x}_3 = \dot{d}$$ESO要做的事情,就是用可测量的输出 $y = x_1$ 同时反推 $x_1$、$x_2$、$x_3$ 三个状态。具体的反馈结构是:先算出输出估计误差 $e = \hat{x}_1 - y$,然后通过三个增益 $l_1、l_2、l_3$ 把这个误差反馈到三个状态方程里,让估计值快速收敛到真实值。只要观测器带宽足够高,$\hat{x}_3$ 就能紧跟真实的总扰动。这里有个非常实用的问题:三个增益 $l_1、l_2、l_3$ 该怎么选?如果手工一个个试,确实是玄学。但有一种成熟的带宽法整定方案:把观测器的特征方程配置成 $(s + \omega_o)^3$,令:$$l_1 = 3\omega_o,\quad l_2 = 3\omega_o^2,\quad l_3 = \omega_o^3$$这样一来,整个ESO的性能只由一个参数 $\omega_o$(观测器带宽)决定,调参的工作量瞬间从一个三变量问题降到一个单变量问题。这是ADRC从“玄学”走向“科学”的关键一步,也是四旋翼工程落地中最常用的配置方式。提示:$\omega_o$ 并不是越大越好。它决定了观测器对扰动的跟踪速度,但也决定了传感器噪声的放大程度。后面调参章节会专门讲这个取舍。2.3 控制律与 $b_0$ 的微妙关系ESO估计出总扰动 $\hat{d}$ 之后,控制律处理得很巧妙。如果想让系统表现为 $\ddot{y} = u_0$,其中 $u_0$ 是我们期望的虚拟控制量,那么实际控制量应该取:$$u = \frac{u_0 - \hat{x}_3}{b_0}$$把这条代入被控对象方程,得到:$$\ddot{y} = b_0 \cdot \frac{u_0 - \hat{x}_3}{b_0} + d = u_0 + (d - \hat{x}_3)$$只要观测器估计够准,$d - \hat{x}_3 \approx 0$,那么整个通道就变成了一个单纯的二阶积分器 $\ddot{y}\approx u_0$。接下来用PD组合去控制这个积分器,性能非常好算。这里 $b_0$ 的意义就体现出来了:它是你对接入被控对象执行机构增益的估计。四旋翼滚转通道里,如果控制量直接是期望滚转力矩,那么 $b_0 \approx 1/J_x$;如果控制量是差动油门指令,那么 $b_0$ 就要包含力臂、螺旋桨升力系数、油门到推力的增益等一系列乘积。ADRC真正神奇的地方在于,$b_0$ 不需要很精确,因为 $b_0$ 的估计误差同样会出现在 $d$ 里面,被ESO一并估计并补偿掉。这也是为什么换了一套桨叶、换了一块更重的电池后,不需要立刻重算全部物理参数,只要按响应现象稍微微调就行。2.4 串级控制结构怎么搭四旋翼姿态控制在工程上普遍采用串级结构:外环角度环,内环角速度环。ADRC在内环,通常以内环角速度作为被控对象,外环角度用得是简单的P控制,输出期望角速度给内环。为什么内环用ADRC?因为角速度环更容易被扰动力矩直接激励,动态范围大,对扰动补偿需求最迫切。而角度环只是对角速度做积分,外环带宽低,对扰动不敏感,用P控制足够。这个分工很清晰:角度环解决“姿态稳不稳”,角速度环解决“姿态抗不抗扰”。结构上可以这样设计:外环:$\dot{\phi}{ref} = k{angle}(\phi_{cmd} - \phi)$,输出期望滚转角速度。内环:用ADRC跟踪 $\dot{\phi}_{ref}$,输出期望滚转力矩或期望角加速度。混控器:把期望力矩映射成四个电机的油门指令。内环ADRC的ESO直接估计的是角速度域的一阶系统,所以用二阶ESO就够了:第一阶估计角速度,第二阶估计角加速度域的总扰动。控制律里的 $b_0$ 对应“力矩到角加速度”的增益。这个写法实现简单、调试直观,也是我推荐大多数开发者采用的方案。3. 从公式到可运行的代码:离散化与实现3.1 连续方程如何离散化文章前面所有的推导都在连续域里,但飞控代码跑在中断里,是典型的离散系统。把连续ESO转成离散最常用的方法就是前向欧拉法。以内环角速度ADRC为例,设采样周期为 $T_s$,角速度测量值为 $y$,控制量为期望滚转力矩 $\tau$。定义ESO三个状态:$z_1$:角速度估计$z_2$:总扰动估计等等,这里要澄清一个容易写错的细节。刚才我说内环是一阶被控对象,那么ESO只需要两阶:$z_1$ 估计角速度,$z_2$ 估计总扰动。如果你用三阶ESO、且把 $z_1$ 当作角度估计,那是针对二阶被控对象的写法,适用于直接用ADRC做主姿态回路的情况。串级结构下内环用两阶ESO就够,这一点写代码前要先想清楚,避免把阶数搞错。两阶ESO的连续形式是:$$\dot{z}_1 = z_2 + b_0 u + l_1 (y - z_1)$$ $$\dot{z}_2 = l_2 (y - z_1)$$其中 $l_1$、$l_2$ 按带宽法配置为 $l_1=2\omega_o$、$l_2=\omega_o^2$。离散化后:$$z_1 \mathrel{+}= T_s \cdot (z_2 + b_0 u + l_1 e)$$ $$z_2 \mathrel{+}= T_s \cdot (l_2 e)$$其中 $e = y - z_1$ 是输出估计误差。控制律为:$$u_0 = k_p(\dot{\phi}_{ref} - z_1)$$ $$\tau = \frac{u_0 - z_2}{b_0}$$这里的 $k_p$ 是内环的比例增益,也就是角速度控制器的带宽 $\omega_c$。如果还想加一点角加速度前馈或微分项,可以把 $k_d$ 放进去,但大多数情况下一个比例项加扰动补偿已经够用。3.2 一个可直接参考的C语言实现把上面的公式翻译成C语言,核心代码非常短。我给出一个可直接运行的示例结构:// 角速度环ADRC参数结构体 typedef struct { float Ts; // 控制周期,单位秒 float b0; // 控制增益估计 float wo; // 观测器带宽 float wc; // 控制器带宽 // 离散ESO状态 float z1; // 角速度估计 float z2; // 总扰动估计 // 离散系数 float l1; float l2; } AdrcRate; // 初始化:按带宽法计算系数 void adrc_rate_init(AdrcRate *adrc) { adrc->l1 = 2.0f * adrc->wo; adrc->l2 = adrc->wo * adrc->wo; adrc->z1 = 0.0f; adrc->z2 = 0.0f; } // 更新函数:输入当前角速度测量值y、期望力矩tau、期望角速度rate_ref float adrc_rate_update(AdrcRate *adrc, float y, float tau, float rate_ref) { // 1. 输出估计误差 float err = y - adrc->z1; // 2. ESO状态更新 adrc->z1 += adrc->Ts * (adrc->z2 + adrc->b0 * tau + adrc->l1 * err); adrc->z2 += adrc->Ts * (adrc->l2 * err); // 3. 控制律:虚拟控制量 float u0 = adrc->wc * (rate_ref - y); // 4. 反解实际控制量,补偿总扰动 float tau_cmd = (u0 - adrc->z2) / adrc->b0; return tau_cmd; }这段代码里的关键点有几个。第一,ESO更新用的是上一次的控制量 $tau$,而不是本次刚计算出的 $tau_cmd$,顺序不能颠倒。第二,控制律误差取的是 $rate_ref - y$ 而不是 $rate_ref - z1$,好处是避免观测器滞后影响控制器性能,坏处是传感器噪声会直接进入控制量。如果你对噪声敏感,可以改用 $rate_ref - z1$,但那样会引入观测器相位延迟。实际工程里我一般用 $rate_ref - y$,并把传感器噪声通过姿态估计器或低通滤波提前压住。3.3 完整控制周期的调用顺序四旋翼姿态控制在一个控制周期内,通常依次执行:传感器读取、姿态估计更新、外环角度控制、内环角速度ADRC、混控器输出、电机指令下发。伪代码如下:void control_loop(void) { // 1. 读取IMU数据 read_imu(); // 2. 姿态解算,得到滚转角phi和角速度rate_phi attitude_estimate(); // 3. 外环角度控制:期望角速度 float rate_cmd = angle_kp * (phi_cmd - phi); // 4. 内环ADRC:输入当前角速度,输出期望力矩 float tau_cmd = adrc_rate_update(&adrc_roll, rate_phi, tau_prev, rate_cmd); // 5. 混控器:期望力矩 -> 四个电机油门 mixer_output(tau_cmd, &motor_cmds); // 6. 保存本次力矩,用于下一次ESO更新 tau_prev = tau_cmd; // 7. 电机指令下发 motor_write(motor_cmds); }注意第6步:ESO更新时需要用“上一次实际执行的控制量”。如果你把本次刚算出的 $tau_cmd$ 直接喂给ESO,会引入一拍代数环,导致估计发散或高频振荡。很多从仿真代码移植到真机的朋友都栽在这个细节上,仿真里IDE不报错,上机就抖,先从这种时序问题排查是对的。3.4 实现中容易忽略的细节第一,限幅。ESO的状态 $z_1$、$z_2$ 不是理论上的无限值。$z_2$ 代表总扰动,在四旋翼上一般不会超过几十个每秒平方弧度,但为了安全,还是应该给它加一个物理量级限幅,防止数值异常时观测器状态爆掉。第二,初始化。上电瞬间 $z_1$ 应该初始化为当前角速度,$z_2$ 初始化为0。这样ESO从零误差起步,容易收敛。如果 $z_1$ 从0开始,而飞机实际正在转动,ESO会先把“姿态估计误差”误认为“扰动”,产生几拍不正常的输出。第三,控制量的单位。混控器期望的是某种归一化油门增量,而ADRC内部计算的是“期望滚转力矩”。中间需要做一次单位映射,而且映射系数恰好是 $b_0$ 的一部分。这个单位混乱是代码落地阶段最常见的错误来源,建议在数据结构里写清楚每个变量的物理单位,减少低级错误。4. 调参不再玄学:b0与带宽法的实操顺序4.1 第一步:估算b0的物理本质调参第一步不是动控制器的 $k_p$,而是先确定 $b_0$。串级结构下,内环ADRC的输出是期望滚转力矩,被控对象方程是:$$\ddot{\phi} = \frac{1}{J_x}\tau_\phi + d$$所以 $b_0 \approx 1/J_x$。如果ADRC输出的是混控器差动油门,则 $b_0$ 还包含力臂和螺旋桨升力系数,表达式大约为:$$b_0 \approx \frac{L \cdot K_F}{J_x}$$其中 $K_F$ 是单位油门差对应的推力增量,$L$ 是电机到机体中轴线的力臂。$J_x$ 怎么估?最粗糙的办法是按集中质量估算整机重量乘以特征半径平方,误差可能在50%以上。但没关系,ADRC允许 $b_0$ 有误差,把误差扔给扰动项就行。实际操作时,可以用一个简单的自检:在飞行日志里看启动瞬间的角速度响应曲线,如果响应太肉(角加速度明显小于预期),说明 $b_0$ 偏小;如果响应尖锐甚至振荡,说明 $b_0$ 偏大。一次真机试飞就能校准到可用范围。注意:更换螺旋桨或机架后,$K_F$ 和 $J_x$ 都会变,$b_0$ 一定要重新过一遍,这是我吃过亏的地方。同一套参数换桨后,原来很稳的ESO变得高频颤抖,就是因为 $b_0$ 与真实增益偏离太多。4.2 第二步:用带宽法确定 $w_c$ 和 $w_o$$b_0$ 确定之后,控制器带宽 $w_c$ 与观测器带宽 $w_o$ 是主要旋钮。基于带宽法的整定逻辑可以概括为两条经验关系:观测器带宽应该是控制器带宽的3到5倍:$w_o \approx (3 \sim 5) w_c$。控制器带宽要低于执行机构能响应的带宽:四旋翼电机加电调的整体响应延迟通常在10到20毫秒左右,换算成带宽大约50到100 rad/s,所以姿态内环的 $w_c$ 先取5到10 rad/s是稳妥的。整定顺序建议从低往高加。先设 $w_c=5$,$w_o=20$,确认系统稳定不抖,然后逐步提高 $w_c$ 到10、15,每加一档配合提升 $w_o$ 到3倍。当出现下列任何现象之一时,说明已经到极限,退回上一档:姿态角出现高频抖动,电机声音发“劈”。快速打杆时出现回过冲振荡,不是平滑收敛。角速度估计值与真实值出现明显的相位滞后,导致响应迟钝。4.3 第三步:根据响应现象微调带宽法给了稳定起点,但真机的细节总是比纸面模型复杂。微调阶段主要看三样东西:阶跃响应、扰动响应、以及ESO的扰动估计波形。先做悬停阶跃测试。打一个小幅滚转杆量,观察姿态角响应。理想曲线是快速上升、略微超调、迅速收敛。如果超调大且收敛慢,说明 $w_c$ 偏高或 $w_o$ 偏低,ESO跟不上扰动,可以降低 $w_c$ 或提高 $w_o$。如果响应肉且拖沓,说明带宽太低,往上调。再看扰动响应。在视距内用手碰一下机臂(注意安全),或者直接在有环境风的日子悬停,观察姿态角偏差恢复速度。经典PID的“恢复时间”受积分限幅影响很大,而ADRC的恢复时间主要受 $w_o$ 影响。$w_o$ 越大,恢复越快,但噪声也越明显。最后看ESO的 $z_2$ 波形。好的 $z_2$ 应该在悬停时是一条带有小幅波动的平直线,波动幅度与风扰水平相当。如果 $z_2$ 出现持续单调增大或大幅度振荡,说明 $b_0$ 偏差过大或 $w_o$ 配置不合理。这也是一个很好的故障诊断入口。4.4 一张参考调参表以小型轴距约250到350mm的四旋翼、常规无刷电机和碳纤维机架为例,内环ADRC的参考初始参数范围如下:参数参考范围说明$b_0$20 ~ 80 (rad/s²/油门差)取决于力臂、转动惯量和电机推力$w_c$5 ~ 15 rad/s从5起调,逐步加大$w_o$(3 ~ 5) $w_c$从3倍起调角度环 $k_p$3 ~ 6 (1/s)外环带宽,通常不做太高控制频率200 ~ 1000 Hz越高对ESO收敛越有利这张表不是死参数,而是给你一个“从这里开始试”的安全起点。不同尺寸、不同重量的机子,表中数值差个两三倍都很正常。最忌讳的是直接抄别人参数,因为 $b_0$ 和机架物理参数强相关,抄过来可能直接高频振荡。5. 真机落地避坑:实际踩过的坑与排查方法5.1 观测器带宽与传感器噪声的矛盾$w_o$ 升高确实能让ESO更敏锐地估计扰动,但代价是传感器噪声被放大。陀螺仪本身有噪声,IMU经过姿态解算后输出角速度仍然带有高频成分。当 $w_o$ 拉到很高时,ESO会把这些噪声当成“高频扰动”去补偿,导致控制量高频抖动,电机发出刺耳的啸叫,姿态角反而出现高频小抖动。解决思路有两个方向。一是从源头降噪,在ADRC之前对角速度信号做低通滤波,但滤波会引入相位延迟,相当于变相降低控制器带宽,需要平衡。二是不要把 $w_o$ 无脑拉高,四旋翼姿态环的实际扰动频率通常不会特别高,取十几到几十rad/s的观测器带宽已经能覆盖大部分风扰和耦合干扰,再高就是给噪声打工了。我个人的经验是:把陀螺仪原始数据先去一波200Hz左右的低通,然后 $w_o$ 控制在 $3w_c$ 左右起步,等系统稳定后再看波形微调,不要一上来就追求“扰动完全不见”的那种激进配置。5.2 执行器饱和导致ESO估计失真这是ADRC工程落地里最隐蔽、也最坑的坑。当控制器输出超出执行器物理上限,比如电机油门已经拉满,实际角加速度跟不上ADRC期望模型时,ESO会把“执行器输出不足”误判成“负的总扰动”,$z_2$ 会持续积分增大。等姿态误差减小、控制量回落时,$z_2$ 里积存的错误扰动补偿会一下子释放出来,导致猛烈的回弹超调,严重时直接掀翻飞机。解决办法是对ESO做抗饱和处理。最实用的手段有两个:一是给 $z_2$ 加限幅,让总扰动估计值不超出物理合理范围;二是当执行器进入饱和状态时,冻结 $z_2$ 的更新,类似PID抗积分饱和的做法。代码里实现很便宜:// 执行器饱和判定:以滚转力矩为例 float tau_limit = max_roll_torque; float tau_clamped = clamp(tau_cmd, -tau_limit, tau_limit); // 如果控制量已饱和,冻结扰动估计更新 float update_enabled = (fabsf(tau_cmd) < tau_limit) ? 1.0f : 0.0f; // ESO更新时乘以更新开关 adrc->z2 += adrc->Ts * (adrc->l2 * err * update_enabled);注意冻结的只是 $z_2$,$z_1$ 还是照常更新,这样解除饱和后观测器能快速恢复正常。5.3 振动、安装模态与滤波延迟四旋翼机体本身是有弹性模态的。机臂在电机高转速激励下会产生高频振动,角速度传感器测到的信号里除了真实姿态运动,还混有结构振动分量。ADRC的ESO如果带宽足够高,它会把结构振动也当扰动去补偿,结果就是控制器和机臂振动形成正反馈,越调越抖。排查这类问题的方法很直观:在日志里同时看陀螺仪原始角速度和控制量。如果角速度里有一簇远高于飞行带宽的高频尖峰,同时控制量也在同一频率上剧烈波动,基本就是结构振动耦合进控制环了。解决手段通常是降低 $w_o$、增加传感器滤波、或者在机械结构上做减振处理。有时候把装配松动的螺丝拧紧,比调任何参数都管用。还有一点容易被忽略:IMU的安装位置。陀螺仪离质心越远,线振动引起的角速度测量误差越明显。飞控板尽量装在机体刚性好、靠近几何中心的位置,振动耦合会小很多。5.4 常见问题速查表现象主要原因排查方向悬停高频抖动,电机噪$w_o$ 过高或传感器噪声大降 $w_o$、加强陀螺仪滤波响应迟钝,跟手性差$w_c$ 偏低或 $b_0$ 偏大提 $w_c$、复查 $b_0$响应尖锐,回弹过冲$w_c$ 偏高或 $b_0$ 偏小降 $w_c$、微调 $b_0$抗风差,姿态恢复慢$w_o$ 偏低提 $w_o$ 但注意噪声猛打杆后回摆很大执行器饱和导致 $z_2$ 积分过大增加 $z_2$ 限幅或饱和冻结滚转和俯仰参数相同但表现不同两个通道 $J$ 不同或等效 $b_0$ 不同分别估算,不要强行共用这条速查表的价值在于,它把“手感”类问题映射到具体参数上。你不需要靠运气反复试错,只要描述出飞机当前的异常表现,就能从表里找到对应参数去调。这也是在我看来ADRC比PID更好调试的根本原因:PID的P、I、D三个参数对系统性能的耦合路径不直观,而ADRC的参数分工明确,$b_0$ 管模型、$w_c$ 管性能、$w_o$ 管扰动观测,各司其职。我自己的体会是,ADRC的调参一旦按这个节奏走下来,从陌生机架到稳定飞行通常只需要一两个小时的试飞时间,比PID的盲目试凑快得多。而且失败过程可控:最坏情况也就是某个参数过头导致抖动,不会出现那种“根轨迹在空中飞没了”的失控感。后面如果还想往深里做,可以继续研究观测器建模误差的影响、把总扰动从常值模型改成时变模型、或者把外环角度环也替换成ADRC结构。但无论如何,起点都是那一条滚转通道的角加速度方程——它把整架四旋翼最复杂的姿态动力学,浓缩成了一个转动惯量、一个控制力矩、一个总扰动,而ADRC做的全部事情,就是和那个“总扰动”打交道。想清楚这一点,剩下的都是水到渠成的工程功夫。
四旋翼的ADRC姿态控制,总给人一种“玄学调参”的错觉。我最早接触自抗扰控制时,也被满屏的扩张状态观测器、总扰动、带宽法这些名词劝退过,直到真正从标题里那条滚转通道的角加速度方程往下一层层拆,才觉得这事其实比想象中有意思得多。这篇文章就从一个贯穿始终的核心公式聊起,把ADRC从物理模型、控制器结构、离散化代码到真机调参整条链路完整走一遍。适合正在做四旋翼姿态控制、想从经典PID转向ADRC,或者被“总扰动”“带宽法”绕晕的开发者。读完你会发现,所谓玄学,大部分只是没把模型到代码之间的因果链条理顺而已。1. 先把核心公式看明白:滚转通道的角加速度方程1.1 方程里每一项的物理含义四旋翼滚转通道的姿态动力学,写到最后其实就是一条二阶方程:$$J_x \ddot{\phi} = \tau_\phi + d_\phi$$其中 $J_x$ 是整机绕机体X轴(机头方向)的转动惯量,$\ddot{\phi}$ 是滚转角加速度,$\tau_\phi$ 是四个电机产生的滚转力矩,$d_\phi$ 则是一个“集所有不确定因素之大成”的扰动项。很多人在这一步就犯迷糊:既然是滚转,为什么方程里没出现陀螺力矩、气动阻尼、重心偏移、电机延迟?答案是它们全都被塞进了 $d_\phi$ 里。ADRC的整个思想内核,就是承认你很难精确写出一个完美的飞机模型,那就把模型误差、外部风扰、交叉耦合、执行器非线性全部打包成一个总扰动,用观测器把它实时估计出来,再在控制量里反向补偿掉。这条方程本身也说明了滚转力矩从哪来:四旋翼产生滚转,靠的是左右两侧电机推力之差。假设力臂为 $L$,右侧电机推力 $F_R$ 与左侧 $F_L$ 的差为 $\Delta F$,那么 $\tau_\phi \approx L \cdot (F_R - F_L)$。螺旋桨产生的推力又与油门或转速近似成平方关系。也就是说,你的控制指令从混控器出来,最终会映射成某个差动油门,再变成角加速度。这个链条上每个环节的增益都不是绝对线性的,但ADRC允许你只用一个粗糙的 $b_0$ 去代表整条链路的增益,剩下的偏差全交给总扰动项处理。注意:这条方程里的 $J_x$ 不是恒定不变的值。电池、载重、机架配件的更换都会改变整机转动惯量,这也是为什么同一套参数换一架飞机后必须重新调。ADRC的好处在于,你不需要把 $J_x$ 测到实验室精度,只要有一个量级准确的估算值就行。1.2 “总扰动”到底是什么把 $d_\phi$ 单独拎出来说,因为它是理解ADRC的门槛。它不是某一个具体的物理量,而是“被控对象真实动态与控制模型之间的全部差异”。包括但不限于:未建模动态:电机和螺旋桨的滞后、电调响应延迟、机架柔性形变。耦合项:四旋翼滚转时会连带引起偏航和俯仰方向的陀螺耦合。外部扰动:突风、载荷变化、推力线不经过质心产生的附加力矩。模型参数误差:$J_x$ 估算不准、混控器增益漂移。如果你用经典PID去处理这些扰动,思路是靠误差反馈硬扛:扰动来了,姿态偏了,控制器看到偏差再使劲拉回来。ADRC的思路则是先把扰动“看”出来,然后直接在控制量里减去一个等量反作用,让系统表现得像个完全的积分器型对象。打个比方。开一辆车走一条坑洼路段,PID是那种眼睛盯着仪表盘,发现车速掉下来了就踩油门、发现车速快了就收油的司机,反应永远滞后一拍。ADRC则是一个经验丰富的老司机,他感觉方向盘和车身反馈不对,反推出现在路面给了多大的阻力,提前一脚油门把阻力抵消掉。这个“反推阻力的过程”,就是扩张状态观测器在做的事。1.3 控制目标在方程里怎么表达有了这条角加速度方程,滚转通道的控制目标就很清晰了:给定期望姿态角 $\phi_{ref}$,你要设计一个控制力矩 $\tau_\phi$,让实际的 $\phi$ 尽可能快而无超调地跟上去,同时抵消掉 $d_\phi$ 的影响。写成误差形式的话,如果定义姿态角误差 $e_\phi = \phi_{ref} - \phi$,角速度误差 $e_{\dot{\phi}} = \dot{\phi}_{ref} - \dot{\phi}$,理想的控制率形式是让角加速度等于某种比例微分组合:$$\ddot{\phi}{des} = k_p e\phi + k_d e_{\dot{\phi}}$$然后反解出需要的力矩:$$\tau_\phi = J_x \ddot{\phi}{des} - d\phi$$这里 $J_x$ 是模型增益,$d_\phi$ 是扰动。问题来了:$d_\phi$ 不可能直接测量,$J_x$ 也未必准。ADRC的做法,是用一个扩张状态观测器(ESO)把 $\phi$、$\dot{\phi}$、$d_\phi$ 三个量全部在线估出来,然后用估计值去实现上面这条控制律。这就是整个算法能落地的底层逻辑。一旦想通了这一点,后面所有代码和调参步骤都是在回答一个问题:怎么把 $d_\phi$ 估准并且补偿掉。2. 自抗扰控制器内部结构与设计逻辑2.1 三个关键模块各司其职一个完整的ADRC姿态控制器,通常包含三个模块:跟踪微分器(TD)、扩张状态观测器(ESO)、以及误差反馈控制律。跟踪微分器的作用是给参考输入“整形”。四旋翼飞控里,遥控器打杆给的是阶跃式的期望姿态角,如果直接让姿态环去追一个阶跃,必然带来超调和振荡。TD会把阶跃参考安排成一条平滑的过渡轨迹,把“麋鹿跳悬崖”变成“盘山公路”,控制器跟着这条平滑轨迹走,大幅降低超调。它的实现形式并不复杂,常见的安排过渡过程可以用二阶最速控制或简单的低通滤波来完成,飞控里不发烧的话用一阶惯性滤波也够用。扩张状态观测器是整个ADRC的心脏。常规状态观测器只能估计系统的已知状态,ESO多出来一阶状态专门用来观测总扰动,这也是“扩张”二字的来源。对于滚转通道这个二阶被控对象,ESO一般设计成三阶结构:第一阶状态估计滚转角速度,第二阶估计滚转角加速度,第三阶估计总扰动。误差反馈控制律则负责把状态误差转换成控制量。它不需要复杂的积分项,因为扰动已经被ESO补偿掉了,剩下的是一个近似纯积分特性的被控对象,用一个PD组合就能获得很好的性能。这也是ADRC对比PID一个直观的差异:PID里积分项在扛扰动,ADRC里观测器在扛扰动,PD组合反而干干净净。2.2 ESO是怎么“看到”扰动的ESO的估计原理值得多说两句。假设你有一个二阶被控对象:$$\ddot{y} = b_0 u + d$$其中 $y$ 是输出(滚转角或角速度),$b_0$ 是控制增益的估计值,$u$ 是控制量,$d$ 是总扰动。把扰动 $d$ 新定义为一个状态变量 $x_3 = d$,并假设它的导数是某个有限值 $\dot{d}$,系统就变成了三阶线性系统:$$\dot{x}_1 = x_2$$ $$\dot{x}_2 = x_3 + b_0 u$$ $$\dot{x}_3 = \dot{d}$$ESO要做的事情,就是用可测量的输出 $y = x_1$ 同时反推 $x_1$、$x_2$、$x_3$ 三个状态。具体的反馈结构是:先算出输出估计误差 $e = \hat{x}_1 - y$,然后通过三个增益 $l_1、l_2、l_3$ 把这个误差反馈到三个状态方程里,让估计值快速收敛到真实值。只要观测器带宽足够高,$\hat{x}_3$ 就能紧跟真实的总扰动。这里有个非常实用的问题:三个增益 $l_1、l_2、l_3$ 该怎么选?如果手工一个个试,确实是玄学。但有一种成熟的带宽法整定方案:把观测器的特征方程配置成 $(s + \omega_o)^3$,令:$$l_1 = 3\omega_o,\quad l_2 = 3\omega_o^2,\quad l_3 = \omega_o^3$$这样一来,整个ESO的性能只由一个参数 $\omega_o$(观测器带宽)决定,调参的工作量瞬间从一个三变量问题降到一个单变量问题。这是ADRC从“玄学”走向“科学”的关键一步,也是四旋翼工程落地中最常用的配置方式。提示:$\omega_o$ 并不是越大越好。它决定了观测器对扰动的跟踪速度,但也决定了传感器噪声的放大程度。后面调参章节会专门讲这个取舍。2.3 控制律与 $b_0$ 的微妙关系ESO估计出总扰动 $\hat{d}$ 之后,控制律处理得很巧妙。如果想让系统表现为 $\ddot{y} = u_0$,其中 $u_0$ 是我们期望的虚拟控制量,那么实际控制量应该取:$$u = \frac{u_0 - \hat{x}_3}{b_0}$$把这条代入被控对象方程,得到:$$\ddot{y} = b_0 \cdot \frac{u_0 - \hat{x}_3}{b_0} + d = u_0 + (d - \hat{x}_3)$$只要观测器估计够准,$d - \hat{x}_3 \approx 0$,那么整个通道就变成了一个单纯的二阶积分器 $\ddot{y}\approx u_0$。接下来用PD组合去控制这个积分器,性能非常好算。这里 $b_0$ 的意义就体现出来了:它是你对接入被控对象执行机构增益的估计。四旋翼滚转通道里,如果控制量直接是期望滚转力矩,那么 $b_0 \approx 1/J_x$;如果控制量是差动油门指令,那么 $b_0$ 就要包含力臂、螺旋桨升力系数、油门到推力的增益等一系列乘积。ADRC真正神奇的地方在于,$b_0$ 不需要很精确,因为 $b_0$ 的估计误差同样会出现在 $d$ 里面,被ESO一并估计并补偿掉。这也是为什么换了一套桨叶、换了一块更重的电池后,不需要立刻重算全部物理参数,只要按响应现象稍微微调就行。2.4 串级控制结构怎么搭四旋翼姿态控制在工程上普遍采用串级结构:外环角度环,内环角速度环。ADRC在内环,通常以内环角速度作为被控对象,外环角度用得是简单的P控制,输出期望角速度给内环。为什么内环用ADRC?因为角速度环更容易被扰动力矩直接激励,动态范围大,对扰动补偿需求最迫切。而角度环只是对角速度做积分,外环带宽低,对扰动不敏感,用P控制足够。这个分工很清晰:角度环解决“姿态稳不稳”,角速度环解决“姿态抗不抗扰”。结构上可以这样设计:外环:$\dot{\phi}{ref} = k{angle}(\phi_{cmd} - \phi)$,输出期望滚转角速度。内环:用ADRC跟踪 $\dot{\phi}_{ref}$,输出期望滚转力矩或期望角加速度。混控器:把期望力矩映射成四个电机的油门指令。内环ADRC的ESO直接估计的是角速度域的一阶系统,所以用二阶ESO就够了:第一阶估计角速度,第二阶估计角加速度域的总扰动。控制律里的 $b_0$ 对应“力矩到角加速度”的增益。这个写法实现简单、调试直观,也是我推荐大多数开发者采用的方案。3. 从公式到可运行的代码:离散化与实现3.1 连续方程如何离散化文章前面所有的推导都在连续域里,但飞控代码跑在中断里,是典型的离散系统。把连续ESO转成离散最常用的方法就是前向欧拉法。以内环角速度ADRC为例,设采样周期为 $T_s$,角速度测量值为 $y$,控制量为期望滚转力矩 $\tau$。定义ESO三个状态:$z_1$:角速度估计$z_2$:总扰动估计等等,这里要澄清一个容易写错的细节。刚才我说内环是一阶被控对象,那么ESO只需要两阶:$z_1$ 估计角速度,$z_2$ 估计总扰动。如果你用三阶ESO、且把 $z_1$ 当作角度估计,那是针对二阶被控对象的写法,适用于直接用ADRC做主姿态回路的情况。串级结构下内环用两阶ESO就够,这一点写代码前要先想清楚,避免把阶数搞错。两阶ESO的连续形式是:$$\dot{z}_1 = z_2 + b_0 u + l_1 (y - z_1)$$ $$\dot{z}_2 = l_2 (y - z_1)$$其中 $l_1$、$l_2$ 按带宽法配置为 $l_1=2\omega_o$、$l_2=\omega_o^2$。离散化后:$$z_1 \mathrel{+}= T_s \cdot (z_2 + b_0 u + l_1 e)$$ $$z_2 \mathrel{+}= T_s \cdot (l_2 e)$$其中 $e = y - z_1$ 是输出估计误差。控制律为:$$u_0 = k_p(\dot{\phi}_{ref} - z_1)$$ $$\tau = \frac{u_0 - z_2}{b_0}$$这里的 $k_p$ 是内环的比例增益,也就是角速度控制器的带宽 $\omega_c$。如果还想加一点角加速度前馈或微分项,可以把 $k_d$ 放进去,但大多数情况下一个比例项加扰动补偿已经够用。3.2 一个可直接参考的C语言实现把上面的公式翻译成C语言,核心代码非常短。我给出一个可直接运行的示例结构:// 角速度环ADRC参数结构体 typedef struct { float Ts; // 控制周期,单位秒 float b0; // 控制增益估计 float wo; // 观测器带宽 float wc; // 控制器带宽 // 离散ESO状态 float z1; // 角速度估计 float z2; // 总扰动估计 // 离散系数 float l1; float l2; } AdrcRate; // 初始化:按带宽法计算系数 void adrc_rate_init(AdrcRate *adrc) { adrc->l1 = 2.0f * adrc->wo; adrc->l2 = adrc->wo * adrc->wo; adrc->z1 = 0.0f; adrc->z2 = 0.0f; } // 更新函数:输入当前角速度测量值y、期望力矩tau、期望角速度rate_ref float adrc_rate_update(AdrcRate *adrc, float y, float tau, float rate_ref) { // 1. 输出估计误差 float err = y - adrc->z1; // 2. ESO状态更新 adrc->z1 += adrc->Ts * (adrc->z2 + adrc->b0 * tau + adrc->l1 * err); adrc->z2 += adrc->Ts * (adrc->l2 * err); // 3. 控制律:虚拟控制量 float u0 = adrc->wc * (rate_ref - y); // 4. 反解实际控制量,补偿总扰动 float tau_cmd = (u0 - adrc->z2) / adrc->b0; return tau_cmd; }这段代码里的关键点有几个。第一,ESO更新用的是上一次的控制量 $tau$,而不是本次刚计算出的 $tau_cmd$,顺序不能颠倒。第二,控制律误差取的是 $rate_ref - y$ 而不是 $rate_ref - z1$,好处是避免观测器滞后影响控制器性能,坏处是传感器噪声会直接进入控制量。如果你对噪声敏感,可以改用 $rate_ref - z1$,但那样会引入观测器相位延迟。实际工程里我一般用 $rate_ref - y$,并把传感器噪声通过姿态估计器或低通滤波提前压住。3.3 完整控制周期的调用顺序四旋翼姿态控制在一个控制周期内,通常依次执行:传感器读取、姿态估计更新、外环角度控制、内环角速度ADRC、混控器输出、电机指令下发。伪代码如下:void control_loop(void) { // 1. 读取IMU数据 read_imu(); // 2. 姿态解算,得到滚转角phi和角速度rate_phi attitude_estimate(); // 3. 外环角度控制:期望角速度 float rate_cmd = angle_kp * (phi_cmd - phi); // 4. 内环ADRC:输入当前角速度,输出期望力矩 float tau_cmd = adrc_rate_update(&adrc_roll, rate_phi, tau_prev, rate_cmd); // 5. 混控器:期望力矩 -> 四个电机油门 mixer_output(tau_cmd, &motor_cmds); // 6. 保存本次力矩,用于下一次ESO更新 tau_prev = tau_cmd; // 7. 电机指令下发 motor_write(motor_cmds); }注意第6步:ESO更新时需要用“上一次实际执行的控制量”。如果你把本次刚算出的 $tau_cmd$ 直接喂给ESO,会引入一拍代数环,导致估计发散或高频振荡。很多从仿真代码移植到真机的朋友都栽在这个细节上,仿真里IDE不报错,上机就抖,先从这种时序问题排查是对的。3.4 实现中容易忽略的细节第一,限幅。ESO的状态 $z_1$、$z_2$ 不是理论上的无限值。$z_2$ 代表总扰动,在四旋翼上一般不会超过几十个每秒平方弧度,但为了安全,还是应该给它加一个物理量级限幅,防止数值异常时观测器状态爆掉。第二,初始化。上电瞬间 $z_1$ 应该初始化为当前角速度,$z_2$ 初始化为0。这样ESO从零误差起步,容易收敛。如果 $z_1$ 从0开始,而飞机实际正在转动,ESO会先把“姿态估计误差”误认为“扰动”,产生几拍不正常的输出。第三,控制量的单位。混控器期望的是某种归一化油门增量,而ADRC内部计算的是“期望滚转力矩”。中间需要做一次单位映射,而且映射系数恰好是 $b_0$ 的一部分。这个单位混乱是代码落地阶段最常见的错误来源,建议在数据结构里写清楚每个变量的物理单位,减少低级错误。4. 调参不再玄学:b0与带宽法的实操顺序4.1 第一步:估算b0的物理本质调参第一步不是动控制器的 $k_p$,而是先确定 $b_0$。串级结构下,内环ADRC的输出是期望滚转力矩,被控对象方程是:$$\ddot{\phi} = \frac{1}{J_x}\tau_\phi + d$$所以 $b_0 \approx 1/J_x$。如果ADRC输出的是混控器差动油门,则 $b_0$ 还包含力臂和螺旋桨升力系数,表达式大约为:$$b_0 \approx \frac{L \cdot K_F}{J_x}$$其中 $K_F$ 是单位油门差对应的推力增量,$L$ 是电机到机体中轴线的力臂。$J_x$ 怎么估?最粗糙的办法是按集中质量估算整机重量乘以特征半径平方,误差可能在50%以上。但没关系,ADRC允许 $b_0$ 有误差,把误差扔给扰动项就行。实际操作时,可以用一个简单的自检:在飞行日志里看启动瞬间的角速度响应曲线,如果响应太肉(角加速度明显小于预期),说明 $b_0$ 偏小;如果响应尖锐甚至振荡,说明 $b_0$ 偏大。一次真机试飞就能校准到可用范围。注意:更换螺旋桨或机架后,$K_F$ 和 $J_x$ 都会变,$b_0$ 一定要重新过一遍,这是我吃过亏的地方。同一套参数换桨后,原来很稳的ESO变得高频颤抖,就是因为 $b_0$ 与真实增益偏离太多。4.2 第二步:用带宽法确定 $w_c$ 和 $w_o$$b_0$ 确定之后,控制器带宽 $w_c$ 与观测器带宽 $w_o$ 是主要旋钮。基于带宽法的整定逻辑可以概括为两条经验关系:观测器带宽应该是控制器带宽的3到5倍:$w_o \approx (3 \sim 5) w_c$。控制器带宽要低于执行机构能响应的带宽:四旋翼电机加电调的整体响应延迟通常在10到20毫秒左右,换算成带宽大约50到100 rad/s,所以姿态内环的 $w_c$ 先取5到10 rad/s是稳妥的。整定顺序建议从低往高加。先设 $w_c=5$,$w_o=20$,确认系统稳定不抖,然后逐步提高 $w_c$ 到10、15,每加一档配合提升 $w_o$ 到3倍。当出现下列任何现象之一时,说明已经到极限,退回上一档:姿态角出现高频抖动,电机声音发“劈”。快速打杆时出现回过冲振荡,不是平滑收敛。角速度估计值与真实值出现明显的相位滞后,导致响应迟钝。4.3 第三步:根据响应现象微调带宽法给了稳定起点,但真机的细节总是比纸面模型复杂。微调阶段主要看三样东西:阶跃响应、扰动响应、以及ESO的扰动估计波形。先做悬停阶跃测试。打一个小幅滚转杆量,观察姿态角响应。理想曲线是快速上升、略微超调、迅速收敛。如果超调大且收敛慢,说明 $w_c$ 偏高或 $w_o$ 偏低,ESO跟不上扰动,可以降低 $w_c$ 或提高 $w_o$。如果响应肉且拖沓,说明带宽太低,往上调。再看扰动响应。在视距内用手碰一下机臂(注意安全),或者直接在有环境风的日子悬停,观察姿态角偏差恢复速度。经典PID的“恢复时间”受积分限幅影响很大,而ADRC的恢复时间主要受 $w_o$ 影响。$w_o$ 越大,恢复越快,但噪声也越明显。最后看ESO的 $z_2$ 波形。好的 $z_2$ 应该在悬停时是一条带有小幅波动的平直线,波动幅度与风扰水平相当。如果 $z_2$ 出现持续单调增大或大幅度振荡,说明 $b_0$ 偏差过大或 $w_o$ 配置不合理。这也是一个很好的故障诊断入口。4.4 一张参考调参表以小型轴距约250到350mm的四旋翼、常规无刷电机和碳纤维机架为例,内环ADRC的参考初始参数范围如下:参数参考范围说明$b_0$20 ~ 80 (rad/s²/油门差)取决于力臂、转动惯量和电机推力$w_c$5 ~ 15 rad/s从5起调,逐步加大$w_o$(3 ~ 5) $w_c$从3倍起调角度环 $k_p$3 ~ 6 (1/s)外环带宽,通常不做太高控制频率200 ~ 1000 Hz越高对ESO收敛越有利这张表不是死参数,而是给你一个“从这里开始试”的安全起点。不同尺寸、不同重量的机子,表中数值差个两三倍都很正常。最忌讳的是直接抄别人参数,因为 $b_0$ 和机架物理参数强相关,抄过来可能直接高频振荡。5. 真机落地避坑:实际踩过的坑与排查方法5.1 观测器带宽与传感器噪声的矛盾$w_o$ 升高确实能让ESO更敏锐地估计扰动,但代价是传感器噪声被放大。陀螺仪本身有噪声,IMU经过姿态解算后输出角速度仍然带有高频成分。当 $w_o$ 拉到很高时,ESO会把这些噪声当成“高频扰动”去补偿,导致控制量高频抖动,电机发出刺耳的啸叫,姿态角反而出现高频小抖动。解决思路有两个方向。一是从源头降噪,在ADRC之前对角速度信号做低通滤波,但滤波会引入相位延迟,相当于变相降低控制器带宽,需要平衡。二是不要把 $w_o$ 无脑拉高,四旋翼姿态环的实际扰动频率通常不会特别高,取十几到几十rad/s的观测器带宽已经能覆盖大部分风扰和耦合干扰,再高就是给噪声打工了。我个人的经验是:把陀螺仪原始数据先去一波200Hz左右的低通,然后 $w_o$ 控制在 $3w_c$ 左右起步,等系统稳定后再看波形微调,不要一上来就追求“扰动完全不见”的那种激进配置。5.2 执行器饱和导致ESO估计失真这是ADRC工程落地里最隐蔽、也最坑的坑。当控制器输出超出执行器物理上限,比如电机油门已经拉满,实际角加速度跟不上ADRC期望模型时,ESO会把“执行器输出不足”误判成“负的总扰动”,$z_2$ 会持续积分增大。等姿态误差减小、控制量回落时,$z_2$ 里积存的错误扰动补偿会一下子释放出来,导致猛烈的回弹超调,严重时直接掀翻飞机。解决办法是对ESO做抗饱和处理。最实用的手段有两个:一是给 $z_2$ 加限幅,让总扰动估计值不超出物理合理范围;二是当执行器进入饱和状态时,冻结 $z_2$ 的更新,类似PID抗积分饱和的做法。代码里实现很便宜:// 执行器饱和判定:以滚转力矩为例 float tau_limit = max_roll_torque; float tau_clamped = clamp(tau_cmd, -tau_limit, tau_limit); // 如果控制量已饱和,冻结扰动估计更新 float update_enabled = (fabsf(tau_cmd) < tau_limit) ? 1.0f : 0.0f; // ESO更新时乘以更新开关 adrc->z2 += adrc->Ts * (adrc->l2 * err * update_enabled);注意冻结的只是 $z_2$,$z_1$ 还是照常更新,这样解除饱和后观测器能快速恢复正常。5.3 振动、安装模态与滤波延迟四旋翼机体本身是有弹性模态的。机臂在电机高转速激励下会产生高频振动,角速度传感器测到的信号里除了真实姿态运动,还混有结构振动分量。ADRC的ESO如果带宽足够高,它会把结构振动也当扰动去补偿,结果就是控制器和机臂振动形成正反馈,越调越抖。排查这类问题的方法很直观:在日志里同时看陀螺仪原始角速度和控制量。如果角速度里有一簇远高于飞行带宽的高频尖峰,同时控制量也在同一频率上剧烈波动,基本就是结构振动耦合进控制环了。解决手段通常是降低 $w_o$、增加传感器滤波、或者在机械结构上做减振处理。有时候把装配松动的螺丝拧紧,比调任何参数都管用。还有一点容易被忽略:IMU的安装位置。陀螺仪离质心越远,线振动引起的角速度测量误差越明显。飞控板尽量装在机体刚性好、靠近几何中心的位置,振动耦合会小很多。5.4 常见问题速查表现象主要原因排查方向悬停高频抖动,电机噪$w_o$ 过高或传感器噪声大降 $w_o$、加强陀螺仪滤波响应迟钝,跟手性差$w_c$ 偏低或 $b_0$ 偏大提 $w_c$、复查 $b_0$响应尖锐,回弹过冲$w_c$ 偏高或 $b_0$ 偏小降 $w_c$、微调 $b_0$抗风差,姿态恢复慢$w_o$ 偏低提 $w_o$ 但注意噪声猛打杆后回摆很大执行器饱和导致 $z_2$ 积分过大增加 $z_2$ 限幅或饱和冻结滚转和俯仰参数相同但表现不同两个通道 $J$ 不同或等效 $b_0$ 不同分别估算,不要强行共用这条速查表的价值在于,它把“手感”类问题映射到具体参数上。你不需要靠运气反复试错,只要描述出飞机当前的异常表现,就能从表里找到对应参数去调。这也是在我看来ADRC比PID更好调试的根本原因:PID的P、I、D三个参数对系统性能的耦合路径不直观,而ADRC的参数分工明确,$b_0$ 管模型、$w_c$ 管性能、$w_o$ 管扰动观测,各司其职。我自己的体会是,ADRC的调参一旦按这个节奏走下来,从陌生机架到稳定飞行通常只需要一两个小时的试飞时间,比PID的盲目试凑快得多。而且失败过程可控:最坏情况也就是某个参数过头导致抖动,不会出现那种“根轨迹在空中飞没了”的失控感。后面如果还想往深里做,可以继续研究观测器建模误差的影响、把总扰动从常值模型改成时变模型、或者把外环角度环也替换成ADRC结构。但无论如何,起点都是那一条滚转通道的角加速度方程——它把整架四旋翼最复杂的姿态动力学,浓缩成了一个转动惯量、一个控制力矩、一个总扰动,而ADRC做的全部事情,就是和那个“总扰动”打交道。想清楚这一点,剩下的都是水到渠成的工程功夫。