7468 字
37 分钟
PID 算法从入门到工程实践 - 完整学习指南

1. 什么是 PID 控制#

1.1 自动控制的核心问题:设定值 vs 反馈值#

自动控制解决一个根本问题:让某个物理量(温度、速度、位置、压力……)维持在期望值

两个关键概念:

  • 设定值(Setpoint,SV):你期望系统达到的目标值,记作 r(t)r(t)
  • 反馈值(Process Variable,PV):传感器实测到的当前值,记作 y(t)y(t)

两者之差即是误差

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

控制器的工作就是根据 e(t)e(t) 计算出控制量(Manipulated Variable,MV) u(t)u(t),驱动执行机构(加热器、电机、阀门……)让 y(t)y(t) 趋近 r(t)r(t)

┌─────────┐ r(t) ┌──────────┐ u(t) ┌─────────┐ y(t)
设定 ──→│ 比较器 │──→ e(t) ──→│ PID控制器 │────→│ 执行器 │──→│ 被控对象 │──→ 输出
└─────────┘ └──────────┘ └─────────┘ └─────────┘
↑ │
└─────────────────── 传感器反馈 ─────────────────────────┘

1.2 PID 的直觉理解#

洗澡调水温

你站在淋浴下,手拧混水阀。水太冷 —— 左手往热水方向拧。太烫 —— 往冷水方向拧。拧动的幅度取决于”当前温度和期望温度的差距”。差距大,拧猛一点(比例 P)。

但光这样不行:调到 38°C 后,冷水管水压波动,温度掉到 36°C。2°C 的偏差太小,你拧一点,温度回到 37.8°C,差 0.2°C 就不再动了 —— 这就是稳态误差

你想”已经偏冷好一会儿了”,于是再多拧一点来补偿这持续存在的偏差 —— 这就是积分 I 在起作用:累积过去的误差,把残余偏差消灭。

水突然变烫,你预判温度还在往上冲,提前往冷水方向拧一下防止烫伤 —— 这就是微分 D:根据误差变化速率提前抑制,防止过冲。

场景对应项直觉
偏差越大,拧得越猛P(比例)看现在
偏差持续存在,越拧越多I(积分)看过去
温度快速上升,提前回调D(微分)看未来

定速巡航

设定 100 km/h。当前 80 km/h,误差 20。ECU 加大油门(P)。到 95 km/h 后误差变小,油门也变小,但最终可能稳定在 98 km/h,差 2 km/h 永远消不掉(稳态误差)。积分项感知到”有 2 km/h 偏差一直存在”,逐渐累积额外的油门补偿,最终推到 100 km/h。前方上坡,车速即将下降,微分项感知到误差变化速率,提前加油防止掉速。

1.3 适用系统与局限性#

PID 擅长的场景:

  • 线性时不变(LTI)系统:系统的动态特性不随时间变化,且输出与输入成比例叠加。
  • 低阶系统(一阶、二阶):温度、液位、压力、流量、速度等大多数工业过程可以用一阶惯性 + 纯滞后(FOPDT)描述,PID 处理得很好。
  • 有明确反馈信号:传感器能提供可靠、足够快的测量值。

PID 面临的挑战:

系统特性问题应对思路
大时滞(纯滞后时间长)控制作用要等很久才看到效果,P 和 D 会过度反应导致振荡Smith 预估器、串级控制
高阶系统(>3 阶)相位滞后大,PID 增益不能太高,响应慢降阶建模、串级 PID
强非线性增益在不同工作点差异巨大增益调度(Gain Scheduling)、模糊 PID、MPC
强耦合多变量一个输入影响多个输出解耦控制、MPC
对噪声极度敏感D 项放大高频噪声不完全微分、微分先行、舍弃 D 只用 PI

经验法则:工业控制中 90%+ 的闭环回路仍然使用 PID 或其变体。当你遇到一个控制问题时,先尝试 PI(多数场景),不够再加 D。


2. PID 的数学原理#

2.1 标准数学形式#

PID 控制器的连续时间表达式:

u(t)=Kp[e(t)+1Ti0te(τ)dτ+Tdde(t)dt]u(t) = K_p \left[ e(t) + \frac{1}{T_i} \int_0^t e(\tau) d\tau + T_d \frac{de(t)}{dt} \right]

或等效的平行形式(工程中更常用):

u(t)=Kpe(t)+Ki0te(τ)dτ+Kdde(t)dtu(t) = K_p e(t) + K_i \int_0^t e(\tau) d\tau + K_d \frac{de(t)}{dt}

其中:

符号名称单位
KpK_p比例增益无量纲(或控制量/误差量量纲)
KiK_i积分增益s1\text{s}^{-1}
KdK_d微分增益s\text{s}
Ti=Kp/KiT_i = K_p/K_i积分时间常数s\text{s}
Td=Kd/KpT_d = K_d/K_p微分时间常数s\text{s}

传递函数形式:

