• 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;
            }
        }
    }
  • 相关阅读:
    nginx配置跨域问题
    几个经典的TCP通信函数
    表达格式和数值格式的转换
    主机字节序与网络字节序的转换
    一对经典的时间获取客户/服务器程序
    关于TIME_WAIT状态
    一个经典的比喻( 关于TCP连接API )
    《UNIX 网络编程 第二版》编译环境的搭建( 运行本专栏代码必读 )
    简述C++中的多态机制
    最佳谓词函数 --- 函数对象
  • 原文地址:https://www.cnblogs.com/aitong/p/11023911.html
Copyright © 2020-2023  润新知