閱讀970 返回首頁    go 技術社區[雲棲]


linux下並口相關(x86)

一 用戶空間操作並口

linux的x86平台下在用戶空間操作並口有兩種方式,要麼寫成驅動,用戶應用程序通過IO設備模塊實現對並口讀寫,還有就是直接在用戶空間I/O內存地址

PC25針並口. 接口定義如下:

方向

2,3,4,5,6,7,8,9

雙向

1,14,16,17

輸出

10,11,12,13,15

輸入

18,19,20,21,22,23,24,25

 

並口編程基礎


這些引腳,其實就是I/O口。而且是可以控製的,以軟件的方式,非常簡單。
在計算機內部,對應的寄存器如下:
0x378是8bit數據寄存器的地址,對應並口針的順序是9,8,7,6,5,4,3,2
0x379是狀態寄存器,11,10,12,13,15 - - -
0x37A是控製寄存器,- - - 並口中斷允許位,17,16,14,1
也就是說PC的0x378可以用來做輸入和輸出,0x37A的低4位可以用來輸出,0x379是輸入。這樣就可以用並口模擬很多時許,比如IIC、JTAG、SPI等等。

例子:用戶空間直接I/O 0x378地址的方法如下

  1.  #include <stdio.h>  
  2. #include <sys/io.h>  
  3. int main()  
  4. {  
  5.      /*向係統申請0x000~0x3FF的地址空間控製權*/  
  6.         ioperm(0x000,0x3FF,1);          
  7.         /*向0x378地址寫入全1*/  
  8.         outb(0xff,0x378);  
  9.         /*向係統交回0x000~0x3FF的地址空間控製權*/  
  10.         ioperm(0x000,0x3FF,0);  
  11.         return 0;  
  12. }  

二 Linux Parport programming

Hardware view of the parport

The parallel port (or printer port, IEEE1284 standard) has 8 data lines, 5 status lines (/ACK, BUSY, PAPER_OUT, SELECT and ERROR) and 4 control lines (/STROBE, /INIT, /SELECT_IN and /AUTO_FEED).
Pin Function Dir
1 /STROBE W
2..9 DATA Bit0..7 R/W
10 ACK R
11 /BUSY R
12 PAPEROUT R
13 SELECT R
14 /AUTOFD W
15 ERROR R
16 INIT W
17 /SELECT W
18..25 GND -
Signals with a / prefix are low-active (i.e. writing logic 1 to them results in 0 Volt output, writing logic 0 results in 5 volts on the output).

Access via raw IO (not recommended)

