简述
一句话总结
线性 ADRC:不管外界怎么干扰、模型多不准,我都能把系统稳住。
用生活例子:你端着一碗水走路
目标:水不洒、不晃。
- 你走路颠簸(= 干扰)
- 碗轻重可能变(= 模型不准)
- 地面不平(= 不确定性)
正常人怎么稳住?不是精确算我该用多少力,而是:
- 先估计现在碗歪没歪、晃不晃
- 再抵消掉颠簸、手抖这些乱七八糟的影响
- 最后轻轻修正,让水保持平稳
这就是 ADRC 的思想。
线性 ADRC 核心就 3 部分(超简单)
1. 跟踪微分器(TD)
作用:让指令变温柔,不猛冲
- 给一个突变目标,它先 "柔一下"
- 防止系统冲过头、震荡
人话:把急刹车变成慢慢刹。
2. 扩张状态观测器(ESO)------ 最灵魂的部分
作用:把所有干扰 + 模型误差,全都看成一个 "总扰动",然后实时估出来。
不管是什么干扰:
- 风吹
- 负载变了
- 摩擦不确定
- 模型建错了
它不问原因,直接估出来 → 直接抵消掉。
人话:你乱我不怕,我能看见你在怎么乱,然后直接给你怼回去。
3. 线性状态误差反馈(SEF)
作用:用简单的比例控制,把系统拉回目标。 因为干扰已经被 ESO 抵消了,这里随便控一控就很稳。
整体工作流程(极简)
- TD 给一个平滑的目标
- ESO 实时估计:当前状态 + 所有干扰
- 控制器 = 反馈 + 直接抵消干扰
LADRC 最牛的地方(人话版)
- 不需要精确模型不像 PID 要反复调,不像 LQR 要建模型
- 抗干扰极强干扰来了直接观测、直接抵消
- 参数极少、超好调 基本就调一个:带宽带宽大 → 快带宽小 → 稳
和你刚才听的 LQR 对比一下
- LQR:我有精确模型,算一个最优的 K
- LADRC:我不管模型,我直接观测干扰、抵消干扰
一句话区分:LQR 是 "算得准",ADRC 是 "抗得狠"。
最终超简总结
线性 ADRC(LADRC) = 平滑指令 + 观测并抵消所有干扰 + 简单反馈 = 鲁棒超强、超好调、不怕乱的控制器。
代码示例(一阶)
cpp
#include <stdint.h>
#include <math.h>
// -------------------------- LADRC 参数配置 --------------------------
// 核心参数:带宽(唯一需要重点调的参数,越大响应越快,越小越稳)
#define LADRC_BW 20.0f // LADRC 带宽,建议先从 10~30 开始试
#define LADRC_TS 0.001f // 控制周期(单位:秒),比如 1ms = 0.001s
// 系统参数(一阶系统:G(s) = b0 / (s + a0),如果不知道,b0 先设为 1.0)
#define LADRC_B0 1.0f // 系统增益(控制量到输出的增益)
// -------------------------- LADRC 内部状态结构体 --------------------------
typedef struct {
// 跟踪微分器(TD)状态
float td_x1; // 跟踪目标的状态
float td_x2; // 跟踪目标的微分状态
// 扩张状态观测器(ESO)状态
float eso_z1; // 观测系统输出
float eso_z2; // 观测系统微分
float eso_z3; // 观测总扰动(核心!)
// 控制器输出
float u; // 最终控制量
} LADRC_HandleTypeDef;
// -------------------------- LADRC 初始化函数 --------------------------
void LADRC_Init(LADRC_HandleTypeDef *hladrc) {
// 初始化所有状态为 0
hladrc->td_x1 = 0.0f;
hladrc->td_x2 = 0.0f;
hladrc->eso_z1 = 0.0f;
hladrc->eso_z2 = 0.0f;
hladrc->eso_z3 = 0.0f;
hladrc->u = 0.0f;
}
// -------------------------- LADRC 核心计算函数 --------------------------
// ref: 目标值(比如要控制的转速、位置)
// y: 系统实际输出(比如传感器采集的转速、位置)
// 返回值:最终控制量 u
float LADRC_Calc(LADRC_HandleTypeDef *hladrc, float ref, float y) {
// ---------------------- 1. 跟踪微分器(TD):平滑目标,避免突变 ----------------------
float td_h0 = LADRC_TS; // TD 步长 = 控制周期
float td_r = LADRC_BW * 2.0f; // TD 速度因子(和带宽正相关)
float td_fh = (td_r*td_r)*(ref - hladrc->td_x1) - 2*td_r*hladrc->td_x2;
// TD 状态更新(最简化的 TD 实现)
hladrc->td_x1 += td_h0 * hladrc->td_x2;
hladrc->td_x2 += td_h0 * td_fh;
// ---------------------- 2. 扩张状态观测器(ESO):观测状态+总扰动 ----------------------
// ESO 增益(线性 ADRC 固定公式,和带宽相关)
float eso_beta1 = 3.0f * LADRC_BW;
float eso_beta2 = 3.0f * LADRC_BW * LADRC_BW;
float eso_beta3 = LADRC_BW * LADRC_BW * LADRC_BW;
// ESO 误差(观测值 - 实际输出)
float eso_e = hladrc->eso_z1 - y;
// ESO 状态更新(核心:观测总扰动 z3)
hladrc->eso_z1 += LADRC_TS * (hladrc->eso_z2 - eso_beta1 * eso_e);
hladrc->eso_z2 += LADRC_TS * (hladrc->eso_z3 - eso_beta2 * eso_e + LADRC_B0 * hladrc->u);
hladrc->eso_z3 += LADRC_TS * (-eso_beta3 * eso_e);
// ---------------------- 3. 状态误差反馈(SEF):计算控制量 + 扰动补偿 ----------------------
// SEF 增益(线性 ADRC 固定公式)
float sef_kp = LADRC_BW;
float sef_kd = 0.5f * LADRC_BW;
// 误差(平滑后的目标 - 观测的输出)
float sef_e1 = hladrc->td_x1 - hladrc->eso_z1;
float sef_e2 = hladrc->td_x2 - hladrc->eso_z2;
// 基础控制量(比例+微分)
float u0 = sef_kp * sef_e1 + sef_kd * sef_e2;
// 扰动补偿(核心!直接抵消观测到的总扰动)
hladrc->u = u0 - hladrc->eso_z3 / LADRC_B0;
// 可选:控制量限幅(根据你的系统调整,比如电机PWM范围 0~1000)
if (hladrc->u > 1000.0f) hladrc->u = 1000.0f;
if (hladrc->u < 0.0f) hladrc->u = 0.0f;
return hladrc->u;
}
// -------------------------- 测试示例(主函数) --------------------------
int main(void) {
LADRC_HandleTypeDef ladrc;
LADRC_Init(&ladrc);
float target = 500.0f; // 目标值(比如控制电机到 500rpm)
float feedback = 0.0f; // 系统反馈值(初始为 0)
float output; // LADRC 输出控制量
// 模拟 1000 次控制周期(1ms 一次,共 1 秒)
for (int i = 0; i < 1000; i++) {
// 1. 计算 LADRC 控制量
output = LADRC_Calc(&ladrc, target, feedback);
// 2. 模拟系统响应(替换为你的实际系统:比如输出控制量到电机,读取传感器反馈)
// 这里用一阶系统模拟:feedback = feedback + (output - feedback)*0.01
feedback += (output - feedback) * 0.01f;
// 3. 模拟干扰(比如第 500 次加入 100 的扰动)
if (i == 500) {
feedback += 100.0f;
}
// 打印结果(实际使用时可注释,仅用于测试)
// printf("第 %d 次:目标=%.1f, 反馈=%.1f, 控制量=%.1f, 观测扰动=%.1f\n",
// i, target, feedback, output, ladrc.eso_z3);
}
return 0;
}
代码关键说明
1. 核心参数怎么调(新手必看)
- LADRC_BW(带宽) :唯一核心调参项
- 调大:系统响应更快,但可能震荡(比如从 20→30)
- 调小:系统更稳,但响应变慢(比如从 20→10)
- 新手建议先设为
20.0f,再根据实际效果微调。
- LADRC_B0(系统增益) :
- 如果知道你的系统模型(比如电机转速 = K×PWM),K 就是 B0;
- 如果不知道,先设为
1.0f,不影响基本使用(只是补偿效果稍差)。
- 控制量限幅 :代码里
u > 1000/u < 0是示例,要改成你系统的实际范围(比如 PWM 范围 0~255、电压范围 -12~12V 等)。
2. 代码移植要点
- 去掉
main函数的测试代码,保留LADRC_Init和LADRC_Calc; - 在你的项目中:
- 初始化 LADRC 结构体(
LADRC_Init); - 在控制周期中断里(比如 1ms 定时器)调用
LADRC_Calc,传入目标值 和传感器反馈值; - 将返回的控制量输出到执行器(电机、阀门等)。
- 初始化 LADRC 结构体(
| 代码模块 | 对应 LADRC 功能 | 作用 |
|---|---|---|
TD 部分 |
跟踪微分器 | 平滑目标值,避免目标突变导致系统冲过头 |
ESO 部分 |
扩张状态观测器 | 观测系统输出、输出微分、总扰动(包括模型误差 + 外界干扰) |
SEF 部分 |
状态误差反馈 | 基础控制 + 扰动补偿(直接抵消观测到的扰动) |
总结
- 核心逻辑 :LADRC 代码核心是「平滑目标 + 观测扰动 + 补偿扰动 + 简单反馈」,其中
eso_z3观测总扰动并补偿是最关键的一步; - 调参重点 :新手只需先调
LADRC_BW(带宽),优先保证系统稳定,再提升响应速度; - 移植要点 :替换控制量限幅范围,将
main中的模拟系统换成你的实际硬件(传感器采集 + 执行器输出)即可直接使用。
二阶和一阶的主要区别
cpp
// 二阶 LADRC 状态结构体(多了一维)
typedef struct {
// TD 多了微分的微分
float td_x1;
float td_x2;
float td_x3; // 新增:二阶微分
// ESO 多了一维观测
float eso_z1;
float eso_z2;
float eso_z3;
float eso_z4; // 新增:二阶微分/扰动相关
float u;
} LADRC2_HandleTypeDef;
// ESO 增益(二阶公式)
float eso_beta1 = 4*BW;
float eso_beta2 = 6*BW*BW;
float eso_beta3 = 4*BW*BW*BW;
float eso_beta4 = BW*BW*BW*BW; // 新增
// SEF 反馈(多了二阶误差)
float sef_e3 = td_x3 - eso_z3; // 新增:二阶误差
float u0 = kp*e1 + kd*e2 + kdd*e3; // 新增:二阶微分反馈
公式原理


cs
hladrc->td_x1 += td_h0 * hladrc->td_x2;
hladrc->td_x2 += td_h0 * td_fh; // td_fh 就是 -r²(x1-r) - 2r x2







cs
hladrc->u = u0 - hladrc->eso_z3 / LADRC_B0;
数学本质------用观测到的 z3 代替真实扰动 w(t),直接除以 b0 补偿。
3. 为什么要限幅?
工程上控制量 u 有物理上限(比如 PWM 最大255、电压最大12V),如果不限制,计算出的 u 超出硬件范围会导致系统饱和、失控,所以必须加限幅。
整个流程的数学目标:通过 TD 平滑目标,ESO 观测扰动,SEF 补偿扰动,最终让系统变成"无扰动、无超调"的理想系统。
总结
- TD 的数学核心:用"弹簧-阻尼"型微分方程生成平滑目标,离散化后实现简单,避免目标突变;
- ESO 的数学核心:把扰动扩张成状态,通过极点配置的增益和观测误差,实时跟踪状态和扰动,这是抗扰的关键;
- SEF 的数学核心:线性反馈+扰动补偿,通过 u=u0−z3/b0 抵消所有扰动,让系统逼近理想无扰状态。
简单说,LADRC 的每一步计算都是"数学推导+工程简化"的结果------既保证理论最优,又能在嵌入式系统里简单实现。