Converting docs to DocBook 3.1

This commit is contained in:
David Schleef 2002-01-21 11:15:31 +00:00
parent 575761aed2
commit f95a0c1eac
12 changed files with 5324 additions and 0 deletions

18
doc/docbook/Makefile Normal file
View file

@ -0,0 +1,18 @@
all: drivers.sgml funcref.sgml
-mkdir html
-mkdir man
-docbook2html -o html comedilib.sgml
-docbook2man -o man comedilib.sgml
funcref.sgml: funcref mkref
./mkref funcref >funcref.sgml
drivers.sgml: drivers.txt mkdr
./mkdr drivers.txt >drivers.sgml
clean:
-rm -rf html
-rm -rf man
-rm -f drivers.sgml funcref.sgml

672
doc/docbook/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

@ -0,0 +1,69 @@
<!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 drivers SYSTEM "drivers.sgml">
<!ENTITY reference SYSTEM "reference.sgml">
<!ENTITY funcref SYSTEM "funcref.sgml">
]>
<article>
<artheader>
<title>
Comedi Documentation
</title>
<author>
<firstname>David</firstname>
<surname>Schleef</surname>
<affiliation>
<address>
ds@schleef.org
</address>
</affiliation>
</author>
<author>
<firstname>Frank</firstname>
<surname>Hess</surname>
<affiliation>
<address>
fmhess@uiuc.edu
</address>
</affiliation>
</author>
</artheader>
&intro
&install
<section>
<title>
Writing programs that use Comedi
</title>
<para>
Empty.
</para>
</section>
&drivers
<section>
<title>
Comedi Reference
</title>
<para>
Reference for functions, macros, and constants.
</para>
&reference
&funcref
</section>
</article>

1075
doc/docbook/drivers.txt Normal file

File diff suppressed because it is too large Load diff

355
doc/docbook/funcref Normal file
View file

