PID 控制算法 第一篇(基础篇)

PID 控制算法基础:从原理到嵌入式实现

PID 控制是自动控制领域中应用最广泛的算法,没有之一。从工业电机调速到家用空调温控,从无人机姿态稳定到 3D 打印机的温度管理,PID 的身影无处不在。本文是 PID 系列的第一篇(基础篇),将从零开始讲解 PID 的核心原理、离散化方法、代码实现和参数整定技巧。如果你已经掌握基础,可以直接阅读第二篇进阶篇,深入了解增量式 PID、抗积分饱和、串级控制等高级主题。


一、PID 是什么?

PIDProportional-Integral-Derivative(比例-积分-微分)的缩写,是一种基于反馈的闭环控制算法。它的核心思想非常直观:根据目标值与实际输出值之间的误差,通过 P、I、D 三个环节协同计算出一个控制量,驱动被控对象朝目标值靠近。

1.1 典型应用场景

应用领域 被控量 控制量
直流电机调速 转速(RPM) PWM 占空比
温度控制 温度(°C) 加热器功率
压力控制 管道压力(kPa) 阀门开度
伺服系统 位置 / 角度 电流指令
无人机姿态 俯仰 / 横滚角 电机转速差

1.2 闭环控制框图

        误差 e(t)        控制量 u(t)        实际输出 y(t)
 r(t) ──→ ⊕ ──→ [ PID 控制器 ] ──→ [ 执行器 + 被控对象 ] ──→──┬──→ y(t)
          ↑ -                                                   │
          └────────────── [ 传感器(反馈) ] ◄──────────────────┘

整个控制过程形成一个闭环:传感器不断测量实际输出,PID 控制器根据误差计算控制量,执行器根据控制量作用于被控对象,循环往复直到误差趋近于零。

关键认知:PID 不直接控制目标状态本身(如温度、转速),而是计算"应该施加多大的控制作用"。例如温控场景中,PID 输出的不是目标温度,而是加热器应该输出多大功率。

二、控制量 u(t) / u[k] 的物理意义

理解 PID 的关键,是搞清楚控制量 u(t) 到底代表什么。

2.1 连续 vs 离散

  • 连续形式 u(t):理论分析中使用,时间 t 连续变化
  • 离散形式 u\lbrack k\rbrack:嵌入式实现中使用,每隔采样周期 T 计算一次

2.2 控制量在不同场景中的物理含义

应用场景 控制量 u 的含义 取值范围示例 作用方式
直流电机调速 PWM 占空比 0% ~ 100% 占空比越大,电机转速越高
温度控制 加热功率(或 PWM) 0 ~ 255(8-bit) 功率越大,升温越快
压力控制 阀门开度 0° ~ 90° 开度越大,流量越大
伺服系统 电流指令 -10A ~ +10A 电流驱动电机产生力矩

2.3 信号流向

 目标值 r(t)                                     实际输出 y(t)
    │                                                 ↑
    ▼                                                 │
  误差 e(t) = r(t) - y(t)                            │
    │                                                 │
    ▼                                                 │
  ┌────────────┐    控制量 u(t)    ┌──────────────┐   │
  │ PID 控制器  │ ───────────────→ │ 执行器 + 对象  │ ──┘
  └────────────┘                  └──────────────┘

2.4 变量定义

符号 名称 含义
r(t) 目标值 / 设定值(Setpoint) 期望的输出值
y(t) 实际输出(Process Variable) 传感器测量到的当前值
e(t) 误差(Error) e(t) = r(t) - y(t)
u(t) 控制量(Control Output) PID 计算得到的输出值

2.5 温控实例 —— 变量对应表

假设我们要将水温控制在 50°C

通用符号 工程术语 本例中含义 当前值示例
r(t) SP(Setpoint) 目标温度 50°C
y(t) PV(Process Variable) 当前水温 42°C
e(t) Error 温度偏差 +8°C
u(t) CO(Control Output) 加热器 PWM 占空比 72%

物理因果链

PID输出 u(t)=72% → PWM占空比72% → 加热器功率↑ → 热量输入↑ → 水温上升 → y(t)→50°C
理解要点:控制量 u(t) 是 PID 唯一的"输出产品"。它不是目标值本身,而是实现目标的"力度"。这个力度通过执行器转化为物理作用,最终改变被控对象的状态。

三、P、I、D 三环节详解

3.1 误差的定义

e(t) = r(t) - y(t)

