The GSI Fragment Separator Website
Your portal to information about the FRS!
 People   Research   Meetings   Technical info 
You are in GSI » FRS » Technical » FRS data acquisition » FRS stand-alone system » FRS user function

The user function f_user.c

When working within the MBS framework, all the individual user has to provide are routines for the initialization and readout of the specific digitizers being used.

To this end, a standard init & readout function (f_user.c) has been developed for use with the FRS detector setup.

This function can of course be modified in order to include other, user-specific, components.

Another important feature, defined by f_user.c, is that the user can define which actions should be taken on each of the 15 available trigger types.

Two trigger types are reserved by the system (type 14 = start-of-acquisition; type 15 = end-of-acquisition), but triggers 1-13 can be user-defined.

In comparison with the previous CAMAC-based acquisition, where reading from and writing to the modules was executed by means of CNAF calls, communication in the VME-based system is a little bit more complicated.

This is because the address space can differ quite substantially between various VME module types, which requires more attention to detail.

In addition, the base address of an individual VME unit is not a priori determined by the slot in which it resides, but it must be defined (before inserting it!) for each module.

In order to facilitate the use of the VME modules in the FRS setup, a library of "definition files" containing various address definitions and functions has been setup.

Files are already available for standard modules, such as ADCs, QDCs, TDCs and scalers, and as more units are implemented more routines will be added.

Sample f_user.c

Let us go through a sample f_user.c file in order to discuss various features.

The example illustrates the readout of three modules:

the TITRIS time stamp unit, a V820 scaler and a V785 ADC, all sitting in the same VME crate (number 0).

Only one trigger type (number 1) is defined.

Preamble

First we need to load various definitions, related to MBS and more specific FRS-related ones.

The time stamp module is defined separately.

// f_user.c
//      version for FRS, VME modules only
 
// include "standard" definitions:
#include 
#include 
#include 
#include 
#include 
#include  

// include FRS-specific definitions:
#include "frsvme.h"
#include "v785a.h"   // peak-sensing ADC
#include "v820a.h"   // scaler

// definitions for the TITRIS time stamp module:
#define TM__STAT_OFF       0x0
#define TM__CTRL_OFF       0x4
#define TM__TIME_L16_OFF   0x8
#define TM__TIME_M16_OFF   0xC
#define TM__TIME_H16_OFF   0x10
#define SUB_SYSTEM_ID      0x200 // sub-system identifier frs
#define TS__ID_L16         0x0f7
#define TS__ID_M16         0x1f7
#define TS__ID_H16         0x2f7

// declarations for the TITRIS time stamp module:
static volatile INTU4 *pl_base;
static volatile INTU4 *pl_stat;
static volatile INTU4 *pl_ctrl;
static volatile INTU4 *pl_time_l16;
static volatile INTU4 *pl_time_m16;
static volatile INTU4 *pl_time_h16;
static INTU4 l_stat;
static INTU4 l_ctrl;
static INTU4  l_time_l16;
static INTU4  l_time_m16;
static INTU4  l_time_h16;

// some other declarations:
chars      c_modnam[] = "";
chars      c_line[256];

Virtual pointers

In order to communicate with the VME modules, the program must know where they reside inside the VME address space - so we must define the pointers to the "base addresses".

We have predefined functions for getting the pointer information for "standard" modules - the time stamp unit is a bit special, so we deal with it "by hand".

int f_user_get_virt_ptr (long  *pl_loc_hwacc, long  pl_rem_cam[])
{
// defining pointers for the TITRIS time stamp module:
  pl_base = (INTU4*) ((INTU4)pl_loc_hwacc);
  pl_stat = (INTU4*) ((INTU4)pl_base + (INTU4)TM__STAT_OFF);
  pl_ctrl = (INTU4*) ((INTU4)pl_base + (INTU4)TM__CTRL_OFF);
  pl_time_l16 = (INTU4*) ((INTU4)pl_base + (INTU4)TM__TIME_L16_OFF);
  pl_time_m16 = (INTU4*) ((INTU4)pl_base + (INTU4)TM__TIME_M16_OFF);
  pl_time_h16 = (INTU4*) ((INTU4)pl_base + (INTU4)TM__TIME_H16_OFF);
  printf ("master timing module: \n");
  printf ("stat:   0x%x, ctrl:   0x%x \n", pl_base, pl_ctrl);
  printf ("low_16: 0x%x, mid_16: 0x%x, high_16: 0x%x \n",
                                        pl_time_l16, pl_time_m16, pl_time_h16);
  printf ("\n");
// create VME segment  with smem_create () function:
    MapFRSVMEMemory() ;
// define pointer for the V785 ADC, using a function defined in v785a.h :
    V785a_GetPointers () ;
// define pointer for the V820 scaler, using a function defined in v820a.h :
    V820a_GetPointers () ;
    return 0;
}

