This example requires a card that has analog or digital input. Right to the source:
#include <stdio.h> /* for printf() */
#include <comedilib.h>
int subdev = 0; /* change this to your input subdevice */
int chan = 0; /* change this to your channel */
int range = 0; /* more on this later */
int aref = AREF_GROUND; /* more on this later */
int main(int argc,char *argv[])
{
comedi_t *it;
int chan=0;
lsampl_t data;
it=comedi_open("/dev/comedi0");
comedi_data_read(it,subdev,chan,range,aref,& data);
printf("%d\n",data);
return 0;
}
Should be understandable. Open the device, get the data,
print it out. This is basically the guts of demo/inp.c
,
without error checking or fancy options. Including all
the appropriate headers is sometimes a little tricky.
Compile it using
cc tut1.c -lcomedi -o tut1
Hopefully it works.
A few notes: The range 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 0, because it won't cause errors. Likewise with aref, which determines the analog reference used.
If you selected an analog input subdevice, you should notice
that the output of tut1
is a number between
0 and 4095, or 0 and 65535, depending on the number of bits
in the A/D converter. Comedi samples are always unsigned,
with 0 representing the lowest voltage of the ADC, and 4095
the highest. Comedi compensates for
anything else the manual for your device says. However,
you probably prefer to have this number translated to
a voltage. Naturally, as a good programmer, your first
question is: "How do I do this in a device-independent
manner?"
For each subdevice, the comedi kernel module keeps a 'range_type' variable. This variable contains the number of available ranges (i.e., gains) that you can select, along with an offset in a list of range information structures. If you know the range_type variable, you can use these macros:
RANGE_OFFSET(range_type) RANGE_LENGTH(range_type)
to extract such information. However, you want the actual voltage information, not some integer offset in a table. Rather than messing with the library internals, use the function
ptr=comedi_get_range(comedi_file,subdevice,channel, range)
which returns a pointer to a comedi_range structure. The comedi_range structure looks like
typedef struct{
double min;
double max;
unsigned int unit;
}comedi_range;
As you might expect, ptr[range] is for range 'range', which you provided to comedi_data_read() above. 'min' represents the voltage corresponding to comedi_data_read() returning 0, and 'max' represents comedi_data_read() returning 'maxdata', (i.e., 4095 for 12 bit A/C converters, 65535 for 16 bit, or, 1 for digital input -- more on this in a bit.) The 'unit' entry tells you if min and max refer to voltage, current, etc.
"Could it get easier?", you say. Well, yes. Use the function comedi_to_phys(), which converts data values to physical units. Call it using something like
volts=comedi_to_phys(it,data,range,maxdata);
and the opposite
data=comedi_from_phys(it,volts,range,maxdata);
You probably noticed (and were worried) that we haven't discussed how to determine maxdata and range_type. Well, you could ask the kernel this information each time you need it, but since there are other variables, special cases, and several subdevices to worry about, it would be nice if the library could take care of this... (read on...)
In addition to providing low level routines for data access, the comedi library provides higher-level access, much like the standard C library provides fopen(), etc. as a high-level (and portable) alternative to the direct UNIX system calls open(), etc. Similarily to fopen(), we have comedi_open():
file=comedi_open("/dev/comedi0");
where file is of type (comedi_t *)
. This function
calls open()
, like we did explicitly in a previous
section, but also fills the comedi_t
structure with
lots of goodies -- information that we will need to use
soon.
Specifically, we needed to know maxdata for a specific subdevice/channel. How about:
maxdata=comedi_get_maxdata(file,subdevice,channel);
Wow. How easy. And the range type?
range_type=comedi_get_rangetype(file,subdevice,channel);
Cool. Other information you need to know about a channel can be gotten in a similar way.
Actually, this is the first comedi program again, just that we've added what we've learned.
#include <stdio.h> /* for printf() */
#include <comedi.h> /* also included by comedilib.h */
#include <comedilib.h> /* for comedi_get() */
int subdev = 0; /* change this to your input subdevice */
int chan = 0; /* change this to your channel */
int range = 0; /* more on this later */
int aref = 0; /* more on this later */
int main(int argc,char *argv[])
{
comedi_t *cf;
int chan=0;
int data;
int maxdata,rangetype;
double volts;
cf=comedi_open("/dev/comedi0");
maxdata=comedi_get_maxdata(cf,subdev,chan);
rangetype=comedi_get_rangetype(cf,subdev,chan);
data=comedi_get(cf->fd,subdev,chan,range,aref);
volts=comedi_to_phys(data,rangetype,range,maxdata);
printf("%d %g\n",data,volts);
return 0;
}
By now, the comedi_read_data()
line looks a little archaic, using
the UNIX file descriptor cf->fd instead of just cf. (By the
way, somewhere in the heart of comedi_open()
is the line
cf->fd=open(filename,O_RDWR)
.) Well, there isn't one good
replacement, since it highly depends on your application
what additional features you might want in a comedi_get()
replacement. But this is the topic of a different section.