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运算,没有考虑从积分分离和死区处理,最重要的代码如下:

   private float prakp, praki, prakd, prvalue, err, err_last, err_next, setvalue;
        int MAXLIM, MINLIM;
  //PID valculate
        public float pidvalc()
        {
            err_next = err_last;        //前两次的误差
            err_last = err;             //前一次的误差
            err = setvalue - prvalue;   //现在的误差

            //增量式计算
            prvalue += prakp * ((err - err_last) + praki * err + prakd * (err - 2 * err_last + err_next));
            //输出上下限值
            if (prvalue > MAXLIM)
                prvalue = MAXLIM;
            if (prvalue < MINLIM)
                prvalue = MINLIM;

            return prvalue;
        }

模拟出来的结果是下面这样,出现输出值收敛振荡后稳定下来。

C#实现PID控制的模拟测试和曲线绘图_第1张图片

现在我们对PID运算部分的代码改进一点,增加了积分分离和死区的功能,完整代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PIDtest
{
    class Class1
    {
        private float prakp, praki, prakd, prvalue, err, err_last, err_next, setvalue, deadband;
        int index, UMAX, UMIN, MAXLIM, MINLIM;
        public float Prakp
        {
            set
            {
                prakp = value;
            }
            get
            {
                return prakp;
            }
        }
        public float Praki
        {
            set
            {
                praki = value;
            }
            get
            {
                return praki;
            }
        }
        public float Prakd
        {
            set
            {
                prakd = value;
            }
            get
            {
                return prakd;
            }
        }
        public float Setvalue
        {
            set
            {
                setvalue = value;
            }
            get
            {
                return setvalue;
            }
        }
        public Class1()
        {
            pidinit();
        }
        //PID参数初始化
        public void pidinit()
        {
            prakp = 0;
            praki = 0;
            prakd = 0;
            prvalue = 0;
            err = 0;
            err_last = 0;
            err_next = 0;
            MAXLIM = 800;
            MINLIM = -200;
            UMAX = 310;
            UMIN = -100;
            deadband = 2;
        }
        //PID valculate
        public float pidvalc()
        {
            err_next = err_last;
            err_last = err;
            err = setvalue - prvalue;
            //抗积分饱和
            if (prvalue > UMAX)
            {

                if (err < 0)
                    index = 1;
                else
                    index = 0;
            }
            else if (prvalue < UMIN)
            {
                if (err > 0)
                    index = 1;
                else
                    index = 0;
            }
                //积分分离
            else
            {
                if (Math.Abs(err) > 0.8 * setvalue)
                    index = 0;
                else
                    index = 1;
            }
            //死区
            if (Math.Abs(err) > deadband)
                prvalue += prakp * ((err - err_last) + index * praki * err + prakd * (err - 2 * err_last + err_next));

            else
                prvalue += 0;
            //输出上下限制
            if (prvalue > MAXLIM)
                prvalue = MAXLIM;
            if (prvalue < MINLIM)
                prvalue = MINLIM;

            return prvalue;
        }
    }
}
再用同样的参数模拟测试,结果如下图,到达稳定的设定值时间快很多,而且没有出现振荡的现象,有明显改善
C#实现PID控制的模拟测试和曲线绘图_第2张图片


第一部分完成对PID的算法实现,接下来开始第二部分动态绘制PID曲线图。

开始还纠结于在哪画图,是在FORM上,还是PICTUREBOX上,动态获取的数据放到一个什么样的数组里还是LIST里?然后怎么让曲线实现从左往右的平移?
参考了其他人的程序和资料后,其实不用这么纠结,分开来看这个问题虽然这次是个很小的问题,但跟大的项目思路是一样的,分成图形显示和数据更新两个部分来看,就豁然开朗了。

整个思路就是 绘制背景-绘制网格线-获取数据-绘制曲线 这样的循环,循环可以放到一个定时器里。对于使用什么样的容器来存放图形和曲线数据,只是实现的细节不同。

