PID算法理解和代码以及PID调参

PID算法理解以及调参

  • 写在前面
  • 正文
    • 1.PID算法的基本公式
    • 2.理解PID算法公式
    • 3.对P、I、D三个参数的理解
    • 4.补充一下电流环
    • 5.调参方法
    • 6.参考代码
    • 7.实际控制遇到的情况

写在前面

在一年前自己学习了一下PID算法,但是对具体的过程,计算式子,并没有做到很好的理解,于是自己总结并再次学习了一遍,以此记录,希望能和大家一起讨论

正文

1.PID算法的基本公式

在离散化的系统中,积分是误差的累计和,微分等于当前误差值与之前的误差值的差
故PID算法的公式如下:
PID算法理解和代码以及PID调参_第1张图片
基本概念得先理解
我们调整的参数是前面的Kp,Ki,Kd
而式子中的Kp×e(k),Ki×Σe(Kn),Kd*(e(k) - e(k-1))是增益,也就是P、I、D分别对应的输出
u(k)是PID算法整个的输出,也就是控制量

2.理解PID算法公式

  • PID的输出就类似于一个控制量,也就是你控制一个系统的控制量的变化,就是PID算法的输出。
    做一个形象的比喻:你需要控制燃气灶的温度,那么PID算法的输出就是火力的大小。你通过控制火力的大小来控制温度。
  • 在控制电机里面,速度环就是控制电流的大小,来控制电机的转速达到目标转速。PID的输出就是电流的值。
  • 位置环一般是用的双环控制,内环是速度环,外环是位置环。大概如下图:
    PID算法理解和代码以及PID调参_第2张图片
  • 首先位置先做一次PID计算,得到的输出是速度的值。再由速度做一次PID计算,得到的输出是电流的值,再把电流发给电调即可

3.对P、I、D三个参数的理解

P的控制作用

  • 根据公式,Kp×e(k),当我们将Kp趋于无穷大的时候,就类似一个开关的作用,用灶做比喻,当目标值大于实际值的时候,开关打开,开始加热,当目标值小于实际值的时候,火力断开,让他降温。但我们实际控制的火力有大小的。那么我们可以这么理解
    当差距不大的时候,Kp*e(k)较小,那么需要输出的火力较小
    当差距较大,并且温度低于目标温度的时候,那么需要输出的火力就会比较大
    当差距较小,并且温度高于目标温度的时候,那么就让输出的火力为负值(假设可以降低温度),降低温度。
  • 简而言之:P是为了建立目标值和当前值的一种线性关系,当你的P较大的时候,你会很快的到达目标值的附近,但是由于P过大,很容易就超过目标值,而超过目标值的时候,你的P很大,又会瞬间拉回来,但拉回来的程度会过大,也就会出现在目标值附近震荡,没办法收敛于目标值的情况。
    形象的比喻就是先粗略的调试一下

D的控制作用

  • 由上面的理论可以得到,在趋于目标值了之后,我们的P作用就不大了,并且越靠近目标值,P的作用越小,因为P太粗暴了,让整个系统都震荡了起来。

  • 而D的作用,根据公式。Kd*(e(k) - e(k-1)),我们可以当从0→目标值的时候,e(k)是在不断减小的,此时e(k) - e(k-1)是一个负值,再*Kd,我们可以得到,D的作用是为了给系统一个阻尼,并且系统变化的越快,这个阻尼也就越大,这就能抵消P那么粗暴的效果,让P温柔一点,在变化小的时候,D给小阻尼。在突然一个大变化的时候,阻尼就会很大, 这也就让系统不会变化过快,从而使得系统从震荡趋于稳定。

  • 网上看到的这句话我觉得是最形象的比喻了。
    在这里插入图片描述

I的控制作用

  • 当PD控制了之后,整个系统就已经可以趋于平衡了,但是如果是在有负载的情况下,尤其是电机要带动某个东西的情况下,很有可能会出现PID输出和消耗抵消了,从而使得系统没有办法达到目标值,例如你的温度已经到达95度,你想要去100度,但是散热的效果和你火力增加温度的效果抵消了,让你永远停在了95度,没有办法继续往上增加。
  • 这时候就需要I的作用,根据公式K1×Ki×Σe(Kn),K1可以暂时不管,K1就是为了实现在某些情况下加入I,某些情况下不加入I。因为正常情况下你的系统只会受到小的扰动,但是如果突然你的系统受到一个很大的变化,如果这时候还要积分,很有可能导致这个积出来的值对整个系统造成比较大的问题,甚至引起系统的崩溃。
  • 这个公式里面,就是把你之前所有的偏差进行累加,并把这个累加的值体现在输出上,这样,如果你的温度停在了95度,那么你的累加值也就会越来越大,最终你的火力也会往上走,实现温度的提升。
  • I其实是对P的一个补充作用,但我们不能让I不断的累加,必须有一个累加的最大值,因为累加过大,会让积分量太大,有可能出现一个跳变,难以控制。
  • 在实际的控制中,I参数其实是有一个让系统超前的作用,而超前的作用就是与D相反,D的增益起到了一个阻尼的作用,减慢你的变换,而I的增益起到了一个补助加火的作用,加速了你的变换。

4.补充一下电流环

