diff --git a/db/daq_soft_proton.db b/db/daq_soft_proton.db index cc3a52c..a9de733 100644 --- a/db/daq_soft_proton.db +++ b/db/daq_soft_proton.db @@ -3,6 +3,14 @@ # # This is created to be able reuse the SinqDAQ interface # in Nicos, to avoid having more code to maintain there. +# +# 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 record(mbbi, "$(INSTR)$(NAME):STATUS") { @@ -18,6 +26,8 @@ record(mbbi, "$(INSTR)$(NAME):STATUS") # 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) } record(longin, "$(INSTR)$(NAME):CHANNELS") @@ -27,65 +37,110 @@ record(longin, "$(INSTR)$(NAME):CHANNELS") field(DISP, 1) } -record(longout, "$(INSTR)$(NAME):FULL-RESET") -{ - field(DESC, "Reset the DAQ") - field(OUT, "@$(PROTO) fullReset($(INSTR)$(NAME):) $(ASYN_PORT)") - field(DTYP, "stream") -} - record(stringin, "$(INSTR)$(NAME):MsgTxt") { field(DESC, "Miscellanous messages") } ################################################################################ -# Count Commands +# Commands +################################################################################ -record(ao,"$(INSTR)$(NAME):PRESET-COUNT") +record(int64in, "$(INSTR)$(NAME):PRESET-COUNT") { field(DESC, "Count until preset reached") - field(DTYP, "stream") - field(OUT, "@$(PROTO) startWithCountPreset$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)") - field(VAL, 0) - field(PREC, 2) - field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") + field(VAL, 0) + field(FLNK, "$(INSTR)$(NAME):PRESET-COUNT-TRIG") } -record(ao,"$(INSTR)$(NAME):PRESET-TIME") +record(longout, "$(INSTR)$(NAME):PRESET-COUNT-TRIG") +{ + field(DESC, "Count until preset reached") + # Signal count preset as command + field(VAL, 1) + field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP") + field(FLNK, "$(INSTR)$(NAME):COUNT-TYPE" +} + +record(calcout, "$(INSTR)$(NAME):PRESET-TIME") { field(DESC, "Count for specified time") - field(DTYP, "stream") - field(OUT, "@$(PROTO) startWithTimePreset$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)") - field(VAL, 0) + field(VAL, 0) field(PREC, 2) - field(EGU, "seconds") - field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") + field(EGU, "seconds") + # Signal count preset as command + field(OCAL, 2) + field(OOPT, "Every Time") + field(DOPT, "Use OCAL") + field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP") + field(FLNK, "$(INSTR)$(NAME):COUNT-TYPE" } -record(bo,"$(INSTR)$(NAME):PAUSE") +record(calcout, "$(INSTR)$(NAME):PAUSE") { field(DESC, "Pause the current count") - field(DTYP, "stream") - field(OUT, "@$(PROTO) pauseCount($(INSTR)$(NAME):) $(ASYN_PORT)") - field(VAL, "0") + field(VAL, 0) field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") + field(OCAL, 3) + field(OOPT, "Every Time") + field(DOPT, "Use OCAL") + field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP") } -record(bo,"$(INSTR)$(NAME):CONTINUE") +record(calcout, "$(INSTR)$(NAME):CONTINUE") { field(DESC, "Continue with a count that was paused") - field(DTYP, "stream") - field(OUT, "@$(PROTO) continueCount($(INSTR)$(NAME):) $(ASYN_PORT)") - field(VAL, "0") + field(VAL, "0") field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") + field(OCAL, 4) + field(OOPT, "Every Time") + field(DOPT, "Use OCAL") + field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP") } -record(longout, "$(INSTR)$(NAME):STOP") +record(calcout, "$(INSTR)$(NAME):STOP") { field(DESC, "Stop the current counting operation") - field(DTYP, "stream") - field(OUT, "@$(PROTO) stopCount($(INSTR)$(NAME):) $(ASYN_PORT)") - field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") + field(OCAL, 5) + field(OOPT, "Every Time") + field(DOPT, "Use OCAL") + field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP") +} + +record(calcout, "$(INSTR)$(NAME):FULL-RESET") +{ + field(DESC, "Reset the DAQ") + field(OCAL, 6) + field(OOPT, "Every Time") + field(DOPT, "Use OCAL") + field(OUT, "$(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(FVST, "Full reset command") + field(FVVL, 6) +} + +# Copy COMMAND-TRIG to memorize what type of count is on-going +record(longin, "$(INSTR)$(NAME):COUNT-TYPE") { + field(INP, "$(INSTR)$(NAME):COMMAND-TRIG.VAL NPP") + field(VAL, 0) } record(longout,"$(INSTR)$(NAME):THRESHOLD-MONITOR") @@ -93,7 +148,7 @@ 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(VAL, "1") # Monitor field(DRVL, "0") # Smallest Threshold Channel (0 is off) field(DRVH, "$(CHANNELS=1)") # Largest Threshold Channel field(DTYP, "stream") @@ -104,7 +159,7 @@ 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(VAL, "1") # Default Rate # Could perhaps still be improved. # It seems to only accept whole counts? field(DRVL, "1") # Minimum Rate @@ -116,43 +171,66 @@ record(ao,"$(INSTR)$(NAME):THRESHOLD") record(ai,"$(INSTR)$(NAME):ELAPSED-TIME") { field(DESC, "DAQ Measured Time") - field(EGU, "sec") + field(EGU, "sec") field(FLNK, "$(INSTR)$(NAME):ETO") } -# Subroutine record which emulates the counterbox functionality -record(sub, "$(INSTR)$(NAME):EMULATION") +# 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, "0.1 seconds" - field(INAM, "InitEmulatedCounter") - field(SNAM, "ProcessEmulatedCounter" - field(INPA, "$(INSTR)$(NAME):STATUS") - field(INPB, "$(INSTR)$(NAME):M$(CHANNEL=0)") - field(INPC, "$(INSTR)$(NAME):ELAPSED-TIME") - field(INPD, "$(INSTR)$(NAME):R$(CHANNEL=0) PP") - field(INPE, "$(INSTR)$(NAME):PRESET-COUNT") - field(INPF, "$(INSTR)$(NAME):PRESET-TIME") - field(INPG, "$(INSTR)$(NAME):PAUSE") - field(INPH, "$(INSTR)$(NAME):CONTINUE") - field(INPI, "$(INSTR)$(NAME):STOP") + field(INAM, "initEmulatedCounter") + 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):M0") + field(FTB, "INT64") + field(INPC, "$(INSTR)$(NAME):ELAPSED-TIME") + field(FTC, "DOUBLE") + field(OUTD, "$(INSTR)$(NAME):COMMAND-TRIG") + field(FTD, "ULONG") + field(INPE, "$(INSTR)$(NAME):COMMAND-TRIG") + field(FTE, "ULONG") + + # Address the PV which are mapped as input backwards + field(INPG, "$(INSTR)$(NAME):PRESET-COUNT") + field(FTG, "INT64") + field(INPH, "$(INSTR)$(NAME):PRESET-TIME") + field(FTH, "DOUBLE") + # L is last input before EPICS 7.0.10 + field(INPL, "$(INSTR)$(NAME):R0 PP") + field(FTL, "DOUBLE") + + # The first 4 outputs are also mapped as the first 4 inputs field(OUTA, "$(INSTR)$(NAME):STATUS PP") - field(OUTB, "$(INSTR)$(NAME):M$(CHANNEL=0) PP") + field(FTVA, "ULONG") + field(OUTB, "$(INSTR)$(NAME):M0 PP") + field(FTVB, "INT64") field(OUTC, "$(INSTR)$(NAME):ELAPSED-TIME PP") + field(FTVC, "DOUBLE") + field(OUTD, "$(INSTR)$(NAME):COMMAND-TRIG PP") + field(FTVD, "ULONG") } ####################### # Channel interface ####################### -record(int64in, "$(INSTR)$(NAME):M$(CHANNEL=0)") +record(int64in, "$(INSTR)$(NAME):M0") { - field(DESC, "DAQ CH$(CHANNEL=0)") - field(EGU, "cts") + field(DESC, "DAQ CH0, proton current") + field(EGU, "cts") } -record(calc, "$(INSTR)$(NAME):R$(CHANNEL=0)") +# 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):R0") { - field(DESC, "Rate of DAQ CH$(CHANNEL=0)") + 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)" @@ -160,26 +238,26 @@ record(calc, "$(INSTR)$(NAME):R$(CHANNEL=0)") field(INPE, "$(SHUTTER2_CLOSED_VAL=1)") # If either shutter is closed we have no rate field(CALC, "B != C && D != E ? A : 0") - field(EGU, "cts/sec") + field(EGU, "cts/sec") } # Clear channel, has to be calcout due to client # writing 1 to it. -record(calcout, "$(INSTR)$(NAME):C$(CHANNEL=0)") +record(calcout, "$(INSTR)$(NAME):C0") { field(DESC, "Clear the current channel count") field(OOPT, "Every Time") field(DOPT, "Use OCAL") field(OCAL, "0") - field(OUT, "$(INSTR)$(NAME):M$(CHANNEL=0) PP" + field(OUT, "$(INSTR)$(NAME):M0 PP" } # Current Status of Channel, i.e. is it ready to count? # This is probbably only need to satify the interface. -record(bi, "$(INSTR)$(NAME):S$(CHANNEL=0)") +record(bi, "$(INSTR)$(NAME):S0") { field(DESC, "Channel Status") - field(VAL, 0) + field(VAL, 0) field(ZNAM, "OK") field(ONAM, "CLEARING") } diff --git a/src/daq_soft_proton.c b/src/daq_soft_proton.c new file mode 100644 index 0000000..121cae6 --- /dev/null +++ b/src/daq_soft_proton.c @@ -0,0 +1,118 @@ +#include +#include +#include + +#include +#include +#include +#include + +/* Sample rate */ +static const epicsFloat64 soft_proton_sample_rate = 0.1; + +/* To allow setting debug pring from iocsh */ +static int softProtonDebug=0; +epicsExportAddress(int, softProtonDebug); + +/* + * This function is called a IOC init + */ +static long initEmulatedCounter(struct subRecord *psub) +{ + if (softProtonDebug) printf("initEmulatedCounter was called\n"); + return 0; +} + +/* + * This functioni is called everytime the record processes. + * Even though this record can process arrays, we only use it with scalars, + * hence all the [0]'s. + */ +static long processEmulatedCounter(struct aSubRecord *psub) +{ + enum commands { + NONE = 0, + COUNT_PRESET = 1, + TIME_PRESET = 2, + PAUSE = 3, + CONTINUE = 4 + STOP = 5, + FULL_RESET = 6 + }; + + enum status { + IDLE = 0, + COUNTING = 1, + LOW_RATE = 2, + PAUSED = 3, + INVALID = 4 + }; + const char[] funcstr = "processEmulatedCounter"; + if (softProtonDebug) printf("%s was called\n", funcstr); + + /* We copy input values to the stack, + but take the pointer to output values. + This to increase readability. */ + /* First all inputs that have a matching output */ + /* Status input and output pointer */ + epicsUInt32 status = psub->a[0]; + epicsUInt32* status_out = psub->vala; + /* Monitor count input and output pointer */ + epicsInt64 monitor_count = psub->b[0]; + epicsInt64* monitor_count_out = psub->valb; + /* Elapsed time input and output pointer */ + epicsFloat64 elapsed_time = psub->c[0]; + epicsFloat64* elaped_time_out = psub->valc; + /* Command trigger input and output pointer */ + epicsUInt32* command_trig = psub->d[0]; + epicsUInt32* command_trig_out = psub->vald; + /* Remaining only inputs */ + epicsUInt32 preset_count = psub->f[0]; + epicsInt64 preset_count = psub->g[0]; + epicsFloat64 preset_time = psub->h[0]; + epicsFloat64 proton_rate = psub->l[0]; + + if (status >= INVALID || command_trig > FULL_RESET) { + /* This shouldn't happen, but let's handle it just in case */ + errlogPrintf("%s: Status and/or command triggers are invalid \n" + "Status has value %d, \n Command trigger has value %d\n", + funcstr, status, command_trig); + return -1; + } + + if (command_trig == FULL_RESET) { + *status_out = IDLE; + *monitor_count_out = 0; + *elapsed_time_out = 0.0; + *command_trig_out = NONE; + if (softProtonDebug) printf("%s: Full reset done!\n", funcstr); + return 0; + } + + /* Determine if we are idle and have not received a command */ + if (status == 0 && command_trig == 0) { + /* Just return as quickly as possible if there is nothing going on */ + return 0; + } + + /* Determine if we are idle but received a count command */ + if (status == 0 && (command_trig == 1 || command_trig == 2)) { + if (softProtonDebug) printf("%s: Starting Count!\n", funcstr); + /* Check that count type is properly stored */ + if ( command_trig != count_type ) { + + } + } + + + /* Placeholder output setting */ + *status_out = status; + *monitor_count_out = monitor_count; + if ( first + *elapsed_time_out = elapsed_time + soft_proton_sample_rate; + + return 0; +} + +epicsRegisterFunction(initEmulatedCounter); +epicsRegisterFunction(processEmulatedCounter);