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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|