In This Issue...
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.
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 |
[ 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.
[ 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:
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 |
[ 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 *--------------------------------- |
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 |
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.
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 *-------------------------------- |