Initialization

In the initialization function, we can specify all function calls that should be performed each time the system receives a Trigger Type 14 - that is, the acquisition is started.

(For advanced users:

If you want to read something out when you start, such as threshold values, you should add these instructions to the readout part, for the case trigger type 14

- otherwise the event length pointer will not be defined correctly!

The same applies for reads in connection with Trigger Type 15.)

int f_user_init (unsigned char  bh_crate_nr,
                 long           *pl_loc_hwacc,
                 long           *pl_rem_cam,
                 long           *pl_stat)
{
    long l_dat; 
    switch (bh_crate_nr)
      {
      case 0:
// initialization of V785a  -- all functions are defined in v785.h, so we must specify the v785a pointer:
// reset the module:
        V785_SoftReset(ps_v785a) ;
// load standard settings (see manual!) :
        V785_StandardSetup(ps_v785a) ;
// for fun, let's turn off zero suppression (which is ON as standard):
        V785_ZeroSuppressionOff(ps_v785a) ;
// now enable all 32 channels by software:
        V785_EnableAllChannels(ps_v785a) ;
// but we only need ch 0-15, so we can disable the rest:
        for(i=16 ; i < 32; i++) 
          V785_DisableChannel(ps_v785a,i) ;
// since we won't use zero suppression, we might as well set all thresholds = 0:
        V785_ResetThresholds(ps_v785a) ;
// initialization of V820a  -- all functions are defined in v820.h, so we must specify the v820a pointer:
// reset the module:
        V820_ResetModule(ps_v820a) ;
        break;
      default:
        break;
      }
      return (1);
}

Readout

int f_user_readout (unsigned char  bh_trig_typ,
                    unsigned char  bh_crate_nr,
                    register long  *pl_loc_hwacc,
                    register long  *pl_rem_cam,
                    long           *pl_dat,
                    s_veshe        *ps_veshe,
                    long           *l_se_read_len,
                    long           *l_read_stat)
{
    int i, ncV775 ;
    long l_chan_nr;
    long l_dat;
    l_chan_nr = 1;
    *l_se_read_len = 0;
    *pl_dat++ = SUB_SYSTEM_ID;
    *l_se_read_len =+ 4;
    switch (bh_crate_nr)
    {
    case 0 :
      switch (bh_trig_typ)
        {
        case 1:   //  normal FRS trigger 
// read the time stamp module :
          l_time_l16 = *pl_time_l16;
          l_time_m16 = *pl_time_m16;
          l_time_h16 = *pl_time_h16;
// reformat the data to include word identifiers :
          l_time_l16 = (l_time_l16 & 0xffff) + (TS__ID_L16 << 16);
          l_time_m16 = (l_time_m16 & 0xffff) + (TS__ID_M16 << 16);
          l_time_h16 = (l_time_h16 & 0xffff) + (TS__ID_H16 << 16);
// output the data into the data stream :
          *pl_dat++ = l_time_l16;
          *pl_dat++ = l_time_m16;
          *pl_dat++ = l_time_h16;
// important: update the event length pointer!!!
          *l_se_read_len += 3 * 4;
// read the scaler data and send it into the data stream. 
//   nifty feature: -- the call returns the relevant increment to the event length pointer
//   NOTE: the number of channels (here 8) to be read is passed by the function call  
          *l_se_read_len += V820_GetOutputPointer(ps_v820a,&pl_dat,8) ;
// read the scaler data and send it into the data stream. 
//   nifty feature: -- the call returns the relevant increment to the event length pointer
//   NOTE: data is returned from all enabled channels that fullfilled the threshold comparison   
          *l_se_read_len += V785_GetOutputPointer(ps_v785a,&pl_dat) ;
          break;
        case 15:   // stop-of-acquisition
// check if memory segment was properly removed: 
              if ((i=smem_remove((char *)FRS__VME_NAME)) != 0)
          printf("\nMemory segment not removed!(%d)\n", i) ;
              break;
        default:
          printf("\nERROR**:crate 0 Unexpected trigger (%d)", bh_trig_typ);
          break;
        }
      break;                      
    default:
      break;
        }
        return (1);
}

