參考:
專家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的隸屬度。