c# PID算法入門


離開工控行業已經有一段時間了,最近回憶起以前的工作,又對 PID 算法有了興趣。所以寫了一個小項目,希望可以幫到需要的人,也算是對那段工作經歷的一個總結。

這是一個 winform 的項目。負載是一個水箱,有一個進水口,一個出水口。設定值為液位,通過控制進水口的閥門開度使液位達到設定值,傳感器的滯后時間為10秒。每秒執行一次 PID 算法(對於運動控制的項目需要將采樣時間調低)。

結果:

左圖采用原生 PID 調節,右圖采用積分分離后的 PID 調節(在誤差小於一定值的情況下積分才開始累積)。可以看出積分分離可以有效的抑制超調量,但是會增加調節時間。

由於微分調節對系統穩定性影響較大,不建議初學者使用。

在分配 PID 的各項參數時,除了使用 “自動控制理論” 中計算傳遞函數,還可以通過試湊的方法。先確定比例的大致范圍,再加入積分。加入積分時,需要先將積分值調到很大(積分值大表示效果較弱),再慢慢降低。

 

 

窗口中的控件:

label : lblInfo1(用於顯示超調)lblInfo2(用於顯示調節時間)

button:btnStart(開始普通 PID 算法)btnStart2(開始改進型 PID 算法)(主要采用積分分離算法)

numericupdown:numP(比例值)numI(積分值)

panel:panel2(用於繪圖顯示 PID 調節過程)

