Howto |
|
Using I/O ports in C programs under Linux Gefunden unter:
2.1 The normal method
Because of a limitation in gcc (present in all versions I know of, including egcs), you have to compile any source code that uses these routines with optimisation turned on (gcc -O1 or higher), or alternatively use #define extern static before you #include <asm/io.h> (remember to #undef externafterwards). For debugging, you can use 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. Permissions
The 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 ioperm() to enable the ports you want to use. You are not required to explicitly drop your port access privileges with ioperm(..., 0) at the end of your program; this is done automatically as the process exits. A setuid() to a non-root user does not disable the port access granted by ioperm(), but a fork() does (the child process does not get access, but the parent retains it). ioperm() can only give access to ports 0x000 through 0x3ff; for higher ports, you need to use iopl() (which gives you access to all ports at once). Use the level argument 3 (i.e., iopl(3)) to give your program access to 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 iopl(). See the iopl(2) manual page for details. Accessing the ports
The inb_p(), outb_p(), inw_p(), and 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 #define REALLY_SLOW_IO before you #include <asm/io.h>. These macros normally (unless you #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 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 ioperm(2), iopl(2), and the above macros in reasonably recent releases of the Linux manual page collection. 2.2 An alternate method: /dev/port
Naturally, for this to work your program needs read/write access to /dev/port. This method is probably slower than the normal method above, but does not need compiler optimisation nor ioperm(). It doesn't need root access either, if you give a non-root user or group access to /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 /dev/port to access hard disks, network cards, etc. directly. You cannot use select(2) or 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. 3. Interrupts (IRQs) and DMA access
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 iopl(3), you can disable interrupts simply by calling asm("cli");, and re-enable them with asm("sti");. 4. High-resolution timing
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 sched_setscheduler(2) for details. There is a special kernel that supports hard real time; see http://luz.cs.nmt.edu/~rtlinux/ for more information on this. Sleeping: sleep() and usleep()
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, usleep(3) usually delays somewhat more than the amount that you specify in the parameters, and at least about 10 ms. nanosleep()
For delays <= 2 ms, if (and only if) your process is set to soft real time scheduling (using sched_setscheduler()), nanosleep() uses a busy loop; otherwise it sleeps, just like usleep(). The busy loop uses 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 /usr/include/asm/delay.h) for details on how it works. Delaying with port I/O
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 inb()s from that port to delay. Delaying with assembler instructions
Instruction i386 clock
cycles i486 clock cycles
Clock cycles for Pentiums should be the same as for i486, except that on Pentium Pro/II, 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 nop and 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. xchg %bx, %bx is a safe choice for a delay instruction. To use these, call asm("instruction") in your program. The syntax of the instructions is as in the table above; if you want multiple instructions in a single asm() statement, separate them with semicolons. For example, asm("nop ; nop ; nop ; nop") executes four nop instructions, delaying for four clock cycles on i486 or Pentium processors (or 12 clock cycles on an i386). 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. rdtsc for Pentiums
--------------------------------------------------------------------------------
4.2 Measuring time
If you want your process to get a signal after some amount of time, use setitimer() or alarm(). See the manual pages of the functions for details. 5. Other programming languages
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 /dev/port as described above. 6. Some useful ports
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 Hardware-HOWTO is a good place to start. http://www.hut.fi/Misc/Electronics/ is a good source for more information on connecting devices to computers (and on electronics in general). 6.1 The parallel port
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 http://www.fapo.com/ and 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 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 BASE+1 (Status port) is read-only, and returns the state of the following input signals: Bits 0 and 1 are reserved.
Bit 0 -STROBE (0=high)
1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6,
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 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.) 6.2 The game (joystick) port
Pinout (a 15-pin female D-shell connector on the port): 1,8,9,15: +5 V (power)
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: R = (t - 24.2) / 0.011,
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: Bit 0: AX (status (1=high) of the multivibrator output)
6.3 The serial port
See the termios(3) manual page, the serial driver source code (linux/drivers/char/serial.c), and http://www.easysw.com/~mike/serial/ for more information on programming serial ports on Unix systems. 7. Hints
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 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 ftp://sunsite.unc.edu/pub/Linux/apps/circuits/ (filename pcb-*). 8. Troubleshooting Q1.
A1.
Q2.
A2.
Q3.
A3.
Q4.
A4.
9. Example code
#include <stdio.h>
#define BASEPORT 0x378 /* lp1 */ int main()
/* Set the data signals (D0-7) of the port to all low (0) */
/* Sleep for a while (100 ms) */
/* Read from the status port (BASE+1) and display the result
*/
/* We don't need the ports anymore */
exit(0);
/* end of example.c */
10. Credits
KOLTER ELECTRONIC ist nicht für die Inhalte fremder Seiten verantwortlich. Es gelten ausschließlich die AGB der Firma KOLTER ELECTRONIC. Für die Richtigkeit der Angaben wird keine Gewähr übernommen. Alle Preisangaben sind gewerblich. Das Zahlungsmittel ist EURO. Alle Rechte vorbehalten. (c) copyright H.Kolter |