This has many disadvantages (it's not portable, needs root privileges, ...) and is mentioned here only for educational purpose.
This will under NO CIRCUMSTANCES work with USB-to-Parallel converters.
This is ONLY for ISA-Bus based 8255-compatible devices.
Unless you are developing a device driver in kernel space, do NOT use it.
#include <stdio.h>#include <sys/io.h>#define BASEADDR 0x378int main(int argc, char **argv){int i;if(ioperm(BASEADDR, 4, 1) < 0) {fprintf(stderr, "could not get i/o permission. are you root ?\n");return 5;}for(i=0; i < 256; i++) {outb(i, BASEADDR);}ioperm(BASEADDR, 4, 0);return 0;}

Access via /dev/lpX

This allows writing the 8 data bits (but not reading them) according to the centronics standard, i.e. after writing of the data, a pulse on STROBE is triggered and an acknowledgement pulse on ACK is expected afterwards. Also the BUSY line might have influence on this. You can also read all other status lines.
A huge advantage is also, that this (in contrary of the raw i/o method) will also work with USB-to-Parport converters.
#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <sys/ioctl.h>#include <linux/lp.h>#define DEVICE "/dev/lp0"int main(int argc, char **argv){int fd, k;if((fd=open(DEVICE, O_WRONLY)) < 0) {fprintf(stderr, "can not open %s\n", DEVICE);return 10;}ioctl(fd, LPRESET); /* generate RESET pulse */if(ioctl(fd, LPGETSTATUS, &k) < 0) {fprintf(stderr, "getstatus ioctl failed\n");return 10;}k^=LP_PBUSY; /* invert PBUSY */printf("BUSY = %s\n", ((k & LP_PBUSY) == LP_PBUSY)?"HI":"LO");printf("POUT = %s\n", ((k & LP_POUTPA) == LP_POUTPA)?"HI":"LO");printf("ERR = %s\n", ((k & LP_PERRORP) == LP_PERRORP)?"HI":"LO");printf("SEL = %s\n", ((k & LP_PSELECD) == LP_PSELECD)?"HI":"LO");close(fd);return 0;}

Full access via /dev/parportX

This gives you full control of the parallel port: You can read and write all data lines, set all control lines (including STROBE) and read all status lines (including ACK).
Prefer this method if you are going to read and write data to any homebrew electronics (because here, e.g. the STOBE line is NOT set automatically and the driver also does NOT wait for an ACK pulse).
#include <stdio.h>#include <fcntl.h> /* open() */#include <sys/types.h> /* open() */#include <sys/stat.h> /* open() */#include <asm/ioctl.h>#include <linux/parport.h>#include <linux/ppdev.h>#define DEVICE "/dev/parport0"int main(int argc, char **argv){struct ppdev_frob_struct frob;int fd;int mode; if((fd=open(DEVICE, O_RDWR)) < 0) {fprintf(stderr, "can not open %s\n", DEVICE);return 10;}if(ioctl(fd, PPCLAIM)) {perror("PPCLAIM");close(fd);return 10;}/* put example code here ... */ioctl(fd, PPRELEASE);close(fd);return 0;}/* trivial example how to write data */int write_data(int fd, unsigned char data){return(ioctl(fd, PPWDATA, &data));}/* example how to read 8 bit from the data lines */int read_data(int fd){int mode, res;unsigned char data;mode = IEEE1284_MODE_ECP;res=ioctl(fd, PPSETMODE, &mode); /* ready to read ? */mode=255;res=ioctl(fd, PPDATADIR, &mode); /* switch output driver off */printf("ready to read data !\n");fflush(stdout);sleep(10);res=ioctl(fd, PPRDATA, &data); /* now fetch the data! */printf("data=%02x\n", data);fflush(stdout);sleep(10);data=0;res=ioctl(fd, PPDATADIR, data);return 0;}/* example how to read the status lines. */int status_pins(int fd){int val;ioctl(fd, PPRSTATUS, &val);val^=PARPORT_STATUS_BUSY; /* /BUSY needs to get inverted */printf("/BUSY = %s\n",((val & PARPORT_STATUS_BUSY)==PARPORT_STATUS_BUSY)?"HI":"LO");printf("ERROR = %s\n",((val & PARPORT_STATUS_ERROR)==PARPORT_STATUS_ERROR)?"HI":"LO");printf("SELECT = %s\n",((val & PARPORT_STATUS_SELECT)==PARPORT_STATUS_SELECT)?"HI":"LO");printf("PAPEROUT = %s\n",((val & PARPORT_STATUS_PAPEROUT)==PARPORT_STATUS_PAPEROUT)?"HI":"LO");printf("ACK = %s\n",((val & PARPORT_STATUS_ACK)==PARPORT_STATUS_ACK)?"HI":"LO");return 0;}/* example how to use frob ... toggle STROBE on and off without messing around the other lines */int strobe_blink(int fd){struct ppdev_frob_struct frob;frob.mask = PARPORT_CONTROL_STROBE; /* change only this pin ! */while(1) {frob.val = PARPORT_CONTROL_STROBE; /* set STROBE ... */ioctl(fd, PPFCONTROL, &frob);usleep(500);frob.val = 0; /* and clear again */ioctl(fd, PPFCONTROL, &frob);usleep(500);}}

List of ioctl()s for /dev/parportX access

# Data registerioctl(fd, PPRDATA, &r); /* read the data register */ioctl(fd, PPWDATA, &r); /* write the data register */# control registerioctl(fd, PPRCONTROL, &r); /* read the control register */ioctl(fd, PPWCONTROL, &r); /* write the control register */ioctl(fd, PPFCONTROL, &frob); /* "atomic" modify (read - write at once) */# status registerioctl(fd, PPRSTATUS, &r); /* read the status register */

Setting direction for Databits

For the databits it can be selected whether they are used for reading (they are not being driven, "tristate"-mode) or writing (they are being driven).
It seems at least on my PC the datalines are not really "tristate", there seem to be internal pull-up resistors because the datapins have 5 volts level when not being driven.
Unfortunately, you also can not set the direction for each bits separate: Either all 8 bits are driven or all 8 bits are used for reading.
This sets the 8 data lines as output:
mode=0;ioctl(fd, PPDATADIR, &mode);
while this sets the 8 data lines as input:
mode=255; /* any other value than zero */ioctl(fd, PPDATADIR, &mode);

三 Writing a parport Device Driver

What parport does

One purpose of parport is to allow multiple device drivers to use the same parallel port. It does this by sitting in-between the port hardware and the parallel port device drivers. When a driver wants to talk to its parallel port device, it calls a function to "claim" the port, and "releases" the port when it is done.

Another thing that parport does is provide a layer of abstraction from the hardware, so that device drivers can be architecture-independent in that they don't need to know which style of parallel port they are using (those currently supported are PC-style, Archimedes, and Sun Ultra/AX architecture).

Interface to parport

Finding a port

To obtain a pointer to a linked list of parport structures, use the parport_enumerate function. This returns a pointer to a struct parport, in which the member next points to the next one in the list, or is NULL at the end of the list.

This structure looks like (from linux/include/linux/parport.h):

/* A parallel port */
struct parport {
        unsigned long base;     /* base address */
        unsigned int size;      /* IO extent */
        char *name;
        int irq;                /* interrupt (or -1 for none) */
        int dma;
        unsigned int modes;

        struct pardevice *devices;
        struct pardevice *cad;  /* port owner */
        struct pardevice *lurker;
        
        struct parport *next;
        unsigned int flags; 

        struct parport_dir pdir;
        struct parport_device_info probe_info; 

        struct parport_operations *ops;
        void *private_data;     /* for lowlevel driver */
};

Device registration

The next thing to do is to register a device on each port that you want to use. This is done with the parport_register_device function, which returns a pointer to astruct pardevice, which you will need in order to use the port.

This structure looks like (again, from linux/include/linux/parport.h):

/* A parallel port device */
struct pardevice {
        char *name;
        struct parport *port;
        int (*preempt)(void *);
        void (*wakeup)(void *);
        void *private;
        void (*irq_func)(int, void *, struct pt_regs *);
        int flags;
        struct pardevice *next;
        struct pardevice *prev;
        struct parport_state *state;     /* saved status over preemption */
};

There are two types of driver that can be registered: "transient" and "lurking". A lurking driver is one that wants to have the port whenever no-one else has it. PLIP is an example of this. A transient driver is one that only needs to use the parallel port occasionally, and for short periods of time (the printer driver and Zip driver are good examples).

Claiming the port

To claim the port, use parport_claim, passing it a pointer to the struct pardevice obtained at device registration. If parport_claim returns zero, the port is yours, otherwise you will have to try again later.

A good way of doing this is to register a "wakeup" function: when a device driver releases the port, other device drivers that are registered on that port have their "wakeup" functions called, and the first one to claim the port gets it. If the parport claim fails, you can go to sleep; when the parport is free again, your wakeup function can wake you up again. For example, declare a global wait queue for each possible port that a device could be on:

static struct wait_queue * wait_q[MAX_MY_DEVICES];

The wakeup function looks like:

void my_wakeup (void * my_stuff)
{
	/* this is our chance to grab the parport */

        struct wait_queue ** wait_q_pointer = (struct wait_queue **) my_stuff;

        if (!waitqueue_active (wait_q_pointer))
                return; /* parport has messed up if we get here */

        /* claim the parport */
        if (parport_claim (wait_q_pointer)))
                return; /* Shouldn't happen */

        wake_up(wait_q_pointer);
}

Then, in the initialisation code, do something like:

struct pardevice * pd[MAX_MY_DEVICES];

int my_driver_init (void)
{
  struct parport * pp = parport_enumerate ();

  int count = 0;

  while (pp) { /* for each port */

    /* set up the wait queue */
    init_waitqueue (&wait_q[count]);

    /* register a device */
    pd[count] = parport_register_device (pp, "Me",
	/* preemption function */	 my_preempt,
	/* wakeup function */		 my_wakeup,
	/* interrupt function */	 my_interrupt,
	/* this driver is transient */	 PARPORT_DEV_TRAN,
	/* private data */		 &wait_q[count]);

    /* try initialising the device */
    if (init_my_device (count) == ERROR)
      /* failed, so unregister */
      parport_unregister_device (pd[count]);
    else if (++count == MAX_MY_DEVICES)
      /* can't handle any more devices */
      break;
  }

Then a typical thing to do to obtain access to the port would be:

	if (parport_claim (pd[n]))
		/* someone else had it */
		sleep_on (&wait_q[n]);	/* will wake up when wakeup */
					/* function called */

	/* (do stuff with the port here) */

	/* finished with the port now */
	parport_release (pd[n]);

Using the port

Operations on the parallel port can be carried out using functions provided by the parport interface:

struct parport_operations {
        void (*write_data)(struct parport *, unsigned int);
        unsigned int (*read_data)(struct parport *);
        void (*write_control)(struct parport *, unsigned int);
        unsigned int (*read_control)(struct parport *);
        unsigned int (*frob_control)(struct parport *, unsigned int mask, unsigned int val);
        void (*write_econtrol)(struct parport *, unsigned int);
        unsigned int (*read_econtrol)(struct parport *);
        unsigned int (*frob_econtrol)(struct parport *, unsigned int mask, unsigned int val);
        void (*write_status)(struct parport *, unsigned int);
        unsigned int (*read_status)(struct parport *);
        void (*write_fifo)(struct parport *, unsigned int);
        unsigned int (*read_fifo)(struct parport *);

        void (*change_mode)(struct parport *, int);

        void (*release_resources)(struct parport *);
        int (*claim_resources)(struct parport *);

        unsigned int (*epp_write_block)(struct parport *, void *, unsigned int);
        unsigned int (*epp_read_block)(struct parport *, void *, unsigned int);

        unsigned int (*ecp_write_block)(struct parport *, void *, unsigned int, void (*fn)(struct parport *, void *, unsigned int), void *);
        unsigned int (*ecp_read_block)(struct parport *, void *, unsigned int, void (*fn)(struct parport *, void *, unsigned int), void *);

        void (*save_state)(struct parport *, struct parport_state *);
        void (*restore_state)(struct parport *, struct parport_state *);

        void (*enable_irq)(struct parport *);
        void (*disable_irq)(struct parport *);
        int (*examine_irq)(struct parport *);

        void (*inc_use_count)(void);
        void (*dec_use_count)(void);
};

However, for generic operations, the following macros should be used (architecture-specific parport implementations may redefine them to avoid function call overheads):

/* Generic operations vector through the dispatch table. */
#define parport_write_data(p,x)            (p)->ops->write_data(p,x)
#define parport_read_data(p)               (p)->ops->read_data(p)
#define parport_write_control(p,x)         (p)->ops->write_control(p,x)
#define parport_read_control(p)            (p)->ops->read_control(p)
#define parport_frob_control(p,m,v)        (p)->ops->frob_control(p,m,v)
#define parport_write_econtrol(p,x)        (p)->ops->write_econtrol(p,x)
#define parport_read_econtrol(p)           (p)->ops->read_econtrol(p)
#define parport_frob_econtrol(p,m,v)       (p)->ops->frob_econtrol(p,m,v)
#define parport_write_status(p,v)          (p)->ops->write_status(p,v)
#define parport_read_status(p)             (p)->ops->read_status(p)
#define parport_write_fifo(p,v)            (p)->ops->write_fifo(p,v)
#define parport_read_fifo(p)               (p)->ops->read_fifo(p)
#define parport_change_mode(p,m)           (p)->ops->change_mode(p,m)
#define parport_release_resources(p)       (p)->ops->release_resources(p)
#define parport_claim_resources(p)         (p)->ops->claim_resources(p)

Releasing the port

When you have finished the sequence of operations on the port that you wanted to do, use release_parport to let any other devices that there may be have a go.

Unregistering the device

If you decide that you don't want to use the port after all (perhaps the device that you wanted to talk to isn't there), use parport_unregister_device.

Something to bear in mind: interrupts

Parallel port devices cannot share interrupts. The parport code shares a parallel port among different devices by means of scheduling - only one device has access to the port at any one time. If a device (a printer, say) is going to generate an interrupt, it could do it when some other driver (like the Zip driver) has the port rather than the printer driver. That would lead to the interrupt being missed altogether. For this reason, drivers should poll their devices unless there are no other drivers using that port. To see how to do this, you might like to take a look at the printer driver.


四 linux/Documentation/parport.txt

   https://www.mjmwired.net/kernel/Documentation/parport.txt

最後更新:2017-04-03 20:19:09

  上一篇:go Linq中兩種更新操作
  下一篇:go Windows的定時任務(Schedule Task)設置