More work on soft proton counter

This commit is contained in:
2026-01-07 17:31:21 +01:00
committed by soederqvist_a
parent 94814b2140
commit bec218ad86
2 changed files with 255 additions and 59 deletions

View File

@@ -3,6 +3,14 @@
# #
# This is created to be able reuse the SinqDAQ interface # This is created to be able reuse the SinqDAQ interface
# in Nicos, to avoid having more code to maintain there. # 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") 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 # 4 should never happen, if it does it means the DAQ reports undocumented statusbits
field(FRVL, "4") field(FRVL, "4")
field(FRST, "INVALID") field(FRST, "INVALID")
# We start in idle
field(VAL, 0)
} }
record(longin, "$(INSTR)$(NAME):CHANNELS") record(longin, "$(INSTR)$(NAME):CHANNELS")
@@ -27,65 +37,110 @@ record(longin, "$(INSTR)$(NAME):CHANNELS")
field(DISP, 1) 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") record(stringin, "$(INSTR)$(NAME):MsgTxt")
{ {
field(DESC, "Miscellanous messages") 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(DESC, "Count until preset reached")
field(DTYP, "stream") field(VAL, 0)
field(OUT, "@$(PROTO) startWithCountPreset$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)") field(FLNK, "$(INSTR)$(NAME):PRESET-COUNT-TRIG")
field(VAL, 0)
field(PREC, 2)
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS")
} }
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(DESC, "Count for specified time")
field(DTYP, "stream") field(VAL, 0)
field(OUT, "@$(PROTO) startWithTimePreset$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(PREC, 2) field(PREC, 2)
field(EGU, "seconds") field(EGU, "seconds")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") # 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(DESC, "Pause the current count")
field(DTYP, "stream") field(VAL, 0)
field(OUT, "@$(PROTO) pauseCount($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, "0")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") 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(DESC, "Continue with a count that was paused")
field(DTYP, "stream") field(VAL, "0")
field(OUT, "@$(PROTO) continueCount($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, "0")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") 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(DESC, "Stop the current counting operation")
field(DTYP, "stream") field(OCAL, 5)
field(OUT, "@$(PROTO) stopCount($(INSTR)$(NAME):) $(ASYN_PORT)") field(OOPT, "Every Time")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS") 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") 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 to RBV to be compatible with higher level interface
alias("$(INSTR)$(NAME):THRESHOLD-MONITOR_RBV") alias("$(INSTR)$(NAME):THRESHOLD-MONITOR_RBV")
field(DESC, "Channel monitored for minimum rate") 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(DRVL, "0") # Smallest Threshold Channel (0 is off)
field(DRVH, "$(CHANNELS=1)") # Largest Threshold Channel field(DRVH, "$(CHANNELS=1)") # Largest Threshold Channel
field(DTYP, "stream") field(DTYP, "stream")
@@ -104,7 +159,7 @@ record(ao,"$(INSTR)$(NAME):THRESHOLD")
# Alias to RBV to be compatible with higher level interface # Alias to RBV to be compatible with higher level interface
alias("$(INSTR)$(NAME):THRESHOLD_RBV") alias("$(INSTR)$(NAME):THRESHOLD_RBV")
field(DESC, "Minimum rate for counting to proceed") field(DESC, "Minimum rate for counting to proceed")
field(VAL, "1") # Default Rate field(VAL, "1") # Default Rate
# Could perhaps still be improved. # Could perhaps still be improved.
# It seems to only accept whole counts? # It seems to only accept whole counts?
field(DRVL, "1") # Minimum Rate field(DRVL, "1") # Minimum Rate
@@ -116,43 +171,66 @@ record(ao,"$(INSTR)$(NAME):THRESHOLD")
record(ai,"$(INSTR)$(NAME):ELAPSED-TIME") record(ai,"$(INSTR)$(NAME):ELAPSED-TIME")
{ {
field(DESC, "DAQ Measured Time") field(DESC, "DAQ Measured Time")
field(EGU, "sec") field(EGU, "sec")
field(FLNK, "$(INSTR)$(NAME):ETO") field(FLNK, "$(INSTR)$(NAME):ETO")
} }
# Subroutine record which emulates the counterbox functionality # Array Subroutine record which emulates the counterbox functionality
record(sub, "$(INSTR)$(NAME):EMULATION") # 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(SCAN, "0.1 seconds"
field(INAM, "InitEmulatedCounter") field(INAM, "initEmulatedCounter")
field(SNAM, "ProcessEmulatedCounter" 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")
# 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(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(OUTC, "$(INSTR)$(NAME):ELAPSED-TIME PP")
field(FTVC, "DOUBLE")
field(OUTD, "$(INSTR)$(NAME):COMMAND-TRIG PP")
field(FTVD, "ULONG")
} }
####################### #######################
# Channel interface # Channel interface
####################### #######################
record(int64in, "$(INSTR)$(NAME):M$(CHANNEL=0)") record(int64in, "$(INSTR)$(NAME):M0")
{ {
field(DESC, "DAQ CH$(CHANNEL=0)") field(DESC, "DAQ CH0, proton current")
field(EGU, "cts") 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(INPA, "$(REMOTE_RATE_PV) CA"
field(INPB, "$(SHUTTER1_PV=0)") field(INPB, "$(SHUTTER1_PV=0)")
field(INPC, "$(SHUTTER1_CLOSED_VAL=1)" field(INPC, "$(SHUTTER1_CLOSED_VAL=1)"
@@ -160,26 +238,26 @@ record(calc, "$(INSTR)$(NAME):R$(CHANNEL=0)")
field(INPE, "$(SHUTTER2_CLOSED_VAL=1)") field(INPE, "$(SHUTTER2_CLOSED_VAL=1)")
# If either shutter is closed we have no rate # If either shutter is closed we have no rate
field(CALC, "B != C && D != E ? A : 0") 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 # Clear channel, has to be calcout due to client
# writing 1 to it. # writing 1 to it.
record(calcout, "$(INSTR)$(NAME):C$(CHANNEL=0)") record(calcout, "$(INSTR)$(NAME):C0")
{ {
field(DESC, "Clear the current channel count") field(DESC, "Clear the current channel count")
field(OOPT, "Every Time") field(OOPT, "Every Time")
field(DOPT, "Use OCAL") field(DOPT, "Use OCAL")
field(OCAL, "0") 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? # Current Status of Channel, i.e. is it ready to count?
# This is probbably only need to satify the interface. # 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(DESC, "Channel Status")
field(VAL, 0) field(VAL, 0)
field(ZNAM, "OK") field(ZNAM, "OK")
field(ONAM, "CLEARING") field(ONAM, "CLEARING")
} }

118
src/daq_soft_proton.c Normal file
View File

@@ -0,0 +1,118 @@
#include <stdio.h>
#include <dbDefs.h>
#include <errlog.h>
#include <aSubRecord.h>
#include <epicsTypes.h>
#include <registryFunction.h>
#include <epicsExport.h>
/* 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);