误差为正表示实际值低于目标值(不足),误差为负表示实际值超过目标值(超调)。

3.2 连续 PID 公式

u(t) = K_p \, e(t) + K_i \int_0^t e(\tau) \, d\tau + K_d \, \frac{de(t)}{dt}

三项分别对应:比例项(P)、积分项(I)、微分项(D)。

3.3 三环节对比

环节 公式 作用 直观理解 缺点
P(比例) K_p \, e(t) 按误差大小比例输出控制量 "踩油门力度正比于偏差"——偏差大就猛踩,偏差小就轻踩 存在稳态误差,无法完全消除偏差
I(积分) K_i \int_0^t e(\tau) d\tau 累积历史误差,消除稳态误差 "记住过去所有偏差,慢慢补偿"——即使当前误差很小,只要累积不为零就持续输出 容易引起超调和振荡,积分饱和问题
D(微分) K_d \frac{de(t)}{dt} 预测误差变化趋势,抑制超调 "预判趋势,提前刹车"——误差快速减小时主动减小控制量 对噪声极其敏感,高频噪声被放大
记忆口诀P 管现在,I 管过去,D 管未来。P 看当前误差有多大;I 看过去累积了多少误差;D 看误差正在往哪个方向变化。三者协同,构成完整的"过去-现在-未来"时间维度控制。

3.4 参数增减对系统的影响

参数变化 响应速度 超调量 稳态误差 稳定性
K_p 增大 加快 增大 减小(但不消除) 降低
K_p 减小 减慢 减小 增大 提高
K_i 增大 略加快 明显增大 消除更快 降低
K_i 减小 略减慢 减小 消除更慢 提高
K_d 增大 略减慢 明显减小 几乎无影响 提高(过大则降低)
K_d 减小 略加快 增大 几乎无影响 降低

四、从连续到离散 —— 嵌入式实现基础

4.1 为什么要离散化?

在嵌入式系统中,传感器以固定周期采样,控制器以固定周期运算。我们无法处理连续时间信号,必须将连续 PID 公式转化为离散形式,让单片机能够"一步一步"地计算。

4.2 离散化基本概念

  • 采样周期 T:两次计算之间的时间间隔(如 10ms)
  • 第 k 次采样:时刻 t = kT 时的采样值
  • 离散误差e\lbrack k\rbrack = r\lbrack k\rbrack - y\lbrack k\rbrack

4.3 离散化近似

连续运算 离散近似方法 离散公式
积分 \int e \, dt 矩形累加法 \sum_{i=0}^{k} e\lbrack i\rbrack \cdot T
微分 \frac{de}{dt} 后向差分法 \frac{e\lbrack k\rbrack - e\lbrack k-1\rbrack}{T}

4.4 离散 PID 公式(位置式)

u\lbrack k\rbrack = K_p \, e\lbrack k\rbrack + K_i \sum_{i=0}^{k} e\lbrack i\rbrack \cdot T + K_d \, \frac{e\lbrack k\rbrack - e\lbrack k-1\rbrack}{T}

展开来看各项的含义:

  • 比例项K_p \, e\lbrack k\rbrack —— 当前误差乘以比例系数
  • 积分项K_i \sum_{i=0}^{k} e\lbrack i\rbrack \cdot T —— 所有历史误差的累加和乘以采样周期
  • 微分项K_d \, \frac{e\lbrack k\rbrack - e\lbrack k-1\rbrack}{T} —— 当前误差与上一次误差的差值除以采样周期
重要提示:积分用累加近似,微分用差分近似。这两个近似的精度取决于采样周期 T——T 越小,近似越准确,但计算负担越大。实际工程中,采样周期 T 必须严格固定,否则积分和微分的计算都会出错。

4.5 两种实现形式

离散 PID 在代码层面有两种经典实现方式:

形式 特点 适用场景
位置式 PID 直接输出控制量的绝对值 舵机位置控制、阀门开度控制
增量式 PID 输出控制量的增量 \Delta u 电机调速、步进电机控制

本篇先讲解位置式 PID 的实现,增量式 PID、抗积分饱和、微分滤波等进阶内容将在第二篇(进阶篇)中详细展开。


五、代码实现

5.1 C 语言实现(位置式 PID)

以下是一个适用于嵌入式平台的完整 PID 控制器实现:

/* ===== PID 控制器 - C 语言实现 ===== */

