PID详解2(分析循迹过程)

PID详解2

上一篇文章系统地分析了PID地理论基础、整定方法。从中我们可以知道PID是通过增加零极点来改善系统地性能,使其达到满意地效果,并且了解了一套整定PID参数地方法。

本篇文章我们希望让理论照进现实,对于寻迹小车来说,光电、红外这些牵引方式的博文确实是不胜枚举,这里笔者为了逼迫自己投入思考,选择相比较而言资源较少的“摄像头循迹”来实践PID巡线。

所谓他山之石,可以攻玉,我们不妨先来看看有很多人研究过的这些牵引方式,再研究怎么化归到我们的项目上来。

这里插一段:PID三个控制器分别的作用:

一文读懂PID控制算法(抛弃公式,从原理上真正理解PID控制)

1.P 比例,也就是按偏移量的倍数调参。相对于就是让偏差大的以更快速度调整,偏差小的按比较慢的速度调整

假设我有一个水缸,最终的控制目的是要保证水缸里的水位永远的维持在1米的高度。假设初试时刻,水缸里的水位是0.2米,那么当前时刻的水位和目标水位之间是存在一个误差的error,且error为0.8.这个时候,假设旁边站着一个人,这个人通过往缸里加水的方式来控制水位。如果单纯的用比例控制算法,就是指加入的水量u和误差error是成正比的。即
u=kperror
假设kp取0.5,
那么t=1时(表示第1次加水,也就是第一次对系统施加控制),那么u=0.5
0.8=0.4,所以这一次加入的水量会使水位在0.2的基础上上升0.4,达到0.6.
接着,t=2时刻(第2次施加控制),当前水位是0.6,所以error是0.4。u=0.50.4=0.2,会使水位再次上升0.2,达到0.8
如此这么循环下去,就是比例控制算法的运行方法。
可以看到,最终水位会达到我们需要的1米。
但是,单单的比例控制存在着一些不足,其中一点就是 –稳态误差!(我也是看了很多,并且想了好久才想通什么是稳态误差以及为什么有稳态误差)。
像上述的例子,根据kp取值不同,系统最后都会达到1米,不会有稳态误差。但是,考虑另外一种情况,假设这个水缸在加水的过程中,存在漏水的情况,假设每次加水的过程,都会漏掉0.1米高度的水。仍然假设kp取0.5,那么会存在着某种情况,假设经过几次加水,水缸中的水位到0.8时,水位将不会再变换!!!因为,水位为0.8,则误差error=0.2. 所以每次往水缸中加水的量为u=0.5
0.2=0.1.同时,每次加水缸里又会流出去0.1米的水!!!加入的水和流出的水相抵消,水位将不再变化!!
也就是说,我的目标是1米,但是最后系统达到0.8米的水位就不在变化了,且系统已经达到稳定。由此产生的误差就是稳态误差了。
(在实际情况中,这种类似水缸漏水的情况往往更加常见,比如控制汽车运动,摩擦阻力就相当于是“漏水”,控制机械臂、无人机的飞行,各类阻力和消耗都可以理解为本例中的“漏水”)
所以,单独的比例控制,在很多时候并不能满足要求。

  1. I,积分:通过积分偏差,放大误差从而避免上述的稳态误差的存在。

  2. D,微分:这里一般会提一个经典的例子:弹簧振荡,放在水中,可以使得它稳定所需的时间大大减小,D在这里就相当于这个水的作用。
    从数学角度来说,这个微分就是上一时刻的误差和此刻误差的差,这相对于PI来说是一个反向的作用,相当于上面那个系统里水的阻尼。而这个微分反映出的就是当前的状态改变速度。而因为他是一个负数,在整个PID的过程中,是一个减速的过程,拒绝过高的变换速度。

我参考的这篇博文里说:

从常识上可以理解,越是靠近停车线,越是应该注意踩刹车,不能让车过线,所以这个微分项的作用,就可以理解为刹车,当车离停车线很近并且车速还很快时,这个微分项的绝对值(实际上是一个负数)就会很大,从而表示应该用力踩刹车才能让车停下来。

当时我看这个博文时就觉得不理解“车离停车线越近”这个点,因为不管是从公式出发还是从我举得那个弹簧系统实例来说,都没有涉及这个点的存在。

随着整体看PID,我发现其实这里作者是站在一个整体的角度上说了,仅仅在D这里并没有涉及所谓的“离预期目标越近”这个条件。

这句话得这么看才对:当偏差很小的时候,P控制器的作用会变得很小,从而对整个PID控制器来说,D的作用显得很大,从而达成这个越靠近预期值,调整得速度越快,这个阻力越大。

另外文中说D的值一定为负显然是错误的,D的值是正是负显然是由当前的状态和预期状态的比较来确定。

