Volume 2 -- Issue 5 | February 1982

In This Issue...

- DOS Error Trapping from Machine Language
- Improving the EPSON Controller Card
- Even Faster Primes
- Printer Handler with FIFO Buffer
- Patches for Applewriter to Unhook PLE
- A Great Free Adventure
- On Dividing by Ten

Renew Now, the Price is Going Up

If you renew your subscription before March 1, 1982, you can renew at the current rate of $12/year. Starting March 1st, the price will go up to $15/year (2nd class mail in the USA). Subscriptions sent First Class Mail to USA, Canada, and Mexico will be $18/year. Air Mail subscriptions to all other countries will be $28/year. The price for back issues will be $1.50 each (plus $1.00 postage outside of USA, Canada, and Mexico).

S-C MACRO Assembler II is almost here!

By the time you read this, I expect to be filling orders for the new MACRO version. This is what I have been calling Version 5.0, but I have decided to call it S-C MACRO Assembler II instead. Version 4.0 will still be sold at $55. The MACRO version will be $80. Owners of Version 4.0 can upgrade for only $27.50. There will be an all new manual, rather than the current 2-part manual.

The MACRO Assembler includes macros (of course!), conditional assembly, EDIT, COPY, global string replacement, and many more new features. And it assembles even faster than version 4.0!

Problem with QD#5

The first 14 copies that I sent out of Quarterly Disk #5 were incomplete. I forgot to include PMD and FPSUBS. If you have one of those with serial #1 thru #14, send it back; I will add the programs and return it. I'm sorry!

Subscriptions Around the World

We are now sending AAL to over 800 subscribers. Of course most of these are in the U.S.A., but an increasing number are subscribing from other countries. We now have:

         15 -- Canada            2 -- Hong Kong
          5 -- Sweden            2 -- Ireland
          5 -- New Zealand       1 -- Israel
          4 -- France            1 -- Italy
          4 -- Japan             1 -- Netherlands
          3 -- Australia         1 -- Qatar
          3 -- England           1 -- Saudi Arabia
          3 -- West Germany      1 -- Spain
          3 -- South Africa      1 -- Thailand
          2 -- Argentina         1 -- Turkey
          2 -- Belgium

And there are also at least a half dozen subscribers with APO addresses, who are stationed in strange exotic lands.

DOS Error Trapping from Machine Language | Lee Meador

I have been working on a text editor program for about three years now at the World Bible Translation Center. It allows us to edit in any two of the following languages: English, Russian, Greek, Hebrew, and Arabic. Hebrew and Arabic move from right to left across the screen, as they should.

Recently we have been making some enhancements to this multi-lingual text editor (called ALPHONSE) which include support of two disk drives (a program disk in drive 1 and a data disk in drive 2). But we didn't want to require the use of two drives. That means a routine must look on the various disks to see if the data is there. We can do this very handily by RENAMEing a certain file -- call it FILE -- and assuming that a DOS error means that the data isn't on that disk. Then we can look on other drives and finally, if it isn't found anywhere, we can prompt the user to put in the data disk. Then we look again -- and so on.

A problem with this is that I need to trap from assembly language any DOS errors which occur, but I want to return to the program if the user accidentally types the RESET key (with ALPHONSE it will always be accidentally). A second use for DOS error trapping came up because I/O errors in a disk file print the error message but do not change from the HIRES page to the text page. That makes it rather difficult to see what the error is -- especially for the less advanced user, who has no idea what is happening.

Below is a program listing of ALPHONSE with all the insides removed. Where the real program would have large sections of code, I have instead comments that look like this:

         *->->->->->->->->->->->->->->->->->->->
         * DO SOME ACTION WHICH IS NOT SHOWN
         *->->->->->->->->->->->->->->->->->->->