typedef struct {
    float Kp, Ki, Kd;        // PID 三个增益参数
    float setpoint;           // 目标值 r(t)
    float integral;           // 积分累积项
    float prev_error;         // 上一次误差(用于微分计算)
    float output_max;         // 输出上限(防止执行器饱和)
    float output_min;         // 输出下限
} PID_t;

/**
 * @brief  初始化 PID 控制器
 * @param  pid       PID 结构体指针
 * @param  kp/ki/kd  三个增益参数
 * @param  setpoint  目标值
 */
void PID_Init(PID_t *pid, float kp, float ki, float kd, float setpoint) {
    pid->Kp = kp;
    pid->Ki = ki;
    pid->Kd = kd;
    pid->setpoint = setpoint;
    pid->integral = 0.0f;
    pid->prev_error = 0.0f;
    pid->output_max = 100.0f;   // 默认输出范围 0~100
    pid->output_min = 0.0f;
}

/**
 * @brief  PID 计算(每个采样周期调用一次)
 * @param  pid       PID 结构体指针
 * @param  feedback  当前反馈值 y[k](传感器测量值)
 * @param  dt        采样周期 T(单位:秒)
 * @return 控制量 u[k]
 */
float PID_Compute(PID_t *pid, float feedback, float dt) {
    // 1. 计算误差
    float error = pid->setpoint - feedback;

    // 2. 积分累加(矩形近似)
    pid->integral += error * dt;

    // 3. 微分计算(后向差分)
    float derivative = (error - pid->prev_error) / dt;

    // 4. 合成 PID 输出
    float output = pid->Kp * error
                 + pid->Ki * pid->integral
                 + pid->Kd * derivative;

    // 5. 保存本次误差供下次微分使用
    pid->prev_error = error;

    // 6. 输出限幅(保护执行器)
    if (output > pid->output_max) output = pid->output_max;
    if (output < pid->output_min) output = pid->output_min;

    return output;
}
代码要点
1. integral 变量在结构体中持续累加,体现了积分的"记忆"特性
2. prev_error 保存上次误差,用于差分近似微分
3. 输出限幅是必须的——不限幅可能烧毁执行器或导致积分饱和

5.2 Python 实现

class PID:
    """
    PID 控制器:P管现在,I管过去,D管未来
    """
    def __init__(self, kp, ki, kd):
        self.kp = kp
        self.ki = ki
        self.kd = kd
        self.integral = 0.0
        self.prev_error = 0.0

    def compute(self, target, current, dt):
        """
        计算 PID 输出
        :param target:  目标值 r[k]
        :param current: 当前反馈值 y[k]
        :param dt:      采样周期 T(秒)
        :return:        控制量 u[k]
        """
        error = target - current              # 误差
        self.integral += error * dt            # 积分累加
        derivative = (error - self.prev_error) / dt  # 微分差分
        self.prev_error = error                # 保存误差

        return self.kp * error + self.ki * self.integral + self.kd * derivative

5.3 调用示例 —— 电机转速控制

# === 直流电机转速 PID 控制示例 ===
import time

pid = PID(kp=2.0, ki=0.5, kd=0.1)
target_rpm = 1000       # 目标转速
dt = 0.01               # 采样周期 10ms

for _ in range(500):
    current_rpm = read_encoder()               # 读取编码器测量转速
    pwm_duty = pid.compute(target_rpm, current_rpm, dt)

    pwm_duty = max(0, min(100, pwm_duty))      # 限幅 0~100%
    set_motor_pwm(pwm_duty)                     # 设置 PWM 输出

    time.sleep(dt)

六、实战:直流电机转速控制

6.1 控制目标

保持直流电机转速稳定在 1000 RPM,使用光电编码器测量实际转速,通过 PWM 驱动电机。

6.2 控制流程

┌─────────────────────────────────────────────────────────────────┐
│ 每隔 T=10ms 执行一次:                                          │
│                                                                 │
│  ① 读取编码器 → y[k] = 820 RPM                                 │
│  ② 计算误差   → e[k] = 1000 - 820 = 180 RPM                   │
│  ③ PID 计算   → u[k] = Kp·180 + Ki·Σe + Kd·Δe/T              │
│  ④ 输出限幅   → u[k] = clamp(u[k], 0, 100)                    │
│  ⑤ 设置 PWM   → PWM占空比 = u[k]%                              │
│                                                                 │
│  等待下一个采样周期...                                            │
└─────────────────────────────────────────────────────────────────┘

6.3 伪代码

PID_Init(&motor_pid, Kp=2.0, Ki=0.5, Kd=0.1, setpoint=1000)

