Cryogenic SMS PSU design
Requirements
These are used to power the HIFI Cryomag. There are four, one (higher max current) controls the main switched cryomagnet, the other three (lower max current) control the X, Y, and Z ‘shim’ coil magnets. They are capable of driving the main cryomagnet up to a field of ~4.9 Tesla.
Magnet cool downs typically take several days, warm ups take a similar length of time. It usually sits at 3-4K, the temperature is monitored by a Keithley 2700.
They are connected via USB to serial RS-232 directly to the NDXHIFI_CRYOMAG PC.
All commands have a longhand and a shorthand version which can be found in the manual on our network share. The emulator has been made such that it can deal with the longhand or the shorthand, since it must be tested against the VI for compatibility, and the VI uses shorthand commands (vs the IOC which uses longhand).
Commands can reply with a myriad of options. Some of these are time stamped, some are not. These are also listed in the manual on the network share.
IOC Structure
The IOC itself has multiple layers. On the surface, there is an EPICS db file with some values
populated by get commands in a stream device proto file, some setpoints that eventually lead
to set commands in that same proto, and a series of calc records. These PVs are the ones the
OPI reads and writes to. For basic interactions, this is all you need to know.
However, behind the scenes you may notice that many of the user-facing setpoints don’t actually
directly write out anywhere, and the ones that do are not exposed to users via the OPI, and also
don’t seem to be written to/triggered from anywhere in the db. This is because there is a driver
in CRYOSMSDriver.cpp built off of asynPortDriver (hereafter referred to as “the driver”)
sitting on top of the IOC, which is looking at what the user is setting and what the device is
currently doing, then writing out to relevant “hidden” setpoints (:_SP rather than :SP).
This allows it to get the device to an end-state the user wants without them having to know
about all the intermediate steps needed to get there, and also making sure the magnet doesn’t
quench.
Attached to the driver is a queued state machine, implemented with a thread that decides when to pop events from a queue to an implementation of boost’s meta state machine. This part of the system is mostly just to ensure all the reads, writes, waits, etc. happen consistently and in the right order.
Safety is the number one concern of this system, and if there appears to be an overly complicated implementation for something, chances are that if it wasn’t there it could lead to a magnet quench. This needs to happen at IOC level because the PSU itself will happily quench the magnet if you give it the wrong commands.
Initialisation
Macros
The IOC has the following macros. A small number (e.g. T_TO_A) are used by the db, but the
majority are only used by the driver. At the bottom of the main file, you can see them
being loaded via the usual method for an asynPortDriver. Upon initialising the driver,
these values are loaded into a map of the form MACRO_NAME: [macro_value] called envVarMap, which
other methods reference whenever they need to check a macro.
Macro |
Description |
Defaults |
|---|---|---|
MAX_CURR |
Define the maximum current that is allowed |
|
T_TO_A |
The conversion value between Tesla and Amps for the system |
|
MAX_VOLT |
Set the maximum voltage for the system |
|
WRITE_UNIT |
Which unit to write to the PSU in |
Amps |
WRITE_UNIT_TIMEOUT |
How long in seconds to wait after write unit is changed before resetting it to |
|
DISPLAY_UNIT |
Which unit will be used when displaying the value from the PSU |
Gauss |
RAMP_FILE |
Location of file containing ramp rates |
|
ALLOW_PERSIST |
Whether or not to allow setting of persistent values |
No |
USE_SWITCH |
Whether or not to monitor and set switches on and off |
No |
SWITCH_TEMP_PV |
PV address of switch temperature |
|
SWITCH_HIGH |
The value at which the switch will be considered warm |
3.7 |
SWITCH_LOW |
The value at which the switch will be considered cold |
3.65 |
SWITCH_STABLE_NUMBER |
The number of readings past a threshold needed before the switch can be considered warm/cold |
10 |
HEATER_TOLERANCE |
The tolerance between the magnet and lead currents before considering them as close enough to allow the leads to warm |
0.2 |
SWITCH_TIMEOUT |
The time to allow for the switch to warm or cold after turning the heater on/off before considering there to be an error situation |
300 |
SWITCH_TEMP_TOLERANCE |
needs clarification |
|
HEATER_OUT |
The heater output to be used |
|
USE_MAGNET_TEMP |
Whether to act if the Magnet Temperature is out of range |
No |
MAGNET_TEMP_PV |
The PV address of the magnet temperature |
|
MAX_MAGNET_TEMP |
Maximum allowed temperature of magnet |
5.5 |
MIN_MAGNET_TEMP |
Temperature below which there is potentially an error reading temperature from the magnet |
1 |
COMP_OFF_ACT |
Whether to act if magnet temperature is out of range |
No |
NO_OF_COMP |
Number of compressors in the system |
2 |
COMP_1_STAT_PV |
The PV address for the status of the first compressor |
|
COMP_2_STAT_PV |
The PV address for the status of the second compressor |
|
FAST_RATE |
The ramp rate to use for the fast ramps |
0.5 |
FAST_PERSISTENT_SETTLETIME |
The number of seconds to settle after a ramp fast to persist |
5 |
PERSISTENT_SETTLETIME |
The number of seconds to settle after a ramp to persist |
60 |
FILTER_VALUE |
The value to use to filter the target reached calculation |
0.1 |
FAST_FILTER_VALUE |
The value to use to filter the target reached calculation when ramping fast to a target |
1 |
NPP |
The npp value when calculating whether or not the target has been reached |
Startup Checks
As some of the checks needed to be run upon initialisation are only possible after the db has
initialised, they are run in the onStart() method that only runs when the INIT PV (with
PINI 1) writes out. This method checks various macros to determine whether certain modes of
operation should be enabled/disabled:
All writes to the device will be disabled in any of these situations:
T_TO_Anot supplied andCRYOMAGNETisYesMAX_CURRnot suppliedALLOW_PERSISTis on, but any of the subsequently required variables (FAST_FILTER_VALUE, FILTER_VALUE, NPP, FAST_PERSISTENT_SETTLETIME, FAST_RATE) are missingUSE_SWITCHis on, but any of the subsequently required variables (SWITCH_TEMP_PV, SWITHCH_HIGH, SWITCH_LOW, SWITCH_STABLE_NUMBER, HEATER_TOLERANCE, SWITCH_TIMEOUT, SWITCH_TEMP_TOLERANCE, HEATER_OUT) are missingUSE_MAGNET_TEMPis on, but any of the subsequently required variables (MAGNET_TEMP_PV, MAX_MAGNET_TEMP, MIN_MAGNET_TEMP) are missingCOMP_OFF_ACTis on, but any of the subsequently required variables (NO_OF_COMP, MIN_NO_OF_COMP, COMP_1_STAT_PV, COMP_2_STAT_PV) are missing
Additionally, the driver will initialise various variables and send several commands to the PSU based on user provided macros:
The Tesla-Amps conversion rate will be set to
T_TO_AifCRYOMAGNETis set toYesThe maximum allowed current will be set to
MAX_CURRThe maximum allowed voltage will be set to
MAX_VOLTThe heater output will be set to
HEATER_OUTThe file named in
RAMP_FILEwill be used to populate a ramp table, consisting of values for the fastest safe ramp within a field range, and the maximum field strength of that range.More info on ramp rates in Performing a Ramp
The ramp rate will be set to an appropriate value read from the ramp table based on the current value of the field
Finally, a thread containing the event queue, a thread which continually checks certain values, and the meta state machine are all started
If no errors occur throughout this process, it will then write 1 back to INIT, which will show
as “Startup complete”. If the IOC doesn’t seem to be starting correctly, check this PV to figure
out if you need to debug whether/where onStart() is hitting an error.
Unit Conversion
The PSU defaults to displaying, and sending/receiving via its USB interface, the values for
current it is outputting to the coils in terms of Amps. It can, however, also output the field
strength in Tesla that will be achieved for a given current if it is provided with a conversion
factor from current to field strength. This conversion factor will likely vary from magnet to
magnet as it is determined by the geometry, material properties, etc. of the magnet itself. We
tell the PSU which to display on its front panel and use for communication through the
OUTPUTMODE:_SP PV based on WRITE_UNIT.
Note
The shim magnets on HIFI are also operated using these PSUs and this IOC, however the
Tesla-Amps conversion factor is far outside what the PSU considers a valid range (as these are
not cryomagnets). For these, we set CRYOMAGNET to “No”, to tell our IOC not to even try
writing T_TO_A to the PSU. Instead, we only read/write in Amps and perform all conversions in
the IOC.
Users additionally often wish to have the PSU output shown as the resulting field strength in
Gauss (10,000 Gauss = 1 Tesla), which is set to be the default value displayed. In order to convert
between values read from the device, you will see several calcs in the db converting from
[value]:RAW, which will be in WRITE_UNIT, to [value]:AMPS, [value]:FIELD:TESLA, and [value] :FIELD:GAUSS before finally the user-facing [value] selects which of these to use based on
DISPLAY_UNIT.
In the driver meanwhile, values that are read from user-facing PVs in the db are taken
to be supplied in terms of DISPLAY_UNIT, and are converted back and forth between values based on
need for certain calculations, before being sent to the device in WRITE_UNIT.
Instrument scientists also wished to be able to temporarily change WRITE_UNIT for use cases
which were never made clear (as of the author Lilith’s memory in Nov 2025, this may be so that
they can tinker with values physically on the PSU?). Nonetheless, this functionality is
available to them. The PV OUTPUTMODE:SP lets users change the write unit for a time period set
by WRITE_UNIT_TIMEOUT, which is propagated through the driver by changing the value
mapped in envVarMap before writing to the device using OUTPUTMODE:_SP. At the end of the
macro-defined timeout, the checks thread will automatically change everything back to the
WRITE_UNIT specified by macros.
Event Queue
The event queue is a double-ended queue which is used to queue up state transitions to be
performed on the state machine. It is processed in a while loop on the eventQueueThread, which
determines when to pop an event and push it to the state machine based on factors that vary by
whichever state the state machine is currently in (for example, making sure the PSU is at a ramp
target before leaving the “ramping” state). Throughout the driver, events are pushed to either
end of this queue (usually the back) by various methods.
Within this queue, individual events are stored inside variant objects from the boost variant
library in order to preserve typing (boost msm fires events based on the typing of the object
being passed to it).
Queued State Machine
The IOC makes use of a queued state machine, based upon the meta state machine library from
boost. This is stored in a separate file from the driver, QueuedStateMachine.h, as it is
header-based.
Here, possible states, events (things that prompt state changes) and actions (code to perform on
given state transitions) are defined, alongside a table of possible transitions and the
respective actions to process when performing them.
Note
To avoid a circular dependency, the state machine imports a dummy version of the main driver,
defined in StateMachineDriver.h, which the main driver inherits from and then overloads the
methods of.
Sets Sent to the Driver
As most of the values used by the driver are read directly by it from the db, there are only a handful of PVs that actually send values to the driver themselves:
Output Mode
As described in Unit Conversion, temporarily tells the PSU to communicate in different units.
Init
Starts the initialisation process described in Startup Checks. This only happens once per boot.
Heater response deconstruction
The PSU’s reply to asking whether the magnet heater is on can get complicated, so it is sent to c++ to be dealt with instead of using stream device (It’s possible that this could all be done in stream device, and if you want to do this, by all means make a ticket).
The response is of the form HEATER STATUS: {OFF|ON} if it is either on or off with the magnet
at 0T, or HEATER STATUS: OFF AT {value} {units} if it is off with the magnet holding a field
of {value} in units of {units}. This is then filtered through to relevant heater readback
and OUTPUT:PERSIST PVs. The PV OUTPUT:COIL then switches which pv it reads from based on
whether the heater is on or off (on - OUTPUT, technically the current through the magnet’s
leads; off - OUTPUT:PERSIST), as it represents the definitive value of the magnet’s output.
Important
If the heater is off and the magnet has current running through it, this means that is in superconducting or “persistent” mode. If you ramp the current in the leads away from this (e.g. to 0 if the user selects to ramp leads to 0 after ramping the magnet), the leads MUST be ramped back to the persistent current before switching the heater back on, else the magnet will quench as the previously superconducting current rapidly changes to meet the current through the leads.
Start
Tells the driver to perform a ramp based on settings provided to other PVs. See Performing a Ramp
Pause
Setting this to 1 pushes a “pause” event to the state machine, then suspends the event queue
thread. If the PSU is ramping, the “pause” event will tell it to pause, and will put the state
machine into the paused state which can only be left by resuming or aborting.
Immediately responds with an “acknowledge” message.
Setting this to 0 will place a “resume” event at the front of the event queue, then restart the event queue thread to immediately process it, putting the state machine back into a ramping state. The ramp will then continue as before.
Abort
Similar to Pause, when abort is set to 1 it will process an “abort” event immediately, however it will not suspend the state machine. It will then attempt to stop any warming or cooling by queuing a “cool” or “warm” respectively. If the PSU was instead ramping, it will pause the ramp, set the PSU ramp endpoint to the current output, empty the rest of the event queue, then resume so that the PSU immediately accepts that the ramp has reached its target at its current output.
Performing a Ramp
When Start is processed, the driver will attempt to construct a queue of state
transitions in the event queue which will make the properties of the magnet be what the user has
specified. this is done by the method setupRamp()
First, the driver checks whether the magnet is currently in persistent mode. If it is, it will queue a fast ramp of the leads to bring them to the persistent current if this is needed, then it will warm the magnet up to take it out of persistent mode.
Note
The leads can be ramped much faster than the coils, so a separate ramp rate set by the
macro FAST_RATE is used when ramping only them.
Next, the driver will take the current field strength in the magnet as the start point and the
desired field strength set in OUTPUT:SP as the end point. It will then compare these points to
the ranges of field strengths in the ramp table. If both are within the same range (and same
polarity, more on that later), it will simply queue a ramp event set to the end point at the
relevant ramp rate for that range, then a “target reached” event. If however, the start and end
points are in different field ranges in the ramp table, it will queue up ramp events to each of the
field range maxima, at ramp rates that correspond to the ranges that each ramp will be going
through, in the order that the maxima will be encountered in on the route between the start and
end point. After each of these individual ramp events, it will also queue a “target reached”
event for immediately after it.
In the cases where the start point and end point have opposite polarities, the above process will be performed first by going from the start to 0, then from 0 up to the end point.
Finally, the driver will queue a cool-down event if the user wants the magnet to be persisting at the end point, and will ram the leads to 0 if the user wants that to happen also.
Ramp Events
Ramps are passed to the state machine with four values:
their endpoint
the ramp rate to use to get the endpoint
the polarity of the endpoint (only needed when going from 0)
ramp type (e.g. whether this is a fast ramp of the leads)
When processed, the driver will set the PSU’s output midpoint (in the context of 0 - arbitrary midpoint - max; midpoint doesn’t have to be half of max and rarely is) and its ramp rate, then tell it to ramp to the midpoint. If a ramp’s start point is 0, it will first make sure the polarity set on the PSU is the same as the endpoint’s polarity.
After being processed, the event queue thread will wait until the PSU’s output is within a tolerance of the ramp’s endpoint and the PSU is reporting that it is holding at target, then process the target reached event.
Target Reached Events
The events which are queued after each ramp mostly exist to better delineate between ramps in
the state machine. The other function of them is that they will each check if the en point
their individual ramp reached is the end point specified by the user - the final end point. If
it is, they will make the driver wait for a period of time specified by relevant hold/settle
time macros before letting it carry on with any further events.
Statuses
There are a variety of statuses that can be put to the STAT pv. Some of them contain extra
contextual messages, but broadly there are the following cases:
Ready - The driver and PSU both are not currently doing anything, and are ready for user inputs
Ramping - The PSU is ramping its output
Paused - Either the user or an internal check has caused a ramp to pause
Holding/Settling - The PSU claims to have finished what it was doing, the driver is waiting for things to settle briefly before it either goes into ready or starts its next task
Cooling/Warming - The heater has recently been switched off or on, we are waiting until its temperature is consistently below/above
SWITCH_LOWorSWITCH_HIGHrespectivelyAborting - Something has caused the driver to abort, it is in the process of stopping everything
Processing - The driver only goes into this status when it can no longer say that its previous status is valid, but it has yet to achieve a subsequent status. For example, when
START:SPis set, it goes from Ready to Processing, then goes to Ramping once the first ramp has started. If the driver is reporting this status for a long time, it means something has gone wrong in its queue somewhere and it needs restarting
The READY PV
The READY PV is simply a boolean pv that simplifies the STAT PV. Either the driver is ready,
and can accept commands, or it is not.
Automatic Pausing/ Aborting
The “checks” thread created during startup checks will run through its loop twice per second, decrementing/checking against any ongoing timers, pausing the ramps for safety if not enough compressors are active, and checking for quenches.
Quenches
The magnet can quench if it is ramped too aggressively, or if its temperature drifts too high. If the PSU detects a quench, it reports that it thinks the magnet has quenched. The IOC will interpret this and halt all queued actions. It will stay in this state until the IOC is restarted.
Important
The PSU lacks any safety measures to prevent you from ordering it to perform actions that will quench a magnet. It will happily turn on the heater when the magnet is at 10T and the leads are at 0. This is why the driver is so careful about everything it sends
Behind the scenes PVs
When the driver sends a value to the PSU, it does so by setting a relevant PV ending in :_SP,
which then sends a command to the PSU via stream device. This does mean that there are all sorts
of PVs which can be tinkered with to sidestep the driver and manually define/start ramps or
toggle the heater. Yes, you can use these if you really want to, but only do so if you are
absolutely sure you’re not quenching the magnet. That cumbersome driver is there for a reason.
Other IOCs that Interface with CRYOSMS
If you write any other IOCs that interface with the CRYOSMS, please make note of them here. If you use Behind the scenes PVs in them please also write a justification of why your use-case is safe.
HIFIMAGS
Most of the magnet control on HIFI is done via the HIFIMAGS IOC, which is a series of db files that get pointed to 4 CRYOSMS IOCS (main magnet, shims for x y and z axis) and a snl state machine which controls when you are and aren’t allowed to change set points within HIFIMAGS. There is an OPI that goes alongside this IOC, which helps scientists consolidate the main numbers they care about from across the system in one place.
HIFI’s Zero Field Controller
HIFI also has a zero field IOC, which uses Behind the scenes PVs to perform tiny, quick ramps on the shim coils to compensate for stray magnetic field. This is safe because it is only the non-cryomagnet coils being used. Additionally, if this IOC is in use, the individual CRYOSMS IOCs being linked to will lock all other input, and refuse to make heater changes.