Helium Liquefaction Documentation
This Documentation shall explain how certain Device Drivers for the EPICS IOC for the Helium Liquefaction works. It shall also serve as an example on how to set up records for interacting with Serial or Ethernet devices. Therefor, for the sake of completeness, all other steps needed to setup the IOC are also included. This IOC requires:
EPICS Base (version 7.x)
Support module Asyn to handle device connections
Support module StreamDevice to handle device communication
For the rest of the documentation, the IOC will be named HeliumLiquefaction
. For a differently named
IOC, HeliumLiquefaction
will be needed to be replaced by the chosen name for the IOC.
Table of Content
Overview
Each different device has his own .db file and for device communication a .proto file with the same name. In this documentation, the focus will lie on the two more complicated records:
- Calculation of the fill level of the Helium tank in Liters via Interpolation
- Handling unsolicited Data from the Liquefier
It is assumed a newly created, empty IOC is already set up with.
Section 1: Filllevel Calculation
Chapter 1: Creating the FillLevel.db and FillLevel.proto
First, a short explanation of how the fill level calculation works: Two sensors, being vertically offset and sensor 2 being the one closer to the bottom, cover the entire tank and measure their percentage of being submerged in liquid helium, giving back a value of 100% if they are fully submerged. The height in millimeters is then calculated depending of the submersion:
If Sensor 2 reads 100% or Sensor 1 reads 5% and Sensor 2 95%, then fill level
is 790 + 785 * <Sensor 1 value>
, else fill level is 785 * <Sensor 2 value>
.
Calculation of fill level in liters is then done via Interpolation.
The fill level record therefor needs to do the following:
Read both sensors
Calculate the fill level in millimeters
Interpolate
Step 1: Reading the sensors
The first step is to understand the device protocoll, i.e. how to talk to it and how responses are formatted. Check the device manual for instructions. Also, with programms like CuteCom (their GitHub) communications can be tested with a graphical interface.
In this case, the device has only the two sensors connected and responds to the request (R1 CR
) (asking for the sensor
1 value) with R+<value> CR
.
CR
stands for carriage return and is in various combinations with LF
(line feed) the way almost all serial devices mark the end of a message.
If the sensor reads 5%, the return will look thusly: R+00050 CR
.
FillLevel.proto
The FillLevel.proto
, belonging inside the folder HeliumLiquefaction/HeliumLiquefactionApp/Db
, for the fill level should look like this:
ReplyTimeout = 2000;
ReadTimeout = 100;
OutTerminator = CR;
InTerminator = CR;
sensor1
{
out "R1";
in "R+%d";
}
sensor2
{
out "R2";
in "R+%d";
}
An explanation for the 4 lines at the top can be
found in the StreamDevice documentation, they manage settings for device
communications.
sensor1 {}
and sensor2 {}
handle the device communication itself.
out "R1"
sends (therefor out
) a command R1 CR
to the device. CR
is appended by StreamDevice due to being set as
the OutTerminator
. With in "R+%d"
the input is processed. R+
matches the R+
at the start of the device reply, %d
is then
set to the value sent, in the example return with 5% sensor reading it will be 50
. Thats the value (here an integer) returned to the
record in the .db
file.
It is important to use %d
, so an integer or LONG
value here, not a DOUBLE
value via %f
. Otherwise, a needed value conversion explained in the following paragraph can not be done. The reason will be explained later in the Chapter Interpolation.
FillLevel.db
The two records for the sensors in the FillLevel.db
, same folder as FillLevel.proto
should look like this:
For conciseness, only one record is written here, since they only differ by sensor1
instead being sensor2
.
record(ai, FillLevel:sensor1)
{
field (DESC, "Read Resistance of Sensor 1")
field (DTYP, "stream")
field (INP, "@FillLevel.proto sensor1 $(PORT)")
field (SCAN, "Passive")
field (LINR, "LINEAR")
field (ASLO, 0.001)
field (EGU, "%")
}
A very thorough explanation of the basics of the different fields and their possible values are found in the official EPICS Documentation.
In the context of this IOC, only why certain values are chosen will be explained:
DTYP
is set tostream
, since StreamDevice is used for device communication.INP
is used by the device support module, i.e. StreamDevice, to obtain the value.@FillLevel.proto
specifies the file,sensor1
the function inside the file.SCAN
is set toPassive
, since the later explainedcalc-Record
will call these records and process them. Be aware that the option"Passive"
is case sensitive.ASLO
is needed, since the value returned by the device is off by a factor of 1000. A value of 5%, i.e.0,05
is returned as00050
from the device, so this record would be set to50
.LINR
hast to be set, otherwiseASLO
is ignored. Again,"LINEAR"
is case sensitive.EGU
is set to"%"
since%
is the unit of the value.
With this, the sensor reading is complete.
Step 2: Fill level calculation
To calculate the fill level, a calcout
record is used. This record uses up to 12 inputs and writes the result of the calculation in another record.
The record should look like this:
record(calcout, FillLevel:heightcalc)
{
field (DESC, "Calculates Filllevel")
field (SCAN, "1 second")
field (PINI, 1)
field (CALC, "(((B)>=(1) || ((A)>=(0.05) && (B)>=(0.95)))?((785 * A) + 790):(785 * B))")
field (INPA, "FillLevel:sensor1 PP")
field (INPB, "FillLevel:sensor2 PP")
field (OOPT, 1)
field (OUT, "FillLevel:filllevelMM.VAL")
field (DOPT, 0)
field (LOPR, 0.0)
field (HOPR, 1487.0)
field (FLNK, "FillLevel:scaling")
}
SCAN
is set to1 second
(case sensitive) to make the calculation every second. The other standard scan options (and how to create other time intervals) are listed in the EPICS documentation.PINI
should almost always be set to1
ifSCAN
is set to a time period. This forces the record to be processed on IOC startup. Otherwise, the record is first processed after the time set inSCAN
. While this doesn’t matter too much for this short period, depending on the record this might be a long time.INPA
andINPB
tells this record where to get its values from, here from the recordsFillLevel:sensor1
and2
. With the optionPP
, these records are processed first before getting their values, to get present values and not old ones.CALC
is the formula to calculate the fill level.A
andB
are replaced with the values fromINPA
andINPB
respectively. Since the actual calculation depends on the sensor readings, firstly an if/else condition is set and then the correct calculation is made.The calculation logic is already at the beginning of the chapter. The implementation with logical operators is explained in the EPICS documentation for the
calcout
record, but should look familiar to anyone already knowledgeable with programming.after the
?
, the different calculations are listed. If the if-condition is met,(785 * A) + 790)
is used, otherwise the formula after the:
,(785 * B)
is used instead.
OUT
specifies the record that is written to, here to the fieldVAL
of the recordFillLevel:filllevelMM
.FLNK
calls the recordFillLevel:scaling
(explained later) to be processed after this one is finished with all calculations.
The record FillLevel:filllevelMM
is more or less a dummy record, which only serves the purpose of saving the fill level in a concise way.
record(ai, FillLevel:filllevelMM)
{
field (DESC, "Filllevel of the Heliumtank in mm")
field (DTYP, "Soft Channel")
field (SCAN, "Passive")
field (EGU, "mm")
}
DTYP
is set toSoft Channel
, a default device type.
Now, the record FillLevel:filllevelMM
contains the current level of the helium tank in millimeters.
Step 3: Interpolation
Theoretically, a single record would be all that is needed for the interpolation. It would read the value of the FillLevel:filllevelMM
and interpolate with a breakpoint table.
However, non-linear interpolations with these values here are a bit tricky in EPICS and need a bit of a workaround.
Interpolation is a type of Linearization (used in the FillLevel:sensor1
record to divide the value by 1000), that uses the RVAL field of a record.
The problem here is that the RVAL field can only be a LONG
value, i.e. integers.
However, the way the fill level is calculated produces a DOUBLE
value.
So here, both the FillLevel:heightcalc
and FillLevel:filllevelMM
records will have an RVAL
value of 0
, and a ai
record that uses their values as INP
would also only save it in their VAL
field
Any sort of Linearization will therefor return a value of 0
. Forcing the fill level to be an integer would lose all decimal digits, which in this application would mean during normal fill levels, only changes of more than 3 liters would be able to be detected.
To circument this, an Analog Output record (ao
) is used that:
gets the value of
FillLevel:filllevelMM
.multiplies it by a factor of 10n,
n
depending of the needed accuracy.writes the new value to the
RVAL
field of an Analog Input record (ai
), which then interpolates.
In this example, n=3
is enough, shifting the comma of the float value by 3, since the fill level in mm can never have more than 3 decimal digits.
The lowest value of the sensors is 0,1%, i.e. 0,001. 785 * 0,001 = 0,785, i.e. 3 decimal digits.
The ao
record should therefor look like this:
record(ao, FillLevel:scaling)
{
field (DTYP, "Raw Soft Channel")
field (OMSL, "closed_loop")
field (SCAN, "Passive")
field (OIF, "Full")
field (DOL, "FillLevel:filllevelMM.VAL PP")
field (OUT, "FillLevel:filllevelL.RVAL PP")
field (LINR, "LINEAR")
field (ASLO, 0.001)
}
DTYP
needs to beRaw Soft Channel
, since theRVAL
value is needed to be written toOUT
.OMSL
set toclosed_loop
enables the input fieldDOL
.OIF
is set toFull
because otherwise theVAL
field is always incremented.DOL
is where the value is taken from, so comparable theINP
field forout
records.OUT
is where is written to. Its important to write to theRVAL
field to enable interpolation in the target record. ThePP
flag is set to process the receiving record.LINR
needs to be set soASLO
is used.ASLO
is set to 10-n. This is because the way this record works is by reading the field specified by theDOL
field and saving that value in itsVAL
field. Then, due to being aRaw Soft Channel
, calculates the outgoingRVAL
. SinceVAL = RVAL * ASLO
,RVAL = VAL / ASLO
.
The receiving record then should look like this:
record(ai, FillLevel:filllevelL)
{
field (DESC, "Filllevel of the Heliumtank in L")
field (DTYP, "Raw Soft Channel")
field (SCAN, "Passive")
field (LINR, "filllevel")
field (EGU, "l")
}
LINR
set tofilllevel
tells the record to use the breakpoint tablefilllevel
DTYP
needs to beRaw Soft Channel
again to useRVAL
.
Step 4: Entire FillLevel.db
The completed file should then look like this:
record(ai, FillLevel:sensor1)
{
field (DESC, "Read Resistance of Sensor 1")
field (DTYP, "stream")
field (INP, "@FillLevel.proto sensor1 $(PORT)")
field (SCAN, "Passive")
field (LINR, "LINEAR")
field (ASLO, 0.001)
field (EGU, "%")
}
record(ai, FillLevel:sensor2)
{
field (DESC, "Read Resistance of Sensor 2")
field (DTYP, "stream")
field (INP, "@FillLevel.proto sensor2 $(PORT)")
field (SCAN, "Passive")
field (LINR, "LINEAR")
field (ASLO, 0.001)
field (EGU, "%")
}
record(calcout, FillLevel:heightcalc)
{
field (DESC, "Calculates Filllevel")
field (SCAN, "1 second")
field (PINI, 1)
field (CALC, "(((B)>=(1)||((A)>=(0.05) && (B)>=(0.95)))?((785 * A) + 790):(785 * B))")
field (INPA, "FillLevel:sensor1 PP")
field (INPB, "FillLevel:sensor2 PP")
field (OOPT, 1)
field (OUT, "FillLevel:filllevelMM.VAL")
field (DOPT, 0)
field (LOPR, 0.0)
field (HOPR, 1487.0)
field (FLNK, "FillLevel:scaling")
}
record(ai, FillLevel:filllevelMM)
{
field (DESC, "Filllevel of the Heliumtank in mm")
field (DTYP, "Soft Channel")
field (SCAN, "Passive")
field (EGU, "mm")
}
record(ao, FillLevel:scaling)
{
field (DTYP, "Raw Soft Channel")
field (OMSL, "closed_loop")
field (SCAN, "Passive")
field (OIF, "Full")
field (DOL, "FillLevel:filllevelMM.VAL PP")
field (OUT, "FillLevel:filllevelL.RVAL PP")
field (LINR, "LINEAR")
field (ASLO, 0.001)
}
record(ai, FillLevel:filllevelL)
{
field (DESC, "Filllevel of the Heliumtank in L")
field (DTYP, "Raw Soft Channel")
field (SCAN, "Passive")
field (LINR, "filllevel")
field (EGU, "l")
}
Step 5: Makefile changes
The Makefile
in this folder needs to be changed to include the newly created files, so add
DB += FillLevel.db
DB += FillLevel.proto
underneath the lines
# Create and install (or just install) into <top>/db
# databases, templates, substitutions like this
#DB += xxx.db
Chapter 2: Import of the Breakpoint Tables
To make the breakpoint table used in the previously described record available, navigate to HeliumLiquefaction/HeliumLiquefactionApp/src
.
Firstly, the basic steps needed to import StreamDevice
and asyn
:
Create a drvHeliumLiquefaction.dbd
with the content
include "stream.dbd"
include "asyn.dbd"
include "drvAsynSerialPort.dbd"
registrar(drvAsynIPPortRegisterCommands)
registrar(drvAsynSerialPortRegisterCommands)
To make a breakpoint table available, two files need to be created. A breakpoint table and a file that makes the table available as a breakpoint table to records.
For a breakpoint table, create a file named <name>.dbd
, in this case called filllevel.dbd
. The file should look like
this (shortened here for readability):
breaktable(filllevel) {
0.0 0.0
10000.0 1.0
20000.0 3.0
30000.0 6.0
...
...
1470000.0 4231.0
1480000.0 4237.0
1487000.0 4239.8
}
This table represents fill levels in mm on the left and their respective values in liters on the right.
Please be aware that the values on the left need to be adjusted due to the steps taken during Step 3: Interpolation.
Actually, 10 mm
are equivalent to 1 l
, 20 mm
to 3 l
etc.
However, the mm
value used for interpolation is multiplied by 103. Therefor, all values representing mm in this breakpoint table also need to be multiplied by 103.
Next, to make this breakpoint table available, copy menuConvert.dbd
from the Epics installation folder \EPICS\base-7.0\dbd
to here.
This table contains a list of all available breakpoint tables and their internal names. This is already filled with breakpoint tables for temperature interpolation.
The newly created table needs to be appended to the end of the list, so the entire file should look like this:
#*************************************************************************
# Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne
# National Laboratory.
# Copyright (c) 2002 The Regents of the University of California, as
# Operator of Los Alamos National Laboratory.
# EPICS BASE is distributed subject to a Software License Agreement found
# in file LICENSE that is included with this distribution.
#*************************************************************************
menu(menuConvert) {
choice(menuConvertNO_CONVERSION,"NO CONVERSION")
choice(menuConvertSLOPE,"SLOPE")
choice(menuConvertLINEAR,"LINEAR")
choice(menuConverttypeKdegF,"typeKdegF")
choice(menuConverttypeKdegC,"typeKdegC")
choice(menuConverttypeJdegF,"typeJdegF")
choice(menuConverttypeJdegC,"typeJdegC")
choice(menuConverttypeEdegF,"typeEdegF(ixe only)")
choice(menuConverttypeEdegC,"typeEdegC(ixe only)")
choice(menuConverttypeTdegF,"typeTdegF")
choice(menuConverttypeTdegC,"typeTdegC")
choice(menuConverttypeRdegF,"typeRdegF")
choice(menuConverttypeRdegC,"typeRdegC")
choice(menuConverttypeSdegF,"typeSdegF")
choice(menuConverttypeSdegC,"typeSdegC")
choice(menuConverttypefilllevel,"filllevel")
}
Lastly, the Makefile
needs to be changed.
As usual, the support applications and libraries needs to be added, and the filllevel.dbd
as well. The Makefile should
look like this:
TOP=../..
include $(TOP)/configure/CONFIG
#----------------------------------------
# ADD MACRO DEFINITIONS AFTER THIS LINE
#=============================
#=============================
# Build the IOC application
PROD_IOC = HeliumLiquefaction
# HeliumLiquefaction.dbd will be created and installed
DBD += HeliumLiquefaction.dbd
# HeliumLiquefaction.dbd will be made up from these files:
HeliumLiquefaction_DBD += base.dbd
# Include dbd files from all support applications:
#HeliumLiquefaction_DBD += xxx.dbd
HeliumLiquefaction_DBD += calc.dbd
HeliumLiquefaction_DBD += drvHeliumLiquefaction.dbd
HeliumLiquefaction_DBD += stream.dbd
HeliumLiquefaction_DBD += asyn.dbd
HeliumLiquefaction_DBD += drvAsynSerialPort.dbd
HeliumLiquefaction_DBD += drvAsynIPPort.dbd
DBD += filllevel.dbd
# Add all the support libraries needed by this IOC
#HeliumLiquefaction_LIBS += xxx
HeliumLiquefaction_LIBS += calc
HeliumLiquefaction_LIBS += stream
HeliumLiquefaction_LIBS += asyn
# HeliumLiquefaction_registerRecordDeviceDriver.cpp derives from HeliumLiquefaction.dbd
HeliumLiquefaction_SRCS += HeliumLiquefaction_registerRecordDeviceDriver.cpp
# Build the main IOC entry point on workstation OSs.
HeliumLiquefaction_SRCS_DEFAULT += HeliumLiquefactionMain.cpp
HeliumLiquefaction_SRCS_vxWorks += -nil-
# Add support from base/src/vxWorks if needed
#HeliumLiquefaction_OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary
# Finally link to the EPICS Base libraries
HeliumLiquefaction_LIBS += $(EPICS_BASE_IOC_LIBS)
#===========================
include $(TOP)/configure/RULES
#----------------------------------------
# ADD RULES AFTER THIS LINE
Section 2: Liquefier Data
The main particularity of the Liquefier is that it doesnt wait to a value request and responds with the requested value, instead it periodically broadcasts all values.
These values consist of binary values 0
and 1
representing Off
and On
and analog values, for example Turbine Speed. Due to the values all being separated by long and different strings, records like aai
or waveform
are not advisable
Instead of showing the entire 160 line Liquefier.db
, which mostly is the same for all records, two example records are instead listed and explained.
For binary values, lets consider
record(bi, Liquefier:CompressorStatus)
{
field (DTYP, "stream")
field (INP, "@Liquefier.proto CompressorStatus $(PORT)")
field (SCAN, "I/O Intr")
field (ZNAM, "Off")
field (ONAM, "On")
}
SCAN
is set to"I/O Intr"
(as always case sensitive). Record processing is now triggered whenever the device sends an input.ZNAM
andONAM
represents the Zero Name, i.e. input is0
, and every other value. Since a0
meansOff
and otherwise itsOn
, it is set here.
For analog values
record(ai, Liquefier:T1Speed)
{
field (DTYP, "stream")
field (INP, "@Liquefier.proto T1Speed $(PORT)")
field (SCAN, "I/O Intr")
field (EGU, "U/min")
}
EGU
is only allowed in analog records, since binary options oviously dont have an Engineering Unit. It is set for each value according to the value send by the Liquefier.
For the Liquefier.proto
, again only the first function is explained, since they are all the same
ReadTimeout = 10000;
InTerminator = CR LF;
extrainput=ignore;
CompressorStatus
{
in "%*/KOMPRESSOR STATUS :/%i";
in "%*/VAKUUM SYSTEM STATUS:/%*i";
in "%*/LN2 SYSTEM STATUS :/%*i";
in "%*/ABKUHLEN :/%*i";
in "%*/IN BETRIEB :/%*i";
in "%*/REINIGER STATUS :/%*i";
in "%*/REINIGER ABKUHLEN :/%*i";
in "%*/REINIGER IN BETRIEB :/%*i";
in "%*/REINIGER REGENER. :/%*i";
in "%*/FTX100 T1 DREHZAHL :/%*f";
in "%*/FTX101 T2 DREHZAHL :/%*f";
in "%*/TTX106 TURB.AUSG.TMP:/%*f";
in "%*/TTX111 JT EING.TEMP :/%*f";
in "%*/PTX102 T1 EING.DRUCK:/%*f";
in "%*/PTX203 PUFFERDRUCK :/%*f";
in "%*/TTX104 REINIG.BETR. :/%*f";
in "%*/TTX102 REINIG.REGEN.:/%*f";
in "%*/TTX108 LN2 BETRIEBS :/%*f";
in "%*/VPI103 CV103 V.POSN :/%*f";
in "%*/VPI111 CV111 V.POSN :/%*f";
}
ReadTimout
is set rather high with 10 seconds, this is because the Liquefier sometimes takes very long to send the messages.extrainput = ignore
so extra bytes arent considered errorsin "%*/KOMPRESSOR STATUS :/%i";
takes in a line and looks for the string inside/ /
, so hereKOMPRESSOR STATUS :
. Due to%*
in front of it, that part is ignored. The value after this is saved as an integer via%i
in the next line, its the same with the value being matched, but it is ignored instead of saved. It is crucial for
StreamDevice
protocols where multiple values need to be saved from messages that every record “sees” every input but only matches the one actually needed for the record. Otherwise, record processing will not work correctly. It might seem as though values of records are updated, however monitoring of these records will fail.
Section 3: Setting up st.cmd for a correct boot of the IOC
Now navigate to /HeliumLiquefaction/iocBoot/iocHeliumLiquefaction
. There, edit the st.cmd to look like this:
Note: for completeness sake, the entire st.cmd
is included, which therefor contains files not explained here but necessary for the complete Helium Liquefaction driver. They are however commented out, so they should not cause problems
#!../../bin/linux-x86_64/HeliumLiquefaction
#- You may have to change HeliumLiquefaction to something else
#- everywhere it appears in this file
< envPaths
epicsEnvSet("STREAM_PROTOCOL_PATH", "$(TOP)/db")
epicsEnvSet "LIQUEFIERPORT" "$(TTY=/dev/tty<X>)"
epicsEnvSet "AGILENT34970APORT" "$(TTY=/dev/tty<X>)"
epicsEnvSet "OXFORDPORT" "$(TTY=/dev/tty<X>)"
epicsEnvSet "WIKA01PORT" "$(TTY=/dev/tty<X>)"
epicsEnvSet "WIKA02PORT" "$(TTY=/dev/tty<X>)"
cd "${TOP}"
## Register all support components
dbLoadDatabase "dbd/HeliumLiquefaction.dbd"
HeliumLiquefaction_registerRecordDeviceDriver pdbbase
## Purity Sensor (MAC-Adress -> A8:61:0A:AE:F3:A6, IP-Adress -> 10.131.162.100)
#drvAsynIPPortConfigure("PURITY","lapheliumpurity1.physik.uni-erlangen.de:80 HTTP")
## Debug, show Asyn-Messages
#asynSetTraceMask("PURITY",-1,0x9)
#asynSetTraceIOMask("PURITY",-1,0x2)
## Liquefier
drvAsynSerialPortConfigure("LIQUEFIER","$(LIQUEFIERPORT)")
asynSetOption("LIQUEFIER", 1, "baud", "9600")
asynSetOption("LIQUEFIER", 1, "parity", "even")
asynSetOption("LIQUEFIER", 1, "bits", "7")
asynSetOption("LIQUEFIER", 1, "stop", "1")
## Debug, show Asyn-Messages
#asynSetTraceMask("LIQUEFIER",-1,0x9)
#asynSetTraceIOMask("LIQUEFIER",-1,0x2)
## Cooling Water
#drvAsynSerialPortConfigure("AGILENT34970A","$(AGILENT34970APORT)")
#asynSetOption("AGILENT34970A", 1, "baud", "9600")
#asynSetOption("AGILENT34970A", 1, "parity", "none")
#asynSetOption("AGILENT34970A", 1, "bits", "8")
#asynSetOption("AGILENT34970A", 1, "stop", "1")
## Debug, show Asyn-Messages
#asynSetTraceMask("AGILENT34970A",-1,0x9)
#asynSetTraceIOMask("AGILENT34970A",-1,0x2)
## Fill Level
drvAsynSerialPortConfigure("OXFORD","$(OXFORDPORT)")
asynSetOption("OXFORD", 1, "baud", "9600")
asynSetOption("OXFORD", 1, "parity", "none")
asynSetOption("OXFORD", 1, "bits", "8")
asynSetOption("OXFORD", 1, "stop", "2")
## Debug, show Asyn-Messages
#asynSetTraceMask("OXFORD",-1,0x9)
#asynSetTraceIOMask("OXFORD",-1,0x2)
## Dewar Pressure
#drvAsynSerialPortConfigure("WIKA01","$(WIKA01PORT)")
#asynSetOption("WIKA01", 1, "baud", "9600")
#asynSetOption("WIKA01", 1, "parity", "none")
#asynSetOption("WIKA01", 1, "bits", "8")
#asynSetOption("WIKA01", 1, "stop", "2")
## Debug, show Asyn-Messages
#asynSetTraceMask("WIKA01",-1,0x9)
#asynSetTraceIOMask("WIKA01",-1,0x2)
## Buffer Pressure
#drvAsynSerialPortConfigure("WIKA02","$(WIKA02PORT)")
#asynSetOption("WIKA02", 1, "baud", "9600")
#asynSetOption("WIKA02", 1, "parity", "none")
#asynSetOption("WIKA02", 1, "bits", "8")
#asynSetOption("WIKA02", 1, "stop", "2")
## Debug, show Asyn-Messages
#asynSetTraceMask("WIKA02",-1,0x9)
#asynSetTraceIOMask("WIKA02",-1,0x2)
## Load record instances
##dbLoadRecords("db/HeliumLiquefaction.db","user=epics")
#dbLoadRecords("db/Purity.db", "PORT=PURITY")
dbLoadRecords("db/Liquefier.db", "PORT=LIQUEFIER, TTY=$(LIQUEFIERPORT)")
#dbLoadRecords("db/CoolingWater.db", "PORT=AGILENT34970A, TTY=$(AGILENT34970APORT)")
dbLoadRecords("db/FillLevel.db", "PORT=OXFORD, TTY=$(OXFORDPORT)")
#dbLoadRecords("db/DewarPressure.db", "PORT=WIKA01, TTY=$(WIKA01PORT)")
#dbLoadRecords("db/BufferPressure.db", "PORT=WIKA02, TTY=$(WIKA02PORT)")
# Load FillLevel Conversion Table and set it to not be monotonic
var dbBptNotMonotonic 1
dbLoadRecords("dbd/filllevel.dbd")
cd "${TOP}/iocBoot/${IOC}"
iocInit
## Start any sequence programs
#seq sncxxx,"user=epics"
the lines after
<env Paths
are:epicsEnvSet("STREAM_PROTOCOL_PATH", "$(TOP)/db")
is always necessary.epicsEnvSet "LIQUEFIERPORT" "$(TTY=/dev/tty<X>)"
sets a variableLIQUEFIERPORT
to$(TTY=/dev/tty<X>)
.TTY
means serial connection,<X>
needs to be replaced with whatever serial port the device is plugged into. To find them, runsudo dmesg | grep tty
, which should list all possible connections. After that, it might be a bit of trial and error to find the correct one, so probably use a tool like CuteCom to quickly find the correct port.
## Liquefier
is commented out to mark the beginning of settings regarding the Liquefier.drvAsynSerialPortConfigure("LIQUEFIER","$(LIQUEFIERPORT)")
makesasyn
open a connection with the device under the address defined in<envPath
and assigns that connection to the variableLIQUEFIER
.asynSetOption
sets the options for the listed connection. These options are device specific and should be available in the manual.asynSetTraceMask
andasynSetTraceIOMask
are debug options, that show every incoming and outcoming message on that port. As most debug options, they should stay commented out during normal operations.
## Load record instances
lists all loads the.db
files viadbLoadRecords()
."db/FillLevel.db,"
names the.db
actually being loaded."PORT=LIQUEFIER"
makes a variable namedPORT
available to the records of that.db
, with the value ofLIQUEFIER
, which itself is replaced with the serial connection fromdrvAsynSerialPortConfigure
Other variables can also be passed off to the records here, with the same syntax.
All these variables are referenced in the record via
$(<name>)
, so for example$(PORT)
.
# Load FillLevel Conversion Table and set it to not be monotonic
does what it saysvar dbBptNotMonotonic 1
sets the breakpointtable below to be not monotonic.dbLoadRecords("dbd/filllevel.dbd")
loads the filllevel.dbd breakpoint table.
Tips for an easier time working with IOCs
Especially during working on a new .db
and .proto
, its often necessary to rebuild the IOC and restart it. To make that a bit faster, consider making an mk.cmd
where the st.cmd
is located with the following content:
#!/bin/bash
cd ../../
sudo make distclean
sudo make
cd iocBoot/iocHeliumLiquefaction
sudo ./st.cmd
this just paths to the main IOC folder, rebuilds it, navigates back to the st.cmd
and starts it