制作JavaScript开关控制器模拟器

自动控制理论的实质意味着构建这样一种系统,该系统可以在给定状态下保持某些对象的某个参数,例如,炉子中的温度或水箱中的水位。为了更好地了解该过程,可以方便地立即考虑特定的控制模型,例如控制水箱中的水位。顺便说一下,在有关TAU的教科书和文章中,经常提到这一过程作为对历史的参考,因为在遥远的1763年,俄国发明家I.I.为他的蒸汽机开发了水位控制系统。顺便说一句,是一种经典的调节器,顺便说一句,它实际上是如图中的两位置调节器(没有水-打开阀门,有水关闭阀门)。







它有两个位置,因为在英语文献中,它有2个位置:打开(打开)和关闭(关闭)。还有三个或更多的位置调节器,即补水阀打开或关闭到主要位置,并添加了“稍微打开”位置。将马桶中的水排干后,浮子下降,完全打开阀门,水以全压进入水箱,但在接近设定水位时,浮子上升,关闭阀门并减少水流。并且当前水位(英文PV-过程值-当前值)一上升到设定(英文SP-设定点-设定点)),阀门关闭,水位停止上升。在所描述的情况下,控制器甚至与比例控制器更相似-控制动作随着不匹配(错误)(即设置水平和当前水平之间的差异)的减小而减小。



通过稍微打开下管以排放水,可以在阀完全打开且水位不降低(即水的流入量等于水源)的情况下达到这种状态-系统进入平衡状态。但是问题在于这种状态非常不稳定-任何外部干扰都可能破坏这种平衡-假设我们可以从水箱中吸取一定量的水,然后可能发生所有水然后从水箱中流出(由于压力变化),或者补给管将被堵塞,流量将减少,或者浮子将破裂,水将溢出。这就是建筑控制系统的复杂性-实际的系统非常复杂,并且具有许多需要考虑的特征。系统具有惯性这样的特性-如果关闭加热的炉子,则它将保持很长时间,这就是为什么使用更复杂的调节器来控制温度的原因,即PID比例积分微分系统。每个组件都有自己的特性-它们在不同条件下的行为各不相同,但是当一起使用时,它们可以实现相当清晰的调节。所有这些系统都是根据公式计算的,但是在这种情况下,重要的是要了解PID控制器系数变化时系统的行为:随着比例链接的增加,初始影响会增加,因此系统将能够快速获得所需的参数。但是,如果您执行的过大,则可能会出现过冲,这可能比系统的低速运行更糟。



在TAU存在期间,发现了许多过程的数学描述,现在我们可以预测系统在某些情况下的行为。有许多仿真程序,您可以在其中设置系统参数,设置控制器参数并大致了解其中的结果。在Internet上浏览时,我遇到了一个面向工程师的Excel站点,并且有多个调节器模拟器,借助它们,您可以在更改控制因子时查看过程中的变化。当然,最容易重复的是ON-OFF控件。,即俄语中的二位调节器。让我提醒您一个工作原理:例如,如果当前过程值(过程值= PV)是温度,例如,低于设定值(SP),则调节器打开(OP)-加热元件以全功率启动。一旦温度达到设定点,调节器就会关闭加热元件的电源。



制作一个JavaScript模拟器



要构建图表,我将使用ZingChart库-事实证明它非常简单易用。文档中有许多示例,您可以为它们构建任何东西。绘制原理非常简单-有一系列值自动按顺序放置在图形上,因此从几百个点开始出现连续的过程图。顺便说一句,在Excel的原始版本中,一切都以完全相同的方式完成-生成了300个值并构建了图形。



实际上,最难的是产生值,即难以正确描述对我们的控制动作做出正确反应的过程的困难-打开加热元件-温度上升,关闭-下降以及系统的惯性必须放在这里。此外,加热环境可能会有所不同,某些介质的加热和冷却速度会更快,反之亦然,如果我们调整液位,则从上方流向相同的情况下,底部面积较小的罐中液位会升高。所有这些导致了一个事实,即过程也将取决于传输(增益)。在最初的过程中,还引入了一个延迟参数(嗯,就像系统没有立即响应控制信号一样),但我决定放弃它-两个就足够了。但是他增加了设置尽管事实证明设定值可以从零更改为100,但超过100时,过程开始表现不同,显然原因是过程公式具有通用性,并未描述特定情况。通常,让我们开始:



