• 转 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. }  
  • 相关阅读:
    纪念日给男(女)朋友的表白页面
    Vue组件的传值(非父子之间)
    express脚手架的安装和使用
    MongoDB数据库
    vuex状态
    MVVM框架的简单理解
    关于vue脚手架
    申请百度密钥
    svg
    微信小程序开发学习笔记
  • 原文地址:https://www.cnblogs.com/wanglg/p/8036038.html
Copyright © 2020-2023  润新知