3. 电流环(一):PI控制器原理与数字实现

电流环,是电机控制的基石。你想想看,不管上层是速度环还是位置环,最终都要落到电流上。电流环调不好,上面再花哨的算法都是空中楼阁。

我个人习惯,拿到一个新项目,第一件事就是把电流环的带宽跑上去。带宽上去了,响应快了,后面的工作才好开展。今天我们就来聊聊电流环的核心——PI控制器,以及它在数字芯片里是怎么落地实现的。

3.1 为什么是PI,而不是P或PID?

先回答一个基础问题:为什么电流环几乎清一色用PI?

比例控制(P)简单直接,误差大就猛推,误差小就轻推。但纯P有个硬伤——静差。比如你目标电流10A,P控制器可能跑到9.5A就停了,因为再往上推,误差变小,输出也变小,永远差那么一点。这在电机控制里是不能接受的,电流不准,转矩就不准。

积分控制(I)就是来解决这个问题的。它把过去的误差累积起来,只要有静差,积分项就会一直增长,直到把误差彻底吃掉。

那为什么不用微分(D)?

微分对噪声太敏感了。电流采样本身就有高频噪声,微分一作用,控制器输出会剧烈抖动。我在项目中遇到过,加了D之后,电流波形像锯齿一样,MOS管发热严重。所以电流环里,PI是主流,D基本不用。

核心结论: P保证响应速度,I保证稳态精度。两者配合,电流环才能又快又准。

3.2 PI控制器的连续域模型

我们先从数学上看看PI长什么样。在连续域(s域)里,PI控制器的传递函数是:

G(s) = Kp + Ki / s

或者写成更常见的形式:

G(s) = Kp * (1 + 1 / (Ti * s))

其中:

  • Kp:比例增益,决定响应速度
  • Ki:积分增益,决定静差消除能力
  • Ti:积分时间常数,Ti = Kp / Ki

时域表达式更直观:

u(t) = Kp * e(t) + Ki * ∫ e(τ) dτ

说白了,输出 = 比例项 + 积分项。比例项看当下,积分项看历史。

3.3 从连续到离散:数字实现的关键

我们的MCU/DSP是数字系统,只能处理离散的采样点。所以要把连续域的PI变成离散域的差分方程。

常用的离散化方法有三种:

方法 原理 特点
前向欧拉 s = (z - 1) / Ts 简单,但可能不稳定
后向欧拉 s = (z - 1) / (z * Ts) 稳定,常用
双线性变换(Tustin) s = (2/Ts) * (z-1)/(z+1) 精度高,计算稍复杂

我个人习惯用后向欧拉。原因很简单:实现简单,数值稳定性好,对于电流环这种采样率(10kHz~20kHz)来说,精度完全够用。

3.4 位置式PI vs 增量式PI

离散化之后,PI有两种实现形式:位置式和增量式。

位置式PI:直接计算完整的输出值。

u(k) = Kp * e(k) + Ki * Ts * Σ e(i)

这种写法,积分项是累加和。问题在于:一旦饱和,积分项会一直累加,导致积分饱和。退出饱和时,输出要很久才能降下来,系统会 overshoot。

增量式PI:计算输出相对于上一次的变化量。

Δu(k) = Kp * [e(k) - e(k-1)] + Ki * Ts * e(k)
u(k) = u(k-1) + Δu(k)

增量式的优势很明显:

  • 积分项不会无限累加,抗饱和能力强
  • 输出变化平滑,不会突变
  • 容易实现手动/自动切换
我的建议: 电机控制电流环,优先用增量式PI。位置式PI虽然直观,但积分饱和处理起来很麻烦。增量式天然抗饱和,省心不少。

3.5 抗积分饱和:一个必须处理的问题

即使用了增量式PI,在某些极端情况下(比如启动时大电流冲击),积分项仍然可能过大。所以,抗积分饱和是必须的。

常用的方法有两种:

  1. 积分限幅:给积分项设置上下限,超过就截断。
  2. 积分冻结:当输出饱和时,停止积分更新。

我曾经在一个项目中,只做了输出限幅,没做积分限幅。结果电机启动时,积分项累积到几万,输出一直饱和,电机像疯了一样抖动。后来加了积分限幅,问题立刻解决。

下面是一个带抗饱和的增量式PI实现:

// 增量式PI,带积分限幅
typedef struct {
    float Kp;
    float Ki;
    float Ts;       // 采样周期
    float integral; // 积分项
    float out_max;  // 输出上限
    float out_min;  // 输出下限
    float err_prev; // 上一次误差
} PI_Controller;

float PI_Update(PI_Controller *pi, float ref, float feedback) {
    float err = ref - feedback;
    float delta_u = pi->Kp * (err - pi->err_prev) + pi->Ki * pi->Ts * err;
    
    // 更新积分项(带限幅)
    pi->integral += pi->Ki * pi->Ts * err;
    if (pi->integral > pi->out_max) pi->integral = pi->out_max;
    if (pi->integral < pi->out_min) pi->integral = pi->out_min;
    
    // 计算输出
    float u = pi->Kp * err + pi->integral;
    
    // 输出限幅
    if (u > pi->out_max) u = pi->out_max;
    if (u < pi->out_min) u = pi->out_min;
    
    pi->err_prev = err;
    return u;
}
注意: 积分限幅的阈值不要设得太宽。一般设为输出限幅的80%~90%即可。太宽了抗饱和效果差,太窄了影响稳态精度。

3.6 电流环PI的知识体系

下面这张图,把电流环PI的核心逻辑串起来了。从连续域到离散域,从位置式到增量式,再到抗饱和处理,每一步都有对应的工程考量。

电流环PI控制器知识体系 连续域PI模型 G(s) = Kp + Ki/s 离散化方法选择 前向欧拉(不推荐) 后向欧拉(推荐) 双线性变换 实现形式对比 位置式PI(需抗饱和处理) 增量式PI(天然抗饱和) 工程实现要点 积分限幅 | 输出限幅 | 采样周期匹配 目标:高带宽 + 无静差 + 抗饱和 理论起点 连续域分析 离散化 关键步骤 实现 形式选择 工程落地 避坑指南

3.7 采样周期与PI参数的关系

最后聊一个容易被忽略的点:采样周期Ts。

很多新手调PI,只调Kp和Ki,忘了Ts变了之后,实际效果会完全不同。你看上面的代码,Ki前面乘了Ts。如果Ts从100us改成50us,同样的Ki值,积分作用会减半。

所以,我建议把PI参数和采样周期解耦。怎么做?用归一化参数:

// 归一化PI参数
// 用户只调 Kp 和 Ki_norm(归一化积分增益)
// 内部自动乘以 Ts
float Ki_actual = Ki_norm * pi->Ts;

这样,不管采样率怎么变,同样的Ki_norm值,积分效果是一样的。我在多个项目中用这个方式,参数移植非常方便。

小技巧: 调试时,先把Ki设为0,只调Kp让系统不振荡。然后慢慢加Ki,直到静差消失。这样一步步来,不容易出问题。