电机驱动开发学习8. PID 算法
- 一、控制系统简介
-
- [1.1 什么是控制系统](#1.1 什么是控制系统)
- [1.2 开环与闭环](#1.2 开环与闭环)
- [1.3 为什么需要 PID](#1.3 为什么需要 PID)
- [二、PID 算法是什么](#二、PID 算法是什么)
-
- [2.1 基本公式](#2.1 基本公式)
- [2.2 三个环节分别做什么](#2.2 三个环节分别做什么)
-
- [P --- 比例](#P — 比例)
- [I --- 积分](#I — 积分)
- [D --- 微分](#D — 微分)
- [2.3 位置式与增量式](#2.3 位置式与增量式)
- [2.4 调参直觉](#2.4 调参直觉)
- [三、Python 搭建 PID 仿真环境](#三、Python 搭建 PID 仿真环境)
-
- [3.1 环境准备](#3.1 环境准备)
- [3.2 被控对象:一阶惯性系统](#3.2 被控对象:一阶惯性系统)
- [3.3 PID 控制器实现](#3.3 PID 控制器实现)
- [3.4 交互式动态仿真](#3.4 交互式动态仿真)
- 四、仿真实验
-
- [4.1 运行方式](#4.1 运行方式)
- [4.2 实验一:只用 P,观察稳态误差](#4.2 实验一:只用 P,观察稳态误差)
- [4.3 实验二:加入 I 消除稳态误差](#4.3 实验二:加入 I 消除稳态误差)
- [4.4 实验三:加入 D 抑制超调](#4.4 实验三:加入 D 抑制超调)
- [4.5 实验四:方波跟踪(推荐观察超调/振荡)](#4.5 实验四:方波跟踪(推荐观察超调/振荡))
- [4.6 实验五:扰动与正弦跟踪](#4.6 实验五:扰动与正弦跟踪)
- [4.7 调参顺序(与在线工具一致)](#4.7 调参顺序(与在线工具一致))
- 五、仿真结果分析
-
- [5.1 阶跃响应指标](#5.1 阶跃响应指标)
- [5.2 常见问题与参数方向](#5.2 常见问题与参数方向)
- 六、从仿真到嵌入式
- 附录:仿真代码结构

一、控制系统简介
在开始学习 PID 之前,需要先理解控制系统在做什么。
1.1 什么是控制系统
控制系统由三部分组成:
| 组成部分 | 作用 | 举例 |
|---|---|---|
| 被控对象 | 接受输入、产生输出的物理或数学过程 | 水箱水位、房间温度、机械位置 |
| 传感器 | 测量被控对象的实际状态 | 液位计、温度计、编码器 |
| 控制器 | 根据目标与实际的偏差,计算控制量 | PID 控制器 |
控制的目标通常是:让被控对象的某个量稳定地跟踪设定值(Setpoint)。
1.2 开环与闭环
开环控制:控制器直接给出固定输出,不参考测量值。
设定值 ──→ 控制器 ──→ 被控对象 ──→ 输出
特点:结构简单,但无法自动纠正误差。负载变化、环境干扰都会导致输出偏离目标。
闭环控制(反馈控制):把传感器测到的实际值反馈回来,与目标比较后修正输出。
┌──────────────────────────────────┐
│ │
设定值 ─┤→ 误差 ──→ 控制器 ──→ 被控对象 ──→ 输出
│ ↑ │
│ └──────── 传感器 ←───────────┘
└──────────────────────────────────┘
其中:
误差 e(t) = 设定值 r(t) - 实际值 y(t)
闭环控制能自动补偿干扰,是 PID 发挥作用的前提。
1.3 为什么需要 PID
闭环里常见的问题是:
- 响应太慢,很久才接近目标
- 超调太大,冲过目标后再回来
- 存在稳态误差,永远差一点点到不了目标
- 负载突变后恢复慢
PID 通过三个环节分别处理这些问题:
| 环节 | 主要作用 |
|---|---|
| P(比例) | 误差越大,输出越大,加快响应 |
| I(积分) | 累积历史误差,消除稳态偏差 |
| D(微分) | 预测误差变化趋势,抑制超调 |
二、PID 算法是什么
2.1 基本公式
连续时间下的标准 PID 公式为:
u(t) = Kp·e(t) + Ki·∫e(t)dt + Kd·de(t)/dt
上式含义:控制量 = 比例项 + 积分项 + 微分项。逐项说明如下。
整体符号
| 符号 | 含义 |
|---|---|
u(t) |
控制量(输出):控制器计算后送给被控对象的量,如加热功率、阀门开度、PWM 等 |
t |
时间:连续变化的时刻 |
误差 e(t)
公式中的 e(t) 通常定义为:
e(t) = r(t) - y(t)
| 符号 | 含义 |
|---|---|
r(t) |
设定值(目标):希望系统达到的值 |
y(t) |
实际值(反馈):传感器测到的当前值 |
e(t) |
误差 :目标与实际之差;e > 0 表示尚未到达目标,e = 0 表示已跟踪上 |
PID 的核心就是:根据误差 e 的大小和变化,决定控制量 u 该加多少、减多少。
第一项 Kp·e(t) --- 比例(P)
| 符号 | 含义 |
|---|---|
Kp |
比例系数:P 环节的"力度" |
Kp·e(t) |
比例项:误差越大,输出越大 |
直觉:现在差多少,就按比例去补多少 。误差大则输出大、响应快;误差小则输出小、动作变缓。单独使用 P 时,系统往往存在稳态误差,即系统已经稳定下来之后,输出仍与设定值之间保留一个固定的小偏差,无法完全归零(例如目标 100,实际长期停在 95)。
第二项 Ki·∫e(t)dt --- 积分(I)
| 符号 | 含义 |
|---|---|
Ki |
积分系数:I 环节的"力度" |
∫e(t)dt |
误差对时间的积分:把历史上每一时刻的误差累加起来 |
Ki·∫e(t)dt |
积分项:对长期偏差的累积补偿 |
直觉:不只看"现在差多少",还看"已经差多久了" 。只要误差持续不为零,积分项就会不断增大,推动输出继续变化,直到误差归零。用来消除稳态误差 ;Ki 过大会导致超调 和积分饱和,即输出冲过目标后才慢慢回落,以及控制量已达上限时积分仍在累加、造成恢复缓慢或大幅振荡。
第三项 Kd·de(t)/dt --- 微分(D)
| 符号 | 含义 |
|---|---|
Kd |
微分系数:D 环节的"力度" |
de(t)/dt |
误差对时间的导数:误差变化有多快 |
Kd·de(t)/dt |
微分项:根据误差变化趋势做"提前刹车" |
直觉:不看误差大小,而看误差变化有多快 。误差快速减小时,D 项产生反向作用,抑制超调;误差突然变大时,D 项会提前加大反应。对测量噪声较敏感,实际系统中常需滤波。
三项合在一起
u(t) = 现在差多少就补多少 + 差多久就补多少 + 差得有多急就提前调节
───────────────── ─────────────── ─────────────────────
Kp·e(t) (P) Ki·∫e(t)dt (I) Kd·de(t)/dt (D)
离散实现(嵌入式和仿真最常用)中,设采样周期为 dt,第 k 次采样时:
e(k) = r(k) - y(k)
P项: Kp · e(k)
I项: Ki · Σ e(i)·dt
D项: Kd · [e(k) - e(k-1)] / dt
u(k) = P项 + I项 + D项
这就是位置式 PID:每次直接算出控制量的绝对值。
2.2 三个环节分别做什么
P --- 比例
u_P = Kp × e
Kp越大:响应越快,但容易振荡Kp越小:响应慢,但更平稳- 单独使用 P 时,系统往往存在稳态误差(例如被控对象有持续负载时,P 项不足以把误差完全消除)
I --- 积分
u_I = Ki × Σ(e × dt)
- 只要误差不为零,积分项就会持续累加,直到把误差"积"到零
- 能消除稳态误差
- 但积分过大会导致超调 和积分饱和(输出已到极限,积分还在累加)
工程上必须加积分限幅和**抗饱和(Anti-windup)**处理。
D --- 微分
u_D = Kd × (e(k) - e(k-1)) / dt
- 相当于看"误差变化有多快"
- 误差快速减小时,D 项产生反向作用,抑制超调
- 对噪声敏感,实际系统中常对测量值滤波后再求微分
2.3 位置式与增量式
| 形式 | 输出含义 | 适用场景 |
|---|---|---|
| 位置式 | 直接输出控制量绝对值 | 温度、位置、PWM 占空比 |
| 增量式 | 输出相对上次的增量 Δu | 步进电机、阀门开度微调 |
本教程仿真采用位置式 PID,与后续嵌入式实现一致。
2.4 调参直觉
调参没有唯一公式,但有一个实用顺序:
- 先设
Ki=0, Kd=0,从小到大调Kp,直到出现轻微振荡 - 加入
Ki,消除稳态误差 - 若超调仍大,再加入少量
Kd
经典 Ziegler-Nichols 法可作为参考,但仿真 + 实机微调往往更直观。
三、Python 搭建 PID 仿真环境
在把 PID 写到 STM32 之前,先用 Python 仿真可以:
- 快速理解 P / I / D 各自对波形的影响
- 安全地试错调参,不烧硬件
- 建立"看到阶跃响应曲线"的直觉
3.1 环境准备
需要 Python 3.8+,安装依赖:
bash
pip install numpy matplotlib
3.2 被控对象:一阶惯性系统
用一个简单的一阶惯性环节作为被控对象:
T · dy/dt + y = K · u
含义:输出 y 不会瞬间跟随输入 u,而是以时间常数 T 逐渐逼近。很多物理过程(温度、液位、RC 电路)在一阶近似下都类似这个模型。
离散化(欧拉法):
python
dy = (K * u - y) / T
y = y + dy * dt
本教程取 K=1.0, T=2.0,即控制量满幅时,输出约 2 秒后接近稳态。
3.3 PID 控制器实现
python
class PIDController:
def __init__(self, kp, ki, kd, dt, out_min=-10, out_max=10, integral_max=50):
self.kp, self.ki, self.kd = kp, ki, kd
self.dt = dt
self.out_min, self.out_max = out_min, out_max
self.integral_max = integral_max
self.integral = 0.0
self.prev_error = 0.0
def update(self, setpoint, measurement):
error = setpoint - measurement
p_term = self.kp * error
self.integral += self.ki * error * self.dt
self.integral = max(-self.integral_max, min(self.integral_max, self.integral))
d_term = self.kd * (error - self.prev_error) / self.dt
self.prev_error = error
output = p_term + self.integral + d_term
output = max(self.out_min, min(self.out_max, output))
# 积分抗饱和
if output >= self.out_max and error > 0:
self.integral -= self.ki * error * self.dt
elif output <= self.out_min and error < 0:
self.integral -= self.ki * error * self.dt
return output
3.4 交互式动态仿真
本教程采用实时交互仿真 ,体验类似在线工具 ElysiaTools PID 可视化 与 Luis Llamas PID Simulator:
- 拖动 Kp / Ki / Kd 滑块,曲线实时变化
- 响应曲线:目标值、实际输出、误差
- PID 分量:P / I / D 三条输出随时间滚动
- 物理动画:红块=目标位置,蓝块=实际位置
- 实时指标:当前误差、积分项、微分项、超调量、调节时间
- 操作按钮 :暂停/继续、重置、阶跃、方波、扰动、正弦跟踪
被控对象为一阶惯性系统 + 纯滞后(更贴近真实工业对象):
T · dy/dt + y = K · u(t - delay)
仿真步长 dt = 0.05 s(20 Hz),由定时刷新间隔决定,不是从 PID 公式推导出来的。
完整代码见 sim/pid_simulation.py。
四、仿真实验
4.1 运行方式
bash
cd sim
python pid_simulation.py # 交互式动态仿真(默认)
python pid_simulation.py --static # 静态 P/PI/PID 对比图
启动后会出现交互窗口,界面布局如下:
| 区域 | 内容 |
|---|---|
| 上方 | 响应曲线(目标 / 输出 / 误差) |
| 左下 | P / I / D 分量曲线 |
| 右下 | 物理位置动画 |
| 底部 | Kp/Ki/Kd/噪声 滑块 + 操作按钮 + 实时指标(目标值滑块仅阶跃模式显示) |
4.2 实验一:只用 P,观察稳态误差
- 将
Ki=0, Kd=0,Kp从 0.5 逐步调到 3.0 - 点击「阶跃」(此时会出现「目标值」滑块)
- 拖动目标值滑块设为 50(或其他值)
- 观察:输出能否快速上升?稳定后是否与目标有固定偏差?
预期:Kp 越大响应越快,但单独 P 往往存在稳态误差 。

4.3 实验二:加入 I 消除稳态误差
- 保持
Kp=2.0,Kd=0 - 将
Ki从 0 逐步增大到 0.1 → 0.5 → 1.0 - 观察稳态误差是否消失,超调是否变大
预期:Ki 合适时可消除稳态误差;过大则超调增加、调节时间变长。


4.4 实验三:加入 D 抑制超调
- 在实验二较好的 PI 参数基础上,加入
Kd(如 0.3 → 0.8) - 观察超调量和振荡是否减小
预期:适量 Kd 可抑制超调;过大且开启「噪声」滑块时,输出会抖动。
4.5 实验四:方波跟踪(推荐观察超调/振荡)
方波目标在高低两档之间反复阶跃,每个边沿都是一次阶跃响应,比正弦更容易观察:
- 上升/下降沿是否有超调
- 平台段是否有稳态误差(纯 P 时明显)
- Ki 过大时是否在边沿附近来回振荡
操作:点击「方波 」,目标在 25↔75 之间切换,周期 4 s(每档 2 s)。建议从 Ki=0, Kd=0 增大 Kp,再逐步加 Ki 对比边沿波形。
4.6 实验五:扰动与正弦跟踪
- 点击「扰动」:对被控对象施加负载突变,观察 PI/PID 能否拉回目标
- 点击「正弦 」:目标值平滑变化,测试动态跟踪(相位滞后、幅值衰减)

4.7 调参顺序(与在线工具一致)
- 先只用 Kp,调到响应可接受
- 有稳态误差时加 Ki
- 超调或振荡过大时再加 Kd
- 每次只改一个参数,小幅调整
也可参考在线工具的观察指南:ElysiaTools 观察指南
五、仿真结果分析
5.1 阶跃响应指标
| 指标 | 含义 | 期望 |
|---|---|---|
| 上升时间 | 输出从 10% 到 90% 目标值的时间 | 越短越好(在稳定前提下) |
| 超调量 | 超过目标值的峰值比例 | 越小越好,工业上常要求 < 10% |
| 调节时间 | 进入并保持在误差带内的时间 | 越短越好 |
| 稳态误差 | 稳定后与目标的差 | 越接近 0 越好 |
5.2 常见问题与参数方向
| 现象 | 可能原因 | 调整方向 |
|---|---|---|
| 响应太慢 | Kp 太小 | 增大 Kp |
| 振荡 / 超调大 | Kp 或 Ki 太大 | 减小 Kp/Ki,或加 Kd |
| 有稳态误差 | 缺少积分或 Ki 太小 | 增大 Ki |
| 控制量长时间饱和 | 积分 windup | 加抗饱和,减小 Ki |
| 输出抖动 | D 项对噪声敏感 | 减小 Kd,或滤波 |
六、从仿真到嵌入式
本篇文章聚焦 PID 原理与 Python 仿真。掌握以下内容后,再进入下一篇(电机速度闭环)会轻松很多:
- 理解误差
e = 设定值 - 测量值 - 能手写位置式 PID,含积分限幅与抗饱和
- 通过仿真观察 P / I / D 对阶跃响应的影响
- 知道"先 P,再 I,最后 D"的调参顺序
下一篇将把同样的 PID 结构移植到 STM32,用编码器反馈实现电机速度闭环控制。
附录:仿真代码结构
lesson8. PID算法/
├── README.md # 本文
└── sim/
└── pid_simulation.py # 完整仿真脚本
依赖安装:
bash
pip install numpy matplotlib
快速验证:
bash
cd sim && python pid_simulation.py