1335 lines
55 KiB
C#
1335 lines
55 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Aitex.Core.Common;
|
||
using Aitex.Core.Common.DeviceData.IoDevice;
|
||
using Aitex.Core.RT.DataCenter;
|
||
using Aitex.Core.RT.Device;
|
||
using Aitex.Core.RT.Device.Devices;
|
||
using Aitex.Core.RT.Device.PmDevices;
|
||
using Aitex.Core.RT.Event;
|
||
using Aitex.Core.RT.Log;
|
||
using Aitex.Core.RT.OperationCenter;
|
||
using Aitex.Core.RT.Routine;
|
||
using Aitex.Core.RT.SCCore;
|
||
using Aitex.Core.RT.Tolerance;
|
||
using Aitex.Core.Util;
|
||
using MECF.Framework.Common.DBCore;
|
||
using MECF.Framework.Common.Equipment;
|
||
using MECF.Framework.Common.Gem;
|
||
using MECF.Framework.Common.SubstrateTrackings;
|
||
using SicModules.PMs.Routines.Base;
|
||
using MECF.Framework.Common.MECF.Framework.Common.RecipeCenter.Recipe;
|
||
using static Aitex.Core.RT.Device.PmDevices.DicMode;
|
||
|
||
namespace SicModules.PMs.RecipeExecutions
|
||
{
|
||
public enum RecipeContinueMode
|
||
{
|
||
None,
|
||
WaferReturnAndJobStop,
|
||
RecipeCompleted,
|
||
StepContinue,
|
||
StepRestart,
|
||
RecipeRestart,
|
||
NextStep,
|
||
}
|
||
|
||
public partial class Process : PMBaseRoutine
|
||
{
|
||
enum RoutineStep
|
||
{
|
||
WaitProcess,
|
||
}
|
||
|
||
enum RecipeRunningState
|
||
{
|
||
Error,
|
||
RecipeCompleted,
|
||
ExecStep,
|
||
TimeWait,
|
||
ConditionWait,
|
||
StepCompleted,
|
||
Paused,
|
||
}
|
||
|
||
private object _recipeLocker = new object();
|
||
private bool _hasRecordRunTime = false;
|
||
|
||
private RecipeRunningState _state = RecipeRunningState.ExecStep;
|
||
private RecipeRunningState _pausedState = RecipeRunningState.ExecStep;
|
||
private DeviceTimer _estimatedTimeCalcTimer = new DeviceTimer(); //用于定时计算工艺程序估计的结束时间
|
||
|
||
private double _curStepElpasedTimeBeforePaused;
|
||
|
||
private double _curStepElpasedTimeBeforePaused2;
|
||
private List<int> _lstSkipSteps = new List<int>();
|
||
|
||
public RecipeContinueMode ContinueAction { get; set; }
|
||
|
||
public DateTime _recipeStartTime { get; private set; }
|
||
|
||
public string CurrentRecipeContent { get; private set; }
|
||
|
||
|
||
private int _currentStepNumber;
|
||
|
||
private int _dummyStepCount;
|
||
|
||
public int CurStepTotalLoopCount { get; private set; }
|
||
|
||
public double CurStepTotalTime
|
||
{
|
||
get
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList == null ||
|
||
PmDevice.RecipeRunningInfo.RecipeStepList.Count == 0 ||
|
||
_state == RecipeRunningState.RecipeCompleted || _state == RecipeRunningState.Error)
|
||
return 0;
|
||
return PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime * 1000;
|
||
}
|
||
}
|
||
|
||
|
||
public int CurrentLoopCount { get; private set; }
|
||
|
||
private DeviceTimer _stepTimer = new DeviceTimer();
|
||
private DeviceTimer _stepTimer2 = new DeviceTimer();
|
||
private DeviceTimer _recipeTimer = new DeviceTimer();
|
||
|
||
public bool IsPaused { private set; get; }
|
||
|
||
|
||
private double CurStepLeftTime
|
||
{
|
||
get { return _stepTimer.GetTotalTime() - _stepTimer.GetElapseTime(); }
|
||
}
|
||
|
||
public double EstimatedTotalLeftTime { get; private set; }
|
||
|
||
private RecipeDBCallback _dbCallback;
|
||
private Fdc _fdc;
|
||
|
||
private bool _isDryRun;
|
||
private int _delayTimeDryRun;
|
||
|
||
private double _tempOffset;
|
||
|
||
private int _currentStepIndex = 99;
|
||
private IoInterLock _pmInterLock;
|
||
|
||
private SicServo _pmSicServo = null;
|
||
|
||
private bool bAlarm = false; //True-Alarm, False-Warning
|
||
private double fThreshold = 3.0f; //阈值,从配置文件里取
|
||
private int iTimes = -1; //超限次数,设定值
|
||
private int iT = 0; //超限次数计数,实际值
|
||
|
||
private double _pressureDifferenceUpperLimit;
|
||
|
||
#region Parse
|
||
|
||
private bool _isPSUHeaterJumpMode;
|
||
private bool _isSCRHeaterJumpMode;
|
||
private bool _isMFCJumpMode;
|
||
|
||
#endregion
|
||
|
||
|
||
#region Check
|
||
|
||
private PeriodicJob _thread;
|
||
private PeriodicJob _threadRotationAlarm;
|
||
|
||
private DeviceTimer _rampCalcTimer = new DeviceTimer(); //用于定时获取PC的Ramp
|
||
|
||
protected ToleranceChecker[] _mfcGapChecker = new ToleranceChecker[32];
|
||
protected R_TRIG[] _mfcTrig = new R_TRIG[32];
|
||
|
||
protected ToleranceChecker[] _pcGapChecker = new ToleranceChecker[7];
|
||
protected R_TRIG[] _pcTrig = new R_TRIG[7];
|
||
protected R_TRIG[] _pcTrig2 = new R_TRIG[7];
|
||
|
||
protected double[] _pressurePrevious = new double[7];
|
||
|
||
#endregion
|
||
|
||
public Process(ModuleName module, PMModule pm1) : base(module, pm1)
|
||
{
|
||
Module = module.ToString();
|
||
Name = "Process";
|
||
|
||
_dbCallback = new RecipeDBCallback();
|
||
_fdc = new Fdc(Module);
|
||
_pmInterLock = DEVICE.GetDevice<IoInterLock>($"{Module}.PMInterLock");
|
||
this._pmSicServo = DEVICE.GetDevice<SicServo>($"{Module}.PMServo");
|
||
|
||
if (this._pmSicServo != null)
|
||
{
|
||
this.bAlarm = SC.GetValue<bool>($"PM.{Module}.RotationAlarm.WarningOrAlarm");
|
||
this.fThreshold = SC.GetValue<double>($"PM.{Module}.RotationAlarm.Threshold");
|
||
this.iTimes = SC.GetValue<int>($"PM.{Module}.RotationAlarm.Times");
|
||
}
|
||
|
||
|
||
for (int i = 0; i < _mfcGapChecker.Length; i++)
|
||
{
|
||
_mfcGapChecker[i] = new ToleranceChecker();
|
||
_mfcTrig[i] = new R_TRIG();
|
||
}
|
||
|
||
for (int i = 0; i < _pcGapChecker.Length; i++)
|
||
{
|
||
_pcGapChecker[i] = new ToleranceChecker();
|
||
_pcTrig[i] = new R_TRIG();
|
||
_pcTrig2[i] = new R_TRIG();
|
||
}
|
||
|
||
|
||
Initialize();
|
||
|
||
Calculte();
|
||
|
||
SC.RegisterValueChangedCallback($"PM.{Module}.PT1PT2PressureDifferenceUpperLimit", (obj) => { _pressureDifferenceUpperLimit = (double)obj; });
|
||
|
||
_thread = new PeriodicJob(10 * 1000, Calculte, "Calculte Standard Deviation", false);
|
||
_threadRotationAlarm =
|
||
new PeriodicJob(1000, CalculteRotationAlarm, "Calculte Rotation Alarm Deviation", false);
|
||
}
|
||
|
||
public override Result Start(params object[] param)
|
||
{
|
||
Reset();
|
||
_hasRecordRunTime = false;
|
||
if (!_pmInterLock.SetPMProcessRunning(true, out string reason))
|
||
{
|
||
EV.PostAlarmLog(Module, $"can not run Process, {reason}");
|
||
return Result.FAIL;
|
||
}
|
||
|
||
_stepTimer = new DeviceTimer();
|
||
_curStepElpasedTimeBeforePaused = 0;
|
||
//_stepTimer2 = new DeviceTimer();
|
||
//_recipeTimer = new DeviceTimer();
|
||
|
||
//_stepTimer.Start(0);
|
||
|
||
_lstSkipSteps = new List<int>();
|
||
|
||
_currentStepIndex = 99;
|
||
_currentStepNumber = CurStepTotalLoopCount = 0;
|
||
_dummyStepCount = 0;
|
||
|
||
_estimatedTimeCalcTimer.Start(1000);
|
||
_rampCalcTimer.Start(1000);
|
||
|
||
PmDevice.RecipeRunningInfo.InnerId = Guid.NewGuid();
|
||
PmDevice.RecipeRunningInfo.BeginTime = DateTime.Now;
|
||
PmDevice.RecipeRunningInfo.TotalTime = CalcRecipeTime();
|
||
PmDevice.RecipeRunningInfo.IsRoutineAbort = false;
|
||
|
||
PmDevice.RecipeRunningInfo.StepNumber = 1;
|
||
PmDevice.RecipeRunningInfo.StepName =
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepName;
|
||
|
||
_state = RecipeRunningState.ExecStep;
|
||
|
||
_recipeTimer.Start(int.MaxValue);
|
||
|
||
_dbCallback.RecipeStart(PmDevice.Module, 0, PmDevice.RecipeRunningInfo.InnerId.ToString(),
|
||
PmDevice.RecipeRunningInfo.RecipeName);
|
||
_dbCallback.RecipeUpdateStatus(PmDevice.RecipeRunningInfo.InnerId.ToString(), "InProcess");
|
||
|
||
//手动工艺时LotID和SequenceID可能是string.Empty
|
||
//补全路径
|
||
string recipeID = PmDevice.RecipeRunningInfo.RecipeName + ".rcp";
|
||
string squenceID = $"Sequence\\{PmDevice.GetWaferSequenceID()}.seq";
|
||
|
||
GemManager.Instance.Equipment?.TriggerEvent("PMRecipeStart", new string[] { "ChamberID", "RecipeID", "LotID", "SequenceID" }, new object[] { PmDevice.Module, recipeID, PmDevice.GetWaferLotID(), squenceID });
|
||
|
||
WaferManager.Instance.UpdateWaferProcessStatus(ModuleHelper.Converter(Module), 0,
|
||
WaferProcessStatus.InProcess);
|
||
WaferManager.Instance.GetWafer(ModuleHelper.Converter(Module), 0).TrayProcessCount--;
|
||
|
||
_fdc.Reset();
|
||
|
||
_isDryRun = SC.GetValue<bool>($"PM.{Module}.DryRun.IsDryRun");
|
||
_delayTimeDryRun = SC.GetValue<int>($"PM.{Module}.DryRun.DryRunDelayTime");
|
||
_tempOffset = SC.GetValue<double>($"PM.{Module}.Process.TempOffset");
|
||
|
||
_pressureDifferenceUpperLimit = SC.GetValue<double>($"PM.{Module}.PT1PT2PressureDifferenceUpperLimit");
|
||
|
||
ResetHeaterResCheckResult();
|
||
|
||
_thread.Start();
|
||
_threadRotationAlarm.Start();
|
||
|
||
Notify($"Start");
|
||
return Result.RUN;
|
||
}
|
||
|
||
#region Heater Resistance Monitor
|
||
|
||
private readonly R_TRIG _trigHeaterResOutOfRange = new();
|
||
private readonly R_TRIG _trigHeaterResMonitorBegin = new();
|
||
private readonly R_TRIG _trigHeaterResMonitorEnd = new();
|
||
private readonly R_TRIG _trigPressureDifference = new();
|
||
|
||
|
||
private void ResetHeaterResCheckResult()
|
||
{
|
||
_trigHeaterResOutOfRange.RST = true;
|
||
_trigHeaterResMonitorBegin.RST = true;
|
||
_trigHeaterResMonitorEnd.RST = true;
|
||
_trigPressureDifference.RST = true;
|
||
}
|
||
|
||
private void MonitorHeaterResistance()
|
||
{
|
||
var reason = "";
|
||
var totalElapseTime = PmDevice.RecipeRunningInfo.TotalElapseTime;//Recipe已经执行的时间
|
||
var totalTime = PmDevice.RecipeRunningInfo.TotalTime;//Recipe总时间
|
||
|
||
if (totalElapseTime == 0) //当开始执行后再去判断时间和电阻
|
||
return;
|
||
|
||
// 工艺刚开始和快结束的一段时间内,不需要检测电阻值。
|
||
var ignoredDurationSec = SC.GetValue<double>($"PM.{Module}.Heater.InProcessResistanceMonitorIgnoreDuration");
|
||
|
||
// 开始监测和节结束监测电阻时,输出信息,方便调试。
|
||
_trigHeaterResMonitorBegin.CLK = totalElapseTime >= ignoredDurationSec;
|
||
_trigHeaterResMonitorEnd.CLK = totalTime - totalElapseTime < ignoredDurationSec;
|
||
if (_trigHeaterResMonitorBegin.Q)
|
||
Notify("Begin Heater Resistance Monitor");
|
||
if (_trigHeaterResMonitorEnd.Q)
|
||
Notify("End Heater Resistance Monitor");
|
||
|
||
// 工艺刚开始和快结束的一段时间内,不需要检测电阻值。
|
||
if (!_trigHeaterResMonitorBegin.M || _trigHeaterResMonitorEnd.M)
|
||
return;
|
||
|
||
// 如果在工艺中,检测Heater电阻
|
||
for (var i = 1; i <= 3; i++) //检测出所有的加热丝是否有断开的,不要提前结束循环
|
||
{
|
||
var ioPsuData = (IoPsuData)DATA.Poll($"{Module}.PSU{i}.DeviceData");
|
||
if (ioPsuData != null)
|
||
{
|
||
if (ioPsuData.IsResistanceOutOfRange)//PSU加热丝断开,后续可能增加条件判断
|
||
reason += $"PSU{i} resistance:{ioPsuData.Resistance} is out of range\r\n ";
|
||
}
|
||
|
||
var ioScrData = (IoPsuData)DATA.Poll($"{Module}.SCR{i}.DeviceData");
|
||
if (ioScrData != null)
|
||
{
|
||
if (ioScrData.IsResistanceOutOfRange)//SCR加热丝断开,后续可能增加条件判断
|
||
reason += $"SCR{i} resistance:{ioScrData.Resistance} is out of range\r\n ";
|
||
}
|
||
}
|
||
|
||
if (reason.Length > 0)
|
||
{
|
||
// 某个加热器电阻超标
|
||
_trigHeaterResOutOfRange.CLK = true;
|
||
if (_trigHeaterResOutOfRange.Q)
|
||
{
|
||
var alarmLevel = SC.SafeGetStringValue($"PM.{Module}.Heater.InProcessResistanceFailAlarmLevel", "Alarm");
|
||
if (alarmLevel == "Alarm")
|
||
Stop(reason);
|
||
else
|
||
EV.PostWarningLog(Module,reason);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void PressureDifferenceDetection()
|
||
{
|
||
double pt1 = PmDevice.GetChamberPressure();
|
||
|
||
double pt2 = PmDevice.GetForelinePressure();
|
||
|
||
double d = Math.Abs(pt1 - pt2);
|
||
if (d >= _pressureDifferenceUpperLimit)
|
||
_trigPressureDifference.CLK = true;
|
||
|
||
if (_trigPressureDifference.Q)
|
||
{
|
||
string reason = $"PT1={pt1} PT2={pt2},Difference {d} over DifferenceMax={_pressureDifferenceUpperLimit} ";
|
||
var alarmLevel = SC.SafeGetStringValue($"PM.{Module}.PT1PT2PressureDifferenceMaxAlarmLevel", "Alarm");
|
||
if (alarmLevel == "Alarm")
|
||
Stop(reason);
|
||
else
|
||
EV.PostWarningLog(Module, reason);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
public override Result Monitor()
|
||
{
|
||
if (!PmDevice.CheckEnableRunProcess(out string reason))
|
||
{
|
||
EV.PostAlarmLog(Module, reason);
|
||
return Result.FAIL;
|
||
}
|
||
|
||
if (_isDryRun) // 空跑工艺
|
||
{
|
||
try
|
||
{
|
||
DryRunProcess((int)RoutineStep.WaitProcess, $"Chamber:{Name}:WaitProcess", _delayTimeDryRun);
|
||
}
|
||
catch (RoutineBreakException)
|
||
{
|
||
return Result.RUN;
|
||
}
|
||
catch (RoutineFaildException)
|
||
{
|
||
return Result.FAIL;
|
||
}
|
||
|
||
Notify("End");
|
||
|
||
return Result.DONE;
|
||
}
|
||
else
|
||
{
|
||
MonitorRecipeEndTime();
|
||
|
||
MonitorRecipeRunInfo();
|
||
|
||
MonitorHeaterResistance(); //加热丝断开检测
|
||
|
||
PressureDifferenceDetection();//腔体和管道压力检测
|
||
|
||
lock (_recipeLocker)
|
||
{
|
||
try
|
||
{
|
||
switch (_state)
|
||
{
|
||
case RecipeRunningState.ExecStep:
|
||
{
|
||
PmDevice.ResetToleranceChecker();
|
||
|
||
//int stepTime = (int)PMDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime * 1000;
|
||
if (ContinueAction != RecipeContinueMode.StepContinue)
|
||
{
|
||
_curStepElpasedTimeBeforePaused = 0;
|
||
_curStepElpasedTimeBeforePaused2 = 0;
|
||
}
|
||
|
||
ContinueAction = RecipeContinueMode.None;
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].IsLoopStartStep)
|
||
{
|
||
CurStepTotalLoopCount = PmDevice.RecipeRunningInfo
|
||
.RecipeStepList[_currentStepNumber].LoopCount;
|
||
if (CurStepTotalLoopCount == 0)
|
||
{
|
||
CurrentLoopCount = 0;
|
||
}
|
||
else
|
||
{
|
||
CurrentLoopCount++;
|
||
}
|
||
}
|
||
|
||
//stepTime = (int)(PMDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime - _curStepElpasedTimeBeforePaused / 1000);
|
||
_stepTimer.Start(
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime * 1000 -
|
||
_curStepElpasedTimeBeforePaused);
|
||
|
||
if (!_stepTimer2.IsIdle())
|
||
{
|
||
_curStepElpasedTimeBeforePaused2 += _stepTimer2.GetElapseTime();
|
||
_stepTimer2.Stop();
|
||
}
|
||
|
||
_stepTimer2.Start(int.MaxValue);
|
||
|
||
Notify($"Running step {_currentStepNumber + 1} : {PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepName}");
|
||
|
||
GemManager.Instance?.TriggerEvent($"{Module}RecipeStepStart", new string[] { $"{Module}.SelectedRecipeName", $"{Module}.RecipeStepNumber",$"{Module}.RecipeStepName" },
|
||
new object[] { PmDevice.RecipeRunningInfo.RecipeName, _currentStepNumber + 1,
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepName });
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].IsDummyStep)
|
||
{
|
||
_dummyStepCount++;
|
||
}
|
||
|
||
//执行工艺程序命令
|
||
foreach (var recipeCmd in PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands.Keys)
|
||
{
|
||
if (recipeCmd == "SusHeaterSetMode")
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands[recipeCmd] == "Jump")
|
||
_isPSUHeaterJumpMode = true;
|
||
else
|
||
_isPSUHeaterJumpMode = false;
|
||
|
||
continue;
|
||
}
|
||
|
||
if (recipeCmd == "WWHeaterSetMode")
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands[recipeCmd] == "Jump")
|
||
_isSCRHeaterJumpMode = true;
|
||
else
|
||
_isSCRHeaterJumpMode = false;
|
||
|
||
continue;
|
||
}
|
||
|
||
if (recipeCmd == "FlowSetMode")
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands[recipeCmd] == "Jump")
|
||
_isMFCJumpMode = true;
|
||
else
|
||
_isMFCJumpMode = false;
|
||
|
||
continue;
|
||
}
|
||
|
||
if (IsCmdSkip(recipeCmd)) // 不是注册的方法,需要跳过
|
||
continue;
|
||
|
||
if (!OP.CanDoOperation($"{Module}.{recipeCmd}", out reason,
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands[recipeCmd]))
|
||
{
|
||
EV.PostAlarmLog(Module, $"Can not execute {recipeCmd}, {reason}");
|
||
return Result.FAIL;
|
||
}
|
||
else
|
||
{
|
||
int time = (int)(PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.StepTime * 1000 -
|
||
_curStepElpasedTimeBeforePaused); // (int)PMDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime * 1000;
|
||
|
||
if (recipeCmd.StartsWith("TC1") && _isPSUHeaterJumpMode)
|
||
{
|
||
time = 1;
|
||
}
|
||
|
||
if (recipeCmd.StartsWith("TC2") && _isSCRHeaterJumpMode)
|
||
{
|
||
time = 1;
|
||
}
|
||
|
||
if (recipeCmd.StartsWith("Mfc") && recipeCmd.EndsWith(".Ramp") &&
|
||
!PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].IsDummyStep)
|
||
{
|
||
if (_isMFCJumpMode)
|
||
{
|
||
time = 1;
|
||
}
|
||
}
|
||
|
||
if ((recipeCmd == "TV.SetPressure"
|
||
|| recipeCmd == "PMServo.SetActualSpeed"
|
||
|| recipeCmd.StartsWith("Pressure") && recipeCmd.EndsWith(".Ramp")
|
||
|| recipeCmd.StartsWith("Mfc") && recipeCmd.EndsWith(".Ramp"))
|
||
&& !PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.IsDummyStep)
|
||
{
|
||
if (_currentStepNumber >= 1)
|
||
{
|
||
int previousStepNumber = _currentStepNumber - 1;
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[previousStepNumber]
|
||
.IsDummyStep)
|
||
previousStepNumber = _currentStepNumber - 2;
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[previousStepNumber]
|
||
.RecipeCommands.ContainsKey(recipeCmd))
|
||
{
|
||
string previousValue = PmDevice.RecipeRunningInfo
|
||
.RecipeStepList[previousStepNumber].RecipeCommands[recipeCmd];
|
||
string currentValue = PmDevice.RecipeRunningInfo
|
||
.RecipeStepList[_currentStepNumber].RecipeCommands[recipeCmd];
|
||
if (previousValue == currentValue)
|
||
{
|
||
if (_lstSkipSteps.Count > 0 &&
|
||
_lstSkipSteps.Contains(
|
||
previousStepNumber)) //上一步是跳步过来的,这一步和上一步的值是相同的不用设置
|
||
{
|
||
continue;
|
||
}
|
||
|
||
time = 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
OP.DoOperation($"{Module}.{recipeCmd}", out string reason1, time,
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands[recipeCmd]);
|
||
}
|
||
}
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].EndBy ==
|
||
EnumEndByCondition.ByTime)
|
||
_state = RecipeRunningState.TimeWait;
|
||
else
|
||
_state = RecipeRunningState.ConditionWait;
|
||
|
||
//ResetChecker();
|
||
|
||
_dbCallback.RecipeStepStart(PmDevice.RecipeRunningInfo.InnerId.ToString(),
|
||
_currentStepNumber,
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepName,
|
||
(float)PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime);
|
||
_fdc.Start(PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber]
|
||
.RecipeCommands);
|
||
}
|
||
break;
|
||
|
||
case RecipeRunningState.TimeWait:
|
||
|
||
if (IsPaused)
|
||
{
|
||
_state = RecipeRunningState.Paused;
|
||
}
|
||
|
||
if (_stepTimer.IsTimeout())
|
||
{
|
||
_state = RecipeRunningState.StepCompleted;
|
||
Grow();
|
||
}
|
||
|
||
if (PmDevice.RecipeRunningInfo.NeedReloadRecipe)
|
||
{
|
||
if (string.IsNullOrEmpty(PmDevice.RecipeRunningInfo.XmlRecipeToReload))
|
||
{
|
||
EV.PostWarningLog(Module, "Recipe is required to be reloaded but no new recipe is received");
|
||
PmDevice.RecipeRunningInfo.NeedReloadRecipe = false;
|
||
}
|
||
else
|
||
{ //重新加载后面的Recipe内容
|
||
if (RecipeParser.ParseXmlString(PmDevice.RecipeRunningInfo.XmlRecipeToReload, Module, out var recipeHead, out var recipeSteps, out var reason1))
|
||
{
|
||
//记录高亮部分
|
||
foreach (var item in PmDevice.RecipeRunningInfo.HighlightDic)
|
||
{
|
||
var s = recipeSteps.Find(i => i.StepUid == item.Key);
|
||
if (s != null)
|
||
{
|
||
s.HighLightList = item.Value;
|
||
}
|
||
}
|
||
PmDevice.RecipeRunningInfo.RecipeStepList = recipeSteps;
|
||
PmDevice.RecipeRunningInfo.TotalTime = CalcRecipeTime();
|
||
PmDevice.RecipeRunningInfo.NeedReloadRecipe = false;
|
||
}
|
||
else
|
||
{
|
||
EV.PostWarningLog(Module, $"Reloading Recipe failed, {reason1}");
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
//ToleranceChecker();
|
||
|
||
SkipStepForHeat();
|
||
|
||
break;
|
||
|
||
case RecipeRunningState.ConditionWait:
|
||
{
|
||
if (_stepTimer.IsTimeout())
|
||
{
|
||
_state = RecipeRunningState.StepCompleted;
|
||
Grow();
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RecipeRunningState.Paused:
|
||
PmDevice.PauseRecipe(out reason);
|
||
if (!_stepTimer.IsIdle())
|
||
{
|
||
_curStepElpasedTimeBeforePaused += _stepTimer.GetElapseTime();
|
||
_stepTimer.Stop();
|
||
}
|
||
|
||
switch (ContinueAction)
|
||
{
|
||
case RecipeContinueMode.None:
|
||
break;
|
||
case RecipeContinueMode.WaferReturnAndJobStop:
|
||
//Singleton<RouteManager>.Instance.CheckToPostMessage((int)RouteManager.MSG.StopJob);
|
||
_state = RecipeRunningState.Error;
|
||
break;
|
||
case RecipeContinueMode.RecipeCompleted:
|
||
_state = RecipeRunningState.RecipeCompleted;
|
||
break;
|
||
case RecipeContinueMode.StepContinue:
|
||
_state = RecipeRunningState.ExecStep;
|
||
break;
|
||
case RecipeContinueMode.StepRestart:
|
||
_state = RecipeRunningState.ExecStep;
|
||
break;
|
||
case RecipeContinueMode.RecipeRestart:
|
||
_currentStepNumber = 0;
|
||
_state = RecipeRunningState.ExecStep;
|
||
break;
|
||
case RecipeContinueMode.NextStep:
|
||
_state = RecipeRunningState.StepCompleted;
|
||
break;
|
||
}
|
||
|
||
break;
|
||
|
||
case RecipeRunningState.StepCompleted:
|
||
{
|
||
//放在前面,stepnumber后面会被更新
|
||
GemManager.Instance?.TriggerEvent($"{Module}RecipeStepComplete",
|
||
new string[] { $"{Module}.SelectedRecipeName", $"{Module}.RecipeStepNumber", $"{Module}.RecipeStepName" },
|
||
new object[] { PmDevice.RecipeRunningInfo.RecipeName, _currentStepNumber + 1,PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepName });
|
||
|
||
_stepTimer2.Stop();
|
||
|
||
_dbCallback.RecipeStepEnd(PmDevice.RecipeRunningInfo.InnerId.ToString(),
|
||
_currentStepNumber, _fdc.DataList);
|
||
_fdc.Stop();
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].IsLoopEndStep)
|
||
{
|
||
//重新读取循环的设定次数
|
||
for (int nn = _currentStepNumber; nn >= 0; nn--)
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[nn].IsLoopStartStep)
|
||
{
|
||
CurStepTotalLoopCount =
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[nn].LoopCount;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (CurrentLoopCount >= CurStepTotalLoopCount)
|
||
{
|
||
CurrentLoopCount = CurStepTotalLoopCount = 0;
|
||
_currentStepNumber++;
|
||
}
|
||
else
|
||
{
|
||
int n = _currentStepNumber - 1;
|
||
int next = -1;
|
||
while (n >= 0)
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[n].IsLoopStartStep)
|
||
{
|
||
next = n;
|
||
break;
|
||
}
|
||
|
||
n--;
|
||
}
|
||
|
||
if (next == -1)
|
||
throw new Exception("Loop End control error");
|
||
_currentStepNumber = next;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_currentStepNumber++;
|
||
}
|
||
|
||
if (_currentStepNumber >= PmDevice.RecipeRunningInfo.RecipeStepList.Count)
|
||
{
|
||
_currentStepNumber = PmDevice.RecipeRunningInfo.RecipeStepList.Count - 1;
|
||
_state = RecipeRunningState.RecipeCompleted;
|
||
}
|
||
else
|
||
{
|
||
_state = RecipeRunningState.ExecStep;
|
||
}
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
case RecipeRunningState.RecipeCompleted:
|
||
{
|
||
|
||
//更新PM的Runtime
|
||
if (!_hasRecordRunTime)
|
||
{
|
||
_hasRecordRunTime = true;
|
||
RuntimeDataRecorder.UpdateElapseTimePM(Module,
|
||
(int)_recipeTimer.GetElapseTime() / 1000);
|
||
}
|
||
|
||
_recipeTimer.Stop();
|
||
Notify("Finished");
|
||
GrowCheck();
|
||
|
||
//手动工艺时LotID和SequenceID可能是string.Empty
|
||
//补全路径
|
||
string recipeID = PmDevice.RecipeRunningInfo.RecipeName + ".rcp";
|
||
string squenceID = $"Sequence\\{PmDevice.GetWaferSequenceID()}.seq";
|
||
|
||
GemManager.Instance.Equipment?.TriggerEvent("PMRecipeComplete", new string[] { "ChamberID", "RecipeID", "LotID", "SequenceID" }, new object[] { PmDevice.Module, recipeID, PmDevice.GetWaferLotID(), squenceID });
|
||
|
||
return Result.DONE;
|
||
}
|
||
|
||
case RecipeRunningState.Error:
|
||
{
|
||
//更新PM的Runtime
|
||
if (!_hasRecordRunTime)
|
||
{
|
||
_hasRecordRunTime = true;
|
||
RuntimeDataRecorder.UpdateElapseTimePM(Module,
|
||
(int)_recipeTimer.GetElapseTime() / 1000);
|
||
}
|
||
GrowCheck();
|
||
return Result.DONE;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
PmDevice.SetHeaterStopRamp();
|
||
PmDevice.SetMfcStopRamp(PmDevice.GetMfcListByGroupName(MfcGroupName.All));
|
||
PmDevice.SetRotationStopRamp();
|
||
|
||
LOG.Write(ex);
|
||
return Result.FAIL;
|
||
}
|
||
}
|
||
|
||
return Result.RUN;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private void MonitorRecipeRunInfo()
|
||
{
|
||
try
|
||
{
|
||
PmDevice.RecipeRunningInfo.StepNumber =
|
||
_currentStepNumber + 1 - _dummyStepCount; //CurStepNum start from 0, ignore dummy step
|
||
PmDevice.RecipeRunningInfo.StepName =
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepName;
|
||
PmDevice.RecipeRunningInfo.StepTime =
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].StepTime;
|
||
PmDevice.RecipeRunningInfo.GrowthRate =
|
||
PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].GrowthRate;
|
||
PmDevice.RecipeRunningInfo.StepElapseTime = _stepTimer.IsIdle()
|
||
? _curStepElpasedTimeBeforePaused / 1000
|
||
: (_stepTimer.GetElapseTime() + _curStepElpasedTimeBeforePaused) / 1000;
|
||
|
||
PmDevice.RecipeRunningInfo.TotalElapseTime = CalcElapseRecipeTime();
|
||
|
||
PmDevice.RecipeRunningInfo.StepElapseTime2 = _stepTimer2.IsIdle()
|
||
? _curStepElpasedTimeBeforePaused2 / 1000
|
||
: (_stepTimer2.GetElapseTime() + _curStepElpasedTimeBeforePaused2) / 1000;
|
||
PmDevice.RecipeRunningInfo.TotalElapseTime2 = _recipeTimer.GetElapseTime() / 1000;
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].RecipeCommands
|
||
.ContainsKey("SHArH2Switch.SetValve"))
|
||
{
|
||
PmDevice.RecipeRunningInfo.ArH2Switch = PmDevice.RecipeRunningInfo
|
||
.RecipeStepList[_currentStepNumber].RecipeCommands["SHArH2Switch.SetValve"];
|
||
}
|
||
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber].RecipeCommands
|
||
.ContainsKey("N2Dilution.SetValve"))
|
||
{
|
||
PmDevice.RecipeRunningInfo.N2FlowMode = PmDevice.RecipeRunningInfo
|
||
.RecipeStepList[_currentStepNumber].RecipeCommands["N2Dilution.SetValve"];
|
||
}
|
||
|
||
if (_currentStepIndex != PmDevice.RecipeRunningInfo.StepNumber)
|
||
{
|
||
Notify(
|
||
$"Start Recipe: Step:{PmDevice.RecipeRunningInfo.StepNumber} Name:{PmDevice.RecipeRunningInfo.StepName} ");
|
||
_currentStepIndex = PmDevice.RecipeRunningInfo.StepNumber;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
public override void Abort()
|
||
{
|
||
//Abort后设置加热比列为0
|
||
PmDevice.TC1.SetRatio(0, 0, 0);
|
||
PmDevice.TC2.SetRatio(0, 0, 0);
|
||
|
||
//更新PM的Runtime
|
||
PmDevice.RecipeRunningInfo.IsRoutineAbort = true;
|
||
if (!_hasRecordRunTime)
|
||
{
|
||
_hasRecordRunTime = true;
|
||
RuntimeDataRecorder.UpdateElapseTimePM(Module,(int)_recipeTimer.GetElapseTime() / 1000);
|
||
}
|
||
|
||
_state = RecipeRunningState.RecipeCompleted;
|
||
|
||
_dbCallback.RecipeFailed(PmDevice.RecipeRunningInfo.InnerId.ToString());
|
||
_fdc.Stop();
|
||
|
||
PmDevice.AbortRunProcess(out string reason);
|
||
|
||
PmDevice.RecipeRunningInfo.StepName = string.Empty;
|
||
PmDevice.RecipeRunningInfo.StepNumber = 0;
|
||
PmDevice.RecipeRunningInfo.StepTime = 0;
|
||
PmDevice.RecipeRunningInfo.StepElapseTime = 0;
|
||
PmDevice.RecipeRunningInfo.TotalTime = 0;
|
||
PmDevice.RecipeRunningInfo.TotalElapseTime = 0;
|
||
|
||
//手动工艺时LotID和SequenceID可能是string.Empty
|
||
//补全路径
|
||
string recipeID = PmDevice.RecipeRunningInfo.RecipeName + ".rcp";
|
||
string squenceID = $"Sequence\\{PmDevice.GetWaferSequenceID()}.seq";
|
||
|
||
GemManager.Instance.Equipment?.TriggerEvent("PMRecipeAbort", new string[] { "ChamberID", "RecipeID", "LotID", "SequenceID" }, new object[] { PmDevice.Module, recipeID, PmDevice.GetWaferLotID(), squenceID });
|
||
|
||
//PMDevice.Rf.SetPowerOnOff(false, out _);
|
||
//PMDevice.Microwave.SetPowerOnOff(false, out _);
|
||
|
||
//PMDevice.GasLine1.SetFlow(out _, 0, 0);
|
||
//PMDevice.GasLine2.SetFlow(out _, 0, 0);
|
||
//PMDevice.GasLine3.SetFlow(out _, 0, 0);
|
||
}
|
||
|
||
public void ExitProcess()
|
||
{
|
||
if (_state == RecipeRunningState.RecipeCompleted)
|
||
{
|
||
_dbCallback.RecipeComplete(PmDevice.RecipeRunningInfo.InnerId.ToString());
|
||
_fdc.Stop();
|
||
}
|
||
else
|
||
{
|
||
_dbCallback.RecipeFailed(PmDevice.RecipeRunningInfo.InnerId.ToString());
|
||
_fdc.Stop();
|
||
}
|
||
|
||
_thread.Stop();
|
||
}
|
||
|
||
|
||
public void PauseRecipe()
|
||
{
|
||
if (_state != RecipeRunningState.TimeWait && _state != RecipeRunningState.ConditionWait)
|
||
return;
|
||
|
||
if (!IsPaused)
|
||
{
|
||
IsPaused = true;
|
||
_pausedState = _state;
|
||
_state = RecipeRunningState.Paused;
|
||
}
|
||
}
|
||
|
||
|
||
public void SkipCurrentRecipeStep()
|
||
{
|
||
if (_state == RecipeRunningState.ConditionWait || _state == RecipeRunningState.TimeWait)
|
||
{
|
||
_lstSkipSteps.Add(_currentStepNumber);
|
||
_state = RecipeRunningState.StepCompleted;
|
||
}
|
||
}
|
||
|
||
|
||
protected int CalcRecipeTime()
|
||
{
|
||
double total = 0;
|
||
for (int i = 0; i < PmDevice.RecipeRunningInfo.RecipeStepList.Count; i++)
|
||
{
|
||
if (!PmDevice.RecipeRunningInfo.RecipeStepList[i].IsDummyStep) // 不统计虚拟Step的时间
|
||
{
|
||
total += PmDevice.RecipeRunningInfo.RecipeStepList[i].StepTime;
|
||
}
|
||
}
|
||
|
||
return (int)total;
|
||
}
|
||
|
||
protected double CalcElapseRecipeTime()
|
||
{
|
||
double total = 0;
|
||
for (int i = 0; i < _currentStepNumber; i++)
|
||
{
|
||
if (!PmDevice.RecipeRunningInfo.RecipeStepList[i].IsDummyStep) // 不统计虚拟Step的时间
|
||
{
|
||
total += PmDevice.RecipeRunningInfo.RecipeStepList[i].StepTime;
|
||
}
|
||
}
|
||
|
||
total += PmDevice.RecipeRunningInfo.StepElapseTime;
|
||
|
||
return total;
|
||
}
|
||
|
||
protected void MonitorRecipeEndTime()
|
||
{
|
||
try
|
||
{
|
||
if (!_estimatedTimeCalcTimer.IsTimeout())
|
||
return;
|
||
_estimatedTimeCalcTimer.Start(1000);
|
||
|
||
EstimatedTotalLeftTime = 0;
|
||
|
||
if (_state == RecipeRunningState.RecipeCompleted)
|
||
return;
|
||
|
||
if (!(_currentStepNumber >= 0 &&
|
||
_currentStepNumber <= PmDevice.RecipeRunningInfo.RecipeStepList.Count - 1))
|
||
return;
|
||
|
||
if (CurStepLeftTime > 0)
|
||
{
|
||
EstimatedTotalLeftTime = CurStepLeftTime;
|
||
}
|
||
|
||
int nextBegin = _currentStepNumber;
|
||
bool IsInLoop = false;
|
||
int iNum1 = 0;
|
||
int iNum2 = 0;
|
||
//int j=i;
|
||
for (int j = _currentStepNumber; j < PmDevice.RecipeRunningInfo.RecipeStepList.Count; j++)
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[j].IsLoopEndStep)
|
||
{
|
||
iNum2 = j;
|
||
IsInLoop = true;
|
||
break;
|
||
}
|
||
else if (j > _currentStepNumber && PmDevice.RecipeRunningInfo.RecipeStepList[j].IsLoopStartStep)
|
||
{
|
||
IsInLoop = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (IsInLoop)
|
||
{
|
||
iNum1 = _currentStepNumber;
|
||
for (int j = _currentStepNumber; j >= 0; j--)
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[j].IsLoopStartStep)
|
||
{
|
||
iNum1 = j;
|
||
break;
|
||
}
|
||
}
|
||
|
||
for (int j = _currentStepNumber + 1; j <= iNum2; j++)
|
||
{
|
||
EstimatedTotalLeftTime += PmDevice.RecipeRunningInfo.RecipeStepList[j].StepTime * 1000 *
|
||
(PmDevice.RecipeRunningInfo.RecipeStepList[iNum1].LoopCount -
|
||
CurrentLoopCount + 1);
|
||
}
|
||
|
||
for (int j = iNum1; j <= _currentStepNumber; j++)
|
||
{
|
||
EstimatedTotalLeftTime += PmDevice.RecipeRunningInfo.RecipeStepList[j].StepTime * 1000 *
|
||
(PmDevice.RecipeRunningInfo.RecipeStepList[iNum1].LoopCount -
|
||
CurrentLoopCount);
|
||
}
|
||
|
||
nextBegin = iNum2 + 1;
|
||
}
|
||
else
|
||
{
|
||
nextBegin++;
|
||
}
|
||
|
||
for (int j = nextBegin; j < PmDevice.RecipeRunningInfo.RecipeStepList.Count; j++)
|
||
{
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[j].IsLoopStartStep)
|
||
{
|
||
//j=i;
|
||
iNum1 = j;
|
||
iNum2 = j + 1;
|
||
double lr1 = 0;
|
||
for (int m = j; m < PmDevice.RecipeRunningInfo.RecipeStepList.Count; m++)
|
||
{
|
||
lr1 += PmDevice.RecipeRunningInfo.RecipeStepList[m].StepTime * 1000;
|
||
if (PmDevice.RecipeRunningInfo.RecipeStepList[m].IsLoopEndStep)
|
||
{
|
||
iNum2 = m;
|
||
break;
|
||
}
|
||
}
|
||
|
||
EstimatedTotalLeftTime += lr1 * PmDevice.RecipeRunningInfo.RecipeStepList[iNum1].LoopCount;
|
||
j = iNum2;
|
||
}
|
||
else
|
||
{
|
||
EstimatedTotalLeftTime += PmDevice.RecipeRunningInfo.RecipeStepList[j].StepTime * 1000;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LOG.Write(ex);
|
||
}
|
||
}
|
||
|
||
public void SetContinue(string continueMode)
|
||
{
|
||
switch (continueMode)
|
||
{
|
||
case "Step continue":
|
||
ContinueAction = RecipeContinueMode.StepContinue;
|
||
break;
|
||
case "Step restart":
|
||
ContinueAction = RecipeContinueMode.StepRestart;
|
||
break;
|
||
case "Next step":
|
||
ContinueAction = RecipeContinueMode.NextStep;
|
||
break;
|
||
case "Recipe restart":
|
||
ContinueAction = RecipeContinueMode.RecipeRestart;
|
||
break;
|
||
case "Recipe complete":
|
||
ContinueAction = RecipeContinueMode.RecipeCompleted;
|
||
break;
|
||
case "Wafer return and job stop":
|
||
ContinueAction = RecipeContinueMode.WaferReturnAndJobStop;
|
||
break;
|
||
}
|
||
|
||
IsPaused = false;
|
||
|
||
PmDevice.ResetToleranceChecker();
|
||
}
|
||
|
||
public void DryRunProcess(int id, string stepName, int delayTime)
|
||
{
|
||
Tuple<bool, Result> ret = Delay(id, () =>
|
||
{
|
||
Notify($"Dry run process {delayTime} seconds");
|
||
|
||
_stepSpan = new TimeSpan(0, 0, 0, (int)delayTime);
|
||
_stepStartTime = DateTime.Now;
|
||
_stepName = stepName;
|
||
|
||
return true;
|
||
}, delayTime * 1000);
|
||
|
||
if (ret.Item1)
|
||
{
|
||
if (ret.Item2 == Result.FAIL)
|
||
{
|
||
throw (new RoutineFaildException());
|
||
}
|
||
|
||
throw new RoutineBreakException();
|
||
}
|
||
}
|
||
|
||
public void ToleranceChecker()
|
||
{
|
||
#region MFC
|
||
|
||
for (int i = 0; i < _mfcGapChecker.Length; i++)
|
||
{
|
||
_mfcGapChecker[i].Monitor(PmDevice._mfcList[i].FeedBack, PmDevice._mfcList[i].SetPoint * (1 - 2 / 100),
|
||
PmDevice._mfcList[i].SetPoint * (1 + 2 / 100), 5 * 60);
|
||
|
||
if (_mfcGapChecker[i].Trig)
|
||
{
|
||
EV.PostWarningLog(Module, $"{PmDevice._mfcList[i].Name} flow gap > 2%, over 5 minites");
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < _mfcTrig.Length; i++)
|
||
{
|
||
double gap = Convert.ToDouble(dbMfcGapResults[i].StdDev);
|
||
double setPoint = Convert.ToDouble(dbMfcSetPointResults[i].Mean);
|
||
|
||
_mfcTrig[i].CLK = gap / setPoint > 1 / 100;
|
||
|
||
if (_mfcTrig[i].Q)
|
||
{
|
||
EV.PostWarningLog(Module,
|
||
$"{PmDevice._mfcList[i].Name} flow standard deviation > 1%, for 5 minites");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region PC
|
||
|
||
for (int i = 0; i < _pcGapChecker.Length; i++)
|
||
{
|
||
_pcGapChecker[i].Monitor(PmDevice._pcList[i].FeedBack, PmDevice._pcList[i].SetPoint - 60,
|
||
PmDevice._pcList[i].SetPoint + 60, 5 * 60);
|
||
|
||
if (_pcGapChecker[i].Trig)
|
||
{
|
||
EV.PostWarningLog(Module, $"{PmDevice._pcList[i].Name} flow gap > 60 mbar over 5 minites");
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < _pcTrig.Length; i++)
|
||
{
|
||
double gap = Convert.ToDouble(dbPcGapResults[i].StdDev);
|
||
//double setPoint = Convert.ToDouble(dbPcSetPointResults[i].StdDev);
|
||
|
||
_pcTrig[i].CLK = gap > 10;
|
||
|
||
if (_pcTrig[i].Q)
|
||
{
|
||
EV.PostWarningLog(Module,
|
||
$"{PmDevice._mfcList[i].Name} flow standard deviation > 1%, for 5 minites");
|
||
}
|
||
}
|
||
|
||
if (_rampCalcTimer.IsTimeout())
|
||
{
|
||
for (int i = 0; i < _pcTrig2.Length; i++)
|
||
{
|
||
_pcTrig2[i].CLK = Math.Abs(PmDevice._pcList[i].FeedBack - _pressurePrevious[i]) > 90;
|
||
|
||
if (_pcTrig2[i].Q)
|
||
{
|
||
EV.PostWarningLog(Module, $"{PmDevice._mfcList[i].Name} pressure ramp > 90 mbar");
|
||
}
|
||
|
||
_pressurePrevious[i] = PmDevice._pcList[i].FeedBack;
|
||
}
|
||
|
||
_rampCalcTimer.Start(1000);
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
|
||
public void ResetChecker()
|
||
{
|
||
#region MFC
|
||
|
||
for (int i = 0; i < _mfcGapChecker.Length; i++)
|
||
{
|
||
_mfcGapChecker[i].Reset(5 * 60);
|
||
}
|
||
|
||
for (int i = 0; i < _mfcTrig.Length; i++)
|
||
{
|
||
_mfcTrig[i].RST = true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region PC
|
||
|
||
for (int i = 0; i < _pcGapChecker.Length; i++)
|
||
{
|
||
_pcGapChecker[i].Reset(5 * 60);
|
||
}
|
||
|
||
for (int i = 0; i < _pcTrig.Length; i++)
|
||
{
|
||
_pcTrig[i].RST = true;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
public void SkipStepForHeat()
|
||
{
|
||
int stepCount = PmDevice.RecipeRunningInfo.RecipeStepList.Count;
|
||
|
||
if (_currentStepNumber + 1 < stepCount)
|
||
{
|
||
var currentStep = PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber];
|
||
var nextStep = PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber + 1];
|
||
|
||
if (currentStep.IsDummyStep)
|
||
return;
|
||
|
||
if (nextStep.IsDummyStep)
|
||
nextStep = PmDevice.RecipeRunningInfo.RecipeStepList[_currentStepNumber + 2];
|
||
|
||
if (!currentStep.RecipeCommands.ContainsKey("TC1.SetHeaterMode") ||
|
||
!nextStep.RecipeCommands.ContainsKey("TC1.SetHeaterMode"))
|
||
return;
|
||
|
||
var currentPSUMode = (HeatStrategy)Enum.Parse(typeof(HeatStrategy),
|
||
currentStep.RecipeCommands["TC1.SetHeaterMode"]);
|
||
var nextPSUMode = (HeatStrategy)Enum.Parse(typeof(HeatStrategy),
|
||
nextStep.RecipeCommands["TC1.SetHeaterMode"]);
|
||
|
||
if (currentPSUMode == HeatStrategy.Power && nextPSUMode != HeatStrategy.Power)
|
||
{
|
||
float PSUL2InputTemp =
|
||
(float)DATA.Poll(
|
||
$"{Module}.TC1.L2InputTempSetPoint"); // (float)DATA.Poll($"{Module}.TC1.L1PVFeedBack"); 测试用
|
||
|
||
float nextL2Temp = Convert.ToSingle(nextStep.RecipeCommands["TC1.SetL2TargetSP"]);
|
||
|
||
if (nextL2Temp > PSUL2InputTemp && nextL2Temp - PSUL2InputTemp < Math.Abs(_tempOffset))
|
||
{
|
||
SkipCurrentRecipeStep();
|
||
|
||
EV.PostInfoLog(Module,
|
||
$"Current PSU middle temperature is {PSUL2InputTemp}℃ and Power mode, next step PSU middle temperature setpoint is {nextL2Temp}℃ and {nextPSUMode} mode." +
|
||
$" TempOffset is {_tempOffset}℃. Need jump step!");
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void Grow()
|
||
{
|
||
try
|
||
{
|
||
var wi = WaferManager.Instance.GetWafer(ModuleHelper.Converter(Module), 0);
|
||
OP.DoOperation($"HistoryCoatingManager.Grow",
|
||
new object[] {
|
||
(wi.TrayInnerID).ToString(),
|
||
PmDevice.RecipeRunningInfo.GrowthRate,
|
||
PmDevice.RecipeRunningInfo.StepTime ,
|
||
Module });
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
LOG.Error(ex.Message, ex);
|
||
}
|
||
|
||
}
|
||
public void GrowCheck()
|
||
{
|
||
try
|
||
{
|
||
var wi = WaferManager.Instance.GetWafer(ModuleHelper.Converter(Module), 0);
|
||
OP.DoOperation($"HistoryCoatingManager.GrowCheck",
|
||
new object[] {
|
||
(wi.WaferInnerID).ToString(),
|
||
Module });
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
LOG.Error(ex.Message, ex);
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|