while (系统运行) {
    current_rpm = Encoder_ReadRPM()          // 读取当前转速
    pwm = PID_Compute(&motor_pid, current_rpm, 0.01)  // 计算控制量
    Motor_SetPWM(pwm)                        // 输出到电机驱动
    Delay_ms(10)                             // 等待一个采样周期
}

6.4 响应过程分析

阶段 转速 y 误差 e P 项 I 项 D 项 控制量 u
启动初期 0 RPM +1000 大(猛加速) 快速累积 大(误差剧变) 接近上限
快速上升 600 RPM +400 中等 继续累积 负值(减速趋势) 中等偏高
接近目标 980 RPM +20 很小 缓慢累积 负值(抑制超调) 小幅调整
稳态运行 1000 RPM ≈0 ≈0 维持补偿 ≈0 稳定值
举一反三:温度控制的逻辑完全相同——只需将"编码器读转速"替换为"温度传感器读温度",将"设置 PWM 驱动电机"替换为"设置 PWM 驱动加热器"即可。PID 算法本身是与具体应用解耦的

七、参数整定方法

PID 参数整定是实际工程中最具挑战性的环节。好的参数能让系统快速、平稳地到达目标;差的参数会导致振荡、超调甚至系统失控。

7.1 常用整定方法对比

方法 原理 优点 缺点 适用场景
经验试凑法 手动调节,观察响应 简单直观,不需数学模型 依赖经验,耗时 简单系统、快速原型
Ziegler-Nichols 法 基于阶跃响应特征参数 有公式可循,较系统化 需要开环阶跃响应数据 可获取阶跃响应的系统
临界比例法 先找临界振荡点,再计算参数 不需要数学模型 需让系统处于临界振荡(有风险) 允许短暂振荡的系统
MATLAB 自动调参 软件自动优化 最精确,可视化调参 需要系统模型或实测数据 复杂系统、精度要求高

7.2 经验试凑法 —— 快速整定步骤

整定口诀:先 P 后 I 再 D

第一步:调 P
将 Ki=0、Kd=0,逐渐增大 Kp。观察系统响应:
- Kp 太小 → 响应慢,误差大
- Kp 合适 → 响应较快,有轻微振荡
- Kp 太大 → 剧烈振荡,系统不稳定
选择"刚好出现轻微振荡"的 Kp 值,然后减小 20%~30% 作为最终 Kp。

第二步:调 I
保持 Kp 不变,Kd=0,从小到大增加 Ki:
- Ki 太小 → 稳态误差消除很慢
- Ki 合适 → 稳态误差在可接受时间内消除,轻微超调
- Ki 太大 → 超调严重,持续振荡

第三步:调 D
保持 Kp、Ki 不变,从小到大增加 Kd:
- Kd 合适 → 超调减小,响应更平稳
- Kd 太大 → 系统变得"迟钝"或出现高频抖动

7.3 观察与诊断

现象 可能原因 调整方向
响应慢,长时间达不到目标 Kp 太小 增大 Kp
到达目标但有持续偏差 缺少积分项或 Ki 太小 增大 Ki
剧烈振荡,无法稳定 Kp 或 Ki 过大 减小 Kp 和 Ki
超调严重(冲过目标再回来) Ki 过大或缺少微分 减小 Ki,增大 Kd
高频抖动 / 输出不断跳变 Kd 过大或传感器噪声 减小 Kd,增加滤波
升温/升速很快但回不来 积分饱和 添加积分限幅(进阶篇讲解)

八、控制模式对比

实际工程中并不总是使用完整的 PID 三项,有时只用 P 或 PI 就够了。

控制模式 组成 优点 缺点 典型应用场景
P 控制 仅比例项 结构简单,响应快 存在稳态误差 对精度要求不高的场景,如粗略调速
PI 控制 比例 + 积分 消除稳态误差 可能超调,响应略慢 温度控制、流量控制等大多数工业场景
PD 控制 比例 + 微分 响应快,超调小 无法消除稳态误差 快速跟踪场景,如机器人关节
PID 控制 比例 + 积分 + 微分 综合性能最优 参数整定复杂 精度和动态性能都要求高的场景
如何选择?
- 如果允许一定偏差且系统简单 → 用 P 控制
- 如果必须消除稳态误差(大多数场景) → 至少用 PI 控制
- 如果超调敏感或需要快速响应 → 加上 D 项,用完整 PID
- 工业界 80% 以上的控制回路使用 PI 控制即可满足需求

