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运算,没有考虑从积分分离和死区处理,最重要的代码如下:
 
  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. prvalue += prakp * ((err - err_last) + praki * err + prakd * (err - 2 * err_last + err_next));
  11. //输出上下限值
  12. if (prvalue > MAXLIM)
  13. prvalue = MAXLIM;
  14. if (prvalue < MINLIM)
  15. prvalue = MINLIM;
  16. return prvalue;
  17. }
 

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

 
 
现在我们对PID运算部分的代码改进一点,增加了积分分离和死区的功能,完整代码如下:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace PIDtest
  6. {
  7. class Class1
  8. {
  9. private float prakp, praki, prakd, prvalue, err, err_last, err_next, setvalue, deadband;
  10. int index, UMAX, UMIN, MAXLIM, MINLIM;
  11. public float Prakp
  12. {
  13. set
  14. {
  15. prakp = value;
  16. }
  17. get
  18. {
  19. return prakp;
  20. }
  21. }
  22. public float Praki
  23. {
  24. set
  25. {
  26. praki = value;
  27. }
  28. get
  29. {
  30. return praki;
  31. }
  32. }
  33. public float Prakd
  34. {
  35. set
  36. {
  37. prakd = value;
  38. }
  39. get
  40. {
  41. return prakd;
  42. }
  43. }
  44. public float Setvalue
  45. {
  46. set
  47. {
  48. setvalue = value;
  49. }
  50. get
  51. {
  52. return setvalue;
  53. }
  54. }
  55. public Class1()
  56. {
  57. pidinit();
  58. }
  59. //PID参数初始化
  60. public void pidinit()
  61. {
  62. prakp = 0;
  63. praki = 0;
  64. prakd = 0;
  65. prvalue = 0;
  66. err = 0;
  67. err_last = 0;
  68. err_next = 0;
  69. MAXLIM = 800;
  70. MINLIM = -200;
  71. UMAX = 310;
  72. UMIN = -100;
  73. deadband = 2;
  74. }
  75. //PID valculate
  76. public float pidvalc()
  77. {
  78. err_next = err_last;
  79. err_last = err;
  80. err = setvalue - prvalue;
  81. //抗积分饱和
  82. if (prvalue > UMAX)
  83. {
  84. if (err < 0)
  85. index = 1;
  86. else
  87. index = 0;
  88. }
  89. else if (prvalue < UMIN)
  90. {
  91. if (err > 0)
  92. index = 1;
  93. else
  94. index = 0;
  95. }
  96. //积分分离
  97. else
  98. {
  99. if (Math.Abs(err) > 0.8 * setvalue)
  100. index = 0;
  101. else
  102. index = 1;
  103. }
  104. //死区
  105. if (Math.Abs(err) > deadband)
  106. prvalue += prakp * ((err - err_last) + index * praki * err + prakd * (err - 2 * err_last + err_next));
  107. else
  108. prvalue += 0;
  109. //输出上下限制
  110. if (prvalue > MAXLIM)
  111. prvalue = MAXLIM;
  112. if (prvalue < MINLIM)
  113. prvalue = MINLIM;
  114. return prvalue;
  115. }
  116. }
  117. }
再用同样的参数模拟测试,结果如下图,到达稳定的设定值时间快很多,而且没有出现振荡的现象,有明显改善
 
 
第一部分完成对PID的算法实现,接下来开始第二部分动态绘制PID曲线图。
 
开始还纠结于在哪画图,是在FORM上,还是PICTUREBOX上,动态获取的数据放到一个什么样的数组里还是LIST里?然后怎么让曲线实现从左往右的平移?
参考了其他人的程序和资料后,其实不用这么纠结,分开来看这个问题虽然这次是个很小的问题,但跟大的项目思路是一样的,分成图形显示和数据更新两个部分来看,就豁然开朗了。
 
整个思路就是 绘制背景-绘制网格线-获取数据-绘制曲线 这样的循环,循环可以放到一个定时器里。对于使用什么样的容器来存放图形和曲线数据,只是实现的细节不同。
 
我用的在PICTUREBOX里绘图,用一个POINTF[ ]数组来存放一次要显示的所有数据,周期触发PICTUREBOX的PAINT事件,同时把POINTF[ ]里的点坐标Y轴都刷新一次。如果用LIST来存放POINT的坐标值,可以使用方法lRemoveAt() 来移除第一个点,Add( )来实现在尾部加上最新的一个点,从而实现曲线的动态平移。
为了方便以后的重复使用,我做成了一个用户控件的形式UserControl1
 
