原创 如何不用访问地址的方式来编写并口程序 ★★★★★

2012-1-12 18:36 2790 16 13 分类: MCU/ 嵌入式

兄弟我在精华发现了一些关于并口打印的文章,但是都是用inb outb操作的,不知道各路高人能否给小弟一个用open write ioctl read close控制的判断状态并读写的例子,尤其是ioctl,该怎么使用才能判断出并口的状态,如未准备好,缺纸等等

 还有一个问题是如果用上面的函数,那对并口操作与对串口的操作除了在ioctl上有所不同外,还有什么地方不一样呢,我对这方面很陌生,对于并口,用设置那些类似串口操作时的什么波特率,校验位等等吗?还是只需要open("/dev/lp0",O_RDWR | O_NOCTTY) 就ok了,而不用再设置什么了??


竟然没有一个人回复,真悲哀!
 这个问题我在别的论坛也问了,那里多少给了点建议,这里却一点动静也没有,我把我自己这两天摸索的心得写出来吧,这个东东我在昨天给我回复的那个论坛发了,想了想,决定在这里也发一个吧,但是真的不希望看到这里死气沉沉的样子

 声明:所有东东都是我自己的理解,又告人觉得不对,希望尽量批评  所有原码,函数名,.h文件名都是在Red Hat Linux上的东东 其它平台可能会有所变化,程序是完全编译通过并正常运行

 首先,用C语言操作并口,本身就存在两种方法
 一种是:ioperm inb outb
 另外一种是:open read write ioctl

 第一种方法是操作并口的内存地址
 第二种方法是对文件描述符操作(是人就知道)

 先说第一种
 ioperm是获取地址操作权限的函数,使用前后必须两次调用,
 类似于open 和 close 但是他仅仅是获得权限,且一旦写入程序
 ,该程序就必须用root用户来运行,无论你对其他用户如何授权,其他用户就是调用不了该函数(该点理解给予我的实践,希望有异议者多多批评)。
 inb是读取寄存器种数据的,outb就是写入,但是对于并口来说,有些特性,即
 数据位地址可读写 控制位地址可写 状态位地址可读 通常/dev/lp0的地址是0x378,所以控制位就是0x378+2 状态位就是0x378+2

 状态位有busy ack pe select error
 控制位有select_in init autofeed strobe
 数据位有D0--D7

 如果只熟悉C语言的哥们,估计看到这里就觉得很烦了,建议去看看并口电路的基本针脚介绍就知道我在说什么了

 一句话,通过上面的这些东东我们就可以知道打印机是什么状态,是否可以发数据,是否缺纸,并且可以传递数据给打印机,但是要清楚,我们说的事并口,并不是打印机,打印机在这里只是个例子,当然并口很多东西当初也是从打印机那里慢慢演化而来的。

 这些控制位和状态位都是做什么的,我研究的也不是很清楚,但是网上有这些文章可查,我这里不多说了,总之如果用ioperm inb outb就要知道确切的作用,否则你控制不了打印机的动作,优点是直接操作地址,速度够快,不过如果操作打印机的话,通常瓶颈在打印机上,而不会是对并口的读写上。

 说了这么多屁话,我想不如我下面的原码来的直观

 大家请看

 #include
#include
#include