我用的在PICTUREBOX里绘图,用一个POINTF[ ]数组来存放一次要显示的所有数据,周期触发PICTUREBOX的PAINT事件,同时把POINTF[ ]里的点坐标Y轴都刷新一次。如果用LIST来存放POINT的坐标值,可以使用方法lRemoveAt() 来移除第一个点,Add( )来实现在尾部加上最新的一个点,从而实现曲线的动态平移。
为了方便以后的重复使用,我做成了一个用户控件的形式UserControl1

修改参考的代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsControlLibrary2
{
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }
        Graphics g;
        //List l = new List();//储存要绘制的数据
        Pen p = new Pen(Color.Green, 1);
        Pen p1 = new Pen(Color.Red, 1);
        //PointF ptfront = new PointF();
        //PointF ptbehond = new PointF();
        private int jiange = 86;//网格间距
        private int pianyi = 2;//绘图两点之间间隔
        private float value1;
        //Random r=new Random ();
        PointF[] data;
        public float Value
        {
            get
            {
                return value1;
            }
            set
            {
                this.value1 = value;
            }
        }
        //获得一个数据
        private void getdata()
        {
            data[data.Length - 1].Y = value1;
            for (int i = 0; i < data.Length - 1; i++)
                data[i].Y = data[i + 1].Y;
            //放数据到LIST
            //if (l.Count >= 80)
            //{
            //    l.RemoveAt(0);
            //    l.Add(value1 );
            //}
        }
        //初始化数据存放数组
        private void UserControl1_Load_1(object sender, EventArgs e)
        {
            timer1.Enabled = true;
            timer1.Interval = 100;
            //l.Add(0);
            data = new PointF[pictureBox1.Width / pianyi];
            for (int i = 0; i < data.Length; i++)
                data[i].X += pianyi * i;
            /*
            for (int i = 0; i < pictureBox1.Width / pianyi; i++)
                {
                    l.Add(r.Next(50));
                }
            */
        }

        private void pictureBox1_Paint_1(object sender, PaintEventArgs e)
        {
            g = e.Graphics;
            //画网格线
            //for (int i = this.pictureBox1.Width; i >= 0; i -= jiange)
            //g.DrawLine(p, i, 0, i, this.pictureBox1.Width);
            //for (int i = this.pictureBox1.Height; i >= 0; i -= jiange)
            //g.DrawLine(p, 0, i, this.pictureBox1.Width , i);
            for (int i = 0; i < pictureBox1.Width; i++)
                if (i % jiange == 0)
                    g.DrawLine(p, i, 0, i, this.pictureBox1.Height);
            for (int i = 0; i < pictureBox1.Height; i++)
                if (i % jiange == 0)
                    g.DrawLine(p, 0, i, pictureBox1.Width, i);
            //画数据曲线
            //    ptbehond.X = 0;
            /*
            for (int i = 0; i < l.Count - 1; i++)
            {
                ptfront.X = ptbehond.X;
                ptfront.Y = l[i];
                ptbehond.X += pianyi;
                ptbehond.Y = l[i + 1];
                g.DrawLine(p1, ptfront, ptbehond);
            }
             */
            g.DrawCurve(p1, data);
        }
        //绘图刷新周期
        private void timer1_Tick_1(object sender, EventArgs e)
        {
            getdata();
            this.pictureBox1.Refresh();
        }

    }
}
 
    
 
   
最后,在FORM1中实现用来PID运算的类Class1和用来显示数据曲线的用户控件UserControl1的调用,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace PIDtest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            timer1.Interval = 5;
            timer1.Enabled = true;
        }
        Class1 pid = new Class1();
        //Random  r = new Random();
        private void timer1_Tick(object sender, EventArgs e)
        {
            userControl11.Value = pid.pidvalc();      
        }
        private void button1_Click(object sender, EventArgs e)
        {
            pid.pidinit();
            pid.Setvalue = float.Parse(textBox1setvalue.Text);
            pid.Prakp = float.Parse(textBox1prakp.Text);
            pid.Praki = float.Parse(textBox2praki.Text);
            pid.Prakd = float.Parse(textBox3prakd.Text);
        }
    }
}






你可能感兴趣的:(C#实现PID控制的模拟测试和曲线绘图)