C(s)=U(s)E(s)=Kp(1+1Tis+Tds)=Kp+Kis+KdsC(s) = \frac{U(s)}{E(s)} = K_p \left(1 + \frac{1}{T_i s} + T_d s\right) = K_p + \frac{K_i}{s} + K_d s

频域视角能更清晰地看到:P 是全频段等比例放大,I 在低频段(稳态)贡献巨大增益,D 在高频段放大噪声。

2.2 三项各自的作用#

比例项(P):即时反应#

uP(t)=Kpe(t)u_P(t) = K_p \cdot e(t)

  • 物理意义:误差越大,控制量越大。像一根弹簧,拉力与伸长量成正比。
  • 单独使用的问题:对纯比例控制,系统稳定后必须有 u=Kpessu = K_p \cdot e_{ss} 维持控制量,这意味着 ess0e_{ss} \neq 0 ——稳态误差
  • KpK_p 增大:响应加快、稳态误差减小,但过大会引起振荡甚至不稳定。

为什么纯 P 控制存在稳态误差? 以加热器为例,环境在散热。系统稳定时,加热功率必须等于散热功率。纯比例控制下,要维持非零的加热功率,误差 e=u/Kpe = u/K_p 也必然非零。只有积分项能”记住”历史偏差,持续修正。

积分项(I):消除稳态误差#

uI(t)=Ki0te(τ)dτu_I(t) = K_i \int_0^t e(\tau) d\tau

  • 物理意义:只要误差存在,积分项就不断累积,直到误差归零。像水龙头慢慢拧:偏差一直存在,积分就一直在加力,直到偏差消失。
  • 副作用:积分累积有滞后性。当误差穿越零时,积分值可能已经累积过头,造成超调和振荡。
  • KiK_i 增大:稳态误差消除更快,但超调增大,振荡倾向增强。

微分项(D):预判未来#

uD(t)=Kdde(t)dtu_D(t) = K_d \frac{de(t)}{dt}

  • 物理意义:根据误差变化的斜率提前干预。误差快速增大时,D 施加反向力抑制变化;误差快速减小时,D 施加同向力防止回调过头。
  • 典型作用:增加系统阻尼,减小超调,缩短调节时间。
  • 致命弱点:对高频噪声敏感。白噪声经过微分后幅度随频率线性增长,可能放大到淹没有用信号的程度。

三项总结:

PID
作用时间现在过去未来
对稳态误差减小但不消除消除无直接影响
对稳定性KpK_p↑ → 稳定性↓KiK_i↑ → 稳定性↓KdK_d↑ → 稳定性↑(适度)
对响应速度加快可能减慢(取饱和方向)影响小
对噪声不敏感不敏感敏感
对超调增大增大减小

3. 离散化与计算机实现#

3.1 位置式 PID 与增量式 PID#

计算机只能处理离散采样值。设采样周期为 TT,第 kk 次采样时的误差为 e[k]e[k]

位置式 PID#

直接对连续公式离散化:

积分用矩形法近似:

0te(τ)dτTi=0ke[i]\int_0^t e(\tau) d\tau \approx T \sum_{i=0}^{k} e[i]

微分用后向差分近似:

de(t)dte[k]e[k1]T\frac{de(t)}{dt} \approx \frac{e[k] - e[k-1]}{T}

代入得:

u[k]=Kpe[k]+KiTi=0ke[i]+KdT(e[k]e[k1])u[k] = K_p e[k] + K_i T \sum_{i=0}^{k} e[i] + \frac{K_d}{T} (e[k] - e[k-1])

特点:输出的是控制量的绝对值 u[k]u[k]。每次计算需要累积所有历史误差。如果有积分饱和或手动切换,u[k]u[k] 可能跳变。

增量式 PID#

u[k]u[k] 求一阶差分 Δu[k]=u[k]u[k1]\Delta u[k] = u[k] - u[k-1]

Δu[k]=Kp(e[k]e[k1])+KiTe[k]+KdT(e[k]2e[k1]+e[k2])\Delta u[k] = K_p (e[k] - e[k-1]) + K_i T e[k] + \frac{K_d}{T} (e[k] - 2e[k-1] + e[k-2])

最终输出:

u[k]=u[k1]+Δu[k]u[k] = u[k-1] + \Delta u[k]

增量式在工程上更常用的原因:

  1. 天然抗饱和:只输出增量,不累积历史。可对 Δu[k]\Delta u[k] 限幅,实现简单的抗积分饱和。
  2. 无扰切换:手动切自动时,u[k]u[k] 从当前值平滑过渡,消除冲击(bumpless transfer)。
  3. 算量更小:只需保存最近三次误差 e[k],e[k1],e[k2]e[k], e[k-1], e[k-2],无需累加全部历史。
  4. 安全性好:控制器故障时输出保持最后值,不会跳变到危险状态。

注意:位置式适用于需要直接计算绝对控制量的场景(如某些阀门需要绝对开度)。增量式适用于步进电机、数字增量输出等场景。实际工程中增量式占绝大多数。

3.2 采样周期的选择#

香农定理要求采样频率 fs2fmaxf_s \geq 2 f_{max}(信号最高频率的两倍)。实际控制中这个标准太宽松 —— 为了可靠跟踪动态过程,工程经验要求:

fs(1030)fcf_s \geq (10 \sim 30) f_c

其中 fcf_c 是闭环系统的带宽频率(近似为闭环阶跃响应的主要频率成分)。

经验法则对照表:

被控对象类型典型时间常数推荐采样周期
流量1 ~ 3 s0.1 ~ 0.5 s
液位5 ~ 30 s0.5 ~ 3 s
压力1 ~ 10 s0.2 ~ 1 s
温度60 ~ 600 s5 ~ 60 s
电机电流环~1 ms50 ~ 200 μs
电机速度环~10 ms1 ~ 5 ms
电机位置环~100 ms10 ~ 50 ms

实用判断依据:

  1. 采样期内系统动态变化不超过期望精度的 1/5 ~ 1/10。
  2. 采样周期应远小于系统的主导时间常数(Tdom/10\leq T_{dom}/10)。
  3. 如果传感器噪声大,采样周期可以适当拉长来获得天然滤波效果。
  4. 如果采样太快(远快于系统动态),e[k]e[k1]e[k] - e[k-1] 会趋近于噪声,D 项严重恶化。

3.3 伪代码实现#

C 语言增量式 PID#

typedef struct {
float Kp, Ki, Kd; // PID 增益
float T; // 采样周期(秒)
float integral_limit; // 积分限幅
float output_limit; // 输出限幅
float prev_error; // e[k-1]
float prev_prev_error; // e[k-2]
float integral; // 积分累积(位置式用)
float output; // 当前输出 u[k]
} PIDController;
void PID_Init(PIDController *pid, float Kp, float Ki, float Kd, float T) {
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->T = T;
pid->integral_limit = 100.0f;
pid->output_limit = 100.0f;
pid->prev_error = 0.0f;
pid->prev_prev_error = 0.0f;
pid->integral = 0.0f;
pid->output = 0.0f;
}
float PID_UpdateIncremental(PIDController *pid, float setpoint, float measurement) {
float error = setpoint - measurement;
// 增量式 PID 公式
float p_term = pid->Kp * (error - pid->prev_error);
float i_term = pid->Ki * pid->T * error;
float d_term = (pid->Kd / pid->T) * (error - 2.0f * pid->prev_error + pid->prev_prev_error);
float delta_u = p_term + i_term + d_term;
// 输出限幅(简单抗饱和)
float new_output = pid->output + delta_u;
if (new_output > pid->output_limit) {
delta_u = pid->output_limit - pid->output;
} else if (new_output < -pid->output_limit) {
delta_u = -pid->output_limit - pid->output;
}
pid->output += delta_u;
pid->prev_prev_error = pid->prev_error;
pid->prev_error = error;
return pid->output;
}

Python 增量式 PID#

from dataclasses import dataclass
@dataclass
class PIDConfig:
Kp: float = 1.0
Ki: float = 0.0
Kd: float = 0.0
T: float = 0.01 # 采样周期 10ms
out_max: float = 100.0
out_min: float = -100.0
class IncrementalPID:
def __init__(self, cfg: PIDConfig):
self.cfg = cfg
self.prev_error = 0.0
self.prev_prev_error = 0.0
self.output = 0.0
def update(self, setpoint: float, measurement: float) -> float:
error = setpoint - measurement
p_delta = self.cfg.Kp * (error - self.prev_error)
i_delta = self.cfg.Ki * self.cfg.T * error
d_delta = (self.cfg.Kd / self.cfg.T) * (
error - 2.0 * self.prev_error + self.prev_prev_error
)
delta_u = p_delta + i_delta + d_delta
new_output = self.output + delta_u
# 输出限幅
new_output = max(self.cfg.out_min, min(self.cfg.out_max, new_output))
delta_u = new_output - self.output
self.output = new_output
self.prev_prev_error = self.prev_error
self.prev_error = error
return self.output

4. 参数整定#

4.1 经验试凑法(先 P 后 I 再 D)#

这是最实用、最通用的人工整定方法,不需要系统模型。

步骤:

  1. 置零 I 和 DKi=0,Kd=0K_i = 0, K_d = 0),从较小的 KpK_p 开始增大。
  2. 每次增大 KpK_p 后施加阶跃信号,观察响应。KpK_p 增大到出现临界振荡(不衰减的等幅振荡),记下此时的 KpK_pKuK_u,振荡周期为 TuT_u
  3. KpK_p 降回 KuK_u 的 60% ~ 70% 作为工作点。
  4. 加入 I:从较小的 KiK_i 开始,逐步增大,直到稳态误差在可接受时间内消除。注意超调会增大。
  5. 加入 D(如需要):从较小的 KdK_d 开始,逐步增大,抑制 I 带来的超调和振荡。如果噪声明显恶化,减小 KdK_d 或直接弃用 D。

调参口诀(定性指导):

现象可能的参数问题调整方向
响应太慢KpK_p 太小增大 KpK_p
超调量大KpK_p 太大 或 KiK_i 太大减小 KpK_p / KiK_i
振荡不衰减KpK_p 太大 或 KiK_i 太大减小 KpK_p / KiK_i
稳态误差长时间不消失KiK_i 太小增大 KiK_i
围绕设定值等幅振荡KiK_i 太大减小 KiK_i
噪声大、执行器抖动KdK_d 太大减小 KdK_d
超调后长时间不收敛(缓慢衰减)缺少 D适当加入 KdK_d

4.2 Ziegler-Nichols 整定法#

方法一:临界比例度法(闭环)#

  1. Ki=0,Kd=0K_i = 0, K_d = 0
  2. 增大 KpK_p 直到输出出现等幅振荡(临界稳定),记录此时的比例增益 KuK_u 和振荡周期 TuT_u
  3. 按下表换算:
控制器类型KpK_pTiT_iTdT_d
P0.5Ku0.5 K_u
PI0.45Ku0.45 K_u0.83Tu0.83 T_u
PID0.6Ku0.6 K_u0.5Tu0.5 T_u0.125Tu0.125 T_u

其中 Ki=Kp/TiK_i = K_p / T_iKd=KpTdK_d = K_p \cdot T_d

注意:Z-N 整定的 PID 参数通常超调偏大(~25%),追求快速响应。工业应用中往往需要在此基础上适当调小 KpK_p

方法二:响应曲线法(开环)#

  1. 断开闭环,手动施加一个阶跃输入 Δu\Delta u
  2. 记录输出响应曲线,做切线找到拐点,测得:
    • 等效滞后时间 LL
    • 等效时间常数 τ\tau
    • 静态增益 K=Δy/ΔuK = \Delta y / \Delta u
  3. 按下表换算:
控制器类型KpK_pTiT_iTdT_d
Pτ/(KL)\tau / (KL)
PI0.9τ/(KL)0.9 \tau / (KL)3L3L
PID1.2τ/(KL)1.2 \tau / (KL)2L2L0.5L0.5L

4.3 常见整定问题速查#

问题根因解决
响应慢,无明显超调KpK_pKiK_i 都偏小增大 KpK_p,再调 KiK_i
超调后衰减很慢(1~2 个周期后仍大幅波动)KiK_i 太大减小 KiK_i,或适当增大 KdK_d
输出高频抖动KdK_d 太大 或 传感器噪声大减小 KdK_d,或给传感器加低通滤波
启动时超调巨大积分饱和(见 5.1)加抗饱和措施
设定值改变后跟踪很慢KpK_p 太小增大 KpK_p
负载扰动后恢复很慢KiK_i 太小增大 KiK_i
多个周期等幅振荡系统临界稳定减小 KpK_p 至少 20%

5. 工程优化与抗积分饱和#

5.1 积分饱和的产生与危害#

什么是积分饱和?

当执行器达到物理极限(阀门全开/全关、电机最大转速、加热器最大功率)时,误差 e(t)e(t) 仍然存在。积分项继续累积,导致 u(t)u(t) 远超物理限制。当误差符号反转时,积分值需要很长时间才能”退回到”有效范围,期间控制器输出仍保持饱和 —— 表现为严重的超调和长时间振荡。

典型场景:加热器从室温加热到 80°C。设定值为 80°C,当前 25°C,误差 55°C。加热器全功率运行(饱和)。PID 输出可能在积分累积下达到 150%(实际物理极限 100%)。当温度到达 80°C 时,积分项仍需很长时间才能降到 100% 以下,导致严重超调

5.2 常用抗饱和方法#

积分分离(Integral Separation)#

当误差大于阈值时,关闭积分项;误差进入阈值范围内才开启积分。

#define INTEGRAL_SEPARATION_THRESHOLD 20.0f
float error = setpoint - measurement;
float i_term = 0.0f;
if (fabsf(error) < INTEGRAL_SEPARATION_THRESHOLD) {
pid->integral += error * pid->T;
i_term = pid->Ki * pid->integral;
}

优点:简单有效,避免大偏差时积分累积。 缺点:阈值设定依赖经验,阈值附近可能有轻微跳变。

遇限削弱积分法(Clamping / 条件积分)#

当输出已饱和且误差与控制量同向时,停止积分累积。这是工程中使用最广泛的抗饱和方法。

float PID_UpdateClamping(PIDController *pid, float setpoint, float measurement) {
float error = setpoint - measurement;
// 条件积分:只有当输出未饱和 或 误差方向使输出退出饱和时才积分
bool output_high_saturated = (pid->output >= pid->output_limit) && (error > 0);
bool output_low_saturated = (pid->output <= -pid->output_limit) && (error < 0);
if (!output_high_saturated && !output_low_saturated) {
pid->integral += error * pid->T;
}
float p_term = pid->Kp * error;
float i_term = pid->Ki * pid->integral;
float d_term = (pid->Kd / pid->T) * (error - pid->prev_error);
float u = p_term + i_term + d_term;
// 输出限幅
if (u > pid->output_limit) u = pid->output_limit;
if (u < -pid->output_limit) u = -pid->output_limit;
pid->output = u;
pid->prev_error = error;
return u;
}

