什么是PID控制?

PID(Proportional-Integral-Derivative)控制是工业控制中最常用的控制算法,由比例、积分、微分三个环节组成。

为什么需要PID?

想象你在开车,需要保持车速100km/h:

  • 看到速度是90km/h,踩油门(比例控制
  • 速度长期达不到100,继续加油(积分控制
  • 速度上升太快,提前收油门(微分控制

这就是PID的基本思想。

PID数学原理

控制公式

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

其中:

  • $u(t)$:控制输出
  • $e(t)$:误差(目标值 - 当前值)
  • $K_p, K_i, K_d$:PID三个参数

离散化形式

实际编程中使用离散形式:

$$
u_k = K_p \cdot e_k + K_i \sum_{i=0}^k e_i \cdot \Delta t + K_d \frac{e_k - e_{k-1}}{\Delta t}
$$

三个环节详解

P - 比例控制

作用:根据当前误差大小,按比例产生控制作用。

1
2
error = target - current
output_p = Kp * error

特点

  • ✅ 响应快速
  • ❌ 存在稳态误差
  • ❌ Kp过大会振荡

I - 积分控制

作用:累积历史误差,消除稳态误差。

1
2
error_sum += error * dt
output_i = Ki * error_sum

特点

  • ✅ 消除稳态误差
  • ❌ 可能导致积分饱和
  • ❌ 响应变慢

D - 微分控制

作用:根据误差变化率,预测趋势,提前调整。

1
2
error_rate = (error - last_error) / dt
output_d = Kd * error_rate

特点

  • ✅ 减少超调
  • ✅ 提高稳定性
  • ❌ 对噪声敏感

完整PID实现

标准PID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class PID:
def __init__(self, Kp, Ki, Kd, setpoint=0):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.setpoint = setpoint

self.last_error = 0
self.integral = 0
self.last_time = time.time()

def update(self, current_value):
# 计算时间间隔
current_time = time.time()
dt = current_time - self.last_time

# 计算误差
error = self.setpoint - current_value

# 比例项
P = self.Kp * error

# 积分项
self.integral += error * dt
I = self.Ki * self.integral

# 微分项
derivative = (error - self.last_error) / dt if dt > 0 else 0
D = self.Kd * derivative

# 总输出
output = P + I + D

# 更新状态
self.last_error = error
self.last_time = current_time

return output

def reset(self):
self.integral = 0
self.last_error = 0

增量式PID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class IncrementalPID:
def __init__(self, Kp, Ki, Kd):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd

self.last_error = 0
self.prev_error = 0

def update(self, error):
# 增量计算
delta_output = (
self.Kp * (error - self.last_error) +
self.Ki * error +
self.Kd * (error - 2 * self.last_error + self.prev_error)
)

# 更新误差
self.prev_error = self.last_error
self.last_error = error

return delta_output

PID改进技巧

1. 积分限幅

防止积分饱和:

1
2
3
4
5
6
7
8
9
def update(self, current_value):
# ... 前面代码 ...

# 积分限幅
self.integral += error * dt
self.integral = max(min(self.integral, self.integral_max), -self.integral_max)

I = self.Ki * self.integral
# ...

2. 微分滤波

降低噪声影响:

1
2
3
4
5
# 一阶低通滤波
alpha = 0.1 # 滤波系数
filtered_derivative = alpha * derivative + (1 - alpha) * self.last_derivative
self.last_derivative = filtered_derivative
D = self.Kd * filtered_derivative

3. 抗积分饱和

1
2
3
# 条件积分
if abs(error) < error_threshold:
self.integral += error * dt

4. 输出限幅

1
output = max(min(output, output_max), output_min)

5. 变速积分

误差大时减小积分,误差小时增大积分:

1
2
3
4
coefficient = 1.0
if abs(error) > threshold:
coefficient = 0.5
I = self.Ki * self.integral * coefficient

参数整定方法

经验法(适合入门)

  1. 先调P

    • 从小开始增大Kp
    • 直到系统出现轻微振荡
    • 然后减小到稳定
  2. 再调D

    • 增大Kd减少超调
    • 过大会导致响应缓慢
  3. 最后调I

    • 从小开始增大Ki
    • 消除稳态误差

Ziegler-Nichols法

  1. 设Ki=0, Kd=0
  2. 增大Kp直到系统临界振荡,记录Ku和Tu
  3. 按表格设置参数:
控制器类型 Kp Ki Kd
P 0.5*Ku - -
PI 0.45*Ku 0.54*Ku/Tu -
PID 0.6*Ku 1.2*Ku/Tu 0.075KuTu

试凑法口诀

1
2
3
4
5
6
7
8
9
参数整定找最佳,从小到大顺序查
先是比例后积分,最后再把微分加
曲线振荡很频繁,比例度盘要放大
曲线漂浮绕大弯,比例度盘往小扳
曲线偏离回复慢,积分时间往下降
曲线波动周期长,积分时间再加长
曲线振荡频率快,先把微分降下来
动差大来波动慢,微分时间应加长
理想曲线两个波,前高后低4比1

实战案例

案例1:温度控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 烤箱温度PID控制
pid = PID(Kp=2.0, Ki=0.5, Kd=0.1, setpoint=200)

while True:
current_temp = read_temperature()

# PID计算
heater_power = pid.update(current_temp)

# 输出限幅(0-100%)
heater_power = max(0, min(100, heater_power))

# 控制加热器
set_heater(heater_power)

time.sleep(0.1)

案例2:电机速度控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MotorPID:
def __init__(self):
self.pid = PID(Kp=0.8, Ki=0.2, Kd=0.05)
self.pid.integral_max = 100
self.output_max = 255

def control(self, target_rpm, current_rpm):
self.pid.setpoint = target_rpm
pwm = self.pid.update(current_rpm)

# PWM限幅
pwm = max(-self.output_max, min(self.output_max, pwm))

# 控制电机
if pwm > 0:
motor.forward(abs(pwm))
else:
motor.backward(abs(pwm))

return pwm

案例3:机器人云台控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 双轴云台PID控制
class GimbalController:
def __init__(self):
self.pitch_pid = PID(Kp=1.2, Ki=0.1, Kd=0.3)
self.yaw_pid = PID(Kp=1.5, Ki=0.15, Kd=0.4)

def track_target(self, target_x, target_y):
# 图像中心坐标
center_x, center_y = 320, 240

# 计算误差
error_x = target_x - center_x
error_y = target_y - center_y

# PID控制
yaw_output = self.yaw_pid.update(error_x)
pitch_output = self.pitch_pid.update(error_y)

# 控制云台电机
gimbal.set_yaw(yaw_output)
gimbal.set_pitch(pitch_output)

调试技巧

1. 可视化曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import matplotlib.pyplot as plt

targets = []
actuals = []
outputs = []

plt.ion()
fig, (ax1, ax2) = plt.subplots(2, 1)

while running:
output = pid.update(current)

targets.append(pid.setpoint)
actuals.append(current)
outputs.append(output)

ax1.clear()
ax1.plot(targets, 'r--', label='Target')
ax1.plot(actuals, 'b-', label='Actual')
ax1.legend()

ax2.clear()
ax2.plot(outputs, 'g-', label='Output')
ax2.legend()

plt.pause(0.01)

2. 分环节测试

1
2
3
4
5
6
7
8
# 先测试P
pid = PID(Kp=1.0, Ki=0, Kd=0)

# 再加I
pid.Ki = 0.1

# 最后加D
pid.Kd = 0.05

3. 阶跃响应测试

1
2
3
4
# 突然改变目标值,观察响应
pid.setpoint = 100
time.sleep(5)
pid.setpoint = 150 # 阶跃

常见问题

1. 系统振荡

  • 原因:Kp过大或Kd过小
  • 解决:减小Kp,增大Kd

2. 响应慢

  • 原因:Kp过小
  • 解决:增大Kp

3. 有稳态误差

  • 原因:Ki=0或太小
  • 解决:增大Ki

4. 超调严重

  • 原因:Kd太小
  • 解决:增大Kd

总结

PID控制简单实用,但参数整定需要经验。建议:

  1. 理解三个环节的作用
  2. 从简单系统开始练习
  3. 记录参数与效果的关系
  4. 多实践,积累经验

推荐资源: