线性自抗扰控制LADRC

简述

一句话总结

线性 ADRC:不管外界怎么干扰、模型多不准,我都能把系统稳住。


用生活例子:你端着一碗水走路

目标:水不洒、不晃。

  • 你走路颠簸(= 干扰)
  • 碗轻重可能变(= 模型不准)
  • 地面不平(= 不确定性)

正常人怎么稳住?不是精确算我该用多少力,而是:

  1. 估计现在碗歪没歪、晃不晃
  2. 抵消掉颠簸、手抖这些乱七八糟的影响
  3. 最后轻轻修正,让水保持平稳

这就是 ADRC 的思想


线性 ADRC 核心就 3 部分(超简单)

1. 跟踪微分器(TD)

作用:让指令变温柔,不猛冲

  • 给一个突变目标,它先 "柔一下"
  • 防止系统冲过头、震荡

人话:把急刹车变成慢慢刹。


2. 扩张状态观测器(ESO)------ 最灵魂的部分

作用:把所有干扰 + 模型误差,全都看成一个 "总扰动",然后实时估出来。

不管是什么干扰:

  • 风吹
  • 负载变了
  • 摩擦不确定
  • 模型建错了

它不问原因,直接估出来 → 直接抵消掉

人话:你乱我不怕,我能看见你在怎么乱,然后直接给你怼回去。


3. 线性状态误差反馈(SEF)

作用:用简单的比例控制,把系统拉回目标。 因为干扰已经被 ESO 抵消了,这里随便控一控就很稳


整体工作流程(极简)

  1. TD 给一个平滑的目标
  2. ESO 实时估计:当前状态 + 所有干扰
  3. 控制器 = 反馈 + 直接抵消干扰

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_InitLADRC_Calc
  • 在你的项目中:
    1. 初始化 LADRC 结构体(LADRC_Init);
    2. 在控制周期中断里(比如 1ms 定时器)调用 LADRC_Calc,传入目标值传感器反馈值
    3. 将返回的控制量输出到执行器(电机、阀门等)。
代码模块 对应 LADRC 功能 作用
TD 部分 跟踪微分器 平滑目标值,避免目标突变导致系统冲过头
ESO 部分 扩张状态观测器 观测系统输出、输出微分、总扰动(包括模型误差 + 外界干扰)
SEF 部分 状态误差反馈 基础控制 + 扰动补偿(直接抵消观测到的扰动)

总结

  1. 核心逻辑 :LADRC 代码核心是「平滑目标 + 观测扰动 + 补偿扰动 + 简单反馈」,其中 eso_z3 观测总扰动并补偿是最关键的一步;
  2. 调参重点 :新手只需先调 LADRC_BW(带宽),优先保证系统稳定,再提升响应速度;
  3. 移植要点 :替换控制量限幅范围,将 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 补偿扰动,最终让系统变成"无扰动、无超调"的理想系统。

总结

  1. TD 的数学核心:用"弹簧-阻尼"型微分方程生成平滑目标,离散化后实现简单,避免目标突变;
  2. ESO 的数学核心:把扰动扩张成状态,通过极点配置的增益和观测误差,实时跟踪状态和扰动,这是抗扰的关键;
  3. SEF 的数学核心:线性反馈+扰动补偿,通过 u=u0−z3/b0 抵消所有扰动,让系统逼近理想无扰状态。

简单说,LADRC 的每一步计算都是"数学推导+工程简化"的结果------既保证理论最优,又能在嵌入式系统里简单实现。

相关推荐
Hhang2 小时前
Pageindex -- 新一代的文档智能检索
前端·人工智能
前端付豪2 小时前
LangChain 模型I/O:输入提示、调用模型、解析输出
人工智能·程序员·langchain
Ivanqhz2 小时前
半格与数据流分析的五个要素(D、V、F、I、Λ)
开发语言·c++·后端·算法·rust
瑞华丽PLM2 小时前
守住数字化的胜算:PLM项目实施风险控制全景方案
大数据·人工智能·plm·国产plm·瑞华丽plm·瑞华丽
恋猫de小郭2 小时前
Claude Code 已经 100% 自己写代码,为什么 Anthropic 还有上百个工程职位空缺?
前端·人工智能·ai编程
董厂长2 小时前
用 LangGraph 实现 Small-to-Big 分块检索策略
人工智能·算法·rag
大江东去浪淘尽千古风流人物2 小时前
【Sensor】IMU传感器选型车轨级 VS 消费级
人工智能·python·算法·机器学习·机器人
A9better2 小时前
C++——指针与内存
c语言·开发语言·c++·学习
坚持编程的菜鸟2 小时前
互质数的个数
c语言·算法