# EPICS database for counterbox-like interface using proton current, # as retrieved via network from HIPA. # # This is created to be able reuse the SinqDAQ interface # in Nicos, to avoid having more code to maintain there. # # Commands that has no actual relevant value that needs to be written to them: # * Pause # * Continue # * Stop # * Full reset # * Reset elapsed time # * Reset the counter/monitor value # Are all implemented as seq records. Due to how the existing # interface worked, they were being written 1 from nicos. # It just so turns out that seq record's VAL field triggers processing, # but a calcout records VAL field doesn't. seq record was the only option # to emulate these features with only 1 record per command. # # This requires following macros to specified: # INSTR: Instrument prefix, e.g. "SQAMOR:" # NAME: Name of the DAQ, e.g. "PROTONDAQ" # SHUTTER1_PV: PV of a shutter state will be taken as a gate signal for the protoon current # SHUTTER1_CLOSED_VAL: Value of the shutter PV when the shutter is closed # SHUTTER2_PV: PV of a second shutter state will be taken as a gate signal for the protoon current # SHUTTER2_CLOSED_VAL: Value of the shutter PV when the shutter is closed ################################################################################ # Count Config ################################################################################ record(longin, "$(INSTR)$(NAME):CHANNELS") { field(DESC, "Total Supported Channels") field(VAL, 1) field(DISP, 1) } record(waveform, "$(INSTR)$(NAME):MsgTxt") { field(DESC, "Miscellanous messages") field(FTVL, "CHAR") field(NELM, 40) } record(bi, "$(INSTR)$(NAME):IS_LOWRATE") { field(ZNAM, "LOW RATE") field(ONAM, "GOOD RATE") } record(longout,"$(INSTR)$(NAME):THRESHOLD-MONITOR") { # Alias to RBV to be compatible with higher level interface alias("$(INSTR)$(NAME):THRESHOLD-MONITOR_RBV") field(DESC, "Channel monitored for minimum rate") field(VAL, "1") # Monitor field(DRVL, "0") # Smallest Threshold Channel (0 is off) field(DRVH, "1") # Largest Threshold Channel } record(ao,"$(INSTR)$(NAME):THRESHOLD") { # Alias to RBV to be compatible with higher level interface alias("$(INSTR)$(NAME):THRESHOLD_RBV") field(DESC, "Minimum rate for counting to proceed") field(VAL, "1") # Default Rate field(DRVL, "1") # Minimum Rate field(DRVH, "100000") # Maximum Rate field(OMSL, "supervisory") } record(longout, "$(INSTR)$(NAME):MONITOR-CHANNEL") { alias("$(INSTR)$(NAME):MONITOR-CHANNEL_RBV") field(DESC, "Count preset channel") field(VAL, 1) } ################################################################################ # Commands ################################################################################ record(seq, "$(INSTR)$(NAME):START") { field(DESC, "Start a count") field(SELM, "All") field(DO0, 1) field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP") } record(seq, "$(INSTR)$(NAME):PAUSE") { field(DESC, "Pause the current count") field(SELM, "All") field(DO0, 3) field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP") } record(seq, "$(INSTR)$(NAME):CONTINUE") { field(DESC, "Continue with a count that was paused") field(SELM, "All") field(DO0, 4) field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP") } record(seq, "$(INSTR)$(NAME):STOP") { field(DESC, "Stop the current counting operation") field(SELM, "All") field(DO0, 5) field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP") } record(seq, "$(INSTR)$(NAME):FULL-RESET") { field(DESC, "Perform full reset") field(SELM, "All") field(DO0, 6) field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP") } # Record is to signal command given by the client to the emulated counter. # It's set back to no command from the emulated counter subroutine record record(mbbi, "$(INSTR)$(NAME):COMMAND-TRIG") { field(DESC, "Command type") field(VAL, 0) field(ZRST, "No command") field(ZRVL, 0) field(ONST, "Count preset command") field(ONVL, 1) # field(TWST, "Time preset command") # field(TWVL, 2) field(THST, "Pause command") field(THVL, 3) field(FRST, "Continue command") field(FRVL, 4) field(FVST, "Stop command") field(FVVL, 5) field(SXST, "Full reset command") field(SXVL, 6) } # Identical to FULL-RESET record(seq, "$(INSTR)$(NAME):RESET") { field(SELM, "All") field(DO0, 1) field(LNK0, "$(INSTR)$(NAME):FULL-RESET.PROC PP") } ################################################################################ # Poll Loop ################################################################################ record(dfanout, "$(INSTR)$(NAME):DB_POLL") { field(SELM, "All") field(OMSL, "closed_loop") field(DOL, 1) # This order is important, to ensure that the status # can't change whichout having first updated the time # and count records to their final values field(OUTA, "$(INSTR)$(NAME):T_STS_RAW.PROC") field(OUTB, "$(INSTR)$(NAME):CH1_STS_RAW.PROC") field(OUTC, "$(INSTR)$(NAME):STATUS.PROC") field(OUTD, "$(INSTR)$(NAME):T_PRESET_CLEAR.PROC") field(OUTE, "$(INSTR)$(NAME):PRESET_CLEAR.PROC") } # Array Subroutine record which emulates the counterbox functionality # Use an aSub because it allows specifying the type. record(aSub, "$(INSTR)$(NAME):EMULATION") { # Scan rate determines how often we sample the rate # and how often the counter value updates. field(SCAN, ".1 second") field(SNAM, "processEmulatedCounter") # The first 4 inputs are also mapped as the first 4 outputs field(INPA, "$(INSTR)$(NAME):STATUS") field(FTA, "ULONG") field(INPB, "$(INSTR)$(NAME):CH1") field(FTB, "INT64") field(INPC, "$(INSTR)$(NAME):T") field(FTC, "DOUBLE") field(INPD, "$(INSTR)$(NAME):COMMAND-TRIG") field(FTD, "ULONG") # Address the PV which are mapped as input backwards field(INPE, "$(INSTR)$(NAME):THRESHOLD-MONITOR") field(FTE, "LONG") field(INPF, "$(INSTR)$(NAME):THRESHOLD") field(FTF, "DOUBLE") # field(INPG, "$(INSTR)$(NAME):COUNT-TYPE") # field(FTG, "ULONG") field(INPH, "$(INSTR)$(NAME):CH1_PRESET") field(FTH, "INT64") field(INPI, "$(INSTR)$(NAME):T_PRESET") field(FTI, "DOUBLE") # L is last input before EPICS 7.0.10 field(INPJ, "$(INSTR)$(NAME):CH1_RATE-PREV") field(FTJ, "DOUBLE") field(INPL, "$(INSTR)$(NAME):CH1_RATE PP") field(FTL, "DOUBLE") # The first 4 outputs are also mapped as the first 4 inputs field(OUTA, "$(INSTR)$(NAME):RAW_STATUS PP") field(FTVA, "ULONG") field(OUTB, "$(INSTR)$(NAME):CH1 PP") field(FTVB, "INT64") field(OUTC, "$(INSTR)$(NAME):T PP") field(FTVC, "DOUBLE") field(OUTD, "$(INSTR)$(NAME):COMMAND-TRIG PP") field(FTVD, "ULONG") field(OUTE, "$(INSTR)$(NAME):CH1_RATE-PREV PP") field(FTVE, "DOUBLE") field(OUTF, "$(INSTR)$(NAME):IS_LOWRATE PP") field(FTVF, "ULONG") field(OUTG, "$(INSTR)$(NAME):MsgTxt PP") field(FTVG, "CHAR") field(NOVG, 40) field(FLNK, "$(INSTR)$(NAME):DB_POLL") } record(mbbi, "$(INSTR)$(NAME):RAW_STATUS") { field(DESC, "Raw returned status value") } record(mbbi, "$(INSTR)$(NAME):STATUS") { field(DESC, "DAQ Status") field(INP, "$(INSTR)$(NAME):RAW_STATUS NPP MS") field(ZRVL, "0") field(ZRST, "Idle") field(ONVL, "1") field(ONST, "Counting") field(TWVL, "2") field(TWST, "Low rate") field(THVL, "3") field(THST, "Paused") # 4 should never happen, if it does it means the DAQ reports undocumented statusbits field(FRVL, "4") field(FRST, "INVALID") # We start in idle field(VAL, 0) } ################################################################################ # Time Channel interface ################################################################################ record(ai,"$(INSTR)$(NAME):T") { field(DESC, "DAQ Measured Time") field(EGU, "sec") field(PREC, 1) } record(calcout, "$(INSTR)$(NAME):T_PRESET_CLEAR") { field(INPA, "$(INSTR)$(NAME):STATUS NPP") field(CALC, "A") field(OOPT, "Transition To Zero") field(DOPT, "Use OCAL") field(OCAL, "0") field(OUT, "$(INSTR)$(NAME):T_PRESET PP") } record(ai, "$(INSTR)$(NAME):T_PRESET") { field(DESC, "Time Preset") field(VAL, 0) } record(calc, "$(INSTR)$(NAME):T_STS_RAW") { field(DESC, "Raw time channel status") field(INPA, "$(INSTR)$(NAME):RAW_STATUS NPP MS") field(INPB, "$(INSTR)$(NAME):T_CLEARED NPP") field(CALC, "(0 < A && A < 4) || B") field(FLNK, "$(INSTR)$(NAME):T_STS") } # Current Status of elapsed time record(bi, "$(INSTR)$(NAME):T_STS") { field(DESC, "Channel Status") field(INP, "$(INSTR)$(NAME):T_STS_RAW NPP MS") field(ZNAM, "Idle") field(ONAM, "Busy") } record(bi, "$(INSTR)$(NAME):T_CLEARED") { field(DESC, "is being cleared") field(VAL, 0) field(ZNAM, "Cleared") field(ONAM, "Clearing") } # Emulate Reset elapsed time # 0. set status to busy # 1. set elapsed time to 0 # 2. set status to OK record(seq, "$(INSTR)$(NAME):T_CLEAR") { field(SELM, "All") field(LNK0, "$(INSTR)$(NAME):T_CLEARED PP") field(DO0, 1) field(LNK1, "$(INSTR)$(NAME):T PP") field(DO1, 0) field(LNK2, "$(INSTR)$(NAME):T_CLEARED PP") field(DO2, 0) } ################################################################################ # Channel interface ################################################################################ record(int64in, "$(INSTR)$(NAME):CH1") { field(DESC, "DAQ CH0, proton current") field(EGU, "$(COUNTER_EGU=cts)") } # The proton rate take by a PV over the network, PV named indicated by $(REMOTE_RATE_PV) macro # It emulates Zero rate if either shutter is closed. record(calc, "$(INSTR)$(NAME):CH1_RATE") { field(DESC, "Rate of DAQ CH0 proton current") field(INPA, "$(REMOTE_RATE_PV) CA") field(INPB, "$(SHUTTER1_PV=0)") field(INPC, "$(SHUTTER1_CLOSED_VAL=1)") field(INPD, "$(SHUTTER2_PV=0)") field(INPE, "$(SHUTTER2_CLOSED_VAL=1)") # The proton current signal routed to real counterboxes # transfer the current to frequency modulated signal. # The relation ship 1uA -> 100Hz, meaning that nominal 1500uA -> 150kHz field(INPF, "$(SCALE_FACTOR=100)") # If either shutter is closed we have no rate field(CALC, "B != C && D != E ? A * F : 0") field(EGU, "$(RATE_EGU=cts/sec)") } # Store previous rate value, so we can average over the period record(ai, "$(INSTR)$(NAME):CH1_RATE-PREV") { field(DESC, "Previous rate of DAQ CH0 proton current") } record(calcout, "$(INSTR)$(NAME):PRESET_CLEAR") { field(INPA, "$(INSTR)$(NAME):STATUS NPP") field(CALC, "A") field(OOPT, "Transition To Zero") field(DOPT, "Use OCAL") field(OCAL, "0") field(OUT, "$(INSTR)$(NAME):CH1_PRESET PP") } record(int64in, "$(INSTR)$(NAME):CH1_PRESET") { field(DESC, "Monitor Count Preset") field(VAL, 0) field(FLNK, "$(INSTR)$(NAME):CH1_SETTING") } record(calcout, "$(INSTR)$(NAME):CH1_SETTING") { field(INPA, "$(INSTR)$(NAME):CH1_PRESET NPP") field(CALC, "A") field(OOPT, "Transition To Non-zero") field(DOPT, "Use OCAL") field(OCAL, "1") field(OUT, "$(INSTR)$(NAME):CH1_SET PP") } record(bo, "$(INSTR)$(NAME):CH1_SET") { field(DESC, "are hw counts being cleared") field(ZNAM, "Set") field(ONAM, "Setting") field(HIGH, 0.1) # Emulate Waiting for HW setting preset } record(calc, "$(INSTR)$(NAME):CH1_STS_RAW") { field(DESC, "Raw Channel Status") field(INPA, "$(INSTR)$(NAME):RAW_STATUS NPP MS") field(INPB, "$(INSTR)$(NAME):CH1_CLEARED NPP") field(INPC, "$(INSTR)$(NAME):CH1_SET NPP") field(CALC, "(0 < A && A < 4) || B || C") field(FLNK, "$(INSTR)$(NAME):CH1_STS") } # Current Status of Channel # This is only to satify the interface. record(bi, "$(INSTR)$(NAME):CH1_STS") { field(DESC, "Channel Status") field(INP, "$(INSTR)$(NAME):CH1_STS_RAW NPP MS") field(ZNAM, "Idle") field(ONAM, "Busy") } record(bi, "$(INSTR)$(NAME):CH1_CLEARED") { field(DESC, "are hw counts being cleared") field(VAL, 0) field(ZNAM, "Cleared") field(ONAM, "Clearing") } # Emulate clearing channel # 0. set status to busy # 1. set elapsed time to 0 # 2. set status to OK record(seq, "$(INSTR)$(NAME):CH1_CLEAR") { field(SELM, "All") field(LNK0, "$(INSTR)$(NAME):CH1_CLEARED PP") field(DO0, 1) field(LNK1, "$(INSTR)$(NAME):CH1 PP") field(DO1, 0) field(LNK2, "$(INSTR)$(NAME):CH1_CLEARED PP") field(DO2, 0) }