2.2. Creating an event segment

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>   (1)
#include <CEventSegment.h> (2)
class CAENcard;             (3)


class CCAENEventSegment : public CEventSegment  (4)
{
private:
  CAENcard*              m_pCard;              (5)
public:
  CCAENEventSegment(uint32_t base, uint8_t id, int crate = 0); (6)
  ~CCAENEventSegment();

(7)
  virtual void   initialize();                      
  virtual void   clear();
  virtual size_t read(void* pBuffer, size_t maxwords);
private:
  bool haveEvent();
};

                
(1)
The stdint.h header contains definitions of standard integer types with known bit widths (e.g. uint16_t).
(2)
We need to include this header because our event segment will need to be derived from the CEventSegment class.
(3)
This is the preferred way to define a class in a header file when the class 'shape' does not need to be known. This forward class declaration says that CAENcard is a class that will be defined later. Using forward class definitions reduces the chances of building circular dependencies between header files.
(4)
As promised the CCAENEventSegment derives from the CEventSegment class. Only CEventSegment derived classes can be registered as event segments with the framework.
(5)
This data element is why we needed the forward declaration of 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.
(6)
Normally your event segments will want to implement a constructor. In this case we provide the constructor with parameters that specify the module base address, virtual slot number (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>      (1)
#include "CCAENEventSegment.h" (2)

#include <CAENcard.h>    (3)

(4)

#include <string>
#include <stdlib.h>
#include <iostream>
                  
                
(1)
All implementation code you write in RingDaq should include config.h as the first header. This file provides definitions that other RingDaq headers may need.
(1)
Since this file will implement the CCAENEventSegment class it needs access to the header so that method prototypes and member data definitions are available to method implementations.
(3)
This satisfies the forward reference to the 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.
(4)
The headers below are standard C/C++ headeres that define funtions and classes we will use in the implementation of this class.

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))  (1)
{

}

CCAENEventSegment::~CCAENEventSegment()
{
  delete m_pCard;   (2)
}

                
(1)
Initializes the 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.

(2)
If we are ever destroyed we must delete the 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();             (1)
  sleep(2);
  for(int i =0; i < 32; i++) {  (2)
    m_pCard->setThreshold(i, 0);
  }
  m_pCard->commonStart();       (3)   
  m_pCard->keepOverflowData();
  m_pCard->keepUnderThresholdData();
  m_pCard->setRange(0x1e);


}
                
(1)
Prior to doing anything to the TDC it is reaset. after being reset it is necessary to wait a bit for the TDC to become ready for programming. The sleep is probably somewhat longer than required. A call to usleep for a few milliseconds is probably more appropriate.
(2)
Since we are going to accept overflow and underthreshold data, the thresholds are just set to zero. Normally you would process some configuration file at this point to determine actual threshold values to program.
(3)
This section of code programs the remainder of the TDC configuration.

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 ) {          (1)
    throw
      std::string(
      "CCAENEventSegment - insufficient buffers space for worst case event");
  }
  
  for (int i =0; i < 30; i++) {
    if(haveEvent()) break;            (2)
  }
  int n = (m_pCard->readEvent(p))/sizeof(uint16_t); (3)


  return n; (4)
}
bool
CCAENEventSegment::haveEvent()     (5)
{
  return m_pCard->dataPresent();
}

                
(1)
When 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.

(2)
It is possible the trigger latency will be shorter than the conversion time of the TDC. In this loop we wait for the module to have an event's worth of data. If it never does after 30 tests (each test will be about 2 μsecs), the loop exits anyway.
(3)
Reads an event worth of data from the module.
(4)
The read function returns the number of bytes of data read from the module. Therefore, n is the number of uint16_t words read. That value is returned.
(5)
This is a convenience function to determine if the module has an event.

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.