原理:只有在”输出还能跟随计算值”的时候才积分。如果输出已饱和且误差还在把输出推向更饱和的方向,积分停掉。

变速积分(Variable-Speed Integration)#

积分速度随误差大小变化:误差大时积分慢,误差小时积分快。

float error_abs = fabsf(error);
float speed;
if (error_abs > 50.0f) {
speed = 0.0f; // 误差大,不积分
} else if (error_abs > 20.0f) {
speed = 0.3f; // 中等误差,慢速积分
} else if (error_abs > 5.0f) {
speed = 0.7f; // 较小误差,接近正常积分
} else {
speed = 1.0f; // 小误差,全速积分
}
pid->integral += speed * error * pid->T;

5.3 微分项的改进#

不完全微分(加入低通滤波器)#

标准微分项 D(s)=KdsD(s) = K_d s 是一个理想微分器,对高频噪声极其敏感。实际工程中使用不完全微分

D(s)=Kds1+τfsD(s) = \frac{K_d s}{1 + \tau_f s}

其中 τf=Kd/(KpN)\tau_f = K_d / (K_p \cdot N)NN 通常在 3 ~ 20 之间。这是一阶低通滤波器 + 微分的组合。

时域实现(后向欧拉离散化):

// alpha = T / (T + tau_f) —— 滤波系数,典型值 0.05 ~ 0.3
float alpha = pid->T / (pid->T + tau_f);
float raw_derivative = (error - pid->prev_error) / pid->T;
pid->filtered_derivative = (1.0f - alpha) * pid->filtered_derivative + alpha * raw_derivative;
float d_term = pid->Kd * pid->filtered_derivative;

效果:滤除高频噪声成分,微分输出更平滑,执行器寿命大大延长。

微分先行(Derivative on Measurement)#

标准 PID 对误差微分:de/dtde/dt。当设定值 r(t)r(t) 发生阶跃变化时,e(t)e(t) 瞬间跳变,微分项产生巨大尖峰(称为微分冲击)。

微分先行对反馈值 y(t)y(t) 微分而非误差:

uD(t)=Kddy(t)dtu_D(t) = -K_d \frac{dy(t)}{dt}

因为 de/dt=dr/dtdy/dtde/dt = dr/dt - dy/dt,当设定值恒定时 dr/dt=0dr/dt = 0,两者等价。但当设定值跳变时,微分先行避免了冲击。

// 微分先行:对测量值微分
float d_term = -(pid->Kd / pid->T) * (measurement - pid->prev_measurement);

一句话选择指南:如果设定值频繁阶跃变化 —— 使用微分先行。如果跟踪斜坡/正弦设定值 —— 标准微分也可以。


6. 性能评估与调优#

6.1 时域性能指标#

对阶跃响应进行分析,四个核心指标:

y(t)
│ ┌──── 超调量 ────┐
│ ╱│ ╲
│ ╱ │ ╲___________ 稳态值
│ ╱ │ │
│╱ │ │
│ │ │
──┼────┼──────────────────┼──────────→ t
│ │← 上升时间 →│ │
│ │←── 调节时间 ──────→│
│ │ (进入±2%误差带) │
指标定义期望
上升时间 trt_r响应从 10% 上升到 90% 稳态值的时间
超调量 MpM_p峰值与稳态值之差,占稳态值的百分比小(< 10%)
调节时间 tst_s响应进入 ±2%(或 ±5%)误差带且不再超出的时间
稳态误差 esse_{ss}无穷时间后的残余误差0

参数对指标的影响方向:

增大参数上升时间超调量调节时间稳态误差
KpK_p↓ 减小↑ 增大小幅变化↓ 减小
KiK_i↓ 小幅减小↑ 增大↑ 增大消除
KdK_d小幅变化↓ 减小↓ 减小无影响

工程权衡:不存在同时让所有指标最优的参数。快速响应和低超调是矛盾的。通常先确保稳定性,再在超调可接受的前提下尽量减小上升时间和调节时间。

6.2 参数自整定简介#

继电反馈自整定(Relay Feedback Autotuning)

1984 年由 Åström 和 Hägglund 提出。用继电环节(Bang-Bang 控制)替代 PID 控制器,使系统产生小幅等幅振荡,从中辨识临界增益 KuK_u 和临界周期 TuT_u,再用 Ziegler-Nichols 表换算 PID 参数。

这是目前工业界最常用的自适应整定方式,被 Siemens、ABB、Honeywell 等厂商广泛采用。

基于模型的自整定

通过在线系统辨识(如递推最小二乘法 RLS)实时估计系统的 FOPDT 模型参数,然后用 IMC(Internal Model Control)等方法计算 PID 参数。需要一定计算能力,但在 DCS 和高端 PLC 中已普及。

6.3 与其他控制算法的比较#

PID模糊控制 (Fuzzy)MPC
依赖模型不需要不需要需要模型
处理非线性差(需增益调度)
处理约束困难(仅输出限幅)无天然约束处理天然处理约束
处理多变量耦合困难困难天然解耦
计算复杂度极低
可解释性
参数整定成熟(Z-N 等)依赖经验规则需要建模技能
适用场景绝大多数工业回路非线性强、难建模多变量、有约束、慢过程

选型建议:

  • 90% 的场合:PID(或 PI)足矣。
  • 强非线性且难以建模:模糊 PID / 模糊控制。
  • 多变量、有约束、慢过程(化工、石化):MPC。
  • 对实时性要求极高且系统模型已知:考虑前馈 + PID。

7. 实战案例#

7.1 温度控制系统(FOPDT 模型整定)#

场景:电阻加热炉,目标温度 200°C,环境温度 25°C,加热功率 0~100% PWM。

系统特性:温度控制典型的一阶惯性 + 纯滞后(FOPDT)系统:

G(s)=Kτs+1eLsG(s) = \frac{K}{\tau s + 1} e^{-Ls}

建模(开环阶跃响应法):

  1. 施加 50% 加热功率,记录温度上升曲线。
  2. 测得:K=3.5K = 3.5 °C/%,τ=180\tau = 180 s,L=30L = 30 s。

整定(响应曲线法):

  • Kp=1.2τ/(KL)=1.2×180/(3.5×30)2.06K_p = 1.2 \tau / (K L) = 1.2 \times 180 / (3.5 \times 30) \approx 2.06
  • Ti=2L=60T_i = 2 L = 60 s → Ki=Kp/Ti=0.034K_i = K_p / T_i = 0.034
  • Td=0.5L=15T_d = 0.5 L = 15 s → Kd=KpTd=30.9K_d = K_p \cdot T_d = 30.9

实现要点

  • 采样周期 T=3T = 3 s(L/10L/10)。
  • 必须使用抗积分饱和(加热器只能 0~100%)。
  • 微分项使用不完全微分抑制传感器噪声。
  • 加热器初始冷态时误差巨大(175°C),强烈推荐积分分离。

Python 仿真代码(见附录 8.1)。

7.2 电机速度 / 位置控制(串级 PID)#

结构:电流环(内环)→ 速度环(中环)→ 位置环(外环)

位置设定 ──→ [位置PID] ──→ [速度PID] ──→ [电流PID] ──→ [PWM / 逆变器] ──→ [电机]
↑ ↑ ↑
位置反馈(编码器) 速度反馈(编码器差分/测速发电机) 电流反馈(霍尔/shunt)
环路带宽(典型)周期作用
电流环1 ~ 2 kHz50 ~ 100 μs控制绕组电流,提供快速力矩响应
速度环100 ~ 300 Hz1 ~ 5 ms控制转速,抑制负载扰动
位置环10 ~ 50 Hz10 ~ 50 ms控制角度/位置,跟踪轨迹

整定顺序(由内而外)

  1. 先调电流环(KpK_p 大、KiK_i 中、KdK_d 小或零),确保电流响应快且不振荡。
  2. 电流环整好后视为一阶环节,再调速度环。速度环通常使用 PI(KdK_d 会放大编码器量化噪声)。
  3. 位置环最外层,通常只用 P(或 P + 小 I)。

FOC(磁场定向控制)中 PID 的特殊性

FOC 将三相电流变换到旋转 dq 坐标系下,idi_d(励磁)和 iqi_q(转矩)分别由两个独立的 PID 控制。此时被控对象近似为纯阻感负载,可以整定得很快。

7.3 四轴飞行器姿态控制(双闭环 PID)#

结构:角度环(外环)→ 角速度环(内环)

目标角度 ──→ [角度PID] ──→ [角速度PID] ──→ [混控器] ──→ [电机/电调] ──→ [机体]
(roll/pitch/yaw) ↑ ↑
姿态解算(IMU + 互补/卡尔曼滤波) ← 陀螺仪 + 加速度计 + 磁力计
环路频率传感器作用
角速度环(内环)1 ~ 8 kHz陀螺仪抑制扰动,提供阻尼,响应极快
角度环(外环)200 ~ 500 Hz加速度计 + 陀螺仪融合跟踪目标姿态角

为什么无人机必须用双闭环(串级)?

单靠角度环控制响应太慢,风扰或电机转速突变时无法及时补偿。角速度环直接感知机体瞬时角速度变化,在角度明显偏离之前就产生修正力矩,是无人机抗风性和稳定的关键。

典型参数参考(穿越机 5 寸,Betaflight 经验值):

角度环 P角度环 I角速度环 P角速度环 I角速度环 D
Roll4040454528
Pitch4242484830
Yaw404060500

