CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
Parallel and Serial Interfacing
In order to create effective graphics software, you must provide
ways for your computer to communicate with the outside world.
Your software can communicate with the user through the use of
CRT displays, printers, plotters, mice, digitizers, light pens,
and a host of other devices. These devices are connected to the
computer in various ways.
This chapter is about the art and science of interfacing. This
subject pertains to all aspects of computer use, not just
graphics. You will find that the skills required for competent
graphics programming embrace all other computer skills. In other
words, if you can create graphics using a computer, you will be
able to do almost any other kind of computer programming.
You might think that interfacing is boring, that it has little
to do with creativity. You will find, however, that interfacing
of peripherals can be done in a variety of ways. Depending on
your choices, you can create magic or end up with software that
balks at everything. All graphics programs rely on successful
interfacing techniques.
When you are working with locator devices, you will need to
receive data from the serial interface. Traditionally, because of
the ineffectiveness of the parallel interface on the IBM PC, the
serial interface is most often used with locator devices. To
receive data you can use serial interrupts, or you can use a
polled interface method. The polled method, which involves using
a polled serial input protocol to ask the sending device for each
byte as it arrives, is the easier one to use.
The code presented in this chapter is intentionally explicit
rather than containing included files and mnemonics. It is
intended to help you understand what is going on in the
functions, rather than serving as an example of code you would
use in a program. You may find that many explicit references
could be made using mnemonics or equates. By all means, feel free
to change the code to reflect your own style. Just be sure that
any changes you make accomplish the same things as the original
code would do.
Throughout this book you have seen interfacing used implicitly
in various functions. No detailed explanation of interface
options was offered. In this chapter you will see how to receive
information from digitizers, mice, and light pens. You will see
how serial and parallel interface work, not only for graphics but
for all of your software. Finally, you will see a serial
interrupt service routine that really works.
PARALLEL PORTS
The parallel interface port on the IBM PC series of computers is
the easiest interface to understand. Most devices that connect to
it are wired at the factory to be compatible. You seldom need to
worry about making cables for parallel ports.
The parallel port is used primarily for interfacing with
printers. It uses eight data wires, one for each bit in a byte to
be sent. The use of eight wires means that eight bits can be sent
at once, in parallel. The serial interface would require you to
send the same eight bits one bit at a time.
Sending Characters Through a Parallel Port
To send a character through the parallel port, you first need to
determine which port you will use. You then use either the BIOS
or direct access to the port registers to send the character.
Because the BIOS in this instance is perfectly adequate, a BIOS
interrupt is the preferred way to send characters through the
parallel port.
To send a character you use BIOS interrupt 17h. Before
executing the interrupt, you load AH with 0, load AL with the
character to be printed, and load DX with the desired printer
number. Printer numbers 0, 1, and 2 correspond with LPT1, LPT2,
and LPT3, respectively.
.pa
Monitoring Printer Status
You must also be concerned with the status of the printer. If the
printer cannot receive the character you wish to send, it will
raise or lower one or more of its status wires. The status wires
are monitored by setting AH to 2 and DX to the desired printer
number. The printer status is returned in AH after you execute
interrupt 17h. In practice, you first generate interrupt 17h with
AH set to 2 and loop until AH returns a nonzero value for bit 8.
When this occurs, you know that the printer is not busy and you
send the desired character. This is all the handshaking you will
ever need for parallel output.
Function 11-1, PUT_OUT.C, shows a complete character output
function that you can use for any software you write. It is very
clean, and you seldom need more that this. You will encounter it
in GRAPHIQ.C in Appendix A in the code used for dithering
pictures out onto the dot-matrix printer.
FUNCTION 11 - 1
-------- ----- ------ ----- ----- --------- ----- --------
PUT_OUT.C
-------- ----- ------ ----- ----- --------- ----- --------
/* PUT_OUT.C puts a character out the parallel
** port using the BIOS.
*/
char put_out(character)
char character;
char status()
Parallel Output Without Using DOS or the BIOS
To go one level further down, you can use the technique shown in
Function 11-2, PRALEL_O.C, to do almost the same thing the BIOS
does, but from the C language. Use this function if you wish to
change low-level access for any reason.
Normally, the standard for parallel interfacing requires 36
pins. IBM wanted to use a 25-pin D connector (a female), leaving
11 pins unused. The idea was to use a male 25-pin D connector for
the serial interface and a female connector between the two
without taking the cover off the computer. This strategy has
backfired because users often plug serial devices into parallel
ports the serial devices happen to have male connectors. This
puts +_12 volts on TTL (transistor-transistor logic) circuits
capable of sinking 0 through 5 volts, and has probably destroyed
many parallel adapter.
Of course, one implication of the unfortunate choice of
connectors is that all the pins necessary to support the full
parallel standard are not available. In its infinite wisdom, IBM
chose to make the parallel BIOS interface capable of only output!
If they had used a 36-pin connector, the means of supporting full
bidirectional input/output could have been parallel.
Three parallel adapters are recognized by the BIOS. Their
addresses are stored starting at 0000:0408 in RAM. The best way
to find the address of the port for a given adapter is to use
word offsets from base 0000:0408, as follows:
unsigned far *base = 0x00000408L + paraport;
The long variable paraport is 0 for LPT1, 2 for LPT2, or 4 for
LPT3. This can be expressed in definitions as follows:
#define PARALLEL2 0L
#define PARALLEL2 2L
#define PARALLEL3 4L
FUNCTION 11-2
-------- ----- ------ ----- ----- --------- ----- -------
PRALEL_O.C
-------- ----- ------ ----- ----- --------- ----- -------
/* PRALEL_O.C uses a non-BIOS method to send a
** character out the parallel port.
** This method uses no timeout checking as
** the BIOS does.
*/
/* possible values for paraport */
#define PARALLEL1 0L
#define PARALLEL2 2L
#define PARALLEL3 4L
unsigned far *base = 0x00000408L;
char stat_o();
char pralel_o(character, paraport)
char character;
long paraport;
char stat_o(paraport)
long paraport;
Note that LPT1, LPT2, and LPT3 are reserved DOS device names,
and so it is unwise to use them as they are in your programs,
unless you wish to refer to devices DOS recognizes.
LPT1 can also appear as PRN. You can send or receive bytes from
the parallel port by using the OUT or IN instructions of the
microprocessor. A typical output to the eight-bit parallel latch
can be executed as follows:
outp(*base, character);
This assumes that you have initialized base to point to the
address of the desired parallel port. The only other
consideration is that you must be able to tell the sending device
when to send the next character. To do this you control the same
pins that the BIOS monitors when receiving a character.
One thing Function 11-2 does not do is repeatedly test port if
the character was not successfully sent. This means that there is
no certainty that the receiving device is working. In practice,
this usually does not create problems. You can avoid testing for
device timeout in this way.
Receiving Characters from a Parallel Port
There is no support in the BIOS for receiving characters using
the parallel interface. This is truly unfortunate. If most
plotters, digitizers, and other devices used the parallel
interface rather than the serial interface, there would be much
less confusion in the computer industry. Further, the parallel
interface is potentially much faster and more reliable than the
RS232C serial interface. The only drawback would be the fact that
cables for parallel interfaces cannot be as long as serial
cables. The voltages on parallel interface wires range from 0 to
+5, relying on ground potential for their off condition. The
serial interface uses voltages lower than -3 volts or greater
than +3 volts to determine the on or off stare of the interface
wire. The usual voltage employed in serial interfaces is +_12
volts. The use of ground potential and a narrowly defined +5-volt
signal level makes parallel input/output more susceptible to
electrical noise than serial input/output.
Because most plotters and digitizers use cables that are less
than 8 feet long, the use of parallel, rather than serial,
interfacing for these devices would have been possible.
Traditionally, though, this was just not done. It is probably a
tragic case of false economy. Is one interface wire really
cheaper than eight? Looking backward, with the benefit of
hindsight, it probably would have been better to use serial
interfacing with modems only for telephone communications (where
one wire is all you get) and full 36-wire parallel interfacing
for everything else.
Receiving a character through the parallel port is a more
difficult task than sending one. To receive a character, you must
violate the usage ground rules of the parallel interface
standard. Normally, receiving would be done using pins 20 through
27 of the 36-pin standard for data. This would enable received
data to be latched simultaneously with sent data. However, the
kludged design of the IBM parallel interface makes this
impossible.
Fortunately, although it is nonstandard, you can use the eight
pins normally used for output to receive as well as send a byte.
Of course, you cannot receive and sent at the same time, as you
can with the full 36-pin standard, but you can multiplex the
interface to share the same eight pins, with some reduction in
speed and improvisation of handshaking.
Function 11-3, PRALEL_I.C, is like Function 11-2 except that
it receives rather than sends a character. You may need to modify
Function 11-3 to meet the needs of specific sending devices. For
example, a specific device may need a handshake using the strobe
wire (pin 1). You cannot send acknowledge or busy signals,
unfortunately, because the status port is meant to be read only.
Also, you should set the bits of the status register that are not
used to states that are required by the sending device. In other
words, you must be careful to meet the requirements of the
sending device, using a modified form of Function 11-3.
FUNCTION 11-3
-------- ----- ------ ----- ----- --------- ----- -------
PRALEL_I.C
-------- ----- ------ ----- ----- --------- ----- -------
/* PRALEL_I.C uses a non-BIOS method to receive a
** character from the parallel port.
** This is a simple handshake protocol that, although
** unable to receive a null byte, is very effective.
*/
/* possible values for paraport */
#define PARALLEL1 0L
#define PARALLEL2 2L
#define PARALLEL3 4L
unsigned far *base = 0x00000408L;
char stat_i();
char pralel_i(paraport)
long paraport;
Understand that Function 11-3 is intended only as a suggestion.
To make it work, you must provide initialization and handshaking
that are appropriate for the sending device.
The technique in Function 11-3 assumes that the sender waits
for all bits of data to be 0 before sending the next character.
Because the handshake is based on setting all data bits to 0, you
cannot receive a null byte. For most purposes this is adequate,
but it depends on the sender recognizing this protocol. The
parallel port is seldom used to receive data; you will probably
use it for this only when you wire your own hardware. This
protocol is suggested for those circumstances.
In order to use Function 11-3, you must create a receiver
buffer of as many characters as will be received in one port
access. You may read the port using Function 11-3 for each
character in the buffer, until you have received as many
characters as you wish.
If you plan to interface parallel equipment professionally and
can create your own hardware, it is wise to use a full 36-pin
standard adapter. The use of this standard is highly encouraged
with the hope that someday it will replace the IBM parallel
interface, to the great benefit of the computer industry.
The Parallel Interface at a Glance
Figure 11-1 shows the IBM parallel port in all its glory, with
the contents of all registers and their relationships to the
wires used to connect the port to external devices. The parallel
port used never again be a mystery.
The diagram shows the pins on the IBM PC connector (female) as
well as the three registers used to establish communication with
the pins. Both the data register and the interrupt enable and
strobe register can be read or written. The status register will
show the current status of the wires, but you cannot use it to
change the status of the wires. The fact that the status register
is passive raised serious objections when the PC was first
marketed. Some PCs may permit handshaking using the status
register. Another way to establish a handshake is to do so using
null data, because null data bytes are seldom sent. This
technique will be discussed later.
Caution: Inverted Pin Status
Note that pins 17, 10, and 0 are inverted when represented in the
interrupt enable and strobe register. This can be very confusing
for people writing drivers because these wires are off when their
bit representations are on and vice versa. It is usually a
mistake to invert logic arbitrarily in designing interfaces, as
has been done in the BIOS.
FIGURE 11-1. The parallel port system at a glance
SERIAL PORTS
The serial interface on IBM PCs is much more versatile than the
parallel interface. Unlike the parallel interface, input as well
as output capabilities are included by design. But the BIOS,
unfortunately, has incredibly poor support for serial
input/output. You can use send characters, but that is about all.
If you wish to receive characters, the BIOS will just wait
forever, unless you happen to use the hardwire handshaking that
the BIOS anticipates. It is best to ignore the BIOS altogether
when working with serial communications.
Asynchronous Communications
The standard for serial data transfer (if something so confusing
can be called a standard) is called asynchronous because two
separate clocks are used, rather than a synchronization wire and
one clock. The two clocks (one for the transmitter and one for
the receiver) are never exactly in step, but their frequencies
are close enough so that the start and stop bits that frame a
word can be used to synchronize the reading of the bits in the
word.
The two clocks in an asynchronous data exchange are almost the
same frequency. Framed between the start bit and the final stop
bit, your data is clocked using the local clock and the known
start bit. The clock gets a little out of synchronization by the
time it reaches the last stop bit. If it is too far out of step,
it generates a framing error. A framing error indicates that the
difference between the clock of the receiver and the clock of the
sender is too great.
The Serial Port Registers
The IBM PC supports full interrupt handling for two serial ports
and leaves space in the table of interrupts for two more. You
will seldom require more than two serial ports (one for a mouse
or digitizer and one for a plotter), and so this is an adequate
arrangement.
Serial Adapter Base Addresses The addresses of each installed
communications adapter can be found in low memory starting at
0000:0400, in 16-bit words. The high byte of the address of the
first adapter is found at 0000:0401, and the low byte is found at
0000:0400. As elsewhere in this book, you can use a far pointer
to retrieve the contents of the word at this address as follows:
unsigned far *base = (unsigned far *)0x00000400L;
base_reg = *base;
The above method will transfer the port address to base_reg,
assuming that base_reg is type unsigned int. The actual address
of COM1 on IBM PCs is 0x3F8. Try using DEBUG to verify this for
yourself by dumping the contents of 0000:0400. It is better to
refer to the base_reg value indirectly, using the far pointer,
than to hardwire the port address. This way address can be
changed and your software will still work.
If you have two ports, the base address for COM2 will be
stored at 0000:0402. If three or four ports are available, the
addresses for the third and fourth will be at 0000:0404 and
0000:0406, respectively.
Starting at the base address for the communications adapter,
the addresses of the six registers associated with each port are
found by adding their offsets to base. Figure 11-2 shows most of
the registers necessary to control the serial interface.
ADAPTER LOGICAL NAME BASE USUAL CONTENTS
COM1 [0000:0400] 3F8
COM2 [0000:0402] 2F8
Third comm adapter [0000:0404]
Fourth comm adapter [0000:0406]
OFFSET COM1 Example REGISTER'S FUNCTION
0 3F8 Data
1 3F9 Interrupt Enable
2 3FA Interrupt Identification
3 3FB Data Format
4 3FC Serial Control
5 3FD Serial Status
6 3FE Input Status
Data Register (Register 0)
Bit 0 Data bit 0
Bit 1 Data bit 1
Bit 2 Data bit 2
Bit 3 Data bit 3
Bit 4 Data bit 4
Bit 5 Data bit 5
Bit 6 Data bit 6
Bit 7 Data bit 7
Interrupt Enable Register (Register 1)
Bit 0 Data Ready
Bit 1 Transmitter Empty
Bit 2 Serial Status Change
Bit 3 Input Status Change
Bits 4 - 7 Not Used
Interrupt Identification Register (Register 2)
Bit 0 0 Means Interrupt Pending
Bits 1 - 2 Interrupt ID;
--- 00 Serial Status Int.
|2|1| 01 Transmitter Status Int.
--- 10 Data Ready Int.
11 Input Status Int.
Bits 3 - 7 Not Used
FIGURE 11-2. The serial interface at a glance
(continued on the next page)
.pa
(continued from the last page)
Data Format Register (Register 3)
Bits 0 - 1 Word Length;
--- 00 5 bits
|1|0| 01 6 bits
--- 10 7 bits
11 8 bits
Bit 2 Stop bits:
0 1 bit
1 2 bits
Bit 3 Parity Enable:
1 Parity On
0 Parity Off
Bit 4 Parity:
1 Even Parity
0 Odd Parity
Bit 5 Stick Parity
Bit 6 Set Break
Bit 7 (selects) Baud Rate Divisor
Serial Control Register (Register 4)
Bit 0 Data Terminal Ready (pin 20)
Bit 1 Request To Send (pin 4)
Bit 2 OUT1 (user defined int. request)
Bit 3 Out2 (enable interrupts)
Bit 4 Loop Test
Bits 5 - 7 Not Used
Serial Status Register (Register 5)
Bit 0 Data Ready
Bit 1 Overrun Error
Bit 2 Parity Error
Bit 3 Froming Error
Bit 4 Received Break
Bit 5 Transmitter Holding Reg. Empty
Bit 6 Transmitter Shift Reg. Empty
Bit 7 Not Used
Input Status Register (Register 6)
Bit 0 Change On Pin 5
Bit 1 Change On Pin 20
Bit 2 Change On Pin 22
Bit 3 Change On Pin 8
Bit 4 Clear To Send (CTS)
Bit 5 Data Set Ready (DSR)
Bit 6 Ring Indicator (RI)
Bit 7 Data Carrier Detect (DCD)
-------- ----- ------ ----- ----- --------- ----- -----
FIGURE 11-2. The serial interface at a glance
If you compare Figure 11-2 with Figure 11-1, you will see how
much more complex than the parallel interface the serial
interface really is. There is a lot more to control with serial
I/O.
Base Addresses Serial Registers - Figure 11-2 shows, from top to
bottom, the locations in memory of the base addresses of each of
the four possible communications adapters. Of course, you could
add many more serial ports if you used unreserved interrupts, but
the four reserved interrupt addresses are in a place in memory
where they are not likely to be interfered with. DOS recognizes
two logical names, COM1 and COM2. Com1 is also called AUX. These
names are shown in the figure, along with their address vectors
and the addresses vectors and the addresses to which they are
typically initialized on an IBM PC. Square brackets indicate the
contents pointed to by an address. Thus, [0000:0400] points to
the word that holds the address 3F8 (hex).
Serial Interface Registers - The registers on the communications
adapter are in a series of addresses that follow the base address
consecutively. You can refer to each register relative to the
base address for the adapter. The second part of Figure 11-2
shows the offsets of each of the six communications registers
from the base address.
The Data Register - The data register is the simplest of the six
registers to understand. It holds a single byte either to send or
to receive. Whether you are sending or receiving depends on
whether you are writing or reading this register. If you use an
outp() to the data register, the data will be prepared and sent
to the receiving device. If you use an inp() from the data
register, the data in it will be made available to you.
If you are sending the byte, you need to know that the
receiving device has room to receive it. If you are receiving the
byte, you need to know that the sending device has indeed sent
something and that a byte is waiting to be read.
The Interrupt Enable Register - If you wish to install an
interrupt-driven serial interface, you must select the kind of
event that will signal the processor that an interrupt
needs service. If any combination of these bits are set, the
event or combination of events will create an interrupt. In
addition, there is a priority in which these interrupt events
will be served.
Interrupt Service Priorities - Any change in the input status will
receive the highest priority. In other words, the input lines
(wires) themselves, if they change state at the same time that
any other change takes place, will be served first.
If data is received (Data Ready), its service will come next.
In other words, if Data Ready occurs at the same time as
Transmitter Empty, then Data Ready will be reserved first.
If the transmiter holding register is empty (as indicated by
Transmitter Empty), it will be served next in priority after Data
Ready. It has the third level of priority.
Finally, a change in the modem status (Serial Status) will be
the last served by the interrupt mechanism. Serial status
indicates whether translation was successful. Any of a number of
errors or other important conditions can occur, and these are
monitored by the serial status register.
The Interrupt Identification Register - When you have enabled
interrupts by using the interrupt-enable register, the serial
control register, and the 8259 interrupt mask register (at 0x21),
you may wish to know when one of the interrupts occurs. You can
receive the current interrupt status from the interrupt
identification register. For example, you may wish to install an
interrupt service routine that is executed only if an interrupt
of a certain type is pending.
The Data Format Register - The data format register sets the data
bits, a parity bit, stop bits, and a baud rate. These govern the
way serial data will be interpreted. You have many options for
these settings.
You could set this register directly or use the DOS MODE
command before running your program. From an overall system
standpoint, you should, as much as possible, keep your software
from setting serial ports internally. From the standpoint of ease
of installation, however, you may wish to set the port for your
user, and then reset it when the user exits the software.
To set the number of data bits, you change bits 0 and 1 of the
data format register, as shown in Figure 11-2. Values of 5 and 6
bits are seldom (if ever) used.
To set the number of stop bits, you change bit 2 of the data
format register. Settings with two stop bits are seldom used.
Remember - there is always one start bit (always 0) and always at
least one stop bit in a complete serial transfer. The two bits,
one always on and one always off, are used to establish
synchronization with the clock.
You can have one or two stop bits. If you specify one stop
bit, eight data bits, and no parity, you will send a total of ten
bits per transfer cycle. There will be one start bit (always),
eight data bits, and the stop bit you requested. This tends to be
confusing, but the actual format is unimportant. The important
part is to make sure that the format used by the receiver matches
the format used by the transmitter.
Selecting Parity - To select parity, you change bit 3 of the data
format register. If on, this bit enables parity checking. This
means that right after the data a bit will be added that reflects
the sum of the bits in the data. If parity is enabled, an
automatic sum is generated each time a full eight bits is
received. The status of the parity bit received will be compared
with the sum of the data bits just generated. If the sum is odd
and the parity bit received says it should be even, a parity
error has occurred.
To select whether the expected parity will be odd or even, you
set bit 4 of the data format register. If you wish it to be even,
turn bit 4 on. If you expect parity to be odd, turn bit 4 off. No
matter how bit 4 is set, it will mean nothing if bit 3 is not on.
The stick parity bit (bit 5) adds to the confusion of it all by
reversing the meaning of the parity bit (bit 4). Make sure you
turn it off.
Break Detection - Bit 6 of the data format register is used to
send a break signal to a terminal. It does this by holding all
data to space voltages (-12 volts) long enough for the terminal
to detect a break in transmission.
The Baud Rate Divisor Latch - Last but not least, bit 7 of the
data format register changes the meaning of registers 0 and 1. It
selects the Baud Rate Divisor Latch. When bit 7 of register 3 is
on, the data register contains the low byte of the baud rate
divisor, and the interrupt enable register contains the high byte
of the baud rate divisor. If you intend to receive or transmit
data, make sure that bit 7 of the data format register (register
3) is off.
The Baud Rate Divisor Latch (see in registers 0 and 1 when bit
7 of register 3 is high) contains a number that, when multiplied
by 16 and divided into the magic clock frequency number
1,843,200, generates a baud rate. The baud rate divisor can be
determined and set by using Function 11-4, SET_BAUD.C.
The Serial Control Register - The serial control register sets the
state of two pins on the serial interface connector, enables
interrupts, and places the communications adapter into test mode.
If you wish to signal a sender to stop sending, you can do so by
lowering pins 4 and/or 20. Of course, the wire you use must be
connected to the sender's pin 5 or it will not do anything. Bit 2
of the serial control register is disabled on some boards and
functions identically to bit 3 on others. Bit 3 has a very
special function.
FUNCTION 11-4
-------- ----- ------ ----- ----- --------- ----- -------
SET_BAUD.C
-------- ----- ------ ----- ----- --------- ----- -------
/* SET_BAUD.C sets baud rate given the
** desired rate. It computes the divisor
** necessary. You should stick to conventional
** rates from 50 through 9600, exceeding at
** your own risk.
*/
union
byte;
struct
whole;
}bytes;
set_baud(baud_rate)
int baud rate;
;
struct ISRVEC isrvec;
unsigned char serisr(), readbuf();
unsigned buffchr();
.pa
Function 11-5
-------- ----- ------ ----- ----- --------- ----- -------
SERISR.ASM
-------- ----- ------ ----- ----- --------- ----- -------
; SERISR.ASM is a serial interrupt service routine.
_TEXT SEGMENT BYTE PUBLIC 'CODE'
_TEXT ENDS
CONST SEGMENT WORD PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT WORD PUBLIC 'BSS'
_BSS ENDS
_DATA SEGMENT WORD PUBLIC 'DATA'
_DATA ENDS
DGROUP GROUP CONST, _BSS, _DATA
ASSUME CS: _TEXT, DS: DGROUP, ES: DGROUP
TOTAL equ 64
TOP equ 63
BOTTOM equ 0
_DATA SEGMENT
buffer db TOTAL dup('@')
front dw BOTTOM
back dw BOTTOM
count dw BOTTOM
_DATA ENDS
_TEXT SEGMENT
PUBLIC_serisr
_serisr PROC FAR
push ax
push dx
push si
push ds
mov ax, DGROUP
mov ds, ax
mov dx, 3F8h ;base register address
in a1, dx ;input a character
mov si, front
mov buffer[si], a1
inc front
inc count
cmp front, TOP
jbe front_below
mov front, BOTTOM
front_below:
cmp count, TOTAL
jbe count_below
mov count, TOTAL
count_below:
mov al, 20h ; signal interrupt mask
out 20h, al ; register complete
pop ds
pop si
pop dx
pop ax
iret
_serisr ENDP
PUBLIC _buffchr
_buffchr PROC FAR
cli
push bp
mov bp, sp
push ds
mov ax, DGROUP
mov ds, ax
xor ax, ax
mov ax, count
pop ds
mov sp, bp
pop bp
sti
ret
_buffchar ENDP
Function 11-5 (continued)
-------- ----- ------ ----- ----- --------- ----- -------
SERISR.ASM
-------- ----- ------ ----- ----- --------- ----- -------
PUBLIC _readbuf
_readbuf PROC FAR
cli
push bp
mov bp, sp
push si
push ds
mov ax, DGROUP
mov ds, ax
xor ax, ax
cmp count, BOTTOM
jz back_below
mov si, back
mov al, buffer[si]
dec count
inc back
cmp back, TOP
jbe back_below
mov back, BOTTOM
back_below:
pop ds
pop si
mov sp, bp
pop bp
sti
ret
_readbuf ENDP
_TEXT ENDS
END
The ISRVEC Structure - If you study Functions 11-6 and 11-7, you
will see that they share the ISRVEC structure. The values placed
in this structure by Function 11-6 are used in Function 11-7 just
before you exit from your program back to DOS. In this way the
original interrupt vector is restored in a clean and responsible
way.
FUNCTION 11-6
-------- ----- ------ ----- ----- --------- ----- --------
INSTISR.C
-------- ----- ------ ----- ----- --------- ----- --------
/* INSTISR.C installs the serial interrupt service
** routine. Refer to 'serial interface at a glance'
** for register offsets used.
*/
#include <stdio.h>
#include <conio.h>
#include <dos.h>
struct ISRVEC
;
extern struct ISRVEC isrvec;
instisr(srvice, request, port)
unsigned (*resvice) ();
unsigned request;
unsigned long port;
setvect(vector, service)
int vector;
unsigned (*service) ();
The buffchr() Function - The word at offset 68 contains a nonzero
value when there is data in the buffer. You can check it while
your program is running to see if any goodies have been gathered
from the sending device. To read the count variable, because it
is not public, you must use the buffchr() function contained in
Function 11-5. If this function returns a nonzero value, it
indicates that there is at least one character in the rotating
buffer. This count is never allowed to exceed the total number of
characters in the buffer.
FUNCTION 11 - 7
-------- ----- ------ ----- ----- --------- ----- --------
UNINSTAL.C
-------- ----- ------ ----- ----- --------- ----- --------
/* UNINSTAL.C uninstalls the serial interrupt service
** routine.
*/
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#define SERPORT1 0x00000400L
#define SERPORT2 0x00000402L
#define SERVEC1 12
#define SERVEC2 11
struct ISRVEC
;
extern struct ISRVEC isrvec;
uninstal(port)
unsigned long port;
unsetvec()
The readbuf() Function - To read a character from the back of the
buffer, you must use the readbuf() function, which is given in
Function 11-5. The interrupt service buffer is intentionally kept
local so that it does not interact with external data. This means
that you must read the buffer with a function that knows where
the buffer is. Interrupts must be turned off before the buffer is
read and turned on again before the function returns to its
caller.
Function 11-5 will handle interrupts from each occurrence of a
full data latch, intercepting each byte as it arrives from the
sender, until the buffer is full. The current character is placed
at the front of the buffer, and the back of the buffer is read by
your program. The delay between the receipt of characters from
the sender and their retrieval by your program can be decreased
by decreasing the size of the receiver buffer. The buffer shown
is 64 bytes long. You can change the number of bytes, but be sure
to reflect the change throughout the routine. If you make the
buffer 20 bytes long, the offsets 64, 66, and 68 must be made 20,
22, and 24, respectively. The buffer never overflows, but if you
do not check it before it goes through one complete cycle, you
will lose one cycle`s worth of data.
Overrun Errors - If a byte arrives in the latch before the current
byte has been read from the latch, the interrupt-service routine
will receive garbled data. If you find that garbled data is being
received, lower the sender`s and receiver`s baud rates. The
interrupt-service routine is generally able to accommodate 9600
baud easily, however.
Making Interrupt-Service Routines Work -Making interrupt-service
routines work can be a difficult undertaking. The primary frus
tration is that not only must you debug your program, but you
must be aware that the interrupt-service routine is working
constantly while your software is running. Interrupts can occur
at any time. Unless you carefully coordinate the interrupt
service so that it does not interfere with the processes that are
being interrupted, you will find that mysterious things happen.
Function 11-5 has been carefully designed to use variables that
are never public. If public variables or calls are used,
interrupt-service routines can become interactive in a very
negative way.
Serial Interrupt Service Works Well -Be assured that Functions
11-5 through 11-7 will work well if you take the time to compile,
assemble, and link them exactly as they are. The serial interrupt
service they install and maintain will be the primary source of
input from your serial devices. These functions represent one
easy technique to make serial input fast and easy for every
application. The advantage of serial interrupts is that they can
keep up with the fastest senders and give you information only
when you want it. 9600 baud is IBM`s recommended limit, but it
can be exceeded at your own risk. Since most C compilers do not
support interrupt-service handling. Functions 11-5 through 11-7
should prove to be very valuable to you in all applications, not
just for graphics.
Digitizer Interfacing
It is difficult to speak in general terms about digitizer
interfacing. Digitizers are perhaps the least standardized of all
computer devices. The tradition in digitizer design is very
liberal. For this reason, as with other devices, a specific
digitizer is used here as an example. This does not constitute an
endorsement of the product, but the digitizer chosen is very
popular.
The Kurta Series One digitizer uses a serial interface to
communicate with the computer. If you use it as it is shipped
from the factory, its cable will plug into your COM1 port, and so
it will be easy to interface. If you wish to employ hardwire
handshaking, follow the user manual to learn how to connect pin 4
(the orange wire inside the connector shell) to the orange wire
coming from the digitizer. This connection is not made at the
factory, but it will enable your software to stop and start the
digitizer using bit 1 of register 4. (See Figure 11-2 for
details.)
Setting the Digitizer`s Switches - To work with serial interrupt
input from the digitizer, you should see the switches on the back
of the digitizer for 1200 baud. There are two banks of switches.
One has four positions, and the other has eight.
On the four-position switch, set all the switches to off. On
the eight-position switch, set positions 2, 3, and 8 to on and
all the others off. These switches govern option settings and
baud rate. Read about them in the digitizer manual.
Setting the Baud Rate - In the early days of the IBM PC,
applications were designed to set the baud rate within most
programs, ignoring the needs of the operating system. These days,
although it is less convenient, the user is encouraged to
establish a system-wide serial interface initialization. If
software changes this system-wide setting, it should restore it
on exit. Function 11-8, TEST_DIG.C, will configure the port for
1200 baud, no parity, 8 data bits, 1 stop bit, and no device
timeouts.
Testing the Digitizer - Using Function 11-5 as shown in Function
11-8, you can see a data stream from the digitizer. Assemble,
compile, and link Functions 11-5 through 11-8. Raw data from the
digitizer (or any other properly configured serial transmitter)
should appear rapidly on the display. As you move the digitizer
stylus, the characters will change. If they jump around wildly
from value to value, you are getting overrun errors, and you
should lower the baud rates of the receiver and sender.
FUNCTION 11-8
-------- ----- ------ ----- ----- --------- ----- --------
TEST_DIG.C
-------- ----- ------ ----- ----- --------- ----- --------
/* TEST_DIG.C tests the digitizer using
** serial interrupt service.
*
#include <stdio.h>
#include <conio.h>
#include <string.h>
#define SERPORT1 0x00000400L /* COM1 interrupt address */
#define SERPORT2 0x00000402L /* COM2 interrupt address */
#define SERVEC1 12 /* COM1 interrupt request (IRQ4) */
#define SERVEC2 11 /* COM2 interrupt request (IRQ3) */
struct ISRVEC
;
struct ISRVEC isrvec;
unsigned char serisr(), readbuf();
unsigned buffchr();
main()
if (thisbuff[5] >= 0x80)
}
}
getch(); /* dispose of pending character */
uninstal(SERPORT1);
/* restore port settings */
outp(*outbase + 3, bits);
outp(*outbase + 0, baudlo);
outp(*outbase + 1, baudhi);
/* turn off divisor latch select bit */
outp(*outbase + 3, inp(*outbase + 3) & 0x7F);
}
When you make a legal exit by pressing a key (other than a
control or shift key) on the keyboard, the original baud rate and
other settings of the serial port will be restored. To make sense
of the binary numbers, read your digitizer`s documentation and
study GRAPHIQ.C in Appendix A.
.pa
Polled Serial Interfacing
If you do not need the elaborate services of an interrupt-driven
serial input routine, you can easily implement polled serial
input. Polled serial input is an input method that waits for a
byte to enter the receiver latch before receiving it. Using
polled serial input, you ask for each character as it becomes
ready.
For most purposes, polled serial I/O is perfectly adequate, as
long as you keep the sender`s baud rate low enough so as not to
overrun the byte in the latch before it is read. You can always
test the overrun error bit (bit 1) in the status register
(register base+5). If it is on, data is being sent too fast for
your software to receive it. Your software can report overrun
errors to the user and explain how to lower the baud rate.
Function 11-9, COM_STR.ASM, gets as many characters as you
specify from the serial port and then lets the port continue
receiving data until the next poll. This function is particularly
good for use with digitizers because it always returns data that
is currently being sent. When using a digitizer or mouse, you do
not want data that has been buffered, because you do not want a
time delay between the coordinates you receive and the actual
position of the digitizer stylus.
Function 11-9 is written in assembler because it must run as
fast as possible. If it runs too slowly, it will be unable to
receive a character from the data latch before the next one
arrives. His overrun condition will result in garbled data. You
use Function 11-9, after assembling it, just as though it were a
function written in C.
FUNCTION 11-9
-------- ----- ------ ----- ----- --------- ----- --------
COM_STR.ASM
-------- ----- ------ ----- ----- --------- ----- --------
; COM_STR.ASM fetches a desired number of bytes
; (0 to 32767) from the serial input device.
; Usage from C:
;
; char buffer[0];
; int count;
;
; com_str(buffer, count);
;
; RETURNs:
; string of count characters in buffer
; return value 0
COM_STR_TEXT SEGMENT BYTE PUBLIC `CODE`
COM_STR_TEXT ENDS
CONST SEGMENT WORD PUBLIC `CONST`
CONST ENDS
_BSS SEGMENT WORD PUBLIC `BSS`
_BSS ENDS
_DATA SEGMENT WORD PUBLIC `DATA`
_DATA ENDS
DGROUP GROUP CONST, _BSS, _DATA
ASSUME CS: COM_STR_TEXT, DS: DGRO, SS: DGROUP, ES: DGROUP
PUBLIC =com_str
COM_STR_TEXT SEGMENT
PUBLIC _com_str
_com_str PROC FAR
push bp
mov bp, sp
; get pointer from stack
mov bx, [bp + 6]
; get count from stack
mov cx, Word Ptr [bp + 8]
cmp cx, 0
je exit
and cx, 7FFFh ; strip off high bit in
; case negative int was passed
not_ready:
; test for character
; in latch and no errors
mov dx, 3F8h ; Register 0 (base)
add dx, 5 ; Register 5
in al, dx ; Serial Status Register
test al, l ; Is only Data Ready set?
jz not_ready
; receive the character
mov dx, 3F3h ; Register 0 (base)
add dx, 3 ; Register 3
FUNCTION 11-9 (continued)
-------- ----- ------ ----- ----- --------- ----- --------
COM_STR.ASM
-------- ----- ------ ----- ----- --------- ----- --------
in al, dx ; Data Format Register
and dx, al ; not selected
mov dx, 3F8h ; Register 0 (base)
in al, dx ; input the type
; store byte in public string
mov Byte Ptr [bx], al
inc bx ; point to next character
; supply ASCIIZ string
; terminator (null byte)
mov Byte Ptr [bx], 0
dec cx
cmp cx, 0
jbe exit ; exit when cx reached
loop not_ready
exit:
mov ax, 0 ; always returns 0
mov sp, bp
pop bp
ret
_com_str ENDP
COM_STR_TEXT ENDS
END
Mouse Interfacing
The mouse, which is perhaps even more ubiquitous than the
digitizer, can also be used as a locator. The advantage in using
a mouse is that it is inexpensive, convenient, and supported by
most modern software. The only real disadvantage in using a mouse
is that you can work only with relative coordinates.
Relative coordinates are derived from the last known position of
the locator, rather than from a constant reference point. In
other words, the mouse knows only its last position and the
distance it has moved from it, not its absolute location on a
grid.
Unless you are tracing drawings, you will not need a locator
that has absolute position-reporting capabilities. To select from
menus, you need only to sense the direction of each successive
movement of the locator - whether up, down, left, or right.
The Mouse - The mouse used for the purposes of this description is
made by Mouse Systems. As with the digitizer, this does not
constitue an endorsement of this particular mouse, but it does
recognize that this mouse is very popular and thus suitable for a
general description of mouse interfacing techniques.
Function 11-9 will be used to receive a stream of characters
from the port to which your mouse is connected. For purposes of
demonstration, this will be COM1, but you can modify the source
code as you choose. You are strongly encouraged to try the mouse
with the serial interrupt-service routines of Functions 11-5
through 11-7 as well.
Receiving a Single Character - To receive one character at a time
quickly from a serial port, you should use a function like
Function 11-10, GET_SER.ASM. Written in assembler and callable
from C, this function waits for a character to appear in the data
latch and then reads the character. You could use Microsoft C`s
inp() function to do the same thing, but it would be slower,
especially using the Medium memory model, as is done throughout
this book. If you are receiving characters one at a time this
way, the baud rates of receiver and sender may need to be kept
very low in order not to overrun the data latch. If you use the
serial interrupt-service routine of Function 11-5 through 11-7,
you will not encounter this problem for baud rates of up to 9600.
FUNCTION 11-10
-------- ----- ------ ----- ----- --------- ----- --------
GET_SER.ASM
-------- ----- ------ ----- ----- --------- ----- --------
; GET_SER.ASM gets a single character from COM1.
; Declaration:
; char get_ser();
; char character;
; Usage:
; character = get_ser();
FUNCTION 11-10 (continued)
-------- ----- ------ ----- ----- --------- ----- --------
GET_SER.ASM
-------- ----- ------ ----- ----- --------- ----- --------
GET_SER_TEXT SEGMENT BYTE PUBLIC `CODE`
GET_SER_TEXT ENDS
CONST SEGMENT WORD PUBLIC `CONST`
CONST ENDS
_BSS SEGMENT WORD PUBLIC `BSS`
_BSS ENDS
_DATA SEGMENT WORD PUBLIC `DATA`
_DATA ENDS
DGROUP GROUP CONST, _BSS, _DATA
ASSUME CS: GET_SER_TEXT, DS: DGROUP, SS: DGROUP, ES: DGROUP
PUBLIC _get_ser
GET_SER_TEXT SEGMENT
PUBLIC _get_ser
_get_ser PROC FAR
push bp
mov bp,sp
not_ready:
; test for character
; in latch and no errors
mov dx, 3F8h ; Register 0 (base)
add dx, 5 ; Register 5
in al, dx ; Serial Status Register
text al, 1 ; Is only Data Ready set?
jz not_ready
; receive the character
mov dx, 3F8h ; Register 0 (base)
add dx, 3 ; Register 3
in al, dx ; Data Format Register
and a 7Fh ; make sure divisor latch
out dx, al ; not selected
mov dx, 3F8h ; Register 0 (base)
in al, dx ; input the type
mov sp, bp
pop bp
ret
_get_ser ENDP
GET_SER_TEXT ENDS
END
Getting Mouse Data Function 11-11, GET_MOUS.C, gets bytes sent
from the mouse over the serial interface and displays them using
standard output to the display. This function also shows how to
initialize the serial port and restore it on exit. The mouse
operates at 1200 baud with 8 data bits, 1 start bit, and 1 stop
bit. It uses no parity checking.
Compile Function 11-11 and link it with Function 11-10 to see
the mouse in action. As you move the mouse on its little pad, you
will see characters on the display that reflect its movement. As
you press buttons on the mouse, characters will appear that
reflect the button pressed.
The Limitations of Polled Mouse Interface - Function 11-11 is only
an example that gets some data from the mouse. It shows only one
way to interface the mouse. You can also use an interrupt-service
routine that you write yourself, like Function 11-5, or you can
use software tools provided by the mouse manufacturer. Since such
tools solve most of your low-level problems, they are highly
recommended.
Light Pen Interfacing
A light pen interface is perphaps the easiest interface of all.
The EGA supports light pens from a wide range of manufacturers,
and so no specific piece of hardware need be discussed. The EGA
BIOS service 4 of interrupt 10 (hex) is adequate to report both
the row and column number of the light pen in character positions
and the location of the pen in pixels.
Reading the Light Pen Position and Switch Function 11-12,
READ_PEN.C, reads the light pen position and indicates whether
the pen switch is pressed or not. The code is very direct and
will work no matter what mode the EGA is in.
FUNCTION 11-11
-------- ----- ------ ----- ----- --------- ----- --------
GET_MOUS.C
-------- ----- ------ ----- ----- --------- ----- --------
/* GET_MOUS.C gets an input stream of characters from
** the scurrying mouse. To make sense of the characters
** see your mouse documentation. Try modifying this
** function to report the x, y location and buttons.
*/
#include <stdio.h>
#include <conio.h>
#define SERPORT1 0x00000400L
#define SERPORT2 0x00000402L
main()
getch(); /* waste character from keyboard */
/* restore port settings */
outp(*base + 3, bits);
outp(*base + 0, baudlo);
outp(*base + 1, baudhi);
/* turn off divisor latch select bit */
outp(*base + 3, inp(*base + 3) & 0x7F);
}
FUNCTION 11-12
-------- ----- ------ ----- ----- --------- ----- --------
READ_PEN.C
-------- ----- ------ ----- ----- --------- ----- --------
/* READ_PEN.C reads the light pen position
** and switch status. Return value is 1
** if switch has been triggered.
*/
#include <conio.h>
#include <dos.h>
read_pen(row, col, x, y)
int *row, *col, *x, *y;
return(regs.h.ah)
}
Serial Output Interfacing
Receiving information from the outside world is usually much more
difficult than sending it. That is why so much attention has been
given to receiving data from the parallel and serial ports.
You can send data out a serial port merely by outputting bytes
to the serial port`s base register. You will face two major
problems if you try this. First, you must make sure that there
is nothing in the data latch that should be read.Second, you must
know if the receiving device has room for the data you are about
to send.
Output Verification and Handshaking - If you refer to Figure 11-2,
you will see the serial status register (register 5). Bit 0 of
this register, data ready, indicates that a byte is available in
the latch and can be read. If this bit is high and you are
interested in the information in the latch, you must receive it
before attempting to transmit a byte. Likewise, if bit 5 of
register r is 0, it means there is a byte in the transmitter
holding register that has not yet been sent. If you care about
the contents of this register, you must wait for this bit to be
set to high before trying to send a character.
The device to which you are sending will usually control pin 5
of the interface, setting it to high if you are clear to send
data and low if you are not clear to send. You must monitor pin 5
to see if the receiving device will permit you to send a byte.
You can do this by monitoring bit 4 of the input status register
(register 6). This register keeps track of the real-time of the
handshake wires connected to the interface pins.
Sending Characters Interactively Function 11-13, SEND_SER.ASM,
is a function, written in assembler and callable from C, that
sends a byte, but will do so only after it has received any
pending characters from the data latch. It is useful for communi
cations programs where you whish to send and receive characters
interactively. The function has no way to stop the sender from
sending more characters, however, and should be used only in
situations where you know that the number of characters to be
received will be limited. If you want full control, you must
implement a handshake and hold wire 20 to the destination low
until you can accept more characters. Function 11-13 must be
called in rapid succession in a tight loop if you are to avoid
overrun errors. If the loop does not run fast enough and will
work only at an extremely low baud rate, implement more of the
loop directly in assembler.
FUNCTION 11-13
-------- ----- ------ ----- ----- --------- ----- --------
SEND_SER.ASM
-------- ----- ------ ----- ----- --------- ----- --------
; SEND_SER.ASM sends and receives characters interactively.
; Declaration:
; char send_char();
; char character;
; Usage;
; rec_char = send_ser(character);
;
; Returns:
; if not zero, return is received character
; Note:
; requires pin 4 held high by destination to send
; no control. of pins 5 or 20
SEND_SER_TEXT SEGMENT BYTE PUBLIC `CODE`
SEND_SER_TEXT ENDS
CONST SEGMENT WORD PUBLIC `CONST`
CONST ENDS
_BSS SEGMENT WORD PUBLIC `BSS`
_BSS ENDS
_DATA SEGMENT WORD PUBLIC `DATA`
FUNCTION 11-13 (continued)
-------- ----- ------ ----- ----- --------- ----- --------
SEND_SER.ASM
-------- ----- ------ ----- ----- --------- ----- --------
_DATA ENDS
DGROUP GROUP CONST, _BSS, _DATA
ASSUME CS: SEND_SER_TEXT, DS: DGROUP, SS: DGROUP, ES: DGROUP
PUBLIC _send_ser
SEND_SER_TEXT SEGMENT
PUBLIC _send_ser
_send_ser PROC FAR
push bp
mov bp,sp
xor ax, ax ; clear AX
; text for character
; in latch and no errors
mov dx, 3F8h ; Register 0 (base)
add dx, 5 ; Register 5
in bh, dx ; Serial Status Register
test bh, 1 ; Is only Data Ready set?
jz no_receive
; receive the character
mov dx, 3F8h ; Register 0 (base)
add dx, 3 ; Register 3
in a1, dx ; Data Format Register
and a1, 7Fh ; make sure divisor latch
out dx, a1 ; not selected
mov dx, 3F8h ; Register 0 (base)
in a1, dx ; input the byte
no_receive:
mov dx, 3F8h ; Register 0 (base)
add dx, 6 ; Input Status Register
in bh, dx ; examine register 6
test bh, 10h ; is bit 4 (pin 5) on?
jz no_receive ; if not, wait until it is
not_empty:
mov dx, 3F8h ; Register0 (base)
add dx, 5 ; Serial Status Register
in bh, dx ; examine register 5
test bh, 90h ; are bits 5 and 6 on?
jz not_empty ; if not, wait until they are
; get character from stack
mov b1, [bp + 6]
mov dx, 3F8h ; Register 0 (base)
out dx, b1 ; send the character
; if char was received in a1
; it will be returned on exit
mov sp,bp
pop bp
ret
_send_ser ENDP
SEND_SER_TEXT ENDS
END
If at First You Don`t Succeed Serial interfacing can be
extremely frustrating. Keep in mind that there are many complex
variables involved and that the functions in this book, although
they work in isolated test situations, may not work if any of a
million combinations of factors are changed. It is impossible to
give examples of all the possible combinations here. On the other
hand, the functions are all sound in principle and throughly
illustrate the options available using serial interface
programming. If you carefully assemble, compile, and link
Functions 11-5 through 11-7 and use serial interrupt service in
your software, you can have done with serial input once and for
all.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1411
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved