<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>专业DCS-PID仿真系统</title>
<style>
body { font-family: "SimHei", "Microsoft YaHei", sans-serif; margin: 0; padding: 8px; background: #e0e0e0; font-size: 12px; }
.container { max-width: 1350px; margin: 0 auto; background: white; padding: 12px; border: 2px solid #333; box-shadow: 0 0 10px rgba(0,0,0,0.3); }
h1 { text-align: center; font-size: 20px; margin-bottom: 10px; color: #000; background: #b0c4de; padding: 8px; border: 1px solid #333; }
.main { display: flex; gap: 12px; }
.left { width: 400px; min-width: 400px; }
.right { flex: 1; border: 2px solid #333; background: #f5f5f5; position: relative; }
.panel { border: 1px solid #666; margin-bottom: 8px; background: #fafafa; }
.panel-title { font-weight: bold; background: #d3d3d3; padding: 6px; border-bottom: 1px solid #666; font-size: 13px; color: #000; }
.panel-body { padding: 8px; }
.row { margin-bottom: 6px; display: flex; align-items: center; min-height: 28px; }
.row label { width: 130px; font-size: 11px; font-weight: bold; color: #333; }
.unit-label { font-size: 10px; color: #666; margin-left: 3px; font-weight: normal; }
input[type="range"] { width: 120px; vertical-align: middle; }
input[type="number"] { width: 60px; padding: 3px; border: 1px solid #666; }
select { width: 180px; padding: 3px; border: 1px solid #666; font-size: 11px; }
.val { display: inline-block; width: 45px; text-align: center; background: #fffacd; border: 1px solid #999; margin-left: 5px; font-size: 11px; color: #c00; font-weight: bold; font-family: monospace; }
.hidden { display: none !important; }
button { padding: 8px 18px; margin: 4px; cursor: pointer; font-weight: bold; font-size: 12px; border: 2px solid #333; }
#btnStart { background: #32cd32; }
#btnPause { background: #ffd700; }
#btnReset { background: #ff6347; color: white; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
canvas { display: block; width: 100%; height: 500px; background: white; }
.legend { text-align: center; margin: 8px 0; font-size: 12px; font-weight: bold; background: #e0e0e0; padding: 5px; border-top: 1px solid #666; }
.legend span { margin: 0 20px; }
.status { padding: 8px; background: #f0f0f0; border-top: 2px solid #333; font-family: "Courier New", monospace; font-size: 13px; font-weight: bold; text-align: center; }
.dcs-info { background: #e6f3ff; border: 1px solid #0066cc; padding: 6px; margin-bottom: 8px; font-size: 11px; color: #004080; }
.algo-desc { font-size: 10px; color: #666; margin-top: 2px; font-style: italic; }
.warning { color: #ff6600; font-size: 10px; margin-left: 5px; }
</style>
<div>
<h1>多DCS-PID控制回路仿真系统 (工业级算法)</h1>
<div>
<div>
<div>
<div>1. DCS系统选择 (决定算法与单位)</div>
<div>
<div>
<label>选择DCS类型:</label>
<select onchange="changeDCS()">
<option value="isa">ISA标准 (理想型)</option>
<option value="emerson">艾默生 DeltaV (非交互式)</option>
<option value="honeywell">霍尼韦尔 TDC3000 (比例度%)</option>
<option value="yokogawa">横河 CENTUM (微分先行)</option>
<option value="siemens">西门子 PCS7 (并行算法)</option>
<option value="hollysys">和利时 MACS (理想型)</option>
<option value="supcon">中控 ECS (理想型)</option>
</select>
</div>
<div>
<strong>当前算法:</strong>位置式PID,标准ISA形式<br>
<strong>单位:</strong>Kp(无量纲增益), Ti(秒), Td(秒)<br>
<strong>公式:</strong>MV = Kp×[e + (1/Ti)∫e + Td×de/dt]
</div>
</div>
</div>
<div>
<div>2. 被控对象模型</div>
<div>
<div>
<label>对象类型:</label>
<select onchange="changeObj()">
<option value="fopdt">一阶惯性+纯滞后</option>
<option value="int">纯积分(液位)</option>
<option value="sopdt">二阶惯性+纯滞后</option>
</select>
</div>
<div>
<div>
<label>增益 K:</label>
<input type="range" min="0.1" max="10" step="0.1" value="1.5" oninput="updateVal('k','v-k')">
<span>1.5</span>
</div>
<div>
<label>时间常数 τ:</label>
<input type="range" min="1" max="100" step="1" value="20" oninput="updateVal('tau','v-tau')">
<span>20</span>
<span>秒</span>
</div>
<div>
<label>滞后时间 θ:</label>
<input type="range" min="0" max="30" step="0.5" value="3" oninput="updateVal('theta','v-theta')">
<span>3</span>
<span>秒</span>
</div>
</div>
<div>
<div>
<label>积分增益 K:</label>
<input type="range" min="0.01" max="1" step="0.01" value="0.05" oninput="updateVal('ki','v-ki')">
<span>0.05</span>
</div>
</div>
<div>
<div>
<label>增益 K:</label>
<input type="range" min="0.1" max="10" step="0.1" value="1" oninput="updateVal('k2','v-k2')">
<span>1.0</span>
</div>
<div>
<label>滞后 θ:</label>
<input type="range" min="0" max="30" step="0.5" value="2" oninput="updateVal('t2','v-t2')">
<span>2</span>
</div>
<div>
<label>时间常数1 τ1:</label>
<input type="range" min="1" max="50" step="1" value="8" oninput="updateVal('tau1','v-t1')">
<span>8</span>
</div>
<div>
<label>时间常数2 τ2:</label>
<input type="range" min="1" max="50" step="1" value="12" oninput="updateVal('tau2','v-t2')">
<span>12</span>
</div>
</div>
</div>
</div>
<div>
<div>3. PID参数整定 (随DCS类型自动切换单位)</div>
<div>
<div>
<label>比例增益 Kp:</label>
<input type="range" min="0.1" max="100" step="0.1" value="2" oninput="updateVal('pVal','v-p')">
<span>2.0</span>
<span>无量纲</span>
</div>
<div>
<label>积分时间 Ti:</label>
<input type="range" min="1" max="100" step="1" value="30" oninput="updateVal('iVal','v-i')">
<span>30</span>
<span>秒</span>
<select onchange="changeDCS()">
<option value="sec">秒</option>
<option value="min">分</option>
</select>
</div>
<div>
<label>微分时间 Td:</label>
<input type="range" min="0" max="20" step="0.5" value="0" oninput="updateVal('dVal','v-d')">
<span>0</span>
<span>秒</span>
</div>
<div>
<label>设定值 SP:</label>
<input type="number" value="50" min="0" max="100">
<span>%</span>
</div>
<div></div>
</div>
</div>
<div>
<div>4. 干扰设置</div>
<div>
<div>
<label>干扰类型:</label>
<select onchange="changeDist()">
<option value="none">无干扰</option>
<option value="step">阶跃干扰</option>
<option value="sine">正弦干扰</option>
<option value="square">方波干扰</option>
</select>
</div>
<div>
<div>
<label>干扰幅度:</label>
<input type="number" value="10">
<span>%</span>
</div>
<div>
<label>施加时间:</label>
<input type="number" value="60">
<span>秒</span>
</div>
<div>
<label>周期:</label>
<input type="range" min="5" max="100" value="30" oninput="updateVal('dPer','v-per')">
<span>30</span>
<span>秒</span>
</div>
</div>
</div>
</div>
<div>
<div>5. 仿真设置</div>
<div>
<div>
<label>仿真速度:</label>
<select>
<option value="0.2">慢速(0.2x)</option>
<option value="0.5" selected="">中速(0.5x)</option>
<option value="1">正常(1x)</option>
<option value="2">快速(2x)</option>
</select>
</div>
<div>
<label>总时长:</label>
<input type="number" value="200" min="50" max="500" step="10">
<span>秒</span>
</div>
</div>
</div>
<div>
<button onclick="startSim()">开始仿真</button>
<button onclick="pauseSim()">暂停</button>
<button onclick="resetSim()">重置系统</button>
</div>
</div>
<div>
<canvas width="850" height="500"></canvas>
<div>
<span>— PV(过程值)</span>
<span>— SP(设定值)</span>
<span>— MV(阀位输出)</span>
</div>
<div>
<span>就绪</span> |
时间: <span>0.0</span>s |
PV: <span>0.00</span>% |
MV: <span>0.00</span>% |
偏差: <span>0.00</span>%
</div>
</div>
</div>
</div>
<script>
var cv, cx, tm=null, run=false, pausef=false, ct=0, lmv=0, lerr=0;
var dt={t:[],pv:[],sp:[],mv:[]};
var ps={y:0,y1:0,i:0,le:0,lpv:0,db:[]};
var DCS_CONFIG = {
isa: {
name: "ISA标准",
pName: "比例增益 Kp",
pUnit: "无量纲",
iName: "积分时间 Ti",
iUnit: "sec",
dName: "微分时间 Td",
dUnit: "秒",
algo: "ideal",
desc: "位置式PID,标准形式: MV = Kp×[e + (1/Ti)∫e·dt + Td×de/dt]",
note: "Ti越大积分作用越弱,经典教科书算法"
},
emerson: {
name: "艾默生 DeltaV",
pName: "比例增益 K",
pUnit: "无量纲",
iName: "复位时间 Reset",
iUnit: "sec",
dName: "速率时间 Rate",
dUnit: "秒",
algo: "noninteractive",
desc: "非交互式算法: 比例、积分、微分独立计算后叠加",
note: "三种作用互不干扰,现代DCS常用"
},
honeywell: {
name: "霍尼韦尔 TDC3000",
pName: "比例度 PB",
pUnit: "%",
iName: "积分时间",
iUnit: "min",
dName: "微分时间",
dUnit: "分",
algo: "pb",
desc: "PB=100/Kp,Ti单位为分(重复/分),传统仪表习惯",
note: "PB越小控制作用越强!Ti越小积分越快!"
},
yokogawa: {
name: "横河 CENTUM",
pName: "比例带 P",
pUnit: "%",
iName: "积分时间 I",
iUnit: "sec",
dName: "微分时间 D",
dUnit: "秒",
algo: "derivative_first",
desc: "PI-D算法: 微分仅作用于测量值,设定值变化无冲击",
note: "SV突变时MV无跳变,适合频繁改设定值的场合"
},
siemens: {
name: "西门子 PCS7",
pName: "增益 Gain",
pUnit: "无量纲",
iName: "积分时间 Ti",
iUnit: "sec",
dName: "微分时间 Td",
dUnit: "秒",
algo: "parallel",
desc: "并行算法: P,I,D分别计算: MV = Kp×e + (Kp/Ti)∫e + Kp×Td×de/dt",
note: "与ISA数学等价,但参数整定思路不同"
},
hollysys: {
name: "和利时 MACS",
pName: "比例系数 Kp",
pUnit: "无量纲",
iName: "积分时间 Ti",
iUnit: "sec",
dName: "微分时间 Td",
dUnit: "秒",
algo: "ideal",
desc: "国产DCS,采用ISA标准算法",
note: "与ISA算法完全一致"
},
supcon: {
name: "中控 ECS",
pName: "比例增益",
pUnit: "无量纲",
iName: "积分时间",
iUnit: "sec",
dName: "微分时间",
dUnit: "秒",
algo: "ideal",
desc: "国产DCS,采用ISA标准算法",
note: "与ISA算法完全一致"
}
};
window.onload=function(){
cv=document.getElementById('trendCanvas');
cx=cv.getContext('2d');
resize();
window.onresize=resize;
changeDCS();
drawChart();
};
function resize(){
var r=cv.parentNode.getBoundingClientRect();
cv.width=r.width-4;
cv.height=500;
}
function updateVal(id,vid){
document.getElementById(vid).innerHTML=document.getElementById(id).value;
}
function changeDCS(){
var type=document.getElementById('dcsType').value;
var cfg=DCS_CONFIG[type];
document.getElementById('lblP').innerHTML=cfg.pName+":";
document.getElementById('unitP').innerHTML=cfg.pUnit;
document.getElementById('lblI').innerHTML=cfg.iName+":";
document.getElementById('lblD').innerHTML=cfg.dName+":";
document.getElementById('unitD').innerHTML=cfg.dUnit;
var iUnitSelect=document.getElementById('iUnit');
if(cfg.iUnit==='min'){
iUnitSelect.value='min';
iUnitSelect.disabled=true;
document.getElementById('unitI').innerHTML='分';
document.getElementById('iVal').max=30;
document.getElementById('iVal').step=0.1;
}else{
iUnitSelect.disabled=false;
document.getElementById('unitI').innerHTML=iUnitSelect.value==='min'?'分':'秒';
document.getElementById('iVal').max=100;
document.getElementById('iVal').step=1;
}
document.getElementById('dcsDesc').innerHTML=
'<strong>当前算法:</strong>'+cfg.desc+'<br>'+
'<strong>单位:</strong>'+cfg.pName.split(' ')[0]+'('+(cfg.pUnit==='%'?'比例度':'增益')+'), '+cfg.iName+(cfg.iUnit==='min'?'(分)':'(秒)')+'<br>'+
'<strong>注意:</strong>'+cfg.note;
document.getElementById('algoNote').innerHTML='算法特性: '+cfg.note;
if(cfg.algo==='pb'){
document.getElementById('pVal').min=1;
document.getElementById('pVal').max=500;
document.getElementById('pVal').step=1;
if(parseFloat(document.getElementById('pVal').value)<1){
document.getElementById('pVal').value=50;
updateVal('pVal','v-p');
}
}else{
document.getElementById('pVal').min=0.1;
document.getElementById('pVal').max=20;
document.getElementById('pVal').step=0.1;
}
}
function changeObj(){
var t=document.getElementById('objType').value;
document.getElementById('par-fopdt').className='hidden';
document.getElementById('par-int').className='hidden';
document.getElementById('par-sopdt').className='hidden';
document.getElementById('par-'+t).className='';
}
function changeDist(){
var t=document.getElementById('distType').value;
var p=document.getElementById('dist-par');
var tr=document.getElementById('d-time-row');
var pr=document.getElementById('d-period-row');
if(t=='none'){p.className='hidden';}
else{
p.className='';
if(t=='step'){tr.className='row';pr.className='row hidden';}
else{tr.className='row hidden';pr.className='row';}
}
}
function getDist(t){
var ty=document.getElementById('distType').value;
var a=parseFloat(document.getElementById('dAmp').value)||0;
if(ty=='none')return 0;
if(ty=='step'){
var st=parseFloat(document.getElementById('dTime').value)||0;
return t>=st?a:0;
}
if(ty=='sine'){
var p=parseFloat(document.getElementById('dPer').value)||20;
return a*Math.sin(6.283185*t/p);
}
if(ty=='square'){
var p=parseFloat(document.getElementById('dPer').value)||20;
return ((t%p)<(p/2))?a:-a;
}
return 0;
}
function calcPID(sp,pv,dt){
var dcsType=document.getElementById('dcsType').value;
var cfg=DCS_CONFIG[dcsType];
var pRaw=parseFloat(document.getElementById('pVal').value)||1;
var iRaw=parseFloat(document.getElementById('iVal').value)||10;
var dRaw=parseFloat(document.getElementById('dVal').value)||0;
var kp, ti, td;
if(cfg.algo==='pb'){
kp=100/pRaw;
ti=iRaw*60;
td=dRaw*60;
}else{
kp=pRaw;
var iu=document.getElementById('iUnit').value;
if(iu==='min'||cfg.iUnit==='min'){
ti=iRaw*60;
td=dRaw*60;
}else{
ti=iRaw;
td=dRaw;
}
}
if(ti<0.1)ti=0.1;
var e=sp-pv;
lerr=e;
var out=0;
var dErr=(e-ps.le)/dt;
switch(cfg.algo){
case 'ideal':
case 'pb':
ps.i+=e*dt;
out=kp*(e+ps.i/ti+td*dErr);
break;
case 'noninteractive':
ps.i+=e*dt;
var pTerm=kp*e;
var iTerm=(kp/ti)*ps.i;
var dTerm=kp*td*dErr;
out=pTerm+iTerm+dTerm;
break;
case 'derivative_first':
ps.i+=e*dt;
var dPV=-(pv-ps.lpv)/dt;
out=kp*(e+ps.i/ti+td*dPV);
ps.lpv=pv;
break;
case 'parallel':
ps.i+=e*dt;
out=kp*e+(kp/ti)*ps.i+kp*td*dErr;
break;
default:
out=kp*e;
}
ps.le=e;
if(out>100){
out=100;
if(e>0)ps.i-=e*dt;
}else if(out<0){
out=0;
if(e<0)ps.i-=e*dt;
}
return out;
}
function calcProc(mv,dt,dist){
var ty=document.getElementById('objType').value,o=0;
if(ty=='fopdt'){
var k=parseFloat(document.getElementById('k').value)||1;
var ta=parseFloat(document.getElementById('tau').value)||10;
var th=parseFloat(document.getElementById('theta').value)||0;
var s=Math.round(th/dt);if(s<0)s=0;
ps.db.push(mv);
if(ps.db.length>s){
var dm=ps.db.shift();
var dy=(k*dm-ps.y)/ta*dt;
ps.y+=dy;
}
o=ps.y;
}else if(ty=='int'){
var k=parseFloat(document.getElementById('ki').value)||0.1;
ps.y+=k*mv*dt;
if(ps.y>100)ps.y=100;if(ps.y<0)ps.y=0;
o=ps.y;
}else if(ty=='sopdt'){
var k=parseFloat(document.getElementById('k2').value)||1;
var t1=parseFloat(document.getElementById('tau1').value)||5;
var t2=parseFloat(document.getElementById('tau2').value)||5;
var th=parseFloat(document.getElementById('t2').value)||0;
var s=Math.round(th/dt);if(s<0)s=0;
ps.db.push(mv);
if(ps.db.length>s){
var dm=ps.db.shift();
var dy1=(dm-ps.y1)/t1*dt;ps.y1+=dy1;
var dy=(k*ps.y1-ps.y)/t2*dt;ps.y+=dy;
}
o=ps.y;
}
o+=dist;
if(o>100)o=100;if(o<0)o=0;
return o;
}
function drawChart(){
var w=cv.width,h=cv.height;
var p={t:25,r:60,b:35,l:60};
cx.fillStyle='white';
cx.fillRect(0,0,w,h);
if(dt.t.length===0){
drawAxes(w,h,p,0,parseFloat(document.getElementById('tMax').value)||200);
return;
}
var tm=parseFloat(document.getElementById('tMax').value)||200;
drawAxes(w,h,p,0,tm);
var gw=w-p.l-p.r,gh=h-p.t-p.b;
drawLine(dt.t,dt.pv,p,gw,gh,'#ff0000',2.5);
drawLine(dt.t,dt.sp,p,gw,gh,'#0000ff',2,true);
drawLine(dt.t,dt.mv,p,gw,gh,'#008000',2);
}
function drawAxes(w,h,p,tmin,tmax){
cx.strokeStyle='#333';cx.lineWidth=1;
cx.beginPath();cx.moveTo(p.l,p.t);cx.lineTo(p.l,h-p.b);cx.stroke();
cx.beginPath();cx.moveTo(p.l,h-p.b);cx.lineTo(w-p.r,h-p.b);cx.stroke();
cx.beginPath();cx.moveTo(w-p.r,p.t);cx.lineTo(w-p.r,h-p.b);cx.stroke();
cx.fillStyle='#000';cx.font='11px Arial';cx.textAlign='right';
for(var i=0;i<=100;i+=20){
var y=(h-p.b)-(i/100)*(h-p.t-p.b);
cx.fillText(i,p.l-5,y+4);
cx.beginPath();cx.moveTo(p.l-3,y);cx.lineTo(p.l,y);cx.stroke();
}
cx.textAlign='left';
for(var i=0;i<=100;i+=20){
var y=(h-p.b)-(i/100)*(h-p.t-p.b);
cx.fillText(i,w-p.r+5,y+4);
}
cx.textAlign='center';
var st=Math.ceil((tmax-tmin)/10);
for(var t=0;t<=tmax;t+=st){
var x=p.l+(t/tmax)*(w-p.l-p.r);
cx.fillText(t,x,h-p.b+15);
cx.beginPath();cx.moveTo(x,h-p.b);cx.lineTo(x,h-p.b+5);cx.stroke();
}
cx.fillText('时间 (秒)',w/2,h-5);
cx.save();cx.translate(15,h/2);cx.rotate(-Math.PI/2);cx.textAlign='center';
cx.fillText('PV / SP (%)',0,0);cx.restore();
cx.save();cx.translate(w-15,h/2);cx.rotate(Math.PI/2);cx.textAlign='center';
cx.fillText('MV (%)',0,0);cx.restore();
}
function drawLine(ta,va,p,gw,gh,color,width,dash){
if(ta.length<2)return;
var tm=parseFloat(document.getElementById('tMax').value)||200;
cx.strokeStyle=color;cx.lineWidth=width;
if(dash)cx.setLineDash([6,4]);else cx.setLineDash([]);
cx.beginPath();
for(var i=0;i<ta.length;i++){
var t=parseFloat(ta
),v=parseFloat(va); var x=p.l+(t/tm)*gw;
var y=(p.t+gh)-(v/100)*gh;
if(i===0)cx.moveTo(x,y);else cx.lineTo(x,y);
}
cx.stroke();cx.setLineDash([]);
}
function simStep(){
try{
var dt=0.5;
var spd=parseFloat(document.getElementById('simSpeed').value)||0.5;
var tm=parseFloat(document.getElementById('tMax').value)||200;
var sp=parseFloat(document.getElementById('sp').value)||50;
var st=Math.max(1,Math.round(spd));
for(var i=0;i<st;i++){
if(ct>=tm){stop();return;}
var d=getDist(ct);
var pv=ps.y;
var mv=calcPID(sp,pv,dt);
lmv=mv;
var npv=calcProc(mv,dt,d);
if(Math.abs(ct%1)<0.1||ct===0){
dt.t.push(ct.toFixed(1));
dt.pv.push(npv);
dt.sp.push(sp);
dt.mv.push(mv);
if(dt.t.length>1200){dt.t.shift();dt.pv.shift();dt.sp.shift();dt.mv.shift();}
}
ct+=dt;
}
drawChart();
document.getElementById('ct').innerHTML=ct.toFixed(1);
document.getElementById('cpv').innerHTML=ps.y.toFixed(2);
document.getElementById('cmv').innerHTML=lmv.toFixed(2);
document.getElementById('cerr').innerHTML=lerr.toFixed(2);
}catch(e){
alert('仿真错误:'+e.message);
stop();
}
}
function startSim(){
if(run&&!pausef)return;
if(!run){dt={t:[],pv:[],sp:[],mv:[]};run=true;}
pausef=false;
document.getElementById('st').innerHTML='运行中';
document.getElementById('btnStart').disabled=true;
if(tm)clearInterval(tm);
tm=setInterval(simStep,100);
}
function pauseSim(){
if(!run)return;
pausef=!pausef;
if(pausef){
clearInterval(tm);tm=null;
document.getElementById('st').innerHTML='已暂停';
document.getElementById('btnStart').disabled=false;
}else startSim();
}
function stop(){
if(tm)clearInterval(tm);tm=null;
run=false;pausef=false;
document.getElementById('st').innerHTML='已完成';
document.getElementById('btnStart').disabled=false;
}
function resetSim(){
stop();ct=0;lmv=0;lerr=0;
ps={y:0,y1:0,i:0,le:0,lpv:0,db:[]};
dt={t:[],pv:[],sp:[],mv:[]};
drawChart();
document.getElementById('st').innerHTML='就绪';
document.getElementById('ct').innerHTML='0.0';
document.getElementById('cpv').innerHTML='0.00';
document.getElementById('cmv').innerHTML='0.00';
document.getElementById('cerr').innerHTML='0.00';
}
</script>