计算机应用研究
APPLICATION RESERCH OF COMPUTERS
2000  Vol.17　No.1　P.111-113




微机打印口中断方式编程技术及应用
郭福田　李从信　刘贤梅
摘 要 根据C语言中断方式编程的方法，结合打印口的中断功能，讨论了如何通过中断方式对打印口编程，并给出了一个简单应用实例。
关键词 打印口 中断 输入输出 ISR
1 引言
　　目前，由于微机的普及，微机在数据采集及控制过程中的应用越来越多，而开发微机硬件接口是建立工业控制系统的主要工作。一种简单快捷的方法是利用微机已有的接口，如微机打印口，可方便地构成工业控制及数据采集系统[1]。在工业控制系统中，数据采集通常采用两种方式，一种是查询方式，另一种是中断方式。对于实时性要求较高的场合，通常选用中断方式，即有中断请求时，暂停正在进行的程序，转而执行想要做的事情，当其完成后，程序又回到原被中断处继续执行。下文讨论采用C语言中断方式编程方法，根据打印口接口原理，以中断方式对打印口编程，并结合一个简单实例介绍打印口中断方式编程的实现过程。
2 微机中断基础知识
　　中断控制是计算机技术的一个重要组成部分，可以提高系统的实时性，提高计算机的工作效率。微机共设置了256个中断，可分为三类：(1)内中断，即系统内部出错或者CPU遇到除数为零而运算溢出等情况时，需要立即处理的一类中断。(2)外中断(也叫硬中断)，是为外部设备向CPU申请中断服务而设的。(3)软中断，它是有关硬件控制或完成某一常用子功能的一些底部程序，它一般是为方便软件编程人员而设计的。无论是哪一种中断类型，计算机都给予统一编号，统一管理。计算机将每一个中断服务程序(interrupt service routine，下文简称ISR)入口地址按它们的中断号顺序分别存在计算机的绝对地址0x00000开始处。由于微机的地址分为段地址和偏移地址两部分，而段地址和偏移地址都需用16位二进制表示，所以每一中断程序的入口地址要占4个字节(即中断向量)，256个中断就要占用1024字节的数据空间。此空间即为中断向量表。当一中断发生时，微机根据中断向量表可知道去哪里处理这个中断。中断向量表中大部分内容在计算机启动时装入，在系统运行时可以对它进行修改。中断向量从内存0x00000开始，即INT0占用0x00000，0x00001，0x00002，0x00003单元，INT1占用后4个单元，等等。从INT 0x08开始的为16个硬件中断，与两个Intel 8259中断控制芯相连，常记作IRQ0~IRQ15，即INT8对应于IRQ0，使用从0x0020开始的4个单元，INT9对应于IRQ1，从0x0024开始，等等。其中IRQ7与打印机端口(LPT1)相连。与硬件相关的中断向量表见图1。

