table { margin: auto }

一、公式拆解

\(PID\)公式展示:

\[u(t)=K_p(e(t)+\frac{1}{T_t } ∫_0^te(t)dt+T_D \frac {de(t)}{dt})
\]

把\(K_p\)乘进去得:

\[u(t)=K_pe(t)+\frac{K_p}{T_t } ∫_0^te(t)dt+K_pT_D \frac {de(t)}{dt}
\]

令 \(K_p\) 为比例时间系数

令 \(K_i=\frac{K_p}{T_t }\) 为积分时间系数

令 \(K_d=K_pT_D\) 为微分时间系数

就变成了这个亚子:

\[u(t)=K_p e(t)+K_i ∫_0^te(t)dt+K_d\frac{de(t)}{dt}
\]

对于这个式子,其实涵盖了三种控制算法,每一种都可以单独拿出来。

比例控制算法\(P\):\(u(t)_1=K_p e(t)\)

积分控制算法\(I\):\(u(t)_2=K_i ∫_0^te(t)dt\)

微分控制算法\(D\):\(u(t)_3=K_d\frac{de(t)}{dt}\)

你没有看错!PID算法其实就是三个算法的组合,而且,在数学上面就是简单的代数和!

二、基于物理进程的解释

A.比例控制算法

在这里面,\(e(t)=目标值-当前值\),自然是离散数据,也就是说\(u(t)_1=K_p e(t)\)这个输出是根据当前值和目标值的差,乘以了一个比例系数得到的输出,

举个例子,假如我们要给一个100ml的A量筒装满水,此时A量筒里面已经有了20ml的水。而我们运水的工具是一个实际容积未知的B杯子

我们假如B杯子是一个50ml的杯子(我们并不知道是50ml),给其划上100等分的刻度,

那么根据条件可得 \(T_1=e(t)=目标值-当前值=100-20=80\) 。因此现在我们给B杯子装入可达第80刻度线的水并“一滴不漏”倒入A量筒,由于B杯子实际容积为50ml,所以实际倒入A量筒的水体积为40ml。

可以发现,我们要给100ml的量筒装满水,通过量筒的刻度我们轻易能获知还需要倒80ml才能装满杯子,但是B杯子的容积我们并不知道。我们给未知容积的B杯子划上刻线,以获知的80这个数据来给A量筒倒水,最好的情况是我的B杯子容积为100ml,这样的话按照80的刻度,一次性就可以把水加满。由于B杯子容积只有50,所以第一次加水只加了40ml,这便是比例系数的引入。

(这之间读者务必注意你的已知和未知)

从上文举的例子,我们可以明白,我们获知量和得知该量继而调整输出之间是难以对等的,例子中都是容积,实际上做控制时,将会是跨物理单位的转换,比如你获知你的小车速度设定为3m/s,实际只有2m/s,但你不可能直接给小车送定额的速度。你只能去调整等效电压、电流的大小,或者是PWM的占空比以使得速度达到3m/s,速度和电压必然是不对等的,但这之间会存在一个未知的关系,在这里,你可以认为是比例系数。

单纯以容积的刻度作为比照,此处B杯子50ml(我们刻了100等分刻度),所以虽然我们不知道容积,但这个过程,B杯子一个刻度的水量相当于A量筒的刻度的一半,所以实际上 Kp=0.5

(务必注意此处的0.5是根据刻度转换得来的,也就是你心中的量度转化为实际输出的关系,我看有些文章以容积为对比,这显然错误)

让强迫症把上面物理进程讲完吧

加了40ml水后,A量筒水位达到20+40=60ml的刻度,\(T_2=e(t)=目标值-当前值=100-60=40\)

依照这个40,B杯子加水到40刻度,\(K_p e(t)=0.5\times40=20ml\),倒进A之后水位达到60+20=80ml,如此不断无穷循环下去,A量筒最终会被装满。

(例子举的是B容积小于A量筒的情况,各位看官可以自行假想B容积大于A量筒的情形。我们使用时肯定是会逐步修正这个系数的)

B.积分控制算法

照前面比例控制算法,量筒最终会被装满,那么为何还需要别的算法呢?客官莫急,待老朽与您娓娓道来(娓娓道来:形容谈论不倦,或说话动听,没错,我是后者)

刚才比例控制算法那段内容我给“一滴不漏”特别标成了红色,实际上我们真的能一滴不漏吗?当然不能!

回到之前举的装水问题,现在已经装了\(80ml\),现在我们多加入一个条件,那就是我们每次加水,都会漏掉10ml

现在可得\(e(t)_2=100-80=20\),那么按照比例控制算法,我们通过B杯子加进去能加入10ml的水,但是现在又会漏掉\(10ml\),\(10ml-10ml=0ml\),哦豁,加完水还是\(80ml\),之后条件不变,意味着\(80ml\)到头了,升不上去了,可是到此为止离我们的目标100ml还差20ml呢!

