NSCL Ring buffer DAQ tutorial | ||
---|---|---|
Prev | Chapter 4. Creating a Readout program from a spectrodaq classic readout program | Next |
Now that we have a readevt
function that
is divorced from Spectrodaq we need to adapt this to the
RingDaq readout framework. The RingDaq readout framework uses
a pair of base classes that separate event and scaler readout.
We will be writing derived classes that simply delegate their
functionality to the functions implemented in the skeleton.cpp
file. If you are familiar with using the
CTraditionalReadoutSegment
and
CTraditionalScalerSegment
classes from
the SPDAQ production readout framework to adapt it to classical
readout code, you will already be familiar with this concept.
CEventSegment
The RingDaq readout framework builds up its response to an event
trigger in terms of event segments. Event segments can be
simple (CEventSegment
derived objects),
or they can be composed of other event segments
(CCompoundEventSegment
). Our job in
this section is going to be to build a CEventSegment
that will wrap the event related functions in skeleton.cpp.
Let's start by comparing the functions in skeleton.cpp
and the related methods in CEventSegment
.
Our modified skeleton.cpp provides three functions that are involved in physics event processing:
initevt
Called to perform one-time initialization as data taking begins (both when the run begins and when it resumes).
clearevt
Clears digitizers so that they can accept
additional triggers. This is called just
after initevt
as well
as after each event is read.
readevt
Called to read an event in response to an event
trigger. The parameter bufpt
is a pointer to storage into which the event
data must be placed.
By contrast, the CEventSegment
provides
the following methods:
initialize
Performs the same sort of initialization
initevt
performs, but
only on the devices managed by this event
segment.
clear
Similarly analagous to clearevt
disable
This method has no corresponding function in skeleton.cpp. It is called at the end of data taking and can be used to do any shutdown tasks that may be required to disable devices.
The preceeding list implies that we should write an event
segment in which initialize
calls
initevt
, clear
calls clearevt
,
read
does some adaptation
and calls readevt
and
disable
is not implemented.
The header for this sort of event segment looks like:
Example 4-7. Header for event segment adapator to readout classic
#include <config.h> #include <CEventSegment.h>class CTraditionalEventSegment : public CEventSegment
{ public: void initialize(); void clear(); size_t read(void* pBuffer, size_t maxwords); };
Now let's look at the implementation and discuss how that adapts to the functions in skeleton.cpp For the most part this is pretty simple as well:
Example 4-8. Implementation for the event segment adaptor to readout classic
#include <config.h> #include "CTraditionalEventSegment.h" #include <stdint.h> #include <string> typedef int16_t WORD;extern void initevt ();
extern void clearevt (); extern WORD readevt (WORD* bufpt);
void CTraditionalEventSegment::initialize() { ::initevt(); } void CTraditionalEventSegment::clear() { ::clearevt(); } size_t CTraditionalEventSegment::read(void* pBuffer, size_t maxwords) { WORD* p = reinterpret_cast<WORD*>(pBuffer);
size_t nWords = ::readevt(p);
if (nWords > maxwords) { throw std::string("readevt read more than maxwords of data");
} return nWords;
}
For the most part this is very straightforward, and similar to the SPDAQ production readout wrapper class for classic event segments.
initialize
and
clear
are trivial delegations
to the corresponding skeleton.cpp
functions.
read
and readevt
have a slight impedance match in their argument signatures
that needs to be dealt with. This line creates a
pointer p
of type WORD*
so that we can pass the correct pointer type to
readevt
.
readevt
funtion
saving the number of words returned.
CScaler
We must also write an adaptor taht wraps the scaler parts of
skeleton.cpp in a CScaler
.
This too is relatively straightforward. Let's once more
start by comparing the two software interfaces:
The functions the skeleton.cpp uses to manage the scaler readout are:
Scaler interface to skeleton.cpp
iniscl
Called to perform run start initialization of the scalers being managed.
clrscl
Called prior to the start of run and after each scaler readout. This function is supposed to clear all scaler counters.
CScaler
methods
initialize
This method is completely analagous to
iniscl
.
clear
This method is completely analaogous to
clrscl
disable
This is called as data taking is shut-down. Any end-run clean up actions can be performed here. This method has no corresponding entry point in skeleton.cpp and therefore need not be implemented in wrapper.
read
This method is called to read the scalers
managed by a CScaler
object. Unlike the readsc
function CScaler
objects
are assumed to be managing some fixed set of
scalers, and therefore know how many scalers
they will read. As we will see there are two
strategies you can follow for adapting to
this difference.
The read
method returns
an std::vector<uint32_t> that
contains the scaler data it has read.
From this comparison we can see it's pretty trivial to
wrap iniscl
and clrscl
.
Here is a header and the first part of the implementation
of a wrapper that shows how these functions get trivially
wrapped:
Example 4-9. Scaler adapter header
#include <CScaler.h>class CTraditionalScaler : public CScaler
{ public:
void initialize(); void clear(); std::vector<uint32_t> read(); };
CTraditionalScaler
from CScaler
we need to make
the shape of CScaler
known to the compiler. This is done by including
this header.
CScaler
this deriviation
allows CTraditionalScaler
objects to be registered with the framework as
CScaler
objects.
disable
method need not be implemented as
CScaler
provides a default
implementation that does nothing.
The implementation of the trivial wrappers is shown below along with the front matter of the implementation file.
Example 4-10. Trival methods of the scaler adapter
#include <config.h> #include "CTraditionalScaler.h" typedef uint16_t UINT16;typedef uint32_t UINT32; extern void iniscl();
extern void clrscl(); extern UINT16 readscl(UINT32* buffer, int numscalers); void CTraditionalScaler::initialize()
{ ::iniscl(); } void CTraditionalScaler::clear()
{ ::clrscl(); }
iniscl
is trivially wrapped
by this method.
clrscl
function is
trivially wrapped by this method.
Wrapping the readscl
function is a bit
trickier. Specifically we have to make some decisions about
how to know the number of scalers that will be read by the
readscl
function. We need to do this
not only to be able to provide the value back to the function
(its second parameter), but also to be able to allocate
storage for the buffer into which readscl
will read its data.
There are several strategies that come to mind:
Hard code the number of scalers in
CTraditionalScaler
.
Add a function to the skeleton.cpp code allowing it to report the number of scalers it will read.
Make the scaler count a construtor parameter fo
the CTraditionalScaler
class.
Provide some mechanism that allows both the
skeleton.cpp and the
CTraditionalScaler
code
to obtain the number of scalers from some external
information (e.g. data file, Tcl Script or
environment variable.
In this example we will assume that a function named
numScalers
has been added to the
skeleton.cpp file that reports the
number of scaler channesl that will be read. We leave it to
you to determine how that function knows this number.
Example 4-11. Adapting the scaler readout
... extern size_t numScalers();... std::vector<uint32_t> CTraditionalScaler::read() { size_t nChannels = ::numScalers();
uint32_t scalerBuffer[nChannels]; std::vector<uint32_t> result; readscl(scalerBuffer, nChannels);
for (int i =0; i < nChannels; i++) { result.push_back(scalerBuffer[i]);
} return result;
}