我们创建5个用于输入参数的字段,将所有这些都放在表格中,然后在CSS上方用漂亮的颜色绘制表格并将其放置在中心:



<table align="center" oninput="setvalues ();">
	<tr>
	<td>
Process parameters <br>
Gain: <input id="gain" type="number" value ="1" ><br>
Time Constant: <input id="time" type="number" value ="100" ><br>
	</td>
	<td>
Control parameters <br>
SetPoint(0-100): <input id="sp" type="number" value ="50"><br>
Hysteresis: <input id="hyst" type="number" value ="1">%<br>
	</td>
	<td>
Plot parameters <br>	
Points: <input id="points" type="number" value ="200"><br>
	</td>
	</tr>
</table>


如您所见,表中字段的值每次更改时,都会调用setvalues()函数。在其中,我们将每个字段的数据读入特殊变量。



	let gain = document.getElementById('gain').value;
	let time = document.getElementById('time').value;
	let sp = document.getElementById('sp').value;
	let points = document.getElementById('points').value;
	let hyst = document.getElementById('hyst').value;


如前所述,要构建图,您需要带有将要构建图的数据的数组,因此我们创建了一堆数组:



let pv = []; //    
let pv100 = []; //   *100
let op = []; //   1 , 0 
let pvp = 0; //  
let low = sp-sp*hyst/100;//  
let high = +sp+(sp*hyst/100); //   
let st=true; //  


让我解释一下磁滞现象。情况是这样的:当温度达到设定值时,加热元件将关闭,并立即(实际上不是立即,因为有惯性)立即开始冷却过程。并且已经冷却了一个度或什至是某个度的一部分-系统知道它已经超出了任务范围,因此有必要再次打开加热元件。在这种模式下,加热元件会经常打开和关闭,甚至每分钟可能会打开几次-对于设备来说,这种模式不是很好,因此,为了消除这种波动,引入了所谓的滞后现象-死区-死区-比说高1度,然后低于设定点,我们将不会做出反应,然后可以显着减少开关次数。因此,变量low是设定点的下限,变量high是上限。st变量会跟踪达到最高级别的过程,并允许该过程降至最低水平。整个过程的逻辑是循环的:



	for (var i=0;i<points;i++) {
		if (pvp<=(low/100)) {
			st=true;
			op[i]=1;
			}//
		else if (pvp<=(high/100)&& st) op[i] = 1;
		else { st=false; op[i]=0;}
		
		let a = Math.pow(2.71828182845904, -1/time);
		let b = gain*(1 -a);
		pv[i] = op[i]*b+pvp*a;
		pv100[i] = pv[i]*100;
		pvp = pv[i];
	}


结果,我们得到了一个具有给定数量点的数组,并将其发送到图表脚本。



scaleX: {
 	zooming: true
  },
      series: [
		{ values: op , text: 'OP' },
        { values: pv100 , text: 'PV'}
      ]
    };


扰流板下的完整代码
<!DOCTYPE html>
<html>
 
<head>
  <meta charset="utf-8">
  <title></title>
 
  <script src="https://cdn.zingchart.com/zingchart.min.js"></script>
  <style>
    html,
    body,
    #myChart {
      width: 100%;
      height: 100%;
    }
	input {
	width: 25%;
	text-align:center;
	}
	td {
	
	background-color: peachpuff;
	text-align: center;
	}	
  </style>
</head>
<body>
<table align="center" oninput="setvalues ();">
	<tr>
	<td>
Process parameters <br>
Gain: <input id="gain" type="number" value ="1" ><br>
Time Constant: <input id="time" type="number" value ="100" ><br>
	</td>
	<td>
Control parameters <br>
SetPoint(0-100): <input id="sp" type="number" value ="50"><br>
Hysteresis: <input id="hyst" type="number" value ="2">%<br>
	</td>
	<td>
Plot parameters <br>	
Points: <input id="points" type="number" value ="250"><br>
Animation: <input type="checkbox" id="animation">
	</td>
	</tr>
</table>

<script>

setTimeout('setvalues ()', 0);

function setvalues (){

	let gain = document.getElementById('gain').value;
	let time = document.getElementById('time').value;
	let sp = document.getElementById('sp').value;
	let points = document.getElementById('points').value;
	let hyst = document.getElementById('hyst').value;
	let anim = document.getElementById('animation').checked ? +1 : 0;
	let pv = []; //    
	let pv100 = []; //   *100
	let op = []; //   1 , 0 
	let pvp = 0; //  
	let low = sp-sp*hyst/100; //  
	let high = +sp+(sp*hyst/100); //  
	let st=true; //  
	for (var i=0;i<points;i++) {
		if (pvp<=(low/100)) {
			st=true;
			op[i]=1;
			}
		else if (pvp<=(high/100)&& st) op[i] = 1;
		else { st=false; op[i]=0;}
		
		let a = Math.pow(2.71828182845904, -1/time);
		let b = gain*(1 -a);
		pv[i] = op[i]*b+pvp*a;
		pv100[i] = pv[i]*100;
		pvp = pv[i];
	}
	
	ZC.LICENSE = ["569d52cefae586f634c54f86dc99e6a9", "b55b025e438fa8a98e32482b5f768ff5"];
    var myConfig = {
    type: "line",
    "plot": {
		"animation": {
          "effect": anim,
          "sequence": 2,
          "speed": 200,
        }
		},
	legend: {
    layout: "1x2", //row x column
    x: "20%",
    y: "5%",
	},
 	crosshairX:{
 	  plotLabel:{
 	    text: "%v"
 	  }
 	},
      "scale-y": {
    item: {
      fontColor: "#7CA82B"
    },
    markers: [
	 {
        type: "area",
        range: [low, high],
        backgroundColor: "#d89108",
        alpha: 0.7
      },
	{
        type: "line",
        range: [sp],
        lineColor: "#7CA82B",
        lineWidth: 2,
		  label: { //define label within marker
          text: "SP = "+sp,
          backgroundColor: "white",
          alpha: 0.7,
          textAlpha: 1,
          offsetX: 60,
          offsetY: -5
        }
      }]
	},	
	scaleX: {
		zooming: true
	},
	  'scale-y-2': {
	  values: "0:1"
	},
      series: [
		{ scales: "scale-x,scale-y-2", values: op , 'legend-text': 'OP' },
        { values: pv100 , text: 'PV'}
      ]
    };
 
    zingchart.render({
      id: 'myChart',
      data: myConfig,
      height: "90%",
      width: "100%"
    });
}


</script>
  <div id='myChart'></div>
</body> 
</html>




好了,既然模拟器已经准备好了,是时候检查一下它如何工作了。您可以在github测试相同的代码,但需在github上进行测试:开关控制模拟器



标准设置:放大链接1,时间常数100秒,滞后2%







现在,如果您设置较大的设置,例如92,则设置为50的过程会突然变慢很多它在相同的71秒内获得增益,但只有到那时曲线才开始以指数形式缓慢地接近任务,并仅在278秒内达到设定点,这就是为什么必须将绘图范围扩大到300点







该示例非常具有指导意义,将情况转换为带有温度的模型,我们可以得出结论,加热器功率不足:加热器100%加载,但温度在特定时刻后停止升高。可能有几种解决方案:放置相同的第二个加热元件,或对其施加2倍以上的电压(但这可能会损坏加热元件),或者将功率增加2倍的加热器,或者在加热时将更多导热液体倒入系统中液体。很有意思的是,如果您需要将温度保持在95-100度左右,那么您甚至不需要安装调节器-放一个这样的低功率加热器,将其调到最大,就可以了-300秒(有条件的300秒)后,您可以得到所需的100度。这样的系统的问题是,如果您在冬天以负40的速度打开窗户,那么温度将立即下降并且非常显着,并且这样的系统的速度非常低。



让我们将增益部分增加2倍-就像安装第二个相同类型的加热元件,或添加另一条管道来补充水箱。







该图也具有指示性-温度达到51度实际上快2倍,但达到92度快4倍。我不知道这样的模拟器离实际过程有多近,但是由于其中指定的依赖性是指数级的,因此这是系统完全可以预期的行为,但是我什至无法想象从添加第二条管道并将填充率提高4倍的角度进行解释。线性函数的响应对于系数的增加更可预测,但是生活中的实际系统很少是线性的。



All Articles