系列文章

智能车复工日记【1】——菜单索引回顾

前言

这里所解释的都是以位置式为基础的。
三个参数含义:
1.比例环节Kp,作用是加快系统的响应速度,提高系统的调节精度,副作用是会导致超调;
2.积分环节Ki,作用是消除稳态误差,副作用是导致积分饱和现象;
3.微分环节Kd,作用是改善系统的动态性能,副作用是延长系统的调节时间。

普通PID

公式:位置式公式

参数讲解:除了三个参数外,我们来看三个参数乘的是哪几个变量
e(k):当前目标值与输出值的偏差。
e(i):此状态之前(连带此状态)所有e(k)之和
e(k)-e(k-1):此状态目标值与输出值的偏差和上状态目标值与输出值的偏差的偏差。、
参数目录一览:

舵机参数:

这里的误差均是以此时中线值与目标中线值之差为例(图像一般是70*185,所以中线值92、93都行)。
不同的打脚行参数也是不同的。
舵机PD控制,一般来说参数KD是参数KP的两倍左右。

电机参数:

舵机参数要分不同的道路来调,电机同样是这个道理,不过略作简化,分为直道和不是直道两种。
直道上响应要快,因为直道需要急停和急加速。I项调小,否则滞后性较严重。D项看着给,调参这事儿说不准的。
量级问题:位置式P和D一般来说以整数为量级,考虑到调整参数的快速性上建议按键按下一次增加/减少3、5左右。
I项以0.01为量级,每次调整0.03-0.05左右为佳。

变结构PI控制(电机控制,这里对公式进行修改采用正态分布公式)

参考文章:https://blog.csdn.net/weixin_42164589/article/details/86291824

//这里为了方便,左右电机参数不做区分,统一管理
//原公式 : U(t,e)=[ Ap + Bp*(1-exp(-Cp|e|))]*e(t) + [ Ai*exp(-Ci|e|)]*sum_e;
//        Kp(e) = Ap + Bp(1-exp(-Cp|e|));
//        Ki(e) = Ai * exp(-Ci|e|);
/*
现公式:  Kp(error) = Ap + Bp(1/(sqrt(2*pi)*a)-1/(sqrt(2*pi)*a)*exp(-(abs(error)-b)^2/(2*a^2)));
          Ki(error) = Ai *1/(sqrt(2*pi)*a)*exp(-(abs(error)-b)^2/(2*a^2));
          Kd( ) 手动调整
          U(error,t)=Kp(error)*error + Ki(error)*sum_error + Kd(error)*(now_error - last_error);
          其中1/sqrt(2*pi)=0.3989;
          修改公式:
          Kp(error) = Ap + Bp(0.3989/a-0.3989/a*exp(-(abs(error)-b)^2/(2*a^2)));
          Ki(error) = Ai *0.3989/a*exp(-(abs(error)-b)^2/(2*a^2));
          Kd( ) 手动调整
          U(error,t)=Kp(error)*error + Ki(error)*sum_error + Kd(error)*(now_error- last_error);

公式讲解:当e->无穷,Kp取max Ap+Bp,Ki取min 0;
当e->0, Kp取min AP,Ki取max Ai;
基本思想:当误差e较小时,比例增益取小值,误差取大值时,取大值。
理想效果:这样既可以调高响应速度,又可以防止产生过大的超调量,同时保证有很好的稳定性。
同时,当误差较大时,希望较小,以防止产生震荡和积分饱和,有利于减小超调量、缩短调节时间,
随着误差的减小,希望逐渐增大,以消除稳态误差。

参数大概范围:
范围

微分先行PID(电机控制)

微分先行在普通PID的基础上改变的就是:对输出进行微分而不是对误差进行微分,具体原理可以去参考刘金坤的先进PID控制。
书

(丢在学校吃灰的书)。
也可以参考这两篇博客:
https://blog.csdn.net/foxclever/article/details/80633275?

ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158583112619725211928147%2522%252C%2522scm%2522%253A%252220140713.130056874…

%2522%257D&request_id=158583112619725211928147&biz_id=0&utm_source=distribute.pc_search_result.none-task

https://wenku.baidu.com/view/820bd2e1581b6bd97e19eacf.html

一开始看书写的代码,由于看不懂公式,所以直接从原理出发:对输出进行微分,然后得到的代码形式是这样的:

int  L_LocPIDcalc_coder(int setpoint,int nextpoint)		//目标值   和 编码器读回来的值
{
	int result=0;
    L_now_output=nextpoint;    	//当前编码器读回来的值
	L_error1_coder=setpoint-nextpoint;		//当前误差
    if(modeflag==0)     //普通微分先行
    {
            L_sumerror_coder +=L_error1_coder;				//I项
            L_error2_coder=L_error1_coder;
            if( street_flag== 1 ||street_flag== 5||street_flag== 7 ||street_flag== 8 ||street_flag== 9 )	//直道
            {
                result=Right_Left_P * L_error1_coder + Right_Left_I*L_sumerror_coder + Right_Left_D*(L_now_output-L_last_output);
            }
            else 		//非直道
            {
                result=Right_Left_P1 * L_error1_coder + Right_Left_I1*L_sumerror_coder + Right_Left_D1*(L_now_output-L_last_output);
            }          
    L_last_output=nextpoint;	
	//		限幅		
    if(result>=10000) result=10000;
    if(result<=-10000) result=-10000;
	return result;	
}

随后翻看其他博客发现公式还需要进行滤波,增加了新变量gama,暂时还不知道gama的取值范围是多少,只知道是在0~1之间。

float c1=0,c2=0,c3=0,temp=0;
L_sumerror_coder +=L_error1_coder;	        //计算I项
if( street_flag== 1 ||street_flag== 5||street_flag== 7 ||street_flag== 8 ||street_flag== 9 )    //直道
{
    temp=gama_str*Right_Left_D_dif+Right_Left_P_dif;      //temp = gama*KD+KP
    c3=Right_Left_D_dif/temp;
    c2=(Right_Left_D_dif+Right_Left_P_dif)/temp;
    c1=gama_str*c3;
    L_derivative=c1*L_derivative+c2*nowinput-c3*L_last_output;
    result=Right_Left_P * L_error1_coder + Right_Left_I*L_sumerror_coder + L_derivative;
    L_error2_coder=L_error1_coder;      //更新lasterror
    L_last_output=nowinput;     //更新lastoutput
}
else            //非直道
{                  
    temp=gama_wan*Right_Left_D_dif+Right_Left_P_dif;      //temp = gama*KD+KP
    c3=Right_Left_D_dif/temp;
    c2=(Right_Left_D_dif+Right_Left_P_dif)/temp;
    c1=gama_wan*c3;
    L_derivative=c1*L_derivative+c2*nowinput-c3*L_last_output;
    result=Right_Left_P * L_error1_coder + Right_Left_I*L_sumerror_coder + L_derivative;
    L_error2_coder=L_error1_coder;      //更新lasterror
    L_last_output=nowinput;     //更新lastoutput
}

模糊PID(用于舵机控制,只选择PD控制)

文章参考见我收藏夹。
之所以不用微分先行是因为微分先行是在目标值变化频率较高时使用较好,而舵机控制目标值不变,一直是93//92.
模糊PID实现步骤:

步骤
模糊PID推导出来的是三个参数的变化值,所以还要加上我们设定的初始值才是真正送入pid计算的三个参数,
以直道为例:

        if(type==1)
    {     
        matrix_x=0;
        steer_P=Proportion_str;
        steer_D=Derivative_str;
		chauncan(steer_P,steer_D,deltap_max_different_road[matrix_x],deltap_min_different_road[matrix_x],deltad_max_different_road[matrix_x],deltad_min_different_road[matrix_x]);        
        steer_fuzzypid_cal(steer_setpoint,nextpoint);    //输入目标值和实际值,得到KP和KD
        result=steerPID.output_Kp*steerPID.e+steerPID.output_Kd*steerPID.de; 
        return   result;          
    } 

调参改动的部分:
【1】模糊规则表(根据专家经验)
建立模糊规则表

【2】输出值限幅
限幅
当将变换幅度限制为0,则公式退化为普通PID控制。

专家PID(基于增量式电机控制)

增量式PID公式:

u[n] = Kp(e[n]-e[n-1]) + Ti*e[n] + Td{e[n]-2e[n-1]+e[n-2]}

基于增量式的专家PID

e(k):当前采样时刻误差值
e(k-1):前一个时刻采样时刻误差值
e(k-1):前两个时刻采样时刻误差值
delta_e(k)=e(k)-e(k-1);
delta_e(k-1)=e(k-1)-e(k-2);
根据误差及其变化,对阶跃相应误差曲线进行如下定性分析:
(1)当|e(k)|>M_max,说明误差的绝对值已经很大,不论误差变化趋势如何,都应考虑控制器的输出应按最大(或最小)输出,以达到迅速调整误差,使误差绝对值以最大速度减少。
此时它相当于实施开环控制。
(2)当e(k)*delta_e(k)>0,说明误差在朝误差绝对值增大方向变化,或误差为某一常数,未发生变化。
如果|e(k)|>=M_middle,说明误差也较大,可考虑由控制器实施较强的控制作用,以达到扭转误差绝对值朝减小方向变化,并迅速减少误差的绝对值,控制器输出为:
u(k)=u(k-1)+k1{kp[e(k)-e(k-1)]+kie(k)+kd[e(k)-2e(k-1)+e(k-2)]} ;
如果|e(k)|<M_middle,说明尽管误差超绝对值增大的方向变化,但误差绝对值本身并不很大,可考虑控制器实施一般的控制作用,扭转误差的变化趋势,
使其朝误差绝对值减小方向变化,控制器输出为:
u(k)=u(k-1)+kp[e(k)-e(k-1)]+kie(k)+kd[e(k)-2e(k-1)+e(k-2)] ; //普通增量式
(3)当e(k)*delta_e(k)<0、delta_e(k)*delta_e(k-1)>0或者e(k)=0时,说明误差的绝对值朝减少的方向变化,或者已经达到平衡状态。此时可以考虑采取保持控制器输出不变。
(4)当e(k)delta_e(k)<0、delta_e(k)delta_e(k-1)<0时,说明误差处于极值状态。
如果此时误差的绝对值较大,即|e(k)|>=M_middle,可考虑实施比较强的控制作用:
u(k)=u(k-1)+k1kpem(k);
如果此时误差的绝对值较小,即|e(k)|<M_middle,可考虑实施比较弱的控制作用:
u(k)=u(k-1)+k2kpem(k);
(5)当|e(k)|<=sigma,说明误差的绝对值很小,此时加入积分控制,以减少稳态误差。
以上:em(k)为误差e的第k个极值;k1为增益放大系数,k1>1;k2为抑制系数,0<k2<1;M_max,M_middle为设定的误差界限,M_max>M_middle>0;
k为控制周期的序号;sigma为任意小的正实数。
误差朝绝对值减少的方向变化,采取保持等待措施。
误差朝绝对值增大的方向变化,根据误差的大小分别实施较强或一般的控制作用,抑制动态误差。

直接上代码:(以左电机为例)

int errorabsmax=400;
int errorabsmid=200;
int errorabsmin=10;
int maximum=10000;      //手工设定的开环输出最大值
int minimum=0;      //手工设定的开环输出最小值
int L_sumerror_coder=0;
int L_derror_coder = 0;
int L_dderror_coder = 0;
int L_error1_coder = 0;
int L_error2_coder = 0;
int L_error3_coder = 0;
int R_sumerror_coder=0;
int R_derror_coder = 0;
int R_dderror_coder =0;
int R_error1_coder = 0;
int R_error2_coder = 0;
int R_error3_coder = 0;
/**********Av为增益:Av>1************/
/**********Dv为衰减:0<Dv<1 ************/
float KP = 50;     //60
float KI=0.55;        //2
float KD = 5.3;      //5
float K_kp_Av_rule2=1.5;
float K_kp_Dv_rule2=0.4;
float K_kp_Av_rule4=2;
float K_kp_Dv_rule4=0.6;
float K_kp_Dv_rule5=0.5;
float K_ki_Dv_rule5=0.3;
int L_LocPIDcalc_coder(int setpoint,int nextpoint)
{
    int result=0;
	L_error1_coder=setpoint-nextpoint; 
    L_derror_coder=L_error1_coder-L_error2_coder;
    L_dderror_coder=L_error2_coder-L_error3_coder;    
    if(abs(L_error1_coder)>=errorabsmax)
    {
        /*执行规则1*/
        if(L_error1_coder>0)
        {
            result=maximum;
        }
        if(L_error1_coder<0)
        {
            result=minimum;
        }
    }
 
    else if((L_error1_coder*L_derror_coder>0)||(L_derror_coder==0))
    {
        /*执行规则2*/
        if(abs(L_error1_coder)>=errorabsmid)
        {
            result=result+K_kp_Av_rule2*(KP*L_derror_coder+KI*L_error1_coder+KD*(L_derror_coder-L_dderror_coder));
        }
        else
        {
            result=result+K_kp_Dv_rule2*(KP*L_derror_coder+KI*L_error1_coder+KD*(L_derror_coder-L_dderror_coder));
        }
    }
 
    else if(((L_error1_coder*L_derror_coder<0)&&(L_derror_coder*L_dderror_coder>0))||(L_error1_coder==0))
    {
        /*执行规则3*/
        result=result;
    }
    else if((L_error1_coder*L_derror_coder<0)&&(L_derror_coder*L_dderror_coder<0))
    {
        /*执行规则4*/
        if(abs(L_error1_coder)>=errorabsmid)
        {
            result=result+K_kp_Av_rule4*KP*L_error1_coder;
        }
        else
        {
            result=result+K_kp_Dv_rule4*KP*L_error1_coder;
        }
    }
    else if((abs(L_error1_coder)<=errorabsmin)&&(abs(L_error1_coder)>0))
    {
        /*执行规则5*/
        result=result+K_kp_Dv_rule5*KP*L_derror_coder+K_ki_Dv_rule5*KI*L_error1_coder;
    } 
    /*对输出限值,避免超调*/
    if(result>=10000)
    {
        result=10000;
    }
    if(result<=-10000)
    {
        result=-10000;
    }    
    L_error3_coder =L_error2_coder;
    L_error2_coder =L_error1_coder;
    return result;
}

总结

感觉模糊PID最难调试,变结构和专家pid次之,微分先行还没开始调(以前调的是错误公式,感觉效果其实还行)。普通pid调试其实也是很复杂。

最后为了节省调试时间,觉得还是电机以微分先行、变结构来调,舵机以模糊pid来调,模糊输出范围限制小一点,效果其实还行。
专家pid好久都没试过,记录的参数也是我一开始调试的,以后不想尝试专家pid了。