← 返回文章库

PID 控制入门:机器人控制的地基,一篇讲透 P、I、D 怎么调

最后更新 2026-06-20
⏱ 约 16 分钟 🟢 软件/低风险
你将学到
  • 理解开环为什么不靠谱、闭环靠误差反馈纠正的核心思路
  • 逐个吃透 P、I、D 三项各自管什么,太大太小分别会带来什么毛病
  • 拿到一段带积分限幅和输出限幅的通用 PID 类,能直接套到电机转速或循迹纠偏上
  • 掌握先P后D再I的调参手法,会用"症状→调哪个参数"对照表救场

你写了个差速小车,让它直走。两个轮子给一样的 PWM,照理该笔直往前。可一松手,它就慢慢往左偏——左边电机摩擦大一点、电池压降一点、地面糙一点,任何一点点不对称,跑出去十米就歪到墙角了。再比如你想让电机稳定在 100 转/秒,空转时给的油门刚好,可一加负载,转速啪地掉到 70,你又得手忙脚乱往上加油门。

这两件事的本质是同一个:你拍脑袋给了一个输出,却没人盯着实际结果对不对。这就是开环。而修好它的办法,几乎整个机器人控制圈都在用同一招——PID。平衡车不倒、3D 打印喷头恒温、无人机悬停、电机精准定速,底层全是它。这一篇,我用人话把 PID 讲透,给你一段能直接跑的代码,再教你怎么调出手感。读完不用你会推公式,但你得能上手把一个抖来抖去的系统调稳。

💡 提示

这篇是纯控制思想,不挑硬件。你只要之前玩过 电机驱动PWM 调速,知道"给个数字能控制输出大小"就够上手了。

为什么必须闭环

开环就是:我给一个输出,然后祈祷。给电机 60% 油门,期望它转 100 转/秒。可现实里,"60% 油门对应多少转速"这件事根本不固定——电池满电时转 110,快没电时转 85;空载转 100,搬了重物转 70。你给的那个数字,只在某一种特定工况下才对。

闭环是另一套思路:我不直接定输出,我定目标,然后让系统自己去逼近目标。具体怎么逼近?靠一个最朴素的量——误差。

误差 error = 目标值 target − 实际测量值 measured

转速目标 100,实测 70,误差就是 +30,说明"还差得远,使劲加油门"。等转到 98,误差只剩 +2,"快到了,轻轻补一点就行"。转到 105 冲过头了,误差变成 −5,"过了,松点油门往回收"。

你看,有了误差这个反馈,控制器就有了眼睛——它时刻知道自己离目标还差多少,然后据此决定输出。PID 干的全部事情,就是把误差换算成一个合适的输出。怎么换算?就靠 P、I、D 这三项,各管一摊。

P:误差越大,纠正越猛

P 是比例(Proportional),也是三项里最直觉的:输出和当前误差成正比。

P 项 = Kp × error

误差大,就猛纠;误差小,就轻纠;误差为零,这项就不出力。差 30 转时狠加油门,差 2 转时轻轻补,完全符合直觉。光一个 P,很多场合就能把系统拉到目标附近。

但 P 有两个天生的毛病,你必须知道:

  • Kp 太大会震荡。纠正太猛,冲过头;冲过头后误差反向,又猛纠回来,又冲过头……系统就在目标值两边来回甩,转速忽高忽低,小车循迹时左右画龙。
  • Kp 太小到不了位,而且单靠 P 往往留个尾巴。比如平衡车有点前倾,P 算出一点点纠正力,可这点力刚好被重力抵消,车就斜着不倒也不正,稳定地差那么一点点——这个甩不掉的残余误差,叫稳态误差。P 自己治不了它,因为误差小到一定程度,Kp×error 这点输出就推不动了。

稳态误差这根尾巴,正是下一项 I 要来收拾的。

I:把长期的偏差一点点磨平

I 是积分(Integral)。积分听着吓人,其实就一句话:把历史上每一时刻的误差累加起来

积分累加 integral += error × dt
I 项 = Ki × integral

它治稳态误差的逻辑很妙:只要误差一直没归零,哪怕只差一点点,这点误差就会一直往 integral 里累加,越攒越多,I 项的输出就越来越大,直到大到足够把那根尾巴顶过去、误差真正归零为止。P 治"现在差多少",I 治"我已经差了多久了"。

