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):
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:
comedi_get_cmd_generic_timed
to fill the command structure with your comedi device,
subdevice, sampling rate and number of channels.
comedi_command_test
with your command structure. Comedi might modify your requested sampling rate and channels.
comedi_command_test
again which now should return zero for success.
comedi_command
to start the asynchronous acquisition. From now on the kernel ringbuffer will be filled at the specified sampling rate.
read
and receive the data. The
result should always be non zero as long as the acquisition
is running.
SDF_LSAMPL
.
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. To
compile it run:
gcc tut3.c -lcomedi -lm -o tut3
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. Below is an
extract from tut3.c
showing the
relevant commands:
/* 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"); 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); } /* 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); 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{ static int col = 0; 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; } } } } } /* * 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){ printf("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; }
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.