3.4. Asynchronous acquisition
Prev | 3. Writing Comedi programs | Next

### 3.4. Asynchronous acquisition

Of special importance is the so called "asynchronous data acquisition" where Comedi is sampling in the background at a given sample rate. The user can retrieve the data whenever it is convenient. Comedi stores the data in a ring-buffer so that programs can perform other tasks in the foreground, for example plotting data or interacting with the user. This technique is used in programs such as ktimetrace or comedirecord.

There are two different ways how a sequence of channels is measured during asynchronous acquisition (see also the Figure in the introduction):

- The channels are measured with the help of a multiplexer which switches to the next channel after each measurement. This means that the sampling rate is divided by the number of channels.
- The channels are all measured at the same time, for example when every channel has its own converter. In this case the sampling rate need not to be divided by the number of channels.

How your Comedi device handles the asynchronous acquisition can be found out with the command comedi_board_info -v.

The program `demo/tut3.c` demonstrates the asynchronous acquisition. The general strategy is always the same: first, we tell Comedi all sampling parameters such as the sampling rate, the number of channels and anything it needs to know so that it can run independently in the background. Then Comedi checks our request and it might modify it. For example we might want to have a sampling rate of 16kHz but we only get 1kHz. Finally we can start the asynchronous acquisition. Once it has been started we need to check periodically if data is available and request it from Comedi so that its internal buffer won't overrun.

In summary the asynchonous acquisition is performed in the following way:

- Create a command structure of type comedi_cmd
- Call the function `comedi_get_cmd_generic_timed` to fill the command structure with your comedi device, subdevice, sampling rate and number of channels.
- Create a channel-list and store it in the command structure. This tells comedi which channels should be sampled in the background.
- Call `comedi_command_test` with your command structure. Comedi might modify your requested sampling rate and channels.
- Call `comedi_command_test` again which now should return zero for success.
- Call `comedi_command` to start the asynchronous acquisition. From now on the kernel ringbuffer will be filled at the specified sampling rate.
- Call periodically the standard function `read` and receive the data. The result should always be non zero as long as the acquisition is running.
- Convert the received data either into lsampl_t or sampl_t depending on the subdevice flag `SDF_LSAMPL`.
- Poll for data with `read` as long as it returns a positive result or until the program terminates.

The program below is a stripped down version of the program `cmd.c` in the demo directory. It requests data from two channels at a sampling rate of 1kHz and a total of 10000 samples. which are then printed to stdout. You can pipe the data into a file and plot it with gnuplot. As mentioned above, central in this program is the loop using the standard C `read` command which receives the buffer contents.

          /*
     * Example of using commands - asynchronous input
     * Part of Comedilib
     *
     * Copyright (c) 1999,2000,2001 David A. Schleef <ds@schleef.org>
     *               2008 Bernd Porr <berndporr@f2s.com>
     *
     * This file may be freely modified, distributed, and combined with
     * other software, as long as proper attribution is given in the
     * source code.
     */

    #include <stdio.h>
    #include <comedilib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <string.h>

    extern comedi_t *device;

    struct parsed_options {
    	char *filename;
    	double value;
    	int subdevice;
    	int channel;
    	int aref;
    	int range;
    	int verbose;
    	int n_chan;
    	int n_scan;
    	double freq;
    };

    #define BUFSZ 10000
    char buf[BUFSZ];

    #define N_CHANS 256
    static unsigned int chanlist[N_CHANS];
    static comedi_range * range_info[N_CHANS];
    static lsampl_t maxdata[N_CHANS];

    int prepare_cmd_lib(comedi_t *dev, int subdevice, int n_scan,
    		    int n_chan, unsigned period_nanosec,
    		    comedi_cmd *cmd);

    void do_cmd(comedi_t *dev,comedi_cmd *cmd);

    void print_datum(lsampl_t raw, int channel_index);

    char *cmdtest_messages[] = {
    	"success",
    	"invalid source",
    	"source conflict",
    	"invalid argument",
    	"argument conflict",
    	"invalid chanlist",
    };

    int main(int argc, char *argv[])
    {
    	comedi_t *dev;
    	comedi_cmd c,*cmd = &c;
    	int ret;
    	int total = 0;
    	int col;
    	int i;
    	int subdev_flags;
    	lsampl_t raw;

    	struct parsed_options options;

    	memset(&options, 0, sizeof(options));
    	options.filename = "/dev/comedi0";
    	options.subdevice = 0;
    	options.channel = 0;
    	options.range = 0;
    	options.aref = AREF_GROUND;
    	options.n_chan = 2;
    	options.n_scan = 10000;
    	options.freq = 1000.0;

    	/* open the device */
    	dev = comedi_open(options.filename);
    	if (!dev) {
    		comedi_perror(options.filename);
    		exit(1);
    	}

    	/* Print numbers for clipped inputs */
    	comedi_set_global_oor_behavior(COMEDI_OOR_NUMBER);

    	/* Set up channel list */
    	for (i = 0; i < options.n_chan; i++) {
    		chanlist[i] =
    			CR_PACK(options.channel + i, options.range,
    				options.aref);
    		range_info[i] =
    			comedi_get_range(dev, options.subdevice,
    					 options.channel, options.range);
    		maxdata[i] =
    			comedi_get_maxdata(dev, options.subdevice,
    					   options.channel);
    	}

    	/* prepare_cmd_lib() uses a Comedilib routine to find a
    	 * good command for the device.  prepare_cmd() explicitly
    	 * creates a command, which may not work for your device. */
    	prepare_cmd_lib(dev, options.subdevice, options.n_scan,
    			options.n_chan, 1e9 / options.freq, cmd);

    	/* comedi_command_test() tests a command to see if the
    	 * trigger sources and arguments are valid for the subdevice.
    	 * If a trigger source is invalid, it will be logically ANDed
    	 * with valid values (trigger sources are actually bitmasks),
    	 * which may or may not result in a valid trigger source.
    	 * If an argument is invalid, it will be adjusted to the
    	 * nearest valid value.  In this way, for many commands, you
    	 * can test it multiple times until it passes.  Typically,
    	 * if you can't get a valid command in two tests, the original
    	 * command wasn't specified very well. */
    	ret = comedi_command_test(dev, cmd);
    	if (ret < 0) {
    		comedi_perror("comedi_command_test");
    		if(errno == EIO){
    			fprintf(stderr,
    				"Ummm... this subdevice doesn't support commands\n");
    		}
    		exit(1);
    	}
    	ret = comedi_command_test(dev, cmd);
    	if (ret < 0) {
    		comedi_perror("comedi_command_test");
    		exit(1);
    	}
    	fprintf(stderr,"second test returned %d (%s)\n", ret,
    		cmdtest_messages[ret]);
    	if (ret != 0) {
    		fprintf(stderr, "Error preparing command\n");
    		exit(1);
    	}

    	/* comedi_set_read_subdevice() attempts to change the current
    	 * 'read' subdevice to the specified subdevice if it is
    	 * different.  Changing the read or write subdevice might not be
    	 * supported by the version of Comedi you are using.  */
    	comedi_set_read_subdevice(dev, cmd->subdev);
    	/* comedi_get_read_subdevice() gets the current 'read'
    	 * subdevice. if any.  This is the subdevice whose buffer the
    	 * read() call will read from.  Check that it is the one we want
    	 * to use.  */
    	ret = comedi_get_read_subdevice(dev);
    	if (ret < 0 || ret != cmd->subdev) {
    		fprintf(stderr,
    			"failed to change 'read' subdevice from %d to %d\n",
    			ret, cmd->subdev);
    		exit(1);
    	}

    	/* start the command */
    	ret = comedi_command(dev, cmd);
    	if (ret < 0) {
    		comedi_perror("comedi_command");
    		exit(1);
    	}
    	subdev_flags = comedi_get_subdevice_flags(dev, options.subdevice);
    	col = 0;
    	while (1) {
    		ret = read(comedi_fileno(dev),buf,BUFSZ);
    		if (ret < 0) {
    			/* some error occurred */
    			perror("read");
    			break;
    		} else if (ret == 0) {
    			/* reached stop condition */
    			break;
    		} else {
    			int bytes_per_sample;

    			total += ret;
    			if (options.verbose) {
    				fprintf(stderr, "read %d %d\n", ret,
    					total);
    			}
    			if (subdev_flags & SDF_LSAMPL) {
    				bytes_per_sample = sizeof(lsampl_t);
    			} else {
    				bytes_per_sample = sizeof(sampl_t);
    			}
    			for (i = 0; i < ret / bytes_per_sample; i++) {
    				if (subdev_flags & SDF_LSAMPL) {
    					raw = ((lsampl_t *)buf)[i];
    				} else {
    					raw = ((sampl_t *)buf)[i];
    				}
    				print_datum(raw, col);
    				col++;
    				if (col == options.n_chan) {
    					printf("\n");
    					col=0;
    				}
    			}
    		}
    	}

    	return 0;
    }

    /*
     * This prepares a command in a pretty generic way.  We ask the
     * library to create a stock command that supports periodic
     * sampling of data, then modify the parts we want.
     */
    int prepare_cmd_lib(comedi_t *dev, int subdevice, int n_scan,
    		    int n_chan, unsigned scan_period_nanosec,
    		    comedi_cmd *cmd)
    {
    	int ret;

    	memset(cmd,0,sizeof(*cmd));

    	/* This comedilib function will get us a generic timed
    	 * command for a particular board.  If it returns -1,
    	 * that's bad. */
    	ret = comedi_get_cmd_generic_timed(dev, subdevice, cmd, n_chan,
    					   scan_period_nanosec);
    	if (ret < 0) {
    		fprintf(stderr,
    			"comedi_get_cmd_generic_timed failed\n");
    		return ret;
    	}

    	/* Modify parts of the command */
    	cmd->chanlist = chanlist;
    	cmd->chanlist_len = n_chan;
    	if (cmd->stop_src == TRIG_COUNT) {
    		cmd->stop_arg = n_scan;
    	}

    	return 0;
    }

    void print_datum(lsampl_t raw, int channel_index)
    {
    	double physical_value;

    	physical_value = comedi_to_phys(raw, range_info[channel_index],
    					maxdata[channel_index]);
    	printf("%#8.6g ", physical_value);
    }

The source code file for the above program can be found in Comedilib, at `demo/tut3.c`. You can compile the program using

          cc tut3.c -lcomedi -lm -o tut3

For advanced programmers the function `comedi_get_buffer_contents` is useful to check if there is actually data in the ringbuffer so that a call of `read` can be avoided for example when the data readout is called by a timer call-back function.

Prev | Up | Next
3.3. Your second Comedi program | Home | 3.5. Further examples