离开工控行业已经有一段时间了,最近回忆起以前的工作,又对 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 Box20, 0.3m, 0.1m, 0m, 0.5m); Init); } //初始化 private void Init) { using Graphics g = panel2.CreateGraphics)) { Pen whitePen = new PenBrushes.White, 2000); Point point1 = new Point0, 0); Point point2 = new Point0, 2000); g.DrawLinewhitePen, point1, point2); } maxLevel = 0; time = 0; lastPoint = new Point0, yBase); } private void btnStart_Clickobject sender, EventArgs e) { Start0); } private void btnStart2_Clickobject sender, EventArgs e) { Start1); } /// <summary> /// 开始 /// </summary> /// <param name="number">0使用普通pi调节,1使用改进pi调节</param> private void Startint number) { Init); frmPid.Init0.8m, numP.Value, numI.Value, 0, 1); frmBox.Init); Pen bluePen = new PenBrushes.Blue, 1); using Graphics g = panel2.CreateGraphics)) { Point point1 = new Point0, yBase - Convert.ToInt32frmPid.Target * 100) * yMul); Point point2 = new Point1000, yBase - Convert.ToInt32frmPid.Target * 100) * yMul); g.DrawLinebluePen, point1, point2); } bool complete = false; for int i = 0; i < 1000; i++) { { time++; frmBox.ChangeLevel); Pen blackPen = new PenBrushes.Black, 1); using Graphics g = panel2.CreateGraphics)) { Point point = new Pointtime * xMul, yBase - Convert.ToInt32frmBox.GetLevel) * 100) * yMul); g.DrawLineblackPen, point, lastPoint); lastPoint = point; } decimal degreeIn = frmPid.GetOutPutValuefrmBox.GetLevel), number); frmBox.ChangeDegreeIndegreeIn); } if frmBox.GetLevel) > maxLevel) { maxLevel = frmBox.GetLevel); } if Math.AbsfrmBox.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 Boxdecimal 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.Add0); } public void Init) { this.levelList = new List<decimal>); this.levelList.Add0); } 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.Addlevel); while this.levelList.Count > 10) { this.levelList.RemoveAt0); } } public decimal GetLevel) { return this.levelList[0]; } /// <summary> /// 改变进水阀开度 /// </summary> public void ChangeDegreeIndecimal 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 Initdecimal 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 GetOutPutValuedecimal feedBack, int number) { decimal error = this.Target - feedBack; if this.I > 0) { if number == 0) { this.IntegralValue += error / this.I; } else { if Math.Abserror) < 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; } } }