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().