但 I 也有它的脾气:

  • Ki 太大会超调,还会让系统变迟钝。误差累积是有惯性的——等系统好不容易到位了,之前攒下的一大坨 integral 还在使劲推,于是冲过头;冲过头后又得反向累积把它泄掉,来回拖泥带水。
  • 积分饱和(windup)是个隐藏大坑。假设小车被你用手按住没法动,误差一直在,integral 就疯狂往上累,累到天文数字。等你一松手,这坨巨大的 integral 会让输出爆表,系统狂冲老半天才反应过来。所以代码里必须给 integral 限幅,这是新手最容易漏的一步,下面代码我会标出来。

D:踩刹车,预判着抑制震荡

D 是微分(Derivative)。微分关心的不是误差本身,而是误差变化的快慢

误差变化率 = (error − 上一次的 error) / dt
D 项 = Kd × 误差变化率

它的作用是踩刹车、做预判。当系统正快速冲向目标时,误差在飞快地缩小,误差变化率是个大负数,D 项就提前往反方向出力,"别冲太猛,要到了,先收着点"。所以 D 专门用来压 P 带来的震荡和超调,让系统稳稳停住而不是甩过去。

D 的代价:

  • 它对噪声极度敏感。微分是算"相邻两次的变化",可传感器读数总有抖动,这点抖动一做微分就被放大成尖刺,D 项跟着乱跳,输出嗡嗡发抖。所以传感器噪声大时,Kd 要给得保守,或者先对测量值做个滤波。

一段能直接跑的 PID 类

讲了这么多,上代码。这是一个通用 PID 类,Arduino / ESP32 / 纯 C++ 都能用,关键的输出限幅积分限幅防饱和都做了——这两个限幅不是可选项,是必需品。

class PID {
public:
  float Kp, Ki, Kd;
  float outMin, outMax;        // 输出限幅,比如 PWM 的 0~255
  float integralMax;           // 积分限幅,防 windup

  float integral = 0;
  float prevError = 0;
  bool  firstRun  = true;

  PID(float kp, float ki, float kd,
      float omin, float omax, float imax)
    : Kp(kp), Ki(ki), Kd(kd),
      outMin(omin), outMax(omax), integralMax(imax) {}

  // target=目标值, measured=实测值, dt=距上次的秒数
  float compute(float target, float measured, float dt) {
    float error = target - measured;

    // ---- P ----
    float pTerm = Kp * error;

    // ---- I ----(带积分限幅,防饱和)
    integral += error * dt;
    if (integral >  integralMax) integral =  integralMax;
    if (integral < -integralMax) integral = -integralMax;
    float iTerm = Ki * integral;

    // ---- D ----(第一次没有上次误差,跳过,避免开机冲一下)
    float dTerm = 0;
    if (!firstRun) {
      dTerm = Kd * (error - prevError) / dt;
    }
    prevError = error;
    firstRun  = false;

    // ---- 求和 + 输出限幅 ----
    float output = pTerm + iTerm + dTerm;
    if (output > outMax) output = outMax;
    if (output < outMin) output = outMin;
    return output;
  }

  void reset() {            // 切换目标或重启时清状态
    integral  = 0;
    prevError = 0;
    firstRun  = true;
  }
};

怎么用?拿电机转速闭环举例,骨架长这样:

// 目标 100 转/秒,输出是 0~255 的 PWM,积分上限给 200
PID speedPID(2.0, 0.5, 0.1, 0, 255, 200);
unsigned long lastMs = 0;

void loop() {
  unsigned long now = millis();
  float dt = (now - lastMs) / 1000.0;   // 秒
  if (dt < 0.01) return;                // 控制周期约 10ms
  lastMs = now;

  float rpm = readEncoderSpeed();       // 编码器测当前转速
  float pwm = speedPID.compute(100.0, rpm, dt);
  analogWrite(MOTOR_PWM_PIN, (int)pwm); // 把 PID 输出喂给电机
}

循迹纠偏也是一个模子:目标是"车在线正中间"(误差=0),measured 换成传感器算出的偏移量,compute 出来的值拿去做两轮差速。换汤不换药,PID 类一行都不用改——这正是它好用的地方。运动那头可以接 差速底盘循迹小车 里现成的函数。

你应该看到什么

调对参数后,把转速实测值打到串口画图,你该看到这样一条曲线:开机时从 0 快速爬升,接近 100 时减速,小小冲过一点点(轻微超调),然后几个来回就稳稳收敛到 100 这条线上,之后哪怕你上手加点负载,转速掉一下又被自动顶回 100。

没调好的样子也很好认:要么死活到不了 100(P 太小或没用 I),要么在 100 上下疯狂蹦迪(P 太大),要么冲到 130 才慢慢晃回来(I 太大超调)。下面这张表就是教你看到症状后该拧哪个旋钮。

调参手感:先 P,后 D,再 I