现实中就是这样,你不可能想给多少那边就接收多少。

于是这里我们加入积分控制算法,

\[u(t)_2=K_i ∫_0^te(t)dt
\]
\[u(t)=u(t)_1+u(t)_2=K_p e(t)+K_i ∫_0^te(t)dt
\]

可以看到这个式子与误差的积分成正比,在积分系数已经定下时(时间肯定取单位时间),误差的积分越大,这个积分控制算法得到的也越大,先设它积分系数为0.2,接着上面的80ml继续计算,已知比例控制算法的输入和漏掉的相等,那么这是整个\(PI\)算法只需要看积分控制算法的输出就行了,,故积分控制输出4ml,很显然,积分控制的加入打破了之前的稳定状态。积分的来源是误差,误差的累加会增大输入,从而不让系统卡在稳定误差,事实上另一方面积分项也加快了整体控制算法的响应速度(因为\(e(t)\)的符号,让正的值更大,负的更小)。

最后到了目标值后,假如没有波动,回过去看看三种控制算法,貌似无论是比例还是微分控制,输出都是0,只有积分的值是固定输出(积分误差累加),很显然,就是为了消除之前那种稳定状态的静差。(静差:被控量的稳定值和给定值之差,一般用于衡量系统的准确性)静差很难被消除,但是通过积分控制可以尽量去减小,而且积分系数不可太大,太大静差反而更大,有网友给出就平衡车工程经验而言,一般\(K_i=\frac{K_p}{200}\),稍微来点就可以。

C.微分控制算法

这里就是对误差进行微分:

\[\frac{de(t)}{dt}=Error(now)-Error(past)
\]

当前值<目标值 时:一般在该调节过程中,误差是越来越小的(正实数运算),这也就可以得

\[Error(now)-Error(past)<0
\]

对于主要的比例控制而言,此时\(e(t)>0\),二者符号相反,换言之,这里微分控制起到了削减比例控制力度的作用

当前值>目标值 时: 输出过大,需要减小,对于比例控制,\(e(t)<0\),反观微分控制:

\[Error(now)<0
\]
\[Error(past)<0
\]
\[|Error(now)|<|Error(past)|
\]
\[-Error(now)<-Error(past)
\]
\[Error(now)>Error(past)
\]
\[Error(now)-Error(past)>0
\]

哦豁,符号又和比例控制相反,太显然了,这明摆着就是和主要的比例控制唱反调啊!太嚣张了!

如果愿意的话,还可以细究一些量的关系,这里就不多说了。

最终结论就是微分起到阻尼作用,减小震荡,提高稳定,减小变化趋势,但是也会降低响应速度。

总结一下,PID三个字母,大致可以说是P主管响应,I减小静差,D抑制震荡,但使用不当还会有反作用。

三、C语言代码框架和解释(单片机可用)

1、定义一个PID结构体代码

//定义一个PID结构体
typedef struct PID
{
double SetValue; //设定的目标值
double Kp; //比例系数
double Kd; //微分系数
double Ki; //积分系数
double Error1; //误差量,即为当前值和目标值的差
double Error2;//也是误差量,但是这里是Error1之后时间的当前值和目标值的差,假设Error1在前
double sum_error; //误差的总和,这个就是误差的积分
}PID;

2、初始化PID结构

//初始化PID结构,这里独立出来比较符合单片机的写法,哈哈,也可以写到主函数里面去
void PIDInit(PID*Stru) //这里就是随便弄了个结构体变量Stru,反正是传递地址,主函数里面随意
{
memset(Stru,0,sizeof(PID)); /*这里把Stru的内存块中全部替换为0,据我的经验,一开始分配好内存后都是些无意义数据或者残留数据,这里memset函数的作用实际上把Stru里面所有字节最后“sizeof(PID)”位的字节全部替换为0,因为这里位数为sizeof(PID),因此是初始化全部字节为0 */
} //PID的内部运算函数
double PID_OP(PID*Stru , double NewValue) //NewValue是新读取到的值
{
double dError,Error; //dError即为对误差微分,由于我们这里是处理离散数据,所以待会其实就是作差
//误差计算
Error= Stru->SetValue-NewValue; //这里是目标值和当前读取值的偏差,这里吧,无论是当前值在前还是目标值在前都是无所谓的,因为对于整个PID而言,误差的比例算法、积分算法、微分算法都是统一用的。不过话说回来,我不知道使用者是打算用什么负号表示输出,姑且这么用吧,使用者用时根据自己的理解用吧,毕竟咱这里只是个大体框架。
//加入微分算法
dError=Stru->Error2-Stru->Error1; //当前微分计算(离散)注意,这里如果误差越来越小的话无疑这里微分是负值
//加入积分算法
Stru->Sum_Error+=Error; //积分运算
Stru->Error1=Stru->Error2; //新误差值传递给旧误差,如此往复传递,为下个离散时间点PID的计算做准备
Stru->Error2=Error; //这里一开始Error2应该是0,但无所谓,这里计算过程用时是循环,后面会有值堆上去。
return(Stru->Kp*Error+Stru->Ki*Stru->Sum_Error+Stru->Kd*dError); //按颜色不同,分别为比例、积分、微分。但是可以注意的是,这里并不是初始的PID系数。在这个PID框架里面,很明显,是把初始的PID数学式展开了,后面的微积分两个系数都是和比例系数发生了运算的,这不影响。
}

3、输入函数和输出函数

//输入函数和输出函数,叫输入输出系统好些
//这里输入的就是读取编码盘数据,输出就是“控制器的输出”,大致就是改变PWM啥的吧,这个之后再说也行。这里就不能具体写了,是单片机的特色部分,本来我是不打算写上去的,不过参考的罗世洲先生的代码里面提到了这个,鄙人思索之下还是加上去了,毕竟框架嘛,嚯哈哈
void Input() //采集编码盘或别的数据
{
//加油
}
void System_Out(PID_out) //注意这里不是PID输出,而且根据PID结果进而进行相应硬件输出
{
//加油
}

4、定时器初始化函数

//定时器初始化函数
//设置定时中断作为控制周期
void time_interrupt_Init(这里可以放装填初值啥的,随意)
{
这里进行一些启动定时器中断、设置工作方式、装填初值啥的骚操作
}

5、中断函数

//中断函数
void Timer0() interrupt 1 //这里我当做51来做了
{
定时器硬件的装初值或其它操作语句,具体硬件具体分析;
PID_in=Input(); //执行输入函数,读取输入
PID_out=PID_OP(&Squ,PID_in); //运行PID的运算函数,得出PID_out,好用于系统输出函数
System_Out(PID_out); //系统输出
}

6、主函数

//主函数
void main()
{
PID Squ; //Squirtle是杰尼龟的英文名,杰尼龟在咱们国家被亲切叫做憨批龟,开个玩笑,这个结构体变量就用憨批龟吧!
double PID_out,PID_in; //定义PID的数据来源和数据输出
PIDInit(&Squ); //执行PID初始化函数,其实这里就只是全部存成0,记得吧,之前说的那个memset函数。
Squ.Kp=0.5; //设置比例系数,当然三个系数和目标值不一定得就这么赤裸裸写出来,换成读取感觉更灵活,随意
Squ.Ki=0.5; //设置积分系数
Squ.Kd=0.1; //设置微分系数,值得注意的是,物理进程里面这里起到阻尼作用,无论是对动态还是静态的阻尼,这都是一个“往回扳”的作用,变化越快阻尼也越大
Squ.SetValue=100.0; //设置目标值,这里是随便弄的 void time_interrupt_Init(这里可以放装填初值啥的,随意) //初始化定时器 while(1); //程序停在这里等待中断发生
主函数里面还可以放一些存储调试数据的语句,这种语句优先等级低,在不干扰正常操作情况下存储或者发送调试数据,个人认为对调试有一定帮助(强烈推荐eeprom);
}

P.S.:我们在PID控制中不可能处理并且输出连续数据,因此这本文档中都是处理离散数据,而这些离散数据的源头是编码盘或者其它硬件,这也就意味着必须考虑采集数据的装置的采集速度、周期。采集完后又得CPU进行处理,处理又需要时间,电压或者电流的响应也需要时间。为了不让其中发生“碰撞”,这就需要我们去设置一个控制周期,调整好二者,给予二者良好配合的空间。

四、PID归纳

写程序不难,难的是调出一个好的参数

在实际使用中,不一定非要把P、I、D三个算法都用上

依据需要进行组合使用吧

各个系数的作用表

性能指标 参数
Kp增大 Ki减小 Kd增大
偏差 增大 增大 减小
稳态误差 减小 ----- -----
超调量 增大 增大 减小
振荡频率 增大 增大 增大

位置式PID讲解的更多相关文章

  1. 位置式PID与增量式PID算法

    位置式PID与增量式PID算法  PID控制是一个二阶线性控制器     定义:通过调整比例.积分和微分三项参数,使得大多数的工业控制系统获得良好的闭环控制性能.     优点             ...

  2. 位置式PID与增量式PID

    //位置式PID float Kp; float Ki; float Kd; float eSum,e0,e1; float pid_control(float now,float target) { ...

  3. 增量式pid和位置式PID参数整定过程对比

    //增量式PID float IncPIDCalc(PID_Typedef* PIDx,float SetValue,float MeaValue)//err»ý·Ö·ÖÀë³£Êý { PIDx-& ...

  4. 【转】位置式、增量式PID算法C语言实现

    位置式.增量式PID算法C语言实现 芯片:STM32F107VC 编译器:KEIL4 作者:SY 日期:2017-9-21 15:29:19 概述 PID 算法是一种工控领域常见的控制算法,用于闭环反 ...

  5. 增量式PID简单翻板角度控制

    1.研究背景 随着电子技术.信息技术和自动控制理论技术的完善与发展,近来微型处理器在控制方面的应用也越来越多.随之逐渐渗透到我们生活的各个领域.如导弹导航装置,飞机上仪表的控制,网络通讯与数据传输,工 ...

  6. 增量式PID的matlab实现

    首先,增量式PID的实现公式: 式中 Δe(k)=e(k)-e(k-1) 进一步可以改写成 式中      . . 为了便于理解,也可写成: 式中e(k)为第k次采样时的设定值与实际值的差,e(k-1 ...

  7. 外置式与增量式PID模板程序(51单片机c语言)

    外置式PID模板 #define MuBiaoCS 0 //目标常数 #define CHang_aCS 0 //比例常数 #define CHang_bCS 0 //积分常数 #define CHa ...

  8. PID控制器开发笔记之十:步进式PID控制器的实现

    对于一般的PID控制系统来说,当设定值发生较大的突变时,很容易产生超调而使系统不稳定.为了解决这种阶跃变化造成的不利影响,人们发明了步进式PID控制算法. 1.步进式PID的基本思想 所谓步进式PID ...

  9. PID控制算法的C语言实现三 位置型PID的C语言实现

    上一节中已经抽象出了位置性PID和增量型PID的数学表达式,这一节,重点讲解C语言代码的实现过程,算法的C语言实现过程具有一般性,通过PID算法的C语言实现,可以以此类推,设计其它算法的C语言实现. ...

随机推荐

  1. [luogu5163]WD与地图

    将删边改为插边,如果是无向图直接线段树合并即可,考虑如何将有向边转换为无向边 令$t_{i}$表示当插入到第$t_{i}$条边时恰好满足$x_{i}$与$y_{i}$在同一个强连通分量中,然后分类讨论 ...

  2. [loj3366]嘉年华奖券

    联系绝对值的几何意义/分类讨论,不难发现若$n$张奖券上的数从小到大依次为$a_{i}$,则收益为$\sum_{i=1}^{\frac{n}{2}}a_{i+\frac{n}{2}}-a_{i}$ 假 ...

  3. Python集合&字典

    Python集合&字典 @ 目录 字典 字典的几种定义方式 第一种 第二种 第三种 字典的一些基本操作 通过key得到value 增加字典键值对 删除字典键值对 格式化操作 清空字典 复制字典 ...

  4. [USACO07NOV]Cow Relays G

    题目大意 给出一张无向连通图(点数小于1000),求S到E经过k条边的最短路. 算法 这是之前国庆模拟赛的题 因为懒 所以就只挑一些题写博客 在考场上写了个dp 然后水到了50分 出考场和神仙们一问才 ...

  5. 洛谷 P5406 - [THUPC2019]找树(FWT+矩阵树定理)

    题面传送门 首先看到这道题你必须要有一个很清楚的认识:这题新定义的 \(\oplus\) 符号非常奇怪,也没有什么性质而言,因此无法通过解决最优化问题的思路来解决这个问题,只好按照计数题的思路来解决, ...

  6. DirectX12 3D 游戏开发与实战第八章内容(上)

    8.光照 学习目标 对光照和材质的交互有基本的了解 了解局部光照和全局光照的区别 探究如何用数学来描述位于物体表面上某一点的"朝向",以此来确定入射光照射到表面的角度 学习如何正确 ...

  7. Linux系统编程之命名管道与共享内存

    在上一篇博客中,我们已经熟悉并使用了匿名管道,这篇博客我们将讲述进程间通信另外两种常见方式--命名管道与共享内存. 1.命名管道 管道是使用文件的方式,进行进程之间的通信.因此对于管道的操作,实际上还 ...

  8. Linux—find在指定路径下查找文件或目录

    find /指定路径  -name  "*filename*" find /指定路径  -name  "*filename*"  2>/dev/null  ...

  9. 39-Remove Duplicates from Sorted Array

    Remove Duplicates from Sorted Array My Submissions QuestionEditorial Solution Total Accepted: 127836 ...

  10. 浏览器点击URL的响应过程

    原文:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/ 作为一个软件开发者,你一定会对网络应用如何工作有 ...