This Section describes how a well-installed and configured Comedi package can be used in an application, to communicate data with a set of Comedi devices. Section 4 gives more details about the various acquisition functions with which the application programmer can perform data acquisition in Comedi.
Also don't forget to take a good look at the demo directory of the Comedilib source code. It contains lots of examples for the basic functionalities of Comedi.
This example requires a card that has analog or digital input. This progam opens the device, gets the data, and prints it out:
#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; lsampl_t data; it=comedi_open("/dev/comedi0"); comedi_data_read(it,subdev,chan,range,aref, & data); printf("%d\n",data); return 0; }The
comedi_open()
can only be successful if the
comedi0 device file is configured to point to a
valid Comedi driver. Section 2.1 explains
how this driver is linked to the "device file".The code above is basically the guts of demo/inp.c, without error checking or fancy options. Compile the program using
cc tut1.c -lcomedi -o tut1
(Replace cc by your favourite C compiler command.)
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 probably noticed 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?"
Most devices give you a choice of gain and unipolar/bipolar input, and Comedi allows you to select which of these to use. This parameter is called the "range parameter," since it specifies the "input range" for analog input (or "output range" for analog output.) The range parameter represents both the gain and the unipolar/bipolar aspects.
Comedi keeps the number of available ranges and the largest sample value for each subdevice/channel combination. (Some devices allow different input/output ranges for different channels in a subdevice.)
The largest sample value can be found using the function
lsampl_t comedi_get_maxdata(comedi_t * device, unsigned int subdevice, unsigned int channel))The number of available ranges can be found using the function:
int comedi_get_n_ranges(comedi_t * device, unsigned int subdevice, unsigned int channel);
For each value of the range parameter for a particular subdevice/channel, you can get range information using:
comedi_range * comedi_get_range(comedi_t * device, unsigned int subdevice, unsigned int channel, unsigned int range);which returns a pointer to a comedi_range structure, which has the following contents:
typedef struct{ double min; double max; unsigned int unit; }comedi_range;The structure element 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, or are dimensionless (e.g., for digital I/O).
"Could it get easier?" you say. Well, yes. Use
the function comedi_to_phys()
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);
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()
, as done explicitly in
a previous section, but also fills the
comedi_t
structure with lots of goodies; this information will be useful soon.
Specifically, you need to know maxdata for a specific subdevice/channel. How about:
maxdata=comedi_get_maxdata(file,subdevice,channel);Wow! How easy. And the range information?
comedi_range * comedi_get_range(comedi_tcomedi_t *it,unsigned int subdevice,unsigned int chan,unsigned int range);
Actually, this is the first Comedi program again, just that we've added what we've learned.
#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 = 0; /* more on this later */ int main(int argc,char *argv[]) { comedi_t *cf; int chan=0; lsampl_t 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); comedi_data_read(cf->fd,subdev,chan,range,aref,&data); volts=comedi_to_phys(data,rangetype,range,maxdata); printf("%d %g\n",data,volts); return 0; }
This program (taken from the set of demonstration examples that come with Comedi) shows how to use a somewhat more flexible acquisition function, the so-called instruction.
#include <stdio.h> #include <comedilib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/time.h> #include <unistd.h> #include "examples.h" /* * This example does 3 instructions in one system call. It does * a gettimeofday() call, then reads N_SAMPLES samples from an * analog input, and the another gettimeofday() call. */ #define MAX_SAMPLES 128 comedi_t *device; int main(int argc, char *argv[]) { int ret,i; comedi_insn insn[3]; comedi_insnlist il; struct timeval t1,t2; lsampl_t data[MAX_SAMPLES]; parse_options(argc,argv); device=comedi_open(filename); if(!device){ comedi_perror(filename); exit(0); } if(verbose){ printf("measuring device=%s subdevice=%d channel=%d range=%d analog reference=%d\n", filename,subdevice,channel,range,aref); } /* Set up a the "instruction list", which is just a pointer * to the array of instructions and the number of instructions. */ il.n_insns=3; il.insns=insn; /* Instruction 0: perform a gettimeofday() */ insn[0].insn=INSN_GTOD; insn[0].n=2; insn[0].data=(void *)&t1; /* Instruction 1: do 10 analog input reads */ insn[1].insn=INSN_READ; insn[1].n=n_scan; insn[1].data=data; insn[1].subdev=subdevice; insn[1].chanspec=CR_PACK(channel,range,aref); /* Instruction 2: perform a gettimeofday() */ insn[2].insn=INSN_GTOD; insn[2].n=2; insn[2].data=(void *)&t2; ret=comedi_do_insnlist(device,&il); if(ret<0){ comedi_perror(filename); exit(0); } printf("initial time: %ld.%06ld\n",t1.tv_sec,t1.tv_usec); for(i=0;i<n_scan;i++){ printf("%d\n",data[i]); } printf("final time: %ld.%06ld\n",t2.tv_sec,t2.tv_usec); printf("difference (us): %ld\n",(t2.tv_sec-t1.tv_sec)*1000000+ (t2.tv_usec-t1.tv_usec)); return 0; }
This example programs an analog output subdevice with Comedi's most powerful acquisition function, the asynchronous command, to generate a waveform.
The waveform in this example is a sine wave, but this can be easily changed to make a generic function generator.
The function generation algorithm is the same as what is typically used in digital function generators. A 32-bit accumulator is incremented by a phase factor, which is the amount (in radians) that the generator advances each time step. The accumulator is then shifted right by 20 bits, to get a 12 bit offset into a lookup table. The value in the lookup table at that offset is then put into a buffer for output to the DAC.
Once you have
issued the command, Comedi expects you to keep the buffer full of
data to output to the acquisition card. This is done by
write()
. Since there may be a delay between the
comedi_command()
and a subsequent write()
, you
should fill the buffer using write()
before you call
comedi_command(),
as is done here.
#include <stdio.h> #include <comedilib.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <getopt.h> #include <ctype.h> #include <math.h> #include "examples.h" double waveform_frequency = 10.0; /* frequency of the sine wave to output */ double amplitude = 4000; /* peak-to-peak amplitude, in DAC units (i.e., 0-4095) */ double offset = 2048; /* offset, in DAC units */ /* This is the size of chunks we deal with when creating and outputting data. This *could* be 1, but that would be inefficient */ #define BUF_LEN 4096 int subdevice; int external_trigger_number = 0; sampl_t data[BUF_LEN]; void dds_output(sampl_t *buf,int n); void dds_init(void); /* This define determines which waveform to use. */ #define dds_init_function dds_init_sine void dds_init_sine(void); void dds_init_pseudocycloid(void); void dds_init_sawtooth(void); int comedi_internal_trigger(comedi_t *dev, unsigned int subd, unsigned int trignum) { comedi_insn insn; lsampl_t data[1]; memset(&insn, 0, sizeof(comedi_insn)); insn.insn = INSN_INTTRIG; insn.subdev = subd; insn.data = data; insn.n = 1; data[0] = trignum; return comedi_do_insn(dev, &insn); } int main(int argc, char *argv[]) { comedi_cmd cmd; int err; int n,m; int total=0; comedi_t *dev; unsigned int chanlist[16]; unsigned int maxdata; comedi_range *rng; int ret; lsampl_t insn_data = 0; parse_options(argc,argv); /* Force n_chan to be 1 */ n_chan = 2; if(value){ waveform_frequency = value; } dev = comedi_open(filename); if(dev == NULL){ fprintf(stderr, "error opening %s\n", filename); return -1; } subdevice = comedi_find_subdevice_by_type(dev,COMEDI_SUBD_AO,0); maxdata = comedi_get_maxdata(dev,subdevice,0); rng = comedi_get_range(dev,subdevice,0,0); offset = (double)comedi_from_phys(0.0,rng,maxdata); amplitude = (double)comedi_from_phys(1.0,rng,maxdata) - offset; memset(&cmd,0,sizeof(cmd)); /* fill in the command data structure: */ cmd.subdev = subdevice; cmd.flags = 0; cmd.start_src = TRIG_INT; cmd.start_arg = 0; cmd.scan_begin_src = TRIG_TIMER; cmd.scan_begin_arg = 1e9/freq; cmd.convert_src = TRIG_NOW; cmd.convert_arg = 0; cmd.scan_end_src = TRIG_COUNT; cmd.scan_end_arg = n_chan; cmd.stop_src = TRIG_NONE; cmd.stop_arg = 0; cmd.chanlist = chanlist; cmd.chanlist_len = n_chan; chanlist[0] = CR_PACK(channel,range,aref); chanlist[1] = CR_PACK(channel+1,range,aref); dds_init(); dds_output(data,BUF_LEN); dds_output(data,BUF_LEN); dump_cmd(stdout,&cmd); if ((err = comedi_command(dev, &cmd)) < 0) { comedi_perror("comedi_command"); exit(1); } m=write(comedi_fileno(dev),data,BUF_LEN*sizeof(sampl_t)); if(m<0){ perror("write"); exit(1); } printf("m=%d\n",m); ret = comedi_internal_trigger(dev, subdevice, 0); if(ret<0){ perror("comedi_internal_trigger\n"); exit(1); } while(1){ dds_output(data,BUF_LEN); n=BUF_LEN*sizeof(sampl_t); while(n>0){ m=write(comedi_fileno(dev),(void *)data+(BUF_LEN*sizeof(sampl_t)-n),n); if(m<0){ perror("write"); exit(0); } printf("m=%d\n",m); n-=m; } total+=BUF_LEN; } return 0; } #define WAVEFORM_SHIFT 16 #define WAVEFORM_LEN (1<<WAVEFORM_SHIFT) #define WAVEFORM_MASK (WAVEFORM_LEN-1) sampl_t waveform[WAVEFORM_LEN]; unsigned int acc; unsigned int adder; void dds_init(void) { adder=waveform_frequency/freq*(1<<16)*(1<<WAVEFORM_SHIFT); dds_init_function(); } void dds_output(sampl_t *buf,int n) { int i; sampl_t *p=buf; for(i=0;i<n;i++){ *p=waveform[(acc>>16)&WAVEFORM_MASK]; p++; acc+=adder; } } void dds_init_sine(void) { int i; for(i=0;i<WAVEFORM_LEN;i++){ waveform[i]=rint(offset+0.5*amplitude*cos(i*2*M_PI/WAVEFORM_LEN)); } } /* Yes, I know this is not the proper equation for a cycloid. Fix it. */ void dds_init_pseudocycloid(void) { int i; double t; for(i=0;i<WAVEFORM_LEN/2;i++){ t=2*((double)i)/WAVEFORM_LEN; waveform[i]=rint(offset+amplitude*sqrt(1-4*t*t)); } for(i=WAVEFORM_LEN/2;i<WAVEFORM_LEN;i++){ t=2*(1-((double)i)/WAVEFORM_LEN); waveform[i]=rint(offset+amplitude*sqrt(1-t*t)); } } void dds_init_sawtooth(void) { int i; for(i=0;i<WAVEFORM_LEN;i++){ waveform[i]=rint(offset+amplitude*((double)i)/WAVEFORM_LEN); } }