I've updated the manual in the section configuration and

also updated the tutorial. I've simplified the tut2.c
by using comedi2phys instead of comedi2physical. The use of
polynomials as a 2nd comedi program is a bit too difficult
for a novice and might deter the user to dig deeper into
the coding. Also, I've added tut3.c which is a stripped
down version of cmd.c and of that again the relevant lines
for the handbook to save space.
This commit is contained in:
Bernd Porr 2012-04-27 01:37:33 +01:00
parent 12155822bc
commit df6a97ec6c
6 changed files with 650 additions and 284 deletions

View file

@ -5,7 +5,7 @@ noinst_PROGRAMS = \
gpct_encoder gpct_pulse_generator \
gpct_simple_counting inp inpn insn ledclock \
mmap outp poll receiver select \
sender sigio sv tut1 tut2
sender sigio sv tut1 tut2 tut3
noinst_HEADERS = examples.h
@ -130,3 +130,6 @@ tut2_CFLAGS = $(COMEDILIB_CFLAGS)
tut2_LDADD = $(COMEDILIB_LIBS)
tut3_SOURCES = tut3.c
tut3_CFLAGS = $(COMEDILIB_CFLAGS)
tut3_LDADD = $(COMEDILIB_LIBS)

View file

@ -13,6 +13,8 @@
#include <stdio.h> /* for printf() */
#include <stdlib.h>
#include <comedilib.h>
#include <ctype.h>
#include <math.h>
int subdev = 0; /* change this to your input subdevice */
int chan = 0; /* change this to your channel */
@ -20,90 +22,48 @@ int range = 0; /* more on this later */
int aref = AREF_GROUND; /* more on this later */
const char filename[] = "/dev/comedi0";
/* figure out if we are talking to a hardware-calibrated or software-calibrated board,
then obtain a comedi_polynomial_t which can be used with comedi_to_physical */
int get_converter(comedi_t *device, unsigned subdevice, unsigned channel,
unsigned range, comedi_polynomial_t *converter)
{
int retval;
int flags;
flags = comedi_get_subdevice_flags(device, subdevice);
if(flags < 0)
{
comedi_perror("comedi_get_subdevice_flags");
return -1;
}
if(flags & SDF_SOFT_CALIBRATED) /* board uses software calibration */
{
char *calibration_file_path = comedi_get_default_calibration_path(device);
/* parse a calibration file which was produced by the
comedi_soft_calibrate program */
comedi_calibration_t* parsed_calibration =
comedi_parse_calibration_file(calibration_file_path);
free(calibration_file_path);
if(parsed_calibration == NULL)
{
comedi_perror("comedi_parse_calibration_file");
return -1;
}
/* get the comedi_polynomial_t for the subdevice/channel/range
we are interested in */
retval = comedi_get_softcal_converter(subdevice, channel, range,
COMEDI_TO_PHYSICAL, parsed_calibration, converter);
comedi_cleanup_calibration(parsed_calibration);
if(retval < 0)
{
comedi_perror("comedi_get_softcal_converter");
return -1;
}
}else /* board uses hardware calibration */
{
retval = comedi_get_hardcal_converter(device, subdevice, channel, range,
COMEDI_TO_PHYSICAL, converter);
if(retval < 0)
{
comedi_perror("comedi_get_hardcal_converter");
return -1;
}
}
return 0;
}
int main(int argc, char *argv[])
{
comedi_t *it;
comedi_t *device;
lsampl_t data;
double physical_value;
int retval;
comedi_polynomial_t converter;
comedi_range * range_info;
lsampl_t maxdata;
it = comedi_open(filename);
if(it == NULL)
device = comedi_open(filename);
if(device == NULL)
{
comedi_perror(filename);
return -1;
}
retval = comedi_data_read(it, subdev, chan, range, aref, &data);
retval = comedi_data_read(device, subdev, chan, range, aref, &data);
if(retval < 0)
{
comedi_perror(filename);
return -1;
}
retval = get_converter(it, subdev, chan, range, &converter);
if(retval < 0)
{
return -1;
comedi_set_global_oor_behavior(COMEDI_OOR_NAN);
range_info = comedi_get_range(device, subdev, chan, range);
maxdata = comedi_get_maxdata(device, subdev, chan);
printf("[0,%d] -> [%g,%g]\n", maxdata,
range_info->min, range_info->max);
physical_value = comedi_to_phys(data, range_info, maxdata);
if(isnan(physical_value)) {
printf("Out of range [%g,%g]",
range_info->min, range_info->max);
} else {
printf("%g", physical_value);
switch(range_info->unit) {
case UNIT_volt: printf(" V"); break;
case UNIT_mA: printf(" mA"); break;
case UNIT_none: break;
default: printf(" (unknown unit %d)",
range_info->unit);
}
printf(" (%lu in raw units)\n", (unsigned long)data);
}
physical_value = comedi_to_physical(data, &converter);
printf("%d %g\n", data, physical_value);
return 0;
}

220
demo/tut3.c Normal file
View file

@ -0,0 +1,220 @@
/*
* 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.
*/
/*
* The program is used to test the usbdux sigma board
*/
#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 i;
struct timeval start,end;
int subdev_flags;
lsampl_t raw;
struct parsed_options options;
/* The following variables used in this demo
* can be modified by command line
* options. When modifying this demo, you may want to
* change them here. */
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);
}
/* 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;
}
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);
}

128
demo/tut3_part.c Normal file
View file

@ -0,0 +1,128 @@
/* 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;
}

View file

@ -23,90 +23,108 @@
Configuration
</title>
<para>
Before being able to get information from a DAQ card, you first have
to tell the &comedi; core kernel module which device you have, which
driver you want to attach to the card, and which run-time options
you want to give to the driver. This configuration is done by running
the <command>comedi_config</command> command (as root).
Here is an example of how to use the command (perhaps you should read
its <command>man</command> page now):
The good news is: on most systems PCI and USB based boards are
configured automatically. The kernel will
detect your data acquisition devices, will load the appropriate
kernel drivers and will create the /dev/comedi entries.
<screen>
comedi_config /dev/comedi0 labpc-1200 0x260,3
bp1@bp1-x61:~/sandbox/comedilib$ ls -l /dev/comedi0*
crw-rw---- 1 root iocard 98, 0 2012-04-26 23:41 /dev/comedi0
crw-rw---- 1 root iocard 98, 48 2012-04-26 23:41 /dev/comedi0_subd0
crw-rw---- 1 root iocard 98, 49 2012-04-26 23:41 /dev/comedi0_subd1
</screen>
This command says that the <quote>file</quote>
<filename>/dev/comedi0</filename> can be used to access the &comedi;
device that uses the <parameter>labpc-1200</parameter> board, and that
you give it two run-time parameters (<literal>0x260</literal> and
<literal>3</literal>). More parameters are possible, and their
meaning is driver dependant.
Usually these devices belong to the group "iocard" as shown here. The only
action you need to take is to become member of this group and
then the &comedi; device is ready to be used.
</para>
<para>
This tutorial goes through the process of configuring &comedi;
for two devices, a
<literal>National Instruments AT-MIO-16E-10</literal>, and a
<literal>Data Translation DT2821-F-8DI</literal>.
Old ISA based cards need to be manually configured which is
explained here. You only need to read on here
if you have one of these old cards.
On embedded systems it might also be necessary to load the
driver and then to configure the boards manually.
In general manual configuration is done by running
the <command>comedi_config</command> command (as root).
Here is an example of how to use the command (perhaps you should read
its <command>man</command> page now):
<screen>
comedi_config /dev/comedi0 labpc-1200 0x260,3
</screen>
This command says that the <quote>file</quote>
<filename>/dev/comedi0</filename> can be used to access the &comedi;
device that uses the <parameter>labpc-1200</parameter> board, and that
you give it two run-time parameters (<literal>0x260</literal> and
<literal>3</literal>). More parameters are possible, and their
meaning is driver dependant.
</para>
<para>
The NI board is plug-and-play. The current ni_atmio driver
has kernel-level ISAPNP support, which is used by default
if you do not specify a base address. So you could simply
run comedi_config as
<screen>
comedi_config /dev/comedi0 ni_atmio
</screen>
For the preceding comedi_config command to succeed, the
ni_atmio kernel module must
be loaded first. For plug-n-play boards on
modern kernels, the appropriate comedi kernel modules should get loaded
automatically when your computer is booted.
The <command>modprobe</command> command can
be used to manually load/unload kernel modules, and <command>lsmod</command>
will list all the currently loaded modules.
</para>
<para>
For the <literal>Data Translation</literal> board, you need to know
how the board's jumpers are configured in order to specify the correct
comedi_config parameters. These parameters for the board are given in the
<link endterm="lowleveldrivers">kernel drivers</link> section about the dt282x
driver.
The card discussed here is a <literal>DT2821-f-8di</literal>. The
entry for the dt282x driver tells you that the
comedi_config parameters give the driver the I/O base,
IRQ, DMA 1, DMA 2, and
in addition the states of the
differential/single-ended and unipolar/bipolar jumpers:
<itemizedlist>
<title>dt282x configuration options:</title>
<listitem><para>[0] - I/O port base address</para></listitem>
<listitem><para>[1] - IRQ</para></listitem>
<listitem><para>[2] - DMA 1</para></listitem>
<listitem><para>[3] - DMA 2</para></listitem>
<listitem><para>[4] - AI jumpered for 0=single ended, 1=differential</para></listitem>
<listitem><para>[5] - AI jumpered for 0=straight binary, 1=2's complement</para></listitem>
<listitem><para>[6] - AO 0 jumpered for 0=straight binary, 1=2's complement</para></listitem>
<listitem><para>[7] - AO 1 jumpered for 0=straight binary, 1=2's complement</para></listitem>
<listitem><para>[8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5]</para></listitem>
<listitem><para>[9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
4=[-2.5,2.5]</para></listitem>
<listitem><para>[10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
4=[-2.5,2.5]</para></listitem>
</itemizedlist>
This tutorial goes through the process of configuring &comedi;
for two devices, a
<literal>National Instruments AT-MIO-16E-10</literal>, and a
<literal>Data Translation DT2821-F-8DI</literal>.
</para>
<para>
So, the appropriate options list might be:
<screen>
0x200,4,0,0,1,1,1,1,0,2,2
</screen>
and the full configuration command is:
<screen>
comedi_config /dev/comedi1 dt2821-f-8di 0x200,4,0,0,1,1,1,1,0,2,2
</screen>
Setting the DMA channels to 0 disables the use of DMA.
The NI board is plug-and-play. The current ni_atmio driver
has kernel-level ISAPNP support, which is used by default
if you do not specify a base address. So you could simply
run comedi_config as
<screen>
comedi_config /dev/comedi0 ni_atmio
</screen>
For the preceding comedi_config command to succeed, the
ni_atmio kernel module must
be loaded first. For plug-n-play boards on
modern kernels, the appropriate comedi kernel modules should get loaded
automatically when your computer is booted.
The <command>modprobe</command> command can
be used to manually load/unload kernel modules, and <command>lsmod</command>
will list all the currently loaded modules.
</para>
<para>
For the <literal>Data Translation</literal> board, you need to know
how the board's jumpers are configured in order to specify the correct
comedi_config parameters. These parameters for the board are given in the
<link endterm="lowleveldrivers">kernel drivers</link> section about the dt282x
driver.
The card discussed here is a <literal>DT2821-f-8di</literal>. The
entry for the dt282x driver tells you that the
comedi_config parameters give the driver the I/O base,
IRQ, DMA 1, DMA 2, and
in addition the states of the
differential/single-ended and unipolar/bipolar jumpers:
<itemizedlist>
<title>dt282x configuration options:</title>
<listitem><para>[0] - I/O port base address</para></listitem>
<listitem><para>[1] - IRQ</para></listitem>
<listitem><para>[2] - DMA 1</para></listitem>
<listitem><para>[3] - DMA 2</para></listitem>
<listitem><para>[4] - AI jumpered for 0=single ended, 1=differential</para></listitem>
<listitem><para>[5] - AI jumpered for 0=straight binary, 1=2's complement</para></listitem>
<listitem><para>[6] - AO 0 jumpered for 0=straight binary, 1=2's complement</para></listitem>
<listitem><para>[7] - AO 1 jumpered for 0=straight binary, 1=2's complement</para></listitem>
<listitem><para>[8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5]</para></listitem>
<listitem><para>[9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
4=[-2.5,2.5]</para></listitem>
<listitem><para>[10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
4=[-2.5,2.5]</para></listitem>
</itemizedlist>
</para>
<para>
So, the appropriate options list might be:
<screen>
0x200,4,0,0,1,1,1,1,0,2,2
</screen>
and the full configuration command is:
<screen>
comedi_config /dev/comedi1 dt2821-f-8di 0x200,4,0,0,1,1,1,1,0,2,2
</screen>
Setting the DMA channels to 0 disables the use of DMA.
</para>
<para>
So now you have your boards configured correctly.
Since data acquisition boards are not typically well-engineered,
@ -134,9 +152,9 @@ comedi0: ni_atmio: 0x0260 at-mio-16e-10 ( irq = 3 )
</screen>
<para>
Note that it also correctly identified the board.
Note that it also correctly identified the board.
</para>
</section>
<section id="gettinginformation">
@ -145,10 +163,10 @@ comedi0: ni_atmio: 0x0260 at-mio-16e-10 ( irq = 3 )
</title>
<para>
So now that you have &comedi; talking to the hardware, try to
talk to &comedi;. Here's some information from comedi's proc
file, which indicates what drivers are loaded and which
boards are configured:
So now that you have &comedi; talking to the hardware, try to
talk to &comedi;. Here's some information from comedi's proc
file, which indicates what drivers are loaded and which
boards are configured:
</para>
<screen>
@ -156,17 +174,17 @@ cat /proc/comedi
</screen>
<para>
For example, on a computer with an NI pxi-6281 configured on
<filename>/dev/comedi0</filename> and
a pxi-6602 configured on <filename>/dev/comedi1</filename> you might
see something like:
For example, on a computer with an NI pxi-6281 configured on
<filename>/dev/comedi0</filename> and
a pxi-6602 configured on <filename>/dev/comedi1</filename> you might
see something like:
</para>
<screen>
comedi version 0.7.74
format string: "%2d: %-20s %-20s %4d",i,driver_name,board_name,n_subdevices
0: ni_pcimio pxi-6281 14
1: ni_660x PXI-6602 10
0: ni_pcimio pxi-6281 14
1: ni_660x PXI-6602 10
ni_pcimio:
ni_pcimio
8255:

View file

@ -6,138 +6,175 @@
]>
<section id="writingprograms" xmlns:xi="http://www.w3.org/2001/XInclude">
<title>
Writing &comedi; programs
</title>
<para>
This section describes how &comedi;
can be used in an application, to communicate data with a set
of &comedi; devices.
<xref linkend="acquisitionfunctions"/> gives more details about
the various acquisition functions with which the application
programmer can perform data acquisition in &comedi;.
</para>
<para>
Also don't forget to take a good look at the
<filename class="directory">demo</filename>
directory of the Comedilib source code. It contains lots of examples
for the basic functionalities of &comedi;.
</para>
<title>
Writing &comedi; programs
</title>
<para>
This section describes how &comedi;
can be used in an application, to communicate data with a set
of &comedi; devices.
<xref linkend="acquisitionfunctions"/> gives more details about
the various acquisition functions with which the application
programmer can perform data acquisition in &comedi;.
</para>
<para>
Also don't forget to take a good look at the
<filename class="directory">demo</filename>
directory of the Comedilib source code. It contains lots of examples
for the basic functionalities of &comedi;.
</para>
<section id="firstprogram">
<title>
Your first &comedi; program
</title>
<para>
This example requires a card that has analog or digital input. This
progam opens the device, gets the data, and prints it out:
<programlisting>
<xi:include href="../demo/tut1.c" parse="text"/>
</programlisting>
</para>
<para>
The source code file for the above program can be found in Comedilib,
at <filename>demo/tut1.c</filename>. You can compile the program using
</para>
<screen>
cc tut1.c -lcomedi -o tut1
</screen>
<para>
The
<function>
<link linkend="func-ref-comedi-open">comedi_open</link>
</function> call can only be successful if the
<filename>comedi0</filename> device file is configured with a
valid &comedi; driver. <xref linkend="cardconfiguration"/> explains
how this driver is linked to the <quote>device file</quote>.
</para>
<para>
The <parameter class="function">range</parameter> variable tells
&comedi; which gain to use when measuring an analog voltage. Since we
don't know (yet) which numbers are valid, or what each means, we'll
use <literal>0</literal>, because it won't cause errors. Likewise
with <parameter class="function">aref</parameter>, which determines the
analog reference used.
</para>
</section>
<section id="firstprogram">
<title>
Your first &comedi; program
</title>
<para>
This example requires a card that has analog or digital input. This
progam opens the device, gets the data, and prints it out:
<programlisting>
<xi:include href="../demo/tut1.c" parse="text"/>
</programlisting>
</para>
<para>
The source code file for the above program can be found in Comedilib,
at <filename>demo/tut1.c</filename>. You can compile the program using
</para>
<section id="convertingsamples">
<title>
Converting between integer data and physical units
</title>
<para>
If you selected an analog input subdevice, you probably noticed
that the output of <command>tut1</command> is an unsigned number, for
example between <literal>0</literal> and <literal>65535</literal>
for a 16 bit analog input. &comedi; samples are
unsigned,
with <literal>0</literal> representing the lowest voltage of the ADC,
and a hardware-dependent maximum value representing the highest voltage.
&comedi; compensates for anything else the manual for
your device says (for example, many boards represent bipolar
analog input voltages as signed integers).
However, you probably prefer to have this number
translated to a voltage. Naturally, as a good programmer, your first
question is: <quote>How do I do this in a device-independent
manner?</quote>
</para>
<para>
The functions
<link linkend="func-ref-comedi-to-physical"><function>comedi_to_physical</function></link>, <link linkend="func-ref-comedi-to-phys"><function>comedi_to_phys</function></link>,
<link linkend="func-ref-comedi-from-physical"><function>comedi_from_physical</function></link> and <link linkend="func-ref-comedi-from-phys"><function>comedi_from_phys</function></link>
are used to convert between &comedi;'s integer data and floating point numbers corresponding
to physical values (voltages, etc.).
</para>
</section>
<screen>
cc tut1.c -lcomedi -o tut1
</screen>
<para>
The
<function>
<link linkend="func-ref-comedi-open">comedi_open</link>
</function> call can only be successful if the
<filename>comedi0</filename> device file is configured with a
valid &comedi; driver. <xref linkend="cardconfiguration"/> explains
how this driver is linked to the <quote>device file</quote>.
</para>
<para>
The <parameter class="function">range</parameter> variable tells
&comedi; which gain to use when measuring an analog voltage. Since we
don't know (yet) which numbers are valid, or what each means, we'll
use <literal>0</literal>, because it won't cause errors. Likewise
with <parameter class="function">aref</parameter>, which determines the
analog reference used.
</para>
<section id="secondprogram">
<title>
Your second &comedi; program
</title>
<para>
Actually, this is the first &comedi; program again, except
we've added code to convert the integer data value to physical units.
</para>
<programlisting>
<xi:include href="../demo/tut2.c" parse="text"/>
</programlisting>
<para>
The source code file for the above program can be found in Comedilib, at demo/tut2.c.
</para>
</section>
<section id="asyncprogram">
<title>
Asynchronous acquisition
</title>
<para>
Of special importance is the so called
"asynchronous data acquisition" where the &comedi; is sampling
in the background at a given sample rate. The
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 <command>ktimetrace</command> or <command>comedirecord</command>.
</para>
<para>
The program <command>tut3.c</command> 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 is started we
need to check periodically if data is available and
request it from &comedi; so that its internal buffer
won't overrun.
</para>
<para>
The program below is a stripped down version
of the program <command>cmd.c</command> in
the demo directory. To compile it run:
</para>
<screen>
gcc tut3.c -lcomedi -lm -o tut3
</screen>
<para>
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. Central in this
program is the loop using the standard C read command
which receives the buffer contents. Below is an
extract from <filename>tut3.c</filename> showing the
relevant commands:
</para>
<programlisting>
<xi:include href="../demo/tut3_part.c" parse="text"/>
</programlisting>
</section>
<section>
<title>Further examples</title>
<para>
See the demo subdirectory of Comedilib for more example programs.
The directory contains
a README file with descriptions of the various demo programs.
</para>
</section>
</section>
<section id="convertingsamples">
<title>
Converting between integer data and physical units
</title>
<para>
If you selected an analog input subdevice, you probably noticed
that the output of <command>tut1</command> is an unsigned number, for
example between <literal>0</literal> and <literal>65535</literal>
for a 16 bit analog input. &comedi; samples are
unsigned,
with <literal>0</literal> representing the lowest voltage of the ADC,
and a hardware-dependent maximum value representing the highest voltage.
&comedi; compensates for anything else the manual for
your device says (for example, many boards represent bipolar
analog input voltages as signed integers). However, you probably prefer to have this number
translated to a voltage. Naturally, as a good programmer, your first
question is: <quote>How do I do this in a device-independent
manner?</quote>
</para>
<para>
The functions
<link linkend="func-ref-comedi-to-physical"><function>comedi_to_physical</function></link> and
<link linkend="func-ref-comedi-from-physical"><function>comedi_from_physical</function></link>
are used to convert between &comedi;'s integer data and floating point numbers corresponding
to physical values (voltages, etc.). In order to use the conversion functions, you must
first obtain a <link linkend="ref-type-comedi-polynomial-t">comedi_polynomial_t</link>
corresponding to the subdevice, range, and possibly channel the integer data is associated
with. The comedi_polynomial_t object specifies the polynomial function that will be applied
to convert between &comedi;'s integer data and physical values.
</para>
<para>
A <link linkend="ref-type-comedi-polynomial-t">comedi_polynomial_t</link> may be obtained
from one of two functions:
<link linkend="func-ref-comedi-get-hardcal-converter"><function>comedi_get_hardcal_converter</function></link> or
<link linkend="func-ref-comedi-get-softcal-converter"><function>comedi_get_softcal_converter</function></link>.
Which function to use depends on whether your board does calibration in hardware, or relies on
the host computer for a software calibration. The
SDF_SOFT_CALIBRATED flag (queried by calling
<link linkend="func-ref-comedi-get-subdevice-flags"><function>comedi_get_subdevice_flags</function></link>)
will be set for boards that use software calibration.
</para>
</section>
<section id="secondprogram">
<title>
Your second &comedi; program
</title>
<para>
Actually, this is the first &comedi; program again, except
we've added code to convert the integer data value to physical units.
The program handles both the case of a software-calibrated
board and a hardware-calibrated board.
</para>
<programlisting>
<xi:include href="../demo/tut2.c" parse="text"/>
</programlisting>
<para>
The source code file for the above program can be found in Comedilib, at demo/tut2.c.
</para>
</section>
<section>
<title>Further examples</title>
<para>
See the demo subdirectory of Comedilib for more example programs. The directory contains
a README file with descriptions of the various demo programs.
</para>
</section>
</section>