新手最大的痛苦不是看不懂 PID,是三个参数一起调,改一个动全身,彻底懵。给你一套经典手法,一个一个来:

  1. 先把 Ki、Kd 设成 0,只调 Kp。从小往大加,直到系统响应够快、开始在目标值附近轻微震荡——这说明 P 到位了,然后稍微回调一点,留点余量。
  2. 再加 Kd 压震荡。慢慢往上加 Kd,你会看到刚才的来回甩被一点点摁住,系统变得干脆利落。加到震荡基本消失就停,加太多反而会因为噪声让输出发抖。
  3. 最后,如果还差那么一点到不了位(有稳态误差),再小心加一点 Ki。Ki 要给得很保守,小步加,一边加一边看有没有超调变严重。没有稳态误差的话,这步直接跳过

业界还有个叫 Ziegler-Nichols 的经验整定法,思路是把 Kd、Ki 关掉、猛加 Kp 直到系统持续等幅震荡,记下临界增益和震荡周期,再套公式算三个参数。它能给你一组起手值,但出来的响应通常偏激进、超调大,基本都得再手调,知道有这么个东西即可,别迷信。

敢说个观点:新手 90% 的场景,PD 就够了,I 慎用。 转速闭环、循迹、平衡车这些,有 P 提供主纠正力、D 压住震荡,大多已经够稳。I 是把双刃剑——它能磨掉稳态误差,但积分饱和、超调、迟钝这些坑全是它带来的,新手十有八九栽在 windup 上。除非你明确看到一根甩不掉的稳态误差尾巴,否则先别碰 I,把 PD 调好再说。

症状 → 调哪个参数

症状 大概率原因 怎么调
在目标值两边来回震荡、画龙 Kp 太大 调小 Kp,或加大 Kd
冲过头老半天才晃回来(超调大) Ki 太大 或 Kd 太小 减小 Ki,加大 Kd
死活差一点到不了目标(稳态误差) 没用 I 或 Ki 太小 小步加一点 Ki
响应太慢、半天才到位 Kp 太小 加大 Kp
输出一直在抖、嗡嗡响 Kd 太大,放大了噪声 减小 Kd,或给测量值加滤波
被挡住后一松手就狂冲 积分饱和(windup) 检查 integralMax 限幅有没有生效

两个进阶变体

把基础 PID 玩熟后,有两个变体值得知道:

  • 增量式 PID。上面那种叫位置式,每次直接算出完整输出。增量式则每次只算"这次比上次多给/少给多少",输出靠累加。它的好处是天然不容易积分饱和、切换控制模式时不会突然跳变,在很多工业电机控制器里更常用。
  • 串级 PID。两个 PID 套娃:外环管"位置/角度",它的输出当作内环"速度"的目标值,内环再去控电机。平衡车的标准做法就是角度环套速度环,响应又快又稳。等你做平衡车做到瓶颈了,就会自然走到这一步。

这俩现在不用急着上手,知道遇到瓶颈往哪个方向找答案就行。

动手挑战

光看不练,手感是练不出来的。给自己一个任务:用编码器电机做一个转速闭环,把它稳定锁在某个转速上

  1. 接好一个带编码器的电机,写好读转速的函数(测每个控制周期编码器走了多少脉冲)。
  2. 套上面的 PID 类,先只给 Kp,Ki=Kd=0,从 1.0 开始往上加,观察转速曲线开始震荡的那个临界点。
  3. 加 Kd 把震荡压下去,体会"加 Kd 系统变干脆"是什么感觉。
  4. 故意用手捏一下转轴加负载,看转速掉下去后能不能自动顶回来——这一刻你就真懂闭环了。
  5. 如果发现转速总差一点到不了设定值,再小心加一丢丢 Ki,看那根尾巴是怎么被磨掉的。

把转速实测值用串口画图工具画出来,对着上面那张症状表调,半小时你就有手感了。

小结

PID 不神秘,核心就一句话:用目标和实际的误差,换算出一个合适的输出,让系统自己逼近目标。P 管"现在差多少、猛纠"、I 管"差了多久、磨掉稳态误差"、D 管"踩刹车、压震荡"。代码里输出限幅和积分限幅一个都不能省。调参就记住先 P 后 D 再 I、新手 PD 优先、I 慎用,再配上那张症状对照表,你已经能搞定绝大多数闭环了。

下一步,去做 PID 最经典也最过瘾的实战——平衡小车自平衡。一根用 PID 撑起来的"倒立摆",会把你今天学的每一项都逼到极致,调通它的那一刻,你对 PID 的理解会上一个台阶。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明