igor-public/pearl/pearl-anneal.ipf

1509 lines
40 KiB
Igor

#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma version = 0.1
#pragma IgorVersion = 6.2
#pragma ModuleName = PearlAnneal
#include "pearl-epics"
/// @file
/// @brief ramp generator for sample annealing
///
/// the ramp generator increases/decreases the heating power
/// according to piecewise target function defined by the user in a set of waves.
/// each row of these waves describes one ramp phase.
/// the rows are processed sequentially.
///
/// target waves define when a ramp phase ends.
/// when a target is reached, the generator starts to process the next one.
/// the end point of the current phase will become the start point of the next phase.
///
/// the power target is interpolated linearly between the start and end points of a phase.
/// the ramp speed is given by the difference of the target values of the previous and current phases,
/// and the duration of the phase.
/// the power target values must be filled and be greater or equal to zero.
///
/// the temperature targets are kept constant.
/// once a temperature target has been reached, the phase ends.
/// temperature targets are not enforced.
/// the phase will end unconditionally when the power target is reached.
/// if you need to reach a temperature target,
/// make sure that the power target is high enough, and that the ramp is slow enough
/// so that the temperature is reached before the phase ends.
/// temperature targets can be disabled by entering NaN.
///
/// limits are not enforced.
/// if a limit is exceeded, the ramp will pause (while the power remains at its last setting).
/// the ramp resumes when the value falls below the limit.
/// limit checking can be disabled by entering NaN.
/// limits remain constant during the phase.
///
/// trips cause the ramp to stop, and the power to be turned off.
/// trip checking can be disabled by entering NaN.
/// trip values remain constant during the phase.
/// caution: do not rely on these trips for protection of equipment or the sample!
/// there may be a considerable lag between the occurrence of a trip condition
/// and the execution of a power reset!
/// either use a suitable hardware trip mechanism, or include an appropriate safety margin.
///
/// @arg minutes: minimum duration of the ramp phase in minutes.
/// determines the ramping speed.
/// the phase may take longer if pressure or temperature limits are reached.
/// required, must be greater than zero except for the first phase.
/// @arg target_watts: power setpoint at the end of the phase.
/// required, must be greater or equal to zero.
/// @arg target_tempA: temperature target of Lakeshore channel A in Kelvin.
/// optional, specify NaN to disable.
/// @arg target_tempB: temperature target of Lakeshore channel B in Kelvin.
/// optional, specify NaN to disable.
/// @arg target_tempPy: temperature target of the pyrometer in Kelvin.
/// optional, specify NaN to disable.
/// @arg limit_pressure: vacuum pressure limit in mbar.
/// optional, specify NaN to disable.
/// @arg trip_tempA: trip temperature of sensor A in Kelvin.
/// optional, specify NaN to disable.
/// @arg trip_tempB: trip temperature of sensor B in Kelvin.
/// optional, specify NaN to disable.
/// @arg trip_tempPy: trip temperature of pyrometer in Kelvin.
/// optional, specify NaN to disable.
/// @arg trip_pressure: trip pressure in mbar.
/// optional, specify NaN to disable.
///
/// at the beginning of the table, a row with the current setpoints (normally zero at the beginning of a ramp) and zero duration must be included.
/// note that the power supply will remain at its last setpoint after the ramp.
/// thus, the last row should bring the power supply to a safe end value, e.g. zero.
///
/// @pre
/// run-time requirements for online mode at the beamline
/// * EPICS.XOP of Paul Scherrer Institut, version 0.3.0 (March 2015) or later, must be loaded.
/// * caRepeater.exe program (from EPICS distribution) must be running.
///
/// @author matthias muntwiler, matthias.muntwiler@psi.ch
///
/// @copyright 2015 Paul Scherrer Institut @n
/// Licensed under the Apache License, Version 2.0 (the "License"); @n
/// you may not use this file except in compliance with the License. @n
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// @namespace PearlAnneal
/// @brief ramp generator for sample annealing
///
/// PearlAnneal is declared in @ref pearl-anneal.ipf.
///
static strconstant package_name = "pearl_anneal"
static strconstant package_path = "root:packages:pearl_anneal:"
// semicolon-separated list of persistent variable, string, and wave names
static strconstant prefs_objects = ""
static function AfterCompiledHook()
// initializes package data once when the procedure is first loaded
dfref savedf = GetDataFolderDFR()
variable do_init = 1
if (DataFolderExists(package_path))
setdatafolder $(package_path)
nvar /z init_done
if (nvar_exists(init_done))
if (init_done)
do_init = 0
endif
endif
endif
if (do_init)
init_package()
load_prefs()
setdatafolder $(package_path)
variable /g init_done = 1
endif
setdatafolder savedf
return 0
end
/// initialize the package data folder
///
/// create the package data folder, and all necessary control and data objects.
/// reset all variables.
static function init_package()
dfref savedf = getdatafolderdfr()
setdatafolder root:
newdatafolder /o/s packages
newdatafolder /o/s $package_name
// configuration (persistent)
string /g graphname = "graph_anneal_tracker"
string /g dataname = "anneal"
// recently used (persistent)
// recently used (volatile)
// run-time variables (volatile)
string /g controls, monitors
variable /g psu_connected = 0
variable /g ls_connected = 0
variable /g pyro_connected = 0
variable /g vac_connected = 0
// channel ID
variable /g chidSetVolts = 0
variable /g chidGetVolts = 0
variable /g chidSetAmps = 0
variable /g chidGetAmps = 0
variable /g chidGetWatts = 0
variable /g chidSetOnOff = 0
variable /g chidGetOnOff = 0
variable /g chidStatCC = 0
variable /g chidStatCV = 0
variable /g chidGetRemote = 0
variable /g chidGetTempA = 0
variable /g chidGetTempB = 0
variable /g chidGetPyro1 = 0
variable /g chidGetPyroQ = 0
variable /g chidGetPressure = 0
// current PV values
variable /g curGetVolts = 0
variable /g curSetVolts = 0
variable /g curGetAmps = 0
variable /g curSetAmps = 0
variable /g curGetWatts = 0
variable /g curSetWatts = 0
variable /g curGetOnOff = 0
variable /g curStatCC = 0
variable /g curStatCV = 0
variable /g curGetTempA = 0
variable /g curGetTempB = 0
variable /g curGetPyro1 = 0
variable /g curGetPyroQ = 0
variable /g curGetPressure = 0
// current phase indicators
variable /g curTargetWatts = nan
variable /g curTargetTempA = nan
variable /g curTargetTempB = nan
variable /g curTargetTempPy = nan
variable /g curTripTempA = nan
variable /g curTripTempB = nan
variable /g curTripTempPy = nan
variable /g curLimitPressure = nan
variable /g curTripPressure = nan
variable /g curPhaseMinutes = 0
// chart control and data
make /n=500 /o recVolts, recAmps, recWatts, recTemp, recPressure, recMinutes
variable /g recPoint
variable /g recMinutesStart
recPoint = 0
recMinutesStart = datetime / 60
// ramp status: 1 = running, 0.5 = paused, 0 = off
variable /g ramp_status
// index of the current ramp phase
variable /g ramp_phase
// ramping direction +1 = up, -1 = down
variable /g ramp_dir
// system time in minutes at the start of the current phase
variable /g minutes_start
// system time in minutes at the time of the previous step
variable /g minutes_previous
// status message for user
string /g ramp_message
ann_new_ramp_table(edit_table=0)
setdatafolder savedf
end
static function save_prefs()
// saves persistent package data to the preferences file
dfref saveDF = GetDataFolderDFR()
string fullPath = SpecialDirPath("Packages", 0, 0, 0)
fullPath += package_name
NewPath/O/C/Q tempPackagePrefsPath, fullPath
fullPath += ":preferences.pxp"
SetDataFolder root:packages
SetDataFolder $package_name
SaveData /O /Q /J=prefs_objects fullPath
KillPath/Z tempPackagePrefsPath
SetDataFolder saveDF
end
static function load_prefs()
// loads persistent package data from the preferences file
// the preferences file is an Igor packed experiment file in a special preferences folder
dfref saveDF = GetDataFolderDFR()
variable result = -1
setdatafolder root:
NewDataFolder /O/S packages
NewDataFolder /O/S $package_name
string fullPath = SpecialDirPath("Packages", 0, 0, 0)
fullPath += package_name
GetFileFolderInfo /Q /Z fullPath
if (V_Flag == 0) // Disk directory exists?
fullPath += ":preferences.pxp"
GetFileFolderInfo /Q /Z fullPath
if (V_Flag == 0) // Preference file exist?
LoadData /O /R /Q fullPath
result = 0
endif
endif
SetDataFolder saveDF
return result
end
/// save current ramp table
///
/// WORK IN PROGRESS
static function save_ramp()
dfref saveDF = GetDataFolderDFR()
SetDataFolder root:packages
SetDataFolder $package_name
wave minutes
wave target_watts
wave target_tempA
wave target_tempB
wave target_tempPy
wave trip_tempA
wave trip_tempB
wave trip_tempPy
wave limit_pressure
wave trip_pressure
Save /T /M="\r\n" /I minutes,target_watts,target_tempPy,target_tempB,target_tempA,limit_pressure,trip_tempPy,trip_tempB,trip_tempA,trip_pressure as "anneal-ramp.itx"
SetDataFolder saveDF
end
/// Igor sometimes crashes if the PVs are not disconnected when it quits
static function IgorQuitHook(app)
string app
epics_disconnect()
end
/// connect to all required EPICS devices.
///
/// can be called repeatedly. the function exits gracefully if connections are existing.
///
/// @return zero if successful, non-zero if an error occurred
static function epics_connect()
dfref savedf = getdatafolderdfr()
setdatafolder $(package_path)
nvar psu_connected
nvar ls_connected
nvar vac_connected
nvar pyro_connected
if (!psu_connected)
epics_connect_psu()
endif
if (!ls_connected)
epics_connect_ls()
endif
if (!vac_connected)
epics_connect_vac()
endif
if (!pyro_connected)
epics_connect_pyro()
endif
setdatafolder saveDF
return !(psu_connected && ls_connected && vac_connected && pyro_connected)
end
/// connect to the power supply unit
///
/// if the EPICS XOP is not loaded, the function does nothing.
/// if channels are not available, the function exits with an error code.
/// the run-time error status is reset.
///
/// @return zero if successful, non-zero if an error occurred
///
/// @todo the X03DA channel names are hard-coded.
static function epics_connect_psu()
dfref savedf = getdatafolderdfr()
setdatafolder $(package_path)
// create variables and waves
nvar connected = psu_connected
svar ramp_message
// channel ID variables
nvar chidSetVolts
nvar chidGetVolts
nvar chidSetAmps
nvar chidGetAmps
nvar chidGetWatts
nvar chidSetOnOff
nvar chidGetOnOff
nvar chidStatCC
nvar chidStatCV
nvar chidGetRemote
nvar curGetVolts
nvar curGetAmps
nvar curGetWatts
nvar curGetOnOff
nvar curStatCC
nvar curStatCV
string psu_name = "X03DA-PSU-XP:"
variable timeout = 5 // seconds
#if exists("pvWait")
// EPICS.XOP version 0.3.0 or later
pvOpen /Q chidSetVolts, psu_name + "SETVOLTS"
pvOpen /Q chidGetVolts, psu_name + "GETVOLTS"
pvOpen /Q chidSetAmps, psu_name + "SETCUR"
pvOpen /Q chidGetAmps, psu_name + "GETCUR"
pvOpen /Q chidGetWatts, psu_name + "CALCPWR"
pvOpen /Q chidSetOnOff, psu_name + "SETONOFF"
pvOpen /Q chidGetOnOff, psu_name + "GETONOFF"
pvOpen /Q chidGetRemote, psu_name + "COMGETRMT" // string
pvOpen /Q chidStatCC, psu_name + "STAT-CC"
pvOpen /Q chidStatCV, psu_name + "STAT-CV"
pvWait timeout
#endif
if (GetRTError(1))
connected = 0
ramp_message = "PSU: no connection"
else
connected = 1
endif
#if exists("pvMonitor")
if (connected)
pvMonitor chidGetVolts, curGetVolts
pvMonitor chidSetVolts, curSetVolts
pvMonitor chidGetAmps, curGetAmps
pvMonitor chidSetAmps, curSetAmps
pvMonitor chidGetWatts, curGetWatts
pvMonitor chidGetOnOff, curGetOnOff
pvMonitor chidStatCC, curStatCC
pvMonitor chidStatCV, curStatCV
endif
#endif
setdatafolder saveDF
return !connected
end
/// connect to the lakeshore temperature controller
///
/// if the EPICS XOP is not loaded, the function does nothing.
/// if channels are not available, the function exits with an error code.
/// the run-time error status is reset.
///
/// @return zero if successful, non-zero if an error occurred
///
/// @todo the X03DA channel names are hard-coded.
static function epics_connect_ls()
dfref savedf = getdatafolderdfr()
setdatafolder $(package_path)
nvar connected = ls_connected
svar ramp_message
nvar chidGetTempA
nvar chidGetTempB
nvar curGetTempA
nvar curGetTempB
string base_name = "X03DA-PC-LAKESHOREXP:"
variable timeout = 5 // seconds
#if exists("pvWait")
// EPICS.XOP version 0.3.0 or later
pvOpen /Q chidGetTempA, base_name + "A-TEMP_RBV"
pvOpen /Q chidGetTempB, base_name + "B-TEMP_RBV"
pvWait timeout
#endif
if (GetRTError(1))
connected = 0
ramp_message = "Lakeshore: no connection"
else
connected = 1
endif
#if exists("pvMonitor")
if (connected)
pvMonitor /F=ann_callback_ls chidGetTempA, curGetTempA
pvMonitor /F=ann_callback_ls chidGetTempB, curGetTempB
endif
#endif
setdatafolder saveDF
return !connected
end
/// connect to the vacuum gauge
///
/// if the EPICS XOP is not loaded, the function does nothing.
/// if channels are not available, the function exits with an error code.
/// the run-time error status is reset.
///
/// @return zero if successful, non-zero if an error occurred
///
/// @todo the X03DA channel names are hard-coded.
static function epics_connect_vac()
dfref savedf = getdatafolderdfr()
setdatafolder $(package_path)
nvar connected = vac_connected
svar ramp_message
nvar chidGetPressure
nvar curGetPressure
string base_name = "X03DA-PVC-XP:"
variable timeout = 5 // seconds
#if exists("pvWait")
// EPICS.XOP version 0.3.0 or later
pvOpen /Q chidGetPressure, base_name + "IG-PRESSURE"
pvWait timeout
#endif
if (GetRTError(1))
connected = 0
ramp_message = "vacuum gauge: no connection"
else
connected = 1
endif
#if exists("pvMonitor")
if (connected)
pvMonitor /F=ann_callback_pvc chidGetPressure, curGetPressure
endif
#endif
setdatafolder saveDF
return !connected
end
/// connect to the pyrometer
///
/// if the EPICS XOP is not loaded, the function does nothing.
/// if channels are not available, the function exits with an error code.
/// the run-time error status is reset.
///
/// @return zero if successful, non-zero if an error occurred
///
/// @todo the X03DA channel names are hard-coded.
///
/// IOC implemented in LabView
/// X03DA-LV-MAURER:TEMP-1
/// X03DA-LV-MAURER:TEMP-Q
static function epics_connect_pyro()
dfref savedf = getdatafolderdfr()
setdatafolder $(package_path)
nvar connected = pyro_connected
svar ramp_message
nvar chidGetPyro1
nvar chidGetPyroQ
nvar curGetPyro1
nvar curGetPyroQ
string base_name = "X03DA-LV-MAURER:"
variable timeout = 5 // seconds
#if exists("pvWait")
// EPICS.XOP version 0.3.0 or later
pvOpen /Q chidGetPyro1, base_name + "TEMP-1"
pvOpen /Q chidGetPyroQ, base_name + "TEMP-Q"
pvWait timeout
#endif
if (GetRTError(1))
connected = 0
ramp_message = "pyrometer: no connection"
else
connected = 1
endif
#if exists("pvMonitor")
if (connected)
pvMonitor /F=ann_callback_ls chidGetPyro1, curGetPyro1
pvMonitor /F=ann_callback_ls chidGetPyroQ, curGetPyroQ
endif
#endif
setdatafolder saveDF
return !connected
end
static function epics_disconnect_chid(chid_var_name)
string chid_var_name
#if exists("pvClose")
nvar /z chid = $chid_var_name
if (nvar_exists(chid))
if (chid != 0)
pvClose chid
endif
chid = 0
endif
#endif
end
/// disconnect from all EPICS devices.
///
static function epics_disconnect()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
epics_disconnect_psu()
epics_disconnect_ls()
epics_disconnect_vac()
epics_disconnect_pyro()
setdatafolder savedf
end
/// disconnect from the power supply unit
///
static function epics_disconnect_psu()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar connected = psu_connected
if (connected)
connected = 0
epics_disconnect_chid("chidSetVolts")
epics_disconnect_chid("chidGetVolts")
epics_disconnect_chid("chidSetAmps")
epics_disconnect_chid("chidGetAmps")
epics_disconnect_chid("chidGetWatts")
epics_disconnect_chid("chidSetOnOff")
epics_disconnect_chid("chidGetOnOff")
epics_disconnect_chid("chidGetRemote")
epics_disconnect_chid("chidStatCC")
epics_disconnect_chid("chidStatCV")
endif
setdatafolder savedf
end
/// disconnect from the lakeshore controller
///
static function epics_disconnect_ls()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar connected = ls_connected
if (connected)
connected = 0
epics_disconnect_chid("chidGetTempA")
epics_disconnect_chid("chidGetTempB")
endif
setdatafolder savedf
end
/// disconnect from the vacuum gauge
///
static function epics_disconnect_vac()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar connected = vac_connected
if (connected)
connected = 0
epics_disconnect_chid("chidGetPressure")
endif
setdatafolder savedf
end
/// disconnect from the pyrometer
///
static function epics_disconnect_pyro()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar connected = pyro_connected
if (connected)
connected = 0
epics_disconnect_chid("chidGetPyro1")
epics_disconnect_chid("chidGetPyroQ")
endif
setdatafolder savedf
end
/// create a new ramp table and open it in a table window.
///
/// the waves are created in the current data folder.
/// to be executed, they must be copied into the package folder.
///
function ann_new_ramp_table([edit_table])
variable edit_table
if (ParamIsDefault(edit_table))
edit_table = 1
endif
dfref savedf = GetDataFolderDFR()
make /n=3 /o minutes,cum_minutes,target_watts
make /n=3 /o target_tempA, target_tempB, target_tempPy
make /n=3 /o limit_pressure
make /n=3 /o trip_tempA, trip_tempB, trip_tempPy
make /n=3 /o trip_pressure
make /n=3 /o est_temp
minutes = 1
cum_minutes = nan
target_watts[0] = {0, 5, 0}
target_tempA = nan
target_tempB = nan
target_tempPy = nan
limit_pressure = 1e-8
trip_tempA = nan
trip_tempB = 450
trip_tempPy = nan
trip_pressure = 5e-8
est_temp = nan
if (edit_table)
edit /k=1 minutes, target_watts
appendtotable target_tempA, trip_tempA
appendtotable target_tempB, trip_tempB
appendtotable target_tempPy, trip_tempPy
appendtotable limit_pressure,trip_pressure
endif
setdatafolder savedf
end
/// initialize the background tasks
///
/// can be called repeatedly. the function exits gracefully if tasks are running.
///
function ann_init_bg()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
CtrlNamedBackground ann_ramp, period = 60, proc = PearlAnneal#ann_ramp_bg
CtrlNamedBackground ann_record, period = 600, proc = PearlAnneal#ann_record_bg
setdatafolder savedf
end
/// background task of the recorder
static function ann_record_bg(s)
STRUCT WMBackgroundStruct &s
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar curGetVolts
nvar curGetAmps
nvar curGetWatts
nvar curGetOnOff
nvar curStatCC
nvar curStatCV
nvar curGetTempA
nvar curGetTempB
nvar curGetPyro1
nvar curGetPyroQ
nvar curGetPressure
nvar recPoint
nvar recMinutesStart
wave recMinutes
wave recVolts
wave recAmps
wave recWatts
wave recTemp
wave recPressure
variable nshift = 12
variable plo
variable phi
if (recPoint >= numpnts(recWatts))
plo = 0
phi = numpnts(recWatts) - nshift - 1
recMinutes[plo, phi] = recMinutes[p + nshift]
recVolts[plo, phi] = recVolts[p + nshift]
recAmps[plo, phi] = recAmps[p + nshift]
recWatts[plo, phi] = recWatts[p + nshift]
recTemp[plo, phi] = recTemp[p + nshift]
recPressure[plo, phi] = recPressure[p + nshift]
plo = numpnts(recWatts) - nshift
phi = numpnts(recWatts) - 1
recMinutes[plo, phi] = nan
recVolts[plo, phi] = nan
recAmps[plo, phi] = nan
recWatts[plo, phi] = nan
recTemp[plo, phi] = nan
recPressure[plo, phi] = nan
recPoint = plo
endif
variable temp = curGetTempA
if (curGetPyroQ >= 625)
temp = max(temp, curGetPyroQ)
elseif (curGetPyro1 >= 625)
temp = max(temp, curGetPyro1)
endif
recMinutes[recPoint] = datetime / 60 - recMinutesStart
recVolts[recPoint] = curGetVolts
recAmps[recPoint] = curGetAmps
recWatts[recPoint] = curGetWatts
recTemp[recPoint] = temp
recPressure[recPoint] = curGetPressure
recPoint += 1
setdatafolder savedf
return 0
End
/// background task of the ramp
static function ann_ramp_bg(s)
STRUCT WMBackgroundStruct &s
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
ann_ramp_step()
setdatafolder savedf
return 0
End
/// prepare and execute the next ramp step
///
/// check trips and limits before calling ann_ramp_step_exec()
///
function ann_ramp_step()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar minutes_start
nvar minutes_previous
nvar ramp_phase
svar ramp_message
wave minutes
wave target_watts
variable minutes_now = datetime / 60
variable minutes_phase = minutes_now - minutes_start
variable minutes_step = minutes_now - minutes_previous
variable trip
variable hold
variable phase_complete
ann_ramp_update(ramp_phase)
// check trip conditions
trip = ann_ramp_trip(ramp_phase)
if (!trip)
// check hold conditions
hold = ann_ramp_hold(ramp_phase)
if (!hold)
// check targets
phase_complete = ann_ramp_target(ramp_phase)
if (!phase_complete)
// execute step
phase_complete = ann_ramp_step_exec(minutes_now)
ramp_message = ""
endif
if (phase_complete)
ramp_phase += 1
minutes_start = minutes_now
// check end of ramp
if ((ramp_phase >= numpnts(minutes)) || (ramp_phase >= numpnts(target_watts)) || numtype(minutes[ramp_phase]) || numtype(target_watts[ramp_phase]))
ann_ramp_stop(0)
endif
endif
endif
endif
minutes_previous = minutes_now
setdatafolder savedf
return 0
end
/// update control panel variables
static function ann_ramp_update(phase)
variable phase
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
wave minutes
wave target_watts
wave target_tempA
wave target_tempB
wave target_tempPy
wave trip_tempA
wave trip_tempB
wave trip_tempPy
wave limit_pressure
wave trip_pressure
nvar curTargetWatts
nvar curTargetTempA
nvar curTargetTempB
nvar curTargetTempPy
nvar curTripTempA
nvar curTripTempB
nvar curTripTempPy
nvar curLimitPressure
nvar curTripPressure
nvar curPhaseMinutes
curPhaseMinutes = minutes[phase]
curTargetWatts = target_watts[phase]
curTargetTempA = target_tempA[phase]
curTargetTempB = target_tempB[phase]
curTargetTempPy = target_tempPy[phase]
curTripTempA = trip_tempA[phase]
curTripTempB = trip_tempB[phase]
curTripTempPy = trip_tempPy[phase]
curLimitPressure = limit_pressure[phase]
curTripPressure = trip_pressure[phase]
setdatafolder savedf
end
/// execute the next ramp step
///
/// calculate new setpoints and update the power supply setpoints.
/// no trips and limits checked in this function.
///
/// @return zero during ramp phase, non-zero at end of ramp phase
static function ann_ramp_step_exec(minutes_now)
variable minutes_now
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar chidSetVolts
nvar chidSetAmps
nvar curGetVolts
nvar curGetAmps
nvar curGetWatts
nvar curSetVolts
nvar curSetAmps
nvar curSetWatts
nvar minutes_start
nvar minutes_previous
nvar ramp_phase
nvar ramp_dir
variable p2 = ramp_phase
variable p1 = p2 - 1
variable minutes_phase = minutes_now - minutes_start
variable minutes_step = minutes_now - minutes_previous
wave minutes
wave target_watts
variable step_watts
variable next_volts
variable next_amps
variable next_watts
variable resistance
variable max_step_volts = 0.5
variable max_step_amps = 0.1
variable max_test_volts = 5.0
variable max_test_amps = 0.5
variable watts_complete
// calculate power step
step_watts = (target_watts[p2] - target_watts[p1]) / minutes[p2] * minutes_step
next_watts = curSetWatts + step_watts
// check end of ramp phase
if (step_watts > 0.0001)
watts_complete = next_watts > target_watts[p2]
elseif (step_watts < -0.0001)
watts_complete = next_watts < target_watts[p2]
else
watts_complete = minutes_phase >= minutes[p2]
endif
// limit power
if (watts_complete)
next_watts = curSetWatts
else
next_watts = limit(next_watts, 0, max(target_watts[p1], target_watts[p2]))
endif
// calculate voltage and current
if (next_watts >= 0.1)
curSetWatts = next_watts
if ((curGetVolts > 0.1) && (curGetAmps > 0.1))
resistance = curGetVolts / curGetAmps
next_volts = limit(sqrt(next_watts * resistance), 0, curGetVolts + max_step_volts)
next_amps = limit(sqrt(next_watts / resistance), 0, curGetAmps + max_step_amps)
else
// apply small voltage to measure the resistance
next_volts = min(curSetVolts + 0.5, max_test_volts)
next_amps = min(curSetAmps + 0.1, max_test_amps)
endif
else
curSetWatts = next_watts
next_volts = 0
next_amps = 0
endif
// change setpoints
pvPutNumber /Q chidSetVolts, next_volts
pvPutNumber /Q chidSetAmps, next_amps
pvWait 5
setdatafolder savedf
return watts_complete
end
/// check trip conditions
///
/// turn of power and stop the ramp if any of the trip conditions is met.
///
/// @return non-zero if a trip condition is detected, zero if everything is okay.
static function ann_ramp_trip(phase)
variable phase
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
variable trip = 0
wave trip_tempA
wave trip_tempB
wave trip_tempPy
wave trip_pressure
nvar curGetTempA
nvar curGetTempB
nvar curGetTempPyro1
nvar curGetTempPyroQ
nvar curGetPressure
svar ramp_message
if ((phase < numpnts(trip_tempA)) && (numtype(trip_tempA[phase]) == 0) && (curGetTempA >= trip_tempA[phase]))
ramp_message = "temperature A trip"
trip = 1
endif
if ((phase < numpnts(trip_tempB)) && (numtype(trip_tempB[phase]) == 0) && (curGetTempB >= trip_tempB[phase]))
ramp_message = "temperature B trip"
trip = 2
endif
if ((phase < numpnts(trip_tempPy)) && (numtype(trip_tempPy[phase]) == 0) && (curGetTempPyro1 >= trip_tempPy[phase]))
ramp_message = "pyrometer trip (1)"
trip = 3
endif
if ((phase < numpnts(trip_tempPy)) && (numtype(trip_tempPy[phase]) == 0) && (curGetTempPyroQ >= trip_tempPy[phase]))
ramp_message = "pyrometer trip (Q)"
trip = 4
endif
if ((phase < numpnts(trip_pressure)) && (numtype(trip_pressure[phase]) == 0) && (curGetPressure >= trip_pressure[phase]))
ramp_message = "pressure trip"
trip = 5
endif
if (trip)
ann_ramp_stop(1)
endif
setdatafolder savedf
return trip
end
/// check hold conditions
///
/// @return non-zero if a hold condition is detected, zero if everything is okay.
///
static function ann_ramp_hold(phase)
variable phase
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
nvar curGetPressure
variable hold = 0
wave limit_pressure
if ((phase < numpnts(limit_pressure)) && (numtype(limit_pressure[phase]) == 0) && (curGetPressure >= limit_pressure[phase]))
hold = 5
endif
setdatafolder savedf
return hold
end
/// check target conditions
///
/// @return non-zero if a target reached, zero otherwise.
static function ann_ramp_target(phase)
variable phase
if (phase < 1)
return 0
endif
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
variable target = 0
wave target_watts
wave target_tempA
wave target_tempB
wave target_tempPy
variable direction_up = target_watts[phase] >= target_watts[phase - 1]
nvar curGetTempA
nvar curGetTempB
nvar curGetTempPyro1
nvar curGetTempPyroQ
svar ramp_message
if ((phase < numpnts(target_tempA)) && (numtype(target_tempA[phase]) == 0))
if (direction_up)
if (curGetTempA >= target_tempA[phase])
target = 1
endif
else
if (curGetTempA <= target_tempA[phase])
target = 1
endif
endif
endif
if ((phase < numpnts(target_tempB)) && (numtype(target_tempB[phase]) == 0))
if (direction_up)
if (curGetTempB >= target_tempB[phase])
target = 2
endif
else
if (curGetTempB <= target_tempB[phase])
target = 2
endif
endif
endif
if ((phase < numpnts(target_tempPy)) && (numtype(target_tempPy[phase]) == 0))
if (direction_up)
if (curGetTempPyro1 >= target_tempPy[phase])
target = 3
endif
else
if (curGetTempPyro1 <= target_tempPy[phase])
target = 3
endif
endif
endif
if ((phase < numpnts(target_tempPy)) && (numtype(target_tempPy[phase]) == 0))
if (direction_up)
if (curGetTempPyroQ >= target_tempPy[phase])
target = 4
endif
else
if (curGetTempPyroQ <= target_tempPy[phase])
target = 4
endif
endif
endif
switch(target)
case 1:
ramp_message = "temperature A target reached"
break
case 2:
ramp_message = "temperature B target reached"
break
case 3:
ramp_message = "pyrometer 1 target reached"
break
case 4:
ramp_message = "pyrometer Q target reached"
break
endswitch
setdatafolder savedf
return target
end
/// start an annealing ramp
function ann_ramp_start()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
epics_connect()
nvar recPoint
nvar recMinutesStart
recPoint = 0
recMinutesStart = datetime / 60
wave recMinutes
wave recVolts
wave recAmps
wave recWatts
wave recTemp
wave recPressure
recMinutes = nan
recVolts = nan
recAmps = nan
recWatts = nan
recTemp = nan
recPressure = nan
nvar minutes_start
minutes_start = datetime / 60
nvar minutes_previous
minutes_previous = minutes_start
nvar ramp_phase
ramp_phase = 1
nvar ramp_status
ramp_status = 1
nvar chidSetVolts
nvar chidSetAmps
nvar chidSetOnOff
wave target_watts
pvPutNumber /Q chidSetVolts, 0.5
pvPutNumber /Q chidSetAmps, 0.5
pvPutNumber /Q chidSetOnOff, 0
pvWait 5
nvar curSetWatts
curSetWatts = target_watts[0]
ann_init_bg()
CtrlNamedBackground ann_ramp, start
CtrlNamedBackground ann_record, start
setdatafolder savedf
return 0
end
function ann_ramp_pause()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
CtrlNamedBackground ann_ramp, stop
nvar ramp_status
ramp_status = 0.5
setdatafolder savedf
return 0
end
function ann_ramp_resume()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
CtrlNamedBackground ann_ramp, start
nvar ramp_status
ramp_status = 1
setdatafolder savedf
return 0
end
/// stop a running annealing ramp
///
/// @param reset_psu 1 = turn off the power supply, 0 = do not change the power supply
/// the power supply is also turned off if the current target power is below 1 W.
function ann_ramp_stop(reset_psu)
variable reset_psu
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
CtrlNamedBackground ann_ramp, stop
CtrlNamedBackground ann_record, stop
nvar ramp_phase
ramp_phase = 0
nvar ramp_status
ramp_status = 0
nvar curTargetWatts
if (reset_psu || (curTargetWatts < 1))
nvar chidSetVolts
nvar chidSetAmps
nvar chidSetOnOff
pvPutNumber /Q chidSetVolts, 0
pvPutNumber /Q chidSetAmps, 0
pvPutNumber /Q chidSetOnOff, 1
pvWait 5
endif
setdatafolder savedf
return 0
end
function ann_display_ramp()
dfref savedf = GetDataFolderDFR()
setdatafolder $(package_path)
wave recMinutes
wave recVolts
wave recAmps
wave recWatts
wave recTemp
wave recPressure
display recWatts vs recMinutes
appendtograph /r recVolts vs recMinutes
appendtograph /r recAmps vs recMinutes
end
Window panel_ramp_gen() : Panel
PauseUpdate; Silent 1 // building window...
NewPanel /K=1 /W=(441,64,887,702) as "ramp generator"
ValDisplay valdisp0,pos={11,178},size={233,42},bodyWidth=200,title="pyro 1"
ValDisplay valdisp0,limits={0,1500,625},barmisc={10,50}
ValDisplay valdisp0,value= #"root:packages:pearl_anneal:curGetPyro1"
ValDisplay valdisp1,pos={11,233},size={233,42},bodyWidth=200,title="pyro q"
ValDisplay valdisp1,limits={0,1500,625},barmisc={10,50}
ValDisplay valdisp1,value= #"root:packages:pearl_anneal:curGetPyroQ"
ValDisplay valdisp2,pos={11,73},size={234,42},bodyWidth=200,title="lake A"
ValDisplay valdisp2,limits={0,1500,300},barmisc={10,50}
ValDisplay valdisp2,value= #"root:packages:pearl_anneal:curGetTempA"
ValDisplay valdisp3,pos={10,128},size={234,42},bodyWidth=200,title="lake B"
ValDisplay valdisp3,limits={0,1500,300},barmisc={10,50}
ValDisplay valdisp3,value= #"root:packages:pearl_anneal:curGetTempB"
ValDisplay valdisp4,pos={16,23},size={229,42},bodyWidth=200,title="watts"
ValDisplay valdisp4,limits={0,90,0},barmisc={10,50}
ValDisplay valdisp4,value= #"root:packages:pearl_anneal:curGetWatts"
ValDisplay valdisp5,pos={6,5},size={60,14},title="phase"
ValDisplay valdisp5,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp5,value= #"root:packages:pearl_anneal:ramp_phase"
ValDisplay valdisp6,pos={0,282},size={244,42},bodyWidth=200,title="pressure"
ValDisplay valdisp6,format="%.1e",limits={1e-09,1e-08,0},barmisc={10,80}
ValDisplay valdisp6,value= #"root:packages:pearl_anneal:curGetPressure"
ValDisplay valdisp7,pos={335,90},size={47,14},bodyWidth=14,title="power"
ValDisplay valdisp7,limits={0,1,0.5},barmisc={0,0},mode= 2,highColor= (60928,60928,60928),lowColor= (0,52224,0),zeroColor= (61440,61440,61440)
ValDisplay valdisp7,value= #"root:packages:pearl_anneal:curGetOnOff"
ValDisplay valdisp16,pos={262,168},size={114,14},bodyWidth=34,title="target temp pyro"
ValDisplay valdisp16,limits={0,0,0},barmisc={0,1000},mode= 2,highColor= (60928,60928,60928),lowColor= (65280,0,0),zeroColor= (0,52224,0)
ValDisplay valdisp16,value= #"root:packages:pearl_anneal:curTargetTempPy"
ValDisplay valdisp17,pos={300,4},size={80,14},bodyWidth=14,title="power supply"
ValDisplay valdisp17,limits={0,1,0.5},barmisc={0,0},mode= 2,highColor= (0,52224,0),lowColor= (65280,0,0),zeroColor= (61440,61440,61440)
ValDisplay valdisp17,value= #"root:packages:pearl_anneal:psu_connected"
ValDisplay valdisp18,pos={317,21},size={64,14},bodyWidth=14,title="lakeshore"
ValDisplay valdisp18,limits={0,1,0.5},barmisc={0,0},mode= 2,highColor= (0,52224,0),lowColor= (65280,0,0),zeroColor= (61440,61440,61440)
ValDisplay valdisp18,value= #"root:packages:pearl_anneal:ls_connected"
ValDisplay valdisp19,pos={318,38},size={64,14},bodyWidth=14,title="pyrometer"
ValDisplay valdisp19,limits={0,1,0.5},barmisc={0,0},mode= 2,highColor= (0,52224,0),lowColor= (65280,0,0),zeroColor= (61440,61440,61440)
ValDisplay valdisp19,value= #"root:packages:pearl_anneal:pyro_connected"
ValDisplay valdisp20,pos={293,52},size={91,14},bodyWidth=14,title="vacuum system"
ValDisplay valdisp20,limits={0,1,0.5},barmisc={0,0},mode= 2,highColor= (0,52224,0),lowColor= (65280,0,0),zeroColor= (61440,61440,61440)
ValDisplay valdisp20,value= #"root:packages:pearl_anneal:vac_connected"
ValDisplay valdisp21,pos={341,76},size={41,14},bodyWidth=14,title="ramp"
ValDisplay valdisp21,limits={0,1,0.5},barmisc={0,0},mode= 2,highColor= (0,52224,0),lowColor= (61440,61440,61440),zeroColor= (0,34816,52224)
ValDisplay valdisp21,value= #"root:packages:pearl_anneal:ramp_status"
ValDisplay valdisp8,pos={278,120},size={100,14},title="target watts"
ValDisplay valdisp8,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp8,value= #"root:packages:pearl_anneal:curTargetWatts"
ValDisplay valdisp9,pos={278,137},size={100,14},title="target temp A"
ValDisplay valdisp9,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp9,value= #"root:packages:pearl_anneal:curTargetTempA"
ValDisplay valdisp10,pos={278,152},size={100,14},title="target temp B"
ValDisplay valdisp10,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp10,value= #"root:packages:pearl_anneal:curTargetTempB"
ValDisplay valdisp11,pos={271,266},size={100,14},title="limit pressure"
ValDisplay valdisp11,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp11,value= #"root:packages:pearl_anneal:curLimitPressure"
ValDisplay valdisp12,pos={261,205},size={100,14},title="trip temp A"
ValDisplay valdisp12,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp12,value= #"root:packages:pearl_anneal:curTripTempA"
ValDisplay valdisp13,pos={262,224},size={100,14},title="trip temp B"
ValDisplay valdisp13,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp13,value= #"root:packages:pearl_anneal:curTripTempB"
ValDisplay valdisp14,pos={276,242},size={100,14},title="trip pyro"
ValDisplay valdisp14,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp14,value= #"root:packages:pearl_anneal:curTripTempPy"
ValDisplay valdisp15,pos={273,309},size={100,14},title="ramp interval"
ValDisplay valdisp15,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp15,value= #"root:packages:pearl_anneal:curPhaseMinutes"
Button b_start,pos={15,362},size={50,20},proc=PearlAnneal#bp_ramp_start,title="start"
Button b_start,fColor=(0,52224,0)
Button b_pause,pos={75,362},size={50,20},proc=PearlAnneal#bp_ramp_pause,title="pause"
Button b_stop,pos={135,362},size={50,20},proc=PearlAnneal#bp_ramp_stop,title="stop"
Button b_stop,fColor=(52224,0,0)
Button b_edit,pos={195,362},size={50,20},proc=PearlAnneal#bp_ramp_edit,title="edit"
ValDisplay valdisp11_1,pos={274,282},size={100,14},title="trip pressure"
ValDisplay valdisp11_1,limits={0,0,0},barmisc={0,1000}
ValDisplay valdisp11_1,value= #"root:packages:pearl_anneal:curTripPressure"
TitleBox title0,pos={16,334},size={400,20}
TitleBox title0,variable= root:packages:pearl_anneal:ramp_message,fixedSize=1
DefineGuide UGH0={FT,390}
String fldrSav0= GetDataFolder(1)
SetDataFolder root:packages:pearl_anneal:
Display/W=(82,153,246,462)/FG=(FL,UGH0,FR,FB)/HOST=# recTemp vs recMinutes
AppendToGraph/R recPressure vs recMinutes
AppendToGraph/L=power recWatts vs recMinutes
NewFreeAxis/O/L power
SetDataFolder fldrSav0
ModifyGraph margin(left)=80
ModifyGraph rgb(recPressure)=(0,0,65280),rgb(recWatts)=(0,39168,0)
ModifyGraph log(right)=1
ModifyGraph mirror(bottom)=2
ModifyGraph nticks=3
ModifyGraph minor=1
ModifyGraph axThick=0.5
ModifyGraph axRGB(left)=(65280,0,0),axRGB(right)=(0,0,65280),axRGB(power)=(0,39168,0)
ModifyGraph tlblRGB(left)=(65280,0,0),tlblRGB(right)=(0,0,65280),tlblRGB(power)=(0,39168,0)
ModifyGraph alblRGB(left)=(65280,0,0),alblRGB(right)=(0,0,65280),alblRGB(power)=(0,39168,0)
ModifyGraph lblPos(left)=42,lblPos(power)=35
ModifyGraph lblLatPos(left)=1
ModifyGraph btLen=4
ModifyGraph freePos(power)=48
Label left "temperature (K)"
Label bottom "time (minutes)"
Label right "pressure (mbar)"
Label power "power (W)"
SetAxis/E=1 power 0,80
RenameWindow #,G0
SetActiveSubwindow ##
EndMacro
static function bp_ramp_start(ba) : ButtonControl
struct WMButtonAction &ba
switch( ba.eventCode )
case 2: // mouse up
dfref ann_df = $(package_path)
nvar /sdfr=ann_df ramp_status
if (ramp_status < 0.4)
ann_ramp_start()
endif
break
case -1: // control being killed
break
endswitch
return 0
end
static function bp_ramp_pause(ba) : ButtonControl
struct WMButtonAction &ba
switch( ba.eventCode )
case 2: // mouse up
dfref ann_df = $(package_path)
nvar /sdfr=ann_df ramp_status
if (ramp_status > 0.6)
ann_ramp_pause()
Button b_pause, win=$(ba.win), title="resume"
else
ann_ramp_resume()
Button b_pause, win=$(ba.win), title="pause"
endif
break
case -1: // control being killed
break
endswitch
return 0
end
static function bp_ramp_stop(ba) : ButtonControl
struct WMButtonAction &ba
switch( ba.eventCode )
case 2: // mouse up
ann_ramp_stop(1)
break
case -1: // control being killed
break
endswitch
return 0
end
static function bp_ramp_edit(ba) : ButtonControl
struct WMButtonAction &ba
dfref savedf = GetDataFolderDFR()
switch( ba.eventCode )
case 2: // mouse up
setdatafolder $(package_path)
wave minutes
wave target_watts
wave target_tempA
wave target_tempB
wave target_tempPy
wave trip_tempA
wave trip_tempB
wave trip_tempPy
wave limit_pressure
wave trip_pressure
edit /k=1 minutes, target_watts
appendtotable target_tempA, trip_tempA
appendtotable target_tempB, trip_tempB
appendtotable target_tempPy, trip_tempPy
appendtotable limit_pressure,trip_pressure
break
case -1: // control being killed
break
endswitch
setdatafolder savedf
return 0
end