引入
PID即:Proportional
(比例)、Integral
(积分)、Differential
(微分)的缩写。顾名思义,PID控制算法是结合比例、积分和微分三种环节于一体的控制算法
PID控制的实质就是根据输入的偏差值,按照比例、积分、微分的函数关系进行运算,运算结果用以控制输出
偏差
偏差
即预定目标和当前状态之间的差值
我们设 预定值target
是我们希望系统平衡时传感器的返回值
传感器实时返回的数值为measure
那么根据偏差
的定义,我们可以得到偏差
的计算式:
其中error
为我们所求的偏差值
比例算法
成比例地反映控制系统的偏差信号,偏差一旦产生,立即产生控制作用以减小偏差。
比例算法的核心思想非常简单:离目标越远,就应该调整的越快
我们举 量取1L水
的例子来说明
如果桶里面只有 10ml 水 ,那么你倒水的时候就会哗哗的往里面倒
而如果此时 桶快满了 ,你为了不倒多了,你就会选择慢慢的向下加
将这样的思想抽象成数学语言:
记比例算法的输出值为 P
那么 error
越大 , P
就应该越大
即
其中 是我们引入的系数,称为比例系数
积分算法
积分环节的作用,主要用于消除静差提高系统的无差度。
定义
积分算法是对比例算法的补充
还是量取1L水
的例子,但是这次桶破了一个洞:
为了方便说明,不妨假设每秒的漏水为 ,
你还是用比例算法
的思想去倒水
然而在你加水加到了时,你会发现此时水位已经不再上涨
这是因为此时你加水的速度是
和漏水的速度持平
如果你可以关注到水位一直都在处这个现象的话,你可以尝试根据累计水位和目标之间的偏差来计算你到底应该额外加入多少的水
记积分算法的输出值为I
,那么:
即
其中为我们引入的常量积分系数
为pid算法从开始运行到当前时间的计时
离散化
显然,的情况在现实中是无法实现的,我们只能采取近似的计算方法
积分算法的理论计算式离散化后可以用下式计算:
用C++实现:
1 2
| I = I + ki*error*(time_now - time_last); time_last = time_now;
|
积分限制
引入积分算法后可能出现以下情况:
- 以电机为例,在电机的启停或设定值大幅变化时,系统在较短时间内产生了很大的偏差。此时积分迅速积累,就会造成控制量输出远远大于电机的极限输入控制量,从而会引起很大的超调,甚至会产生震荡。
- 积分饱和:当系统一直存在一个方向的偏差时,积分会不断增大,会造成控制量进入饱和区,一旦出现反向的偏差时,需要很长时间才能推出饱和区,而去响应反向偏差。也以电机为例,电机在积分饱和时,电机响应延时较大,会出现电机超出目标位置,需来回调整数次才能稳定。
此时就有必要限制积分算法的输出
如果超出了某一预定的范围,可以对I的大小进行限制
1 2 3 4 5 6 7 8 9
| void I_limit(float& I,float I_max){ if (I>I_max){ I = I_max; } else if (I<-I_max){ I = -I_max; } return; }
|
积分分离
积分分离同样是一种避免过调的手段
既然积分算法的目的是消除静差,而且会在偏差值过于大的时候异常运行,那么不妨对积分算法做这样的限制:需要积分算法的时候再开启积分算法,不需要就关闭
设定积分控制阈值,根据和的大小关系来决定积分项的开关系数的取值:
微分算法
微分环节的作用能反映偏差信号的变化趋势(变化速率),并能在偏差信号的值变得太大之前,在系统中引入一个有效的早期修正信号,从而加快系统的动作速度,减小调节时间。
定义
微分算法同样是对比例算法的补充
这次的例子换成在手指上面立筷子
吧
立筷子还不简单,你轻易的让筷子保持了竖直
正当我们放松的时候,一阵阴风袭来,把筷子稍稍吹歪了
看来我们需要新的方法
立筷子之所以容易失败,是因为筷子只要有一点偏差,如果不迅速的加以修正,筷子就会快速的倒下
如果在和的基础上,再增加一项,使得
问题就可以迎刃而解
注意到微分
是衡量瞬时变化率
的很好的数学工具,于是我们可以写出:
即
其中为我们引入的微分系数
离散化
同样的,由于在现实中无法实现,微分算法的计算同样需要离散化
用c++实现:
1 2 3
| D = kd*(error-error_last)/(time_now-time_last); error_last = error; time_last = time_now;
|
微分先行
在某些给定值频繁且大幅变化的场合,微分项常常会引起系统的振荡。这是因为我们的微分项是对偏差值的微分,当短时间内实际值变化不大而目标值突变的情况下,偏差值会发生突变,从而让微分项错误的介入控制过程。
明明实际值没有多大变化,微分项没理由发生突变啊。那只对实际值微分不就好了
综合使用
使用、、三种算法,足以满足大多数输出的控制了
总输出值的理想计算式:
如果全部带入:
离散化后可以用C++实现
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| # pragma once #include "main.h"
typedef struct { int frequency;
int target;
float measure; float last_measure;
float error; float p; float k_p;
float i; float k_i; float i_limit; int episilon; uint8_t beta;
float d; float k_d;
float max_output; }PID;
uint8_t PID_Init( PID* pid, float k_p, float k_i, float k_d, float target, int frequency, float max_output, float i_limit, float episilon );
float PID_Calc( PID* pid, float measure);
|
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| # include "pid.h"
uint8_t PID_Init( PID* pid, float k_p, float k_i, float k_d, float target, int frequency, float max_output, float i_limit, float episilon ) { if (pid == NULL || frequency <= 0 || max_output < 0 || i_limit < 0) { return 0; }
pid->k_p = k_p; pid->k_i = k_i; pid->k_d = k_d; pid->target = target; pid->frequency = frequency; pid->max_output = max_output; pid->i_limit = i_limit; pid->episilon = episilon;
pid->error = 0.0f; pid->p = 0.0f; pid->i = 0.0f; pid->d = 0.0f; pid->beta = 0; pid->last_measure = 0.0f;
return 1; }
float PID_Calc(PID* pid, float measure) { pid->measure = measure; pid->last_measure = pid->last_measure * 0.9f + measure * 0.1f; pid->error = pid->target - measure;
pid->p = pid->k_p * pid->error;
if ( fabs(pid->error) < pid->episilon ) { pid->beta = 1; } else { pid->beta = 0; }
if ( pid->beta ) { pid->i += pid->k_i * pid->error / pid->frequency ; if ( fabs(pid->i) > pid->i_limit ){ pid->i = pid->i > 0 ? pid->i_limit : -pid->i_limit; } } else { pid->i = 0; }
pid->d = (measure - pid->last_measure) * pid->k_d * pid->frequency;
float output = pid->p + pid->i * pid->beta + pid->d; return fabs(output) > pid->max_output ? output > 0 ? pid->max_output : -pid->max_output : output; }
|
调试参数
—更新中—