图1　部分硬件中断向量
　　可利用debug检查IRQ0到IRQ7的中断向量表。方法如下：
-d 00000:0020 20
B3 10 3B 0B 73 2C 3B 0B-57 00 70 03 8B 3B 3B 0B
ED 3B 3B 0B AC 3A 3B 0B-B7 00 70 03 F4 06 70 00
　　从这里可以看到ISR的地址，对于IRQ0(INT8)是0B3B:10B3，对于IRQ7(INT15，16进制表示为INT0x0f)是0070：06F4，即当IRQ7中断发生时，对应的中断为INT 0x0f，而且中断程序的地址为0070：06F4。
3 打印口中断功能及编程技术
　　下面先对打印口中断功能作一介绍，然后逐一介绍用C语言编写中断程序，应注意的问题：
　　。修改中断向量表
　　。中断屏蔽
　　。IRQ7产生控制
　　。中断服务程序ISR
　　(1)打印口中断功能
　　分析微机接口电路可知，通过打印口的ACK(接收认可)引脚可实现中断。中断实现由打印机接口卡和微机的8259中断控制器控制两级控制。打印机接口卡控制ACK信号能否产生中断请求信号IRQ7，具体讲由控制口中第4位决定，当它为1时，将使打印机的ACK反相驱动后，成为中断请求IRQ7信号送入8259中断控制器[2]。该位缺省为0，即通常不会产生IRQ7。在数据采集及控制系统中，为提高系统的实时性，常常需要采用中断方式，这时，可将/IRQ Enable设为1。这样，当ACK信号从1变为0时，将产生中断IRQ7，然后，由8259允许IRQ7申请中断。例如，在温度监视系统监视温度的过程中，可以从ACK引脚输入一个中断信号。如在图2中断信号产生电路中，当按钮S1按下时，ACK由将1变为0，产生中断IRQ7，系统转入ISR。可在ISR中处理其它事情，如记下系统的时间和日期，并存入一个文件中，完成这些工作后，系统继续监视温度变化。
　　(2)修改中断向量表
　　假设要使INT15中断发生时，程序执行用户的irq7_int_serv中断程序。为达到这个目的，首先应该修改中断向量表，当然，在修改之前，应该先保存原来的向量，以便退出ISR时，恢复原来的值。
Borland C提供了相应的功能，如：
int intlev=0x0f;
oldfunc=getvect(intlev);
//保存0x0f的中断向量，留恢复用
setvect(intlev,irq7_int_serv);
//将用户ISR地址放入0x0f处
//irq7_int_serv是用户ISR名
　　将获得的INT 0x0f的中断向量存为变量oldfunc，然后设置INT 0x0f的入口地址为irq7_int_serv的地址。一旦完成应恢复其值：
setvect(intlev,oldfunc);
　　(3)中断屏蔽
　　中断可以被屏蔽，对某一中断屏蔽后计算机将忽略对该中断的响应。需要中断时可将屏蔽位设为Enabled(允许)，即开放中断。微机中端口0x21用于中断屏蔽，为开放某一个中断，将相应位清零，但不要影响其它位。如开放IRQ7中断方法如下：
mask=inportb(0x21)&~0x80;
//取得原屏蔽字，将第7位设为0，其它位不变
outportb(0x21,mask);
　　这样，当有来自IRQ7的中断请求时，程序将无条件地转到irq7_int_serv程序(ISR)。在该ISR中，必须用下面命令结束中断，即通知8259中断已被处理了。
outportb(0x20,0x20);
　　在退出系统程序之前，应该恢复系统的原始状态，如设置中断屏蔽字位7为l并恢复中断屏蔽字(禁止IRQ7中断)：
mask=inportb(0x2l) | 0x80;
outportb(0x21,mask);
setvect(intlev,oldfunc);
　　(4)IRQ7产生控制
表l 控制口中各位功能
位76543210
功能保留保留保留/IRQ Enable/Select InInit/Auto FeedStrobe

　　打印口上/ACK信号能否产生IRQ7，由控制口中第4位即/IRQ Enable(中断允许请求位)控制，参见表1。要产生中断，除设置8259中断控制器的屏蔽字允许IRQ7中断外，还必须设置打印机控制口/IRQ Enable位为1。/IRQ Enable为1，则当/ACK输入引脚上有负跳变时，允许产生IRQ7；为0则ACK输入不会产生IRQ7。设置/IRQ Enable为1的方法如下：
mask=inportb(0x2l) & ~0x80;
outportb(0x21,mask);
outportb(CONTROL,inportb(CONTROL) | 0x10);
　　注意在设置控制口位4为1时，其它位应保持不变。在退出程序以前，应将控制口位4按原来位清0。如：
outportb(CONTROL，inportb(CONTROL) & ~0xl0);
　　(5)中断服务程序ISR
　　理论上，可在ISR中做任何事情，例如，外部事件要求中断时，ISR可记录下系统时钟，打开一个文件，将这个时间和其它信息写入文件中，关闭文件然后返回主程序。然而，事实上，在ISR中编程有一定的限制，可使用下述方法：
　　。屏蔽其它中断
　　。在ISR中设置一个变量，表明中断已经发生
　　。用outportb(0x20,0x20)结束中断
　　。开放中断
　　当进入ISR时关闭中断后，应避免使用C语言函数。例如，打开文件函数，C语言用中断方式与磁盘通信。因这时中断已经关闭，系统无法响应而死机。对于简单系统，可使用上述设置中断发生变量的方法。对于更复杂的情况，可使用下述方法：
　　。关闭所有中断：disable()
　　。屏蔽IRQ7中断
　　。outportb(0x20,0x20)
　　。允许所有中断：enable()
　　。要做的事情
　　。disable()
　　。允许IRQ7中断
　　。enable()
　　与前一种方法不同，在ISR中，不能再进入IRQ7中断，但允许其它中断，这样将允许所有的C语言函数工作。
4 编程举例
　　下面通过一具体程序TIME_INT.C介绍打印口中断方式编程的实现过程，具体介绍见程序注释。在该程序中，当中断发生时，记录系统时间，否则显示两次中断的时间差。程序中用全局变量int_occurred记录中断是否发生，程序开始声明为FALSE，一旦中断发生，将设置为TRUE。主程序中判断int_occurred，当它为TRUE时，计算两次中断的时间差。
//程序TIME_INT.
//用ISR记录打印口的中断发生，中断由/ACK上低电平产生。
//这里端口地址为0x0378，相关中断为IRQ7。
//计算时间，并显示两次中断的时间差。
#include <stdio.h>
#include <bios.h>
#include <dos.h>
#include <sys\timeb.h>
#define DATA 0x0378
#define STATUS DATA+1
#define CONTROL DATA+2
#define TRUE 1
#define FALSE 0
void open_intserv(void);
void close_intserv(void);
void int_processed(void);
void interrupt far intserv(void);
int intlev=0x0f; //采用中断源IRQ7
void interrupt far (*oldfunc)( ) ;
int int_occured=FALSE; //记录中断发生
int main(void)
{
　　int first=FALSE;
　　int secs, msecs;
　　struct timeb tl, t2;
　　open_intserv();
　　outportb(CONTROL,inportb(CONTROL) | 0x10);
　　//设置控制口位4(/IRQ enable)为'1'
　　while(l)
　　{
　　　if (int-occurred)
　　　{
　　int_occurred=FALSE; //中断发生变量复位
　　if(first==FALSE)
　　//取得第一次中断的时间
　　{
　　　　　　ftime(&t2);
　　　　　　first=TRUE;
　　}
　　　　else
　　{
　　　　　tl=t2; //保存第一次中断时间
　　　　　ftime(&t2); //计算时间差
　　　　　secs=t2.time-t1.time;
　　　　　msecs=t2.millitm-t1.millitm;
　　　　　if (msecs<0)
　　　　　{
　　　　　　　- -secs;
　　　　　　　msecs=msecs+1000;
　　　　　}
　　　　　　　printf("中断时间差：%d\n", 1000*secs+msecs);
　　　　　}
　　}
}
close_intserv();
return(0);
}
　　void interrupt far intserv(void)
　　//在ISR中，必须清除中断源，并清8259
　　{
　　　　disable( );
　　　　int_processed( ); //通知8259中断结束
　　　　int_occurred=TRUE; //记录中断发生了
　　　　enable( );
　　}
void open_intserv(void)
//允许IRQ7中断。中断发生(/ACK为低)时跳到intserv
//注意：设置时关闭中断，设置后开放中断
{
　　int int_mask;
　　disable(); //关闭所有的中断
　　oldfunc=getvect(intlev); //保存原中断向量
　　setvect(intlev,intserv); //建立新中断向量
　　int_mask=inportb(0x21); //读取中断屏蔽字
　　outportb(0x21, int_mask &~0x80);
　　　　//设置位7为0，允许IRQ7，而其它位不变
　　enable(); //开放所有的中断
}
void close_intserv(void)
//关闭IRQ7中断
{
　　int int_mask;
　　disable( );
　　setvect(intlev,oldfunc); //恢复原中断向量
　　int_mask=inportb(0x21) | 0x80;
　　//设置位7为1，禁止IRQ7，而其它位不变
　　outportb(0x21,int_mask);
　　enable( )
}
void int_processed(void)
//通知8259中断结束
{
　　outportb(0x20,0x20);
}
5 小结
　　通过前面的介绍，在初学者看来，很难掌握的中断编程，事实上十分简单。本文主要讨论了在打印口中使用中断的技术，通过此技术可将打印口用于各种数据采集及控制项目中。另外，本文采用的中断编程方法同样可用于ISA总线上的中断，即通过数据采集板进行控制的系统中。
郭福田（大庆石油学院计算机系 黑龙江安达 151400）　
李从信（大庆石油学院计算机系 黑龙江安达 151400）　
刘贤梅（大庆油学院计算机系 黑龙江安达 151400）
参考文献
1，张 利. PC机打印口的原理及应用开发. 清华大学出版社, 1996
2，张昆藏. IBM微型计算机接口技术. 清华大学出版社, 1991
收稿日期：1999年7月7日