在我们实际的控制电机的过程中,我们以DJI官网3508电机为例,其搭配有一个C620电调,在我们实际的控制中,我们用PID算法解算出来的是一个电流值,我们通过通信发送给电调,让电调控制电流的输出,但是在这个里面,其实还有一层电流环的控制,所以当我们发送了电流之后,其实电调内部还有一圈电流环,这也就导致了我们的输出其实没有那么好的实时性,是有一定延时的。

5.调参方法

  • 1)
    PID调试一般原则
    a.在输出不振荡时,增大比例系数P。
    b.在输出不振荡时,减小积分系数Ki。
    c.在输出不振荡时,增大微分系数Kd。
    (他们三个任何谁过大都会造成系统的震荡。)

  • 2)
    a.确定比例增益P :确定比例增益P 时,首先去掉PID的积分项和微分项,一般是令Ki=0、Kd=0(具体见PID的参数设定说明),使PID为纯比例调节。输入设定为系统允许的最大值的60%70%,由0逐渐加大比例增益P,直至系统出现振荡;再反过来,从此时的比例增益P逐渐减小,直至系统振荡消失,记录此时的比例增益P,设定PID的比例增益P为当前值的60%70%。比例增益P调试完成。
    b.确定积分时间常数Ki比例增益P确定后,设定一个较大的积分时间常数Ki的初值,然后逐渐减小Ki,直至系统出现振荡,之后在反过来,逐渐加大Ki,直至系统振荡消失。记录此时的Ki,设定PID的积分时间常数Ki为当前值的150%~180%。积分时间常数Ki调试完成。但是由于实际控制电机的过程中,很有可能不给i也不会引起系统的震荡,这个时候就需要我们自己判断给一个i的值来实现系统更快的响应。
    c.确定积分时间常数Kd 积分时间常数Kd一般不用设定,为0即可。若要设定,与确定 P和Ki的方法相同,取不振荡时的30%。
    d.系统空载、带载联调,再对PID参数进行微调,直至满足要求。
    PID算法理解和代码以及PID调参_第3张图片
    PID算法理解和代码以及PID调参_第4张图片
    PID算法理解和代码以及PID调参_第5张图片

图片来自 https://blog.csdn.net/wb790238030/article/details/92809538?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control

6.参考代码

//结构体部分
typedef struct
{
     
	uint8_t enable 					: 1;	//输出使能
	uint8_t enable_lim_sum_error	: 1;	//积分限幅使能
	uint8_t enable_lim_ouput		: 1;	//输出限幅使能
	uint8_t count					: 4;	
	
	double kp;  			//比例参数
	double ki;  			//积分参数
	double kd;  			//微分参数

	double lim_sum_error;	//误差积分限幅	
	double lim_output;		//输出限幅 防止输出过大

	double sum_error;		//误差积分
	double last_error;		//上一次的误差
	double last_last_error;	//上上一次的误差
	
	double kd_output;
	double ki_output;
	double kp_output;
	double error_dec;

	double pid_output;
	double error;
} PID_HANDLE, *P_PID_HANDLE;

//代码运算部分
double pidProcess(P_PID_HANDLE phdl, double input, double measure)
{
     
    double output = 0;
    double error = input - measure;         //计算误差

    phdl->sum_error += error;

    //误差积分限幅
    if(phdl->enable_lim_sum_error == 1 && fabs(phdl->sum_error) > phdl->lim_sum_error)
    {
     
        if(phdl->sum_error > 0)
            phdl->sum_error = phdl->lim_sum_error;
        else
            phdl->sum_error = -phdl->lim_sum_error;
    }

    output =        phdl->kp * error
                +   phdl->ki * phdl->sum_error
                +   phdl->kd * (error - phdl->last_error) ;
		phdl->kp_output = phdl->kp * error;
		phdl->ki_output = phdl->ki * phdl->sum_error;
		phdl->kd_output = phdl->kd * (error - phdl->last_error);

		phdl->error_dec = error - phdl->last_error;
		
    //输出限幅
    if(phdl->enable_lim_ouput == 1 && fabs(output) > phdl->lim_output)
    {
     
        if(output > 0)
            output = phdl->lim_output;
        else
            output = -phdl->lim_output;
    }

    //更新误差值
    phdl->last_error = error;

    //是否使能输出
    if(phdl->enable == 1)
	{
     
		phdl->pid_output = output;
		return output;
	}
    else
	{
     
		phdl->pid_output = 0;
		return 0;
	}
}

7.实际控制遇到的情况

结合实际控制电机说明几种情况
①堵转
在实际的控制电机的过程中,堵转是一种很常见的情况。下面结合PID算法总结一下。
当我们的实际值和我们的目标值之间保持了一个恒定差值,这个时候会出现什么情况呢?
根据计算式子,会发现D的增益Kd*(e(k) - e(k-1))为0,P的增益Kp×e(k)为一个恒定值,而I的值Ki×Σe(Kn)却是在不断累计的,这也就导致了我们发送给电调的值始终保持在我们限定的最大值输出,也就是lim_output这一个值,但是电机因为处于堵转状态,没有办法旋转,而电流在其中流动,能量守恒,这一部分电流就会以热量的形式散发出去,也就导致了电机的过热。
试想一下:如果不给output限定值,那么不断累计,最终发送的电流过大,会直接导致电调或者电机的烧毁。

—————————————————————后续补充——————————————————————
②速度环控制电机
③位置环控制电机

你可能感兴趣的:(STM32,算法,pid,嵌入式)