@ -0,0 +1,355 @@
Function: comedi_close -- close a Comedi device
Retval: int
Param: comedi * device
Description:
Close a device previously opened by comedi_open().
Function: comedi_open -- open a Comedi device
Retval: comedi_t
Param: const char * filename
Description:
Open a Comedi device represented by the file filename.
Function: comedi_loglevel -- change Comedilib logging properties
Retval: int
Param: int loglevel
Description:
Function: comedi_perror -- print a Comedilib error message
Retval: void
Param: const char * s
Description:
Function: comedi_strerror -- return string describing Comedilib error code
Retval: char *
Param: int errnum
Description:
Function: comedi_errno -- number of last Comedilib error
Retval: int
Param: void
Description:
Function: comedi_fileno -- integer descriptor of Comedilib device
Retval: int
Param: comedi_t * device
Description:
Function: comedi_get_n_subdevices -- number of subdevices
Retval: int
Param: comedi_t * device
Description:
Function: comedi_get_version_code -- Comedi version code
Retval: int
Param: comedi_t * device
Description:
Function: comedi_get_driver_name -- Comedi driver name
Retval: char *
Param: comedi_t * device
Description:
Function: comedi_get_board_name -- Comedi device name
Retval: char *
Param: comedi_t * device
Description:
Function: comedi_get_subdevice_type -- type of subdevice
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_find_subdevice_by_type -- search for subdevice type
Retval: int
Param: comedi_t * device
Param: int type
Param: unsigned int start_subdevice
Description:
Function: comedi_get_subdevice_flags -- properties of subdevice
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_get_n_channels -- number of subdevice channels
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_range_is_chan_specific -- range information depends on channel
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_maxdata_is_chan_specific -- maximum sample depends on channel
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_get_maxdata -- maximum sample of channel
Retval: lsampl_t
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Description:
Function: comedi_get_n_ranges -- number of ranges of channel
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Description:
Function: comedi_get_range -- range information of channel
Retval: comedi_range *
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Param: unsigned int range
Description:
Function: comedi_find_range -- search for range
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Param: unsigned int unit
Param: double min
Param: double max
Description:
Function: comedi_get_buffer_size -- streaming buffer size of subdevice
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_get_max_buffer_size -- maximum streaming buffer size
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_set_buffer_size -- streaming buffer size of subdevice
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int size
Description:
Function: comedi_trigger -- perform streaming input/output (deprecated)
Retval: int
Param: comedi_t * device
Param: comedi_trig * trig
Description:
Status: deprecated
Function: comedi_do_insnlist -- perform multiple instructions
Retval: int
Param: comedi_t * device
Param: comedi_insnlist * list
Description:
Function: comedi_do_insn -- perform instruction
Retval: int
Param: comedi_t * device
Param: comedi_insn * instruction
Description:
Function: comedi_lock -- subdevice reservation
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_unlock -- subdevice reservation
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_to_phys -- convert sample to physical units
Retval: double
Param: lsampl_t data
Param: comedi_range * range
Param: lsampl_t maxdata
Description:
Function: comedi_from_phys -- convert physical units to sample
Retval: lsampl_t
Param: double data
Param: comedi_range * range
Param: lsampl_t maxdata
Description:
Function: comedi_data_read -- read single sample from channel
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
Description:
Function: comedi_data_write -- write single sample to channel
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
Description:
Function: comedi_dio_config -- change input/output properties of channel
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Param: unsigned int direction
Description:
Function: comedi_dio_read -- read single bit from digital channel
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Param: unsigned int * bit
Description:
Function: comedi_dio_write -- write single bit to digital channel
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Param: unsigned int bit
Description:
Function: comedi_dio_bitfield -- read/write multiple digital channels
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int write_mask
Param: unsigned int * bits
Description:
Function: comedi_sv_init -- slowly-varying inputs
Retval: int
Param: comedi_sv_t * sv
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Description:
Function: comedi_sv_update -- slowly-varying inputs
Retval: int
Param: comedi_sv_t * sv
Description:
Function: comedi_sv_measure -- slowly-varying inputs
Retval: int
Param: comedi_sv_t * sv
Param: double * data
Description:
Function: comedi_get_cmd_src_mask -- streaming input/output capabilities
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: comedi_cmd * command
Description:
Function: comedi_get_cmd_generic_timed -- streaming input/output capabilities
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: comedi_cmd * command
Param: unsigned int period_ns
Description:
Function: comedi_cancel -- stop streaming input/outpu in progress
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_command -- start streaming input/output
Retval: int
Param: comedi_t * device
Param: comedi_cmd * command
Description:
Function: comedi_command_test -- test streaming input/output configuration
Retval: int
Param: comedi_t * device
Param: comedi_cmd * command
Description:
Function: comedi_poll -- force updating of streaming buffer
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_set_max_buffer_size -- streaming buffer size of subdevice
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int max_size
Description:
Function: comedi_get_buffer_contents -- streaming buffer status
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_mark_buffer_read -- streaming buffer status
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int num_bytes
Description:
Function: comedi_get_buffer_offset -- streaming buffer status
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Description:
Function: comedi_get_timer -- timer information (deprecated)
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: double frequency
Param: unsigned int * trigvar
Param: double * actual_frequency
Description:
Status: deprecated
Function: comedi_timed_1chan -- streaming input (deprecated)
Retval: int
Param: comedi_t * device
Param: unsigned int subdevice
Param: unsigned int channel
Param: unsigned int range
Param: unsigned int aref
Param: double frequency
Param: unsigned int num_samples
Param: double * data
Description:
Status: deprecated
Function: comedi_set_global_oor_behavior -- out-of-range behavior
Retval: int
Param: enum comedi_oor_behavior behavior
Description:
Status: alpha

234
doc/docbook/install.sgml Normal file
View file

@ -0,0 +1,234 @@
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V3.1//EN">
<section>
<title>
Installation and 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
ranges, etc.
</para>
<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.
</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.
</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:
</para>
<screen>
# ANSI string --&gt;National Instruments, AT-MIO-16E-10&lt;--
(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)
))
</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.
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.
</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 line
</para>
<screen>
/usr/sbin/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.
</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
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:
</para>
<!-- XXX
<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>
-->
<para>
(ai=analog input, ao=analog output.) From this, I decide that
the appropriate options list is
</para>
<screen>
0x200,4,,1,1,1
</screen>
<para>
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.
</para>
<screen>
/usr/sbin/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.
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):
</para>
<screen>
comedi0: ni_atmio: 0x0200 can't find board
</screen>
<para>
When it does work, I get:
</para>
<screen>
comedi0: ni_atmio: 0x0260 at-mio-16e-10 ( irq = 3 )
</screen>
<para>
Note that it also correctly identified my board.
</para>
<section>
<title>
Getting information from comedi
</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:
</para>
<screen>
cat /proc/comedi
</screen>
<para>
Right now, on my computer, this command gives:
</para>
<screen>
comedi version 0.6.4
format string
0: ni_atmio at-mio-16e-10 7
1: dt282x dt2821-f-8di 4
</screen>
<para>
This is a feature that is not well-developed yet. Basically, it
currently tells you driver name, device name, and 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')
</para>
<screen>
overall info:
version code: 0x000604
driver name: ni_atmio
board name: at-mio-16e-10
number of subdevices: 7
subdevice 0:
type: 1 (unknown)
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.
</para>
<para>
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.
</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.
</para>
</section>
</section>

315
doc/docbook/intro.sgml Normal file
View file

@ -0,0 +1,315 @@
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V3.1//EN">
<!--ignore the error caused by previous line-->
<section>
<title>
Introduction
</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.
</para>
<section>
<title>
What is a "device driver"?
</title>
<para>
A device driver is a piece of software that interfaces a particular
piece of hardware: a printer, a sound card, a motor drive, etc. It
translates the primitive, device-dependent commands with which the
hardware manufacturer want you to configure, read and write the
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
lots of different cards for measurement and control purposes. This
type of cards are often called Data AcQuisition cards, or DAQ cards.
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.
</para>
</section>
<section>
<title>
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>
<para>
Mechanism.
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>
<para>
Policy.
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
stuctures offered by the mechanism are interpreted in specific
physical units, or some of them are taken together because this
composition is relevant for the application. For example, a analog
output card can be used to generate voltages that are the inputs for
the electronic drivers of the motors of a robot; these voltages can be
interpreted as setpoints for the desired velocity of these motors, and
six of them are taken together to steer one particular robot with
six-degrees of freedom. Some of the other outputs of the same physical
device can be used by another application program, for example to
generate a sine wave that drives a vibration shaker.
</para>
</section>
<section>
<title>
Overview of Comedi.
</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>
<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.
</para>
<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.
</para>
<para>
The kernel space structures that Comedi uses have the following
hierarchy:
</para>
<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.
</para>
<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.
</para>
<para>
- device: 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
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>
<para>
The basic functionalities offered by Comedi are:
</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.
</para>
<para>
- scan: repeated instructions on a number of different channels, with a
programmed sequence and timing.
</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.
</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.).
</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.
</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.
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.
</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.
</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
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.
</para>
</section>
</section>

149
doc/docbook/mkdr Executable file
View file

@ -0,0 +1,149 @@
#!/usr/bin/perl
# vim: set ts=4:
$manvolnum="3";
$header="#include &lt;comedilib.h&gt;";
$end = "";
print
"<!--This file is autogenerated. Do not edit-->
<section>
<title>
Low-level drivers
</title>
";
while(<>){
push @lines,$_;
}
$secend="";
while($s = shift @lines){
@x = $s;
if($s =~ m/^\w+\:/){
$blank=0;
chomp $s;
$x = $s;
LOOP: while($s = shift @lines){
if($s =~ m/^\w+:/ or $s eq "\n"){
unshift @lines, $s;
last LOOP;
}
chomp $s;
$x = "$x $s";
}
if($x =~ m/^Driver: (.*)/){
$driver = $1;
}elsif($x =~ m/^Description: (.*)/){
$description = $1;
}elsif($x =~ m/^Devices: (.*)/){
$devices = $1;
}elsif($x =~ m/^Author: (.*)/){
$author = $1
}
}else{
if($s eq "\n"){
$blank ++;
}else{
$blank = 0;
}
$comment = $comment . $s;
}
if($blank==3){
$comment =~ s/@/&#64;/g;
$comment =~ s/</&lt;/g;
$comment =~ s/>/&gt;/g;
$author =~ s/@/&#64;/g;
$author =~ s/</&lt;/g;
$author =~ s/>/&gt;/g;
print
"
<section>
<title>
$driver -- $description
</title>
<para>
Author: $author
</para>
";
if($devices ne ""){
print
" <informaltable>
<tgroup cols='3' align='left'>
<thead>
<row>
<entry>Manufacturer</entry>
<entry>Device</entry>
<entry>Name</entry>
</row>
</thead>
<tbody>
";
while($devices){
$_=$devices;
if(m/^ *\[([^\]]+)\](.*)/){
$mfr = $1;
$devices = $2;
}elsif(m/^ *\(([^\)]+)\)(.*)/){
$name = $1;
$devices = $2;
}elsif(m/^ *([^\(,]+)(.*)/){
$dev = $1;
$devices = $2;
$dev =~ s/ *$//;
}elsif(m/^ *,(.*)/){
$devices = $1;
print
" <row>
<entry>
$mfr
</entry>
<entry>
$dev
</entry>
<entry>
$name
</entry>
</row>
";
}else{
die "parse error";
}
}
print
" <row>
<entry>
$mfr
</entry>
<entry>
$dev
</entry>
<entry>
$name
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
";
}
print
"
<screen>
$comment
</screen>
</section>";
$blank=0;
$comment="";
}
}
print
"</section>
";
exit(0);

