汽包锅炉液位控制原理
本实验拟使用PID算法实现液位控制,如下图所示:
水位控制系统(网络图片,侵权删除)
其中有若干部件构成,分别说明如下:
1,水池Pool,底面积为1m2,初始液位为1m,水的初始容积为1m3,目标水位(targetLevel)控制在1.2m,实际液位(actualLevel)受入口管道(TubeIn)和出口管道(TubeOut)流动情况而定。
2,出口管道(TubeOu)截面积(orificeArea)默认等于1cm圆形的面积,即3.14×0.01×0.01m2。出口流速根据无局部阻力损失的伯努利方程确定,即,其中g为重力加速度,h为水池液位(= actualLevel)。
3,入口管道(TubeIn)默认最大流量为0.01m3/s,入口管道实际流量受电磁阀(Valve)的开度(valveOpenning)控制,并与开度成正比。
4,电磁阀(Valve)的开度(valveOpenning)决定了入口管道水流量(flux),开度介于0和1,等于0时入口完全关闭,开度为1时流量达到最大0.01m3/s。
5,液位传感器(LevelSensor)可以实时测量水池液位,并反馈给PID控制器。
6,PID控制器(Controller),PID控制器根据目标水位(targetLevel)和传感器测量的实际水位(actualLevel)确定电磁阀(Valve)的开度(valveOpenning),从而形成闭环控制:
其中Kp、TI、TD分别为静态比例放大系数、积分时间和微分时间,μ(t)为位移量,决定了执行机构的动作,e(t)用于表征目标设定值(如目标水位targetLevel所对应的电信号量)与实际值(如实际水位actualLevel经过传感器和信号处理最终得到的电信号量)的差值。
先写一个html界面用于绘制液位高度(这个html文档本身不包含js脚本程序,而是链接到外部脚本js文件PID.Controller.js,这个文件后期给出,注意此html文档):
1. <!DOCTYPE html>
2. <html style="height: 100%">
3. <head>
4. <meta charset="utf-8">
5. </head>
6. <body style="height: 100%;margin: 0">
7. <div id="container"style="height:100%"></div>
8. <script type="text/javascript"src="http://echarts.baidu.com/gallery/vendors/echarts/echarts.min.js"></script>
9. <script type="text/javascript"src="PID.Controller.js"></script>
10. </body>
11. </html>
封装几个类,闭住眼睛设想一下这几个类就是真实的设备,依次是进水水管类TubeIn、出水水管类TubeOut、阀门类Valve、液位传感器类LevelSensor以及PID控制器类Controller:
1. class TubeIn{
2. constructor(flux) {
3. this.flux = flux;
4. }
5. }
6.
7. class TubeOut{
8. constructor(orificeArea) {
9. this.orificeArea = orificeArea;
10. }
11. }
12.
13. class Valve{
14. constructor(valveOpenning) {
15. this.setValveOpenning(valveOpenning);
16. }
17. setValveOpenning(value){
18. if(value>1) value=1;
19. if(value<0) value=0;
20. this.valveOpenning = value;
21. }
22. }
23.
24. class LevelSensor{
25. static getLevel(pool){
26. return pool.actualLevel;
27. }
28. }
29.
30. class Controller{
31. static PID(target,current,KP,TI=0,TD=0){
32. var out=target-current;
33. return out*KP;
34. }
35. }
然后是我们的水池类Pool(假设它是锅炉汽包),其中进水量根据进水管的阀门开度而定,出水量根据伯努利方程由液位而定:
1. class Pool {
2. constructor(radius,iniVolume,targetLevel) {
3. this.radius = radius;
4. this.volume = iniVolume;
5. this.targetLevel = targetLevel;
6. this.actualLevel = iniVolume/Math.PI/radius/radius;
7.
8. this.tubeIn=new TubeIn(0.01);
9. this.valve=newValve(1);
10. this.tubeOut=newTubeOut(Math.PI*0.01*0.01);
11. }
12. waterFlowIn(timeStep){
13. this.volume+=this.valve.valveOpenning*this.tubeIn.flux*timeStep;
14. this.actualLevel = this.volume/Math.PI/this.radius/this.radius;
15. }
16. waterFlowOut(timeStep){
17. this.actualLevel = this.volume/Math.PI/this.radius/this.radius;
18. var velocityOut=Math.sqrt(2*9.8*this.actualLevel);
19. this.volume-=velocityOut*this.tubeOut.orificeArea*timeStep;
20. this.actualLevel = this.volume/Math.PI/this.radius/this.radius;
21. }
22. }
好了,看看随着时间液位是怎么变化的?
1. var pool=new Pool(Math.sqrt(1/Math.PI),1,1.2);
2. varstartTime=0,endTime=40,timeStep=0.1,flowTime=0;
3. variteratorNum=(endTime-startTime)/timeStep;
4. varKP=20,TI=0,TD=0;
5. var flowTimes=[],actualLevels=[];
6. for(var iter=0;iter<iteratorNum;iter++){
7. var actualLevel=LevelSensor.getLevel(pool);//传感器反馈测量的水位值
8. varmu=Controller.PID(pool.targetLevel,actualLevel,KP,TI,TD);//PID控制器输出位移量
9. pool.valve.setValveOpenning(mu);//根据位移量设定阀门开度
10. pool.waterFlowIn(timeStep);//流入水
11. pool.waterFlowOut(timeStep);//流出水
12. flowTime+=timeStep;
13. flowTimes.push(flowTime);
14. actualLevels.push(pool.actualLevel);
15. }
好了,绘制液位变化的时候到了:
1. var dom = document.getElementById("container");
2. var myChart =echarts.init(dom);
3. var app = {};
4. option= null;
5. option= {
6. xAxis:{
7. type:'category',
8. data:flowTimes
9. },
10. yAxis:{
11. type:'value',
12. min:1
13. },
14. series:[{
15. data:actualLevels,
16. type:'line'
17. }],
18. title:{
19. text:"PID控制器下的水位"
20. },
21. tooltip:{
22. trigger:'axis',
23. formatter:function (params) {
24. params= params[0];
25. return "水位/m:"+params.value+"\r\n"+"时间/s:"+params.name;
26. },
27. axisPointer:{
28. animation:false
29. }
30. }
31. };
32.
33. if (option&& typeof option === "object") {
34. myChart.setOption(option,true);
35. }
点击运行网页(由于使用了js较新的ES6特性,旧版本的浏览器可能不支持class关键字,请使用最新的浏览器,本文使用微软浏览器Microsoft Edge 42.17134.1.0),得到液位随时间变化曲线:
整个js代码(js脚本文件PID.Controller.js):
1. class TubeIn{
2. constructor(flux) {
3. this.flux = flux;
4. }
5. }
6.
7. class TubeOut{
8. constructor(orificeArea) {
9. this.orificeArea = orificeArea;
10. }
11. }
12.
13. class Valve{
14. constructor(valveOpenning) {
15. this.setValveOpenning(valveOpenning);
16. }
17. setValveOpenning(value){
18. if(value>1) value=1;
19. if(value<0) value=0;
20. this.valveOpenning = value;
21. }
22. }
23.
24. class LevelSensor{
25. static getLevel(pool){
26. return pool.actualLevel;
27. }
28. }
29.
30. class Controller{
31. static PID(target,current,KP,TI=0,TD=0){
32. var out=target-current;
33. return out*KP;
34. }
35. }
36.
37. class Pool {
38. constructor(radius,iniVolume,targetLevel) {
39. this.radius = radius;
40. this.volume = iniVolume;
41. this.targetLevel = targetLevel;
42. this.actualLevel = iniVolume/Math.PI/radius/radius;
43.
44. this.tubeIn=newTubeIn(0.01);
45. this.valve=newValve(1);
46. this.tubeOut=newTubeOut(Math.PI*0.01*0.01);
47. }
48. waterFlowIn(timeStep){
49. this.volume+=this.valve.valveOpenning*this.tubeIn.flux*timeStep;
50. this.actualLevel = this.volume/Math.PI/this.radius/this.radius;
51. }
52. waterFlowOut(timeStep){
53. this.actualLevel = this.volume/Math.PI/this.radius/this.radius;
54. var velocityOut=Math.sqrt(2*9.8*this.actualLevel);
55. this.volume-=velocityOut*this.tubeOut.orificeArea*timeStep;
56. this.actualLevel = this.volume/Math.PI/this.radius/this.radius;
57. }
58. }
59.
60. var pool=new Pool(Math.sqrt(1/Math.PI),1,1.2);
61. varstartTime=0,endTime=40,timeStep=0.1,flowTime=0;
62. variteratorNum=(endTime-startTime)/timeStep;
63. varKP=20,TI=0,TD=0;
64. varflowTimes=[],actualLevels=[];
65. for(var iter=0;iter<iteratorNum;iter++){
66. var actualLevel=LevelSensor.getLevel(pool);//传感器反馈测量的水位值
67. varmu=Controller.PID(pool.targetLevel,actualLevel,KP,TI,TD);//PID控制器输出位移量
68. pool.valve.setValveOpenning(mu);//根据位移量设定阀门开度
69. pool.waterFlowIn(timeStep);//流入水
70. pool.waterFlowOut(timeStep);//流出水
71. flowTime+=timeStep;
72. flowTimes.push(flowTime);
73. actualLevels.push(pool.actualLevel);
74. }
75.
76. var dom = document.getElementById("container");
77. var myChart =echarts.init(dom);
78. var app = {};
79. option= null;
80. option= {
81. xAxis:{
82. type:'category',
83. data:flowTimes
84. },
85. yAxis:{
86. type:'value',
87. min:1
88. },
89. series:[{
90. data:actualLevels,
91. type:'line'
92. }],
93. title:{
94. text:"PID控制器下的水位"
95. },
96. tooltip:{
97. trigger:'axis',
98. formatter:function (params) {
99. params= params[0];
100. return "水位/m:"+params.value+"\r\n"+"时间/s:"+params.name;
101. },
102. axisPointer:{
103. animation:false
104. }
105. }
106.};
107.
108.if (option && typeofoption === "object") {
109. myChart.setOption(option,true);
110.}
111.
112.console.log(actualLevels,"end")
[结语]:曲线异常光滑,与通常控制曲线不同,这是因为我们的K控制器(阀门开度)调节性能太理想了,实际上的阀门控制不可能这么光滑。另外程序之给出了P控制器的代码,读者可以改为PI或PID控制器。