#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 ", inb(BASEPORT + 1));

 outb(0x80,BASEPORT+2);
 system("/home/ShenLin/ParPort/Check");
 usleep(10000000);
 system("/home/ShenLin/ParPort/Check");

 outb(0x40,BASEPORT+2);
 system("/home/ShenLin/ParPort/Check");
 usleep(10000000);
 system("/home/ShenLin/ParPort/Check");

 outb(0x20,BASEPORT+2);
 system("/home/ShenLin/ParPort/Check");
 usleep(10000000);
 system("/home/ShenLin/ParPort/Check");

 outb(0x10,BASEPORT+2);
 system("/home/ShenLin/ParPort/Check");
 usleep(10000000);
 system("/home/ShenLin/ParPort/Check");

 outb(0x08,BASEPORT+2);
 system("/home/ShenLin/ParPort/Check");
 usleep(10000000);
 system("/home/ShenLin/ParPort/Check");

 /* We don't need the ports anymore */
 if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

 exit(0);

 }
 上面的程序要注意的是下面这几点
 ioperm(BASEPORT, 3, 1) 这个函数就如我说的调了两次,注意区别
 system("/home/ShenLin/ParPort/Check");这里面调用的Check程序是我自己写的(就是用open ioctl写的),这个明天再介绍,我要吃饭去了
 inb(BASEPORT + 1));这个就是在读取状态地址的值,用它就可以判断是忙还是缺纸还是什么别的(在Check程序中,我用ioctl来获得)

 至于打印机的字符集,输出格式等等,不再并口操作讨论范围内,我个人理解他们应该和串口的是一样的,即串口的fd被write了什么,并口就应该被write什么,不知道我理解得对不对,换句话说如果有一个串口的打印机驱动程序,那么我要移植到成并口的,理论上讲应该只替换掉其中打开设备的部分,即open的部分,还有就是替换掉状态判断部分,即ioctl部分,至于别的,我理解应该不需要替换的,这一点我不敢肯定,请各位高人给予分析,多谢!!

 

 补充一下昨天方法中要注意的,就是如果用到了ioperm这些函数,编译的时候要加入 O2或者O 。
 即gcc -O2 -o test test.c,不要这样gcc -o test test.c


 我接着把昨天的写完

 用操作文件描述符的方法主要问题在于ioctl函数上,这个函数实在是变化无穷,第一个参数是文件描述符,但是该文件描述符是如何获得的,将直接影响第二个参数用什么,而第二个参数又直接影响第三个参数的指针类型

 总之,就第二个参数而言,其针对串口,并口,socket,普通文件等等是各有一套定义的,而且分别存放在不同的.h中的,如果用错了(假如把socket的用到并口上),编译的时候不会有问题,但是运行的时候,肯定会告诉你参数无效的。

 对于并口来说,首先你的系统就要支持并口的那种,如果各位裁减内核的时候把这个东西裁掉了,那么,你用open 打开/dev/lp0的时候会告诉你没有这个设备,昨天我下午曾经一度陷入了这个设备中/dev/parport0,这也是个并口,但是不对应硬件,我不敢肯定的说他可能是个虚拟的并口(希望研究比较明白的人给个答案),对于这个并口的操作,我们必须用到
 #include <linux/parport.h>
 #include <linux/ppdev.h>
 这两个库文件,这里的名字和路径,我在强调一下,是Red Hat Linux上的,其它平台应该会有少许差别。这两个库里是什么东西呢,只要看了,就会明白,其实这里定义了ioctl中第二个参数可以用哪些,以及对应第三个参数是什么类型的指针,包括各种并口状态位的宏定义,以及并口的模式标准(EPP,IEEE1284等)等等,但是问题在于这个设备对应的不是打印机口,所以虽然可以操作了,但是也没用,操作这个东东的ioctl函数的第二个参数可谓十分丰富,只是跟下面将要说的/dev/lp0比起来,/dev/lp0提供给ioctl函数的第二个参数就少得可怜了。
 这也是我很疑惑的问题,难道对于/dev/lp0来说,只要我open了,就不需要对这个被open的文件描述符进行任何初始化了吗?
 下面我们先看一下操作lp0得源码,强调一下,虽然有了
 #include <linux/parport.h>
 #include <linux/ppdev.h>
 这两个库中定义的那么多的东东,很可惜他们全都不能用到lp0上,否则告诉你参数无效
 要操作lp0关键在于下面的库,上面两个可以不要
 #include <linux/lp.h>

 源码如下:

 #include
#include
#include
#include
#include
#include

#include <linux/lp.h>


 int CheckParPortState(int fd, int m_bits)
 {
 int status;

 /* Get Par Port State */
 if (ioctl(fd, LPGETSTATUS, (char*)&status) < 0) {
 return (0);
 }
 if (m_bits != 0x20) return ( status & m_bits );
 else return( !(status & m_bits) );
 }

 main()
 {
 int fd;

 if ( (fd = open("/dev/lp0", O_WRONLY , 0)) < 0) {
 perror("/dev/lp0 error");
 exit(-1);
 };


 if (!CheckParPortState(fd, LP_PBUSY))
 printf("printer is busy ");

 if (!CheckParPortState(fd, LP_PACK))
 printf("printer answer ");

 if (!CheckParPortState(fd, LP_POUTPA))
 printf("printer paper out ");

 if (!CheckParPortState(fd, LP_PERRORP))
 printf("printer error ");


 close(fd);

 }

 

 上面这个程序就是我昨天说到的那个Check的源码
 程序要注意的:
 1.判断paper out的时候,其逻辑与其他判断相反
 2.我到现在也不知道打开lp0这个并口后要不要做什么初始化,
 好像这个工作bios里设定了,包括并口传输模式,物理地址,中断等等
 3.lp.h中提供的东东比较少,目前我觉得有用的就是这个获得状态位的
 command,以及这些状态位判断用的宏。
 其他的,如果哪位高人领悟了含义和具体用法,请告诉我,谢谢!
 4.某种状态不是唯一出现,比如我关闭打印机,可能同时报忙.缺纸.Error
 如果缺纸,可能同时报缺纸.忙两个状态
 5.状态以打印机的指示灯为准,即,有的打印机没有纸的时候不会提醒缺纸,只有当你触发换行或换页,缺纸指示灯才会亮,程序也才会受到这个状态


 到这里我们只是了解了个大概,希望能有高人能告诉我打印机对应的lp0是否只要open了就可以了,还是要类似于串口一样设置一堆东西(如校验位,波特率等)
 此外,对lp0的控制位的操作,我猜想可能被write屏蔽掉了,即它不需要像操作地址一样改变不同的控制位,以便控制打印机能否接受数据等等动作,调用write就会默认去修改一些控制位等。这是我的猜想,希望有高人能证明


====================================

去弄本apue看看,里面有很清楚的讲解,如果看不懂可以再提问。

======================

一般来说直接通过inb/outb访问目的是把并口作为GPIO,每个脚都可以独立使用,其含义由并口上接的设备而定。
 通过open等访问的是打印机这样的设备,这时并口是按8位数据线传输的,同时有一些状态线控制,有一定的逻辑关系,EPP什么的是并口的传输模式,在http://www.beyondlogic.org有一些文档讲并口,我并不是很熟。

========================================

文章评论0条评论)

登录后参与讨论
我要评论
0
16
关闭 站长推荐上一条 /2 下一条