PID轨迹跟踪 | python实现
基本概念
关于PID的基本概念网上有很多,简单来说就是用P项(比例项)实现负反馈效果,用I项(积分项)消除稳态误差,用D项(微分项)提供误差信号微分的预测效果。
连续时间下的PID公式如下所示:
u
(
t
)
=
K
p
(
e
(
t
)
+
1
T
t
∫
0
t
e
(
t
)
d
t
+
T
D
d
e
(
t
)
d
t
)
u(t) = K_p\left(e(t) + \frac{1}{T_t}\int_{0}^{t}e(t)dt + T_D\frac{de(t)}{dt}\right)
u(t)=Kp(e(t)+Tt1∫0te(t)dt+TDdtde(t))
式中,
u
(
t
)
u(t)
u(t)表示PID控制器的输出,系统的控制输入;
e
(
t
)
=
r
e
f
−
f
e
e
d
b
a
c
k
e(t) = ref-feedback
e(t)=ref−feedback表示误差;
K
p
K_p
Kp表示比例增益系数;
T
t
T_t
Tt表示积分时间常数,
K
i
=
K
p
T
t
K_i = \frac{K_p}{T_t}
Ki=TtKp表示积分增益系数;
T
D
T_D
TD表示微分时间常数,
K
d
=
K
p
T
D
K_d = K_pT_D
Kd=KpTD表示微分增益系数。
离散时间下的PID公式如下所示:
u
(
k
)
=
K
p
(
e
(
k
)
+
1
T
t
∑
0
k
e
(
k
)
+
T
D
e
(
k
)
−
e
(
k
−
1
)
d
t
)
u(k) = K_p\left(e(k) + \frac{1}{T_t}\sum_{0}^{k}e(k) + T_D\frac{e(k) - e(k-1)}{dt}\right)
u(k)=Kp(e(k)+Tt10∑ke(k)+TDdte(k)−e(k−1))
式中,
d
t
dt
dt表示离散的周期,与连续时间公式中的
d
t
dt
dt表示微分时间单元不同;其它符号的含义与连续时间公式中的含义相同。离散时间的PID用求和代替了积分,用后向差分代替了微分。实际计算机控制时只能是离散的,所以对离散控制进行分析。
PID是一个无模型控制器,没有太多具体的数学上的原理,更多是根据工程中经验而来,因此很难解释公式为什么这么设计、为什么要用这三项,不必在此纠结,只需了解各项作用即可。
比例项 K p e ( k ) K_pe(k) Kpe(k)表示通过比例项增益 K p K_p Kp对误差进行粗调,向着减小误差的方向调整,给出第一部分输出;积分项 K p T t ∑ 0 k e ( k ) \frac{K_p}{T_t}\sum_{0}^{k}e(k) TtKp∑0ke(k)表示对误差的累计求和,如果存在稳态误差,这一项通过累加放大并向着减小误差的方向调整,给出第二部分输出;微分项 K p T D d t ( e ( k ) − e ( k − 1 ) ) \frac{K_pT_D}{dt}(e(k) - e(k-1)) dtKpTD(e(k)−e(k−1))表示通过误差信号过去对时间的导数,对误差信号未来的一个预测(这里是假设误差信号一阶时间连续,而在实际情况中也大多如此),提前向着减小误差的方向调整,给出第三部分输出。最后对三部分的输出进行求和,得到最终的PID输出 u ( t ) u(t) u(t)。
问题描述
用PID实现轨迹跟踪,就是计算前轮转向角 δ f \delta_f δf,使得车辆与参考轨迹上的参考点 P r = ( x r , y r ) P_r=(x_r, y_r) Pr=(xr,yr)之间的横向误差 e y e_y ey趋近与0。那么这里有两个问题,一是参考点 P r P_r Pr怎么找,二是横向误差 e y e_y ey怎么算。
找参考点
参考点就是人为定义的一个点,可以找当前离自车最近的点为参考点,也可以类似于纯跟踪控制里面找一个预瞄点作为参考点。
实际仿真做下来感觉找离自车最近的点有一个问题,就是车必须在有移动的情况下打方向才会向着参考轨迹移动。但是以参考线上最近点为参考点的话,会导致车还没靠近前一个参考点就要向后一个参考点运动了,就是说留给车的运动空间或者调整距离不够,整个车的行为很滞后。
因此可以考虑找一个预瞄点为参考点,这样就能打一个提前量,车的行为不至于滞后,在轨迹跟踪时能表现好一点。
算横向误差 e y e_y ey
![](https://img-blog.csdnimg.cn/img_convert/762f1523f98f5b6f340547f4dc1eb102.png)
e
y
=
l
d
sin
(
θ
e
)
l
d
=
(
x
r
−
x
h
)
2
+
(
y
r
−
y
h
)
2
θ
e
=
α
−
ψ
α
=
arctan
(
y
r
−
y
h
x
r
−
x
h
)
\begin{aligned} &e_y = l_d\sin(\theta_e)\\ &l_d = \sqrt{(x_r - x_h)^2 + (y_r - y_h)^2}\\ &\theta_e = \alpha - \psi\\ &\alpha = \arctan{(\frac{y_r - y_h}{x_r - x_h})} \end{aligned}
ey=ldsin(θe)ld=(xr−xh)2+(yr−yh)2θe=α−ψα=arctan(xr−xhyr−yh)
其中
P
h
=
(
x
h
,
y
h
)
P_h = (x_h, y_h)
Ph=(xh,yh)表示自车的当前坐标点。
PID控制的目标是让某个误差量为0,这里以横向误差 e y e_y ey为误差量,其中 l d l_d ld始终为正,误差量的正负号由 sin ( θ e ) \sin{(\theta_e)} sin(θe)提供。就是说参考点在自车左侧时误差为正,参考点在自车右侧时误差为负。需要注意下编程时跟自车方向盘打角的一个正负关系对应,方向盘打角往左为负往右为正时需要加一个负号。
应用demo
PID轨迹跟踪演示的完整代码在github仓库,这里仅给出PID计算部分。图中轨迹跟踪效果比较一般的原因个人理解有几点,一是PID是无模型控制,这里只用了一套参数进行控制,像LQR和MPC好歹模型参数也在实时更新以及在实时求解;二是控制周期给太短了,参考点实时在更新;三是参数没有仔细整定。有兴趣的话可以从这三个角度入手进行改进,以提升轨迹跟踪效果。
![](https://img-blog.csdnimg.cn/img_convert/ccfd566a20793ee9d0ac5066d4441662.gif)
class PID:
"""位置式实现
"""
def __init__(self, upper, lower, k = [1., 0., 0.]):
self.kp, self.ki, self.kd = k
self.e = 0.0 # error
self.pre_e = 0.0 # previous error
self.sum_e = 0.0 # sum of error
self.upper_bound = upper # upper bound of output
self.lower_bound = lower # lower bound of output
def set_param(self, k, upper, lower):
self.kp, self.ki, self.kd = k
self.upper_bound = upper
self.lower_bound = lower
def cal_output(self, ref, feedback): # calculate output
self.e = ref - feedback
pid_out = self.e * self.kp + self.sum_e * self.ki + (self.e - self.pre_e) * self.kd
if pid_out < self.lower_bound:
pid_out = self.lower_bound
elif pid_out > self.upper_bound:
pid_out = self.upper_bound
self.pre_e = self.e
self.sum_e += self.e
return pid_out