The Library
Here you can find all the information about the fmc-tdc API and the main library behaviour that you need to be aware of to write applications.
This document introduces the developers to the development with the TDC library. Here you can find an overview about the API, the rational behind it and examples of its usage. It is not the purpose of the document to describe the API details. The complete API is available in the Library API section.
Note
The TDC hardware design diverged into different buffering structures. One based on FIFOs for SVEC, and one based on double-buffering in DDR for SPEC. The API tries to provide the same user-experience, however this is not always possible. Functions having different behaviour are properly declaring it in their documentation.
Note
This document provides also snippet of code from example.c. This is only to show you an example, please avoid to blindly copy and paste.
Initialization and Cleanup
The library may keep internal information, so the application should
call its initialization function fmctdc_init()
. After use,
it should call the exit function fmctdc_exit()
to release
any internal data.
Note
fmctdc_exit()
is not mandatory, the operating system
releases anything in any case – the library doesn’t leave unexpected
files in persistent storage.
These functions don’t do anything at this point, but they may be implemented in later releases. For example, the library may scan the system and cache the list of peripheral cards found, to make later open calls faster. For this reason it is recommended to, at least, initialize and release the library before starting.
Following an example from the example.c
code available under tools
err = fmctdc_init();
if (err)
exit(EXIT_FAILURE);
err = use_fmctdc_library();
if (err)
exit(EXIT_FAILURE);
fmctdc_exit(); /* optional, indeed in the error condition
we do not do it */
Error Reporting
Each library function returns values according to standard libc
conventions: -1 or NULL (for functions returning int
or pointers,
resp.) is an error indication. When error happens, the errno
variable is set appropriately.
The errno values can be standard Posix items like
EINVAL
, or library-specific values, for example
FMCTDC_ERR_VMALLOC
(driver vmalloc allocator not available). All
library-specific error values have a value greater than 4096, to
prevent collision with standard values. To convert such values to a
string please use fmctdc_strerror()
Following an example from the example.c
code available under tools
fprintf(stderr, "%s: Cannot open device: %s\n",
prog_name, fmctdc_strerror(errno));
Opening and closing
Each device must be opened before use by calling fmctdc_open()
,
and it should be closed after use by calling fmctdc_close()
.
Note
fmctdc_close()
is not mandatory, but it is recommended, to
close if the process is going to terminate, as the library has no
persistent storage to clean up – but there may be persistent buffer
storage allocated, and fmctdc_close()
may release it in
future versions.
The data structure returned by fmctdc_open()
is an opaque pointer
used as token to access the API functions. The user is not supposed to use
or modify this pointer.
Another kind of open function has been provided to satisfy CERN’s developers
needs. Function fmctdc_open_by_lun()
is the open by LUN
(Logic Unit Number); here the LUN concept reflects the CERN one.
The usage is exactly the same as fmctdc_open()
only that it uses
the LUN instead of the device ID.
No automatic action is taken by fmctdc_open()
. Hence, you
may want to flush the buffers before starting a new acquisition
session. You can do this with fmctdc_flush()
tdc = fmctdc_open(0x0000);
if (!tdc) {
fprintf(stderr, "%s: Cannot open device: %s\n",
prog_name, fmctdc_strerror(errno));
return -1;
}
err = fmctdc_flush(tdc, channel);
if (err)
return err;
err = config_and_acquire(tdc);
if (err) {
fprintf(stderr, "%s: Error: %s\n",
prog_name, fmctdc_strerror(errno));
return -1;
}
fmctdc_close(tdc);
Configuration and Status
The TDC configuration API is based on a number of getter and setter function for each option. These include: termination, IRQ coalescing timeout, board time, white-rabbit, timestamp mode.
The termination options allows you to set the 50 Ohm channel
termination. You can use the following getter and setter:
fmctdc_get_termination()
,
fmctdc_set_termination()
.
err = fmctdc_set_termination(tdc, channel, termination);
if (err)
return err;
termination_rb = fmctdc_get_termination(tdc, channel);
if (termination_rb < 0)
return termination_rb;
The IRQ coalescing timeout option allows to force an IRQ when the
timeout expire to inform the driver that there is at least one pending
timestamp to be transfered. You can use the following getter and setter:
fmctdc_coalescing_timeout_get()
,
fmctdc_coalescing_timeout_set()
.
err = fmctdc_coalescing_timeout_set(tdc, channel, coalescing_timeout);
if (err)
return err;
err = fmctdc_coalescing_timeout_get(tdc, channel, &coalescing_timeout_rb);
if (err)
return err;
The TDC main functionality is to timestap incoming pulses. To assign a
timestamp the board needs a time reference. This can be provided by
the on-board clock, or by the more accurate white-rabbit network. You
can enable or disable white-rabbit using
fmctdc_wr_mode()
. You can check the white-rabbit status
with fmctdc_check_wr_mode()
. When working with
white-rabbit the time reference is handled by the white-rabbit
network.
err = fmctdc_wr_mode(tdc, wr_mode);
if (err)
return err;
wr_mode_rb = fmctdc_check_wr_mode(tdc);
if (wr_mode_rb < 0)
return wr_mode_rb;
If you do not have white-rabbit connected to the TDC, or simply this
is not what you want, then be sure to disable. When white-rabbit is
disabled the TDC will use the on-board clock to keep a time
reference. However, in this scenario the user is asked to set first
the time using fmctdc_set_time()
or
fmctdc_set_host_time()
.
err = fmctdc_set_time(tdc, &time);
if (err)
return err;
Whater you are using white-rabbit or not, you can get the current
board time with fmctdc_get_time()
.
err = fmctdc_get_time(tdc, &time_rb);
if (err)
return err;
Still about time, the user can add it’s own offset without changing
the timebase using fmctdc_get_offset_user()
and
fmctdc_set_offset_user()
.
err = fmctdc_set_offset_user(tdc, channel, offset_user);
if (err)
return err;
err = fmctdc_get_offset_user(tdc, channel, &offset_user_rb);
if (err)
return err;
Finally, you can monitor the board temperature using
fmctdc_read_temperature()
, and pulse and timestamps
statistics with fmctdc_stats_recv_get()
and
fmctdc_stats_trans_get()
.
err = fmctdc_stats_recv_get(tdc, channel, &recv);
if (err)
return err;
err = fmctdc_stats_trans_get(tdc, channel, &trans);
if (err)
return err;
Note
If it can be useful there is one last status function in the API
used to detect the transfer mode between the driver and the
board. This function is fmctdc_transfer_mode()
Timestamp buffering has its own set of options. Buffering in hardware
is fixed, it can’t be configured, so what we are going to describe
here is the Linux device driver buffering configuration. Because the
TDC driver is based on ZIO, then you can choose the buffer
allocator type. You can handle this option with the pair:
fmctdc_get_buffer_type()
and
fmctdc_set_buffer_type()
.
err = fmctdc_set_buffer_type(tdc, buffer_type);
if (err)
return err;
buffer_type_rb = fmctdc_get_buffer_type(tdc);
if (buffer_type_rb < 0)
return buffer_type_rb;
You can configure - and get - the buffer size (number of
timestamps) with: fmctdc_get_buffer_len()
and
fmctdc_set_buffer_len()
. Beware, that this function works
only when using FMCTDC_BUFFER_VMALLOC
.
err = fmctdc_set_buffer_len(tdc, channel, buffer_len);
if (err)
return err;
buffer_len_rb = fmctdc_get_buffer_len(tdc, channel);
if (buffer_len_rb < 0)
return buffer_len_rb;
Finally, you can select between to modes to handle buffer’s overflows:
FMCTDC_BUFFER_CIRC
and FMCTDC_BUFFER_FIFO
. The
first will discard old timestamps to make space for the new ones, the
latter will discard any new timestamp until the buffer get
consumed. To configure this option you can use:
fmctdc_get_buffer_mode()
and
fmctdc_set_buffer_mode()
.
err = fmctdc_set_buffer_mode(tdc, channel, buffer_mode);
if (err)
return err;
buffer_mode_rb = fmctdc_get_buffer_mode(tdc, channel);
if (buffer_mode_rb < 0)
return buffer_mode_rb;
Acquisiton
Before actually being able to get timestamps, the TDC acquisition must
be enabled. The acquisition can be enabled or disabled through its
gateware using, respectivily, fmctdc_channel_enable()
and
fmctdc_channel_disable()
.
err = fmctdc_channel_enable(tdc, channel);
if (err)
return err;
err = fetch_and_process(tdc);
if (err)
return err;
err = fmctdc_channel_disable(tdc, channel);
if (err)
return err;
To read timestamps you may use functions fmctdc_read()
and fmctdc_fread()
. As the name may suggest, the first
behaves like read and the second as fread.
do {
n = fmctdc_read(tdc, channel, ts, max, O_NONBLOCK);
} while (n < 0 && errno == EAGAIN);
if (n < 0)
return n;
If you need to flush the buffer, you can use fmctdc_flush()
.
err = fmctdc_flush(tdc, channel);
if (err)
return err;
Timestamp Math
The TDC library API has functions to support timestamp math. They
allow you to add, subtract, normalize, and
approximate. These functions are: fmctdc_ts_add()
,
fmctdc_ts_sub()
,
fmctdc_ts_norm()
, fmctdc_ts_ps()
, and
fmctdc_ts_approx_ns()
.