九、基础问答

Q1:PID 三个参数各自的作用是什么?如何整定?

**P(比例)**:按当前误差比例输出控制量,Kp 越大响应越快但容易振荡。
**I(积分)**:累积历史误差,消除稳态偏差,Ki 越大消除越快但容易超调。
**D(微分)**:预测误差变化趋势,抑制超调和振荡,Kd 越大减振效果越强但对噪声敏感。

整定方法:经验试凑法"先 P 后 I 再 D"。先将 Ki=Kd=0 单独调 P 到轻微振荡后减小 20%~30%;再加 I 消除稳态误差;最后加 D 减小超调。也可以使用 Ziegler-Nichols 法或 MATLAB 自动调参。

Q2:PID 控制的本质是什么?u(t) 代表什么?

PID 控制的本质是**基于误差的反馈控制**——根据目标值与实际输出之间的偏差,计算应该施加多大的控制作用。

u(t) 是**控制量**,代表 PID 输出给执行器的指令。它不是目标值本身,而是实现目标的"力度"。例如在电机控制中 u(t) 是 PWM 占空比,在温控中 u(t) 是加热器功率。u(t) 通过执行器转化为物理作用,改变被控对象的状态。

Q3:离散化 PID 为什么需要固定采样周期?

离散 PID 中,积分使用累加近似(\sum e\lbrack i\rbrack \cdot T),微分使用差分近似(\frac{e\lbrack k\rbrack - e\lbrack k-1\rbrack}{T})。如果采样周期 T 不固定:

1. **积分失真**:每次累加的时间权重不同,积分值不准确
2. **微分失真**:相同的误差变化量在不同 T 下计算出不同的变化率
3. **参数失效**:Ki 和 Kd 本质上与 T 耦合,T 变化等于参数在变化

因此嵌入式实现中通常使用定时器中断确保 PID 在严格固定的周期内执行。

Q4:P 控制和 PI 控制的区别?什么时候必须加 I?

**P 控制**只有比例项,输出正比于当前误差。当系统存在外部扰动或负载时,P 控制会产生**稳态误差**——系统稳定后偏差不为零。这是因为一旦误差为零,P 项输出也为零,无法维持控制作用。

**PI 控制**增加了积分项。即使当前误差很小,只要累积误差不为零,积分项就会持续输出控制量,最终把稳态误差"积"到零。

**必须加 I 的场景**:
- 系统有恒定外部扰动(如电机带负载,需要持续力矩维持转速)
- 对稳态精度有要求(如温度必须精确控制在 ±0.5°C 以内)
- 被控对象本身不含积分环节(如大多数热系统、流量系统)

Q5:PID 有哪些局限性?什么场景下 PID 不适用?

PID 的局限性:
1. **依赖线性假设**:PID 本质是线性控制器,对强非线性系统效果差
2. **单输入单输出**:标准 PID 只处理一个误差、一个输出,多变量耦合系统需要更复杂的方案
3. **无模型预测能力**:PID 是"事后反应",不能提前预知扰动
4. **参数固定**:一组参数只在特定工作点附近最优,工况大范围变化时需要自适应或增益调度

不适用的场景:纯滞后很大的系统(如长管道温度控制)、强非线性系统(如化学反应釜)、多变量耦合系统。这些场景通常需要模型预测控制(MPC)、模糊控制或自适应控制等高级方法。


十、总结

本篇作为 PID 系列的基础篇,覆盖了从理论到实现的核心内容:

  • PID 的本质:基于误差的闭环反馈控制,通过 P(比例)、I(积分)、D(微分)三项协同工作
  • 控制量 u(t):PID 唯一的输出产品,是施加给执行器的"控制力度",不是目标值本身
  • 三环节分工:P 管现在(按误差比例响应)、I 管过去(累积消除稳态误差)、D 管未来(预判趋势抑制超调)
  • 离散化要点:积分用累加、微分用差分、采样周期必须固定
  • 代码实现:结构体封装参数,每个周期调用一次计算函数,注意输出限幅
  • 参数整定:先 P 后 I 再 D,根据响应现象诊断并调整
下篇预告:第二篇「PID 控制进阶篇」将深入讲解增量式 PID、积分饱和与抗饱和策略、微分滤波、不完全微分、串级 PID 控制、以及 STM32 实战案例。如果你已经理解了本篇的基础概念,就可以继续进入进阶篇了。

本文为 PID 控制系列第 1 篇(共 2 篇)。