12.3. Setting up the software

The software modifications needed to make the system work can be divided into a few tasks:

  1. Defining which pieces of hardware are being used.

  2. Defining what to do with the hardware when readout is triggered

  3. Defining the conditions for triggering readout

In order to tell the software about our electronics, we are going to develop a C++ class for our module. That class will be a derived class but we don't need to concern ourselves with the details of the parent class. Like all of the software tailoring we need to do, most of the details are hidden and we need only to fill in a few holes.

The following commands will make a new directory and copy the skeleton files to it:


mkdir -p ~/experiment/readout
cd ~/experiment/readout
cp $DAQROOT/skeletons/sbs/* .
    

12.3.1. Modifying the Readout Skeleton

Now that we have obtained a copy of the Skeleton file we can begin to think about the modifications we need to make. We need to tell the software what kind of module we are using, how to initialize it, how to clear it, and how to read it. We will do this by creating a class called by MyEventSegment. In that class we will also define a timestamp for our data so that the Readout program will produce a body header. For lack of anything better to in this setup, we will use the event counter as the timestamp. We will not have to write custom software for the trigger condition because a class is already provided by NSCLDAQ that we will make use of.

In order to follow good coding practice and to make our code as versatile as possible, we will write our class in two separate files, a header file and an implementation file. Start by creating a file called MyEventSement.h. It should look like this:

Example 12-1. Header for MyEventSegment


#ifndef MYEVENTSEGMENT_H                          (1)
#define MYEVENTSEGMENT_H

#include <CEventSegment.h>
#include <CDocumentedPacket.h>
#include <CAENcard.h>

/*! \brief A class to read out a V785
 *
 * This class derives from the CEventSegment class and defines the basic
 * functionality we desire of the V785 during read. It will format the data in
 * a documented packet before sending it out.
 * 
 */
class MyEventSegment : public CEventSegment               (2)
{
  private:
    CDocumentedPacket m_myPacket;                         (3)
    CAENcard          m_module;                           (4)

  public:
    MyEventSegment(short slot, unsigned short Id);        (5)
    virtual void initialize();                            (6) 
    virtual void clear();                                 (7)
    virtual size_t read(void* pBuffer, size_t maxwords);  (8)

  private:
    uint64_t extractEventCount(uint16_t* pEOE);          (9)
};
#endif 
        

The header defines the class, its internal data and the services it exports to the readout framework. Refer to the circled numbers in the listing above when reading the following explanation

(1)
Each header should protect itself against being included more than once per compilation unit. This #ifndef directive and the subsequente #define do this. The first time the header is included, MYEVENTSEGMENT_H is not defined, and the #ifndef is true. This causes MYEVENTSEGMENT_H to be defined, which prevents subsequent inclusions from doing anything.
(2)
The class we are defining, MyEventSegment, implements the services of a base class called CEventSegment. Anything that reads out a chunk of the experiment is an event segment and must be derived from the CEventSegment base class.
(3)
The m_myPacket is a utility that will automate some of the formatting of the data. It will wrap the data read out of this device in a packet structure. This structure is a 32-bit inclusive size (units=16-bit words) followed by a 16-bit packet id and then a payload of data. Documented packets do the book-keeping associated with maintaining the packet structure as well as documenting their presence in documentation records written at the beginning of the run.
(4)
The support software for the CAEN V785 ADC is itself a class: CAENcard. We will create an object of this class in order to access the module.
(5)
This line declares the constructor for MyEventSegment. A constructor is a function that is called to initialize the data of an object when the object is being created (constructed).
(6)
The Initialize member function is called whenever the run is about to become active. If hardware or software requires initialization, it can be done here. This function will be called both when a run is begun as well as when a run is resumed.
(7)
The clear method is called when it is appropriate to clear any data that may have been latched into the digitizers. This occurs at the beginning of a run and after each event is read out.
(8)
The read method is called in response to a trigger. This member must read the part of the event that is managed by this event segment.
(9)
We are declaring here a method that will extract the event count from the end of event word added by the V785. This will be used for our timestamp.

We will also create an implementation file: MyEventSegment.cpp. This file will implement the member functions that were defined by MyEventSegment.h above. The contents of this file are shown below:

Example 12-2. Impementation of CMyEventSegment


#include <config.h>
#include <string>
#include <stdint.h>

#ifdef HAVE_STD_NAMESPACE                             (1)
using namespace std;
#endif

// Set the polling limit for a timeout
static unsigned int CAENTIMEOUT = 100;

#include "MyEventSegment.h"                             (2)

// Packet version -should be changed whenever major changes are made
// to the packet structure.
static const char* pPacketVersion = "1.0";                    (3)

//constructor set Packet details
MyEventSegment::MyEventSegment(short slot, unsigned short Id):
  m_myPacket(Id,"My Packet","Sample documented packet",pPacketVersion),
  m_module(slot)                                           (4)
{
}

// Is called right after the module is created. All one time Setup
// should be done now.
void MyEventSegment::initialize()
{
  m_module.reset();                                        (5)
  clear();
}

// Is called after reading data buffer
void MyEventSegment::clear()
{
  // Clear data buffer
  m_module.clearData();                                    (6) 
}

//Is called to readout data on m_module
size_t MyEventSegment::read(void* pBuffer, size_t maxsize)
{
  // Loop waits for data to become ready
  for(int i=0;i<CAENTIMEOUT; i++) {                      (7)

    // If data is ready stop looping
    if(m_module.dataPresent()) {
      break;
    }
  }

  size_t nShorts = 0;
  // Tests again that data is ready
  if(m_module.dataPresent())
  {
    // Opens a new Packet
    uint16_t* pBufBegin = reinterpret_cast<uint16_t*>(pBuffer); (8)
    uint16_t* pBuf = m_myPacket.Begin(pBufBegin);            (9)

    // Reads data into the Packet
    int nBytesRead = m_module.readEvent(pBuf);               (10)

    // Closes the open Packet
    uint16_t* pBufEnd = m_myPacket.End(pBuf+nBytesRead/sizeof(uint16_t));  (11)

    nShorts = (pBufEnd-pBufBegin);                           (12)

    // set the timestamp
    setTimestamp(extractEventCount(pBufEnd-2));             (13) 
  }

  return nShorts;                                            (14)
}

