Much fixage and new text from Herman.
This commit is contained in:
parent
8e33ed9c6e
commit
d70d0eef2c
11 changed files with 4814 additions and 1768 deletions
672
doc/advanced.sgml
Normal file
672
doc/advanced.sgml
Normal file
|
@ -0,0 +1,672 @@
|
|||
<p>
|
||||
<sect1>Configuring comedi for your hardware
|
||||
<p>
|
||||
|
||||
|
||||
I assume that your hardware device is in your computer, and that
|
||||
you know the relevant details about it, i.e., what kind of card
|
||||
it is, the I/O base, the IRQ, jumper settings related to input
|
||||
ranges, etc.
|
||||
|
||||
To tell the comedi kernel module that you have a particular device, and
|
||||
some information about it, you will be running the <tt>comedi_config</tt>
|
||||
command. Perhaps you should read the man page now.
|
||||
|
||||
In this tutorial, I will go through the process of configuring comedi
|
||||
for two devices, a National Instruments AT-MIO-16E-10
|
||||
and a Data Translation DT2821-F-8DI.
|
||||
|
||||
The NI board is plug-and-play, and the man page tells me that I need
|
||||
to configure the PnP part of the board with isapnptools. The isapnptools
|
||||
package is a little cryptic, but the concepts are simple. Once I
|
||||
learned how to use it, I settled on a /etc/isapnp.conf file that
|
||||
contained the lines:
|
||||
|
||||
|
||||
<tscreen><verb>
|
||||
# ANSI string -->National Instruments, AT-MIO-16E-10<--
|
||||
(CONFIGURE NIC2400/10725401 (LD 0
|
||||
(IO 0 (BASE 0x0260))
|
||||
(INT 0 (IRQ 3 (MODE +E)))
|
||||
# (DMA 0 (CHANNEL 5))
|
||||
# (DMA 1 (CHANNEL 6))
|
||||
(ACT Y)
|
||||
))
|
||||
</verb></tscreen>
|
||||
|
||||
|
||||
It also contains a few lines about overall configuration and about my
|
||||
sound card. I found out after a bit of trial-and-error that the NI
|
||||
board does not always work with interrupts other than IRQ 3. YMMV.
|
||||
Currently, the driver doesn't use DMA, but it may in the future, so
|
||||
I commented out the DMA lines. It is a curious fact that the device
|
||||
ignores the IRQ and DMA information given here, however, I keep the
|
||||
information here to remind myself that the numbers aren't arbitrary.
|
||||
|
||||
When I run comedi_config (as root, of course), I provide the same
|
||||
information. Since I want to have the board configured every time
|
||||
I boot, I put the line
|
||||
|
||||
<tscreen><verb>
|
||||
/usr/sbin/comedi_config /dev/comedi0 atmio-E 0x260,3
|
||||
</verb></tscreen>
|
||||
|
||||
into <tt>/etc/rc.d/rc.local</tt>. You can, of course, run this command at
|
||||
a command prompt. The man page tells me that the option list
|
||||
is supposed to be "(I/O base),(IRQ)", so I used the same numbers
|
||||
as I put in /etc/isapnp.conf, i.e., 0x260,3.
|
||||
|
||||
For the Data Translation board, I need to have a list of the
|
||||
jumper settings. Fortunately, I wrote them all down in the
|
||||
manual -- I hope they are still correct. However, I had to
|
||||
open the case to figure out which board in the series I had.
|
||||
It is a DT2821-f-8di. The man page of comedi_config tells
|
||||
me that I need to know the I/O base, IRQ, DMA 1, DMA 2. However,
|
||||
since I wrote the driver, I know that it also recognizes the
|
||||
differential/single-ended and unipolar/bipolar jumpers. As always,
|
||||
the source is the final authority, and looking in module/dt282x.c
|
||||
tells me that the options list is interpreted as:
|
||||
|
||||
<itemize>
|
||||
<item>I/O base
|
||||
<item>IRQ
|
||||
<item>1=differential, 0=single ended
|
||||
<item>ai 0=unipolar, 1=bipolar
|
||||
<item>ao0 0=unipolar, 1=bipolar
|
||||
<item>ao1 0=unipolar, 1=bipolar
|
||||
<item>dma1
|
||||
<item>dma2
|
||||
</itemize>
|
||||
|
||||
(ai=analog input, ao=analog output.) From this, I decide that
|
||||
the appropriate options list is
|
||||
|
||||
<tscreen><verb>
|
||||
0x200,4,,1,1,1
|
||||
</verb></tscreen>
|
||||
|
||||
I left the differential/single-ended number blank, since the
|
||||
driver already knowns (from the board name), that it is
|
||||
differential. I also left the DMA numbers blank, since I
|
||||
don't want the driver to use DMA. (Don't want it to interfere
|
||||
with my sound card -- life is full of difficult choices.)
|
||||
Keep in mind that things commented in the source, but not in
|
||||
the documentation are about as likely to change as the weather,
|
||||
so I put good comments next to the following line when I put
|
||||
it in rc.local.
|
||||
|
||||
<tscreen><verb>
|
||||
/usr/sbin/comedi_config /dev/comedi1 dt2821-f-8di 0x200,4,,1,1,1
|
||||
</verb></tscreen>
|
||||
|
||||
So now I think that I have my boards configured correctly.
|
||||
Since data acquisition boards are not typically well-engineered,
|
||||
comedi sometimes can't figure out if the board is actually there.
|
||||
If it can't, it assumes you are right. Both of these boards
|
||||
are well-made, so comedi will give me an error message if it
|
||||
can't find them. The comedi kernel module, since it is a part
|
||||
of the kernel, prints messages to the kernel logs, which you
|
||||
can access through the command 'dmesg' or /var/log/messages.
|
||||
Here is a configuration failure (from dmesg):
|
||||
|
||||
<tscreen><verb>
|
||||
comedi0: ni_E: 0x0200 can't find board
|
||||
</verb></tscreen>
|
||||
|
||||
When it does work, I get:
|
||||
|
||||
<tscreen><verb>
|
||||
comedi0: ni_E: 0x0260 at-mio-16e-10 ( irq = 3 )
|
||||
</verb></tscreen>
|
||||
|
||||
Note that it also correctly identified my board.
|
||||
|
||||
|
||||
<p>
|
||||
<sect1>Getting information from comedi
|
||||
<p>
|
||||
|
||||
|
||||
So now that we have comedi talking to the hardware, we want to
|
||||
talk to comedi. Here's some pretty low-level information --
|
||||
it's sometimes useful for debugging:
|
||||
|
||||
<p>
|
||||
|
||||
<tscreen><verb>
|
||||
cat /proc/comedi
|
||||
</verb></tscreen>
|
||||
|
||||
Right now, on my computer, this command gives:
|
||||
|
||||
<tscreen><verb>
|
||||
comedi version 0.6.4
|
||||
format string
|
||||
0: atmio-E at-mio-16e-10 7
|
||||
1: dt282x dt2821-f-8di 4
|
||||
</verb></tscreen>
|
||||
|
||||
This is a feature that is not well-developed yet. Basically, it
|
||||
currently tells you driver name, device name, and number of
|
||||
subdevices.
|
||||
|
||||
In the <tt>demo/</tt> directory, there is a command called
|
||||
<tt>info</tt>, which provides information about each subdevice on the
|
||||
board. The output of it is rather long, since I have 7
|
||||
subdevices (4 or fewer is common for other boards.)
|
||||
Here's part of the output of the NI board (which
|
||||
is on <tt>/dev/comedi0</tt>.) ('demo/info /dev/comedi0')
|
||||
|
||||
<tscreen><verb>
|
||||
overall info:
|
||||
version code: 0x000604
|
||||
driver name: atmio-E
|
||||
board name: at-mio-16e-10
|
||||
number of subdevices: 7
|
||||
subdevice 0:
|
||||
type: 1 (unknown)
|
||||
number of channels: 16
|
||||
max data value: 4095
|
||||
</verb>
|
||||
...
|
||||
</tscreen>
|
||||
|
||||
The overall info gives information about the device -- basically
|
||||
the same information as /proc/comedi.
|
||||
|
||||
This board has 7 subdevices. Devices are separated into
|
||||
subdevices that each have a distinct purpose -- e.g., analog
|
||||
input, analog output, digital input/output. This board also
|
||||
has an EEPROM and calibration DACs that are also subdevices.
|
||||
|
||||
Subdevice 0 is the analog input subdevice. You would have
|
||||
known this from the 'type: 1 (unknown)' line, if I've updated
|
||||
demo/info recently, because it would say 'type: 1 (analog input)'
|
||||
instead. The other lines should be self-explanitory. Comedi
|
||||
has more information about the device, but demo/info doesn't
|
||||
currently display this.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<sect>Writing programs that use comedi and comedilib
|
||||
<p>
|
||||
|
||||
<sect1>Your first comedi program
|
||||
<p>
|
||||
|
||||
This example requires a card that has analog or
|
||||
digital input. Right to the source:
|
||||
|
||||
<tscreen><verb>
|
||||
#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;
|
||||
}
|
||||
</verb></tscreen>
|
||||
|
||||
|
||||
Should be understandable: open the device, get the data,
|
||||
print it out. This is basically the guts of <tt>demo/inp.c</tt>,
|
||||
without error checking or fancy options.
|
||||
Compile it using
|
||||
|
||||
<tscreen><verb>
|
||||
cc tut1.c -lcomedi -o tut1
|
||||
</verb></tscreen>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<p>
|
||||
<sect1>Converting samples to voltages
|
||||
<p>
|
||||
|
||||
If you selected an analog input subdevice, you probably noticed
|
||||
that the output of <tt>tut1</tt> 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 <bf>always</bf> 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:
|
||||
|
||||
comedi_get_maxdata()
|
||||
|
||||
The number of available ranges can be found using the function:
|
||||
|
||||
comedi_get_n_ranges()
|
||||
|
||||
For each value of the range parameter for a particular
|
||||
subdevice/channel, you can get range information using 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
|
||||
|
||||
<p>
|
||||
<tscreen><verb>
|
||||
typedef struct{
|
||||
double min;
|
||||
double max;
|
||||
unsigned int unit;
|
||||
}comedi_range;
|
||||
</verb></tscreen>
|
||||
|
||||
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, 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
|
||||
|
||||
<tscreen><verb>
|
||||
volts=comedi_to_phys(it,data,range,maxdata);
|
||||
</verb></tscreen>
|
||||
|
||||
and the opposite
|
||||
|
||||
<tscreen><verb>
|
||||
data=comedi_from_phys(it,volts,range,maxdata);
|
||||
</verb></tscreen>
|
||||
|
||||
|
||||
<p>
|
||||
<sect1>Another section
|
||||
<p>
|
||||
|
||||
|
||||
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():
|
||||
|
||||
<p>
|
||||
<tscreen><verb>
|
||||
file=comedi_open("/dev/comedi0");
|
||||
</verb></tscreen>
|
||||
|
||||
where file is of type <tt>(comedi_t *)</tt>. This function
|
||||
calls <tt>open()</tt>, like we did explicitly in a previous
|
||||
section, but also fills the <tt>comedi_t</tt> 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:
|
||||
|
||||
<tscreen><verb>
|
||||
maxdata=comedi_get_maxdata(file,subdevice,channel);
|
||||
</verb></tscreen>
|
||||
|
||||
Wow. How easy. And the range type?
|
||||
|
||||
<tscreen><verb>
|
||||
range_type=comedi_get_rangetype(file,subdevice,channel);
|
||||
</verb></tscreen>
|
||||
|
||||
Cool. Other information you need to know about a channel
|
||||
can be gotten in a similar way.
|
||||
|
||||
|
||||
|
||||
<sect1>Your second comedi program
|
||||
<p>
|
||||
|
||||
|
||||
Actually, this is the first comedi program again, just
|
||||
that we've added what we've learned.
|
||||
|
||||
|
||||
<tscreen><verb>
|
||||
#include <stdio.h> /* for printf() */
|
||||
#include <comedi.h> /* also included by comedilib.h */
|
||||
#include <comedilib.h> /* 'cuz we're using comedilib */
|
||||
|
||||
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;
|
||||
}
|
||||
</verb></tscreen>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
<sect>Application-specific functions
|
||||
<p>
|
||||
|
||||
<sect1>Digital Input/Output
|
||||
<p>
|
||||
|
||||
Many boards supported by comedi have digital input and output
|
||||
channels. Some boards allow the direction of a channel to be
|
||||
specified in software.
|
||||
|
||||
Comedi groups digital channels into subdevice, which is a group
|
||||
of digital channels that have the same characteristics. For
|
||||
example, digital output lines will be grouped into a digital
|
||||
output subdevice, bidirectional digital lines will be grouped
|
||||
into a digital I/O subdevice. Thus, there can be multiple
|
||||
digital subdevices on a particular board.
|
||||
|
||||
Individual digital lines can be read and written using the
|
||||
functions
|
||||
|
||||
<tt/comedi_dio_read(device,subdevice,channel,unsigned int *bit);/
|
||||
<tt/comedi_dio_write(device,subdevice,channel,unsigned int bit);/
|
||||
|
||||
The direction of bidirectional lines can be configured using
|
||||
the function
|
||||
|
||||
<tt/comedi_dio_config(device,subdevice,channel,unsigned int dir);/
|
||||
|
||||
The parameter <tt/dir/ should be either COMEDI_INPUT or COMEDI_OUTPUT.
|
||||
Many digital I/O subdevices group channels into blocks for
|
||||
configuring direction. Changing one channel in a block changes
|
||||
the entire block.
|
||||
|
||||
Multiple channels can be read and written simultaneously using the
|
||||
function
|
||||
|
||||
<tt/comedi_dio_bitfield(device,subdevice,unsigned int write_mask,unsigned int *bits);/
|
||||
|
||||
Each channel is assigned to a bit in the <tt/write_mask/ and <tt/bits/
|
||||
bitfield. If a bit in <tt/write_mask/ is set, the corresponding bit
|
||||
in <tt/*bits/ will be written to the corresponding digital output line.
|
||||
Each digital line is then read and placed into <tt/*bits/. The value
|
||||
of bits in <tt/*bits/ corresponding to digital output lines is
|
||||
undefined and device-specific. Channel 0 is the least significant
|
||||
bit in the bitfield; channel 31 is the most significant bit. Channels
|
||||
higher than 31 cannot be accessed using this method.
|
||||
|
||||
|
||||
<p>
|
||||
<sect1>Slowly-varying inputs
|
||||
<p>
|
||||
|
||||
|
||||
Sometimes, your input channels change slowly enough that
|
||||
you are able to average many sucessive input values to get a
|
||||
more accurate measurement of the actual value. In general,
|
||||
the more samples you average, the better your estimate
|
||||
gets, roughly by a factor of sqrt(number_of_samples).
|
||||
Obviously, there are limitations to this:
|
||||
|
||||
<p>
|
||||
<itemize>
|
||||
<item>
|
||||
you are ultimately limited by "spurious free dynamic range"
|
||||
|
||||
<item>
|
||||
you need to have _some_ noise on the input channel,
|
||||
otherwise you will be averaging the same number N times.
|
||||
|
||||
<item>
|
||||
the more noise you have, the greater your SFDR, but it
|
||||
takes many more samples to compensate for the increased
|
||||
noise
|
||||
|
||||
<item>
|
||||
if you feel the need to average samples for 2 seconds,
|
||||
your signal will need to be _very_ slowly-varying, i.e.,
|
||||
not varying more than your target uncertainty for the
|
||||
entire 2 seconds.
|
||||
|
||||
</itemize>
|
||||
|
||||
As you might have guessed, the comedi library has functions
|
||||
to help you in your quest to accurately measure slowly varying
|
||||
inputs. I use these functions to measure thermocouple voltages
|
||||
-- actually, the library functions came from a section of code
|
||||
that was previously part of the thermocouple reading program.
|
||||
|
||||
The comedi self-calibration utility also uses these functions.
|
||||
On some hardware, it is possible to tell it to measure an
|
||||
internal stable voltage reference, which is typically going
|
||||
to be very slowly varying -- on the kilosecond time scale
|
||||
or more. So it is reasonable to measure millions of samples,
|
||||
to get a very accurate measurement of the A/D converter output
|
||||
value that corresponds to the voltage reference. Sometimes,
|
||||
however, this is overkill, since there is no need to
|
||||
perform a part-per-million calibration to a standard that
|
||||
is only accurate to part-per-thousand.
|
||||
|
||||
|
||||
<p>
|
||||
<sect1>Commands
|
||||
<label id="command_section">
|
||||
<p>
|
||||
|
||||
|
||||
Many data acquisition devices have the capability to directly
|
||||
control acquisition using either an on-board timer or an external
|
||||
triggering input. Comedi commands are used to control this kind
|
||||
of acquisition. The <ref id="comedi_cmd" name="comedi_cmd"> structure is
|
||||
used to control acquisition and query the capabilities of a device
|
||||
(see also <ref id="comedi_command" name="comedi_command()">,
|
||||
<ref id="comedi_command_test" name="comedi_command_test()">, and
|
||||
<ref id="comedi_get_cmd_src_mask" name="comedi_get_cmd_src_mask()">).
|
||||
|
||||
Commands specify a particular data acquisition sequence, which
|
||||
is comprised of a number of scans. Each scan is comprised of
|
||||
a number of conversions, which usually corresponds to a single
|
||||
A/D or D/A conversion. The start and end of the sequence, and
|
||||
the start and end of each scan, and each conversion is called an
|
||||
event.
|
||||
|
||||
Each of these 5 types of events are caused by a triggering
|
||||
source, specified through the <tt/*_src/ members of the
|
||||
<ref id="comedi_cmd" name="comedi_cmd"> structure. The source types are:
|
||||
|
||||
<itemize>
|
||||
<item>TRIG_NONE: don't ever cause an event
|
||||
<item>TRIG_NOW: cause event to occur immediately
|
||||
<item>TRIG_FOLLOW: see notes below
|
||||
<item>TRIG_TIME: cause event to occur at a particular time
|
||||
<item>TRIG_TIMER: cause event to occur repeatedly at a specific rate
|
||||
<item>TRIG_COUNT: cause event when count reaches specific value
|
||||
<item>TRIG_EXT: external signal causes event
|
||||
<item>TRIG_INT: internal signal causes event
|
||||
<item>TRIG_OTHER: driver-specific meaning
|
||||
</itemize>
|
||||
|
||||
Not all triggers are applicable to all events. Supported triggers
|
||||
for specific events depend significantly on your particular
|
||||
device. The <ref id="comedi_get_cmd_src_mask" name="comedi_get_cmd_src_mask()">
|
||||
function is useful for determining what triggers a subdevice supports.
|
||||
|
||||
For every trigger, there is a corresponding
|
||||
argument (the <tt/*_arg/ members of the <ref id="comedi_cmd" name="comedi_cmd">
|
||||
structure) whose meaning depends on the type of trigger. The meanings
|
||||
of the arguments are as follows:
|
||||
|
||||
TRIG_NONE is typically used only as a <tt/stop_src/. The argument for TRIG_NONE
|
||||
is reserved and should be set to 0.
|
||||
|
||||
TRIG_NOW is most often used as a <tt/start_src/. The argument for TRIG_NOW is
|
||||
the number of nanoseconds between when the command is issued and when
|
||||
the event should occur. In the case of using TRIG now as a <tt/start_src/,
|
||||
it indicates a delay between issuing the command and the start of
|
||||
acquisition. Most drivers only support a delay of 0.
|
||||
|
||||
TRIG_FOLLOW is a special type of trigger for events that trigger on
|
||||
the completion of some other, logically connected event. The argument
|
||||
is reserved and should be set to 0. When used
|
||||
as a <tt/scan_begin_src/, it indicates that a trigger should occur as a
|
||||
logical continuation of convert events. This is done in order to
|
||||
properly describe boards that do not have separate timers for
|
||||
convert and scan_begin events. When used as a <tt/start_src/ for analog
|
||||
output subdevices, it indicates that conversion of output samples
|
||||
should begin when samples are written to the buffer.
|
||||
|
||||
TRIG_TIME is reserved for future use.
|
||||
|
||||
TRIG_TIMER is most often used as a <tt/convert_src/, a <tt/scan_begin_src/, or
|
||||
both. It indicates that triggers should occur at a specific rate.
|
||||
The argument specifies the interval between triggers in nanoseconds.
|
||||
|
||||
TRIG_COUNT is used for <tt/scan_end_src/ and <tt/stop_src/. It indicates that
|
||||
a trigger should occur when the specified number of corresponding
|
||||
lower-level triggers (convert and scan_begin, respectively) occur.
|
||||
The argument is the count of lower-level triggers.
|
||||
|
||||
TRIG_EXT can be useful as any of the trigger sources. It indicates
|
||||
that an external digital line should be used to trigger the event.
|
||||
The exact meaning of digital line is device-dependent. Some devices
|
||||
have one dedicated line, others may allow generic digital input
|
||||
lines to be used. The argument indicates the particular external
|
||||
line to use as the trigger.
|
||||
|
||||
TRIG_INT is typically used as a <tt/start_src/. This trigger occurs when
|
||||
the application performs an INSN_INTTRIG instruction. Using TRIG_INT
|
||||
is a method by which the application can accurately record the time of
|
||||
the start of acquisition, since the parsing and setup time of a
|
||||
particular command may be significant. The argument associated with
|
||||
TRIG_INT is reserved and should be set to 0.
|
||||
|
||||
TRIG_OTHER can be useful as any of the trigger sources. The exact
|
||||
meaning of TRIG_OTHER is driver-specific, and implements a feature
|
||||
that otherwise does not fit into the command interface. Configuration
|
||||
of TRIG_OTHER features are done by INSN_CONFIG insns. The argument
|
||||
is reserved and should be set to 0.
|
||||
|
||||
Ths <tt/subdev/ member of the <ref id="comedi_cmd" name="comedi_cmd">
|
||||
structure is the index of the subdevice the command is intended for. The
|
||||
<ref id="comedi_find_subdevice_by_type" name="comedi_find_subdevice_by_type()">
|
||||
function can be useful in discovering the index of your desired subdevice.
|
||||
|
||||
The <tt/chanlist/ member of the <ref id="comedi_cmd" name="comedi_cmd">
|
||||
structure should point to an array whose number of elements is specificed by <tt/chanlist_len/
|
||||
(this will generally be the same as the scan_end_arg).
|
||||
The chanlist specifies the sequence of channels and gains (and analog references)
|
||||
that should be stepped through for each scan. The elements of the chanlist array
|
||||
should be initialized by packing the channel, range and reference information
|
||||
together with the <ref id="CR_PACK" name="CR_PACK()"> macro.
|
||||
|
||||
The <tt/data/ and <tt/data_len/ members can be safely ignored when issueing commands
|
||||
from a user-space program. They only have meaning when a command is sent from a kernel
|
||||
module using the kcomedilib interface, in which case they specify the buffer where
|
||||
the driver should write/read its data to/from.
|
||||
|
||||
The final member of the <ref id="comedi_cmd" name="comedi_cmd"> structure is <tt/flags/.
|
||||
The following flags are valid, and can be bitwise-or'd together.
|
||||
|
||||
<itemize>
|
||||
<item>TRIG_BOGUS: do the motions??
|
||||
<item>TRIG_DITHER: enable dithering??
|
||||
<item>TRIG_DEGLITCH: enable deglitching??
|
||||
<item>TRIG_RT: ask driver to use a hard real-time interrupt handler. This will
|
||||
reduce latency in handling interrupts from your data aquisition hardware. It can
|
||||
be useful if you are sampling at high frequency, or if your hardware has a small onboard
|
||||
fifo. You must have a real-time kernel (RTAI or RTLinux) and must compile
|
||||
comedi with real-time support or this flag will do nothing.
|
||||
<item>TRIG_CONFIG: perform configuration, not triggering. This is a legacy of the
|
||||
deprecated comedi_trig_struct, and has no function at present.
|
||||
<item>TRIG_WAKE_EOS: some drivers will change their behaviour when this flag is set,
|
||||
trying to transfer data at the end of every scan (instead of, for example, passing
|
||||
data in chunks whenever the board's onboard fifo is half full). This flag
|
||||
may degrade a driver's performance at high frequencies.
|
||||
<item>TRIG_WRITE: write to bidirectional devices. Could be useful in principle, if someone
|
||||
wrote a driver that supported commands for a digital i/o device that could do either
|
||||
input or output.
|
||||
</itemize>
|
||||
There are also a few flags that indicate how timing arguments should be rounded
|
||||
if the hardware cannot achieve the exact timing requested.
|
||||
<itemize>
|
||||
<item>TRIG_ROUND_NEAREST: round to nearest supported timing period, the default.
|
||||
<item>TRIG_ROUND_DOWN: round period down.
|
||||
<item>TRIG_ROUND_UP: round period up.
|
||||
<item>TRIG_ROUND_UP_NEXT: this one doesn't do anything, and I don't know what it was intended
|
||||
to do??
|
||||
</itemize>
|
||||
|
||||
<p>
|
||||
|
||||
The typical sequence for executing a command is to first send
|
||||
the command through
|
||||
<ref id="comedi_command_test" name="comedi_command_test()">
|
||||
once or twice. The test will check that the command is valid for the particular
|
||||
device, and often makes some adjustments to the command arguments, which
|
||||
can then be read back by the user to see the actual values used. The
|
||||
command is executed with
|
||||
<ref id="comedi_command" name="comedi_command()">. For input/output commands, data
|
||||
is read from or written to the device file /dev/comedi[0..3] you are using.
|
||||
|
|
@ -1,20 +1,27 @@
|
|||
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
|
||||
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V3.1//EN"
|
||||
"docbook/dtd/3.1/docbook.dtd" [
|
||||
<!ENTITY intro SYSTEM "intro.sgml">
|
||||
<!ENTITY install SYSTEM "install.sgml">
|
||||
<!ENTITY tutorial SYSTEM "tutorial.sgml">
|
||||
<!ENTITY other SYSTEM "other.sgml">
|
||||
<!ENTITY driverwriting SYSTEM "driverwriting.sgml">
|
||||
<!ENTITY drivers SYSTEM "drivers.sgml">
|
||||
<!ENTITY reference SYSTEM "reference.sgml">
|
||||
<!ENTITY funcref SYSTEM "funcref.sgml">
|
||||
<!ENTITY glossary SYSTEM "glossary.sgml">
|
||||
<!ENTITY comedi "<acronym>Comedi</acronym>">
|
||||
]>
|
||||
|
||||
<article>
|
||||
|
||||
<artheader>
|
||||
<title>
|
||||
Comedi Documentation
|
||||
Comedi
|
||||
</title>
|
||||
<subtitle>
|
||||
The <emphasis>Control and Measurement Device Interface</emphasis>
|
||||
handbook
|
||||
</subtitle>
|
||||
<author>
|
||||
<firstname>David</firstname>
|
||||
<surname>Schleef</surname>
|
||||
|
@ -42,35 +49,92 @@
|
|||
</address>
|
||||
</affiliation>
|
||||
</author>
|
||||
<copyright>
|
||||
<year>1998-2003</year>
|
||||
<holder>David Schleef</holder>
|
||||
</copyright>
|
||||
|
||||
<abstract>
|
||||
<para>
|
||||
<emphasis role="strong">Abstract</emphasis>
|
||||
</para>
|
||||
<para>
|
||||
&comedi; is a free software project to interface
|
||||
<emphasis>digital acquisition</emphasis> (DAQ) cards. It is the
|
||||
combination of three complementary software items: (i) a generic,
|
||||
device-independent API, (ii) a collection of Linux kernel modules that
|
||||
implement this API for a wide range of cards, and (iii) a Linux user
|
||||
space library with a developer-oriented programming interface to
|
||||
configure and use the cards.
|
||||
</para>
|
||||
</abstract>
|
||||
|
||||
<legalnotice>
|
||||
<para>
|
||||
This document is part of Comedilib. In the context of this
|
||||
document, the term "source code" as defined by the license is
|
||||
interpreted as the SGML source.
|
||||
|
||||
COMEDILIB - Linux Control and Measurement Device Interface Library
|
||||
Copyright (C) 1997-2003 David A. Schleef <ds@schleef.org>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, version 2.1
|
||||
of the License.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
USA.
|
||||
</legalnotice>
|
||||
|
||||
</artheader>
|
||||
|
||||
&intro
|
||||
&intro;
|
||||
|
||||
&install
|
||||
&install;
|
||||
|
||||
&tutorial
|
||||
&tutorial;
|
||||
|
||||
&other
|
||||
&other;
|
||||
|
||||
&drivers
|
||||
&driverwriting;
|
||||
|
||||
<section>
|
||||
<section id="lowleveldrivers">
|
||||
<title>
|
||||
Comedi Reference
|
||||
Low-level drivers
|
||||
</title>
|
||||
<para>
|
||||
Reference for functions, macros, and constants.
|
||||
</para>
|
||||
|
||||
&reference
|
||||
|
||||
&funcref
|
||||
|
||||
&glossary
|
||||
&drivers;
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comedireference">
|
||||
<title>
|
||||
&comedi; Reference
|
||||
</title>
|
||||
<para>
|
||||
Reference for
|
||||
<link linkend="constantsmacros">constants and macros</link>,
|
||||
<link linkend="datatypesstructures">data types and structures</link>,
|
||||
and <link linkend="functionreference">functions</link>.
|
||||
</para>
|
||||
|
||||
&reference;
|
||||
|
||||
<!-- </section> -->
|
||||
|
||||
&funcref;
|
||||
|
||||
</section>
|
||||
|
||||
&glossary;
|
||||
|
||||
|
||||
</article>
|
||||
|
|
894
doc/driverwriting.sgml
Normal file
894
doc/driverwriting.sgml
Normal file
|
@ -0,0 +1,894 @@
|
|||
<section id="driverwriting">
|
||||
<title>
|
||||
Writing a &comedi; driver
|
||||
</title>
|
||||
|
||||
<para>
|
||||
This Section explains the most important implementations aspects of
|
||||
the &comedi; device drivers. It tries to give the interested device
|
||||
driver writer an overview of the different steps required to write a
|
||||
new device driver.
|
||||
</para>
|
||||
<para>
|
||||
This Section does <emphasis>not</emphasis> explain all implementation
|
||||
details of the &comedi; software itself: &comedi; has once and for
|
||||
all solved lots of boring but indispensable infrastructural things,
|
||||
such as: timers, management of which drivers
|
||||
are active, memory management for drivers and buffers, wrapping
|
||||
of RTOS-specific interfaces, interrupt handler management, general
|
||||
error handling, the <filename role=directory>/proc</filename>
|
||||
interface, etc. So,
|
||||
the device driver writers can concentrate on the interesting stuff:
|
||||
implementing their specific interface card's DAQ functionalities.
|
||||
</para>
|
||||
<para>
|
||||
In order to make a decent &comedi; device driver, you must
|
||||
know the answers to the following questions:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
How does the
|
||||
<link linkend="userkernelhow">communication</link> between user space
|
||||
and kernel space work?
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
What functionality is provided by the
|
||||
<link linkend="comedikernelgeneric">generic</link> kernel-space
|
||||
&comedi; functions, and what must be provided for each
|
||||
<link linkend="boardspecific">specific new driver</link>?
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
How to use <link linkend="drivercallbacks">DMA and interrupts</link>?
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
What are the addresses and meanings of all the card's registers?
|
||||
</para>
|
||||
<para>
|
||||
This information is to be found in the so-called “register level
|
||||
manual” of the card. Without it, coding a device driver is close
|
||||
to hopeless. It is also something that &comedi; (and hence also this
|
||||
handbook) cannot give any support or information for: board
|
||||
manufacturers all use their own design and nomenclature.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
<section id="userkernelhow">
|
||||
<title>
|
||||
Communication user space-kernel space
|
||||
</title>
|
||||
|
||||
<para>
|
||||
In user space, you interact with the functions implemented in the
|
||||
<filename role=directory>/usr/src/comedilib</filename> directory. Most
|
||||
of the device driver core of the Comedilib library is found in
|
||||
<filename role=directory>lib</filename> subdirectory.
|
||||
</para>
|
||||
<para>
|
||||
All user-space &comedi;
|
||||
<link linkend="instructions">instructions</link> and
|
||||
<link linkend="commandsstreaming">commands</link>
|
||||
are transmitted to kernel space through a traditional
|
||||
<function>ioctl</function> system call.
|
||||
(See <filename>/usr/src/comedilib/lib/ioctl.c</filename>.)
|
||||
The user space information command is <emphasis>encoded</emphasis> as
|
||||
a number in the <function>ioctl</function> call, and decoded in the
|
||||
kernel space library. There, they are executed by their kernel-space
|
||||
counterparts. This is done in the
|
||||
<filename>/usr/src/comedi/comedi/comedi_fops.c</filename> file: the
|
||||
<function>comedi_ioctl()</function> function processes the results of
|
||||
the <function>ioctl</function> system call, interprets its contents,
|
||||
and then calls the corresponding kernel space
|
||||
<function>do_…_ioctl</function> function(s).
|
||||
For example, a &comedi;
|
||||
<link linkend="instructions">instruction</link> is further processed
|
||||
by the <function>do_insn_ioctl()</function>function. (Which, in turn,
|
||||
uses <function>parse_insn()</function> for further detailed processing.)
|
||||
</para>
|
||||
<para>
|
||||
The data corresponding to instructions and commands is transmitted
|
||||
with the <function>copy_from_user()</function> system call;
|
||||
acquisition data captured by the interface card passes the kernel-user
|
||||
space boundary with the help of a <function>copy_to_user()</function>
|
||||
system call.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comedikernelgeneric">
|
||||
<title>
|
||||
Generic functionality
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The major include files of the kernel-space part of &comedi; are:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<filename>include/linux/comedidev.h</filename>: the
|
||||
header file for kernel-only structures (device, subdevice, async
|
||||
(i.e., buffer/event/interrupt/callback functionality for asynchronous
|
||||
DAQ in a &comedi; command), driver, lrange), variables, inline functions
|
||||
and constants.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<filename>include/linux/comedi_rt.h</filename>:
|
||||
all the real-time stuff, such as management of ISR in RTAI and
|
||||
RTLinux/Free, and spinlocks for atomic sections.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<filename>include/linux/comedilib.h</filename>: the header file for
|
||||
the kernel library of &comedi;.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
From all the relevant &comedi; device driver code that is found in the
|
||||
<filename role=directory>/usr/src/comedi/comedi</filename> directory
|
||||
(<emphasis>if</emphasis> the &comedi; source has been installed in its
|
||||
normal <filename role=directory>/usr/src/comedi</filename> location),
|
||||
the <emphasis role="strong">generic</emphasis> functionality is
|
||||
contained in two parts:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
A couple of <filename>C</filename> files contain the <emphasis
|
||||
role="strong">infrastructural support</emphasis>.
|
||||
From these <filename>C</filename> files, it's especially the
|
||||
<filename>comedi_fops.c</filename> file that implements what makes
|
||||
&comedi; into what people want to use it for: a library that has
|
||||
solved 90% of the DAQ device driver efforts, once and for all.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
For <emphasis role="strong">real-time</emphasis> applications,
|
||||
the subdirectory <filename role=directory>kcomedilib</filename>
|
||||
implements an interface in the kernel that is similar to the &comedi;
|
||||
interface accessible through the
|
||||
<link linkend="functionreference">user-space Comedi library</link>.
|
||||
</para>
|
||||
<para>
|
||||
There are some differences in what is possible and/or needed
|
||||
in kernel space and in user space, so the functionalities offered in
|
||||
<filename role=directory>kcomedilib</filename> are not an exact copy
|
||||
of the user-space library. For example, locking, interrupt handling,
|
||||
real-time execution, callback handling, etc., are only available in
|
||||
kernel space.
|
||||
</para>
|
||||
<para>
|
||||
Most drivers don't make use (yet) of these real-time functionalities.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
|
||||
<section id="driverdatastructures">
|
||||
<title>
|
||||
Data structures
|
||||
</title>
|
||||
|
||||
<para>
|
||||
This Section explains the generic data structures that a device driver
|
||||
interacts with:
|
||||
<programlisting>
|
||||
typedef struct comedi_lrange_struct <link linkend="comedilrange">comedi_lrange</link>;
|
||||
typedef struct comedi_subdevice_struct <link linkend="comedisubdevice">comedi_subdevice</link>;
|
||||
typedef struct comedi_device_struct <link linkend="comedidevice">comedi_device</link>:
|
||||
typedef struct comedi_async_struct <link linkend="comediasync">comedi_async</link>
|
||||
typedef struct comedi_driver_struct <link linkend="comedidriver">comedi_driver</link>;
|
||||
</programlisting>
|
||||
They can be found in
|
||||
<filename>/usr/src/comedi/include/linux/comedidev.h</filename>.
|
||||
Most of the fields are filled in by the &comedi; infrastructure, but
|
||||
there are still quite a handful that your driver must provide or use.
|
||||
As for the user-level &comedi;, each of the hierarchical layers has
|
||||
its own data structures: channel (<function>comedi_lrange</function>),
|
||||
subdevice, and device.
|
||||
</para>
|
||||
<para>
|
||||
Note that these kernel-space data structures have similar names as
|
||||
their
|
||||
<link linkend="datatypesstructures">user-space equivalents</link>, but
|
||||
they have a different (kernel-side) view on the DAQ problem and a
|
||||
different meaning: they encode the interaction with the
|
||||
<emphasis>hardware</emphasis>, not with the <emphasis>user</emphasis>.
|
||||
</para>
|
||||
<para>
|
||||
However, the <link linkend="ref-type-comedi-insn">comedi_insn</link>
|
||||
and <link linkend="ref-type-comedi-cmd">comedi_cmd</link>
|
||||
data structures are shared between user space and kernel space: this
|
||||
should come as no surprise, since these data structures contain all
|
||||
information that the user-space program must transfer to the
|
||||
kernel-space driver for each acquisition.
|
||||
</para>
|
||||
<para>
|
||||
In addition to these data entities that are also known at the user
|
||||
level (device, sub-device, channel), the device driver level provides
|
||||
two more data structures which the application programmer doesn't get
|
||||
in touch with: the data structure
|
||||
<link linkend="comedidriver">comedi_driver</link>
|
||||
that stores the device driver information that is relevant at the
|
||||
operating system level, and the data structure
|
||||
<link linkend="comediasync">comedi_async</link> that stores the
|
||||
information about all <emphasis>asynchronous</emphasis> activities
|
||||
(interrupts, callbacks and events).
|
||||
</para>
|
||||
|
||||
<section id="comedilrange">
|
||||
<title>
|
||||
<function>comedi_lrange</function>
|
||||
</title>
|
||||
<para>
|
||||
The channel information is simple, since it contains only the signal
|
||||
range information:
|
||||
<programlisting>
|
||||
struct comedi_lrange_struct{
|
||||
int length;
|
||||
<link linkend="ref-type-comedi-krange">comedi_krange</link> range[GCC_ZERO_LENGTH_ARRAY];
|
||||
};
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<section id="comedisubdevice">
|
||||
<title>
|
||||
<function>comedi_subdevice</function>
|
||||
<para>
|
||||
The subdevice is the smallest &comedi; entity that can be used for
|
||||
“stand-alone” DAQ, so it is no surprise that it is
|
||||
quite big:
|
||||
<programlisting>
|
||||
struct comedi_subdevice_struct{
|
||||
int type;
|
||||
int n_chan;
|
||||
int subdev_flags;
|
||||
int len_chanlist; /* maximum length of channel/gain list */
|
||||
|
||||
void *private;
|
||||
|
||||
<link linkend="comediasync">comedi_async</link> *async;
|
||||
|
||||
void *lock;
|
||||
void *busy;
|
||||
unsigned int runflags;
|
||||
|
||||
int io_bits;
|
||||
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> maxdata; /* if maxdata==0, use list */
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> *maxdata_list; /* list is channel specific */
|
||||
|
||||
unsigned int flags;
|
||||
unsigned int *flaglist;
|
||||
|
||||
<link linkend="comedilrange">comedi_lrange</link> *range_table;
|
||||
<link linkend="comedilrange">comedi_lrange</link> **range_table_list;
|
||||
|
||||
unsigned int *chanlist; /* driver-owned chanlist (not used) */
|
||||
|
||||
int (*insn_read)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
||||
int (*insn_write)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
||||
int (*insn_bits)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
||||
int (*insn_config)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
||||
|
||||
int (*do_cmd)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *);
|
||||
int (*do_cmdtest)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-cmd">comedi_cmd</link> *);
|
||||
int (*poll)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *);
|
||||
int (*cancel)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *);
|
||||
|
||||
int (*buf_change)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *s,unsigned long new_size);
|
||||
void (*munge)(<link linkend="comedidevice">comedi_device</link> *, <link linkend="comedisubdevice">comedi_subdevice</link> *s, void *data, unsigned int num_bytes, unsigned int start_chan_index );
|
||||
|
||||
unsigned int state;
|
||||
};
|
||||
</programlisting>
|
||||
The function pointers <function>(*insn_read)</function> …
|
||||
<function>(*cancel)</function> .
|
||||
offer (pointers to) the standardized
|
||||
<link linkend="functionreference">user-visible API</link>
|
||||
that every subdevice should offer; every device driver has to fill
|
||||
in these functions with their board-specific implementations.
|
||||
(Functionality for which &comedi; provides generic functions will, by
|
||||
definition, not show up in the device driver data structures.)
|
||||
</para>
|
||||
<para>
|
||||
The <function>buf_change()</function> and <function>munge()</function>
|
||||
functions offer functionality that is not visible to the user and for
|
||||
which the device driver writer must provide a board-specific
|
||||
implementation:
|
||||
<function>buf_change()</function> is called when a change in the
|
||||
data buffer requires handling; <function>munge()</function> transforms
|
||||
different bit-representations of DAQ values, for example from
|
||||
<emphasis>unsigned</emphasis> to <emphasis>2's complement</emphasis>.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comedidevice">
|
||||
<title>
|
||||
<function>comedi_device</function>
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The last data structure stores the information at the
|
||||
<emphasis>device</emphasis> level:
|
||||
<programlisting>
|
||||
struct comedi_device_struct{
|
||||
int use_count;
|
||||
<link linkend="comedidriver">comedi_driver</link> *driver;
|
||||
void *private;
|
||||
kdev_t minor;
|
||||
char *board_name;
|
||||
const void *board_ptr;
|
||||
int attached;
|
||||
int rt;
|
||||
spinlock_t spinlock;
|
||||
int in_request_module;
|
||||
|
||||
int n_subdevices;
|
||||
<link linkend="comedisubdevice">comedi_subdevice</link> *subdevices;
|
||||
int options[COMEDI_NDEVCONFOPTS];
|
||||
|
||||
/* dumb */
|
||||
int iobase;
|
||||
int irq;
|
||||
|
||||
<link linkend="comedisubdevice">comedi_subdevice</link> *read_subdev;
|
||||
wait_queue_head_t read_wait;
|
||||
|
||||
<link linkend="comedisubdevice">comedi_subdevice</link> *write_subdev;
|
||||
wait_queue_head_t write_wait;
|
||||
|
||||
struct fasync_struct *async_queue;
|
||||
|
||||
void (*open)(<link linkend="comedidevice">comedi_device</link> *dev);
|
||||
void (*close)(<link linkend="comedidevice">comedi_device</link> *dev);
|
||||
};
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comediasync">
|
||||
<title>
|
||||
<function>comedi_async</function>
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The following data structure contains all relevant information:
|
||||
addresses and sizes of buffers, pointers to the actual data, and the
|
||||
information needed for
|
||||
<link linkend="drivercallbacks">event handling</link>:
|
||||
<programlisting>
|
||||
struct comedi_async_struct{
|
||||
void *prealloc_buf; /* pre-allocated buffer */
|
||||
unsigned int prealloc_bufsz; /* buffer size, in bytes */
|
||||
unsigned long *buf_page_list; /* physical address of each page */
|
||||
unsigned int max_bufsize; /* maximum buffer size, bytes */
|
||||
unsigned int mmap_count; /* current number of mmaps of prealloc_buf */
|
||||
|
||||
volatile unsigned int buf_write_count; /* byte count for writer (write completed) */
|
||||
volatile unsigned int buf_write_alloc_count; /* byte count for writer (allocated for writing) */
|
||||
volatile unsigned int buf_read_count; /* byte count for reader (read completed)*/
|
||||
|
||||
unsigned int buf_write_ptr; /* buffer marker for writer */
|
||||
unsigned int buf_read_ptr; /* buffer marker for reader */
|
||||
|
||||
unsigned int cur_chan; /* useless channel marker for interrupt */
|
||||
/* number of bytes that have been received for current scan */
|
||||
unsigned int scan_progress;
|
||||
/* keeps track of where we are in chanlist as for munging */
|
||||
unsigned int munge_chan;
|
||||
|
||||
unsigned int events; /* events that have occurred */
|
||||
|
||||
<link linkend="ref-type-comedi-cmd">comedi_cmd</link> cmd;
|
||||
|
||||
// callback stuff
|
||||
unsigned int cb_mask;
|
||||
int (*cb_func)(unsigned int flags,void *);
|
||||
void *cb_arg;
|
||||
|
||||
int (*inttrig)(<link linkend="comedidevice">comedi_device</link> *dev,<link linkend="comedisubdevice">comedi_subdevice</link> *s,unsigned int x);
|
||||
};
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comedidriver">
|
||||
<title>
|
||||
<function>comedi_driver</function>
|
||||
</title>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
struct comedi_driver_struct{
|
||||
struct comedi_driver_struct *next;
|
||||
|
||||
char *driver_name;
|
||||
struct module *module;
|
||||
int (*attach)(<link linkend="comedidevice">comedi_device</link> *,comedi_devconfig *);
|
||||
int (*detach)(<link linkend="comedidevice">comedi_device</link> *);
|
||||
|
||||
/* number of elements in board_name and board_id arrays */
|
||||
unsigned int num_names;
|
||||
void *board_name;
|
||||
/* offset in bytes from one board name pointer to the next */
|
||||
int offset;
|
||||
};
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<section id="driversupportfunctions">
|
||||
<title>
|
||||
Generic driver support functions
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The directory
|
||||
<filename role=directory>comedi</filename> contains a large set of
|
||||
support functions. Some of the most important ones are given below.
|
||||
</para>
|
||||
<para>
|
||||
From <filename>comedi/comedi_fops.c</filename>, functions to handle the
|
||||
hardware events (which also runs the registered callback function), to
|
||||
get data in and out of the software data buffer, and to parse the
|
||||
incoming functional requests:
|
||||
<programlisting>
|
||||
void comedi_event(<link linkend="comedidevice">comedi_device</link> *dev,<link linkend="comedisubdevice">comedi_subdevice</link> *s,unsigned int mask);
|
||||
|
||||
int comedi_buf_put(<link linkend="comediasync">comedi_async</link> *async, <link linkend="ref-type-sampl-t">sampl_t</link> x);
|
||||
int comedi_buf_get(<link linkend="comediasync">comedi_async</link> *async, <link linkend="ref-type-sampl-t">sampl_t</link> *x);
|
||||
|
||||
static int parse_insn(<link linkend="comedidevice">comedi_device</link> *dev,<link linkend="ref-type-comedi-insn">comedi_insn</link> *insn,<link linkend="ref-type-lsampl-t">lsampl_t</link> *data,void *file);
|
||||
</programlisting>
|
||||
The file <filename>comedi/kcomedilib/kcomedilib_main.c</filename> provides
|
||||
functions to register a callback, to poll an ongoing data acquisition,
|
||||
and to print an error message:
|
||||
<programlisting>
|
||||
int comedi_register_callback(<link linkend="ref-type-comedi-t">comedi_t</link> *d,unsigned int subdevice, unsigned int mask,int (*cb)(unsigned int,void *),void *arg);
|
||||
|
||||
int comedi_poll(<link linkend="ref-type-comedi-t">comedi_t</link> *d, unsigned int subdevice);
|
||||
|
||||
void comedi_perror(const char *message);
|
||||
</programlisting>
|
||||
The file <filename>comedi/rt.c</filename> provides interrupt handling
|
||||
for real-time tasks (one interrupt per <emphasis>device</emphasis>!):
|
||||
<programlisting>
|
||||
int comedi_request_irq(unsigned irq,void (*handler)(int, void *,struct pt_regs *), unsigned long flags,const char *device,<link linkend="comedidevice">comedi_device</link> *dev_id);
|
||||
void comedi_free_irq(unsigned int irq,<link linkend="comedidevice">comedi_device</link> *dev_id)
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<section id="boardspecific">
|
||||
<title>
|
||||
Board-specific functionality
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The <filename role=directory>/usr/src/comedi/comedi/drivers</filename>
|
||||
subdirectory contains
|
||||
the <emphasis role="strong">board-specific</emphasis> device driver
|
||||
code. Each new card must get an entry in this directory.
|
||||
<emphasis role="strong">Or</emphasis>
|
||||
extend the functionality of an already existing driver file if the new
|
||||
card is quite similar to that implemented in an already existing
|
||||
driver. For example, many of the National Instruments DAQ cards use
|
||||
the same driver files.
|
||||
</para>
|
||||
<para>
|
||||
To help device driver writers,
|
||||
&comedi; provides the “skeleton” of a new device driver,
|
||||
in the <filename>comedi/drivers/skel.c</filename> file. Before
|
||||
starting to write a new driver, make sure you understand this file,
|
||||
and compare it to what you find in the other already available
|
||||
board-specific files in the same directory.
|
||||
</para>
|
||||
<para>
|
||||
The first thing you notice in <filename>skel.c</filename> is the
|
||||
documentation section: the &comedi; documentation is partially
|
||||
generated automatically, from the information that is given in this
|
||||
section. So, please comply with the structure and the keywords
|
||||
provided as &comedi; standards.
|
||||
</para>
|
||||
<para>
|
||||
The second part of the device driver contains board-specific static
|
||||
data structure and defines: addresses of hardware registers; defines and
|
||||
function prototypes for functionality that is only used inside of the
|
||||
device driver for this board; the encoding of the types and number of
|
||||
available channels; PCI information; etc.
|
||||
</para>
|
||||
<para>
|
||||
Each driver has to register two functions which are called when you
|
||||
load and unload your board's device driver (typically via a kernel
|
||||
module):
|
||||
<programlisting>
|
||||
mydriver_attach();
|
||||
mydriver_detach();
|
||||
</programlisting>
|
||||
In the “attach” function, memory is allocated for the
|
||||
necessary <link linkend="driverdatastructures">data structures</link>,
|
||||
all properties of a device and its subdevices are defined, and filled
|
||||
in in the generic &comedi; data structures. As part of this, pointers
|
||||
to the low level instructions being supported by the subdevice have to
|
||||
be set, which define the basic functionality. In somewhat more detail,
|
||||
the <function>mydriver_attach()</function> function must:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
check and request the I/O port region, IRQ, DMA, and other hardware
|
||||
resources. It is convenient here if you verify the existence of the
|
||||
hardware and the correctness of the other information given.
|
||||
Sometimes, unfortunately, this cannot be done.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
allocate memory for the private data structures.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
initialize the board registers and possible subdevices (timer, DMA, PCI,
|
||||
hardware FIFO, etc.).
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
return 1, indicating success. If there were any errors along the way,
|
||||
you should return the appropriate error number. If an error is
|
||||
returned, the <function>mydriver_detach()</function> function is
|
||||
called. The <function>mydriver_detach()</function> function should
|
||||
check any resources that may have been allocated and release them as
|
||||
necessary. The &comedi; core frees
|
||||
<function>dev->subdevices</function> and
|
||||
<function>dev->private</function>, so this does not need to be done in
|
||||
<function>detach</function>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
If the driver has the possibility to offer asynchronous data
|
||||
acquisition, you have to code an interrupt service routine, event
|
||||
handling routines, and/or callback routines.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
Typically, you will be able to implement most of
|
||||
the above-mentioned functionality by
|
||||
<emphasis>cut-and-paste</emphasis> from already existing drivers. The
|
||||
<function>mydriver_attach()</function> function needs most of your
|
||||
attention, because it must correctly define and allocate the (private
|
||||
and generic) data structures that are needed for this device. That is,
|
||||
each sub-device and each channel must get appropriate data fields, and
|
||||
an appropriate initialization. The good news, of course, is that
|
||||
&comedi; provides the data structures and the defines that fit very
|
||||
well with almost all DAQ functionalities found on interface cards.
|
||||
These can be found in the
|
||||
<link linkend="comedikernelgeneric">header files</link> of the
|
||||
<filename role=directory>/usr/src/comedi/include/linux/</filename>
|
||||
directory.
|
||||
</para>
|
||||
<para>
|
||||
Drivers for digital IOs should implement the following functions:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<function>insn_bits()</function>: drivers set this if they have a
|
||||
function that supports reading and writing multiple bits in a digital
|
||||
I/O subdevice at the same time. Most (if not all) of the drivers use
|
||||
this interface instead of insn_read and insn_write for DIO subdevices.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<function>insn_config()</function>: implements INSN_CONFIG
|
||||
instructions. Currently used for configuring the direction of digital
|
||||
I/O lines, although will eventually be used for generic configuration
|
||||
of drivers that is outside the scope of the currently defined &comedi;
|
||||
interface.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
Finally, the device driver writer must implement the
|
||||
<function>read</function> and <function>write</function> functions for
|
||||
the analog channels on the card:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<function>insn_read()</function>: acquire the inputs on the board and
|
||||
transfer them to the software buffer of the driver.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<function>insn_write()</function>: transfer data from the software
|
||||
buffer to the card, and execute the appropriate output conversions.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
In some drivers, you want to catch interrupts, and/or want to use the
|
||||
<link linkend="insn-inttrig">INSN_INTTRIG</link> instruction. In this
|
||||
case, you must provide and register these
|
||||
<link linkend="drivercallbacks">callback</link> functions.
|
||||
</para>
|
||||
<para>
|
||||
Implementation of all of the above-mentioned functions requires
|
||||
perfect knowledge about the hardware registers and addresses of the
|
||||
interface card. In general, you can find
|
||||
<emphasis>some</emphasis> inspiration in the already available device
|
||||
drivers, but don't trust that blind
|
||||
<emphasis>cut-and-paste</emphasis> will bring you far…
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="drivercallbacks">
|
||||
<title>
|
||||
Callbacks, events and interrupts
|
||||
</title>
|
||||
|
||||
<para>
|
||||
Continuous acquisition is tyically an
|
||||
<emphasis>asynchronous</emphasis> activity: the function call that
|
||||
has set the acquisition in motion has returned before the acquisition
|
||||
has finished (or even started). So, not only the acquired data must be
|
||||
sent back to the user's buffer “in the background”, but
|
||||
various types of asynchronous <emphasis>event handling</emphasis> can
|
||||
be needed during the acquisition:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The <emphasis>hardware</emphasis> can generate some error or
|
||||
warning events.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Normal functional interrupts are generated by the hardware, e.g.,
|
||||
signalling the filling-up of the card's hardware buffer, or the end of
|
||||
an acquisition <link linkend="scan">scan</link>, etc.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The device driver writer can register a driver-supplied
|
||||
”callback” function, that is called at the end of each
|
||||
hardware interrupt routine.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Another driver-supplied callback function is executed when the user
|
||||
program launches an <link linkend="insn-inttrig">INSN_INTTRIG</link>
|
||||
instruction. This event handling is executed
|
||||
<emphasis>synchronously</emphasis> with the execution of the
|
||||
triggering instruction.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
The interrupt handlers are registered through the functions mentioned
|
||||
<link linkend="driversupportfunctions">before</link>
|
||||
The event handling is done in the existing &comedi; drivers in
|
||||
statements such as this one:
|
||||
<programlisting>
|
||||
<anchor id="async-events">
|
||||
s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR
|
||||
</programlisting>
|
||||
It fills in the bits corresponding to particular events in the
|
||||
<link linkend="comediasync">comedi_async</link> data structure.
|
||||
The possible event bits are:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<anchor id="comedi-cb-eoa">
|
||||
<parameter>COMEDI_CB_EOA</parameter>: execute the callback at the
|
||||
“End Of-Acquisition”.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<anchor id="comedi-cb-eos">
|
||||
<parameter>COMEDI_CB_EOS</parameter>: execute the callback at the
|
||||
“End-Of-Scan”.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<anchor id="comedi-cb-overflow">
|
||||
<parameter>COMEDI_CB_OVERFLOW</parameter>: execute the callback when a
|
||||
buffer overflow has occurred.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<anchor id="comedi-cb-error">
|
||||
<parameter>COMEDI_CB_ERROR</parameter>: execute the callback at the
|
||||
occurrence of an (undetermined) error.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<section id="drivercaveats">
|
||||
<title>
|
||||
Device driver caveats
|
||||
</title>
|
||||
|
||||
<para>
|
||||
A few things to strive for when writing a new driver:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Some DAQ cards consist of different “layers” of hardware,
|
||||
which can each be given their own device driver. Examples are:
|
||||
some of the National Instruments cards, that all share the same
|
||||
<emphasis>Mite</emphasis> PCI driver chip; the ubiquitous parallel
|
||||
port, that can be used for simple digital IO acquisitions. If your
|
||||
new card has such a multi-layer design too, please take the effort to
|
||||
provide drivers for each layer separately.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Your hardware driver should be functional appropriate to the resources
|
||||
allocated. I.e., if the driver is fully functional when configured
|
||||
with an IRQ and DMA, it should still function moderately well with
|
||||
just an IRQ, or still do minor tasks without IRQ or DMA. Does your
|
||||
driver really require an IRQ to do digital I/O? Maybe someone will
|
||||
want to use your driver <emphasis>just</emphasis> to do digital I/O
|
||||
and has no interrupts available.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Drivers are to have absolutely <emphasis role="strong">no</emphasis>
|
||||
global variables, mainly because the existence of global variables
|
||||
immediately negates any possibility of using the driver for two
|
||||
devices. The pointer <function>dev->private</function> should be used
|
||||
to point to a structure containing any additional variables needed by
|
||||
a driver/device combination.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Drivers should report errors and warnings via the
|
||||
<function>comedi_error()</function> function.
|
||||
(This is <emphasis>not</emphasis> the same function as the user-space
|
||||
<link linkend="func-ref-comedi-perror">comedi_perror()</link> function.)
|
||||
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="integratingdriver">
|
||||
<title>
|
||||
Integrating the driver in the &comedi; library
|
||||
</title>
|
||||
|
||||
<para>
|
||||
For integrating new drivers in the &comedi;'s source tree the following
|
||||
things have to be done:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Choose a senseful name for the source code file. Let's assume here
|
||||
that you call it “mydriver.c”
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Put your new driver into “comedi/drivers/mydriver.c”.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Edit “comedi/Config.in” and add a new
|
||||
“dep_tristate” line (look at the other examples). Invent a
|
||||
senseful name for the driver's variable. For example:
|
||||
<programlisting>
|
||||
dep_tristate 'MYDRIVER' CONFIG_COMEDI_MYDRIVER $CONFIG_COMEDI
|
||||
</programlisting>
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Add a line to “comedi/drivers/Makefile.in”, using your
|
||||
freshly defined variable, i.e., CONFIG_COMEDI_MYDRIVER.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Now <command>make distclean</command>, reconfigure &comedi; with a new
|
||||
<command>make</command>, rebuild and be happy.
|
||||
</para>
|
||||
<para>
|
||||
If you want to have your driver included in the &comedi; distribution
|
||||
(you <emphasis>definitely</emphasis> want to :-) ) send it to David
|
||||
Schleef <address><email>ds@schleef.org</email></address> for
|
||||
review and integration.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
171
doc/funcref
171
doc/funcref
|
@ -7,7 +7,7 @@ Returns:
|
|||
If sucessful, comedi_close returns 0. On failure, -1 is returned.
|
||||
|
||||
Function: comedi_open -- open a Comedi device
|
||||
Retval: comedi_t *
|
||||
Retval: comedi_t
|
||||
Param: const char * filename
|
||||
Description:
|
||||
Open a Comedi device specified by the file filename.
|
||||
|
@ -464,77 +464,22 @@ Description:
|
|||
For the A/D conversion (if appropriate),
|
||||
the device is configured to use range specification
|
||||
range and (if appropriate) analog reference type
|
||||
aref. Analog reference types that are not supported
|
||||
aref. Analog reference types that are not supported
|
||||
by the device are silently ignored.
|
||||
|
||||
The function comedi_data_read() reads one data value from
|
||||
the specified channel and places the data value in the
|
||||
location pointed to by data.
|
||||
|
||||
WARNING: comedi_data_read() does not do any pausing to
|
||||
allow multiplexed analog inputs to settle before
|
||||
performing an analog to digital conversion. If you are
|
||||
switching between different channels and need to allow
|
||||
your analog input to settle for an accurate reading,
|
||||
use comedi_data_read_delayed(), or set the
|
||||
input channel at an earlier time with
|
||||
comedi_data_read_hint().
|
||||
|
||||
On sucess, comedi_data_read() returns 1 (the number of samples
|
||||
read). If there is an error, -1 is returned.
|
||||
|
||||
On sucess, comedi_data_read() returns 0. If there is an
|
||||
error, -1 is returned.
|
||||
|
||||
Data values returned by this function are unsigned integers
|
||||
less than or equal to the maximum sample value of the channel,
|
||||
which can be determined using the function comedi_get_maxdata().
|
||||
Conversion of data values to physical units can be performed
|
||||
by the function comedi_to_phys().
|
||||
|
||||
Function: comedi_data_read_delayed -- read single sample from channel after delaying for specified settling time
|
||||
Retval: int
|
||||
Param: comedi_t * device
|
||||
Param: unsigned int subdevice
|
||||
Param: unsigned int channel
|
||||
Param: unsigned int range
|
||||
Param: unsigned int aref
|
||||
Param: lsampl_t * data
|
||||
Param: unsigned int nanosec
|
||||
Description:
|
||||
Similar to comedi_data_read() except it will wait for the
|
||||
specified number of nanoseconds between setting the input
|
||||
channel and taking a sample. For analog inputs, most
|
||||
boards have a single
|
||||
analog to digital converter which is multiplexed to be
|
||||
able to read multiple channels. If the input is not allowed
|
||||
to settle after the multiplexer switches channels, the
|
||||
reading will be inaccurate. This function is useful
|
||||
for allowing a multiplexed analog input to settle
|
||||
when switching channels.
|
||||
|
||||
Although the settling time is specified in nanoseconds, the
|
||||
actual settling time will be rounded up to the nearest
|
||||
microsecond.
|
||||
|
||||
Function: comedi_data_read_hint -- tell driver which channel/range/aref you are going to read from next
|
||||
Retval: int
|
||||
Param: comedi_t * device
|
||||
Param: unsigned int subdevice
|
||||
Param: unsigned int channel
|
||||
Param: unsigned int range
|
||||
Param: unsigned int aref
|
||||
Description:
|
||||
Used to prepare an analog input for a subsequent call to
|
||||
comedi_data_read(). It is not necessary to use this
|
||||
function, but it can be useful for eliminating inaccuaracies
|
||||
caused by insufficient settling times when switching the
|
||||
channel
|
||||
or gain on an analog input. This function sets an analog input
|
||||
to the channel, range, and aref specified but does not
|
||||
perform an actual analog to digital conversion.
|
||||
|
||||
Alternatively, one can simply use comedi_data_read_delayed(),
|
||||
which sets up the
|
||||
input, pauses to allow settling, then performs a conversion.
|
||||
|
||||
Function: comedi_data_write -- write single sample to channel
|
||||
Retval: int
|
||||
Param: comedi_t * device
|
||||
|
@ -554,8 +499,8 @@ Description:
|
|||
The function comedi_data_write() writes the data value specified
|
||||
by the parameter data to the specified channel.
|
||||
|
||||
On sucess, comedi_data_write() returns 1 (the number of samples
|
||||
written). If there is an error, -1 is returned.
|
||||
On sucess, comedi_data_write() returns 0. If there is an error, -1 is
|
||||
returned.
|
||||
|
||||
Function: comedi_dio_config -- change input/output properties of channel
|
||||
Retval: int
|
||||
|
@ -574,7 +519,7 @@ Description:
|
|||
case, a single call to comedi_dio_config() for any channel in the
|
||||
group will affect the entire group.
|
||||
|
||||
If sucessful, 1 is returned, otherwise -1.
|
||||
If sucessful, 0 is returned, otherwise -1.
|
||||
|
||||
Function: comedi_dio_read -- read single bit from digital channel
|
||||
Retval: int
|
||||
|
@ -779,10 +724,10 @@ Retval: int
|
|||
Param: comedi_t * device
|
||||
Param: unsigned int subdevice
|
||||
Description:
|
||||
The function comedi_get_buffer_offset() is used on a subdevice
|
||||
The function comedi_mark_buffer_read() is used on a subdevice
|
||||
that has a Comedi command in progress. This function returns
|
||||
the offset in bytes of the read pointer in the streaming buffer.
|
||||
This offset is only useful for memory mapped buffers.
|
||||
the offset of the read pointer in the streaming buffer. This
|
||||
offset is only useful for memory mapped buffers.
|
||||
If there is an error, -1 is returned.
|
||||
|
||||
Function: comedi_get_timer -- timer information (deprecated)
|
||||
|
@ -792,8 +737,8 @@ Param: unsigned int subdevice
|
|||
Param: double frequency
|
||||
Param: unsigned int * trigvar
|
||||
Param: double * actual_frequency
|
||||
Status: deprecated
|
||||
Description:
|
||||
Status: deprecated
|
||||
The function comedi_get_timer converts the frequency frequency
|
||||
to a number suitable to send to the driver in a comedi_trig
|
||||
structure. This function remains for compatibility with very
|
||||
|
@ -812,15 +757,15 @@ Param: unsigned int aref
|
|||
Param: double frequency
|
||||
Param: unsigned int num_samples
|
||||
Param: double * data
|
||||
Status: deprecated
|
||||
Description:
|
||||
Status: deprecated
|
||||
Not documented.
|
||||
|
||||
Function: comedi_set_global_oor_behavior -- out-of-range behavior
|
||||
Retval: int
|
||||
Param: enum comedi_oor_behavior behavior
|
||||
Status: alpha
|
||||
Description:
|
||||
Status: alpha
|
||||
This function changes the Comedilib out-of-range behavior.
|
||||
This currently affects the behavior of comedi_to_phys() when
|
||||
converting endpoint sample values, that is, sample values
|
||||
|
@ -831,91 +776,3 @@ Description:
|
|||
|
||||
The previous out-of-range behavior is returned.
|
||||
|
||||
Function: comedi_apply_calibration -- set calibration from file
|
||||
Retval: int
|
||||
Param: comedi_t *device
|
||||
Param: unsigned int subdevice
|
||||
Param: unsigned int channel
|
||||
Param: unsigned int range
|
||||
Param: unsigned int aref
|
||||
Param: const char *file_path
|
||||
Status: alpha
|
||||
Description:
|
||||
This function sets the calibration of the specified subdevice
|
||||
so that it is in proper calibration when using the specified
|
||||
channel, range and aref. Depending on the hardware, the
|
||||
calibration settings used may or may not depend on the channel,
|
||||
range, or aref. The file_path parameter can be used
|
||||
to specify the file which contains the calibration information.
|
||||
If <parameter>file_path</parameter> is NULL, then comedilib
|
||||
will use a default
|
||||
file location. The calibration information used by this function
|
||||
is generated by the comedi_calibrate program (see its man page).
|
||||
|
||||
The functions comedi_parse_calibration_file(),
|
||||
comedi_apply_parsed_calibration(), and comedi_cleanup_calibration()
|
||||
provide the same functionality at a slightly lower level.
|
||||
Returns:
|
||||
Zero on success, a negative number on failure.
|
||||
|
||||
Function: comedi_apply_parsed_calibration -- set calibration from memory
|
||||
Retval: int
|
||||
Param: comedi_t * device
|
||||
Param: unsigned int subdevice
|
||||
Param: unsigned int channel
|
||||
Param: unsigned int range
|
||||
Param: unsigned int aref
|
||||
Param: const comedi_calibration_t *calibration
|
||||
Status: alpha
|
||||
Description:
|
||||
This function is similar to comedi_apply_calibration()
|
||||
except the calibration information is read from memory
|
||||
instead of a file. This function can be more
|
||||
efficient than comedi_apply_calibration() since the
|
||||
calibration file does not need to be reparsed with
|
||||
every call. The <parameter>calibration</parameter> is
|
||||
obtained by a call to comedi_parse_calibration_file().
|
||||
|
||||
Returns:
|
||||
Zero on success, a negative number on failure.
|
||||
|
||||
Function: comedi_cleanup_calibration_file -- free calibration resources
|
||||
Retval: void
|
||||
Param: comedi_calibration_t *calibration
|
||||
Status: alpha
|
||||
Description:
|
||||
This function frees the resources associated with a
|
||||
<parameter>calibration</parameter> obtained from
|
||||
comedi_parse_calibration_file(). <parameter>calibration</parameter>
|
||||
can not be used again after calling this function.
|
||||
|
||||
Function: comedi_get_default_calibration_path -- get default calibration file path
|
||||
Retval: char*
|
||||
Param: comedi_t *dev
|
||||
Status: alpha
|
||||
Description:
|
||||
This function returns a string containing a default calibration file
|
||||
path appropriate for <parameter>dev</parameter>. Memory for the
|
||||
string is allocated by the function, and should be freed when
|
||||
the string is no longer needed.
|
||||
Returns:
|
||||
A string which contains a file path useable by
|
||||
comedi_parse_calibration_file(). On error, NULL is returned.
|
||||
|
||||
Function: comedi_parse_calibration_file -- set calibration
|
||||
Retval: comedi_calibration_t*
|
||||
Param: const char *file_path
|
||||
Status: alpha
|
||||
Description:
|
||||
This function parses a calibration file (produced by the
|
||||
comedi_calibrate program) and returns a pointer to a
|
||||
comedi_calibration_t which can be passed to the
|
||||
comedi_apply_parsed_calibration() function. When you are
|
||||
finished using the comedi_calibration_t, you should
|
||||
call comedi_cleanup_calibration() to free the resources
|
||||
associated with the comedi_calibration_t.
|
||||
|
||||
The comedi_get_default_calibration_path() function may
|
||||
be useful in conjunction with this function.
|
||||
Returns:
|
||||
A pointer to parsed calibration information on success, or NULL on failure.
|
||||
|
|
|
@ -4,69 +4,72 @@
|
|||
<title>
|
||||
Glossary
|
||||
</title>
|
||||
<glossentry>
|
||||
|
||||
<glossentry id="api">
|
||||
<glossterm>
|
||||
Application Program Interface
|
||||
</glossterm>
|
||||
<acronym>API</acronym>
|
||||
<glossdef>
|
||||
<para>
|
||||
The (documented) set of function calls supported by a particular
|
||||
application, by which programmers can access the functionality
|
||||
available in the application.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
|
||||
<glossentry id="buffer">
|
||||
<glossterm>
|
||||
buffer
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
Comedi uses permanently allocated kernel memory for streaming input
|
||||
&comedi; uses permanently allocated kernel memory for streaming input
|
||||
and output to store data that has been measured by a device, but has
|
||||
not been read by an application. These buffers can be resized by the
|
||||
Comedilib function comedi_buffer_XXX() or the coemdi_config
|
||||
Comedilib function <function>comedi_buffer_XXX()</function> or the
|
||||
<function>comedi_config</function>
|
||||
utility.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="bufferoverflow">
|
||||
<glossterm>
|
||||
buffer overflow
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
This is an error message that indicates that the driver ran out of
|
||||
space in a Comedi buffer to put samples. It means that the application
|
||||
space in a &comedi; buffer to put samples. It means that the application
|
||||
is not copying data out of the buffer quickly enough. Often, this
|
||||
problem can be fixed by making the Comedi buffer larger. See
|
||||
comedi_buffer_XXX for more information.
|
||||
problem can be fixed by making the &comedi; buffer larger. See
|
||||
<function>comedi_buffer_XXX</function> for more information.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="differentialIO">
|
||||
<glossterm>
|
||||
overrun
|
||||
Differential IO
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
This is an error message that indicates that there was device-level
|
||||
problem, typically with trigger pulses occurring faster than the
|
||||
board can handle.
|
||||
…
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="dma">
|
||||
<glossterm>
|
||||
command
|
||||
Direct Memory Access
|
||||
</glossterm>
|
||||
<acronym>DMA</acronym>
|
||||
<glossdef>
|
||||
<para>
|
||||
Comedi commands are the mechanism that applications configure
|
||||
subdevices for streaming input and output.
|
||||
(also: cmd, comedi_command)
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossterm>
|
||||
DMA
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
Direct memory access. DMA is a method of transferring data between
|
||||
DMA is a method of transferring data between
|
||||
a device and the main memory of a computer. DMA operates differently
|
||||
on ISA and PCI cards. ISA DMA is handled by a controller on the
|
||||
motherboard and is limited to transfers to/from the lowest 16 MB of
|
||||
|
@ -80,10 +83,11 @@ be the optimal transfer mechanism for a particular situation.
|
|||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="fifo">
|
||||
<glossterm>
|
||||
FIFO
|
||||
First In, First Out
|
||||
</glossterm>
|
||||
<acronym>FIFO</acronym>
|
||||
<glossdef>
|
||||
<para>
|
||||
Most devices have a limited amount of on-board space to store samples
|
||||
|
@ -96,59 +100,115 @@ interruptions in data.
|
|||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
|
||||
<glossentry id="comedicommand">
|
||||
<glossterm>
|
||||
&comedi; command
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
&comedi; commands are the mechanism that applications configure
|
||||
subdevices for streaming input and output.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry id="command">
|
||||
<glossterm>
|
||||
command
|
||||
</glossterm>
|
||||
<glosssee otherterm="comedicommand">
|
||||
</glossentry>
|
||||
|
||||
<glossentry id="configoption">
|
||||
<glossterm>
|
||||
configuration option
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry id="instruction">
|
||||
<glossterm>
|
||||
instruction
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
Comedi instructions are the mechanism used by applications to do
|
||||
&comedi; instructions are the mechanism used by applications to do
|
||||
immediate input from channels, output to channels, and configuration
|
||||
of subdevices and channels.
|
||||
(also: insn)
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="instructionlist">
|
||||
<glossterm>
|
||||
instruction list
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
Instruction lists allow the application to perform multiple Comedi
|
||||
Instruction lists allow the application to perform multiple &comedi;
|
||||
instructions in the same system call.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="option">
|
||||
<glossterm>
|
||||
option
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
</para>
|
||||
<glossseealso otherterm="optionlist">
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
|
||||
<glossentry id="optionlist">
|
||||
<glossterm>
|
||||
option list
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
Option lists are used with comedi_config to perform driver
|
||||
configuration.
|
||||
(also: configuration options, options)
|
||||
Option lists are used with <function>comedi_config</function> to
|
||||
perform driver configuration.
|
||||
</para>
|
||||
<glossseealso otherterm="configoption">
|
||||
<glossseealso otherterm="option">
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry id="overrun">
|
||||
<glossterm>
|
||||
overrun
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
This is an error message that indicates that there was device-level
|
||||
problem, typically with trigger pulses occurring faster than the
|
||||
board can handle.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
||||
<glossentry>
|
||||
<glossentry id="poll">
|
||||
<glossterm>
|
||||
poll
|
||||
</glossterm>
|
||||
<glossdef>
|
||||
<para>
|
||||
The term poll (and polling) is used for several different related
|
||||
concepts in Comedi. Comedi implements the poll() system call for
|
||||
Comedi devices, which is similar to select(), and returns information
|
||||
concepts in &comedi;. &comedi; implements the
|
||||
<function>poll()</function> system call for Comedi devices, which is
|
||||
similar to <function>select()</function>, and returns information
|
||||
about file descriptors that can be read or written. Comedilib also
|
||||
has a function called comedi_poll(), which causes the driver to
|
||||
copy all available data from the device to the Comedi buffer. In
|
||||
addition, some drivers may use a polling technique in place of
|
||||
interrupts.
|
||||
has a function called <function>comedi_poll()</function>, which causes
|
||||
the driver to copy all available data from the device to the &comedi;
|
||||
buffer. In addition, some drivers may use a polling technique in
|
||||
place of interrupts.
|
||||
</para>
|
||||
</glossdef>
|
||||
</glossentry>
|
||||
|
|
219
doc/install.sgml
219
doc/install.sgml
|
@ -1,38 +1,67 @@
|
|||
<!-- <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V3.1//EN"> -->
|
||||
|
||||
<section>
|
||||
<section id="install">
|
||||
<title>
|
||||
Installation and Configuration
|
||||
Configuration
|
||||
</title>
|
||||
|
||||
<para>
|
||||
I assume that your hardware device is in your computer, and that
|
||||
you know the relevant details about it, i.e., what kind of card
|
||||
it is, the I/O base, the IRQ, jumper settings related to input
|
||||
This section assumes that you have successfully compiled and installed
|
||||
the &comedi; software, that your hardware device is in your computer,
|
||||
and that you know the relevant details about it, i.e., what kind of
|
||||
card it is, the I/O base, the IRQ, jumper settings related to input
|
||||
ranges, etc.
|
||||
</para>
|
||||
|
||||
|
||||
<section id="cardconfiguration">
|
||||
<title>
|
||||
Configuration
|
||||
</title>
|
||||
<para>
|
||||
To tell the comedi kernel module that you have a particular device, and
|
||||
some information about it, you will be running the comedi_config
|
||||
command. Perhaps you should read the man page now.
|
||||
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 of course.)
|
||||
Here is an example of how to use the command (perhaps you should read
|
||||
its <command>man</command> page now):
|
||||
<screen>
|
||||
/usr/sbin/comedi_config /dev/comedi0 ni_atmio 0x260,3
|
||||
</screen>
|
||||
This command says that the “file”
|
||||
<filename>/dev/comedi0</filename> can be used to access the &comedi;
|
||||
device that uses the <parameter>ni_atmio</parameter> driver, and that
|
||||
you give it two run-time parameters (<literal>0x260</literal> and
|
||||
<literal>3</literal>). More parameters are possible, for example to
|
||||
discriminate between two or more identical cards in your system.
|
||||
</para>
|
||||
<para>
|
||||
If you want to have the board configured in this way every time you
|
||||
boot, put the line above into a start-up script file of your Linux
|
||||
system (for example, the
|
||||
<filename>/etc/rc.d/rc.local</filename> file), or in the system-wide
|
||||
&comedi; configuration file <filename>/etc/comedi.conf</filename>.
|
||||
You can, of course, also run this command at a command prompt.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In this tutorial, I will go through the process of configuring comedi
|
||||
for two devices, a National Instruments AT-MIO-16E-10
|
||||
and a Data Translation DT2821-F-8DI.
|
||||
This tutorial goes through the process of configuring &comedi;
|
||||
for two devices, a
|
||||
<literal>National Instruments AT-MIO-16E-10</literal> (which has the
|
||||
driver mentioned above), and a
|
||||
<literal>Data Translation DT2821-F-8DI</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The NI board is plug-and-play, and the man page tells me that I need
|
||||
to configure the PnP part of the board with isapnptools. The isapnptools
|
||||
package is a little cryptic, but the concepts are simple. Once I
|
||||
learned how to use it, I settled on a /etc/isapnp.conf file that
|
||||
contained the lines:
|
||||
The NI board is plug-and-play, and the <command>man</command> page
|
||||
tells you that you need to configure the PnP part of the board with
|
||||
<command>isapnptools</command>. The <command>isapnptools</command>
|
||||
package is a little cryptic, but the concepts are simple. Once you've
|
||||
learned how to use it, you can settle on a
|
||||
<filename>/etc/isapnp.conf</filename> file such as this:
|
||||
</para>
|
||||
|
||||
|
||||
<screen>
|
||||
# ANSI string -->National Instruments, AT-MIO-16E-10<--
|
||||
(CONFIGURE NIC2400/10725401 (LD 0
|
||||
|
@ -44,46 +73,41 @@ contained the lines:
|
|||
))
|
||||
</screen>
|
||||
|
||||
|
||||
<para>
|
||||
It also contains a few lines about overall configuration and about my
|
||||
sound card. I found out after a bit of trial-and-error that the NI
|
||||
board does not always work with interrupts other than IRQ 3. YMMV.
|
||||
(This file also contains a few lines about overall configuration and
|
||||
about the sound card that happens to be in the same computer.)
|
||||
Currently, the driver doesn't use DMA, but it may in the future, so
|
||||
I commented out the DMA lines. It is a curious fact that the device
|
||||
ignores the IRQ and DMA information given here, however, I keep the
|
||||
information here to remind myself that the numbers aren't arbitrary.
|
||||
the DMA lines are commented out. It has been reported that the
|
||||
National Instruments board does not always work with interrupts other
|
||||
than IRQ 3, and that the device ignores the IRQ and DMA information
|
||||
given here. However, keep the information here to remind yourself that
|
||||
the numbers aren't arbitrary.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When I run comedi_config (as root, of course), I provide the same
|
||||
information. Since I want to have the board configured every time
|
||||
I boot, I put the lines
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
export PATH=/sbin:/usr/sbin:/usr/local/sbin:$PATH
|
||||
comedi_config /dev/comedi0 ni_atmio 0x260,3
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
into <filename>/etc/rc.d/rc.local</filename>. You can, of course, run this command at
|
||||
a command prompt. The man page tells me that the option list
|
||||
is supposed to be "(I/O base),(IRQ)", so I used the same numbers
|
||||
as I put in /etc/isapnp.conf, i.e., 0x260,3.
|
||||
The <command>man</command> page
|
||||
explains that the option list is supposed to be
|
||||
“<literal>(I/O base),(IRQ)</literal>”, so use the same
|
||||
numbers as in <filename>/etc/isapnp.conf</filename>, i.e.,
|
||||
<literal>0x260,3</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For the Data Translation board, I need to have a list of the
|
||||
jumper settings. Fortunately, I wrote them all down in the
|
||||
manual -- I hope they are still correct. However, I had to
|
||||
open the case to figure out which board in the series I had.
|
||||
It is a DT2821-f-8di. The man page of comedi_config tells
|
||||
me that I need to know the I/O base, IRQ, DMA 1, DMA 2. However,
|
||||
since I wrote the driver, I know that it also recognizes the
|
||||
For the <literal>Data Translation</literal> board, you need to have a
|
||||
list of the jumper settings; these are given in the &comedi; manual
|
||||
section about this card. (Check first to see whether they are still
|
||||
correct!)
|
||||
The card discussed her is a <literal>DT2821-f-8di</literal>. The
|
||||
<command>man</command> page of <command>comedi_config</command> tells
|
||||
you that you need to know the I/O base, IRQ, DMA 1, DMA 2. However,
|
||||
the &comedi; driver also recognizes the
|
||||
differential/single-ended and unipolar/bipolar jumpers. As always,
|
||||
the source is the final authority, and looking in module/dt282x.c
|
||||
tells me that the options list is interpreted as:
|
||||
the source is the final authority, and looking in
|
||||
<filename>module/dt282x.c</filename>
|
||||
tells us that the options list is interpreted as:
|
||||
</para>
|
||||
<para>
|
||||
(... TO BE FILLED IN ...)
|
||||
</para>
|
||||
|
||||
<!-- XXX
|
||||
|
@ -97,44 +121,40 @@ tells me that the options list is interpreted as:
|
|||
<item>dma1
|
||||
<item>dma2
|
||||
</itemize>
|
||||
(ai=analog input, ao=analog output.)
|
||||
-->
|
||||
|
||||
<para>
|
||||
(ai=analog input, ao=analog output.) From this, I decide that
|
||||
the appropriate options list is
|
||||
</para>
|
||||
|
||||
So, the appropriate options list is:
|
||||
<screen>
|
||||
0x200,4,,1,1,1
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
I left the differential/single-ended number blank, since the
|
||||
and the full configuration command is:
|
||||
<screen>
|
||||
/usr/sbin/comedi_config /dev/comedi1 dt2821-f-8di 0x200,4,,1,1,1
|
||||
</screen>
|
||||
The differential/single-ended number is left blank, since the
|
||||
driver already knowns (from the board name), that it is
|
||||
differential. I also left the DMA numbers blank, since I
|
||||
don't want the driver to use DMA. (Don't want it to interfere
|
||||
with my sound card -- life is full of difficult choices.)
|
||||
differential. Also the DMA numbers are left blank, since we
|
||||
don't want the driver to use DMA. (Which could interfere
|
||||
with the sound card...)
|
||||
Keep in mind that things commented in the source, but not in
|
||||
the documentation are about as likely to change as the weather,
|
||||
so I put good comments next to the following lines when I put
|
||||
it in rc.local.
|
||||
so put good comments next to the following line when you put
|
||||
it in a start-up file.
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
export PATH=/sbin:/usr/sbin:/usr/local/sbin:$PATH
|
||||
comedi_config /dev/comedi1 dt2821-f-8di 0x200,4,,1,1,1
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
So now I think that I have my boards configured correctly.
|
||||
So now you have your boards configured correctly.
|
||||
Since data acquisition boards are not typically well-engineered,
|
||||
Comedi sometimes can't figure out if the board is actually there.
|
||||
&comedi; sometimes can't figure out if the board is actually there.
|
||||
If it can't, it assumes you are right. Both of these boards
|
||||
are well-made, so comedi will give me an error message if it
|
||||
can't find them. The comedi kernel module, since it is a part
|
||||
are well-made, so &comedi; will give an error message if it
|
||||
can't find them. The &comedi; kernel module, since it is a part
|
||||
of the kernel, prints messages to the kernel logs, which you
|
||||
can access through the command 'dmesg' or /var/log/messages.
|
||||
Here is a configuration failure (from dmesg):
|
||||
can access through the command <command>dmesg</command> or the file
|
||||
<filename>/var/log/messages</filename>.
|
||||
Here is a configuration failure (from <command>dmesg</command>):
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
|
@ -142,7 +162,7 @@ comedi0: ni_atmio: 0x0200 can't find board
|
|||
</screen>
|
||||
|
||||
<para>
|
||||
When it does work, I get:
|
||||
When it does work, you get:
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
|
@ -150,19 +170,20 @@ comedi0: ni_atmio: 0x0260 at-mio-16e-10 ( irq = 3 )
|
|||
</screen>
|
||||
|
||||
<para>
|
||||
Note that it also correctly identified my board.
|
||||
Note that it also correctly identified the board.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="gettinginformation">
|
||||
<title>
|
||||
Getting information from comedi
|
||||
Getting information about a card
|
||||
</title>
|
||||
|
||||
<para>
|
||||
So now that we have comedi talking to the hardware, we want to
|
||||
talk to comedi. Here's some pretty low-level information --
|
||||
it's sometimes useful for debugging:
|
||||
So now that you have &comedi; talking to the hardware, try to
|
||||
talk to &comedi;. Here's some pretty low-level information, which can
|
||||
sometimes be useful for debugging:
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
|
@ -170,7 +191,8 @@ cat /proc/comedi
|
|||
</screen>
|
||||
|
||||
<para>
|
||||
Right now, on my computer, this command gives:
|
||||
On the particular system this demonstration was carried out, this
|
||||
command gives:
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
|
@ -181,19 +203,19 @@ format string
|
|||
</screen>
|
||||
|
||||
<para>
|
||||
This is a feature that is not well-developed yet. Basically, it
|
||||
currently tells you driver name, device name, and number of
|
||||
This documentation feature is not well-developed yet. Basically, it
|
||||
currently returns the driver name, the device name, and the number of
|
||||
subdevices.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In the <filename>demo/</filename> directory, there is a command called
|
||||
<command>info</command>, which provides information about each
|
||||
subdevice on the
|
||||
board. The output of it is rather long, since I have 7
|
||||
subdevices (4 or fewer is common for other boards.)
|
||||
Here's part of the output of the NI board (which
|
||||
is on <filename>/dev/comedi0</filename>.) ('demo/info /dev/comedi0')
|
||||
In the <filename role=directory>demo/</filename> directory, there is a
|
||||
command called <command>info</command>, which provides information
|
||||
about each subdevice on the board. Its output can be rather long,
|
||||
if the board has several subdevices.
|
||||
Here's part of the output of the <literal>National Instruments</literal>
|
||||
board (which is on <filename>/dev/comedi0</filename>), as a result of
|
||||
the command <command>demo/info /dev/comedi0</command>:
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
|
@ -203,33 +225,30 @@ overall info:
|
|||
board name: at-mio-16e-10
|
||||
number of subdevices: 7
|
||||
subdevice 0:
|
||||
type: 1 (unknown)
|
||||
type: 1 (analog input)
|
||||
number of channels: 16
|
||||
max data value: 4095
|
||||
...
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
The overall info gives information about the device -- basically
|
||||
the same information as /proc/comedi.
|
||||
The overall info gives information about the device; basically
|
||||
the same information as <filename>/proc/comedi</filename>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This board has 7 subdevices. Devices are separated into
|
||||
subdevices that each have a distinct purpose -- e.g., analog
|
||||
This board has seven subdevices. Devices are separated into
|
||||
subdevices that each have a distinct purpose; e.g., analog
|
||||
input, analog output, digital input/output. This board also
|
||||
has an EEPROM and calibration DACs that are also subdevices.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Subdevice 0 is the analog input subdevice. You would have
|
||||
known this from the 'type: 1 (unknown)' line, if I've updated
|
||||
demo/info recently, because it would say 'type: 1 (analog input)'
|
||||
instead. The other lines should be self-explanitory. Comedi
|
||||
has more information about the device, but demo/info doesn't
|
||||
currently display this.
|
||||
&comedi; has more information about the device than what is displayed
|
||||
above, but <command>demo/info</command> doesn't currently display
|
||||
this.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
|
795
doc/intro.sgml
795
doc/intro.sgml
|
@ -1,157 +1,139 @@
|
|||
<!-- <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V3.1//EN"> -->
|
||||
|
||||
<section>
|
||||
<section id="introduction">
|
||||
|
||||
<title>
|
||||
Introduction
|
||||
Overview
|
||||
</title>
|
||||
|
||||
<para>
|
||||
This section gives high-level explanation about which functionality
|
||||
you can expect from the software. Details are given in the following
|
||||
sections of this document.
|
||||
&comedi; is a <emphasis>free software</emphasis> project that develops
|
||||
drivers, tools, and libraries for various forms of
|
||||
<emphasis>data acquisition</emphasis>: reading and writing of analog
|
||||
signals; reading and writing of digital inputs/outputs; pulse and
|
||||
frequency counting; pulse generation; reading encoders; etc. The
|
||||
project's source code is distributed in two packages,
|
||||
<literal>
|
||||
<ulink url="http://www.comedi.org/download.php">comedi</ulink>
|
||||
</literal> and
|
||||
<literal>
|
||||
<ulink url="http://www.comedi.org/download.php">comedilib</ulink>
|
||||
</literal>, and provides several Linux
|
||||
<emphasis>kernel modules</emphasis> and a
|
||||
<emphasis>user space</emphasis> library:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Comedi</emphasis> is a collection of drivers for a variety
|
||||
of common data acquisition plug-in boards (which are called
|
||||
“devices” in &comedi; terminology). The drivers are
|
||||
implemented as the combination of (i) one single core Linux kernel module
|
||||
(called “<literal>comedi</literal>”) providing common
|
||||
functionality, and (ii) individual low-level driver modules for
|
||||
each device.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Comedilib</emphasis> is a separately distributed package
|
||||
containing a user-space library that provides a
|
||||
developer-friendly interface to the &comedi; devices. Included in the
|
||||
<emphasis>Comedilib</emphasis> package are documentation,
|
||||
configuration and calibration utilities, and demonstration programs.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Kcomedilib</emphasis> is a Linux kernel module
|
||||
(distributed with the <literal>comedi</literal> package) that provides
|
||||
the same interface as <emphasis>comedilib</emphasis> in kernel space,
|
||||
and suitable for <emphasis>real-time</emphasis> tasks. It is
|
||||
effectively a “kernel library” for using &comedi; from
|
||||
real-time tasks.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
&comedi; works with standard Linux kernels, but also with its
|
||||
real-time extensions <ulink url="http://www.rtai.org">RTAI</ulink> and
|
||||
<ulink url="http://www.fsmlabs.com/products/openrtlinux/">RTLinux/Free</ulink>.
|
||||
</para>
|
||||
<para>
|
||||
This section gives a high-level introduction to which functionality
|
||||
you can expect from the software. More technical details and
|
||||
programming examples are given in the following sections of this
|
||||
document.
|
||||
</para>
|
||||
|
||||
<section>
|
||||
<section id="whatisdevicedriver">
|
||||
<title>
|
||||
What is a "device driver"?
|
||||
What is a “device driver”?
|
||||
</title>
|
||||
<para>
|
||||
A device driver is a piece of software that interfaces a particular
|
||||
piece of hardware: a printer, a sound card, a motor drive, etc. It
|
||||
translates the primitive, device-dependent commands with which the
|
||||
hardware manufacturer want you to configure, read and write the
|
||||
hardware manufacturer allows you to configure, read and write the
|
||||
electronics of the hardware interface into more abstract and generic
|
||||
function calls and data structures for the application programmer.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
David Schleef started the Comedi project to interface
|
||||
David Schleef started the &comedi; project to put a generic interface
|
||||
on top of
|
||||
lots of different cards for measurement and control purposes. This
|
||||
type of cards are often called Data AcQuisition cards, or DAQ cards.
|
||||
type of cards are often called <emphasis>data acquisition</emphasis>
|
||||
(or <emphasis role="strong">DAQ</emphasis>) cards.
|
||||
</para>
|
||||
<para>
|
||||
<emphasis>Analog input and output</emphasis> cards were the first goal
|
||||
of the project, but now &comedi; also provides a device
|
||||
independent interface to digital <emphasis>input and output</emphasis>
|
||||
cards, and <emphasis>counter and timer</emphasis> cards (including
|
||||
encoders, pulse generators, frequency and pulse timers, etc.).
|
||||
</para>
|
||||
<para>
|
||||
Schleef designed a structure which is a balance between
|
||||
modularity (i.e., it's fairly easy to integrate a new card because
|
||||
most of the infrastructure part of the driver can be reused) and
|
||||
complexity (i.e., the structure doesn't present so much overhead that
|
||||
new contributors are scared away from writing their new drivers within
|
||||
the Comedi framework). The Comedi project consists of two
|
||||
complementary packages: "comedi" (which implements the kernel space
|
||||
functionality) and "comedilib" (which implements the user space access
|
||||
to the device driver functionality). Comedi works with a standard
|
||||
Linux kernel, but also with its real-time extensions RTAI and
|
||||
Real-Time Linux.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>
|
||||
A general DAQ device driver package.
|
||||
</title>
|
||||
<para>
|
||||
From the point of view of system developers, it is worthwhile to
|
||||
standardize the structure and API (Application Programming Interface)
|
||||
for device drivers as much as possible:
|
||||
</para>
|
||||
|
||||
<para>
|
||||
API: devices that offer similar functionalities, should have the same
|
||||
software interface, and their differences should be coped with by
|
||||
parameterizing the interfaces, not by changing the interface for
|
||||
each new device in the family.
|
||||
|
||||
<para>
|
||||
Structure: many electronic interfaces have more than one layer of
|
||||
functionality between the hardware and the operating system, and
|
||||
the device driver code should reflect this fact. For example, many
|
||||
different interface cards use the same PCI driver chips, or use the
|
||||
parallel port to connect to the hardware device. Hence, providing
|
||||
"low-level" device drivers for these PCI chips and parallel ports
|
||||
allows for an increased modularity and re-useability of the software.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In the case of Linux as the host operating system, device driver
|
||||
writers must keep the following Linux-specific issues in mind:
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Kernel space vs. User space.
|
||||
The Linux operating system has two levels: only privileged processes
|
||||
can run in the kernel, where they have access to all hardware and to
|
||||
all kernel data structures and system calls; normal application
|
||||
programs can run their processes only in user space, where these
|
||||
processes are shielded from each other, and from direct access to
|
||||
hardware and to critical data of the operating system. Device drivers
|
||||
typically must access specific addresses on the bus, and hence use
|
||||
privileged system calls. Therefore, a device driver has a component
|
||||
in kernel space. One can write a user space driver for, for example,
|
||||
a device on the parallel port, but in this case, the basic parallel
|
||||
port device driver runs already in the kernel by default; the
|
||||
interaction with the hardware then takes place via the method
|
||||
explained below.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Device files or device file system.
|
||||
The users that want to write an application for a particular device,
|
||||
must link their application to the device's device driver. This device
|
||||
driver, however, runs in kernel space, and the user application in
|
||||
user space. So, the operating system provides an interface between
|
||||
both. In Linux or Unix, these interfaces are in the form of "files"
|
||||
in the /dev directory (2.2.x kernels or earlier) or /devfs directory
|
||||
(2.4.x kernels and later). Each device has a representative, and can
|
||||
be accessed by the classical Unix file I/O calls: open, close, read,
|
||||
write, and ioctl.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
/proc interface.
|
||||
Linux offers a file-like interface to attached devices (and other
|
||||
OS-related information) via the /proc directories. This interface
|
||||
allows to inspect the current status of each device.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Direct Memory Access (DMA) vs. Programmed Input/Output (PIO).
|
||||
Almost all devices can be interfaced in PIO mode: the processor is
|
||||
responsible for accessing bus addresses allocated to the device, and
|
||||
to read or write data. Some devices also allow DMA: the device and the
|
||||
memory "talk" to each other directly, without needing the processor.
|
||||
DMA is a feature of the bus, not of the operating system (which has
|
||||
to support its processes to use the feature, of course).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the device is to be used in a Real-Time Linux or RTAI application,
|
||||
there are a few extra requirements, because not all system calls are
|
||||
available in the RTOS kernel of Real-Time Linux or RTAI.
|
||||
<emphasis>modularity</emphasis> and <emphasis>complexity</emphasis>:
|
||||
it's fairly easy to integrate a new card because most of the
|
||||
infrastructure part of other, similar drivers can be reused, and
|
||||
learning the generic and hence somewhat “heavier” &comedi;
|
||||
API doesn't scare away new contributors from integrating their drivers
|
||||
into the &comedi; framework.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<section id="policymechanism">
|
||||
<title>
|
||||
Policy vs. mechanism.
|
||||
Policy vs. mechanism
|
||||
</title>
|
||||
<para>
|
||||
Device drivers are often written by application programmers, that have
|
||||
a particular application in mind. For example, one writes a driver for
|
||||
the parallel port, because one wants to use it to generate pulses that
|
||||
drive a stepper motor. This approach often leads to device drivers
|
||||
that depend too much on the application, and are not general enough to
|
||||
be re-used for other applications. One golden rule for the device
|
||||
driver writer is to separate mechanism and policy:
|
||||
</para>
|
||||
only their particular application in mind; especially in real-time
|
||||
applications. For example, one writes a
|
||||
driver for the parallel port, because one wants to use it to generate
|
||||
pulses that drive a stepper motor. This approach often leads to device
|
||||
drivers that depend too much on that particular application, and are
|
||||
not general enough to be re-used for other applications. One golden
|
||||
rule for the device driver writer is to separate mechanism and policy:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Mechanism.
|
||||
<emphasis role="strong">Mechanism.</emphasis>
|
||||
The mechanism part of the device interface is a faithful
|
||||
representation of the bare functionality of the device, independent of
|
||||
what part of the functionality an application will use.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Policy.
|
||||
<emphasis role="strong">Policy.</emphasis>
|
||||
Once a device driver offers a software interface to the mechanism of
|
||||
the device, an application writer can use this mechanism interface to
|
||||
use the device in one particular fashion. That is, some of the data
|
||||
|
@ -166,147 +148,558 @@ driver writer is to separate mechanism and policy:
|
|||
device can be used by another application program, for example to
|
||||
generate a sine wave that drives a vibration shaker.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
So, &comedi; focuses only on the <emphasis>mechanism</emphasis> part
|
||||
of DAQ interfacing. The project does not provide the policy parts,
|
||||
such as Graphical User Interfaces to program and display acquisitions,
|
||||
signal processing libraries, or control algorithms.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
<section id="generaldaqpackage">
|
||||
<title>
|
||||
Overview of Comedi.
|
||||
A general DAQ device driver package
|
||||
</title>
|
||||
<para>
|
||||
The supported cards in Comedi have one or more of the following
|
||||
features: analog input channels, analog output channels, digital input
|
||||
channels, and digital output channels. The digital channels are
|
||||
conceptually quite simple, and don't need much configuration: the
|
||||
number of channels, their addresses on the bus, and their direction
|
||||
(input/output).
|
||||
</para>
|
||||
From the point of view of application developers, there are many
|
||||
reasons to welcome the standardization of the API and the
|
||||
architectural structure of DAQ software:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The analog channels are a bit more complicated. Typically, an analog
|
||||
channel can be programmed to generate or read a voltage between a
|
||||
lower and an upper threshold (e.g., -10V and +10V); the card's
|
||||
electronics can be programmed to automatically sample a set of
|
||||
channels, in a prescribed order; top buffer sequences of data on the
|
||||
board; or to use DMA to dump the data in an available part of memory,
|
||||
without intervention from the processor.
|
||||
<emphasis role="strong">API</emphasis>: devices that offer similar functionalities, should have the same
|
||||
software interface, and their differences should be coped with by
|
||||
parameterizing the interfaces, not by changing the interface for
|
||||
each new device in the family. However, the DAQ manufacturers
|
||||
have never been able (or willing) to come up with such a
|
||||
standardization effort themselves.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Many interface cards have extra functionality, besides the analog and
|
||||
digital channels. For example, an EEPROM for configuration and board
|
||||
parameters, calibration inputs, counters and timers, encoders (=
|
||||
quadrature counter on two channels), etc. Therefore, Comedi offers
|
||||
more than just analog and digital data acquisition.
|
||||
<emphasis role="strong">Architectural structure</emphasis>: many electronic interfaces have more than one layer of
|
||||
functionality between the hardware and the operating system, and
|
||||
the device driver code should reflect this fact. For example, many
|
||||
different interface cards use the same PCI driver chips, or use the
|
||||
parallel port as an intermediate means to connect to the hardware
|
||||
device. Hence, “lower-level” device drivers for
|
||||
these PCI chips and parallel ports allow for an increased modularity
|
||||
and re-useability of the software. Finding the generic
|
||||
similarities and structure among different cards helps in developing
|
||||
device drivers faster and with better documentation.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
In the case of Linux as the host operating system, device driver
|
||||
writers must keep the following Linux-specific issues in mind:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
The kernel space structures that Comedi uses have the following
|
||||
<emphasis role="strong">Kernel space vs. User space.</emphasis>
|
||||
The Linux operating system has two levels that require
|
||||
basically different programming approaches. Only privileged processes
|
||||
can run in the kernel, where they have access to all hardware and to
|
||||
all kernel data structures. Normal application
|
||||
programs can run their processes only in user space, where these
|
||||
processes are shielded from each other, and from direct access to
|
||||
hardware and to critical data of the operating system; these user
|
||||
space programs execute much of the operating system's functionality
|
||||
through <emphasis>system calls</emphasis>.
|
||||
</para>
|
||||
<para>
|
||||
Device drivers typically must access specific addresses on the bus,
|
||||
and hence must (at least partially) run in kernel space. Normal users
|
||||
program against the API of <emphasis>Comedi</emphasis>, while
|
||||
&comedi; device driver writers use the API offered by
|
||||
<emphasis>Kcomedilib</emphasis>. Typical examples of the latter are
|
||||
the registration of interrupt handler routines, and the handling of
|
||||
events.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Device files or device file system.</emphasis>
|
||||
Users who write an application for a particular device,
|
||||
must link their application to that device's device driver. Part of
|
||||
this device driver, however, runs in kernel space, and the user
|
||||
application in user space. So, the operating system provides an
|
||||
interface between both. In Linux or Unix, these interfaces are in the
|
||||
form of “files”
|
||||
in the <filename class=directory>/dev</filename> directory (2.2.x kernels or
|
||||
earlier) or <filename class=directory>/devfs</filename> directory
|
||||
(2.4.x kernels and later). Each device supported in the kernel has a
|
||||
representative as such a user space device file, and its functionality can
|
||||
be accessed by classical Unix file I/O:
|
||||
<function>open</function>,
|
||||
<function>close</function>, <function>read</function>,
|
||||
<function>write</function>, and <function>ioctl</function>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong"><filename class=directory>/proc</filename> interface.</emphasis>
|
||||
Linux (and some other UNIX operating systems) offer a file-like
|
||||
interface to attached devices (and other OS-related information) via
|
||||
the <filename class=directory>/proc</filename> directories. These
|
||||
“files” do not really exist, but it gives a familiar
|
||||
interface to users, with which they can inspect the current status of
|
||||
each device.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Direct Memory Access (DMA) vs. Programmed
|
||||
Input/Output (PIO).</emphasis>
|
||||
Almost all devices can be interfaced in PIO mode: the processor is
|
||||
responsible for directly accessing the bus addresses allocated to
|
||||
the device whenever it needs
|
||||
to read or write data. Some devices also allow DMA: the device and the
|
||||
memory “talk” to each other directly, without needing the processor.
|
||||
DMA is a feature of the bus, not of the operating system (which, of
|
||||
course, has
|
||||
to support its processes to use the feature).
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Real-time vs. non real-time.</emphasis>
|
||||
If the device is to be used in a
|
||||
<ulink
|
||||
url="http://www.fsmlabs.com/products/openrtlinux/">RTLinux/Free
|
||||
</ulink>
|
||||
or <ulink url="http://www.rtai.org">RTAI</ulink> application,
|
||||
there are a few extra requirements, because not all system calls are
|
||||
available in the kernel of the real-time operating systems
|
||||
<ulink
|
||||
url="http://www.fsmlabs.com/products/openrtlinux/">RTLinux/Free
|
||||
</ulink>
|
||||
or <ulink url="http://www.rtai.org">RTAI</ulink>.
|
||||
The APIs of RTAI and RTLinux/Free differ in
|
||||
different ways, so the &comedi; developers have spent a lot of efforts
|
||||
to make generic wrappers to the required RTOS primitives: timers,
|
||||
memory allocation, registration of interrupt handlers, etc.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comediosignals">
|
||||
<title>
|
||||
DAQ signals
|
||||
</title>
|
||||
<para>
|
||||
The cards supported in &comedi; have one or more of the following
|
||||
<emphasis role="strong">signals</emphasis>: analog input, analog
|
||||
output, digital input, digital output, counter input, counter output,
|
||||
pulse input, pulse output:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Digital</emphasis> signals are conceptually quite simple, and don't need
|
||||
much configuration: the number of channels, their addresses on the
|
||||
bus, and their input or output direction.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Analog</emphasis> signals are a bit more complicated. Typically, an analog
|
||||
acquisition channel can be programmed to generate or read a voltage between a
|
||||
lower and an upper threshold (e.g., <literal>-10V</literal> and
|
||||
<literal>+10V</literal>); the card's electronics can be programmed to
|
||||
automatically sample a set of channels, in a prescribed order, to
|
||||
<emphasis>buffer</emphasis> sequences of data on the board; or to use
|
||||
DMA or an interrupt routine to dump the data in a prescribed part of
|
||||
memory.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Pulse</emphasis>-based signals (counters,
|
||||
timers, encoders, etc.) are conceptually
|
||||
only a bit more complex than digital inputs and outputs, in that
|
||||
they only add some <emphasis>timing specifications</emphasis> to the
|
||||
signal. &comedi; has still only a limited number of drivers for this
|
||||
kind of signals, although most of the necessary API and support
|
||||
functionality is available.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
In addition to these “real” DAQ functions, &comedi; also
|
||||
offers basic timer access.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="comedidevices">
|
||||
<title>
|
||||
Device hierarchy
|
||||
</title>
|
||||
<para>
|
||||
&comedi; organizes all hardware according to the following generic
|
||||
hierarchy:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
- channel: the lowest-level component, that represents the properties
|
||||
of one single data channel (analog in or out; digital in or out).
|
||||
Each channel has parameters for: the voltage range, the reference
|
||||
voltage, the channel polarity (unipolar, bipolar), a conversion
|
||||
factor between voltages and physical units.
|
||||
<emphasis role="strong">Channel</emphasis>: the lowest-level hardware
|
||||
component, that represents the properties of one single data channel;
|
||||
for example, an analog input, or a digital output.
|
||||
Each channel has several parameters, such as: the voltage range; the
|
||||
reference voltage; the channel polarity (unipolar, bipolar); a
|
||||
conversion factor between voltages and physical units; the binary
|
||||
values “0” and “1”; etc.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
- sub-device: a set of functionally identical channels that are
|
||||
physically implemented on the same (chip on an) interface card. For
|
||||
example, a set of 16 identical analog outputs.
|
||||
Each sub-device has parameters for: the number of channels, and the type
|
||||
of the channels.
|
||||
<emphasis role="strong">Sub-device</emphasis>: a set of functionally
|
||||
identical channels that are physically implemented on the same (chip
|
||||
on an) interface card. For example, a set of 16 identical analog
|
||||
outputs. Each sub-device has parameters for: the number of channel
|
||||
and the type of the channels.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
- device: a set of sub-devices that are physically implemented on the
|
||||
<emphasis role="strong">Device</emphasis>: a set of sub-devices that are physically implemented on the
|
||||
same interface card; in other words, the interface card itself.
|
||||
For example, the NI 6024E device has a sub-device with 16 analog input
|
||||
channels, another sub-device with two analog output channels, and a
|
||||
For example, the <literal>National Instruments 6024E</literal>
|
||||
device has a sub-device with 16 analog input channels, another
|
||||
sub-device with two analog output channels, and a
|
||||
third sub-device with eight digital inputs/outputs.
|
||||
Each device has parameters for: the device identification tag from
|
||||
the manufacturer, the identification tag given by the operating system
|
||||
(in order to discriminate between multiple interface cards of the same
|
||||
type), the number of sub-devices, etc.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
Some interface cards have extra components that don't fit in the
|
||||
above-mentioned classification, such as an EEPROM to store
|
||||
configuration and board parameters, or calibration inputs. These
|
||||
special components are also classified as “sub-devices” in
|
||||
&comedi;.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="acquisitionterminology">
|
||||
<title>
|
||||
Acquisition terminology
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The basic functionalities offered by Comedi are:
|
||||
This Section introduces the terminology that this document uses when
|
||||
talking about “acquisitions.” <xref linkend="fig-acq-seq">
|
||||
depicts a typical acquisition <emphasis role="strong">sequence</emphasis>:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The sequence has a <emphasis role="strong">start</emphasis> and an
|
||||
<emphasis role="strong">end</emphasis>. At both sides, the software
|
||||
and the hardware need some finite
|
||||
<emphasis role="strong">initialization or settling time</emphasis>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<anchor id="scan">
|
||||
The sequence consists of a number of identically repeated
|
||||
<emphasis role="strong">scans</emphasis>. This is where the actual
|
||||
data acquisitions are taking place: data is read from the card, or
|
||||
written to it. Each scan also has a
|
||||
<emphasis role="strong">begin</emphasis>, an
|
||||
<emphasis role="strong">end</emphasis>, and a finite
|
||||
<emphasis role="strong">setup time</emphasis>. Possibly, there is also
|
||||
a settling time
|
||||
(“<emphasis role="strong">scan delay</emphasis>”) at the
|
||||
end of a scan.
|
||||
</para>
|
||||
<para>
|
||||
- instruction: to synchronously perform one single data acquisition on a
|
||||
specified channel, or to perform a configuration on the channel.
|
||||
"Synchronous" means that the calling process blocks until the data
|
||||
acquisition has finished.
|
||||
So, the hardware puts a
|
||||
lower boundary (the <emphasis role="strong">scan interval</emphasis>)
|
||||
on the minimum time needed to complete a full scan.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Each scan contains one or more
|
||||
<anchor id="conversion">
|
||||
<emphasis role="strong">conversions</emphasis> on particular channels,
|
||||
i.e., the AD/DA converter is activated on each of the programmed
|
||||
channels, and produces a sample, again in a finite
|
||||
<emphasis role="strong">conversion time</emphasis>, starting from the
|
||||
moment in time called the
|
||||
<emphasis role="strong">sample time</emphasis>
|
||||
in <xref linkend="fig-acq-seq">
|
||||
(sometimes also called the “timestamp”),
|
||||
and caused by a
|
||||
triggering event, called <emphasis role="strong">convert</emphasis>.
|
||||
In addition, each hardware has limits on the minimum
|
||||
<emphasis role="strong">conversion interval</emphasis> it can achieve,
|
||||
i.e., the minimum time it needs between
|
||||
<emphasis>subsequent</emphasis> conversions.
|
||||
</para>
|
||||
<para>
|
||||
- scan: repeated instructions on a number of different channels, with a
|
||||
programmed sequence and timing.
|
||||
Some hardware must <emphasis>multiplex</emphasis> the conversions onto
|
||||
one single AD/DA hardware, such that the conversions are done serially
|
||||
in time (as shown on the <link linkend="fig-acq-seq">Figure</link>);
|
||||
other cards have the hardware to do two or more acquisitions in
|
||||
parallel. The begin of each conversion is “triggered” by
|
||||
some internally or externally generated pulse, e.g., a timer.
|
||||
</para>
|
||||
<para>
|
||||
- command: start or stop an autonomous (and hence aynchronous) data
|
||||
acquisition (i.e., a number of scans) on a specified set of
|
||||
channels. "Autonomous" means: without interaction from the software,
|
||||
i.e., by means of on-board timers or possibly external triggers.
|
||||
</para>
|
||||
<para>
|
||||
This command functionality is not offered by all DAQ cards. When
|
||||
using RTAI or Real-Time Linux, the command functionality is emulated
|
||||
through the "comedi_rt_timer" virtual driver.
|
||||
The command functionality is very configurable, with respect to the
|
||||
choice of events with which to signal the progress of the programmed
|
||||
scans: external triggers, end of instruction, etc.
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
In general, not only the begin of a <emphasis>conversion</emphasis> is
|
||||
triggered, but also the begin of a <emphasis>scan</emphasis> and of a
|
||||
<emphasis>sequence</emphasis>. &comedi; provides the API to configure
|
||||
what <link linkend="comedicmdsources">triggering source</link>
|
||||
one wants to use in each case. The API also
|
||||
allows to specify the <emphasis role="strong">channel list</emphasis>,
|
||||
i.e., the sequence of channels that needs to be acquired during each
|
||||
scan.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Comedi not only offers the API to access the functionality of the
|
||||
cards, but also to query the capabilities of the installed Comedi
|
||||
devices. That is, a user process can find out on-line what channels
|
||||
are available, and what their physical parameters are (range,
|
||||
direction of input/output, etc.).
|
||||
<figure id="fig-acq-seq" float="1" pgwide="0">
|
||||
<title>
|
||||
Acquisition sequence. (Figure courtesy of
|
||||
<ulink url="mailto:Kurt.Mueller@sfwte.ch">Kurt Müller</ulink>.)
|
||||
</title>
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="figures/acq-seq.gif" format="GIF">
|
||||
</imageobject>
|
||||
<!--
|
||||
<imageobject>
|
||||
<imagedata fileref="/prior-inv.eps" format="EPS">
|
||||
</imageobject>
|
||||
-->
|
||||
</mediaobject>
|
||||
</figure>
|
||||
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<section id="comedifunctions">
|
||||
<title>
|
||||
DAQ functions
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The basic data acquisition functionalities that &comedi; offers work
|
||||
on channels, or sets of channels:
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Single acquisition</emphasis>: &comedi; has
|
||||
function calls to synchronously perform
|
||||
<emphasis>one single</emphasis> data acquisition on a specified
|
||||
channel: <function>comedi_data_read()</function>,
|
||||
<function>comedi_data_write()</function>,
|
||||
<function>comedi_dio_read()</function>,
|
||||
<function>comedi_dio_write()</function>.
|
||||
“Synchronous” means that the calling process
|
||||
blocks until the data acquisition has finished.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Instruction</emphasis>: a
|
||||
<function>comedi_do_insn()</function> instruction
|
||||
performs (possibly multiple) data acquisitions on a specified channel,
|
||||
in a <emphasis role="strong">synchronous</emphasis> way. So, the
|
||||
function call blocks until the whole acquisition has finished.
|
||||
</para>
|
||||
<para>
|
||||
In addition, <function>comedi_do_insnlist()</function> executes a
|
||||
<emphasis>list</emphasis> of instructions (on different channels) in
|
||||
one single (blocking, synchronous) call, such that the overhead
|
||||
involved in configuring each individual acquisition is reduced.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Scan</emphasis>: a scan is an acquisition on a
|
||||
set of different channels, with a <emphasis>specified sequence and
|
||||
timing</emphasis>.
|
||||
</para>
|
||||
<para>
|
||||
Scans are not directly available as stand-alone function calls in the
|
||||
&comedi; API. They are the internal building blocks of a &comedi;
|
||||
<emphasis>command</emphasis> (see below).
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Command</emphasis>: a command is
|
||||
<emphasis>sequence</emphasis> of
|
||||
<emphasis>scans</emphasis>, for which conditions have been specified
|
||||
that determine when the acquisition will start and stop. A
|
||||
<function>comedi_command()</function> function call generates
|
||||
<emphasis role="strong">aynchronous</emphasis> data acquisition:
|
||||
as soon as the command information has been filled in, the
|
||||
<function>comedi_command()</function> function call returns,
|
||||
the hardware of the card takes care of the sequencing and the timing of
|
||||
the data acquisition,
|
||||
and makes sure that the acquired data is delivered
|
||||
in a software buffer provided by the calling process. Asynchronous
|
||||
operation requires some form of “callback” functionality
|
||||
to prevent buffer overflow: after the calling process has launched the
|
||||
acquisition command, it goes off doing other things, but not after it
|
||||
has configured the “handler” that the interface card can
|
||||
use when it needs to put data in the calling process's buffer.
|
||||
Interrupt routines or DMA are typical techniques to allow such
|
||||
asynchronous operation. Their handlers are configured at driver load
|
||||
time, and can typically not be altered from user space.
|
||||
</para>
|
||||
<para>
|
||||
Buffer management is not the only asynchronous activity: a running
|
||||
acquisition must eventually be stopped too, or it must be started
|
||||
after the <function>comedi_command()</function> function call has
|
||||
prepared (but not started) the hardware for the acquisition.
|
||||
The command functionality is very configurable with respect to
|
||||
choosing which <emphasis role="strong">events</emphasis> will signal
|
||||
the starting or stopping of the programmed acquisition: external triggers,
|
||||
internal triggers, end of scan interrupts, timers, etc.
|
||||
The user of the driver can execute a &comedi;
|
||||
<emphasis>instruction</emphasis> that sends a
|
||||
trigger signal to the device driver. What the driver does exactly with
|
||||
this trigger signal is determined in the specific driver. For example,
|
||||
it starts or stops the ongoing acquisition. The execution of the event
|
||||
associated with this trigger instruction is
|
||||
<emphasis role="strong">synchronous</emphasis> with the execution of
|
||||
the trigger instruction in the device driver, but it is
|
||||
<emphasis role="strong">asynchronous</emphasis> with respect to the
|
||||
instruction or command that initiated the current acquisition.
|
||||
</para>
|
||||
<para>
|
||||
Typically, there is one synchronous triggering instruction for each
|
||||
<emphasis>subdevice</emphasis>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
Note that software triggering is only relevant for commands, and not
|
||||
for instructions: instructions are executed
|
||||
<emphasis>synchronously</emphasis> in the sense that the instruction
|
||||
call blocks until the whole instruction has finished. The command call, on
|
||||
the other hand, activates an acquisition and returns before this
|
||||
acquisition has finished. So, the software trigger works
|
||||
asynchronously for the ongoing acquisition.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="comedisupporting">
|
||||
<title>
|
||||
Supporting functionality
|
||||
</title>
|
||||
|
||||
<para>
|
||||
The full command functionality cannot be offered by DAQ cards that
|
||||
lack the hardware to autonomously sequence a series of
|
||||
scans, and/or to support interrupt or DMA callback functionality.
|
||||
For these cards, the command functionality must be provided in
|
||||
software. And because of the quite strict real-time requirements for a
|
||||
command acquisition, a real-time operating system should be used to
|
||||
translate the command specification into a correctly timed sequence of
|
||||
instructions. Such a correct translation is the responsibility of the
|
||||
device driver developer for the card. However,
|
||||
&comedi; provides the <function>comedi_rt_timer</function> kernel
|
||||
module to support such a
|
||||
<emphasis role="strong">virtual command execution</emphasis> under
|
||||
<acronym>RTAI</acronym> or <acronym>RTLinux/Free</acronym>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Buffers are an important aspect of device drivers: the data has
|
||||
to be stored in such buffers, if the application program cannot
|
||||
guarantee to read or write the data as soon as the interface board
|
||||
wants to do so. Therefore, Comedi offers functionality to configure
|
||||
and manage data buffers.
|
||||
&comedi; not only offers the API
|
||||
<emphasis role="strong">to access</emphasis> the functionality of the
|
||||
cards, but also <emphasis role="strong">to query</emphasis> the
|
||||
capabilities of the installed devices. That is, a user process can
|
||||
find out <emphasis>on-line</emphasis> what channels are available, and
|
||||
what their physical parameters are (range, direction of input/output,
|
||||
etc.).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Comedi contains more than just procedural function calls: it also
|
||||
offers event-driven functionality. The data acquisition can signal
|
||||
its completion by means of an interrupt or a callback function call.
|
||||
<emphasis role="strong">Buffering</emphasis> is another important
|
||||
aspect of device drivers: the acquired data has to be stored in such
|
||||
buffers, because, in general, the application program cannot guarantee
|
||||
to always be ready to provide or accept data as soon as the interface
|
||||
board wants to do a read or write operation. Therefore, &comedi;
|
||||
offers all functionality to configure and manage data buffers,
|
||||
abstracting away the intricacies of buffer management at the bare
|
||||
operating system level.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
As already mentioned before, &comedi; contains more than just
|
||||
procedural function calls, since it also offers
|
||||
<emphasis role="strong">event-driven</emphasis>
|
||||
(“asynchronous”) functionality:
|
||||
the data acquisition can signal
|
||||
its completion by means of an interrupt or a
|
||||
<emphasis>callback</emphasis> function call.
|
||||
Callbacks are also used to signal errors during the data
|
||||
acquisition or when writing to buffers, or at the end of a scan or
|
||||
acquisition that has been launched previously to take place
|
||||
asynchronously (i.e., the card fills up som shared memory buffer
|
||||
autonomously, and only warns the user program after it has finished).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The mechanisms for synchronization and interrupt handling are a bit
|
||||
different when used in a real-time context (i.e., with either RTAI or
|
||||
Real-Time Linux), but both are encapsulated behind the same Comedi calls.
|
||||
different when used in real-time
|
||||
(<application>RTAI</application> or
|
||||
<application>RTLinux/Free</application>) or non real-time, but both
|
||||
contexts are encapsulated wihting the same &comedi; calls.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Because multiple devices can all be active at the same time, Comedi
|
||||
provides (non-SMP!) locking primitives to ensure atomic operations on
|
||||
critical sections of the code or data structures.
|
||||
Because multiple devices can all be active at the same time, &comedi;
|
||||
provides <emphasis role="strong">locking</emphasis> primitives to
|
||||
ensure atomic operations on critical sections of the code or data
|
||||
structures.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Finally, Comedi offers the above-mentioned "high-level" interaction,
|
||||
i.e., at the level of user space device drivers, through file
|
||||
operations on entries in the /dev directory (for access to the
|
||||
Finally, &comedi; offers the previously mentioned
|
||||
“high-level” interaction, i.e., at the level of user space
|
||||
device drivers, through file operations on entries in the
|
||||
<filename class=directory>/dev</filename> directory (for access to the
|
||||
device's functionality), or interactively from the command line
|
||||
through the "files" in the /proc directory (which allow to inspect
|
||||
the status of a Comedi device). This high-level interface resides in
|
||||
the "comedilib" tarball, which is the user space library, with
|
||||
facilities to connect to the kernel space drivers residing in the
|
||||
"comedi" tarball.
|
||||
through the “files” in the
|
||||
<filename class=directory>/proc</filename> directory (which allow to
|
||||
inspect the status of a &comedi; device).
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ $end = "";
|
|||
|
||||
print
|
||||
"<!--This file is autogenerated. Do not edit-->
|
||||
<section>
|
||||
<section id="functionreference">
|
||||
<title>
|
||||
Comedi Function Reference
|
||||
</title>
|
||||
|
|
2029
doc/other.sgml
2029
doc/other.sgml
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,66 +1,87 @@
|
|||
<!-- <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V3.1//EN"> -->
|
||||
<!-- <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V3.1//EN" "docbook/dtd/3.1/docbook.dtd"> -->
|
||||
|
||||
<section>
|
||||
<section id="writingprograms">
|
||||
<title>
|
||||
Writing programs that use comedi and comedilib
|
||||
Writing &comedi; programs
|
||||
</title>
|
||||
<para>
|
||||
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.
|
||||
<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>
|
||||
<section id="firstprogram">
|
||||
<title>
|
||||
Your first comedi program
|
||||
Your first &comedi; program
|
||||
</title>
|
||||
|
||||
<para>
|
||||
This example requires a card that has analog or
|
||||
digital input. Right to the source:
|
||||
</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>
|
||||
#include <stdio.h> /* for printf() */
|
||||
#include <comedilib.h>
|
||||
#include <![CDATA[<stdio.h>]]> /* for printf() */
|
||||
#include <![CDATA[<]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>]]>
|
||||
|
||||
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 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 = <link linkend="aref-ground">AREF_GROUND</link>; /* more on this later */
|
||||
|
||||
int main(int argc,char *argv[])
|
||||
{
|
||||
comedi_t *it;
|
||||
lsampl_t data;
|
||||
<link linkend="ref-type-comedi-t">comedi_t</link> *it;
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> data;
|
||||
|
||||
it=comedi_open("/dev/comedi0");
|
||||
|
||||
comedi_data_read(it,subdev,chan,range,aref,&data);
|
||||
|
||||
printf("%d\n",data);
|
||||
|
||||
return 0;
|
||||
it=<link linkend="func-ref-comedi-open">comedi_open</link>("/dev/comedi0");
|
||||
|
||||
<link linkend="func-ref-comedi-data-read">comedi_data_read</link>(it,subdev,chan,range,aref, & data);
|
||||
|
||||
printf("%d\n",data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
The
|
||||
<function>
|
||||
<link linkend="func-ref-comedi-open">comedi_open()</link>
|
||||
</function> can only be successful if the
|
||||
<filename>comedi0</filename> device file is configured to point to a
|
||||
valid &comedi; driver. <xref linkend="cardconfiguration"> explains
|
||||
how this driver is linked to the “device file”.
|
||||
<para>
|
||||
Should be understandable: open the device, get the data,
|
||||
print it out. This is basically the guts of <filename>demo/inp.c</filename>,
|
||||
without error checking or fancy options.
|
||||
Compile it using
|
||||
The code above is basically the guts of
|
||||
<filename>demo/inp.c</filename>, without error checking or fancy
|
||||
options. Compile the program using
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
cc tut1.c -lcomedi -o tut1
|
||||
</screen>
|
||||
<para>
|
||||
(Replace <literal>cc</literal> by your favourite C compiler command.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
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.
|
||||
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>
|
||||
<section id="convertingsamples">
|
||||
<title>
|
||||
Converting samples to voltages
|
||||
</title>
|
||||
|
@ -68,66 +89,56 @@ Converting samples to voltages
|
|||
<para>
|
||||
If you selected an analog input subdevice, you probably noticed
|
||||
that the output of <command>tut1</command> 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 <emphasis>always</emphasis>
|
||||
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?"
|
||||
<literal>0</literal> and <literal>4095</literal>, or
|
||||
<literal>0</literal> and <literal>65535</literal>, depending on the
|
||||
number of bits in the A/D converter. &comedi; samples are
|
||||
<emphasis>always</emphasis> unsigned,
|
||||
with <literal>0</literal> representing the lowest voltage of the ADC,
|
||||
and <literal>4095</literal>
|
||||
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?”
|
||||
</para>
|
||||
|
||||
<para>
|
||||
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.
|
||||
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.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Comedi keeps the number of available ranges and the largest
|
||||
&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.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The largest sample value can be found using the function:
|
||||
</para>
|
||||
|
||||
The largest sample value can be found using the function
|
||||
<programlisting>
|
||||
comedi_get_maxdata()
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> <link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(<link linkend="ref-type-comedi-t">comedi_t</link> * device, unsigned int subdevice, unsigned int channel))
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
The number of available ranges can be found using the function:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
comedi_get_n_ranges()
|
||||
int <link linkend="func-ref-comedi-get-n-ranges">comedi_get_n_ranges</link>(<link linkend="ref-type-comedi-t">comedi_t</link> * device, unsigned int subdevice, unsigned int channel);
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For each value of the range parameter for a particular
|
||||
subdevice/channel, you can get range information using the
|
||||
function:
|
||||
</para>
|
||||
|
||||
subdevice/channel, you can get range information using:
|
||||
<programlisting>
|
||||
ptr=comedi_get_range(comedi_file,subdevice,channel,
|
||||
range);
|
||||
<link linkend="ref-type-comedi-range">comedi_range</link> * <link linkend="func-ref-comedi-get-range">comedi_get_range</link>(<link linkend="ref-type-comedi-t">comedi_t</link> * device,
|
||||
unsigned int subdevice, unsigned int channel, unsigned int range);
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
which returns a pointer to a comedi_range structure.
|
||||
The comedi_range structure looks like
|
||||
</para>
|
||||
|
||||
which returns a pointer to a
|
||||
<link linkend="ref-type-comedi-range">comedi_range</link>
|
||||
structure, which has the following contents:
|
||||
<programlisting>
|
||||
typedef struct{
|
||||
double min;
|
||||
|
@ -135,25 +146,31 @@ typedef struct{
|
|||
unsigned int unit;
|
||||
}comedi_range;
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
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, etc.
|
||||
The structure element <parameter class=function>min</parameter>
|
||||
represents the voltage corresponding to
|
||||
<link linkend="func-ref-comedi-data-read">comedi_data_read()</link>
|
||||
returning <literal>0</literal>,
|
||||
and <parameter class=function>max</parameter> represents
|
||||
<link linkend="func-ref-comedi-data-read">comedi_data_read()</link>
|
||||
returning <parameter class=function>maxdata</parameter>,
|
||||
(i.e., <literal>4095</literal> for <literal>12</literal> bit A/C
|
||||
converters, <literal>65535</literal> for <literal>16</literal> bit,
|
||||
or, <literal>1</literal> for digital input; more on this in a bit.)
|
||||
The <parameter class=function>unit</parameter> entry tells you if
|
||||
<parameter class=function>min</parameter> and
|
||||
<parameter class=function>max</parameter> refer to voltage, current,
|
||||
or are dimensionless (e.g., for digital I/O).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
"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
|
||||
“Could it get easier?” you say. Well, yes. Use
|
||||
the function <function>comedi_to_phys()</function>
|
||||
<link linkend="func-ref-comedi-to-phys">comedi_to_phys()</link>, which
|
||||
converts data values to physical units. Call it using something like
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
volts=comedi_to_phys(it,data,range,maxdata);
|
||||
volts=<link linkend="func-ref-comedi-to-phys">comedi_to_phys</link>(it,data,range,maxdata);
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
|
@ -161,79 +178,76 @@ and the opposite
|
|||
</para>
|
||||
|
||||
<programlisting>
|
||||
data=comedi_from_phys(it,volts,range,maxdata);
|
||||
data=<link linkend="func-ref-comedi-from-phys">comedi_from_phy</link>s(it,volts,range,maxdata);
|
||||
</programlisting>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="usingfileinterface">
|
||||
<title>
|
||||
Another section
|
||||
Using the file interface
|
||||
</title>
|
||||
|
||||
|
||||
<para>
|
||||
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():
|
||||
access, the &comedi; library provides higher-level access,
|
||||
much like the standard <acronym>C</acronym> library provides
|
||||
<function>fopen()</function>, etc. as a high-level (and portable)
|
||||
alternative to the direct <acronym>UNIX</acronym> system calls
|
||||
<function>open()</function>, etc. Similarily to
|
||||
<function>fopen()</function>, we have
|
||||
<link linkend="func-ref-comedi-open">comedi_open()</link>:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
file=comedi_open("/dev/comedi0");
|
||||
file=<link linkend="func-ref-comedi-open">comedi_open</link>("/dev/comedi0");
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
where file is of type (comedi_t *). This function
|
||||
calls <function>open()</function>, 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.
|
||||
where <parameter class=function>file</parameter> is of type
|
||||
<parameter>(<link linkend="ref-type-comedi-t">comedi_t</link> *)</parameter>.
|
||||
This function calls <function>open()</function>, as done explicitly in
|
||||
a previous section, but also fills the
|
||||
<link linkend="ref-type-comedi-t">comedi_t</link>
|
||||
structure with lots of goodies; this information will be useful soon.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Specifically, we needed to know maxdata for a specific
|
||||
subdevice/channel. How about:
|
||||
</para>
|
||||
Specifically, you need to know
|
||||
<parameter class=function>maxdata</parameter> for a specific
|
||||
subdevice/channel. How about:
|
||||
|
||||
<programlisting>
|
||||
maxdata=comedi_get_maxdata(file,subdevice,channel);
|
||||
maxdata=<link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(file,subdevice,channel);
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
Wow. How easy. And the range type?
|
||||
</para>
|
||||
Wow! How easy. And the range information?
|
||||
|
||||
<programlisting>
|
||||
range_type=comedi_get_rangetype(file,subdevice,channel);
|
||||
<link linkend="ref-type-comedi-range">comedi_range</link> * <link linkend="func-ref-comedi-get-range">comedi_get_range(<link linkend="ref-type-comedi-t">comedi_t</link>comedi_t</link> *it,unsigned int subdevice,unsigned int chan,unsigned int range);
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
Cool. Other information you need to know about a channel
|
||||
can be gotten in a similar way.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<section id="secondprogram">
|
||||
<title>
|
||||
Your second comedi program
|
||||
Your second &comedi; program: simple acquisition
|
||||
</title>
|
||||
|
||||
|
||||
<para>
|
||||
Actually, this is the first comedi program again, just
|
||||
Actually, this is the first &comedi; program again, just
|
||||
that we've added what we've learned.
|
||||
</para>
|
||||
|
||||
|
||||
<programlisting>
|
||||
#include <stdio.h> /* for printf() */
|
||||
#include <comedi.h> /* also included by comedilib.h */
|
||||
#include <comedilib.h> /* 'cuz we're using comedilib */
|
||||
#include <![CDATA[<]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>]]>
|
||||
|
||||
int subdev = 0; /* change this to your input subdevice */
|
||||
int chan = 0; /* change this to your channel */
|
||||
|
@ -242,28 +256,396 @@ 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;
|
||||
<link linkend="ref-type-comedi-t">comedi_t</link> *cf;
|
||||
int chan=0;
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> data;
|
||||
int maxdata,rangetype;
|
||||
double volts;
|
||||
|
||||
cf=comedi_open("/dev/comedi0");
|
||||
cf=<link linkend="func-ref-comedi-open">comedi_open</link>("/dev/comedi0");
|
||||
|
||||
maxdata=comedi_get_maxdata(cf,subdev,chan);
|
||||
maxdata=<link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(cf,subdev,chan);
|
||||
|
||||
rangetype=comedi_get_rangetype(cf,subdev,chan);
|
||||
rangetype=comedi_get_rangetype(cf,subdev,chan);
|
||||
|
||||
comedi_data_read(cf->fd,subdev,chan,range,aref,&data);
|
||||
<link linkend="func-ref-comedi-data-read">comedi_data_read</link>(cf->fd,subdev,chan,range,aref,&data);
|
||||
|
||||
volts=comedi_to_phys(data,rangetype,range,maxdata);
|
||||
volts=<link linkend="func-ref-comedi-to-phys">comedi_to_phys</link>(data,rangetype,range,maxdata);
|
||||
|
||||
printf("%d %g\n",data,volts);
|
||||
printf("%d %g\n",data,volts);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="thirdprogram">
|
||||
<title>
|
||||
Your third &comedi; program: instructions
|
||||
</title>
|
||||
<para>
|
||||
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 <link linkend="instructions">instruction</link>.
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
#include <stdio.h>
|
||||
#include <]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>
|
||||
#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
|
||||
|
||||
<link linkend="ref-type-comedi-t">comedi_t</link> *device;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret,i;
|
||||
<link linkend="ref-type-comedi-insn">comedi_insn</link> insn[3];
|
||||
<link linkend="ref-type-comedi-insnlist">comedi_insnlist</link> il;
|
||||
struct timeval t1,t2;
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> data[MAX_SAMPLES];
|
||||
|
||||
parse_options(argc,argv);
|
||||
|
||||
|
||||
device=<link linkend="func-ref-comedi-open">comedi_open</link>(filename);
|
||||
if(!device){
|
||||
<link linkend="func-ref-comedi-perror">comedi_perror</link>(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=<link linkend="insn-gtod">INSN_GTOD</link>;
|
||||
insn[0].n=2;
|
||||
insn[0].data=(void *)&t1;
|
||||
|
||||
/* Instruction 1: do 10 analog input reads */
|
||||
insn[1].insn=<link linkend="insn-read">INSN_READ</link>;
|
||||
insn[1].n=n_scan;
|
||||
insn[1].data=data;
|
||||
insn[1].subdev=subdevice;
|
||||
insn[1].chanspec=<link linkend="ref-macro-CR-PACK">CR_PACK</link>(channel,range,aref);
|
||||
|
||||
/* Instruction 2: perform a gettimeofday() */
|
||||
insn[2].insn=<link linkend="insn-gtod">INSN_GTOD</link>;
|
||||
insn[2].n=2;
|
||||
insn[2].data=(void *)&t2;
|
||||
|
||||
ret=<link linkend="func-ref-comedi-do-insnlist">comedi_do_insnlist</link>(device,&il);
|
||||
if(ret<![CDATA[<]]>0){
|
||||
<link linkend="func-ref-comedi-perror">comedi_perror</link>(filename);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printf("initial time: %ld.%06ld\n",t1.tv_sec,t1.tv_usec);
|
||||
for(i=0;i<![CDATA[<]]>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;
|
||||
}
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="fourthprogram">
|
||||
<title>
|
||||
Your fourth &comedi; program: commands
|
||||
</title>
|
||||
|
||||
<para>
|
||||
This example programs an analog output subdevice with &comedi;'s most
|
||||
powerful acquisition function, the asynchronous
|
||||
<link linkend="commandsstreaming">command</link>, to generate a waveform.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The waveform in this example is a sine wave, but this can be easily
|
||||
changed to make a generic function generator.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
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
|
||||
<function>write()</function>. Since there may be a delay between the
|
||||
<link linkend="func-ref-comedi-command">comedi_command()</link>
|
||||
and a subsequent <function>write()</function>, you
|
||||
should fill the buffer using <function>write()</function> before you call
|
||||
<link linkend="func-ref-comedi-command">comedi_command()</link>,
|
||||
as is done here.
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
#include <stdio.h>
|
||||
#include <]]><link linkend="comedi-comedilib-h">comedilib.h</link><![CDATA[>
|
||||
#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 <link linkend="dds-output">dds_output</link>(sampl_t *buf,int n);
|
||||
void <link linkend="dds-init">dds_init</link>(void);
|
||||
|
||||
/* This define determines which waveform to use. */
|
||||
#define <anchor id="dds-init-function">dds_init_function <link linkend="dds-init-sine">dds_init_sine</link>
|
||||
|
||||
void <link linkend="dds-init-sine">dds_init_sine</link>(void);
|
||||
void <link linkend="dds-init-pseudocycloid">dds_init_pseudocycloid</link>(void);
|
||||
void <link linkend="dds-init-sawtooth">dds_init_sawtooth</link>(void);
|
||||
|
||||
int <anchor id="comedi-internal-trigger">comedi_internal_trigger(<link linkend="ref-type-comedi-t">comedi_t</link> *dev, unsigned int subd, unsigned int trignum)
|
||||
{
|
||||
<link linkend="ref-type-comedi-insn">comedi_insn</link> insn;
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> data[1];
|
||||
|
||||
memset(<![CDATA[&insn]]>, 0, sizeof(<link linkend="ref-type-comedi-insn">comedi_insn</link>));
|
||||
insn.insn = <link linkend="insn-inttrig">INSN_INTTRIG</link>;
|
||||
insn.subdev = subd;
|
||||
insn.data = data;
|
||||
insn.n = 1;
|
||||
|
||||
data[0] = trignum;
|
||||
|
||||
return <link linkend="func-ref-comedi-do-insn">comedi_do_insn</link>(dev, <![CDATA[&insn]]>);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
<link linkend="ref-type-comedi-cmd">comedi_cmd</link> cmd;
|
||||
int err;
|
||||
int n,m;
|
||||
int total=0;
|
||||
<link linkend="ref-type-comedi-t">comedi_t</link> *dev;
|
||||
unsigned int chanlist[16];
|
||||
unsigned int maxdata;
|
||||
<link linkend="ref-type-comedi-range">comedi_range</link> *rng;
|
||||
int ret;
|
||||
<link linkend="ref-type-lsampl-t">lsampl_t</link> insn_data = 0;
|
||||
|
||||
parse_options(argc,argv);
|
||||
|
||||
/* Force n_chan to be 1 */
|
||||
n_chan = 2;
|
||||
|
||||
if(value){ waveform_frequency = value; }
|
||||
|
||||
dev = <link linkend="func-ref-comedi-open">comedi_open</link>(filename);
|
||||
if(dev == NULL){
|
||||
fprintf(stderr, "error opening %s\n", filename);
|
||||
return -1;
|
||||
}
|
||||
subdevice = <link linkend="func-ref-comedi-find-subdevice-by-type">comedi_find_subdevice_by_type</link>(dev,COMEDI_SUBD_AO,0);
|
||||
|
||||
maxdata = <link linkend="func-ref-comedi-get-maxdata">comedi_get_maxdata</link>(dev,subdevice,0);
|
||||
rng = <link linkend="func-ref-comedi-get-range">comedi_get_range</link>(dev,subdevice,0,0);
|
||||
offset = (double)<link linkend="func-ref-comedi-from-phys">comedi_from_phys</link>(0.0,rng,maxdata);
|
||||
amplitude = (double)<link linkend="func-ref-comedi-from-phys">comedi_from_phys</link>(1.0,rng,maxdata) - offset;
|
||||
|
||||
memset(<![CDATA[&cmd]]>,0,sizeof(cmd));
|
||||
/* fill in the <link linkend="ref-type-comedi-cmd">command data structure</link>: */
|
||||
cmd.subdev = subdevice;
|
||||
cmd.flags = 0;
|
||||
cmd.start_src = <link linkend="trig-int-start-src">TRIG_INT</link>;
|
||||
cmd.start_arg = 0;
|
||||
cmd.scan_begin_src = <link linkend="trig-timer">TRIG_TIMER</link>;
|
||||
cmd.scan_begin_arg = 1e9/freq;
|
||||
cmd.convert_src = <link linkend="trig-now">TRIG_NOW</link>;
|
||||
cmd.convert_arg = 0;
|
||||
cmd.scan_end_src = <link linkend="trig-count">TRIG_COUNT</link>;
|
||||
cmd.scan_end_arg = n_chan;
|
||||
cmd.stop_src = <link linkend="trig-none">TRIG_NONE</link>;
|
||||
cmd.stop_arg = 0;
|
||||
|
||||
cmd.chanlist = chanlist;
|
||||
cmd.chanlist_len = n_chan;
|
||||
|
||||
chanlist[0] = <link linkend="ref-macro-CR-PACK">CR_PACK</link>(channel,range,aref);
|
||||
chanlist[1] = <link linkend="ref-macro-CR-PACK">CR_PACK</link>(channel+1,range,aref);
|
||||
|
||||
<link linkend="dds-init">dds_init</link>();
|
||||
|
||||
<link linkend="dds-output">dds_output</link>(data,BUF_LEN);
|
||||
<link linkend="dds-output">dds_output</link>(data,BUF_LEN);
|
||||
|
||||
dump_cmd(stdout,<![CDATA[&cmd]]>);
|
||||
|
||||
if ((err = <link linkend="func-ref-comedi-command">comedi_command</link>(dev, <![CDATA[&cmd]]>)) < 0) {
|
||||
<link linkend="func-ref-comedi-perror">comedi_perror</link>("comedi_command");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
m=write(comedi_fileno(dev),data,BUF_LEN*sizeof(sampl_t));
|
||||
if(<![CDATA[m<0]]>){
|
||||
perror("write");
|
||||
exit(1);
|
||||
}
|
||||
printf("m=%d\n",m);
|
||||
|
||||
ret = <link linkend="comedi-internal-trigger">comedi_internal_trigger</link>(dev, subdevice, 0);
|
||||
<![CDATA[
|
||||
if(ret<0){
|
||||
]]>
|
||||
perror("comedi_internal_trigger\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while(1){
|
||||
<link linkend="dds-output">dds_output</link>(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);
|
||||
<![CDATA[
|
||||
if(m<0){
|
||||
]]>
|
||||
perror("write");
|
||||
exit(0);
|
||||
}
|
||||
printf("m=%d\n",m);
|
||||
n-=m;
|
||||
}
|
||||
total+=BUF_LEN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define WAVEFORM_SHIFT 16
|
||||
<![CDATA[
|
||||
#define WAVEFORM_LEN (1<<WAVEFORM_SHIFT)
|
||||
]]>
|
||||
#define WAVEFORM_MASK (WAVEFORM_LEN-1)
|
||||
|
||||
sampl_t waveform[WAVEFORM_LEN];
|
||||
|
||||
unsigned int acc;
|
||||
unsigned int adder;
|
||||
|
||||
void <anchor id="dds-init">dds_init(void)
|
||||
{
|
||||
<![CDATA[
|
||||
adder=waveform_frequency/freq*(1<<16)*(1<<WAVEFORM_SHIFT);
|
||||
]]>
|
||||
|
||||
<link linkend="dds-init-function">dds_init_function</link>();
|
||||
}
|
||||
|
||||
void <anchor id="dds-output">dds_output(sampl_t *buf,int n)
|
||||
{
|
||||
int i;
|
||||
sampl_t *p=buf;
|
||||
|
||||
<![CDATA[
|
||||
for(i=0;i<n;i++){
|
||||
*p=waveform[(acc>>16)&WAVEFORM_MASK];
|
||||
]]>
|
||||
p++;
|
||||
acc+=adder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void <anchor id="dds-init-sine">dds_init_sine(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
<![CDATA[
|
||||
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 <anchor id="dds-init-pseudocycloid">dds_init_pseudocycloid(void)
|
||||
{
|
||||
int i;
|
||||
double t;
|
||||
|
||||
<![CDATA[
|
||||
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 <anchor id="dds-init-sawtooth">dds_init_sawtooth(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
<![CDATA[
|
||||
for(i=0;i<WAVEFORM_LEN;i++){
|
||||
waveform[i]=rint(offset+amplitude*((double)i)/WAVEFORM_LEN);
|
||||
]]>
|
||||
}
|
||||
}
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue