其中KP、KI、KD分别为比例系数、积分系数、微分系数。
比例系数KP :反应系统当前最基本的误差,系数大,可以加快调节,减小误差,但是过大的比例使系统稳定性下降,甚至造成系统的不稳定。
积分系数KI :反应系统的累计误差,使系统消除稳态误差,提高无差度,只要有误差,积分调节就会起作用。
微分系数KD :反应系统误差的变化率,具有预见性,们可以预见偏差的变化趋势,产生超前的控制效果。因此可以改善系统的动态性能。但是微分对噪声有放大作用,会减弱系统的抗干扰性。
所以将上式转化为时域表达式为:
其中 T 为PID的计算周期,就是 dt 。其值由微控制器的定时器决定,自己设置pid的运算周期。
e(k) 为本次计算时,传感器反馈的值和设定值之间的差。
e(k-1) 为上次计算时,传感器反馈的值和设定值之间的差。
e(k-2) 为上上次计算时,传感器反馈的值和设定值之间的差。
有了pid算法的差分方程,就可以通过编程来实现这个算法。之所以得通过差分方程来实现,是因为差分方程的一般解法是迭代法,而迭代法只需要知道初值和通项公式就能计算出所有时刻的的值。这是计算机最擅长的事情了。
这个式子就是最本质的离散化PID表达式。后面我将基于他推导出其他几个常见的表达式,但是实质都是这个式子。
在推导其他的表达式之前,请注意这几个变量的 关系:
比例系数:KP
积分系数:KI = KP*T/Ti 其中Ti 为积分时间
微分系数:KD = KP*Td/T 其中Td为微分时间
之所以叫时间,是因为考虑了系统的量纲。
因为pid输出的控制信号应该和被控量或参考信号有同样的物理单位。因为在物理系统中他们是描述的同一种物理量。假设被控量是位移,单位是m, 时间单位是s,那么KI KD的单位应该为单位1,而根据上面的式子,有一个运算周期T,所以Ti Td 相应的应该也有与之倒数的时间单位,来抵消T 的单位。所以称之为时间。
因为KP 和 T 都是常数,所以积分系数和积分时间、微分系数和微分时间都是成严格的比例关系的。他们的区别就是数值不一样,实质都是积分项和微分项的系数。如果调节的时候,看到别人说增大积分时间,而你的程序中只有积分系数。那么这时,就应该减小积分系数。其他类推。
对于上面的(1)式,因为 KD/T 是一个常数,KI * T也是常数,所以就有些人由于默认pid运算周期,就把 KD / T直接写成 KD,KI * T直接写成 KI。
其实严格来说,这样写是不严谨的。因为调节过程中,运算周期 T 也是一个很重要的参数。也需要对他进行反复的调节。
在调节完毕之后,我们可以根据这几个常数自己计算他们的乘积,直接写在代码中,以减少以后处理器的运算量。
所以能推导出这么几个式子:
==============================================================================
现在根据最初的原式来进行pid算法的实现。
首先做一下变量代换,方便后面进行调试:
当KD为0的时候,系统为PI控制器。
当KI为0的时候,系统为PD控制器。
当KD KI 都不为0的时候,系统为PID控制器。
可以根据系统的情况选择使用不同的控制器。我使用的是PID控制器。
首先就是PID中需要用到的参数的声明,建议最好把需要用到的参数全部都声明一下,就算有的变量显得有些多余,这样可以帮助自己理解。减少BUG
声明pid需要参数的结构体:
1 //================== 2 //PID.H 3 //================== 4 5 6 #ifndef __PID_H 7 #define __PID_H 8 9 10 //PID计算需要的参数 11 typedef struct pid 12 { 13 float ref; //系统待调节量的设定值 14 float fdb; //系统待调节量的反馈值,就是传感器实际测量的值 15 16 17 float KP; //比例系数 18 float KI; //积分系数 19 float KD; //微分系数 20 21 float T; //离散化系统的采样周期 22 23 float a0; //变量代替三项的运算结果 24 float a1; 25 float a2; 26 27 float error; //当前偏差e(k) 28 float error_1; //前一步的偏差 29 float error_2; //前前一步的偏差 30 31 float output; //pid控制器的输出 32 float output_1; //pid的前一步输出 33 float out_max; //输出上限 34 float out_min; //输出下限 35 36 37 }PID_value; //定义一个PID_value类型, 此时PID_value为一个数据类型标识符,数据类型为结构体 38 49 50 //条件编译的判别条件,用于调试 51 #define PID_DEBUG 1 52 53 //pid函数声明 54 void PID_operation(PID_value *p); 55 void PID_out(void); 56 57 float constrain_float(float amt, float low, float high); //浮点数限幅 58 //int constrain_int16(int amt, int low, int high); //整型数限幅 59 60 #endif 61 62 //======================== 63 //END OF FILE 64 //========================
复制代码//=============//PID.C //============= #include "main.h" #define set_distance 10.00 //设定距离 #define allow_error 1.0 //死区,允许误差带,0.5太小,系统不稳定,一直在调节,2就太大, extern float real_distance ; //实际距离 extern PID_value xdata ASR ; /* ******************************************************** ** 作者 :Andrew ** 日期 :2018.3.8 ** 说明: 1、PID默认为PI调节器 2、使用了条件编译进行功能切换,节省计算时间 在校正PID参数的时候,将宏定义 PID_DEBUG 设为1; 校正完毕后,置0; 3、同时在初始化的时候直接为a0,a1,a2赋值 ******************************************************** */ void PID_operation(PID_value *p) { //使用条件编译进行功能切换 #if (PID_DEBUG) float a0,a1,a2; //计算中间变量a0,a1,a2; a0 = p->KP + p->KI*p->T + p->KD/p->T ; a1 = p->KP + 2*p->KD/p->T ; a2 = p->KD/p->T ; //计算输出 p->output = p->output_1 + a0*p->error - a1*p->error_1 + a2*p->error_2 ; #else //非调试状态下,直接给a赋值计算输出,减小计算量,因为一旦三个系数确定,T已知,即可自己计算出对应的a p->output = p->output_1 + p->a0*p->error - p->a1*p->error_1 + p->a2*p->error_2 ; #endif //输出限幅 p->output = constrain_float(p->output,p->out_min,p->out_max); //为下次计算迭代 //这里顺序千万不要搞错,不然输出占空比是错误的。 p->output_1 = p->output; p->error_2 = p->error_1; p->error_1 = p->error; } /* ******************************************************** ** 作者 :Andrew ** 日期 :2018.3.8 ** 说明: 1、首先根据设定与实际的距离差,判断需要前进还是后退 2、在误差为 allow_error 之内,停车,防止小车一直在抖,毕竟超声波有误差。 ******************************************************** */ void PID_out(void) { float xdata duty; ASR.ref = set_distance; //距离给定 ASR.fdb = real_distance; //获取实际距离反馈 ASR.error = ASR.ref - ASR.fdb; //偏差 PID_operation(&ASR); duty = ASR.output; if(ASR.error > allow_error) //设定值大于实际值,小车需要后退 { left_go_back; right_go_back; Left_Forward_Duty = (int)Low_Speed + (int)duty; //带符号数和无符号数运算会转换为无符号数 Right_Forward_Duty = Left_Forward_Duty; //速度一样 } else if((-ASR.error) > allow_error) //设定值小于实际值,小车前进 { left_go_ahead; right_go_ahead; Left_Forward_Duty = (int)Low_Speed + (int)duty; Right_Forward_Duty = Left_Forward_Duty ; //速度一样 } else //在误差范围内,停车 car_stop(); } //浮点数限幅,constrain ->约束,限制 //如果输入不是数字,则返回极端值的平均值 //isnan函数检测输入是否是数字,is not a number float constrain_float(float amt, float low, float high) { // if (isnan(amt)) //51里面没有这个库函数,需要自己实现 // { // return (low+high)*0.5f; // } return ((amt)<(low)?(low):((amt)>(high)?(high):(amt))); } /* //16位整型数限幅 int constrain_int16(int amt, int low, int high) { return ((amt)<(low)?(low):((amt)>(high)?(high):(amt))); } */ //======================== //END OF FILE //========================
复制代码关于pid结构体的初始化,我在主函数中使用 memset()函数进行初始化,比较方便。
初始化之后,就是给赋上自己调试的参数。
//初始化PI调节器参数全为0, 该函数在string.h memset(&ASR, 0, sizeof(PID_value)); ASR.KP = distance_kp; ASR.KI = distance_ki; ASR.KD = distance_kd; ASR.T = T0; ASR.out_max = limit_max; ASR.out_min = limit_min;
复制代码============================================================
关于参数的整定:
首先是一位师傅给出的建议,记在这里,作为指导;《大体上是这个架构,采集,然后计算,但是有几个要注意得问题,不注意这些,可能永远调不好PID或者调出来得不理想,首先你的传感器响应速度?以及执行单元执行到温度传感器得时间是多少,这个要去测量一下。比如我加热单元开始加热,我需要0.5得控制精度,那么我从开始加热到传感器发现温度变化0.5所需得时间,t,那么就以这个得0.3-0.6去做采集时间,并以这个采集时间得3-5倍去作为PID计算得时间,并把这个时间,和Ki,Kd系数做一个处理。》
主控:STC89C52
超声波模块获取实际距离。三次平均滤波后,精度可达1cm。
程序中我设置距离为10cm,死区为(+-1.0cm)
当仅仅采用比例控制时,小车震荡比较严重,基本无法稳定
1、理论计算法:采用被控对象的准确模型进行数学建模。
2、工程整定法:不依赖被控对象的数学模型,直接在控制系统中进行现场整定,也就是现场调试。
其实这个调节关键还是多看,多试,如果有能力进行模型建立的话,那将更加精确。但是一般情况下使用试凑法。刚开始我尝试了不大概30组参数,总是不能达到理想的效果。
最后增加KI用以消除系统的稳态误差,但是注意要一点点的增加。不要加的太多。
进行了pid整定以后,效果如图。但是由于芯片处理速度和电机驱动性的问题,导致一直无法调节到最佳状态。
/******************************
参数整定找最佳,从小到大顺序查,
先是比例后积分,最后再把微分加,
曲线振荡很频繁,比例度盘要放大,
曲线漂浮绕大湾,比例度盘往小扳,
曲线偏离回复慢,积分时间往下降,
曲线波动周期长,积分时间再加长,
曲线振荡频率快,先把微分降下来,
动差大来波动慢,微分时间应加长,
理想曲线两个波,前高后低四比一,
一看二调多分析,调节质量不会低
*******************************/
关于PID还有很多很多地方等着去实验,我这里只是面向新手入门的。写的很浅显。但是一点点经验不至于让大家走那么多弯路。能对PID有一个初步的了解,这篇文章的目的算是达到了。
参考资料:
1、原理介绍:http://www.cnblogs.com/cjq0301/p/5184808.html
2、原理介绍:https://wenku.baidu.com/view/827c5423647d27284a735105.html
3、调参经验:http://www.51hei.com/bbs/dpj-51884-1.html
转载于:https://www.cnblogs.com/qsyll0916/p/8580211.html