赞
踩
PID控制器(比例-积分-微分控制器),首先以一个通俗而又经典的小故事来介绍它。
/************************小故事分界线*****************************/
小明接到这样一个任务:
有一个水缸点漏水(而且漏水的速度还不一定固定不变),
要求水面高度维持在某个位置,
一旦发现水面高度低于要求位置,就要往水缸里加水。
小明接到任务后就一直守在水缸旁边,
时间长就觉得无聊,就跑到房里看小说了,
每30分钟来检查一次水面高度。水漏得太快,
每次小明来检查时,水都快漏完了,离要求的高度相差很远
,小明改为每3分钟来检查一次,结果每次来水都没怎么漏
,不需要加水,来得太频繁做的是无用功。几次试验后,
确定每10分钟来检查一次。这个检查时间就称为采样周期。
开始小明用瓢加水,水龙头离水缸有十几米的距离,
经常要跑好几趟才加够水,于是小明又改为用桶加,
一加就是一桶,跑的次数少了,加水的速度也快了,
但好几次将缸给加溢出了,不小心弄湿了几次鞋,小明又动脑筋,
我不用瓢也不用桶,老子用盆,几次下来,发现刚刚好,不用跑太多次,
也不会让水溢出。这个加水工具的大小就称为比例系数。
小明又发现水虽然不会加过量溢出了,有时会高过要求位置比较多
,还是有打湿鞋的危险。他又想了个办法,在水缸上装一个漏斗,
每次加水不直接倒进水缸,而是倒进漏斗让它慢慢加。这样溢出的问题解决了,
但加水的速度又慢了,有时还赶不上漏水的速度。
于是他试着变换不同大小口径的漏斗来控制加水的速度
,最后终于找到了满意的漏斗。漏斗的时间就称为积分时间 。
小明终于喘了一口,但任务的要求突然严了,
水位控制的及时性要求大大提高,一旦水位过低,
必须立即将水加到要求位置,而且不能高出太多,否则不给工钱。
小明又为难了!于是他又开努脑筋,终于让它想到一个办法,常放一盆备用水在旁边,
一发现水位低了,不经过漏斗就是一盆水下去,这样及时性是保证了,但水位有时会高多了。
他又在要求水面位置上面一点将水凿一孔,再接一根管子到下面的备用桶里这样多出的水会从上面的孔里漏出来。
这个水漏出的快慢就称为微分时间。
/************************小故事分界线*****************************/
好,故事看完了,相信大家对PID算法有了初步认识,接下来就深入一点进行讲解:
PID控制器由比例单元(P)单元(I)单元(D),其输入err(t)与输出u(t)的关系为:
①比例P控制
理解:比例控制中控制器的输出u与输入误差信号err成比例关系。假如某一刻t你的预期值是E,而实际值为A,那么误差值就为err(t)=E-A,这时候控制器输出就为u(t)=Kp*err(t)。
*问:假设你有一个加减计算器,由于某种原因你每次只能加或减u(t)并且比例系数Kp=0.4,让你从0开始去逼近100。
*解:显然,这时候你的预期值E为100,而实际值为0,那么误差值就为err(t1)=100-0,进行第一次加减运算(你按计算器加的值为u(t1)=0.4*100=40),好了,第一次计算器从0加到了40;
第二次,预期100,实际40,误差60,进行加减运算(你按计算机加的值为u(t2)=0.4*60=24),第二次计算器从40加到了64;
第三次,预期100,实际64,误差36,进行加减运算(你按计算器加的值为u(t3)=0.4*36=14.4),第三次计算器从64加到了78.4;
第四次从78.4加到87.04;
第五次87.04加到92.224
.
.
.
剩下的就不打了,贴图吧(data0)
由于比例系数较低,大家可以看到数据是慢慢地逼近100,实际上加减运算到第23次时,实际值A=99.999,显然比例控制已经到达一个比较可观的精度了。如果比例系数Kp设为1则可以一次运算到达100。
②积分I控制
理解:在积分控制中控制器的输出与输入误差信号的积分成正比关系。如果系统进入稳态后存在稳态误差,必须引入积分环节以消除误差。积分项为误差对时间的积分,即便误差很小,随着时间的增加积分项也会增大,引导控制器增大输出使稳态误差减小,直至稳态误差为0。如下图所示(图中红色为稳态误差,积分控制下逐渐消除)
③微分D控制
理解:在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。误差变化越快,其微分绝对值越大。误差增大时,其微分为正;误差减小时,其微分为负;只有误差为常数时,微分才为0。总体来讲微分环节能预测变化的趋势来提前抑制误差直至微分为0。如上图所示(曲线的斜率为微分值,当误差为常数或者为0时,微分值即曲线的斜率为0)
/*****************************总结********************************/
对于上述3种控制环节:
比例(P)+积分(I)控制器可以使系统在进入稳态后无稳态误差;
比例(P)+微分(D)控制器能改善系统在调节过程中的动态特性;
总的来说,PID控制其实是对反馈的误差进行控制的过程,如果误差为0,则比例环节不起作用,只有误差存在才进行比例调节;积分环节是用来消除稳态误差;微分环节根据误差信号的变化趋势进行超前调节。
/**************************************************************************/
PID基本原理讲解到此结束,接下来讲讲编程实现。
原始公式是连续状态的,为了方便在处理器上实现,通常对PID连续系统离散化。
假设采样周期间隔为T,则在KT时刻:
偏差err(K)=E(k)-A(k);
积分环节用加和形式表示:err(k)+err(K-1)+…err(0);
微分环节用斜率形式表示:[err(k)-err(k-1)]/T;
比例系数:Kp;
积分系数:Ki=Kp*T/Ti;
微分系数:Kd=Kp*Td/T;
从而得到PID离散表示形式:
上式也是位置式PID的表达形式。
我们求出u(k-1),然后求出△u(k)=u(k)-u(k-1),就得到增量式PID的表达形式:
既然表达式都有了,那现在就用C语言来实例化PID控制器:
- struct _pid
- {
- float ExpectedValue;//定义设定值
- float ActualValue;//定义实际值
- float err;//定义偏差值
- float err_last;//定义上一个偏差值
- float err_prev;//定义前一个的偏差值
- float Kp, Ki, Kd;//定义比例、积分、微分系数
- }pid;
-
- void PID_Init()
- {
- pid.ExpectedValue = 0.0;
- pid.ActualValue = 0.0;
- pid.err = 0.0;
- pid.err_prev = 0.0;
- pid.err_last = 0.0;
- pid.Kp = 0.4;
- pid.Ki = 0.2;
- pid.Kd = 0.0;
- }
-
- float PID_Realize(float speed) {
- float index;
- pid.ExpectedValue = speed;
- pid.err = pid.ExpectedValue - pid.ActualValue;
- //增量式pid公式
- float incrementValue = pid.Kp*(pid.err - pid.err_last) + pid.Ki*pid.err + pid.Kd*(pid.err - 2 * pid.err_last + pid.err_prev);
- pid.ActualValue += incrementValue;
- pid.err_prev = pid.err_last;
- pid.err_last = pid.err;
- return pid.ActualValue;
- }
曲线1:设初始值为0,期望值ExpectedValue为100,比例系数Kp为0.5,积分系数Ki为0.2,微分系数kd为0.1,采样周期为20ms,让PID进行200次调节,得到如下曲线(data1):
设当实际值ActualValue达到199.999时认为稳态,通过查看数据得第84次运算时达到条件,所用时间为84*20(ms)=1680(ms)=1.68(s)
曲线2:设初始值为0,期望值ExpectedValue为100,比例系数Kp为0.5,积分系数Ki为0.3,微分系数kd为0.0,采样周期为20ms,让PID进行200次调节,得到如下曲线:
设当ActualValue达到199.999时认为稳态,通过查看数据得第56次运算时达到条件,所用时间为56*20(ms)=1120(ms)=1.12(s),可见调参之后系统达到稳态时间缩减33.3%,并且调节过程更为稳定。
*注:上面用反应曲线法进行粗略地参数整定,但PID控制器的参数整定往往需要实际工程操作进行调整修改才能得出最优解,这就要求大家要多动手实操了。
经过几天对PID的学习,以及各种的查阅资料,今天自己动手写了一个闭环小车的PID调速程序。网上关于PID原理的资料文献都有很多,这里我就不多说了,直接给出自己写的程序,希望对那些看了很多资料不敢动手写程序的学者有一些帮助。(我自己也是一个PID的初学者,写这篇博客也是为了加深自己的理解,写的不好的地方,请大家多多提意见)
下面是.c文件的程序
#include<pid.h>
#include<intrins.h>
typedef unsigned char u8;
u8 PWM1,PWM2;
typedef struct _pid
{
float SetSpeed; //定义设定值
float ActualSpeed;//定义实际值
float err; //定义偏差值
float err_next; //定义上一个偏差值
float err_last; //定义最上前的偏差值
float err_sum; //定义偏差和
float Kp,Ki,Kd; //定义比例、积分、微分系数
}PID_Value;
PID_Value pid;
void PIDInit()
{
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_next=0.0;
pid.err_last=0.0;
pid.err_sum=0.0;
pid.Kp=1;
pid.Ki=0;
pid.Kd=0;
}
/*PID运算函数*/
float SetSpeed_PID(float speed1,float speed2)
{
float PIDIncSpeed;
pid.SetSpeed=speed1;
pid.ActualSpeed=speed2;
pid.err=pid.SetSpeed-pid.ActualSpeed;
pid.err_sum+=pid.err;
PIDIncSpeed=pid.Kp*(pid.err)+pid.Ki*pid.err_sum+pid.Kd*(pid.err_next-pid.err_last);
pid.ActualSpeed+=PIDIncSpeed;
pid.err_last=pid.err_next;
pid.err_next=pid.err;
return pid.ActualSpeed;
}
void forward()
{
left_motor_1=1;
left_motor_2=0;
right_motor_1=1;
right_motor_1=0;
PWM1=SetSpeed_PID(20.0,Speed);
PWM2=20;
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
.c文件采用的是增量型PID的调节
这里是.h文件的程序
#ifndef __PID_H
#define __PID_H
#include<reg52.h>
#include<intrins.h>
sbit ENB=P2^0; //右轮电机转动使能控制位
sbit right_motor_1=P2^2;//右电机控制位1
sbit right_motor_2=P2^1;//右电机控制位2
sbit left_motor_1=P2^3; //左电机控制位1
sbit left_motor_2=P2^4; //左电机控制位2
sbit ENA=P2^5; //左轮电机转动使能控制位
extern u16 PWM1,PWM2;
void forward();
float SetSpeed_PID(float speed1,float speed2);
#endif
1234567891011121314
程序就写到这里了,希望对我自己和初学者的你们都有帮助。
对于三个参数的确定,一般的也是大都数人都在使用的方法就是:将Ki、Kd先赋值为零,确定Kp,之后是第二个参数Ki,最后才是微分参数Kd。(三个系数的确定需要不断的仿真,找到比较适合的值)
---------------------
作者:不怨天、不尤人
来源:CSDN
原文:PID(程序学习)_键盘上的手艺人-CSDN博客_pid程序
版权声明:本文为博主原创文章,转载请附上博文链接!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。