PID控制算法的C语言实现
参考:
专家PID控制在快速系统中的仿真及应用(这篇了论文介绍的积分分离PID、专家PID(脚本实现和simulink实现)很详细)
PID控制算法的C语言实现一 PID算法原理
在工业应用中PID及其衍生算法是应用最广泛的算法之一,是当之无愧的万能算法,如果能够熟练掌握PID算法的设计与实现过程,对于一般的研发人员来讲,应该是足够应对一般研发问题了,而难能可贵的是,在我所接触的控制算法当中,PID控制算法又是最简单,最能体现反馈思想的控制算法,可谓经典中的经典。经典的未必是复杂的,经典的东西常常是简单的,而且是最简单的,想想牛顿的力学三大定律吧,想想爱因斯坦的质能方程吧,何等的简单!简单的不是原始的,简单的也不是落后的,简单到了美的程度。先看看PID算法的一般形式:
PID的流程简单到了不能再简单的程度,通过误差信号控制被控量,而控制器本身就是比例、积分、微分三个环节的加和。这里我们规定(在t时刻):
1.输入量为rin(t);
2.输出量为rout(t);
3.偏差量为err(t)=rin(t)-rout(t);
pid的控制规律为
理解一下这个公式,主要从下面几个问题着手,为了便于理解,把控制环境具体一下:
1.规定这个流程是用来为直流电机调速的;
2.输入量rin(t)为电机转速预定值;
3.输出量rout(t)为电机转速实际值;
4.执行器为直流电机;
5.传感器为光电码盘,假设码盘为10线;
6.直流电机采用PWM调速 转速用单位 转/min 表示;
不难看出以下结论:
1.输入量rin(t)为电机转速预定值(转/min);
2. 输出量rout(t)为电机转速实际值(转/min);
3.偏差量为预定值和实际值之差(转/min);
那么以下几个问题需要弄清楚:
1.通过PID环节之后的U(t)是什么值呢?
2.控制执行器(直流电机)转动转速应该为电压值(也就是PWM占空比)。
3.那么U(t)与PWM之间存在怎样的联系呢?
http://blog.21ic.com/user1/3407/archives/2006/33541.html(见附录1)这篇文章上给出了一种方法,即,每个电压对应一个转速,电压和转速之间呈现线性关系。但是我考虑这种方法的前提是把直流电机的特性理解为线性了,而实际情况下,直流电机的特性绝对不是线性的,或者说在局部上是趋于线性的,这就是为什么说PID调速有个范围的问题。具体看一下http://articles.e-works.net.cn/component/article90249.htm(见附录2)这篇文章就可以了解了。所以在正式进行调速设计之前,需要现有开环系统,测试电机和转速之间的特性曲线(或者查阅电机的资料说明),然后再进行闭环参数整定。这篇先写到这,下一篇说明连续系统的离散化问题。并根据离散化后的特点讲述位置型PID和增量型PID的用法和C语言实现过程。
PID控制算法的C语言实现二 PID算法的离散化
上一节中,我论述了PID算法的基本形式,并对其控制过程的实现有了一个简要的说明,通过上一节的总结,基本已经可以明白PID控制的过程。这一节中先继续上一节内容补充说明一下。
1.说明一下反馈控制的原理,通过上一节的框图不难看出,PID控制其实是对偏差的控制过程;
2.如果偏差为0,则比例环节不起作用,只有存在偏差时,比例环节才起作用。
3.积分环节主要是用来消除静差,所谓静差,就是系统稳定后输出值和设定值之间的差值,积分环节实际上就是偏差累计的过程,把累计的误差加到原有系统上以抵消系统造成的静差。
4.而微分信号则反应了偏差信号的变化规律,或者说是变化趋势,根据偏差信号的变化趋势来进行超前调节,从而增加了系统的快速性。
好了,关于PID的基本说明就补充到这里,下面将对PID连续系统离散化,从而方便在处理器上实现。下面把连续状态的公式再贴一下:
假设采样间隔为T,则在第K个 T时刻:
偏差err(K)=rin(K)-rout(K);
积分环节用加和的形式表示,即err(K)+err(K+1)+……;
微分环节用斜率的形式表示,即[err(K)-err(K-1)]/T;
从而形成如下PID离散表示形式:
其中T为采样时间,Kp为比例带,TI为积分时间,TD为微分时间。PID控制的基本原理就是如此。
则u(K)可表示成为:位置型PID
可以这么理解:比例环节将误差线性放大,积分环节将误差值的积分放大,微分环节将两次的误差值放大,所有的值相加得到最终输出值
在不断变化中,err(k)(设定值与实际输出值差值)会降低或者升高,不断降低时比例和微分环节对最终的输出贡献变少而积分环节因为误差值的不断累加贡献最大。
至于说Kp、Ki、Kd三个参数的具体表达式,我想可以轻松的推出了,这里节省时间,不再详细表示了。
其实到这里为止,PID的基本离散表示形式已经出来了。目前的这种表述形式属于位置型PID,另外一种表述方式为增量式PID,由上述表达式可以轻易得到:增量式PID
那么:
这就是离散化PID的增量式表示方式,由公式可以看出,增量式的表达结果和最近三次的偏差有关,这样就大大提高了系统的稳定性。需要注意的是最终的输出结果应该为
u(K)+增量调节值;
PID的离散化过程基本思路就是这样,下面是将离散化的公式转换成为C语言,从而实现微控制器的控制作用。
当然,增量型PID必须记得一点,就是在记住U(k)=U(k-1)+∆U(k)。
PID 控制算法可以分为位置式 PID 和增量式 PID 控制算法。两者的区别
PID 控制算法可以分为位置式 PID 和增量式 PID 控制算法。
两者的区别:
(1)位置式PID控制的输出与整个过去的状态有关,用到了误差的累加值;而增量式PID的输出只与当前拍和前两拍的误差有关,因此位置式PID控制的累积误差相对更大;
(2)增量式PID控制输出的是控制量增量,并无积分作用,因此该方法适用于执行机构带积分部件的对象,如步进电机等,而位置式PID适用于执行机构不带积分部件的对象,如电液伺服阀。
(3)由于增量式PID输出的是控制量增量,如果计算机出现故障,误动作影响较小,而执行机构本身有记忆功能,可仍保持原位,不会严重影响系统的工作,而位置式的输出直接对应对象的输出,因此对系统影响较大。
PID控制算法的C语言实现三 位置型PID的C语言实现
上一节中已经抽象出了位置性PID和增量型PID的数学表达式,这一节,重点讲解C语言代码的实现过程,算法的C语言实现过程具有一般性,通过PID算法的C语言实现,可以以此类推,设计其它算法的C语言实现。
第一步:定义PID变量结构体,代码如下:
struct _pid{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义电压值(控制执行器的变量)
float integral; //定义积分值
}pid;
控制算法中所需要用到的参数在一个结构体中统一定义,方便后面的使用。
第二部:初始化变量,代码如下:
void PID_init(){
printf("PID_init begin \n");
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.voltage=0.0;
pid.integral=0.0;
pid.Kp=0.2;
pid.Ki=0.015;
pid.Kd=0.2;
printf("PID_init end \n");
}
统一初始化变量,尤其是Kp,Ki,Kd三个参数,调试过程当中,对于要求的控制效果,可以通过调节这三个量直接进行调节。
第三步:编写控制算法,代码如下:
float PID_realize(float speed){
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
pid.integral+=pid.err;
pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
注意:这里用了最基本的算法实现形式,没有考虑死区问题,没有设定上下限,只是对公式的一种直接的实现,后面的介绍当中还会逐渐的对此改进。
到此为止,PID的基本实现部分就初步完成了。下面是测试代码:
int main(){
printf("System begin \n");
PID_init();
int count=0;
while(count<1000)
{
float speed=PID_realize(200.0);
printf("%f\n",speed);
count++;
}
return 0;
}
下面是经过1000次的调节后输出的1000个数据(具体的参数整定过程就不说明了,网上这种说明非常多):(结果自行运行查询)
1 #include<stdio.h>
2
3 struct _pid {
4 float SetSpeed; //定义设定值
5 float ActualSpeed; //定义实际值
6 float err; //定义偏差值
7 float err_last; //定义上一个偏差值
8 float Kp, Ki, Kd; //定义比例、积分、微分系数
9 float voltage; //定义电压值(控制执行器的变量)
10 float integral; //定义积分值
11 }pid;
12
13 void PID_init() {
14 printf("PID_init begin \n");
15 pid.SetSpeed = 0.0;
16 pid.ActualSpeed = 0.0;
17 pid.err = 0.0;
18 pid.err_last = 0.0;
19 pid.voltage = 0.0;
20 pid.integral = 0.0;
21 pid.Kp = 0.2;
22 pid.Ki = 0.015;
23 pid.Kd = 0.2;
24 printf("PID_init end \n");
25 }
26
27 float PID_realize(float speed) {
28 pid.SetSpeed = speed;
29 pid.err = pid.SetSpeed - pid.ActualSpeed;
30 pid.integral += pid.err;
31 pid.voltage = pid.Kp*pid.err + pid.Ki*pid.integral + pid.Kd*(pid.err - pid.err_last);
32 pid.err_last = pid.err;
33 pid.ActualSpeed = pid.voltage*1.0;
34 return pid.ActualSpeed;
35 }
36
37 int main()
38 {
39 printf("System begin \n");
40 PID_init();
41 int count = 0;
42 while (count < 1500)
43 {
44 float speed = PID_realize(200.0);
45 printf("%f\n", speed);
46 count++;
47 }
48 return 0;
49 }
PID控制算法的C语言实现四 增量型PID的C语言实现
上一节中介绍了最简单的位置型PID的实现手段,这一节主要讲解增量式PID的实现方法,位置型和增量型PID的数学公式请参见我的系列文《PID控制算法的C语言实现二》中的讲解。实现过程仍然是分为定义变量、初始化变量、实现控制算法函数、算法测试四个部分,详细分类请参加《PID控制算法的C语言实现三》中的讲解,这里直接给出代码了。
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 struct _pid{
5 float SetSpeed; //定义设定值
6 float ActualSpeed; //定义实际值
7 float err; //定义偏差值
8 float err_next; //定义上一个偏差值
9 float err_last; //定义最上前的偏差值
10 float Kp,Ki,Kd; //定义比例、积分、微分系数
11 }pid;
12
13 void PID_init(){
14 pid.SetSpeed=0.0;
15 pid.ActualSpeed=0.0;
16 pid.err=0.0;
17 pid.err_last=0.0;
18 pid.err_next=0.0;
19 pid.Kp=0.2;
20 pid.Ki=0.015;
21 pid.Kd=0.2;
22 }
23
24 float PID_realize(float speed){
25 pid.SetSpeed=speed;
26 pid.err=pid.SetSpeed-pid.ActualSpeed;
27 float incrementSpeed=pid.Kp*(pid.err-pid.err_next)+pid.Ki*pid.err+pid.Kd*(pid.err-2*pid.err_next+pid.err_last);
28 pid.ActualSpeed+=incrementSpeed;
29 pid.err_last=pid.err_next;
30 pid.err_next=pid.err;
31 return pid.ActualSpeed;
32 }
33
34 int main(){
35 PID_init();
36 int count=0;
37 while(count<1000)
38 {
39 float speed=PID_realize(200.0);
40 printf("%f\n",speed);
41 count++;
42 }
43 return 0;
44 }
运行后的1000个数据为:(结果自行运行观看)
结论:从最终数据的结果显示来看,增量式PID数据的稳定性要好于位置型PID的;
PID控制算法的C语言实现五 积分分离的PID控制算法C语言实现
通过三、四两篇文章,基本上已经弄清楚了PID控制算法的最常规的表达方法。在普通PID控制中,引入积分环节的目的,主要是为了消除静差,提高控制精度。但是在启动、结束或大幅度增减设定时,短时间内系统输出有很大的偏差,会造成PID运算的积分积累,导致控制量超过执行机构可能允许的最大动作范围对应极限控制量,从而引起较大的超调,甚至是震荡,这是绝对不允许的。
为了克服这一问题,引入了积分分离的概念,其基本思路是 当被控量与设定值偏差较大时,取消积分作用; 当被控量接近给定值时,引入积分控制,以消除静差,提高精度。其具体实现代码如下:
具体的实现步骤是:根据实际情况,设定一个阈值;当偏差大于阈值时,消除积分仅用PD控制;当偏差小于等于阈值时,引入积分采用PID控制。则控制算法可表示为:
其中β称为积分开关系数,其取值范围为:
由上述表述及公式我们可以知道,积分分离算法的效果其实与ε值的选取有莫大关系,所以ε值的选取实际上是实现的难点,ε值过大则达不到积分分离的效果,而ε值过小则难以进入积分区,ε值的选取存在很大的主观因素。
对于经典的增量式PID算法,似乎没有办法由以上的公式推导而来,因为β随着err(k)的变化在修改着控制器的表达式。其实我们可以换一种角度考虑,每次系统调节未定后,偏差应该为零,然后只有当设定值改变时,系统才会响应而开始调节。设定值的改变实际上是一个阶跃变化,此时的控制输出记为U0,开始调节时,其调节增量其实与之前的一切没有关系。所以我们就可以以变化时刻开始为起点,而得到带积分分离的增量算法,以保证在启动、停止和快速变化时防止超调。公式如下:
其中β的取值与位置型PID算法一致。可能有人会担心偏差来回变化,造成积分作用的频繁分离和引入,进而使上述的增量表达式无法实现。其实我们分析一下就能发现,在开始时,由于设定值变化引起的偏差大而分离了积分作用,在接近设定值时,偏差变小就引入了积分,一边消除静差,而后处于稳态,直到下一次变化。
我们根据前面对其基本思想的描述,来实现基于积分分离的PID算法实现,同样是包括位置型和增量型两种实现方式。首先我们来看一下算法的实现过程,具体的流程图如下:
有上图我们知道,与普通的PID算法的区别,只是判断偏差的大小,偏差大时,为PD算法,偏差小时为PID算法。于是我们需要一个偏差检测与积分项分离系数β的函数。
积分分离的PID控制算法——位置型PID的变化
红色标记是相对于位置型PID的不同的地方
1 #include<stdio.h>
2
3 struct _pid {
4 float SetSpeed; //定义设定值
5 float ActualSpeed; //定义实际值
6 float err; //定义偏差值
7 float err_last; //定义上一个偏差值
8 float Kp, Ki, Kd; //定义比例、积分、微分系数
9 float voltage; //定义电压值(控制执行器的变量)
10 float integral; //定义积分值
11 }pid;
12
13 void PID_init() {
14 printf("PID_init begin \n");
15 pid.SetSpeed = 0.0;
16 pid.ActualSpeed = 0.0;
17 pid.err = 0.0;
18 pid.err_last = 0.0;
19 pid.voltage = 0.0;
20 pid.integral = 0.0;
21 pid.Kp = 0.2;
22 pid.Ki = 0.04; //之前KI值是0.015比之前的大
23 pid.Kd = 0.2; //初始化过程
24 printf("PID_init end \n");
25 }
26
27 float PID_realize(float speed) {
28 int index;
29 pid.SetSpeed = speed;
30 pid.err = pid.SetSpeed - pid.ActualSpeed;
31 pid.integral += pid.err;
32 if (abs(pid.err) > 200)
33 {
34 index = 0;
35 }
36
37 else
38
39 {
40 index = 1;
41 // pid.integral += pid.err;
42 }
43 pid.voltage = pid.Kp*pid.err + index * pid.Ki*pid.integral + pid.Kd*(pid.err - pid.err_last); //算法具体实现过程
44 pid.err_last = pid.err;
45 pid.ActualSpeed = pid.voltage*1.0;
46 return pid.ActualSpeed;
47 }
48
49 int main()
50 {
51 printf("System begin \n");
52 PID_init();
53 int count = 0;
54 while (count < 1500)
55 {
56 float speed = PID_realize(200.0);
57 printf("%f\n", speed);
58 count++;
59 }
60 return 0;
61 }
运行后的1000个数据为:(结果自行运行观看)
结论:同样采集1000个量,会发现,系统到199所有的时间是原来时间的1/2,系统的快速性得到了提高。
积分分离的PID控制算法——增量型PID的变化
方法一:最后结果在两个数之间波动(没搞清为什么)
1 #include<stdio.h>
2 #include<stdint.h>
3 #include<math.h>
4 #pragma warning(disable : 4305)
5 #pragma warning(disable : 4578)
6
7 static uint16_t BetaGeneration(float error, float epsilon)
8 {
9 int beta;
10 if (abs(error) > epsilon)
11 {
12 beta = 0;
13 }
14 else
15 {
16 beta = 1;
17
18 }
19
20 return beta;
21
22 }
23
24
25 /*定义结构体和公用体*/
26 typedef struct
27 {
28 float setpoint; //设定值
29 float proportiongain; //比例系数
30 float integralgain; //积分系数
31 float derivativegain; //微分系数
32 float lasterror; //前一拍偏差
33 float preerror; //前两拍偏差
34 float deadband; //死区
35 float result; //输出值
36 float epsilon; //偏差检测阈值
37 }PID;
38
39 //接下来实现PID控制器:
40 void PIDRegulation(PID *vPID, float processValue)
41 {
42 float thisError;
43 float increment;
44 float pError, dError, iError;
45
46 thisError = vPID->setpoint - processValue; //得到偏差值
47 pError = thisError - vPID->lasterror;
48 iError = thisError;
49 dError = thisError - 2 * (vPID->lasterror) + vPID->preerror;
50 uint16_t beta = BetaGeneration(thisError, vPID->epsilon);
51
52 if (beta > 0)
53 {
54 increment = vPID->proportiongain*pError + vPID->derivativegain*dError; //增量计算
55 }
56 else
57 {
58 increment = vPID->proportiongain*pError + vPID->integralgain*iError + vPID->derivativegain*dError; //增量计算
59 }
60 vPID->preerror = vPID->lasterror; //存放偏差用于下次运算
61 vPID->lasterror = thisError;
62 vPID->result += increment;
63 }
64 void PID_init(PID *pid)
65 {
66 printf("PID_init begin \n");
67 pid->setpoint = 200.0; //设定值
68 pid->proportiongain = 0.2; //比例系数
69 pid->integralgain = 0.35; //积分系数
70 pid->derivativegain = 0.2; //微分系数
71 pid->lasterror = 0; //前一拍偏差
72 pid->preerror = 0; //前两拍偏差
73 pid->deadband; //死区
74 pid->result = 0; //输出值
75 pid->epsilon = 0.1; //偏差检测阈值
76 printf("PID_init end \n");
77 }
78
79 int main()
80 {
81 printf("System begin \n");
82 PID pid1;
83 PID_init(&pid1);
84 int cout = 1000;
85 while (cout)
86 {
87 PIDRegulation(&pid1, pid1.result);
88 printf("%f\n", pid1.result);
89 cout--;
90 }
91 return 0;
92 }
方法二:
1 #include<stdio.h>
2 #include<stdint.h>
3 #include<math.h>
4 #pragma warning(disable : 4305)
5 #pragma warning(disable : 4578)
6 struct _pid {
7 float SetSpeed; //定义设定值
8 float ActualSpeed; //定义实际值
9 float err; //定义偏差值
10 float err_next; //定义上一个偏差值
11 float err_last; //定义最上前的偏差值
12 float Kp, Ki, Kd; //定义比例、积分、微分系数
13 }pid;
14
15 void PID_init() {
16 pid.SetSpeed = 0.0;
17 pid.ActualSpeed = 0.0;
18 pid.err = 0.0;
19 pid.err_last = 0.0;
20 pid.err_next = 0.0;
21 pid.Kp = 0.2;
22 pid.Ki = 0.015;
23 pid.Kd = 0.2;
24 }
25
26 float PID_realize(float speed)
27 {
28 int index;
29 pid.SetSpeed = speed;
30 pid.err = pid.SetSpeed - pid.ActualSpeed;
31 if (abs(pid.err) > 200)
32 {
33 index = 0;
34 }
35 else
36
37 {
38 index = 1;
39
40 }
41
42 float incrementSpeed = pid.Kp*(pid.err - pid.err_next) + index*pid.Ki*pid.err + pid.Kd*(pid.err - 2 * pid.err_next + pid.err_last);
43 pid.ActualSpeed += incrementSpeed;
44 pid.err_last = pid.err_next;
45 pid.err_next = pid.err;
46 return pid.ActualSpeed;
47 }
48
49 int main() {
50 PID_init();
51 int count = 0;
52 while (count < 1000)
53 {
54 float speed = PID_realize(200.0);
55 printf("%f\n", speed);
56 count++;
57 }
58 return 0;
59 }
PID控制算法的C语言实现六 抗积分饱和的PID控制算法C语言实现
所谓的积分饱和现象是指如果系统存在一个方向的偏差,PID控制器的输出由于积分作用的不断累加而加大,从而导致执行机构达到极限位置(即转速达到最大),若控制器输出U(k)继续增大,执行器开度不可能再增大(转速不在升高),此时计算机输出控制量超出了正常运行范围而进入饱和区。
一旦系统出现反向偏差,u(k)逐渐从饱和区退出。进入饱和区越深(理论里比实际极限转速差值越大)则退出饱和区时间越长。在这段时间里,执行机构仍然停留在极限位置而不随偏差反向而立即做出相应的改变,这时系统就像失控一样,造成控制性能恶化,这种现象称为积分饱和现象或积分失控现象。
防止积分饱和的方法之一就是抗积分饱和法,该方法的思路是在计算u(k)时,首先判断上一时刻的控制量u(k-1)是否已经超出了极限范围: 如果u(k-1)>umax,则只累加负偏差; 如果u(k-1)<umin,则只累加正偏差。从而避免控制量长时间停留在饱和区。直接贴出代码,不懂的看看前面几节的介绍
抗积分饱和的思想很简单,解释在控制器输出的最大最小值附近限制积分的累积情况,以防止在恢复时没有响应。根据前面得分系我们可以得到如下的流程图:
1 #include<stdio.h>
2
3 struct _pid {
4 float SetSpeed; //定义设定值
5 float ActualSpeed; //定义实际值
6 float err; //定义偏差值
7 float err_last; //定义上一个偏差值
8 float Kp, Ki, Kd; //定义比例、积分、微分系数
9 float voltage; //定义电压值(控制执行器的变量)
10 float integral; //定义积分值
11 float umax;
12 float umin;
13 }pid;
14
15 void PID_init() {
16 printf("PID_init begin \n");
17 pid.SetSpeed = 0.0;
18 pid.ActualSpeed = 0.0;
19 pid.err = 0.0;
20 pid.err_last = 0.0;
21 pid.voltage = 0.0;
22 pid.integral = 0.0;
23 pid.Kp = 0.2;
24 pid.Ki = 0.1; //注意,和上几次相比,这里加大了积分环节的值
25 pid.Kd = 0.2;
26 pid.umax = 400;
27 pid.umin = -200;
28 printf("PID_init end \n");
29 }
30 float PID_realize(float speed) {
31 int index;
32 pid.SetSpeed = speed;
33 pid.err = pid.SetSpeed - pid.ActualSpeed;
34
35 if (pid.ActualSpeed > pid.umax) //灰色底色表示抗积分饱和的实现
36 {
37
38 if (abs(pid.err) > 200) //蓝色标注为积分分离过程
39 {
40 index = 0;
41 }
42 else {
43 index = 1;
44 if (pid.err < 0)
45 {
46 pid.integral += pid.err;
47 }
48 }
49 }
50 else if (pid.ActualSpeed < pid.umin) {
51 if (abs(pid.err) > 200) //积分分离过程
52 {
53 index = 0;
54 }
55 else {
56 index = 1;
57 if (pid.err > 0)
58 {
59 pid.integral += pid.err;
60 }
61 }
62 }
63 else {
64 if (abs(pid.err) > 200) //积分分离过程
65 {
66 index = 0;
67 }
68 else {
69 index = 1;
70 pid.integral += pid.err;
71 }
72 }
73
74 pid.voltage = pid.Kp*pid.err + index * pid.Ki*pid.integral + pid.Kd*(pid.err - pid.err_last);
75
76 pid.err_last = pid.err;
77 pid.ActualSpeed = pid.voltage*1.0;
78 return pid.ActualSpeed;
79 }
80
81 int main()
82 {
83 printf("System begin \n");
84 PID_init();
85 int count = 0;
86 while (count < 1500)
87 {
88 float speed = PID_realize(200.0);
89 printf("%f\n", speed);
90 count++;
91 }
92 return 0;
93 }
总结:所谓抗积分饱和就是防止由于长期存在一个方向的偏差而对相反方向的偏差迟滞响应。本文的方法是在达到极值后将不再对这一方向的偏差做出反应相反只对另一方向的偏差做出反应。事实上由于偏差的存在有可能造成输出值超限的情况,所以还需要对输出值作出限制。
PID控制算法的C语言实现七 梯形积分的PID控制算法C语言实现
先看一下梯形算法的积分环节公式
作为PID控制里的积分项,其作用是消除余差,为了尽量减小余差,应提高积分项运算精度,为此可以将矩形积分改为梯形积分,具体实现的语句为:
于是如果在位置型PID算法中引入梯形积分则可以修改计算公式如下:
同样要在增量型PID算法中引入梯形积分则可以修改计算公式如下:
pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral/2+pid.Kd*(pid.err-pid.err_last); //梯形积分 相对于实现六的pid.voltage代码变换
1 #include<stdio.h>
2
3 struct _pid {
4 float SetSpeed; //定义设定值
5 float ActualSpeed; //定义实际值
6 float err; //定义偏差值
7 float err_last; //定义上一个偏差值
8 float Kp, Ki, Kd; //定义比例、积分、微分系数
9 float voltage; //定义电压值(控制执行器的变量)
10 float integral; //定义积分值
11 float umax;
12 float umin;
13 }pid;
14
15 void PID_init() {
16 printf("PID_init begin \n");
17 pid.SetSpeed = 0.0;
18 pid.ActualSpeed = 0.0;
19 pid.err = 0.0;
20 pid.err_last = 0.0;
21 pid.voltage = 0.0;
22 pid.integral = 0.0;
23 pid.Kp = 0.2;
24 pid.Ki = 0.1; //注意,和上几次相比,这里加大了积分环节的值
25 pid.Kd = 0.2;
26 pid.umax = 400;
27 pid.umin = -200;
28 printf("PID_init end \n");
29 }
30 float PID_realize(float speed) {
31 int index;
32 pid.SetSpeed = speed;
33 pid.err = pid.SetSpeed - pid.ActualSpeed;
34
35 if (pid.ActualSpeed > pid.umax) //灰色底色表示抗积分饱和的实现
36 {
37
38 if (abs(pid.err) > 200) //蓝色标注为积分分离过程
39 {
40 index = 0;
41 }
42 else {
43 index = 1;
44 if (pid.err < 0)
45 {
46 pid.integral += pid.err;
47 }
48 }
49 }
50 else if (pid.ActualSpeed < pid.umin) {
51 if (abs(pid.err) > 200) //积分分离过程
52 {
53 index = 0;
54 }
55 else {
56 index = 1;
57 if (pid.err > 0)
58 {
59 pid.integral += pid.err;
60 }
61 }
62 }
63 else {
64 if (abs(pid.err) > 200) //积分分离过程
65 {
66 index = 0;
67 }
68 else {
69 index = 1;
70 pid.integral += pid.err;
71 }
72 }
73
74 pid.voltage = pid.Kp*pid.err + index * pid.Ki*pid.integral / 2 + pid.Kd*(pid.err - pid.err_last); //梯形积分
75
76 pid.err_last = pid.err;
77 pid.ActualSpeed = pid.voltage*1.0;
78 return pid.ActualSpeed;
79 }
80
81 int main()
82 {
83 printf("System begin \n");
84 PID_init();
85 int count = 0;
86 while (count < 1500)
87 {
88 float speed = PID_realize(200.0);
89 printf("%f\n", speed);
90 count++;
91 }
92 return 0;
93 }
结论最后运算的稳定数据为:199.999878,较教程六中的199.9999390而言,精度进一步提高。
总结:积分项的引入目的就是为了消除系统的余差,那么积分项的计算精度越高,对消除系统的余差就越有利。梯形积分相较于矩形积分其精度有比较大的提高,所以对消除余差也就越有效。
PID控制算法的C语言实现八 变积分的PID控制算法C语言实现
变积分PID可以看成是积分分离的PID算法的更一般的形式。在普通的PID控制算法中,由于积分系数ki是常数,所以在整个控制过程中,积分增量是不变的。但是,系统对于积分项的要求是,系统偏差大时,积分作用应该减弱甚至是全无,而在偏差小时,则应该加强。积分系数取大了会产生超调,甚至积分饱和,取小了又不能短时间内消除静差。因此,根据系统的偏差大小改变积分速度是有必要的。
变积分PID的基本思想是设法改变积分项的累加速度,使其与偏差大小相对应:偏差越大,积分越慢; 偏差越小,积分越快。
设置系数f(e(k)),它是e(k)的函数。当∣e(k)∣增大时,f减小,反之增大。变速积分的PID积分项表达式为:
系数f与偏差当前值∣e(k)∣的关系可以是线性的或是非线性的,例如,可设为:
由以上公式可知,f(err(k))的值在[0,1]区间变化,当偏差值|err(k)|大于分离区间A+B时,不对当前偏差err(k)进行累加;当偏差值|err(k)|小于B时,加入当前偏差err(k)进行累加;介于B和A+B的区间时,按一定函数关系随err(k)变化。于是变积分PID算法可以表示为:
上述的f(err(k))函数只是我们列举的一种,事实上可以采取任何可行的方式,甚至是非线性函数,只要更符合控制对象的特性。
对于用增量型PID算法的变积分表示如下:
看到这个公式,很多人可能会发觉与前面的积分分离算法的公式很象。特别是在增量型算法中,它们的形式确实是一样的,但表达的意思确是有一定区别,那么我们来看看有什么不同呢?在后面我们再作总结。
变积分实际上是通过对偏差的判断,让积分以不同的速度累计。这一系数介于0-1之间,可以通过多种方式实现,在这里我们按线性方式实现。变积分的控制流程图如下:
这里给积分系数前加上一个比例值index:
当abs(err)<180时,index=1;
当180<abs(err)<200时,index=(200-abs(err))/20;
当abs(err)>200时,index=0;
最终的比例环节的比例系数值为ki*index;
具体PID实现代码如下:再实现三的基础上改动
pid.Kp=0.4;
pid.Ki=0.2; //增加了积分系数
pid.Kd=0.2;
float PID_realize(float speed){
float index;
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
if(abs(pid.err)>200) //变积分过程
{
index=0.0;
}else if(abs(pid.err)<180){
index=1.0;
pid.integral+=pid.err;
}else{
index=(200-abs(pid.err))/20;
pid.integral+=pid.err;
}
pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
1 #include<stdio.h>
2
3 struct _pid {
4 float SetSpeed; //定义设定值
5 float ActualSpeed; //定义实际值
6 float err; //定义偏差值
7 float err_last; //定义上一个偏差值
8 float Kp, Ki, Kd; //定义比例、积分、微分系数
9 float voltage; //定义电压值(控制执行器的变量)
10 float integral; //定义积分值
11 }pid;
12
13 void PID_init() {
14 printf("PID_init begin \n");
15 pid.SetSpeed = 0.0;
16 pid.ActualSpeed = 0.0;
17 pid.err = 0.0;
18 pid.err_last = 0.0;
19 pid.voltage = 0.0;
20 pid.integral = 0.0;
21 pid.Kp = 0.4;
22 pid.Ki = 0.2; //增加了积分系数
23 pid.Kd = 0.2;
24 printf("PID_init end \n");
25 }
26
27 float PID_realize(float speed) {
28 float index;
29 pid.SetSpeed = speed;
30 pid.err = pid.SetSpeed - pid.ActualSpeed;
31
32 if (abs(pid.err) > 200) //变积分过程
33 {
34 index = 0.0;
35 }
36 else if (abs(pid.err) < 180) {
37 index = 1.0;
38 pid.integral += pid.err;
39 }
40 else {
41 index = (200 - abs(pid.err)) / 20;
42 pid.integral += pid.err;
43 }
44 pid.voltage = pid.Kp*pid.err + index * pid.Ki*pid.integral + pid.Kd*(pid.err - pid.err_last);
45
46 pid.err_last = pid.err;
47 pid.ActualSpeed = pid.voltage*1.0;
48 return pid.ActualSpeed;
49 }
50
51 int main()
52 {
53 printf("System begin \n");
54 PID_init();
55 int count = 0;
56 while (count < 1500)
57 {
58 float speed = PID_realize(200.0);
59 printf("%f\n", speed);
60 count++;
61 }
62 return 0;
63 }
代码结果自行运行
最终结果可以看出,系统的稳定速度非常快(测试程序参见本系列教程3)
总结:
变积分实际上有一定的专家经验在里面,因为限值的选取以及采用什么样的函数计算系数,有很大的灵活性。
我们在前面做了积分分离的算法,这次又说了变积分的算法。他们有相通的地方,也有不同的地方,下面对他们进行一些说明。
首先这两种算法的设计思想是有区别的。积分分离的思想是偏差较大时,取消积分;而偏差较小时引入积分。变积分的实现是想是设法改变积分项的累加速度,偏差大时减弱积分;而偏差小时强化积分。有些所谓改进型的积分分离算法实际已经脱离了积分分离的基本思想,而是动态改变积分系数。就这一点而言,特别是在增量型算法中,已经属于变积分的思想了。
其次,对于积分分离来说,该操作是针对整个积分项操作,这一点在位置型PID算法中,表现的尤为明显。而对于变积分来说,是针对当前偏差的积分累计,就是说只影响当前这次的积分部分。再者,具体的实现方式也存在区别,特别是在位置型PID方式下尤为明显。
我们在这里讨论它们的区别原因,佷显然就是我们没办法同时采用这两种优化方式,只能分别实现,在后面我们将实现基于积分项的优化。
PID控制算法的C语言实现九
(1)微分先行PID控制算法
微分先行PID控制的特点是只对输出量yout(k)进行微分,而对给定值rin(k)不进行微分。这样,在改变给定值时,输出不会改变,而被控量的变化通常是比较缓和的。这种输出量先行微分控制适用于给定值rin(k)频繁升降的场合,可以避免给定值升降时引起系统振荡,从而明显地改善了系统的动态特性
(2)不完全微分PID控制算法
在PID控制中,微分信号的引入可改善系统的动态特性,但也易引进高频干扰,在误差扰动突变时尤其显出微分项的不足。若在控制算法中加入低通滤波器,则可使系统性能得到改善 不完全微分PID的结构如下图。左图将低通滤波器直接加在微分环节上,右图是将低通滤波器加在整个PID控制器之后
(3)带死区的PID控制算法
在计算机控制系统中,某些系统为了避免控制作用过于频繁,消除由于频繁动作所引起的振荡,可采用带死区的PID控制算法,控制算式为:
式中,e(k)为位置跟踪偏差,e0是一个可调参数,其具体数值可根据实际控制对象由实验确定。若e0值太小,会使控制动作过于频繁,达不到稳定被控对象的目的;若e0太大,则系统将产生较大的滞后 控制算法流程:
注:<我们电子设计竞赛里,在简易倒立摆控制装置中就采用了带死区的PID控制算法,当时并不知道这个名称,这也就是在现场测试的时候为什么老师会问我们摆能够保持倒立静止不动,而不是靠左右抖动来控制平衡,就是因为我在里面设置了死区:好像是5度的角度>
PID控制算法的C语言实现十 专家PID与模糊PID的C语言实现
本节是PID控制算法的C语言实现系列的最后一节,前面8节中,已经分别从PID的实现到深入的过程进行了一个简要的讲解,从前面的讲解中不难看出,PID的控制思想非常简单,其主要问题点和难点在于比例、积分、微分环节上的参数整定过程,对于执行器控制模型确定或者控制模型简单的系统而言,参数的整定可以通过计算获得,对于一般精度要求不是很高的执行器系统,可以采用拼凑的方法进行实验型的整定。
然而,在实际的控制系统中,线性系统毕竟是少数,大部分的系统属于非线性系统,或者说是系统模型不确定的系统,如果控制精度要求较高的话,那么对于参数的整定过程是有难度的。专家PID和模糊PID就是为满足这方面的需求而设计的。专家算法和模糊算法都归属于智能算法的范畴,智能算法最大的优点就是在控制模型未知的情况下,可以对模型进行控制。这里需要注意的是,专家PID也好,模糊PID也罢,绝对不是专家系统或模糊算法与PID控制算法的简单加和,他是专家系统或者模糊算法在PID控制器参数整定上的应用。也就是说,智能算法是辅助PID进行参数整定的手段。
其实在前面几节的讲述中,已经用到了专家PID的一些特例行为了,从第五节到第八节都是专家系统一些特列化的算法,对某些条件进行了局部的判定,比如如果偏差太大的话,就去除积分项,这本身就是含有经验的专家系统。
专家系统、模糊算法,需要参数整定就一定要有整定的依据,也就是说什么情况下整定什么值是要有依据的,这个依据是一些逻辑的组合,只要找出其中的逻辑组合关系来,这些依据就再明显不过了。下面先说一下专家PID的C语言实现。正如前面所说,需要找到一些依据,还得从PID系数本身说起。
1.比例系数Kp的作用是加快系统的响应速度,提高系统的调节精度。Kp越大,系统的响应速度越快,系统的调节精度越高,但是容易产生超调,甚至会使系统不稳定。Kp取值过小,则会降低调节精度,使响应速度缓慢,从而延长调节时间,是系统静态、动态特性变差;
2.积分作用系数Ki的作用是消除系统的稳态误差。Ki越大,系统的静态误差消除的越快,但是Ki过大,在响应过程的初期会产生积分饱和的现象,从而引起响应过程的较大超调。若Ki过小,将使系统静态误差难以消除,影响系统的调节精度;
3.微分系数Kd的作用是改善系统的动态特性,其作用主要是在响应过程中抑制偏差向任何方向的变化,对偏差变化进行提前预报。但是kd过大,会使响应过程提前制动,从而延长调节时间,而且会降低系统的抗干扰性。
反应系统性能的两个参数是系统误差e和误差变化律ec,这点还是好理解的:
首先我们规定一个误差的极限值,假设为Mmax;规定一个误差的比较大的值,假设为Mmid;规定一个误差的较小值,假设为Mmin;
当abs(e)>Mmax时,说明误差的绝对值已经很大了,不论误差变化趋势如何,都应该考虑控制器的输入应按最大(或最小)输出,以达到迅速调整误差的效果,使误差绝对值以最大的速度减小。此时,相当于实施开环控制。
当e*ec>0时,说明误差在朝向误差绝对值增大的方向变化,此时,如果abs(e)>Mmid,说明误差也较大,可考虑由控制器实施较强的控制作用,以达到扭转误差绝对值向减小的方向变化,并迅速减小误差的绝对值。此时如果abs(e)<Mmid,说明尽管误差是向绝对值增大的方向变化,但是误差绝对值本身并不是很大,可以考虑控制器实施一般的控制作用,只需要扭转误差的变化趋势,使其向误差绝对值减小的方向变化即可。
当e*err<0且e*err(k-1)>0或者e=0时,说明误差的绝对值向减小的方向变化,或者已经达到平衡状态,此时保持控制器输出不变即可。
当e*err<0且e*err(k-1)<0时,说明误差处于极限状态。如果此时误差的绝对值较大,大于Mmin,可以考虑实施较强控制作用。如果此时误差绝对值较小,可以考虑实施较弱控制作用。
当abs(e)<Mmin时,说明误差绝对值很小,此时加入积分,减小静态误差。
上面的逻辑判断过程,实际上就是对于控制系统的一个专家判断过程。
PID控制算法的C语言实现十一模糊算法简介
在PID控制算法的C语言实现九中,文章已经对模糊PID的实质做了一个简要说明。本来打算等到完成毕业设计,工作稳定了再着力完成剩下的部分。鉴于网友的要求和信任,抽出时间来,对模糊PID做一个较为详细的论述,这里我不打算做出仿真程序了,但就基本概念和思路进行一下说明,相信有C语言基础的朋友可以通过这些介绍性的文字自行实现。这篇文章主要说明一下模糊算法的含义和原理。
实际上模糊算法属于智能算法,智能算法也可以叫非模型算法,也就是说,当我们对于系统的模型认识不是很深刻,或者说客观的原因导致我们无法对系统的控制模型进行深入研究的时候,智能算法常常能够起到不小的作用。这点是方便理解的,如果一个系统的模型可以轻易的获得,那么就可以根据系统的模型进行模型分析,设计出适合系统模型的控制器。但是现实世界中,可以说所有的系统都是非线性的,是不可预测的。但这并不是说我们就无从建立控制器,因为,大部分的系统在一定的条件和范围内是可以抽象成为线性系统的。问题的关键是,当我们系统设计的范围超出了线性的范围,我们又该如何处理。显然,智能算法是一条很不错的途径。智能算法包含了专家系统、模糊算法、遗传算法、神经网络算法等。其实这其中的任何一种算法都可以跟PID去做结合,而选择的关键在于,处理的实时性能不能得到满足。当我们处理器的速度足够快速时,我们可以选择更为复杂的、精度更加高的算法。但是,控制器的处理速度限制了我们算法的选择。当然,成本是限制处理器速度最根本的原因。这个道理很简单,51单片机和DSP的成本肯定大不相同。专家PID和模糊PID是常用的两种PID选择方式。其实,模糊PID适应一般的控制系统是没有问题。文章接下来将说明模糊算法的一些基本常识。
模糊算法其实并不模糊。模糊算法其实也是逐次求精的过程。这里举个例子说明。我们设计一个倒立摆系统,假如摆针偏差<5°,我们说它的偏差比较“小”;摆针偏差在5°和10°之间,我们说它的偏差处于“中”的状态;当摆针偏差>10°的时候,我们说它的偏差有点儿“大”了。对于“小”、“中”、“大”这样的词汇来讲,他们是精确的表述,可问题是如果摆针偏差是3°呢,那么这是一种什么样的状态呢。我们可以用“很小”来表述它。如果是7°呢,可以说它是“中”偏“小”。那么如果到了80°呢,它的偏差可以说“非常大”。而我们调节的过程实际上就是让系统的偏差由非常“大”逐渐向非常“小”过度的过程。当然,我们系统这个调节过程是快速稳定的。通过上面的说明,可以认识到,其实对于每一种状态都可以划分到大、中、小三个状态当中去,只不过他们隶属的程度不太一样,比如6°隶属于小的程度可能是0.3,隶属于中的程度是0.7,隶属于大的程度是0。这里实际上是有一个问题的,就是这个隶属的程度怎么确定?这就要求我们去设计一个隶属函数。详细内容可以查阅相关的资料,这里没有办法那么详细的说明了。http://baike.baidu.com/view/150383.htm(见附录3)这里面有些说明。那么,知道了隶属度的问题,就可以根据目前隶属的程度来控制电机以多大的速度和方向转动了,当然,最终的控制量肯定要落实在控制电压上。这点可以很容易的想想,我们控制的目的就是让倒立摆从隶属“大”的程度为1的状态,调节到隶属“小”的程度为1的状态。当隶属大多一些的时候,我们就加快调节的速度,当隶属小多一些的时候,我们就减慢调节的速度,进行微调。可问题是,大、中、小的状态是汉字,怎么用数字表示,进而用程序代码表示呢?其实我们可以给大、中、小三个状态设定三个数字来表示,比如大表示用3表示,中用2表示,小用1表示。那么我们完全可以用1*0.3+2*0.7+3*0.0=1.7来表示它,当然这个公式也不一定是这样的,这个公式的设计是系统模糊化和精确化的一个过程,读者也可参见相关文献理解。但就1.7这个数字而言,可以说明,目前6°的角度偏差处于小和中之间,但是更偏向于中。我们就可以根据这个数字来调节电机的转动速度和时间了。当然,这个数字与电机转速的对应关系,也需要根据实际情况进行设计和调节。
前面一个例子已经基本上说明了模糊算法的基本原理了。可是实际上,一个系统的限制因素常常不是一个。上面的例子中,只有偏差角度成为了系统调节的参考因素。而实际系统中,比如PID系统,我们需要调节的是比例、积分、微分三个环节,那么这三个环节的作用就需要我们认清,也就是说,我们需要根据超调量、调节时间、震荡情况等信息来考虑对这三个环节调节的比重,输入量和输出量都不是单一的,可是其中必然有某种内在的逻辑联系。所以这种逻辑联系就成为我们设计工作的重点了。下一篇文章将详细分析PID三个变量和系统性能参数之间的联系。
PID控制算法的c语言实现十二 模糊PID的参数整定
这几天一直在考虑如何能够把这一节的内容说清楚,对于PID而言应用并没有多大难度,按照基本的算法设计思路和成熟的参数整定方法,就算是没有经过特殊训练和培训的人,也能够在较短的时间内容学会使用PID算法。可问题是,如何能够透彻的理解PID算法,从而能够根据实际的情况设计出优秀的算法呢。
通过讲述公式和基本原理肯定是最能说明问题的,可是这样的话怕是犯了“专家”的错误了。对于门槛比较低的技术人员来讲,依然不能透彻理解。可是说的入耳了,能不能透彻说明也是一个问题,所以斟酌了几天,整理了一下思路才开始完成PID系列文章的最后一篇。
我所说的最后一篇不代表PID的功能和发展就止步与此,仅仅是说明,透过这一些列的文章,基本上已经可以涵盖PID设计的要点,至于更深入的研究,就交给有需要的读者去做。
上一节中大致讲述了一下模糊算法。实际上模糊算法的很多概念在上一节中并没有深入的解释。举的例子也只是为了说明模糊算法的基本含义,真正的模糊算法是不能这么设计的,当然也不会这么简单。模糊算法的核心是模糊规则,如果模糊规则制定的出色,那么模糊算法的控制效率就高。其实这是智能算法的一般特性,规则是系统判断和处理的前提。那么就说说PID的规则该怎么制定。
我们知道,模糊算法的本质是对PID的三个参数进行智能调节。那么首先要提出的问题是如何对PID的参数进行调节?这个问题其实是参数整定的问题,现实当中有很多整定方法。可是我们需要从根本上了解为什么这么整定,才能知道该如何建立数学模型进行分析。那么要回答如何整定参数的问题,就需要先明白PID参数的作用都是什么?对系统有什么影响?
我们从作用和副作用两个方面说明参数对系统的影响。
1.比例环节Kp,作用是加快系统的响应速度,提高系统的调节精度,副作用是会导致超调;
2.积分环节Ki,作用是消除稳态误差,副作用是导致积分饱和现象;
3.微分环节Kd,作用是改善系统的动态性能,副作用是延长系统的调节时间。
理解了上述问题,那么就可以“辩证施治,对症下药”了。比如说,如果系统响应速度慢,我们就加大Kp的取值,如果超调量过大我们就减小Kp的取值等等。可是问题这些语言的描述该如何用数学形式表达出来呢。我们所知道的,反馈系统的实质就是系统的输出量作为反馈量与系统的输入量进行作差,从而得到系统的误差e,那么这个误差e就能够反应目前系统所处的状态。误差e可以表明目前系统的输出状态到底偏离要求多少。而误差e的变化律ec,表示误差变化的速度。这样,我们可以根据这两个量的状态来分析三个参数此时应该如何取值,假如e为负方向比较大,ec也为负方向增大状态,此时比例环节要大一些,从而加快调节速度,而积分环节要小一些,甚至不加积分环节,从而防止负方向上出现饱和积分的现象。微分环节可以稍加一些,在不影响调节时间的情况下,起到改善系统动态性能的作用。
附录1
看到有不少人问到底如何让UK值与PWM占空比值对应,进而实现占空比输出和输出控制电压对应。
(注意,我这里讨论的前提是输出控制的是电压,不是PWM方波。PWM输出后要经过滤波整形再输出控制。)
前提条件:
输出电压控制电压范围是0-10V。
给定、反馈、输出电压采样输入电压范围是0-5V(经过运放)。
使用单片机AD为10位AD芯片。
那么10位AD芯片电压采集得到的数据范围就是0-1024。
PWM为 8位可调占空比方波,0对应输出占空比为0的方波,255对应输出占空比100%的方波,127对应输出50%的方波。
比如当前给定是2.5V,反馈电压是1V。(KP,KI,KD等系数略,关于PID算法的整数实现我在前文中有论述如何实现)。
那么经过AD采样
1、给定2.5V对应为 512
2、反馈1V对应为 205
假定经过PID计算得到的UK为400
也就意味着输出电压应当为(400*(UPWM峰值电压))/1024
那么UK对应的PWM占空比是多少呢?
我们知道,UK=1024对应占空比为100,也就是PWM的占空比系数为255。可知,PWM系数 = UK/4;
那么400就应当对应系数 400/4=100。
也就是输出电压=400*10/1024=3.9V
同时,由于采样精度以及PWM输出占空比精度控制的问题,将导致输出电压和期望值不是那么线性,所以,我在项目内加入了输出电压采样的控制。
采样AD输入为0-5V,所以,对于输出0-10V有一个缩小的比例。
输出10V则采样值对应为255
输出5V则采样之对应127
可知,3.9V对应AD结果为97
采样输出电压值,可以针对性的调整一下占空比输出,从而得到误差允许范围内的一个控制输出电压。
同时,经过一些加速控制的手段。可以比较迅速的达到控制的目的。
下文中的UK控制方法是针对增量式PID控制而来做的。
1 /****************************************************/
2
3 void PWMProcess(void)
4
5 {
6
7 uint16 idata temp;
8
9 uint16 idata UKTemp;
10
11 temp = 0;
12
13 UKTemp = 0;
14
15 if( Pwm.ChangeFlag_Uint8 != 0 ) //判断是否需要改变占空比
16
17 { //是否需要改变占空比和你的被控系统特性有关
18
19 Pwm.ChangeFlag_Uint8 = 0;
20
21 UKTemp = PID.Uk_Uint16 + SwIn.AddValue_Uint16;
22
23 //计算UK控制量
24
25 //控制量和计算值以及一个开关量有关,我这里的开关量是系统需要的时候叠加在控制量上的一个变量。
26
27 if(UKTemp>999)
28
29 {
30
31 UKTemp = 999;
32
33 }
34
35 //这里只所以是999封顶而不是1024是因为我的系统PWM的峰值电压是12V导致。
36
37 while(1) //如果输出电压和期望电压相差 Delta,则继续调整占空比,直到在误差以内
38
39 {
40
41 ADChPro(UPWMADCH); //测量输出电压
42
43 if( ADPool.Value_Uint16[UPWMADCH] == UKTemp)
44
45 {
46
47 return;
48
49 }
50
51 if( ADPool.Value_Uint16[UPWMADCH] > UKTemp) //如果当前电压大于输出电压,减小占空比
52
53 {
54
55 if( ( ADPool.Value_Uint16[UPWMADCH] - UKTemp ) > UDELTA )
56
57 {
58
59 temp = ADPool.Value_Uint16[UPWMADCH] - UKTemp; //
60
61 temp = temp / 2; //下降可以加速下降,所以下降参数加倍
63 if( Pwm.DutyCycle_Uint8 > temp )
65 {
67 Pwm.DutyCycle_Uint8 = Pwm.DutyCycle_Uint8 - temp;
69 }
71 else
73 {
75 Pwm.DutyCycle_Uint8 = 0;
77 }
79 }
81 else
83 {
85 return;
87 }
89 }
91 else //如果当前电压小于输出电压
93 {
94
95 if( ( UKTemp - ADPool.Value_Uint16[UPWMADCH] ) > UDELTA )
96
97 {
98
99 temp = UKTemp - ADPool.Value_Uint16[UPWMADCH];
100
101 temp = temp / 4; //上升处理不要超调,所以每次只+一半
102
103 if( (255-Pwm.DutyCycle_Uint8) > temp )
104
105 {
106
107 Pwm.DutyCycle_Uint8 += (temp/2);
108
109 }
110
111 else
112
113 {
114
115 Pwm.DutyCycle_Uint8 = 255;
116
117 }
118
119 }
120
121 else
122
123 {
124
125 return;
126
127 }
128
129 }
130
131 DisPlayVoltage();
132
133 PWMChangeDuty(Pwm.DutyCycle_Uint8); //改变占空比
134
135 Delay(10,10);
136
137
138
139 }
140
141 }
142
143 }
144
145 /*****************************************************/
附录2
直流电机PWM调速系统中控制电压非线性研究
引言
由于线性放大驱动方式效率和散热问题严重,目前绝大多数直流电动机采用开关驱动方式。开关驱动方式是半导体功率器件工作在开关状态,通过脉宽调制PWM控制电动机电枢电压,实现调速。目前已有许多文献介绍直流电机调速,宋卫国等用89C51单片机实现了直流电机闭环调速;张立勋等用AVR单片机实现了直流电机PWM调速;郭崇军等用C8051实现了无刷直流电机控制;张红娟等用PIC单片机实现了直流电机PWM调速;王晨阳等用DSP实现了无刷直流电机控制。上述文献对实现调速的硬件电路和软件流程的设计有较详细的描述,但没有说明具体的调压调速方法,也没有提及占空比与电机端电压平均值之间的关系。在李维军等基于单片机用软件实现直流电机PWM调速系统中提到平均速度与占空比并不是严格的线性关系,在一般的应用中,可以将其近似地看作线性关系。但没有做深入的研究。本文通过实验验证,在不带电机情况下,PWM波占空比与控制输出端电压平均值之间呈线性关系;在带电机情况下,占空比与电机端电压平均值满足抛物线方程,能取得精确的控制。本文的电机闭环调速是运用Matlab拟合的关系式通过PID控制算法实现。
1 系统硬件设计
本系统是基于TX-1C实验板上的AT89C52单片机,调速系统的硬件原理图如图1所示,主要由AT89C52单片机、555振荡电路、L298驱动电路、光电隔离、霍尔元件测速电路、MAX 232电平转换电路等组成。
2 系统软件设计
系统采用模块化设计,软件由1个主程序,3个中断子程序,即外部中断0、外部中断1,定时器0子程序,PID算法子程序,测速子程序及发送数据到串口显示子程序组成,主程序流程图如图2所示。外部中断0通过比较直流电平与锯齿波信号产生PWM波,外部中断1用于对传感器的脉冲计数。定时器0用于对计数脉冲定时。测得的转速通过串口发送到上位机显示,通过PID模块调整转速到设定值。本实验采用M/T法测速,它是同时测量检测时间和在此检测时间内霍尔传感器所产生的转速脉冲信号的个数来确定转速。由外部中断1对霍尔传感器脉冲计数,同时起动定时器0,当计数个数到预定值2 000后,关定时器0,可得到计2 000个脉冲的计数时间,由式计算出转速:
n=60f/K=60N/(KT) (1)
式中:n为直流电机的转速;K为霍尔传感器转盘上磁钢数;f为脉冲频率;N为脉冲个数;T为采样周期。
3 实验结果及原因分析
3.1 端电压平均值与转速关系
3.1.1 实验结果
实验用的是永磁稳速直流电机,型号是EG-530YD-2BH,额定转速2 000~4 000 r/min,额定电压12 V。电机在空载的情况下,测得的数据用Matlab做一次线性拟合,拟合的端电压平均值与转速关系曲线如图3(a)所示。相关系数R-square:0.952 1。拟合曲线方程为:
y=0.001 852x+0.296 3 (2)
由式(2)可知,端电压平均值与转速可近似为线性关系,根椐此关系式,在已测得的转速的情况下可以计算出当前电压。为了比较分析,同样用Matlab做二次线性拟合,拟合的端电压平均值与转速关系曲线如图3(b)所示。相关系数R-square:0.986 7。
3.1.2 原因分析
比较图3(a)可知,当转速在0~1 500 r/min和4 000~5 000 r/min,端电压平均值与转速间存在的非线性,用二次曲拟合如图3(b)所示,拟合相关系数较高。由图3(a)可见,当电机转速为0时电机两端电压平均值约为1.3 V。这是因为电机处于静止状态时,摩擦力为静摩擦力,静摩擦力是非线性的。随着外力的增加而增加,最大值发生在运动前的瞬间。电磁转矩为负载制动转矩和空载制动转矩之和,由于本系统不带负载,因此电磁转矩为空载制动转矩。空载制动转矩与转速之间此时是非线性的。电磁转矩与电流成正比,电流又与电压成正比,因此此时电压与转速之间是非线性的。
当转速在2 000~4 000 r/min线性关系较好,占空比的微小改变带来的转速改变较大,因此具有较好的调速性能。这是因为随着运动速度的增加,摩擦力成线性的增加,此时的摩擦力为粘性摩擦力。粘性摩擦是线性的,与速度成正比,空载制动转矩与速度成正比,也即电磁转矩与电流成正比,电流又与电压成正比,因此此时电压与转速之间是线性的。当转速大于4 000 r/min。由于超出了额定转速所以线性度较差且调速性能较差。此时用二次曲线拟合结果较好,因为当电机高速旋转时,摩擦阻力小到可以忽略,此时主要受电机风阻型负荷的影响,当运动部件在气体或液体中运动时,其受到的摩擦阻力或摩擦阻力矩被称为风机型负荷。对同一物体,风阻系数一般为固定值。阻力大小与速度的平方成正比。即空载制动转矩与速度的平方成正比,也即电磁转矩与速度的平方成正比,电磁转矩与电流成正比,电流又与电压成正比,因此此时电压与转速之间是非线性的。
3.2 占空比与端电压平均值关系
3.2.1 实验结果
拟合占空比与端电压平均值关系曲线如图4所示。相关系数R-square:0.998 4。拟合曲线方程为:
如图4所示,占空比与端电压平均值满足抛物线方程。运用积分分离的PID算法改变电机端电压平均值,可以运用此关系式改变占空比,从而实现了PWM调速。
用示波器分别测出电压的顶端值Utop与底端值Ubase,端电压平均值Uarg满足关系式:
其中:α为占空比。
正是由于所测得的电机端电压底端值Ubase不为0,所以得出的占空比与端电压平均值之间关系曲线为抛物线。若将电机取下,直接测L298的out1与out2输出电压。所测得的电机端电压底端值Ubase约为0,所得的占空比与端电压平均值满足线性关系,即令式(4)中Ubase为0,式(4)变为:
3.2.2 原因分析
将电机取下后,直接测L298的输出端之间的电压,占空比与端电压平均值满足关系式(5),说明整个硬件电路的设计以及软件编程的正确性。从电机反电势角度分析,当直流电机旋转时,电枢导体切割气隙磁场,在电枢绕组中产生感应电动势。由于感应电动势方向与电流的方向相反,感应电动势也即反电势。直流电机的等效模型如图5所示。图5(a)表示电机工作在电动机状态。图5(b)表示电机工作在发电机状态。
如图5(a)所示,电压平衡方程为:
式中:U为外加电压;Ia为电枢电流;Ra为电枢绕组电阻;2△Ub为一对电刷接触压降,一般取2△Ub为0.5~2 V;Ea为电枢绕组内的感应电动势。电机空载时,电枢电流可忽略不计,即电流Ia为0。空载时的磁场由主磁极的励磁磁动势单独作用产生。给电机外加12 V的额定电压,由(6)可得反电势:
以40%的占空比为例,电机端电压Uab是测量中的电压平均值Uarg,其值为8.34 V,测量中的电压底端值Ubase约为7 V。由式(7)可得Ea的值范围应在6.34~7.84 V。由图5(b)可见,此时Uab的值是测得的底端值Ubase即电机的电动势Ea为7 V。
当PWM工作在低电平状态,直流电机不会立刻停止,会继续旋转,电枢绕组切割气隙磁场,电机此时工作在发电机状态,产生感应电动势E。
式中:Ce为电机电动势常数;φ为每级磁通量。由于电机空载,所以图5(b)中无法形成回路。用单片机仿真软件Proteus可直观的看出在PWM为低电平状态,电机处于减速状态。低电平持续时间越长,电机减速量越大。正是由于在低电平期间,电机处于减速状态,由式(8)可知,Ce,φ均为不变量,转速n的变化引起E的改变。此时Uab的值等于E的值。电机在低电平期间不断的减速,由于PWM周期较短,本文中取20 ms,电机在低电平期间转速还未减至0,PWM又变为高电平了。这样,就使测得的Ubase值不为0。以40%的占空比为例,当PWM工作在低电平状态,测得Ubase的值约为7 V。由式(8)可知,当正占空比越大,转速也就越大,同时减速时间越短,感应电势E的值越大。所以Ubase的值也就越大。
4 结语
重点分析了直流电机PWM调速过程中控制电压的非线性,对非线性的影响因素做了详细的分析。由于PWM在低电平期间电压的底端值不为0,导致了占空比与电机端电压平均值之间呈抛物线关系。因此,可用得出的抛物线关系式实现精确调速。本系统的非线性研究可为电机控制中非线性的进一步研究提供依据,在实际运用中,可用于移动机器人、飞行模拟机的精确控制。
附录3
隶属函数(membership function),用于表征模糊集合的数学工具。对于普通集合A,它可以理解为某个论域U上的一个子集。为了描述论域U中任一元素u是否属于集合A,通常可以用0或1标志。用0表示u不属于A,而用1表示属于A ,从而得到了U上的一个二值函数χA(u),它表征了U的元素u对普通集合的从属关系,通常称为A的特征函数,为了描述元素u对U上的一个模糊集合的隶属关系,由于这种关系的不分明性,它将用从区间[0,1]中所取的数值代替0,1这两值来描述,记为(u),数值(u)表示元素隶属于模糊集的程度,论域U上的函数μ即为模糊集的隶属函数,而(u)即为u对A的隶属度。