Event segments are software components that manage the dgitizers associated with a logical part of your experiment. You can create an arbitrary number of event segments and control the order in which they are read. The abstraction of event segments supports the fact that experiments may be composed of re-usable detector subsystems and systems.
At the NSCL for example: One might run an experiment using the SeGA gamma ray spectrometer and the S800 spectrograph. One way to do this is to have a re-usable event segment for the S800, another for SeGA and to register them both with the readout framework to build the experiment.
Event segments are instances of classes (objects) that are
derived from the class CEventSegment
.
This section will also describe the CEventPacket
base class which is an event segment that wraps an event segment
into an NSCL tagged packet.
The methods an event segment can implement are:
initialize
Is called before data taking starts and is expected to initialize the data taking devices to prepare them and enable them to take data. This method is optional. If omitted the framework does nothing to initialize this event segment.
clear
Is called to clear digitizers to prepare them
to respond to the next trigger. It is called
just prior to waiting for a trigger (at the
start of the run after initialize
is called as well as after each event). This
method is optional and if not implemented
the framework does nothing for this event segment
at clear time.
disable
This method is called as data taking is being shutdown. If your devices require any actions to disable them you can perform those actions in this method. One place you might use this would be if you have programmed a user specific trigger based on VME interrupts. You could use the method to disable the interrupts on your trigger device.
This method is optional and the framework will do nothing if it is not implementerd.
read
This method is called on each trigger it is expected to read the data from the devices managed by this event segment from the digitizer hardware. Parameters are as follows:
type: void*
parameter: pBuffer
Purpose: Pointer to storage into which this event segment should store its data. Usually the first thing you will need to do is re-cast this pointer to the appropriate data type.
type: size_t
parameter: maxWords
Purpose:
The maximum number of uint16_t
units that can fit in the space pointed to by
pBuffer
.
Very bad things will happen if you read
more than this number of words.
The return value is expected to be the number of uint16_t units of data read by this segment.
Enough theory already. Let's look at a sample implemetation of an event segment. First the header:
Example 2-2. Simple Event Segment Header
#include <stdint.h>#include <CEventSegment.h>
class CAENcard;
class CCAENEventSegment : public CEventSegment
{ private: CAENcard* m_pCard;
public: CCAENEventSegment(uint32_t base, uint8_t id, int crate = 0);
~CCAENEventSegment();
virtual void initialize(); virtual void clear(); virtual size_t read(void* pBuffer, size_t maxwords); private: bool haveEvent(); };
CEventSegment
class.
CAENcard
is a class that
will be defined later. Using forward class definitions
reduces the chances of building circular dependencies
between header files.
CCAENEventSegment
derives from the CEventSegment
class.
Only CEventSegment
derived
classes can be registered as event segments with the
framework.
CAENcard
.
It is going to be a pointer to a CAENcard
object we will create in our class constructor. That
object will be used to manipulate the CAEN digitizer.
id
).
and VME crate number.
Lets look at the implementation (.cpp) file of the event segment a chunk at a time:
Example 2-3. Event segment front matter
#include <config.h>#include "CCAENEventSegment.h"
#include <CAENcard.h>
![]()
#include <string> #include <stdlib.h> #include <iostream>
CCAENEventSegment
class it
needs access to the header so that method prototypes
and member data definitions are available to
method implementations.
CAENcard
class
we made in the header. Since CAENEventSegment
is going to call CAENcard
methods,
we'll the compiler will need the actual class
definition.
The next code section we will look at contains the constructor and destructor of the event segment. The constructor is invoked when the event segment is created (usually in the Skeleton.cpp just prior to registration). The destructor is usually never invoked. However if you have some overarching event segment that, at initialization time, creates other event segments it contains, destructors may be called. In order to allow your code to be embedded in environments you don't initially anticipate, you should write correct constructors for all event segments.
Example 2-4. Event segment constructor and destructor
CCAENEventSegment::CCAENEventSegment(uint32_t base, uint8_t id, int packet, int crate) : m_pCard(new CAENcard(id, 0, false, base)){ } CCAENEventSegment::~CCAENEventSegment() { delete m_pCard;
}
m_pCare
member data
with a pointer to a CAENcard
object that will manage the CAEN tdc we are
operating with.
At this time, no operations are perfomed on the device itself as we've not yet been asked to initialize it.
CAENcard
object the constructor
created or memory and SBS mapping resources will
be leaked for each construction/destruction cycle.
Next lets look at the initialization code. In a production environment, this code might open a file and read some configuration data, using that data to figure out how to initialize the device. In keeping with showing the simplest code possible, we are going to hard code all configuration information.
Example 2-5. Event segment initialize
implementation
void CCAENEventSegment::initialize() { m_pCard->reset();sleep(2); for(int i =0; i < 32; i++) {
m_pCard->setThreshold(i, 0); } m_pCard->commonStart();
m_pCard->keepOverflowData(); m_pCard->keepUnderThresholdData(); m_pCard->setRange(0x1e); }
sleep
is probably somewhat longer
than required. A call to usleep
for a few milliseconds is probably more appropriate.
The clear
function is trivial:
Example 2-6. Event segment clear
implementation
CCAENEventSegment::clear() { m_pCard->clearData(); }
The heart of the event segment is, of course, the code that reads out the module:
Example 2-7. Event segment read
method implementation
size_t CCAENEventSegment::read(void* pBuffer, size_t maxwords) { // Maximum number of words is 34*2: if (maxwords < 34*2 ) {throw std::string( "CCAENEventSegment - insufficient buffers space for worst case event"); } for (int i =0; i < 30; i++) { if(haveEvent()) break;
} int n = (m_pCard->readEvent(p))/sizeof(uint16_t);
return n;
} bool CCAENEventSegment::haveEvent()
{ return m_pCard->dataPresent(); }
read
is called it is
given a pointer that describes where to put data
that has been read; pBuffer
and a size_t maxwords
that describes the amount of space remaining in the
buffer in words (uint16_t sized units).
This code ensures that the maximum TDC event size
will fit in the remaining buffer space, throwing
an exception if it won't.
The computation of the largest TDC event size comes from the fact that the TDC has 32 channels, that each event will have a header and trailer, and that each item will be a uint32_t, which uses two uint16_t units of storage.
n
is the number of
uint16_t words read. That value is returned.
Before leaving the subject of event segments for the scaler readout, I want to touch on two other important classes you can use to help you organize your code:
CCompoundEventSegment
The CCompoundEventSegment
class is an event segment that consists of
an ordered list of event segments (including
other compound event segments).
This can be used to organize a detector system that consists of many detector elements into a single event segment you can hand to your users
CEventPacket
NSCL events are often broken into packets. A packet consists of a header that has a size (note in RingDaq the size is a uint32_t while in SPDAQ, it is a uint16_t), a tag, and following the header, the payload of the packet.
CEventPacket
allows you to wrap an existing event
segment (including a compound event segment),
in a packet without the event segment knowing it's
being wrapped.
Let's look at a simple example of a compound event segment.
Earlier in this section, we've built a class;
CCAENEventSegment
that encapsulated
a CAEN 32 channel TDC. Suppose we had an experiment
that consisted of several of these TDCs (I know it's a bad
example but it prevents me from having to build additional
event segments and all I really want to show is the mechanics
of using compound event segments.)
We might have code the builds a Compound event segment as follows:
Example 2-8. Using a compound event segment
... // Create a few CCAENEventSegment objects: CCAENEventSegment tdc1(0x10000000, 1); CCAENEventSegment tdc2(0x10010000, 2); CCAENEventSegment tdc3(0x10020000, 3); // Create a compound event segment that reads out tdc1, tdc2, tdc3 in order: CCompoundEventSegment tdcs; tdcs.AddEventSegment(&tdc1); tdcs.AddEventSegment(&tdc2); tdcs.AddEventSegment(&tdc3); ...
The key to this example is that a CCompoundEventSegment
has a method named AddEventSegment
whose
parameter is a pointer to an existing event segment. This
creates an ordered list of event segments.
Since a CCompoundEventSegment
is itself
an event segment it can be added to another
CCompoundEventSegment
as well.
Continuing the example above:
... // Assume we've made more event segments named adcs and qdcs: CCompoundEventSegment myDetector; myDetector.AddEventSegment(&tdcs); myDetector.AddEventSegment(&adcs); myDetector.AddEventSegment(&qdcs); ...
The event segment hierarchy you build up can be as deep as you need to capture the needs of your detector system.
Suppose now that myDetector
in the previous
example is a know NSCL detector that has been assigned a packet
id of 0x1234. We can now create an event segment that
wraps our detector in that packet by using a
CEventPacket
as follows:
Example 2-9. Using CEventPacket
... CEventPacket myDetectorPacket(myDetector, 0x1234, "My Detector", "Packet for the My Detector device" "V1.0");
It's pretty easy to understand what the first two parameters are, a reference to an event packet and the tag. The remaining information are bundled into a documentation event emitted at the start of run that describe the set of event packets that you can expect to see in the run. Each documenation event contains a set of strings, one for each packet you created. Each string is a colon separated set of fields consisting of the short name of the packet (My Detector above), The stringified packet id (0x1234) as a hexadecimal, a long name of the packet (Packet for the My Detector device above), and a packet version which should be changed whenever the format of the packet changes (V1.0 in the example above), and the date and time the packet object was constructed (e.g. Tue Oct 18 15:32:20 2011).
If you want to see what the description string looks like, you can
call the Format
method of the
CEventPacket
object.