整定过程

  1. 先调角速度环:KiK_iKdK_d 置零,增大 KpK_p 到出现微振(高频抖动),退回 70%。
  2. 加入 KiK_i(消除角速度稳态误差),KdK_d(抑制过冲和噪声)。
  3. 角速度环整好后,调角度环:只用 P(KiK_i 很小或零,避免姿态恢复时超调引起振荡)。
  4. 实际飞行测试:快速拨动摇杆,观察姿态跟踪有无过冲和振荡。用黑盒日志(Blackbox Log)分析频响。

D 项的低通滤波至关重要:陀螺仪噪声经过 D 项放大后会直接耦合到电机输出,表现为电机发热、抖动,甚至烧毁。Betaflight 默认使用两阶动态低通滤波器 + 动态 notch 滤波处理陀螺仪信号。


8. 附录#

8.1 完整实现代码#

C 语言抗饱和增量式 PID(完整版)#

#include <math.h>
#include <stdbool.h>
typedef struct {
// 参数
float Kp, Ki, Kd; // PID 增益
float T; // 采样周期 (s)
float tau_f; // 不完全微分滤波时间常数 (s)
// 限幅
float out_max, out_min;
float integ_limit;
// 状态
float prev_error; // e[k-1]
float prev_prev_error; // e[k-2]
float prev_measurement; // y[k-1] (微分先行用)
float integral; // 积分累积
float filtered_derivative; // 不完全微分滤波值
float output; // 当前输出 u[k]
// 功能开关
bool use_clamping; // 遇限削弱积分
bool use_deriv_on_meas; // 微分先行
bool use_filtered_deriv; // 不完全微分
bool use_integ_separation; // 积分分离
float integ_sep_threshold; // 积分分离阈值
} PIDController;
void PID_Init(PIDController *p, float Kp, float Ki, float Kd, float T) {
p->Kp = Kp;
p->Ki = Ki;
p->Kd = Kd;
p->T = T;
p->tau_f = Kd / (Kp * 10.0f); // N = 10
p->out_max = 100.0f;
p->out_min = -100.0f;
p->integ_limit = 100.0f;
p->prev_error = 0.0f;
p->prev_prev_error = 0.0f;
p->prev_measurement = 0.0f;
p->integral = 0.0f;
p->filtered_derivative = 0.0f;
p->output = 0.0f;
p->use_clamping = true;
p->use_deriv_on_meas = false;
p->use_filtered_deriv = false;
p->use_integ_separation = true;
p->integ_sep_threshold = 30.0f;
}
float PID_Update(PIDController *p, float setpoint, float measurement) {
float error = setpoint - measurement;
// ---- 比例项 ----
float p_term = p->Kp * error;
// ---- 积分项(带抗饱和) ----
bool sat_high = (p->output >= p->out_max) && (error > 0);
bool sat_low = (p->output <= p->out_min) && (error < 0);
bool integrate = true;
if (p->use_clamping && (sat_high || sat_low)) {
integrate = false;
}
if (p->use_integ_separation && fabsf(error) > p->integ_sep_threshold) {
integrate = false;
}
if (integrate) {
p->integral += error * p->T;
// 积分限幅
if (p->integral > p->integ_limit) p->integral = p->integ_limit;
if (p->integral < -p->integ_limit) p->integral = -p->integ_limit;
}
float i_term = p->Ki * p->integral;
// ---- 微分项 ----
float d_term = 0.0f;
float derivative_input;
if (p->use_deriv_on_meas) {
// 微分先行:对测量值微分
derivative_input = -(measurement - p->prev_measurement) / p->T;
} else {
// 标准:对误差微分
derivative_input = (error - p->prev_error) / p->T;
}
if (p->use_filtered_deriv) {
// 不完全微分(一阶低通滤波)
float alpha = p->T / (p->T + p->tau_f);
p->filtered_derivative = (1.0f - alpha) * p->filtered_derivative
+ alpha * derivative_input;
d_term = p->Kd * p->filtered_derivative;
} else {
d_term = p->Kd * derivative_input;
}
// ---- 合成 ----
float u = p_term + i_term + d_term;
// 输出限幅
if (u > p->out_max) u = p->out_max;
if (u < p->out_min) u = p->out_min;
p->output = u;
p->prev_prev_error = p->prev_error;
p->prev_error = error;
p->prev_measurement = measurement;
return u;
}

Python 温度控制系统仿真#

