Much fixage and new text from Herman.

This commit is contained in:
David Schleef 2003-07-07 22:34:18 +00:00
parent 8e33ed9c6e
commit d70d0eef2c
11 changed files with 4814 additions and 1768 deletions

672
doc/advanced.sgml Normal file
View 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,&amp;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,&amp;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.

View file

@ -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 &lt;ds@schleef.org&gt;
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
View 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 &ldquo;register level
manual&rdquo; 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_&hellip;_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
&ldquo;stand-alone&rdquo; 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> &hellip;
<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 &ldquo;skeleton&rdquo; 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 &ldquo;attach&rdquo; 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&hellip;
</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 &ldquo;in the background&rdquo;, 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
&rdquo;callback&rdquo; 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
&ldquo;End Of-Acquisition&rdquo;.
</para>
</listitem>
<listitem>
<para>
<anchor id="comedi-cb-eos">
<parameter>COMEDI_CB_EOS</parameter>: execute the callback at the
&ldquo;End-Of-Scan&rdquo;.
</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 &ldquo;layers&rdquo; 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 &ldquo;mydriver.c&rdquo;
</para>
</listitem>
<listitem>
<para>
Put your new driver into &ldquo;comedi/drivers/mydriver.c&rdquo;.
</para>
</listitem>
<listitem>
<para>
Edit &ldquo;comedi/Config.in&rdquo; and add a new
&ldquo;dep_tristate&rdquo; 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 &ldquo;comedi/drivers/Makefile.in&rdquo;, 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>

View file

@ -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.

View file

@ -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.
&hellip;
</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>

View file

@ -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 &ldquo;file&rdquo;
<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 --&gt;National Instruments, AT-MIO-16E-10&lt;--
(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
&ldquo;<literal>(I/O base),(IRQ)</literal>&rdquo;, 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>

View file

@ -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
&ldquo;devices&rdquo; in &comedi; terminology). The drivers are
implemented as the combination of (i) one single core Linux kernel module
(called &ldquo;<literal>comedi</literal>&rdquo;) 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 &ldquo;kernel library&rdquo; 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 &ldquo;device driver&rdquo;?
</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 &ldquo;heavier&rdquo; &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, &ldquo;lower-level&rdquo; 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 &ldquo;files&rdquo;
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
&ldquo;files&rdquo; 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 &ldquo;talk&rdquo; 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 &ldquo;real&rdquo; 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 &ldquo;0&rdquo; and &ldquo;1&rdquo;; 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 &ldquo;sub-devices&rdquo; 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 &ldquo;acquisitions.&rdquo; <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
(&ldquo;<emphasis role="strong">scan delay</emphasis>&rdquo;) 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 &ldquo;timestamp&rdquo;),
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 &ldquo;triggered&rdquo; 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&uuml;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>.
&ldquo;Synchronous&rdquo; 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 &ldquo;callback&rdquo; 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 &ldquo;handler&rdquo; 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>
(&ldquo;asynchronous&rdquo;) 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
&ldquo;high-level&rdquo; 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 &ldquo;files&rdquo; in the
<filename class=directory>/proc</filename> directory (which allow to
inspect the status of a &comedi; device).
</para>
</section>

View file

@ -8,7 +8,7 @@ $end = "";
print
"<!--This file is autogenerated. Do not edit-->
<section>
<section id="functionreference">
<title>
Comedi Function Reference
</title>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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 &lt;stdio.h&gt; /* for printf() */
#include &lt;comedilib.h&gt;
#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,&amp;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 &ldquo;device file&rdquo;.
<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: &ldquo;How do I do this in a device-independent
manner?&rdquo;
</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 &ldquo;range parameter,&rdquo; since it
specifies the &ldquo;input range&rdquo; for analog input (or
&ldquo;output range&rdquo; 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
&ldquo;Could it get easier?&rdquo; 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 &lt;stdio.h&gt; /* for printf() */
#include &lt;comedi.h&gt; /* also included by comedilib.h */
#include &lt;comedilib.h&gt; /* '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,&amp;data);
<link linkend="func-ref-comedi-data-read">comedi_data_read</link>(cf->fd,subdev,chan,range,aref,&amp;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 *)&amp;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 *)&amp;t2;
ret=<link linkend="func-ref-comedi-do-insnlist">comedi_do_insnlist</link>(device,&amp;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>