📄 use of a pc printer port for control and data acquisition.htm
字号:
*/
#include <stdio.h>
#include <dos.h> /* required for delay function */
#define DATA 0x03bc /* for the PC I used */
#define STATUS DATA+1
#define CONTROL DATA+2
void main(void)
{
int in, n;
outportb(DATA,0x00); /* turn on all LEDs on Data Port */
outportb(CONTROL, 0x00^0x0b); /* same with Control Port */
/* now turn off each LED on Data Port in turn by positioning a logic
one in each bit position and outputing.
*/
for (n=7; n>=0; n++)
{
outportb(DATA, 0x01 << n);
delay(1000);
}
outportb(DATA, 0x00);
/* now turnoff each LED on control port in turn
** note exclusive-or to compensate for hardware inversions
*/
outportb(CONTROL, 0x08^0x0b); /* bit 3 */
delay(1000);
outportb(CONTROL, 0x04^0x0b); /* bit 2 */
delay(1000);
outportb(CONTROL, 0x02^0x0b); /* bit 1 */
delay(1000);
outportb(CONTROL, 0x01^0x0b); /* bit 0 */
delay(1000);
outportb(CONTROL, 0x00);
/* Continuously scan switches and print result in hexadecimal */
while(1)
{
in = (inportb(STATUS)^0x80)&0xf8;
/* Note that BUSY (msbit) is inverted and only the
** five most significant bits on the Status Port are displayed.
*/
printf("%x\n", in);
}
}
</PRE>
<HR>
<P>
<CENTER>
<H3>F. Interrupts</H3></CENTER>
<P>Again refer to Figure #2-Port assignments. Note that bit 4 on the Control Bit
is identified as IRQ Enable. Normally, this bit is set to zero.
<P>However, there are times when interrupts are of great value. An interrupt is
nothing more than a hardware event which causes your program to momentarily stop
what it is doing, and jump to a function to do what you desire. When this is
complete, your program returns to where it left off.
<P>In using the printer port, if the IRQ Enable is set to a logic one, an
interrupt occurs when input ACK next goes from a logic one to logic zero. For
example, you might use input ACK for an intrusion alarm. You might have a
program running which is continually monitoring temperature. But, when input ACK
goes low, it interrupts your temperature monitoring and goes to some other code
you have written to handle an alarm. Perhaps, to fetch the time and date off the
system clock and write this to an Intrusion File. When done, your program
continues monitoring temperature.
<P>Interrupts are treated in detail elsewhere in this manual.
<P>
<CENTER>
<H3>G. Changing Only Selected Bits</H3></CENTER>
<P>Frequently, when outputting, the programmer is interested in only a portion
of a byte and it is a burden to remember what all the other bits are. Please
review the following where bit 2 is brought high for 100ms and then brought low.
<PRE>
int data;
...
data=data | 0x04;/* bring bit 2 high */
outportb(DATA,data);
delay(100);
data=data & 0xfb; /* bring bit 2 low */
outportb(DATA,data);
</PRE>
<P>Note that variable data keeps track of the current state on the output port.
Each bit manipulation is performed on data and variable data is then output.
<P>To bring a specific bit to a logic one, use the OR function to OR that bit
with a logic one (and all others with a logic zero.)
<P>To bring a specific bit to a logic zero, AND that bit with a zero (and all
others with a logic one.) Calculating this value can be tedious. Consider this
alternative: <PRE>
data=data & 0xfb; /* hard to calculate */
data=data & (~0x04); /* the same but a lot easier */
</PRE>
<P>This really isn't very difficult. Assume, you currently have; XXXX XXXX and
desire XX01 X100. <PRE>
data=data & (~0x20) | 0x10 | 0x04 & (~0x02) & (~0x01);
</PRE>
<P>
<CENTER>
<H3>H. Ports on Newer PC's</H3></CENTER>
<P>A few words about the DIRECTION bit on the Control Port. I have seen PC's
where this bit may be set to a logic "one" which turns around the Data Port such
that all of the Data leads are inputs. I have also seen PC's where this worked
for only the lower nibble of the Data Port and other PC's where it did nothing.
It is probably best not to use this feature. Rather, leave the DIRECTION bit set
to logic "zero".
<P>
<CENTER>
<H3>I. Differences In Printer Ports</H3></CENTER>
<P>The material discussed above is believed to be pretty generic; that is,
common to all manufacturers.
<P>You may well ask, "why would they be different. After all, programs such as
WordPerfect must work on all machines." The answer is that the programmers who
write such programs as WordPerfect do not get down to this low level of hardware
detail. Rather, they write to interface with the PC's BIOS.
<P>The BIOS (Basic Input-Output System) is a ROM built in to the PC which makes
all PC's appear the same. This is a pretty nice way for each vendor to implement
their design with a degree of flexibility.
<P>An example is the port assignments discussed above. This data is read from
the BIOS ROM when your PC boots up and written to memory locations beginning at
0040:0008. Thus, the designers of WordPerfect don't worry about the port
assignments. Rather, they read the appropriate memory location.
<P>In the same way, they interface with the BIOS for printing. For example, if
the designers want to print a character, the AH register is set to zero, the
character to be printed is loaded into AL, the port (LPT1, LPT2, etc) is loaded
into the DX register. They then execute a BIOS INT 17h. Program control is then
passed to the BIOS and which performs at the low level of hardware design which
we are trying to work. The BIOS varies from one hardware design to another; it's
purpose is to work with the hardware. If inversions are necessary, it is done in
the BIOS. When the BIOS has completed whatever bit sequencing is required to
write the character to the printer, control is passed back to the program with
status information in the AH register.
<P>
<CENTER>
<H3>J. Summary</H3></CENTER>
<P>In summary, the printer port affords a very simple technique for interfacing
with external circuitry. Twelve output bits are available, eight on the Data
Port and four on the lower nibble of the Control Port. Inversions are necessary
on three of the bits on the Control Port. Five inputs are available on the
Status Port. One software inversion is necessary when reading these bits.
<HR>
<P>
<CENTER>
<H2>II. Forcing an Interrupt on the Printer Port.</H2></CENTER>
<P>
<CENTER>
<H3>A. Introduction</H3></CENTER>
<P>This section describes how to use hardware interrupts using the printer port.
The discussion closely follows programs prnt_int.c and time_int.c
<P>A hardware interrupt is a capability where a hardware event causes the
software to stop whatever it is doing and to be redirected to a function to
handle the interrupt. When done, the program picks up where it left off. Aside
from loosing time in executing the interrupt service routine, the operation of
the main program remains unaffected by the interrupt.
<P>This is quite powerful and although at first, the whole process may appear
difficult to grasp, it is in fact quite simple.
<P>Although this discussion focuses on using the interrupt associated with the
printer port, the same technique may be adapted to exerting interrupts directly
on the ISA bus.
<P>
<CENTER>
<H3>B. Interrupt Handler Table</H3></CENTER>
<P>When an interrupt occurs, the PC must know where to go to handle the
interrupt.
<P>The original 8088 PC design provided for up to 256 interrupts (0x00 - 0xff).
This includes both hardware and software interrupts. Each of these 256 interrupt
types has four bytes in a table beginning at memory location 0x00000. Thus, INT
0 uses memory locations 0x00000, 0x00001, 0x00002, 0x00003, INT 1 uses the next
four bytes in the table, etc. Note that INT 8 then uses the four bytes at
0x0020, INT 9 begins at 0x0024, etc. This 1024 bytes (256x4) is termed the
interrupt vector table.
<P>These four bytes contain the address of where the PC is to go to when an
interrupt occurs. Most of the table is loaded when you boot up the machine. The
table may be added to or entries modified when you run various applications.
<P>IBM reserved eight hardware interrupts beginning at INT 0x08 for interrupt
expansion. These are commonly known as IRQ0 - IRQ7, the IRQ corresponding to the
lead designations associated with the Intel 8259 which was used to control these
interrupts. Thus, IRQ 0 corresponds to INT 8, IRQ 1 corresponds to INT 9, etc.
<P>Exercise. Use debug to examine the interrupt vector table which is assigned
to IRQ 0 through IRQ 7. <PRE>
-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
</PRE>(Recall that the table allocation for INT 8 begins at 0x0020).
<P>From this I can see that the address for the interrupt service routine
associated with IRQ 0 is 0B3B:10B3. For IRQ 7, 0070:06F4. You should be able to
see the algorithm I used to obtain this.
<P>Thus, when an IRQ 7 interrupt occurs, we know this corresponds to INT 0x0f
and the address of the interrupt service routine is located at 0070:06F4.
<P>Exercise. Use the debugger to examine the interrupt vector table. Then use
Microsoft Diagnostics (MSD) and examine the IRQ addresses and compare the two.
<P>
<CENTER>
<H3>C. Modifying the Interrupt Handler Table</H3></CENTER>
<P>Assume, you are going to use IRQ 7. Assume that when an IRQ 7 interrupt
occurs, you desire your program to proceed to function irq7_int_serv, a function
which you wrote. In order to do so, you must first modify the interrupt handler
table. Of course, you may wish to carefully take what is already there in the
table and save it somewhere and then when you leave your program, put the old
value back.
<P>Borland's Turbo C provides functions to do this. <PRE>
int intlev=0x0f;
oldfunc = getvect (intlev);
/* The content of 0x0f is fetched and saved for future
**use. */
setvect (intlev, irq7_int_serv);
/* New address is placed in table. */
/* irq7_int_serv is the name of routine and is of type
** interrupt far*/
</PRE>
<P>This may look bad, but, in fact it isn't. Simply get the vector now
associated with INT 0x0f and save it in a variable named oldfunc. Then set the
entry associated with INT 0x0f to be the address of your interrupt service
routine.
<P>Good programming dictates that once you are done with your program, you would
restore the entry to what it was; <PRE>
setvect(intlev, oldfunc);
</PRE>
<P>After all, what would you think of WordPerfect, if after running it, you
couldn't use your modem without rebooting.
<P>
<CENTER>
<H3>D. Masking</H3></CENTER>
<P>The programmer can mask interrupts. If an interrupt is masked you are saying
to the PC, "for the moment ignore any IRQ 7 type interrupts". Normally, we don't
do this. Rather we desire to set the interrupt mask such that IRQ 7 is enabled.
<P>Port 0x21 is associated with the interrupt mask. To enable a particular IRQ,
write a zero to that bit location. However, you don't want to disturb any of the
other bits. <PRE>
mask=inportb(0x21) & ~0x80;
/* Get current mask. Set bit 7 to 0. Leave other bits
** undisturbed. */
outportb(0x21, mask);
</PRE>
<P>The user is now ready for IRQ 7 interrupts. Note that each time there is an
IRQ 7 interrupt, the program is unconditionally redirected to the function
irq7_int_serv. The user is free to do whatever they like but must tell the PC
that the interrupt has been processed; <PRE>
outportb(0x20, 0x20);
</PRE>
<P>Prior to exiting the program, the user should return the system to its
original state; setting bit 7 of the interrupt mask to logic one and restoring
the interrupt vector. <PRE>
mask=inportb(0x21) | 0x80;
outportb(0x21, mask);
setvect(intlev, oldfunc);
</PRE>
<P>
<CENTER>
<H3>E. Interrupt Service Routine</H3></CENTER>
<P>In theory, you should be able to do anything in your interrupt service
routine (ISR). For example, an interrupt might be forced by external hardware
detecting an intrusion. The ISR might fetch the time off the system clock, open
a file and write the time and other information to the file, close the file and
then return to the main program.
<P>In fact, I have not had good luck in doing this and you will note that my
interrupt service routines are limited;
<UL>
<LI>disable any further interrupts.
<LI>set a variable such that in returning to the main program there is an
indication that an interrupt occurred.
<LI>indicate to the PC that the interrupt was processed; outportb(0x20,0x20);
<LI>enable interrupts. </LI></UL>
<P>I think that my problem is that interrupts are turned off during the entire
ISR which may well preclude a C function which may use interrupts. For example,
in opening a file, I assume interrupts are used by Turbo C to interface with the
disk drive. Unlike the IRQ we are discussing, the actual implementation of how C
handles these interrupts necessary to implement a C function will not be "heard"
by the PC and the program will appear to bomb.
<P>My suggestion is that you initially use the technique I have used in writing
your interrupt service routine. That is, very simple; either setting or
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -