你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

PID控制器算法

[复制链接]
gaosmile 发布时间:2020-9-25 22:07
网上关于PID算法的文章很多,但是感觉有必要自己再进行一次总结,抽丝剥茧地重新认识了一下PID;
1 前言" A; j0 l' _; g6 a' `' J
控制系统通常根据有没有反馈会分为开环系统和闭环系统,在闭环系统的控制中,PID算法非常强大,其三个部分分别为;
  • P:比例环节;
  • I:积分环节;
  • D:微分环节;1 H6 `& A. M  b# t' @
PID算法可以自动对控制系统进行准确且迅速的校正,因此被广泛地应用于工业控制系统。
2 开环控制
首先来看开环控制系统,如下图所示,隆哥蒙着眼,需要走到虚线旗帜所表示的目标位置,由于缺少反馈(眼睛可以感知当前距离和位置,由于眼睛被蒙上没有反馈,所以这也是一个开环系统),最终隆哥会较大概率偏离预期的目标,可能会运行到途中实线旗帜所表示的位置。
微信图片_20200925215752.png
开环系统的整体结构如下所示;
微信图片_20200925215755.png
这里做一个不是很恰当的比喻;) o2 S  g6 o* d  ?. m" m4 a5 Y' y0 x
  • Input:告诉隆哥目标距离的直线位置(10米);
  • Controller:隆哥大脑中计算出到达目标所需要走多少步;
  • Process:双腿作为执行机构,输出了相应的步数,但是最终仍然偏离了目标;
    " c7 A; `4 R. Q/ z7 R
看来没有反馈的存在,很难准确到达目标位置。
3 闭环控制
所以为了准确到达目标位置,这里就需要引入反馈,具体如下图所示;
微信图片_20200925215759.png
在这里继续举个不怎么恰当的比喻;隆哥重获光明之后,基本可以看到目标位置了;
  • 第一步Input:告诉隆哥目标距离的直线位置(10米);
  • 第二步Controller:隆哥大脑中计算出到达目标所需要走多少步;
  • 第三步Process:双腿作为执行机构,输出了相应的步数,但是最终仍然偏离了目标;
  • 第四步Feedback:通过视觉获取到目前已经前进的距离,(比如前进了2米,那么还有8米的偏差);
  • 第五步err:根据偏差重新计算所需要的步数,然后重复上述四个步骤,最终隆哥达到最终的目标位置。. s' K4 H% w& a# t; f
    ! _; U0 C/ ?" s. C& H
4 PID4.1 系统架构
虽然在反馈系统下,隆哥最终到达目标位置,但是现在又来了新的任务,就是又快又准地到达目标位置。所以这里隆哥开始采用PID Controller,只要适当调整P,I和D的参数,就可以到达目标位置,具体如下图所示;
微信图片_20200925215802.png
隆哥为了最短时间内到达目标位置,进行了不断的尝试,分别出现了以下几种情况;
  • 跑得太快,最终导致冲过了目标位置还得往回跑;
  • 跑得太慢,最终导致到达目标位置所用时间太长;1 |3 b6 P3 Q% S4 g: x, ^" ^$ i
经过不断的尝试,终于找到了最佳的方式,其过程大概如下图所示;
微信图片_20200925215805.gif
这里依然举一个不是很恰当的比喻;
8 w& i. G8 Y" O* ]
  • 第一步:得到与目标位置的距离偏差(比如最开始是10米,后面会逐渐变小);
  • 第二步:根据误差,预估需要多少速度,如何估算呢,看下面几步;, Y" [- w' V. H- }% E# T
P比例则是给定一个速度的大致范围,满足下面这个公式;
因此比例作用相当于某一时刻的偏差(err)与比例系数的乘积,具体如下所示; 微信图片_20200925215809.png 比例作用
绿色线为上述例子中从初始位置到目标位置的距离变化;红色线为上述例子中从初始位置到目标位置的偏差变化,两者为互补的关系;

I积分则是误差在一定时间内的和,满足以下公式;
1601042693(1).png
如下图所示;
微信图片_20200925215812.png
红色曲线阴影部分面积即为积分作用的结果,其不断累积的误差,最终乘以积分系数就得到了积分部分的输出;
D微分则是误差变化曲线某处的导数,或者说是某一点的斜率,因此这里需要引入微分;
1601042724(1).png
微信图片_20200925215816.png
从图中可知,当偏差变化过快,微分环节会输出较大的负数,作为抑制输出继续上升,从而抑制过冲。

