home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Crawly Crypt Collection 1
/
crawlyvol1.bin
/
program
/
books
/
68k_book
/
arp_doc
/
chap_09.doc
< prev
next >
Wrap
Text File
|
1985-11-20
|
229KB
|
4,783 lines
Atari ST Machine Specific Programming In assembly
Chapter 9: Interfacing With Peripheral Hardware
I am going to spend most of this chapter discussing printing
algorithms and print buffers. Not because these objects alone
are important enough to warrant the considerable time I shall
devote to them, but because the topics will create an atmosphere
in which it will be convenient to discuss other relevant issues.
These other issues involve the parallel port, in general, and
that itself is an important resource, yet I will not explore all
of the ramifications of its use, but I will certainly try to
eliminate any mystery surrounding such use; certain system
functions that are useful, as well, in applications which do not
involve printing, and among these will be at least one which does
not function as specified; and certain problems that materialize
during the testing of algorithms--sometimes it is difficult to
determine just what is being tested. Yes, the central theme will
be print buffers, but the asides shall provide opportunities for
an expansion of knowledge about machine specific programming in
assembly language on the Atari ST.
Because the data transfer rates of printing mechanisms are
usually much slower than that of the other components involved
with printing, most users prefer to lessen the effect of the
printing mechanism's speed impact on the rest of the system by
installing some sort of buffer between the program that is
controlling the printing and that mechanism. The buffer is, of
course, memory, which can be part of that which we call main ram;
or it can be memory within the printer itself; or it can be the
memory in a device that is designed to be installed between the
computer and the printer. In all cases, the memory that is used
is hardware; but when the memory comprising the buffer is a
portion of computer ram that is allocated by a program executed
within the computer, the term software buffer is often applied.
When the behavior of the algorithm that stores data in the buffer
permits us to visualize the buffer's structure as if it were
circular, as opposed to linear, the term spooler is often
applied. In many references the word queue is used in place of
the word buffer.
There is a description of a print spooler on pages 68
through 70 of Atari ST Tricks & Tips, a Data Becker book,
published by Abacus Software. Following that discussion is an
assembly language program. If you are able to read the
discussion in that book, it will provide you with additional
descriptive information concerning print spoolers with which to
complement that which I am going to present here. However, I
suggest that you do not try to implement the program given as an
example in that book; there are much better ones available. In
fact, several will be presented shortly.
Data Transfer Rates
The term parallel interface port includes all circuits
involved with data transfer through the port connector that is
located at the rear of the computer. When we execute an
algorithm to send data through the port, the rate at which data
flows is the port's instantaneous data transfer rate. I say
instantaneous transfer rate because the data flow could be
limited by the algorithm which is sending the data; a different
algorithm might permit the exchange of data at a different rate.
In this particular case, there are two data transfer rates
involved; the explicit instantaneous rate and the implicit
maximum data transfer rate of the port. For sure, sending
algorithms are not going to push valid data through the port at
rates that exceed its maximum data transfer rate. Other devices
which could exert pressure on the transfer of data through the
port are the printer's port, and its internal buffer, if it has
one; the printing mechanism; an external print buffer connected
between the printer and the port; and any buffer that has been
constructed by software within the computer.
By peripheral hardware, in this chapter, I mean those
semiconductors that exist on the periphery of the microprocessor.
These semiconductors interface the MC68000 to other components of
the ST system and to devices connected to the computer. Among
others, this group of integrated circuits includes the MC68901
Multi-Function Peripheral (MFP) and the AY-3-8910 (aka YM-2149,
according to the Abacus Internals book and their Graphics & Sound
book) Programmable Sound Generator (PSG). These are the two
peripheral hardware components I will discuss in this chapter.
I will concentrate my discussions of the MFP and the PSG on
those portions of the devices which are involved with the
parallel interface port. You can obtain a complete reference
guide for the MFP from a local Motorola office, from a
distributor or from Motorola Semiconductors, 3501 Ed Bluestein
Blvd., Austin, Texas 78721. You can find a reference guide for
the PSG (listed as AY-3-8910A) in the ARCHER Semiconductor
Reference Guide, available from Radio Shack stores.
Unfortunately, this guide does not present any information
concerning the use of the two PSG I/O ports. Naturally, these
are the parts of the integrated circuit with which I am most
interested. In addition, these devices are scantily
discussed in the Internals book. I suggest that you try to
obtain General Instrument's AY-3-8910/8912 Programmable Sound
Generator Data Manual. If you are interested in the sound
capabilities of the PSG, you can refer to appropriate books and
magazine articles that address sound on the ST. The other
peripheral components, such as the disk drive circuits and the
RS-232 circuits, are also discussed in various references. You
can begin with the material in the Internals book and progress to
other references as you desire.
The proper system configuration for the material in this
chapter consists of the ST system and a printer connected to the
parallel port. Initially, there should be nothing but a cable
between the ST port and the printer port; that is, eliminate all
dongles (A dongle is a hardware device that is designed to be
installed in the parallel interface port, but which has nothing
to do with printing.) and hardware print buffers. If your
printer contains an internal buffer that is greater that one line
in length, and if this buffer can be disabled, you will be able
to gain from the experience if you do disable it until you are
finished with the material in this chapter.
The Multi-Function Peripheral
The MFP is a very complicated device. I am going to discuss
only the components of this chip that I must in order to provide
a lucid presentation of its interaction with the ST's parallel
interface port. The MFP has 24 directly addressable registers:
of these, as far as this discussion is concerned, we are
interested in the General Purpose I/O Register (GPIR)--for some
reason Motorola uses the mnemonic GPIP for this register--as you
can see, I use GPIR; the Active Edge Register (AER); the Data
Direction Register (DDR); the Interrupt In-Service Register B
(ISRB) and the Vector Register (VR). In addition, we will be
interested in bit 0 of the 8-bit General Purpose I/O Interrupt
Port (GPIP).
The MFP registers are discussed on pages 32 through 40 of
the Internals book. The memory addresses for the registers are
listed on pages 60 and 61. Access to these locations is
permitted only while the processor is in supervisor mode. You
can enter the supervisor mode from the AssemPro debugger simply
by clicking on the S status register bit. The ST's interrupt
structure is discussed on pages 240 through 244. The precise
bits of knowledge that must be extracted from the references in
the Internals book, from the Motorola MFP manual (pages 3 and 4)
and from explorations within the AssemPro debugger are these:
1. The contents of the VB register can be read at
location $FFFA17. Those contents are $48
(binary 01001000). Notice that the 3rd bit,
called the S bit is set. Because this
particular bit is set, the MFP operates in the
sosftware end-of-interrupt mode. When the MFP
operates in this mode, interrupt handlers for
MFP generated interrupts must clear the
appropriate bit in the appropriate in-service
register at the conclusion of the handler
algorithm.
2. For pin 11 of the parallel interface port the
in-service bit is bit 0 of ISRB. The address
of this register is $FFFA11. An instruction
that will clear the appropriate bit is:
bclr #0, $FFFA11.
3. The contents of the AER can be read at $FFFA03.
The byte of data there is $4 (binary 00000100).
The state of bit 0 determines whether an
interrupt will be generated by a one-to-zero
transition on pin 11, or by a zero-to-one
transition. Because the state of pin 0 is 0,
interrupts will be generated by a one-to-zero
transition.
4. The contents of the DDR can be read at $FFFA05.
The byte of data there is $0 (binary 00000000).
The state of each bit determines whether the
corresponding bit of the GPIP is to function as
an input pin or an output pin. When the logic
level of a bit in the DDR is 0, the associated
GPIP pin functions as an input. Of interest,
in this particular case, is the fact that bit 0
is programmed to be an input pin. And this is
the bit to which pin 11 of the parallel
interface is connected.
5. The contents of the GPIR can be read at
$FFFA01. The data stored in the GPIR is
precisely that which is present at the GPIP;
therefore, we can determine the logic state of
pin 11 any time we choose to do so, by
observing the bit 0 state of the data byte
stored at $FFFA01.
When the printer is off; that is, no power applied, the
logic state of pin 11 is high. When the printer is on; that is,
power is applied, and on-line (there is a difference) the logic
state is low. When the printer is on (power applied) but off-
line, I can't tell you the state for your particular printer. I
can tell you that my Star NX-10 keeps the line low when the
printer is initially put off-line, which is a little silly
because the printer cannot accept data when it is off-line.
However, if the computer attempts to send a byte of data through
the interface, the printer forces pin 11 high. On page 218 of
the printer manual is a statement which indicates that pin 11 is
high when the printer is off-line, but the restriction mentioned
above is not specified in detail. Two other conditions force the
logic state of pin 11 high; a full buffer and lack of paper.
In summary, bit 0 of the MFP GPIP is programmed by the
operating system to be an input; as such it is connected to pin
11 of the parallel interface port. The logic state of this pin
is controlled by the printer connected to the port. If the logic
state is low the computer assumes that the printer is ready to
receive data; if the logic state is high the computer assumes
that the printer cannot receive data.
We can read the byte of data stored at $FFFA01 almost as
easily as we can read data at any other location; almost as
easily because the data at $FFFA01 can't be read unless the
processor is in supervisor mode--remember the second MC68000
flaw. Let us observe the data stored at that location, now. In
the AssemPro debugger, click on the Status Register S-bit to
toggle the processor into the supervisor state. In the register
field, double click on one of the registers, say A4, and, in the
dialog box which appears, select .B, then clear the dialog line
by pressing the Esc key. Finally, type $FFFA01 on that line and
press the Return key or click on the OK box. In place of
L:A4 = 0
you should see
B:$FFFA01 = $77
if the printer is off, but
B:$FFFA01 = $76
if the printer is on and ready to receive data. If you see a
value other than $76 or $77, it will be because one or more of
the GPIR bit values in your system differ from those of mine.
Refer to page 61 of the Internals book for a list of the GPIP
bits and the use applied to each by the ST. For example, bit 7
is used to detect a monochrome monitor. I have a monochrome
monitor and that bit is 0 in my GPIR. I deduce that you will see
that bit as a 1 if you have a color monitor; other things being
equal, you would probably see $F6 or $F7 as the contents of
$FFFA01 if you have a color monitor.
If the printer was off, turn it on now; if it was on, turn
it off. The contents displayed for $FFFA01 will not change until
you do something to cause the register field portion of the
debugger screen to be rewritten. You can force that simply by
double clicking on the Show register button just beneath the
register field. Using this method, you can determine the logic
state of pin 11 for any printer state. Remember that bit 0 of
the data at $FFFA01 indicates the state of pin 11; hence, the
relevant data nibble is the least significant of the two; that
is, the second digit will be even if the printer is on, but it
will be odd when the printer is off.
Program 60 has been designed to determine printer status via
the invocation of BIOS function #8. See pages 161 and 154 of the
Internals book. When the program is executed from the desktop,
the ready condition of the printer is printed on the video
screen. Press the Return key to terminate the program. Program
61 determines printer status via direct communication with the
MFP GPIR. You should execute these programs for each of your
printer states.
Program 60. Using BIOS Function #8 to Determine Printer
Status.
; Program Name: PRG_7AP.S
; Version: 1.002
; Assembly Instruction:
; Assemble in PC-relative mode; save program with .TOS suffix.
; Execution Instructions:
; Execute from the desktop, once for each relevant printer state.
; Function:
; Invokes BIOS function #8, bcostat, to determine the logic state of
; pin 11 of the parallel interface port. See pages 161 and 154 of the
; Internals book. When this function is invoked, the value -1 is returned
; in data register D0 if the logic state of pin 11 is low; the value 0 is
; returned if the logic state is high.
determine_printer_status:
move.w #0, -(sp) ; 0 means pin 11 of parallel interface.
move.w #8, -(sp) ; Function = BIOS bcostat.
trap #13 ; Value returned in D0 indicates logic
addq.l #4, sp ; state of pin 11 of parallel interface.
btst #0, d0 ; If bit 0 = 0, logic state is high,
beq.s printer_not_ready ; else logic state is low.
lea ready, a0
bra.s print_string
printer_not_ready:
lea not_ready, a0
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp) ; Function = GEMDOS p_term_old.
trap #1
data
ready: dc.b 'Printer is ready.',$D,$A,0
not_ready: dc.b 'Printer is not ready.',$D,$A,0
align
end
Program 61. Determining Printer Status via direct
communication with the MFP GPIR.
; Program Name: PRG_7BP.S
; Version: 1.003
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS suffix.
; Execution Instructions:
; Execute from the desktop, once for each relevant printer state.
; Function:
; Determines the logic state of pin 11 of the parallel interface port by
; testing bit 0 of the data stored at address $FFFA01, which is the
; address of the MFP's General Purpose I/O register. If the state of this
; bit is zero (low), then the printer is ready to receive data; if the state
; of the bit is one (high), then the printer is not ready to receive data.
; Remember: if your printer keeps the logic level of pin 11 low when
; it is off-line, then the test will erroneously indicate that
; the printer is ready to receive data when the printer is in
; that state.
mainline:
; NOTE: Must enter supervisor mode in order to access the MFP's addresses.
enter_supervisor_mode:
move.l #0, -(sp)
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; Supervisor mode is active after TRAP.
addq.l #6, sp ; D0 = SSP.
move.l d0, d3 ; Preserve SSP in D3.
btst #0, $FFFA01 ; $FFFA01 is address of MFP GPIR.
beq.s printer_ready ; Branch if logic state of pin 11 is low.
lea not_ready, a0 ; Print string if logic state is high.
bra.s print_string
printer_ready:
lea ready, a0
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
enter_user_mode:
move.l d3, -(sp) ; D3 contains SSP.
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; User mode is active after TRAP.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = GEMDOS p_term_old.
trap #1
data
ready: dc.b 'Printer is ready.',$D,$A,0
not_ready: dc.b 'Printer is not ready.',$D,$A,0
align
end
Using the AssemPro debugger to monitor address $FFFA01, or
by executing programs 60 and 61, we are able to determine the
static logic level of pin 11 of the parallel interface port.
When data is being transferred through the port, however, the
logic level of pin 11 is dynamic; that is, constantly changing to
reflect the printer's ability to receive a data byte from the
computer. The printer forces pin 11 high shortly after pin 1 of
the parallel interface is forced low by the computer. The
computer forces pin 1 low just before it sends a data byte to the
printer, then it forces pin 1 high after the data byte has been
sent. Shortly after pin 1 has been forced high, the printer
forces pin 11 low again. During data transfers, therefore, the
logic level of pin 11 is represented by a series of pulses,
instead of a steady state level.
When the operating system is used to effect data transfers
to the printer, its functions take care of pin 1's logic levels,
and these functions monitor the state of pin 11. A programmer
who restricts his repertoire to operating system functions need
not be concerned with such detail. It is only when one desires
to bypass the operating system that intimate knowledge of the MFP
and the PSG becomes important. Of course, we want to bypass the
operating system; that's why we are writing and reading this
book.
The Programmable Sound Generator
I begin with the information given on pages 59 and 60 of the
Internals book, under the heading $FF8800 Sound Chip. There, it
is stated that the PSG has 16 registers, none of which is
directly addressed. This information does not agree with the
specification guide in the Radio Shack book; the information in
the Radio Shack book is incorrect. I dwell on this point because
a device such as the PSG will often permit direct access to its
registers via memory-mapped I/O. This device, however, does not;
yet the PSG is connected in the ST so that it behaves as if data
exchanges with the parallel interface are partially accomplished
via directly addressable registers.
In the Internals book, reference is made, on pages 59 and
60, to a select register. Yet, there is no mention of this
register on pages 48 through 54, where the PSG and all of its
registers is discussed. A select register is not mentioned in
the Radio Shack description of the PSG. However, PSG I/O port
selection in the ST is accomplished via a memory location which
behaves as if it is a select register. The term select register
is used to describe a register through which device parameters
are set. The conclusion to be drawn at this point is that we are
more concerned with the apparent behavior of the PSG as it
operates in the ST than we are in the description of the PSG as
given by reference books or by the manufacturer of the device.
Register 7 of the PSG is described as a mixer control-- I/O
Enable register in the Radio Shack book. The second half of this
description is the one in which we are interested, because it
refers to the I/O ports. It is the state of two bits in that
register which determine the direction of data flow through the
two I/O ports. Bit 6 controls the direction of data flow through
port A; bit 7 controls the direction of data flow through port B.
When a port bit in register 7 is 0, the direction of flow must be
into the PSG; when a port bit is 1, the direction of flow must be
out of the PSG. In the ST, both port bits are set to 1 during
the system initialization sequence, therefore, data flow through
both ports must be out of the PSG, unless the bits are altered by
an appplication.
Since that is the configuration we need, it would seem that
nothing else about this subject need be said. However, because
register 7 is programmable, and because it may come to past that
one of your applications (or one that you purchase) alters the
bits in register 7, it is reasonable that I give the following
admonition: if you alter specific bits in register 7, then you
must do so in a manner that alters only those you intend. In
order to assure this, you make changes in the register with a
mask. You will see how this is done with the OR and AND
instructions in an example to be presented shortly.
PSG register 17 (aka port B) is the one through which the
code bound for the printer actually travels. Just to confuse
you, the designers of the chip made the address of this register
15, which is hex F. Register 16 (aka port A) is the one through
which a signal is sent to inform the printer that the computer
wants to send a byte of data. Again, just to make your life
miserable, the designers of the chip decided that the address of
this register should be 14 = $E.
The data byte bound for the printer is usually stored in
port B as the second step in a two-step process. The first step
involves the placement of the register number on the PSG's
internal bus located at its so called base address (A base
address is usually the address of the first memory-mapped
register of a device.). Let me say this carefully, because it is
as confusing as it seems. Address $FF8800 is the address to
which we write the number of the PSG register in which we want to
place data or from which we want to retrieve data, and it is also
the address from which we do read data. That is, we select a
register by storing its address at ram location $FF8800; then, if
we want to read the data stored in the selected register, we read
location $FF8800. If we want to write to the selected register,
on the other hand, ram location $FF8802 is the address to which
we write the data that is to be stored in a previously selected
register.
This information does not agree with that given in any
specification guide that I have seen, and I have seen them all,
but I have verified that these are the only memory locations
which can be used to pass the data through registers 16 and 17 of
the PSG. Note that the information in the Internals book is not
entirely correct. There it states that reading address $FF8800
gives you the last PSG register used. The truth is this: if you
simply read the content of address $FF8800, perhaps by moving
that content into a data register, the data read (moved into the
data register) will be the content (not the register number
itself) of the last PSG register selected. However, unless you
already know which register was the last to be selected, you
can't know from which register the data is being read.
The two-step write process mentioned above
requires two move instructions, as follows:
1. move.b register_address, $FF8800
2. move.b data, $FF8802
where register_address is the address (using any valid addressing
mode) of the register that is to receive the data which will
subsequently be placed on the PSG internal bus, in this case
register 17 at address $F, and data is that data (using any valid
addressing mode).
Once the data has been placed in PSG register 17, pin 1 of
the parallel interface must be pulsed from high to low and back
to high to effect the data transfer through the parallel
interface port. This is also known as strobing pin 1. Pin 1 of
the parallel interface is normally held high by the computer.
When the computer is ready to send data to the printer, it
strobes pin 1 low for a specific length of time.
When the printer has received the data, it strobes pin 10 of
the parallel interface low for a specific length of time. In
other words, the printer acknowledges reception of the data.
Unfortunately, the ST does not provide a method of recognizing
this acknowledge strobe. Too bad, it would be of great
assistance were it available. To circumvent this inconvenience,
we are forced to rely on pin 11 of the parallel interface to tell
us, in a roundabout way, that the printer has received the data.
Pin 11 of the parallel interface is held high by the printer
while data transfer is occurring and when the printer is off (and
probably when it is off-line); in other words, whenever the
printer cannot accept data. At all other times, the printer
holds this pin low. So, we use this pin as a pseudo ACK
(acknowledge) pin. Because of this double duty assigned to pin
11, when we check the dynamic status of pin 11 and find that it
is high, we can't determine the true printer status; we don't
know whether it is off or simply transferring data.
Therefore, when we want to send a character to the printer,
we sometimes program a waiting period in an elapsed time counter,
or counting loop, and continuously check the status of pin 11
until its logic level is low, but give up the waiting if the
predetermined elapsed time expires. (It is also possible to let
the printer interrupt the processor and declare that it is ready
to accept a byte of data.) If pin 11 has not become low during
the waiting period, we assume that the printer is turned off,
then we abandon the data transfer attempt and/or send a warning
message to the video screen.
To complete the data transfer through the PSG, then, pin 1
of the interface must be strobed. PSG register 16 (aka port A),
at address $E, provides the access to this pin. However, as
indicated in the Internals book, this port is used for 7 other
functions as well. Therefore, when we manipulate the port A bit
connected to pin 1 of the parallel interface, we must leave the
other bits unaltered.
Bit 5 of register 16 is the bit we strobe to transfer the
data from register 17 to the printer. To preserve the state of
the other seven bits, we first select register 16 for the
transaction, and read its content into a data register. Then we
place a 0 in bit 5 of the data register by logically ANDING the
binary value 11011111 (hexadecimal DF) with the content of the
data register. Finally, we write the data register content to
port A. The instructions are as follows:
1. move.b #$E, $FF8800 (select register 16)
2. move.b $FF8800, dn (read contents)
3. andi.b #$DF, dn (combine with mask)
4. move.b dn, $FF8802 (write combination)
These four instructions strobe pin 1 of the printer's
parallel interface low. We finish up by strobing the pin high
with the following instructions, again, preserving the state of
the other seven bits:
1. ori.b #$20, dn (combine with new mask)
2. move.b dn, $FF8802 (write combination)
where, dn, in the above sets of instructions is any data
register. When setting up the data register for the high strobe,
we OR the binary value 00100000 (hexadecimal #$20) with the data
register's content. Using the strobing instructions, we generate
electrical signals in a manner that is analogous to flipping
switches manually. Program 62 is an example which illustrates a
method of programming the PSG to effect printer output without
the assistance of the ST's operating system functions. Of
course, since the addresses by which we access the PSG are
protected, we must enter supervisor mode in order to gain
accessibility. We do use system commands for this.
Program 62. Direct output via the parallel port.
; Program Name: PRG_7CP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS suffix.
; Function:
; Provides direct output via the Parallel Port Interface.
; DESCRIPTION
; The object of this experiment is to produce a minimal algorithm that
; will send a string of characters to a printer connected to the parallel
; port.
mainline:
; NOTE: Must enter supervisor mode in order to access MFP and PSG addresses.
enter_supervisor_mode:
move.l #0, -(sp)
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; Supervisor mode is active after TRAP.
addq.l #6, sp ; D0 = SSP.
print_string:
lea string, a3 ; Fetch address of string.
print_character:
move.b (a3)+, d1
beq enter_user_mode
printer_ready_test:
btst #0, $FFFA01 ; Check logic level of pin 11.
bne.s printer_ready_test ; Loop until printer is ready to receive.
; NOTE: If my printer is off-line, the program will remain in the above loop
; after the first character is sent, until the printer is placed back
; on line. Why? Because the printer does not permit pin 11 to return
; to zero after it receives that first character. Therefore, there is
; a kind of delayed reaction to the off-line condition.
lea $FF8800, a2 ; Fetch address of PSG data bus.
move.b #$F, (a2) ; Latch address of port B.
move.b d1, 2(a2) ; Write character to port B.
move.b #$E, (a2) ; Latch address of port A.
move.b (a2), d1 ; Read content of port A.
andi.b #$DF, d1 ; Reset bit 5 of d1 to zero.
move.b d1, 2(a2) ; Reset bit 5 of port A. Strobe low.
ori.b #$20, d1 ; Set bit 5 of d1.
move.b d1, 2(a2) ; Set bit 5 of port A. Strobe high.
bra print_character
enter_user_mode:
move.l d0, -(sp) ; D0 contains SSP.
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; User mode is active after TRAP.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
data
string: dc.b 'This string is written to the printer using a routine',$D,$A
dc.b 'that communicates directly with the PSG ports.',$D,$A,0
align
end
Sending Files To The Printer
I have covered MFP and PSG basics. Now, it order to delve a
little deeper, I must move into the general subject of sending
files to the printer. But before I begin, let me simplify some
of the terminology that I will have to use. Usually, we are
forced to utilize cumbersome phrases to designate the transfer of
data from the computer to a peripheral. One reason is that we
use the word printing when referring to the sending of data to
the video screen, as well as when referring to the sending of
data to the printer. I intend to eliminate the need for phrases
by calling the one screening and the other printing. I shall use
the word print to indicate output from the computer to the
printer; and I shall use the word screen to indicate output from
the computer to the video screen.
I will breach the subject slowly, beginning with program 63.
I try nothing fancy in this program. It is assembled as a TTP
program that accepts a file name on the TTP dialog box input
parameter line and prints, not screens, the file, using simple
operating system functions. With this program, I also illustrate
the kind of sequential process that I use to obtain information
about the operation of the system.
Program 63. Printing a file with a TTP program using
system functions.
; Program Name: PRG_7DP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TTP extension.
; Function:
; Accepts a file name on the TTP dialog box input parameter line and
; prints that file using operating system functions.
; Execution Instructions:
; Can be executed from the desktop, and, if the command line is fixed
; manually as described in the note below, can be executed from the AssemPro
; debugger in an uncorrupted system environment; that is, don't try it if
; you are using a program such as Switch/Back or Revolver to divide your
; system into partitions. Both this program and the file to be printed must
; be in the same directory.
; Note:
; This program was designed and assembled using a MEGA ST2. While
; most of the information concerning the GEMDOS functions probably apply
; to all ST's, there may be some minor differences in the MEGA, such as
; the size of the buffer that is used to store a file as it is read.
; The name of the file used to test the program was GEMDOS_9.S, which
; was 859 decimal bytes long. First executions were from the desktop. The
; name of the file to be printed was typed on the TTP dialog box input
; parameter line; once in upper case, once in lower case. The file was
; printed correctly in both instances.
; The program was then executed from the AssemPro debugger, using the
; following steps:
; 1. Single click on Execute program button.
; 2. On the command line, typed a leading space followed by the name of
; the file, GEMDOS_9.S.
; 3. Clicked on OK button in dialog box.
; 4. In the File Selector box, double clicked on PRG_7DP.TTP.
; 5. On the first line in debugger output window, noted address of
; command line in the instruction: LEA $89A68(PC),A3.
; 6. Clicked on from address button and typed 89A68 on dialog line at
; bottom of screen.
; 7. Clicked on disassembled button. Noted file name in output window.
; 8. Counted number of characters in file name = 10.
; 9. Typed in 0A at address $89A68.
; 10. Clicked on disassembled button.
; 11. Double clicked on from address button.
; 12. Installed a breakpoint at TST.W D0 instruction located just below
; the TRAP #1 instruction, which is located just below the fetch_byte:
; label in the source listing.
; 13. Clicked on the Run program button to execute to breakpoint.
; 14. Removed the breakpoint.
; 15. Clicked on the from address button.
; 16. Pressed the Return key to get to address $0.
; 17. Clicked on the Search button.
; 18. Typed ' ; Program Name', which is part of the first line of file
; GEMDOS_9.S, on the parameter line of the Search for : dialog box.
; 19. Clicked on the dialog box's OK button.
; 20. Clicked on disassembled button. Noted that file GEMDOS_9.S was
; in ram; location of first byte at address $79C4.
; 21. Clicked on output window's right bar to get to the end of the data
; read into the GEMDOS function $3F buffer in order to observe the
; number of bytes of the file which had just been read in. The last
; character that could be identified as part of file GEMDOS_9.S was
; the ) character, which is part of the line that reads
; move.w #9, -(sp)
; in the source listing. This character was located at address $7BC3.
; 22. Calculated the number of bytes read as
; $7BC3 - $79C4 + 1 = $200 = 512 decimal bytes.
; No output to printer yet, at this point.
; 23. Clicked on disassembled.
; 24. Double clicked on from address button to get back to program.
; 25. Clicked on right window bar to get to the LEA $C(A7),A7 instruction,
; which is located just below the "end_of_file" label in the source
; listing. Placed a breakpoint there.
; 26. Clicked on the Run program button in order to execute program to
; breakpoint. Output to printer began. Entire file was printed.
; 27. Removed breakpoint.
; 28. Clicked on from address button.
; 29. Typed address 79C4 on parameter line.
; 30. Clicked on disassembled button.
; 31. At address $79C4, again observed the first line of file GEMDOS_9.S.
; 32. Clicked on the right window bar to get to the end of the file, noting
; that the entire file was in ram. Last byte of the file proper
; was a linefeed character, $0A, located at address $7D1D. The first
; byte of this file segment was located at the byte following the last
; byte of the previously read segment; that is, at address $7BC4.
; Following the character located at $7D1D was a NULL byte at location
; $7D1E.
; 33. Calculated the number of bytes read as
; $7D1D - $7BC4 + 1 = $15A = 346 decimal bytes.
; The sum of the first and second reads = 512 + 346 = 858 bytes, one
; byte less than the size of the file on disk. Concluded that the NULL
; byte is supposed to be included as part of the file; added 1 to 858
; to obtain the correct size of 859 bytes.
; 34. Clicked on the disassembled button.
; 35. Clicked on the Run program button. Program terminated.
; Conclusions:
; The GEMDOS $3F function reads in 512 bytes each disk access and stores
; the data in a buffer that is larger than 512 bytes but probably smaller
; than 1 megabyte. If the file is smaller than the buffer, then the function
; will continue to read until the entire file has been read before moving on.
; By processing a 16,758 byte file and searching through ram after the
; file had been printed, I was able to determine that the GEMDOS function $3F
; buffer size is $400 = 1024 decimal.
fetch_pertinent_addresses:
lea -$82(pc), a3 ; Fetch "command line" address.
lea -$80(a3), a1 ; Fetch "basepage" address.
lea program_end, a0 ; Fetch "end of program" address.
lea stack, a7 ; Fetch stack address.
lea buffer, a4 ; Fetch buffer address.
calculate_program_size:
suba.l a1, a0
return_unused_memory:
pea (a0) ; Push program length.
pea (a1) ; Push basepage address.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; Invoke GEMDOS exception.
lea $C(a7), sp ; Reset stack pointer to top of stack.
process_command_line:
move.b (a3)+, d0 ; Fetch command line character count.
ext.w d0 ; Extend to word for next instruction.
move.b #0, 0(a3,d0.w) ; Store a null at end of command line input.
open_file: ; Function returns file handle in D0.
move.w #0, -(a7) ; Open as read only.
pea (a3) ; Push address of file name.
move.w #$3D, -(a7) ; See Internals page 127.
trap #1
addq.l #8, a7
move.w d0, d3 ; Store file handle in D3.
read_file: ; Function returns 1 in D0 unless end of file is
; reached or a read error occurs.
pea (a4) ; Push address of buffer.
move.l #1, -(a7) ; Number of bytes to read from
; GEMDOS $3F's buffer.
move.w d3, -(a7) ; Push file handle.
move.w #$3F, -(a7) ; See Internals page 129.
; Note: The system will not disturb this stack setup, therefore, it must be
; initialized only once. Thereafter, a branch need be taken only to the
; following instruction.
fetch_byte:
trap #1
tst.w d0 ; When NULL at end of file is read,
ble.s end_of_file ; D0 will be 0.
print_byte:
move.b (a4), d0 ; Must read buffer one byte each time.
move.w d0, -(sp) ; But must push a word here.
move.w #0, -(sp)
move.w #3, -(sp) ; See Internals page 155.
trap #13
addq #6, sp
bra.s fetch_byte
end_of_file:
lea $C(sp), sp
close_file:
move.w d3, -(sp)
move.w #$3E, -(sp) ; See Internals page 128.
trap #1
addq #4, sp
terminate:
move.w #0, -(sp)
trap #1
data
buffer: dc.w $0 ; Character buffer.
bss
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
Now we begin to get fancy. The next program accomplishes
the same task as does program 63. Here, however, the parallel
interface is accessed directly. The documentation presented in
programs 62 and 63 apply to this program also. Note that I am
not wasting space on error checking of any kind; nothing should
go wrong; but if anything does go wrong, please feel free to use
the switches located on the rear of your computer.
Program 64. Printing a file by accessing the parallel
interface directly.
; Program Name: PRG_7EP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Function:
; Accepts a file name on the TTP dialog box input parameter line and
; prints that file, as does PRG_7DP.S, however, this program accesses the
; parallel interface directly, as does program PRG_7CP.S.
; Execution Instructions:
; Execute from the desktop. The name of the file typed on the TTP
; dialog parameter line and PRG_7EP.TTP must reside in the same directory.
; See documentation in PRG_7CP.S and PRG_7DP.S.
fetch_pertinent_addresses:
lea -$82(pc), a3 ; Fetch "command line" address.
lea -$80(a3), a1 ; Fetch "basepage" address.
lea program_end, a0 ; Fetch "end of program" address.
lea stack, a7 ; Fetch stack address.
lea buffer, a4 ; Fetch buffer address.
calculate_program_size:
suba.l a1, a0
return_unused_memory:
pea (a0) ; Push program length.
pea (a1) ; Push basepage address.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; Invoke GEMDOS exception.
lea $C(a7), sp ; Reset stack pointer to top of stack.
process_command_line:
move.b (a3)+, d0 ; Fetch command line character count.
ext.w d0 ; Extend to word for next instruction.
move.b #0, 0(a3,d0.w) ; Store a null at end of command line input.
open_file: ; Function returns file handle in D0.
move.w #0, -(a7) ; Open as read only.
pea (a3) ; Push address of file name.
move.w #$3D, -(a7) ; See Internals page 127.
trap #1
addq.l #8, a7
move.w d0, d3 ; Store file handle in D3.
; NOTE: Must enter supervisor mode in order to access MFP and PSG addresses.
enter_supervisor_mode:
move.l #0, -(sp)
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; Supervisor mode is active after TRAP.
addq.l #6, sp ; D0 = SSP.
move.l d0, d4 ; Preserve SSP in D4.
read_file:
pea (a4) ; Push address of buffer.
move.l #1, -(a7) ; Read one byte.
move.w d3, -(a7) ; Push file handle.
move.w #$3F, -(a7) ; See Internals page 129.
fetch_byte:
trap #1
tst.w d0 ; When NULL at end of file is read,
ble.s end_of_file ; D0 will be 0.
print_byte:
move.b (a4), d0 ; Must read buffer one byte each time.
printer_ready_test:
btst #0, $FFFA01 ; Check logic level of pin 11.
bne.s printer_ready_test ; Loop until printer is ready to receive.
lea $FF8800, a2 ; Fetch address of PSG data bus.
move.b #$F, (a2) ; Latch address of port B.
move.b d0, 2(a2) ; Write character to port B.
move.b #$E, (a2) ; Latch address of port A.
move.b (a2), d0 ; Read content of port A.
andi.b #$DF, d0 ; Reset bit 5 of d1 to zero.
move.b d0, 2(a2) ; Reset bit 5 of port A. Strobe low.
ori.b #$20, d0 ; Set bit 5 of d1.
move.b d0, 2(a2) ; Set bit 5 of port A. Strobe high.
bra.s fetch_byte
end_of_file:
lea $C(sp), sp
close_file:
move.w d3, -(sp) ; Push file handle.
move.w #$3E, -(sp) ; See Internals page 128.
trap #1
addq #4, sp
enter_user_mode:
move.l d4, -(sp) ; D4 contains SSP.
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; User mode is active after TRAP.
addq.l #6, sp
terminate:
move.w #0, -(sp)
trap #1
data
buffer: dc.w $0 ; Character buffer.
bss
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
The custom trap handler used in program PRG_6KC.S can be
improved by replacing the algorithm used to print therein with
that used to print in program 64. Multiple entries into the trap
#13 handler is eliminated, and the function required to screen
can be removed. Now that I think about it, maybe that particular
function could have been eliminated in PRG_6JC and PRG_6KC, in
the first place. No matter, If I were that good, I'd be rich and
I would not be doing this, in the second place. Anyway, program
65 is an improvement; I can see that easily enough. And it gets
us a step closer to the pertinent goals to be reached in this
chapter.
Program 65. An improved version of the program which
redirects output bound for the screen to both the
printer and the screen.
; Program Name: PRG_7FP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Function:
; The function of this program is identical to that of PRG_6JC.S and
; PRG_6KC.S. This program also redirects output bound for the screen to the
; printer; however, this program accesses the parallel port directly, as does
; program PRG_7EP.S.
; Execution Instructions:
; Execute from the desktop. This program expects the printer to be on
; and will loop at the printer ready test until that condition is true.
; After this program has been executed, execute REG_TST2.TOS to exercise
; it. Turn the printer on before executing REG_TST2.TOS.
program_start: ; Compute program size and retain result.
lea program_end, a3 ; Fetch program end address.
movea.l 4(a7), a4 ; Fetch basepage address.
suba.l a4, a3 ; Program size in A3.
load_stack_address:
lea stack, a7
install_custom_trap_13_vector:
pea custom_trap_handler
move.w #$2D, -(sp) ; Trap 13 vector number.
move.w #5, -(sp) ; Function = setexec = BIOS $5.
trap #13 ; Current trap handler vector returned in D0.
addq.l #8, sp
lea preempted_handler_address, a0
move.l d0, (a0)
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Size of memory to remain resident.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
custom_trap_handler:
move.l usp, a0 ; Load address of current top of user stack.
get_processor_status:
btst #5, (sp) ; User mode test.
beq.s was_user_mode ; No adjustment is necessary.
movea.l sp, a0 ; Load current top of supervisor stack.
addq.l #6, a0 ; Adjust SSP for user mode type data access.
was_supervisor_mode:
was_user_mode: ; Processing for either mode follows.
cmpi.w #3, (a0) ; Writing a character to a device?
bne not_bconout_call
cmpi.w #2, 2(a0) ; Is device the screen?
bne not_screen
esc_sequence_test:
lea esc_sequence_flag, a1
tst.b (a1)
bne.s reset_esc_sequence_flag
cmpi.w #$1B, 4(a0)
bne.s not_esc_sequence
move.b #1, (a1)
bra.s use_preempted_handler
reset_esc_sequence_flag:
move.b #0, (a1)
use_preempted_handler:
movea.l preempted_handler_address, a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
not_esc_sequence:
move.w 4(a0), d0 ; Store character for printer.
ascii_code_test: ; Filter out undesirable codes.
cmpi.w #$1B, d0
bgt.s printer_ready_test
cmpi.w #$A, d0
beq.s printer_ready_test
cmpi.w #$D, d0
bne.s undesirable_ascii
printer_ready_test:
btst #0, $FFFA01 ; Check logic level of pin 11.
bne.s printer_ready_test ; Loop until printer is ready to receive.
lea $FF8800, a0 ; Fetch address of PSG data bus.
move.b #$F, (a0) ; Latch address of port B.
move.b d0, 2(a0) ; Write character to port B.
move.b #$E, (a0) ; Latch address of port A.
move.b (a0), d0 ; Read content of port A.
andi.b #$DF, d0 ; Reset bit 5 of d1 to zero.
move.b d0, 2(a0) ; Reset bit 5 of port A. Strobe low.
ori.b #$20, d0 ; Set bit 5 of d1.
move.b d0, 2(a0) ; Set bit 5 of port A. Strobe high.
write_character_to_screen:
undesirable_ascii:
not_screen:
not_bconout_call:
movea.l preempted_handler_address, a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER
bss
character: ds.w 1
preempted_handler_address: ds.l 1
esc_sequence_flag: ds.b 1
align
ds.l 96 ; Stack
stack: ds.l 1 ; Address of stack.
program_end: ds.l 0
end
Print Buffers
A print buffer is memory that is used as a reservoir in
which characters are stored. Because of the speed differential
between the printer and other computer system hardware, the
reservoir can be filled faster than it can be emptied. This is
good. That's how reservoirs are supposed to work. I have
already discussed, in qualitative terms, the maximum rate at
which the reservoir may be emptied; that is accomplished by
accessing the parallel port directly when sending data to the
printer. Now I shall present methods of filling the reservoir.
But first, let me preface that discussion with my
interpretation of the reason that print buffers are used and the
reason that they are constructed as they are. In the old days,
when computers were housed in a large room in a building far away
from the many programmers using it, we communicated with the
computer via a video terminal (before that, we used teletype
machines--before that we beat hollow logs with sticks). Most
often, only one printer was available for the general programming
population, and it was also located at the computer site.
Programmers did not request hard copy until it was absolutely
necessary; for two reasons: one, each printing job was placed in
a queue, and since each programmer's request was likely to be one
of many, some time was likely to elapse before the hard copy
appeared; two, the walk to the printer was usually not a short
one. Under such conditions, it was easy to justify the existence
of a print buffer (or queue).
Now, as we sit at our complete systems, we become so spoiled
with immediate response that we find it difficult to occupy
ourselves for a few minutes while the printer does its business.
Of course, some printings require much more than a few minutes,
but, for those, I get up and walk away because I don't have room
for a printer silencer and I can't stand the noise generated by
the printer for more than a minute or two. Still, I find print
buffers convenient because data transfers between the printing
program and a buffer is so much faster than it is between the
program and a bufferless printer, usually.
Now, because memory is, and always has been, a limited
commodity in any computer system, the buffers that we are most
likely to acquire are circular; that is, the buffers are of a
significant length, with a beginning address and an ending
address; and when we have sent enough data to fill a buffer, it
does not overflow as would a reservoir; instead, it begins to
refill, overwriting previously entered data. At this point, the
first disadvantage of circular buffers become evident. What do
you suppose happens to the data transfer rate of a print buffer
when it has become full. Well, from that point on, it can accept
a new character only after one has been extracted from the buffer
by the printer, therefore, the buffer's data transfer rate
degenerates to that of the printing mechanism. Note that the
buffer is considered to be empty when a particular printing task
has been accomplished. For example, if we send a single file to
the printer through the buffer and permit the task to terminate
before sending another, then the second file sees an empty
buffer. If, on the other hand, we send a two or more files back
to back, the buffer will not appear to be empty until all files
have been processed.
I am going to explain the operation of a circular print
buffer in detail, not because I think that it is the type you
should use, but because it is the type you are most likely to
see, because that is the historical print buffer design. After
considering its weaknesses, I shall present another type--a
linear buffer. In addition, the format of circular buffers
demands the use of algorithms which you might find useful in
other applications. Finally, it is by comparing the data
transfer rate of the linear buffer to that of the circular buffer
that I shall be able to stress the linear buffer's advantage.
Program 66 introduces a program that installs a circular print
buffer in ram.
Program 66. This program installs a circular print
buffer. The buffer has not been designed for practical
use, it has been designed for study.
; Program Name: PRG_7GR.S
; Version: 1.014
; Assembly Instructions:
; Assemble in Relocatable mode and save with a TOS and TTP suffix.
; Program Function:
; Circular printer buffer. The default buffer size for the TOS version
; of this program is only 100 bytes because this is a special program that is
; designed to be used to test printing programs and similar endeavors. While
; this program is resident, those other programs can be executed from the
; AssemPro debugger, permitting this program's address locations and registers
; to be monitored. Breakpoints can't be set in this program from the debugger
; window after it is resident, but, from a program under test in the debugger,
; you can single step in such a way as to end up within the custom trap #13
; handler installed by this program.
; Once a printing session has begun, it cannot be cancelled merely by
; turning the printer off. The problem is this: after a printing session
; has been initiated, if the printer is turned off to cancel the session,
; printing resumes when the printer is turned back on, because turning the
; printer back on causes the logic level of pin 11 to drop from high to low,
; thereby providing the falling edge required to invoke the interrupt handler.
; Two exception handlers are installed by this program. The trap #13
; handler intercepts all trap #13 invocations. It handles only those that
; are relevant to the printing process; the others are passed on to the trap
; #13 handler that is preempted by the custom handler installed by this
; program. The interrupt handler is invoked each time the logic level of
; pin 11 of the parallel interface port drops from high to low. No provisions
; are made within this program to preserve the function of a pin 11 handler
; existing at the time this program's custom interrupt handler is installed.
; When the printing program begins to send characters to the parallel
; port via system function invocations, if the print buffer established by
; this program is empty (because it has not yet been used or because all
; characters previously stored have been processed), then characters will be
; send to the printer by the trap #13 handler until the printer becomes
; busy enough so that it is unable to keep up with the flow of characters
; into and out of the trap #13 handler. Once the printer falls behind,
; characters are thereafter stored in the print buffer. From that point on,
; characters must be processed by the interrupt handler.
; A special file has been prepared to provide a visual view of the
; concepts in the above paragraph. With the printer on and this program
; installed, execute PRG_7DP.TTP and type SPOOLTST.DOC on the parameter input
; line. All characters processed by the trap #13 handler will be printed
; in lower case; those printed by the interrupt handler will be printed in
; upper case. You should print SPOOLTST.DOC once with the default buffer
; size, then execute PRG_7GR.TTP and type 32 on its input parameter line to
; increase the buffer size to 32 kilobytes. Then print SPOOLTST.DOC again
; using PRG_7DP.TTP.
; The instruction within the interrupt handler that converts characters
; from lower case to upper case is prominently marked. No other file should
; be printed while this instruction is active because the character conversion
; instruction will wreck havoc with numeric and punctuation characters. To
; print any other file with this program, use a semicolon to inactivate that
; instruction and assemble this program again. Other versions of the circular
; print buffer which do not include that instruction will be introduced
; shortly.
; Execution Instructions:
; May be executed from the desktop with a TOS or TTP extension. May be
; executed from an AUTO folder if it has a PRG extension.
; If a larger buffer size is desired, the program extension can be
; changed from TOS to TTP. If a buffer size is declared on the TTP parameter
; line, any size, up to the maximum available machine memory, may be specified.
; When the TTP program is executed, type the desired size, in kilobytes, on the
; parameter line; the maximum number of digits which can be typed on the
; parameter line is 4. Do not type spaces before the digits. Do not type
; anything between digits. Do not type anything after the digits, except the
; Return key. The OK button may be clicked in lieu of a Return key press.
; The length of the buffer will be (input X 1024) bytes. Ex: if input is
; 32, length of the buffer will be 32,768 bytes. After converting the ASCII
; decimal input to binary, it is much easier to simply shift the binary result
; 10 bits left to multiply it by 1024 than it is to multiply it by 1000.
; The buffer length (either the default size or the specified size) is
; combined with the program size to determine the parameter which must be
; passed to GEMDOS function $31.
; In order to permit observation of this programs variables and registers,
; its location in memory and other pertinent addresses are screened as the
; program is being loaded.
; When the addresses appear on the screen, write them down. Terminate
; execution by pressing the Return key. In the AssemPro debugger, you can go
; to pertinent addresses using the "from address" function. You can double
; click on any register in the register output field and use the dialog box to
; specify an address in this program that you wish to monitor.
; Once this program has been executed, it will remain in ram until the
; computer is rebooted.
; WARNING:
; If this program or any other LSR program is executed from within the
; AssemPro debugger, upon exit from AssemPro, the operating system will not be
; able to clear the program from memory. Because the program will be residing
; in an area of memory that was controlled by AssemPro, the system environment
; will be corrupted. You can confirm this by trying to reexecute AssemPro
; after you exit. You will receive a Bus error message.
; Furthermore, you may not be able to assemble a program until you reset
; the system.
; There is only one thing you can do if you execute a LSR program from
; within AssemPro; you must reset the system by turning it completely off, then
; back on. You can try a warm reset, but all bets are off if you do that.
; Furthermore, remember that programs with a memory preserving or limiting
; algorithm require special treatment if they are executed while they are in
; memory because of having just been assembled; that is, when they are not
; loaded via the "execute program" button. The special treatment consist of
; skipping over the initializing instructions that reference the locations
; 4(a7) and the basepage address. That's because programs not loaded via the
; "execute program" button do not have a basepage. The special treatment also
; means that the system function used to relinquish processor control must not be
; executed.
; NOTE:
; The loop which processes the ASCII decimal input is formed with a dbra
; instruction. This is a word size instruction, therefore, when the loop
; counter register is prepared by subtracting one from the number of input
; digits, the value in the counter register must be extended to word size to
; match the size of the dbra instruction.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a3 ; Fetch program end address into A3.
; If this program is assembled and observed in the debugger, the label
; program_end will not be seen because the label buffer has been declared in
; in the bss section as buffer: ds.l 0, and the end of the program has also
; been marked with program_end: ds.l 0. Effectively, the label program_end
; does not even exist as far as the assembled bss section of the program is
; concerned.
movea.l 4(a7), a4 ; Fetch basepage address into A4.
suba.l a4, a3 ; Program size is in A3.
lea stack(pc), a7 ; Fetch address of the program stack.
process_input_parameter: ; Calculate requested buffer length.
lea $80(a4), a4 ; Fetch basepage parameter line start address.
lea length(pc), a0 ; Fetch address of the length variable.
moveq #0, d0 ; Clear counter register.
move.b (a4)+, d0 ; Fetch parameter line character count.
beq.s compute_size ; Branch if no parameter--use default length.
cmpi.b #4, d0 ; Greater than four test.
bgt too_many_characters ; Branch if more than four characters.
subq.b #1, d0 ; Set up counter.
ext.w d0 ; Extend to match size of DBRA instruction.
moveq #0, d2 ; Initialize accumulator.
moveq #0, d1 ; Clear scratch register.
fetch_digit: ; Convert input from ASCII decimal to binary.
move.b (a4)+, d1 ; ASCII decimal digit to D1.
sub.b #$30, d1 ; Convert ASCII decimal digit to decimal digit.
bmi not_decimal ; Branch if digit is less than zero.
cmp.b #9, d1 ; Greater than nine test.
bgt not_decimal ; Branch if digit is greater than nine.
multiply_accumulator_content_by_ten:
move.l d2, d3 ; Copy accumulator in D3.
lsl.l #3, d2 ; Shift accumulator content to multiply by 8.
add.l d3, d2 ; Add D2's original quantity twice to
add.l d3, d2 ; complete the multiplication by 10.
add_new_digit_to_accumulator_content:
add.l d1, d2 ; Add decimal number in D1 to accumulator.
dbra d0, fetch_digit ; Loop until d0 becomes negative.
extend_to_thousands_of_bytes: ; Length will be (input X 1000) plus
moveq #10, d3 ; some even number of bytes that is less
lsl.l d3, d2 ; than 1000. Ex: if input is 32, length
move.l d2, (a0) ; is 32768. A0 contains address of length
; variable
compute_size: ; This will be parameter to GEMDOS $31.
adda.l (a0), a3 ; Add buffer length to program size.
compute_and_store_buffer_end: ; Presently, buffer_end = buffer start.
lea buffer(pc), a1 ; Load buffer starting address.
adda.l (a0), a1 ; Add buffer length to buffer start address.
move.l a1, buffer_end ; Store buffer_end address.
install_custom_trap_13_vector: ; ******************************************
pea trap_13_handler(pc) ; Push custom trap handler address onto stack.
move.w #$2D, -(sp) ; Trap 13 vector number.
move.w #5, -(sp) ; Function = setexec.
trap #13 ; Current trap 13 vector returned in D0.
addq.l #8, sp
move.l d0, preempted_trap_13_address
install_interrupt_handler_vector: ; ***************************************
pea interrupt_handler(pc) ; Push interrupt handler address.
move.w #0, -(sp) ; Interrupt level for centronics ready.
move.w #$D, -(sp) ; Functon = XBIOS #13 dec = mfpint.
trap #14
addq.l #8, sp
print_memory_locations:
bsr print_newline ; For debugger screen protection.
lea load_message(pc), a0
bsr print_string
move.l #program_start, d1 ; Print program start address.
bsr bin_to_hex ; bin_to_hex expects binary number in D1.
lea hexadecimal(pc), a0 ; Print the hexadecimal string.
bsr print_string
lea separator(pc), a0 ; Print a separator between addresses.
bsr print_string
move.l #program_end, d1 ; Print program end address.
bsr print_address
bsr print_newline
lea int_hand_msg(pc), a0 ; Print interrupt handler address.
bsr print_string
move.l #interrupt_handler, d1
bsr print_address
bsr print_newline
lea trap_13_msg(pc), a0 ; Print custom trap #13 address.
bsr print_string
move.l #trap_13_handler, d1
bsr print_address
bsr print_newline
lea io_buffer_msg(pc), a0 ; Print address of variable 'io_buffer'.
bsr print_string
move.l #io_buffer, d1
bsr print_address
lea content_msg(pc), a0 ; Print content of io_buffer variable.
bsr print_string
move.l io_buffer, d1
bsr print_content
lea length_msg(pc), a0 ; Print address of variable 'length'.
bsr print_string
move.l #length, d1
bsr print_address
lea content_msg(pc), a0 ; Print content of length variable.
bsr print_string
move.l length, d1
bsr print_content
lea extract_msg(pc), a0 ; Print add of variable 'extract_pointer'.
bsr print_string
move.l #extract_pointer, d1
bsr print_address
lea content_msg(pc), a0 ; Print content of extract_pointer.
bsr print_string
move.l extract_pointer, d1
bsr print_content
lea insert_msg(pc), a0 ; Print add of variable 'insert_pointer'.
bsr print_string
move.l #insert_pointer, d1
bsr print_address
lea content_msg(pc), a0 ; Print content of insert_pointer.
bsr print_string
move.l insert_pointer, d1
bsr print_content
lea end_msg(pc), a0 ; Print add of variable 'buffer_end'.
bsr print_string
move.l #buffer_end, d1
bsr print_address
lea content_msg(pc), a0 ; Print content of buffer_end.
bsr print_string
move.l buffer_end, d1
bsr print_content
wait_for_keypress: ; Give time to write down memory addresses.
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
not_decimal:
pea message_1(pc) ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
bra.s terminate
too_many_characters:
pea message_2(pc) ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
terminate: ; Wait for keypress.
move.w #8, -(sp) ; Function = GEMDOS $8 = cnecin.
trap #1
addq.l #2, sp
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
; In the following discussion, whenever the word "printer" is used,
; realize that it includes any hardware print buffer that is connected
; between the computer and the printer. In other words, the word printer,
; as it is used below, can mean printer, print buffer or the peripheral
; combination. Note also that an external print buffer can function while
; the printer is off; that is, it can receive characters and store them.
; This means that you can complete a lot of print handling testing with the
; printer off to save paper. To get rid of text in an external buffer, just
; press its clear button; to print the data stored, just turn on the printer.
; The interrupt handler is activated whenever pin 11 of the centronics
; interface is forced from a high to a low logic level. The printer does
; does this when it turned on and when it sends a busy signal over pin 11.
; When the printer is off, the logic level of pin 11 is constantly high, but
; during the time that the printer is busy receiving a character, the logic
; level of pin 11 is placed high and held there only until the printer is
; ready to receive another character from the computer. It is the falling
; edge of the logic level pulse generated by the printer that causes an MFP
; pin 0 interrupt. This can be verified by observing the contents of the
; MFP's Active Edge Register (AER) located at address $FFFA03. The content
; of that register is 4, therefore, the GPIP bit 0 input pin generates an
; interrupt on a one-to-zero transition. Reference pages 4-1 and 4-2 of
; the Motorola MC68901 Multi-Function Peripheral manual and pages 32 and
; 60 of the Internals book.
; So, whenever the interrupt occurrs, we know that the printer is ready to
; receive a character, therefore, we can immediately send one to it.
; However, when we want to send a character, but we have no knowledge of
; the printer's capability to receive, we must perform a test to detect the
; printers state. This is done by testing bit #0 of the MFP's General
; Purpose I/O data register (GPIP). If that bit is a zero, then the printer
; is ready to receive a character. The trap handler must perform this test.
; NOTE: Both the interrupt handler and the trap handler use a similiar
; algorithm to print. A common subroutine was not used (but could
; be used) because the additional time required by the interrupt
; handler would be 34 clock cycles for each character printed. It
; is the interrupt handler that actually does the bulk of the printing.
; The more time used by the interrupt handler, the less time
; available to the process you are running while printing is being
; accomplished via the interrupt handler.
interrupt_handler: ; Parallel interface pin #11 interrupt handler.
movem.l d0/a0-a1, -(sp) ; Save content of registers used by handler.
lea io_buffer, a0 ; Load buffer structure pointer into A0.
movea.l 8(a0), a1 ; Content of buffer extract pointer into A1.
; NOTE: A1 is now a simulated extract pointer. It contains the location
; from which the next character is to be fetched. The simulated
; pointer is used to scout ahead and determine what would happen
; if the actual extract pointer were incremented.
cmpa.l 12(a0), a1 ; Compare location stored in insert pointer
; to location stored in simulated extract
; pointer.
beq.s buffer_is_empty ; Branch if location in insert pointer is equal
; to location stored in simulated extract
; pointer.
addq.l #1, a1 ; Increment simulated extract pointer to
; location of next character.
cmpa.l 16(a0), a1 ; Compare buffer_end to location stored in
; simulated extract pointer.
bcs.s end_of_buffer_not_reached
movea.l (a0), a1 ; Move simulated extract pointer to front of
; buffer by storing buffer address in simulated
; extract pointer because the simulated extract
; pointer has reached the buffer limit.
end_of_buffer_not_reached:
move.b (a1), d0 ; Fetch character at location stored in
; simulated extract pointer.
move.l a1, 8(a0) ; Put location stored in simulated extract
; pointer in actual extract pointer.
print_character:
lea $FF8800, a1 ; Address of Programmable Sound Generator.
; *************************************************************************
andi.b #$DF, d0 ; Convert character to upper case.
; The above statement is used to determine the frequency of interrupt handler
; use by various printing programs. It does this by converting lower case
; characters to upper case each time a character is processed by this
; interrupt handler. For a properly functioning printer buffer, the above
; statement must be removed. Experiments show that most of the characters
; handled by the print buffer will be processed by the interrupt handler,
; unless there is an external print buffer connected between the printer
; and the parallel port, or unless the printer contains a significantly
; large buffer and that buffer is able to keep up with the flow of characters
; into the trap #13 handler.
; *************************************************************************
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
buffer_is_empty:
bclr #0, $FFFA11 ; Clear MFP in service bit.
movem.l (sp)+, d0/a0-a1 ; Restore registers used by handler.
rte
trap_13_handler:
move.l usp, a0 ; Load address of current top of user stack.
get_processor_status:
btst #5, (sp) ; User mode test.
beq.s was_user_mode ; No adjustment is necessary if the
; processor was in user mode.
movea.l sp, a0 ; Load current top of supervisor stack.
addq.l #6, a0 ; Adjust SSP for user mode type data access.
was_supervisor_mode:
was_user_mode: ; Processing for either mode follows.
tst.w 2(a0) ; Is device the printer?
bne not_printer
cmpi.w #3, (a0) ; Writing a character to a device?
bne not_bconout_call
fetch_character:
move.w 4(a0), d0
move.w #$2700, SR ; Disable interrupts.
; NOTE: If interrupts are not disabled during this critical portion of the
; trap handler algorithm, the interrupt handler could be invoked and
; screw up the buffer pointers.
buffer_empty_test: ; Want to know if buffer is empty or not.
lea io_buffer, a0 ; Fetch buffer structure address.
; NOTE: The address stored in the insert pointer is placed in register A1
; for two reasons; (1) for comparison with the extract pointer,
; (2) in order to simulate an increment of the address later.
movea.l 12(a0), a1 ; Fetch content of buffer insert pointer.
cmpa.l 8(a0), a1 ; Compare content of extract pointer with
; content of insert pointer to determine if
bne buffer_not_empty ; buffer is empty.
try_to_print_character:
btst #0, $FFFA01 ; Is printer (or hardware print buffer)
; busy (or off)?
bne.s printer_busy ; Branch if either peripheral is busy
; or off.
print_character_if_not_busy: ; Means peripherals are not busy and are on.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B of PSG.
move.b d0, 2(a1) ; Write character to PSG.
move.b #$E, (a1) ; Select port A of PSG.
move.b (a1), d1 ; Get current register 16 value.
_strobe_low:
andi.b #$DF, d1
move.b d1, 2(a1)
_strobe_high:
ori.b #$20, d1
move.b d1, 2(a1)
moveq #-1, d0 ; In case calling source wants to know
rte ; that character was dispatched.
buffer_not_empty:
printer_busy:
addq.l #1, a1 ; Simulate insert location increment.
cmpa.l 16(a0), a1 ; Compare buffer_end to simulated insert
; location.
bcs.s not_end_of_buffer ; Branch if simulated location is not equal
; to the end of buffer location.
movea.l (a0), a1 ; Simulate a reset of insert pointer to the
; first buffer location; that is, the front of
; the buffer is the new simulated insert
; location.
not_end_of_buffer:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer
; to simulated insert location.
beq.s buffer_full ; If they are equal, the buffer is full.
store_character_in_buffer:
move.b d0, (a1) ; Store character at the now valid location.
move.l a1, 12(a0) ; Store simulated location as the new insert
; location in the insert pointer.
moveq #-1, d0 ; In case calling source wants to know
rte ; that character was dispatched.
buffer_full:
move.l $4BA, d1 ; Fetch current _hz_200 value.
add.l #$BB8, d1 ; Add count for desired elapsed time value.
; NOTE: The content of $4BA is incremented every 5 milliseconds. Compute
; the value to add to the current count by dividing the number of
; milliseconds to wait (equals number of seconds times 1000) by 5.
; In this case, to wait 15 seconds, 15000/5 = 3000 = $BB8.
move.w #$2300, SR ; Enable interrupts.
; NOTE: Here interrupts are enabled because the trap handler algorithm may
; be idle for some time. During this idle time, invoked interrupts
; would have to remain pending. There is a maximum amount of time
; that an interrupt may remain pending, and that time depends on the
; number of instructions a program can execute with the interrupts
; masked off. The amount of time that interrupts can be disabled is
; usually restricted.
; Reference: Programming the 68000 by Steve Williams, page 366.
; Published by SYBEX, Inc. 1985
wait:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer to
; location stored in simulated insert pointer.
bne.s store_character_in_buffer
cmp.l $4BA, d1 ; Elapsed time check.
bhi.s wait ; Wait until there is room in the buffer for
; another character, or until the programmed
; wait time has expired.
moveq #0, d0 ; In case calling source wants to know
rte ; that character was not dispatched.
not_bconout_call:
cmpi.w #8, (a0) ; Requesting output device status?
bne.s not_bcostat_call
; NOTE: The buffer status is returned in response to the bcostat invocation,
; not the printer status.
determine_software_buffer_status:
moveq #-1, d0 ; Assume buffer is not full.
lea io_buffer, a0 ; Fetch buffer structure pointer.
move.l 12(a0), a1 ; Fetch insert pointer for simulation.
addq.l #1, a1 ; Simulate moving insert pointer to next buffer
; location.
cmpa.l 16(a0), a1 ; Compare buffer_end to simulated insert
; location.
bcs.s not_buffer_end
movea.l (a0), a1 ; Simulate resetting insert pointer to front of
; buffer.
not_buffer_end:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer
; to simulated insert location.
bne.s buffer_not_full ; Buffer not full if extract location is not
; equal to simulated insert location.
moveq #0, d0 ; Return buffer status to calling source.
buffer_not_full: ; Return 0 if full, -1 if not.
rte
not_bcostat_call:
not_printer:
movea.l preempted_trap_13_address, a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
;
; SUBROUTINES
;
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D1. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. Maximum size of the binary number is 32 bits = 8 nibbles.
; The algorithm discards leading zeroes.
; The conversion from binary nibble to hex digit is accomplished by
; extracting the character in the hex table that is located at the position
; defined by the decimal value of the nibble. For example, if the nibble
; is "1111", the decimal value is 15; the 15th element of the hex table is
; the letter F. The location in the table is specified by an offset from
; the address of the first character of the table, which is stored in A1.
; The value of the offset is stored in register D0. The addressing mode
; used to locate the appropriate table entry is "address register indirect
; with offset".
bin_to_hex: ; Expects binary number in D1.
lea hexadecimal, a0 ; A0 is pointer to array "hexadecimal".
tst.l d1 ; Test for contents = 0.
beq.s zero_passed ; Branch if number is 0.
lea hex_table, a1 ; A1 is pointer to array "hex_table".
lea hex_table, a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter for 8 nibbles.
discard_leading_zeroes:
rol.l #4, d1 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
bne.s store_digit ; Branch and store if not leading zero.
dbra d2, discard_leading_zeroes
continue:
rol.l #4, d1 ; Rotate most significant nibble.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, continue ; Continue looping until D2 = -1.
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
zero_passed:
move.b #$30, (a0)+ ; Store an ASCII zero in "hexadecimal".
move.b #0, (a0) ; Terminate ASCII hexadecimal string with null.
lea hexadecimal, a0
rts
print_address:
bsr bin_to_hex
lea hexadecimal(pc), a0
bsr print_string
rts
print_content:
bsr bin_to_hex
lea hexadecimal(pc), a0
bsr print_string
bsr print_newline
rts
print_string: ; Expects address of string to be in A0.
pea (a0) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
load_message: dc.b 'Installing PRT_BUF program between hex addresses: ',0
separator: dc.b ' - ',0
trap_13_msg: dc.b 'Custom trap #13 address: $',0
int_hand_msg: dc.b 'Interrupt handler address: $',0
io_buffer_msg:dc.b 'variable io_buffer address: $',0
length_msg: dc.b 'variable length address: $',0
extract_msg: dc.b 'extract_pointer address: $',0
insert_msg: dc.b 'insert_pointer address: $',0
end_msg: dc.b 'buffer_end pointer address: $',0
content_msg: dc.b ' content = $',0
message_1: dc.b 'Nondecimal character digit detected on parameter line.'
dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
message_2: dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
dc.b 'The program converts the input to thousands of bytes.'
dc.b $D,$A,'Default buffer size is 100 bytes.',$D,$A
dc.b 'Press Return key to terminate.',$D,$A,0
align
; The variable "io_buffer" is a pointer to the print buffer's starting
; address. The address of io_buffer also serves as the address of a
; structure that is composed of the variables io_buffer, at offset 0,
; length, at offset 4, extract_pointer (also called the buffer output pointer),
; at offset 8, and insert_pointer (also called the buffer input pointer), at
; offset 12. In this service, the address of io_buffer provides the means
; by which the other members of the structure may be referrenced.
; Characters are placed in the buffer at the address stored in the variable
; 'insert_pointer'; they are extracted from the buffer and sent to the printer
; from the address stored in the variable 'extract_pointer'.
; Time is saved because only the address of io_buffer need be loaded into
; an address register. The other variables in the structure are referenced
; by adding their offsets to the content of the chosen address register.
; NOTE: Because this program is assembled in Relocatable mode, the run time
; address for the variable "buffer" will be stored in the four pointers
; at which it is declared in the io_buffer structure below.
io_buffer: dc.l buffer ; Pointer to buffer's starting address.
length: dc.l $64 ; Default buffer length = 100 bytes.
extract_pointer: dc.l buffer ; Buffer character output pointer.
insert_pointer: dc.l buffer ; Buffer character input pointer.
buffer_end: dc.l buffer ; Last address in buffer = buffer + length.
bss
preempted_trap_13_address: ds.l 1
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
ds.l 96
stack: ds.l 1
buffer: ds.l 0 ; Length of buffer must be added to length of
program_end: ds.l 0 ; program to determine total size to pass to
end ; GEMDOS function $31.
How the Circular Print Buffer Functions
I have prepared a document to be printed while the handlers
of program 66 are installed in ram. The document is shown as
Document 1. This document can be printed with PRG_7DP.TTP, but
there is no reason that any other program which uses system
functions to send data to the parallel port can't be used.
Document 1, SPOOLTST.DOC, has been prepared entirely in lower
case characters. Within the interrupt handler of program 66,
there is an instruction that converts lower case characters to
upper case. This instruction wrecks havoc when punctuation and
numeric characters are converted, so they have been omitted from
SPOOLTST.DOC. The purpose of document 1 is to assist my
descriptions of program 66 with a visual representation of
character processing by the handlers installed by the program.
Baring major differences between our printers and computers, the
first line of SPOOLTST.DOC should appear in lower case, if your
printer contains only a single line buffer, and the rest of the
document should appear in upper case.
Document 1. SPOOLTST.DOC has been prepared entirely in
lower case so that you can determine which characters
are processed by program 66's trap #13 handler and
which characters are processed by the interrupt handler
when the document is printed.
punctuation this file dont need no stinking punctuation
all of the text in this file is lower case and no punctuation is used because
the purpose of this file is to permit the testing of circular print buffers
that contain a parallel interface interrupt handler and a trap thirteen handler
in the interrupt handler just before each character is printed there should be
an instruction to convert the character to upper case
all characters printed by the trap thirteen handler will be printed in lower
case therefore the printed output will separate characters printed by each
handler
such a test is conducted to prove that both handlers are functioning as expected
the number of characters of the file being printed that are processed by the
trap thirteen handler depends on the size of any buffers which exist between
the parallel interface port and the printer's print mechanism
this is so because such buffers can usually keep up with the flow of characters
into the trap handler
when that situation exists the trap handler does not perceive a busy signal
on pin 13 of the interface therefore no characters are stored in the trap
thirteen print buffer
instead the characters are sent immediately to the port
for example with only my star nx ten connected to the parallel port and with
its internal five kilobyte buffer disabled there exists within the printer
only a one line buffer
therefore the first line of text in this file will be processed by the trap
thirteen handler because the buffer response is fast enough to accept those
characters as they flow into the trap handler so no characters are stored in
the trap buffer
but as soon as the printer line buffer becomes full the trap thirteen handler
will begin storing characters in the buffer
when the attention of the printer returns to the parallel port the logic
state of pin eleven will drop from high to low and trigger the interrupt
handler into action
thereafter all characters will be processed by the interrupt handler
because the printer line buffer will not be empty again until all characters
of the file have been printed
if there is a print buffer between the parallel port and the printer or if the
printer five kilobyte internal buffer is enabled and if these other buffers are
able to keep up with the flow of characters into the trap thirteen handler then
then the interrupt handler may never be invoked
at least it will not be utilized until any and all external buffers are full
the point is this if the data transfer rate through the parallel port is fast
enough so that a character can be sent there as soon as it is detected by the
trap thirteen handler then all characters that are detected by the trap handler
are also printed by the trap handler in such cases the parallel port interface
interrupt handler does not function
Significantly, the system functions which exchange data
through the parallel interface port do so via trap #13
invocations. Therefore, program 66 installs a custom trap #13
handler which intercepts all trap #13 invocations. When the
program 66 specific handler is installed, it preempts whatever
trap #13 handler is installed before it; the address of that
handler is stored in a variable in program 66. Those invocations
which are not relevant to the transfer of data through the
parallel interface are simply passed on to the preempted handler
by jumping to its address.
Program 66 also installs an interrupt handler that is
invoked each time the logic level of pin 11 of the parallel
interface port drops from high to low. The installation of the
interrupt handler does not assume any prior installation; that
is, it does not save the address of any handler which may be
installed before the program 66 custom interrupt handler. In
addition, program 66 creates a buffer in which characters are
stored by the trap handler and extracted by the interrupt
handler.
Refer to figure 9.1 during the following discussion. Each
time the trap #13 handler is invoked by a program trying to send
data to the printer, the handler tries to send the character to
the printer immediately if program 66's buffer is empty and the
logic level of pin 11 of the parallel interface is low. If the
buffer is not empty, or if pin 11 is high, then the character is
immediately stored, unless the buffer is full. If the buffer is
full, a loop is executed until there is room for the character in
the buffer. Assume that the buffer is empty because it has not
yet been used. The first pictorial in figure 9.1 illustrates
this situation. Both the insert pointer and the extract pointer
contain the address of the first buffer element.
Now, assume that a printing program begins to send
characters to the printer. Further, assume that the printer
contains an 80 character buffer. The response of the buffer is
quick enough so that the first 80 characters flow through the
handler directly to the printer. Now the printer starts putting
characters on paper. (Note this: the printer will begin to print
when its line buffer is full (assuming that any larger printer
buffer is disabled) or when it receives a carriage return.) As
soon as the printer's line buffer becomes full logic level of pin
11 goes high long enough so that the trap handler is forced to
begin storing characters in program 66's buffer. The second
pictorial in figure 9.1 depicts the situation just after the
first character has been stored. From this point on, no
characters will be sent directly from the trap handler, unless
some event occurs that permits the extract pointer to catch up
with the insert pointer, which is an indication that the buffer
is empty.
The trap handler continues to insert characters into the
buffer in a linear fashion until the insert pointer reaches the
end of the buffer; in fact, it stops just short of the end
because within the trap handler algorithm the end of buffer test
is performed with a simulated insert pointer. This simulated
pointer scouts ahead to determine if the actual pointer can be
safely moved. The third pictorial of figure 9.1 illustrates this
instance. Note that the last element of the buffer never
receives a character. When the end of the buffer is reached, the
insert pointer must be wrapped around to the front of the buffer;
it is this action that instigates the circular configuration of
the buffer; that is, the buffer activity simulates that which it
would be if the buffer were circular. Refer to the fourth
pictorial.
Figure 9.1 Circular buffer pointer activity. The extract pointer
is represented by the letter E; the insert pointer by the letter
I. Characters which have been inserted into the buffer, but have
not yet been extracted are represented by the letter C;
characters which have been extracted by the letter X.
While the insert pointer is placing characters into the
buffer at a transfer rate that depends on speed of the trap
handler and the transfer rate of the printing program, the
interrupt handler continues to extract characters from the
buffer. Remember that the interrupt handler extraction process
began when a falling edge of the pulses being placed on pin 11 by
the printer invoked the interrupt handler at a time when the
buffer contained one or more characters. (Note this: if program
66's buffer somehow became full, but nothing occurred to cause
the logic level on pin 11 to drop from high to low, the interrupt
handler extraction process would not begin.)
If character insertion continues past the wrap around point,
the new characters will simply be placed in previously occupied
buffer locations. If the number of characters sent to the buffer
exceeds the buffer capacity, eventually the insert pointer will
catch up with the extract pointer; this is the full buffer
condition illustrated by pictorial 5 of figure 9.1. When that
occurs, the trap handler wait loop is executed for a short period
of time until there is room for the character in holding. When
all characters have been inserted, the insert pointer will be
idle, and the extract pointer will arrive at the location to
which the insert pointer is pointing. This event simulates the
empty buffer condition depicted by pictorial 6. I use the word
simulate here only to indicate that the buffer still contains the
characters not overwritten by insertions, but the buffer is
effectively empty because all characters have been sent to the
printer.
As stated previously, program 66 was prepared for use as a
tutorial agent. Programs 67 and 68 are the practical versions of
the program; in these programs the instructions that print out
the locations of pertinent addresses have been omitted, as has
been the instruction which converts characters in the interrupt
handler. Program 67 has been designed for assembly in the
Relocatable mode; program 68 for the PC-relative mode. Having
the two versions to examine will permit you to see the special
handling required for the PC-relative version as compared to the
Relocatable version.
Program 67. A practical version of program 66, which is
designed for assembly in AssemPro's Relocatable mode.
; Program Name: PRG_7HR.S
; Version: 1.007
; Assembly Instructions:
; Assemble in Relocatable mode and save with a TOS or TTP suffix.
; Program Function:
; Circular printer buffer. The default buffer size for the TOS version
; is 32,768 bytes. See PRG_7GR.S for further documentation.
; Execution Instructions:
; May be executed from the desktop with a TOS or TTP extension. May be
; executed from an AUTO folder if it has a PRG extension.
; If a larger buffer size is desired, the program extension can be
; changed from TOS to TTP. If a buffer size is declared on the TTP parameter
; line, any size, up to the maximum available machine memory, may be specified.
; When the TTP program is executed, type the desired size, in kilobytes, on the
; parameter line; the maximum number of digits which can be typed on the
; parameter line is 4. Do not type spaces before the digits. Do not type
; anything between digits. Do not type anything after the digits, except the
; Return key. The OK button may be clicked in lieu of a Return key press.
; The length of the buffer will be (input X 1024) bytes. Ex: if input is
; 32, length of the buffer will be 32,768 bytes. After converting the ASCII
; decimal input to binary, it is much easier to simply shift the binary result
; 10 bits left to multiply by 1024, than it is to multiply it by 1000.
; The buffer length (either the default size or the specified size) is
; combined with the program size to determine the parameter which must be
; passed to GEMDOS function $31.
program_start: ; Calculate program size and retain result.
lea -$82(pc), a4 ; Fetch "command line" address.
lea -$80(a4), a0 ; Fetch "basepage" address.
lea program_end(pc), a3 ; Fetch "end of program" address.
suba.l a0, a3 ; Program size is in A3.
lea stack(pc), a7 ; Fetch address of the program stack.
process_input_parameter: ; Calculate requested buffer length.
lea length(pc), a0 ; Fetch address of the length variable.
moveq #0, d0 ; Clear counter register.
move.b (a4)+, d0 ; Fetch parameter line character count.
beq.s compute_size ; Branch if no parameter--use default length.
cmpi.b #4, d0 ; Greater than four test.
bgt.s too_many_characters ; Branch if more than four characters.
subq.b #1, d0 ; Set up counter.
ext.w d0 ; Extend to match size of DBRA instruction.
moveq #0, d2 ; Initialize accumulator.
moveq #0, d1 ; Clear scratch register.
fetch_digit: ; Convert input from ASCII decimal to binary.
move.b (a4)+, d1 ; ASCII decimal digit to D1.
sub.b #$30, d1 ; Convert ASCII decimal digit to decimal digit.
bmi.s not_decimal ; Branch if digit is less than zero.
cmp.b #9, d1 ; Greater than nine test.
bgt.s not_decimal ; Branch if digit is greater than nine.
multiply_accumulator_content_by_ten:
move.l d2, d3 ; Copy accumulator in D3.
lsl.l #3, d2 ; Shift accumulator content to multiply by 8.
add.l d3, d2 ; Add D2's original quantity twice to
add.l d3, d2 ; complete the multiplication by 10.
add_new_digit_to_accumulator_content:
add.l d1, d2 ; Add decimal number in D1 to accumulator.
dbra d0, fetch_digit ; Loop until d0 becomes negative.
extend_to_thousands_of_bytes: ; Length will be (input X 1000) plus
moveq #10, d3 ; some even number of bytes that is less
lsl.l d3, d2 ; than 1000. Ex: if input is 32, length
move.l d2, (a0) ; is 32768. Store requested buffer size.
compute_size: ; This will be parameter to GEMDOS $31.
adda.l (a0), a3 ; Add buffer length to program size.
compute_and_store_buffer_end:
lea buffer(pc), a1 ; Fetch buffer starting address.
adda.l (a0), a1 ; Add buffer length to buffer start address.
move.l a1, buffer_end ; Store buffer_end address.
install_custom_trap_13_vector:
pea trap_13_handler(pc) ; Push custom trap handler address onto stack.
move.w #$2D, -(sp) ; Trap 13 vector number.
move.w #5, -(sp) ; Function = setexec.
trap #13 ; Current trap 13 vector returned in D0.
addq.l #8, sp
move.l d0, preempted_trap_13_address
install_interrupt_handler_vector:
pea interrupt_handler(pc); Push interrupt handler address.
move.w #0, -(sp) ; Interrupt level for centronics ready.
move.w #$D, -(sp) ; Functon = XBIOS #13 dec = mfpint.
trap #14
addq.l #8, sp
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size (plus buffer length).
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
not_decimal:
pea message_1(pc) ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
bra.s terminate
too_many_characters:
pea message_2(pc) ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
terminate: ; Wait for keypress.
move.w #8, -(sp) ; Function = GEMDOS $8 = cnecin.
trap #1
addq.l #2, sp
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
interrupt_handler: ; Parallel interface pin #11 interrupt handler.
movem.l d0/a0-a1, -(sp) ; Save content of registers used handler.
lea io_buffer(pc), a0 ; Load buffer structure pointer into A0.
movea.l 8(a0), a1 ; Content of buffer extract pointer into A1.
; NOTE: A1 is now a simulated extract pointer. It contains the location
; from which the next character is to be fetched. The simulated
; pointer is used to scout ahead and determine what would happen
; if the actual extract pointer were incremented.
cmpa.l 12(a0), a1 ; Compare location stored in insert pointer
; to location stored in simulated extract
; pointer.
beq.s buffer_is_empty ; Branch if location in insert pointer is equal
; to location stored in simulated extract
; pointer.
addq.l #1, a1 ; Increment simulated extract pointer to
; location of next character.
cmpa.l 16(a0), a1 ; Compare buffer_end to location stored in
; simulated extract pointer.
bcs.s end_of_buffer_not_reached
movea.l (a0), a1 ; Move simulated extract pointer to front of
; buffer by storing buffer address in simulated
; extract pointer because the simulated extract
; pointer has reached the buffer limit.
end_of_buffer_not_reached:
move.b (a1), d0 ; Fetch character at location stored in
; simulated extract pointer.
move.l a1, 8(a0) ; Put location stored in simulated extract
; pointer in actual extract pointer.
print_character:
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17..
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
buffer_is_empty:
bclr #0, $FFFA11 ; Clear MFP in service bit.
movem.l (sp)+, d0/a0-a1 ; Restore registers used by handler.
rte
trap_13_handler:
move.l usp, a0 ; Load address of current top of user stack.
get_processor_status:
btst #5, (sp) ; User mode test.
beq.s was_user_mode ; No adjustment is necessary if the
; processor was in user mode.
movea.l sp, a0 ; Load current top of supervisor stack.
addq.l #6, a0 ; Adjust SSP for user mode type data access.
was_supervisor_mode:
was_user_mode: ; Processing for either mode follows.
tst.w 2(a0) ; Is device the printer?
bne not_printer
cmpi.w #3, (a0) ; Writing a character to a device?
bne not_bconout_call
fetch_character:
move.w 4(a0), d0
move.w #$2700, SR ; Disable interrupts.
; NOTE: If interrupts are not disabled during this critical portion of the
; trap handler algorithm, the interrupt handler could be invoked and
; screw up the buffer pointers.
buffer_empty_test: ; Want to know if buffer is empty or not.
lea io_buffer(pc), a0 ; Fetch buffer structure address.
; NOTE: The address stored in the insert pointer is placed in register A1
; for two reasons; (1) for comparison with the extract pointer,
; (2) in order to simulate an increment of the address later.
movea.l 12(a0), a1 ; Fetch buffer insert pointer.
cmpa.l 8(a0), a1 ; Compare content of extract pointer with
; content of insert pointer to determine if
bne.s buffer_not_empty ; buffer is empty.
try_to_print_character:
btst #0, $FFFA01 ; Is printer (or hardware print buffer)
; busy (or off)?
bne.s printer_busy ; Branch if either peripheral is busy
; or off.
print_character_if_not_busy: ; Means peripherals are not busy and are on.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B of PSG.
move.b d0, 2(a1) ; Write character to PSG.
move.b #$E, (a1) ; Select port A of PSG.
move.b (a1), d1 ; Get current register value.
_strobe_low:
andi.b #$DF, d1
move.b d1, 2(a1)
_strobe_high:
ori.b #$20, d1
move.b d1, 2(a1)
moveq #-1, d0 ; In case calling source wants to know
rte ; that character was dispatched.
buffer_not_empty:
printer_busy:
addq.l #1, a1 ; Simulate insert location increment.
cmpa.l 16(a0), a1 ; Compare buffer_end to simulated insert
; location.
bcs.s not_end_of_buffer ; Branch if simulated location is not equal
; to the end of buffer location.
movea.l (a0), a1 ; Simulate a reset of insert pointer to the
; first buffer location; that is, the front of
; the buffer is the new simulated insert
; location.
not_end_of_buffer:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer
; to simulated insert location.
beq.s buffer_full ; If they are equal, the buffer is full.
store_character_in_buffer:
move.b d0, (a1) ; Store character at the now valid location.
move.l a1, 12(a0) ; Store simulated location as the new insert
; location in the insert pointer.
moveq #-1, d0 ; In case calling source wants to know
rte ; that character was dispatched.
buffer_full:
move.l $4BA, d1 ; Fetch current _hz_200 value.
add.l #$BB8, d1 ; Add count for desired elapsed time value.
; NOTE: The content of $4BA is incremented every 5 milliseconds. Compute
; the value to add to the current count by dividing the number of
; milliseconds to wait (equals number of seconds times 1000) by 5.
; In this case, to wait 15 seconds, 15000/5 = 3000 = $BB8.
move.w #$2300, SR ; Enable interrupts.
; NOTE: Here interrupts are enabled because the trap handler algorithm may
; be idle for some time. During this idle time, invoked interrupts
; would have to remain pending. There is a maximum amount of time
; that an interrupt may remain pending, and that time depends on the
; number of instructions a program can execute with the interrupts
; masked off. The amount of time that interrupts can be disabled is
; usually restricted.
; Reference: Programming the 68000 by Steve Williams, page 366.
; Published by SYBEX, Inc. 1985
wait:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer to
; location stored in simulated insert pointer.
bne.s store_character_in_buffer
cmp.l $4BA, d1 ; Elapsed time check.
bhi.s wait ; Wait until there is room in the buffer for
; another character, or until the programmed
; wait time has expired.
moveq #0, d0 ; In case calling source wants to know
rte ; that character was not dispatched.
not_bconout_call:
cmpi.w #8, (a0) ; Requesting output device status?
bne.s not_bcostat_call
; NOTE: The buffer status is returned in response to the bcostat invocation,
; not the printer status.
determine_software_buffer_status:
moveq #-1, d0 ; Assume buffer is not full.
lea io_buffer(pc), a0 ; Fetch buffer structure pointer.
move.l 12(a0), a1 ; Fetch insert pointer for simulation.
addq.l #1, a1 ; Simulate moving insert pointer to next buffer
; location.
cmpa.l 16(a0), a1 ; Compare buffer_end to simulated insert
; location.
bcs.s not_buffer_end
movea.l (a0), a1 ; Simulate resetting insert pointer to front of
; buffer.
not_buffer_end:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer
; to simulated insert location.
bne.s buffer_not_full ; Buffer not full if extract location is not
; equal to simulated insert location.
moveq #0, d0 ; Return buffer status to calling source.
buffer_not_full: ; Return 0 if full, -1 if not.
rte
not_bcostat_call:
not_printer:
movea.l preempted_trap_13_address, a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
data
message_1: dc.b 'Nondecimal character digit detected on parameter line.'
dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
message_2: dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
dc.b 'The program converts the input to thousands of bytes.'
dc.b $D,$A,'Default buffer size is 32,768 bytes.',$D,$A
dc.b 'Press Return key to terminate.',$D,$A,0
align
; The variable "io_buffer" is a pointer to the print buffer's starting
; address. The address of io_buffer also serves as the address of a
; structure that is composed of the variables io_buffer, at offset 0,
; length, at offset 4, extract_pointer (also called the buffer output pointer),
; at offset 8, and insert_pointer (also called the buffer input pointer), at
; offset 12. In this service, the address of io_buffer provides the means
; by which the other members of the structure may be referrenced.
; Characters are placed in the buffer at the address stored in the variable
; 'insert_pointer'; they are extracted from the buffer and sent to the printer
; from the address stored in the variable 'extract_pointer'.
; Time is saved because only the address of io_buffer need be loaded into
; an address register. The other variables in the structure are referenced
; by adding their offsets to the content of the chosen address register.
; NOTE: Because this program is assembled in Relocatable mode, the run time
; address for the variable "buffer" will be stored in the four pointers
; at which it is declared in the io_buffer structure below.
io_buffer: dc.l buffer ; Pointer to buffer's starting address.
length: dc.l $8000 ; Default buffer length = 32,768 bytes.
extract_pointer: dc.l buffer ; Buffer character output pointer.
insert_pointer: dc.l buffer ; Buffer character input pointer.
buffer_end: dc.l buffer ; Last address in buffer = buffer + length.
bss
preempted_trap_13_address: ds.l 1
ds.l 96
stack: ds.l 1
buffer: ds.l 0 ; Length of buffer must be added to length of
program_end: ds.l 0 ; program to determine total size to pass to
end ; GEMDOS function $31.
Program 68. A practical version of program 66, which is
designed for assembly in AssemPro's PC-relative mode.
; Program Name: PRG_7HP.S
; Version: 1.007
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS or TTP suffix.
; Program Function:
; Circular printer buffer. The default buffer size for the TOS version
; is 32,768 bytes. See PRG_7GR.S for further documentation.
; Execution Instructions:
; May be executed from the desktop with a TOS or TTP extension. May be
; executed from an AUTO folder if it has a PRG extension.
; If a larger buffer size is desired, the program extension can be
; changed from TOS to TTP. If a buffer size is declared on the TTP parameter
; line, any size, up to the maximum available machine memory, may be specified.
; When the TTP program is executed, type the desired size, in kilobytes, on the
; parameter line; the maximum number of digits which can be typed on the
; parameter line is 4. Do not type spaces before the digits. Do not type
; anything between digits. Do not type anything after the digits, except the
; Return key. The OK button may be clicked in lieu of a Return key press.
; The length of the buffer will be (input X 1024) bytes. Ex: if input is
; 32, length of the buffer will be 32,768 bytes. After converting the ASCII
; decimal input to binary, it is much easier to simply shift the binary result
; 10 bits left to multiply by 1024, than it is to multiply it by 1000.
; The buffer length (either the default size or the specified size) is
; combined with the program size to determine the parameter which must be
; passed to GEMDOS function $31.
program_start: ; Calculate program size and retain result.
lea -$82(pc), a4 ; Fetch "command line" address.
lea -$80(a4), a0 ; Fetch "basepage" address.
lea program_end, a3 ; Fetch "end of program" address.
suba.l a0, a3 ; Program size is in A3.
lea stack, a7 ; Fetch address of the program stack.
process_input_parameter: ; Calculate requested buffer length.
lea length, a0 ; Fetch address of the length variable.
moveq #0, d0 ; Clear counter register.
move.b (a4)+, d0 ; Fetch parameter line character count.
beq.s compute_size ; Branch if no parameter--use default length.
cmpi.b #4, d0 ; Greater than four test.
bgt too_many_characters ; Branch if more than four characters.
subq.b #1, d0 ; Set up counter.
ext.w d0 ; Extend to match size of DBRA instruction.
moveq #0, d2 ; Initialize accumulator.
moveq #0, d1 ; Clear scratch register.
fetch_digit: ; Convert input from ASCII decimal to binary.
move.b (a4)+, d1 ; ASCII decimal digit to D1.
sub.b #$30, d1 ; Convert ASCII decimal digit to decimal digit.
bmi.s not_decimal ; Branch if digit is less than zero.
cmp.b #9, d1 ; Greater than nine test.
bgt.s not_decimal ; Branch if digit is greater than nine.
multiply_accumulator_content_by_ten:
move.l d2, d3 ; Copy accumulator in D3.
lsl.l #3, d2 ; Shift accumulator content to multiply by 8.
add.l d3, d2 ; Add D2's original quantity twice to
add.l d3, d2 ; complete the multiplication by 10.
add_new_digit_to_accumulator_content:
add.l d1, d2 ; Add decimal number in D1 to accumulator.
dbra d0, fetch_digit ; Loop until d0 becomes negative.
extend_to_thousands_of_bytes: ; Length will be (input X 1000) plus
moveq #10, d3 ; some even number of bytes that is less
lsl.l d3, d2 ; than 1000. Ex: if input is 32, length
move.l d2, (a0) ; is 32768. Store requested buffer size.
compute_size: ; This will be parameter to GEMDOS $31.
adda.l (a0), a3 ; Add buffer length to program size.
store_io_buffer_structure_components:
; NOTE: This program is to be assembled in PC-relative mode; therefore, the
; buffer start address must be explicitly stored in the appropriate
; io_buffer structure components. In the Relocatable assembled version
; of the program, this need not be done because the operating system
; conveniently handles this task when the program is loaded for
; execution, forcing the run time buffer start address into each
; pointer at which it is declared in the data section of the program.
lea buffer, a1 ; Fetch buffer starting address.
lea io_buffer, a2 ; Fetch io_buffer structure base address.
move.l a1, (a2) ; Buffer starting address to base pointer.
move.l a1, 8(a2) ; Also to extract_pointer.
move.l a1, 12(a2) ; Also to insert_pointer.
adda.l (a0), a1 ; Add buffer length to buffer start address.
move.l a1, 16(a2) ; Store buffer_end address.
install_custom_trap_13_vector:
pea trap_13_handler ; Push custom trap handler address onto stack.
move.w #$2D, -(sp) ; Trap 13 vector number.
move.w #5, -(sp) ; Function = setexec.
trap #13 ; Current trap 13 vector returned in D0.
addq.l #8, sp
lea preempted_trap_13_address, a0
move.l d0, (a0)
install_interrupt_handler_vector:
pea interrupt_handler ; Push interrupt handler address.
move.w #0, -(sp) ; Interrupt level for centronics ready.
move.w #$D, -(sp) ; Functon = XBIOS #13 dec = mfpint.
trap #14
addq.l #8, sp
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size (plus buffer length).
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
not_decimal:
pea message_1 ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
bra.s terminate
too_many_characters:
pea message_2 ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
terminate: ; Wait for keypress.
move.w #8, -(sp) ; Function = GEMDOS $8 = cnecin.
trap #1
addq.l #2, sp
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
interrupt_handler: ; Parallel interface interrupt handler.
movem.l d0/a0-a1, -(sp) ; Save content of registers used by handler.
lea io_buffer, a0 ; Load buffer structure pointer into A0.
movea.l 8(a0), a1 ; Content of buffer extract pointer into A1.
; NOTE: A1 is now a simulated extract pointer. It contains the location
; from which the next character is to be fetched. The simulated
; pointer is used to scout ahead and determine what would happen
; if the actual extract pointer were incremented.
cmpa.l 12(a0), a1 ; Compare location stored in insert pointer
; to location stored in simulated extract
; pointer.
beq.s buffer_is_empty ; Branch if location in insert pointer is equal
; to location stored in simulated extract
; pointer.
addq.l #1, a1 ; Increment simulated extract pointer to
; location of next character.
cmpa.l 16(a0), a1 ; Compare buffer_end to location stored in
; simulated extract pointer.
bcs.s end_of_buffer_not_reached
movea.l (a0), a1 ; Move simulated extract pointer to front of
; buffer by storing buffer address in simulated
; extract pointer because the simulated extract
; pointer has reached the buffer limit.
end_of_buffer_not_reached:
move.b (a1), d0 ; Fetch character at location stored in
; simulated extract pointer.
move.l a1, 8(a0) ; Put location stored in simulated extract
; pointer in actual extract pointer.
print_character:
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
buffer_is_empty:
bclr #0, $FFFA11 ; Clear MFP in service bit.
movem.l (sp)+, d0/a0-a1 ; Restore registers used by handler.
rte
trap_13_handler:
move.l usp, a0 ; Load address of current top of user stack.
get_processor_status:
btst #5, (sp) ; User mode test.
beq.s was_user_mode ; No adjustment is necessary if the
; processor was in user mode.
movea.l sp, a0 ; Load current top of supervisor stack.
addq.l #6, a0 ; Adjust SSP for user mode type data access.
was_supervisor_mode:
was_user_mode: ; Processing for either mode follows.
tst.w 2(a0) ; Is device the printer?
bne not_printer
cmpi.w #3, (a0) ; Writing a character to a device?
bne not_bconout_call
fetch_character:
move.w 4(a0), d0
move.w #$2700, SR ; Disable interrupts.
; NOTE: If interrupts are not disabled during this critical portion of the
; trap handler algorithm, the interrupt handler could be invoked and
; screw up the buffer pointers.
buffer_empty_test: ; Want to know if buffer is empty or not.
lea io_buffer, a0 ; Fetch buffer structure address.
; NOTE: The address stored in the insert pointer is placed in register A1
; for two reasons; (1) for comparison with the extract pointer,
; (2) in order to simulate an increment of the address later.
movea.l 12(a0), a1 ; Fetch content of buffer insert pointer.
cmpa.l 8(a0), a1 ; Compare content of extract pointer with
; content of insert pointer to determine if
bne buffer_not_empty ; buffer is empty.
try_to_print_character:
btst #0, $FFFA01 ; Is printer (or hardware print buffer)
; busy (or off)?
bne.s printer_busy ; Branch if either peripheral is busy
; or off.
print_character_if_not_busy: ; Means peripherals are not busy and are on.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B of PSG.
move.b d0, 2(a1) ; Write character to PSG.
move.b #$E, (a1) ; Select port A of PSG.
move.b (a1), d1 ; Get current register value.
_strobe_low:
andi.b #$DF, d1
move.b d1, 2(a1)
_strobe_high:
ori.b #$20, d1
move.b d1, 2(a1)
moveq #-1, d0 ; In case calling source wants to know
rte ; that character was dispatched.
buffer_not_empty:
printer_busy:
addq.l #1, a1 ; Simulate insert location increment.
cmpa.l 16(a0), a1 ; Compare buffer_end to simulated insert
; location.
bcs.s not_end_of_buffer ; Branch if simulated location is not equal
; to the end of buffer location.
movea.l (a0), a1 ; Simulate a reset of insert pointer to the
; first buffer location; that is, the front of
; the buffer is the new simulated insert
; location.
not_end_of_buffer:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer
; to simulated insert location.
beq.s buffer_full ; If they are equal, the buffer is full.
store_character_in_buffer:
move.b d0, (a1) ; Store character at the now valid location.
move.l a1, 12(a0) ; Store simulated location as the new insert
; location in the insert pointer.
moveq #-1, d0 ; In case calling source wants to know
rte ; that character was dispatched.
buffer_full:
move.l $4BA, d1 ; Fetch current _hz_200 value.
add.l #$BB8, d1 ; Add count for desired elapsed time value.
; NOTE: The content of $4BA is incremented every 5 milliseconds. Compute
; the value to add to the current count by dividing the number of
; milliseconds to wait (equals number of seconds times 1000) by 5.
; In this case, to wait 15 seconds, 15000/5 = 3000 = $BB8.
move.w #$2300, SR ; Enable interrupts.
; NOTE: Here interrupts are enabled because the trap handler algorithm may
; be idle for some time. During this idle time, invoked interrupts
; would have to remain pending. There is a maximum amount of time
; that an interrupt may remain pending, and that time depends on the
; number of instructions a program can execute with the interrupts
; masked off. The amount of time that interrupts can be disabled is
; usually restricted.
; Reference: Programming the 68000 by Steve Williams, page 366.
; Published by SYBEX, Inc. 1985
wait:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer to
; location stored in simulated insert pointer.
bne.s store_character_in_buffer
cmp.l $4BA, d1 ; Elapsed time check.
bhi.s wait ; Wait until there is room in the buffer for
; another character, or until the programmed
; wait time has expired.
moveq #0, d0 ; In case calling source wants to know
rte ; that character was not dispatched.
not_bconout_call:
cmpi.w #8, (a0) ; Requesting output device status?
bne.s not_bcostat_call
; NOTE: The buffer status is returned in response to the bcostat invocation,
; not the printer status.
determine_software_buffer_status:
moveq #-1, d0 ; Assume buffer is not full.
lea io_buffer, a0 ; Fetch buffer structure pointer.
move.l 12(a0), a1 ; Fetch insert pointer for simulation.
addq.l #1, a1 ; Simulate moving insert pointer to next buffer
; location.
cmpa.l 16(a0), a1 ; Compare buffer_end to simulated insert
; location.
bcs.s not_buffer_end
movea.l (a0), a1 ; Simulate resetting insert pointer to front of
; buffer.
not_buffer_end:
cmpa.l 8(a0), a1 ; Compare location stored in extract pointer
; to simulated insert location.
bne.s buffer_not_full ; Buffer not full if extract location is not
; equal to simulated insert location.
moveq #0, d0 ; Return buffer status to calling source.
buffer_not_full: ; Return 0 if full, -1 if not.
rte
not_bcostat_call:
not_printer:
lea preempted_trap_13_address, a0
movea.l (a0), a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
data
message_1: dc.b 'Nondecimal character digit detected on parameter line.'
dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
message_2: dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
dc.b 'The program converts the input to thousands of bytes.'
dc.b $D,$A,'Default buffer size is 32,768 bytes.',$D,$A
dc.b 'Press Return key to terminate.',$D,$A,0
align
; The variable "io_buffer" is a pointer to the print buffer's starting
; address. The address of io_buffer also serves as the address of a
; structure that is composed of the variables io_buffer, at offset 0,
; length, at offset 4, extract_pointer (also called the buffer output pointer),
; at offset 8, and insert_pointer (also called the buffer input pointer), at
; offset 12. In this service, the address of io_buffer provides the means
; by which the other members of the structure may be referrenced.
; Characters are placed in the buffer at the address stored in the variable
; 'insert_pointer'; they are extracted from the buffer and sent to the printer
; from the address stored in the variable 'extract_pointer'.
; Time is saved because only the address of io_buffer need be loaded into
; an address register. The other variables in the structure are referenced
; by adding their offsets to the content of the chosen address register.
; NOTE: Even though the variable "buffer" is shown as being "stored" in
; four pointers in the io_buffer structure, remember that IT DOES
; NOT HAPPEN when a program is assembled in PC-relative mode. The
; address stored in those pointers during assembly is the ASSEMBLY
; TIME address. Therefore, the RUN TIME address must be explicitly
; stored in those pointers for a PC-relative assembled program.
io_buffer: dc.l buffer ; Pointer to buffer's starting address.
length: dc.l $8000 ; Default buffer length = 32,768 bytes.
extract_pointer: dc.l buffer ; Buffer character output pointer.
insert_pointer: dc.l buffer ; Buffer character input pointer.
buffer_end: dc.l buffer ; Last address in buffer = buffer + length.
bss
preempted_trap_13_address: ds.l 1
ds.l 96
stack: ds.l 1
buffer: ds.l 0 ; Length of buffer must be added to length of
program_end: ds.l 0 ; program to determine total size to pass to
end ; GEMDOS function $31.
A Linear Print Buffer
Circular print buffers are archaic, in spite of the fact
that they are still in use. Remember that COBOL is also still in
use, as is PASCAL, BASIC, and FORTRAN. I'm not saying that
archaic tools should not be used; I occasionally use them myself.
But I prefer faster more powerful tools when they are available
and applicable. In that spirit, I am going to introduce you to a
different type of buffer. At first, it will appear to be
cumbersome. That is only because we have not yet reached the
point in the book at which desk accessories and the file selector
are among the tools available. Trust me, I know what I'm doing.
Program 69 installs a linear buffer and a parallel interface pin
#11 interrupt handler. Its companion, program 70, locates the
address of program 69's buffer, then reads a file and stores the
file directly in the buffer. When the entire file has been
stored, program 70 initiates the interrupt controlled printing
process by invoking the handler installed by program 69. The
speed differential between a printing program/circular buffer
combination and a printing program/linear buffer combination will
be quite startling. Of course there will be quantitative
analyses shortly.
Program 69. This program installs a linear buffer and a
parallel interface pin #11 interrupt handler; it must
be executed prior to the execution of program 70.
; Program Name: PRG_7IP.S
; Version: 1.005
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS and a TTP suffix.
; Program Function:
; Linear printer buffer. The default buffer size is 32,768 bytes.
; This program does not use a circular queue; instead, it uses a linear
; queue; therefore, no custom trap #13 handler is installed by this program;
; only an interrupt handler is required.
; But this interrupt handler is different from the one used for the
; programs that established a circular buffer. The data read from a file is
; placed in the handler's buffer by the GEMDOS $3F function. PRG_7JP.TTP does
; this by locating the buffer and passing its address as a parameter to the
; the GEMDOS $3F function. In addition, PRG_7JP.TTP stores the length of the
; file in this program's filesize variable so that the interrupt handler can
; disable itself at the end of file.
; After the data has been transferred from the file to the buffer,
; PRG_7JP.TTP initiates the transfer through the parallel port by sending
; a NULL character through the port. When the printer receives the NULL
; character, it will print nothing, but the falling edge of pin 11's busy
; indicative pulse will invoke the interrupt handler so that it can send the
; first character of the file. The signal send by the printer after receiving
; that character and after receipt of each character thereafter will perpetuate
; the interrupt handler process until the end of the file has been reached.
; As the interrupt handler transfers data from the buffer through the
; parallel port, it decrements the variable "filesize". It stops sending
; when filesize = 0.
; The two programs, PRG_7IP.S and PRG_7JP.S have been designed to work
; together. This design was chosen more for its tutorial value than for its
; practical value. The program combination can handle only one file at a
; time. At times, it is convenient to print more than one file, and that can
; be done if a circular buffer of sufficient size is in use.
; When a linear buffer is in use, the easiest way to permit the printing
; of multiple files is to create a file name buffer. Actually, since it is
; not reasonable to expect all of the files to be printed to reside in the same
; directory, therefore, the buffer would be a "path" buffer.
; The design of such a program is much easier when the file selector is
; used to gather the file paths. Furthermore, the method obviates the need
; for two programs. This type of program will be introduced in the chapter
; that discusses the file selector.
; Execution Instructions:
; May be executed from the desktop with a TOS or TTP extension. May be
; executed from an AUTO folder if it has a PRG extension.
; If a larger buffer size is desired, the TTP version of this program can
; be used to declare a buffer size on the TTP parameter line. Any size, up
; to the maximum available machine memory, may be specified. When the TTP
; program is executed, type the desired size, in kilobytes, on the parameter
; line; the maximum number of digits which can be typed on the parameter line
; is 4. Do not type spaces before the digits. Do not type anything between
; digits. Do not type anything after the digits, except the Return key.
; The OK button may be clicked in lieu of a Return key press.
program_start: ; Calculate program size and retain result.
lea -$82(pc), a4 ; Fetch "command line" address.
lea -$80(a4), a0 ; Fetch "basepage" address.
lea program_end, a3 ; Fetch "end of program" address.
suba.l a0, a3 ; Program size is in A3.
lea stack, a7 ; Fetch address of the program stack.
process_input_parameter: ; Calculate requested buffer length.
lea length, a0 ; Fetch address of the length variable.
moveq #0, d0 ; Clear counter register.
move.b (a4)+, d0 ; Fetch parameter line character count.
beq.s compute_size ; Branch if no parameter--use default length.
cmpi.b #4, d0 ; Greater than four test.
bgt too_many_characters ; Branch if more than four characters.
subq.b #1, d0 ; Set up counter.
ext.w d0 ; Extend to match size of DBRA instruction.
moveq #0, d2 ; Initialize accumulator.
moveq #0, d1 ; Clear scratch register.
fetch_digit: ; Convert input from ASCII decimal to binary.
move.b (a4)+, d1 ; ASCII decimal digit to D1.
sub.b #$30, d1 ; Convert ASCII decimal digit to decimal digit.
bmi.s not_decimal ; Branch if digit is less than zero.
cmp.b #9, d1 ; Greater than nine test.
bgt.s not_decimal ; Branch if digit is greater than nine.
multiply_accumulator_content_by_ten:
move.l d2, d3 ; Copy accumulator in D3.
lsl.l #3, d2 ; Shift accumulator content to multiply by 8.
add.l d3, d2 ; Add D2's original quantity twice to
add.l d3, d2 ; complete the multiplication by 10.
add_new_digit_to_accumulator_content:
add.l d1, d2 ; Add decimal number in D1 to accumulator.
dbra d0, fetch_digit ; Loop until d0 becomes negative.
extend_to_thousands_of_bytes: ; Length will be (input X 1000) plus
moveq #10, d3 ; some even number of bytes that is less
lsl.l d3, d2 ; than 1000. Ex: if input is 32, length
move.l d2, (a0) ; is 32768. Store requested buffer size.
compute_size: ; This will be parameter to GEMDOS $31.
adda.l (a0), a3 ; Add buffer length to program size.
store_io_buffer_structure_components:
lea buffer, a1 ; Fetch buffer starting address.
lea io_buffer, a2 ; Fetch io_buffer structure base address.
move.l a1, (a2) ; Buffer starting address to base pointer.
move.l a1, 8(a2) ; Also to extract_pointer.
install_interrupt_handler_vector:
pea interrupt_handler ; Push interrupt handler address.
move.w #0, -(sp) ; Interrupt level for centronics ready.
move.w #$D, -(sp) ; Functon = XBIOS #13 dec = mfpint.
trap #14
addq.l #8, sp
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size (plus buffer length).
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
not_decimal:
pea message_1 ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
bra.s terminate
too_many_characters:
pea message_2 ; Print error message.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
terminate: ; Wait for keypress.
move.w #8, -(sp) ; Function = GEMDOS $8 = cnecin.
trap #1
addq.l #2, sp
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
interrupt_handler: ; Parallel interface interrupt handler.
movem.l d0/a0-a1, -(sp) ; Save content of registers used by handler.
move.l filesize, d0 ; Fetch file size.
beq.s clear_in_service_bit ; Exit if buffer is empty (filesize = 0).
lea io_buffer, a0 ; Fetch address of io_buffer pointer.
movea.l 8(a0), a1 ; Fetch content of extract pointer.
subq.l #1, d0 ; Decrement filesize.
move.l d0, 12(a0) ; Store new filesize value.
bne.s print_character ; Stop printing if filesize is 0.
move.l (a0), 8(a0) ; Put buffer start address in extract pointer.
bra.s clear_in_service_bit
print_character:
move.b (a1)+, d0 ; Fetch character. Increment extract pointer.
move.l a1, 8(a0) ; Store location of next character.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
clear_in_service_bit:
movem.l (sp)+, d0/a0-a1 ; Restore registers used by handler.
bclr #0, $FFFA11 ; Clear MFP in service bit.
rte
data
message_1: dc.b 'Nondecimal character digit detected on parameter line.'
dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
message_2: dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
dc.b 'The program converts the input to thousands of bytes.'
dc.b $D,$A,'Default buffer size is 32,768 bytes.',$D,$A
dc.b 'Press Return key to terminate.',$D,$A,0
align
io_buffer: dc.l buffer ; Pointer to buffer's starting address.
length: dc.l $8000 ; Default buffer length = 32,768 bytes.
extract_pointer: dc.l buffer ; Buffer character output pointer.
filesize: dc.l 0 ; filesize = 0 when buffer is empty.
bss
ds.l 96
stack: ds.l 1
buffer: ds.l 0 ; Length of buffer must be added to length of
program_end: ds.l 0 ; program to determine total size to pass to
end ; GEMDOS function $31.
Program 70. This program reads the file designated by
input on its command line into the buffer installed by
program 69, then it initiates the interrupt printing
process that is controlled by program 69.
; Program Name: PRG_7JP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TPP extension.
; Function:
; Reads the file designated by the parameter line input. The address
; of the buffer passed to the GEMDOS $3F function is that of the parallel
; interface interrupt handler installed by PRG_7IP.TOS or TTP.
; This program initiates the interrupt controlled printing process by
; sending a NULL character to the printer (A BELL character could also be
; used, if that is desirable.).
; NOTE: Initially, I tried to send the initiating character using the
; BIOS #3 function (bconout); that method worked occasionally, but was not
; at all reliable.
; Execution Instructions:
; PRG_7IP.TOS or TTP must install the parallel interface interrupt
; handler before this program is executed. Execute this program from the
; desktop. This program and the file to be printed must reside in the same
; directory.
fetch_pertinent_addresses:
lea -$82(pc), a3 ; Fetch "command line" address.
lea -$80(a3), a1 ; Fetch "basepage" address.
lea program_end, a0 ; Fetch "end of program" address.
lea stack, a7 ; Fetch stack address.
lea dta, a5 ; Fetch dta buffer address.
calculate_program_size:
suba.l a1, a0
return_unused_memory:
pea (a0) ; Push program length.
pea (a1) ; Push basepage address.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; Invoke GEMDOS exception.
lea $C(a7), sp ; Reset stack pointer to top of stack.
set_dta:
pea (a5) ; dta = address of 44 byte buffer.
move.w #$1A, -(sp) ; GEMDOS function = set dta.
trap #1
addq.l #6, sp
process_command_line:
move.b (a3)+, d0 ; Fetch command line character count.
ext.w d0 ; Extend to word for next instruction.
move.b #0, 0(a3,d0.w) ; Store a null at end of command line input.
search_for_file:
move.w #0, -(sp) ; Attribute = normal access.
pea (a3) ; Push address of file name.
move.w #$4E, -(sp) ; GEMDOS function = search first.
trap #1
addq.l #8, sp
tst d0
bne.s terminate ; Terminate if file not found.
process_interrupt_handler_variables:
pea fetch_interrupt_handler_address
move.w #$26, -(sp) ; Execute a routine in supervisor mode.
trap #14
addq.l #6, sp
lea handler_address, a4 ; Fetch handler address.
movea.l (a4), a4
adda.l #$16C, a4 ; Add offset to variable "filesize".
move.l $1A(a5), (a4) ; Store file size in handler variable.
adda.l #$188, a4 ; Add offset to buffer variable.
open_file: ; Function returns file handle in D0.
move.w #0, -(a7) ; Open as read only.
pea (a3) ; Push address of file name.
move.w #$3D, -(a7) ; See Internals page 127.
trap #1
addq.l #8, a7
move.w d0, d3 ; Store file handle in D3.
read_file:
pea (a4) ; Push buffer address
move.l $1A(a5), -(sp) ; Number of bytes to read.
move.w d3, -(sp) ; File's handle number.
move.w #$3F, -(sp) ; GEMDOS function = read.
trap #1
lea $C(sp), sp ; Reposition stack pointer.
close_file:
move.w d3, -(sp) ; Push file handle.
move.w #$3E, -(sp) ; See Internals page 128.
trap #1
addq #4, sp
initiate_interrupt_process: ; Execute in supervisor mode.
pea send_null
move.w #$26, -(sp)
trap #14
addq.l #6, sp
terminate:
move.w #0, -(sp)
trap #1
fetch_interrupt_handler_address:
lea handler_address, a0
move.l $100, (a0) ; Interrupt handler address is stored at
rts ; location $100.
send_null: ; The printer will drop the logic level of
moveq.l #0, d0 ; pin 11 from high to low after receiving this
; character, even though nothing will be
; printed.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
rts
data
bss
dta: ds.l 11
handler_address: ds.l 1
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
How Communication Between the Programs Was Established
It is the requirement that the print buffer and the
interrupt handler be established with a LSR algorithm which
necessitates the exclusion of the printing algorithm from the LSR
program. Later it will be possible to combine both algorithms in
one program. Because the printing program must pass the address
of the print buffer as a parameter when invoking the GEMDOS $3F
function, a communication link must be established between the
two algorithms. I am listing the steps that I used to establish
that communication link so that you could use a similar process
if you ever found it necessary to do so. The addresses shown in
my steps serve only as an indication of those you might see in
your system.
1. Load PRG_7IP.S into the AssemPro editor.
2. Assemble in PC-relative mode.
3. Save with TOS and TTP extensions.
4. Go to the debugger.
5. Click on symbolic button.
6. Click on from address button. Backspace over the $ and type the label
"interrupt_handler", sans parentheses. Press the Return key. Write
down address of the label = $84F8C.
7. Similarly, advance to the label "io_buffer".
8. Write addresses and contents for the following variables. The contents
can be obtained by assigning the address of each variable a slot in the
register field. For example: double click on A0 in the register field;
press the Esc key; type $850EC; press the Return key; note the contents
of the address in the register field. Follow a similar procedure to
assign $850F0 to A1's slot, $850F4 to A2's slot and $85058 to A3's slot.
Variable Address Contents
io_buffer $850EC $396 ; Assembly time address of "buffer".
length $850F0 $8000 ; Default buffer length.
extract_pointer: $850F4 $396 ; Assembly time address of "buffer".
filesize: $850F8 0
9. Click on the from address button. Backspace over the $ and type the
label "buffer", sans parentheses. Press the Return key. Write down the
label's address, but ignore the contents, which are meaningless at this
time. Address = $85280. Another way to obtain the buffer's address is
to assign the label "buffer" to A4's slot. Double click on A4; press
the Esc key; type "buffer.l", sans parentheses; click on the address
button in the dialog box; press the Return key. In the register field,
you would see: BUFFER.L ^ $85280.
10. Click on the from address button. Backspace over the $ and type the
label "store_io_buffer_structure_components", sans parentheses. Press
the Return key. Move the PC cursor to that label--press Control key and
left mouse button simultaneously.
11. Single step to the next label. In the register field you will see the
run time address of "buffer" stored at "io_buffer" and
"extract_pointer". Exit to the desktop.
12. Calculate required offsets to pertinent variables.
variable address variable address
filesize: $850F8 buffer: $85280
interrupt_handler: -$84F8C filesize: -$850F8
------ ------
offsets $ 16C $ 188
In program 70, we can easily obtain the address of the label
"interrupt_handler" because it will be stored at address $100,
the address set aside for the MFP level 0 interrupt handler. See
pages 235-244 of the Internals book. The reference does not
clearly state that address $100 is the relevant address, but it
can be calculated from the information given within those pages.
Once the address of "interrupt_handler" is obtained, the address
of filesize can be calculated by adding its offset, $16C, from
the address stored in location $100. Then, the address of the
label "buffer" can be calculated by adding its offset, $188, from
"filesize" to the address calculated for filesize.
Data Transfer Rate Quantitative Analyses
I have prepared several programs that calculate data
transfer rates. Program 71 calculates the data transfer rate for
a circular buffer by printing characters in a loop in order to
eliminate the influence of a printing algorithm on the buffer's
transfer rate. Of course, we must assume that the character
printing loop probably influences that rate, especially since it
uses the same bios function to transmit characters to the buffer;
but, there is a limit to what we can do to limit the interaction
because the circular buffers are bound to that function via their
dependence on the trap #13 handler through which they receive
characters. I admit that there may be esoteric design that is
not being consided, but it really doesn't matter, because it is
the linear type of buffer arrangement which offers the best
opportunity for maximum data transfer rates. That shall be
proven before the conclusion of this chapter.
Program 71. A program that calculates a circular print buffer's
data transfer rate by printing characters in a loop. Following
the execution instructions listed in the program's documentation.
; Program Name: PRG_7KP.S
; Version: 1.004
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Function:
; This program is used to determine the input data transfer rate of the
; trap #13 handler for a circular print buffer. In order to remove the
; influence of the output data transfer rate of an algorithm that reads a file
; from a disk and sends each character to the parallel port using system
; functions, this program this program prints characters in a loop.
; At the onset of execution the program prompts for the name of the print
; buffer that is being tested.
; The program's output data is stored in a file bearing a name that is
; composed of that given for the print buffer and the extension DAT. The data
; stored in that file is the number of characters printed; the name of the
; print buffer being tested and the buffer's input data transfer rate in bytes
; per second. The name given for the print buffer can be no more than 8 valid
; ST file name characters.
; The input data transfer rate file can be used to compare the results of
; two or more print buffers to each other. In addition, the input data
; transfer rate of linear buffers can be compared to that of circular buffers.
; Execution Instructions:
; 1. If necessary, remove all installed print buffers by cold booting.
; 2. Turn the printer on.
; 3. If the print buffer that is to be tested is not yet installed,
; install it now.
; 4. Execute CUSTOM.PRG to install the custom traps invoked by this
; program.
; 5. Execute this program from the desktop. The program's output will
; be stored in a file that will reside in the same directory from
; which this program is executed.
calculate_program_size:
lea -$102(pc), a1 ; Fetch basepage start address.
lea program_end, a0 ; Fetch program end address.
trap #6 ; Return unused memory to op system.
lea stack, a7 ; Point A7 to this program's stack.
get_name_of_print_buffer:
; The first byte of the array 'buffer_name_buffer' contains the maximum
; permissible length of the input string. After the GEMDOS $A function
; invocation, the second byte of the array will contain the number of
; characters read as input; the carriage return character will not be included
; in the string length value. The address of the first byte of the input
; string is that of the third byte of the array.
lea buffer_prompt, a0
bsr print_string
lea buffer_name_buffer, a3 ; Fetch structure's base address.
pea (a3) ; Push address of the array.
move.w #$A, -(sp) ; GEMDOS readline function.
trap #1
addq.l #6, sp
store_name_for_header_use:
move.b 1(a3), d0 ; Fetch length of buffer name.
ext.w d0 ; Extend to word length.
addq.l #2, a3 ; Move to name element in array.
movea.l a3, a2 ; Prepare for suffix algorithm.
adda.w d0, a2 ; Same preparation.
lea buffer_name, a1 ; Fetch address of buffer_name array.
subq.w #1, d0 ; Initialize counter.
store_character: ; Store name in another array for use
move.b (a3)+, (a1)+ ; in a header.
dbra d0, store_character
add_output_file_name_suffix: ; Attach DAT suffix to buffer name.
move.b #$2E, (a2)+ ; Attach a period.
move.b #$44, (a2)+ ; Attach letter 'D'.
move.b #$41, (a2)+ ; Attach letter 'A'.
move.b #$54, (a2)+ ; Attach letter 'T'.
move.b #$0, (a2) ; Finish with a NULL.
get_start_time:
trap #3 ; Value of system clock returned in D0.
move.l d0, d4 ; Store time in D4.
print_characters:
move.l #3000, d6 ; Number of characters to print.
move.w d6, d3 ; Initialize loop counter.
subq.w #1, d3
move.b #$7A, d1
reset_generator:
move.b #$20, d5 ; Character number generator.
print_byte:
cmp.b d1, d5
beq.s reset_generator
addq.b #1, d5
move.w d5, -(sp)
move.w #0, -(sp)
move.w #3, -(sp) ; See Internals page 155.
trap #13
addq #6, sp
dbra d3, print_byte ; Print 3000 characters.
transfer_rate_calculations:
trap #3 ; Fetch current time.
move.l d0, d5 ; Save end time.
sub.l d4, d0 ; Subtract start time from end time.
convert_time_to_milliseconds: ; Multiply by 5.
move.l d0, d2 ; Save copy to add.
asl.l #2, d2 ; Multiply by 4.
add.l d0, d2 ; To complete multiplication by 5.
compute_transfer_rate:
move.l #3000, d0 ; Fetch number of characters printed.
divu d2, d0 ; Divide size by time in milliseconds.
copy_to_d1: ; Will extract remainder from D1.
move.l d0, d1 ; Quotient is in lower word of D0.
mulu #1000, d0 ; Multiply quotient by 1000.
clr.w d1 ; Get rid of quotient.
swap d1 ; Remainder is now in lower word of D1.
mulu #1000, d1 ; Multiply remainder by 1000.
divu d2, d1 ; Divide remainder by time in milliseconds.
swap d1 ; Put remainder in lower word.
clr.w d1 ; Get rid of remainder.
swap d1 ; Get quotient back in lower word.
add.l d0, d1 ; Transfer rate is now in D1.
; Note: The order in which things are done in the calculation algorithm
; is critical. The remainder obtained from the first division must
; be manipulated just right. One other point is that the quotient
; will be zero whenever the time in milliseconds is a value greater
; than the size of the file.
; I have verified the accuracy of the above algorithm, but I have
; decided to print the number of 5 milliseconds clock ticks obtained
; for both the start and end times. This will permit the transfer
; rate to be calculated manually. Remember that those clock ticks
; must be multiplied by 5 to convert them to milliseconds. And, of
; course, the number of milliseconds must be divided by 1000 to convert
; them to seonds; or one can divide the file size by the number of
; milliseconds and multiply the quotient by 1000.
; The equation being used in this algorithm is:
; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
; X 1000 (msec/second).
trap #4 ; Convert to ASCII decimal.
lea transfer_rate, a1 ; Fetch address of transfer_rate.
move.l a0, (a1) ; Store address of decimal string.
; NOTE: The variable 'transfer_rate' now contains the address of the
; ASCII decimal string that is the transfer rate. Trap #4 can't
; be invoked until that value is used by the program, otherwise
; the value will be corrupted.
create_output_file:
move.w #0, -(sp) ; File attribute = read/write.
pea output_file ; Push address of output file's name.
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
lea output_file_handle, a1
move.w d0, (a1)
redirect_output_bound_for_screen:
; NOTE: If the output file's handle is exchanged with the video screen's
; handle, then the printline function = GEMDOS $9 can be used to
; write to the file.
redirect_output: ; Exchange file handle with screen's handle.
move.w d0, -(sp) ; This is the disk file's handle.
move.w #1, -(sp) ; This is the video screen's handle.
move.w #$46, -(sp) ; Function = f_force = GEMDOS $46.
trap #1
addq.l #6, sp
print_data_report:
lea heading(pc), a0 ; Print data file heading.
bsr print_string
lea buffer_name(pc), a0 ; Name of buffer for which transfer rate is
bsr print_string ; being reported.
lea header_1(pc), a0 ; Transfer Rate header.
bsr print_string
move.l transfer_rate, a0 ; Transfer Rate. Fetch from trap #4
bsr print_string ; location. Quick before it goes away.
lea header_2(pc), a0 ; Transfer Rate Units.
bsr print_string
lea header_3(pc), a0 ; Characters printed header.
bsr print_string
move.l d6, d1 ; Fetch number of bytes printed from D6.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_7(pc), a0 ; End Time header.
bsr print_string
move.l d5, d1 ; Fetch end time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8(pc), a0 ; Time units = 5-millisecond ticks.
bsr print_string
lea header_6(pc), a0 ; Start Time header.
bsr print_string
move.l d4, d1 ; Fetch start time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8(pc), a0 ; Time units = 5-millisecond ticks.
bsr print_string
close_output_file:
move.w output_file_handle, -(sp)
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
terminate:
move.w #0, -(sp)
trap #1
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
data
heading: dc.b $D,$A,'PRG_7KP.TOS Execution Results',$D,$A,$D,$A
dc.b ' Print Buffer: ',0
header_1: dc.b $D,$A,' Transfer Rate: ',0
header_2: dc.b ' bytes/second',$D,$A,$D,$A,0
header_3: dc.b ' Characters Printed: ',0
header_7: dc.b $D,$A,$D,$A,' End Time: ',0
header_6: dc.b ' Start Time: ',0
header_8: dc.b ' 5-millisecond ticks',$A,$D,0
buffer_prompt:
dc.b $D,$A,$D,$A
dc.b 'Data produced by this program will be stored in a file with',$D,$A
dc.b 'name composed of the name of the print buffer and a DAT suffix.',$D,$A
dc.b $D,$A,'Name of print buffer (8 characters max, no suffix): ',0
buffer_name_buffer:
dc.b 10 ; Maximum length of input string = 10 characters.
dc.b 0 ; String's length will be stored here.
output_file: ds.b 14 ; String will be stored here.
buffer_name: ds.b 10 ; Buffer name for heading.
align
buffer: dc.w $0 ; Character buffer.
bss
output_file_handle: ds.w 1
transfer_rate: ds.l 1
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
PRG_7KP.TOS Execution Results
Print Buffer: PRG_7HP
Transfer Rate: 13636 bytes/second
Characters Printed: 3000
End Time: 1447716 5-millisecond ticks
Start Time: 1447672 5-millisecond ticks
Program 72 calculates the data transfer rate for a
particular printing program/circular print buffer combination.
This program prompts for the name of a file to be printed and the
name of the buffer being tested. The data transfer rate will
vary somewhat depending on the directory path from which the file
is read, therefore, that path is included in the program's output
data. If the program is executed from a floppy disk, the total
execution time will be much longer than the data transfer time
because the output file must also be stored in the directory from
which the file is read and from which program 72 is executed.
Program 72. Calculates the data transfer rate for a particular
printing program/circular buffer combination. Follow the
execution instructions listed in the program's documentation.
; Program Name: PRG_7LP.S
; Version: 1.004
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Function:
; This program is used to determine a the data transfer rate of a printing
; program/print buffer combination. The influence of the printing program's
; output data transfer rate is inherent for print buffers designed to intercept
; operating system trap #13 invocations.
; The program prompts for the name of a file to be printed; then it
; prompts for the name of the print buffer that is being tested. The
; program's output data is stored in a file bearing a name that is composed of
; that given for the print buffer and the extension DAT. The data stored in
; that file is the name of the printed file and its length; the name of the
; print buffer and the printing program/print buffer combination's data
; transfer rate. The name given for the print buffer can be no more than 8
; valid ST file name characters.
; The data transfer rate file can be used to compare the results of two
; or more printing program/print buffer combinations to each other.
; Execution Instructions:
; 1. If necessary, remove all installed print buffers by cold booting.
; 2. Turn the printer on.
; 3. If the print buffer that is to be involved is a software buffer,
; execute the program that installs the print buffer.
; 4. Execute CUSTOM.PRG to install the custom traps invoked by this
; program.
; 5. Execute this program from the desktop. The file that is to be
; printed and this program must reside in the same directory.
calculate_program_size:
lea -$102(pc), a1 ; Fetch basepage start address.
lea program_end, a0 ; Fetch program end address.
trap #6 ; Return unused memory to op system.
lea stack, a7 ; Point A7 to this program's stack.
get_name_of_file: ; Request name of file to be printed.
lea file_prompt, a0
bsr print_string
; The first byte of the array 'file_name_buffer' contains the maximum
; permissible length of the input string. After the GEMDOS $A function
; invocation, the second byte of the array will contain the number of
; characters read as input; the carriage return character will not be included
; in the string length value. The address of the first byte of the input
; string is that of the third byte of the array.
lea file_name_buffer, a3 ; Fetch structure's base address.
pea (a3) ; Push address of the array.
move.w #$A, -(sp) ; Function = GEMDOS $A = readline.
trap #1
addq.l #6, sp
terminate_file_name_with_null: ; So that name can be printed as a string.
move.b 1(a3), d0 ; Fetch length of file name.
ext.w d0 ; Extend to word length.
move.b #0, 2(a3,d0.w) ; Store NULL at end of string.
get_name_of_print_buffer:
lea buffer_prompt, a0
bsr print_string
lea buffer_name_buffer, a3 ; Fetch structure's base address.
pea (a3) ; Push address of the array.
move.w #$A, -(sp) ; GEMDOS readline function.
trap #1
addq.l #6, sp
store_name_for_header_use:
move.b 1(a3), d0 ; Fetch length of buffer name.
ext.w d0 ; Extend to word length.
addq.l #2, a3 ; Move to name element in array.
movea.l a3, a2 ; Prepare for suffix algorithm.
adda.w d0, a2 ; Same preparation.
lea buffer_name, a1 ; Fetch address of buffer_name array.
subq.w #1, d0 ; Initialize counter.
store_character: ; Store name in another array for use
move.b (a3)+, (a1)+ ; in a header.
dbra d0, store_character
add_output_file_name_suffix: ; Attach DAT suffix to buffer name.
move.b #$2E, (a2)+ ; Attach a period.
move.b #$44, (a2)+ ; Attach letter 'D'.
move.b #$41, (a2)+ ; Attach letter 'A'.
move.b #$54, (a2)+ ; Attach letter 'T'.
move.b #$0, (a2) ; Finish with a NULL.
get_drive:
move.w #$19, -(sp) ; See Internals page 116, but note the
trap #1 ; errors there.
addq.l #2, sp ; Note first error at this instruction.
add.w #$41, d0 ; Note 2nd error at this instruction.
lea path, a6 ; Fetch address of path array.
move.b d0, (a6)+ ; Store drive letter in path array.
move.b #$3A, (a6)+ ; Store a colon in path array.
get_directory_path:
move.w #0, -(sp)
pea (a6) ; Push address of next path array element.
move.w #$47, -(sp) ; See Internals page 135.
trap #1
addq.l #8, sp
print_the_file:
pea dta ; dta = address of 44 byte buffer.
move.w #$1A, -(sp) ; GEMDOS function = set dta.
trap #1
addq.l #6, sp
search_for_file:
move.w #0, -(sp) ; Attribute = normal access.
pea file_name ; Push address of file name.
move.w #$4E, -(sp) ; GEMDOS function = search first.
trap #1
addq.l #8, sp
tst d0
bne terminate ; Exit if file not found.
lea dta, a0
move.l $1A(a0), d6
lea buffer, a3
open_file: ; Function returns file handle in D0.
move.w #0, -(a7) ; Open as read only.
pea file_name ; Push address of file name.
move.w #$3D, -(a7) ; See Internals page 127.
trap #1
addq.l #8, a7
move.w d0, d3 ; Store file handle in D3.
trap #3 ; Value of system clock returned in D0.
move.l d0, d4 ; Store time in D4.
read_file: ; Function returns 1 in D0 unless end of file
; is reached or a read error occurs.
pea (a3) ; Push address of character buffer.
move.l #1, -(a7) ; Number of bytes to read from the file.
; GEMDOS $3F's buffer.
move.w d3, -(a7) ; Push file handle.
move.w #$3F, -(a7) ; See Internals page 129.
; Note: The system will not disturb this stack setup, therefore, it must be
; initialized only once. Thereafter, a branch need be taken only to the
; following instruction.
fetch_byte:
trap #1
tst.w d0 ; When NULL at end of file is read,
ble.s end_of_file ; D0 will be 0.
print_byte:
move.b (a3), d0 ; Must read buffer one byte each time.
move.w d0, -(sp) ; But must push a word here.
move.w #0, -(sp)
move.w #3, -(sp) ; See Internals page 155.
trap #13
addq #6, sp
bra.s fetch_byte
end_of_file:
lea $C(sp), sp
close_file:
move.w d3, -(sp) ; Push handle for the file that was read.
move.w #$3E, -(sp) ; See Internals page 128.
trap #1
addq #4, sp
transfer_rate_calculations:
trap #3 ; Fetch current time.
move.l d0, d5 ; Store end time for data file.
sub.l d4, d0 ; Subtract start time from end time.
convert_time_to_milliseconds: ; Multiply by 5.
move.l d0, d2 ; Save copy to add.
asl.l #2, d2 ; Multiply by 4.
add.l d0, d2 ; To complete multiplication by 5.
compute_transfer_rate:
move.l d6, d0 ; Fetch file size from D6.
divu d2, d0 ; Divide size by time in milliseconds.
copy_to_d1: ; Will extract remainder from D1.
move.l d0, d1 ; Quotient is in lower word of D0.
mulu #1000, d0 ; Multiply quotient by 1000.
clr.w d1 ; Get rid of quotient.
swap d1 ; Remainder is now in lower word of D1.
mulu #1000, d1 ; Multiply remainder by 1000.
divu d2, d1 ; Divide remainder by time in milliseconds.
swap d1 ; Put remainder in lower word.
clr.w d1 ; Get rid of remainder.
swap d1 ; Get quotient back in lower word.
add.l d0, d1 ; Transfer rate is now in D1.
; Note: The order in which things are done in the calculation algorithm
; is critical. The remainder obtained from the first division must
; be manipulated just right. One other point is that the quotient
; will be zero whenever the time in milliseconds is a value greater
; than the size of the file.
; I have verified the accuracy of the above algorithm, but I have
; decided to print the number of 5 milliseconds clock ticks obtained
; for both the start and end times. This will permit the transfer
; rate to be calculated manually. Remember that those clock ticks
; must be multiplied by 5 to convert them to milliseconds. And, of
; course, the number of milliseconds must be divided by 1000 to convert
; them to seonds; or one can divide the file size by the number of
; milliseconds and multiply the quotient by 1000.
; The equation being used in this algorithm is:
; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
; X 1000 (msec/second).
trap #4 ; Convert to ASCII decimal.
lea transfer_rate, a1
move.l a0, (a1) ; Store address of decimal string.
; NOTE: The variable 'transfer_rate' now contains the address of the
; ASCII decimal string that is the transfer rate. Trap #4 can't
; be invoked until that value is used by the program, otherwise
; the value will be corrupted.
create_output_file:
move.w #0, -(sp) ; File attribute = read/write.
pea output_file ; Push address of output file's name.
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
lea output_file_handle, a0
move.w d0, (a0)
redirect_output_bound_for_screen:
; NOTE: If the output file's handle is exchanged with the video screen's
; handle, then the printline function = GEMDOS $9 can be used to
; write to the file.
redirect_output: ; Exchange file handle with screen's handle.
move.w d0, -(sp) ; This is the disk file's handle.
move.w #1, -(sp) ; This is the video screen's handle.
move.w #$46, -(sp) ; Function = f_force = GEMDOS $46.
trap #1
addq.l #6, sp
print_data_report:
lea heading, a0 ; Print data file heading.
bsr print_string
lea buffer_name, a0 ; Name of buffer for which transfer rate is
bsr print_string ; being reported.
lea header_1, a0 ; Transfer Rate header.
bsr print_string
move.l transfer_rate, a0 ; Transfer Rate. Fetch from trap #4
bsr print_string ; location. Quick before it goes away.
lea header_2, a0 ; Transfer Rate Units.
bsr print_string
lea header_3, a0 ; File name header.
bsr print_string
lea file_name, a0 ; File name.
bsr print_string
lea header_4, a0 ; File length header.
bsr print_string
move.l d6, d1 ; Fetch file size from D6.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_5, a0 ; File size units.
bsr print_string
lea header_9, a0 ; Directory path.
bsr print_string
lea path, a0
bsr print_string
lea header_7, a0 ; End Time header.
bsr print_string
move.l d5, d1 ; Fetch end time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8, a0 ; Time units = 5-millisecond ticks.
bsr print_string
lea header_6, a0 ; Start Time header.
bsr print_string
move.l d4, d1 ; Fetch start time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8, a0 ; Time units = 5-millisecond ticks.
bsr print_string
close_output_file:
move.w output_file_handle, -(sp)
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
terminate:
move.w #0, -(sp)
trap #1
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
data
heading: dc.b $D,$A,'PRG_7LP.TOS Execution Results',$D,$A,$D,$A
dc.b ' Print Buffer: ',0
header_1: dc.b $D,$A,' Transfer Rate: ',0
header_2: dc.b ' bytes/second',$D,$A,0
header_3: dc.b ' File Printed: ',0
header_4: dc.b $D,$A,' File Length: ',0
header_5: dc.b ' bytes',$D,$A,0
header_9 dc.b $D,$A,' Path: ',0
header_7: dc.b $D,$A,$D,$A,' End Time: ',0
header_6: dc.b ' Start Time: ',0
header_8: dc.b ' 5-millisecond ticks',$A,$D,0
file_prompt: dc.b $D,$A,'Name of file to be printed (include suffix): ',0
buffer_prompt:
dc.b $D,$A,$D,$A
dc.b 'Data produced by this program will be stored in a file with',$D,$A
dc.b 'name composed of the name of the print buffer and a DAT suffix.',$D,$A
dc.b $D,$A,'Name of print buffer (8 characters max, no suffix): ',0
file_name_buffer:
dc.b 14 ; Maximum length of input string = 14 characters.
dc.b 0 ; String's length will be stored here.
file_name: ds.b 14 ; String will be stored here.
buffer_name_buffer:
dc.b 10 ; Maximum length of input string = 10 characters.
dc.b 0 ; String's length will be stored here.
output_file: ds.b 14 ; String will be stored here.
buffer_name: ds.b 10 ; Buffer name for heading.
align
buffer: dc.w $0 ; Character buffer.
bss
output_file_handle: ds.w 1
dta: ds.l 11
transfer_rate: ds.l 1
path: ds.b 120
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
PRG_7LP.TOS Execution Results
Print Buffer: PRG_7HP
Transfer Rate: 995 bytes/second
File Printed: PRG_7LP.S
File Length: 15107 bytes
Path: P: Note: P was a ram disk.
End Time: 670922 5-millisecond ticks
Start Time: 667886 5-millisecond ticks
PRG_7LP.TOS Execution Results
Print Buffer: PRG_7HP
Transfer Rate: 797 bytes/second
File Printed: PRG_7LP.S
File Length: 15107 bytes
Path: A:\PRG_7
End Time: 732462 5-millisecond ticks
Start Time: 728672 5-millisecond ticks
Program 73 has been designed to measure the data transfer
rate through the ST's parallel interface port to a device
connected to that port. For the test, I used my Supra
Corporation MicroStuffer printer buffer. Because I believe that
this external buffer can accept data as fast as the ST's port can
move it, I consider the test to reflect the capacity of the ST's
circuitry. And even though I can't prove that fact,
specifically, I can state that there is evidence in at least one
reference of a limit to the port's data transfer rate. On page
3-26 of the Peel book, the ST port's rate is said to be 4000
bytes per second, typically. The point is this: no matter the
quality of an external buffer connected to the ST's port, the
data transfer rate to that buffer will never approach the speed
at which we are able to transfer data to a linear buffer within
ST ram. Program 74 will illustrate that clearly enough. Please
especially note the data transfer rate achieved when the file
being printed is read from a ram disk.
Program 73. This program will measure the data transfer rate of
the ST's parallel port when an external buffer is connected to
that port.
; Program Name: PRG_7MP.S
; Version: 1.004
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Function:
; A program to measure the transfer rate of data through the ST's
; parallel interface port to a device connected to that port. In this
; particular instance the device connected to the port may be the printer,
; the printer's internal buffer or an external print buffer such as my Supra
; Corporation MicroStuffer printer buffer. Unfortunately, MicroStuffer's
; input data transfer rate is not specified in its operator's manual;
; therefore, I can't compare the results obtained with this program to its
; hardware specification.
; However, in appendix G of my Star NX-10 User's Manual, the data
; transfer rate for the printer's parallel interface is given: it is listed
; as 1,000 to 6,000 characters per second.
; In addition, there is some information given on page 3-26 of the
; Peel book. The data transfer rate for the ST's parallel port is mentioned
; there: it is said to be 4000 bytes per second, typically.
; It is reasonable to assume that, regardless of any faster data transfer
; rate that may be possible with the MicroStuffer printer buffer, we are not
; going to be able to send data to it faster than the maximum data transfer
; rate of the ST's parallel interface port.
; Still, in effect, since there are two devices involved in the
; experiment, and because there is no way to isolate the influence of each
; on the outcome of the experiment, the validity of the results is relevant
; only as they are applied to the device combination.
;
; The program does not use system functions to print; instead it prints
; directly to the port. Furthermore, this program does not read and print a
; file. Instead, it prints characters in a loop, thereby eliminating the
; influence of a file reading algorithm.
; The purpose of this experiment is to obtain information so that the
; data transfer rate obtained with an external buffer connected to the
; parallel interface port can be compared to that which is obtained with
; buffers created within the ST by software. I assume that all print buffers
; involved in the comparison permit interrupt controlled printing (background
; printing), thereby effecting a multi-tasking environment.
; NOTE: THE PRINTER DOES NOT HAVE TO BE ON WHEN THE EXTERNAL BUFFER
; IS CONNECTED. CAN SAVE PAPER THAT WAY. JUST CLEAR THE
; EXTERNAL BUFFER AFTER A TEST RUN.
; This program prints characters in a loop, so it does not prompt for
; the name of a file to be printed, but it does prompt for the name of the
; print buffer that is being tested. The program's output data is stored in
; a file bearing a name that is composed of that given for the print buffer
; and the extension DAT. The data stored in that file is the number of
; characters printed; and the name of the print buffer and its input data
; transfer rate in bytes per second. The name given for the print buffer can
; be no more than 8 valid ST file name characters.
; Execution Instructions:
; 1. If necessary, remove all installed print buffers by cold booting.
; 2. Turn the printer on.
; 3. Turn the external buffer on.
; 4. Execute CUSTOM.PRG to install the custom traps invoked by this
; program.
; 5. Execute this program from the desktop. The program's output will
; be stored in a file that will reside in the same directory from
; which this program is executed.
calculate_program_size:
lea -$102(pc), a1 ; Fetch basepage start address.
lea program_end, a0 ; Fetch program end address.
trap #6 ; Return unused memory to op system.
lea stack, a7 ; Point A7 to this program's stack.
trap #0 ; Enter supervisor mode.
get_name_of_print_buffer:
; The first byte of the array 'buffer_name_buffer' contains the maximum
; permissible length of the input string. After the GEMDOS $A function
; invocation, the second byte of the array will contain the number of
; characters read as input; the carriage return character will not be included
; in the string length value. The address of the first byte of the input
; string is that of the third byte of the array.
lea buffer_prompt, a0
bsr print_string
lea buffer_name_buffer, a3 ; Fetch structure's base address.
pea (a3) ; Push address of the array.
move.w #$A, -(sp) ; GEMDOS readline function.
trap #1
addq.l #6, sp
store_name_for_header_use:
move.b 1(a3), d0 ; Fetch length of buffer name.
ext.w d0 ; Extend to word length.
addq.l #2, a3 ; Move to name element in array.
movea.l a3, a2 ; Prepare for suffix algorithm.
adda.w d0, a2 ; Same preparation.
lea buffer_name, a1 ; Fetch address of buffer_name array.
subq.w #1, d0 ; Initialize counter.
store_character: ; Store name in another array for use
move.b (a3)+, (a1)+ ; in a header.
dbra d0, store_character
add_output_file_name_suffix: ; Attach DAT suffix to buffer name.
move.b #$2E, (a2)+ ; Attach a period.
move.b #$44, (a2)+ ; Attach letter 'D'.
move.b #$41, (a2)+ ; Attach letter 'A'.
move.b #$54, (a2)+ ; Attach letter 'T'.
move.b #$0, (a2) ; Finish with a NULL.
get_start_time:
trap #3 ; Value of system clock returned in D0.
move.l d0, d4 ; Store time in D4.
print_characters:
move.l #3000, d6 ; Number of characters to print.
move.w d6, d3 ; Initialize loop counter.
subq.w #1, d3
move.b #$7A, d1
reset_generator:
move.b #$20, d5 ; Character number generator.
print_byte:
cmp.b d1, d5
beq.s reset_generator
addq.b #1, d5
printer_ready_test:
btst #0, $FFFA01 ; Check logic level of pin 11.
bne.s printer_ready_test ; Loop until printer is ready to receive.
lea $FF8800, a2 ; Fetch address of PSG data bus.
move.b #$F, (a2) ; Latch address of port B.
move.b d5, 2(a2) ; Write character to port B.
move.b #$E, (a2) ; Latch address of port A.
move.b (a2), d0 ; Read content of port A.
andi.b #$DF, d0 ; Reset bit 5 of d1 to zero.
move.b d0, 2(a2) ; Reset bit 5 of port A. Strobe low.
ori.b #$20, d0 ; Set bit 5 of d1.
move.b d0, 2(a2) ; Set bit 5 of port A. Strobe high.
dbra d3, print_byte ; Print 3000 characters.
transfer_rate_calculations:
trap #3 ; Fetch current time.
move.l d0, d5 ; Save end time.
sub.l d4, d0 ; Subtract start time from end time.
convert_time_to_milliseconds: ; Multiply by 5.
move.l d0, d2 ; Save copy to add.
asl.l #2, d2 ; Multiply by 4.
add.l d0, d2 ; To complete multiplication by 5.
compute_transfer_rate:
move.l #3000, d0 ; Fetch number of characters printed.
divu d2, d0 ; Divide size by time in milliseconds.
copy_to_d1: ; Will extract remainder from D1.
move.l d0, d1 ; Quotient is in lower word of D0.
mulu #1000, d0 ; Multiply quotient by 1000.
clr.w d1 ; Get rid of quotient.
swap d1 ; Remainder is now in lower word of D1.
mulu #1000, d1 ; Multiply remainder by 1000.
divu d2, d1 ; Divide remainder by time in milliseconds.
swap d1 ; Put remainder in lower word.
clr.w d1 ; Get rid of remainder.
swap d1 ; Get quotient back in lower word.
add.l d0, d1 ; Transfer rate is now in D1.
; Note: The order in which things are done in the calculation algorithm
; is critical. The remainder obtained from the first division must
; be manipulated just right. One other point is that the quotient
; will be zero whenever the time in milliseconds is a value greater
; than the size of the file.
; I have verified the accuracy of the above algorithm, but I have
; decided to print the number of 5 milliseconds clock ticks obtained
; for both the start and end times. This will permit the transfer
; rate to be calculated manually. Remember that those clock ticks
; must be multiplied by 5 to convert them to milliseconds. And, of
; course, the number of milliseconds must be divided by 1000 to convert
; them to seonds; or one can divide the file size by the number of
; milliseconds and multiply the quotient by 1000.
; The equation being used in this algorithm is:
; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
; X 1000 (msec/second).
trap #4 ; Convert to ASCII decimal.
lea transfer_rate, a1 ; Fetch address of transfer_rate.
move.l a0, (a1) ; Store address of decimal string.
; NOTE: The variable 'transfer_rate' now contains the address of the
; ASCII decimal string that is the transfer rate. Trap #4 can't
; be invoked until that value is used by the program, otherwise
; the value will be corrupted.
create_output_file:
move.w #0, -(sp) ; File attribute = read/write.
pea output_file ; Push address of output file's name.
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
lea output_file_handle, a1
move.w d0, (a1)
redirect_output_bound_for_screen:
; NOTE: If the output file's handle is exchanged with the video screen's
; handle, then the printline function = GEMDOS $9 can be used to
; write to the file.
redirect_output: ; Exchange file handle with screen's handle.
move.w d0, -(sp) ; This is the disk file's handle.
move.w #1, -(sp) ; This is the video screen's handle.
move.w #$46, -(sp) ; Function = f_force = GEMDOS $46.
trap #1
addq.l #6, sp
print_data_report:
lea heading(pc), a0 ; Print data file heading.
bsr print_string
lea buffer_name(pc), a0 ; Name of buffer for which transfer rate is
bsr print_string ; being reported.
lea header_1(pc), a0 ; Transfer Rate header.
bsr print_string
move.l transfer_rate, a0 ; Transfer Rate. Fetch from trap #4
bsr print_string ; location. Quick before it goes away.
lea header_2(pc), a0 ; Transfer Rate Units.
bsr print_string
lea header_3(pc), a0 ; Characters printed header.
bsr print_string
move.l d6, d1 ; Fetch number of bytes printed from D6.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_7(pc), a0 ; End Time header.
bsr print_string
move.l d5, d1 ; Fetch end time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8(pc), a0 ; Time units = 5-millisecond ticks.
bsr print_string
lea header_6(pc), a0 ; Start Time header.
bsr print_string
move.l d4, d1 ; Fetch start time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8(pc), a0 ; Time units = 5-millisecond ticks.
bsr print_string
close_output_file:
move.w output_file_handle, -(sp)
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
terminate:
andi.w #$DFFF, SR ; Return to User Mode.
move.w #0, -(sp)
trap #1
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
data
heading: dc.b $D,$A,'PRG_7MP.TOS Execution Results',$D,$A,$D,$A
dc.b ' Print Buffer: ',0
header_1: dc.b $D,$A,' Transfer Rate: ',0
header_2: dc.b ' bytes/second',$D,$A,$D,$A,0
header_3: dc.b ' Characters Printed: ',0
header_7: dc.b $D,$A,$D,$A,' End Time: ',0
header_6: dc.b ' Start Time: ',0
header_8: dc.b ' 5-millisecond ticks',$A,$D,0
buffer_prompt:
dc.b $D,$A,$D,$A
dc.b 'Data produced by this program will be stored in a file with',$D,$A
dc.b 'name composed of the name of the print buffer and a DAT suffix.',$D,$A
dc.b $D,$A,'Name of print buffer (8 characters max, no suffix): ',0
buffer_name_buffer:
dc.b 10 ; Maximum length of input string = 10 characters.
dc.b 0 ; String's length will be stored here.
output_file: ds.b 14 ; String will be stored here.
buffer_name: ds.b 10 ; Buffer name for heading.
align
buffer: dc.w $0 ; Character buffer.
bss
output_file_handle: ds.w 1
transfer_rate: ds.l 1 ; Pointer to ASCII decimal string.
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
PRG_7NP.TOS Execution Results
Print Buffer: EXTERNAL
Transfer Rate: 6451 bytes/second
Characters Printed: 3000
End Time: 21991 5-millisecond ticks
Start Time: 21898 5-millisecond ticks
Program 74. Measures the data transfer rate of the linear buffer.
The printing program has much less effect on the data transfer
rate of the printing program/linear buffer combination than that
experienced with printing program/circular buffer combinations.
Program PRG_7IP.TOS or TTP must be executed prior to the
execution of this program.
; Program Name: PRG_7NP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TPP extension.
; Function:
; Reads the file designated by the parameter line input. The address
; of the buffer passed to the GEMDOS $3F function is that of the parallel
; interface interrupt handler installed by PRG_7IP.TOS or TTP. This program
; is a version of PRG_7JP. This version measures the data transfer rate for
; the PRG_7IP/PRG_7JP combination. The name of this program's output file is
; PRG_7IP.DAT.
; This program initiates the interrupt controlled printing process by
; sending a NULL character to the printer.
; Execution Instructions:
; PRG_7IP.TOS or TTP must install the parallel interface interrupt
; handler before this program is executed. Execute this program from the
; desktop. This program and the file to be printed must reside in the same
; directory.
fetch_pertinent_addresses:
lea -$82(pc), a3 ; Fetch "command line" address.
lea -$80(a3), a1 ; Fetch "basepage" address.
lea program_end, a0 ; Fetch "end of program" address.
lea stack, a7 ; Fetch stack address.
lea dta, a5 ; Fetch dta buffer address.
calculate_program_size:
suba.l a1, a0
return_unused_memory:
pea (a0) ; Push program length.
pea (a1) ; Push basepage address.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; Invoke GEMDOS exception.
lea $C(a7), sp ; Reset stack pointer to top of stack.
set_dta:
pea (a5) ; dta = address of 44 byte buffer.
move.w #$1A, -(sp) ; GEMDOS function = set dta.
trap #1
addq.l #6, sp
get_drive:
move.w #$19, -(sp) ; See Internals page 116, but note the
trap #1 ; errors there.
addq.l #2, sp ; Note first error at this instruction.
add.w #$41, d0 ; Note 2nd error at this instruction.
lea path, a6 ; Fetch address of path array.
move.b d0, (a6)+ ; Store drive letter in path array.
move.b #$3A, (a6)+ ; Store a colon in path array.
get_directory_path:
move.w #0, -(sp)
pea (a6) ; Push address of next path array element.
move.w #$47, -(sp) ; See Internals page 135.
trap #1
addq.l #8, sp
process_command_line:
move.b (a3)+, d0 ; Fetch command line character count.
ext.w d0 ; Extend to word for next instruction.
move.b #0, 0(a3,d0.w) ; Store a null at end of command line input.
lea file_name, a0 ; Fetch address of variable.
move.l a3, (a0) ; Store address of file name for output data.
search_for_file:
move.w #0, -(sp) ; Attribute = normal access.
pea (a3) ; Push address of file name.
move.w #$4E, -(sp) ; GEMDOS function = search first.
trap #1
addq.l #8, sp
tst d0
bne terminate ; Terminate if file not found.
process_interrupt_handler_variables:
pea fetch_interrupt_handler_address
move.w #$26, -(sp) ; Execute a routine in supervisor mode.
trap #14
addq.l #6, sp
lea handler_address, a4 ; Fetch handler address.
movea.l (a4), a4
adda.l #$16C, a4 ; Add offset to variable "filesize".
move.l $1A(a5), d6 ; Fetch file size.
move.l d6, (a4) ; Store file size in handler variable.
adda.l #$188, a4 ; Add offset to buffer variable.
open_file: ; Function returns file handle in D0.
move.w #0, -(a7) ; Open as read only.
pea (a3) ; Push address of file name.
move.w #$3D, -(a7) ; See Internals page 127.
trap #1
addq.l #8, a7
move.w d0, d3 ; Store file handle in D3.
trap #3 ; Value of system clock returned in D0.
move.l d0, d4 ; Store time in D4.
read_file:
pea (a4) ; Push buffer address
move.l $1A(a5), -(sp) ; Number of bytes to read.
move.w d3, -(sp) ; File's handle number.
move.w #$3F, -(sp) ; GEMDOS function = read.
trap #1
lea $C(sp), sp ; Reposition stack pointer.
close_file:
move.w d3, -(sp) ; Push file handle.
move.w #$3E, -(sp) ; See Internals page 128.
trap #1
addq #4, sp
transfer_rate_calculations:
trap #3 ; Fetch current time.
move.l d0, d5 ; Store end time for data file.
sub.l d4, d0 ; Subtract start time from end time.
convert_time_to_milliseconds: ; Multiply by 5.
move.l d0, d2 ; Save copy to add.
asl.l #2, d2 ; Multiply by 4.
add.l d0, d2 ; To complete multiplication by 5.
compute_transfer_rate:
move.l d6, d0 ; Fetch file size from D6.
divu d2, d0 ; Divide size by time in milliseconds.
copy_to_d1: ; Will extract remainder from D1.
move.l d0, d1 ; Quotient is in lower word of D0.
mulu #1000, d0 ; Multiply quotient by 1000.
clr.w d1 ; Get rid of quotient.
swap d1 ; Remainder is now in lower word of D1.
mulu #1000, d1 ; Multiply remainder by 1000.
divu d2, d1 ; Divide remainder by time in milliseconds.
swap d1 ; Put remainder in lower word.
clr.w d1 ; Get rid of remainder.
swap d1 ; Get quotient back in lower word.
add.l d0, d1 ; Transfer rate is now in D1.
; Note: The order in which things are done in the calculation algorithm
; is critical. The remainder obtained from the first division must
; be manipulated just right. One other point is that the quotient
; will be zero whenever the time in milliseconds is a value greater
; than the size of the file.
; I have verified the accuracy of the above algorithm, but I have
; decided to print the number of 5 milliseconds clock ticks obtained
; for both the start and end times. This will permit the transfer
; rate to be calculated manually. Remember that those clock ticks
; must be multiplied by 5 to convert them to milliseconds. And, of
; course, the number of milliseconds must be divided by 1000 to convert
; them to seonds; or one can divide the file size by the number of
; milliseconds and multiply the quotient by 1000.
; The equation being used in this algorithm is:
; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
; X 1000 (msec/second).
trap #4 ; Convert to ASCII decimal.
lea transfer_rate, a1
move.l a0, (a1) ; Store address of decimal string.
; NOTE: The variable 'transfer_rate' now contains the address of the
; ASCII decimal string that is the transfer rate. Trap #4 can't
; be invoked until that value is used by the program, otherwise
; the value will be corrupted.
create_output_file:
move.w #0, -(sp) ; File attribute = read/write.
pea output_file ; Push address of output file's name.
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
lea output_file_handle, a0
move.w d0, (a0)
redirect_output_bound_for_screen:
; NOTE: If the output file's handle is exchanged with the video screen's
; handle, then the printline function = GEMDOS $9 can be used to
; write to the file.
redirect_output: ; Exchange file handle with screen's handle.
move.w d0, -(sp) ; This is the disk file's handle.
move.w #1, -(sp) ; This is the video screen's handle.
move.w #$46, -(sp) ; Function = f_force = GEMDOS $46.
trap #1
addq.l #6, sp
print_data_report:
lea heading, a0 ; Print data file heading.
bsr print_string
lea header_1, a0 ; Transfer Rate header.
bsr print_string
move.l transfer_rate, a0 ; Transfer Rate. Fetch from trap #4
bsr print_string ; location. Quick before it goes away.
lea header_2, a0 ; Transfer Rate Units.
bsr print_string
lea header_3, a0 ; File name header.
bsr print_string
lea file_name, a1 ; File name.
movea.l (a1), a0 ; File's name is stored in command line.
bsr print_string
lea header_4, a0 ; File length header.
bsr print_string
move.l d6, d1 ; Fetch file size from D6.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_5, a0 ; File size units.
bsr print_string
lea header_9, a0 ; Directory path.
bsr print_string
lea path, a0
bsr print_string
lea header_7, a0 ; End Time header.
bsr print_string
move.l d5, d1 ; Fetch end time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8, a0 ; Time units = 5-millisecond ticks.
bsr print_string
lea header_6, a0 ; Start Time header.
bsr print_string
move.l d4, d1 ; Fetch start time.
trap #4 ; Convert to ASCII decimal.
bsr print_string
lea header_8, a0 ; Time units = 5-millisecond ticks.
bsr print_string
close_output_file:
move.w output_file_handle, -(sp)
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
initiate_interrupt_process: ; Execute in supervisor mode.
pea send_null
move.w #$26, -(sp)
trap #14
addq.l #6, sp
terminate:
move.w #0, -(sp)
trap #1
fetch_interrupt_handler_address:
lea handler_address, a0
move.l $100, (a0) ; Interrupt handler address is stored at
rts ; location $100.
send_null: ; The printer will drop the logic level of
moveq.l #0, d0 ; pin 11 from high to low after receiving this
; character, even though nothing will be
; printed.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
rts
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
data
heading: dc.b $D,$A,'PRG_7NP.TOS Execution Results',$D,$A,$D,$A
dc.b ' Print Buffer: PRG_7IP',0
header_1: dc.b $D,$A,' Transfer Rate: ',0
header_2: dc.b ' bytes/second',$D,$A,0
header_3: dc.b ' File Printed: ',0
header_4: dc.b $D,$A,' File Length: ',0
header_5: dc.b ' bytes',$D,$A,0
header_9 dc.b $D,$A,' Path: ',0
header_7: dc.b $D,$A,$D,$A,' End Time: ',0
header_6: dc.b ' Start Time: ',0
header_8: dc.b ' 5-millisecond ticks',$A,$D,0
output_file: dc.b 'PRG_7IP.DAT',0
bss
output_file_handle: ds.w 1
transfer_rate: ds.l 1
dta: ds.l 11
file_name: ds.l 1 ; Address of command line (input file name).
handler_address: ds.l 1
path: ds.b 120
ds.l 96 ; Program stack.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0
end
PRG_7NP.TOS Execution Results
Print Buffer: PRG_7IP
Transfer Rate: 434133 bytes/second
File Printed: PRG_7NP.S
File Length: 13024 bytes
Path: P: ; Ram disk.
End Time: 333018 5-millisecond ticks
Start Time: 333012 5-millisecond ticks
PRG_7NP.TOS Execution Results
Print Buffer: PRG_7IP
Transfer Rate: 41346 bytes/second
File Printed: PRG_7NP.S
File Length: 13024 bytes
Path: E:\PRG_7 ; Hard disk.
End Time: 462620 5-millisecond ticks
Start Time: 462557 5-millisecond ticks
PRG_7NP.TOS Execution Results
Print Buffer: PRG_7IP
Transfer Rate: 9336 bytes/second
File Printed: PRG_7NP.S
File Length: 13024 bytes
Path: A:\PRG_7
End Time: 426667 5-millisecond ticks
Start Time: 426388 5-millisecond ticks
The final program in this chapter has been designed to help
you to find out a few facts about your printer's buffers. It
should permit you to determine the length of your printer's line
buffer and some details of its operation. It's best that you
read the program completely so that you can understand just what
it attempts to accomplish. Program 75 is short and uncluttered,
so you should be able to alter it to conform to any particular
experiment you might want to attempt.
Program 75. A program to let you experiment with your
printer's line buffer.
; Program Name: PRG_7OP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS suffix.
; Function:
; Provides direct output via the Parallel Port Interface. This program
; is to be used to illustrate the operation of a printer's line buffer. First
; it prints a short string which is terminated with a carriage return/line
; feed. Then it prints a character string that is greater than 200 characters
; long so that the length of the line buffer can be determined. Finally, it
; prints a short string with no terminating carriage return/line feed.
mainline:
; NOTE: Must enter supervisor mode in order to access MFP and PSG addresses.
enter_supervisor_mode:
move.l #0, -(sp)
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; Supervisor mode is active after TRAP.
addq.l #6, sp ; D0 = SSP.
print_string_1:
lea string_1, a3 ; Fetch address of string.
bsr print_character ; Print the string.
print_strin_2:
lea string_2, a3
bsr print_character
wait_for_return_key_press:
move.w #8, -(sp)
trap #1
addq.l #2, sp
print_string_3:
lea string_3, a3
bsr print_character
enter_user_mode:
move.l d0, -(sp) ; D0 contains SSP.
move.w #$20, -(sp) ; Function = SUPER.
trap #1 ; User mode is active after TRAP.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
print_character:
move.b (a3)+, d1
beq return
printer_ready_test:
btst #0, $FFFA01 ; Check logic level of pin 11.
bne.s printer_ready_test ; Loop until printer is ready to receive.
lea $FF8800, a2 ; Fetch address of PSG data bus.
move.b #$F, (a2) ; Latch address of port B.
move.b d1, 2(a2) ; Write character to port B.
move.b #$E, (a2) ; Latch address of port A.
move.b (a2), d1 ; Read content of port A.
andi.b #$DF, d1 ; Reset bit 5 of d1 to zero.
move.b d1, 2(a2) ; Reset bit 5 of port A. Strobe low.
ori.b #$20, d1 ; Set bit 5 of d1.
move.b d1, 2(a2) ; Set bit 5 of port A. Strobe high.
bra print_character
return:
rts
data
string_1: dc.b 'This is the short string with cr/lf.',$D,$A,0
string_2: dc.b 'This is the long string which does not terminate '
dc.b 'with a carriage return/line feed. It is greater than two '
dc.b 'hundred characters long, which should be longer than '
dc.b 'the printers line buffer. All but the last line should '
dc.b 'probably print because the line buffer fills; but '
dc.b 'you should probably have to turn the printer off-line, '
dc.b 'then back on the get the final line to print. The ',
dc.b 'program will pause for you to do this.',0
string_3: dc.b 'This string has no cr/lf. The off-line/on-line sequence '
dc.b 'should make it print.',0
align
end
A Note About GEMDOS Function $46
I have used this function in several programs to redirect
data flow via GEMDOS function $9 so that the string of characters
is written to a file instead of to the screen. If you read the
description of this function on page 134 of the Internals book, I
think that you will conclude, as I did, that it should also be
possible to redirect output bound for the printer to a file also.
I know that the reference seems to indicate that GEMDOS function
$46 might be more appropriately used in conjunction with the
Write functions, but you have seen that I use it with the Print
Line function without problem. Anyway, I have not been able to
use function $46 to redirect output from the printer to a file.
I can't conclude that the function doesn't work that way, but I
think that it would be a valuable asset if it did work in the
manner that I would like.
Conclusion
I have always considered the output from a computer to be
its most valuable service. I've never been impressed with what a
computer could do unless it could show me the results quickly and
in forms that permitted me to rapidly assess the output data.
But I've always been amazed by the lack of attention to detail
paid by most reference books and owner's manuals concerning such
output. Let me not be guilty of that in this book.
At the time that I am writing this, I am reading the newest
COMPUTE!'s technical reference guide for the Atari ST, TOS, by
Sheldon Leemon. There is sufficient assembly language references
in the book to permit me to heartily recommend it, if you can
afford the purchase. Much of the material in the book is also
covered in the Internals book, but in many areas Leemon does a
better job of presenting the material. I wish that I could as
heartily recommend COMPUTE!'s assembly language programming book,
but I find it to be a feeble attempt. Nevertheless, it is
certainly much better than the Abacus attempt on the same
subject.