import numpy as np
import matplotlib.pyplot as plt
from dataclasses import dataclass, field
@dataclass
class FOPDTPlant:
"""一阶惯性 + 纯滞后被控对象"""
gain: float # K
tau: float # 时间常数 (s)
delay: float # 纯滞后 (s)
y: float = 25.0 # 当前输出 (初始环境温度)
_delay_buffer: list = field(default_factory=list)
def __post_init__(self):
self._delay_buffer = [self.y] * max(1, int(self.delay / 0.1))
def update(self, u: float, dt: float) -> float:
# 一阶惯性:tau * dy/dt + y = K * u
dy = (self.gain * u - self.y) / self.tau * dt
self.y += dy
self._delay_buffer.append(self.y)
if len(self._delay_buffer) > max(1, int(self.delay / dt)):
self._delay_buffer.pop(0)
return self._delay_buffer[0]
class PID:
def __init__(self, Kp, Ki, Kd, T, out_max=100, out_min=0):
self.Kp, self.Ki, self.Kd, self.T = Kp, Ki, Kd, T
self.out_max, self.out_min = out_max, out_min
self.prev_error = 0.0
self.prev_prev_error = 0.0
self.integral = 0.0
self.output = 0.0
def update(self, sp, pv):
error = sp - pv
# 增量式
p_delta = self.Kp * (error - self.prev_error)
i_delta = self.Ki * self.T * error
d_delta = (self.Kd / self.T) * (
error - 2 * self.prev_error + self.prev_prev_error
)
delta = p_delta + i_delta + d_delta
new_out = self.output + delta
# 抗饱和:遇限削弱积分
clamped = max(self.out_min, min(self.out_max, new_out))
if clamped != new_out:
self.integral -= (new_out - clamped) * 0.1 # 反算抗饱和
self.output = clamped
self.prev_prev_error = self.prev_error
self.prev_error = error
return self.output
# 仿真参数
K, tau, L = 3.5, 180.0, 30.0
plant = FOPDTPlant(K, tau, L, y=25.0)
Kp, Ki, Kd = 2.06, 0.034, 30.9
T = 3.0
pid = PID(Kp, Ki, Kd, T, out_max=100, out_min=0)
setpoint = 200.0
t_total = 1200 # 20 分钟
t = np.arange(0, t_total, T)
y_hist, u_hist, sp_hist = [], [], []
for _ in t:
pv = plant.update(pid.output, T)
pid.update(setpoint, pv)
y_hist.append(pv)
u_hist.append(pid.output)
sp_hist.append(setpoint)
# 绘图
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
ax1.plot(t, sp_hist, 'k--', label='Setpoint')
ax1.plot(t, y_hist, 'b-', label='PV (Temperature)')
ax1.set_ylabel('Temperature (°C)')
ax1.legend(); ax1.grid(True)
ax2.plot(t, u_hist, 'r-', label='Control Output')
ax2.set_ylabel('Output (%)')
ax2.set_xlabel('Time (s)')
ax2.legend(); ax2.grid(True)
plt.tight_layout()
plt.show()

8.2 在线仿真工具推荐#

工具类型特点
PID Tuner (MATLAB/Simulink)商业最强大的 PID 整定工具,自动线性化 + 参数优化
Python Control Systems Library开源control.pid() + step_response() 快速原型验证
Scipy signal开源低层控制工具箱,配合 NumPy 使用
KiCad + ngspice开源模拟电路 PID 的 SPICE 仿真
WebPID在线无需安装的在线 PID 仿真,适合教学演示
Betaflight Configurator开源无人机 PID 整定专用,含黑盒分析

8.3 推荐阅读经典论文与书籍#

书籍:

  • Åström, K. J. & Hägglund, T. (2006). Advanced PID Control. ISA. — PID 领域的”圣经”,涵盖结构、整定、实现、自适应。
  • Åström, K. J. & Murray, R. M. (2020). Feedback Systems: An Introduction for Scientists and Engineers. Princeton. — 从零开始的反馈控制入门。
  • 刘金琨 (2016). 先进 PID 控制 MATLAB 仿真(第 4 版). 电子工业出版社. — 中文领域最好的 PID 工程参考书,大量 MATLAB 仿真实例。

论文:

  • Ziegler, J. G. & Nichols, N. B. (1942). “Optimum Settings for Automatic Controllers.” Transactions of the ASME, 64, 759-768. — Z-N 整定的原始论文。
  • Åström, K. J. & Hägglund, T. (1984). “Automatic Tuning of Simple Regulators with Specifications on Phase and Amplitude Margins.” Automatica, 20(5), 645-651. — 继电反馈自整定的开创性工作。
  • Rivera, D. E., Morari, M. & Skogestad, S. (1986). “Internal Model Control. 4. PID Controller Design.” Industrial & Engineering Chemistry Process Design and Development, 25(1), 252-265. — IMC-PID 设计方法。
  • Skogestad, S. (2003). “Simple Analytic Rules for Model Reduction and PID Controller Tuning.” Journal of Process Control, 13(4), 291-309. — SIMC 整定规则,目前工业界最实用的基于模型的整定方法。

从纸上公式到实际系统:PID 算法是控制工程的基石。掌握它不仅仅是背下三条公式,而是理解每一项如何影响闭环性能,知道什么场景用什么改进,以及能根据实际响应曲线判断参数调整方向。希望这份指南能帮助你从”会用 PID”走向”理解 PID”再走向”驾驭 PID”。

PID 算法从入门到工程实践 - 完整学习指南
https://blog.syomega.top/posts/pid-algorithm-complete-guide/
作者
酱w
发布于
2026-05-16
许可协议
CC BY-NC-SA 4.0