106
doc/docbook/mkref Executable file
View file

@ -0,0 +1,106 @@
#!/usr/bin/perl
# vim: set ts=4:
$manvolnum="3";
$header="#include &lt;comedilib.h&gt;";
$end = "";
print
"<!--This file is autogenerated. Do not edit-->
<section>
<title>
Comedi Function Reference
</title>
";
while($s = <>){
chomp $s;
if($s eq ""){
print $end;
print
"</refentry>
";
$end = "";
}elsif($s =~ m/^Function: (.*)/){
$funcname = $1;
$refpurpose = "";
if($s =~ m/^Function: (.*) -- (.*)/){
$funcname = $1;
$refpurpose = $2;
}
$refname = $funcname;
$refname =~ s/_/-/g;
$refname = "func-ref-" . $refname;
print $end;
print
"<refentry id=\"$refname\">
<refmeta>
<refentrytitle>$funcname</refentrytitle>
<manvolnum>$manvolnum</manvolnum>
</refmeta>
<refnamediv>
<refname>$funcname</refname>
<refpurpose>$refpurpose</refpurpose>
</refnamediv>
";
$end = "";
}elsif($s =~ m/^Retval: (.*)/){
print
" <refsynopsisdiv>
<funcsynopsis>
<funcsynopsisinfo>$header</funcsynopsisinfo>
<funcprototype>
<funcdef>$1 <function>$funcname</function></funcdef>
";
$end =
" </funcprototype>
</funcsynopsis>
</refsynopsisdiv>
";
}elsif($s =~ m/^Param: (.*)/){
$p = $1;
$p =~ m/(.*) (.*)/;
print
" <paramdef>$1 <parameter>$2</parameter></paramdef>
"
}elsif($s =~ m/^Description:/){
print $end;
print
" <refsect1>
<title>
Description
</title>
<para>
";
$end =
" </para>
</refsect1>
";
}elsif($s =~ m/^Status: (.*)/){
print $end;
print
" <refsect1>
<title>
Status
</title>
<para>
$1
";
$end =
" </para>
</refsect1>
";
}elsif($s =~ m/^ (.*)/){
print "$1\n";
}
}
print
"</section>
";
exit(0);

672
doc/docbook/other.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.

987
doc/docbook/reference.sgml Normal file
View file

@ -0,0 +1,987 @@
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V3.1//EN">
<section>
<title>
Constants and Macros
</title>
<section id="ref-macro-CR-PACK">
<title>
CR_PACK
</title>
<para>
CR_PACK is used to initialize the elements of the chanlist array in the
comedi_cmd structure, and the chanspec member
of the comedi_insn structure.
</para>
<para>
The channel argument is the channel you wish to use, with the channel
numbering starting at zero.
</para>
<para>
The range is an index, starting at zero, whose meaning
is device dependent. The
comedi_get_n_ranges() and
comedi_get_range functions
are useful in discovering information about the available ranges.
</para>
<para>
The aref argument indicates what reference you want the device to use. It
can be any of the following:
</para>
<variablelist>
<varlistentry>
<term>AREF_GROUND</term>
<listitem>
<para>
is for inputs/outputs referenced to ground
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>AREF_COMMON</term>
<listitem>
<para>
is for a `common' reference (the low inputs of all the channels are tied
together, but are isolated from ground)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>AREF_DIFF</term>
<listitem>
<para>
is for differential inputs/outputs
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>AREF_OTHER</term>
<listitem>
<para>
is for any reference that does not fit into the above categories
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Particular drivers may or may not use the AREF flags. If they are
not supported, they are silently ignored.
</para>
</section>
<section id="ref-macro-RANGE-LENGTH">
<title>
RANGE_LENGTH (deprecated)
</title>
<para>
Rangetype values are library-internal tokens that represent an
array of range information structures. These numbers are primarily
used for communication between the kernel and library.
</para>
<para>
The RANGE_LENGTH() macro returns the length of the array that is
specified by the rangetype token.
</para>
<para>
The RANGE_LENGTH() macro is deprecated, and should not be used in
new applications. It is scheduled to be removed from the header
file at version 1.0. Binary compatibility may be broken for version
1.1.
</para>
</section>
</section>
<section>
<title>
Data Types and Structures
</title>
<section id="ref-type-comedi-t">
<title>
comedi_t
</title>
<para>
The data type comedi_t is used to represent an open Comedi
device. A valid comedi_t pointer is returned by a successful
call to comedi_open(), and should be used for subsequent
access to the device.
It is a transparent type, and pointers to type comedi_t
should not be dereferenced by the application.
</para>
</section>
<section id="ref-type-sampl-t">
<title>
sampl_t
</title>
<para>
The data type sampl_t is one of the generic types used to represent
data values in Comedilib. It is used in a few places where a data type
shorter than lsampl_t is useful. On most architectures, sampl_t
is defined to be uint16.
</para>
<para>
Most drivers represent data trasferred by read() and write()
using sampl_t. Applications should check the subdevice flag
SDF_LSAMPL to determine if the subdevice uses sampl_t or
lsampl_t.
</para>
</section>
<section id="ref-type-lsampl-t">
<title>
lsampl_t
</title>
<para>
The data type lsampl_t is the data type typically used to represent
data values in libcomedi. On most architectures, lsampl_t is
defined to be uint32.
</para>
</section>
<section id="ref-type-comedi-trig">
<title>
comedi_trig (deprecated)
</title>
<programlisting>
struct comedi_trig_struct{
unsigned int subdev; /* subdevice */
unsigned int mode; /* mode */
unsigned int flags;
unsigned int n_chan; /* number of channels */
unsigned int *chanlist; /* channel/range list */
sampl_t *data; /* data list, size depends on subd flags */
unsigned int n; /* number of scans */
unsigned int trigsrc;
unsigned int trigvar;
unsigned int trigvar1;
unsigned int data_len;
unsigned int unused[3];
}
</programlisting>
<para>
The comedi_trig structure is a control structure used by the
COMEDI_TRIG ioctl, an older method of communicating
instructions to the driver and hardware. Use of comedi_trig is
deprecated, and should not be used in new applications.
</para>
</section>
<section id="ref-type-comedi-sv-t">
<title>
comedi_sv_t
</title>
<programlisting>
struct comedi_sv_struct{
comedi_t *dev;
unsigned int subdevice;
unsigned int chan;
/* range policy */
int range;
int aref;
/* number of measurements to average (for ai) */
int n;
lsampl_t maxdata;
}
</programlisting>
<para>
The comedi_sv_t structure is used by the comedi_sv_*()
functions to provide a simple method of accurately measuring
slowly varying inputs. See the relevant section for more
details.
</para>
</section>
<section id="ref-type-comedi-cmd">
<title>
comedi_cmd
</title>
<programlisting>
typedef struct comedi_cmd_struct comedi_cmd;
struct comedi_cmd_struct{
unsigned int subdev;
unsigned int flags;
unsigned int start_src;
unsigned int start_arg;
unsigned int scan_begin_src;
unsigned int scan_begin_arg;
unsigned int convert_src;
unsigned int convert_arg;
unsigned int scan_end_src;
unsigned int scan_end_arg;
unsigned int stop_src;
unsigned int stop_arg;
unsigned int *chanlist;
unsigned int chanlist_len;
sampl_t *data;
unsigned int data_len;
};
</programlisting>
<para>
More information on using commands can be found in the
command section.
</para>
</section>
<section id="ref-type-comedi-insn">
<title>
comedi_insn
</title>
<programlisting>
typedef struct comedi_insn_struct comedi_insn;
struct comedi_insn_struct{
unsigned int insn;
unsigned int n;
lsampl_t *data;
unsigned int subdev;
unsigned int chanspec;
unsigned int unused[3];
};
</programlisting>
<para>
Comedi instructions are described by the comedi_insn structure.
Applications send instructions to the driver in order to preform
control and measurement operations that are done immediately or
synchronously, i.e., the operations complete before program control
returns to the application. In particular, instructions cannot
describe acquisition that involves timers or external events.
</para>
<para>
The field insn determines the type of instruction that is sent
to the driver. Valid instruction types are
</para>
<variablelist>
<varlistentry>
<term>
INSN_READ
</term>
<listitem>
<para>
read values from an input channel
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
INSN_WRITE
</term>
<listitem>
<para>
write values to an output channel
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
INSN_BITS
</term>
<listitem>
<para>
read/write values on multiple digital I/O channels
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
INSN_CONFIG
</term>
<listitem>
<para>
configure a subdevice
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
INSN_GTOD
</term>
<listitem>
<para>
read a timestamp, identical to gettimeofday()
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
INSN_WAIT
</term>
<listitem>
<para>
wait a specified number of nanoseconds
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
The number of samples to read or write, or the size of the configuration
structure is specified by the field n, and the buffer for those
samples by data. The field subdev is the subdevice index
that the instruction is sent to. The field chanspec specifies
the channel, range, and analog reference (if applicable).
</para>
<para>
Instructions can be sent to drivers using comedi_do_insn().
Multiple instructions can be sent to drivers in the same system
call using comedi_do_insnlist().
</para>
</section>
<section id="ref-type-comedi-range">
<title>
comedi_range
</title>
<programlisting>
typedef struct{
double min;
double max;
unsigned int unit;
}comedi_range;
</programlisting>
<para>
The comedi_range structure conveys part of the information
necessary to translate sample values to physical units, in particular,
the endpoints of the range and the physical unit type. The
physical unit type is specified by the field unit, which may
take the values UNIT_volt for volts, UNIT_mA for milliamps,
or UNIT_none for unitless. The endpoints are specified by
the fields min and max.
</para>
</section>
<section id="ref-type-comedi-krange">
<title>
comedi_krange
</title>
<programlisting>
struct comedi_krange_struct{
int min;
int max;
unsigned int flags;
};
</programlisting>
<para>
The comedi_krange structure is used to transfer range information
between the driver and Comedilib, and should not normally be used
by applications. The structure conveys the same information as the
comedi_range structure, except the fields min and max
are integers, multiplied by a factor of 1000000 compared to the
counterparts in comedi_range.
</para>
</section>
</section>
<section>
<title>
Interface reference
</title>
<para>
This chapter is meant to be a reference for some of the advanced
features of Comedi.
</para>
<section>
<title>
Digital input combining machines
</title>
<para>
When one or several digital inputs are used to modify an output
value, either an accumulator or a single digital line or bit,
a bitfield structure is typically used in the Comedi interface.
The digital inputs have two properties, "sensitive" inputs and
"modifier" inputs. Edge transitions on sensitive inputs cause
changes in the output signal, whereas modifier inputs change the
effect of edge transitions on sensitive inputs. Note that inputs
can be both modifier inputs and sensitive inputs.
</para>
<para>
For simplification purposes, it is assumed that multiple digital
inputs do not change simultaneously.
</para>
<para>
The combined state of the modifier inputs determine a modifier
state. For each combination of modifier state and sensitive
input, there is a set of bits that determine the effect on the
output value due to positive or negative transitions of the
sensitive input. For each transition direction, there are two
bits defined as follows:
</para>
<variablelist>
<varlistentry>
<term>
00
</term>
<listitem>
<para>
transition is ignored
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
01
</term>
<listitem>
<para>
accumulator is incremented, or output is set
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
10
</term>
<listitem>
<para>
accumulator is decremented, or output is cleared
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
11
</term>
<listitem>
<para>
reserved
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
For example, a simple digital follower is specified by the bit
pattern 01 10, because it sets the output on positive transitions
of the input, and clears the output on negative transitions. A
digital inverter is similarily 10 01. These systems have only
one sensitive input.
</para>
<para>
As another example, a simple up counter, which increments on
positive transitions of one input, is specified by 01 00. This
system has only one sensitive input.
</para>
<para>
When multiple digital inputs are used, the inputs are divided
into two types, inputs which cause changes in the accumulator, and
those that only modify the meaning of transitions on other inputs.
Modifier inputs do not require bitfields, but there needs to be
a bitfield of length 4*(2^(N-1)) for each edge sensitive input,
where N is the total number of inputs. Since N is usually 2 or
3, with only one edge sensitive input, the scaling issues are
not significant.
</para>
</section>
<section>
<title>
INSN_CONFIG
</title>
<para>
Configuration instructions are used to access device and driver features
that do not fit well into other parts of the Comedi interface. This
includes changing the direction of configurable digital I/O lines,
configuring complex triggering engines, and counter/timer configuration.
</para>
<para>
If a specified ID is not supported, the driver must return -EINVAL.
</para>
<section>
<title>
Digital I/O configuration
</title>
<para></para>
<simplelist>
<member>
Status: Implemented
</member>
<member>
ID: COMEDI_INPUT, COMEDI_OUTPUT, COMEDI_OPENDRAIN
</member>
<member>
Length: 1
</member>
<member>
Chanspec: used to specify channel
</member>
</simplelist>
<para>
These IDs are used to configure direction of digital I/O lines.
Direction is chosen by the ID. On typical devices, multiple
channels are grouped together in blocks for determining direction.
Configuring one channel in a block configures the entire block.
</para>
<para>
There should also be a method to read the configuration.
</para>
<para>
Errors: Should return -EINVAL if the ID is not supported.
</para>
</section>
<section>
<title>
Analog conversion configuration
</title>
<simplelist>
<member>
Status: design
</member>
<member>
ID: not assigned
</member>
<member>
Length:
</member>
<member>
Chanspec: used to specify channel
</member>
</simplelist>
<para>
Some devices have the capability to add white noise (dithering) to
analog input measurement. This additional noise can then be averaged
out, to get a more accurate measurement of the input signal. It
should not be assumed that channels can be separately configured.
A simple design can use 1 bit to turn this feature on/off.
</para>
<para>
Some devices have the capability of changing the glitch characteristics
of analog output subsytems. The default (off) case should be where
the average settling time is lowest. A simple design can use 1 bit
to turn this feature on/off.
</para>
<para>
Some devices have a configurable analog filters as part of the analog
input stage. A simple designe can use 1 bit to enable/disable the
filter. Default is disabled, i.e., the filter being bypassed, or if
the choice is between two filters, the filter with the largest
bandwidth.
</para>
</section>
<section>
<title>
Analog Output Waveform Generation
</title>
<simplelist>
<member>
Status: design
</member>
<member>
ID: not assigned
</member>
<member>
Length:
</member>
<member>
Chanspec: ignored
</member>
</simplelist>
<para>
Some devices have the ability to cyclicly loop through samples kept in
an on-board analog output FIFO. This config should allow the user to
enable/disable this mode.
</para>
<para>
This config should allow the user to configure the number of samples
to loop through. It may be necessary to configure the channels used.
</para>
</section>
<section>
<title>
Extended Triggering
</title>
<simplelist>
<member>
Status: alpha
</member>
<member>
ID: not assigned
</member>
<member>
Chanspec: ignored
</member>
</simplelist>
<para>
This section covers common information for all extended
triggering configuration, and doesn't describe a particular
type of extended trigger.
</para>
<para>
Extended triggering is used to configure triggering engines that
do not fit into commands. In a typical programming sequence, the
application will use configuration instructions to configure an
extended trigger, and the issue a command, specifying TRIG_OTHER
as one of the trigger sources.
</para>
<para>
Extended trigger configuration should be designed in such a way
that the user can probe for valid parameters, similar to how
command testing works. An extended trigger config instruction
should not configure the hardware directly, rather, the configuration
should be saved until the subsequent command is issued. This
allows more flexibility for future interface changes.
</para>
<para>
It has not been decided whether the config stage should return a
token that is then used as the trigger argument in the command.
Using tokens is one method to satisfy the problem that extended
trigger configurations may have subtle compatiblity issues with
other trigger sources/arguments that can only be determined at
command test time. Passing all stages of a command test should
only be allowed with a properly configured extended trigger.
</para>
<para>
Extended triggers must use data[1] as flags. The upper 16 bits
are reserved and used only for flags that are common to
all extended triggers. The lower 16 bits may be defined by the
particular type of extended trigger.
</para>
<para>
Various types of extended triggers must use data[1] to know which
event the extended trigger will be assigned to in the command
structure. The possible values are an OR'd mask of the following:
</para>
<itemizedlist>
<listitem>
<para>
COMEDI_EV_START
</para>
</listitem>
<listitem>
<para>
COMEDI_EV_SCAN_BEGIN
</para>
</listitem>
<listitem>
<para>
COMEDI_EV_CONVERT
</para>
</listitem>
<listitem>
<para>
COMEDI_EV_SCAN_END
</para>
</listitem>
<listitem>
<para>
COMEDI_EV_STOP
</para>
</listitem>
</itemizedlist>
</section>
<section>
<title>
Analog Triggering
</title>
<simplelist>
<member>
Status: alpha
</member>
<member>
ID: not assigned
</member>
<member>
Implementation: ni_mio_common
</member>
<member>
Chanspec: ignored
</member>
</simplelist>
<simplelist>
<member>
data 1 - trigger and combining machine configuration
</member>
<member>
data 2 - analog triggering signal chanspec
</member>
<member>
data 3 - primary analog level
</member>
<member>
data 4 - secondary analog level
</member>
</simplelist>
<para>
Analog triggering is described by a digital combining machine that
has two sensitive digital inputs. The sensitive digital inputs are
generated by configurable analog comparators. The analog comparators
generate a digital 1 when the analog triggering signal is greater
than the comparator level. The digital inputs are not modifier
inputs. Note, however, there is an effective modifier due to the
restriction that the primary analog comparator level must be less
than the secondary analog comparator level.
</para>
<para>
If only one analog comparator signal is used, the combining machine
for the secondary input should be set to ignored, and the secondary
analog level should be set to 0.
</para>
<para>
The interpretation of the chanspec and voltage levels is device
dependent, but should correspond to similar values of the analog
input subdevice, if possible.
</para>
<para>
Notes: Reading range information is not addressed. This makes it
difficult to convert comparator voltages to data values.
</para>
<para>
Possible extensions: A parameter that specifies the necessary time
that the set condition has to be true before the trigger is generated.
A parameter that specifies the necessary time that the reset condition
has to be true before the state machine is reset.
</para>
</section>
<section>
<title>
Bitfield Pattern Matching Extended Trigger
</title>
<simplelist>
<member>
Status: design
</member>
<member>
ID: not assigned
</member>
<member>
Chanspec: ignored
</member>
</simplelist>
<simplelist>
<member>
data 1 - trigger flags
</member>
<member>
data 2 - mask
</member>
<member>
data 3 - pattern
</member>
</simplelist>
<para>
The pattern matching trigger issues a trigger when all of a specifed
set of input lines match a specified pattern. If the device allows,
the input lines should correspond to the input lines of a digital input
subdevice, however, this will necessarily be device dependent. Each
possible digital line that can be matched is assigned a bit in the
mask and pattern. A bit set in the mask indicates that the
input line must match the corresponding bit in the pattern.
A bit cleared in the mask indicates that the input line is ignored.
</para>
<para>
Notes: This only allows 32 bits in the pattern/mask, which may be
too few. Devices may support selecting different sets of lines from
which to match a pattern.
</para>
<para>
Discovery: The number of bits can be discovered by setting the mask
to all 1's. The driver must modify this value and return -EAGAIN.
</para>
</section>
<section>
<title>
Counter configuration
</title>
<simplelist>
<member>
Status: design
</member>
<member>
ID: not assigned
</member>
<member>
Chanspec: used to specify counter
</member>
</simplelist>
<simplelist>
<member>
data 1 - trigger configuration
</member>
<member>
data 2 - primary input chanspec
</member>
<member>
data 3 - primary combining machine configuration
</member>
<member>
data 4 - secondary input chanspec
</member>
<member>
data 5 - secondary combining machine configuration
</member>
<member>
data 6 - latch configuration
</member>
</simplelist>
<para>
Counters can be operated either in synchronous mode (using insn_read)
or asynchronous mode (using commands), similar to analog input subdevices.
The input signal for both modes is the accumulator.
Commands on counter subdevices are almost always specified using
scan_begin_src=TRIG_OTHER, with the counter configuration also serving
as the extended configuration for the scan begin source.
</para>
<para>
Counters are made up of an accumulator and a combining machine that
determines when the accumulator should be incremented or decremented
based on the values of the input signals. The combining machine
optionally determines when the accumulator should be latched and
put into a buffer. This feature is used in asynchronous mode.
</para>
<para>
Notes: How to access multiple pieces of data acquired at each event?
</para>
</section>
<section>
<title>
One source plus auxiliary counter configuration
</title>
<simplelist>
<member>
Status: design
</member>
<member>
ID: not assigned
</member>
<member>
Chanspec: ?
</member>
</simplelist>
<para>
data[1] is flags, including the flags for the command triggering
configuration. If a command is not subsequently issued on the
subdevice, the command triggering portion of the flags are ignored.
</para>
<para>
data[2] determines the mode of operation. The mode of operation
is actually a bitfield that encodes what to do for various
transitions of the source signals.
</para>
<para>
data[3] and data[4] determine the primary source for the counter,
similar to _src and _arg used in commands.
</para>
<para>
Notes: How to specify which events cause a latch and push, and what
should get latched.
</para>
</section>
</section>
</section>

672
doc/docbook/tutorial.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.