home *** CD-ROM | disk | FTP | other *** search
/ Crawly Crypt Collection 1 / crawlyvol1.bin / program / books / 68k_book / arp_doc / chap_09.doc < prev    next >
Text File  |  1985-11-20  |  229KB  |  4,783 lines

  1.         Atari ST Machine Specific Programming In assembly
  2.  
  3. Chapter 9: Interfacing With Peripheral Hardware
  4.  
  5.      I am going to spend most of this chapter discussing printing 
  6. algorithms and print buffers.  Not because these objects alone 
  7. are important enough to warrant the considerable time I shall 
  8. devote to them, but because the topics will create an atmosphere 
  9. in which it will be convenient to discuss other relevant issues.  
  10. These other issues involve the parallel port, in general, and 
  11. that itself is an important resource, yet I will not explore all 
  12. of the ramifications of its use, but I will certainly try to 
  13. eliminate any mystery surrounding such use; certain system 
  14. functions that are useful, as well, in applications which do not 
  15. involve printing, and among these will be at least one which does 
  16. not function as specified; and certain problems that materialize 
  17. during the testing of algorithms--sometimes it is difficult to 
  18. determine just what is being tested.  Yes, the central theme will 
  19. be print buffers, but the asides shall provide opportunities for 
  20. an expansion of knowledge about machine specific programming in 
  21. assembly language on the Atari ST.
  22.      Because the data transfer rates of printing mechanisms are 
  23. usually much slower than that of the other components involved 
  24. with printing, most users prefer to lessen the effect of the 
  25. printing mechanism's speed impact on the rest of the system by 
  26. installing some sort of buffer between the program that is 
  27. controlling the printing and that mechanism.  The buffer is, of 
  28. course, memory, which can be part of that which we call main ram; 
  29. or it can be memory within the printer itself; or it can be the 
  30. memory in a device that is designed to be installed between the 
  31. computer and the printer.  In all cases, the memory that is used 
  32. is hardware; but when the memory comprising the buffer is a 
  33. portion of computer ram that is allocated by a program executed 
  34. within the computer, the term software buffer is often applied.  
  35. When the behavior of the algorithm that stores data in the buffer 
  36. permits us to visualize the buffer's structure as if it were 
  37. circular, as opposed to linear, the term spooler is often 
  38. applied.  In many references the word queue is used in place of 
  39. the word buffer.
  40.      There is a description of a print spooler on pages 68 
  41. through 70 of Atari ST Tricks & Tips, a Data Becker book, 
  42. published by Abacus Software.  Following that discussion is an 
  43. assembly language program.  If you are able to read the 
  44. discussion in that book, it will provide you with additional 
  45. descriptive information concerning print spoolers with which to 
  46. complement that which I am going to present here.  However, I 
  47. suggest that you do not try to implement the program given as an 
  48. example in that book; there are much better ones available.  In 
  49. fact, several will be presented shortly.
  50.  
  51. Data Transfer Rates
  52.   
  53.      The term parallel interface port includes all circuits 
  54. involved with data transfer through the port connector that is 
  55. located at the rear of the computer.  When we execute an 
  56. algorithm to send data through the port, the rate at which data 
  57. flows is the port's instantaneous data transfer rate.  I say 
  58. instantaneous transfer rate because the data flow could be 
  59. limited by the algorithm which is sending the data; a different 
  60. algorithm might permit the exchange of data at a different rate.  
  61. In this particular case, there are two data transfer rates 
  62. involved; the explicit instantaneous rate and the implicit 
  63. maximum data transfer rate of the port.  For sure, sending 
  64. algorithms are not going to push valid data through the port at 
  65. rates that exceed its maximum data transfer rate.  Other devices 
  66. which could exert pressure on the transfer of data through the 
  67. port are the printer's port, and its internal buffer, if it has 
  68. one; the printing mechanism; an external print buffer connected 
  69. between the printer and the port; and any buffer that has been 
  70. constructed by software within the computer.
  71.      By peripheral hardware, in this chapter, I mean those 
  72. semiconductors that exist on the periphery of the microprocessor.  
  73. These semiconductors interface the MC68000 to other components of 
  74. the ST system and to devices connected to the computer.  Among 
  75. others, this group of integrated circuits includes the MC68901 
  76. Multi-Function Peripheral (MFP) and the AY-3-8910 (aka YM-2149, 
  77. according to the Abacus Internals book and their Graphics & Sound 
  78. book) Programmable Sound Generator (PSG).  These are the two 
  79. peripheral hardware components I will discuss in this chapter.
  80.      I will concentrate my discussions of the MFP and the PSG on 
  81. those portions of the devices which are involved with the 
  82. parallel interface port.  You can obtain a complete reference 
  83. guide for the MFP from a local Motorola office, from a 
  84. distributor or from Motorola Semiconductors, 3501 Ed Bluestein 
  85. Blvd., Austin, Texas 78721.  You can find a reference guide for 
  86. the PSG (listed as AY-3-8910A) in the ARCHER Semiconductor 
  87. Reference Guide, available from Radio Shack stores.  
  88. Unfortunately, this guide does not present any information 
  89. concerning the use of the two PSG I/O ports.  Naturally, these 
  90. are the parts of the integrated circuit with which I am most 
  91. interested.      In addition, these devices are scantily 
  92. discussed in the Internals book.  I suggest that you try to 
  93. obtain General Instrument's AY-3-8910/8912 Programmable Sound 
  94. Generator Data Manual.  If you are interested in the sound 
  95. capabilities of the PSG, you can refer to appropriate books and 
  96. magazine articles that address sound on the ST.  The other 
  97. peripheral components, such as the disk drive circuits and the 
  98. RS-232 circuits, are also discussed in various references.  You 
  99. can begin with the material in the Internals book and progress to 
  100. other references as you desire.
  101.      The proper system configuration for the material in this 
  102. chapter consists of the ST system and a printer connected to the 
  103. parallel port.  Initially, there should be nothing but a cable 
  104. between the ST port and the printer port; that is, eliminate all 
  105. dongles (A dongle is a hardware device that is designed to be 
  106. installed in the parallel interface port, but which has nothing 
  107. to do with printing.) and hardware print buffers.  If your 
  108. printer contains an internal buffer that is greater that one line 
  109. in length, and if this buffer can be disabled, you will be able 
  110. to gain from the experience if you do disable it until you are 
  111. finished with the material in this chapter.
  112.  
  113. The Multi-Function Peripheral
  114.   
  115.      The MFP is a very complicated device.  I am going to discuss 
  116. only the components of this chip that I must in order to provide 
  117. a lucid presentation of its interaction with the ST's parallel 
  118. interface port.  The MFP has 24 directly addressable registers: 
  119. of these, as far as this discussion is concerned, we are 
  120. interested in the General Purpose I/O Register (GPIR)--for some 
  121. reason Motorola uses the mnemonic GPIP for this register--as you 
  122. can see, I use GPIR; the Active Edge Register (AER); the Data 
  123. Direction Register (DDR); the Interrupt In-Service Register B 
  124. (ISRB) and the Vector Register (VR).  In addition, we will be 
  125. interested in bit 0 of the 8-bit General Purpose I/O Interrupt 
  126. Port (GPIP).
  127.      The MFP registers are discussed on pages 32 through 40 of 
  128. the Internals book.  The memory addresses for the registers are 
  129. listed on pages 60 and 61.  Access to these locations is 
  130. permitted only while the processor is in supervisor mode.  You 
  131. can enter the supervisor mode from the AssemPro debugger simply 
  132. by clicking on the S status register bit.  The ST's interrupt 
  133. structure is discussed on pages 240 through 244.  The precise 
  134. bits of knowledge that must be extracted from the references in 
  135. the Internals book, from the Motorola MFP manual (pages 3 and 4) 
  136. and from explorations within the AssemPro debugger are these:
  137.  
  138.      1. The contents of the VB register can be read at 
  139.         location $FFFA17.  Those contents are $48 
  140.         (binary 01001000).  Notice that the 3rd bit, 
  141.         called the S bit is set.  Because this 
  142.         particular bit is set, the MFP operates in the 
  143.         sosftware end-of-interrupt mode.  When the MFP 
  144.         operates in this mode, interrupt handlers for 
  145.         MFP generated interrupts must clear the 
  146.         appropriate bit in the appropriate in-service 
  147.         register at the conclusion of the handler 
  148.         algorithm.
  149.         
  150.      2. For pin 11 of the parallel interface port the 
  151.         in-service bit is bit 0 of ISRB.  The address 
  152.         of this register is $FFFA11.  An instruction 
  153.         that will clear the appropriate bit is:
  154.  
  155.                    bclr #0, $FFFA11.
  156.                   
  157.      3. The contents of the AER can be read at $FFFA03.  
  158.         The byte of data there is $4 (binary 00000100).  
  159.         The state of bit 0 determines whether an 
  160.         interrupt will be generated by a one-to-zero 
  161.         transition on pin 11, or by a zero-to-one 
  162.         transition.  Because the state of pin 0 is 0, 
  163.         interrupts will be generated by a one-to-zero 
  164.         transition.
  165.         
  166.      4. The contents of the DDR can be read at $FFFA05.  
  167.         The byte of data there is $0 (binary 00000000).  
  168.         The state of each bit determines whether the 
  169.         corresponding bit of the GPIP is to function as 
  170.         an input pin or an output pin.  When the logic 
  171.         level of a bit in the DDR is 0, the associated 
  172.         GPIP pin functions as an input.  Of interest, 
  173.         in this particular case, is the fact that bit 0 
  174.         is programmed to be an input pin.  And this is 
  175.         the bit to which pin 11 of the parallel 
  176.         interface is connected.
  177.         
  178.      5. The contents of the GPIR can be read at 
  179.         $FFFA01.  The data stored in the GPIR is 
  180.         precisely that which is present at the GPIP; 
  181.         therefore, we can determine the logic state of 
  182.         pin 11 any time we choose to do so, by 
  183.         observing the bit 0 state of the data byte 
  184.         stored at $FFFA01.
  185.   
  186.      When the printer is off; that is, no power applied, the 
  187. logic state of pin 11 is high.  When the printer is on; that is, 
  188. power is applied, and on-line (there is a difference) the logic 
  189. state is low.  When the printer is on (power applied) but off-
  190. line, I can't tell you the state for your particular printer.  I 
  191. can tell you that my Star NX-10 keeps the line low when the 
  192. printer is initially put off-line, which is a little silly 
  193. because the printer cannot accept data when it is off-line.  
  194. However, if the computer attempts to send a byte of data through 
  195. the interface, the printer forces pin 11 high.  On page 218 of 
  196. the printer manual is a statement which indicates that pin 11 is 
  197. high when the printer is off-line, but the restriction mentioned 
  198. above is not specified in detail.  Two other conditions force the 
  199. logic state of pin 11 high; a full buffer and lack of paper.
  200.      In summary, bit 0 of the MFP GPIP is programmed by the 
  201. operating system to be an input; as such it is connected to pin 
  202. 11 of the parallel interface port.  The logic state of this pin 
  203. is controlled by the printer connected to the port.  If the logic 
  204. state is low the computer assumes that the printer is ready to 
  205. receive data; if the logic state is high the computer assumes 
  206. that the printer cannot receive data.
  207.      We can read the byte of data stored at $FFFA01 almost as 
  208. easily as we can read data at any other location; almost as 
  209. easily because the data at $FFFA01 can't be read unless the 
  210. processor is in supervisor mode--remember the second MC68000 
  211. flaw.  Let us observe the data stored at that location, now.  In 
  212. the AssemPro debugger, click on the Status Register S-bit to 
  213. toggle the processor into the supervisor state.  In the register 
  214. field, double click on one of the registers, say A4, and, in the 
  215. dialog box which appears, select .B, then clear the dialog line 
  216. by pressing the Esc key.  Finally, type $FFFA01 on that line and 
  217. press the Return key or click on the OK box.  In place of
  218.  
  219.                  L:A4       =        0
  220.   
  221. you should see
  222.   
  223.                  B:$FFFA01  =      $77
  224.   
  225. if the printer is off, but
  226.   
  227.                  B:$FFFA01  =      $76
  228.   
  229. if the printer is on and ready to receive data.  If you see a 
  230. value other than $76 or $77, it will be because one or more of 
  231. the GPIR bit values in your system differ from those of mine.  
  232. Refer to page 61 of the Internals book for a list of the GPIP 
  233. bits and the use applied to each by the ST.  For example, bit 7 
  234. is used to detect a monochrome monitor.  I have a monochrome 
  235. monitor and that bit is 0 in my GPIR.  I deduce that you will see 
  236. that bit as a 1 if you have a color monitor; other things being 
  237. equal, you would probably see $F6 or $F7 as the contents of 
  238. $FFFA01 if you have a color monitor.
  239.      If the printer was off, turn it on now; if it was on, turn 
  240. it off.  The contents displayed for $FFFA01 will not change until 
  241. you do something to cause the register field portion of the 
  242. debugger screen to be rewritten.  You can force that simply by 
  243. double clicking on the Show register button just beneath the 
  244. register field.  Using this method, you can determine the logic 
  245. state of pin 11 for any printer state.  Remember that bit 0 of 
  246. the data at $FFFA01 indicates the state of pin 11; hence, the 
  247. relevant data nibble is the least significant of the two; that 
  248. is, the second digit will be even if the printer is on, but it 
  249. will be odd when the printer is off.
  250.      Program 60 has been designed to determine printer status via 
  251. the invocation of BIOS function #8.  See pages 161 and 154 of the 
  252. Internals book.  When the program is executed from the desktop, 
  253. the ready condition of the printer is printed on the video 
  254. screen.  Press the Return key to terminate the program.  Program 
  255. 61 determines printer status via direct communication with the 
  256. MFP GPIR.  You should execute these programs for each of your 
  257. printer states.
  258.  
  259. Program 60. Using BIOS Function #8 to Determine Printer 
  260. Status.
  261.  
  262.  ; Program Name: PRG_7AP.S
  263.  ;      Version: 1.002
  264.  
  265.  ; Assembly Instruction:
  266.  
  267.  ;     Assemble in PC-relative mode; save program with .TOS suffix.
  268.  
  269.  ; Execution Instructions:
  270.  
  271.  ;     Execute from the desktop, once for each relevant printer state.
  272.  
  273.  ; Function:
  274.  
  275.  ;     Invokes BIOS function #8, bcostat, to determine the logic state of
  276.  ; pin 11 of the parallel interface port.  See pages 161 and 154 of the
  277.  ; Internals book.  When this function is invoked, the value -1 is returned
  278.  ; in data register D0 if the logic state of pin 11 is low; the value 0 is
  279.  ; returned if the logic state is high.
  280.  
  281. determine_printer_status:
  282.  move.w  #0, -(sp)              ; 0 means pin 11 of parallel interface.
  283.  move.w  #8, -(sp)              ; Function = BIOS bcostat.
  284.  trap    #13                    ; Value returned in D0 indicates logic
  285.  addq.l  #4, sp                 ; state of pin 11 of parallel interface.
  286.  btst    #0, d0                 ; If bit 0 = 0, logic state is high,
  287.  beq.s   printer_not_ready      ; else logic state is low.
  288.  lea     ready, a0
  289.  bra.s   print_string
  290. printer_not_ready:
  291.  lea     not_ready, a0
  292. print_string:
  293.  pea        (a0)
  294.  move.w     #9, -(sp)
  295.  trap       #1
  296.  addq.l     #6, sp
  297. wait_for_keypress:
  298.  move.w    #8, -(sp)
  299.  trap      #1
  300.  addq.l    #2, sp
  301. terminate:
  302.  move.w     #0, -(sp)           ; Function = GEMDOS p_term_old.
  303.  trap       #1                 
  304.  
  305.  data
  306. ready:     dc.b 'Printer is ready.',$D,$A,0
  307. not_ready: dc.b 'Printer is not ready.',$D,$A,0
  308.  align
  309.  end
  310.  
  311.  
  312. Program 61. Determining Printer Status via direct 
  313. communication with the MFP GPIR.
  314.  
  315.  ; Program Name: PRG_7BP.S
  316.  ;      Version: 1.003    
  317.  
  318.  ; Assembly Instructions:
  319.  
  320.  ;      Assemble in PC-relative mode and save with a TOS suffix.
  321.  
  322.  ; Execution Instructions:
  323.  
  324.  ;     Execute from the desktop, once for each relevant printer state.
  325.  
  326.  ; Function:
  327.  
  328.  ;     Determines the logic state of pin 11 of the parallel interface port by
  329.  ; testing bit 0 of the data stored at address $FFFA01, which is the
  330.  ; address of the MFP's General Purpose I/O register.  If the state of this
  331.  ; bit is zero (low), then the printer is ready to receive data; if the state
  332.  ; of the bit is one (high), then the printer is not ready to receive data.
  333.  
  334.  ;     Remember: if your printer keeps the logic level of pin 11 low when
  335.  ;               it is off-line, then the test will erroneously indicate that
  336.  ;               the printer is ready to receive data when the printer is in
  337.  ;               that state.
  338.  
  339. mainline:
  340.  
  341.  ; NOTE: Must enter supervisor mode in order to access the MFP's addresses.
  342.  
  343. enter_supervisor_mode:
  344.  move.l    #0, -(sp)
  345.  move.w    #$20, -(sp)          ; Function = SUPER.
  346.  trap      #1                   ; Supervisor mode is active after TRAP.
  347.  addq.l    #6, sp               ; D0 = SSP.
  348.  move.l    d0, d3               ; Preserve SSP in D3.
  349.  
  350.  btst      #0, $FFFA01          ; $FFFA01 is address of MFP GPIR.
  351.  beq.s     printer_ready        ; Branch if logic state of pin 11 is low.
  352.  lea       not_ready, a0        ; Print string if logic state is high.
  353.  bra.s     print_string
  354. printer_ready:
  355.  lea       ready, a0
  356. print_string:
  357.  pea        (a0)
  358.  move.w     #9, -(sp)
  359.  trap       #1
  360.  addq.l     #6, sp
  361. wait_for_keypress:
  362.  move.w    #8, -(sp)
  363.  trap      #1
  364.  addq.l    #2, sp
  365.  
  366. enter_user_mode:
  367.  move.l    d3, -(sp)            ; D3 contains SSP.
  368.  move.w    #$20, -(sp)          ; Function = SUPER.
  369.  trap      #1                   ; User mode is active after TRAP.
  370.  addq.l    #6, sp
  371.  
  372. terminate:
  373.  move.w    #0, -(sp)            ; Function = GEMDOS p_term_old.
  374.  trap      #1
  375.  
  376.  data
  377. ready:     dc.b 'Printer is ready.',$D,$A,0
  378. not_ready: dc.b 'Printer is not ready.',$D,$A,0
  379.  align
  380.  end
  381.  
  382.  
  383.      Using the AssemPro debugger to monitor address $FFFA01, or 
  384. by executing programs 60 and 61, we are able to determine the 
  385. static logic level of pin 11 of the parallel interface port.  
  386. When data is being transferred through the port, however, the 
  387. logic level of pin 11 is dynamic; that is, constantly changing to 
  388. reflect the printer's ability to receive a data byte from the 
  389. computer.  The printer forces pin 11 high shortly after pin 1 of 
  390. the parallel interface is forced low by the computer.  The 
  391. computer forces pin 1 low just before it sends a data byte to the 
  392. printer, then it forces pin 1 high after the data byte has been 
  393. sent.  Shortly after pin 1 has been forced high, the printer 
  394. forces pin 11 low again.  During data transfers, therefore, the 
  395. logic level of pin 11 is represented by a series of pulses, 
  396. instead of a steady state level.
  397.      When the operating system is used to effect data transfers 
  398. to the printer, its functions take care of pin 1's logic levels, 
  399. and these functions monitor the state of pin 11.  A programmer 
  400. who restricts his repertoire to operating system functions need 
  401. not be concerned with such detail.  It is only when one desires 
  402. to bypass the operating system that intimate knowledge of the MFP 
  403. and the PSG becomes important.  Of course, we want to bypass the 
  404. operating system; that's why we are writing and reading this 
  405. book.
  406.  
  407. The Programmable Sound Generator
  408.   
  409.      I begin with the information given on pages 59 and 60 of the 
  410. Internals book, under the heading $FF8800 Sound Chip.  There, it 
  411. is stated that the PSG has 16 registers, none of which is 
  412. directly addressed.  This information does not agree with the 
  413. specification guide in the Radio Shack book; the information in 
  414. the Radio Shack book is incorrect.  I dwell on this point because 
  415. a device such as the PSG will often permit direct access to its 
  416. registers via memory-mapped I/O.  This device, however, does not; 
  417. yet the PSG is connected in the ST so that it behaves as if data 
  418. exchanges with the parallel interface are partially accomplished 
  419. via directly addressable registers.
  420.      In the Internals book, reference is made, on pages 59 and 
  421. 60, to a select register.  Yet, there is no mention of this 
  422. register on pages 48 through 54, where the PSG and all of its 
  423. registers is discussed.  A select register is not mentioned in 
  424. the Radio Shack description of the PSG.  However, PSG I/O port 
  425. selection in the ST is accomplished via a memory location which 
  426. behaves as if it is a select register.  The term select register 
  427. is used to describe a register through which device parameters 
  428. are set.  The conclusion to be drawn at this point is that we are 
  429. more concerned with the apparent behavior of the PSG as it 
  430. operates in the ST than we are in the description of the PSG as 
  431. given by reference books or by the manufacturer of the device.
  432.      Register 7 of the PSG is described as a mixer control-- I/O 
  433. Enable register in the Radio Shack book.  The second half of this 
  434. description is the one in which we are interested, because it 
  435. refers to the I/O ports.  It is the state of two bits in that 
  436. register which determine the direction of data flow through the 
  437. two I/O ports.  Bit 6 controls the direction of data flow through 
  438. port A; bit 7 controls the direction of data flow through port B.  
  439. When a port bit in register 7 is 0, the direction of flow must be 
  440. into the PSG; when a port bit is 1, the direction of flow must be 
  441. out of the PSG.  In the ST, both port bits are set to 1 during 
  442. the system initialization sequence, therefore, data flow through 
  443. both ports must be out of the PSG, unless the bits are altered by 
  444. an appplication.
  445.      Since that is the configuration we need, it would seem that 
  446. nothing else about this subject need be said.  However, because 
  447. register 7 is programmable, and because it may come to past that 
  448. one of your applications (or one that you purchase) alters the 
  449. bits in register 7, it is reasonable that I give the following 
  450. admonition: if you alter specific bits in register 7, then you 
  451. must do so in a manner that alters only those you intend.  In 
  452. order to assure this, you make changes in the register with a 
  453. mask.  You will see how this is done with the OR and AND 
  454. instructions in an example to be presented shortly.
  455.      PSG register 17 (aka port B) is the one through which the 
  456. code bound for the printer actually travels.  Just to confuse 
  457. you, the designers of the chip made the address of this register 
  458. 15, which is hex F.  Register 16 (aka port A) is the one through 
  459. which a signal is sent to inform the printer that the computer 
  460. wants to send a byte of data.  Again, just to make your life 
  461. miserable, the designers of the chip decided that the address of 
  462. this register should be 14 = $E.
  463.      The data byte bound for the printer is usually stored in 
  464. port B as the second step in a two-step process.  The first step 
  465. involves the placement of the register number on the PSG's 
  466. internal bus located at its so called base address (A base 
  467. address is usually the address of the first memory-mapped 
  468. register of a device.).  Let me say this carefully, because it is 
  469. as confusing as it seems.  Address $FF8800 is the address to 
  470. which we write the number of the PSG register in which we want to 
  471. place data or from which we want to retrieve data, and it is also 
  472. the address from which we do read data.  That is, we select a 
  473. register by storing its address at ram location $FF8800; then, if 
  474. we want to read the data stored in the selected register, we read 
  475. location $FF8800.  If we want to write to the selected register, 
  476. on the other hand, ram location $FF8802 is the address to which 
  477. we write the data that is to be stored in a previously selected 
  478. register.
  479.      This information does not agree with that given in any 
  480. specification guide that I have seen, and I have seen them all, 
  481. but I have verified that these are the only memory locations 
  482. which can be used to pass the data through registers 16 and 17 of 
  483. the PSG.  Note that the information in the Internals book is not 
  484. entirely correct.  There it states that reading address $FF8800 
  485. gives you the last PSG register used.  The truth is this: if you 
  486. simply read the content of address $FF8800, perhaps by moving 
  487. that content into a data register, the data read (moved into the 
  488. data register) will be the content (not the register number 
  489. itself) of the last PSG register selected.  However, unless you 
  490. already know which register was the last to be selected, you 
  491. can't know from which register the data is being read.
  492.      The two-step write process mentioned above
  493. requires two move instructions, as follows:
  494.   
  495.      1. move.b  register_address, $FF8800
  496.   
  497.      2. move.b  data, $FF8802
  498.   
  499. where register_address is the address (using any valid addressing 
  500. mode) of the register that is to receive the data which will 
  501. subsequently be placed on the PSG internal bus, in this case 
  502. register 17 at address $F, and data is that data (using any valid 
  503. addressing mode).
  504.      Once the data has been placed in PSG register 17, pin 1 of 
  505. the parallel interface must be pulsed from high to low and back 
  506. to high to effect the data transfer through the parallel 
  507. interface port.  This is also known as strobing pin 1.  Pin 1 of 
  508. the parallel interface is normally held high by the computer.  
  509. When the computer is ready to send data to the printer, it 
  510. strobes pin 1 low for a specific length of time.
  511.      When the printer has received the data, it strobes pin 10 of 
  512. the parallel interface low for a specific length of time.  In 
  513. other words, the printer acknowledges reception of the data.  
  514. Unfortunately, the ST does not provide a method of recognizing 
  515. this acknowledge strobe.  Too bad, it would be of great 
  516. assistance were it available.  To circumvent this inconvenience, 
  517. we are forced to rely on pin 11 of the parallel interface to tell 
  518. us, in a roundabout way, that the printer has received the data.
  519.      Pin 11 of the parallel interface is held high by the printer 
  520. while data transfer is occurring and when the printer is off (and 
  521. probably when it is off-line); in other words, whenever the 
  522. printer cannot accept data.  At all other times, the printer 
  523. holds this pin low.  So, we use this pin as a pseudo ACK 
  524. (acknowledge) pin.  Because of this double duty assigned to pin 
  525. 11, when we check the dynamic status of pin 11 and find that it 
  526. is high, we can't determine the true printer status; we don't 
  527. know whether it is off or simply transferring data.
  528.      Therefore, when we want to send a character to the printer, 
  529. we sometimes program a waiting period in an elapsed time counter, 
  530. or counting loop, and continuously check the status of pin 11 
  531. until its logic level is low, but give up the waiting if the 
  532. predetermined elapsed time expires.  (It is also possible to let 
  533. the printer interrupt the processor and declare that it is ready 
  534. to accept a byte of data.)  If pin 11 has not become low during 
  535. the waiting period, we assume that the printer is turned off, 
  536. then we abandon the data transfer attempt and/or send a warning 
  537. message to the video screen.
  538.      To complete the data transfer through the PSG, then, pin 1 
  539. of the interface must be strobed.  PSG register 16 (aka port A), 
  540. at address $E, provides the access to this pin.  However, as 
  541. indicated in the Internals book, this port is used for 7 other 
  542. functions as well.  Therefore, when we manipulate the port A bit 
  543. connected to pin 1 of the parallel interface, we must leave the 
  544. other bits unaltered.
  545.      Bit 5 of register 16 is the bit we strobe to transfer the 
  546. data from register 17 to the printer.  To preserve the state of 
  547. the other seven bits, we first select register 16 for the 
  548. transaction, and read its content into a data register.  Then we 
  549. place a 0 in bit 5 of the data register by logically ANDING the 
  550. binary value 11011111 (hexadecimal DF) with the content of the 
  551. data register.  Finally, we write the data register content to 
  552. port A.  The instructions are as follows:
  553.  
  554.      1. move.b  #$E, $FF8800 (select register 16)
  555.   
  556.      2. move.b  $FF8800, dn  (read contents)
  557.   
  558.      3. andi.b  #$DF, dn     (combine with mask)
  559.   
  560.      4. move.b  dn, $FF8802  (write combination)
  561.  
  562.    
  563.      These four instructions strobe pin 1 of the printer's 
  564. parallel interface low.  We finish up by strobing the pin high 
  565. with the following instructions, again, preserving the state of 
  566. the other seven bits:
  567.  
  568.      1. ori.b   #$20, dn     (combine with new mask)
  569.   
  570.      2. move.b  dn, $FF8802  (write combination)
  571.   
  572. where, dn, in the above sets of instructions is any data 
  573. register.  When setting up the data register for the high strobe, 
  574. we OR the binary value 00100000 (hexadecimal #$20) with the data 
  575. register's content.  Using the strobing instructions, we generate 
  576. electrical signals in a manner that is analogous to flipping 
  577. switches manually.  Program 62 is an example which illustrates a 
  578. method of programming the PSG to effect printer output without 
  579. the assistance of the ST's operating system functions.  Of 
  580. course, since the addresses by which we access the PSG are 
  581. protected, we must enter supervisor mode in order to gain 
  582. accessibility.  We do use system commands for this.
  583.  
  584. Program 62. Direct output via the parallel port.
  585.   
  586.  ; Program Name: PRG_7CP.S
  587.  ;      Version: 1.002    
  588.  
  589.  ; Assembly Instructions:
  590.  
  591.  ;      Assemble in PC-relative mode and save with a TOS suffix.
  592.  
  593.  ; Function:
  594.  
  595.  ;      Provides direct output via the Parallel Port Interface.
  596.  
  597.  
  598.  ; DESCRIPTION
  599.  
  600.  ;    The object of this experiment is to produce a minimal algorithm that
  601.  ;    will send a string of characters to a printer connected to the parallel
  602.  ;    port.
  603.  
  604. mainline:
  605.  
  606.  ; NOTE: Must enter supervisor mode in order to access MFP and PSG addresses.
  607.  
  608. enter_supervisor_mode:
  609.  move.l    #0, -(sp)
  610.  move.w    #$20, -(sp)          ; Function = SUPER.
  611.  trap      #1                   ; Supervisor mode is active after TRAP.
  612.  addq.l    #6, sp               ; D0 = SSP.
  613.  
  614. print_string:
  615.  lea       string, a3           ; Fetch address of string.
  616. print_character:
  617.  move.b    (a3)+, d1
  618.  beq       enter_user_mode
  619. printer_ready_test:
  620.  btst      #0, $FFFA01          ; Check logic level of pin 11.
  621.  bne.s     printer_ready_test   ; Loop until printer is ready to receive.
  622.  
  623.  ; NOTE: If my printer is off-line, the program will remain in the above loop
  624.  ;       after the first character is sent, until the printer is placed back
  625.  ;       on line.  Why?  Because the printer does not permit pin 11 to return
  626.  ;       to zero after it receives that first character.  Therefore, there is
  627.  ;       a kind of delayed reaction to the off-line condition.
  628.  
  629.  lea       $FF8800, a2          ; Fetch address of PSG data bus.
  630.  move.b    #$F, (a2)            ; Latch address of port B.     
  631.  move.b    d1, 2(a2)            ; Write character to port B.
  632.  move.b    #$E, (a2)            ; Latch address of port A.
  633.  move.b    (a2), d1             ; Read content of port A. 
  634.  andi.b    #$DF, d1             ; Reset bit 5 of d1 to zero.
  635.  move.b    d1, 2(a2)            ; Reset bit 5 of port A. Strobe low.
  636.  ori.b     #$20, d1             ; Set bit 5 of d1.
  637.  move.b    d1, 2(a2)            ; Set bit 5 of port A. Strobe high.
  638.  bra       print_character
  639.  
  640. enter_user_mode:
  641.  move.l    d0, -(sp)            ; D0 contains SSP.
  642.  move.w    #$20, -(sp)          ; Function = SUPER.
  643.  trap      #1                   ; User mode is active after TRAP.
  644.  addq.l    #6, sp
  645.  
  646. terminate:
  647.  move.w    #0, -(sp)           ; Function = p_term_old = GEMDOS $0.
  648.  trap      #1                  ; GEMDOS call.
  649.  
  650.  data
  651. string:   dc.b  'This string is written to the printer using a routine',$D,$A
  652.           dc.b  'that communicates directly with the PSG ports.',$D,$A,0
  653.  align
  654.  end
  655.  
  656.  
  657. Sending Files To The Printer
  658.  
  659.      I have covered MFP and PSG basics.  Now, it order to delve a 
  660. little deeper, I must move into the general subject of sending 
  661. files to the printer.  But before I begin, let me simplify some 
  662. of the terminology that I will have to use.  Usually, we are 
  663. forced to utilize cumbersome phrases to designate the transfer of 
  664. data from the computer to a peripheral.  One reason is that we 
  665. use the word printing when referring to the sending of data to 
  666. the video screen, as well as when referring to the sending of 
  667. data to the printer.  I intend to eliminate the need for phrases 
  668. by calling the one screening and the other printing.  I shall use 
  669. the word print to indicate output from the computer to the 
  670. printer; and I shall use the word screen to indicate output from 
  671. the computer to the video screen.
  672.      I will breach the subject slowly, beginning with program 63.  
  673. I try nothing fancy in this program.  It is assembled as a TTP 
  674. program that accepts a file name on the TTP dialog box input 
  675. parameter line and prints, not screens, the file, using simple 
  676. operating system functions.  With this program, I also illustrate 
  677. the kind of sequential process that I use to obtain information 
  678. about the operation of the system.
  679.  
  680. Program 63. Printing a file with a TTP program using 
  681. system functions.
  682.  
  683.  ; Program Name: PRG_7DP.S
  684.  ;      Version: 1.002
  685.  
  686.  ; Assembly Instructions:
  687.  
  688.  ;      Assemble in PC-relative mode and save with a TTP extension.
  689.  
  690.  ; Function:
  691.  
  692.  ;      Accepts a file name on the TTP dialog box input parameter line and
  693.  ; prints that file using operating system functions.
  694.  
  695.  ; Execution Instructions:
  696.  
  697.  ;      Can be executed from the desktop, and, if the command line is fixed
  698.  ; manually as described in the note below, can be executed from the AssemPro
  699.  ; debugger in an uncorrupted system environment; that is, don't try it if
  700.  ; you are using a program such as Switch/Back or Revolver to divide your
  701.  ; system into partitions.  Both this program and the file to be printed must
  702.  ; be in the same directory.
  703.  
  704.  ; Note:
  705.  
  706.  ;      This program was designed and assembled using a MEGA ST2.  While
  707.  ; most of the information concerning the GEMDOS functions probably apply
  708.  ; to all ST's, there may be some minor differences in the MEGA, such as
  709.  ; the size of the buffer that is used to store a file as it is read.
  710.  
  711.  ;      The name of the file used to test the program was GEMDOS_9.S, which
  712.  ; was 859 decimal bytes long.  First executions were from the desktop.  The
  713.  ; name of the file to be printed was typed on the TTP dialog box input
  714.  ; parameter line; once in upper case, once in lower case.  The file was
  715.  ; printed correctly in both instances.
  716.  
  717.  ;      The program was then executed from the AssemPro debugger, using the
  718.  ; following steps:
  719.  
  720.  ;      1. Single click on Execute program button.
  721.  ;      2. On the command line, typed a leading space followed by the name of
  722.  ;         the file, GEMDOS_9.S.
  723.  ;      3. Clicked on OK button in dialog box.
  724.  ;      4. In the File Selector box, double clicked on PRG_7DP.TTP.
  725.  ;      5. On the first line in debugger output window, noted address of
  726.  ;         command line in the instruction: LEA $89A68(PC),A3.
  727.  ;      6. Clicked on from address button and typed 89A68 on dialog line at
  728.  ;         bottom of screen.
  729.  ;      7. Clicked on disassembled button.  Noted file name in output window.
  730.  ;      8. Counted number of characters in file name = 10.
  731.  ;      9. Typed in 0A at address $89A68.
  732.  ;     10. Clicked on disassembled button.
  733.  ;     11. Double clicked on from address button.
  734.  ;     12. Installed a breakpoint at TST.W D0 instruction located just below
  735.  ;         the TRAP #1 instruction, which is located just below the fetch_byte:
  736.  ;         label in the source listing.
  737.  ;     13. Clicked on the Run program button to execute to breakpoint.
  738.  ;     14. Removed the breakpoint.
  739.  ;     15. Clicked on the from address button.
  740.  ;     16. Pressed the Return key to get to address $0.
  741.  ;     17. Clicked on the Search button.
  742.  ;     18. Typed ' ; Program Name', which is part of the first line of file
  743.  ;         GEMDOS_9.S, on the parameter line of the Search for : dialog box.
  744.  ;     19. Clicked on the dialog box's OK button.
  745.  ;     20. Clicked on disassembled button.  Noted that file GEMDOS_9.S was
  746.  ;         in ram; location of first byte at address $79C4.
  747.  ;     21. Clicked on output window's right bar to get to the end of the data
  748.  ;         read into the GEMDOS function $3F buffer in order to observe the
  749.  ;         number of bytes of the file which had just been read in.  The last
  750.  ;         character that could be identified as part of file GEMDOS_9.S was
  751.  ;         the ) character, which is part of the line that reads
  752.  
  753.  ;                              move.w   #9, -(sp)
  754.  
  755.  ;         in the source listing.  This character was located at address $7BC3.
  756.  ;     22. Calculated the number of bytes read as
  757.  
  758.  ;                 $7BC3 - $79C4 + 1 = $200 = 512 decimal bytes.
  759.  
  760.  ;         No output to printer yet, at this point.
  761.  ;     23. Clicked on disassembled.
  762.  ;     24. Double clicked on from address button to get back to program.
  763.  ;     25. Clicked on right window bar to get to the LEA $C(A7),A7 instruction,
  764.  ;         which is located just below the "end_of_file" label in the source
  765.  ;         listing.  Placed a breakpoint there.
  766.  ;     26. Clicked on the Run program button in order to execute program to
  767.  ;         breakpoint.  Output to printer began.  Entire file was printed.
  768.  ;     27. Removed breakpoint.
  769.  ;     28. Clicked on from address button.
  770.  ;     29. Typed address 79C4 on parameter line.
  771.  ;     30. Clicked on disassembled button.
  772.  ;     31. At address $79C4, again observed the first line of file GEMDOS_9.S.
  773.  ;     32. Clicked on the right window bar to get to the end of the file, noting
  774.  ;         that the entire file was in ram.  Last byte of the file proper
  775.  ;         was a linefeed character, $0A, located at address $7D1D.  The first
  776.  ;         byte of this file segment was located at the byte following the last
  777.  ;         byte of the previously read segment; that is, at address $7BC4.
  778.  ;         Following the character located at $7D1D was a NULL byte at location
  779.  ;         $7D1E.
  780.  ;     33. Calculated the number of bytes read as
  781.  
  782.  ;                 $7D1D - $7BC4 + 1 = $15A = 346 decimal bytes.
  783.  
  784.  ;         The sum of the first and second reads = 512 + 346 = 858 bytes, one
  785.  ;         byte less than the size of the file on disk.  Concluded that the NULL
  786.  ;         byte is supposed to be included as part of the file; added 1 to 858
  787.  ;         to obtain the correct size of 859 bytes.
  788.  ;     34. Clicked on the disassembled button.
  789.  ;     35. Clicked on the Run program button.  Program terminated. 
  790.  
  791.  ; Conclusions:
  792.  
  793.  ;      The GEMDOS $3F function reads in 512 bytes each disk access and stores
  794.  ; the data in a buffer that is larger than 512 bytes but probably smaller
  795.  ; than 1 megabyte.  If the file is smaller than the buffer, then the function
  796.  ; will continue to read until the entire file has been read before moving on.
  797.  
  798.  ;      By processing a 16,758 byte file and searching through ram after the
  799.  ; file had been printed, I was able to determine that the GEMDOS function $3F
  800.  ; buffer size is $400 = 1024 decimal.
  801.        
  802. fetch_pertinent_addresses:
  803.  lea        -$82(pc), a3        ; Fetch "command line" address.
  804.  lea        -$80(a3), a1        ; Fetch "basepage" address.
  805.  lea        program_end, a0     ; Fetch "end of program" address.
  806.  lea        stack, a7           ; Fetch stack address.
  807.  lea        buffer, a4          ; Fetch buffer address.
  808. calculate_program_size:
  809.  suba.l     a1, a0             
  810. return_unused_memory:
  811.  pea        (a0)                ; Push program length.
  812.  pea        (a1)                ; Push basepage address.
  813.  move.l     #$4A0000, -(sp)     ; Function = m_shrink = GEMDOS $4A.
  814.  trap       #1                  ; Invoke GEMDOS exception.
  815.  lea        $C(a7), sp          ; Reset stack pointer to top of stack.
  816.  
  817. process_command_line:
  818.  move.b     (a3)+, d0           ; Fetch command line character count.
  819.  ext.w      d0                  ; Extend to word for next instruction.
  820.  move.b     #0, 0(a3,d0.w)      ; Store a null at end of command line input.
  821.  
  822. open_file:                      ; Function returns file handle in D0.
  823.  move.w     #0, -(a7)           ; Open as read only.
  824.  pea        (a3)                ; Push address of file name.
  825.  move.w     #$3D, -(a7)         ; See Internals page 127.
  826.  trap       #1
  827.  addq.l     #8, a7
  828.  move.w     d0, d3              ; Store file handle in D3.
  829.  
  830. read_file:                      ; Function returns 1 in D0 unless end of file is
  831.                                 ; reached or a read error occurs.
  832.  pea        (a4)                ; Push address of buffer.
  833.  move.l     #1, -(a7)           ; Number of bytes to read from
  834.                                 ; GEMDOS $3F's buffer.
  835.  move.w     d3, -(a7)           ; Push file handle.
  836.  move.w     #$3F, -(a7)         ; See Internals page 129.
  837.  
  838.  ; Note: The system will not disturb this stack setup, therefore, it must be
  839.  ;       initialized only once.  Thereafter, a branch need be taken only to the
  840.  ;       following instruction.
  841.  
  842. fetch_byte:
  843.  trap       #1
  844.  tst.w      d0                  ; When NULL at end of file is read,
  845.  ble.s      end_of_file         ; D0 will be 0.
  846. print_byte:
  847.  move.b     (a4), d0            ; Must read buffer one byte each time.
  848.  move.w     d0, -(sp)           ; But must push a word here.
  849.  move.w     #0, -(sp)
  850.  move.w     #3, -(sp)           ; See Internals page 155.
  851.  trap       #13
  852.  addq       #6, sp
  853.  bra.s      fetch_byte
  854. end_of_file:
  855.  lea        $C(sp), sp
  856.  
  857. close_file:
  858.  move.w     d3, -(sp)
  859.  move.w     #$3E, -(sp)         ; See Internals page 128.
  860.  trap       #1
  861.  addq       #4, sp
  862.  
  863. terminate:
  864.  move.w     #0, -(sp)
  865.  trap       #1
  866.  
  867.  data
  868. buffer:       dc.w   $0  ; Character buffer.      
  869.  bss
  870.               ds.l   96  ; Program stack.
  871. stack:        ds.l    0  ; Address of program stack.
  872. program_end:  ds.l    0
  873.  end
  874.  
  875.   
  876.      Now we begin to get fancy.  The next program accomplishes 
  877. the same task as does program 63.  Here, however, the parallel 
  878. interface is accessed directly.  The documentation presented in 
  879. programs 62 and 63 apply to this program also.  Note that I am 
  880. not wasting space on error checking of any kind; nothing should 
  881. go wrong; but if anything does go wrong, please feel free to use 
  882. the switches located on the rear of your computer.
  883.   
  884. Program 64. Printing a file by accessing the parallel 
  885. interface directly.
  886.  
  887.  ; Program Name: PRG_7EP.S
  888.  ;      Version: 1.001
  889.  
  890.  ; Assembly Instructions:
  891.  
  892.  ;      Assemble in PC-relative mode and save with a TOS extension.
  893.  
  894.  ; Function:
  895.  
  896.  ;      Accepts a file name on the TTP dialog box input parameter line and
  897.  ; prints that file, as does PRG_7DP.S, however, this program accesses the
  898.  ; parallel interface directly, as does program PRG_7CP.S.
  899.  
  900.  ; Execution Instructions:
  901.  
  902.  ;      Execute from the desktop.  The name of the file typed on the TTP
  903.  ; dialog parameter line and PRG_7EP.TTP must reside in the same directory.
  904.  ; See documentation in PRG_7CP.S and PRG_7DP.S.
  905.  
  906. fetch_pertinent_addresses:
  907.  lea        -$82(pc), a3        ; Fetch "command line" address.
  908.  lea        -$80(a3), a1        ; Fetch "basepage" address.
  909.  lea        program_end, a0     ; Fetch "end of program" address.
  910.  lea        stack, a7           ; Fetch stack address.
  911.  lea        buffer, a4          ; Fetch buffer address.
  912. calculate_program_size:
  913.  suba.l     a1, a0             
  914. return_unused_memory:
  915.  pea        (a0)                ; Push program length.
  916.  pea        (a1)                ; Push basepage address.
  917.  move.l     #$4A0000, -(sp)     ; Function = m_shrink = GEMDOS $4A.
  918.  trap       #1                  ; Invoke GEMDOS exception.
  919.  lea        $C(a7), sp          ; Reset stack pointer to top of stack.
  920.  
  921. process_command_line:
  922.  move.b     (a3)+, d0           ; Fetch command line character count.
  923.  ext.w      d0                  ; Extend to word for next instruction.
  924.  move.b     #0, 0(a3,d0.w)      ; Store a null at end of command line input.
  925.  
  926. open_file:                      ; Function returns file handle in D0.
  927.  move.w     #0, -(a7)           ; Open as read only.
  928.  pea        (a3)                ; Push address of file name.
  929.  move.w     #$3D, -(a7)         ; See Internals page 127.
  930.  trap       #1
  931.  addq.l     #8, a7
  932.  move.w     d0, d3              ; Store file handle in D3.
  933.  
  934.  ; NOTE: Must enter supervisor mode in order to access MFP and PSG addresses.
  935.  
  936. enter_supervisor_mode:
  937.  move.l    #0, -(sp)
  938.  move.w    #$20, -(sp)          ; Function = SUPER.
  939.  trap      #1                   ; Supervisor mode is active after TRAP.
  940.  addq.l    #6, sp               ; D0 = SSP.
  941.  move.l    d0, d4               ; Preserve SSP in D4.
  942.  
  943. read_file:                   
  944.  pea        (a4)                ; Push address of buffer.
  945.  move.l     #1, -(a7)           ; Read one byte.
  946.  move.w     d3, -(a7)           ; Push file handle.
  947.  move.w     #$3F, -(a7)         ; See Internals page 129.
  948. fetch_byte:
  949.  trap       #1
  950.  tst.w      d0                  ; When NULL at end of file is read,
  951.  ble.s      end_of_file         ; D0 will be 0.
  952. print_byte:
  953.  move.b     (a4), d0            ; Must read buffer one byte each time.
  954. printer_ready_test:
  955.  btst       #0, $FFFA01         ; Check logic level of pin 11.
  956.  bne.s      printer_ready_test  ; Loop until printer is ready to receive.
  957.  lea        $FF8800, a2         ; Fetch address of PSG data bus.
  958.  move.b     #$F, (a2)           ; Latch address of port B.     
  959.  move.b     d0, 2(a2)           ; Write character to port B.
  960.  move.b     #$E, (a2)           ; Latch address of port A.
  961.  move.b     (a2), d0            ; Read content of port A. 
  962.  andi.b     #$DF, d0            ; Reset bit 5 of d1 to zero.
  963.  move.b     d0, 2(a2)           ; Reset bit 5 of port A. Strobe low.
  964.  ori.b      #$20, d0            ; Set bit 5 of d1.
  965.  move.b     d0, 2(a2)           ; Set bit 5 of port A. Strobe high.
  966.  bra.s      fetch_byte
  967. end_of_file:
  968.  lea        $C(sp), sp
  969.  
  970. close_file:
  971.  move.w     d3, -(sp)           ; Push file handle.
  972.  move.w     #$3E, -(sp)         ; See Internals page 128.
  973.  trap       #1
  974.  addq       #4, sp
  975.  
  976. enter_user_mode:
  977.  move.l    d4, -(sp)            ; D4 contains SSP.
  978.  move.w    #$20, -(sp)          ; Function = SUPER.
  979.  trap      #1                   ; User mode is active after TRAP.
  980.  addq.l    #6, sp
  981.  
  982. terminate:
  983.  move.w     #0, -(sp)
  984.  trap       #1
  985.  
  986.  data
  987. buffer:       dc.w   $0  ; Character buffer.      
  988.  bss
  989.               ds.l   96  ; Program stack.
  990. stack:        ds.l    0  ; Address of program stack.
  991. program_end:  ds.l    0
  992.  end
  993.  
  994.  
  995.      The custom trap handler used in program PRG_6KC.S can be 
  996. improved by replacing the algorithm used to print therein with 
  997. that used to print in program 64.  Multiple entries into the trap 
  998. #13 handler is eliminated, and the function required to screen 
  999. can be removed.  Now that I think about it, maybe that particular 
  1000. function could have been eliminated in PRG_6JC and PRG_6KC, in 
  1001. the first place.  No matter, If I were that good, I'd be rich and 
  1002. I would not be doing this, in the second place.  Anyway, program 
  1003. 65 is an improvement; I can see that easily enough.  And it gets 
  1004. us a step closer to the pertinent goals to be reached in this 
  1005. chapter.
  1006.  
  1007. Program 65. An improved version of the program which 
  1008. redirects output bound for the screen to both the 
  1009. printer and the screen.
  1010.  
  1011.  ; Program Name: PRG_7FP.S
  1012.  ;      Version: 1.001
  1013.  
  1014.  ; Assembly Instructions:
  1015.  
  1016.  ;      Assemble in PC-relative mode and save with a TOS extension.
  1017.  
  1018.  ; Function:
  1019.  
  1020.  ;      The function of this program is identical to that of PRG_6JC.S and
  1021.  ; PRG_6KC.S.  This program also redirects output bound for the screen to the
  1022.  ; printer; however, this program accesses the parallel port directly, as does
  1023.  ; program PRG_7EP.S.
  1024.  
  1025.  ; Execution Instructions:
  1026.  
  1027.  ;      Execute from the desktop.  This program expects the printer to be on
  1028.  ; and will loop at the printer ready test until that condition is true.
  1029.  
  1030.  ;      After this program has been executed, execute REG_TST2.TOS to exercise
  1031.  ; it.  Turn the printer on before executing REG_TST2.TOS.
  1032.  
  1033. program_start:                  ; Compute program size and retain result.
  1034.  lea        program_end, a3     ; Fetch program end address.
  1035.  movea.l    4(a7), a4           ; Fetch basepage address.
  1036.  suba.l     a4, a3              ; Program size in A3.
  1037. load_stack_address:
  1038.  lea        stack, a7
  1039.  
  1040. install_custom_trap_13_vector:
  1041.  pea        custom_trap_handler
  1042.  move.w     #$2D, -(sp)          ; Trap 13 vector number.
  1043.  move.w     #5, -(sp)            ; Function = setexec = BIOS $5.
  1044.  trap       #13                  ; Current trap handler vector returned in D0.
  1045.  addq.l     #8, sp
  1046.  lea        preempted_handler_address, a0
  1047.  move.l     d0, (a0)
  1048.  
  1049. relinquish_processor_control:    ; Maintain memory residency.
  1050.  move.w     #0, -(sp)            ; See page 121 of Internals book.
  1051.  move.l     a3, -(sp)            ; Size of memory to remain resident.
  1052.  move.w     #$31, -(sp)          ; Function = ptermres = GEMDOS $31.
  1053.  trap       #1
  1054.  
  1055. custom_trap_handler:
  1056.  move.l     usp, a0              ; Load address of current top of user stack.
  1057.  
  1058. get_processor_status:
  1059.  btst       #5, (sp)             ; User mode test.
  1060.  beq.s      was_user_mode        ; No adjustment is necessary.
  1061.  movea.l    sp, a0               ; Load current top of supervisor stack.
  1062.  addq.l     #6, a0               ; Adjust SSP for user mode type data access.
  1063.  
  1064. was_supervisor_mode:
  1065. was_user_mode:                   ; Processing for either mode follows.
  1066.  cmpi.w     #3, (a0)             ; Writing a character to a device?
  1067.  bne        not_bconout_call
  1068.  cmpi.w     #2, 2(a0)            ; Is device the screen?
  1069.  bne        not_screen
  1070.  
  1071. esc_sequence_test:
  1072.  lea        esc_sequence_flag, a1
  1073.  tst.b      (a1)
  1074.  bne.s      reset_esc_sequence_flag
  1075.  cmpi.w     #$1B, 4(a0)           
  1076.  bne.s      not_esc_sequence
  1077.  move.b     #1, (a1)
  1078.  bra.s      use_preempted_handler
  1079. reset_esc_sequence_flag:
  1080.  move.b     #0, (a1)
  1081. use_preempted_handler:
  1082.  movea.l    preempted_handler_address, a0
  1083.  jmp        (a0)                 ; JUMP TO PREEMPTED TRAP #13 HANDLER.
  1084.  
  1085. not_esc_sequence:
  1086.  move.w     4(a0), d0            ; Store character for printer.
  1087. ascii_code_test:                 ; Filter out undesirable codes.
  1088.  cmpi.w     #$1B, d0
  1089.  bgt.s      printer_ready_test
  1090.  cmpi.w     #$A, d0
  1091.  beq.s      printer_ready_test
  1092.  cmpi.w     #$D, d0
  1093.  bne.s      undesirable_ascii
  1094. printer_ready_test:
  1095.  btst       #0, $FFFA01          ; Check logic level of pin 11.
  1096.  bne.s      printer_ready_test   ; Loop until printer is ready to receive.
  1097.  lea        $FF8800, a0          ; Fetch address of PSG data bus.
  1098.  move.b     #$F, (a0)            ; Latch address of port B.     
  1099.  move.b     d0, 2(a0)            ; Write character to port B.
  1100.  move.b     #$E, (a0)            ; Latch address of port A.
  1101.  move.b     (a0), d0             ; Read content of port A. 
  1102.  andi.b     #$DF, d0             ; Reset bit 5 of d1 to zero.
  1103.  move.b     d0, 2(a0)            ; Reset bit 5 of port A. Strobe low.
  1104.  ori.b      #$20, d0             ; Set bit 5 of d1.
  1105.  move.b     d0, 2(a0)            ; Set bit 5 of port A. Strobe high.
  1106.  
  1107. write_character_to_screen:
  1108. undesirable_ascii:
  1109. not_screen:
  1110. not_bconout_call:
  1111.  movea.l    preempted_handler_address, a0
  1112.  jmp        (a0)                 ; JUMP TO PREEMPTED TRAP #13 HANDLER
  1113.  
  1114.  bss
  1115. character:                  ds.w    1
  1116. preempted_handler_address:  ds.l    1
  1117. esc_sequence_flag:          ds.b    1
  1118.  align
  1119.                             ds.l   96    ; Stack
  1120. stack:                      ds.l    1    ; Address of stack.
  1121. program_end:                ds.l    0
  1122.  end
  1123.  
  1124.  
  1125. Print Buffers
  1126.   
  1127.      A print buffer is memory that is used as a reservoir in 
  1128. which characters are stored.  Because of the speed differential 
  1129. between the printer and other computer system hardware, the 
  1130. reservoir can be filled faster than it can be emptied.  This is 
  1131. good.  That's how reservoirs are supposed to work.  I have 
  1132. already discussed, in qualitative terms, the maximum rate at 
  1133. which the reservoir may be emptied; that is accomplished by 
  1134. accessing the parallel port directly when sending data to the 
  1135. printer.  Now I shall present methods of filling the reservoir.
  1136.      But first, let me preface that discussion with my 
  1137. interpretation of the reason that print buffers are used and the 
  1138. reason that they are constructed as they are.  In the old days, 
  1139. when computers were housed in a large room in a building far away 
  1140. from the many programmers using it, we communicated with the 
  1141. computer via a video terminal (before that, we used teletype 
  1142. machines--before that we beat hollow logs with sticks).  Most 
  1143. often, only one printer was available for the general programming 
  1144. population, and it was also located at the computer site.  
  1145. Programmers did not request hard copy until it was absolutely 
  1146. necessary; for two reasons: one, each printing job was placed in 
  1147. a queue, and since each programmer's request was likely to be one 
  1148. of many, some time was likely to elapse before the hard copy 
  1149. appeared; two, the walk to the printer was usually not a short 
  1150. one.  Under such conditions, it was easy to justify the existence 
  1151. of a print buffer (or queue).
  1152.      Now, as we sit at our complete systems, we become so spoiled 
  1153. with immediate response that we find it difficult to occupy 
  1154. ourselves for a few minutes while the printer does its business.  
  1155. Of course, some printings require much more than a few minutes, 
  1156. but, for those, I get up and walk away because I don't have room 
  1157. for a printer silencer and I can't stand the noise generated by 
  1158. the printer for more than a minute or two.  Still, I find print 
  1159. buffers convenient because data transfers between the printing 
  1160. program and a buffer is so much faster than it is between the 
  1161. program and a bufferless printer, usually.
  1162.      Now, because memory is, and always has been, a limited 
  1163. commodity in any computer system, the buffers that we are most 
  1164. likely to acquire are circular; that is, the buffers are of a 
  1165. significant length, with a beginning address and an ending 
  1166. address; and when we have sent enough data to fill a buffer, it 
  1167. does not overflow as would a reservoir; instead, it begins to 
  1168. refill, overwriting previously entered data.  At this point, the 
  1169. first disadvantage of circular buffers become evident.  What do 
  1170. you suppose happens to the data transfer rate of a print buffer 
  1171. when it has become full.  Well, from that point on, it can accept 
  1172. a new character only after one has been extracted from the buffer 
  1173. by the printer, therefore, the buffer's data transfer rate 
  1174. degenerates to that of the printing mechanism.  Note that the 
  1175. buffer is considered to be empty when a particular printing task 
  1176. has been accomplished.  For example, if we send a single file to 
  1177. the printer through the buffer and permit the task to terminate 
  1178. before sending another, then the second file sees an empty 
  1179. buffer.  If, on the other hand, we send a two or more files back 
  1180. to back, the buffer will not appear to be empty until all files 
  1181. have been processed.
  1182.      I am going to explain the operation of a circular print 
  1183. buffer in detail, not because I think that it is the type you 
  1184. should use, but because it is the type you are most likely to 
  1185. see, because that is the historical print buffer design.  After 
  1186. considering its weaknesses, I shall present another type--a 
  1187. linear buffer.  In addition, the format of circular buffers 
  1188. demands the use of algorithms which you might find useful in 
  1189. other applications.  Finally, it is by comparing the data 
  1190. transfer rate of the linear buffer to that of the circular buffer 
  1191. that I shall be able to stress the linear buffer's advantage.  
  1192. Program 66 introduces a program that installs a circular print 
  1193. buffer in ram.
  1194.  
  1195. Program 66. This program installs a circular print 
  1196. buffer.  The buffer has not been designed for practical 
  1197. use, it has been designed for study.
  1198.  
  1199.  ; Program Name: PRG_7GR.S
  1200.  ;      Version: 1.014
  1201.  
  1202.  ; Assembly Instructions:
  1203.  
  1204.  ;      Assemble in Relocatable mode and save with a TOS and TTP suffix.
  1205.  
  1206.  ; Program Function:
  1207.  
  1208.  ;      Circular printer buffer.  The default buffer size for the TOS version
  1209.  ; of this program is only 100 bytes because this is a special program that is
  1210.  ; designed to be used to test printing programs and similar endeavors.  While
  1211.  ; this program is resident, those other programs can be executed from the
  1212.  ; AssemPro debugger, permitting this program's address locations and registers
  1213.  ; to be monitored.  Breakpoints can't be set in this program from the debugger
  1214.  ; window after it is resident, but, from a program under test in the debugger,
  1215.  ; you can single step in such a way as to end up within the custom trap #13
  1216.  ; handler installed by this program. 
  1217.  
  1218.  ;      Once a printing session has begun, it cannot be cancelled merely by
  1219.  ; turning the printer off.  The problem is this: after a printing session
  1220.  ; has been initiated, if the printer is turned off to cancel the session,
  1221.  ; printing resumes when the printer is turned back on, because turning the
  1222.  ; printer back on causes the logic level of pin 11 to drop from high to low,
  1223.  ; thereby providing the falling edge required to invoke the interrupt handler.
  1224.  
  1225.  ;      Two exception handlers are installed by this program.  The trap #13
  1226.  ; handler intercepts all trap #13 invocations.  It handles only those that
  1227.  ; are relevant to the printing process; the others are passed on to the trap
  1228.  ; #13 handler that is preempted by the custom handler installed by this
  1229.  ; program.  The interrupt handler is invoked each time the logic level of
  1230.  ; pin 11 of the parallel interface port drops from high to low.  No provisions
  1231.  ; are made within this program to preserve the function of a pin 11 handler
  1232.  ; existing at the time this program's custom interrupt handler is installed. 
  1233.  
  1234.  ;      When the printing program begins to send characters to the parallel
  1235.  ; port via system function invocations, if the print buffer established by
  1236.  ; this program is empty (because it has not yet been used or because all
  1237.  ; characters previously stored have been processed), then characters will be
  1238.  ; send to the printer by the trap #13 handler until the printer becomes
  1239.  ; busy enough so that it is unable to keep up with the flow of characters
  1240.  ; into and out of the trap #13 handler.  Once the printer falls behind,
  1241.  ; characters are thereafter stored in the print buffer.  From that point on,
  1242.  ; characters must be processed by the interrupt handler.
  1243.  
  1244.  ;      A special file has been prepared to provide a visual view of the
  1245.  ; concepts in the above paragraph.  With the printer on and this program
  1246.  ; installed, execute PRG_7DP.TTP and type SPOOLTST.DOC on the parameter input
  1247.  ; line.  All characters processed by the trap #13 handler will be printed
  1248.  ; in lower case; those printed by the interrupt handler will be printed in
  1249.  ; upper case.  You should print SPOOLTST.DOC once with the default buffer
  1250.  ; size, then execute PRG_7GR.TTP and type 32 on its input parameter line to
  1251.  ; increase the buffer size to 32 kilobytes.  Then print SPOOLTST.DOC again
  1252.  ; using PRG_7DP.TTP.
  1253.  
  1254.  ;      The instruction within the interrupt handler that converts characters
  1255.  ; from lower case to upper case is prominently marked.  No other file should
  1256.  ; be printed while this instruction is active because the character conversion
  1257.  ; instruction will wreck havoc with numeric and punctuation characters.  To
  1258.  ; print any other file with this program, use a semicolon to inactivate that
  1259.  ; instruction and assemble this program again.  Other versions of the circular
  1260.  ; print buffer which do not include that instruction will be introduced
  1261.  ; shortly.
  1262.     
  1263.  ; Execution Instructions:
  1264.  
  1265.  ;      May be executed from the desktop with a TOS or TTP extension.  May be
  1266.  ; executed from an AUTO folder if it has a PRG extension.
  1267.  
  1268.  ;      If a larger buffer size is desired, the program extension can be
  1269.  ; changed from TOS to TTP.  If a buffer size is declared on the TTP parameter
  1270.  ; line, any size, up to the maximum available machine memory, may be specified.
  1271.  ; When the TTP program is executed, type the desired size, in kilobytes, on the
  1272.  ; parameter line; the maximum number of digits which can be typed on the
  1273.  ; parameter line is 4.  Do not type spaces before the digits.  Do not type
  1274.  ; anything between digits.  Do not type anything after the digits, except the
  1275.  ; Return key.  The OK button may be clicked in lieu of a Return key press.
  1276.  
  1277.  ;      The length of the buffer will be (input X 1024) bytes.  Ex: if input is
  1278.  ; 32, length of the buffer will be 32,768 bytes.  After converting the ASCII
  1279.  ; decimal input to binary, it is much easier to simply shift the binary result
  1280.  ; 10 bits left to multiply it by 1024 than it is to multiply it by 1000. 
  1281.  
  1282.  ;      The buffer length (either the default size or the specified size) is
  1283.  ; combined with the program size to determine the parameter which must be
  1284.  ; passed to GEMDOS function $31.
  1285.  
  1286.  ;      In order to permit observation of this programs variables and registers,
  1287.  ; its location in memory and other pertinent addresses are screened as the
  1288.  ; program is being loaded.
  1289.  
  1290.  ;      When the addresses appear on the screen, write them down.  Terminate
  1291.  ; execution by pressing the Return key.  In the AssemPro debugger, you can go
  1292.  ; to pertinent addresses using the "from address" function.  You can double
  1293.  ; click on any register in the register output field and use the dialog box to
  1294.  ; specify an address in this program that you wish to monitor.
  1295.  
  1296.  ;    Once this program has been executed, it will remain in ram until the
  1297.  ; computer is rebooted.
  1298.  
  1299.  ; WARNING:
  1300.  
  1301.  ;      If this program or any other LSR program is executed from within the
  1302.  ; AssemPro debugger, upon exit from AssemPro, the operating system will not be
  1303.  ; able to clear the program from memory.  Because the program will be residing
  1304.  ; in an area of memory that was controlled by AssemPro, the system environment
  1305.  ; will be corrupted.  You can confirm this by trying to reexecute AssemPro
  1306.  ; after you exit.  You will receive a Bus error message.
  1307.  
  1308.  ;      Furthermore, you may not be able to assemble a program until you reset
  1309.  ; the system.
  1310.  
  1311.  ;      There is only one thing you can do if you execute a LSR program from
  1312.  ; within AssemPro; you must reset the system by turning it completely off, then
  1313.  ; back on.  You can try a warm reset, but all bets are off if you do that.
  1314.  
  1315.  ;      Furthermore, remember that programs with a memory preserving or limiting
  1316.  ; algorithm require special treatment if they are executed while they are in
  1317.  ; memory because of having just been assembled; that is, when they are not
  1318.  ; loaded via the "execute program" button.  The special treatment consist of
  1319.  ; skipping over the initializing instructions that reference the locations
  1320.  ; 4(a7) and the basepage address.  That's because programs not loaded via the
  1321.  ; "execute program" button do not have a basepage.  The special treatment also
  1322.  ; means that the system function used to relinquish processor control must not be
  1323.  ; executed.
  1324.  
  1325.  ; NOTE:
  1326.  
  1327.  ;      The loop which processes the ASCII decimal input is formed with a dbra
  1328.  ; instruction.  This is a word size instruction, therefore, when the loop
  1329.  ; counter register is prepared by subtracting one from the number of input
  1330.  ; digits, the value in the counter register must be extended to word size to
  1331.  ; match the size of the dbra instruction.
  1332.  
  1333. program_start:                  ; Calculate program size and retain result.
  1334.  lea        program_end(pc), a3 ; Fetch program end address into A3.
  1335.  
  1336.  ; If this program is assembled and observed in the debugger, the label
  1337.  ; program_end will not be seen because the label buffer has been declared in
  1338.  ; in the bss section as buffer: ds.l 0, and the end of the program has also
  1339.  ; been marked with program_end: ds.l 0.  Effectively, the label program_end
  1340.  ; does not even exist as far as the assembled bss section of the program is
  1341.  ; concerned.
  1342.  
  1343.  movea.l    4(a7), a4           ; Fetch basepage address into A4.
  1344.  suba.l     a4, a3              ; Program size is in A3.
  1345.  lea        stack(pc), a7       ; Fetch address of the program stack.
  1346.  
  1347. process_input_parameter:        ; Calculate requested buffer length.
  1348.  lea        $80(a4), a4         ; Fetch basepage parameter line start address.
  1349.  lea        length(pc), a0      ; Fetch address of the length variable.
  1350.  moveq      #0, d0              ; Clear counter register.
  1351.  move.b     (a4)+, d0           ; Fetch parameter line character count.
  1352.  beq.s      compute_size        ; Branch if no parameter--use default length.
  1353.  cmpi.b     #4, d0              ; Greater than four test.
  1354.  bgt        too_many_characters ; Branch if more than four characters.
  1355.  subq.b     #1, d0              ; Set up counter.
  1356.  ext.w      d0                  ; Extend to match size of DBRA instruction.
  1357.  moveq      #0, d2              ; Initialize accumulator.
  1358.  moveq      #0, d1              ; Clear scratch register.
  1359.  
  1360. fetch_digit:                    ; Convert input from ASCII decimal to binary.
  1361.  move.b     (a4)+, d1           ; ASCII decimal digit to D1.
  1362.  sub.b      #$30, d1            ; Convert ASCII decimal digit to decimal digit.
  1363.  bmi        not_decimal         ; Branch if digit is less than zero.
  1364.  cmp.b      #9, d1              ; Greater than nine test.
  1365.  bgt        not_decimal         ; Branch if digit is greater than nine.
  1366.  
  1367. multiply_accumulator_content_by_ten:
  1368.  move.l     d2, d3              ; Copy accumulator in D3.
  1369.  lsl.l      #3, d2              ; Shift accumulator content to multiply by 8.
  1370.  add.l      d3, d2              ; Add D2's original quantity twice to
  1371.  add.l      d3, d2              ; complete the multiplication by 10.
  1372.  
  1373. add_new_digit_to_accumulator_content:
  1374.  add.l      d1, d2              ; Add decimal number in D1 to accumulator.
  1375.  dbra       d0, fetch_digit     ; Loop until d0 becomes negative.
  1376.  
  1377. extend_to_thousands_of_bytes:   ; Length will be (input X 1000) plus 
  1378.  moveq      #10, d3             ; some even number of bytes that is less
  1379.  lsl.l      d3, d2              ; than 1000.  Ex: if input is 32, length
  1380.  move.l     d2, (a0)            ; is 32768.  A0 contains address of length
  1381.                                 ; variable
  1382.  
  1383. compute_size:                   ; This will be parameter to GEMDOS $31.
  1384.  adda.l     (a0), a3            ; Add buffer length to program size.
  1385.  
  1386. compute_and_store_buffer_end:   ; Presently, buffer_end = buffer start.
  1387.  lea        buffer(pc), a1      ; Load buffer starting address.
  1388.  adda.l     (a0), a1            ; Add buffer length to buffer start address.
  1389.  move.l     a1, buffer_end      ; Store buffer_end address.
  1390.  
  1391. install_custom_trap_13_vector:  ; ******************************************
  1392.  pea        trap_13_handler(pc) ; Push custom trap handler address onto stack.
  1393.  move.w     #$2D, -(sp)         ; Trap 13 vector number.
  1394.  move.w     #5, -(sp)           ; Function = setexec.
  1395.  trap       #13                 ; Current trap 13 vector returned in D0.
  1396.  addq.l     #8, sp
  1397.  move.l     d0, preempted_trap_13_address
  1398.  
  1399. install_interrupt_handler_vector:  ; ***************************************
  1400.  pea        interrupt_handler(pc)  ; Push interrupt handler address.
  1401.  move.w     #0, -(sp)              ; Interrupt level for centronics ready.
  1402.  move.w     #$D, -(sp)             ; Functon = XBIOS #13 dec = mfpint.
  1403.  trap       #14
  1404.  addq.l     #8, sp
  1405.  
  1406. print_memory_locations:
  1407.  bsr        print_newline          ; For debugger screen protection.
  1408.  lea        load_message(pc), a0
  1409.  bsr        print_string
  1410.  move.l     #program_start, d1     ; Print program start address.
  1411.  bsr        bin_to_hex             ; bin_to_hex expects binary number in D1.
  1412.  lea        hexadecimal(pc), a0    ; Print the hexadecimal string.
  1413.  bsr        print_string
  1414.  lea        separator(pc), a0      ; Print a separator between addresses.
  1415.  bsr        print_string
  1416.  move.l     #program_end, d1       ; Print program end address.
  1417.  bsr        print_address
  1418.  bsr        print_newline          
  1419.  lea        int_hand_msg(pc), a0   ; Print interrupt handler address.
  1420.  bsr        print_string
  1421.  move.l     #interrupt_handler, d1
  1422.  bsr        print_address
  1423.  bsr        print_newline
  1424.  lea        trap_13_msg(pc), a0    ; Print custom trap #13 address.
  1425.  bsr        print_string
  1426.  move.l     #trap_13_handler, d1
  1427.  bsr        print_address
  1428.  bsr        print_newline
  1429.  lea        io_buffer_msg(pc), a0  ; Print address of variable 'io_buffer'.
  1430.  bsr        print_string
  1431.  move.l     #io_buffer, d1 
  1432.  bsr        print_address
  1433.  lea        content_msg(pc), a0    ; Print content of io_buffer variable.
  1434.  bsr        print_string
  1435.  move.l     io_buffer, d1
  1436.  bsr        print_content
  1437.  lea        length_msg(pc), a0     ; Print address of variable 'length'.
  1438.  bsr        print_string
  1439.  move.l     #length, d1
  1440.  bsr        print_address
  1441.  lea        content_msg(pc), a0    ; Print content of length variable.
  1442.  bsr        print_string
  1443.  move.l     length, d1
  1444.  bsr        print_content
  1445.  lea        extract_msg(pc), a0    ; Print add of variable 'extract_pointer'.
  1446.  bsr        print_string
  1447.  move.l     #extract_pointer, d1
  1448.  bsr        print_address
  1449.  lea        content_msg(pc), a0    ; Print content of extract_pointer.
  1450.  bsr        print_string
  1451.  move.l     extract_pointer, d1
  1452.  bsr        print_content
  1453.  lea        insert_msg(pc), a0     ; Print add of variable 'insert_pointer'.
  1454.  bsr        print_string
  1455.  move.l     #insert_pointer, d1
  1456.  bsr        print_address
  1457.  lea        content_msg(pc), a0    ; Print content of insert_pointer.
  1458.  bsr        print_string
  1459.  move.l     insert_pointer, d1
  1460.  bsr        print_content
  1461.  lea        end_msg(pc), a0        ; Print add of variable 'buffer_end'.
  1462.  bsr        print_string
  1463.  move.l     #buffer_end, d1
  1464.  bsr        print_address
  1465.  lea        content_msg(pc), a0    ; Print content of buffer_end.
  1466.  bsr        print_string
  1467.  move.l     buffer_end, d1
  1468.  bsr        print_content
  1469.  
  1470. wait_for_keypress:              ; Give time to write down memory addresses.
  1471.  move.w     #8, -(sp)           ; Function = c_necin = GEMDOS $8.
  1472.  trap       #1                  ; GEMDOS call.
  1473.  addq.l     #2, sp
  1474.  
  1475. relinquish_processor_control:   ; Maintain memory residency.
  1476.  move.w     #0, -(sp)           ; See page 121 of Internals book.
  1477.  move.l     a3, -(sp)           ; Program size.
  1478.  move.w     #$31, -(sp)         ; Function = ptermres = GEMDOS $31.
  1479.  trap       #1
  1480.  
  1481. not_decimal:
  1482.  pea        message_1(pc)       ; Print error message.
  1483.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1484.  trap       #1                  ; GEMDOS call
  1485.  addq.l     #6, sp              ; Reset stack pointer to top of stack.
  1486.  bra.s      terminate
  1487.  
  1488. too_many_characters:
  1489.  pea        message_2(pc)       ; Print error message.
  1490.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1491.  trap       #1                  ; GEMDOS call
  1492.  addq.l     #6, sp              ; Reset stack pointer to top of stack.
  1493.  
  1494. terminate:                      ; Wait for keypress.
  1495.  move.w     #8, -(sp)           ; Function = GEMDOS $8 = cnecin.
  1496.  trap       #1
  1497.  addq.l     #2, sp
  1498.  move.w     #0, -(sp)           ; Function = p_term_old = GEMDOS $0.
  1499.  trap       #1                  ; GEMDOS call.
  1500.  
  1501.  ; In the following discussion, whenever the word "printer" is used,
  1502.  ; realize that it includes any hardware print buffer that is connected
  1503.  ; between the computer and the printer.  In other words, the word printer,
  1504.  ; as it is used below, can mean printer, print buffer or the peripheral
  1505.  ; combination.  Note also that an external print buffer can function while
  1506.  ; the printer is off; that is, it can receive characters and store them.
  1507.  ; This means that you can complete a lot of print handling testing with the
  1508.  ; printer off to save paper.  To get rid of text in an external buffer, just
  1509.  ; press its clear button; to print the data stored, just turn on the printer. 
  1510.  
  1511.  ; The interrupt handler is activated whenever pin 11 of the centronics
  1512.  ; interface is forced from a high to a low logic level.  The printer does
  1513.  ; does this when it turned on and when it sends a busy signal over pin 11.
  1514.  ; When the printer is off, the logic level of pin 11 is constantly high, but
  1515.  ; during the time that the printer is busy receiving a character, the logic
  1516.  ; level of pin 11 is placed high and held there only until the printer is
  1517.  ; ready to receive another character from the computer.  It is the falling
  1518.  ; edge of the logic level pulse generated by the printer that causes an MFP
  1519.  ; pin 0 interrupt.  This can be verified by observing the contents of the
  1520.  ; MFP's Active Edge Register (AER) located at address $FFFA03.  The content
  1521.  ; of that register is 4, therefore, the GPIP bit 0 input pin generates an
  1522.  ; interrupt on a one-to-zero transition.  Reference pages 4-1 and 4-2 of
  1523.  ; the Motorola MC68901 Multi-Function Peripheral manual and pages 32 and
  1524.  ; 60 of the Internals book.  
  1525.  
  1526.  ; So, whenever the interrupt occurrs, we know that the printer is ready to
  1527.  ; receive a character, therefore, we can immediately send one to it.
  1528.  
  1529.  ; However, when we want to send a character, but we have no knowledge of
  1530.  ; the printer's capability to receive, we must perform a test to detect the
  1531.  ; printers state.  This is done by testing bit #0 of the MFP's General
  1532.  ; Purpose I/O data register (GPIP).  If that bit is a zero, then the printer
  1533.  ; is ready to receive a character.  The trap handler must perform this test.
  1534.  
  1535.  ; NOTE: Both the interrupt handler and the trap handler use a similiar
  1536.  ;       algorithm to print.  A common subroutine was not used (but could
  1537.  ;       be used) because the additional time required by the interrupt
  1538.  ;       handler would be 34 clock cycles for each character printed.  It
  1539.  ;       is the interrupt handler that actually does the bulk of the printing.
  1540.  
  1541.  ;       The more time used by the interrupt handler, the less time
  1542.  ;       available to the process you are running while printing is being
  1543.  ;       accomplished via the interrupt handler.
  1544.   
  1545. interrupt_handler:              ; Parallel interface pin #11 interrupt handler.
  1546.  movem.l    d0/a0-a1, -(sp)     ; Save content of registers used by handler.
  1547.  lea        io_buffer, a0       ; Load buffer structure pointer into A0.
  1548.  movea.l    8(a0), a1           ; Content of buffer extract pointer into A1.
  1549.  
  1550.  ; NOTE:    A1 is now a simulated extract pointer.  It contains the location
  1551.  ;          from which the next character is to be fetched.  The simulated
  1552.  ;          pointer is used to scout ahead and determine what would happen
  1553.  ;          if the actual extract pointer were incremented.
  1554.                               
  1555.  cmpa.l     12(a0), a1          ; Compare location stored in insert pointer
  1556.                                 ; to location stored in simulated extract
  1557.                                 ; pointer.
  1558.  beq.s      buffer_is_empty     ; Branch if location in insert pointer is equal
  1559.                                 ; to location stored in simulated extract
  1560.                                 ; pointer.
  1561.  addq.l     #1, a1              ; Increment simulated extract pointer to
  1562.                                 ; location of next character.
  1563.  cmpa.l     16(a0), a1          ; Compare buffer_end to location stored in
  1564.                                 ; simulated extract pointer.
  1565.  bcs.s      end_of_buffer_not_reached
  1566.  movea.l    (a0), a1            ; Move simulated extract pointer to front of
  1567.                                 ; buffer by storing buffer address in simulated
  1568.                                 ; extract pointer because the simulated extract
  1569.                                 ; pointer has reached the buffer limit.
  1570.  
  1571. end_of_buffer_not_reached:
  1572.  move.b     (a1), d0            ; Fetch character at location stored in
  1573.                                 ; simulated extract pointer.
  1574.  move.l     a1, 8(a0)           ; Put location stored in simulated extract
  1575.                                 ; pointer in actual extract pointer.
  1576.  
  1577. print_character:
  1578.  lea        $FF8800, a1         ; Address of Programmable Sound Generator.
  1579.  
  1580.  ; *************************************************************************
  1581.  andi.b     #$DF, d0            ; Convert character to upper case.
  1582.  
  1583.  ; The above statement is used to determine the frequency of interrupt handler
  1584.  ; use by various printing programs.  It does this by converting lower case
  1585.  ; characters to upper case each time a character is processed by this
  1586.  ; interrupt handler.  For a properly functioning printer buffer, the above
  1587.  ; statement must be removed.  Experiments show that most of the characters
  1588.  ; handled by the print buffer will be processed by the interrupt handler,
  1589.  ; unless there is an external print buffer connected between the printer
  1590.  ; and the parallel port, or unless the printer contains a significantly
  1591.  ; large buffer and that buffer is able to keep up with the flow of characters
  1592.  ; into the trap #13 handler.
  1593.  
  1594.  ; *************************************************************************
  1595.  
  1596.  move.b     #$F, (a1)           ; Select port B (register 17) of PSG.
  1597.  move.b     d0, 2(a1)           ; Write character to PSG register 17.
  1598.  move.b     #$E, (a1)           ; Select port A (register 16) of PSG.
  1599.  move.b     (a1), d0            ; Get current register 16 value.
  1600.  
  1601. strobe_low:                   
  1602.  andi.b     #$DF, d0            ; Bit 5 to zero.         
  1603.  move.b     d0, 2(a1)           ; bit 5 of port A to zero.
  1604.                               
  1605. strobe_high:                  
  1606.  ori.b      #$20, d0            ; Bit 5 to one.
  1607.  move.b     d0, 2(a1)           ; Bit 5 of port A to one.
  1608.  
  1609. buffer_is_empty:
  1610.  bclr       #0, $FFFA11         ; Clear MFP in service bit.
  1611.  movem.l    (sp)+, d0/a0-a1     ; Restore registers used by handler.
  1612.  rte
  1613.  
  1614. trap_13_handler:
  1615.  move.l    usp, a0              ; Load address of current top of user stack.
  1616. get_processor_status:
  1617.  btst      #5, (sp)             ; User mode test.
  1618.  beq.s     was_user_mode        ; No adjustment is necessary if the
  1619.                                 ; processor was in user mode.
  1620.  movea.l   sp, a0               ; Load current top of supervisor stack.
  1621.  addq.l    #6, a0               ; Adjust SSP for user mode type data access.
  1622.  
  1623. was_supervisor_mode:
  1624. was_user_mode:                  ; Processing for either mode follows.
  1625.  tst.w      2(a0)               ; Is device the printer?
  1626.  bne        not_printer
  1627.  cmpi.w     #3, (a0)            ; Writing a character to a device?
  1628.  bne        not_bconout_call
  1629.  
  1630. fetch_character:
  1631.  move.w     4(a0), d0
  1632.  move.w     #$2700, SR          ; Disable interrupts.
  1633.  
  1634.  ; NOTE: If interrupts are not disabled during this critical portion of the
  1635.  ;       trap handler algorithm, the interrupt handler could be invoked and
  1636.  ;       screw up the buffer pointers.
  1637.  
  1638. buffer_empty_test:              ; Want to know if buffer is empty or not.
  1639.  lea        io_buffer, a0       ; Fetch buffer structure address.
  1640.  
  1641.  ; NOTE: The address stored in the insert pointer is placed in register A1
  1642.  ;       for two reasons; (1) for comparison with the extract pointer,
  1643.  ;       (2) in order to simulate an increment of the address later.
  1644.  
  1645.  movea.l    12(a0), a1          ; Fetch content of buffer insert pointer.
  1646.  cmpa.l     8(a0), a1           ; Compare content of extract pointer with
  1647.                                 ; content of insert pointer to determine if
  1648.  bne        buffer_not_empty    ; buffer is empty.
  1649. try_to_print_character:
  1650.  btst       #0, $FFFA01         ; Is printer (or hardware print buffer)
  1651.                                 ; busy (or off)?
  1652.  bne.s      printer_busy        ; Branch if either peripheral is busy
  1653.                                 ; or off.
  1654. print_character_if_not_busy:    ; Means peripherals are not busy and are on.
  1655.  lea        $FF8800, a1         ; Address of Programmable Sound Generator.
  1656.  move.b     #$F, (a1)           ; Select port B of PSG.
  1657.  move.b     d0, 2(a1)           ; Write character to PSG.
  1658.  move.b     #$E, (a1)           ; Select port A of PSG.
  1659.  move.b     (a1), d1            ; Get current register 16 value.
  1660.  
  1661. _strobe_low:
  1662.  andi.b     #$DF, d1
  1663.  move.b     d1, 2(a1)
  1664. _strobe_high:
  1665.  ori.b      #$20, d1
  1666.  move.b     d1, 2(a1)       
  1667.  moveq      #-1, d0             ; In case calling source wants to know
  1668.  rte                            ; that character was dispatched.
  1669.  
  1670. buffer_not_empty:
  1671. printer_busy:
  1672.  addq.l     #1, a1              ; Simulate insert location increment.
  1673.  cmpa.l     16(a0), a1          ; Compare buffer_end to simulated insert
  1674.                                 ; location.
  1675.  bcs.s      not_end_of_buffer   ; Branch if simulated location is not equal
  1676.                                 ; to the end of buffer location.
  1677.  movea.l    (a0), a1            ; Simulate a reset of insert pointer to the
  1678.                                 ; first buffer location; that is, the front of
  1679.                                 ; the buffer is the new simulated insert
  1680.                                 ; location.
  1681. not_end_of_buffer:
  1682.  cmpa.l     8(a0), a1           ; Compare location stored in extract pointer
  1683.                                 ; to simulated insert location.
  1684.  beq.s      buffer_full         ; If they are equal, the buffer is full.
  1685. store_character_in_buffer: 
  1686.  move.b     d0, (a1)            ; Store character at the now valid location.    
  1687.     
  1688.  move.l     a1, 12(a0)          ; Store simulated location as the new insert
  1689.                                 ; location in the insert pointer.
  1690.  moveq      #-1, d0             ; In case calling source wants to know
  1691.  rte                            ; that character was dispatched.
  1692.  
  1693. buffer_full:
  1694.  move.l     $4BA, d1            ; Fetch current _hz_200 value.
  1695.  add.l      #$BB8, d1           ; Add count for desired elapsed time value.
  1696.  
  1697.  ; NOTE: The content of $4BA is incremented every 5 milliseconds.  Compute
  1698.  ;       the value to add to the current count by dividing the number of
  1699.  ;       milliseconds to wait (equals number of seconds times 1000) by 5.
  1700.  ;       In this case, to wait 15 seconds, 15000/5 = 3000 = $BB8.
  1701.  
  1702.  move.w     #$2300, SR          ; Enable interrupts.
  1703.  
  1704.  ; NOTE: Here interrupts are enabled because the trap handler algorithm may
  1705.  ;       be idle for some time.  During this idle time, invoked interrupts
  1706.  ;       would have to remain pending.  There is a maximum amount of time
  1707.  ;       that an interrupt may remain pending, and that time depends on the
  1708.  ;       number of instructions a program can execute with the interrupts
  1709.  ;       masked off.  The amount of time that interrupts can be disabled is
  1710.  ;       usually restricted.
  1711.  
  1712.  ;       Reference: Programming the 68000 by Steve Williams, page 366.
  1713.  ;                  Published by SYBEX, Inc. 1985
  1714.  
  1715. wait:
  1716.  cmpa.l     8(a0), a1           ; Compare location stored in extract pointer to
  1717.                                 ; location stored in simulated insert pointer.
  1718.  bne.s      store_character_in_buffer
  1719.  cmp.l      $4BA, d1            ; Elapsed time check.
  1720.  bhi.s      wait                ; Wait until there is room in the buffer for
  1721.                                 ; another character, or until the programmed
  1722.                                 ; wait time has expired.
  1723.  moveq      #0, d0              ; In case calling source wants to know
  1724.  rte                            ; that character was not dispatched.
  1725.  
  1726. not_bconout_call:
  1727.  cmpi.w     #8, (a0)            ; Requesting output device status?
  1728.  bne.s      not_bcostat_call
  1729.  
  1730.  ; NOTE: The buffer status is returned in response to the bcostat invocation,
  1731.  ;       not the printer status.
  1732.  
  1733. determine_software_buffer_status:
  1734.  moveq      #-1, d0             ; Assume buffer is not full.
  1735.  lea        io_buffer, a0       ; Fetch buffer structure pointer.
  1736.  move.l     12(a0), a1          ; Fetch insert pointer for simulation.
  1737.  addq.l     #1, a1              ; Simulate moving insert pointer to next buffer
  1738.                                 ; location.
  1739.  cmpa.l     16(a0), a1          ; Compare buffer_end to simulated insert
  1740.                                 ; location.
  1741.  bcs.s      not_buffer_end      
  1742.  movea.l    (a0), a1            ; Simulate resetting insert pointer to front of
  1743.                                 ; buffer.
  1744. not_buffer_end:
  1745.  cmpa.l     8(a0), a1           ; Compare location stored in extract pointer
  1746.                                 ; to simulated insert location.
  1747.  bne.s      buffer_not_full     ; Buffer not full if extract location is not
  1748.                                 ; equal to simulated insert location.
  1749.  moveq      #0, d0              ; Return buffer status to calling source.
  1750. buffer_not_full:                ; Return 0 if full, -1 if not.
  1751.  rte
  1752.  
  1753. not_bcostat_call:
  1754. not_printer:
  1755.  movea.l    preempted_trap_13_address, a0
  1756.  jmp        (a0)                ; JUMP TO PREEMPTED TRAP #13 HANDLER.
  1757.  
  1758.  ; 
  1759.  ; SUBROUTINES
  1760.  ;
  1761.  
  1762.  ; The binary to ASCII hexadecimal conversion routine expects a number to be
  1763.  ; passed as a longword in register D1.  Beginning with the most significant
  1764.  ; nibble (a nibble = four bits), each nibble is converted to its ASCII
  1765.  ; hexadecimal equivalent and stored in "hexadecimal", a null terminated
  1766.  ; buffer.  Maximum size of the binary number is 32 bits = 8 nibbles.
  1767.  
  1768.  ; The algorithm discards leading zeroes.
  1769.  
  1770.  ; The conversion from binary nibble to hex digit is accomplished by 
  1771.  ; extracting the character in the hex table that is located at the position
  1772.  ; defined by the decimal value of the nibble.  For example, if the nibble
  1773.  ; is "1111", the decimal value is 15; the 15th element of the hex table is
  1774.  ; the letter F.  The location in the table is specified by an offset from
  1775.  ; the address of the first character of the table, which is stored in A1.
  1776.  ; The value of the offset is stored in register D0.  The addressing mode
  1777.  ; used to locate the appropriate table entry is "address register indirect
  1778.  ; with offset".  
  1779.  
  1780. bin_to_hex:                      ; Expects binary number in D1.   
  1781.  lea        hexadecimal, a0      ; A0 is pointer to array "hexadecimal".
  1782.  tst.l      d1                   ; Test for contents = 0.
  1783.  beq.s      zero_passed          ; Branch if number is 0.
  1784.  lea        hex_table, a1        ; A1 is pointer to array "hex_table".
  1785.  lea        hex_table, a1        ; A1 is pointer to array "hex_table".
  1786.  moveq      #7, d2               ; D2 is the loop counter for 8 nibbles.
  1787.  
  1788. discard_leading_zeroes: 
  1789.  rol.l      #4, d1               ; Rotate most significant nibble to the
  1790.                                  ; least significant nibble position.
  1791.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  1792.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  1793.  bne.s      store_digit          ; Branch and store if not leading zero.
  1794.  dbra       d2, discard_leading_zeroes
  1795. continue:
  1796.  rol.l      #4, d1               ; Rotate most significant nibble.
  1797.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  1798.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  1799. store_digit:
  1800.  move.b     0(a1,d0.w), (a0)+    ; Store ASCII hexadecimal digit in buffer.
  1801.  dbra       d2, continue         ; Continue looping until D2 = -1.
  1802.  move.b     #0, (a0)             ; Terminate hexadecimal string with a null.
  1803.  rts
  1804. zero_passed:
  1805.  move.b     #$30, (a0)+         ; Store an ASCII zero in "hexadecimal".
  1806.  move.b     #0, (a0)            ; Terminate ASCII hexadecimal string with null.
  1807.  lea        hexadecimal, a0
  1808.  rts
  1809.  
  1810. print_address:
  1811.  bsr        bin_to_hex      
  1812.  lea        hexadecimal(pc), a0
  1813.  bsr        print_string
  1814.  rts
  1815.  
  1816. print_content:
  1817.  bsr        bin_to_hex      
  1818.  lea        hexadecimal(pc), a0
  1819.  bsr        print_string
  1820.  bsr        print_newline
  1821.  rts
  1822.  
  1823. print_string:                   ; Expects address of string to be in A0.
  1824.  pea        (a0)                ; Push address of string onto stack.
  1825.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1826.  trap       #1                  ; GEMDOS call
  1827.  addq.l     #6, sp              ; Reset stack pointer to top of stack.
  1828.  rts
  1829.  
  1830. print_newline:                  ; Prints a carriage return and linefeed.
  1831.  pea        newline             ; Push address of string onto stack.
  1832.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1833.  trap       #1                  ; GEMDOS call
  1834.  addq.l     #6, sp
  1835.  rts
  1836.  
  1837.  data
  1838. hex_table:    dc.b  '0123456789ABCDEF'
  1839. newline:      dc.b  $D,$A,0
  1840. load_message: dc.b  'Installing PRT_BUF program between hex addresses: ',0
  1841. separator:    dc.b  ' - ',0
  1842. trap_13_msg:  dc.b  'Custom trap #13 address:    $',0
  1843. int_hand_msg: dc.b  'Interrupt handler address:  $',0
  1844. io_buffer_msg:dc.b  'variable io_buffer address: $',0
  1845. length_msg:   dc.b  'variable length address:    $',0
  1846. extract_msg:  dc.b  'extract_pointer address:    $',0
  1847. insert_msg:   dc.b  'insert_pointer address:     $',0
  1848. end_msg:      dc.b  'buffer_end pointer address: $',0
  1849. content_msg:  dc.b  '  content = $',0
  1850.  
  1851. message_1:    dc.b 'Nondecimal character digit detected on parameter line.'
  1852.               dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
  1853. message_2:    dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
  1854.               dc.b 'The program converts the input to thousands of bytes.'
  1855.               dc.b  $D,$A,'Default buffer size is 100 bytes.',$D,$A
  1856.               dc.b  'Press Return key to terminate.',$D,$A,0
  1857.  align
  1858.  
  1859.  ; The variable "io_buffer" is a pointer to the print buffer's starting
  1860.  ; address.  The address of io_buffer also serves as the address of a
  1861.  ; structure that is composed of the variables io_buffer, at offset 0,
  1862.  ; length, at offset 4, extract_pointer (also called the buffer output pointer),
  1863.  ; at offset 8, and insert_pointer (also called the buffer input pointer), at
  1864.  ; offset 12.  In this service, the address of io_buffer provides the means
  1865.  ; by which the other members of the structure may be referrenced.
  1866.  
  1867.  ; Characters are placed in the buffer at the address stored in the variable
  1868.  ; 'insert_pointer'; they are extracted from the buffer and sent to the printer
  1869.  ; from the address stored in the variable 'extract_pointer'.
  1870.  
  1871.  ; Time is saved because only the address of io_buffer need be loaded into
  1872.  ; an address register.  The other variables in the structure are referenced
  1873.  ; by adding their offsets to the content of the chosen address register.
  1874.  
  1875.  ; NOTE: Because this program is assembled in Relocatable mode, the run time
  1876.  ;       address for the variable "buffer" will be stored in the four pointers
  1877.  ;       at which it is declared in the io_buffer structure below.
  1878.  
  1879. io_buffer:       dc.l  buffer     ; Pointer to buffer's starting address. 
  1880. length:          dc.l     $64     ; Default buffer length = 100 bytes.
  1881. extract_pointer: dc.l  buffer     ; Buffer character output pointer.
  1882. insert_pointer:  dc.l  buffer     ; Buffer character input pointer.
  1883. buffer_end:      dc.l  buffer     ; Last address in buffer = buffer + length.
  1884.  bss
  1885. preempted_trap_13_address:  ds.l   1
  1886.  
  1887. hexadecimal:  ds.l   3     ; Output buffer.  Must be NULL terminated.
  1888.               ds.l  96
  1889. stack:        ds.l   1
  1890. buffer:       ds.l   0     ; Length of buffer must be added to length of
  1891. program_end:  ds.l   0     ; program to determine total size to pass to
  1892.  end                       ; GEMDOS function $31.
  1893.  
  1894.  
  1895. How the Circular Print Buffer Functions
  1896.   
  1897.      I have prepared a document to be printed while the handlers 
  1898. of program 66 are installed in ram.  The document is shown as 
  1899. Document 1.  This document can be printed with PRG_7DP.TTP, but 
  1900. there is no reason that any other program which uses system 
  1901. functions to send data to the parallel port can't be used.  
  1902. Document 1, SPOOLTST.DOC, has been prepared entirely in lower 
  1903. case characters.  Within the interrupt handler of program 66, 
  1904. there is an instruction that converts lower case characters to 
  1905. upper case.  This instruction wrecks havoc when punctuation and 
  1906. numeric characters are converted, so they have been omitted from 
  1907. SPOOLTST.DOC.  The purpose of document 1 is to assist my 
  1908. descriptions of program 66 with a visual representation of 
  1909. character processing by the handlers installed by the program.  
  1910. Baring major differences between our printers and computers, the 
  1911. first line of SPOOLTST.DOC should appear in lower case, if your 
  1912. printer contains only a single line buffer, and the rest of the 
  1913. document should appear in upper case.
  1914.  
  1915. Document 1. SPOOLTST.DOC has been prepared entirely in 
  1916. lower case so that you can determine which characters 
  1917. are processed by program 66's trap #13 handler and 
  1918. which characters are processed by the interrupt handler 
  1919. when the document is printed.
  1920.  
  1921. punctuation this file dont need no stinking punctuation
  1922. all of the text in this file is lower case and no punctuation is used because
  1923. the purpose of this file is to permit the testing of circular print buffers
  1924. that contain a parallel interface interrupt handler and a trap thirteen handler
  1925. in the interrupt handler just before each character is printed there should be
  1926. an instruction to convert the character to upper case
  1927. all characters printed by the trap thirteen handler will be printed in lower
  1928. case therefore the printed output will separate characters printed by each
  1929. handler
  1930. such a test is conducted to prove that both handlers are functioning as expected
  1931. the number of characters of the file being printed that are processed by the
  1932. trap thirteen handler depends on the size of any buffers which exist between
  1933. the parallel interface port and the printer's print mechanism 
  1934. this is so because such buffers can usually keep up with the flow of characters
  1935. into the trap handler
  1936. when that situation exists the trap handler does not perceive a busy signal
  1937. on pin 13 of the interface therefore no characters are stored in the trap
  1938. thirteen print buffer
  1939. instead the characters are sent immediately to the port
  1940. for example with only my star nx ten connected to the parallel port and with
  1941. its internal five kilobyte buffer disabled there exists within the printer
  1942. only a one line buffer
  1943. therefore the first line of text in this file will be processed by the trap
  1944. thirteen handler because the buffer response is fast enough to accept those
  1945. characters as they flow into the trap handler so no characters are stored in
  1946. the trap buffer
  1947. but as soon as the printer line buffer becomes full the trap thirteen handler
  1948. will begin storing characters in the buffer
  1949. when the attention of the printer returns to the parallel port the logic
  1950. state of pin eleven will drop from high to low and trigger the interrupt
  1951. handler into action
  1952. thereafter all characters will be processed by the interrupt handler
  1953. because the printer line buffer will not be empty again until all characters
  1954. of the file have been printed
  1955. if there is a print buffer between the parallel port and the printer or if the
  1956. printer five kilobyte internal buffer is enabled and if these other buffers are
  1957. able to keep up with the flow of characters into the trap thirteen handler then
  1958. then the interrupt handler may never be invoked
  1959. at least it will not be utilized until any and all external buffers are full
  1960. the point is this if the data transfer rate through the parallel port is fast
  1961. enough so that a character can be sent there as soon as it is detected by the
  1962. trap thirteen handler then all characters that are detected by the trap handler
  1963. are also printed by the trap handler in such cases the parallel port interface
  1964. interrupt handler does not function
  1965.  
  1966.  
  1967.      Significantly, the system functions which exchange data 
  1968. through the parallel interface port do so via trap #13 
  1969. invocations.  Therefore, program 66 installs a custom trap #13 
  1970. handler which intercepts all trap #13 invocations.  When the 
  1971. program 66 specific handler is installed, it preempts whatever 
  1972. trap #13 handler is installed before it; the address of that 
  1973. handler is stored in a variable in program 66.  Those invocations 
  1974. which are not relevant to the transfer of data through the 
  1975. parallel interface are simply passed on to the preempted handler 
  1976. by jumping to its address.
  1977.      Program 66 also installs an interrupt handler that is 
  1978. invoked each time the logic level of pin 11 of the parallel 
  1979. interface port drops from high to low.  The installation of the 
  1980. interrupt handler does not assume any prior installation; that 
  1981. is, it does not save the address of any handler which may be 
  1982. installed before the program 66 custom interrupt handler.  In 
  1983. addition, program 66 creates a buffer in which characters are 
  1984. stored by the trap handler and extracted by the interrupt 
  1985. handler.
  1986.      Refer to figure 9.1 during the following discussion.  Each 
  1987. time the trap #13 handler is invoked by a program trying to send 
  1988. data to the printer, the handler tries to send the character to 
  1989. the printer immediately if program 66's buffer is empty and the 
  1990. logic level of pin 11 of the parallel interface is low.  If the 
  1991. buffer is not empty, or if pin 11 is high, then the character is 
  1992. immediately stored, unless the buffer is full.  If the buffer is 
  1993. full, a loop is executed until there is room for the character in 
  1994. the buffer.  Assume that the buffer is empty because it has not 
  1995. yet been used.  The first pictorial in figure 9.1 illustrates 
  1996. this situation.  Both the insert pointer and the extract pointer 
  1997. contain the address of the first buffer element.
  1998.      Now, assume that a printing program begins to send 
  1999. characters to the printer.  Further, assume that the printer 
  2000. contains an 80 character buffer.  The response of the buffer is 
  2001. quick enough so that the first 80 characters flow through the 
  2002. handler directly to the printer.  Now the printer starts putting 
  2003. characters on paper.  (Note this: the printer will begin to print 
  2004. when its line buffer is full (assuming that any larger printer 
  2005. buffer is disabled) or when it receives a carriage return.)  As 
  2006. soon as the printer's line buffer becomes full logic level of pin 
  2007. 11 goes high long enough so that the trap handler is forced to 
  2008. begin storing characters in program 66's buffer.  The second 
  2009. pictorial in figure 9.1 depicts the situation just after the 
  2010. first character has been stored.  From this point on, no 
  2011. characters will be sent directly from the trap handler, unless 
  2012. some event occurs that permits the extract pointer to catch up 
  2013. with the insert pointer, which is an indication that the buffer 
  2014. is empty.
  2015.      The trap handler continues to insert characters into the 
  2016. buffer in a linear fashion until the insert pointer reaches the 
  2017. end of the buffer; in fact, it stops just short of the end 
  2018. because within the trap handler algorithm the end of buffer test 
  2019. is performed with a simulated insert pointer.  This simulated 
  2020. pointer scouts ahead to determine if the actual pointer can be 
  2021. safely moved.  The third pictorial of figure 9.1 illustrates this 
  2022. instance.  Note that the last element of the buffer never 
  2023. receives a character.  When the end of the buffer is reached, the 
  2024. insert pointer must be wrapped around to the front of the buffer; 
  2025. it is this action that instigates the circular configuration of 
  2026. the buffer; that is, the buffer activity simulates that which it 
  2027. would be if the buffer were circular.  Refer to the fourth 
  2028. pictorial.
  2029.  
  2030. Figure 9.1 Circular buffer pointer activity.  The extract pointer 
  2031. is represented by the letter E; the insert pointer by the letter 
  2032. I.  Characters which have been inserted into the buffer, but have 
  2033. not yet been extracted are represented by the letter C; 
  2034. characters which have been extracted by the letter X.
  2035.  
  2036.  
  2037.  
  2038.  
  2039.  
  2040.  
  2041.  
  2042.  
  2043.  
  2044.  
  2045.  
  2046.  
  2047.  
  2048.  
  2049.  
  2050.  
  2051.  
  2052.  
  2053.  
  2054.  
  2055.  
  2056.  
  2057.  
  2058.  
  2059.  
  2060.  
  2061.  
  2062.  
  2063.  
  2064.  
  2065.  
  2066.  
  2067.  
  2068.  
  2069.  
  2070.      While the insert pointer is placing characters into the 
  2071. buffer at a transfer rate that depends on speed of the trap 
  2072. handler and the transfer rate of the printing program, the 
  2073. interrupt handler continues to extract characters from the 
  2074. buffer.  Remember that the interrupt handler extraction process 
  2075. began when a falling edge of the pulses being placed on pin 11 by 
  2076. the printer invoked the interrupt handler at a time when the 
  2077. buffer contained one or more characters.  (Note this: if program 
  2078. 66's buffer somehow became full, but nothing occurred to cause 
  2079. the logic level on pin 11 to drop from high to low, the interrupt 
  2080. handler extraction process would not begin.)
  2081.      If character insertion continues past the wrap around point, 
  2082. the new characters will simply be placed in previously occupied 
  2083. buffer locations.  If the number of characters sent to the buffer 
  2084. exceeds the buffer capacity, eventually the insert pointer will 
  2085. catch up with the extract pointer; this is the full buffer 
  2086. condition illustrated by pictorial 5 of figure 9.1.  When that 
  2087. occurs, the trap handler wait loop is executed for a short period 
  2088. of time until there is room for the character in holding.  When 
  2089. all characters have been inserted, the insert pointer will be 
  2090. idle, and the extract pointer will arrive at the location to 
  2091. which the insert pointer is pointing.  This event simulates the 
  2092. empty buffer condition depicted by pictorial 6.  I use the word 
  2093. simulate here only to indicate that the buffer still contains the 
  2094. characters not overwritten by insertions, but the buffer is 
  2095. effectively empty because all characters have been sent to the 
  2096. printer.
  2097.      As stated previously, program 66 was prepared for use as a 
  2098. tutorial agent.  Programs 67 and 68 are the practical versions of 
  2099. the program; in these programs the instructions that print out 
  2100. the locations of pertinent addresses have been omitted, as has 
  2101. been the instruction which converts characters in the interrupt 
  2102. handler.  Program 67 has been designed for assembly in the 
  2103. Relocatable mode; program 68 for the PC-relative mode.  Having 
  2104. the two versions to examine will permit you to see the special 
  2105. handling required for the PC-relative version as compared to the 
  2106. Relocatable version.
  2107.  
  2108. Program 67. A practical version of program 66, which is 
  2109. designed for assembly in AssemPro's Relocatable mode.
  2110.  
  2111.  ; Program Name: PRG_7HR.S
  2112.  ;      Version: 1.007
  2113.  
  2114.  ; Assembly Instructions:
  2115.  
  2116.  ;      Assemble in Relocatable mode and save with a TOS or TTP suffix.
  2117.  
  2118.  ; Program Function:
  2119.  
  2120.  ;      Circular printer buffer.  The default buffer size for the TOS version
  2121.  ; is 32,768 bytes.  See PRG_7GR.S for further documentation.
  2122.  
  2123.  ; Execution Instructions:
  2124.  
  2125.  ;      May be executed from the desktop with a TOS or TTP extension.  May be   
  2126.  ; executed from an AUTO folder if it has a PRG extension.
  2127.  
  2128.  ;      If a larger buffer size is desired, the program extension can be
  2129.  ; changed from TOS to TTP.  If a buffer size is declared on the TTP parameter
  2130.  ; line, any size, up to the maximum available machine memory, may be specified.
  2131.  ; When the TTP program is executed, type the desired size, in kilobytes, on the
  2132.  ; parameter line; the maximum number of digits which can be typed on the
  2133.  ; parameter line is 4.  Do not type spaces before the digits.  Do not type
  2134.  ; anything between digits.  Do not type anything after the digits, except the
  2135.  ; Return key.  The OK button may be clicked in lieu of a Return key press.
  2136.  
  2137.  ;      The length of the buffer will be (input X 1024) bytes.  Ex: if input is
  2138.  ; 32, length of the buffer will be 32,768 bytes.  After converting the ASCII
  2139.  ; decimal input to binary, it is much easier to simply shift the binary result
  2140.  ; 10 bits left to multiply by 1024, than it is to multiply it by 1000. 
  2141.  
  2142.  ;      The buffer length (either the default size or the specified size) is
  2143.  ; combined with the program size to determine the parameter which must be
  2144.  ; passed to GEMDOS function $31.
  2145.  
  2146. program_start:                   ; Calculate program size and retain result.
  2147.  lea        -$82(pc), a4         ; Fetch "command line" address.
  2148.  lea        -$80(a4), a0         ; Fetch "basepage" address.
  2149.  lea        program_end(pc), a3  ; Fetch "end of program" address.
  2150.  suba.l     a0, a3               ; Program size is in A3.
  2151.  lea        stack(pc), a7        ; Fetch address of the program stack.
  2152.  
  2153. process_input_parameter:         ; Calculate requested buffer length.
  2154.  lea        length(pc), a0       ; Fetch address of the length variable.
  2155.  moveq      #0, d0               ; Clear counter register.
  2156.  move.b     (a4)+, d0            ; Fetch parameter line character count.
  2157.  beq.s      compute_size         ; Branch if no parameter--use default length.
  2158.  cmpi.b     #4, d0               ; Greater than four test.
  2159.  bgt.s      too_many_characters  ; Branch if more than four characters.
  2160.  subq.b     #1, d0               ; Set up counter.
  2161.  ext.w      d0                   ; Extend to match size of DBRA instruction.
  2162.  moveq      #0, d2               ; Initialize accumulator.
  2163.  moveq      #0, d1               ; Clear scratch register.
  2164. fetch_digit:                     ; Convert input from ASCII decimal to binary.
  2165.  move.b     (a4)+, d1            ; ASCII decimal digit to D1.
  2166.  sub.b      #$30, d1             ; Convert ASCII decimal digit to decimal digit.
  2167.  bmi.s      not_decimal          ; Branch if digit is less than zero.
  2168.  cmp.b      #9, d1               ; Greater than nine test.
  2169.  bgt.s      not_decimal          ; Branch if digit is greater than nine.
  2170. multiply_accumulator_content_by_ten:
  2171.  move.l     d2, d3               ; Copy accumulator in D3.
  2172.  lsl.l      #3, d2               ; Shift accumulator content to multiply by 8.
  2173.  add.l      d3, d2               ; Add D2's original quantity twice to
  2174.  add.l      d3, d2               ; complete the multiplication by 10.
  2175. add_new_digit_to_accumulator_content:
  2176.  add.l      d1, d2               ; Add decimal number in D1 to accumulator.
  2177.  dbra       d0, fetch_digit      ; Loop until d0 becomes negative.
  2178. extend_to_thousands_of_bytes:    ; Length will be (input X 1000) plus 
  2179.  moveq      #10, d3              ; some even number of bytes that is less
  2180.  lsl.l      d3, d2               ; than 1000.  Ex: if input is 32, length
  2181.  move.l     d2, (a0)             ; is 32768.  Store requested buffer size.
  2182. compute_size:                    ; This will be parameter to GEMDOS $31.
  2183.  adda.l     (a0), a3             ; Add buffer length to program size.
  2184. compute_and_store_buffer_end:
  2185.  lea        buffer(pc), a1       ; Fetch buffer starting address.
  2186.  adda.l     (a0), a1             ; Add buffer length to buffer start address.
  2187.  move.l     a1, buffer_end       ; Store buffer_end address.
  2188.  
  2189. install_custom_trap_13_vector:
  2190.  pea        trap_13_handler(pc)  ; Push custom trap handler address onto stack.
  2191.  move.w     #$2D, -(sp)          ; Trap 13 vector number.
  2192.  move.w     #5, -(sp)            ; Function = setexec.
  2193.  trap       #13                  ; Current trap 13 vector returned in D0.
  2194.  addq.l     #8, sp
  2195.  move.l     d0, preempted_trap_13_address
  2196.  
  2197. install_interrupt_handler_vector:
  2198.  pea        interrupt_handler(pc); Push interrupt handler address.
  2199.  move.w     #0, -(sp)            ; Interrupt level for centronics ready.
  2200.  move.w     #$D, -(sp)           ; Functon = XBIOS #13 dec = mfpint.
  2201.  trap       #14
  2202.  addq.l     #8, sp
  2203.  
  2204. relinquish_processor_control:    ; Maintain memory residency.
  2205.  move.w     #0, -(sp)            ; See page 121 of Internals book.
  2206.  move.l     a3, -(sp)            ; Program size (plus buffer length).
  2207.  move.w     #$31, -(sp)          ; Function = ptermres = GEMDOS $31.
  2208.  trap       #1
  2209.  
  2210. not_decimal:
  2211.  pea        message_1(pc)        ; Print error message.
  2212.  move.w     #9, -(sp)            ; Function = c_conws = GEMDOS $9.
  2213.  trap       #1                   ; GEMDOS call
  2214.  addq.l     #6, sp               ; Reset stack pointer to top of stack.
  2215.  bra.s      terminate
  2216.  
  2217. too_many_characters:
  2218.  pea        message_2(pc)        ; Print error message.
  2219.  move.w     #9, -(sp)            ; Function = c_conws = GEMDOS $9.
  2220.  trap       #1                   ; GEMDOS call
  2221.  addq.l     #6, sp               ; Reset stack pointer to top of stack.
  2222.  
  2223. terminate:                       ; Wait for keypress.
  2224.  move.w     #8, -(sp)            ; Function = GEMDOS $8 = cnecin.
  2225.  trap       #1
  2226.  addq.l     #2, sp
  2227.  move.w     #0, -(sp)            ; Function = p_term_old = GEMDOS $0.
  2228.  trap       #1                   ; GEMDOS call.
  2229.  
  2230. interrupt_handler:               ; Parallel interface pin #11 interrupt handler.
  2231.  movem.l    d0/a0-a1, -(sp)      ; Save content of registers used handler.
  2232.  lea        io_buffer(pc), a0    ; Load buffer structure pointer into A0.
  2233.  movea.l    8(a0), a1            ; Content of buffer extract pointer into A1.
  2234.  
  2235.  ; NOTE:    A1 is now a simulated extract pointer.  It contains the location
  2236.  ;          from which the next character is to be fetched.  The simulated
  2237.  ;          pointer is used to scout ahead and determine what would happen
  2238.  ;          if the actual extract pointer were incremented.
  2239.  
  2240.  cmpa.l     12(a0), a1           ; Compare location stored in insert pointer
  2241.                                  ; to location stored in simulated extract
  2242.                                  ; pointer.
  2243.  beq.s      buffer_is_empty      ; Branch if location in insert pointer is equal
  2244.                                  ; to location stored in simulated extract
  2245.                                  ; pointer.
  2246.  addq.l     #1, a1               ; Increment simulated extract pointer to
  2247.                                  ; location of next character.
  2248.  cmpa.l     16(a0), a1           ; Compare buffer_end to location stored in
  2249.                                  ; simulated extract pointer.
  2250.  bcs.s      end_of_buffer_not_reached
  2251.  movea.l    (a0), a1             ; Move simulated extract pointer to front of
  2252.                                  ; buffer by storing buffer address in simulated
  2253.                                  ; extract pointer because the simulated extract
  2254.                                  ; pointer has reached the buffer limit.
  2255. end_of_buffer_not_reached:
  2256.  move.b     (a1), d0             ; Fetch character at location stored in
  2257.                                  ; simulated extract pointer.
  2258.  move.l     a1, 8(a0)            ; Put location stored in simulated extract
  2259.                                  ; pointer in actual extract pointer.
  2260. print_character:
  2261.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  2262.  move.b     #$F, (a1)            ; Select port B (register 17) of PSG.
  2263.  move.b     d0, 2(a1)            ; Write character to PSG register 17..
  2264.  move.b     #$E, (a1)            ; Select port A (register 16) of PSG.
  2265.  move.b     (a1), d0             ; Get current register 16 value.
  2266.  
  2267. strobe_low:                   
  2268.  andi.b     #$DF, d0             ; Bit 5 to zero.          
  2269.  move.b     d0, 2(a1)            ; Bit 5 of port A to zero. 
  2270.                               
  2271. strobe_high:                  
  2272.  ori.b      #$20, d0             ; Bit 5 to one.       
  2273.  move.b     d0, 2(a1)            ; Bit 5 of port A to one.   
  2274.  
  2275. buffer_is_empty:
  2276.  bclr       #0, $FFFA11          ; Clear MFP in service bit.
  2277.  movem.l    (sp)+, d0/a0-a1      ; Restore registers used by handler.
  2278.  rte
  2279.  
  2280. trap_13_handler:
  2281.  move.l    usp, a0               ; Load address of current top of user stack.
  2282. get_processor_status:
  2283.  btst      #5, (sp)              ; User mode test.
  2284.  beq.s     was_user_mode         ; No adjustment is necessary if the
  2285.                                  ; processor was in user mode.
  2286.  movea.l   sp, a0                ; Load current top of supervisor stack.
  2287.  addq.l    #6, a0                ; Adjust SSP for user mode type data access.
  2288.  
  2289. was_supervisor_mode:
  2290. was_user_mode:                   ; Processing for either mode follows.
  2291.  tst.w      2(a0)                ; Is device the printer?
  2292.  bne        not_printer 
  2293.  cmpi.w     #3, (a0)             ; Writing a character to a device?
  2294.  bne        not_bconout_call
  2295.  
  2296. fetch_character:
  2297.  move.w     4(a0), d0
  2298.  move.w     #$2700, SR           ; Disable interrupts.
  2299.  
  2300.  ; NOTE: If interrupts are not disabled during this critical portion of the
  2301.  ;       trap handler algorithm, the interrupt handler could be invoked and
  2302.  ;       screw up the buffer pointers.
  2303.  
  2304. buffer_empty_test:               ; Want to know if buffer is empty or not.
  2305.  lea        io_buffer(pc), a0    ; Fetch buffer structure address.
  2306.  
  2307.  ; NOTE: The address stored in the insert pointer is placed in register A1
  2308.  ;       for two reasons; (1) for comparison with the extract pointer,
  2309.  ;       (2) in order to simulate an increment of the address later.
  2310.  
  2311.  movea.l    12(a0), a1           ; Fetch buffer insert pointer.
  2312.  cmpa.l     8(a0), a1            ; Compare content of extract pointer with
  2313.                                  ; content of insert pointer to determine if
  2314.  bne.s      buffer_not_empty     ; buffer is empty.
  2315. try_to_print_character:
  2316.  btst       #0, $FFFA01          ; Is printer (or hardware print buffer)
  2317.                                  ; busy (or off)?
  2318.  bne.s      printer_busy         ; Branch if either peripheral is busy
  2319.                                  ; or off.
  2320. print_character_if_not_busy:     ; Means peripherals are not busy and are on.
  2321.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  2322.  move.b     #$F, (a1)            ; Select port B of PSG.
  2323.  move.b     d0, 2(a1)            ; Write character to PSG.
  2324.  move.b     #$E, (a1)            ; Select port A of PSG.
  2325.  move.b     (a1), d1             ; Get current register value.
  2326.  
  2327. _strobe_low:
  2328.  andi.b     #$DF, d1
  2329.  move.b     d1, 2(a1)
  2330. _strobe_high:
  2331.  ori.b      #$20, d1
  2332.  move.b     d1, 2(a1)    
  2333.  moveq      #-1, d0              ; In case calling source wants to know
  2334.  rte                             ; that character was dispatched.
  2335.  
  2336. buffer_not_empty:
  2337. printer_busy:
  2338.  addq.l     #1, a1               ; Simulate insert location increment.
  2339.  cmpa.l     16(a0), a1           ; Compare buffer_end to simulated insert
  2340.                                  ; location.
  2341.  bcs.s      not_end_of_buffer    ; Branch if simulated location is not equal
  2342.                                  ; to the end of buffer location.
  2343.  movea.l    (a0), a1             ; Simulate a reset of insert pointer to the
  2344.                                  ; first buffer location; that is, the front of
  2345.                                  ; the buffer is the new simulated insert
  2346.                                  ; location.
  2347. not_end_of_buffer:
  2348.  cmpa.l     8(a0), a1            ; Compare location stored in extract pointer
  2349.                                  ; to simulated insert location.
  2350.  beq.s      buffer_full          ; If they are equal, the buffer is full.
  2351. store_character_in_buffer: 
  2352.  move.b     d0, (a1)             ; Store character at the now valid location.        
  2353.  move.l     a1, 12(a0)           ; Store simulated location as the new insert
  2354.                                  ; location in the insert pointer.
  2355.  moveq      #-1, d0              ; In case calling source wants to know
  2356.  rte                             ; that character was dispatched.
  2357.  
  2358. buffer_full:
  2359.  move.l     $4BA, d1             ; Fetch current _hz_200 value.
  2360.  add.l      #$BB8, d1            ; Add count for desired elapsed time value.
  2361.  
  2362.  ; NOTE: The content of $4BA is incremented every 5 milliseconds.  Compute
  2363.  ;       the value to add to the current count by dividing the number of
  2364.  ;       milliseconds to wait (equals number of seconds times 1000) by 5.
  2365.  ;       In this case, to wait 15 seconds, 15000/5 = 3000 = $BB8.
  2366.  
  2367.  move.w     #$2300, SR           ; Enable interrupts.
  2368.  
  2369.  ; NOTE: Here interrupts are enabled because the trap handler algorithm may
  2370.  ;       be idle for some time.  During this idle time, invoked interrupts
  2371.  ;       would have to remain pending.  There is a maximum amount of time
  2372.  ;       that an interrupt may remain pending, and that time depends on the
  2373.  ;       number of instructions a program can execute with the interrupts
  2374.  ;       masked off.  The amount of time that interrupts can be disabled is
  2375.  ;       usually restricted.
  2376.  
  2377.  ;       Reference: Programming the 68000 by Steve Williams, page 366.
  2378.  ;                  Published by SYBEX, Inc. 1985
  2379.  
  2380. wait:
  2381.  cmpa.l     8(a0), a1            ; Compare location stored in extract pointer to
  2382.                                  ; location stored in simulated insert pointer.
  2383.  bne.s      store_character_in_buffer
  2384.  cmp.l      $4BA, d1             ; Elapsed time check.
  2385.  bhi.s      wait                 ; Wait until there is room in the buffer for
  2386.                                  ; another character, or until the programmed
  2387.                                  ; wait time has expired.
  2388.  moveq      #0, d0               ; In case calling source wants to know
  2389.  rte                             ; that character was not dispatched.
  2390.  
  2391. not_bconout_call:
  2392.  cmpi.w     #8, (a0)             ; Requesting output device status?
  2393.  bne.s      not_bcostat_call
  2394.  
  2395.  ; NOTE: The buffer status is returned in response to the bcostat invocation,
  2396.  ;       not the printer status.
  2397.  
  2398. determine_software_buffer_status:
  2399.  moveq      #-1, d0              ; Assume buffer is not full.
  2400.  lea        io_buffer(pc), a0    ; Fetch buffer structure pointer.
  2401.  move.l     12(a0), a1           ; Fetch insert pointer for simulation.
  2402.  addq.l     #1, a1               ; Simulate moving insert pointer to next buffer
  2403.                                  ; location.
  2404.  cmpa.l     16(a0), a1           ; Compare buffer_end to simulated insert
  2405.                                  ; location.
  2406.  bcs.s      not_buffer_end       
  2407.  movea.l    (a0), a1             ; Simulate resetting insert pointer to front of
  2408.                                  ; buffer.
  2409. not_buffer_end:
  2410.  cmpa.l     8(a0), a1            ; Compare location stored in extract pointer
  2411.                                  ; to simulated insert location.
  2412.  bne.s      buffer_not_full      ; Buffer not full if extract location is not
  2413.                                  ; equal to simulated insert location.
  2414.  moveq      #0, d0               ; Return buffer status to calling source.
  2415. buffer_not_full:                 ; Return 0 if full, -1 if not.
  2416.  rte
  2417.  
  2418. not_bcostat_call:
  2419. not_printer:
  2420.  movea.l    preempted_trap_13_address, a0
  2421.  jmp        (a0)                 ; JUMP TO PREEMPTED TRAP #13 HANDLER.
  2422.  
  2423.  data
  2424. message_1:    dc.b 'Nondecimal character digit detected on parameter line.'
  2425.               dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
  2426. message_2:    dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
  2427.               dc.b 'The program converts the input to thousands of bytes.'
  2428.               dc.b  $D,$A,'Default buffer size is 32,768 bytes.',$D,$A
  2429.               dc.b  'Press Return key to terminate.',$D,$A,0
  2430.  align
  2431.  
  2432.  ; The variable "io_buffer" is a pointer to the print buffer's starting
  2433.  ; address.  The address of io_buffer also serves as the address of a
  2434.  ; structure that is composed of the variables io_buffer, at offset 0,
  2435.  ; length, at offset 4, extract_pointer (also called the buffer output pointer),
  2436.  ; at offset 8, and insert_pointer (also called the buffer input pointer), at
  2437.  ; offset 12.  In this service, the address of io_buffer provides the means
  2438.  ; by which the other members of the structure may be referrenced.
  2439.  
  2440.  ; Characters are placed in the buffer at the address stored in the variable
  2441.  ; 'insert_pointer'; they are extracted from the buffer and sent to the printer
  2442.  ; from the address stored in the variable 'extract_pointer'.
  2443.  
  2444.  ; Time is saved because only the address of io_buffer need be loaded into
  2445.  ; an address register.  The other variables in the structure are referenced
  2446.  ; by adding their offsets to the content of the chosen address register.
  2447.  
  2448.  ; NOTE: Because this program is assembled in Relocatable mode, the run time
  2449.  ;       address for the variable "buffer" will be stored in the four pointers
  2450.  ;       at which it is declared in the io_buffer structure below.
  2451.  
  2452. io_buffer:       dc.l  buffer     ; Pointer to buffer's starting address. 
  2453. length:          dc.l   $8000     ; Default buffer length = 32,768 bytes.
  2454. extract_pointer: dc.l  buffer     ; Buffer character output pointer.
  2455. insert_pointer:  dc.l  buffer     ; Buffer character input pointer.
  2456. buffer_end:      dc.l  buffer     ; Last address in buffer = buffer + length.
  2457.  
  2458.  bss
  2459. preempted_trap_13_address:  ds.l   1
  2460.  
  2461.                  ds.l  96
  2462. stack:           ds.l   1
  2463. buffer:          ds.l   0     ; Length of buffer must be added to length of
  2464. program_end:     ds.l   0     ; program to determine total size to pass to
  2465.  end                          ; GEMDOS function $31.
  2466.  
  2467. Program 68. A practical version of program 66, which is 
  2468. designed for assembly in AssemPro's PC-relative mode.
  2469.  
  2470.  ; Program Name: PRG_7HP.S
  2471.  ;      Version: 1.007
  2472.  
  2473.  ; Assembly Instructions:
  2474.  
  2475.  ;      Assemble in PC-relative mode and save with a TOS or TTP suffix.
  2476.  
  2477.  ; Program Function:
  2478.  
  2479.  ;      Circular printer buffer.  The default buffer size for the TOS version
  2480.  ; is 32,768 bytes.  See PRG_7GR.S for further documentation.
  2481.  
  2482.  ; Execution Instructions:
  2483.  
  2484.  ;      May be executed from the desktop with a TOS or TTP extension.  May be   
  2485.  ; executed from an AUTO folder if it has a PRG extension.
  2486.  
  2487.  ;      If a larger buffer size is desired, the program extension can be
  2488.  ; changed from TOS to TTP.  If a buffer size is declared on the TTP parameter
  2489.  ; line, any size, up to the maximum available machine memory, may be specified.
  2490.  ; When the TTP program is executed, type the desired size, in kilobytes, on the
  2491.  ; parameter line; the maximum number of digits which can be typed on the
  2492.  ; parameter line is 4.  Do not type spaces before the digits.  Do not type
  2493.  ; anything between digits.  Do not type anything after the digits, except the
  2494.  ; Return key.  The OK button may be clicked in lieu of a Return key press.
  2495.  
  2496.  ;      The length of the buffer will be (input X 1024) bytes.  Ex: if input is
  2497.  ; 32, length of the buffer will be 32,768 bytes.  After converting the ASCII
  2498.  ; decimal input to binary, it is much easier to simply shift the binary result
  2499.  ; 10 bits left to multiply by 1024, than it is to multiply it by 1000. 
  2500.  
  2501.  ;      The buffer length (either the default size or the specified size) is
  2502.  ; combined with the program size to determine the parameter which must be
  2503.  ; passed to GEMDOS function $31.
  2504.  
  2505. program_start:                   ; Calculate program size and retain result.
  2506.  lea        -$82(pc), a4         ; Fetch "command line" address.
  2507.  lea        -$80(a4), a0         ; Fetch "basepage" address.
  2508.  lea        program_end, a3      ; Fetch "end of program" address.
  2509.  suba.l     a0, a3               ; Program size is in A3.
  2510.  lea        stack, a7            ; Fetch address of the program stack.
  2511.  
  2512. process_input_parameter:         ; Calculate requested buffer length.
  2513.  lea        length, a0           ; Fetch address of the length variable.
  2514.  moveq      #0, d0               ; Clear counter register.
  2515.  move.b     (a4)+, d0            ; Fetch parameter line character count.
  2516.  beq.s      compute_size         ; Branch if no parameter--use default length.
  2517.  cmpi.b     #4, d0               ; Greater than four test.
  2518.  bgt        too_many_characters  ; Branch if more than four characters.
  2519.  subq.b     #1, d0               ; Set up counter.
  2520.  ext.w      d0                   ; Extend to match size of DBRA instruction.
  2521.  moveq      #0, d2               ; Initialize accumulator.
  2522.  moveq      #0, d1               ; Clear scratch register.
  2523. fetch_digit:                     ; Convert input from ASCII decimal to binary.
  2524.  move.b     (a4)+, d1            ; ASCII decimal digit to D1.
  2525.  sub.b      #$30, d1             ; Convert ASCII decimal digit to decimal digit.
  2526.  bmi.s      not_decimal          ; Branch if digit is less than zero.
  2527.  cmp.b      #9, d1               ; Greater than nine test.
  2528.  bgt.s      not_decimal          ; Branch if digit is greater than nine.
  2529. multiply_accumulator_content_by_ten:
  2530.  move.l     d2, d3               ; Copy accumulator in D3.
  2531.  lsl.l      #3, d2               ; Shift accumulator content to multiply by 8.
  2532.  add.l      d3, d2               ; Add D2's original quantity twice to
  2533.  add.l      d3, d2               ; complete the multiplication by 10.
  2534. add_new_digit_to_accumulator_content:
  2535.  add.l      d1, d2               ; Add decimal number in D1 to accumulator.
  2536.  dbra       d0, fetch_digit      ; Loop until d0 becomes negative.
  2537. extend_to_thousands_of_bytes:    ; Length will be (input X 1000) plus 
  2538.  moveq      #10, d3              ; some even number of bytes that is less
  2539.  lsl.l      d3, d2               ; than 1000.  Ex: if input is 32, length
  2540.  move.l     d2, (a0)             ; is 32768.  Store requested buffer size.
  2541. compute_size:                    ; This will be parameter to GEMDOS $31.
  2542.  adda.l     (a0), a3             ; Add buffer length to program size.
  2543. store_io_buffer_structure_components:
  2544.  
  2545.  ; NOTE: This program is to be assembled in PC-relative mode; therefore, the
  2546.  ;       buffer start address must be explicitly stored in the appropriate
  2547.  ;       io_buffer structure components.  In the Relocatable assembled version
  2548.  ;       of the program, this need not be done because the operating system
  2549.  ;       conveniently handles this task when the program is loaded for
  2550.  ;       execution, forcing the run time buffer start address into each
  2551.  ;       pointer at which it is declared in the data section of the program.
  2552.  
  2553.  lea        buffer, a1           ; Fetch buffer starting address.
  2554.  lea        io_buffer, a2        ; Fetch io_buffer structure base address.
  2555.  move.l     a1, (a2)             ; Buffer starting address to base pointer.
  2556.  move.l     a1, 8(a2)            ; Also to extract_pointer.
  2557.  move.l     a1, 12(a2)           ; Also to insert_pointer. 
  2558.  adda.l     (a0), a1             ; Add buffer length to buffer start address.
  2559.  move.l     a1, 16(a2)           ; Store buffer_end address.
  2560.  
  2561. install_custom_trap_13_vector:
  2562.  pea        trap_13_handler      ; Push custom trap handler address onto stack.
  2563.  move.w     #$2D, -(sp)          ; Trap 13 vector number.
  2564.  move.w     #5, -(sp)            ; Function = setexec.
  2565.  trap       #13                  ; Current trap 13 vector returned in D0.
  2566.  addq.l     #8, sp
  2567.  lea        preempted_trap_13_address, a0
  2568.  move.l     d0, (a0)
  2569.  
  2570. install_interrupt_handler_vector:
  2571.  pea        interrupt_handler    ; Push interrupt handler address.
  2572.  move.w     #0, -(sp)            ; Interrupt level for centronics ready.
  2573.  move.w     #$D, -(sp)           ; Functon = XBIOS #13 dec = mfpint.
  2574.  trap       #14
  2575.  addq.l     #8, sp
  2576.  
  2577. relinquish_processor_control:    ; Maintain memory residency.
  2578.  move.w     #0, -(sp)            ; See page 121 of Internals book.
  2579.  move.l     a3, -(sp)            ; Program size (plus buffer length).
  2580.  move.w     #$31, -(sp)          ; Function = ptermres = GEMDOS $31.
  2581.  trap       #1
  2582.  
  2583. not_decimal:
  2584.  pea        message_1            ; Print error message.
  2585.  move.w     #9, -(sp)            ; Function = c_conws = GEMDOS $9.
  2586.  trap       #1                   ; GEMDOS call
  2587.  addq.l     #6, sp               ; Reset stack pointer to top of stack.
  2588.  bra.s      terminate
  2589.  
  2590. too_many_characters:
  2591.  pea        message_2            ; Print error message.
  2592.  move.w     #9, -(sp)            ; Function = c_conws = GEMDOS $9.
  2593.  trap       #1                   ; GEMDOS call
  2594.  addq.l     #6, sp               ; Reset stack pointer to top of stack.
  2595.  
  2596. terminate:                       ; Wait for keypress.
  2597.  move.w     #8, -(sp)            ; Function = GEMDOS $8 = cnecin.
  2598.  trap       #1
  2599.  addq.l     #2, sp
  2600.  move.w     #0, -(sp)            ; Function = p_term_old = GEMDOS $0.
  2601.  trap       #1                   ; GEMDOS call.
  2602.  
  2603. interrupt_handler:               ; Parallel interface interrupt handler.
  2604.  movem.l    d0/a0-a1, -(sp)      ; Save content of registers used by handler.
  2605.  lea        io_buffer, a0        ; Load buffer structure pointer into A0.
  2606.  movea.l    8(a0), a1            ; Content of buffer extract pointer into A1.
  2607.  
  2608.  ; NOTE:    A1 is now a simulated extract pointer.  It contains the location
  2609.  ;          from which the next character is to be fetched.  The simulated
  2610.  ;          pointer is used to scout ahead and determine what would happen
  2611.  ;          if the actual extract pointer were incremented.
  2612.  
  2613.  cmpa.l     12(a0), a1           ; Compare location stored in insert pointer
  2614.                                  ; to location stored in simulated extract
  2615.                                  ; pointer.
  2616.  beq.s      buffer_is_empty      ; Branch if location in insert pointer is equal
  2617.                                  ; to location stored in simulated extract
  2618.                                  ; pointer.
  2619.  addq.l     #1, a1               ; Increment simulated extract pointer to
  2620.                                  ; location of next character.
  2621.  cmpa.l     16(a0), a1           ; Compare buffer_end to location stored in
  2622.                                  ; simulated extract pointer.
  2623.  bcs.s      end_of_buffer_not_reached
  2624.  movea.l    (a0), a1             ; Move simulated extract pointer to front of
  2625.                                  ; buffer by storing buffer address in simulated
  2626.                                  ; extract pointer because the simulated extract
  2627.                                  ; pointer has reached the buffer limit.
  2628. end_of_buffer_not_reached:
  2629.  move.b     (a1), d0             ; Fetch character at location stored in
  2630.                                  ; simulated extract pointer.
  2631.  move.l     a1, 8(a0)            ; Put location stored in simulated extract
  2632.                                  ; pointer in actual extract pointer.
  2633. print_character:
  2634.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  2635.  move.b     #$F, (a1)            ; Select port B (register 17) of PSG.
  2636.  move.b     d0, 2(a1)            ; Write character to PSG register 17.
  2637.  move.b     #$E, (a1)            ; Select port A (register 16) of PSG.
  2638.  move.b     (a1), d0             ; Get current register 16 value.
  2639.  
  2640. strobe_low:                   
  2641.  andi.b     #$DF, d0             ; Bit 5 to zero.          
  2642.  move.b     d0, 2(a1)            ; Bit 5 of port A to zero.
  2643.                               
  2644. strobe_high:                  
  2645.  ori.b      #$20, d0             ; Bit 5 to one.
  2646.  move.b     d0, 2(a1)            ; Bit 5 of port A to one.
  2647.  
  2648. buffer_is_empty:
  2649.  bclr       #0, $FFFA11          ; Clear MFP in service bit.
  2650.  movem.l    (sp)+, d0/a0-a1      ; Restore registers used by handler.
  2651.  rte
  2652.  
  2653. trap_13_handler:
  2654.  move.l    usp, a0               ; Load address of current top of user stack.
  2655. get_processor_status:
  2656.  btst      #5, (sp)              ; User mode test.
  2657.  beq.s     was_user_mode         ; No adjustment is necessary if the
  2658.                                  ; processor was in user mode.
  2659.  movea.l   sp, a0                ; Load current top of supervisor stack.
  2660.  addq.l    #6, a0                ; Adjust SSP for user mode type data access.
  2661.  
  2662. was_supervisor_mode:
  2663. was_user_mode:                   ; Processing for either mode follows.
  2664.  tst.w      2(a0)                ; Is device the printer?
  2665.  bne        not_printer 
  2666.  cmpi.w     #3, (a0)             ; Writing a character to a device?
  2667.  bne        not_bconout_call
  2668.  
  2669. fetch_character:
  2670.  move.w     4(a0), d0
  2671.  move.w     #$2700, SR           ; Disable interrupts.
  2672.  
  2673.  ; NOTE: If interrupts are not disabled during this critical portion of the
  2674.  ;       trap handler algorithm, the interrupt handler could be invoked and
  2675.  ;       screw up the buffer pointers.
  2676.  
  2677. buffer_empty_test:               ; Want to know if buffer is empty or not.
  2678.  lea        io_buffer, a0        ; Fetch buffer structure address.
  2679.  
  2680.  ; NOTE: The address stored in the insert pointer is placed in register A1
  2681.  ;       for two reasons; (1) for comparison with the extract pointer,
  2682.  ;       (2) in order to simulate an increment of the address later.
  2683.  
  2684.  movea.l    12(a0), a1           ; Fetch content of buffer insert pointer.
  2685.  cmpa.l     8(a0), a1            ; Compare content of extract pointer with
  2686.                                  ; content of insert pointer to determine if
  2687.  bne        buffer_not_empty     ; buffer is empty.
  2688. try_to_print_character:
  2689.  btst       #0, $FFFA01          ; Is printer (or hardware print buffer)
  2690.                                  ; busy (or off)?
  2691.  bne.s      printer_busy         ; Branch if either peripheral is busy
  2692.                                  ; or off.
  2693. print_character_if_not_busy:     ; Means peripherals are not busy and are on.
  2694.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  2695.  move.b     #$F, (a1)            ; Select port B of PSG.
  2696.  move.b     d0, 2(a1)            ; Write character to PSG.
  2697.  move.b     #$E, (a1)            ; Select port A of PSG.
  2698.  move.b     (a1), d1             ; Get current register value.
  2699.  
  2700. _strobe_low:
  2701.  andi.b     #$DF, d1
  2702.  move.b     d1, 2(a1)
  2703. _strobe_high:
  2704.  ori.b      #$20, d1
  2705.  move.b     d1, 2(a1)    
  2706.  moveq      #-1, d0              ; In case calling source wants to know
  2707.  rte                             ; that character was dispatched.
  2708.  
  2709. buffer_not_empty:
  2710. printer_busy:
  2711.  addq.l     #1, a1               ; Simulate insert location increment.
  2712.  cmpa.l     16(a0), a1           ; Compare buffer_end to simulated insert
  2713.                                  ; location.
  2714.  bcs.s      not_end_of_buffer    ; Branch if simulated location is not equal
  2715.                                  ; to the end of buffer location.
  2716.  movea.l    (a0), a1             ; Simulate a reset of insert pointer to the
  2717.                                  ; first buffer location; that is, the front of
  2718.                                  ; the buffer is the new simulated insert
  2719.                                  ; location.
  2720. not_end_of_buffer:
  2721.  cmpa.l     8(a0), a1            ; Compare location stored in extract pointer
  2722.                                  ; to simulated insert location.
  2723.  beq.s      buffer_full          ; If they are equal, the buffer is full.
  2724. store_character_in_buffer: 
  2725.  move.b     d0, (a1)             ; Store character at the now valid location.        
  2726.  move.l     a1, 12(a0)           ; Store simulated location as the new insert
  2727.                                  ; location in the insert pointer.
  2728.  moveq      #-1, d0              ; In case calling source wants to know
  2729.  rte                             ; that character was dispatched.
  2730.  
  2731. buffer_full:
  2732.  move.l     $4BA, d1             ; Fetch current _hz_200 value.
  2733.  add.l      #$BB8, d1            ; Add count for desired elapsed time value.
  2734.  
  2735.  ; NOTE: The content of $4BA is incremented every 5 milliseconds.  Compute
  2736.  ;       the value to add to the current count by dividing the number of
  2737.  ;       milliseconds to wait (equals number of seconds times 1000) by 5.
  2738.  ;       In this case, to wait 15 seconds, 15000/5 = 3000 = $BB8.
  2739.  
  2740.  move.w     #$2300, SR           ; Enable interrupts.
  2741.  
  2742.  ; NOTE: Here interrupts are enabled because the trap handler algorithm may
  2743.  ;       be idle for some time.  During this idle time, invoked interrupts
  2744.  ;       would have to remain pending.  There is a maximum amount of time
  2745.  ;       that an interrupt may remain pending, and that time depends on the
  2746.  ;       number of instructions a program can execute with the interrupts
  2747.  ;       masked off.  The amount of time that interrupts can be disabled is
  2748.  ;       usually restricted.
  2749.  
  2750.  ;       Reference: Programming the 68000 by Steve Williams, page 366.
  2751.  ;                  Published by SYBEX, Inc. 1985
  2752.                   
  2753. wait:
  2754.  cmpa.l     8(a0), a1            ; Compare location stored in extract pointer to
  2755.                                  ; location stored in simulated insert pointer.
  2756.  bne.s      store_character_in_buffer
  2757.  cmp.l      $4BA, d1             ; Elapsed time check.
  2758.  bhi.s      wait                 ; Wait until there is room in the buffer for
  2759.                                  ; another character, or until the programmed
  2760.                                  ; wait time has expired.
  2761.  moveq      #0, d0               ; In case calling source wants to know
  2762.  rte                             ; that character was not dispatched.
  2763.  
  2764. not_bconout_call:
  2765.  cmpi.w     #8, (a0)             ; Requesting output device status?
  2766.  bne.s      not_bcostat_call
  2767.  
  2768.  ; NOTE: The buffer status is returned in response to the bcostat invocation,
  2769.  ;       not the printer status.
  2770.  
  2771. determine_software_buffer_status:
  2772.  moveq      #-1, d0              ; Assume buffer is not full.
  2773.  lea        io_buffer, a0        ; Fetch buffer structure pointer.
  2774.  move.l     12(a0), a1           ; Fetch insert pointer for simulation.
  2775.  addq.l     #1, a1               ; Simulate moving insert pointer to next buffer
  2776.                                  ; location.
  2777.  cmpa.l     16(a0), a1           ; Compare buffer_end to simulated insert
  2778.                                  ; location.
  2779.  bcs.s      not_buffer_end       
  2780.  movea.l    (a0), a1             ; Simulate resetting insert pointer to front of
  2781.                                  ; buffer.
  2782. not_buffer_end:
  2783.  cmpa.l     8(a0), a1            ; Compare location stored in extract pointer
  2784.                                  ; to simulated insert location.
  2785.  bne.s      buffer_not_full      ; Buffer not full if extract location is not
  2786.                                  ; equal to simulated insert location.
  2787.  moveq      #0, d0               ; Return buffer status to calling source.
  2788. buffer_not_full:                 ; Return 0 if full, -1 if not.
  2789.  rte
  2790.  
  2791. not_bcostat_call:
  2792. not_printer:
  2793.  lea        preempted_trap_13_address, a0
  2794.  movea.l    (a0), a0
  2795.  jmp        (a0)                 ; JUMP TO PREEMPTED TRAP #13 HANDLER.
  2796.  
  2797.  data
  2798. message_1:    dc.b 'Nondecimal character digit detected on parameter line.'
  2799.               dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
  2800. message_2:    dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
  2801.               dc.b 'The program converts the input to thousands of bytes.'
  2802.               dc.b  $D,$A,'Default buffer size is 32,768 bytes.',$D,$A
  2803.               dc.b  'Press Return key to terminate.',$D,$A,0
  2804.  align
  2805.  
  2806.  ; The variable "io_buffer" is a pointer to the print buffer's starting
  2807.  ; address.  The address of io_buffer also serves as the address of a
  2808.  ; structure that is composed of the variables io_buffer, at offset 0,
  2809.  ; length, at offset 4, extract_pointer (also called the buffer output pointer),
  2810.  ; at offset 8, and insert_pointer (also called the buffer input pointer), at
  2811.  ; offset 12.  In this service, the address of io_buffer provides the means
  2812.  ; by which the other members of the structure may be referrenced.
  2813.  
  2814.  ; Characters are placed in the buffer at the address stored in the variable
  2815.  ; 'insert_pointer'; they are extracted from the buffer and sent to the printer
  2816.  ; from the address stored in the variable 'extract_pointer'.
  2817.  
  2818.  ; Time is saved because only the address of io_buffer need be loaded into
  2819.  ; an address register.  The other variables in the structure are referenced
  2820.  ; by adding their offsets to the content of the chosen address register.
  2821.  
  2822.  ; NOTE: Even though the variable "buffer" is shown as being "stored" in
  2823.  ;       four pointers in the io_buffer structure, remember that IT DOES
  2824.  ;       NOT HAPPEN when a program is assembled in PC-relative mode.  The
  2825.  ;       address stored in those pointers during assembly is the ASSEMBLY
  2826.  ;       TIME address.  Therefore, the RUN TIME address must be explicitly
  2827.  ;       stored in those pointers for a PC-relative assembled program.
  2828.  
  2829. io_buffer:       dc.l  buffer     ; Pointer to buffer's starting address. 
  2830. length:          dc.l   $8000     ; Default buffer length = 32,768 bytes.
  2831. extract_pointer: dc.l  buffer     ; Buffer character output pointer.
  2832. insert_pointer:  dc.l  buffer     ; Buffer character input pointer.
  2833. buffer_end:      dc.l  buffer     ; Last address in buffer = buffer + length.
  2834.  
  2835.  bss
  2836. preempted_trap_13_address:  ds.l   1
  2837.  
  2838.                  ds.l  96
  2839. stack:           ds.l   1
  2840. buffer:          ds.l   0     ; Length of buffer must be added to length of
  2841. program_end:     ds.l   0     ; program to determine total size to pass to
  2842.  end                          ; GEMDOS function $31.
  2843.  
  2844.  
  2845. A Linear Print Buffer
  2846.  
  2847.      Circular print buffers are archaic, in spite of the fact 
  2848. that they are still in use.  Remember that COBOL is also still in 
  2849. use, as is PASCAL, BASIC, and FORTRAN.  I'm not saying that 
  2850. archaic tools should not be used; I occasionally use them myself.  
  2851. But I prefer faster more powerful tools when they are available 
  2852. and applicable.  In that spirit, I am going to introduce you to a 
  2853. different type of buffer.  At first, it will appear to be 
  2854. cumbersome.  That is only because we have not yet reached the 
  2855. point in the book at which desk accessories and the file selector 
  2856. are among the tools available.  Trust me, I know what I'm doing.  
  2857. Program 69 installs a linear buffer and a parallel interface pin 
  2858. #11 interrupt handler.  Its companion, program 70, locates the 
  2859. address of program 69's buffer, then reads a file and stores the 
  2860. file directly in the buffer.  When the entire file has been 
  2861. stored, program 70 initiates the interrupt controlled printing 
  2862. process by invoking the handler installed by program 69.  The 
  2863. speed differential between a printing program/circular buffer 
  2864. combination and a printing program/linear buffer combination will 
  2865. be quite startling.  Of course there will be quantitative 
  2866. analyses shortly.
  2867.  
  2868. Program 69. This program installs a linear buffer and a 
  2869. parallel interface pin #11 interrupt handler; it must 
  2870. be executed prior to the execution of program 70.
  2871.  
  2872.  ; Program Name: PRG_7IP.S
  2873.  ;      Version: 1.005
  2874.  
  2875.  ; Assembly Instructions:
  2876.  
  2877.  ;      Assemble in PC-relative mode and save with a TOS and a TTP suffix.
  2878.  
  2879.  ; Program Function:
  2880.  
  2881.  ;      Linear printer buffer.  The default buffer size is 32,768 bytes.
  2882.  ; This program does not use a circular queue; instead, it uses a linear
  2883.  ; queue; therefore, no custom trap #13 handler is installed by this program;
  2884.  ; only an interrupt handler is required.
  2885.  
  2886.  ;      But this interrupt handler is different from the one used for the
  2887.  ; programs that established a circular buffer.  The data read from a file is
  2888.  ; placed in the handler's buffer by the GEMDOS $3F function.  PRG_7JP.TTP does
  2889.  ; this by locating the buffer and passing its address as a parameter to the
  2890.  ; the GEMDOS $3F function.  In addition, PRG_7JP.TTP stores the length of the
  2891.  ; file in this program's filesize variable so that the interrupt handler can
  2892.  ; disable itself at the end of file.  
  2893.  
  2894.  ;      After the data has been transferred from the file to the buffer,
  2895.  ; PRG_7JP.TTP initiates the transfer through the parallel port by sending
  2896.  ; a NULL character through the port.  When the printer receives the NULL
  2897.  ; character, it will print nothing, but the falling edge of pin 11's busy
  2898.  ; indicative pulse will invoke the interrupt handler so that it can send the
  2899.  ; first character of the file.  The signal send by the printer after receiving
  2900.  ; that character and after receipt of each character thereafter will perpetuate
  2901.  ; the interrupt handler process until the end of the file has been reached. 
  2902.  
  2903.  ;      As the interrupt handler transfers data from the buffer through the
  2904.  ; parallel port, it decrements the variable "filesize".  It stops sending
  2905.  ; when filesize = 0.
  2906.  
  2907.  ;      The two programs, PRG_7IP.S and PRG_7JP.S have been designed to work
  2908.  ; together.  This design was chosen more for its tutorial value than for its
  2909.  ; practical value.  The program combination can handle only one file at a
  2910.  ; time.  At times, it is convenient to print more than one file, and that can
  2911.  ; be done if a circular buffer of sufficient size is in use.
  2912.  
  2913.  ;      When a linear buffer is in use, the easiest way to permit the printing
  2914.  ; of multiple files is to create a file name buffer.  Actually, since it is
  2915.  ; not reasonable to expect all of the files to be printed to reside in the same
  2916.  ; directory, therefore, the buffer would be a "path" buffer.
  2917.  
  2918.  ;      The design of such a program is much easier when the file selector is
  2919.  ; used to gather the file paths.  Furthermore, the method obviates the need 
  2920.  ; for two programs.  This type of program will be introduced in the chapter
  2921.  ; that discusses the file selector.  
  2922.   
  2923.  ; Execution Instructions:
  2924.  
  2925.  ;      May be executed from the desktop with a TOS or TTP extension.  May be   
  2926.  ; executed from an AUTO folder if it has a PRG extension.
  2927.  
  2928.  ;      If a larger buffer size is desired, the TTP version of this program can
  2929.  ; be used to declare a buffer size on the TTP parameter line.  Any size, up
  2930.  ; to the maximum available machine memory, may be specified.  When the TTP
  2931.  ; program is executed, type the desired size, in kilobytes, on the parameter
  2932.  ; line; the maximum number of digits which can be typed on the parameter line
  2933.  ; is 4.  Do not type spaces before the digits.  Do not type anything between
  2934.  ; digits.  Do not type anything after the digits, except the Return key.
  2935.  ; The OK button may be clicked in lieu of a Return key press.
  2936.  
  2937. program_start:                   ; Calculate program size and retain result.
  2938.  lea        -$82(pc), a4         ; Fetch "command line" address.
  2939.  lea        -$80(a4), a0         ; Fetch "basepage" address.
  2940.  lea        program_end, a3      ; Fetch "end of program" address.
  2941.  suba.l     a0, a3               ; Program size is in A3.
  2942.  lea        stack, a7            ; Fetch address of the program stack.
  2943.  
  2944. process_input_parameter:         ; Calculate requested buffer length.
  2945.  lea        length, a0           ; Fetch address of the length variable.
  2946.  moveq      #0, d0               ; Clear counter register.
  2947.  move.b     (a4)+, d0            ; Fetch parameter line character count.
  2948.  beq.s      compute_size         ; Branch if no parameter--use default length.
  2949.  cmpi.b     #4, d0               ; Greater than four test.
  2950.  bgt        too_many_characters  ; Branch if more than four characters.
  2951.  subq.b     #1, d0               ; Set up counter.
  2952.  ext.w      d0                   ; Extend to match size of DBRA instruction.
  2953.  moveq      #0, d2               ; Initialize accumulator.
  2954.  moveq      #0, d1               ; Clear scratch register.
  2955. fetch_digit:                     ; Convert input from ASCII decimal to binary.
  2956.  move.b     (a4)+, d1            ; ASCII decimal digit to D1.
  2957.  sub.b      #$30, d1             ; Convert ASCII decimal digit to decimal digit.
  2958.  bmi.s      not_decimal          ; Branch if digit is less than zero.
  2959.  cmp.b      #9, d1               ; Greater than nine test.
  2960.  bgt.s      not_decimal          ; Branch if digit is greater than nine.
  2961. multiply_accumulator_content_by_ten:
  2962.  move.l     d2, d3               ; Copy accumulator in D3.
  2963.  lsl.l      #3, d2               ; Shift accumulator content to multiply by 8.
  2964.  add.l      d3, d2               ; Add D2's original quantity twice to
  2965.  add.l      d3, d2               ; complete the multiplication by 10.
  2966. add_new_digit_to_accumulator_content:
  2967.  add.l      d1, d2               ; Add decimal number in D1 to accumulator.
  2968.  dbra       d0, fetch_digit      ; Loop until d0 becomes negative.
  2969. extend_to_thousands_of_bytes:    ; Length will be (input X 1000) plus 
  2970.  moveq      #10, d3              ; some even number of bytes that is less
  2971.  lsl.l      d3, d2               ; than 1000.  Ex: if input is 32, length
  2972.  move.l     d2, (a0)             ; is 32768.  Store requested buffer size.
  2973. compute_size:                    ; This will be parameter to GEMDOS $31.
  2974.  adda.l     (a0), a3             ; Add buffer length to program size.
  2975. store_io_buffer_structure_components:
  2976.  lea        buffer, a1           ; Fetch buffer starting address.
  2977.  lea        io_buffer, a2        ; Fetch io_buffer structure base address.
  2978.  move.l     a1, (a2)             ; Buffer starting address to base pointer.
  2979.  move.l     a1, 8(a2)            ; Also to extract_pointer.
  2980.  
  2981. install_interrupt_handler_vector:
  2982.  pea        interrupt_handler    ; Push interrupt handler address.
  2983.  move.w     #0, -(sp)            ; Interrupt level for centronics ready.
  2984.  move.w     #$D, -(sp)           ; Functon = XBIOS #13 dec = mfpint.
  2985.  trap       #14
  2986.  addq.l     #8, sp
  2987.  
  2988. relinquish_processor_control:    ; Maintain memory residency.
  2989.  move.w     #0, -(sp)            ; See page 121 of Internals book.
  2990.  move.l     a3, -(sp)            ; Program size (plus buffer length).
  2991.  move.w     #$31, -(sp)          ; Function = ptermres = GEMDOS $31.
  2992.  trap       #1
  2993.  
  2994. not_decimal:
  2995.  pea        message_1            ; Print error message.
  2996.  move.w     #9, -(sp)            ; Function = c_conws = GEMDOS $9.
  2997.  trap       #1                   ; GEMDOS call
  2998.  addq.l     #6, sp               ; Reset stack pointer to top of stack.
  2999.  bra.s      terminate
  3000.  
  3001. too_many_characters:
  3002.  pea        message_2            ; Print error message.
  3003.  move.w     #9, -(sp)            ; Function = c_conws = GEMDOS $9.
  3004.  trap       #1                   ; GEMDOS call
  3005.  addq.l     #6, sp               ; Reset stack pointer to top of stack.
  3006.  
  3007. terminate:                       ; Wait for keypress.
  3008.  move.w     #8, -(sp)            ; Function = GEMDOS $8 = cnecin.
  3009.  trap       #1
  3010.  addq.l     #2, sp
  3011.  move.w     #0, -(sp)            ; Function = p_term_old = GEMDOS $0.
  3012.  trap       #1                   ; GEMDOS call.
  3013.  
  3014. interrupt_handler:               ; Parallel interface interrupt handler.
  3015.  movem.l    d0/a0-a1, -(sp)      ; Save content of registers used by handler.
  3016.  move.l     filesize, d0         ; Fetch file size.
  3017.  beq.s      clear_in_service_bit ; Exit if buffer is empty (filesize = 0).
  3018.  lea        io_buffer, a0        ; Fetch address of io_buffer pointer.
  3019.  movea.l    8(a0), a1            ; Fetch content of extract pointer.
  3020.  subq.l     #1, d0               ; Decrement filesize.
  3021.  move.l     d0, 12(a0)           ; Store new filesize value.
  3022.  bne.s      print_character      ; Stop printing if filesize is 0.
  3023.  move.l     (a0), 8(a0)          ; Put buffer start address in extract pointer.
  3024.  bra.s      clear_in_service_bit
  3025.  
  3026. print_character:
  3027.  move.b     (a1)+, d0            ; Fetch character.  Increment extract pointer.
  3028.  move.l     a1, 8(a0)            ; Store location of next character.
  3029.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  3030.  move.b     #$F, (a1)            ; Select port B (register 17) of PSG.
  3031.  move.b     d0, 2(a1)            ; Write character to PSG register 17.
  3032.  move.b     #$E, (a1)            ; Select port A (register 16) of PSG.
  3033.  move.b     (a1), d0             ; Get current register 16 value.
  3034.  
  3035. strobe_low:                   
  3036.  andi.b     #$DF, d0             ; Bit 5 to zero.          
  3037.  move.b     d0, 2(a1)            ; Bit 5 of port A to zero.
  3038.                               
  3039. strobe_high:                  
  3040.  ori.b      #$20, d0             ; Bit 5 to one.
  3041.  move.b     d0, 2(a1)            ; Bit 5 of port A to one.
  3042.  
  3043. clear_in_service_bit:
  3044.  movem.l    (sp)+, d0/a0-a1      ; Restore registers used by handler.
  3045.  bclr       #0, $FFFA11          ; Clear MFP in service bit.
  3046.  rte
  3047.  
  3048.  data
  3049. message_1:    dc.b 'Nondecimal character digit detected on parameter line.'
  3050.               dc.b $D,$A,'Press Return key to terminate.',$D,$A,0
  3051. message_2:    dc.b 'Parameter line input limit is 4 decimal digits.',$D,$A
  3052.               dc.b 'The program converts the input to thousands of bytes.'
  3053.               dc.b  $D,$A,'Default buffer size is 32,768 bytes.',$D,$A
  3054.               dc.b  'Press Return key to terminate.',$D,$A,0
  3055.  align
  3056. io_buffer:       dc.l  buffer     ; Pointer to buffer's starting address. 
  3057. length:          dc.l   $8000     ; Default buffer length = 32,768 bytes.
  3058. extract_pointer: dc.l  buffer     ; Buffer character output pointer.
  3059. filesize:        dc.l       0     ; filesize = 0 when buffer is empty.
  3060.  
  3061.  bss
  3062.                  ds.l  96
  3063. stack:           ds.l   1
  3064. buffer:          ds.l   0     ; Length of buffer must be added to length of
  3065. program_end:     ds.l   0     ; program to determine total size to pass to
  3066.  end                          ; GEMDOS function $31.
  3067.  
  3068.  
  3069. Program 70. This program reads the file designated by 
  3070. input on its command line into the buffer installed by 
  3071. program 69, then it initiates the interrupt printing 
  3072. process that is controlled by program 69.
  3073.  
  3074.  ; Program Name: PRG_7JP.S
  3075.  ;      Version: 1.002
  3076.  
  3077.  ; Assembly Instructions:
  3078.  
  3079.  ;      Assemble in PC-relative mode and save with a TPP extension.
  3080.  
  3081.  ; Function:
  3082.  
  3083.  ;      Reads the file designated by the parameter line input.  The address
  3084.  ; of the buffer passed to the GEMDOS $3F function is that of the parallel
  3085.  ; interface interrupt handler installed by PRG_7IP.TOS or TTP.
  3086.  
  3087.  ;      This program initiates the interrupt controlled printing process by
  3088.  ; sending a NULL character to the printer (A BELL character could also be
  3089.  ; used, if that is desirable.).
  3090.  
  3091.  ;      NOTE: Initially, I tried to send the initiating character using the
  3092.  ; BIOS #3 function (bconout); that method worked occasionally, but was not
  3093.  ; at all reliable.
  3094.  
  3095.  ; Execution Instructions:
  3096.  
  3097.  ;      PRG_7IP.TOS or TTP must install the parallel interface interrupt
  3098.  ; handler before this program is executed.  Execute this program from the
  3099.  ; desktop.  This program and the file to be printed must reside in the same
  3100.  ; directory.
  3101.  
  3102. fetch_pertinent_addresses:
  3103.  lea        -$82(pc), a3         ; Fetch "command line" address.
  3104.  lea        -$80(a3), a1         ; Fetch "basepage" address.
  3105.  lea        program_end, a0      ; Fetch "end of program" address.
  3106.  lea        stack, a7            ; Fetch stack address.
  3107.  lea        dta, a5              ; Fetch dta buffer address.
  3108.  
  3109. calculate_program_size:
  3110.  suba.l     a1, a0             
  3111. return_unused_memory:
  3112.  pea        (a0)                 ; Push program length.
  3113.  pea        (a1)                 ; Push basepage address.
  3114.  move.l     #$4A0000, -(sp)      ; Function = m_shrink = GEMDOS $4A.
  3115.  trap       #1                   ; Invoke GEMDOS exception.
  3116.  lea        $C(a7), sp           ; Reset stack pointer to top of stack.
  3117.  
  3118. set_dta:
  3119.  pea        (a5)                 ; dta = address of 44 byte buffer.
  3120.  move.w     #$1A, -(sp)          ; GEMDOS function = set dta.
  3121.  trap       #1
  3122.  addq.l     #6, sp
  3123.  
  3124. process_command_line:
  3125.  move.b     (a3)+, d0            ; Fetch command line character count.
  3126.  ext.w      d0                   ; Extend to word for next instruction.
  3127.  move.b     #0, 0(a3,d0.w)       ; Store a null at end of command line input.
  3128.  
  3129. search_for_file: 
  3130.  move.w     #0, -(sp)            ; Attribute = normal access.
  3131.  pea        (a3)                 ; Push address of file name.
  3132.  move.w     #$4E, -(sp)          ; GEMDOS function = search first.
  3133.  trap       #1
  3134.  addq.l     #8, sp
  3135.  tst        d0
  3136.  bne.s      terminate            ; Terminate if file not found.
  3137.  
  3138. process_interrupt_handler_variables:
  3139.  pea        fetch_interrupt_handler_address
  3140.  move.w     #$26, -(sp)          ; Execute a routine in supervisor mode.
  3141.  trap       #14               
  3142.  addq.l     #6, sp
  3143.  
  3144.  lea        handler_address, a4  ; Fetch handler address.
  3145.  movea.l    (a4), a4
  3146.  adda.l     #$16C, a4            ; Add offset to variable "filesize".
  3147.  move.l     $1A(a5), (a4)        ; Store file size in handler variable.
  3148.  adda.l     #$188, a4            ; Add offset to buffer variable.
  3149.  
  3150. open_file:                       ; Function returns file handle in D0.
  3151.  move.w     #0, -(a7)            ; Open as read only.
  3152.  pea        (a3)                 ; Push address of file name.
  3153.  move.w     #$3D, -(a7)          ; See Internals page 127.
  3154.  trap       #1
  3155.  addq.l     #8, a7
  3156.  move.w     d0, d3               ; Store file handle in D3.
  3157.  
  3158. read_file:
  3159.  pea        (a4)                 ; Push buffer address
  3160.  move.l     $1A(a5), -(sp)       ; Number of bytes to read.
  3161.  move.w     d3, -(sp)            ; File's handle number.
  3162.  move.w     #$3F, -(sp)          ; GEMDOS function = read.
  3163.  trap       #1
  3164.  lea        $C(sp), sp           ; Reposition stack pointer.
  3165.  
  3166. close_file:
  3167.  move.w     d3, -(sp)            ; Push file handle.
  3168.  move.w     #$3E, -(sp)          ; See Internals page 128.
  3169.  trap       #1
  3170.  addq       #4, sp
  3171.  
  3172. initiate_interrupt_process:      ; Execute in supervisor mode.
  3173.  pea        send_null
  3174.  move.w     #$26, -(sp)
  3175.  trap       #14               
  3176.  addq.l     #6, sp
  3177.  
  3178. terminate:
  3179.  move.w     #0, -(sp)
  3180.  trap       #1
  3181.  
  3182. fetch_interrupt_handler_address:     
  3183.  lea        handler_address, a0  
  3184.  move.l     $100, (a0)           ; Interrupt handler address is stored at
  3185.  rts                             ; location $100.
  3186.  
  3187. send_null:                       ; The printer will drop the logic level of
  3188.  moveq.l    #0, d0               ; pin 11 from high to low after receiving this
  3189.                                  ; character, even though nothing will be
  3190.                                  ; printed.
  3191.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  3192.  move.b     #$F, (a1)            ; Select port B (register 17) of PSG.
  3193.  move.b     d0, 2(a1)            ; Write character to PSG register 17.
  3194.  move.b     #$E, (a1)            ; Select port A (register 16) of PSG.
  3195.  move.b     (a1), d0             ; Get current register 16 value.
  3196.  
  3197. strobe_low:                   
  3198.  andi.b     #$DF, d0             ; Bit 5 to zero.          
  3199.  move.b     d0, 2(a1)            ; Bit 5 of port A to zero.
  3200.                               
  3201. strobe_high:                  
  3202.  ori.b      #$20, d0             ; Bit 5 to one.
  3203.  move.b     d0, 2(a1)            ; Bit 5 of port A to one.
  3204.  rts
  3205.  
  3206.  data
  3207.  bss
  3208. dta:               ds.l   11
  3209. handler_address:   ds.l    1
  3210.                    ds.l   96     ; Program stack.
  3211. stack:             ds.l    0     ; Address of program stack.
  3212. program_end:       ds.l    0
  3213.  end
  3214.  
  3215.  
  3216. How Communication Between the Programs Was Established
  3217.   
  3218.      It is the requirement that the print buffer and the 
  3219. interrupt handler be established with a LSR algorithm which 
  3220. necessitates the exclusion of the printing algorithm from the LSR 
  3221. program.  Later it will be possible to combine both algorithms in 
  3222. one program.  Because the printing program must pass the address 
  3223. of the print buffer as a parameter when invoking the GEMDOS $3F 
  3224. function, a communication link must be established between the 
  3225. two algorithms.  I am listing the steps that I used to establish 
  3226. that communication link so that you could use a similar process 
  3227. if you ever found it necessary to do so.  The addresses shown in 
  3228. my steps serve only as an indication of those you might see in 
  3229. your system.
  3230.    
  3231.      1. Load PRG_7IP.S into the AssemPro editor.
  3232.      2. Assemble in PC-relative mode.
  3233.      3. Save with TOS and TTP extensions.
  3234.      4. Go to the debugger.
  3235.      5. Click on symbolic button.
  3236.      6. Click on from address button.  Backspace over the $ and type the label 
  3237.         "interrupt_handler", sans parentheses.  Press the Return key.  Write 
  3238.         down address of the label = $84F8C.
  3239.      7. Similarly, advance to the label "io_buffer".
  3240.      8. Write addresses and contents for the following variables.  The contents 
  3241.         can be obtained by assigning the address of each variable a slot in the 
  3242.         register field.  For example: double click on A0 in the register field; 
  3243.         press the Esc key; type $850EC; press the Return key; note the contents 
  3244.         of the address in the register field.  Follow a similar procedure to 
  3245.         assign $850F0 to A1's slot, $850F4 to A2's slot and $85058 to A3's slot.
  3246.  
  3247.         Variable    Address   Contents
  3248.   
  3249.         io_buffer        $850EC    $396    ; Assembly time address of "buffer".
  3250.   
  3251.         length           $850F0    $8000   ; Default buffer length.
  3252.   
  3253.         extract_pointer: $850F4    $396    ; Assembly time address of "buffer".
  3254.   
  3255.         filesize:        $850F8    0
  3256.  
  3257.  
  3258.      9. Click on the from address button.  Backspace over the $ and type the 
  3259.         label "buffer", sans parentheses.  Press the Return key.  Write down the 
  3260.         label's address, but ignore the contents, which are meaningless at this 
  3261.         time.  Address = $85280.  Another way to obtain the buffer's address is 
  3262.         to assign the label "buffer" to A4's slot.  Double click on A4; press 
  3263.         the Esc key; type "buffer.l", sans parentheses; click on the address 
  3264.         button in the dialog box; press the Return key.  In the register field, 
  3265.         you would see: BUFFER.L  ^  $85280.
  3266.     10. Click on the from address button.  Backspace over the $ and type the 
  3267.         label "store_io_buffer_structure_components", sans parentheses.  Press 
  3268.         the Return key.  Move the PC cursor to that label--press Control key and 
  3269.         left mouse button simultaneously.
  3270.     11. Single step to the next label.  In the register field you will see the 
  3271.         run time address of "buffer" stored at "io_buffer" and 
  3272.         "extract_pointer".  Exit to the desktop.
  3273.     12. Calculate required offsets to pertinent variables.
  3274.   
  3275.         variable            address   variable   address
  3276.   
  3277.         filesize:           $850F8    buffer:    $85280
  3278.         interrupt_handler: -$84F8C    filesize: -$850F8
  3279.                             ------               ------
  3280.         offsets             $  16C               $  188
  3281.  
  3282.  
  3283.      In program 70, we can easily obtain the address of the label 
  3284. "interrupt_handler" because it will be stored at address $100, 
  3285. the address set aside for the MFP level 0 interrupt handler.  See 
  3286. pages 235-244 of the Internals book.  The reference does not 
  3287. clearly state that address $100 is the relevant address, but it 
  3288. can be calculated from the information given within those pages.  
  3289. Once the address of "interrupt_handler" is obtained, the address 
  3290. of filesize can be calculated by adding its offset, $16C, from 
  3291. the address stored in location $100.  Then, the address of the 
  3292. label "buffer" can be calculated by adding its offset, $188, from 
  3293. "filesize" to the address calculated for filesize.
  3294.  
  3295. Data Transfer Rate Quantitative Analyses
  3296.   
  3297.      I have prepared several programs that calculate data 
  3298. transfer rates.  Program 71 calculates the data transfer rate for 
  3299. a circular buffer by printing characters in a loop in order to 
  3300. eliminate the influence of a printing algorithm on the buffer's 
  3301. transfer rate.  Of course, we must assume that the character 
  3302. printing loop probably influences that rate, especially since it 
  3303. uses the same bios function to transmit characters to the buffer; 
  3304. but, there is a limit to what we can do to limit the interaction 
  3305. because the circular buffers are bound to that function via their 
  3306. dependence on the trap #13 handler through which they receive 
  3307. characters.  I admit that there may be esoteric design that is 
  3308. not being consided, but it really doesn't matter, because it is 
  3309. the linear type of buffer arrangement which offers the best 
  3310. opportunity for maximum data transfer rates.  That shall be 
  3311. proven before the conclusion of this chapter.
  3312.  
  3313. Program 71. A program that calculates a circular print buffer's 
  3314. data transfer rate by printing characters in a loop.  Following 
  3315. the execution instructions listed in the program's documentation.
  3316.  
  3317.  ; Program Name: PRG_7KP.S
  3318.  ;      Version: 1.004
  3319.  
  3320.  ; Assembly Instructions:
  3321.  
  3322.  ;      Assemble in PC-relative mode and save with a TOS extension.
  3323.  
  3324.  ; Function:
  3325.  
  3326.  ;      This program is used to determine the input data transfer rate of the
  3327.  ; trap #13 handler for a circular print buffer.  In order to remove the
  3328.  ; influence of the output data transfer rate of an algorithm that reads a file
  3329.  ; from a disk and sends each character to the parallel port using system
  3330.  ; functions, this program this program prints characters in a loop.
  3331.  
  3332.  ;      At the onset of execution the program prompts for the name of the print
  3333.  ; buffer that is being tested.
  3334.  
  3335.  ;      The program's output data is stored in a file bearing a name that is
  3336.  ; composed of that given for the print buffer and the extension DAT.  The data
  3337.  ; stored in that file is the number of characters printed; the name of the
  3338.  ; print buffer being tested and the buffer's input data transfer rate in bytes
  3339.  ; per second.  The name given for the print buffer can be no more than 8 valid
  3340.  ; ST file name characters.
  3341.  
  3342.  ;      The input data transfer rate file can be used to compare the results of
  3343.  ; two or more print buffers to each other.  In addition, the input data
  3344.  ; transfer rate of linear buffers can be compared to that of circular buffers.
  3345.  
  3346.  ; Execution Instructions:
  3347.  
  3348.  ;      1. If necessary, remove all installed print buffers by cold booting.
  3349.  ;      2. Turn the printer on.
  3350.  ;      3. If the print buffer that is to be tested is not yet installed,
  3351.  ;         install it now.
  3352.  ;      4. Execute CUSTOM.PRG to install the custom traps invoked by this
  3353.  ;         program.
  3354.  ;      5. Execute this program from the desktop.  The program's output will
  3355.  ;         be stored in a file that will reside in the same directory from
  3356.  ;         which this program is executed.
  3357.  
  3358. calculate_program_size:
  3359.  lea        -$102(pc), a1        ; Fetch basepage start address.
  3360.  lea        program_end, a0      ; Fetch program end address.
  3361.  trap       #6                   ; Return unused memory to op system.
  3362.  lea        stack, a7            ; Point A7 to this program's stack.
  3363.  
  3364. get_name_of_print_buffer:
  3365.  
  3366.  ; The first byte of the array 'buffer_name_buffer' contains the maximum
  3367.  ; permissible length of the input string.  After the GEMDOS $A function
  3368.  ; invocation, the second byte of the array will contain the number of
  3369.  ; characters read as input; the carriage return character will not be included
  3370.  ; in the string length value.  The address of the first byte of the input
  3371.  ; string is that of the third byte of the array.
  3372.  
  3373.  lea        buffer_prompt, a0
  3374.  bsr        print_string
  3375.  lea        buffer_name_buffer, a3 ; Fetch structure's base address.
  3376.  pea        (a3)                   ; Push address of the array.
  3377.  move.w     #$A, -(sp)             ; GEMDOS readline function.
  3378.  trap       #1
  3379.  addq.l      #6, sp
  3380. store_name_for_header_use:
  3381.  move.b     1(a3), d0            ; Fetch length of buffer name.
  3382.  ext.w      d0                   ; Extend to word length.
  3383.  addq.l     #2, a3               ; Move to name element in array.
  3384.  movea.l    a3, a2               ; Prepare for suffix algorithm.
  3385.  adda.w     d0, a2               ; Same preparation.
  3386.  lea        buffer_name, a1      ; Fetch address of buffer_name array.
  3387.  subq.w     #1, d0               ; Initialize counter.
  3388. store_character:                 ; Store name in another array for use
  3389.  move.b     (a3)+, (a1)+         ; in a header.
  3390.  dbra       d0, store_character
  3391. add_output_file_name_suffix:     ; Attach DAT suffix to buffer name.
  3392.  move.b     #$2E, (a2)+          ; Attach a period.
  3393.  move.b     #$44, (a2)+          ; Attach letter 'D'.
  3394.  move.b     #$41, (a2)+          ; Attach letter 'A'.
  3395.  move.b     #$54, (a2)+          ; Attach letter 'T'.
  3396.  move.b     #$0, (a2)            ; Finish with a NULL.
  3397.  
  3398. get_start_time:
  3399.  trap       #3                   ; Value of system clock returned in D0.
  3400.  move.l     d0, d4               ; Store time in D4.
  3401.  
  3402. print_characters:
  3403.  move.l     #3000, d6            ; Number of characters to print.
  3404.  move.w     d6, d3               ; Initialize loop counter.
  3405.  subq.w     #1, d3
  3406.  move.b     #$7A, d1
  3407. reset_generator:
  3408.  move.b     #$20, d5             ; Character number generator.
  3409. print_byte:
  3410.  cmp.b      d1, d5
  3411.  beq.s      reset_generator
  3412.  addq.b     #1, d5
  3413.  move.w     d5, -(sp)       
  3414.  move.w     #0, -(sp)
  3415.  move.w     #3, -(sp)            ; See Internals page 155.
  3416.  trap       #13
  3417.  addq       #6, sp
  3418.  dbra       d3, print_byte       ; Print 3000 characters.
  3419.  
  3420. transfer_rate_calculations:
  3421.  trap       #3                   ; Fetch current time.
  3422.  move.l     d0, d5               ; Save end time.
  3423.  sub.l      d4, d0               ; Subtract start time from end time.
  3424. convert_time_to_milliseconds:    ; Multiply by 5.
  3425.  move.l     d0, d2               ; Save copy to add.
  3426.  asl.l      #2, d2               ; Multiply by 4.
  3427.  add.l      d0, d2               ; To complete multiplication by 5.
  3428. compute_transfer_rate:        
  3429.  move.l     #3000, d0            ; Fetch number of characters printed.
  3430.  divu       d2, d0               ; Divide size by time in milliseconds.
  3431. copy_to_d1:                      ; Will extract remainder from D1.
  3432.  move.l     d0, d1               ; Quotient is in lower word of D0.
  3433.  mulu       #1000, d0            ; Multiply quotient by 1000.
  3434.  clr.w      d1                   ; Get rid of quotient.
  3435.  swap       d1                   ; Remainder is now in lower word of D1.
  3436.  mulu       #1000, d1            ; Multiply remainder by 1000.
  3437.  divu       d2, d1               ; Divide remainder by time in milliseconds.
  3438.  swap       d1                   ; Put remainder in lower word.
  3439.  clr.w      d1                   ; Get rid of remainder.
  3440.  swap       d1                   ; Get quotient back in lower word.
  3441.  add.l      d0, d1               ; Transfer rate is now in D1.
  3442.  
  3443.  ; Note: The order in which things are done in the calculation algorithm
  3444.  ;       is critical.  The remainder obtained from the first division must
  3445.  ;       be manipulated just right.  One other point is that the quotient
  3446.  ;       will be zero whenever the time in milliseconds is a value greater
  3447.  ;       than the size of the file. 
  3448.  
  3449.  ;       I have verified the accuracy of the above algorithm, but I have
  3450.  ;       decided to print the number of 5 milliseconds clock ticks obtained
  3451.  ;       for both the start and end times.  This will permit the transfer
  3452.  ;       rate to be calculated manually.  Remember that those clock ticks
  3453.  ;       must be multiplied by 5 to convert them to milliseconds.  And, of
  3454.  ;       course, the number of milliseconds must be divided by 1000 to convert
  3455.  ;       them to seonds; or one can divide the file size by the number of
  3456.  ;       milliseconds and multiply the quotient by 1000.
  3457.  
  3458.  ; The equation being used in this algorithm is:
  3459.  
  3460.  ; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
  3461.  ;                                X 1000 (msec/second).
  3462.  
  3463.  trap       #4                   ; Convert to ASCII decimal.
  3464.  lea        transfer_rate, a1    ; Fetch address of transfer_rate.
  3465.  move.l     a0, (a1)             ; Store address of decimal string.
  3466.  
  3467.  ; NOTE: The variable 'transfer_rate' now contains the address of the
  3468.  ;       ASCII decimal string that is the transfer rate.  Trap #4 can't
  3469.  ;       be invoked until that value is used by the program, otherwise
  3470.  ;       the value will be corrupted.
  3471.  
  3472. create_output_file:
  3473.  move.w     #0, -(sp)            ; File attribute = read/write.
  3474.  pea        output_file          ; Push address of output file's name.
  3475.  move.w     #$3C, -(sp)          ; Function = f_create = GEMDOS $3C.
  3476.  trap       #1                   ; File handle is returned in D0.
  3477.  addq.l     #8, sp
  3478.  lea        output_file_handle, a1
  3479.  move.w     d0, (a1)
  3480.  
  3481. redirect_output_bound_for_screen:
  3482.  
  3483.  ; NOTE: If the output file's handle is exchanged with the video screen's
  3484.  ;       handle, then the printline function = GEMDOS $9 can be used to 
  3485.  ;       write to the file.
  3486.  
  3487.  
  3488. redirect_output:                 ; Exchange file handle with screen's handle.
  3489.  move.w     d0, -(sp)            ; This is the disk file's handle.
  3490.  move.w     #1, -(sp)            ; This is the video screen's handle.
  3491.  move.w     #$46, -(sp)          ; Function = f_force = GEMDOS $46.
  3492.  trap       #1
  3493.  addq.l     #6, sp
  3494.  
  3495. print_data_report:
  3496.  lea        heading(pc), a0      ; Print data file heading.
  3497.  bsr        print_string
  3498.  lea        buffer_name(pc), a0  ; Name of buffer for which transfer rate is
  3499.  bsr        print_string         ; being reported.
  3500.  lea        header_1(pc), a0     ; Transfer Rate header.
  3501.  bsr        print_string
  3502.  move.l     transfer_rate, a0    ; Transfer Rate.  Fetch from trap #4
  3503.  bsr        print_string         ; location.  Quick before it goes away.
  3504.  lea        header_2(pc), a0     ; Transfer Rate Units.
  3505.  bsr        print_string
  3506.  lea        header_3(pc), a0     ; Characters printed header.
  3507.  bsr        print_string
  3508.  move.l     d6, d1               ; Fetch number of bytes printed from D6.           
  3509.  trap       #4                   ; Convert to ASCII decimal.
  3510.  bsr        print_string
  3511.  lea        header_7(pc), a0     ; End Time header.
  3512.  bsr        print_string
  3513.  move.l     d5, d1               ; Fetch end time.
  3514.  trap       #4                   ; Convert to ASCII decimal.
  3515.  bsr        print_string
  3516.  lea        header_8(pc), a0     ; Time units = 5-millisecond ticks.
  3517.  bsr        print_string
  3518.  lea        header_6(pc), a0     ; Start Time header.
  3519.  bsr        print_string
  3520.  move.l     d4, d1               ; Fetch start time.
  3521.  trap       #4                   ; Convert to ASCII decimal.
  3522.  bsr        print_string
  3523.  lea        header_8(pc), a0     ; Time units = 5-millisecond ticks.
  3524.  bsr        print_string
  3525.  
  3526. close_output_file:
  3527.  move.w     output_file_handle, -(sp) 
  3528.  move.w     #$3E, -(sp)          ; Function = GEMDOS $3E = f_close.
  3529.  trap       #1
  3530.  addq.l     #4, sp
  3531.  
  3532. terminate:
  3533.  move.w     #0, -(sp)
  3534.  trap       #1
  3535.  
  3536. print_string:
  3537.  pea        (a0)
  3538.  move.w     #9, -(sp)
  3539.  trap       #1
  3540.  addq.l     #6, sp
  3541.  rts
  3542.  
  3543.  data
  3544. heading:      dc.b $D,$A,'PRG_7KP.TOS Execution Results',$D,$A,$D,$A
  3545.               dc.b '  Print Buffer:        ',0
  3546. header_1:     dc.b $D,$A,'  Transfer Rate:      ',0
  3547. header_2:     dc.b ' bytes/second',$D,$A,$D,$A,0
  3548. header_3:     dc.b '  Characters Printed: ',0
  3549. header_7:     dc.b $D,$A,$D,$A,'  End Time:      ',0
  3550. header_6:     dc.b '  Start Time:    ',0
  3551. header_8:     dc.b ' 5-millisecond ticks',$A,$D,0
  3552.  
  3553. buffer_prompt:
  3554.     dc.b $D,$A,$D,$A
  3555.     dc.b 'Data produced by this program will be stored in a file with',$D,$A
  3556.     dc.b 'name composed of the name of the print buffer and a DAT suffix.',$D,$A
  3557.     dc.b $D,$A,'Name of print buffer (8 characters max, no suffix): ',0
  3558.  
  3559. buffer_name_buffer:
  3560.                     dc.b  10  ; Maximum length of input string = 10 characters.
  3561.                     dc.b   0  ; String's length will be stored here.
  3562. output_file:        ds.b  14  ; String will be stored here.
  3563. buffer_name:        ds.b  10  ; Buffer name for heading.
  3564.  align
  3565. buffer:             dc.w  $0  ; Character buffer.
  3566.  bss
  3567. output_file_handle: ds.w   1
  3568. transfer_rate:      ds.l   1
  3569.                     ds.l  96  ; Program stack.
  3570. stack:              ds.l   0  ; Address of program stack.
  3571. program_end:        ds.l   0
  3572.  end
  3573.  
  3574.  
  3575. PRG_7KP.TOS Execution Results
  3576.  
  3577.   Print Buffer:        PRG_7HP
  3578.   Transfer Rate:       13636 bytes/second
  3579.  
  3580.   Characters Printed:  3000
  3581.  
  3582.   End Time:       1447716 5-millisecond ticks
  3583.   Start Time:     1447672 5-millisecond ticks
  3584.  
  3585.  
  3586.      Program 72 calculates the data transfer rate for a 
  3587. particular printing program/circular print buffer combination.  
  3588. This program prompts for the name of a file to be printed and the 
  3589. name of the buffer being tested.  The data transfer rate will 
  3590. vary somewhat depending on the directory path from which the file 
  3591. is read, therefore, that path is included in the program's output 
  3592. data.  If the program is executed from a floppy disk, the total 
  3593. execution time will be much longer than the data transfer time 
  3594. because the output file must also be stored in the directory from 
  3595. which the file is read and from which program 72 is executed.
  3596.  
  3597. Program 72. Calculates the data transfer rate for a particular 
  3598. printing program/circular buffer combination.  Follow the 
  3599. execution instructions listed in the program's documentation.
  3600.  
  3601.  ; Program Name: PRG_7LP.S
  3602.  ;      Version: 1.004
  3603.  
  3604.  ; Assembly Instructions:
  3605.  
  3606.  ;      Assemble in PC-relative mode and save with a TOS extension.
  3607.  
  3608.  ; Function:
  3609.  
  3610.  ;      This program is used to determine a the data transfer rate of a printing
  3611.  ; program/print buffer combination.  The influence of the printing program's
  3612.  ; output data transfer rate is inherent for print buffers designed to intercept
  3613.  ; operating system trap #13 invocations.
  3614.  
  3615.  ;      The program prompts for the name of a file to be printed; then it
  3616.  ; prompts for the name of the print buffer that is being tested.  The
  3617.  ; program's output data is stored in a file bearing a name that is composed of
  3618.  ; that given for the print buffer and the extension DAT.  The data stored in
  3619.  ; that file is the name of the printed file and its length; the name of the
  3620.  ; print buffer and the printing program/print buffer combination's data
  3621.  ; transfer rate.  The name given for the print buffer can be no more than 8
  3622.  ; valid ST file name characters.
  3623.  
  3624.  ;      The data transfer rate file can be used to compare the results of two 
  3625.  ; or more printing program/print buffer combinations to each other.
  3626.  
  3627.  ; Execution Instructions:
  3628.  
  3629.  ;      1. If necessary, remove all installed print buffers by cold booting.
  3630.  ;      2. Turn the printer on.
  3631.  ;      3. If the print buffer that is to be involved is a software buffer,
  3632.  ;         execute the program that installs the print buffer.
  3633.  ;      4. Execute CUSTOM.PRG to install the custom traps invoked by this
  3634.  ;         program.
  3635.  ;      5. Execute this program from the desktop.  The file that is to be
  3636.  ;         printed and this program must reside in the same directory.
  3637.  
  3638. calculate_program_size:
  3639.  lea        -$102(pc), a1        ; Fetch basepage start address.
  3640.  lea        program_end, a0      ; Fetch program end address.
  3641.  trap       #6                   ; Return unused memory to op system.
  3642.  lea        stack, a7            ; Point A7 to this program's stack.
  3643.  
  3644. get_name_of_file:                ; Request name of file to be printed.
  3645.  lea        file_prompt, a0
  3646.  bsr        print_string
  3647.  
  3648.  ; The first byte of the array 'file_name_buffer' contains the maximum
  3649.  ; permissible length of the input string.  After the GEMDOS $A function
  3650.  ; invocation, the second byte of the array will contain the number of
  3651.  ; characters read as input; the carriage return character will not be included
  3652.  ; in the string length value.  The address of the first byte of the input
  3653.  ; string is that of the third byte of the array.
  3654.  
  3655.  lea        file_name_buffer, a3 ; Fetch structure's base address. 
  3656.  pea        (a3)                 ; Push address of the array.
  3657.  move.w     #$A, -(sp)           ; Function = GEMDOS $A = readline.
  3658.  trap       #1
  3659.  addq.l     #6, sp
  3660. terminate_file_name_with_null:   ; So that name can be printed as a string.
  3661.  move.b     1(a3), d0            ; Fetch length of file name.
  3662.  ext.w      d0                   ; Extend to word length.
  3663.  move.b     #0, 2(a3,d0.w)       ; Store NULL at end of string.
  3664.  
  3665. get_name_of_print_buffer:
  3666.  lea        buffer_prompt, a0
  3667.  bsr        print_string
  3668.  lea        buffer_name_buffer, a3 ; Fetch structure's base address.
  3669.  pea        (a3)                   ; Push address of the array.
  3670.  move.w     #$A, -(sp)             ; GEMDOS readline function.
  3671.  trap       #1
  3672.  addq.l      #6, sp
  3673. store_name_for_header_use:
  3674.  move.b     1(a3), d0            ; Fetch length of buffer name.
  3675.  ext.w      d0                   ; Extend to word length.
  3676.  addq.l     #2, a3               ; Move to name element in array.
  3677.  movea.l    a3, a2               ; Prepare for suffix algorithm.
  3678.  adda.w     d0, a2               ; Same preparation.
  3679.  lea        buffer_name, a1      ; Fetch address of buffer_name array.
  3680.  subq.w     #1, d0               ; Initialize counter.
  3681. store_character:                 ; Store name in another array for use
  3682.  move.b     (a3)+, (a1)+         ; in a header.
  3683.  dbra       d0, store_character
  3684. add_output_file_name_suffix:     ; Attach DAT suffix to buffer name.
  3685.  move.b     #$2E, (a2)+          ; Attach a period.
  3686.  move.b     #$44, (a2)+          ; Attach letter 'D'.
  3687.  move.b     #$41, (a2)+          ; Attach letter 'A'.
  3688.  move.b     #$54, (a2)+          ; Attach letter 'T'.
  3689.  move.b     #$0, (a2)            ; Finish with a NULL.
  3690.  
  3691. get_drive:
  3692.  move.w     #$19, -(sp)          ; See Internals page 116, but note the 
  3693.  trap       #1                   ; errors there.
  3694.  addq.l     #2, sp               ; Note first error at this instruction.
  3695.  add.w      #$41, d0             ; Note 2nd error at this instruction.
  3696.  lea        path, a6             ; Fetch address of path array.
  3697.  move.b     d0, (a6)+            ; Store drive letter in path array.
  3698.  move.b     #$3A, (a6)+          ; Store a colon in path array.
  3699.  
  3700. get_directory_path:
  3701.  move.w     #0, -(sp)
  3702.  pea        (a6)                 ; Push address of next path array element.
  3703.  move.w     #$47, -(sp)          ; See Internals page 135.
  3704.  trap       #1
  3705.  addq.l     #8, sp
  3706.  
  3707. print_the_file:
  3708.  pea        dta                  ; dta = address of 44 byte buffer.
  3709.  move.w     #$1A, -(sp)          ; GEMDOS function = set dta.
  3710.  trap       #1
  3711.  addq.l     #6, sp
  3712. search_for_file: 
  3713.  move.w     #0, -(sp)            ; Attribute = normal access.
  3714.  pea        file_name            ; Push address of file name.
  3715.  move.w     #$4E, -(sp)          ; GEMDOS function = search first.
  3716.  trap       #1
  3717.  addq.l     #8, sp
  3718.  tst        d0
  3719.  bne        terminate            ; Exit if file not found.
  3720.  lea        dta, a0
  3721.  move.l     $1A(a0), d6
  3722.  lea        buffer, a3
  3723. open_file:                       ; Function returns file handle in D0.
  3724.  move.w     #0, -(a7)            ; Open as read only.
  3725.  pea        file_name            ; Push address of file name.
  3726.  move.w     #$3D, -(a7)          ; See Internals page 127.
  3727.  trap       #1
  3728.  addq.l     #8, a7
  3729.  move.w     d0, d3               ; Store file handle in D3.
  3730.  
  3731.  trap       #3                   ; Value of system clock returned in D0.
  3732.  move.l     d0, d4               ; Store time in D4.
  3733.  
  3734. read_file:                       ; Function returns 1 in D0 unless end of file
  3735.                                  ; is reached or a read error occurs.
  3736.  pea        (a3)                 ; Push address of character buffer.
  3737.  move.l     #1, -(a7)            ; Number of bytes to read from the file.
  3738.                                  ; GEMDOS $3F's buffer.
  3739.  move.w     d3, -(a7)            ; Push file handle.
  3740.  move.w     #$3F, -(a7)          ; See Internals page 129.
  3741.  
  3742.  ; Note: The system will not disturb this stack setup, therefore, it must be
  3743.  ;       initialized only once.  Thereafter, a branch need be taken only to the
  3744.  ;       following instruction.
  3745.  
  3746. fetch_byte:
  3747.  trap       #1
  3748.  tst.w      d0                   ; When NULL at end of file is read,
  3749.  ble.s      end_of_file          ; D0 will be 0.
  3750. print_byte:
  3751.  move.b     (a3), d0             ; Must read buffer one byte each time.
  3752.  move.w     d0, -(sp)            ; But must push a word here.   
  3753.  move.w     #0, -(sp)
  3754.  move.w     #3, -(sp)            ; See Internals page 155.
  3755.  trap       #13
  3756.  addq       #6, sp
  3757.  bra.s      fetch_byte
  3758. end_of_file:
  3759.  lea        $C(sp), sp
  3760.  
  3761. close_file:
  3762.  move.w     d3, -(sp)            ; Push handle for the file that was read.
  3763.  move.w     #$3E, -(sp)          ; See Internals page 128.
  3764.  trap       #1
  3765.  addq       #4, sp
  3766.  
  3767. transfer_rate_calculations:
  3768.  trap       #3                   ; Fetch current time.
  3769.  move.l     d0, d5               ; Store end time for data file.
  3770.  sub.l      d4, d0               ; Subtract start time from end time.
  3771. convert_time_to_milliseconds:    ; Multiply by 5.
  3772.  move.l     d0, d2               ; Save copy to add.
  3773.  asl.l      #2, d2               ; Multiply by 4.
  3774.  add.l      d0, d2               ; To complete multiplication by 5.
  3775. compute_transfer_rate:        
  3776.  move.l     d6, d0               ; Fetch file size from D6.
  3777.  divu       d2, d0               ; Divide size by time in milliseconds.
  3778. copy_to_d1:                      ; Will extract remainder from D1.
  3779.  move.l     d0, d1               ; Quotient is in lower word of D0.
  3780.  mulu       #1000, d0            ; Multiply quotient by 1000.
  3781.  clr.w      d1                   ; Get rid of quotient.
  3782.  swap       d1                   ; Remainder is now in lower word of D1.
  3783.  mulu       #1000, d1            ; Multiply remainder by 1000.
  3784.  divu       d2, d1               ; Divide remainder by time in milliseconds.
  3785.  swap       d1                   ; Put remainder in lower word.
  3786.  clr.w      d1                   ; Get rid of remainder.
  3787.  swap       d1                   ; Get quotient back in lower word.
  3788.  add.l      d0, d1               ; Transfer rate is now in D1.
  3789.  
  3790.  ; Note: The order in which things are done in the calculation algorithm
  3791.  ;       is critical.  The remainder obtained from the first division must
  3792.  ;       be manipulated just right.  One other point is that the quotient
  3793.  ;       will be zero whenever the time in milliseconds is a value greater
  3794.  ;       than the size of the file. 
  3795.  
  3796.  ;       I have verified the accuracy of the above algorithm, but I have
  3797.  ;       decided to print the number of 5 milliseconds clock ticks obtained
  3798.  ;       for both the start and end times.  This will permit the transfer
  3799.  ;       rate to be calculated manually.  Remember that those clock ticks
  3800.  ;       must be multiplied by 5 to convert them to milliseconds.  And, of
  3801.  ;       course, the number of milliseconds must be divided by 1000 to convert
  3802.  ;       them to seonds; or one can divide the file size by the number of
  3803.  ;       milliseconds and multiply the quotient by 1000.
  3804.  
  3805.  ; The equation being used in this algorithm is:
  3806.  
  3807.  ; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
  3808.  ;                                X 1000 (msec/second). 
  3809.    
  3810.  trap       #4                   ; Convert to ASCII decimal.
  3811.  lea        transfer_rate, a1
  3812.  move.l     a0, (a1)             ; Store address of decimal string.
  3813.  
  3814.  ; NOTE: The variable 'transfer_rate' now contains the address of the
  3815.  ;       ASCII decimal string that is the transfer rate.  Trap #4 can't
  3816.  ;       be invoked until that value is used by the program, otherwise
  3817.  ;       the value will be corrupted.
  3818.  
  3819. create_output_file:
  3820.  move.w     #0, -(sp)            ; File attribute = read/write.
  3821.  pea        output_file          ; Push address of output file's name.
  3822.  move.w     #$3C, -(sp)          ; Function = f_create = GEMDOS $3C.
  3823.  trap       #1                   ; File handle is returned in D0.
  3824.  addq.l     #8, sp
  3825.  lea        output_file_handle, a0
  3826.  move.w     d0, (a0)
  3827.  
  3828. redirect_output_bound_for_screen:
  3829.  
  3830.  ; NOTE: If the output file's handle is exchanged with the video screen's
  3831.  ;       handle, then the printline function = GEMDOS $9 can be used to 
  3832.  ;       write to the file.
  3833.  
  3834.  
  3835. redirect_output:                 ; Exchange file handle with screen's handle.
  3836.  move.w     d0, -(sp)            ; This is the disk file's handle.
  3837.  move.w     #1, -(sp)            ; This is the video screen's handle.
  3838.  move.w     #$46, -(sp)          ; Function = f_force = GEMDOS $46.
  3839.  trap       #1
  3840.  addq.l     #6, sp
  3841.  
  3842. print_data_report:
  3843.  lea        heading, a0          ; Print data file heading.
  3844.  bsr        print_string
  3845.  lea        buffer_name, a0      ; Name of buffer for which transfer rate is
  3846.  bsr        print_string         ; being reported.
  3847.  lea        header_1, a0         ; Transfer Rate header.
  3848.  bsr        print_string
  3849.  move.l     transfer_rate, a0    ; Transfer Rate.  Fetch from trap #4
  3850.  bsr        print_string         ; location.  Quick before it goes away.
  3851.  lea        header_2, a0         ; Transfer Rate Units.
  3852.  bsr        print_string
  3853.  lea        header_3, a0         ; File name header.
  3854.  bsr        print_string
  3855.  lea        file_name, a0        ; File name.
  3856.  bsr        print_string
  3857.  lea        header_4, a0         ; File length header.
  3858.  bsr        print_string
  3859.  move.l     d6, d1               ; Fetch file size from D6.           
  3860.  trap       #4                   ; Convert to ASCII decimal.
  3861.  bsr        print_string
  3862.  lea        header_5, a0         ; File size units.
  3863.  bsr        print_string
  3864.  lea        header_9, a0         ; Directory path.
  3865.  bsr        print_string
  3866.  lea        path, a0
  3867.  bsr        print_string
  3868.  lea        header_7, a0         ; End Time header.
  3869.  bsr        print_string
  3870.  move.l     d5, d1               ; Fetch end time.
  3871.  trap       #4                   ; Convert to ASCII decimal.
  3872.  bsr        print_string
  3873.  lea        header_8, a0         ; Time units = 5-millisecond ticks.
  3874.  bsr        print_string
  3875.  lea        header_6, a0         ; Start Time header.
  3876.  bsr        print_string
  3877.  move.l     d4, d1               ; Fetch start time.
  3878.  trap       #4                   ; Convert to ASCII decimal.
  3879.  bsr        print_string
  3880.  lea        header_8, a0         ; Time units = 5-millisecond ticks.
  3881.  bsr        print_string
  3882.  
  3883. close_output_file:
  3884.  move.w     output_file_handle, -(sp) 
  3885.  move.w     #$3E, -(sp)          ; Function = GEMDOS $3E = f_close.
  3886.  trap       #1
  3887.  addq.l     #4, sp
  3888.  
  3889. terminate:
  3890.  move.w     #0, -(sp)
  3891.  trap       #1
  3892.  
  3893. print_string:
  3894.  pea        (a0)
  3895.  move.w     #9, -(sp)
  3896.  trap       #1
  3897.  addq.l     #6, sp
  3898.  rts
  3899.  
  3900.  data
  3901. heading:      dc.b $D,$A,'PRG_7LP.TOS Execution Results',$D,$A,$D,$A
  3902.               dc.b '  Print Buffer:   ',0
  3903. header_1:     dc.b $D,$A,'  Transfer Rate: ',0
  3904. header_2:     dc.b ' bytes/second',$D,$A,0
  3905. header_3:     dc.b '  File Printed:   ',0
  3906. header_4:     dc.b $D,$A,'  File Length:  ',0
  3907. header_5:     dc.b ' bytes',$D,$A,0
  3908. header_9      dc.b $D,$A,'  Path: ',0
  3909. header_7:     dc.b $D,$A,$D,$A,'  End Time:      ',0
  3910. header_6:     dc.b '  Start Time:    ',0
  3911. header_8:     dc.b ' 5-millisecond ticks',$A,$D,0
  3912. file_prompt:  dc.b $D,$A,'Name of file to be printed (include suffix): ',0
  3913.  
  3914. buffer_prompt:
  3915.     dc.b $D,$A,$D,$A
  3916.     dc.b 'Data produced by this program will be stored in a file with',$D,$A
  3917.     dc.b 'name composed of the name of the print buffer and a DAT suffix.',$D,$A
  3918.     dc.b $D,$A,'Name of print buffer (8 characters max, no suffix): ',0
  3919.  
  3920. file_name_buffer:
  3921.                      dc.b  14  ; Maximum length of input string = 14 characters.
  3922.                      dc.b   0  ; String's length will be stored here.
  3923. file_name:           ds.b  14  ; String will be stored here.
  3924. buffer_name_buffer:
  3925.                      dc.b  10  ; Maximum length of input string = 10 characters.
  3926.                      dc.b   0  ; String's length will be stored here.
  3927. output_file:         ds.b  14  ; String will be stored here.
  3928. buffer_name:         ds.b  10  ; Buffer name for heading.
  3929.  align
  3930. buffer:              dc.w  $0  ; Character buffer.
  3931.  bss
  3932. output_file_handle:  ds.w   1
  3933. dta:                 ds.l  11
  3934. transfer_rate:       ds.l   1
  3935. path:                ds.b 120
  3936.                      ds.l  96  ; Program stack.
  3937. stack:               ds.l   0  ; Address of program stack.
  3938. program_end:         ds.l   0
  3939.  end
  3940.  
  3941.  
  3942. PRG_7LP.TOS Execution Results
  3943.  
  3944.   Print Buffer:   PRG_7HP
  3945.   Transfer Rate:  995 bytes/second
  3946.   File Printed:   PRG_7LP.S
  3947.   File Length:   15107 bytes
  3948.  
  3949.   Path: P:        Note: P was a ram disk.
  3950.  
  3951.   End Time:       670922 5-millisecond ticks
  3952.   Start Time:     667886 5-millisecond ticks
  3953.  
  3954.  
  3955. PRG_7LP.TOS Execution Results
  3956.  
  3957.   Print Buffer:   PRG_7HP
  3958.   Transfer Rate:  797 bytes/second
  3959.   File Printed:   PRG_7LP.S
  3960.   File Length:   15107 bytes
  3961.  
  3962.   Path: A:\PRG_7
  3963.  
  3964.   End Time:       732462 5-millisecond ticks
  3965.   Start Time:     728672 5-millisecond ticks
  3966.  
  3967.  
  3968.      Program 73 has been designed to measure the data transfer 
  3969. rate through the ST's parallel interface port to a device 
  3970. connected to that port.  For the test, I used my Supra 
  3971. Corporation MicroStuffer printer buffer.  Because I believe that 
  3972. this external buffer can accept data as fast as the ST's port can 
  3973. move it, I consider the test to reflect the capacity of the ST's 
  3974. circuitry.  And even though I can't prove that fact, 
  3975. specifically, I can state that there is evidence in at least one 
  3976. reference of a limit to the port's data transfer rate.  On page 
  3977. 3-26 of the Peel book, the ST port's rate is said to be 4000 
  3978. bytes per second, typically.  The point is this: no matter the 
  3979. quality of an external buffer connected to the ST's port, the 
  3980. data transfer rate to that buffer will never approach the speed 
  3981. at which we are able to transfer data to a linear buffer within 
  3982. ST ram.  Program 74 will illustrate that clearly enough.  Please 
  3983. especially note the data transfer rate achieved when the file 
  3984. being printed is read from a ram disk.
  3985.  
  3986. Program 73.  This program will measure the data transfer rate of 
  3987. the ST's parallel port when an external buffer is connected to 
  3988. that port.
  3989.  
  3990.  ; Program Name: PRG_7MP.S
  3991.  ;      Version: 1.004
  3992.  
  3993.  ; Assembly Instructions:
  3994.  
  3995.  ;      Assemble in PC-relative mode and save with a TOS extension.
  3996.  
  3997.  ; Function:
  3998.  
  3999.  ;      A program to measure the transfer rate of data through the ST's
  4000.  ; parallel interface port to a device connected to that port.  In this
  4001.  ; particular instance the device connected to the port may be the printer,
  4002.  ; the printer's internal buffer or an external print buffer such as my Supra
  4003.  ; Corporation MicroStuffer printer buffer.  Unfortunately, MicroStuffer's
  4004.  ; input data transfer rate is not specified in its operator's manual;
  4005.  ; therefore, I can't compare the results obtained with this program to its
  4006.  ; hardware specification.
  4007.  
  4008.  ;      However, in appendix G of my Star NX-10 User's Manual, the data
  4009.  ; transfer rate for the printer's parallel interface is given: it is listed
  4010.  ; as 1,000 to 6,000 characters per second.
  4011.  
  4012.  ;      In addition, there is some information given on page 3-26 of the
  4013.  ; Peel book.  The data transfer rate for the ST's parallel port is mentioned
  4014.  ; there: it is said to be 4000 bytes per second, typically.
  4015.  
  4016.  ;      It is reasonable to assume that, regardless of any faster data transfer
  4017.  ; rate that may be possible with the MicroStuffer printer buffer, we are not
  4018.  ; going to be able to send data to it faster than the maximum data transfer
  4019.  ; rate of the ST's parallel interface port.
  4020.  
  4021.  ;      Still, in effect, since there are two devices involved in the
  4022.  ; experiment, and because there is no way to isolate the influence of each
  4023.  ; on the outcome of the experiment, the validity of the results is relevant
  4024.  ; only as they are applied to the device combination.
  4025.  ; 
  4026.  ;      The program does not use system functions to print; instead it prints
  4027.  ; directly to the port.  Furthermore, this program does not read and print a
  4028.  ; file.  Instead, it prints characters in a loop, thereby eliminating the
  4029.  ; influence of a file reading algorithm.
  4030.  
  4031.  ;      The purpose of this experiment is to obtain information so that the
  4032.  ; data transfer rate obtained with an external buffer connected to the
  4033.  ; parallel interface port can be compared to that which is obtained with
  4034.  ; buffers created within the ST by software.  I assume that all print buffers
  4035.  ; involved in the comparison permit interrupt controlled printing (background
  4036.  ; printing), thereby effecting a multi-tasking environment.
  4037.  
  4038.  ;      NOTE: THE PRINTER DOES NOT HAVE TO BE ON WHEN THE EXTERNAL BUFFER
  4039.  ;            IS CONNECTED.  CAN SAVE PAPER THAT WAY.  JUST CLEAR THE 
  4040.  ;            EXTERNAL BUFFER AFTER A TEST RUN.
  4041.  
  4042.  ;      This program prints characters in a loop, so it does not prompt for
  4043.  ; the name of a file to be printed, but it does prompt for the name of the
  4044.  ; print buffer that is being tested.  The program's output data is stored in
  4045.  ; a file bearing a name that is composed of that given for the print buffer
  4046.  ; and the extension DAT.  The data stored in that file is the number of
  4047.  ; characters printed; and the name of the print buffer and its input data
  4048.  ; transfer rate in bytes per second.  The name given for the print buffer can
  4049.  ; be no more than 8 valid ST file name characters.
  4050.  
  4051.  ; Execution Instructions:
  4052.  
  4053.  ;      1. If necessary, remove all installed print buffers by cold booting.
  4054.  ;      2. Turn the printer on.
  4055.  ;      3. Turn the external buffer on.
  4056.  ;      4. Execute CUSTOM.PRG to install the custom traps invoked by this
  4057.  ;         program.
  4058.  ;      5. Execute this program from the desktop.  The program's output will
  4059.  ;         be stored in a file that will reside in the same directory from
  4060.  ;         which this program is executed.
  4061.  
  4062. calculate_program_size:
  4063.  lea        -$102(pc), a1       ; Fetch basepage start address.
  4064.  lea        program_end, a0     ; Fetch program end address.
  4065.  trap       #6                  ; Return unused memory to op system.
  4066.  lea        stack, a7           ; Point A7 to this program's stack.
  4067.  trap       #0                  ; Enter supervisor mode.
  4068.  
  4069. get_name_of_print_buffer:
  4070.  
  4071.  ; The first byte of the array 'buffer_name_buffer' contains the maximum
  4072.  ; permissible length of the input string.  After the GEMDOS $A function
  4073.  ; invocation, the second byte of the array will contain the number of
  4074.  ; characters read as input; the carriage return character will not be included
  4075.  ; in the string length value.  The address of the first byte of the input
  4076.  ; string is that of the third byte of the array.
  4077.  
  4078.  lea        buffer_prompt, a0
  4079.  bsr        print_string
  4080.  lea        buffer_name_buffer, a3 ; Fetch structure's base address.
  4081.  pea        (a3)                   ; Push address of the array.
  4082.  move.w     #$A, -(sp)             ; GEMDOS readline function.
  4083.  trap       #1
  4084.  addq.l      #6, sp
  4085. store_name_for_header_use:
  4086.  move.b     1(a3), d0           ; Fetch length of buffer name.
  4087.  ext.w      d0                  ; Extend to word length.
  4088.  addq.l     #2, a3              ; Move to name element in array.
  4089.  movea.l    a3, a2              ; Prepare for suffix algorithm.
  4090.  adda.w     d0, a2              ; Same preparation.
  4091.  lea        buffer_name, a1     ; Fetch address of buffer_name array.
  4092.  subq.w     #1, d0              ; Initialize counter.
  4093. store_character:                ; Store name in another array for use
  4094.  move.b     (a3)+, (a1)+        ; in a header.
  4095.  dbra       d0, store_character
  4096. add_output_file_name_suffix:    ; Attach DAT suffix to buffer name.
  4097.  move.b     #$2E, (a2)+         ; Attach a period.
  4098.  move.b     #$44, (a2)+         ; Attach letter 'D'.
  4099.  move.b     #$41, (a2)+         ; Attach letter 'A'.
  4100.  move.b     #$54, (a2)+         ; Attach letter 'T'.
  4101.  move.b     #$0, (a2)           ; Finish with a NULL.
  4102.  
  4103. get_start_time:
  4104.  trap       #3                  ; Value of system clock returned in D0.
  4105.  move.l     d0, d4              ; Store time in D4.
  4106.  
  4107. print_characters:
  4108.  move.l     #3000, d6             ; Number of characters to print.
  4109.  move.w     d6, d3                ; Initialize loop counter.
  4110.  subq.w     #1, d3
  4111.  move.b     #$7A, d1
  4112. reset_generator:
  4113.  move.b     #$20, d5             ; Character number generator.
  4114. print_byte:
  4115.  cmp.b      d1, d5
  4116.  beq.s      reset_generator
  4117.  addq.b     #1, d5
  4118. printer_ready_test:
  4119.  btst       #0, $FFFA01         ; Check logic level of pin 11.
  4120.  bne.s      printer_ready_test  ; Loop until printer is ready to receive.
  4121.  lea        $FF8800, a2         ; Fetch address of PSG data bus.
  4122.  move.b     #$F, (a2)           ; Latch address of port B.     
  4123.  move.b     d5, 2(a2)           ; Write character to port B.
  4124.  move.b     #$E, (a2)           ; Latch address of port A.
  4125.  move.b     (a2), d0            ; Read content of port A. 
  4126.  andi.b     #$DF, d0            ; Reset bit 5 of d1 to zero.
  4127.  move.b     d0, 2(a2)           ; Reset bit 5 of port A. Strobe low.
  4128.  ori.b      #$20, d0            ; Set bit 5 of d1.
  4129.  move.b     d0, 2(a2)           ; Set bit 5 of port A. Strobe high.
  4130.  dbra       d3, print_byte      ; Print 3000 characters.
  4131.  
  4132. transfer_rate_calculations:
  4133.  trap       #3                  ; Fetch current time.
  4134.  move.l     d0, d5              ; Save end time.
  4135.  sub.l      d4, d0              ; Subtract start time from end time.
  4136. convert_time_to_milliseconds:   ; Multiply by 5.
  4137.  move.l     d0, d2              ; Save copy to add.
  4138.  asl.l      #2, d2              ; Multiply by 4.
  4139.  add.l      d0, d2              ; To complete multiplication by 5.
  4140. compute_transfer_rate:        
  4141.  move.l     #3000, d0           ; Fetch number of characters printed.
  4142.  divu       d2, d0               ; Divide size by time in milliseconds.
  4143. copy_to_d1:                      ; Will extract remainder from D1.
  4144.  move.l     d0, d1               ; Quotient is in lower word of D0.
  4145.  mulu       #1000, d0            ; Multiply quotient by 1000.
  4146.  clr.w      d1                   ; Get rid of quotient.
  4147.  swap       d1                   ; Remainder is now in lower word of D1.
  4148.  mulu       #1000, d1            ; Multiply remainder by 1000.
  4149.  divu       d2, d1               ; Divide remainder by time in milliseconds.
  4150.  swap       d1                   ; Put remainder in lower word.
  4151.  clr.w      d1                   ; Get rid of remainder.
  4152.  swap       d1                   ; Get quotient back in lower word.
  4153.  add.l      d0, d1               ; Transfer rate is now in D1.
  4154.  
  4155.  ; Note: The order in which things are done in the calculation algorithm
  4156.  ;       is critical.  The remainder obtained from the first division must
  4157.  ;       be manipulated just right.  One other point is that the quotient
  4158.  ;       will be zero whenever the time in milliseconds is a value greater
  4159.  ;       than the size of the file. 
  4160.  
  4161.  ;       I have verified the accuracy of the above algorithm, but I have
  4162.  ;       decided to print the number of 5 milliseconds clock ticks obtained
  4163.  ;       for both the start and end times.  This will permit the transfer
  4164.  ;       rate to be calculated manually.  Remember that those clock ticks
  4165.  ;       must be multiplied by 5 to convert them to milliseconds.  And, of
  4166.  ;       course, the number of milliseconds must be divided by 1000 to convert
  4167.  ;       them to seonds; or one can divide the file size by the number of
  4168.  ;       milliseconds and multiply the quotient by 1000.
  4169.  
  4170.  ; The equation being used in this algorithm is:
  4171.  
  4172.  ; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
  4173.  ;                                X 1000 (msec/second).
  4174.  
  4175.  trap       #4                  ; Convert to ASCII decimal.
  4176.  lea        transfer_rate, a1   ; Fetch address of transfer_rate.
  4177.  move.l     a0, (a1)            ; Store address of decimal string.
  4178.  
  4179.  ; NOTE: The variable 'transfer_rate' now contains the address of the
  4180.  ;       ASCII decimal string that is the transfer rate.  Trap #4 can't
  4181.  ;       be invoked until that value is used by the program, otherwise
  4182.  ;       the value will be corrupted.
  4183.  
  4184. create_output_file:
  4185.  move.w     #0, -(sp)           ; File attribute = read/write.
  4186.  pea        output_file         ; Push address of output file's name.
  4187.  move.w     #$3C, -(sp)         ; Function = f_create = GEMDOS $3C.
  4188.  trap       #1                  ; File handle is returned in D0.
  4189.  addq.l     #8, sp
  4190.  lea        output_file_handle, a1
  4191.  move.w     d0, (a1)
  4192.  
  4193. redirect_output_bound_for_screen:
  4194.  
  4195.  ; NOTE: If the output file's handle is exchanged with the video screen's
  4196.  ;       handle, then the printline function = GEMDOS $9 can be used to 
  4197.  ;       write to the file.
  4198.  
  4199.  
  4200. redirect_output:                ; Exchange file handle with screen's handle.
  4201.  move.w     d0, -(sp)           ; This is the disk file's handle.
  4202.  move.w     #1, -(sp)           ; This is the video screen's handle.
  4203.  move.w     #$46, -(sp)         ; Function = f_force = GEMDOS $46.
  4204.  trap       #1
  4205.  addq.l     #6, sp
  4206.  
  4207. print_data_report:
  4208.  lea        heading(pc), a0      ; Print data file heading.
  4209.  bsr        print_string
  4210.  lea        buffer_name(pc), a0  ; Name of buffer for which transfer rate is
  4211.  bsr        print_string         ; being reported.
  4212.  lea        header_1(pc), a0     ; Transfer Rate header.
  4213.  bsr        print_string
  4214.  move.l     transfer_rate, a0    ; Transfer Rate.  Fetch from trap #4
  4215.  bsr        print_string         ; location.  Quick before it goes away.
  4216.  lea        header_2(pc), a0     ; Transfer Rate Units.
  4217.  bsr        print_string
  4218.  lea        header_3(pc), a0     ; Characters printed header.
  4219.  bsr        print_string
  4220.  move.l     d6, d1               ; Fetch number of bytes printed from D6.           
  4221.  trap       #4                   ; Convert to ASCII decimal.
  4222.  bsr        print_string
  4223.  lea        header_7(pc), a0     ; End Time header.
  4224.  bsr        print_string
  4225.  move.l     d5, d1               ; Fetch end time.
  4226.  trap       #4                   ; Convert to ASCII decimal.
  4227.  bsr        print_string
  4228.  lea        header_8(pc), a0     ; Time units = 5-millisecond ticks.
  4229.  bsr        print_string
  4230.  lea        header_6(pc), a0     ; Start Time header.
  4231.  bsr        print_string
  4232.  move.l     d4, d1               ; Fetch start time.
  4233.  trap       #4                   ; Convert to ASCII decimal.
  4234.  bsr        print_string
  4235.  lea        header_8(pc), a0     ; Time units = 5-millisecond ticks.
  4236.  bsr        print_string
  4237.  
  4238. close_output_file:
  4239.  move.w     output_file_handle, -(sp) 
  4240.  move.w     #$3E, -(sp)         ; Function = GEMDOS $3E = f_close.
  4241.  trap       #1
  4242.  addq.l     #4, sp
  4243.  
  4244. terminate:
  4245.  andi.w     #$DFFF, SR           ; Return to User Mode.
  4246.  move.w     #0, -(sp)
  4247.  trap       #1
  4248.  
  4249. print_string:
  4250.  pea        (a0)
  4251.  move.w     #9, -(sp)
  4252.  trap       #1
  4253.  addq.l     #6, sp
  4254.  rts
  4255.  
  4256.  data
  4257. heading:      dc.b $D,$A,'PRG_7MP.TOS Execution Results',$D,$A,$D,$A
  4258.               dc.b '  Print Buffer:        ',0
  4259. header_1:     dc.b $D,$A,'  Transfer Rate:      ',0
  4260. header_2:     dc.b ' bytes/second',$D,$A,$D,$A,0
  4261. header_3:     dc.b '  Characters Printed: ',0
  4262. header_7:     dc.b $D,$A,$D,$A,'  End Time:      ',0
  4263. header_6:     dc.b '  Start Time:    ',0
  4264. header_8:     dc.b ' 5-millisecond ticks',$A,$D,0
  4265. buffer_prompt:
  4266.     dc.b $D,$A,$D,$A
  4267.     dc.b 'Data produced by this program will be stored in a file with',$D,$A
  4268.     dc.b 'name composed of the name of the print buffer and a DAT suffix.',$D,$A
  4269.     dc.b $D,$A,'Name of print buffer (8 characters max, no suffix): ',0
  4270.  
  4271. buffer_name_buffer:
  4272.                      dc.b  10  ; Maximum length of input string = 10 characters.
  4273.                      dc.b   0  ; String's length will be stored here.
  4274. output_file:         ds.b  14  ; String will be stored here.
  4275. buffer_name:         ds.b  10  ; Buffer name for heading.
  4276.  align
  4277. buffer:              dc.w  $0  ; Character buffer.
  4278.  bss
  4279. output_file_handle:  ds.w   1
  4280. transfer_rate:       ds.l   1  ; Pointer to ASCII decimal string.
  4281.                      ds.l  96  ; Program stack.
  4282. stack:               ds.l   0  ; Address of program stack.
  4283. program_end:         ds.l   0
  4284.  end
  4285.  
  4286.  
  4287. PRG_7NP.TOS Execution Results
  4288.  
  4289.   Print Buffer:        EXTERNAL
  4290.   Transfer Rate:       6451 bytes/second
  4291.  
  4292.   Characters Printed:  3000
  4293.  
  4294.   End Time:       21991 5-millisecond ticks
  4295.   Start Time:     21898 5-millisecond ticks
  4296.  
  4297.   
  4298. Program 74. Measures the data transfer rate of the linear buffer.  
  4299. The printing program has much less effect on the data transfer 
  4300. rate of the printing program/linear buffer combination than that 
  4301. experienced with printing program/circular buffer combinations.  
  4302. Program PRG_7IP.TOS or TTP must be executed prior to the 
  4303. execution of this program.
  4304.  
  4305.  ; Program Name: PRG_7NP.S
  4306.  ;      Version: 1.002
  4307.  
  4308.  ; Assembly Instructions:
  4309.  
  4310.  ;      Assemble in PC-relative mode and save with a TPP extension.
  4311.  
  4312.  ; Function:
  4313.  
  4314.  ;      Reads the file designated by the parameter line input.  The address
  4315.  ; of the buffer passed to the GEMDOS $3F function is that of the parallel
  4316.  ; interface interrupt handler installed by PRG_7IP.TOS or TTP.  This program
  4317.  ; is a version of PRG_7JP.  This version measures the data transfer rate for
  4318.  ; the PRG_7IP/PRG_7JP combination.  The name of this program's output file is
  4319.  ; PRG_7IP.DAT.
  4320.  
  4321.  ;      This program initiates the interrupt controlled printing process by
  4322.  ; sending a NULL character to the printer.
  4323.  
  4324.  ; Execution Instructions:
  4325.  
  4326.  ;      PRG_7IP.TOS or TTP must install the parallel interface interrupt
  4327.  ; handler before this program is executed.  Execute this program from the
  4328.  ; desktop.  This program and the file to be printed must reside in the same
  4329.  ; directory.
  4330.  
  4331. fetch_pertinent_addresses:
  4332.  lea        -$82(pc), a3         ; Fetch "command line" address.
  4333.  lea        -$80(a3), a1         ; Fetch "basepage" address.
  4334.  lea        program_end, a0      ; Fetch "end of program" address.
  4335.  lea        stack, a7            ; Fetch stack address.
  4336.  lea        dta, a5              ; Fetch dta buffer address.
  4337.  
  4338. calculate_program_size:
  4339.  suba.l     a1, a0             
  4340. return_unused_memory:
  4341.  pea        (a0)                 ; Push program length.
  4342.  pea        (a1)                 ; Push basepage address.
  4343.  move.l     #$4A0000, -(sp)      ; Function = m_shrink = GEMDOS $4A.
  4344.  trap       #1                   ; Invoke GEMDOS exception.
  4345.  lea        $C(a7), sp           ; Reset stack pointer to top of stack.
  4346.  
  4347. set_dta:
  4348.  pea        (a5)                 ; dta = address of 44 byte buffer.
  4349.  move.w     #$1A, -(sp)          ; GEMDOS function = set dta.
  4350.  trap       #1
  4351.  addq.l     #6, sp
  4352.  
  4353. get_drive:
  4354.  move.w     #$19, -(sp)          ; See Internals page 116, but note the 
  4355.  trap       #1                   ; errors there.
  4356.  addq.l     #2, sp               ; Note first error at this instruction.
  4357.  add.w      #$41, d0             ; Note 2nd error at this instruction.
  4358.  lea        path, a6             ; Fetch address of path array.
  4359.  move.b     d0, (a6)+            ; Store drive letter in path array.
  4360.  move.b     #$3A, (a6)+          ; Store a colon in path array.
  4361.  
  4362. get_directory_path:
  4363.  move.w     #0, -(sp)
  4364.  pea        (a6)                 ; Push address of next path array element.
  4365.  move.w     #$47, -(sp)          ; See Internals page 135.
  4366.  trap       #1
  4367.  addq.l     #8, sp
  4368.  
  4369. process_command_line:
  4370.  move.b     (a3)+, d0            ; Fetch command line character count.
  4371.  ext.w      d0                   ; Extend to word for next instruction.
  4372.  move.b     #0, 0(a3,d0.w)       ; Store a null at end of command line input.
  4373.  lea        file_name, a0        ; Fetch address of variable.
  4374.  move.l     a3, (a0)             ; Store address of file name for output data.
  4375.  
  4376. search_for_file: 
  4377.  move.w     #0, -(sp)            ; Attribute = normal access.
  4378.  pea        (a3)                 ; Push address of file name.
  4379.  move.w     #$4E, -(sp)          ; GEMDOS function = search first.
  4380.  trap       #1
  4381.  addq.l     #8, sp
  4382.  tst        d0
  4383.  bne        terminate            ; Terminate if file not found.
  4384.  
  4385. process_interrupt_handler_variables:
  4386.  pea        fetch_interrupt_handler_address
  4387.  move.w     #$26, -(sp)          ; Execute a routine in supervisor mode.
  4388.  trap       #14               
  4389.  addq.l     #6, sp
  4390.  
  4391.  lea        handler_address, a4  ; Fetch handler address.
  4392.  movea.l    (a4), a4
  4393.  adda.l     #$16C, a4            ; Add offset to variable "filesize".
  4394.  move.l     $1A(a5), d6          ; Fetch file size.
  4395.  move.l     d6, (a4)             ; Store file size in handler variable.
  4396.  adda.l     #$188, a4            ; Add offset to buffer variable.
  4397.  
  4398. open_file:                       ; Function returns file handle in D0.
  4399.  move.w     #0, -(a7)            ; Open as read only.
  4400.  pea        (a3)                 ; Push address of file name.
  4401.  move.w     #$3D, -(a7)          ; See Internals page 127.
  4402.  trap       #1
  4403.  addq.l     #8, a7
  4404.  move.w     d0, d3               ; Store file handle in D3.
  4405.  trap       #3                   ; Value of system clock returned in D0.
  4406.  move.l     d0, d4               ; Store time in D4.
  4407.  
  4408. read_file:
  4409.  pea        (a4)                 ; Push buffer address
  4410.  move.l     $1A(a5), -(sp)       ; Number of bytes to read.
  4411.  move.w     d3, -(sp)            ; File's handle number.
  4412.  move.w     #$3F, -(sp)          ; GEMDOS function = read.
  4413.  trap       #1
  4414.  lea        $C(sp), sp           ; Reposition stack pointer.
  4415.  
  4416. close_file:
  4417.  move.w     d3, -(sp)            ; Push file handle.
  4418.  move.w     #$3E, -(sp)          ; See Internals page 128.
  4419.  trap       #1
  4420.  addq       #4, sp
  4421.  
  4422. transfer_rate_calculations:
  4423.  trap       #3                   ; Fetch current time.
  4424.  move.l     d0, d5               ; Store end time for data file.
  4425.  sub.l      d4, d0               ; Subtract start time from end time.
  4426. convert_time_to_milliseconds:    ; Multiply by 5.
  4427.  move.l     d0, d2               ; Save copy to add.
  4428.  asl.l      #2, d2               ; Multiply by 4.
  4429.  add.l      d0, d2               ; To complete multiplication by 5.
  4430. compute_transfer_rate:        
  4431.  move.l     d6, d0               ; Fetch file size from D6.
  4432.  divu       d2, d0               ; Divide size by time in milliseconds.
  4433. copy_to_d1:                      ; Will extract remainder from D1.
  4434.  move.l     d0, d1               ; Quotient is in lower word of D0.
  4435.  mulu       #1000, d0            ; Multiply quotient by 1000.
  4436.  clr.w      d1                   ; Get rid of quotient.
  4437.  swap       d1                   ; Remainder is now in lower word of D1.
  4438.  mulu       #1000, d1            ; Multiply remainder by 1000.
  4439.  divu       d2, d1               ; Divide remainder by time in milliseconds.
  4440.  swap       d1                   ; Put remainder in lower word.
  4441.  clr.w      d1                   ; Get rid of remainder.
  4442.  swap       d1                   ; Get quotient back in lower word.
  4443.  add.l      d0, d1               ; Transfer rate is now in D1.
  4444.  
  4445.  ; Note: The order in which things are done in the calculation algorithm
  4446.  ;       is critical.  The remainder obtained from the first division must
  4447.  ;       be manipulated just right.  One other point is that the quotient
  4448.  ;       will be zero whenever the time in milliseconds is a value greater
  4449.  ;       than the size of the file. 
  4450.  
  4451.  ;       I have verified the accuracy of the above algorithm, but I have
  4452.  ;       decided to print the number of 5 milliseconds clock ticks obtained
  4453.  ;       for both the start and end times.  This will permit the transfer
  4454.  ;       rate to be calculated manually.  Remember that those clock ticks
  4455.  ;       must be multiplied by 5 to convert them to milliseconds.  And, of
  4456.  ;       course, the number of milliseconds must be divided by 1000 to convert
  4457.  ;       them to seonds; or one can divide the file size by the number of
  4458.  ;       milliseconds and multiply the quotient by 1000.
  4459.  
  4460.  ; The equation being used in this algorithm is:
  4461.  
  4462.  ; transfer rate (bytes/second) = [file size (bytes)/transfer time (msec)]
  4463.  ;                                X 1000 (msec/second). 
  4464.    
  4465.  trap       #4                   ; Convert to ASCII decimal.
  4466.  lea        transfer_rate, a1
  4467.  move.l     a0, (a1)             ; Store address of decimal string.
  4468.  
  4469.  ; NOTE: The variable 'transfer_rate' now contains the address of the
  4470.  ;       ASCII decimal string that is the transfer rate.  Trap #4 can't
  4471.  ;       be invoked until that value is used by the program, otherwise
  4472.  ;       the value will be corrupted.
  4473.  
  4474. create_output_file:
  4475.  move.w     #0, -(sp)            ; File attribute = read/write.
  4476.  pea        output_file          ; Push address of output file's name.
  4477.  move.w     #$3C, -(sp)          ; Function = f_create = GEMDOS $3C.
  4478.  trap       #1                   ; File handle is returned in D0.
  4479.  addq.l     #8, sp
  4480.  lea        output_file_handle, a0
  4481.  move.w     d0, (a0)
  4482.  
  4483. redirect_output_bound_for_screen:
  4484.  
  4485.  ; NOTE: If the output file's handle is exchanged with the video screen's
  4486.  ;       handle, then the printline function = GEMDOS $9 can be used to 
  4487.  ;       write to the file.
  4488.  
  4489.  
  4490. redirect_output:                 ; Exchange file handle with screen's handle.
  4491.  move.w     d0, -(sp)            ; This is the disk file's handle.
  4492.  move.w     #1, -(sp)            ; This is the video screen's handle.
  4493.  move.w     #$46, -(sp)          ; Function = f_force = GEMDOS $46.
  4494.  trap       #1
  4495.  addq.l     #6, sp
  4496.  
  4497. print_data_report:
  4498.  lea        heading, a0          ; Print data file heading.
  4499.  bsr        print_string
  4500.  lea        header_1, a0         ; Transfer Rate header.
  4501.  bsr        print_string
  4502.  move.l     transfer_rate, a0    ; Transfer Rate.  Fetch from trap #4
  4503.  bsr        print_string         ; location.  Quick before it goes away.
  4504.  lea        header_2, a0         ; Transfer Rate Units.
  4505.  bsr        print_string
  4506.  lea        header_3, a0         ; File name header.
  4507.  bsr        print_string
  4508.  lea        file_name, a1        ; File name.
  4509.  movea.l    (a1), a0             ; File's name is stored in command line.
  4510.  bsr        print_string
  4511.  lea        header_4, a0         ; File length header.
  4512.  bsr        print_string
  4513.  move.l     d6, d1               ; Fetch file size from D6.           
  4514.  trap       #4                   ; Convert to ASCII decimal.
  4515.  bsr        print_string
  4516.  lea        header_5, a0         ; File size units.
  4517.  bsr        print_string
  4518.  lea        header_9, a0         ; Directory path.
  4519.  bsr        print_string
  4520.  lea        path, a0
  4521.  bsr        print_string
  4522.  lea        header_7, a0         ; End Time header.
  4523.  bsr        print_string
  4524.  move.l     d5, d1               ; Fetch end time.
  4525.  trap       #4                   ; Convert to ASCII decimal.
  4526.  bsr        print_string
  4527.  lea        header_8, a0         ; Time units = 5-millisecond ticks.
  4528.  bsr        print_string
  4529.  lea        header_6, a0         ; Start Time header.
  4530.  bsr        print_string
  4531.  move.l     d4, d1               ; Fetch start time.
  4532.  trap       #4                   ; Convert to ASCII decimal.
  4533.  bsr        print_string
  4534.  lea        header_8, a0         ; Time units = 5-millisecond ticks.
  4535.  bsr        print_string
  4536.  
  4537. close_output_file:
  4538.  move.w     output_file_handle, -(sp) 
  4539.  move.w     #$3E, -(sp)          ; Function = GEMDOS $3E = f_close.
  4540.  trap       #1
  4541.  addq.l     #4, sp
  4542.  
  4543. initiate_interrupt_process:      ; Execute in supervisor mode.
  4544.  pea        send_null
  4545.  move.w     #$26, -(sp)
  4546.  trap       #14               
  4547.  addq.l     #6, sp
  4548.  
  4549. terminate:
  4550.  move.w     #0, -(sp)
  4551.  trap       #1
  4552.  
  4553. fetch_interrupt_handler_address:     
  4554.  lea        handler_address, a0  
  4555.  move.l     $100, (a0)           ; Interrupt handler address is stored at
  4556.  rts                             ; location $100.
  4557.  
  4558. send_null:                       ; The printer will drop the logic level of
  4559.  moveq.l    #0, d0               ; pin 11 from high to low after receiving this
  4560.                                  ; character, even though nothing will be
  4561.                                  ; printed.
  4562.  lea        $FF8800, a1          ; Address of Programmable Sound Generator.
  4563.  move.b     #$F, (a1)            ; Select port B (register 17) of PSG.
  4564.  move.b     d0, 2(a1)            ; Write character to PSG register 17.
  4565.  move.b     #$E, (a1)            ; Select port A (register 16) of PSG.
  4566.  move.b     (a1), d0             ; Get current register 16 value.
  4567.  
  4568. strobe_low:                   
  4569.  andi.b     #$DF, d0             ; Bit 5 to zero.          
  4570.  move.b     d0, 2(a1)            ; Bit 5 of port A to zero.
  4571.                               
  4572. strobe_high:                  
  4573.  ori.b      #$20, d0             ; Bit 5 to one.
  4574.  move.b     d0, 2(a1)            ; Bit 5 of port A to one.
  4575.  rts
  4576.  
  4577. print_string:
  4578.  pea        (a0)
  4579.  move.w     #9, -(sp)
  4580.  trap       #1
  4581.  addq.l     #6, sp
  4582.  rts
  4583.  
  4584.  data
  4585. heading:      dc.b $D,$A,'PRG_7NP.TOS Execution Results',$D,$A,$D,$A
  4586.               dc.b '  Print Buffer:   PRG_7IP',0
  4587. header_1:     dc.b $D,$A,'  Transfer Rate: ',0
  4588. header_2:     dc.b ' bytes/second',$D,$A,0
  4589. header_3:     dc.b '  File Printed:   ',0
  4590. header_4:     dc.b $D,$A,'  File Length:   ',0
  4591. header_5:     dc.b ' bytes',$D,$A,0
  4592. header_9      dc.b $D,$A,'  Path: ',0
  4593. header_7:     dc.b $D,$A,$D,$A,'  End Time:      ',0
  4594. header_6:     dc.b '  Start Time:    ',0
  4595. header_8:     dc.b ' 5-millisecond ticks',$A,$D,0
  4596. output_file:  dc.b 'PRG_7IP.DAT',0
  4597.  bss
  4598. output_file_handle:  ds.w   1
  4599. transfer_rate:       ds.l   1
  4600. dta:                 ds.l  11
  4601. file_name:           ds.l   1   ; Address of command line (input file name).
  4602. handler_address:     ds.l   1
  4603. path:                ds.b 120 
  4604.                      ds.l  96   ; Program stack.
  4605. stack:               ds.l   0   ; Address of program stack.
  4606. program_end:         ds.l   0
  4607.  end
  4608.  
  4609.  
  4610. PRG_7NP.TOS Execution Results
  4611.  
  4612.   Print Buffer:   PRG_7IP
  4613.   Transfer Rate:  434133 bytes/second
  4614.   File Printed:   PRG_7NP.S
  4615.   File Length:    13024 bytes
  4616.  
  4617.   Path: P:        ; Ram disk.
  4618.  
  4619.   End Time:       333018 5-millisecond ticks
  4620.   Start Time:     333012 5-millisecond ticks
  4621.  
  4622.  
  4623. PRG_7NP.TOS Execution Results
  4624.  
  4625.   Print Buffer:   PRG_7IP
  4626.   Transfer Rate:  41346 bytes/second
  4627.   File Printed:   PRG_7NP.S
  4628.   File Length:    13024 bytes
  4629.  
  4630.   Path: E:\PRG_7  ; Hard disk.
  4631.  
  4632.   End Time:       462620 5-millisecond ticks
  4633.   Start Time:     462557 5-millisecond ticks
  4634.  
  4635.   
  4636. PRG_7NP.TOS Execution Results
  4637.  
  4638.   Print Buffer:   PRG_7IP
  4639.   Transfer Rate:  9336 bytes/second
  4640.   File Printed:   PRG_7NP.S
  4641.   File Length:    13024 bytes
  4642.  
  4643.   Path: A:\PRG_7
  4644.  
  4645.   End Time:       426667 5-millisecond ticks
  4646.   Start Time:     426388 5-millisecond ticks
  4647.  
  4648.   
  4649.      The final program in this chapter has been designed to help 
  4650. you to find out a few facts about your printer's buffers.  It 
  4651. should permit you to determine the length of your printer's line 
  4652. buffer and some details of its operation.  It's best that you 
  4653. read the program completely so that you can understand just what 
  4654. it attempts to accomplish.  Program 75 is short and uncluttered, 
  4655. so you should be able to alter it to conform to any particular 
  4656. experiment you might want to attempt.
  4657.  
  4658. Program 75. A program to let you experiment with your 
  4659. printer's line buffer.
  4660.  
  4661.  ; Program Name: PRG_7OP.S
  4662.  ;      Version: 1.002    
  4663.  
  4664.  ; Assembly Instructions:
  4665.  
  4666.  ;      Assemble in PC-relative mode and save with a TOS suffix.
  4667.  
  4668.  ; Function:
  4669.  
  4670.  ;      Provides direct output via the Parallel Port Interface.  This program
  4671.  ; is to be used to illustrate the operation of a printer's line buffer.  First
  4672.  ; it prints a short string which is terminated with a carriage return/line
  4673.  ; feed.  Then it prints a character string that is greater than 200 characters
  4674.  ; long so that the length of the line buffer can be determined.  Finally, it
  4675.  ; prints a short string with no terminating carriage return/line feed.  
  4676.  
  4677. mainline:
  4678.  
  4679.  ; NOTE: Must enter supervisor mode in order to access MFP and PSG addresses.
  4680.  
  4681. enter_supervisor_mode:
  4682.  move.l    #0, -(sp)
  4683.  move.w    #$20, -(sp)          ; Function = SUPER.
  4684.  trap      #1                   ; Supervisor mode is active after TRAP.
  4685.  addq.l    #6, sp               ; D0 = SSP.
  4686.  
  4687. print_string_1:
  4688.  lea       string_1, a3         ; Fetch address of string.
  4689.  bsr       print_character      ; Print the string.
  4690. print_strin_2:
  4691.  lea       string_2, a3        
  4692.  bsr       print_character
  4693. wait_for_return_key_press:
  4694.  move.w    #8, -(sp)
  4695.  trap      #1
  4696.  addq.l    #2, sp
  4697. print_string_3:
  4698.  lea       string_3, a3
  4699.  bsr       print_character 
  4700.  
  4701. enter_user_mode:
  4702.  move.l    d0, -(sp)            ; D0 contains SSP.
  4703.  move.w    #$20, -(sp)          ; Function = SUPER.
  4704.  trap      #1                   ; User mode is active after TRAP.
  4705.  addq.l    #6, sp
  4706.  
  4707. terminate:
  4708.  move.w    #0, -(sp)           ; Function = p_term_old = GEMDOS $0.
  4709.  trap      #1                  ; GEMDOS call.
  4710.  
  4711. print_character:
  4712.  move.b    (a3)+, d1
  4713.  beq       return
  4714. printer_ready_test:
  4715.  btst      #0, $FFFA01          ; Check logic level of pin 11.
  4716.  bne.s     printer_ready_test   ; Loop until printer is ready to receive.
  4717.  lea       $FF8800, a2          ; Fetch address of PSG data bus.
  4718.  move.b    #$F, (a2)            ; Latch address of port B.     
  4719.  move.b    d1, 2(a2)            ; Write character to port B.
  4720.  move.b    #$E, (a2)            ; Latch address of port A.
  4721.  move.b    (a2), d1             ; Read content of port A. 
  4722.  andi.b    #$DF, d1             ; Reset bit 5 of d1 to zero.
  4723.  move.b    d1, 2(a2)            ; Reset bit 5 of port A. Strobe low.
  4724.  ori.b     #$20, d1             ; Set bit 5 of d1.
  4725.  move.b    d1, 2(a2)            ; Set bit 5 of port A. Strobe high.
  4726.  bra       print_character
  4727. return:
  4728.  rts
  4729.  
  4730.  data
  4731. string_1: dc.b  'This is the short string with cr/lf.',$D,$A,0
  4732. string_2: dc.b  'This is the long string which does not terminate '
  4733.           dc.b  'with a carriage return/line feed.  It is greater than two '
  4734.           dc.b  'hundred characters long, which should be longer than '
  4735.           dc.b  'the printers line buffer.  All but the last line should '
  4736.           dc.b  'probably print because the line buffer fills; but '
  4737.           dc.b  'you should probably have to turn the printer off-line, '
  4738.           dc.b  'then back on the get the final line to print.  The ',
  4739.           dc.b  'program will pause for you to do this.',0
  4740. string_3: dc.b  'This string has no cr/lf.  The off-line/on-line sequence '
  4741.           dc.b  'should make it print.',0 
  4742.  align
  4743.  end
  4744.  
  4745. A Note About GEMDOS Function $46
  4746.   
  4747.      I have used this function in several programs to redirect 
  4748. data flow via GEMDOS function $9 so that the string of characters 
  4749. is written to a file instead of to the screen.  If you read the 
  4750. description of this function on page 134 of the Internals book, I 
  4751. think that you will conclude, as I did, that it should also be 
  4752. possible to redirect output bound for the printer to a file also.  
  4753. I know that the reference seems to indicate that GEMDOS function 
  4754. $46 might be more appropriately used in conjunction with the 
  4755. Write functions, but you have seen that I use it with the Print 
  4756. Line function without problem.  Anyway, I have not been able to 
  4757. use function $46 to redirect output from the printer to a file.  
  4758. I can't conclude that the function doesn't work that way, but I 
  4759. think that it would be a valuable asset if it did work in the 
  4760. manner that I would like.
  4761.  
  4762. Conclusion
  4763.  
  4764.      I have always considered the output from a computer to be 
  4765. its most valuable service.  I've never been impressed with what a 
  4766. computer could do unless it could show me the results quickly and 
  4767. in forms that permitted me to rapidly assess the output data.  
  4768. But I've always been amazed by the lack of attention to detail 
  4769. paid by most reference books and owner's manuals concerning such 
  4770. output.  Let me not be guilty of that in this book.
  4771.      At the time that I am writing this, I am reading the newest 
  4772. COMPUTE!'s technical reference guide for the Atari ST, TOS, by 
  4773. Sheldon Leemon.  There is sufficient assembly language references 
  4774. in the book to permit me to heartily recommend it, if you can 
  4775. afford the purchase.  Much of the material in the book is also 
  4776. covered in the Internals book, but in many areas Leemon does a 
  4777. better job of presenting the material.  I wish that I could as 
  4778. heartily recommend COMPUTE!'s assembly language programming book, 
  4779. but I find it to be a feeble attempt.  Nevertheless, it is 
  4780. certainly much better than the Abacus attempt on the same 
  4781. subject.
  4782.  
  4783.