#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