修改参考的代码如下:
  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. namespace WindowsFormsControlLibrary2
  10. {
  11. public partial class UserControl1 : UserControl
  12. {
  13. public UserControl1()
  14. {
  15. InitializeComponent();
  16. }
  17. Graphics g;
  18. //List<float> l = new List<float>();//储存要绘制的数据
  19. Pen p = new Pen(Color.Green, 1);
  20. Pen p1 = new Pen(Color.Red, 1);
  21. //PointF ptfront = new PointF();
  22. //PointF ptbehond = new PointF();
  23. private int jiange = 86;//网格间距
  24. private int pianyi = 2;//绘图两点之间间隔
  25. private float value1;
  26. //Random r=new Random ();
  27. PointF[] data;
  28. public float Value
  29. {
  30. get
  31. {
  32. return value1;
  33. }
  34. set
  35. {
  36. this.value1 = value;
  37. }
  38. }
  39. //获得一个数据
  40. private void getdata()
  41. {
  42. data[data.Length - 1].Y = value1;
  43. for (int i = 0; i < data.Length - 1; i++)
  44. data[i].Y = data[i + 1].Y;
  45. //放数据到LIST
  46. //if (l.Count >= 80)
  47. //{
  48. //    l.RemoveAt(0);
  49. //    l.Add(value1 );
  50. //}
  51. }
  52. //初始化数据存放数组
  53. private void UserControl1_Load_1(object sender, EventArgs e)
  54. {
  55. timer1.Enabled = true;
  56. timer1.Interval = 100;
  57. //l.Add(0);
  58. data = new PointF[pictureBox1.Width / pianyi];
  59. for (int i = 0; i < data.Length; i++)
  60. data[i].X += pianyi * i;
  61. /*
  62. for (int i = 0; i < pictureBox1.Width / pianyi; i++)
  63. {
  64. l.Add(r.Next(50));
  65. }
  66. */
  67. }
  68. private void pictureBox1_Paint_1(object sender, PaintEventArgs e)
  69. {
  70. g = e.Graphics;
  71. //画网格线
  72. //for (int i = this.pictureBox1.Width; i >= 0; i -= jiange)
  73. //g.DrawLine(p, i, 0, i, this.pictureBox1.Width);
  74. //for (int i = this.pictureBox1.Height; i >= 0; i -= jiange)
  75. //g.DrawLine(p, 0, i, this.pictureBox1.Width , i);
  76. for (int i = 0; i < pictureBox1.Width; i++)
  77. if (i % jiange == 0)
  78. g.DrawLine(p, i, 0, i, this.pictureBox1.Height);
  79. for (int i = 0; i < pictureBox1.Height; i++)
  80. if (i % jiange == 0)
  81. g.DrawLine(p, 0, i, pictureBox1.Width, i);
  82. //画数据曲线
  83. //    ptbehond.X = 0;
  84. /*
  85. for (int i = 0; i < l.Count - 1; i++)
  86. {
  87. ptfront.X = ptbehond.X;
  88. ptfront.Y = l[i];
  89. ptbehond.X += pianyi;
  90. ptbehond.Y = l[i + 1];
  91. g.DrawLine(p1, ptfront, ptbehond);
  92. }
  93. */
  94. g.DrawCurve(p1, data);
  95. }
  96. //绘图刷新周期
  97. private void timer1_Tick_1(object sender, EventArgs e)
  98. {
  99. getdata();
  100. this.pictureBox1.Refresh();
  101. }
  102. }
  103. }


最后,在FORM1中实现用来PID运算的类Class1和用来显示数据曲线的用户控件UserControl1的调用,代码如下:
 
  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. namespace PIDtest
  10. {
  11. public partial class Form1 : Form
  12. {
  13. public Form1()
  14. {
  15. InitializeComponent();
  16. timer1.Interval = 5;
  17. timer1.Enabled = true;
  18. }
  19. Class1 pid = new Class1();
  20. //Random  r = new Random();
  21. private void timer1_Tick(object sender, EventArgs e)
  22. {
  23. userControl11.Value = pid.pidvalc();
  24. }
  25. private void button1_Click(object sender, EventArgs e)
  26. {
  27. pid.pidinit();
  28. pid.Setvalue = float.Parse(textBox1setvalue.Text);
  29. pid.Prakp = float.Parse(textBox1prakp.Text);
  30. pid.Praki = float.Parse(textBox2praki.Text);
  31. pid.Prakd = float.Parse(textBox3prakd.Text);
  32. }
  33. }
  34. }

转 C#实现PID控制的模拟测试和曲线绘图的更多相关文章

  1. STM32L476应用开发之七:流量的PID控制

    在气体分析仪使用过程中,为了力求分析结果的准确性,一般要求通过的气体流量尽可能的稳定.为了保证流量控制的稳定,我们采用PID调节来控制气路阀门的开度. 1.硬件设计 我们采用的流量计为气体质量流量计, ...

  2. PID控制及整定算法

    一.PID控制算法 PID是比例.积分.微分的简称,PID控制的难点不是编程,而是控制器的参数整定.参数整定的关键是正确地理解各参数的物理意义,PID 控制的原理可以用人对炉温的手动控制来理解.阅读本 ...

  3. Android单元测试与模拟测试详解

    测试与基本规范 为什么需要测试? 为了稳定性,能够明确的了解是否正确的完成开发. 更加易于维护,能够在修改代码后保证功能不被破坏. 集成一些工具,规范开发规范,使得代码更加稳定( 如通过 phabri ...

  4. 两轮自平衡小车双闭环PID控制设计

                                                                                            两轮自平衡小车的研究意义 ...

  5. 数字式PID控制的应用总结

    PID控制是一个二阶线性闭环控制器,通过调整比例.积分和微分三项参数,使得大多数的工业控制系统获得良好的闭环控制性能.PID控制优点:a. 技术成熟,b. 易被人们熟悉和掌握,c. 不需要建立数学模型 ...

  6. PID控制最通俗的解释与PID参数的整定方法

    转自->这里 PID是比例.积分.微分的简称,PID控制的难点不是编程,而是控制器的参数整定.参数整定的关键是正确地理解各参数的物理意义,PID控制的原理可以用人对炉温的手动控制来理解.阅读本文 ...

  7. 经典PID控制及应用体会总结

    经典PID控制及应用体会总结 PID控制原理 PID是一种线性控制器,它根据给定值rin(t)与实际输出值yout(t)构成控制方案: 重点关注相关算法是如何对偏差进行处理的: PID控制器各校正环节 ...

  8. springboot2.0入门(四)----mock模拟测试+单元测试

    一.本节主要记录模拟测试.单元测试: 二.mock 测试 1.1什么是Mock? 在面向对象程序设计中,模拟对象(英语:mock object,也译作模仿对象)是以可控的方式模拟真实对象行为的假的对象 ...

  9. 四轴飞行器飞行原理与双闭环PID控制

    四轴轴飞行器是微型飞行器的其中一种,相对于固定翼飞行器,它的方向控制灵活.抗干扰能力强.飞行稳定,能够携带一定的负载和有悬停功能,因此能够很好地进行空中拍摄.监视.侦查等功能,在军事和民用上具备广泛的 ...

随机推荐

  1. 关于oracle result_cache

    结果集缓存 和聚合物化视图类似,报表系统和数据仓库系统是最适合结果集缓存的,这些系统通常具有大量复杂的SQL,其中不少子查询包含聚合函数,如果能够尽可能重用这些已经计算过的聚合结果集,将极大的提升系统 ...

  2. JS设计模式(2)策略模式

    什么是策略模式? 定义:根据不同参数可以命中不同的策略 主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护. 何时使用:有许多种情况,而区分它们的只是他们直接的行为. ...

  3. JS设计模式(5)发布订阅模式

    什么是发布订阅模式(观察者模式)? 定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新. 主要解决:一个对象状态改变给其他对象通知的问题,而且 ...

  4. 20175312 2018-2019-2 《Java程序设计》第1周学习总结

    20175312 2018-2019-2 <Java程序设计>第1周学习总结 教材学习内容总结 已依照教材要求完成了第一章的学习,我总结的话,主要的学习量还是在安装相关软件上.其他的,比如 ...

  5. Learning-Python【22】:面向对象初识

    一.面向过程:是一种编程思想,核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么然后干什么,基于该编程思想写程序就好比在设计一条流水线,是一种机械式的思维方式 优点:把复杂问题流程化,进而简 ...

  6. 1_bytes和str

    数据运算全跳过,语言都一样,格式差异 bytes/str的区别    Python3不会以任意隐式的方式混用bytes和str,不能拼接字符串和字节包也无法在字节包里搜索字符串(反之亦然)    二进 ...

  7. Qt 程序获取程序所在路径、用户目录路径、临时文件夹等特殊路径的方法

    Qt 程序获取程序所在路径.用户目录路径.临时文件夹等特殊路径的方法 经常我们的程序中需要访问一些特殊的路径,比如程序所在的路径.用户目录路径.临时文件夹等.在 Qt 中实现这几个功能所用的方法虽然都 ...

  8. abstract class和interface的异同

    含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象.含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必 ...

  9. RabbitMQ(2) 一般介绍

    RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用. 消息中间件在互联网公司的使用中越来越多,刚才还看到新闻阿里将RocketMQ捐献给了apa ...

  10. yii框架通过http协议获取地址栏中的内容

    //创建一个控制器 <?php namespace frontend\controllers; use frontend\models\Zhuce; use Yii; use yii\web\C ...