Back: 14.2.4 Returning data from EDL functions Forward: 14.3.1 EDL Functions   FastBack: 14. Writing Modules Up: 14. Writing Modules FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3 How to write a new module


Back: 14.3 How to write a new module Forward: 14.3.2 Files to be included   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.1 EDL Functions

Each module has its own unique name, it's often most convenient to pick the name of the device. Beside, a device usually belongs to a certain group, i.e. lock-in-amplifiers, digitizers, gaussmeters etc. As you will already have understood, the names of the EDL functions should be chosen to start with the type of the device, followed by an underscore and a name, describing what the function is supposed to do. Typical examples are lockin_get_data() or digitzer_time_constant().

Please note that there aren't two separate function, one for setting the digitizers time constant and one for asking the digitizer for the currently set time constant. Instead there is a single function that can be used for both purposes. What it's supposed to do gets recognized from the number of arguments: if there's no argument it returns the currently set time constant, otherwise it sets the time constant to the value passed to the function (at least if the value is reasonable). You should try to follow this convention if possible.

Another convention I am following when inventing function names is that if one can either only set a certain value for a device or get some data from it with that function I always use either set or get in the function name. E.g. it's not possible to send data values to a lockin-amplifier, thus I use the name lockin_get_data() (and not e.g. lockin_data()). In names for functions that can be used for setting as well as receiving data I try to avoid these words.

All functions to be invoked via an EDL script take their arguments in the form of the variables as described above and return a pointer to such a variable.


Back: 14.3.1 EDL Functions Forward: 14.3.3 Variables a module must define   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.2 Files to be included

First of all, each module has to include the header file `fsc2_module.h' - otherwise it will not be able to use fsc2's variables. It should not include `fsc2.h', this header file is for fsc2 itself. `fsc2_module.h' already includes all definitions and declarations of macros, variables and functions of fsc2 that can be used within modules.

The module may also have to include a header file for a library dealing with the interface used for connecting the device to the computer. If you're using a device on the GPIB bus you need to include `gpib.h', if it's connected to one of the serial ports `serial.h' or, if the device is controlled via LAN, the file `lan.h' (all these files are in the source directory of fsc2, so just put the file name into double quotes and it will be found automatically). For other devices some other include file may be required, e.g. `rulbus.h' for the RULBUS, that usually reside in some of the standard include directories and thus the file name will have to be enclosed in angle braces.

Finally, each module must put its basic configuration information into a special file which should be commented well enough to allow even people without much programming experience to adapt the behavior of the module to his/her needs. A good example are modules for devices that are accessed via the serial port. Because you probably won't know which serial port the user is going to use you shouldn't hide this information somewhere deep down in the innards of your module but put it in a prominent place where it is easy to find. Thus this is one of the items that should go into the configuration file.

All configuration files are in the `config' directory. For obvious reasons the names of the configuration files should make it clear for which module they are used for. Currently, all of them have the extension .conf. Each configuration file must contain at least two items: first a string with the device name should be defined, e.g.

 
#define DEVICE_NAME     "TDS754A"

This device name should be used in all places where the module has to print out error messages or warnings. For devices connected via the GPIB bus this device name should be identical to the one it is advertised as in the GPIB configuration file (usually `/etc/gpib.conf').

It is probably a good idea to select a name for a device that is identical to the name of the module in order to avoid confusion for the users.

For each module also a second string needs to be defined which describes the device type, e.g.

 
#define DEVICE_TYPE     "digitizer"

The device type string is used by fsc2 when more than one device with the same functionality is being used by an EDL script. You probably already have read that when you have two such devices you can access the second one by appending a '#2' when calling an EDL-function. But, obviously, for this to work fsc2 must know which devices have similar capabilities and which don't. This it finds out from the device type string. Thus if you decide which device type string you're going to use please first check the device types of other devices as defined in their configuration files. If your device is similar enough to one of the existing devices pick the same device type string, otherwise pick a new and descriptive name.


Back: 14.3.2 Files to be included Forward: 14.3.4 Global variables   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.3 Variables a module must define

In the previous section the meanings of the device name and type strings have already been discussed. While the definitions of the strings should go into the configuration file for the device, no memory has been allocated for these strings yet. This should be done as one of the first things after the include files have been included. Each and every device module should define two constant character arrays called device_name and generic_type, that contain the device name and type strings, i.e. one of the first lines should always be

 
const char device_name[ ]  = DEVICE_NAME;
const char generic_type[ ] = DEVICE_TYPE;

or

 
const char * device_name  = DEVICE_NAME;
const char * generic_type = DEVICE_TYPE;

fsc2 will use the first variable with the device name while printing warnings and error messages. The second string is needed to find out about the type of the device. If this variable does not exist fsc2 won't have any information about the device type and recognizing another device of the same type automatically will fail (in which case e.g. using more than one device of a certain type won't work)-

Another important point is that if in two modules (with different generic_type settings) define the same function name only one of these modules can be used at the same time. If the user tries to load both modules in the DEVICES section an error message will be printed and interpretation of the EDL script stopped. Thus it must be avoided to use identical EDL function names in modules for devices of different types.


Back: 14.3.3 Variables a module must define Forward: 14.3.5 Device Locking   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.4 Global variables

First, there is a global variable(25), called FSC2_MODE, which tells you in which context your module function is called. There are three different contexts: the program can be either interpreting the VARIABLES or PREPARATIONS section, do a test run, or do the experiment. While the program interprets the VARIABLES or PREPARATIONS section FSC2_MODE is set to the predefined value PREPARATION. At this stage the devices are not initialized yet and can't be accessed.

Before the real experiment is started a test run of the EXPERIMENT section must is done. In this context your module function still can't access the devices but must try to return reasonable dummy data. That means that the module functions should at least return data of the same type as they will do in the actual experiment. E.g., if a function will return an array during the experiment it should do the same during the a test run, even though the data in the array probably are going to be completely bogus. During the the test run the variable FSC2_MODE is set to TEST.

