PID控制原理:比例、积分、微分的作用与离散化实现

聊到电机控制,PID 是绕不开的核心。说实话,我入行头两年,觉得 PID 就是调三个数,没什么技术含量。直到有一次做伺服驱动器,电机在低速时抖得像筛子,我才意识到——不懂原理,调参就是瞎蒙。

这一章,咱们把 PID 的底裤扒干净。比例、积分、微分到底在干什么?怎么从连续公式变成单片机里跑的代码?位置式和增量式有什么区别?

连续域下的 PID 表达式

先看最经典的连续形式:

u(t) = Kp * e(t) + Ki * ∫e(τ)dτ + Kd * de(t)/dt

三个部分各司其职。我习惯用一个比喻来理解:

  • 比例项——就像你开车时看到偏离车道,立刻打方向盘。反应快,但容易过头。
  • 积分项——方向盘打正了车还在偏?那就持续加力,直到完全回正。能消除静差,但容易超调。
  • 微分项——提前预判车的偏离趋势,在偏差变大之前就收油或补油。能抑制震荡,但对噪声敏感。

核心观点:比例决定响应速度,积分消除稳态误差,微分提供阻尼。三者配合,才能让电机又快又稳地到达目标位置。

比例作用:最直接的反馈

比例项的输出 = Kp × 当前偏差。偏差越大,输出越强。我在做云台电机时遇到过一个问题:Kp 调大了,电机响应快,但到位后总在目标值附近来回晃;Kp 调小了,晃是不晃了,但反应慢半拍。

为什么会这样?因为比例控制天生有静差。电机带负载时,需要一定的输出才能维持位置,而比例项只有在偏差存在时才有输出。这就导致——你永远到不了目标值,只能无限接近。

我的经验:纯比例控制适合对精度要求不高的场合,比如风机、水泵。但做伺服定位,必须加积分项。

积分作用:消除静差的利器

积分项累加过去的偏差。只要偏差存在,积分值就会一直增长,直到输出足够大,把电机推到目标位置。

但积分有个副作用——积分饱和。我在做电动工具项目时踩过这个坑:电机启动时偏差很大,积分项快速累积到上限。等电机到达目标位置时,积分值还卡在上限,导致输出迟迟降不下来,电机冲过头,来回震荡好几秒才稳定。

解决办法?限幅、积分分离、抗饱和算法。后面章节会细讲,这里先记住:积分不是越大越好。

微分作用:提前预判,抑制震荡

微分项看的是偏差的变化率。偏差变化越快,微分输出越大。说白了,它是在跟偏差的「趋势」作斗争。

我做过一个高速贴片机的运动控制,电机加减速时震荡特别严重。加了微分项之后,震荡明显被压住了。但微分对噪声极其敏感——传感器信号稍微有点毛刺,微分输出就会剧烈跳动。

注意:实际工程中,微分项通常要配合低通滤波使用。否则,高频噪声会被微分放大,反而让系统更不稳定。

PID 的离散化:从连续到数字

单片机不认识连续积分和微分。我们需要把公式变成离散的、可迭代的形式。

离散化的核心思路:

  • 积分 → 累加求和:∫e(τ)dτ ≈ Ts × Σe(k)
  • 微分 → 后向差分:de(t)/dt ≈ (e(k) - e(k-1)) / Ts

其中 Ts 是采样周期。采样周期怎么选?我一般遵循一个原则:采样频率至少是系统带宽的 10 倍。比如电机电流环带宽 1kHz,采样频率至少 10kHz。

离散后的位置式 PID 公式:

u(k) = Kp * e(k) + Ki * Ts * Σe(i) + Kd * (e(k) - e(k-1)) / Ts

为了方便,通常把 Ki 和 Kd 重新定义为离散域系数:

u(k) = Kp * e(k) + Ki_d * Σe(i) + Kd_d * (e(k) - e(k-1))

其中:
Ki_d = Ki * Ts
Kd_d = Kd / Ts

位置式 PID vs 增量式 PID

这是面试常考题,也是实际选型的关键。

