简介:面向四旋翼无人机控制研究者的ADRC控制完整资源包,聚焦高级直接反应控制器(ADRC)及其LADRC实现,内容涵盖动力学建模、扰动观测、参数配置与自适应控制设计。ADRC结合PID与扩展状态观测器,能有效抑制内外扰动,LADRC则通过自适应算法动态调整参数,提升系统鲁棒性。包内包含3个文件:2个Simulink模型(PlantModel.slx用于四旋翼物理建模,LADRC_GAO.slx用于控制器实现),1个MATLAB脚本(Quadparameters.m用于设定质量、惯量等关键参数),压缩包整体约113KB,结构紧凑便于快速加载。已有180人学习。借助这套资源,可掌握四旋翼Simulink建模流程、ADRC/LADRC控制器设计方法,以及通过脚本调整系统参数的实验思路,适合无人机控制课程设计、毕业设计或相关科研入门参考。
1. QuadcopterADRC:四旋翼自抗扰控制的出发点
QuadcopterADRC_quadcopter_ 这个标题连起来读,就是在四旋翼上做 ADRC(自抗扰控制)的一套控制方案。ADRC 对无人机最有吸引力的地方不是“无模型”,而是把建模误差、桨叶老化、阵风力矩全部装进一个扩张状态观测器里,在控制量里实时消掉。跟串级 PID 相比,它把“被扰动拉偏了再去纠正”改成了“扰动刚露头就被抵消”。
当 PID 在悬停时被一阵横风推出二十厘米要晃两三秒,或者油门变化时姿态角总要超调一点,ADRC 的思路是用观测器看见扰动,而不是等误差积累起来再动手。这篇文章从 ADRC 的三个核心构成拆起,给出姿态环可复现的离散实现、整定顺序和实机排错路径,适合已经调过串级 PID、想把手上的 Quadcopter 再往下压一拍的控制工程师。
2. ADRC 的核心构成:韩京清留下的三块积木,四旋翼怎么对齐
韩京清提出的 ADRC 核心构成通常被归纳为三块:跟踪微分器(TD)、扩张状态观测器(ESO)、误差反馈与扰动补偿。三块拼起来后的逻辑是:让 ESO 把总扰动估出来,再在输入端把它抵消掉,剩下的系统在控制器眼里退化成积分串联型。这样一来,被控对象是电机响应偏慢还是机架刚度不足,都不需要写进微分方程。
对四旋翼而言,这套框架拆到姿态环里非常自然:角度环带宽低,交给比例控制器;角速度环遇到的带宽高、力矩扰动杂,正好是 ADRC 的主场。下面按三个积木分别说清楚它在飞控里到底干了什么。
2.1 跟踪微分器 TD:把姿态指令先整形再送进内环
姿态环从位置环拿到的期望角通常带台阶,直接进角度环会让角速度指令瞬间顶到限幅。TD 的作用是生成一条平滑过渡的轨迹,同时给出这条轨迹的微分。这个微分质量比直接对测量信号差分好一个数量级,因为差分会放大陀螺噪声,而 TD 是先滤波再求导。
# 二阶 TD 离散递推,fhan 是最速控制综合函数 r = 10.0 # 跟踪速度:越大越接近原始指令 h0 = 0.01 # 滤波因子:比控制周期略大,滤掉设定值噪声 h = 0.001 # 实际控制周期 def fhan(x1, x2, r, h): d = r * h d0 = h * d y = x1 + h * x2 a0 = (d * d + 8.0 * r * abs(y)) ** 0.5 a = x2 + (a0 - d) / 2.0 if abs(y) > d0 else x2 + y / h return -r * (a / abs(a)) if abs(a) > d else -r * a / d x1 += h * x2 x2 += h * fhan(x1 - ref, x2, r, h0)这段递推里,x1是平滑后的姿态角指令,x2是它的微分,ref是外环给的原始期望角。r控制跟踪速度,姿态通道一般从 5 试到 20,太小会让整体响应显得迟钝,太大则失去整形意义。h0要独立于h设置,如果直接用h,微分通道会把设定值上的台阶噪声原样放出来;把h0放得比h大一点,相当于先滤后微分。
2.2 扩张状态观测器 ESO:把总扰动拆出来
ESO 是 ADRC 与 PID 最本质的区别。它以控制量和被控输出为输入,z1跟踪输出,z2跟踪输出的导数,z3专门估计那块说不清楚的总扰动。对四旋翼角速度回路来讲,y是陀螺角速度,u是归一化力矩指令,b0是控制增益的粗估计。模型误差、风阻、电机响应滞后,全都会折进z3里。
# 三阶 ESO,对应一阶角速度对象:dot_omega = b0 * u + f e = z1 - omega # 输出估计误差 z1 += h * (z2 - beta1 * e) # 角速度估计 z2 += h * (z3 - beta2 * e + b0 * u) # 角加速度估计 z3 += h * (-beta3 * e) # 总扰动估计 # 观测器带宽 wo 决定增益: # beta1 = 3 * wo # beta2 = 3 * wo * wo # beta3 = wo * wo * wo这里用带宽形式给出观测器增益,是线性扩张状态观测器 LESO 的常规做法。韩京清原始论文里用的是非线性fal函数,工程实现大多走线性化这条路,参数从三个非线性曲线换成单个wo,整定负担小很多。需要注意,z3估出来的总扰动量纲是角加速度,不能直接当作修正量叠加到输出上,要经过b0归一化后再做补偿,否则补偿强度会随模型偏差漂移。
2.3 误差反馈和扰动补偿:三块积木拼成一个控制器
当z3能可靠跟踪总扰动后,施加给电机的指令就不再是误差的简单放大,而是先由误差反馈给出一份基础控制量,再减去z3 / b0做补偿。误差反馈本身对模型精度要求不高,因为没被建模的部分已经被z3兜住。常见的做法是省略非线性反馈律,直接用 PD,也就是 LADRC 的写法。
| ADRC 构成 | 离散实现 | 在四旋翼上的角色 | 常见误用 |
|---|---|---|---|
| TD | 最速控制综合函数递推 | 平滑位置环发来的姿态角指令,输出参考角速度 | 把 TD 当低通滤波器直接放在测量信号上 |
| ESO | 三阶扩张状态观测器 | 估计角速度回路的角加速度和总扰动 | 带宽开太大,陀螺噪声灌进 z3 |
| 误差反馈与补偿 | PD + b0 归一化补偿 | 生成归一化力矩指令给混控器 | 补偿输出不加限幅,饱和后观测器失真 |
表格里最后一行是最容易被忽略的。限幅必须在补偿之后做,而且限幅后的值要反馈给 ESO 作为下一拍的控制量输入,否则观测器追踪的是一个从未执行的指令,z3会越估越偏。
2.4 它和串级 PID 的差别
串级 PID 里外环的输出是内环期望,误差方向完全是预设的;ADRC 多了一层先验判断:先分清这个误差是被拉走的,还是设定值本身变了。代价是多了观测器增益和b0两个需要挂到实机上的系数,多数踩坑也集中在这两个地方。
工程上先调 roll 和 pitch 中的任意一条,因为它们转动惯量接近,参数可以直接复制。yaw 通道的转动惯量和电机反应都比横滚慢,必须重新估b0,直接用同一组观测器带宽容易出现z3满量程跳动。这也是从仿真搬到实机时最容易翻车的一步。
3. 在四旋翼姿态环落地 ADRC:一套能复现的离散实现
ADRC 在四旋翼上的落地位置几乎都在角速度内环。角度环继续用比例控制器就够了,因为角度环带宽低、相位裕度充足,ESO 在这里反而有点大材小用;把 ADRC 放在角速度环,保留一条快速力矩扰动通道,正好发挥它估计高频扰动的能力。整套结构与常见飞控里的 attitude_rate 控制器相似,只是替换了内环 PID 那一段,改动面小,回退也快。
3.1 姿态环路结构:角度外环加角速度内环
按惯导输出 roll、pitch、yaw 角度,外环 P 把角度误差换算成角速度期望:rate_ref = Kp * (angle_ref - angle)。内环 ADRC 的输入是rate_ref和陀螺角速度,输出归一化力矩指令,再经混控器转成四个电机的油门。外环带宽一般取 3 到 6 rad/s,内环带宽取它的四到五倍,两层之间留出足够间隔,避免内环扰动诱导外环振荡。
b0是这个方案里唯一没有明确物理来源的参数。估算方法不复杂:给定一个固定归一化油门增量,记录 0.1 秒内的角速度变化量,用角速度增量除以油门增量再除以时间,就得到这一通道b0的量级。估算误差在正负 30% 以内都能被 ESO 吸收,所以不需要精密仪器,飞控日志里的电机指令和陀螺数据就够了。
3.2 角速度环 ADRC 的最小代码
class AngularRateADRC: def __init__(self, b0, wo, wc, dt): self.b0 = b0 self.dt = dt self.u = 0.0 # 上一拍实际输出的控制量 self.z1 = 0.0 # 角速度估计 self.z2 = 0.0 # 角加速度估计 self.z3 = 0.0 # 总扰动估计,角加速度量纲 self.beta1 = 3.0 * wo self.beta2 = 3.0 * wo * wo self.beta3 = wo * wo * wo self.kp = wc * wc self.kd = 2.0 * wc def update(self, rate_ref, rate, limit): e = self.z1 - rate self.z1 += self.dt * (self.z2 - self.beta1 * e) self.z2 += self.dt * (self.z3 - self.beta2 * e + self.b0 * self.u) self.z3 += self.dt * (-self.beta3 * e) error = rate_ref - self.z1 u0 = self.kp * error - self.kd * self.z2 self.u = u0 - self.z3 / self.b0 self.u = max(-limit, min(limit, self.u)) return self.u代码执行顺序是先更新 ESO,再用z1、z2计算控制量。这里有一个容易被忽视的细节:ESO 更新时用的u是上一拍的控制量,所以self.u在初始化里必须存在,并且每次更新结束时都要存成限幅后的值。kp、kd用wc表示,是把闭环期望带宽直接写进参数,wc通常在 10 到 20 rad/s 之间,wo再取wc的三到五倍,保证扰动估计比控制动作快半拍。
实践中还有一种常见写法是让 z2 直接跟踪角速度的微分,z3 只跟踪外部扰动。这样在模型准确时扰动估计更干净,但代价是需要知道比较准确的转动惯量。对于桨效随电压变化、机架刚性一般的消费级机架,让 z3 把未建模动态一起吃掉更稳,上面这段代码就是按这个思路写的。
3.3 1 kHz 控制帧里的执行顺序
上面的类对应一个控制周期里的一段完整逻辑,飞控执行时建议按下面的顺序跑。顺序反了会让观测结果滞后一个周期,高频时表现为虚假相位延迟,悬停看不出来,快速翻转时z3会出现与角速度同相的抖动。
- 读取陀螺和姿态解算输出,得到
angle和rate。 - 外环计算
rate_ref = Kp * (angle_ref - angle)。 - 内环调用
adrc.update(rate_ref, rate, limit)。 - 根据 roll、pitch、yaw 三个通道的力矩指令做混控。
- 对四个电机归一化油门做限幅和急停保护。
步骤三里的limit是归一化力矩上限,直接取电机油门能动的最大幅度。步骤五的急停判断要放在 ADRC 输出之后,但不要把急停分支插进观测器更新里,否则下一次update的self.u会被污染。
3.4 仿真里先跑通再上机
仿真阶段不需要建很精细的气动模型,把对象简化成单通道一阶惯性系统就能验证 ADRC 的收敛行为。重点看三件事:阶跃响应有没有振铃,叠加力矩扰动后z3能否在 0.2 秒内跟上,以及限幅触发时观测器是否会发散。
| 通道 | 简化对象 | 典型 b0 量级 | 验证内容 |
|---|---|---|---|
| roll / pitch | 1 / (Jx s) | 200 到 500 | 30 度阶跃,看超调与恢复时间 |
| yaw | 1 / (Jz s) | 100 到 300 | 90 度阶跃,检查 z3 是否随偏航力矩变化 |
| 悬停阵风 | 叠加常值力矩扰动 | 同上 | 1 到 3 秒扰动,观察 z3 跟踪与补偿后的位置保持 |
表里的b0是归一化力矩指令到角加速度的增益,不同机型差异很大。拿不准时可以在仿真里用上一节提到的油门增量法现场估一次,比对着论文公式猜快得多。给同一个通道加阶跃参考和正弦扰动各跑一遍,记录的z3曲线会直接暴露观测器带宽是否合适。
4. ESO 带宽与 b0 整定:一条能照着拉的扫参路径
整定顺序建议固定成“先 b0,再 wo,最后 wc”,不要三个一起动。原因是b0决定补偿力度,wo决定扰动估计速度,wc决定闭环响应快慢,三者相互耦合,同时调分不清问题出在哪一环。每改变一个参数,至少跑一次阶跃和一次扰动恢复测试再动下一个。
4.1 四个参数各自管什么
b0最先定,它只控制补偿增益,不参与误差反馈。wo是 ESO 带宽,直接影响扰动收敛速度和噪声放大,是实机上最需要反复试的参数。wc决定误差反馈的收益,调高会让响应变快,但相位裕度下降。TD 的r和h0只在指令整形里出现,不参与扰动补偿回路,对整定顺序影响最弱。
| 参数 | 作用对象 | 调大后果 | 调小后果 |
|---|---|---|---|
| b0 | 扰动补偿增益 | 补偿过头、高频振铃 | 扰动残留、稳态漂移 |
| wo | ESO 收敛速度 | 扰动估计快、噪声放大 | 估计滞后、风扰恢复慢 |
| wc | 闭环带宽 | 响应快、相位裕度下降 | 响应慢、抗扰恢复时间拉长 |
| r | TD 跟踪速度 | 指令跟踪快、超调恢复快 | 指令平滑但通道响应迟钝 |
一个经验性结论是:wo大于五倍wc之后,噪声带来的收益就不划算了。很多四旋翼姿态环最终停在wo = 4*wc附近,这个区间里z3能跟上桨叶气流突变产生的力矩变化,又不会把陀螺高频噪声放大成油门抖动。
4.2 用一段扫参脚本选出 wo
下面的脚本把被控对象简化成dot_x = b0_real * u + d,控制器用三阶 ESO。扫不同的wo,统计一秒钟之后的绝对误差积分,就能看到带宽的边界在哪里。
def run_case(dt=1e-3, N=4000, b0_est=300.0, b0_real=280.0, wo=90.0, wc=18.0): b1, b2, b3 = 3 * wo, 3 * wo * wo, wo * wo * wo x, z1, z2, z3, u = 0.0, 0.0, 0.0, 0.0, 0.0 error_acc = 0.0 for i in range(N): e = z1 - x # 观测器只使用测量输出 z1 += dt * (z2 - b1 * e) z2 += dt * (z3 - b2 * e + b0_est * u) z3 += dt * (-b3 * e) u0 = wc * wc * (1.0 - z1) - 2 * wc * z2 u = u0 - z3 / b0_est d = 2.0 if 0.4 < i * dt < 0.6 else 0.0 # 0.4 到 0.6 秒加扰动 x += dt * (d + b0_real * u) if i * dt > 1.0: error_acc += abs(1.0 - x) * dt return error_acc脚本里b0_est是控制器里预估的增益,b0_real是仿真对象的真实增益,两者故意差了 20,模拟建模误差。运行时可把wo从 30 扫到 300,误差指标先下降再回升,拐点就是合适带宽。扫完wo再以同样的方式扫wc,每次都固定其余参数,这样留下的记录会告诉你每个参数的敏感区间,实机出问题时能立刻定位到是带宽不够还是噪声过大。
提示:扫参脚本里所有参数都要带单位,dt 是秒,角速度是 rad/s,否则
wo的数值在四旋翼和固定翼之间根本无法横向比较。
4.3 实机最容易踩的四个坑
四旋翼上 ADRC 的失败大多不在算法本身,而在实现细节。第一是b0估得离谱,补偿量放大或缩小了一倍,现象是某个通道轻轻一推就抖,但另一个通道又绵软无力。第二是wo开太大,陀螺高频噪声直接灌进z1,再经过kp放大成电机的高频嗞嗞声。第三是限幅前把未限幅的指令喂给 ESO,导致z3一路积分飞掉。第四是电机饱和后z3还在继续积累,等退饱和时会猛烈回弹一下。
对应调整方法是:b0用日志里的油门和角速度重新量一次;wo先压到3 * wc看波形,再逐步往上加;确保三阶 ESO 更新用的控制量来自限幅后的输出;最后在电机限幅触发的周期里把z3的增量乘以 0.5 或直接冻结,等退饱和再恢复更新。这四个问题在日志里都有明确特征:高频等幅振荡、低频漂移、饱和后收敛失败、退饱和超调。
5. 从单通道到真机:位置环 ADRC 和三个上机前提
5.1 位置环要不要也换成 ADRC
姿态环稳定后,下一步是位置环。x 和 y 方向靠倾斜姿态产生水平加速度,z 方向靠总油门克服重力,风阻在这里体现为水平方向的等效加速度扰动。位置环带宽通常在 1 rad/s 以下,主流做法是保留 PID 外环,只把重力补偿和加速度前馈做好。位置环 ADRC 的收益主要体现在长时间抗风悬停,但如果姿态内环已经能把力矩扰动压住,位置环看不到太多区别。
我一般会先在姿态环上验证整套观测器逻辑,再决定是否给加速度内环加 ESO。位置环用 ADRC 时,外环设定值是加速度,内环输出是期望姿态角,这时候一定要把 ESO 估计出的水平加速度扰动除以重力常数换算成角度补偿,否则会有一个隐含的比例因子漂移。
5.2 三个上机前提
上机前必须确认三件事。仿真里z3能跟上一段 1 到 2 秒的常值力矩扰动,并且扰动结束后没有残留的直流偏移。阶跃响应在两到三个振荡周期内收敛到稳态,而不是一直小幅回荡。实测日志里陀螺噪声经观测器后在z3上的映射方差不超过限幅幅度的十分之一,超过就说明wo开大了。
这三个前提都满足后,先在地面站做小角度姿态响应测试,再切手动模式做快速俯仰阶跃。观察z2和z3两条曲线:z2应该紧跟角速度,z3在阶跃瞬间会有一个尖峰,那是电机扭矩响应的未建模部分,尖峰回落速度就是观测器收敛速度。
5.3 一个值得保留的细节
电机饱和是最常被忽略的 ADRC 破坏场景。当u0超过限幅值时,实际执行的控制量已经截断,但 ESO 的z3还在按未截断的指令积分,退饱和瞬间会出现一个反向过冲。处理办法是在限幅逻辑后加一个标志位,当u0与限幅值之差超过阈值时,把z3的这一步更新增量取消,用 上一拍的值顶替。把z3冻结逻辑写在限幅之后,可以保证下一次 ESO 更新使用的控制量始终和真实执行值对齐。
本文还有配套的精品资源,点击获取