undoing regression

This commit is contained in:
Frank Mori Hess 2003-07-09 02:46:39 +00:00
parent 5cc75f5efe
commit d7fc976d8b

View file

@ -1,672 +0,0 @@
<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.