Finally the experiment gets started. Now your module can talk to the devices and can return 'real' values. During this stage the FSC2_MODE variable is set to the value EXPERIMENT (it's already set to this value when the exp_hook functions (see below) are run).

Thus you will probably often have constructs like the following in your module functions:

 
switch ( FSC2_MODE )
{
    case PREPARATION :
        /* print an error message that this functionality is */
        /* only available from within the EXPERIMENT section */
        break;

    case TEST :
        /* return some reasonable dummy value */
        break;

    case EXPERIMENT :
        /* do something only allowed when you can talk to the */
        /* device, i.e. from within the EXPERIMENT section    */
        break;
}

The second important global variable, Need_GPIB, is of type bool and has to be set in the init hook function if the device is controlled via the GPIB bus. Thus, if the GPIB bus is needed, include a line in the init hook function similar to

 
Need_GPIB = SET;

If you forget to set this variable chances are high that the program will stop with an error message, complaining that it can't access the GPIB bus.

For devices that use the Rulbus another global variable, Need_RULBUS and also of type bool, has to be set in the init hook function.

For devices that are connected via a USB port and require support via the libusb-0.1 or libusb-1.0 library the global variable Need_USB (again of type bool) must be set in the init hook function. libusb library initialization is then done by fsc2, the module just has to find and open the device and then should be able to communicate with it.

Finally, for devices for which LAN is used for communication the variable Need_LAN (also of type bool) need to be set in the init hook function.


Back: 14.3.4 Global variables Forward: 14.3.6 Handling GPIB Devices   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.5 Device Locking

While being used in the experiment devices must be locked against the use by other instances of fsc2. In most cases you don't have to take care of this yourself, e.g. as long as you use just the functions built-in into fsc2 to communicate with the devices this happens automatically.

But in case you have to write a module that by-passes the built-in methods you also have to take care of locking. The that case the following two functions can be used:

This function tries to obtain a lock on a device. Its declaration is

 
bool fsc2_obtain_lock( const char * name );

where the only argument is the (unique) device name. The function tries to create a UUCCP style lock file. typically in `/var/lock' (unless the configuration variable LOCK_DIR has been set to something different, make sure fsc2 has write permissions for that directory). On success the function returns OK (1), otherwise FAIL (0). You shouldn't continue unless the function was successful.

This function releases the lock on the device. Its declaration is

 
void fsc2_release_lock( const char * name );

The argument is again the same device name as was used when obtaining the lock.


Back: 14.3.5 Device Locking Forward: 14.3.7 Serial Port Handling   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.6 Handling GPIB Devices

To make dealing with the GPIB bus simpler there are several routines that can be used when writing a module, which then call the needed functions from the GPIB library you choose when installing fsc2. To be able to use that functions you must include the appropriate header file for tge GPIB interface, i.e.. you need

 
#include "gpib.h"

Please note that these function can't be invoked before the exp_hook function.

As already pointed out above, to be able to use the GPIB bus your module must have set the boolean variable need_GPIB in the init hook function!

All of the GPIB functions return a value indicating success or failure, on success the value SUCCESS is returned, otherwise FAILURE.

The first thing to do is to obtain the device from the library dealing with the GPIB bus. This should be done in the exp_hook function (see below) via a call of the function

 
int gpib_init_device( const char * name,
                      int        * device );

This function expects the name of the device (which will be used to look it up in the GPIB configuration file) and the address of an integer, which, on successful return, will contain a number now associated with the device and to be used in all further calls of GPIB functions for this device. The function may fail if either a device of that name can't be found in the GPIB configuration file or if the device is already in use by a different instance of fsc2.

The next two most important functions are

 
int gpib_write( int          device,
                const char * buffer,
                long         length );

int gpib_read( int    device,
               char * buffer,
               long * length );

The first functions sends length data contained in buffer to the device designated by device (which you got from a call of gpib_init_device()). The second function gpib_read() reads a maximum of length bytes from the device device and stores them in buffer. Before gpib_read() is called length must have been set to the maximum number of data that should be read and after a successful call length contains the number of bytes that really have been read.

When you're done dealing with a device you should call

 
int gpib_local( int device );

to bring it back into the local state. This function should be called in the end_of_exp_hook function (see below).

Using the function

 
int gpib_timeout( int device,
                  int period );

a new timeout value can be set for the device. The value of period depends on the values that the GPIB library you are using expect. Please check the manual for the library.

The function

 
int gpib_clear_device( int device );

clears the device by sending it the Selected Device Clear (SDC) message.

 
int gpib_trigger( int device );

triggers the device by sending it a Device Trigger Command.

Finally, there is a function that lets you obtain a string with a description of the last error that happened in the call of one of the GPIB functions.

 
const char * gpib_last_error( void )

It expects no arguments and returns a pointer to a string (that you may not modify) with the error description. Please note that the content of the string may change on each invokation of a GPIB function, so make a copy if you need to store the string.


Back: 14.3.6 Handling GPIB Devices Forward: 14.3.8 Handling devices connected via USB   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.7 Serial Port Handling

To be able use fsc2's interface to the serial ports you must include the appropriate header file, i.e. you need

 
#include "serial.h"

For serial ports things are handled a bit differently from GPIB devices. You don't need to set a special variable to advertise your intention of using one of them. Instead, in the init hook function you must request the serial port you need by calling the function

 
int fsc2_request_serial_port( const char * device_file,
                              const char * device_name );

with the name of the device file for the serial port (typically `/dev/ttyS0', `/dev/ttyS1' for normal serial ports and `/dev/ttyUSB0' etc. for USB-serial converters) as the first and a device name as the second argument. The device name is for use in lock and log files, error messages etc.

If the requested serial port has already been claimed by a different device the function will print an error message and stop the EDL script, so you don't have to deal with error handling yourself. In case of success it returns an integer number that in calls of all other functions identifies the serial port requested.

No other serial port function may be called before the exp_hook function.

During initialization of the experiment (and before the exp_hooks are called) fsc2 tries to obtain locks on all requested serial port devices and to open log files for each of them.

For all functions dealing directly with file descriptors for the serial port device files there are replacement functions. The following table lists all functions that normally are used with device file descriptors for serial ports with their replacements:

`open()'

fsc2_serial_open()

`close()'

fsc2_serial_close()

`write()'

fsc2_serial_write()

`read()'

fsc2_serial_read()

`tcgetattr()'

fsc2_tcgetattr()

`tcsetattr()'

fsc2_tcsetattr()

`tcsendbreak()'

fsc2_tcsendbreak()

`tcdrain()'

fsc2_tcdrain()

`tcflush()'

fsc2_tcflush()

`tcflow()'

fsc2_tcflow()

Note that all functions that normally expect a a file descriptor now must be called with the handle returned by fsc2_request_serial_port().

The only functions that are different from their normal counterparts are fsc2_serial_open(), fsc2_serial_write(), fsc2_serial_read() and fsc2_serial_close():

fsc2_serial_open() is defined as

 
struct termios * fsc2_serial_open( int sn,
                                   int flags )

where sn is the handle for the serial port as returned by fsc2_request_serial_port() and flags are the same flags you would pass to a normal open() call to determine if the port is to be used for writing only (O_WRONLY), for reading only (O_RDONLY) or for reading and writing (O_RDWR), other necessary flags are set automatically. The function opens the file and determines the current communication parameter settings for the serial port. These are returned via a pointer to a termios structure, that can be freely changed within the module. If the function returns a NULL pointer opening the device file failed and you can determine the reasons by checking errno. In case fsc2_request_serial_port() function wasn't called before an exception gets thrown.

fsc2_serial_write() and fsc2_serial_read() are defined as

 
ssize_t fsc2_serial_write( int    sn,
                           void * buf,
                           size_t count,
                           long   us_timeout,
                           bool   quit_on_signal );

ssize_t fsc2_serial_read( int          sn,
                          void       * buf,
                          size_t       count,
                          const char * term,
                          long         us_timeout,
                          bool         quit_on_signal );

where sn is the number of the serial port as returned by fsc2_request_serial_port(), buf is a buffer of length count for the data to be written or read. us_timeout is the time in microseconds you are prepared to wait the serial port becoming ready for writing or reading the data. Specifying a negative value is interpreted to mean that you want to wait indefinitely for data, while a value of 0 for us_timeout means not to wait at all. Finally, quit_on_signal determines if the function returns immediately when a signal has been received. This could for example happen when the user presses the Stop button while the function is waiting for data. For fsc2_serial_read() the term argument is an optional string with a termination sequence on which reading stops. If the device does not send a termination sequence at the end of the data simply pass a NULL pointer or an empty string to the function.

The functions return on success the number of bytes written or read, 0 if no data could be written or read (e.g. because the maximum time to wait was exceeded or a signal was received before the data could be written or read), and -1 on any other form of error.

fsc2_serial_close() expects just one argument, the serial port number. Before closing the serial port device file it flushes it and resets the communication parameters to their initial state. It also deletes lock files. (If you don't close the serial ports device files explicitely they will be automatically closed at the end of the experiment.)

All remaining functions are identical to their usual form (see the termios(3) man page for all details) except that the first argument is always the serial port number instead of a file descriptor. If the function gets passed an invalid serial port number errno is set to EBADF.


Back: 14.3.7 Serial Port Handling Forward: 14.3.9 Handling devices connected via LAN   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.8 Handling devices connected via USB

Currently, versions 1.0 and 0.1 of the libusb library are supported for communication with devices controlled via USB. You can recognize within your module which one gets compiled in by testing the macros WITH_LIBUSB_1_0 and WITH_LIBUSB_0_1 for existence (only one of them will be set, never both).

Please note: make sure that the DO_QUIT signal is blocked during calls of the functions from libusb, i.e. do something like this:

 
sigset_t new_mask,
         old_mask;

raise_permissions( );
sigemptyset( &new_mask );
sigaddset( &new_mask, DO_QUIT );
sigprocmask( SIG_BLOCK, &new_mask, &old_mask );

libusb_do_something( );

sigprocmask( SIG_SETMASK, &old_mask, NULL );
lower_permissions( );

Otherwise it could happen that this signal (that is raised when the user clicks onto the Stop button) gets received. And in this case communication with the device might be interrupted and the device left in an unknown and potentially unresponsive state. By blocking the signal this can be avoided.


Back: 14.3.8 Handling devices connected via USB Forward: 14.3.9.1 VXI-11 Protocol   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.9 Handling devices connected via LAN

There are some functions for the communication with devices connected via LAN (TCP only, there's no support for UDP yet) provided by fsc2. They allow you to connect to a device, send and receive data and finally close the connection. To be able to use these functions you need to include the appropriate header file, i.e. you need

 
#include "lan.h"

Naturally, the functions aren't written with a certain protocol in mind - writing that is your business (unless it's either the VXI-11 or LeCroy's VICP protocol, for which support already exists) - but just provide a set of fundamental routines on top of which you can write your own functions. You can as well forego using them and write your own code for talking with the devices directly, they only exist for your convenience. But if you use them make sure they don't get invoked before the exp_hook function.

The advantage of using them is that you don't have to care about basic operations (including dealing with timeouts etc.) and that the functions automatically log whatever gets send between the computer and the devices. How much is written to the log file (and where this log file is and is named) can be set as a compilation option, see the main `Makefile' or the settings for your machine in the `machines' subdirectory (if you created such a file).

Here is a complete list of these convenience functions:

`fsc2_lan_open()'

Opens a connection to a device

`fsc2_lan_write()'

Sends data to the device from a single buffer

`fsc2_lan_writev()'

Sends data to the device from a set of buffers

`fsc2_lan_read()'

Reads data from the device into a single buffer

`fsc2_lan_readv()'

Reads data from the device into a set of buffers

`fsc2_lan_close()'

Closes the connection to the device

While they are similar to the open(), write(), read() and close() function for dealing with normal file descriptors the all need several extra arguments.

The function opens a connection to the device and is declared as

 
int fsc2_lan_open( const char * dev_name,
                   const char * address,
                   int          port,
                   long         us_timeout,
                   bool         quit_on_signal )

The first argument, dev_name, is a string with the name of the device to be used when writing to the log file. The second argument, address is a string with the IP address of the device. This can be either the numeric IP address in dotted-quad format (e.g. "123.97.243.12") or a symbolic host name (e.g. "hex.discworld.unseen.edu"), for which the IP address then is determined via a DNS request. The third argument, port, is the port the device is accepting connections on, i.e. a number between 0 and 65535. The fourth argument, us_timeout, is the maximum time (in micro-seconds) to wait for a connection to be achieved. If set to 0 (or a negative value) the connection will only time out after a system dependent delay (typically in the order of a minute). Finally, the last argument quit_on_signal tells the function if it is supposed to return immediately if a signal is received while the function is trying to connect to the device.

The function returns a positive integer on success that can be used as a handle for the device in all the following function calls, and -1 on errors. The handle returned by the function is the file descriptor for the socket connected to the device. You are free to use this file descriptor for whatever you want, but please call the function fsc2_lan_close() to close it. The only two options set for the socket are SO_KEEPALIVE and TCP_NODELAY (but you can unset them if you want). Later on the options SO_SNDTIMEO and SO_RCVTIMEO may be set for timeouts for read and write operations (if the system supports setting these options), so you better leave them alone if you plan to use the functions fsc2_lan_write() and fsc2_lan_read().

The function for writing data from a single buffer is declared as

 
ssize_t fsc2_lan_write( int          handle,
                        const char * message,
                        long         length,
                        long         us_timeout,
                        bool         quit_on_signal )

The first argument is the handle returned by fsc2_lan_open(), identifying the device. The second argument, message, is a buffer with the data to be send to the device. The third, length, is the length of the data buffer. The fourth argument, us_timeout, is the amount of time to wait for data to be written to the device before timing out. If this is set to 0 (or a negative value) no timeout is used, i.e. the write function will wait indefinitely if necessary. The last argument, quit_on_signal, again indicates i the function must return immediately if a signal is received.

Please note that (as always with writing to the network) the function may return before as many bytes as requested have been send. The return value will tell you how many actually were sent or, if -1 is returned, that the write operation failed.

The function for writing from a set of buffers is declared as

 
ssize_t fsc2_lan_writev( int                  handle,
                         const struct iovec * data,
                         int                  count,
                         long                 us_timeout,
                         bool                 quit_on_signal )

The first argument is the handle returned by fsc2_lan_open(), identifying the device. The second is a pointer to a set of buffers, described by a structure of type struct iovec as used by the writev() function and defined in the include file `<sys/uio.h>' (it contains a pointer to the data and a size_t with the number of data bytes). The third argument is the number of such iovec structures passed to the function via the second argument. The remaining two arguments as well as the return value have exactly the same meaning as for the fsc2_lan_write() function.

The function for reading into a single buffer is declared as

 
ssize_t fsc2_lan_read( int    handle,
                       char * buffer,
                       long   length,
                       long   us_timeout,
                       bool   quit_on_signal )

The arguments are identical to the ones of fsc2_lan_write(), with the only difference that the second, buffer, is a buffer large enough to hold at least length bytes (make sure that this is the case!).

As for fsc2_lan_write() it isn't guaranteed that has many bytes as requested get read. The return value tells you how many actually got read - if this should be -1 reading failed.

The function for reading data into a set of buffers is declared as

 
ssize_t fsc2_lan_readv( int            handle,
                        struct iovec * data,
                        int            count,
                        long           us_timeout,
                        bool           quit_on_signal )

The arguments are identical to the ones of fsc2_lan_writev().

As for fsc2_lan_writev() it isn't guaranteed that has many bytes as requested get read. The return value tells you how many actually got read - if this should be -1 reading failed.

The function for closing the connection to the device is declared as

 
int fsc2_lan_close( int handle )

It only expects a single argument, the handle of for the connection to close. It normally returns 0, but when you try to close a connection with an invalid handle (possibly because the connection had already been closed), -1 gets returned.

If you instead want (or have) to do most things all by yourself you can at lease use some functions for logging. Please note that in this case you also have to take care of device locking yourself, i.e. before opening a connection to the device call fsc2_obtain_lock() (and only continue when the function returns indicating success) and release the lock after connection has been closed using fsc2_release_lock().

These are for logging are:

`fsc2_lan_open_log()'

Opens a file for logs

`fsc2_lan_close_log()'

Closes the log file

`fsc2_lan_log_message()'

Writes a message to the LAN log file

`fsc2_lan_log_function_start()'

Write a message to the LAN log file about the start of a function

`fsc2_lan_log_function_end()'

Write a message to the LAN log file about the end of a function

`fsc2_lan_log_data()'

Write data directly to the LAN log file

`fsc2_lan_log_level()'

Returns the log level for LAN log file messages.

This function lets you open a log file for the device:

 
FILE *fsc2_lan_open_log( const char * dev_name );

It takes the name of the device and opens up a log file (typically in `/tmp' unless the configuration variable LAN_LOG_DIR hasn't been set to something else. The function returns a file pointer to be used in the rest of the functions. The function may only be called after you have successfully locked the device using fsc2_obtain_lock().

This function lets you open a log file for the device:

 
FILE *fsc2_lan_open_log( FILE * fp );

When called it closes the log file pointed to bt fp and returns always NULL. This function must be called before the lock on the device is released using fsc2_release_lock().

The function allows you to write to the log file that stores information about the details of the communication over the LAN:

 
void fsc2_lan_log_message( FILE       * fp,
                           const char * fmt,
                           ... );

It expects the pointer to the log file, a format string as you would use in the C printf() function plus arguments corresponding to the conversion specifiers in the format string.

To put a message into the log file about the start of a function use

 
void fsc2_lan_log_function_start( FILE       * fp,
                                  const char * function,
                                  const char * dev_name )

It expects three arguments: the pointer to the log file, the name of the function and the name of the device.

To put a message into the log file about the end of a function use

 
void fsc2_lan_log_function_end( FILE       * fp,
                                const char * function,
                                const char * dev_name )

It expects three arguments: the pointer to the log file, the name of the function and the name of the device.

The function for writing data directly into the LAN lof file, e.g. to report data read from or send to a device, is declared as

 
void fsc2_lan_log_data( FILE       * fp,
                        long         length,
                        const char * buffer )

The first argument is the pointer to the log file, the second the number of bytes of the data to be written to the LAN log file and the third is a pointer to the data itself.

The function to determine which log level is to be used is declared as

 
int fsc2_lan_log_level( void )

It returns an integer with a value of the constants LL_NONE (don't log at all), LL_ERR (log errors only), LL_CE (log only start and end of function calls) or LL_HIGH (also log data receive or send) as defined in `lan.h'.


Back: 14.3.9 Handling devices connected via LAN Forward: 14.3.9.2 LeCroy's VICP Protocol   FastBack: 14. Writing Modules Up: 14.3.9 Handling devices connected via LAN FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.9.1 VXI-11 Protocol

The VXI-11 protocol (VMEbus Extensions for Instrumentation TCI/IP Instrument Protocol) is used for a lot of devices e.g. by Agilent (formerly Hewlett-Packard), Tektronix, Wavetek etc. and basically allows to control instruments over LAN in a similar manner to GPIB. The following functions built into fsc2 exist to communicate with devices using this protocol:

`vxi11_open()'

Establish a connection with the device

`vxi11_close()'

Close the connection to a device

`vxi11_set_timeout()'

Set a read or write timeout for message exchanges with the device

`vxi11_read_stb()'

Ask the device to send its status byte

`vxi11_lock_out()'

Switch local lockout state on or off

`vxi11_device_clear()'

Send a device clear command to the device

`vxi11_device_trigger()'

Send a device trigger command to the device

`vxi11_write()'

Send data to the device

`vxi11_read()'

Read data sent by the device

In order to use these functions include the appropriate header files have to be included, i.e. use

 
#include "vxi11_user.h"

and, when creating the shared library for the module, make sure to link against both the files `vxi11_user.c', `vxi11_clnt.c' and vxi11_xdr.c.

All function calls get logged automatically to a device specific log file with a verbosity according to the general settings for this log file.

The function

 
int vxi11_open( const char * dev_name,
	            const char * address,
			    const char * vxi11_name,
                bool         lock_device,
                bool         create_async,
			    long         us_timeout )

must be the first function to be called. It establishes the connection to the device which then is used for all further function calls. The function expects a symbolic name for the device (only to be used for the log file), its IP address, either as a hostname (like "hex.discworld.unseen.edu") or or a numeric IP address in dotted-quad format (like "123.97.243.12"), the name assigned to the device for the VXI-11 protocol (often per default set to "inst0"), a flag that indicates if an exclusive lock on the device is required (should normally set to 'true'), a flag that indicates if also an asynchronous connection should be established (in order to be able to abort long transfers, but note that not all devices support tis) and a positive or zero maximum time (in micro-seconds, were a zero value means a nearly indefinitely long timeout) to wait for a device being locked by another process to become unlocked. Please note that the function can't be called when a connection has already been created. On success the function returns SUCCESS, on failure (either because the connection has already been opened, connecting to the device fails or not enough memory is available) it returns FAILURE after printing out an error message. Please note that the time the call will take depends on the device and can take up to 25 seconds.

Opening the connection to the device should normally done with a request for an exclusive lock (by the lock_device argument being set to true) in order to keep other processes from also accessing the device. With that it's not necessary to obtain a different lock, e.g. by calling fsc2_obtain_lock(). But it also requires that vxi11_close() is called when you're done with it, otherwise the device will refuse new connections!

The function

 
void vxi11_close( void )

is the opposite of vxi11_open(), i.e. it closes down the existing connection(s). The function normally returns SUCCESS but when there were no connection or shutting down the connection(s) failed an warning messages is printed out and FAILURE is returned.

The function

 
void vxi11_set_timeout( int  dir,
                        long us_timeout )

allows to set timeouts for read or write (and similar) operations. If the first argument is the symbolic value READ (or 0), a timeout for read operations gets set, if it's WRITE (or 1) the timeout for writes gets set. Timeouts must be specified in micro-seconds and must be either positive numbers or zero, in which case a (nearly) indefinitely long timeout is used. Without calling the function a default timeout of 5 seconds will be used. The function always returns SUCSESS except for the case that the timeout value was negative, then an error message is printed out and FAILURE gets returned.

The function

 
int vxi11_read_stb( unsigned char * stb )

asks the device to send back its status byte which then is passed back to the caller via the stb argument. On success the function returns SUCESS, otherwise (because no connection exists or the operation takes longer than the timeout set for reads) an error message is printed out and FAILURE is returned.

The function

 
int vxi11_lock_out( bool lock_state )

allows to control if the device is in local lockout state or not. By calling the funtion with a true boolean value local lockout gets switched on, switch it off by calling it with a false value. Normally returns SUCCESS but may return FAILURE (and print out an error message) if there's no connection, the device doesn't support the command or the command takes longer than the write timeout value. Please note tha some devices that don;t support this command may not indicate this, thus SUCCESS may get returned even though the command had no effect.

The function

 
int vxi11_device_clear( void )

sends a device clear command to the device. Normally the function returns SUCCESS but FAILURE may be the result (in which case an error message is printed out) if there's no connection, the device doesn't support the command or the command takes longer than the write timeout value. Please note tha some devices that don't support this command may not indicate this, thus SUCCESS may get returned even though the command had no effect.

The function

 
int vxi11_device_trigger( void )

sends a device trigger command to the device. Normally the function returns SUCCESS but FAILURE may be the result (in which case an error message is printed out) if there's no connection, the device doesn't support the command or the command takes longer than the write timeout value. Please note tha some devices that don't support this command may not indicate this, thus SUCCESS may get returned even though the command had no effect.

The function

 
int vxi11_write( const char * buffer,
			     size_t     * length,
			     bool         allow_abort )

is for sending data to the device. It takes three arguments, a pointer to an array with the data, a pointer to a variable with the length of that array and a flag that tells if the write operation may be interrupted when the user clicks on the STOP button. An write operation can only be interrupted if the device was opened with the create_async flag set to true and the data are sent in more than one package - in that case the operation is aborted and a USER_BREAK_EXCEPTION gets thrown. On success the function returns SUCCESS, on failure (because there's no connection, invalid arguments or the operation takes longer than the write timeout) an error message is printed out and FAILURE is returned. On return the number of bytes sent to the device is returned via the length pointer.

The function

 
int vxi11_read( const char * buffer,
			    size_t     * length,
			    bool         allow_abort )

reads data send by the device. It takes three arguments, a pointer to an array for storing the received data, a pointer to a variable with the length of that array (i.e. the maximum length of the message that can be accepted) and a flag that tells if the read operation may be interrupted when the user clicks on the STOP button. A read operation can only be interrupted if the device was opened with the create_async flag set to true and the data get send by the device in more than one package - in that case the operation is aborted and a USER_BREAK_EXCEPTION gets thrown. On success the function returns SUCCESS, on failure (because there's no connection, invalid arguments or the operation takes longer than the read timeout) an error message is printed out and FAILURE is returned. On return the number of bytes sent by the device is returned via the length pointer.


Back: 14.3.9.1 VXI-11 Protocol Forward: 14.3.10 Hook functions   FastBack: 14. Writing Modules Up: 14.3.9 Handling devices connected via LAN FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.9.2 LeCroy's VICP Protocol

LeCroy's VICP (Versatile Instrument Control Protocol), running on top of TCP/IP, is already is directy supported. The following functions exist to communicate with devices using this protocol:

`vicp_open()'

Establish a connection with the device

`vicp_close()'

Close the connection to the device

`vicp_lock_out()'

Switch local lockout state on or off

`vicp_set_timeout()'

Set imeout for read or write operations

`vicp_write()'

Send data to the device

`vicp_read()'

Read data send by the device

`vicp_device_clear()'

Clear the device

In order to use these functions include the appropriate header file, i.e. use

 
#include "vicp.h"

and, when creating the shared library for the module, make sure to link against `vicp.c'.

All function calls get logged automatically to the log file for LAN connections with a verbosity according to the general settings for this log file.

The function

 
void vicp_open( const char * dev_name,
                const char * address,
                long         us_timeout,
                bool         quit_on_signal )

must be the first function to be called. It establishes the connection to the device which then is used for all further function calls. The function expects a symbolic name for the device (only to be used for the log file), its IP address, either as a hostname (like "hex.discworld.unseen.edu") or or a numeric IP address in dotted-quad format (like "123.97.243.12"), a positive or zero maximum time (in micro-seconds, a zero value means indefinitely long timeout) the function is allowed to wait for successfully establishing the connection and a flag that tells if the function is supposed to return immediately on receipt of a signal. Please note that a timer, raising a SIGALRM signal on expiry, is used for controlling the timeout. Thus the function temporarily installs its own signal handler for SIGALRM, so the caller should make sure that it doesn't initate anything that would also raise such a signal. Please also note that the function can't be called when an connection has already been created. On failure (either because the connection has already been opened, connecting to the device fails or not enough memory is available) the function throws an exception.

The function

 
void vicp_close( void )

is the opposite of vicp_open(), i.e. it closes down the existing connection. It throws an exception when you try to close an already closed connection.

The function

 
void vicp_lock_out( bool lock_out )

allows to control if the device is in local lockout state (the default when a connection is made) or not. By calling the funtion with a true boolean value local lockout gets switched on, switch it off by calling it with a false value.

The function

 
void vicp_set_timeout( int  dir,
                       long us_timeout )

allows to set timeouts for read or write operations. If the first argument is the symbolic value READ (or 0), a timeout for read operations gets set, if it's WRITE (or 1) the timeout for writes gets set. Timeouts must be specified in micro-seconds and must be either positive numbers or zero, in which case an indefinitely long timeout is used. Without calling the function a default timeout of 5 seconds will be used. Please note that the function can only be called after the connection has been established. Please also note that on some systems (those tht don't support the SO_RCVTIMEO and SO_SNDTIMEO socket options) the timeouts get created via a timer that raises a SIGALRM signal. Thus on these systems the caller may not initiate any action that would raise such a signal when calling one of the functions that can timeout.

The function

 
int vicp_write( const char * buffer,
                ssize_t    * length,
                bool         with_eoi,
                bool         quit_on_signal )

is used to send data to the device. It requires four arguments, a buffer with the data to be send, a pointer to a variable with the number of bytes to be send, a flag that tells if the data are send with a trailing EOI and a flag that tells if the function is supposed to return immediately when receiving a signal. The function returns either SUCCESS (0) if the date could be send successfully, or FAILURE (-1) if sending the data aborted due to receiving a signal. On errors or timeouts the function closes the connection and throws an exception. On return the variable pointed to by the second argument will contain the number of bytes that have been sent - this also is the case if the function returns FAILURE or did throw an exception.

The function

 
int vicp_read( char *    buffer,
               ssize_t * length,
               bool    * with_eoi,
               bool      quit_on_signal )

is for reading data the device sends. It takes four arguments, a buffer for storing the data, a pointer to a variable with the length of the buffer, a pointer to a variable that tells on return if EOI was set for the data and a flag telling if the function is supposed to return immediately on signals. The function returns SUCCESS_BUT MORE (1) if a reply was received but not all data could be read because they wouldn't have fit into the user supplied buffer, SUCCESS (0) if the reply was read completely and FAILURE (-1) if the function aborted because a signal was received. On erros or timeouts the function closes the connection and throws an exception. On return the variable pointed to by the second argument is set to the number of bytes that have been received and the third one shows if the data sent have a trailing EOI, even if the function did return with FAILURE or threw an exception.

If the function returns less data than the device was willing to send (in which case SUCCESS_BUT_MORE gets returned) the next invocation of vicp_read() will return these yet unread data, even if another reply by the device was initiated by sending another command in between. I.e. "new data" (data resulting from the next command) only will be returned after the function has been called until SUCCESS has been returned.

Please note: If a read or write gets aborted due to a signal there may still be data to be read from the device or the device may still be waiting to receive more data. Unless you're closing the connection after receipt of a signal you must make sure that the remaining data are fetched from or get send to the device.

The function

 
void vicp_device_clear( void )

allows to clear the device and reset the connection. Clearing the device includes clearing its input and output buffers and aborting the interpretation of the current command (if any) as well as clearing all pending commands. Status and status enable registers remain unchanged. All of this may take several seconds to finish. The function also closes and reopens the connection to the device.


Back: 14.3.9.2 LeCroy's VICP Protocol Forward: 14.3.11 Caveat for the test run   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.10 Hook functions

As you already know the interpretation of an EDL file consists of several steps. When the file is tested and a DEVICES section is found all modules for the devices listed are loaded. When in the test the EXPERIMENT section is found the test run is started in which the script is tested as far as possible. When the test was successful the experiment may be run repeatedly. To allow initialization of the modules internal parameters, initialization of the devices etc. for each of these stages hook functions can be defined in the modules that will be executed automatically at appropriate times (if they exist).

Thus, each module may contain up to six pre-defined hook functions that don't have to be declared in the function data base file, `Functions'. They all start with the name of the module followed by _init_hook, _test_hook, _end_of_test_hook, _exp_hook, _end_of_exp_hook and _exit_hook. Thus, if the new device is named `SR510' (as the lock-in amplifier mentioned at the start and thus the module is `sr510') these functions are (together with the parameters):

 
int sr510_init_hook( void );
int sr510_test_hook( void );
int sr510_end_of_test_hook( void );
int sr510_exp_hook( void );
int sr510_end_of_exp_hook( void );
void sr510_exit_hook( void );

If it exists, the first function, i.e. sr510_init_hook() is called immediately after the functions defined in all modules are loaded. That means that the internal loader loads the module libraries and when done runs the init hook functions of the modules in the order the modules did appear in the devices section. The main purpose of the init hook functions is to allow the modules to get all kinds of initialization done. Since all other modules are already loaded, they also may be used to test for the existence of other modules by calling a function called exist_device(). But you should not call functions from other modules at this stage because the other modules may still be uninitialized. If the initialization completes successfully, the function must return a non-zero value. If there are problems that don't make the module unusable it may return a zero value - in this case a warning message will be printed. If the initialization fails in a non-recoverable way the function should throw an exception - in this case also the exit_hook() won't get run.

The second function, sr510_test_hook(), is called at the start of the test run of the EXPERIMENT section of the EDL input file. Again, it can be used for initializations. But it should be noted that changes to the variables defined in the EDL file will remain only visible for the test run, after the test is completed they will revert to their former values, i.e. the ones they had before the test run started! The return code of the function is the same as for the init hook function (i.e. always return a non-zero value on success).

The third function, sr510_end_of_test_hook() is called when the test hook functions of the modules have been run. This hook function can be used to reset internal variables of the module that got changed during the test run. The return code of the function is the same as for the init hook and test hook functions (i.e. always return a non-zero value on success).

The fourth function, sr510_exp_hook(), is run when the actual experiment is started. Initialization of devices should be done here. Return codes are again identical to the ones of the former functions.

The fifth function, sr510_end_of_exp_hook() is run after the experiment has been stopped. This hook function should be used to get the device back into a usable state with local control and must close any existing connections to the device.

Finally, the sixth and final function, sr510_exit_hook(), is run just before the module is unloaded except for the case that an exception had been thrown while the init_hook() was run. Please take care: you can't talk to the device anymore when this hook is called. It should only be used to e.g. get rid of memory allocated within the module before it becomes unloaded (and thus any memory not deallocated properly would result in a memory leak). The exit hook can be run immediately after the init hook, so write your module in a way that it also can handle this case!

Please note that the first three functions, i.e. sr510_init_hook, sr510_test_hook and sr510_end_of_test_hook as well as the last function, sr510_exit_hook(), will be run only once, while both the remaining functions, sr510_exp_hook() and sr510_end_of_exp_hook() will be run each time the experiment is started.


Back: 14.3.10 Hook functions Forward: 14.3.12 How to compile a module   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.11 Caveat for the test run

There is one rather nasty problem with the test run. In the test run the EDL script is checked extensively and, as far as possible, everything is done as in the real experiment. But this leads to the problem that the functions in the module must return data even though they can't talk to the devices yet. If the script asks for the measured value from a device reasonable data most be returned. This can be quite tricky, because it sometimes may be not completely clear what will be reasonable data in all imaginable situations.

I don't have a failsafe method to select data to return during the test run and I also fear that there isn't one. But after some experimenting the values now used in the modules didn't lead to too many problems. To make them stand out they are always defined as macros at some prominent place at the start of the module. If necessary the users must be made aware of possible problems, i.e. if they test values returned from within the EDL file they must be prepared to write the EDL script to accept some unexpected values.


Back: 14.3.11 Caveat for the test run Forward: 14.3.13 Linker scripts   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.12 How to compile a module

A module is a shareable library (with an extension of `.fsc2_so') that gets loaded while fsc2 is running if the name of the module is listed in the DEVICES section of an EDL file. Probably the simplest way to make such a shareable library from the source files you have written is to include it in the existing `Makefile' in the `modules' subdirectory of the packages. But, of course, it's also possible to use other methods.

If you want to include your module into the existing Makefile you have to distinguish between two cases:

  1. The new module consists of just one C file with the same name as the module and the config file (residing in the `config' directory)
  2. The new module consists of several source files, one header file (with the same name as the module and the extension .h) and and the config file

In both cases all you have to do is to edit the `Makefile' in the `modules' directory. In the first case look for the variable simp_modules, defined near the start of the file. The line defining this variable is at the moment (while I'm writing this):

 
simp_modules  := User_Functions sr510 sr530 sr810 sr830 aeg_s_band        \
                 aeg_x_band er035m er035m_s hp5340a er035m_sa er035m_sas  \
                 bh15 keithley228a egg4402 kontron4060 lakeshore330       \
                 pt2025 er032m ips20_4 $(s_band_list) ni6601 me6000       \
                 thurlby330 hjs_daadc gg_chopper itc503

(The \ characters at the ends of the lines tell make that the line continues on the next line.) All you've got to do to include your new module is to append the name of the single C file you have written to this list but without the .c extension, i.e. if it is called `abc.c' just change the last line to

 
simp_modules  := User_Functions sr510 sr530 sr810 sr830 aeg_s_band        \
                 aeg_x_band er035m er035m_s hp5340a er035m_sa er035m_sas  \
                 bh15 keithley228a egg4402 kontron4060 lakeshore330       \
                 pt2025 er032m ips20_4 $(s_band_list) ni6601 me6000       \
                 thurlby330 hjs_daadc gg_chopper itc503 abc

If you now re-compile it will also be compiled, a shareable library will be created from it and when you do make install it will be copied to the appropriate place where fsc2 will find it (but don't forget that you also have to declare it in the devices data base file `config/Devices' and the functions it exports in the functions data base file `config/Functions').

If you wrote a larger module that consists of more than just one source file you will have to apply two changes to the `Makefile'. Directly beneath the definition of the make variable simp_modules another variable, comp_modules, is defined, which (at the moment) is set to:

 
comp_modules  := dg2020_f dg2020_b hfs9000 ep385 rs690 er023m lecroy9400  \
                 hp8672a $(hp864_list) $(tds_list) spectrapro_300i        \
                 hjs_attenuator hjs_sfc lecroy9400_s spex_cd2a rs_sml01

Here you have to append the name of your own module (just the name with no extension). Next you have to create a second variable that has the same name as your module (again without extension, i.e. identical to what you just appended to comp_modules) and which has to be set to the list of the all the names of your C source files. As an example have a look at the definition of the variable lecroy9400:

 
lecroy9400    := lecroy9400.c lecroy9400_gpib.c lecroy9400_util.c

The module lecroy9400 consists of the three C source files listed here. You have to create a similar entry for your own module. That's all that's need done and you can now re-compile to create the new module and re-install to make it available to fsc2.

If you should want to compile a module 'by hand' you'll have to make sure that the `src' and the `config' directory are in the include paths and that both the flags -shared and -fpic are set (for both compiling and linking). If you have a C source file called abc123.c in the `modules' directory and you want to make a shareable library out of it you should compile and link it with at least

 
  gcc -I../src -I../config -shared -fpic -o abc.fsc2_so abc.c

(assuming you're using GNU's gcc). Note that to distinguish modules from "normal" shared libraries (even modules are shared libraries) they are expected to have an extension of .fsc2_so instead of .so.

If this succeeds you will still have to copy the library to the place where fsc2 expects it, i.e. usually `/usr/local/lib/fsc2' (or you need to set the environment variable LD_LIBRARY_PATH to point to the place where the library can be found before running fsc2).


Back: 14.3.12 How to compile a module Forward: 14.3.14 Making fsc2 aware of the module   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.13 Linker scripts

As you may have noticed, for every of the modules made of more than a single source file there exists a file with the extension map. This file is a linker script which restricts the number of symbols exported by the shared library representing the module to the smallest possible subset.

The reason for the existence of these linker scripts is to avoid name space polution, unfortunately not too uncommon a problem with C. For the modules made from a single source file this isn't a problem - one should just declare all functions (and global variables) that are to be restriced to the scope of the module as static. It's different for libraries that get made from several files, here usually at least some of the functions and global variables need a scope that isn't restricted to just the file they are defined in. To restrict the visibility of these symbols to the library (so that they are not "picked up" by the fsc2 program when the module gets loaded) the linker scripts are used. Within a linker script it's possible to declare which of the functions and global variables of the library are to be exported and which ones not.

Of course, using linker scripts isn't required, modules may work perfectly well without one. They are only meant to help keep things cleanly separated, thereby reducing the probability of making some difficult to trace errors. But if you feel you don't need them you can just as well skip the rest of this subsection.

To be able to write a linker script one needs to know which symbols must be exported by a module. And there are only three classes of symbols. First, there are the two global variables that need to be exported by each and every module, generic_type and device_name. Both of them have been discussed in detail above. Then there are the hook functions. And finally we have the EDL functions, i.e. the functions that get invoked when an function call for the module is found in an EDL script. And, luckily, both the hook functions as well as the EDL functions have to have simply structured names. The names of hook functions have to start with the name of the module they belong to and to end in the word _hook. EDL functions always start with the name of the type of the device the module controls, e.g. all EDL functions for a pulser start with pulser_.

With taking just a bit of care when writing a module the simple pattern used for both hook and EDL functions makes writing a linker script extremely simple. The only thing to keep in mind is to avoid to use names for other types of functions or global variables that would fit these patterns (which isn't really difficult). Already for the purpose of self-documentation it seems to be advisable to avoid function names that start with the generic type of the device - functions with these names should stand out to be easily recognizable as EDL function. And having functions that start of with the module name and end in the string _hook usually makes not much sense anyway (except for hook functions of course).

Under these conditions a linker script (i.e. the file ending with the extension map) for e.g. a pulser with module name abcd may look like this:

 
ABCD {
    global:    generic_type;
               device_name;
               abcd_*_hook;
               pulser_*;
    local:     *;
};

All the interesting things are enclosed in curly braces. You have two sections, one for global symbols (i.e. functions and global variables exported by the module) and another one for local symbols. Into the section for global symbols belong the two variables that every module needs to export, generic_type and device_name. Then there are the hook functions - and as you can see things are much simplified by the fact that the * can be used as a wildcard character. Finally, all functions starting with the generic type of the device, the EDL functions also belong to this set. The section for local symbols is even simpler, a simple * stands for all the remaining symbols of the module that otherwise would get also exported even though they aren't needed.

Both these sections for global and local symbols are enclosed by curly braces and at the very start you have just e.g. the name of the module. This is no magic but due to the fact that these kinds of linker scripts are usually used to control what symbols different versions of a library do export. Here we don't care at all about versions of the modules, you should always use the ones compiled for the version of fsc2 you're using anyway, but we have to start of with the version command, so I picked the name of the module as the version name, this being as good as any other value.

If you create such a linker script for a module just name it like the name of the module and append the map extension. Then it will be automatically used by the Makefile that takes care of creating the shared library for the module. But if it does not exist creating the module will also work without pronblems.


Back: 14.3.13 Linker scripts Forward: 14.3.15 Calling EDL functions from a modules   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.14 Making fsc2 aware of the module

fsc2 must be made aware of the existence of a new module and of the EDL functions supplied by the module. Thus a new device driver has to be included into the device name data base called `Devices' , which is a simple ASCII file consisting of the names of all the supported devices. It can be found in the `config' subdirectory of the source tree and usually gets installed in the directory `/usr/local/lib/fsc2'. The entries in this file are case-insensitive, so you could add `SR510', `sr510' or `Sr510' etc. Within the file C and C++ style comments can be used. By adding the device name to this file you tell fsc2 that there is now a module called `sr510' (take care - all modules are spelled with lower case characters!). Actually, the file compiled from the C file defining the functions has to be `sr510.fsc2_so' - that means it is a shared library that can be used as a plugin for fsc2 (how to create one from the C file is described later). Here is a short snippet from the `Devices' file with the entries for the lock-in amplifiers:

 
sr510         // Stanford Research lock-in amplifier, model 510
sr530         // Stanford Research lock-in amplifier, model 530
sr810         // Stanford Research lock-in amplifier, model 810
sr830         // Stanford Research lock-in amplifier, model 830
er023m        // Bruker Signal Channel, model ER 023 M

The next thing is to append the function(s) exported by the module (in the sense that they can be used from EDL scripts) to the function data base file called `Functions' . Also this file is located in the `config' subdirectory of the source tree and also will usually be installed under `/usr/local/lib/fsc2'. Here one adds lines consisting of two or three entries, separated by commas and ending with a semicolon. Please note that you can't use function names that contain a # character.

  1. Each line must start with the names of the exported function, i.e. lockin_get_data.
  2. This has to be followed by the number of arguments the function takes - if the function accepts a variable number of arguments specify an arbitrary negative number or just a minus sign (-).
  3. Optionally, you can add the keywords ALL, EXP or PREP, where ALL means that the function can be used in all parts of the EDL file, while EXP tells fsc2 to use this functions only during an experiment and, finally, PREP restricts the use of the function to the PREPARATION section of the EDL file.

As in the file with the device list, C and C++ style comments can be used. Here are a few lines from a valid `Functions' file with the entries for lock-in amplifier functions:

 
/* Functions exported by the lock-in amplifier modules
   (SR510, SR530, SR810, SR830) */

lockin_name,           0, ALL;  // return the device name
lockin_get_data,      -1, EXP;  // return the lock-in voltage 
lockin_get_adc_data,   1, EXP;  // return a ADC voltage
lockin_dac_voltage,   -1, ALL;  // get/set DAC voltage
lockin_sensitivity,   -1, ALL;  // get/set the sensitivity
lockin_time_constant, -1, ALL;  // get/set the time constant
lockin_phase,         -1, ALL;  // get/set the phase
lockin_ref_freq,      -1, ALL;  // Get/set mod. frequency (SR8x0 only)
lockin_ref_mode,       0, EXP;  // Get mod. mode (SR8x0 only)
lockin_ref_level,     -1, EXP;  // Get/set mod. level (SR8x0 only)
lockin_lock_keyboard, -1, EXP;  // Lock/unlock the keyboard

For example, lockin_get_adc_data (a function that allows you to read the voltage at one of the lock-ins ADCs) expects 1 argument (the number of the ADC) and can only be used in the EXPERIMENT section. In contrast, lockin_sensitivity can be called with a variable number of arguments (if called without an argument it returns the sensitivity setting of the lock-in, if called with an argument the function treats this as the new sensitivity to be set). This function can be used in all parts of the EDL script - but because querying the lock-in for its sensitivity won't work as long as the program can't talk with the lock-in, i.e. while not in the EXPERIMENT section the function must test for this case and emit an appropriate error message all by itself.


Back: 14.3.14 Making fsc2 aware of the module Forward: 14.4 Additional utilities provided by fsc2   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.3.15 Calling EDL functions from a modules

Calling an EDL function (built-in as well as EDL functions defined in modules) consists of three to four steps:

  1. You may first want to check if the function you're planning to call exists at all. To do so call func_exists() with the name of the function as the argument. It will return 0 if the function does not exist and can not be used, otherwise a non-zero value.
  2. Call func_get() with the name of the function you want to call as the first argument and the address of an integer variable for returning the access flags (you may specify also NULL instead if you're not interested in the access flag) - this will return a variable with a pointer to the function which you have to store. If the returned pointer is NULL the function does not exist or isn't loaded. The variable pointed to by the second argument will be set to either ALL, PREP or EXP.
  3. Now call vars_push() for each of the arguments of the function - see the description of vars_push() in the section about fsc2's built in variable types.
  4. Finally, call func_call() with the pointer returned by the call to func_get() as the argument. This will return a pointer to the variable with the result.

As an example let's assume there is an EDL function named foo() you want to call from your module, that takes two arguments, an integer and a floating point value. Then a typical piece of C code to call the function would be

 
Var_T * my_function( Var_T * var )
{
    Var_T * func_ptr;
    Var_T * ret_value;
    int access;

    if ( ! func_exists( "foo" ) )              /* test if function exists */
    {
        /* do your error handling here */ 
    }
    else                                  
    {                                    
        func_ptr = func_get( "foo", &access ); /* get pointer to function */
        vars_push( INT_VAR, 5 );               /* push first argument */
        vars_push( FLOAT_VAR, 3.1415 );        /* push second argument */
        ret_value = func_call( func_ptr );     /* call the function */
    }

    ...
}

There is one point that needs attention: After the call to func_call() the variable func_ptr with the pointer to the function returned by func_get() will disappear automatically. Thus, when you need to call the function again you will have to go through this procedure, since the value stored in func_ptr after the call to func_call() is completely useless and even dangerous to use for any purpose whatsoever! So, don't assume that the value of func_prtr you got from func_get() will have any meaning later on. Not only will the value be invalid but, even worse, there is a high probability that hard to trace bugs will result if you try to use it.

If you should be wondering what happens if you call an EDL function defined in your own module from within the module you can be sure that you will always get the function from this module even if there are other modules with the same generic type and thus supplying a function of the same name. I.e. if there are e.g. two lock-in amplifier modules loaded, both having a lockin_get_data() function, and within one of the lock-in modules this function is called it is guaranteed that the function of this name from the same module gets called and not the one from the other module. So you don't have to care about appending a # and a device number to the function name - fsc2 will do this automatically when necessary.


Footnotes

(25)

Actually, FSC2_MODE isn't a real variable. While you can obtain its value you can't assign values to it, and if your try the compiler will complain about an error like 'invalid lvalue in assignment'.


Back: 14.3.14 Making fsc2 aware of the module Forward: 14.4 Additional utilities provided by fsc2   FastBack: 14. Writing Modules Up: 14.3 How to write a new module FastForward: A. Installation

This document was generated by Jens Thoms Toerring on September 6, 2017 using texi2html 1.82.