导纳控制原理:导纳控制与阻抗控制的对偶关系、导纳控制器的离散化实现

各位工程师朋友,今天我们来聊聊导纳控制。说实话,这个知识点在机器人柔顺控制里,属于那种「看着简单,用起来容易翻车」的类型。我最早接触导纳控制是在做力控打磨项目的时候,当时被阻抗控制和导纳控制的关系绕得有点晕,后来才慢慢理清楚——它们俩其实就是一枚硬币的两面。

一、导纳控制与阻抗控制:一对天生的搭档

先说说这两个概念的本质区别。你想想看,阻抗控制描述的是「位置偏差产生力」——机器人碰到障碍物,位置被推偏了,就会产生一个反作用力。而导纳控制正好反过来,它是「力输入产生位置变化」——你推它,它就动。

用数学语言来说:

  • 阻抗控制:F = M·Δẍ + B·Δẋ + K·Δx(力是位置偏差的函数)
  • 导纳控制:Δx = G(s)·F(位置变化是力的函数)

说白了,阻抗控制是「硬碰硬」的思路,导纳控制是「以柔克刚」的思路。我在做机器人拖动示教的时候,就发现导纳控制特别适合这种场景——操作者施加力,机器人跟着走,感觉就像在推一个带阻尼的滑块。

核心理解:阻抗控制适用于机器人主动对环境施加力(比如打磨、装配),导纳控制适用于机器人被动响应外力(比如人机协作、拖动示教)。两者在频域上是互为倒数的关系——导纳 = 1 / 阻抗。

二、导纳控制器的数学模型

标准的导纳控制模型长这样:

M_d · (ẍ_c - ẍ_r) + B_d · (ẋ_c - ẋ_r) + K_d · (x_c - x_r) = F_ext

其中:

  • M_d、B_d、K_d 是期望的惯量、阻尼、刚度
  • x_c 是实际位置,x_r 是参考位置
  • F_ext 是外部施加的力

嗯,这里要注意:我们通常把参考轨迹设为零,只考虑外力引起的修正量。这样公式就简化成:

M_d · Δẍ + B_d · Δẋ + K_d · Δx = F_ext

这个二阶系统,说白了就是一个「质量-弹簧-阻尼」模型。你施加一个力,系统就会按照你设定的惯量、阻尼、刚度来响应。我习惯把这三个参数叫做「手感三要素」——调好了,机器人就像在黄油里滑动;调不好,要么太硬像撞墙,要么太软像在果冻里游泳。

三、导纳控制器的离散化实现

连续域的理论很漂亮,但到了嵌入式控制器里,我们得把它变成离散的。我个人习惯用双线性变换法(Tustin变换),因为它稳定性好,而且没有频率扭曲的问题。

离散化的步骤是这样的:

  1. 把连续域的传递函数写出来:G(s) = 1 / (M_d·s² + B_d·s + K_d)
  2. 用双线性变换 s = (2/T) · (z-1)/(z+1) 代入
  3. 整理成差分方程的形式

直接给代码吧,这是我项目里用过的实现:

// 导纳控制器离散化实现(C语言)
typedef struct {
    float M_d;   // 期望惯量
    float B_d;   // 期望阻尼
    float K_d;   // 期望刚度
    float T;     // 控制周期
    
    float x_prev;   // x(k-1)
    float x_prev2;  // x(k-2)
    float f_prev;   // f(k-1)
    float f_prev2;  // f(k-2)
    
    float a0, a1, a2;  // 分子系数
    float b1, b2;      // 分母系数
} AdmittanceController;

void Admittance_Init(AdmittanceController* ctrl) {
    float T = ctrl->T;
    float M = ctrl->M_d;
    float B = ctrl->B_d;
    float K = ctrl->K_d;
    
    // 双线性变换后的系数
    float den = 4*M + 2*B*T + K*T*T;
    ctrl->a0 = (T*T) / den;
    ctrl->a1 = (2*T*T) / den;
    ctrl->a2 = (T*T) / den;
    ctrl->b1 = (2*K*T*T - 8*M) / den;
    ctrl->b2 = (4*M - 2*B*T + K*T*T) / den;
}

float Admittance_Update(AdmittanceController* ctrl, float f_ext) {
    // 差分方程:x(k) = a0*f(k) + a1*f(k-1) + a2*f(k-2) - b1*x(k-1) - b2*x(k-2)
    float x = ctrl->a0 * f_ext 
            + ctrl->a1 * ctrl->f_prev 
            + ctrl->a2 * ctrl->f_prev2 
            - ctrl->b1 * ctrl->x_prev 
            - ctrl->b2 * ctrl->x_prev2;
    
    // 更新状态
    ctrl->f_prev2 = ctrl->f_prev;
    ctrl->f_prev = f_ext;
    ctrl->x_prev2 = ctrl->x_prev;
    ctrl->x_prev = x;
    
    return x;
}

避坑指南:我曾经在采样周期不匹配的情况下直接套用这个公式,结果系统震荡得一塌糊涂。后来发现,控制周期T必须和实际采样周期严格一致,差1ms都不行。建议用定时器中断来保证时序。

四、参数整定的实战经验

参数怎么调?我总结了一个「三步法」:

参数 作用 调大效果 调小效果
M_d(惯量) 决定响应速度 响应变慢,更平滑 响应变快,容易抖动
B_d(阻尼) 决定能量耗散 运动更「黏」,停止快 运动更「滑」,容易超调
K_d(刚度) 决定位置恢复力 像弹簧,回中力大 像软糖,回中力小

我的习惯是:先固定K_d为0(纯阻尼模式),调B_d让系统不震荡,再加M_d让响应变柔和,最后加一点点K_d防止漂移。你想想看,这就像调音响——先调音量,再调均衡,最后微调。

五、导纳控制与阻抗控制的对偶关系图解

下面这张图是我自己画的,帮你直观理解两者的关系:

导纳控制 vs 阻抗控制:对偶关系 阻抗控制 输入:位置偏差 Δx 输出:力 F 模型:F = Z(s) · Δx Z(s) = M·s² + B·s + K 应用场景: · 主动力控(打磨、装配) · 环境刚度已知 对偶 导纳 = 1 / 阻抗 导纳控制 输入:力 F 输出:位置修正 Δx 模型:Δx = Y(s) · F Y(s) = 1/(M·s²+B·s+K) 应用场景: · 被动柔顺(拖动示教) · 人机协作 选择依据:环境刚度高 → 用导纳控制;环境刚度低 → 用阻抗控制

注意:实际应用中,导纳控制和阻抗控制的选择不是绝对的。我见过有人把两者混合使用——内环用阻抗控制保证稳定性,外环用导纳控制实现柔顺。但这样做调试难度会翻倍,新手不建议尝试。

六、离散化实现的常见坑

最后说说我踩过的几个坑:

  • 数值溢出:当K_d接近0时,分母会变得很小,导致系数爆炸。我习惯给K_d设一个下限,比如0.1。
  • 初始状态:启动时力传感器有零偏,如果不做补偿,机器人会自己跑起来。记得做力传感器零点校准。
  • 积分饱和:虽然导纳控制器本身没有积分项,但位置修正量累积起来也会很大。我一般会加一个限幅,防止修正量超出安全范围。

嗯,导纳控制这块内容就讲到这里。说白了,它就是让机器人学会「顺势而为」——你推它,它让路;你不推,它停住。这个思路在协作机器人上特别实用,也是实现安全人机交互的基础。


公众号:蓝海资料掘金营,微信deep3321