光电传感器

循迹的光电搬运小车含PID_博文原址

在此文中,针对一款2路灰度传感器设计了一套PID控制器,作者在文中提供了3种循迹方案,颇有一种从无到有的演变之风,此处记录一下:

1.粗糙的循迹

思想:车偏右就往左开,车偏左就往右开
显然,这种方法对于走直线来说很好用,但对于弧线就不那么优秀了。
代码来自那位博主,稍作修改:

#define change  40
//Val1-4采用平均法简单处理
void Car_RUN(int speeda, int speedb) {//沿线走
  int average = 1500;//(QvalueMax + QvalueMin )/2
  getABCD();
  if (val4 < average && val5 < average ) { //全黑
    goStraight(speeda,speedb);
  }
  if (val4 < average && val5 > average) { //偏右
    goStraight(speeda + change ,speedb - change );
  }//右轮加速,左轮减速
  if (val4 > average && val5 < average) { //偏左
   goStraight(speeda - change ,speedb + change );
  }//右轮减速,左轮加速
  if (val4 > average && val5 > average) { //全白
    goStraight(speeda,speedb);
  }//右轮保持,左轮保持
2.简单PID控制器的使用:
void getABCD()
{ //获取4、5、6、7号灯值

  val4 = 0;
  val5 = 0;
  val6 = 0;
  val7 = 0;
  int i;
  for (i = 0; i < 4; i++) {
    val4 += analogRead(QI4);
    val5 += analogRead(QI5);
    val7 += analogRead(HI4);
    val6 += analogRead(HI5);
  }
  val4 = val4 / 4;
  val5 = val5 / 4;
  val6 = val6 / 4;
  val7 = val7 / 4;
}
void Sbigcircle(int speeda , int speedb )//顺时针走大圆
{
  getABCD();
  int average = (QvalueMax + QvalueMin)/2 ;//1850
  P_error1 = val4 - average ;
    if(P_error1 < 0) 
    P_error1 = 0 ;
  D_error1 = P_error1 - D_error1 ;
  I_error1 += P_error1 ;
  P_error2 = val5 - average ;
    if(P_error2 < 0) 
    P_error2 = 0 ;
  D_error2 = P_error2 - D_error2 ;
  I_error2 += P_error2 ;

  if(val4 < average + 1000 && val5 < average )
  {
    I_error1 = 0 ;
    I_error2 = 0 ;
  }
  double tem = 0.8 * Kp1;
  
  if(val4 < 600 && val5 < 600)
  goStraight(speeda - 10 ,speedb + 25);
  else if(val4 < 1000 && val5 > 2000)//偏右   1.3倍Kp稳定
  goStraight(speeda + Kp1 *P_error1 + Ki1 * I_error1+ Kd1 *D_error1   ,speedb - Kp1 *P_error2  - Ki1 * I_error2 - Kd1 *D_error2 );
  else if(val4 > 2000  && val5 < 1000 )//偏左
  goStraight(speeda - Kp1 *P_error1 - Ki1 * I_error1 - Kd1 *D_error1 - 10 ,speedb +  Kp1 *P_error2 + Ki1 * I_error2 + Kd1 *D_error2 + 25);
  else 
  goStraight(speeda - 10 ,speedb + 25 );//25
}

这个代码最珍贵的地方就是指明了,对于循迹小车来说,最终我们调整的参数是两轮间的差速。很多朋友可能搞了半天PID,却甚至没明白自己要调整的参数是什么。
但这个代码似乎也存在一些问题,那就是太过“匠气”了,应该是对照某个模板来写的。

3.改进版PID

按照作者所说,这份代码已经是一份成熟的代码了,所以我们得抱着看成功案例的心情来看这个代码,他没有用工业上的微积分时间作为参数,但这里并不需要那么精确,所以作者这么做是可行的,一起来看看:

/循迹
const float Kp1 = 13, Ki1 = 0.25, Kd1 = 1.5;                    //pid前进参数参数 
const float Kp2 = 11, Ki2 = 0.2, Kd2 = 4.5;                    //pid后退参数参数 
float error = 0, P = 0, I = 0, D = 0, PID_value = 0; 
float previous_error = 0, previous_I = 0;

void getABCD()
{ //获取4、5、6、7号灯值

  val4 = 0;
  val5 = 0;
  val6 = 0;
  val7 = 0;
  int i = 0;
  ///滤波
  for (i = 0; i < 4; i++) {
    val4 += analogRead(Qvalue4);
    val5 += analogRead(Qvalue5);
    val6 += analogRead(Hvalue4);
    val7 += analogRead(Hvalue5);
  }
  val4 = val4 / 4;
  val5 = val5 / 4;
  val6 = val6 / 4;
  val7 = val7 / 4;
  
  if (flag == 1) {//前进flag设为1
    if (val4 < 800 && val5 < 600)//在黑线
      error = 0;
    else if (val4 < 800 && val5 > 600)//偏右
      error = 3;
    else if (val4 > 800&& val5 < 600)//偏左
      error = 3;
    else
      error = 0;
  }
  else {//后退
    if (val6 < 600 && val7 <1000)//在黑线
      error = 0;
    else if (val6 < 600 && val7 > 1000)//偏右
      error = 3;
    else if (val6 > 600 && val7 < 1000)//偏左
      error = 3;
    else
      error = 0;
  }
}

void calc_pid() {
  P = error;
  I = I + error;
  D = error - previous_error;
  
  if(flag == 1)
  {
	  if (I > 30) I = 10;
	  PID_value = (Kp1 * P) + (Ki1 * I) + (Kd1* D);
  }
  else 
  {
      if (I > 25) I = 15;
	  PID_value = (Kp2 * P) + (Ki2 * I) + (Kd2* D);
  }
  previous_error = error;
}

void yanxianzou(int a, int b) {
  flag = 1;
  getABCD();
  calc_pid();
  if (val4 < 1000 && val5 < 900)//在黑线
    goStraight(a, b);
  else if (val4 < 1000 && val5 > 900)//偏右
    goStraight(a + PID_value, b - PID_value);
  else if (val4 > 1000&& val5 < 900)//偏左
    goStraight(a - PID_value, b + PID_value);
  else
    goStraight(a, b);
}

void yanxiantui(int a, int b) {
  flag = 0;
  getABCD();
  calc_pid();
  if (val6 < 800 && val7 <1400)//在黑线
    retreat(a, b);
  else if (val6 >  800  && val7 < 1400)//偏左
    retreat(a - PID_value, b +PID_value);
  else if (val6 <  800  && val7 > 1400)//偏右
    retreat(a+PID_value , b - PID_value);
  else
    retreat(a, b);
}

在偏移时设定固定的err,计算出P,I,D,通过改变 K P , K I , K D K_P,K_I,K_D KP,KI,KD来得到 V a l Val Val,由此改变赋给两轮的速度,从而完成控制。

大概比例: K P : K I : K D = 100 : 1 : 10 K_P:K_I:K_D = 100:1:10 KP:KI:KD=100:1:10

4.位置式PID和增量式PID

PID循迹控制算法:引用博文原址

//定义pid结构体
struct PID
{
	float kp;
	float ki;
	float kd;
};
typedef struct PID _PID;

//定义转向pid参数结构体
_PID TurnPdate=
{
	.kp=100,			//赋值比例值60
	.ki=0,			//赋值积分值
	.kd=-34			//赋值微分值-45
};		
//定义速度pid参数结构体
_PID SpdPdate=
{
	.kp=-50,			//赋值比例值-80
	.ki=-6,			//赋值积分值-10
	.kd=0			//赋值微分值
};	

/*@brief: 位置式PID控制器
 * @param:
 *        [in] float deviation: 和目标值得偏差
 *        [in] _PID pid: 位置式pid的参数
 * @return: 调节占空比的一个整形数
 */
int PositionPID(float deviation,_PID pid)
{
	float Position_KP=pid.kp,Position_KI=pid.ki,Position_KD=pid.kd;
	static float Bias,Pwm,Integral_bias,Last_Bias;
	Bias=deviation;                         		         //计算偏差
	Integral_bias+=Bias;	                                 //求出偏差的积分
	Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias); //位置式PID控制器
	Last_Bias=Bias;                                      	 //保存上一次偏差 
	return Pwm;    
}

/*@brief: 为速度pid使用位置式PID控制器,对积分值进行限幅
 * @param:
 *        [in] float deviation: 和目标值得偏差
 *        [in] _PID pid: 位置式pid的参数
 * @return: 调节占空比的一个整形数
 */
int PositionPIDToSpd(float deviation,_PID pid)
{
	float Position_KP=pid.kp,Position_KI=pid.ki,Position_KD=pid.kd;
	static float Bias,Pwm,Integral_bias,Last_Bias,pwmKI=0;
	Bias=deviation;                         		         //计算偏差
	Integral_bias+=Bias;	                                 //求出偏差的积分
	pwmKI=Position_KI*Integral_bias;
	if(pwmKI>MAX_MOTOR_PWM) Integral_bias=MAX_MOTOR_PWM/Position_KI;
	Pwm=Position_KP*Bias+pwmKI+Position_KD*(Bias-Last_Bias);       //位置式PID控制器
	Last_Bias=Bias;                                      	 //保存上一次偏差 
	return Pwm;    
}

  • 22
    点赞
  • 261
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值