Now, that wasn't too complicated, was it?

Sample definition files: v785.h and v785a.h

Because the definition files were written for use also with other programs than f_user.c in mind, they contain a large number of functions and definitions which are not specifically needed for the readout.

Here we present and discuss only the most important features - you can learn more by browsing copies of the actual files themselves.

v785a.h - the individual module

Each individual module ofd a specific type should have its own .h-file - if you add another V785 to the readout, it should be called "v785b" and its pointer information be defined in v785b.h.

All V785-specific routines only need to be defined once in v785.h.

//            V785a

#ifndef V785a__NAME
#define V785a__NAME      "V785a"

// check hardware setting on module and change accordingly:
#define V785a__VME_BASE   0x50030000L  
//------------------------^^^   rio-2: 0xE...,  rio-3: 0x5... !!!

// include other files with definitions:
#include "frsvme.h"
#include "v785.h"

// declare specific variable type:
s_v785 *ps_v785a  ;
char  *ph_v785a_seg ;

// ----- Pointer construction -----
int V785a_GetPointers () 
{  
  ps_v785a = (s_v785 *) malloc (sizeof (s_v785));
  V785_GetPointers ((long)V785a__VME_BASE, ps_v785a, ph_v785a_seg) ;
  strcpy(ps_v785a->name, (char *)V785a__NAME) ;
  
  return (1);
}

#endif

v785.h - the V785 ''class''

This file contains all V785-specific information - such as pointer address offsets relative to the base address - and all routines needed to communicate with modules of this type.

To execute a given function for a specific V785 unit (here v785a), the function call just needs the appropriate pointer name as argument.

In the following, an abbreviated version of v785.h is presented, illustrating some of the functions that could typically be called from within f_user.c:

//            V785.h
// definitions and elementary functions
//

#ifndef V785__NAME
#define V785__NAME      "V785"
#define V785__VME_LEN    0x10000   

#include "frsvme.h"

// ----- V785 key addresses offsets with respect to V785__VME_BASE 
#define V785__OUTPUT_BUFFER        0x0000   
#define V785__GEO_ADDR             0x1002
#define V785__BIT_SET_1            0x1006
#define V785__BIT_CLEAR_1          0x1008
#define V785__BIT_SET_2            0x1032
#define V785__BIT_CLEAR_2          0x1034
#define V785__THRESHOLDS           0x1080       

// define the s_v785 struct:
typedef struct
{
  char name[20] ;
  long volatile  *pl_output_buffer;
  short volatile *pi_geo_addr    ;  
  short volatile *pi_bit_set_1   ;         
  short volatile *pi_bit_clear_1   ;      
  short volatile *pi_bit_set_2   ;
  short volatile *pi_bit_clear_2   ;      
  short volatile *pi_event_counter_reset   ;   
  short volatile *pi_thresholds       ;
} s_v785 ;

// define some helper variables:
int ncV785, val[32] ;
long temp, dummy, lwtype;

// now define pointers, using the offsets defined above...:
int V785_GetPointers (long V785__VME_BASE, s_v785 *ps_v785, 
              char  *ph_v785_seg) 
{
  if (FRSVMECrate) 
    ph_v785_seg = (char *) ((long) ph_frs_vme_modules_seg + 
                (long) V785__VME_BASE - 
                (long) FRS__VME_MODULES_BASE );
  ps_v785->pl_output_buffer = 
    (long *) ((long) ph_v785_seg + (long) V785__OUTPUT_BUFFER );
  ps_v785->pi_geo_addr      = 
    (short*) ((long) ph_v785_seg + (long) V785__GEO_ADDR );
  ps_v785->pi_bit_set_1     = 
    (short*) ((long) ph_v785_seg + (long) V785__BIT_SET_1);         
  ps_v785->pi_bit_clear_1   = 
    (short*) ((long) ph_v785_seg + (long) V785__BIT_CLEAR_1);      
  ps_v785->pi_bit_set_2     = 
    (short*) ((long) ph_v785_seg + (long) V785__BIT_SET_2);         
  ps_v785->pi_bit_clear_2   = 
    (short*) ((long) ph_v785_seg + (long) V785__BIT_CLEAR_2);      
  ps_v785->pi_event_counter_reset = 
    (short*) ((long) ph_v785_seg + (long)  V785__EVENT_COUNTER_RESET);   
  ps_v785->pi_thresholds      =
    (short*) ((long) ph_v785_seg + (long) V785__THRESHOLDS);  
  return (1);
}
       

void V785_SoftReset (s_v785 *ps_v785)
{
//  Achieve a "soft reset" by toggling bit 7 of register 1
  l_val = *ps_v785->pi_bit_set_1 ;
  SetBit1(&l_val, 7) ;
  *ps_v785->pi_bit_set_1 = l_val;
  l_val = *ps_v785->pi_bit_clear_1 ;
  SetBit1(&l_val, 7) ;
  // in addition, set bits 3 and 4 to OFF -- this clears ev. buffer errors etc.
  SetBit1(&l_val, 3) ;
  SetBit1(&l_val, 4) ;
  *ps_v785->pi_bit_clear_1 = l_val;
  return ;
}

void V785_StandardSetup (s_v785 *ps_v785)
{
//  Initialize the V785 ADC for standard list mode operation & clear data & pointers...
//
  // toggle bit 2 to clear data & read/write pointers:
  l_val = *ps_v785->pi_bit_set_2 ;
  SetBit1(&l_val, 2) ;
  SetBit0(&l_val, 6) ;
  *ps_v785->pi_bit_set_2 = l_val;
  l_val = *ps_v785->pi_bit_clear_2 ;
  SetBit1(&l_val, 2) ;
  *ps_v785->pi_bit_clear_2 = l_val;
  // clear the event counter:
  *ps_v785->pi_event_counter_reset = 1 ;
  // ----- turn OFF the following non-reserved bits:
  // (see v785 manual for details!)
  l_val = *ps_v785->pi_bit_clear_2 ;
  // bit 0: MEM TEST [default = 0]
  SetBit1(&l_val, 0) ;
  // bit 1: OFFLINE [default = 0]
  SetBit1(&l_val, 1) ;
  // bit 3: OVER RANGE [default = 0]
  SetBit1(&l_val, 3) ;
  // bit 4: LOW THRESHOLD [default = 0]
  SetBit1(&l_val, 4) ;
  // bit 5: ??? [default = 0]
  //  SetBit1(&l_val, 5) ;
  // bit 6: TEST ACQ [default = 0]
  SetBit1(&l_val, 6) ;
  // bit 12: EMPTY PROG [default = 0]
  SetBit1(&l_val, 12) ;
  // bit 13: SLIDE_SUB ENABLE [default = 0]
  SetBit1(&l_val, 13) ;
  *ps_v785->pi_bit_clear_2 = l_val;
  // ----- turn on relevant bits of BIT SET 2 register
  // (see v785 manual for details!)
  l_val = *ps_v785->pi_bit_set_2 ;
  // bit 7: SLD ENABLE [default = 1]
  SetBit1(&l_val, 7) ;
  // bit 8: STEP THRESHOLD [default = 0, but we chose to turn this ON]
  SetBit1(&l_val, 8) ;
  // bit 11: AUTO INCR [default = 1]
  SetBit1(&l_val, 11) ;
  // bit 14: ALL TRG [default = 1]
  SetBit1(&l_val, 14) ;
  *ps_v785->pi_bit_set_2 = l_val;
  return ;
}

void V785_ZeroSuppressionOn (s_v785 *ps_v785)
{
//  this function turns zero suppression ON
  l_val = *ps_v785->pi_bit_clear_2 ;
  SetBit1(&l_val, 4) ;
  *ps_v785->pi_bit_clear_2 = l_val;
  return ;
}