综上,,,,分别增加其中一项参数会对系统造成的影响总结如下表所示;[td]
参数
上升时间
超调量
响应时间
稳态误差
稳定性
Kp/ d3 Z5 o* C( I6 T
减少增加小变化减少降级
Ki. Q  c& [( @' Q9 ?  E, B7 K, Y. {
减少增加增加消除降级
Kd( ~0 G1 s5 e' f; a
微小的变化减少减少理论上没有影响小,稳定性会提升
4.2 理论基础
上面扯了这么多,无非是为了初步理解PID在负反馈系统中的调节作用,下面开始推导一下算法实现的具体过程;PID控制器的系统框图如下所示;
微信图片_20200925215819.png 图片来自Wiki因此不难得出输入和输出的关系;
微信图片_20200925215823.png
是比例增益;是积分增益;是微分增益;
4.3 离散化, p1 A! h$ _$ G; h$ O7 j! t
在数字系统中进行PID算法控制,需要对上述算法进行离散化;假设系统采样时间为则将输入序列化得到;
微信图片_20200925215827.png
将输出序列化得到;
微信图片_20200925215830.png
5 E2 F( c  y* f! n
微信图片_20200925215833.png
所以最终可以得到式①,也就是网上所说的位置式PID:
微信图片_20200925215837.png
将式①再做一下简化;
1601042803(1).png
最终得到增量式PID的离散公式如下:
微信图片_20200925215841.png
. `) _. E, o! G
4.4 伪算法
这里简单总结一下增量式PID实现的伪算法;

- S, @) Q" L# Q) H1 B. _5 r6 gprevious_error := 0  //上一次偏差3 x7 C+ E. w+ ~# ^* X1 p) M
integral := 0   //积分和
# V$ J+ y& H4 X( L9 k! [) H) ~1 G8 S! h9 ~4 ~* I7 P
//循环
& X1 x$ e" |% P5 x3 a//采样周期为dt
! Q7 k$ _( Y! }loop:
0 F+ E2 B5 F; y$ {4 E2 B- | //setpoint 设定值
. D7 {6 P- v% v" d. J //measured_value 反馈值
# D2 [, ?4 G% ]    error := setpoint − measured_value //计算得到偏差+ A; m3 ~. D2 W3 F4 ^$ p
    integral := integral + error × dt //计算得到积分累加和7 B/ k6 M' ?  Y( I3 I
    derivative := (error − previous_error) / dt //计算得到微分
, G2 d6 W3 M8 M    output := Kp × error + Ki × integral + Kd × derivative //计算得到PID输出' z* S; Q" H. S% ]6 {; v& Z
    previous_error := error //保存当前偏差为下一次采样时所需要的历史偏差
. X- `0 [  E; y; W) F6 I+ i    wait(dt) //等待下一次采用% |2 [5 l- p9 z7 \+ j& S, m
    goto loop' E5 Y4 T' e$ _$ g' s2 s

0 c. f2 W3 G0 E  }, G# P5 a
5 C++实现
这里是增量式PID算法的C语言实现;
pid.cpp
#ifndef _PID_SOURCE_
; v3 v0 D! v( ]3 B$ O& ~#define _PID_SOURCE_
8 k' Z2 a/ _  ]+ {5 ?: F- t7 ~" f# a& ~2 B" D' u! P! ]- }
#include
  I4 y: g/ {/ f6 f4 H#include
. b3 i8 [3 X" q7 m9 e1 J: X9 m#include "pid.h"
$ `% f' h% K7 m1 n; d  C9 V
: R) t. w8 c$ V0 Rusing namespace std;6 T  V2 W/ s/ r: t
# O5 W9 ]2 f, E9 U3 Z* b
class PIDImpl) h# b0 d, h) \( l
{6 a2 X8 K+ B. T6 K6 _. E8 L
    public:& a1 E1 a7 T/ c8 {, N5 Y+ p' f6 P. Q( H
        PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki );! g. \, d, k+ i% @1 B5 m
        ~PIDImpl();6 ]$ M* S- ~( P1 ^" _$ x$ I
        double calculate( double setpoint, double pv );+ E+ N& C3 {8 B. o; M

' ~( a* |: o% Y    private:
/ }7 b; p: _- S9 L, K0 E        double _dt;
  k. k, P5 J9 \        double _max;
5 O9 {  A0 X$ _* ~2 K1 G: }3 @        double _min;: _2 U! X) n/ u
        double _Kp;& R& k- j0 C! k8 M7 o. P
        double _Kd;
+ F0 R" Y4 l' c' `! ^9 c        double _Ki;
4 `9 e& h) J, K( Z% t8 ]        double _pre_error;
! G6 y8 W, ?+ ]# Q        double _integral;2 Z5 u7 e/ @! T3 N9 I2 \" @
};
5 z$ G3 f1 z4 @( ]# S4 T- g
% R7 X& T# D5 R0 ?' J: K- }8 R+ R2 B2 n8 E
PID:ID( double dt, double max, double min, double Kp, double Kd, double Ki )# z: `- O8 C7 l: w- `5 T  V
{
8 v1 W" Q" _3 t. b    pimpl = new PIDImpl(dt,max,min,Kp,Kd,Ki);2 [! Y; V& h; M& _
}
) \8 v! I7 O" g. V) U* vdouble PID::calculate( double setpoint, double pv )3 K4 A; V9 ]2 m" R3 y" |& b3 h5 u
{0 k) X, O  N  ?$ N1 Z
    return pimpl->calculate(setpoint,pv);
1 E3 n) _! A8 E% P, D- L0 ~) d+ Y1 [}2 u& Z, R; y3 @9 i1 x/ `& a; w) k
PID::~PID() 6 h0 |! l1 u1 F
{
4 a0 F) C+ R2 @, \# n% v    delete pimpl;; w! e9 K2 C( k$ N/ U4 K! ?4 j
}
2 L+ t1 i) I/ Z% i* y
* m; m( s& B4 w9 M
. q- i" x/ T2 S6 E/ ^; W3 c/**2 E" `" i3 p9 M) M+ |3 P* r3 T
* Implementation
+ U% c2 P5 w0 [3 {1 b8 ~; `) Q */

% i! r/ B# x; g0 s3 N! RPIDImpl:IDImpl( double dt, double max, double min, double Kp, double Kd, double Ki ) :2 {% C3 l, }! ]1 j' H2 N
    _dt(dt),8 o% j4 s$ C% M# O2 j5 y
    _max(max)," U6 R: [. _3 ?0 h5 g0 |
    _min(min),8 r5 E% ~2 w5 b( p% n2 m1 `
    _Kp(Kp),
$ u( ^, [9 t/ w; s) ]    _Kd(Kd),
% x9 G6 p) q$ l) Q; G    _Ki(Ki),  Y0 Z6 M+ s7 {2 r$ T+ V$ n; d
    _pre_error(0),+ z# r( N) g% L1 X
    _integral(0)3 k# [8 C, Q% P. G
{
9 ~. n9 l* J  M- \9 U: ~}# Q5 S: L: _' G$ _  j$ K2 f
1 f6 _* y: m( A1 `9 u5 x! i( h" K
double PIDImpl::calculate( double setpoint, double pv )
' U: t; \7 M! D9 h7 q; W{
# G7 h3 u' P: H, C    ) h, ~$ S  ~  R6 t
    // Calculate error0 D) f" W% J0 M& ]! b4 r( \
    double error = setpoint - pv;
, a3 [6 J( g: J/ u4 r) R  L- O( K4 a, \& k
    // Proportional term6 n. s! U& m6 D% Y
    double Pout = _Kp * error;
. m* O9 m3 j: B: R3 f9 `
5 C" |9 `$ X1 q# Y3 ]& g    // Integral term
" j3 w% }& w1 |) d' i6 D8 _    _integral += error * _dt;
0 u' X- [* h4 A% N2 M4 c    double Iout = _Ki * _integral;) p8 H' I* n, }
8 \* c9 H8 N; T
    // Derivative term
/ L1 d8 A4 H- g1 [" Q& V. Y    double derivative = (error - _pre_error) / _dt;
2 S- r3 u6 _5 W& o' e    double Dout = _Kd * derivative;
8 ?- m2 L8 p  F$ W" z* G
$ |  X  O- i- E: N& c: H2 @8 U4 Y    // Calculate total output, g- E+ S( d* D
    double output = Pout + Iout + Dout;8 @6 Q* m7 d. r3 N
: r; X, h; |$ {2 o; l& @
    // Restrict to max/min
6 O! k" U! g+ \- b' ?" i0 S: m    if( output > _max )
& a6 r5 U: X8 p& q3 l9 Q        output = _max;* ~; Y* k9 f% i
    else if( output < _min )" B+ C, D( a( b) c' K) }
        output = _min;3 c9 M( D+ E9 p) v1 M# B" O
+ g+ j% v6 S, `6 L8 B6 c% t
    // Save error to previous error9 N  @% A$ O* i' u
    _pre_error = error;5 A! Y% g4 Q' J7 Q1 L, M

8 `4 }% w& O. r+ j, S  Q    return output;
# A# h# H- F6 J2 m4 w) i6 c}5 f6 w2 U0 y4 [( C

: j2 L* p# _7 ^8 DPIDImpl::~PIDImpl()
' k0 _, `! M& g  Z* I+ F0 {{+ h# S7 m2 _2 `. C! M9 F! R
}
" w1 n9 L) z6 C( e' F; M% @1 Y! |4 O
#endif
2 O! n3 A3 Y" m6 n6 W% U: h
pid.h
#ifndef _PID_H_5 C1 s9 B/ k( z0 {
#define _PID_H_
7 V7 n. s/ x$ i$ z; {- j1 u8 d% e3 K/ X5 W- u& O- j
class PIDImpl;% n/ i; X4 U& |- q6 b3 I
class PID
7 P+ w6 h) O+ T' t' w. e! _{
" ]! Y' l( t: N2 `    public:: v. ]3 x( Y0 A- [& V2 \
        // Kp -  proportional gain  h* O4 @& B' B4 |& Y2 g! q
        // Ki -  Integral gain
4 d# a( m# ^& g        // Kd -  derivative gain' o0 r; i. ~9 g; h$ x/ p
        // dt -  loop interval time
/ Q' z7 Z( s& m# G$ H        // max - maximum value of manipulated variable. S( O3 q  a' E4 A: i* Z, B" K
        // min - minimum value of manipulated variable3 A- j, j- B+ B3 i7 O9 i
        PID( double dt, double max, double min, double Kp, double Kd, double Ki );
  |+ v/ g. P/ I& f
( c+ A+ `$ i4 O- |/ {9 l. @( U        // Returns the manipulated variable given a setpoint and current process value
4 ?& _0 W# z8 w, n: g  S! p        double calculate( double setpoint, double pv );
6 I5 M/ b: G* m% M        ~PID();
  W* l+ p/ \! q# b' ~  `  @! R" Y" v3 S% O, u) `
    private:+ V) P, C( Z( k+ D; Q
        PIDImpl *pimpl;* w5 O: v$ O5 V/ N
};
/ U. |# W2 g7 {6 I; _- }
' V. V* U3 c- W& C  r# X) _#endif4 Y. I2 e! P, a& f  A
pid_example.cpp
#include "pid.h"3 r. q1 ^4 T) v- s$ i! X0 C% d" X+ S
#include 7 f7 S+ p* _5 X- s) f& \+ f2 Q
- K) }7 K( `6 \
int main() {1 B, U1 C+ o- v( {% ^, i
- z" {: ?( z( ?2 @1 {) G
    PID pid = PID(0.1, 100, -100, 0.1, 0.01, 0.5);
) e+ U1 r- x; ?4 E/ G7 |; [) G7 D) N) h# s+ O% c- w
    double val = 20;$ M9 q7 Q7 w0 L# {
    for (int i = 0; i < 100; i++) {
& s) z# b/ i5 u4 J) K        double inc = pid.calculate(0, val);
1 F2 n& R8 I% G        printf("val:% 7.3f inc:% 7.3f\n", val, inc);- w; Q; C: _9 m; S. Z: p7 j+ g7 Z( k' m$ c
        val += inc;
$ v8 u; ]+ \: T, B    }
7 I% H5 C, X, ?# i# g
* J+ S+ P# y6 D0 `' k- O) J    return 0;
4 ?' b% |; u) {! h7 G8 C: m}
  _0 k* b3 P& N& G, V& n/ g
编译并测试;
g++ -c pid.cpp -o pid.o
4 I% s7 ~$ ^3 x5 R8 V$ V/ M# To compile example code:! h+ s3 w+ N- J
g++ pid_example.cpp pid.o -o pid_example" W0 N# \1 E" @: {( V% V6 a6 s: \
6 总结
本文总结了PID控制器算法在闭环系统中根据偏差变化的具体调节作用,每个环节可能对系统输出造成什么样的变化,给出了位置式和增量式离散PID算法的推导过程,并给出了位置式算法的C++程序实现。

+ ~/ \0 |, R* {. G) H' S, B) J' r: W' z
赞 1 收藏 1 评论1 发布时间:2020-9-25 22:07

举报

1个回答
jeffhe1 回答时间:2020-9-26 08:56:23
感謝大神, 真是淺顯易懂
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版