mirror of https://github.com/tLDP/LDP
628 lines
26 KiB
Plaintext
628 lines
26 KiB
Plaintext
<!doctype linuxdoc system>
|
|
<article>
|
|
|
|
<title>Linux I/O port programming mini-HOWTO
|
|
<author>Author: Riku Saikkonen <tt/<Riku.Saikkonen@hut.fi>/
|
|
<date>v3.0, 2000-12-13
|
|
|
|
<abstract>
|
|
This HOWTO document describes programming hardware I/O ports and
|
|
waiting for small periods of time in user-mode Linux programs running
|
|
on the Intel x86 architecture.
|
|
</abstract>
|
|
|
|
<toc>
|
|
|
|
|
|
<sect>Introduction
|
|
<p>
|
|
This HOWTO document describes programming hardware I/O ports and
|
|
waiting for small periods of time in user-mode Linux programs running
|
|
on the Intel x86 architecture. This document is a descendant of the
|
|
very small IO-Port mini-HOWTO by the same author.
|
|
|
|
This document is Copyright 1995-2000 Riku Saikkonen. See the <url
|
|
url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/COPYRIGHT"
|
|
name="Linux HOWTO copyright"> for details.
|
|
|
|
If you have corrections or something to add, feel free to e-mail me
|
|
(<tt/Riku.Saikkonen@hut.fi/)...
|
|
|
|
|
|
<sect>Using I/O ports in C programs
|
|
|
|
|
|
<sect1>The normal method
|
|
<p>
|
|
Routines for accessing I/O ports are in <tt>/usr/include/asm/io.h</>
|
|
(or <tt>linux/include/asm-i386/io.h</> in the kernel source
|
|
distribution). The routines there are inline macros, so it is enough
|
|
to <tt>#include <asm/io.h></>; you do not need any additional
|
|
libraries.
|
|
|
|
Because of a limitation in gcc (present in all versions I know of,
|
|
including egcs), you <em/have to/ compile any source code that uses
|
|
these routines with optimisation turned on (<tt/gcc -O1/ or higher),
|
|
or alternatively use <tt>#define extern static</> before you
|
|
<tt>#include <asm/io.h></> (remember to <tt>#undef
|
|
extern</>afterwards).
|
|
|
|
For debugging, you can use <tt/gcc -g -O/ (at least with modern
|
|
versions of gcc), though optimisation can sometimes make the debugger
|
|
behave a bit strangely. If this bothers you, put the routines that use
|
|
I/O port access in a separate source file and compile only that with
|
|
optimisation turned on.
|
|
|
|
<sect2>Permissions
|
|
<p>
|
|
Before you access any ports, you must give your program permission to
|
|
do so. This is done by calling the <tt/ioperm()/ function (declared
|
|
in <tt/unistd.h/, and defined in the kernel) somewhere near the start
|
|
of your program (before any I/O port accesses). The syntax is
|
|
<tt>ioperm(from, num, turn_on)</>, where <tt/from/ is the first port
|
|
number to give access to, and <tt/num/ the number of consecutive ports
|
|
to give access to. For example, <tt/ioperm(0x300, 5, 1)/ would give
|
|
access to ports 0x300 through 0x304 (a total of 5 ports). The last
|
|
argument is a Boolean value specifying whether to give access to the
|
|
program to the ports (true (1)) or to remove access (false (0)). You
|
|
can call <tt/ioperm()/ multiple times to enable multiple
|
|
non-consecutive ports. See the <tt/ioperm(2)/ manual page for details
|
|
on the syntax.
|
|
|
|
The <tt/ioperm()/ call requires your program to have root privileges;
|
|
thus you need to either run it as the root user, or make it setuid
|
|
root. You can drop the root privileges after you have called
|
|
<tt/ioperm()/ to enable the ports you want to use. You are not
|
|
required to explicitly drop your port access privileges with
|
|
<tt/ioperm(..., 0)/ at the end of your program; this is done
|
|
automatically as the process exits.
|
|
|
|
A <tt/setuid()/ to a non-root user does not disable the port access
|
|
granted by <tt/ioperm()/, but a <tt/fork()/ does (the child process
|
|
does not get access, but the parent retains it).
|
|
|
|
<tt/ioperm()/ can only give access to ports 0x000 through 0x3ff; for
|
|
higher ports, you need to use <tt/iopl()/ (which gives you access to
|
|
all ports at once). Use the level argument 3 (i.e., <tt/iopl(3)/) to
|
|
give your program access to <em/all/ I/O ports (so be careful ---
|
|
accessing the wrong ports can do all sorts of nasty things to your
|
|
computer). Again, you need root privileges to call <tt/iopl()/. See
|
|
the <tt/iopl(2)/ manual page for details.
|
|
|
|
<sect2>Accessing the ports
|
|
<p>
|
|
|
|
To input a byte (8 bits) from a port, call <tt/inb(port)/, it returns
|
|
the byte it got. To output a byte, call <tt/outb(value, port)/ (please
|
|
note the order of the parameters). To input a word (16 bits) from
|
|
ports <tt/x/ and <tt/x+1/ (one byte from each to form the word, using
|
|
the assembler instruction <tt/inw/), call <tt/inw(x)/. To output a
|
|
word to the two ports, use <tt/outw(value, x)/. If you're unsure of
|
|
which port instructions (byte or word) to use, you probably want
|
|
<tt/inb()/ and <tt/outb()/ --- most devices are designed for bytewise
|
|
port access. Note that all port access instructions take at least
|
|
about a microsecond to execute.
|
|
|
|
The <tt/inb_p()/, <tt/outb_p()/, <tt/inw_p()/, and <tt/outw_p()/
|
|
macros work otherwise identically to the ones above, but they do an
|
|
additional short (about one microsecond) delay after the port access;
|
|
you can make the delay about four microseconds with <tt/#define
|
|
REALLY_SLOW_IO/ before you <tt>#include <asm/io.h></>. These
|
|
macros normally (unless you <tt/#define SLOW_IO_BY_JUMPING/, which
|
|
is probably less accurate) use a port output to port 0x80 for their
|
|
delay, so you need to give access to port 0x80 with <tt/ioperm()/
|
|
first (outputs to port 0x80 should not affect any part of the
|
|
system). For more versatile methods of delaying, read on.
|
|
|
|
There are manual pages for <tt/ioperm(2)/, <tt/iopl(2)/, and the above
|
|
macros in reasonably recent releases of the Linux manual page
|
|
collection.
|
|
|
|
|
|
<sect1>An alternate method: <tt>/dev/port</>
|
|
<p>
|
|
Another way to access I/O ports is to <tt/open()/ <tt>/dev/port</> (a
|
|
character device, major number 1, minor 4) for reading and/or writing
|
|
(the stdio <tt/f*()/ functions have internal buffering, so avoid
|
|
them). Then <tt/lseek()/ to the appropriate byte in the file (file
|
|
position 0 = port 0x00, file position 1 = port 0x01, and so on), and
|
|
<tt/read()/ or <tt/write()/ a byte or word from or to it.
|
|
|
|
Naturally, for this to work your program needs read/write access to
|
|
<tt>/dev/port</>. This method is probably slower than the normal
|
|
method above, but does not need compiler optimisation nor
|
|
<tt/ioperm()/. It doesn't need root access either, if you give a
|
|
non-root user or group access to <tt>/dev/port</> --- but this is a
|
|
very bad thing to do in terms of system security, since it is possible
|
|
to hurt the system, perhaps even gain root access, by using
|
|
<tt>/dev/port</> to access hard disks, network cards, etc. directly.
|
|
|
|
You cannot use <tt/select(2)/ or <tt/poll(2)/ to read /dev/port,
|
|
because the hardware does not have a facility for notifying the CPU
|
|
when a value in an input port changes.
|
|
|
|
|
|
<sect>Interrupts (IRQs) and DMA access
|
|
<p>
|
|
You cannot use IRQs or DMA directly from a user-mode process. You need
|
|
to write a kernel driver; see <url
|
|
url="http://www.redhat.com:8080/HyperNews/get/khg.html" name="The
|
|
Linux Kernel Hacker's Guide"> for details and the kernel source code
|
|
for examples.
|
|
|
|
You can disable interrupts from within a user-mode program, though it can
|
|
be dangerous (even kernel drivers do it for as short a time as possible).
|
|
After calling <tt/iopl(3)/, you can disable interrupts simply by calling
|
|
<tt/asm("cli");/, and re-enable them with <tt/asm("sti");/.
|
|
|
|
|
|
<sect>High-resolution timing
|
|
|
|
|
|
<sect1>Delays
|
|
<p>
|
|
First of all, I should say that you cannot guarantee user-mode
|
|
processes to have exact control of timing because of the multi-tasking
|
|
nature of Linux. Your process might be scheduled out at any time for
|
|
anything from about 10 milliseconds to a few seconds (on a system with
|
|
very high load). However, for most applications using I/O ports, this
|
|
does not really matter. To minimise this, you may want to nice your
|
|
process to a high-priority value (see the <tt/nice(2)/ manual page) or
|
|
use real-time scheduling (see below).
|
|
|
|
If you want more precise timing than normal user-mode processes give
|
|
you, there are some provisions for user-mode `real time' support.
|
|
Linux 2.x kernels have soft real time support; see the manual page for
|
|
<tt/sched_setscheduler(2)/ for details. There is a special kernel that
|
|
supports hard real time; see <url
|
|
url="http://luz.cs.nmt.edu/˜rtlinux/"> for more information on
|
|
this.
|
|
|
|
<sect2>Sleeping: <tt/sleep()/ and <tt/usleep()/
|
|
<p>
|
|
Now, let me start with the easier timing calls. For delays of multiple
|
|
seconds, your best bet is probably to use <tt/sleep()/. For delays of
|
|
at least tens of milliseconds (about 10 ms seems to be the minimum
|
|
delay), <tt/usleep()/ should work. These functions give the CPU to
|
|
other processes (``sleep''), so CPU time isn't wasted. See the manual
|
|
pages <tt/sleep(3)/ and <tt/usleep(3)/ for details.
|
|
|
|
For delays of under about 50 milliseconds (depending on the speed of
|
|
your processor and machine, and the system load), giving up the CPU
|
|
takes too much time, because the Linux scheduler (for the x86
|
|
architecture) usually takes at least about 10-30 milliseconds before
|
|
it returns control to your process. Due to this, in small delays,
|
|
<tt/usleep(3)/ usually delays somewhat more than the amount that you
|
|
specify in the parameters, and at least about 10 ms.
|
|
|
|
<sect2><tt/nanosleep()/
|
|
<p>
|
|
In the 2.0.x series of Linux kernels, there is a new system call,
|
|
<tt/nanosleep()/ (see the <tt/nanosleep(2)/ manual page), that allows
|
|
you to sleep or delay for short times (a few microseconds or more).
|
|
|
|
For delays <= 2 ms, if (and only if) your process is set to soft
|
|
real time scheduling (using <tt/sched_setscheduler()/),
|
|
<tt/nanosleep()/ uses a busy loop; otherwise it sleeps, just like
|
|
<tt/usleep()/.
|
|
|
|
The busy loop uses <tt/udelay()/ (an internal kernel function used by
|
|
many kernel drivers), and the length of the loop is calculated using
|
|
the BogoMips value (the speed of this kind of busy loop is one of the
|
|
things that BogoMips measures accurately). See
|
|
<tt>/usr/include/asm/delay.h</>) for details on how it works.
|
|
|
|
<sect2>Delaying with port I/O
|
|
<p>
|
|
Another way of delaying small numbers of microseconds is port
|
|
I/O. Inputting or outputting any byte from/to port 0x80 (see above for
|
|
how to do it) should wait for almost exactly 1 microsecond independent
|
|
of your processor type and speed. You can do this multiple times to
|
|
wait a few microseconds. The port output should have no harmful side
|
|
effects on any standard machine (and some kernel drivers use it). This
|
|
is how <tt/{in|out}[bw]_p()/ normally do the delay (see
|
|
<tt>asm/io.h</>).
|
|
|
|
Actually, a port I/O instruction on most ports in the 0-0x3ff range
|
|
takes almost exactly 1 microsecond, so if you're, for example, using
|
|
the parallel port directly, just do additional <tt/inb()/s from that
|
|
port to delay.
|
|
|
|
<sect2>Delaying with assembler instructions
|
|
<p>
|
|
If you know the processor type and clock speed of the machine the
|
|
program will be running on, you can hard-code shorter delays by
|
|
running certain assembler instructions (but remember, your process
|
|
might be scheduled out at any time, so the delays might well be longer
|
|
every now and then). For the table below, the internal processor speed
|
|
determines the number of clock cycles taken; e.g., for a 50 MHz
|
|
processor (e.g. 486DX-50 or 486DX2-50), one clock cycle takes
|
|
1/50000000 seconds (=200 nanoseconds).
|
|
|
|
<tscreen><verb>
|
|
Instruction i386 clock cycles i486 clock cycles
|
|
xchg %bx,%bx 3 3
|
|
nop 3 1
|
|
or %ax,%ax 2 1
|
|
mov %ax,%ax 2 1
|
|
add %ax,0 2 1
|
|
</verb></tscreen>
|
|
|
|
<p>
|
|
Clock cycles for Pentiums should be the same as for i486, except that
|
|
on Pentium Pro/II, <tt/add %ax, 0/ may take only 1/2 clock cycles. It
|
|
can sometimes be paired with another instruction (because of
|
|
out-of-order execution, this need not even be the very next
|
|
instruction in the instruction stream).
|
|
|
|
The instructions <tt/nop/ and <tt/xchg/ in the table should have no
|
|
side effects. The rest may modify the flags register, but this
|
|
shouldn't matter since gcc should detect it. <tt/xchg %bx, %bx/ is a
|
|
safe choice for a delay instruction.
|
|
|
|
To use these, call <tt/asm(&dquot;instruction&dquot;)/ in your
|
|
program. The syntax of the instructions is as in the table above; if
|
|
you want multiple instructions in a single <tt/asm()/ statement,
|
|
separate them with semicolons. For example,
|
|
<tt/asm(&dquot;nop ; nop ; nop ; nop&dquot;)/ executes four <tt/nop/
|
|
instructions, delaying for four clock cycles on i486 or Pentium
|
|
processors (or 12 clock cycles on an i386).
|
|
|
|
<tt/asm()/ is translated into inline assembler code by gcc, so there
|
|
is no function call overhead.
|
|
|
|
Shorter delays than one clock cycle are impossible in the Intel x86
|
|
architecture.
|
|
|
|
<sect2><tt/rdtsc/ for Pentiums
|
|
<p>
|
|
For Pentiums, you can get the number of clock cycles elapsed since the
|
|
last reboot with the following C code (which executes the CPU
|
|
instrution named RDTSC):
|
|
|
|
<tscreen><code>
|
|
extern __inline__ unsigned long long int rdtsc()
|
|
{
|
|
unsigned long long int x;
|
|
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
|
|
return x;
|
|
}
|
|
</code></tscreen>
|
|
|
|
You can poll this value in a busy loop to delay for as many clock
|
|
cycles as you want.
|
|
|
|
|
|
<sect1>Measuring time
|
|
<p>
|
|
For times accurate to one second, it is probably easiest to use
|
|
<tt/time()/. For more accurate times, <tt/gettimeofday()/ is accurate
|
|
to about a microsecond (but see above about scheduling). For Pentiums,
|
|
the <tt/rdtsc/ code fragment above is accurate to one clock cycle.
|
|
|
|
If you want your process to get a signal after some amount of time,
|
|
use <tt/setitimer()/ or <tt/alarm()/. See the manual pages of the
|
|
functions for details.
|
|
|
|
|
|
<sect>Other programming languages
|
|
<p>
|
|
The description above concentrates on the C programming language. It
|
|
should apply directly to C++ and Objective C. In assembler, you have
|
|
to call <tt/ioperm()/ or <tt/iopl()/ as in C, but after that you can
|
|
use the I/O port read/write instructions directly.
|
|
|
|
In other languages, unless you can insert inline assembler or C code
|
|
into the program or use the system calls mentioned above, it is
|
|
probably easiest to write a simple C source file with functions for
|
|
the I/O port accesses or delays that you need, and compile and link it
|
|
in with the rest of your program. Or use <tt>/dev/port</> as described
|
|
above.
|
|
|
|
|
|
<sect>Some useful ports
|
|
<p>
|
|
Here is some programming information for common ports that can be directly
|
|
used for general-purpose TTL (or CMOS) logic I/O.
|
|
|
|
If you want to use these or other common ports for their intended
|
|
purpose (e.g., to control a normal printer or modem), you should most
|
|
likely use existing drivers (which are usually included in the kernel)
|
|
instead of programming the ports directly as this HOWTO describes.
|
|
This section is intended for those people who want to connect LCD
|
|
displays, stepper motors, or other custom electronics to a PC's
|
|
standard ports.
|
|
|
|
If you want to control a mass-market device like a scanner (that has
|
|
been on the market for a while), look for an existing Linux driver for
|
|
it. The <url
|
|
url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Hardware-HOWTO"
|
|
name="Hardware-HOWTO"> is a good place to start.
|
|
|
|
<url url="http://www.hut.fi/Misc/Electronics/"> is a good source for
|
|
more information on connecting devices to computers (and on
|
|
electronics in general).
|
|
|
|
|
|
<sect1>The parallel port
|
|
<p>
|
|
The parallel port's base address (called ``<tt/BASE/'' below) is 0x3bc
|
|
for <tt>/dev/lp0</>, 0x378 for <tt>/dev/lp1</>, and 0x278 for
|
|
<tt>/dev/lp2</>. If you only want to control something that acts like
|
|
a normal printer, see the <url
|
|
url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Printing-HOWTO"
|
|
name="Printing-HOWTO">.
|
|
|
|
In addition to the standard output-only mode described below, there is
|
|
an `extended' bidirectional mode in most parallel ports. For
|
|
information on this and the newer ECP/EPP modes (and the IEEE 1284
|
|
standard in general), see <url url="http://www.fapo.com/"> and <url
|
|
url="http://www.senet.com.au/˜cpeacock/parallel.htm">. Remember
|
|
that since you cannot use IRQs or DMA in a user-mode program, you will
|
|
probably have to write a kernel driver to use ECP/EPP; I think someone
|
|
is writing such a driver, but I don't know the details.
|
|
|
|
The port <tt/BASE+0/ (Data port) controls the data signals of the port (D0
|
|
to D7 for bits 0 to 7, respectively; states: 0 = low (0 V), 1 = high
|
|
(5 V)). A write to this port latches the data on the pins. A read
|
|
returns the data last written in standard or extended write mode, or
|
|
the data in the pins from another device in extended read mode.
|
|
|
|
The port <tt/BASE+1/ (Status port) is read-only, and returns the state
|
|
of the following input signals:
|
|
<itemize>
|
|
<item>Bits 0 and 1 are reserved.
|
|
<item>Bit 2 IRQ status (not a pin, I don't know how this works)
|
|
<item>Bit 3 ERROR (1=high)
|
|
<item>Bit 4 SLCT (1=high)
|
|
<item>Bit 5 PE (1=high)
|
|
<item>Bit 6 ACK (1=high)
|
|
<item>Bit 7 -BUSY (0=high)
|
|
</itemize>
|
|
|
|
The port <tt/BASE+2/ (Control port) is write-only (a read returns the
|
|
data last written), and controls the following status signals:
|
|
<itemize>
|
|
<item>Bit 0 -STROBE (0=high)
|
|
<item>Bit 1 -AUTO_FD_XT (0=high)
|
|
<item>Bit 2 INIT (1=high)
|
|
<item>Bit 3 -SLCT_IN (0=high)
|
|
<item>Bit 4 enables the parallel port IRQ (which occurs on the
|
|
low-to-high transition of ACK) when set to 1.
|
|
<item>Bit 5 controls the extended mode direction (0 = write, 1 =
|
|
read), and is completely write-only (a read returns nothing
|
|
useful for this bit).
|
|
<item>Bits 6 and 7 are reserved.
|
|
</itemize>
|
|
|
|
Pinout (a 25-pin female D-shell connector on the port) (i=input, o=output):
|
|
<tscreen><verb>
|
|
1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6,
|
|
9io D7, 10i ACK, 11i -BUSY, 12i PE, 13i SLCT, 14o -AUTO_FD_XT,
|
|
15i ERROR, 16o INIT, 17o -SLCT_IN, 18-25 Ground
|
|
</verb></tscreen>
|
|
|
|
The IBM specifications say that pins 1, 14, 16, and 17 (the control
|
|
outputs) have open collector drivers pulled to 5 V through 4.7 kiloohm
|
|
resistors (sink 20 mA, source 0.55 mA, high-level output 5.0 V minus
|
|
pullup). The rest of the pins sink 24 mA, source 15 mA, and their
|
|
high-level output is min. 2.4 V. The low state for both is max. 0.5 V.
|
|
Non-IBM parallel ports probably deviate from this standard. For more
|
|
information on this, see <url
|
|
url="http://www.hut.fi/Misc/Electronics/circuits/lptpower.html">.
|
|
|
|
Finally, a warning: Be careful with grounding. I've broken several
|
|
parallel ports by connecting to them while the computer is turned
|
|
on. It might be a good thing to use a parallel port not integrated on
|
|
the motherboard for things like this. (You can usually get a second
|
|
parallel port for your machine with a cheap standard `multi-I/O' card;
|
|
just disable the ports that you don't need, and set the parallel port
|
|
I/O address on the card to a free address. You don't need to care
|
|
about the parallel port IRQ if you don't use it.)
|
|
|
|
|
|
<sect1>The game (joystick) port
|
|
<p>
|
|
The game port is located at port addresses 0x200-0x207. If you want to
|
|
control normal joysticks, you're probably better off using the drivers
|
|
distributed with the Linux kernel.
|
|
|
|
Pinout (a 15-pin female D-shell connector on the port):
|
|
<itemize>
|
|
<item>1,8,9,15: +5 V (power)
|
|
<item>4,5,12: Ground
|
|
<item>2,7,10,14: Digital inputs BA1, BA2, BB1, and BB2, respectively
|
|
<item>3,6,11,13: ``Analog'' inputs AX, AY, BX, and BY, respectively
|
|
</itemize>
|
|
|
|
The +5 V pins seem to often be connected directly to the power lines
|
|
in the motherboard, so they may be able to source quite a lot of
|
|
power, depending on the motherboard, power supply and game port.
|
|
|
|
The digital inputs are used for the buttons of the two joysticks
|
|
(joystick A and joystick B, with two buttons each) that you can
|
|
connect to the port. They should be normal TTL-level inputs, and you
|
|
can read their status directly from the status port (see below). A
|
|
real joystick returns a low (0 V) status when the button is pressed
|
|
and a high (the 5 V from the power pins through an 1 Kohm resistor)
|
|
status otherwise.
|
|
|
|
The so-called analog inputs actually measure resistance. The game port
|
|
has a quad one-shot multivibrator (a 558 chip) connected to the four
|
|
inputs. In each input, there is a 2.2 Kohm resistor between the input
|
|
pin and the multivibrator output, and a 0.01 uF timing capacitor
|
|
between the multivibrator output and the ground. A real joystick has a
|
|
potentiometer for each axis (X and Y), wired between +5 V and the
|
|
appropriate input pin (AX or AY for joystick A, or BX or BY for
|
|
joystick B).
|
|
|
|
The multivibrator, when activated, sets its output lines high (5 V)
|
|
and waits for each timing capacitor to reach 3.3 V before lowering the
|
|
respective output line. Thus the high period duration of the
|
|
multivibrator is proportional to the resistance of the potentiometer
|
|
in the joystick (i.e., the position of the joystick in the appropriate
|
|
axis), as follows:
|
|
<quote>
|
|
R = (t - 24.2) / 0.011,
|
|
</quote>
|
|
where R is the resistance (ohms) of the potentiometer and t the high
|
|
period duration (microseconds).
|
|
|
|
Thus, to read the analog inputs, you first activate the multivibrator
|
|
(with a port write; see below), then poll the state of the four axes
|
|
(with repeated port reads) until they drop from high to low state,
|
|
measuring their high period duration. This polling uses quite a lot of
|
|
CPU time, and on a non-realtime multitasking system like (normal
|
|
user-mode) Linux, the result is not very accurate because you cannot
|
|
poll the port constantly (unless you use a kernel-level driver and
|
|
disable interrupts while polling, but this wastes even more CPU
|
|
time). If you know that the signal is going to take a long time (tens
|
|
of ms) to go down, you can call usleep() before polling to give CPU
|
|
time to other processes.
|
|
|
|
The only I/O port you need to access is port 0x201 (the other ports
|
|
either behave identically or do nothing). Any write to this port (it
|
|
doesn't matter what you write) activates the multivibrator. A read
|
|
from this port returns the state of the input signals:
|
|
<itemize>
|
|
<item>Bit 0: AX (status (1=high) of the multivibrator output)
|
|
<item>Bit 1: AY (status (1=high) of the multivibrator output)
|
|
<item>Bit 2: BX (status (1=high) of the multivibrator output)
|
|
<item>Bit 3: BY (status (1=high) of the multivibrator output)
|
|
<item>Bit 4: BA1 (digital input, 1=high)
|
|
<item>Bit 5: BA2 (digital input, 1=high)
|
|
<item>Bit 6: BB1 (digital input, 1=high)
|
|
<item>Bit 7: BB2 (digital input, 1=high)
|
|
</itemize>
|
|
|
|
|
|
<sect1>The serial port
|
|
<p>
|
|
If the device you're talking to supports something resembling RS-232,
|
|
you should be able to use the serial port to talk to it. The Linux
|
|
serial driver should be enough for almost all applications (you
|
|
shouldn't have to program the serial port directly, and you'd probably
|
|
have to write a kernel driver to do it); it is quite versatile, so
|
|
using non-standard bps rates and so on shouldn't be a problem.
|
|
|
|
See the <tt/termios(3)/ manual page, the serial driver source code
|
|
(<tt>linux/drivers/char/serial.c</>), and <url
|
|
url="http://www.easysw.com/˜mike/serial/"> for more
|
|
information on programming serial ports on Unix systems.
|
|
|
|
|
|
<sect>Hints
|
|
<p>
|
|
If you want good analog I/O, you can wire up ADC and/or DAC chips to
|
|
the parallel port (hint: for power, use the game port connector or a
|
|
spare disk drive power connector wired to outside the computer case,
|
|
unless you have a low-power device and can use the parallel port
|
|
itself for power, or use an external power supply), or buy an AD/DA
|
|
card (most of the older/slower ones are controlled by I/O ports). Or,
|
|
if you're satisfied with 1 or 2 channels, inaccuracy, and (probably)
|
|
bad zeroing, a cheap sound card supported by the Linux sound driver
|
|
should do (and it's quite fast).
|
|
|
|
With accurate analog devices, improper grounding may generate errors
|
|
in the analog inputs or outputs. If you experience something like
|
|
this, you could try electrically isolating your device from the
|
|
computer with optocouplers (on <em/all/ signals between the computer
|
|
and your device). Try to get power for the optocouplers from the
|
|
computer (spare signals on the port may give enough power) to achieve
|
|
better isolation.
|
|
|
|
If you're looking for printed circuit board design software for Linux,
|
|
there is a free X11 application called Pcb that should do a nice job,
|
|
at least if you aren't doing anything very complex. It is included in
|
|
many Linux distributions, and available in <url
|
|
url="ftp://sunsite.unc.edu/pub/Linux/apps/circuits/"> (filename
|
|
<tt/pcb-*/).
|
|
|
|
|
|
<sect>Troubleshooting
|
|
<p>
|
|
<descrip>
|
|
<tag/Q1./ I get segmentation faults when accessing ports.
|
|
|
|
<tag/A1./ Either your program does not have root privileges, or the
|
|
<tt/ioperm()/ call failed for some other reason. Check the return
|
|
value of <tt/ioperm()/. Also, check that you're actually accessing the
|
|
ports that you enabled with <tt/ioperm()/ (see Q3). If you're using
|
|
the delaying macros (<tt/inb_p()/, <tt/outb_p()/, and so on), remember
|
|
to call <tt/ioperm()/ to get access to port 0x80 too.
|
|
|
|
<tag/Q2./ I can't find the <tt/in*()/, <tt/out*()/ functions defined
|
|
anywhere, and gcc complains about undefined references.
|
|
|
|
<tag/A2./ You did not compile with optimisation turned on (<tt/-O/),
|
|
and thus gcc could not resolve the macros in <tt>asm/io.h</>. Or you
|
|
did not <tt>#include <asm/io.h></> at all.
|
|
|
|
<tag/Q3./ <tt/out*()/ doesn't do anything, or does something weird.
|
|
|
|
<tag/A3./ Check the order of the parameters; it should be
|
|
<tt/outb(value, port)/, not <tt/outportb(port, value)/ as is common in
|
|
MS-DOS.
|
|
|
|
<tag/Q4./ I want to control a standard RS-232 device/parallel
|
|
printer/joystick...
|
|
|
|
<tag/A4./ You're probably better off using existing drivers (in the
|
|
Linux kernel or an X server or somewhere else) to do it. The drivers
|
|
are usually quite versatile, so even slightly non-standard devices
|
|
usually work with them. See the information on standard ports above
|
|
for pointers to documentation for them.
|
|
</descrip>
|
|
|
|
|
|
<sect>Example code
|
|
<p>
|
|
Here's a piece of simple example code for I/O port access:
|
|
|
|
<tscreen><code>
|
|
/*
|
|
* example.c: very simple example of port I/O
|
|
*
|
|
* This code does nothing useful, just a port write, a pause,
|
|
* and a port read. Compile with `gcc -O2 -o example example.c',
|
|
* and run as root with `./example'.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <asm/io.h>
|
|
|
|
#define BASEPORT 0x378 /* lp1 */
|
|
|
|
int main()
|
|
{
|
|
/* Get access to the ports */
|
|
if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}
|
|
|
|
/* Set the data signals (D0-7) of the port to all low (0) */
|
|
outb(0, BASEPORT);
|
|
|
|
/* Sleep for a while (100 ms) */
|
|
usleep(100000);
|
|
|
|
/* Read from the status port (BASE+1) and display the result */
|
|
printf("status: %d\n", inb(BASEPORT + 1));
|
|
|
|
/* We don't need the ports anymore */
|
|
if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}
|
|
|
|
exit(0);
|
|
}
|
|
|
|
/* end of example.c */
|
|
</code></tscreen>
|
|
|
|
|
|
<sect>Credits
|
|
<p>
|
|
Too many people have contributed for me to list, but thanks a lot,
|
|
everyone. I have not replied to all the contributions that I've
|
|
received; sorry for that, and thanks again for the help.
|
|
|
|
</article>
|