代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PID
{
    public partial class Form1 : Form
    {
        PID frmPid;
        Box frmBox;
        const int yBase = 500;
        const int yMul = 5;
        const int xMul = 1;
        int time = 0;//上次采樣時間 時間為秒
        Point lastPoint;
        decimal maxLevel = 0;//最大值用於求超調
        public Form1()
        {
            InitializeComponent();
            frmPid = new PID();
            frmBox = new Box(20, 0.3m, 0.1m, 0m, 0.5m);
            Init();
        }
        //初始化
        private void Init()
        {
            using (Graphics g = panel2.CreateGraphics())
            {
                Pen whitePen = new Pen(Brushes.White, 2000);
                Point point1 = new Point(0, 0);
                Point point2 = new Point(0, 2000);
                g.DrawLine(whitePen, point1, point2);
            }
            maxLevel = 0;
            time = 0;
            lastPoint = new Point(0, yBase);
        }
        private void btnStart_Click(object sender, EventArgs e)
        {
            Start(0);
        }

        private void btnStart2_Click(object sender, EventArgs e)
        {
            Start(1);
        }

        /// <summary>
        /// 開始
        /// </summary>
        /// <param name="number">0使用普通pi調節,1使用改進pi調節</param>
        private void Start(int number)
        {
            Init();
            frmPid.Init(0.8m, numP.Value, numI.Value, 0, 1);
            frmBox.Init();
            Pen bluePen = new Pen(Brushes.Blue, 1);
            using (Graphics g = panel2.CreateGraphics())
            {
                Point point1 = new Point(0, yBase - Convert.ToInt32(frmPid.Target * 100) * yMul);
                Point point2 = new Point(1000, yBase - Convert.ToInt32(frmPid.Target * 100) * yMul);
                g.DrawLine(bluePen, point1, point2);
            }
            bool complete = false;
            for (int i = 0; i < 1000; i++)
            {
                {
                    time++;
                    frmBox.ChangeLevel();
                    Pen blackPen = new Pen(Brushes.Black, 1);
                    using (Graphics g = panel2.CreateGraphics())
                    {
                        Point point = new Point(time * xMul, yBase - Convert.ToInt32(frmBox.GetLevel() * 100) * yMul);
                        g.DrawLine(blackPen, point, lastPoint);
                        lastPoint = point;
                    }
                    decimal degreeIn = frmPid.GetOutPutValue(frmBox.GetLevel(), number);
                    frmBox.ChangeDegreeIn(degreeIn);
                }

                if (frmBox.GetLevel() > maxLevel)
                {
                    maxLevel = frmBox.GetLevel();
                }
                if ((Math.Abs(frmBox.GetLevel() - frmPid.Target) / frmPid.Target < 0.01m) && (!complete))
                {
                    complete = true;
                    lblInfo2.Text = "調節時間:" + time;
                }
            }
            decimal up = 0;
            if (maxLevel > frmPid.Target)
            {
                up = (maxLevel - frmPid.Target) / frmPid.Target;
            }
            lblInfo1.Text = "超調:" + up.ToString("0.000");
        }
    }

    public class Box
    {
        private List<decimal> levelList;
        private decimal area; //底面積  平方米
        private decimal maxFlowOut = 0.05m; //出水閥最大流量立方每秒
        private decimal maxFlowIn = 0.1m;  //進水閥最大流量 立方每秒
        private decimal degreeIn;   //進水閥開度
        private decimal degreeOut; //出水閥開度

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="area">底面積</param>  
        /// <param name="maxFlowIn">進水閥最大流量 立方每秒</param>
        /// <param name="maxFlowOut">出水閥最大流量立方每秒</param>
        /// <param name="degreeIn">進水閥開度</param>
        /// <param name="degreeOut">出水閥開度</param>
        public Box(decimal area, decimal maxFlowIn, decimal maxFlowOut, decimal degreeIn, decimal degreeOut)
        {
            this.area = area;
            this.maxFlowOut = maxFlowOut;
            this.maxFlowIn = maxFlowIn;
            this.degreeIn = degreeIn;
            this.degreeOut = degreeOut;
            this.levelList = new List<decimal>();
            this.levelList.Add(0);
        }
        public void Init()
        {
            this.levelList = new List<decimal>();
            this.levelList.Add(0);
        }
        private decimal GetActualLevel()
        {
            return this.levelList[this.levelList.Count - 1];
        }
        /// <summary>
        ///每調用一次表示經過了一秒
        /// </summary>
        public void ChangeLevel()
        {
            decimal myflow = this.degreeIn * this.maxFlowIn - this.degreeOut * this.maxFlowOut;//增加的流量
            decimal level = this.GetActualLevel() + myflow / this.area;//新的液位
            if (level < 0)
            {
                level = 0;
            }
            if (level > 1)
            {
                level = 1;
            }
            this.levelList.Add(level);
            while (this.levelList.Count > 10)
            {
                this.levelList.RemoveAt(0);
            }
        }

        public decimal GetLevel()
        {
            return this.levelList[0];
        }

        /// <summary>
        /// 改變進水閥開度
        /// </summary>
        public void ChangeDegreeIn(decimal degreeIn)
        {
            this.degreeIn = degreeIn;
        }
    }

    /// <summary>
    /// PID控制類
    /// </summary>
    public class PID
    {
        /// <summary>
        /// 積分累計值
        /// </summary>
        public decimal IntegralValue { get; set; }
        /// <summary>
        /// 設定值
        /// </summary>
        public decimal Target { get; set; }
        /// <summary>
        /// 比例
        /// </summary>
        public decimal P { get; set; }
        /// <summary>
        /// 積分
        /// </summary>
        public decimal I { get; set; }
        /// <summary>
        /// 輸出限幅
        /// </summary>
        private decimal MinOutPut { get; set; }
        /// <summary>
        /// 輸出限幅
        /// </summary>
        private decimal MaxOutPut { get; set; }

        public void Init(decimal target, decimal p, decimal i, decimal minOutput, decimal maxOutput)
        {
            this.Target = target;
            this.P = p;
            this.I = i;
            IntegralValue = 0;
            if (minOutput > maxOutput)
            {
                throw new Exception("下限幅不能大於上限幅");
            }
            this.MinOutPut = minOutput;
            this.MaxOutPut = maxOutput;
        }

        /// <summary>
        /// 獲得輸出值
        /// </summary>
        /// <param name="feedBack">反饋值</param>
        /// <param name="number">0普通算法,1改進后的算法</param>
        /// <returns></returns>
        public decimal GetOutPutValue(decimal feedBack, int number)
        {
            decimal error = this.Target - feedBack;
            if (this.I > 0)
            {
                if (number == 0)
                {
                    this.IntegralValue += error / this.I;
                }
                else
                {
                    if ((Math.Abs(error) < 0.5m))
                    {
                        this.IntegralValue += error / this.I;
                    }
                }
            }
            decimal output = error * this.P + this.IntegralValue;
            if (output < this.MinOutPut)
            {
                return this.MinOutPut;
            }
            if (output > this.MaxOutPut)
            {
                return this.MaxOutPut;
            }
            return output;
        }
    }
}

 


免責聲明!

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



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