// Extract the lower 24-bits of the end of event word
uint64_t MyEventSegment::extractEventCount(uint16_t* pEOE) 
{
    uint64_t count =  *(pEOE)<<16;
    count          |= *(pEOE+1);
    return (count&0x00ffffff);                          (15)
}
        
(1)
The lines beginning with the #include of config.h and ending with the #endif near here are boilerplate that is required for all implementation (.cpp) files for Readout skeletons at version 8.0 and later.
(2)
In order to get access to the class definition, we must include the header that we wrote that defines the class
(3)
Recall that we will be putting our event segment into a packet. The packet will be managed by a documented packet (CDocumentedPacket) object. This packet can document the revision level, or version of the structure of its body. The string pPacketVersion will document the revision level of the packet body for our packet.
(4)
This code in the constructor is called an initializer list. Initializer lists specify a list of constructor calls that are used to construct base classes and data members of an object under construction. The Id constructor parameter is used as the id of the m_myPacket CDocumentedPacket. The three strings that follow are, respectively, a packet name, a packet description and the packet body revision level. These items are put in the documentation entry for the packet that is created by the packet at the beginning of the run. Lastly, the CAENcard object is constructed using slot number of the V785 for the geographical address of the module.
(5)
This code initializes the CAEN V785 module by resetting it to the default data taking settings, and then invoking our clear member function to clear any data that may be latched.
(6)
This code clears any data that is latched in the module.
(7)
The trigger for reading this device out was generated by a separate module that may have been generated prior to the conversion has been completed and thereby ready to read out. For this reason, we will poll the device a finite number of times until the device indicates it is ready to be read out. The CAENcard::dataPresent() function returns true when the module has converted data buffered for read out. At that time, control breaks out of the loop.
(8)
The address of the buffer was passed to the method as a pointer without a type. We call this a "void pointer" (i.e. void*). Though useful for passing an address to a generic chunk of memory, there is little else useful that can actually be done with it. To make this more meaningful for filling our buffer, we declare that the pointer is referring to memory segments of 16-bit width (i.e. type = uint16_t). Since the compiler has no way of understanding that this is valid way to treat the buffer, we must flag this as a special type of cast where the memory is reinterpreted to be of a different type. We are basically telling the compiler that we know enough about what we are doing that it should allow us to do so.
(9)
This call to the Begin member of our documented packet indicates that we are starting to read data into the body of the packet. The code reserves 32-bits for the word count and then writes the 16-bit packet id. The return value is a "pointer" to the body of the packet.
(10)
Data are read from the ADC into the packet.
(11)
The size of the packet is written to the space reserved for it, the packet is closed, and a "pointer" is returned to the next free word in the buffer.
(12)
The number of 16-bit words that have been added to the buffer is computed. It is computed by taking the distance between pointers that reference the original position in the buffer before data was added and the position afterwards.
(13)
By calling the CEventSegment::setTimestamp() method, we are ensuring that the event is emitted with a body header. Our solution is to just assign the event count.
(14)
Returns the number of 16-bit words added to the data.
(15)
The function takes a pointer to the first 16-bits of the end of event word. From the V785 manual, we know that this is a 32-bit word whose lower 24 bits encode the event count. We just need to do some bit-wise arithmetic to extract those 24 bits and return their value.

The numbers below refer to the circled numbers in the example above.

12.3.2. Integrating your event segment with Readout

Once you have created one or more event segments, you must register them with the Readout software. Whenever the Readout software must do something to its event segments, it calls the appropriate member function in each event segment that has been registered, in the order in which it has been registered.

Edit Skeleton.cpp. Towards the top of that file, after all the other #include statements, add:


#include <CCAENV262Trigger.h>
#include "MyEventSegment.h"
            

This is necessary because we will be creating an object of class MyEventSegment. We have also included a predefined class for the CCAENV262 IO register. We use this device as a trigger interface.

Next, locate the function CMyExperiment::SetupReadout() Modify it to create an instance of MyEventSegment and register it to the experiment. In this method we will also instantiate our trigger instance. We provide the base address as the argument to the CCAENV262Trigger constructor which should have been set using the jumper switches on the board to be 0x00100000.


void
CMyExperiment::SetupReadout(CExperiment* pExperiment)
{
  assert(pExperiment!=0);
  CReadoutMain::SetupReadout(pExperiment);
  pExperiment->AddEventSegment(new MyEventSegment(10, 0xff00));

  // Register a the trigger module that is situated at base address
  // 0x00100000.
  pExperiment->EstablishTrigger(new CCAENV262Trigger(0x00100000));
}
            

This code creates a new event segment for a CAEN V785 in slot 10, which will be read out into a packet with ID 0xff00.

12.3.3. Compiling the Readout program

Now that the source code for the Readout program is complete, we need to compile it into an executable.

First, edit the Makefile supplied with the skeleton code so that it knows about your additional program files. Locate the line that reads:


Objects=Skeleton.o
            

and modify it so that it reads:


Objects=Skeleton.o MyEventSegment.o
            

Save this edit, exit the editor and type:


make
            

This will attempt to compile your readout software into an executable program called Readout. If the make command fails, fix the compilation errors indicated by it and retry until you get an error free compilation