(Of course, ALPHONSE is about 8K long and the listing is nearly 1/2-inch thick, so this isn't the whole listing.)

MAIN PROGRAM OUTLINE

Here is an outline of the main program:

         MACH:   GLOBAL INITIALIZATION;
         REENT:  LOCAL INITIALIZATION;
                 REPEAT
                   READ EDITOR COMMAND;
                   PROCESS EDITOR COMMAND;
                 UNTIL EDITOR COMMAND = QUIT;
         END.

In the global initialization we have to do four things related to error-trapping:

> 1. Call SETUP.DOS.TABLE to copy my addresses into the table at $9D56 of DOS. This makes DOS come back to my program when any soft entry of a funny DOS command occurs. Just calling SETUP.DOS.TABLE will not really trap any errors, but it will keep DOS from terminating your program if a DOS error does occur (that usually means SYNTAX ERROR, I/O ERROR, or FILE NOT FOUND).

> 2. Call CLEAR.ERROR to initialize the ONERR trapping mechanism in my program.

> 3. Call ON.ERROR with the address of the error-handling routine in the A and Y registers (LO, HI). This sets up the DOS error-handling capabilities as if Applesoft were running and ONERR were set.

> 4. After doing all the global initialization of files and such, we need to call OFF.ERROR to turn off the error handling that ON.ERROR set up. After calling OFF.ERROR any DOS error will beep and go to the soft entry point. (We have already set the soft entry point in step one to be MY.RESET.)

In the local initialization we take care of a few more things that have to be done every time the program is run -- not just the first time. The call to OFF.ERROR cleared any error trapping so we can call SETUP.DOS.TABLE and CLEAR.ERROR again without causing any problems.

Note that the call to LOOK.FOR.FILE changes the error address so we have to call ON.ERROR with MY.ERROR again to make sure that an error doesn't throw us off into never-never land. LOOK.FOR.FILE returns the carry clear if FILE is found. Carry set signals that the file isn't on any available drives; in that case, ALPHONSE would print a message like "INSERT DATA DISK AND HIT ANY KEY," then wait for a key to be pushed and call LOOK.FOR.FILE another time.

The main program loop is not really of interest here, but it is shown in the listing in skeleton form.

SUB-PROGRAMS

Now, how do the subroutines work? First, the one that you wouldn't use in your program: LOOK.FOR.FILE has to save the stack pointer. This is because we expect DOS errors to occur inside the routine. A DOS error will mess up the stack. Saving the stack lets us remember where we were. (By the way, DOS just adds things to the stack and never removes them when there is an error. The LOOK.FOR.FILE return addresses will not be messed up.)

LOOK.FOR.FILE sets its own DOS error trap address. Then the program looks through trying to find FILE on the various slots and drives. It does this by printing the DOS commands <CTRL-D>, RENAME FILE, FILE, Sx, Dy with x and y filled in. Appropriate values for x are six, five, and seven; y would be one or two. The order in which you try the slot/drive combinations will determine which of two disks are chosen if you put two data disks in at the same time. I used a table of six slot/drive combinations to choose the order and positions to try. Notice that before printing the DOS RENAME command, I had to check to see if there was a disk card in the slot. Choosing a slot without a disk card in it for a DOS command will cause DOS to hang when you try the next DOS command with a different slot. DOS is waiting for the last drive to quit running. Little does DOS know that an empty slot always seems to be running (to DOS at least).

If the DOS RENAME command fails or there is no disk card in the slot, LOOK.FOR.FILE will jump to LOOK.ERR to loop and try the next slot/drive. If it runs out of slot/drives the program returns with carry set to indicate FILE was not found. Carry clear indicates that the last-used drive has FILE on it.

There are several routines you might want to copy as is to your program. Calling them takes care of error trapping and reset trapping.

SETUP.DOS.TABLE: copies MY.TABLE into DOS to jump to my program on any DOS error or RESET. Unfortunately, at this point you can't tell them apart.

ON.ERROR: sets the error address to the value in the A, Y (LO, HI) registers. When a DOS error occurs after ON.ERROR has been called, DOS will jump to this address with the error number in the X register. All other registers will have been changed.

OFF.ERROR: turns off the error trapping and resets DOS to the state it was in before ON.ERROR was first called. SAVE.AAB6 is used to keep track of which BASIC language DOS thinks was active. Restoring AAB6 before exiting your program will help DOS keep things sorted out. Calling OFF.ERROR restores AAB6. (By the way, while ON.ERROR is active, DOS thinks that Applesoft is currently running a program and that there has been an ONERR statement. Zero page locations $D8, $76, and $33 are used for this.)

CLEAR.ERROR: call this the first thing in your program to set up the flags used by ON.ERROR and OFF.ERROR.

Note: MY.RESET just reenters the program loop if someone types the RESET key. That makes it a null key. MY.ERROR should be looked at to see how the DOS error message comes back to you. You can use the message to print various messages depending upon what is wrong. Or, you can take various actions depending upon the error message. Pages 114-115 of the DOS manual show what the various error numbers are that come back in the X register.

The program listing should show how most of these things are handled.

     1000         .OR $803
     1010  *--------------------------------
     1020  *
     1030  * ALPHONSE - MULTI-LINGUAL TEXT EDITOR
     1040  *
     1050  * Chopped up to show ERROR trapping
     1060  *  ala Applesoft ONERR command.
     1070  *  NOTE: There is no RESUME but
     1080  *  you are able to easily pick
     1090  *  up DOS errors and handle them
     1100  *  while disabling the RESET
     1110  *  (on AutoStart ROM).
     1120  *
     1130  * by Lee Meador
     1140  *
     1150  *      MACH - Main program entry
     1160  *      REENT- Program re-entry
     1170  *      ULOOP- Main program loop
     1180  *      MY.RESET- handle RESET key pushed
     1190  *      MY.ERROR- default error handler
     1200  *      END  - Exit to BASIC
     1210  *      SETUP.DOS.TABLE- hook in RESET trapping
     1220  *      ON.ERROR - set error trap
     1230  *      OFF.ERROR- kill error trap
     1240  *      CLEAR.ERROR- init error flags
     1250  *      LOOK.FOR.FILE- find S,D of FILE
     1260  *      MY.TABLE - copied into DOS table
     1270  *
     1280  *--------------------------------
     1290  DOS.TABLE .EQ $9D56
     1300  HOME.TEXT .EQ $FC58
     1310  TMP1      .EQ 0     PAGE 0
     1320  *--------------------------------
     1330  *
     1340  * THIS IS THE MAIN ENTRY POINT
     1350  *  FOR ALPHONSE.
     1360  *
     1370  *--------------------------------
     1380  MACH   JSR SETUP.DOS.TABLE
     1390         JSR CLEAR.ERROR  NO ONERR
     1400         LDA #MY.ERROR    THEN SET IT
     1410         LDY /MY.ERROR    .. TO MY.ERROR
     1420         JSR ON.ERROR
     1430         JSR HOME.TEXT CLR TXT SCR
     1440  *->->->->->->->->->->->->->->->->
     1450  * DO INITIALIZE PROCESSING
     1460  *->->->->->->->->->->->->->->->->
     1470         JSR OFF.ERROR ON ERR TURNED OFF
     1480  *--------------------------------
     1490  * RE-ENTRY POINT. NORMAL ENTRY COMES HERE TOO
     1500  *--------------------------------
     1510  REENT  JSR SETUP.DOS.TABLE
     1520         JSR CLEAR.ERROR NO ON ERR
     1530  .10    JSR LOOK.FOR.FILE
     1540         BCC LOAD.FILE
     1550         LDA #MY.ERROR     SET ERROR
     1560         LDY /MY.ERROR     .. TO MY.ERROR
     1570         JSR ON.ERROR
     1580         JSR HOME.TEXT CLEAR SCRN
     1590  *->->->->->->->->->->->->->->->->
     1600  * PRINT "INSERT CORRECT DISK"
     1610  *->->->->->->->->->->->->->->->->
     1620         JMP .10       TRY AGAIN TO FIND TEXT.DIR
     1630  *--------------------------------
     1640  LOAD.FILE
     1650         LDA #MY.ERROR FIX ERROR HANDLER
     1660         LDY /MY.ERROR
     1670         JSR ON.ERROR
     1680  *->->->->->->->->->->->->->->->->
     1690  * THE REST OF INITIALIZING
     1700  *->->->->->->->->->->->->->->->->
     1710
     1720  ULOOP
     1730  *->->->->->->->->->->->->->->->->
     1740  * MAIN PROGRAM LOOP DOES EACH
     1750  *  COMMAND TYPED
     1760  * EXIT COMMAND JUMPS TO "END"
     1770  *->->->->->->->->->->->->->->->->
     1780         JMP ULOOP    .. LOOP IF UNDEF
     1790  *--------------------------------
     1800  *
     1810  * ROUTINE TO HANDLE USER HITTING
     1820  *  RESET. (HANDLED IN DOS--DOS
     1830  *  FIXES IT ON $3D3 EXIT.)
     1840  * NO HOOKS TO CHANGE AND FIX BACK
     1850  *
     1860  *--------------------------------
     1870
     1880  MY.RESET
     1890  *->->->->->->->->->->->->->->->->
     1900  * RESET POINTERS AND HIRES PAGE2
     1910  *->->->->->->->->->->->->->->->->
     1920         JMP ULOOP
     1930
     1940  *--------------------------------
     1950  *
     1960  * MY GENERAL ERROR HANDLER JUST
     1970  *  PRINTS "ERROR NUMBER " AND
     1980  *  THE NUMBER FOR THE ERROR THEN
     1990  *  EXITS TO WHATEVER BASIC WAS
     2000  *  RUNNING BEFORE.
     2010  *
     2020  *--------------------------------
     2030  MY.ERROR
     2040         TXA          SAVE ERR NUM
     2050         PHA
     2060  *->->->->->->->->->->->->->->->->
     2070  * HOME SCREEN AND PRINT THE
     2080  *  MESSAGE "ERROR NUMBER "
     2090  *->->->->->->->->->->->->->->->->
     2100         PLA          ERR NUMBER
     2110  *->->->->->->->->->->->->->->->->
     2120  * PRINT ACC AS DECIMAL NUMBER
     2130  *  FOLLOWED BY A <RETURN>
     2140  *->->->->->->->->->->->->->->->->
     2150  END    JSR OFF.ERROR FIX UP $AAB6
     2160         JMP $3D3      HARD EXIT RESTORS DOS.TABLE
     2170  *--------------------------------
     2180  *
     2190  * COPY MY ADDRESSES INTO THE DOS
     2200  *  TABLE OF JUMPS (AT $9D56).
     2210  *
     2220  *--------------------------------
     2230  SETUP.DOS.TABLE
     2240         LDX #12      12 BYTES
     2250  .10    LDA MY.TABLE-1,X
     2260         STA DOS.TABLE-1,X
     2270         DEX
     2280         BNE .10
     2290         RTS
     2300  *--------------------------------
     2310  *
     2320  * DOS ERROR SETUP/RESET
     2330  *
     2340  *  CALL CLEAR.ERROR AT START OF
     2350  *   PROGRAM TO SET UP FLAG
     2360  *   (ITS ALSO OK AFTER OFF.ERROR)
     2370  *  CALL ON.ERROR WITH A,Y HOLDING
     2380  *   THE ADDRESS YOU WANT TO JUMP
     2390  *   TO IF A DOS ERROR OCCURS.
     2400  *  CALL OFF.ERROR TO CANCEL ERROR
     2410  *   TRAPPING AND REVERT TO NORMAL
     2420  *   ERROR MSG AND JUMP TO BASIC
     2430  *
     2440  *  WHEN THE ERROR ROUTINE IS
     2450  *   CALLED (ON AN ERROR) THE X
     2460  *   REGISTER HOLDS THE ERROR
     2470  *   NUMBER AS LISTED P 114-115 OF
     2480  *   THE DOS MANUAL.
     2490  *
     2500  *  AN ERROR WILL CAUSE THE STACK
     2510  *   TO BE MESSED UP. SO, SAVE IT
     2520  *   WHEN YOU EXPECT ERRORS.
     2530  *
     2540  *--------------------------------
     2550  ON.ERROR
     2560         STA DOS.TABLE+4
     2570         STY DOS.TABLE+5
     2580         LDA SAVE.AAB6     48K ONLY
     2590         BNE .10
     2600         LDX $AAB6         48K ONLY !!!!!
     2610         DEX
     2620         STX SAVE.AAB6     48K ONLY
     2630  .10    LDA #$40     PRETEND AS(])
     2640         STA $AAB6         48K ONLY
     2650         ASL          $80
     2660         STA $D8      ONERR ACTIVE
     2670         ASL          $00
     2680         STA $76      AS(]) RUNNING
     2690         STA $33       (REALLY)
     2700         RTS
     2710  OFF.ERROR
     2720         LDX SAVE.AAB6
     2730         BEQ CLEAR.ERROR   ZERO->NEVER SET
     2740         INX
     2750         STX $AAB6         48K ONLY
     2760  CLEAR.ERROR
     2770         LDA #0            CLEAR FLAGS
     2780         STA SAVE.AAB6
     2790         STA $D8           CLEAR ONERR FLAG
     2800         RTS
     2810  *--------------------------------
     2820  SAVE.AAB6 .HS 00    FLAG
     2830  *--------------------------------
     2840  *
     2850  * LOOK FOR FILE ON VARIOUS DRIVES
     2860  *
     2870  *  RETURNS CARRY CLEAR IF FOUND
     2880  *   AND SET IF NOT. USES RENAME
     2890  *   FILE,FILE TO SEE IF FILE EXISTS
     2900  *
     2910  *--------------------------------
     2920  LOOK.FOR.FILE
     2930         TSX          SAVE STACK
     2940         STX LOOK.STACK
     2950         LDA #LOOK.ERR
     2960         LDY /LOOK.ERR
     2970         JSR ON.ERROR
     2980         LDA #0       TABLE OFFSET
     2990         STA LOOK.CNT
     3000  *--------------------------------
     3010  LOOK.LOOP
     3020         LDX LOOK.CNT
     3030         CPX LOOK.MAX (# OF TRYS)
     3040         BCS .99      FAIL EXIT
     3050  *->->->->->->->->->->->->->->->->
     3060  * CHECK FOR DISK CARD IN SLOT
     3070  *  SO THINGS WON'T HANG. FIRST,
     3080  *  LOAD THE ACC WITH THE SLOT
     3090  *  THEN ...
     3100  *->->->->->->->->->->->->->->->->
     3110
     3120         AND #$07     SLOT #
     3130         ORA #$C0
     3140         STA TMP1+1
     3150         LDA #0
     3160         STA TMP1     TMP1=CS00
     3170  * TMP1 IS SLOT ADDRESS
     3180  * CHECK BYTES 7,5,3,1 FOR MATCH
     3190  *  AS AUTO MONITOR DOES
     3200         LDY #$07     SAME AS MONITOR ($FABA AUTO)
     3210  .10    LDA (TMP1),Y FETCH SLOT BYTE
     3220         CMP DISKID-1,Y  IS IT DISK?
     3230         BNE LOOK.ERR NOPE...
     3240         DEY          DOWN TWO
     3250         DEY
     3260         BPL .10      AND LOOP
     3270  * THERE IS A DISK CARD THERE
     3280  *--------------------------------
     3290  *->->->->->->->->->->->->->->->->
     3300  * PRINT OUT CTRL-D THEN "RENAME
     3310  *  FILE,FILE,SX,DX" FILLING IN
     3320  *  X ACCORDING TO THE VALUE OF
     3330  *  LOOK.CNT
     3340  *->->->->->->->->->->->->->->->->
     3350         CLC          FOUND IT
     3360  .99    LDX LOOK.STACK
     3370         TXS          RESTORE STACK
     3380         RTS
     3390  *--------------------------------
     3400  * COME HERE IF DOS COMMAND FAILS
     3410  *--------------------------------
     3420  LOOK.ERR
     3430         INC LOOK.CNT
     3440         JMP LOOK.LOOP
     3450  *--------------------------------
     3460  LOOK.MAX   .DA #6
     3470  LOOK.CNT   .BS 1
     3480  LOOK.STACK .BS 1
     3490  *--------------------------------
     3500  DISKID     .HS 20FF00FF03FF3C MATCHES DISK CARD+1 TO 7
     3510  *--------------------------------
     3520  *
     3530  * TABLE OF ERR/RESET ADDRESSES
     3540  *
     3550  *--------------------------------
     3560  MY.TABLE .DA MY.RESET
     3570           .DA MY.RESET
     3580           .DA MY.ERROR
     3590           .DA $E000
     3600           .DA MY.RESET
     3610           .DA MY.RESET
     3620         .EN

Improving the Epson Controller | Peter G. Bartlett, Jr.

[ I recently bought an NEC PC-8023 dot matrix printer, which has fabulous features. The store sold me an Epson controller to run it, assuring me it was all I needed. Naturally, they were wrong. To get all features, just as with the Epson printer, you need to be able to send 8-bit characters. I figured out how, and was just about to write an article about it, when the following one came from Peter Bartlett of Chicago, Illinois. (Bob Sander-Cederlof) ]

As you may know, the Epson MX-80 printer is somewhat hamstrung by the Epson Controller. Certain features, such as the "TRS-80" graphics character set, are not available. You can buy the Graftrax kit to enable dot graphics, but these built-in character graphics are still inaccessible.

The problem is in the card Epson makes to interface its line of printers with the Apple. (The problem is not present if you use a non-Epson card.) Hardware on the Epson controller card masks out the high-order bit, eliminating the ability to access the standard graphics characters and some of the dot graphics capabilities.

Epson's reasoning is that the Apple only sends characters with the high-bit set, so Epson has hardware on the card to mask out that bit. That way the normal characters print as they should.

If Epson had masked out the high bit in their printer driver routine instead, then machine language programmers like us could have accessed all the features of the printer. We could bypass the printer driver and work directly with the printer I/O port.

Fortunately, the card has jumpers that can be changed and the printer driver is on an EPROM that can be changed.

So you will need a soldering iron, an EPROM blaster, and an erased 2708 EPROM. [ The EPROM Blaster from Apparat can blow 2708's. I don't believe the Mountain Hardware ROMWRITER can. ]

On the Epson interface card, the jumper marked "P4" should be removed and installed on "M4" instead. This jumper are directly underneath the EPROM and are labelled. [ Ignore the three jumpers on the right side of the EPROM. ] This fix is not documented, although you can see it in the Schematic Drawing of the card. I called Epson on the phone, and they told me about it. With the jumper moved to M4, the high-bit is transmitted correctly.

BUT!!! Now normal characters do not print normally! Instead, you get the graphics characters! Okay, we need to modify the program inside the EPROM. At location $C120 (assuming the card is in slot 1) you will find an instruction "AND #$7F". This clears the high-bit for processing control codes only. We need to move this instruction to $C112, so that the high-bit is cleared for transmitted codes also. Here is a listing of the BEFORE and AFTER programs, with the moved instruction starred. (Note that the hex values for the BCC and BPL instructions changes too.)

     1000  *--------------------------------
     1010  *      CHANGES TO EPSON CONTROLLER 2708 ROM
     1020  *--------------------------------
     1030  *---AS IT NOW IS-----------------
     1040         .OR $C111
     1050         .TA $811
     1060         PLA
     1070         TAY
     1080         DEX
     1090         TXS
     1100         PLA
     1110         PLP
     1120         TAX
     1130         BCC $C152
     1140         LDA $5B8,X
     1150         BPL $C138
     1160         TYA
     1170         AND #$7F     STRIP OFF SIGN
     1180         EOR #$30
     1190  *---AS IT NEEDS TO BE------------
     1200         .OR $C111
     1210         .TA $811
     1220         PLA
     1230         AND #$7F     STRIP OFF SIGN BIT
     1240         TAY
     1250         DEX
     1260         TXS
     1270         PLA
     1280         PLP
     1290         TAX
     1300         BCC $C152
     1310         LDA $5B8,X
     1320         BPL $C138
     1330         TYA
     1340         EOR #$30
     1350         .LIF

That fix in the program will clear the high-bit off every character sent via the printer driver to the printer. We are back where we started. Except that now the clever programmer can send characters directly to the printer, bypassing the EPROM resident driver. Here is how to send one character directly to the printer:

         OUTPUT STA $C090   Assuming slot 1
         .1     BIT #C1C1   Character picked up by printer?
                BMI .1      No, keep testing

I have tried everything above, and it all works perfectly. I hope it proves useful to lots of you AAL readers.

Even Faster Primes | Charles Putney

[ Charlie is a long-time friend and subscriber in Ireland ]

Bob, I wanted to answer your challenge in the October 1981 AAL for some time, but this is the first chance I had. You sifted out the primes in 690 milliseconds, and challenged readers to beat your time. I did it!

I increased the speed by using a faster algorithm, and by using some self-modifying code in the loops. I know self-modifying code is dangerous, and a NO-NO, but it amounts to about 50 milliseconds improvement.

The algorithm changes are an even greater factor. The main ideas for the sieve are:

1. Only check odd numbers
2. Get next increment from the prime array. This means you only knock out primes.
3. Start knocking out at P^2. That is, if prime found is 3, start at 9.
4. Increment the knock-out index by 2*P. This avoids knocking out even numbers.
5. Stop at the square-root of the maximum number.

Your algorithm did all the above except 3 and 4.

With these routines, a generation takes 330 milliseconds. This is over twice as fast as yours!

You could still shave a little time off by optimizing the square routine, and even including it inline since it is only called from one place.

I'll grant you that this is not the same algorithm, but the goal is to find primes fast. I know throw down the glove for the next challenger!

       13  TEXT : HOME :
           PRINT "CHARLES PUTNEY'S FASTER PRIME GENERATOR
                  ---------------------------------------"
       20  VTAB 10: HTAB 15: PRINT "LOADING . . ."
       30  HGR : TEXT
       40 D$ =  CHR$ (4): PRINT D$"BLOAD B.PUTNEY'S PRIMES"
       50  HOME : VTAB 10: HTAB 10: PRINT "HIT ANY KEY TO START"
       60  POKE 49168,0: GET A$: POKE 49168,0
       80  POKE 49232,0: POKE 49239,0
       90  CALL 32768
       95  TEXT : FOR A = 8195 TO 24576 STEP 2:
           IF  PEEK (A) = 0 THEN  PRINT A - 8192;" ";
       98  NEXT
      100  REM   PRIME TESTER
      110  REM   CHARLES H. PUTNEY
      120  REM   18 QUINNS ROAD
      130  REM   SHANKILL
      140  REM   CO. DUBLIN
      150  REM   IRELAND
      160  REM   TIME FOR 100 RUNS = 42 SECONDS

     1000         .OR $8000    SAFELY OUT OF WAY
     1010         .TF B.PUTNEY'S PRIMES
     1020  *---------------------------------
     1030  BASE   .EQ $2000    BASE OF PRIME ARRAY
     1040  BEEP   .EQ $FF3A    BEEP THE SPEAKER
     1050  *---------------------------------
     1060  *      MAIN CALLING ROUTINE
     1070  *
     1080  MAIN   LDA #100     DO 100 TIMES SO WE CAN MEASURE
     1090         STA COUNT    THE TIME IT TAKES
     1100         JSR BEEP     ANNOUNCE START
     1110  .1     JSR ZERO     CLEAR ARRAY
     1120         LDA #$03
     1130         STA START    SET STARTING VALUE
     1140         JSR PRIME
     1150         DEC COUNT    CHECK COUNT
     1160         BNE .1       DONE ?
     1170         JSR BEEP     SAY WE'RE DONE
     1180         RTS
     1190  *---------------------------------
     1200  *      ROUTINE TO ZERO MEMORY
     1210  *      FROM $2000 TO $6000
     1220  *
     1230  ZERO   LDA #BASE+1  START AT $2001
     1240         STA .1+1     MODIFY OUR STORE
     1250         LDA /BASE+1
     1260         STA .1+2
     1270         LDA #$00     GET A ZERO
     1280         TAX          SET INDEX
     1290         LDY #$40     NUMBER OF PAGES
     1300  .1     STA $FFFF,X  MODIFIED AS WE GO
     1310         INX          EVERY ODD LOCATION
     1320         INX
     1330         BNE .1       NOT DONE
     1340         INC .1+2     NEXT PAGE
     1350         DEY
     1360         BNE .1       NOT YET
     1370         RTS
     1380  *---------------------------------
     1390  *      PRIME ROUTINE
     1400  *      SETS ARRAY STARTING AT BASE
     1410  *      TO $FF IF NUMBER IS NOT PRIME
     1420  *      CHECKS ONLY ODD NUMBERS > 3
     1430  *      INC = INCREMENT OF KNOCKOUT
     1440  *      N = KNOCKOUT VARIABLE
     1450  *
     1460  PRIME  LDA START
     1470         ASL          INC = START * 2
     1480         STA INC
     1490         JSR SQUARE   SET N = N * N
     1500         CLC          ADD BASE TO N
     1510         LDA N+1
     1520         ADC #BASE
     1530         TAX          KEEP LOW ORDER PART IN X
     1540         LDA #0       N+1 TO ZERO
     1550         STA N+1
     1560         LDA N+2
     1570         ADC /BASE
     1580         STA N+2
     1590         TAY
     1600  LOOP   LDA #$FF     FLAG AS NOT PRIME
     1610  N      STA $FFFF,X  REMEMBER THAT N IS REALLY AT N+1
     1620         CLC          N = N + INC
     1630         TXA          N=N+INC
     1640         ADC INC
     1650         TAX
     1660         BCC LOOP     DONT'T BOTHER TO ADD, NO CARRY
     1670         INY          INC HIGH ORDER
     1680         STY N+2
     1690         CPY /BASE+$4000  IF IS GREATER THAN $6000
     1700         BCC LOOP     NO, REPEAT
     1710         LDX START    GET OUR NEXT KNOCKOUT
     1720  .1     INX
     1730         INX          START = START + 2
     1740         BMI .2       WE'RE DONE IF X>$7F
     1750         LDA BASE,X   GET A POSSIBLE PRIME
     1760         BNE .1       THIS ONE HAS BEEN KNOCKED OUT
     1770         STX START
     1780         BEQ PRIME    ...ALWAYS
     1790  .2     RTS
     1800  *---------------------------------
     1810  *      SQUARE ROUTINE
     1820  *      TAKES SQUARE OF NUMBER
     1830  *      IN START (ONE BYTE) AND
     1840  *      PUTS RESULT IN N+1 (LOW)
     1850  *      AND N+2 (HIGH)
     1860  *
     1870  SQUARE LDA #$00
     1880         STA N+1      CLEAR N
     1890         STA N+2
     1900         STA MULT+1   AND MULTIPLIER HIGH
     1910         LDA START
     1920         STA MULT     MULT LOW = START
     1930         STA SHCNT    SHIFT COUNTER
     1940         LDX #$08     EIGHT SHIFTS
     1950  .1     ROR SHCNT    GET LS BIT IN CARRY
     1960         BCC .2       DON'T ADD THIS TIME
     1970         CLC          N = N + MULT
     1980         LDA N+1
     1990         ADC MULT
     2000         STA N+1
     2010         LDA N+2
     2020         ADC MULT+1
     2030         STA N+2
     2040  .2     CLC          SHIFT MULT (BOTH BYTES)
     2050         ROL MULT
     2060         ROL MULT+1
     2070         DEX
     2080         BNE .1       MORE BITS ?
     2090         RTS
     2100  START  .DA #*-*     STARTING KNOCKOUT
     2110  INC    .DA #*-*     INCREMENT FOR KNOCKOUT
     2120  COUNT  .DA #*-*     COUNT FOR 100 TIMES LOOP
     2130  MULT   .DA *-*      MULTIPIER
     2140  SHCNT  .DA #*-*     SHIFT COUNT MULTIPLIER

Printer Handler with FIFO Buffer | Jim Kassel

[ Jim Kassel is a subscriber from St. Paul, Minnesota. ]

Before I get on with technical discussions, first let me say that I have had a ball using S-C Assembler II Version 4.0. It definitely has earned a place on the list of "The Greatest Things Since Sliced Bread." My current version incorporates the block move and copy feature described in the December '80 and January '81 issues of AAL which have been a welcome enhancement.

Now...on with the article, about a super simple programming technique that I have used extensively. I am a hardware logic designer by trade and before the introduction of First In-First Out (FIFO) memory chips, designers had to implement that function using an input address up-counter, an output address up-counter, and an up/down counter to determine character count. Now that the FIFO chips are available, they are still a bit expensive for home computer use. By using the old counter method implemented in software, not only is the FIFO free but also extremely expandable in size (within the bounds of the computer memory, of course).

I am going to give a little background into the necessity, in my case, for using this technique. I feel that the problem I experienced may be interesting reading to others who may have had similar occurrences.

I was writing an assembly language program that would allow my Apple II to become a terminal, using the Hayes Micromodem II and Epson MX-80 printer/Orange Micro Grappler interface card.

For the sake of versatility I would have preferred to perform operations like JSR $Cx00 (x = slot number) when transferring data with these devices. However, it became apparent that I would have to bypass the firmware on the other interface cards. This was especially true with the printer interface card. Because the printer takes 1-2 seconds to print out a line of characters, the interface becomes unavailable for storage. Since the modem wants to supply characters at a rate of up to 30 cps, at least that many characters were being "dropped on the floor" while the printer interface card kept program control.

I finally had to get the schematics and/or firmware disassemblies of the other interface cards. From them I figured out the addresses of, and the methods of communications for, the various control, data, and (most important) status registers. This allowed me to check for printer busy, modem transmit register not yet empty and modem receive register not yet full. Now I could do other things when no data could be transferred. No longer would I have to be a slave to the equipment that is used for support!

The only other problem, then, was to be able to save the print characters in a FIFO print buffer so they would not be forgotten while the printer was busy printing the previous line of characters. In my version I allow a whole page of memory ($94) to be used for the buffer space. As long as there is not a horribly long burst of received carriage returns (the slowest printer operation), 256 locations is more than adequate because the MX-80 prints at least twice as fast as the modem data rate. Plus non-control characters are transferred into the printer line buffer much faster than incoming modem characters and the FIFO almost always stays empty because of this.

As characters arrive from the modem they are placed into the FIFO (by executing a JSR PRINT.FIFO.INPUT), then the input index (PBII) and the character counter (PBCC) are incremented. Whenever the program is in a wait loop (keyboard entries, modem data transfers, etc.) there are no less than 33 milliseconds (300 baud/30 cps) to do non-critical operations. This is more than enough time to execute a JSR PRINT.FIFO.OUTPUT.1 instruction during each "round trip" of the wait loops. If the printer is busy, the program is returned to with no data transferred; if the printer is not busy, the program is returned to after the next FIFO output character is sent, the output index (PBOI) is incremented, and the character counter (PBCC) is decremented. In any case, the program does not depend on the outcome of the subroutine results. The subroutines maintain their independence by correctly updating and monitoring the character counter (PBCC).

In my version, I must append a line feed (<LF>) character to every carriage return (<CR>) that is sent. I check every FIFO input character to see if it is a <CR>. If so, I store a <LF> into the next FIFO input location. Note that if I had decided to send the <LF> directly to the printer by monitoring for the <CR> in the FIFO output subroutine, I would again have been a slave to the printer while waiting for it to become unbusy with the <CR> operation.

By making PRINT.FIFO.OUTPUT.2 a separate subroutine, I could write it for any printer interface card with data and status registers and still not require any changes to subroutines PRINT.FIFO.INPUT and PRINT.FIFO.OUTPUT.1. This provided some versatility for converting the program for some friends with different interfaces.

     1000  *---------------------------------
     1010  *         PRINTER HANDLER
     1020  * USED SO THAT PROGRAM DOESN'T HANG
     1030  *      WHEN PRINTER IS BUSY
     1040  *
     1050  *           JIM KASSEL
     1060  *       1161 GOODRICH AVE.
     1070  *       ST. PAUL, MN 55105
     1080  *---------------------------------
     1090  PRINT.SLOT.SHIFTED  .EQ $10
     1100  *                   PRINTER SLOT # SHIFTED LEFT BY 4
     1110  PBII   .EQ $CE      PRINT BUFF INPUT INDEX
     1120  PBOI   .EQ $CF      PRINT BUFF OUTPUT INDEX
     1130  PBCC   .EQ $1F      PRINT BUFF CHAR COUNT
     1140  PBUFF  .EQ $9400    PRINT BUFF BASE ADDRESS
     1150  CR     .EQ $D       CARRIAGE RETURN WITH MSB CLR
     1160  LF     .EQ $A       LINE FEED WITH MSB CLR
     1170  *---------------------------------
     1180  START  .EQ $800
     1190         .OR START
     1200  *---------------------------------
     1210  * PRINT BUFF INPUT SUBROUTINE
     1220  *---------------------------------
     1230  PRINT.FIFO.INPUT
     1240         PHA
     1250         AND #$7F     CLEAR BIT 7
     1260
     1270  .1     LDY PBII
     1280         STA PBUFF,Y  STORE CHAR IN PRINT BUFF
     1290         INC PBII     INCREMENT INPUT INDEX
     1300         INC PBCC     INCREMENT CHAR COUNT
     1310
     1320         CMP #CR      CARRIAGE RETURN?
     1330         BNE .2       NO
     1340         LDA #LF      YES
     1350         BNE .1       SEND <LF>
     1360
     1370  .2     PLA          RESTORE CHAR
     1380         RTS
     1390  *---------------------------------
     1400  * PRINTER OUTPUT SUBROUTINE
     1410  *---------------------------------
     1420  PRINT.FIFO.OUTPUT.1
     1430         LDA PBCC     PRINT BUFF EMPTY?
     1440         BEQ .1       YES
     1450
     1460         LDY PBOI     NO
     1470         LDA PBUFF,Y  GET PRINT CHAR
     1480         JSR PRINT.FIFO.OUTPUT.2
     1490  *                     HANDLER OF SPECIFIC INTERFACE
     1500         BCS .1       DON'T UPDATE IF PRINTER WAS BUSY
     1510
     1520         INC PBOI     ELSE, INCREMENT OUTPUT INDEX
     1530         DEC PBCC     AND DECREMENT CHAR COUNT
     1540
     1550  .1     RTS
     1560  *---------------------------------
     1570  *   HANDLER FOR THE GRAPPLER (+)
     1580  *         INTERFACE CARD
     1590  *      AND MX-80 PRINTER(++)
     1600
     1610  * PRINT CHAR MUST BE IN THE A-REG
     1620  *   CARRY SET IF CHAR NOT SENT
     1630  *   CARRY CLEARED IF CHAR SENT
     1640
     1650  PSTAT  .EQ $C081    PRINTER STATUS REG
     1660  PREG   .EQ $C081    PRINTER DATA REG
     1670  PSTRBL .EQ $C082    PRINTER STROBE LOW
     1680  PSTRBH .EQ $C084    PRINTER STROBE HIGH
     1690  *---------------------------------
     1700  PRINT.FIFO.OUTPUT.2
     1710         TAX          SAVE PRINT CHAR
     1720         LDY #PRINT.SLOT.SHIFTED
     1730         LDA PSTAT,Y  GET PRINTER STATUS
     1740         AND #$A      MASK
     1750         EOR #$2      PRINTER SELECTED AND NOT BUSY?
     1760         BNE .1       NO, EXIT
     1770
     1780         TXA          YES, RESTORE PRINT CHAR
     1790         STA PREG,Y   LOAD PRINTER OUTPUT REG
     1800         STA PSTRBL,Y SET STROBE
     1810         STA PSTRBH,Y CLR STROBE
     1820         CLC          CLEAR CARRY
     1830         BCC .2       EXIT
     1850  .1     SEC          SET CARRY
     1860
     1870  .2     RTS
     1880  *---------------------------------
     1890  END
     1900  SIZE   .EQ END-START
     1910  *---------------------------------
     1920  * NOTE:
     1930  * (+) : TRADEMARK OF ORANGE MICRO, INC.
     1940  * (++): TRADEMARK OF EPSON AMERICA, INC.
     1950  *---------------------------------

Patches for Applewriter to Unhook PLE | Bob Sander-Cederlof

If you use Applewriter a lot, like I do.... And if you use Neil Konzen's Program Line Editor (PLE) a lot, like I do.... Then you probably have at least once tried to BRUN TEDITOR while PLE was still installed, like I have....

The result is maddening, to say the least. Everything seems fine. You can load a file into Applewriter, or enter a new one. You can edit to your hearts content. Then you try to SAVE it on disk. POW! What happened?!! Since PLE is still hooked into DOS, it needs to remain unmolested in memory. But Applewriter ignores its presence, and puts the text right over the top of it.

I thought I had finally learned my lesson, but then I did it again!

Finally, I decided to make Applewriter unhook everything that PLE might have hooked in, during initialization. It turned out to be surprisingly easy. Here are the patches. I have moved them up high enough so that if you have the lower case patches installed there is no conflict. Now I do not need to reboot to get rid of PLE.

     1000  *--------------------------------
     1010  * APPLEWRITER PATCH TO UNHOOK PLE
     1020  *--------------------------------
     1030         .OR $803
     1040         .TF AW.1
     1050         JSR PATCH    REPLACES "JSR $10F8"
     1060  *--------------------------------
     1070         .OR $1873    SAFE PATCH AREA
     1080         .TF AW.2
     1090  PATCH  JSR $FE89    SET INPUT TO KEYBOARD
     1100         JSR $FE93    SET OUTPUT TO SCREEN
     1110         LDA #$9C
     1120         STA $9D01    RESTORE NORMAL DOS BUFFERS
     1130         LDA #3
     1140         STA $AA57    MAXFILES=3
     1150         JSR $A7D4
     1160         LDX #$2F
     1170  .1     LDA $9E51,X  RESTORE PAGE 3 POINTERS
     1180         STA $3D0,X
     1190         DEX
     1200         BPL .1
     1210         JSR $3EA     RE-HOOK DOS
     1220         JMP $10F8    DO WHAT THE "JSR PATCH" COVERED

Great Adventure | Jeff Jacobsen

Have you ever played the ORIGINAL game? Adventure game, that is? Adventure was originally developed by Willie Crowther and Don Woods in FORTRAN on a DEC PDP-10 computer. It is the grandfather, or maybe great-grandfather by now, of the hundreds of Adventure games you see in the advertisements (you might even have bought some!).

I used the S-C Assembler II to write an Apple version of the original Adventure game. By using text compression techniques, I was able to squeeze the entire game into 48K RAM. The interaction is lightning fast, and nothing ever has to be found on the disk. The whole game is in there: over 130 rooms, 15 treasures, 40 useful objects, and 12 obstacles or opponents.

I will send you a copy FREE! Just send me a blank diskette and postage. Or send $5.00 and I will send the disk and pay the postage. Write to me, Jeff Jacobsen, at Frontier Computing Inc., P. O. Box 402, Logan, Utah 84321.

On Dividing by Ten | Jim Church

Some time ago you asked readers to come up with subroutines to divide by ten (or multiply by one-tenth). I may have come up with the smallest one, although it is certainly not the fastest.

By using SWEET-16 to merely subtract 10 over and over, until the remainder is less than 10, and counting the number of subtractions, I can divide a 16-bit value by ten in a 10-byte subroutine!

In fact, you can divide by any 16-bit value. My program assumes the divisor is in $02,03 and the dividend is in $04,05. These are the Sweet-16 registers 1 and 2. The quotient will be left in $04,05; the remainder will be in $00,01.

I used a copy of Sweet-16 in RAM, from the source code on the S-C Assembler II disk. If you use the copy in the Integer BASIC ROM's, or in the RAM card Integer BASIC, change line 1130 to "SW16 .EQ $F689".

Here is the program listing:

     1000  *--------------------------------
     1010  * DIVIDE ANY NUMBER BY 10 OR BY *
     1020  * ANYTHING ELSE FOR THAT MATTER *
     1030  *                               *
     1040  * DIVIDEND - REGISTER 0  $00.01 *
     1050  * DIVISOR  - REGISTER 1  $02.03 *
     1060  * QUOTIENT - REGISTER 2  $04.05 *
     1070  *                               *
     1080  * EXAMPLE  - DIVIDE 65534 BY 10 *
     1090  * 00:FE FF 0A 00 00 00 N 300G   *
     1100  *                               *
     1110  *          JIM CHURCH           *
     1120  *--------------------------------
     1130         .OR $300
     1140  SW16   .EQ $9B89   (SWEET-16 ADDRESS IN RAM)
     1150  *--------------------------------
     1160  GO     JSR SW16
     1170  STILL.GREATER
     1180         SUB 1        DEDUCT DIVISOR FROM DIVIDEND
     1190         INR 2        ADD 1 TO QUOTIENT
     1200         CPR 1        DIVIDEND > DIVISOR?
     1210         BC  STILL.GREATER
     1220         RTN          LEAVE SWEET-16
     1230         RTS
     1240  LENGTH .EQ *-GO
     1250  *--------------------------------
     1260  * LOOK IN $00.01 FOR REMAINDER  *
     1270  *--------------------------------

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 280300, Dallas, TX 75228. Phone (214) 324-2050 Subscription rate is $15 per year, in the USA, sent Second Class Mail; $18 per year sent First Class Mail in USA, Canada, and Mexico; $28 per year sent Air Mail to other countries. Back issues are available for $1.50 each (other countries add $1 per back issue for postage). All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)