尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

自抗扰控制ADRC的C语言工程实现与调参实战

自抗扰控制ADRC的C语言工程实现与调参实战 简介本资源是一套可直接集成到嵌入式系统的自抗扰控制ADRCC语言实现方案面向自动控制、电机驱动、无人机姿态调节等实时控制领域的开发者与工程师解决传统PID在模型不确定、强扰动场景下鲁棒性不足的问题。代码完整包含跟踪微分器TD、扩张状态观测器ESO、非线性反馈组合及扰动补偿四大核心模块并对最速TD进行了h0N·h改进支持参数在线调节与工程快速部署。压缩包共36个文件含10个典型应用示例sample、4个主控逻辑文件master、2个头文件.h、1个关键参数说明文本txt以及.git版本管理相关元数据和配置文件整体仅32KB轻量紧凑便于移植至STM32、GD32等MCU平台。目前已有128人学习下载提供开箱即用的ADRC.c与ADRC.h接口附带清晰参数映射表r/h/N/beta_0x/b0/alpha1/alpha2等显著降低算法落地门槛。 自抗扰控制这几年在工业现场和嵌入式圈子里越来越热但网上的资料大多是理论推导和仿真真正能直接落到C语言工程里的代码反而很少。我当时做电机调速项目被模型不确定性折腾得够呛PID参数换一组工况就要重调一轮后来深入了解ADRC之后发现它能把模型误差和外部扰动都当作总扰动去估计和补偿这个思路确实对工程很友好。这篇就把我从理论到C代码落地的完整过程整理出来包括数据结构怎么设计、离散化公式怎么推、参数怎么调、实机上会踩哪些坑希望对想把ADRC搬进单片机和嵌入式系统的朋友有帮助。1. 项目整体设计与核心思路1.1 ADRC到底解决什么问题传统PID的核心是把误差的比例、积分、微分线性组合起来结构简单、应用广泛但有个天然短板它不知道对象的内部特性也不知道外部扰动是什么只能靠积分项慢慢磨。比如控制一个直流电机转速负载突变时PID要等转速掉下来才反应积分项还要花时间累积所以就容易出现超调大、恢复慢的现象。ADRC的思路不一样它把被控对象看成一个“串联积分器”加上一个“总扰动”然后专门设计一个扩张状态观测器ESO把这个总扰动实时估计出来在控制量里直接补偿掉。相当于系统里多了一个“扰动探针”能把内部参数变化、外部负载力矩、未建模动态全部实时测出来控制起来自然就更从容。这个思路对工程意义很大尤其是在工况变化频繁的场合不用频繁换PID参数。1.2 为什么选择C语言实现做ADRC落地的工程师绝大部分面对的是MCU、DSP或者工控ARM这些平台的开发环境基本都是以C语言为主。C语言的优势在于接近硬件内存布局可控执行效率高能满足控制环的实时性要求可移植性好从STM32换到其他内核的芯片算法核心代码基本不用改工程生态成熟可以和现有驱动、通信、RTOS无缝集成。我选择把ADRC做成一个独立的模块不依赖任何特定硬件数据结构和函数接口设计成纯C风格方便大家粘贴到自己的工程里。1.3 ADRC的结构组成标准ADRC由三部分组成跟踪微分器TD、扩张状态观测器ESO、非线性状态误差反馈NLSEF。TD负责安排过渡过程给输入信号做平滑处理避免阶跃指令直接冲击被控对象同时提取输入信号的微分ESO是整个ADRC的核心它把输出信号和输入信号结合起来估计出系统各阶状态以及总扰动NLSEF把TD输出的目标状态与实际状态之间的误差用非线性组合形成初步控制量再叠加扰动补偿形成最终控制量。完整控制律可以理解为u u0 - z3 / b0其中u0是NLSEF输出的基础控制量z3是ESO估计的总扰动b0是控制增益的估计值。这个b0是个关键参数后面会详细说。2. 核心模块原理与离散化推导2.1 跟踪微分器TD的离散形式TD的连续形式可以从最速控制综合函数来推导但工程实现时我们直接使用离散递推公式。针对二阶系统常用的是以下形式fh fhan(x1 - v, x2, r, h0) x1 x1 h * x2 x2 x2 h * fh其中v是目标输入x1是跟踪信号x2是跟踪信号的微分r决定跟踪速度h是采样周期h0是滤波因子。fhan函数是最速控制综合函数它在保证快速性的同时避免了抖动具体公式为d r * h0 d0 h0 * d y x1 h0 * x2 a0 sqrt(d * d 8 * r * fabs(y)) a (fabs(y) d0) ? x2 y / h0 : x2 0.5 * (a0 - d) * sign(y) fhan -r * sat(a, d)这里的sat是饱和函数sign是符号函数。在实际代码中r的值决定了系统跟踪指令的速度r越大跟踪越快但也会放大噪声。h0一般取比采样周期稍大一点的值用来滤除微分信号中的噪声我通常取h0为采样周期的2到10倍实际调试时看波形调整。2.2 扩张状态观测器ESO的离散化ESO是ADRC里面最重要也最容易出问题的地方。以二阶对象为例系统模型为y f(y, y, w, t) b0 * u其中f包含了内扰和外扰的总和。ESO的离散递推形式为e z1 - y fe fal(e, 0.5, delta) fe1 fal(e, 0.25, delta) z1 z1 h * (z2 - beta1 * e) z2 z2 h * (z3 - beta2 * fe b0 * u) z3 z3 h * (-beta3 * fe1)这里z1估计系统的输出z2估计输出的微分z3就是扩张出来的“总扰动”估计值。beta1、beta2、beta3是观测器增益它们和观测器带宽相关。工程上可以用带宽法来配置beta1 3 * w0beta2 3 * w0 * w0beta3 w0 * w0 * w0w0就是观测器带宽。这个带宽越大扰动估计越快但同时对噪声也越敏感需要在快速性和抗噪之间取平衡。fal函数是非线性函数定义是fal(e, alpha, delta) (fabs(e) delta) ? e / pow(delta, 1 - alpha) : pow(fabs(e), alpha) * sign(e)alpha取0.5和0.25是韩京清老师文章里的常用配置delta一般取0.01到0.1之间。这个函数的意义是在小误差区域使用线性区间避免在零点附近出现增益过大的问题同时保留非线性段高增益、快收敛的特性。2.3 非线性状态误差反馈NLSEFNLSEF的结构相对简单就是把TD输出的目标状态v1、v2和ESO估计的z1、z2做差然后非线性组合e1 v1 - z1 e2 v2 - z2 u0 kp * fal(e1, alpha1, delta) kd * fal(e2, alpha2, delta)alpha1一般为0.5到0.75alpha2一般取1到1.5。这里使用fal函数的好处是误差大的时候增益相对小避免过大超调误差小的时候增益相对大能保持收敛精度。这和线性PD有些类似但非线性特性让它在不同误差范围内的表现更平滑。最终控制量u u0 - z3 / b0注意这个u通常需要做限幅处理防止控制量超过执行机构的物理范围。b0是控制增益的估计值它的物理意义是“单位控制量能产生多少加速度”在电机调速里它和转动惯量、力矩系数有关在温控系统里它和热容、加热功率有关。b0不准没关系ESO会把模型偏差当作扰动一并估计并补偿但b0偏差过大时系统稳定性会变差所以b0一般通过实验粗略标定即可。3. 完整C代码实现过程3.1 数据结构设计写C代码第一步是定义ADRC对象的结构体。因为TD、ESO、NLSEF都是带状态递推的所以必须把状态变量保存下来不能放在函数内部当局部变量。我习惯用一个结构体把所有状态和参数都封装起来这样每种被控对象只需要声明一个结构体变量代码可读性和可维护性都很好。typedef struct { // 采样周期 float dt; // TD参数 float r; float h0; // ESO参数 float w0; float b0; float delta; // NLSEF参数 float kp; float kd; float alpha1; float alpha2; // TD状态 float x1; float x2; // ESO状态 float z1; float z2; float z3; // 控制量限幅 float u_max; float u_min; // 输出控制量 float u; } ADRC_Handle;这里所有的状态变量都是float类型对于大多数MCU来说足够用了。如果用的是没有浮点单元的廉价单片机可以考虑把状态变量改成double或者用Q格式定点数但性能影响很大后面在常见问题里会说。3.2 基础函数实现fhan和fal是ADRC里的两个核心基础函数它们的实现质量直接决定了控制效果。这两个函数在C语言里实现起来不复杂但要注意边界条件。static float sign_func(float x) { if (x 0.0f) return 1.0f; if (x 0.0f) return -1.0f; return 0.0f; } static float sat_func(float x, float limit) { if (x limit) return limit; if (x -limit) return -limit; return x; } static float fhan_func(float x1, float x2, float r, float h0) { float d, d0, y, a0, a, fh; d r * h0 * h0; d0 h0 * d; y x1 h0 * x2; a0 sqrtf(d * d 8.0f * r * fabsf(y)); if (fabsf(y) d0) { a x2 y / h0; } else { a x2 0.5f * (a0 - d) * sign_func(y); } fh -r * sat_func(a, d); return fh; } static float fal_func(float e, float alpha, float delta) { if (fabsf(e) delta) { return e / powf(delta, 1.0f - alpha); } else { return powf(fabsf(e), alpha) * sign_func(e); } }注意fhan函数里的d是用r * h0 * h0计算的不是r * h0这个细节很容易写错。因为h0被当作“步长”使用二次方意味着这个时间量在公式里是有平方的量纲关系的。另外为了提高运行效率fabsf、sqrtf、powf这些函数在嵌入式环境里应该放在循环外计算的部分尽量提前算好不过这里为了代码可读性先写成函数调用实际工程中如果对性能敏感可以用查表法替代powf。3.3 ADRC核心计算函数核心计算函数负责一次完整的控制周期运算接收目标指令和系统反馈计算出控制量。实现思路是先更新TD再更新ESO最后算NLSEF和扰动补偿。void ADRC_Calc(ADRC_Handle *adrc, float ref, float feedback) { float fh, e, fe, fe1; float e1, e2, u0; // 1. 跟踪微分器TD安排过渡过程 fh fhan_func(adrc-x1 - ref, adrc-x2, adrc-r, adrc-h0); adrc-x1 adrc-dt * adrc-x2; adrc-x2 adrc-dt * fh; // 2. 扩张状态观测器ESO估计状态和总扰动 e adrc-z1 - feedback; fe fal_func(e, 0.5f, adrc-delta); fe1 fal_func(e, 0.25f, adrc-delta); adrc-z1 adrc-dt * (adrc-z2 - 3.0f * adrc-w0 * e); adrc-z2 adrc-dt * (adrc-z3 - 3.0f * adrc-w0 * adrc-w0 * fe adrc-b0 * adrc-u); adrc-z3 adrc-dt * (-adrc-w0 * adrc-w0 * adrc-w0 * fe1); // 3. 非线性状态误差反馈NLSEF e1 adrc-x1 - adrc-z1; e2 adrc-x2 - adrc-z2; u0 adrc-kp * fal_func(e1, adrc-alpha1, adrc-delta) adrc-kd * fal_func(e2, adrc-alpha2, adrc-delta); // 4. 扰动补偿与控制量限幅 adrc-u u0 - adrc-z3 / adrc-b0; if (adrc-u adrc-u_max) adrc-u adrc-u_max; if (adrc-u adrc-u_min) adrc-u adrc-u_min; }这段代码是ADRC核心的主干结构上很清晰。需要注意的是ESO更新时用到的u是上一次的控制量也就是上一拍计算出来的值不能直接用这一拍刚算的u0。原因在于ESO的微分方程f(y, y, w) b0*u里面u是过去这一段时间内实际作用的控制量不是当前时刻还没有执行的控制量。所以代码里先更新ESO后计算新的控制量这样的顺序才是正确的。3.4 初始化函数初始化函数负责把结构体里的参数都设置好。参数从哪里来通常的做法是先根据经验粗略估算再通过实验微调我后面会详细说调参流程。void ADRC_Init(ADRC_Handle *adrc, float dt, float r, float h0, float w0, float b0, float delta, float kp, float kd, float alpha1, float alpha2, float u_min, float u_max) { adrc-dt dt; adrc-r r; adrc-h0 h0; adrc-w0 w0; adrc-b0 b0; adrc-delta delta; adrc-kp kp; adrc-kd kd; adrc-alpha1 alpha1; adrc-alpha2 alpha2; adrc-u_min u_min; adrc-u_max u_max; adrc-x1 0.0f; adrc-x2 0.0f; adrc-z1 0.0f; adrc-z2 0.0f; adrc-z3 0.0f; adrc-u 0.0f; }初始状态清零尤其是z3清零很重要。如果上电时系统输出已经有偏移而z3初始值是0那么ESO会先在开始阶段估计出这个“初始误差”表现为一个较大的扰动估计值控制量在启动阶段会有一个跳变。所以如果系统启动时反馈信号不是0最好把z1初始化成当前的反馈值我实际工程里就遇到过这个问题。4. 定时器调度与实时性实现4.1 控制周期的确定ADRC的控制周期取决于被控对象的时间常数。对电机电流环采样周期一般在10kHz到20kHz对应dt为50微秒到100微秒对电机速度环一般在1kHz到10kHz对温度控制这类大惯性对象0.1Hz到10Hz都有可能。需要注意的是ESO的带宽w0必须远小于采样频率的1/2否则离散化误差会急剧增大。实践经验是w0尽量小于0.1倍的采样频率换算成周期就是控制周期比观测器带宽对应的时间常数至少快10倍。以常见的电机转速控制为例控制周期取1kHz1ms观测器带宽w0取50到200delta取0.01到0.05b0通过空载加速实验粗略标定。这样设置之后整套系统都能稳定工作。4.2 定时器中断里的调用在嵌入式C工程中ADRC计算应该放在定时器中断或者RTOS的高优先级任务里执行并且要保证周期严格固定。下面是一个基于定时器中断的伪代码示例void TIM_PeriodElapsedCallback(void) { float ref Get_Ref(); // 获取目标值 float fb Get_Feedback(); // 获取反馈值 ADRC_Calc(adrc, ref, fb); // 执行ADRC控制 Set_Actuator(adrc.u); // 输出控制量 }这里有几个重要细节Get_Ref和Get_Feedback必须是在同一时刻采集的避免时间错位ADRC_Calc内部不能有阻塞等待、延时、浮点除法慢操作以外的复杂计算如果MCU有FPU把浮点运算打开速度会快很多尽量避免在中断里调用printf否则会产生不确定的延迟。我当时在STM32F405上做实验用1ms周期控制电机转速整段ADRC计算耗时大约几十微秒完全没问题。但是如果换成8位单片机或低主频MCUfloat运算会慢很多需要考虑定点化或者降低控制频率。4.3 定时器与控制周期不匹配的问题很多初学者会遇到一个坑定时器配置成1ms中断但控制周期实际上是1.05ms或者0.98ms。原因可能是定时器的预分频和自动重装载值设置不对。ADRC对时间参数的敏感性不高但如果dt和中断周期差太多会影响离散化精度。一个简单的校验方法是在中断里累加计数然后每1秒钟把计数值打印出来如果累加1000次对应的实际时间是1.5秒说明定时器配置有误直接用这个偏差修正dt。5. ADRC参数整定流程与实战经验5.1 参数整定的先后顺序ADRC调参是一个从里到外的过程不能一上来就乱调。我推荐的顺序是先标定b0。b0是控制增益可以通过开环实验来获得。比如电机空载时给定一个固定的控制量u测量加速度b0约等于加速度除以u。粗略估算可以用b0 最大加速度 / 额定控制量。调ESO的w0。把TD和NLSEF暂时去掉或者把它们的作用减弱先用ESO去观测输出。观察z1是否快速跟踪反馈、z3是否平缓如果z1跟踪慢增大w0如果z3波动大减小w0。调TD的r。r决定跟踪速度控制指令不能超过执行器的物理能力。把r从小往大调观察参考信号跟踪效果不要把r调到让控制量频繁饱和。最后调NLSEF的kp和kd。这两个参数类似PD的增益可以先给一个保守的kp例如0.1到1然后逐步增大直到系统出现轻微振荡再退回一点。kd也一样主要用于抑制动态超调。5.2 参数影响速查表下面这个表格是我在调参过程中的经验总结可以根据异常现象快速定位该调哪个参数。现象优先调整参数调整方向输出跟踪参考信号太慢r增大参考信号出现振铃h0增大反馈信号估计滞后明显w0增大观测的z3噪声非常大w0减小系统稳态误差大kp增大动态超调大kd增大控制量频繁饱和u_max/u_min检查限幅高频抖振delta增大注意这个表是经验参考不是绝对的。不同对象的物理特性和噪声水平差异很大一定要结合示波器或数据采集来观察波形不要闭着眼调。5.3 调参中的一个具体案例之前调一个直流电机转速环一开始b0取20w0取50kp取1系统可以稳定但响应偏慢转速从启动到目标值大约需要2秒。然后逐步增大w0到150z3估计的扰动曲线明显更平滑了响应提升到1.2秒左右。接着增大kp到3响应进一步缩短到0.6秒但电机声音开始有一点啸叫用示波器看转速波形已经有了小幅振荡。于是增大kd到0.5振荡消失。最后把r从100提到300启动过程更利索但控制量峰值也明显变大好在执行器余量足够就这样定了下来。这个过程里每个参数都是单独调整、观察、记录不建议同时动多个参数。因为ADRC是强耦合的非线性系统同时改多个参数出了问题根本不知道是谁引起的。6. 实机调试常见问题与排查技巧6.1 高频振荡问题高频振荡是ADRC最容易出现的问题之一尤其在w0过大、delta过小、采样周期偏大的组合下更容易发生。表现为控制量在某个范围内快速抖动执行器发出高频噪声。排查思路先降低w0如果振荡明显缓解说明是观测器带宽太高导致的再把delta从0.01增大到0.1检查是否是因为fal线性区间过窄导致增益突变最后用示波器观察反馈信号的噪声水平如果噪声本来就大先做一阶低通滤波。这里有个原则ADRC的ESO本身有滤波能力但不要把滤波全部依赖ESO在反馈采集端做适当的硬件滤波或者软件滤波效果更好。我当时在电机霍尔传感器反馈上串联了一个截止频率为控制频率1/10的一阶低通滤波ADRC的压力一下子小了很多。6.2 初始扰动估计跳变问题我前面提到过如果系统上电时反馈值不为零而z1初始化为0那么启动瞬间z1 - feedback会是一个比较大的值导致z3迅速飙高控制量瞬间冲到限幅值整个系统剧烈抖一下。解决办法简单粗暴上电初始化时把z1赋值为当前反馈值。或者在定时器运行起来后、首次执行ADRC之前先读取一次反馈值赋给z1。这个“初始化状态匹配”的习惯在做任何带观测器的控制算法时都适用。6.3 b0不准带来的影响b0是ADRC里面最需要实测的参数。如果b0比真实值偏小ESO会把一部分真实控制量当作“扰动”去补偿导致控制量偏大、系统更容易振荡如果b0比真实值偏大扰动补偿力度不够系统的抗扰能力会变差。实测b0的方法不复杂让系统进入开环状态给一个固定的PWM占空比记录下转速变化曲线然后求加速度用加速度除以PWM占空比就是b0的粗略值。多测几个占空比点取平均值。要注意的是b0在工作点变化时可能会有变化所以选一个代表性的工作点来标定即可不必追求全工况精确因为ESO会自适应补偿剩余误差。6.4 数据监视与调试接口实机调试ADRC时只靠示波器看转速波形往往不够需要把z1、z2、z3这些状态量导出来观察。我当时用了一个比较土但有效的方法把定时器中断里的数据打包进一个环形缓冲区然后在后台任务里通过串口周期性地发送出来在上位机用串口助手或者Python脚本画波形。具体的实现思路是在ADRC_Calc之后把x1、x2、z1、z2、z3、u这些变量写入一个全局数组然后在主循环里每隔一段时间把这些数组内容通过串口发出去。串口波特率不用太高115200足够。上位机这边我用Python的pyserial读取数据用matplotlib实时绘图就能直观看到ESO的观测效果和扰动的估计情况。6.5 从仿真到实机的注意事项很多人先在MATLAB/Simulink里搭了一个ADRC模型仿真效果很好但搬到实际MCU上就各种问题。核心原因有三个仿真里的模型是精确的实际系统有噪声、有迟滞、有死区仿真的控制周期是连续或非常小的实际MCU采样周期只有1ms甚至更差仿真的饱和、限幅、异常处理没有充分考虑工程细节。所以从仿真到实机一定要在代码里加上限幅、抗积分饱和、状态初始化匹配、输出平滑等工程化处理。不要以为ADRC自带抗扰能力就等于自带“免死金牌”工程该做的事一样都不能少。7. 扩展与变体不同场景的ADRC改造7.1 一阶对象的ADRC简化二阶对象的ADRC需要三个ESO状态但对很多工业对象比如温度、流量来说本质上是一阶惯性加纯滞后如果强行用二阶模型系统参数会变多调起来也麻烦。对一阶对象可以把ESO简化为两个状态扰动只扩张一阶对应代码里去掉z2即可。简化后观测器递推为e z1 - y; z1 dt * (z2 - beta1 * e b0 * u); z2 dt * (-beta2 * fe);TD也可以退化成只做一阶惯性滤波。这种简化的好处是参数更少工程上更容易调试。很多商业PLC里的“自抗扰温控”其实就是这种简化结构。7.2 线性ADRCLADRC高志强老师提出的LADRC把这套非线性结构简化成了线性形式TD用线性跟踪器、ESO用线性观测器、NLSEF用PD控制器替代。这样原本需要调的七八个参数压缩成三个观测器带宽w0、控制器带宽wc、控制增益b0。我当时在一个不太需要快速响应的温度控制项目里尝试了LADRC调参确实比非线性ADRC容易得多而且对于滞后又大的对象LADRC配合一个Smith预估器效果也不错。如果第一次接触ADRC我建议先上LADRC把套路熟悉了再回头加非线性函数。代码改动很简单把fal函数换成线性函数e本身fhan不常用的话用一个简单的二阶低通替代。7.3 与PID的对比实测我这里放一个当时在同一电机平台上做的对比记录。同样给定转速阶跃PID经过精细调参后启动超调约12%受到负载突变时转速跌落约15%恢复时间约800ms。而ADRC这里用非线性ADRC启动超调控制在3%以内负载突变跌落在5%以内恢复时间约200ms。但ADRC不是万能药它的代价是参数更多、调试门槛更高、状态观测量对噪声更敏感。所以选型时要看场景如果工况很稳定、只有一组固定参数PID完全够用如果工况变化大、负载经常突变、调试预算充足ADRC更值得投入。8. 几个让ADRC更好用的工程小技巧8.1 用查表法优化fal函数在低端MCU上fal函数里的powf运算是一个比较大的开销。如果控制周期短或者MCU性能弱建议把fal函数做成查表形式。因为输入误差e通常都在某个范围内提前算好一张表运行时线性插值取值精度完全够。float fal_table[TABLE_SIZE]; float fal_error_min, fal_error_max; void fal_table_init(float alpha, float delta, float min, float max) { for (int i 0; i TABLE_SIZE; i) { float e min (max - min) * i / (TABLE_SIZE - 1); fal_table[i] fal_func(e, alpha, delta); } }实际操作时把误差e映射到表格索引然后取值即可。注意正负对称只存一半表格也行。8.2 在代码里留好数据记录开关写代码时我习惯用条件编译把数据记录功能独立出来发布版不开调试时开。比如#define ADRC_DEBUG_LOG 1 #if ADRC_DEBUG_LOG #define LOG_VAR(x) log_buffer[idx] x; #else #define LOG_VAR(x) #endif这样在调试阶段可以方便地记录ADRC内部状态部署到正式环境时关闭日志宏代码性能不受影响。8.3 实测中要注意执行器的饱和ADRC的扰动补偿机制会在ESO估计到扰动后立刻产生一个很大的补偿量如果执行器已经饱和这个补偿量是无法实现的。系统就处于“伪线性”状态。这时候如果不做限制会导致积分项或者z3越积越大等扰动消失后出现较大超调。我的习惯是在控制量限幅的同时把z3也做一个软限幅方式很简单如果u达到上限z3的更新量就不再增加正值如果u达到下限z3就不再增加负值。这样可以让ESO在饱和状态下进入“待机”状态避免状态量异常堆积。// 抗饱和处理在ESO更新后调用 if ((adrc-u adrc-u_max) (adrc-z3 0.0f)) { // 不更新z3的正向积分 } else if ((adrc-u adrc-u_min) (adrc-z3 0.0f)) { // 不更新z3的负向积分 } else { adrc-z3 adrc-dt * (-adrc-w0 * adrc-w0 * adrc-w0 * fe1); }当然上面这段是示意实际工程里要根据控制量的正负方向来写判断逻辑。这个细节调试时经常被忽略但在实际系统上却非常关键。最后再分享一个小经验ADRC这个算法别看公式里面全是非线性函数最核心的竞争力其实来自ESO对总扰动的实时估计。你在一阶对象上把ESO调通之后再去看二阶的、三阶的ADRC本质上都只是一个“多估一阶状态”的扩展。先把代码跑起来再逐步理解每个fa函数为什么这么取比死磕公式要有效得多。希望这一篇能帮你把ADRC从论文里的公式变成你自己手里能跑的C代码。本文还有配套的精品资源点击获取
返回列表