void V785_EnableAllChannels (s_v785 *ps_v785 )
{
// enables all channels
//
  for (i=0; i<32; i++) {
    l_val = *(ps_v785->pi_thresholds+1*i) ;
    SetBit0(&l_val , 8) ;
    *(ps_v785->pi_thresholds+1*i) = l_val;
   }
  return ;
}

void V785_DisableChannel (s_v785 *ps_v785 , int n)
{
// disables a specific channel
//
  if ( n>0 && n<32 ) {
    l_val = *(ps_v785->pi_thresholds+1*n) ;
    SetBit1(&l_val , 8) ;
    *(ps_v785->pi_thresholds+1*n) = l_val;
  }
  return ;
}

void V785_ResetThresholds (s_v785 *ps_v785)
{
//  Reset/Initialize the V785 thresholds to 0
//
  for(i=0 ; i < 32 ; i++)
    V785_SetThreshold(ps_v785, i, 0) ;
}

void V785_SetThreshold (s_v785 *ps_v785, int n, int val)
{
// Set the threshold value [val] for the n_th channel w/o overwriting the "kill" bit (8)!
//
  if (n>= 0 && n < 32 && val <= 255) {
    l_val = ( (*(ps_v785->pi_thresholds + 1*n) >> 8)<< 8) + val;
    *(ps_v785->pi_thresholds + 1*n) = l_val ;
      }
  return ;
}

int V785_GetOutputPointer (s_v785 *ps_v785, long **pl_dat)
{
// Read the Output Buffer, pass the content of the ADC into pl_dat & and return the length in bytes
//
  // read the first longword:
    temp = *ps_v785->pl_output_buffer;
  // extract longword "type" (2=header,6=invalid data):
    lwtype = (temp & 0x7000000) >> 24 ;
  if ( lwtype == 2 ) {
  // extract number of data longwords:
    ncV785 = ( temp >> 8 ) & 0x3F ;
  // pass on the header long word after reformatting it a bit:
  //  push the "number of converted chns" to the lowest 6 bits...
    *((*pl_dat)++) = (temp & 0xffff0000) + ncV785;
  // read and pass on the data in "original" format (long words):
    for(i=0 ; i < ncV785; i++)
      *((*pl_dat)++) = *ps_v785->pl_output_buffer;  
  // read the EOB longword (GEO and "event counter" info):
    *((*pl_dat)++) = *ps_v785->pl_output_buffer ;
    ncV785++ ;    // N_Channel + EventCounter-buffer
  } else {  // most likely no valid data (lwtype was 6)
  // IF "no valid data" we need to reformat the header info a bit:
  //  we pass on the GEO address + the "not valid datum" identifier
  //  and also return "number of channels" as zero
    ncV785 = 0.;
    temp = (temp & 0x7000000) + ((*ps_v785->pi_geo_addr & 0x1F) << 27) ;
    *((*pl_dat)++) = temp;
  }
  // ----- Length of 1 V785 -> 1 (WordCount) + ncV785 [in bytes]  
  return (1 + ncV785) * 4 ;
}

int V785_GetStatusPointer (s_v785 *ps_v785, long **pl_dat)
{
// Read various status info (thresholds etc.) and return this info as a simulated data event
// (to be used e.g. for Trigger Type 14...)
//
  // extract GEO etc. from module
    temp = ((*ps_v785->pi_geo_addr & 0x1F) << 27) +
      (0x3 << 24) + 32;
    *((*pl_dat)++) = temp;
  // cycle over all 32 chns and write threshold info
    for(i=0 ; i < 32; i++) {
      temp = ((*ps_v785->pi_geo_addr & 0x1F) << 27) +
        (0x0 << 24) + (i << 16) +
        (*(ps_v785->pi_thresholds+1*i) & 0x1FF ) ;
      *((*pl_dat)++) = temp;
    }
  // create an end-of-block containing BitRegister2
    temp = ((*ps_v785->pi_geo_addr & 0x1F) << 27) +
      (0x4 << 24) + *ps_v785->pi_bit_set_2;
    *((*pl_dat)++) = temp;
  return (34) * 4 ;
}

#endif
 
Comments? Questions? Error reports? Please contact the webmaster!
The FRS web pages have been accessed 26999 times since October 27, 2003!
Script file last updated on July 21, 2006.