对比项 位置式 PID 增量式 PID
输出含义 直接输出控制量(如占空比) 输出控制量的增量(Δu)
计算公式 u(k) = Kp·e(k) + Ki·Σe + Kd·Δe Δu(k) = u(k) - u(k-1)
积分累积 需要单独累加偏差 隐含在输出累加中
积分饱和 容易发生,需额外处理 不易发生,天然抗饱和
执行器切换 切换时输出可能突变 切换平滑,无冲击
适用场景 位置环、速度环 电流环、步进电机控制

增量式 PID 的推导其实很简单:

Δu(k) = Kp * (e(k) - e(k-1)) + Ki * e(k) + Kd * (e(k) - 2*e(k-1) + e(k-2))

你看,公式里没有累加项 Σe。这意味着什么?就算你把 Δu 算错了,下一拍就能纠正回来。而位置式一旦积分累积出错,要花很长时间才能消化掉。

我的建议:做电流环控制,优先用增量式。做位置环,位置式更直观。但无论哪种,都要做输出限幅——这是保命措施。

两种 PID 的代码实现对比

下面是我在实际项目中用过的代码骨架,去掉了一些工程细节,保留核心逻辑。

位置式 PID:

typedef struct {
    float Kp, Ki, Kd;
    float integral;
    float prev_error;
    float out_max, out_min;
} PositionPID;

float PositionPID_Update(PositionPID *pid, float setpoint, float feedback) {
    float error = setpoint - feedback;
    pid->integral += error;
    // 积分限幅
    if (pid->integral > pid->out_max) pid->integral = pid->out_max;
    if (pid->integral < pid->out_min) pid->integral = pid->out_min;

    float output = pid->Kp * error
                 + pid->Ki * pid->integral
                 + pid->Kd * (error - pid->prev_error);
    pid->prev_error = error;

    // 输出限幅
    if (output > pid->out_max) output = pid->out_max;
    if (output < pid->out_min) output = pid->out_min;
    return output;
}

增量式 PID:

typedef struct {
    float Kp, Ki, Kd;
    float error[3];  // e(k), e(k-1), e(k-2)
    float out_max, out_min;
} IncrementalPID;

float IncrementalPID_Update(IncrementalPID *pid, float setpoint, float feedback) {
    pid->error[2] = pid->error[1];
    pid->error[1] = pid->error[0];
    pid->error[0] = setpoint - feedback;

    float delta = pid->Kp * (pid->error[0] - pid->error[1])
                + pid->Ki * pid->error[0]
                + pid->Kd * (pid->error[0] - 2*pid->error[1] + pid->error[2]);

    // delta 累加到外部变量,不做内部积分
    return delta;
}

注意增量式的输出是 Δu,需要在外部累加:u += Δu。累加后的 u 再做限幅。

知识体系总览

下面这张图把本章的核心逻辑串起来了:

PID 控制原理与离散化知识体系 PID 控制器 比例 (P) — 快速响应 积分 (I) — 消除静差 微分 (D) — 抑制震荡 离散化:积分→累加,微分→差分 位置式 PID:u(k) = 三项和 增量式 PID:Δu(k) = 三项差 位置式特点 输出直接控制量,需处理积分饱和 适合位置环、速度环 增量式特点 输出增量,天然抗饱和 适合电流环、步进电机 选型原则:看执行器、看安全要求、看计算资源

避坑指南

最后分享几个我踩过的坑,希望能帮你省点时间:

  • 积分限幅一定要做。我曾经在位置式 PID 里忘了加积分限幅,电机启动时积分累积到几万,输出直接饱和,电机像疯了一样冲出去。嗯,那次差点把夹具撞坏。
  • 微分项慎用。如果传感器噪声大,宁可不加微分,也别加一个没滤波的微分。否则系统会高频抖动,听起来像电机在「唱歌」。
  • 采样周期要固定。离散化公式依赖固定的 Ts。如果采样周期不固定,PID 参数会「漂移」。我习惯用定时器中断来保证采样间隔一致。
  • 增量式也要做输出限幅。虽然增量式不容易积分饱和,但累加后的 u 如果不限幅,一样会溢出。

好了,这一章的内容就到这里。PID 的原理和离散化是后续调参的基础,理解透了,后面调 Kp、Ki、Kd 才能有的放矢。