14.3 How to write a new module
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.
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.
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.
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.
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.
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.
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()'
- `close()'
- `write()'
- `read()'
- `tcgetattr()'
- `tcsetattr()'
- `tcsendbreak()'
- `tcdrain()'
- `tcflush()'
- `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
.
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.
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
to close it. The only two
options set for the socket are fsc2_lan_close()
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'.
• VXI-11 Protocol | Support for the VXI-11 protocol | |
• LeCroy's VICP Protocol | Support for LeCroy's VICP protocol |
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.
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!
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
- The new module consists of just one
C
file with the same name as the module and theconfig
file (residing in the `config' directory) - The new module consists of several source files, one header file
(with the same name as the module and the extension
.h
) and and theconfig
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
).
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.
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.
- Each line must start with the names of the exported function, i.e.
lockin_get_data
. - 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 (
-
). - Optionally, you can add the keywords
ALL
,EXP
orPREP
, whereALL
means that the function can be used in all parts of theEDL
file, whileEXP
tellsfsc2
to use this functions only during an experiment and, finally,PREP
restricts the use of the function to thePREPARATION
section of theEDL
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.
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:
-
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 return0
if the function does not exist and can not be used, otherwise a non-zero value. -
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 alsoNULL
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 isNULL
the function does not exist or isn't loaded. The variable pointed to by the second argument will be set to eitherALL
,PREP
orEXP
. -
Now call
vars_push()
for each of the arguments of the function - see the description ofvars_push()
in the section aboutfsc2
's built in variable types. -
Finally, call
func_call()
with the pointer returned by the call tofunc_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
'.
This document was generated by Jens Thoms Toerring on September 6, 2017 using texi2html 1.82.