3. 电流环(一):PI控制器原理与数字实现
电流环,是电机控制的基石。你想想看,不管上层是速度环还是位置环,最终都要落到电流上。电流环调不好,上面再花哨的算法都是空中楼阁。
我个人习惯,拿到一个新项目,第一件事就是把电流环的带宽跑上去。带宽上去了,响应快了,后面的工作才好开展。今天我们就来聊聊电流环的核心——PI控制器,以及它在数字芯片里是怎么落地实现的。
3.1 为什么是PI,而不是P或PID?
先回答一个基础问题:为什么电流环几乎清一色用PI?
比例控制(P)简单直接,误差大就猛推,误差小就轻推。但纯P有个硬伤——静差。比如你目标电流10A,P控制器可能跑到9.5A就停了,因为再往上推,误差变小,输出也变小,永远差那么一点。这在电机控制里是不能接受的,电流不准,转矩就不准。
积分控制(I)就是来解决这个问题的。它把过去的误差累积起来,只要有静差,积分项就会一直增长,直到把误差彻底吃掉。
那为什么不用微分(D)?
微分对噪声太敏感了。电流采样本身就有高频噪声,微分一作用,控制器输出会剧烈抖动。我在项目中遇到过,加了D之后,电流波形像锯齿一样,MOS管发热严重。所以电流环里,PI是主流,D基本不用。
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)
增量式的优势很明显:
- 积分项不会无限累加,抗饱和能力强
- 输出变化平滑,不会突变
- 容易实现手动/自动切换
3.5 抗积分饱和:一个必须处理的问题
即使用了增量式PI,在某些极端情况下(比如启动时大电流冲击),积分项仍然可能过大。所以,抗积分饱和是必须的。
常用的方法有两种:
- 积分限幅:给积分项设置上下限,超过就截断。
- 积分冻结:当输出饱和时,停止积分更新。
我曾经在一个项目中,只做了输出限幅,没做积分限幅。结果电机启动时,积分项累积到几万,输出一直饱和,电机像疯了一样抖动。后来加了积分限幅,问题立刻解决。
下面是一个带抗饱和的增量式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;
}
3.6 电流环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值,积分效果是一样的。我在多个项目中用这个方式,参数移植非常方便。