home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-10-23 | 223.2 KB | 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.
-
-