定义
PID, Proportional, Integral and Derivative, 比例-积分-微分 控制系统.
一种 “控制-观察-调整控制-再观察” 的迭代方法. 每轮迭代中, 得到系统的输出后,计算与预定目标的误差, 包括 {比例,积分,微分} 3种表达,将其叠加到输入中,从而控制系统的行为.
直接上公式.
u
(
t
)
=
P
+
I
+
D
=
K
p
∗
e
(
t
)
+
K
i
∗
∫
0
t
e
(
t
)
d
t
+
K
d
∗
d
e
(
t
)
d
t
(1)
u(t)=P+I+D \\ = K_p*e(t)+K_i*\int_0^t e(t)\mathrm dt+K_d*\frac{\mathrm d\ e(t)}{\mathrm dt} \tag 1
u(t)=P+I+D=Kp∗e(t)+Ki∗∫0te(t)dt+Kd∗dtd e(t)(1)
适用场景
自动驾驶中, 希望汽车提速并稳定到100Km/h, 应该怎么控制动力输出(油门)?
整个系统中有 {风向风力, 地面摩擦力(干燥/湿滑), 汽车载重, 轮胎温度}
等多个影响因素. 控制变量为汽车油门, 即前进动力
.
当一切都已知, 自然可以直接计算出来最优的动力分配函数f(t). 但这个假设太理想了, 并且计算难以完成, 所以需要 “控制-观察反馈-调整控制” 这样迭代去完成任务.
工作原理举例
同上汽车加速的任务.
e
(
t
)
=
100
−
v
t
e(t)=100-v_t
e(t)=100−vt, 表示当前车速与目标的差值.
u(t)表示各策略叠加后的当前动力输出.
proportional-比例部分
一开始e最大, 乘以固定的系数 P 后也较大.
随着速度的提升, 该控制项的输出会逐步减弱.
但因为风力, 地面摩擦力等原因, 会在某个时刻与 该控制项输出 相等, 速度再也提不上去, 形成稳态误差.
integral-积分部分
离散情况下, 积分就是求和, 把历史上近几轮迭代的 e(t) 相加.
功用:
- 达到稳态误差后, 该控制项的和继续增大, 叠加到动力上可以消除稳态误差, 最终达到目标.
- p部分力量较弱时, 方便加速提前达到目标.
derivative-微分部分
离散情况下, 微分就是
Δ
e
=
e
(
t
)
−
e
(
t
−
1
)
\Delta e=e(t)-e(t-1)
Δe=e(t)−e(t−1),
汽车加速中, 离目标值越来越近, 所以
Δ
e
\Delta e
Δe为负, 与一个整数D相乘后依旧为负数.
功用:
- 如果动力足够大, 离散的间隔也大, 就会超速, 此控制项用于避免超速, 避免震荡.
可视化仿真
见参考[1]文末.
调参经验
Kp,Ki,Kd 三个参数是 task-specific 的, 通常通过仿真系统人工寻参而得.
Kp 是基础部分, 可先控制 Ki, Kd 两项为0, 二分法迭代.
继续 调整 Ki 与 Kd. 当一个系统没有稳态误差时, Ki 可以置0.
python简易实现
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.axes
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
class Car:
"""
被控制系统是一辆汽车
新的速度=原有速度+加速度-阻力
"""
def __init__(self):
self.mass = 100
self.velocity = 0
self.accelerated_velocity_arr = []
def get_current_speed(self, force):
force_accelerated_velocity = force / self.mass
wind_accelerated_velocity = - self.velocity * 0.1 / self.mass
self.velocity = self.velocity + force_accelerated_velocity + wind_accelerated_velocity
self.accelerated_velocity_arr.append(force_accelerated_velocity + wind_accelerated_velocity)
return self.velocity
class PIDController:
"""
输出
"""
def __init__(self, target_val, kp, ki, kd):
self.target_val = target_val
self.controlled_system = Car()
self.kp = kp
self.ki = ki
self.kd = kd
self.out_put_arr = [0]
self.observed_val_arr = []
self.now_val = 0
self.sum_err = 0
self.now_err = 0
self.last_err = 0
def iterate(self):
self.observed_val_arr.append(self.controlled_system.get_current_speed(self.out_put_arr[-1]))
self.now_err = self.target_val - self.observed_val_arr[-1]
# 这一块是严格按照公式来写的
out_put = self.kp * self.now_err \
+ self.ki * self.sum_err \
+ self.kd * (self.now_err - self.last_err)
self.out_put_arr.append(out_put)
self.last_err = self.now_err
self.sum_err += self.last_err
return out_put
# 对pid进行初始化,目标值是1000 ,Kp=0.1 ,Ki=0.15, Kd=0.1
controller = PIDController(100, 3, 0.1, 0.2)
# 然后循环100次把数存进数组中去
for i in range(0, 300):
controller.iterate()
print('controller.out_put_arr,', controller.out_put_arr)
print('car.ccelerated_velocity_arr,', controller.controlled_system.accelerated_velocity_arr)
print('controller.observed_val_arr,', controller.observed_val_arr)
fig, ax1 = plt.subplots()
ax1 = ax1 # type: matplotlib.axes.Axes
ax1.set_xlabel('iterations')
ax1.set_ylabel('force_out_put (N)', color='red')
# ax1.set_ylim()
ax1.plot(controller.out_put_arr, color="red", label='force_out_put')
ax1.legend(loc=2)
ax2 = ax1.twinx()
ax2.plot(controller.observed_val_arr, color='blue', label='car_speed')
ax2.set_ylabel('car_speed (Km/h)',color='blue')
ax2.legend(loc=1)
plt.title('PID 控制系统示意')
plt.show()
参考
- 知乎, PID控制算法原理
- 知乎, 广告出价–如何使用PID控制广告投放成本
- mathworks 无人机例子视频讲解, What is PID Control?