parapin makes it easy to write C code under Linux that controls individual pins on a PC parallel port. This kind of control is very useful for electronics projects that use the PC's parallel port as a generic digital I/O interface. Parapin goes to great lengths to insulate the programmer from the somewhat complex parallel port programming interface provided by the PC hardware, making it easy to use the parallel port for digital I/O. By the same token, this abstraction also makes Parapin less useful in applications that need to actually use the parallel port as a parallel port (e.g., for talking to a printer).
Parapin has two ``personalities'': it can either be used as a user-space C library, or linked as part of a Linux kernel module. The user and kernel personalities were both written with efficiency in mind, so that Parapin can be used in time-sensitive applications. Using Parapin should be very nearly as fast as writing directly to the parallel port registers manually.
The kernel module personality of parapin is not directly accesible by programs running in userspace. However, a device driver called parapindriver is also included that allows userspace programs to control the parallel port through standard device system calls such as open(), close(), and ioctl().
Parapin provides a simple interface that lets programs use pins of the PC parallel port as digital inputs or outputs. Using this interface, it is easy to assert high or low TTL logic values on output pins or poll the state of input pins. Some pins are bidirectional--that is, they can be switched between input and output modes on the fly.
Starting with version 1.5.0, a standardized approach to support multiple language bindings is in place. The first binding to be released is a Python binding, created by parapin developer Pedro Werneck (pwerneck at users dot sf dot net). As of the inital 1.5.0 release documentation is not yet in place for the language bindings infrastructure, but it is quite simple. Study how the parapin_py binding has been done, and ask on the parapin-internals mailing list for assistance if you would like to work on a binding for another language.
Parapin was written by Jeremy Elson while at the University of Southern California's Information Sciences Institute. This work was supported by DARPA under grant No. DABT63-99-1-0011 as part of the SCADDS project, and was also made possible in part due to support from Cisco Systems. It is freely available under the GNU Library General Public License (LGPL). Up-to-date information about Parapin, including the latest version of the software, can be found at the Parapin Home Page.
The device driver parapindriver was written by Al Hooton (ahooton@users.sourceforge.net), current maintainer of the parapin package, and is also licensed under the GNU Library GeneralPublic License (LGPL). This work was supported by a very understanding spouse and quite a lot of black pekoe tea.
Warning:
Attaching custom electronics to your PC using the parallel port as a digital I/O interface can damage both the PC and the electronics if you make a mistake. If you're using high voltage electronics, a mistake can also cause serious personal injury. Be careful.
If possible, use a parallel port that is on an ISA card, instead of one integrated onto the motherboard, to minimize the expense of replacing a parallel port controller that you destroy.
USE THIS SOFTWARE AT YOUR OWN RISK.
This section will briefly outline some of the basic electrical operating characteristics of a PC parallel port. More detail can be found in the IBM Parallel Port FAQ, written by Zhahai Stewart, or the PC Parallel Port Mini-FAQ by Kris Heidenstrom. Those documents have a lot of detail about registers, bit numbers, and inverted logic--those topics won't be discussed here, because the entire point of Parapin is to let programmers control the port without knowing those gory details.
The PC parallel port usually consists of 25 pins in a DB-25 connector. These pins can interface to the TTL logic of an external device, either as inputs or outputs. Some pins can be used as inputs only, while some can be switched in software, on-the-fly, between input mode and output mode. Note, however, that it can be dangerous for two devices to assert an output value on the same line at the same time. Devices that are using bidirectional pins must agree (somehow) on who is supposed to control the line at any given time.
From the point of view of Parapin, the pinout of the parallel port is as follows:
Pin | Direction |
1 | In/Out |
2-9 | In/Out (see note) |
10 | Input, Interrupt Generator |
11 | Input |
12 | Input |
13 | Input |
14 | In/Out |
15 | Input |
16 | In/Out |
17 | In/Out |
18-25 | Ground |
Pins 2-9--called the parallel port's ``Data Pins''--are ganged. That is, their directions are not individually controllable; they must be either all inputs or all outputs. (The actual values of the pins--on or off--are individually controllable.) Also, some of the oldest parallel ports do not support switching between inputs and outputs on pins 2-9 at all, so pins 2-9 are always outputs. Many PC motherboards allow the user to select the personality of the parallel port in the BIOS. If you need to use pins 2-9 as bidirectional or input pins, make sure your port is configured as a ``PS/2'' port or one of the other advanced port types; ports in SPP (Standard Parallel Port) mode may not support direction switching on pins 2-9.
Pin 10 is special because it can generate interrupts. Interrupt handling is discussed in more detail in Section 9.
Output pins can assert either a TTL high value (between +2.4v and +5.0v), or a TTL low (between 0v and +0.8v). The port can not source much current. Specs for different implementations vary somewhat, but a safe assumption (staying within spec) is that the voltage will be at least 2.5v when drawing up to 2.5mA. In reality you can sometimes get away with using an output pin to power a component that uses 3v or even 5v logic, but the supplied voltage may sag if more than 2.5mA is drawn. Input pins are typically spec'ed to be able to sink up to about 20mA. For a more detailed treatment of the parallel port electronics, see the references above.
Parapin has two personalities: one for plain user-space operation (i.e., linking Parapin with a regular C program), and one for use with Linux kernel modules (kparapin. The two personalities differ in how they are compiled (Section 4) and initialized (Section 5). Also, only the kernel version is capable of servicing parallel-port interrupts that are generated from Pin 10. Otherwise, the two personalities are the same.
The user-space library version of Parapin works very much like any other C library. However, kernel programming differs from user-space programming in many important ways; these details are far beyond the scope of this document. The best reference available is a fantastic book by Rubini and Corbet, Linux Device Drivers. Go buy it from your favorite technical bookstore, or download it as an Open Book from http://www.xml.com/ldd/chapter/book/ A far inferior alternative (but better than nothing) is the free Linux Kernel Hacker's Guide.
If you would like to use the kernel version of parapin from a userspace program without writing your own hardware-specific driver, you can load the parapindriver module after loading kparapin. This device driver exposes the functionality of kparapin through a normal character-device interface (except for interrupt handling). The primary advantages are that access to the parallel port functionality of parapin may be controlled through /dev filesystem permissions, and all interaction is done through standard device-oriented system calls such as open(), close(), and ioctl().
In Parapin, there are five basic operations possible:
Each of these functions will be discussed in later sections.
Most of the functions take a pins argument--a single integer that is actually a bitmap representing the set of pins that are being changed or queried. Parapin's header file, parapin.h, defines a number of constants of the form ``LP_PINnn'' that applications should use to specify pins. The nn represents the pin number, ranging from 01 to 17. For example, the command
set_pin(LP_PIN05);turns on pin number 5 (assuming that pin is configured as an output pin; the exact semantics of set_pin are discussed in later sections). C's logical-or operator can be used to combine multiple pins into a single argument, so
set_pin(LP_PIN02 | LP_PIN14 | LP_PIN17);turns on pins 2, 14, and 17.
Usually, it is most convenient to use #define statements to give pins logical names that have meaning in the context of your application. The documentation of most ICs and other electronics gives names to I/O pins; using those names makes the most sense. For example, a National Semiconductor ADC0831 Analog-to-Digital converter has four I/O pins called VCC (the supply voltage), CS (chip select), CLK (clock), and D0 (data output 0). A fragment of code to control this chip might look something like this:
#include "parapin.h" #define VCC LP_PIN02 #define CS LP_PIN03 #define CLK LP_PIN04 #define D0 LP_PIN10 /* input pin */ ... clear_pin(CS); /* pull Chip Select low, tell it to acquire */ ... set_pin(CLK); /* clock it */ clear_pin(CLK);
This method has a number of advantages. First, it makes the code more readable to someone who has the ADC0831 documentation in hand. Second, the #define statements summarize the mappings of IC pins to parallel port pins, making it easier to build the physical interface. Also, if the physical interface changes, the #defines make it easy to remap parallel port pins to new IC pins.
Parapin's header file also provides these constants in an array called LP_PIN. This array is useful in some contexts where the pin number is being specified with a variable instead of a constant. For example:
/* runway lights: light up pins 1 through 9 in sequence */ while (1) { for (i = 1; i <= 9; i++) { set_pin(LP_PIN[i]); usleep(200); clear_pin(LP_PIN[i]); } }
Code such as the above fragment would be much more complex if the
programmer were forced to use constants. There is a direct
correspondence between indices in the array and pin numbers;
accordingly, only indices from 1 to 17 should be used. Programs
should never reference LP_PIN[0]
or LP_PIN[i]
where i > 17.
Note: Please see the file INSTALL, located in the root directory of the parapin distribution, for details on building and installing parapin on your system.
The user-space version of Parapin is compiled and linked very much like any other C library. If you installed Parapin on your system using ``make install'', the library (libparapin.a) was probably installed in /usr/local/lib. The header file with the library's function prototypes and other definitions, parapin.h, is in the location given in the parapin Makefile as $[IMAGE png]/include.
Important: Parapin must be compiled with gcc using compiler optimization of -O or better. This is because Parapin uses the inb and outb functions that exist only with compiler optimization.
To use the library, first make sure to #include parapin.h in your C source file. When linking, add -lparapin along with any other libraries you might be using.
As with the C library version, kernel source code that uses Parapin
must #include parapin.h. Make sure that __KERNEL__
is
defined before the include statement.
Important: Parapin must be compiled with gcc using compiler optimization of -O or better. This is because Parapin uses the inb and outb functions that exist only with compiler optimization.
Linking is a little more complicated. As with any other interdependent kernel modules, there are actually two ways of using parapin with your Linux device driver:
The Makefiles that come with Parapin compiles Parapin into its own independent module, called kparapin.o. This module can be inserted into the kernel using modprobe (as described in the first method). If you'd rather use the second method, and link Parapin directly with your device driver module instead, do the following:
-D__KERNEL__
directive, but without the
-DMODULE directive.
If your driver only consists of one other source file, and you have never linked multiple .o's together into a single kernel module, it's easy. First, compile each .c file into a .o using gcc -c (the same as with normal C programs that span multiple source files); then, link all the .o's together into one big .o using ld -i -o final-driver.o component1.o component2.o component3.o ....
Important note: Whichever method you use, Parapin also requires functions provided by the standard Linux kernel module parport. Make sure you insert the parport module with the modprobe command before inserting any modules that use Parapin; otherwise, you will get unresolved symbols.
When building a userspace program that will make use of the parapindriver interface to kparapin, you must include parapindriver.h. This header file defines the device-specific ioctl commands used to communicate with the device driver. It also includes parapin.h, so your program can make use of all the ``LP_*'' constants. The parapindriver system calls take arguments using these constants and pass them unchanged down to the kparapin routines.
Before any other Parapin functions can be used, the library must be initialized. Parapin will fail ungracefully if any functions are called before its initialization. This is because the library's other functions do not check that the library is in a sane state before doing their jobs. This was an intentional design decision because the maximum possible efficiency is required in many applications that drive the parallel port at high speeds. The goal is for Parapin to be almost as fast as directly writing to registers.
The exact initialization method varies, depending on if Parapin is being used as a C library, as a kernel module, or through the parapindriver device interface.
C library initialization is performed using the function
int pin_init_user(int lp_base);whose single argument, lp_base, specifies the base I/O address of the parallel port being controlled. Common values of lp_base are 0x378 for LPT1, or 0x278 for LPT2; parapin.h defines the constants LPT1 and LPT2 to these values for convenience. However, the exact port address may vary depending on the configuration of your computer. If you're unsure, the BIOS status screen displayed while the computer boots usually shows the base addresses of all detected parallel ports.
Programs using Parapin must be running as root when they are initialized. Initialization of the library will fail if the process is owned by any user other than the super-user because Parapin has to request the right to write directly to hardware I/O registers using the ioperm function. The security-conscious programmer is encouraged to drop root privileges using setuid after a successful call to pin_init_user.
The return value of pin_init_user will be 0 if initialization is successful, or -1 if there is an error. Applications must not call other Parapin functions if initialization fails. Failed initialization is usually because the program is not running as root.
No shutdown function needs to be called in the C library version of Parapin.
Initialization and shutdown in the kernel flavor of Parapin is done using the following two functions:
int pin_init_kernel(int lpt, void (*irq_func)(int, void *, struct pt_regs *)); void pin_release();
The first function is not as intimidating as it looks. Its first argument, lpt, is the parallel port number that you want to control. The number references the kernel's table of detected parallel ports; 0 is the first parallel port and is a safe guess as a default.
The second argument, irq_func, is a pointer to a callback function to be used for servicing interrupts generated by the parallel port. This argument may be NULL if the driver does not need to handle interrupts. Details about interrupt handling are discussed in Section 9.
pin_init_kernel will return 0 on success, or a number less than 0 on error. The return value will be a standard errno value such as -ENODEV, suitable for passing up to higher layers. If Parapin initialization fails, the driver must not call Parapin's other functions. As described earlier, this requirement is not enforced for efficiency reasons.
When a driver is finished controlling the parallel port using Parapin, it must call pin_release. The state of the parallel port's interrupt-enable bit will be restored to the state it was in at the time pin_init_kernel was originally called.
Parapin's pin_init_kernel and pin_release functions work with the Linux kernel's standard parport facility; as noted above, the parport module must be loaded along with any module that uses Parapin. When initialized, Parapin will register itself as a user of the parallel port, and claim exclusive access to that port. This means no other processes will be allowed to use the parallel port until pin_release is called.
Before the parapindriver device interface may be used, an entry in /dev must be created that corresponds to the device driver module loaded in to the kernel. The easiest way to do this is to execute the initialization script ppdrv_load.sh, included in the parapin distribution, late in the normal boot process with root privileges. A common way to do this is to run ppdrv_load.sh at the end of /etc/rc.local. Note that ppdrv_load.sh was installed in /usr/local/bin if you built the modulesinstall target in the distribution Makefile.
The ppdrv_load.sh script performs the following steps, if you find that you need to do things manually or in some other way:
These steps only need to be done once each time the machine is booted. If there are problems, either insmod(8) or parapindriver will display errors on the console and through the syslog mechanism (if you have it running).
Once parapindriver is successfully loaded, and a corresponding /dev entry is in place, initialization and shutdown of the parallel port are easy. To initialize the parapin system, just call open(2) on the /dev entry as you would any other device file:
int device; device = open("/dev/<device-name>", 0); if (device < 0) { fprintf(stderr, "device open failed\n"); exit(-1); }
Standard system errors with negative values will be returned from open(2) if it fails. If the device is already open, the return value will be -EBUSY.
To shutdown the parapin system you simply have to call close(2):
close(device);
See the example file ppdrv-test.c for further information.
As shown in the table in Section 2, most of the pins on modern parallel ports can be configured as either inputs or outputs. When Parapin is initialized, it is safest to assume that the state of all these switchable pins is undefined. That is, they must be explicitly configured as outputs before values are asserted, or configured as inputs before they are queried.
As mentioned earlier, Pins 2-9 can not be configured independently. They are always in the same mode: either all input or all output. Configuring any pin in that range has the effect of configuring all of them. The constant LP_DATA_PINS is an alias that refers to all the pins in this range (2-9).
The other switchable pins (Pins 1, 14, 16, and 17) may be independently configured. Pins 10, 11, 12, 13, and 15 are always inputs and can not be configured.
It is also worth reiterating that having two devices both try to assert (output) a value on the same pin at the same time can be dangerous. The results are undefined and are often dependent on the exact hardware implementation of your particular parallel port. This situation should be avoided. The protocol spoken between the PC and the external device you're controlling to should always agree who is asserting a value on a pin and who is reading that pin as an input.
In both the userspace C library version, and the kernel version, pins are configured using one of the following three functions:
void pin_input_mode(int pins); void pin_output_mode(int pins); void pin_mode(int pins, int mode);
The pins argument of all three functions accepts the LP_PINnn constants described in Section 3. The pin_mode function is just provided for convenience; it does the same thing as the pin_input and pin_output functions. Its mode argument takes one of the two constants LP_INPUT or LP_OUTPUT. Calling pin_mode(pins, LP_INPUT) is exactly the same as calling pin_input(pins).
Examples:
pin_input_mode(LP_PIN01); /* Pin 1 is now an input */ pin_output_mode(LP_PIN14 | LP_PIN16); /* Pins 14 and 16 are now outputs */ pin_mode(LP_PIN02, LP_INPUT); /* Pins 2 through 9 are now ALL inputs */ pin_mode(LP_PIN01 | LP_PIN02, LP_OUTPUT); /* Pin 1, and Pins 2-9 are ALL outputs */ pin_input_mode(LP_DATA_PINS); /* The constant LP_DATA_PINS is an alias for Pins 2-9 */
When using the parapindriver device interface, all functionality related to pins on the parallel port is invoked via the ioctl(2) system call. The ioctl commands for parapindriver are defined in the parapindriver.h header file. The two used to set pins to be either output pins or input pins are ``PPDRV_IOC_PINMODE_OUT'' and ``PPDRV_IOC_PINMODE_IN'', respectively.
Examples:
ioctl(device, PPDRV_IOC_PINMODE_OUT, LP_PIN01 | LP_PIN02); ioctl(device, PPDRV_IOC_PINMODE_IN, LP_PIN11);
Note that the arguments to these ioctl calls use exactly the same constants as are used by the userspace and kernel versions of parapin itself. The rules for combining constants using bitwise 'or' are allowed as well. These constants are made available to your program because the parapindriver.h header file also includes the parapin.h header file to define the constant values.
Return values from these ioctl calls are the same as those defined for the corresponding functions in kparapin. There is one additional return value from an ioctl call in to parapindriver, the value -ENOTTY. This indicates that an invalid ioctl command value was passed down.
Once Parapin has been initialized (Section 5), and pins have been configured as output pins (Section 6), values can be asserted on those pins using the following functions:
void set_pin(int pins); void clear_pin(int pins); void change_pin(int pins, int state);
The pins argument of all three functions accepts the LP_PINnn constants described in Section 3. set_pin turns pins on, electrically asserting high TTL values. clear_pin turns pins off, electrically asserting low TTL values. The convenience function change_pin does the same thing as set_pin and clear_pin; its state argument takes one of the constants LP_SET or LP_CLEAR. Calling change_pin(pins, LP_SET) has exactly the same effect as calling set_pin(pins).
These three functions will only have effects on pins that were previously configured as output pins as described in Section 6. Attempting to assert a value on an input pin will have no effect.
Note that while the direction of Pins 2-9 must be the same at all times (i.e., either all input or all output), the actual values of these pins are individually controllable when they are in output mode.
Examples:
pin_output_mode(LP_PIN01|LP_DATA_PINS|LP_PIN14|LP_PIN16|LP_PIN17); /* All these pins are now in output mode */ set_pin(LP_PIN01 | LP_PIN04 | LP_PIN07 | LP_PIN14 | LP_PIN16); /* Pins 1, 4, 7, 14, and 16 are all on */ clear_pin(LP_PIN01 | LP_PIN16); /* Pins 1 and 16 are now off */ change_pin(LP_PIN01 | LP_PIN02, some_integer ? LP_SET : LP_CLEAR); /* Pins 1 and 2 are now off if and only if some_integer == 0 */
Setting pin state through the device driver interface follows all the same rules as described above. The ioctl commands used are ``PPDRV_IOC_PINSET'' and ``PPDRV_IOC_PINCLEAR''.
Examples:
ioctl(device, PPDRV_IOC_PINSET, LP_PIN01 | LP_PIN02); ioctl(device, PPDRV_IOC_PINCLEAR, LP_PIN01);
Return values from these ioctl calls are the same as those defined for the corresponding functions in kparapin. There is one additional return value from an ioctl call in to parapindriver, the value -ENOTTY. This indicates that an invalid ioctl command value was passed down.
Once Parapin has been initialized (Section 5), and pins have been configured as input pins (Section 6), the value being asserted by the ``far end'' can be queried using the following function:
int pin_is_set(int pins);
Any number of pins can be queried simultaneously. The pins argument accepts the LP_PINnn constants described in Section 3. The return value is an integer in the same format. Any pins that are set (electrically high) will be set in the return value; pins that are clear (electrically low) will be clear in the return value.
Pins may only be queried if:
Any query to an output pin will always return a value indicating that the pin is clear. In other words, this function can not be used to determine what value was previously asserted to an output pin.
Examples:
pin_input_mode(LP_PIN01 | LP_DATA_PINS | LP_PIN17); /* Pins 1, 2-9, and 17 are now in input mode, along with Pins 10, 11, 12, 13, and 15, which are always inputs */ /* check the state of pin 1 */ printf("Pin 1 is %s!\n", pin_is_set(LP_PIN01) ? "on" : "off"); /* check pins 2, 5, 10, and 17 - demonstrating a multiple query */ int result = pin_is_set(LP_PIN02 | LP_PIN05 | LP_PIN10 | LP_PIN17); if (!result) printf("Pins 2, 5, 10 and 17 are all off\n"); else { if (result & LP_PIN02) printf("Pin 2 is high!\n"); if (result & LP_PIN05) printf("Pin 5 is high!\n"); if (result & LP_PIN10) printf("Pin 10 is high!\n"); if (result & LP_PIN17) printf("Pin 17 is high!\n"); }
Querying pin state through the device driver interface follows all the same rules as described above. The ioctl command used is ``PPDRV_IOC_PINGET''.
Examples:
int value; value = ioctl(device, PPDRV_IOC_PINGET, DATA);
Return values from these ioctl calls are the same as those defined for the corresponding functions in kparapin. There is one additional return value from an ioctl call in to parapindriver, the value -ENOTTY. This indicates that an invalid ioctl command value was passed down.
The kernel-module version of Parapin lets parallel port drivers catch interrupts generated by devices connected to the parallel port. Most hardware generates interrupts on the rising edge1 of the input to Pin 10.
Before Parapin's interrupt-handling can be used, the Linux kernel itself must be configured to handle parallel port interrupts. Unlike most other hardware devices, the kernel does not detect or claim the parallel port's interrupts by default. It is possible to manually enable kernel IRQ handling for the parallel port by writing the interrupt number into the special file /proc/parport/[IMAGE png]/irq, where [IMAGE png] is the parallel port number. For example, the following command tells the kernel that parport0 is using IRQ 7:
echo 7 > /proc/parport/0/irqIf parallel port support is being provided to the kernel through modules, it is also possible to configure the IRQ number as an argument to the parport_pc module when it is loaded. For example:
insmod parport insmod parport_pc io=0x378 irq=7Note that both the io and irq arguments are required, even if the parallel port is using the default I/O base address of 0x378.
The actual interrupt number used by the kernel (7 in the examples above) must, of course, match the interrupt line being used by the hardware. The IRQ used by the parallel port hardware is usually configured in the BIOS setup screen on modern motherboards that have built-in parallel ports. Older motherboards or stand-alone ISA cards usually have jumpers or DIP switches for configuring the interrupt number. The typical assignment of interrupts to parallel ports is as follows:
Port | Interrupt |
LPT1 | 7 |
LPT2 | 5 |
These are reasonable defaults if the actual hardware configuration is not known.
As described in Section 5, the pin_init_kernel() function allows installation of an interrupt handler function by passing a pointer to the handler as the second argument. A NULL value for this parameter means that interrupts are disabled. A non-NULL value should be a pointer to a callback function that has the following prototype:
void my_interrupt_handler(int irq, void *dev_id, struct pt_regs *regs);
If and only if a pointer to such a handler function is passed to pin_init_kernel, the following functions can then be used to turn interrupts on and off:
pin_enable_irq(); pin_disable_irq();
Parapin turns parallel port interrupts off by default. That is, no interrupts will be generated until after a call to pin_init_kernel() to install the interrupt handler, and a subsequent call to pin_enable_irq();
Interrupts must not be enabled and disabled from within the interrupt handler itself. In other words, the interrupt handling function passed to pin_init_kernel() must not call pin_enable_irq() or pin_disable_irq(). However, the handler function does not need to be reentrant: the IRQ line that causes an interrupt handler to run is automatically disabled by the Linux kernel while the interrupt's handler is running. There are other important restrictions on kernel programming in general and interrupt-handler writing in particular; these issues are beyond the scope of this document. For more details, the reader is referred to the Linux kernel programming guides mentioned in Section 3.
Some PC hardware generates a spurious parallel port interrupt immediately after the parallel port's interrupts are enabled (perhaps to help auto-detection of the IRQ number). Parapin includes a workaround that prevents this interrupt from being delivered. This is done to ensure consistent interrupt behavior across all platforms.
Several programs with example code using Parapin are included in the Parapin distribution. Look in the /examples subfolder.
Currently, Parapin only supports a single parallel port at a time. It is not possible to have a single instance of the library manage multiple instances of a parallel port. This may be a problem for software that is simultaneously trying to control multiple parallel ports. Someday, I may fix this, but it will make the interface messier (a port handle will have to be passed to every function along with pin specifiers).
The C-library version of Parapin should probably do better probing of the parallel port, but any desire to do this was limited because it replicates what is already done by the Linux kernel and we expect that most production use of parapin will be with the kernel version anyway.
If you have bug reports, patches, suggestions, or any other comments, please feel free to visit the project page at http://parapin.sourceforge.net/. You may also contact the current maintainer, Al Hooton, directly at ahooton@users.sourceforge.net.
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.71)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split 0 -ascii_mode -noauto_link -rootdir /tmp/parapintemp parapin
The translation was initiated by Al Hooton on 2007-01-14