轉 C#實現PID控制的模擬測試和曲線繪圖


C#實現PID控制的模擬測試和曲線繪圖

 

本文分兩部分,一部分是講PID算法的實現,另一部分是講如何用動態的曲線繪制出PID運算的結果。

首先,PID算法的理論模型請參考自動控制理論,最早出現的是模擬PID控制,后來計算機成為控制器,由於計算機控制是一種采樣控制,需把模擬PID轉換成數字PID,就是模擬PID的離散化,兩者中間是香濃定理。當然這些和編程是沒關系的,我們只需要有個數字模型就能開展后面的工作了。

 

在編程時,可寫成:
 
絕對式計算公式
Uo(n) = P *e(n) + I*[e(n)+e(n-1)+...+e(0)]+ D *[e(n)-e(n-1)]
Uo(n-1) = P *e(n-1) + I*[e(n-1)+e(n-2)+...+e(0)]+ D *[e(n-1)-e(n-2)]
 
二者相減就得到增量式計算公式
Uo = P *(e(n)-e(n-1)) + I*e(n)+ D *[e(n)-2*e(n-1)+e(n-2)]
 
e(n)--------------------------本次誤差
 
接下來的任務就是用代碼來實現上面的公式了,我把PID運算部分做成一個類Class1供其他程序調用,開始只實現最基本的PID運算,沒有考慮從積分分離和死區處理,最重要的代碼如下:
 
[csharp]  view plain  copy
 
  1.  private float prakp, praki, prakd, prvalue, err, err_last, err_next, setvalue;  
  2.       int MAXLIM, MINLIM;  
  3. //PID valculate  
  4.       public float pidvalc()  
  5.       {  
  6.           err_next = err_last;        //前兩次的誤差  
  7.           err_last = err;             //前一次的誤差  
  8.           err = setvalue - prvalue;   //現在的誤差  
  9.   
  10.           //增量式計算  
  11.           prvalue += prakp * ((err - err_last) + praki * err + prakd * (err - 2 * err_last + err_next));  
  12.           //輸出上下限值  
  13.           if (prvalue > MAXLIM)  
  14.               prvalue = MAXLIM;  
  15.           if (prvalue < MINLIM)  
  16.               prvalue = MINLIM;  
  17.   
  18.           return prvalue;  
  19.       }  
 
模擬出來的結果是下面這樣,出現輸出值收斂振盪后穩定下來。
 
 
現在我們對PID運算部分的代碼改進一點,增加了積分分離和死區的功能,完整代碼如下:
[csharp]  view plain  copy
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace PIDtest  
  7. {  
  8.     class Class1  
  9.     {  
  10.         private float prakp, praki, prakd, prvalue, err, err_last, err_next, setvalue, deadband;  
  11.         int index, UMAX, UMIN, MAXLIM, MINLIM;  
  12.         public float Prakp  
  13.         {  
  14.             set  
  15.             {  
  16.                 prakp = value;  
  17.             }  
  18.             get  
  19.             {  
  20.                 return prakp;  
  21.             }  
  22.         }  
  23.         public float Praki  
  24.         {  
  25.             set  
  26.             {  
  27.                 praki = value;  
  28.             }  
  29.             get  
  30.             {  
  31.                 return praki;  
  32.             }  
  33.         }  
  34.         public float Prakd  
  35.         {  
  36.             set  
  37.             {  
  38.                 prakd = value;  
  39.             }  
  40.             get  
  41.             {  
  42.                 return prakd;  
  43.             }  
  44.         }  
  45.         public float Setvalue  
  46.         {  
  47.             set  
  48.             {  
  49.                 setvalue = value;  
  50.             }  
  51.             get  
  52.             {  
  53.                 return setvalue;  
  54.             }  
  55.         }  
  56.         public Class1()  
  57.         {  
  58.             pidinit();  
  59.         }  
  60.         //PID參數初始化  
  61.         public void pidinit()  
  62.         {  
  63.             prakp = 0;  
  64.             praki = 0;  
  65.             prakd = 0;  
  66.             prvalue = 0;  
  67.             err = 0;  
  68.             err_last = 0;  
  69.             err_next = 0;  
  70.             MAXLIM = 800;  
  71.             MINLIM = -200;  
  72.             UMAX = 310;  
  73.             UMIN = -100;  
  74.             deadband = 2;  
  75.         }  
  76.         //PID valculate  
  77.         public float pidvalc()  
  78.         {  
  79.             err_next = err_last;  
  80.             err_last = err;  
  81.             err = setvalue - prvalue;  
  82.             //抗積分飽和  
  83.             if (prvalue > UMAX)  
  84.             {  
  85.   
  86.                 if (err < 0)  
  87.                     index = 1;  
  88.                 else  
  89.                     index = 0;  
  90.             }  
  91.             else if (prvalue < UMIN)  
  92.             {  
  93.                 if (err > 0)  
  94.                     index = 1;  
  95.                 else  
  96.                     index = 0;  
  97.             }  
  98.                 //積分分離  
  99.             else  
  100.             {  
  101.                 if (Math.Abs(err) > 0.8 * setvalue)  
  102.                     index = 0;  
  103.                 else  
  104.                     index = 1;  
  105.             }  
  106.             //死區  
  107.             if (Math.Abs(err) > deadband)  
  108.                 prvalue += prakp * ((err - err_last) + index * praki * err + prakd * (err - 2 * err_last + err_next));  
  109.   
  110.             else  
  111.                 prvalue += 0;  
  112.             //輸出上下限制  
  113.             if (prvalue > MAXLIM)  
  114.                 prvalue = MAXLIM;  
  115.             if (prvalue < MINLIM)  
  116.                 prvalue = MINLIM;  
  117.   
  118.             return prvalue;  
  119.         }  
  120.     }  
  121. }  
再用同樣的參數模擬測試,結果如下圖,到達穩定的設定值時間快很多,而且沒有出現振盪的現象,有明顯改善
 
 
第一部分完成對PID的算法實現,接下來開始第二部分動態繪制PID曲線圖。
 
開始還糾結於在哪畫圖,是在FORM上,還是PICTUREBOX上,動態獲取的數據放到一個什么樣的數組里還是LIST里?然后怎么讓曲線實現從左往右的平移?
參考了其他人的程序和資料后,其實不用這么糾結,分開來看這個問題雖然這次是個很小的問題,但跟大的項目思路是一樣的,分成圖形顯示和數據更新兩個部分來看,就豁然開朗了。
 
整個思路就是 繪制背景-繪制網格線-獲取數據-繪制曲線 這樣的循環,循環可以放到一個定時器里。對於使用什么樣的容器來存放圖形和曲線數據,只是實現的細節不同。
 
我用的在PICTUREBOX里繪圖,用一個POINTF[ ]數組來存放一次要顯示的所有數據,周期觸發PICTUREBOX的PAINT事件,同時把POINTF[ ]里的點坐標Y軸都刷新一次。如果用LIST來存放POINT的坐標值,可以使用方法lRemoveAt() 來移除第一個點,Add( )來實現在尾部加上最新的一個點,從而實現曲線的動態平移。
為了方便以后的重復使用,我做成了一個用戶控件的形式UserControl1
 
修改參考的代碼如下:
[csharp]  view plain  copy
 
  1. <pre name="code" class="csharp">using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Drawing;  
  5. using System.Data;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9.   
  10. namespace WindowsFormsControlLibrary2  
  11. {  
  12.     public partial class UserControl1 : UserControl  
  13.     {  
  14.         public UserControl1()  
  15.         {  
  16.             InitializeComponent();  
  17.         }  
  18.         Graphics g;  
  19.         //List<float> l = new List<float>();//儲存要繪制的數據  
  20.         Pen p = new Pen(Color.Green, 1);  
  21.         Pen p1 = new Pen(Color.Red, 1);  
  22.         //PointF ptfront = new PointF();  
  23.         //PointF ptbehond = new PointF();  
  24.         private int jiange = 86;//網格間距  
  25.         private int pianyi = 2;//繪圖兩點之間間隔  
  26.         private float value1;  
  27.         //Random r=new Random ();  
  28.         PointF[] data;  
  29.         public float Value  
  30.         {  
  31.             get  
  32.             {  
  33.                 return value1;  
  34.             }  
  35.             set  
  36.             {  
  37.                 this.value1 = value;  
  38.             }  
  39.         }  
  40.         //獲得一個數據  
  41.         private void getdata()  
  42.         {  
  43.             data[data.Length - 1].Y = value1;  
  44.             for (int i = 0; i < data.Length - 1; i++)  
  45.                 data[i].Y = data[i + 1].Y;  
  46.             //放數據到LIST  
  47.             //if (l.Count >= 80)  
  48.             //{  
  49.             //    l.RemoveAt(0);  
  50.             //    l.Add(value1 );  
  51.             //}  
  52.         }  
  53.         //初始化數據存放數組  
  54.         private void UserControl1_Load_1(object sender, EventArgs e)  
  55.         {  
  56.             timer1.Enabled = true;  
  57.             timer1.Interval = 100;  
  58.             //l.Add(0);  
  59.             data = new PointF[pictureBox1.Width / pianyi];  
  60.             for (int i = 0; i < data.Length; i++)  
  61.                 data[i].X += pianyi * i;  
  62.             /* 
  63.             for (int i = 0; i < pictureBox1.Width / pianyi; i++) 
  64.                 { 
  65.                     l.Add(r.Next(50)); 
  66.                 } 
  67.             */  
  68.         }  
  69.   
  70.         private void pictureBox1_Paint_1(object sender, PaintEventArgs e)  
  71.         {  
  72.             g = e.Graphics;  
  73.             //畫網格線  
  74.             //for (int i = this.pictureBox1.Width; i >= 0; i -= jiange)  
  75.             //g.DrawLine(p, i, 0, i, this.pictureBox1.Width);  
  76.             //for (int i = this.pictureBox1.Height; i >= 0; i -= jiange)  
  77.             //g.DrawLine(p, 0, i, this.pictureBox1.Width , i);  
  78.             for (int i = 0; i < pictureBox1.Width; i++)  
  79.                 if (i % jiange == 0)  
  80.                     g.DrawLine(p, i, 0, i, this.pictureBox1.Height);  
  81.             for (int i = 0; i < pictureBox1.Height; i++)  
  82.                 if (i % jiange == 0)  
  83.                     g.DrawLine(p, 0, i, pictureBox1.Width, i);  
  84.             //畫數據曲線  
  85.             //    ptbehond.X = 0;  
  86.             /* 
  87.             for (int i = 0; i < l.Count - 1; i++) 
  88.             { 
  89.                 ptfront.X = ptbehond.X; 
  90.                 ptfront.Y = l[i]; 
  91.                 ptbehond.X += pianyi; 
  92.                 ptbehond.Y = l[i + 1]; 
  93.                 g.DrawLine(p1, ptfront, ptbehond); 
  94.             } 
  95.              */  
  96.             g.DrawCurve(p1, data);  
  97.         }  
  98.         //繪圖刷新周期  
  99.         private void timer1_Tick_1(object sender, EventArgs e)  
  100.         {  
  101.             getdata();  
  102.             this.pictureBox1.Refresh();  
  103.         }  
  104.   
  105.     }  
  106. }  
 
           
 
          
最后,在FORM1中實現用來PID運算的類Class1和用來顯示數據曲線的用戶控件UserControl1的調用,代碼如下:
 
[csharp]  view plain  copy
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9.   
  10. namespace PIDtest  
  11. {  
  12.     public partial class Form1 : Form  
  13.     {  
  14.         public Form1()  
  15.         {  
  16.             InitializeComponent();  
  17.             timer1.Interval = 5;  
  18.             timer1.Enabled = true;  
  19.         }  
  20.         Class1 pid = new Class1();  
  21.         //Random  r = new Random();  
  22.         private void timer1_Tick(object sender, EventArgs e)  
  23.         {  
  24.             userControl11.Value = pid.pidvalc();        
  25.         }  
  26.         private void button1_Click(object sender, EventArgs e)  
  27.         {  
  28.             pid.pidinit();  
  29.             pid.Setvalue = float.Parse(textBox1setvalue.Text);  
  30.             pid.Prakp = float.Parse(textBox1prakp.Text);  
  31.             pid.Praki = float.Parse(textBox2praki.Text);  
  32.             pid.Prakd = float.Parse(textBox3prakd.Text);  
  33.         }  
  34.     }  
  35. }  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM