In This Issue...
65816 News -- Talked with Bill Mensch a few days ago, and he expects full production in just a few weeks. There should be a lot of sources soon. Bill has a few more great chips in mind, upgrading the 6502 family even further.
David Eyes is writing a detailed programmer's reference manual for the 65816, to be published about July by Brady. Bill says it should answer all our questions. I'll be reviewing it as soon as possible.
We hear of a 6MHz 65816 board with 256K RAM for plugging into Apples. Let you know when we learn more details.
Woz News -- We hear Steve, Wendell Sander (/// designer), and Joe Ennis (//c designer have teamed up to form a new enterprise, outside Apple, with plans to produce a device for the home video market.
Apple II Forever College -- If you would like in-depth training in Cupertino, $500 buys 3 days under the masters. One session starts March 6th, another May 8th. Call Marian Djurovich at (408) 973-6411 for details.
At least one error crept into the PRINT USING program we printed last month. A line should be inserted to correct the problem:
3045 JSR PRUS.CLEAR YES, NEW FIELD
This is what I expect to be the final installment of the DP18 series. Some of you have been typing in and trying out the various installments, and others buying the source code on the various quarterly disks. We plan to make the composite DP18 source available at a reasonable price: all parts will be properly integrated as a set of 12 source files, ready to assemble with the S-C Macro Assembler. The disk will also include example programs illustrating the various features, the object file of DP18, and a loader program for installing DP18. The price for all of it, on one diskette, will be $50.
Normal Applesoft INPUT statements can be written in several ways. An optional quotation can be used for a prompting message; if one is used a semicolon must follow the quotation. A list of one or more variables follows.
INPUT variable INPUT "quote";variable
In DP18 we implemented the two forms of the INPUT statement shown above, except that only a single variable may be used in each statement. We also implemented two additional kinds of INPUT statements. INPUT# statements allow expressions to be entered during execution. INPUT$ statements allow picture- controlled input.
INPUT # variable INPUT # "quote";variable INPUT $ string,variable-list
The INPUT# statement allows you to read expressions and evaluate them during an INPUT operation. This can greatly simplify entering some numbers. For example, one-third can be entered as either ".3333333333333333333333" or simply as "1/3". You can enter values such as SQR(2), 2*PI, and so on. You can even refer to variables used in the program. After you have entered the expression and typed RETURN, DP18 calls on Applesoft to tokenize the line, evaluates the expression to a numeric value, and stores the value in the INPUT variable your program specified.
We call the INPUT$ statement "INPUT using". It is analogous to "PRINT using", or the PRINT$ statement discussed last month. All characters in the INPUT$ picture are proccessed the same as for PRINT$ until characters defining a numeric or string field are encountered. Then the magic begins....
For a numeric field, underlines are printed to indicate digit positions. The cursor is placed after the last underline. If there is a decimal point in the picture it will be printed. A plus sign in the picture will also be printed. All other positions of the field will be printed as underlines. Once the field has been displayed in this fashion, DP18 will check the current value in the variable corresponding with the field. If the current value is zero, DP18 merely waits for you to enter digits. If the current value is non-zero, that value is displayed in the field on the screen, to be used as a default value.
When INPUT$ is waiting for you to enter a numeric value, you can type the RETURN key to accept the default value. If no default value is displayed and you type the RETURN key, you will be entering a value of zero. If you begin to type digits, they will enter the field from the right end in "calculator style". Using backspace will cause the displayed value to be popped to the right, deleting the last digit you typed. One digit will be deleted each time you type backspace.
If you type a period, enough zeroes will be automatically entered to reach the displayed decimal point. This makes the digits you typed before the period into an integer. Then as you continue to type digits they will be appended after the decimal point. If you type more fractional digits than can be seen in the displayed field, they do become part of the input value; you just cannot see them on the screen. The value on the screen is rounded up if necessary.
A control-X will erase everything you have typed in the current field and allow you to start over. A control-C will immediately BREAK, stopping the program.
If you type a backspace when there are no digits remaining in a field, DP18 will attempt to go back to the previous field in the same picture. This will only work if the screen has not scrolled during the development of the picture, and requires a little bit of planning. (Isn't that what programming is all about?)
Probably it is time for an example.
100 &DP: INPUT $ "HV>>'ENTER X: '###.#/ 'ENTER Y: '###.#",X(0),Y(0)
Remember how to read pictures from last month's article? The "H" all by itself sets the horizontal cursor position to 0 (beginning of the line). Likewise, "V" sets us to the top line. The ">>" clears from cursor to end of screen. Therefore the "HV>>" does the same thing as a normal HOME command, but from within a picture. The string between apostrophes is printed on the screen. Then "###.#" defines a numeric field, corresponding to the variable X(0). The "/" causes a carriage return to be displayed, and then "ENTER Y:" and the second field.
During execution you will first see the screen clear and the top line become "ENTER X: ___._" followed by a flashing cursor. You can type digits, a sign, a decimal point, backspace, and so on. When you finally type the RETURN a second line will appear: "ENTER Y: ___._". If you then type a backspace, the cursor will move back to the first line, displaying as a default value whatever you left in that line.
And what about string fields in the INPUT$ command? Again, underlines will be displayed for each position of the string field. If the string already is non-null, its current value will be displayed as a default.
The code that follows is, as has been our practice throughout the DP18 series, preceded by some .EQ lines to define routines previously published, or part of the Apple ROMs. Variable storage is also defined. In the integrated source all these definitions are only done once, and the whole program is assembled together.
When the main execution loop of DP18 encounters the INPUT token, we land at line 1840. Lines 1850-1860 get the character following INPUT, and abort with SYNTAX ERROR if that character is a colon or end-of-line token. Lines 1870-1910 handle INPUT$, by merely loading up zero in the A-register and jumping to PRINT.INPUT (which was listed last month as part of the PRINT USING code). The zero value will be stored in a flag, indicating to PRINT.INPUT later on that it was called from INPUT$ rather than PRINT$. When the picture processor encounters a numeric or string field description in the picture either INPUT.NUM or INPUT.STR will be called, rather than PRINT.NUM or PRINT.STR.
Lines 1930-2510 handle the normal INPUT and INPUT# modes. The character which follows INPUT is stored at INPUT.TYPE, to be checked later. If that character was "#", line 1960 gets the next character to position properly for scanning optional quote or the variable name. Lines 1970-2120 process the optional quote. If it is not there, a "?" prompt is used; it it is there, the string itself is printed. Lines 2090-2110 make a ";" optional after the quote. Normal Applesoft INPUT requires a semicolon after the quote, but DP18's INPUT makes it optional. In fact, you could even get by with a whole bunch of semicolons, if you feel like it....
Lines 2140-2190 read a line of text. If the first character of the line is a control-C, we abort just like Applesoft. An empty line returns a zero value, using line 2500-2510.
Lines 2210-2270 set up the input line, which begins at $0200 (WBUF), so that it can be scanned using CHRGET, after pushing current TXTPTR value on the stack. If the INPUT.TYPE was "#", AS.PARSE and DP.EVALUATE convert the expression down to a value. If not, FIN converts the number string to a value. I could have used PARSE and EVALUATE regardless, but it would take a lot more time to convert plain numbers that way. Lines 2400-2430 restore the old value of TXTPTR, so that we can continue scanning the program.
Lines 2440-2480 scan the input variable name, and store the converted value in that variable. Then back to DP18's main loop to get the next command!
If we are processing an INPUT$ statement, chances are good that we will input a number. If so, the picture processor will call on INPUT.NUM at line 2530. WBUF at this time holds the image of the numeric field description, as amplified from the picture. Lines 2540-2600 copy it into IBUF, because we are going to clobber the WBUF version everytime we re-display the value being entered. IBUF is currently assembled as a 256 byte buffer, which is quite extravagant. Probably this is an area where things could be tightened up, if you need the memory space.
The code beyond line 2530 is hard to follow. I am reminded of the original Adventure game, and its twisty little passages, little twisty passages, and so on. I am going to give it a broad brush, and those of you with an intense interest can explore in more detail on your own.
As each digit is typed, it is appended to the numeric value by ACCUMULATE.DIGIT. Then, after refreshing the picture of the field from IBUF, the value is reconverted to display format and shown on the screen. It may sound inefficient, but it all works nicely. Trimming off digits when backspace is typed is done by truncating the DP18 value and then redisplaying.
LAST.FLD is the routine that tries to back up input to a previous field when you type backspace beyond the first digit. At the beginning of each field, all the necessary parameters are pushed on DP18's stack. LAST.FLD pops these back to move to a previous field. Guess what ... I forgot to check for stack overflow in the STACK.IT subroutine. Should be no problem, however, because only five bytes are stacked for each field, there is room for 24 fields. Since a picture must necessarily be less than 256 characters (maximum length of an Applesoft string) thereby limiting the number of fields, it is unlikely that you will have more than 24 fields stack up. If you think it important to have more, you had better increase the size of STACK.
String input is handled in an analogous fashion by INPUT.STR, starting at line 4970.
As I mentioned before, this is my final article on DP18. But maybe not, if you want more. Some of you might send improvements, corrections, or whatever, and I might pass them along in these pages.
DP18 works, and works well; we're proud of it. You can use DP18 in your programs, even those you plan to sell. Just give us credit where appropriate in your documentation. Remember, you can get all the source code already typed in and integrated together from us for only $50.
1000 *SAVE S.DP18 INPUT 1010 *------------------------------- 1020 * APPLESOFT SUBROUTINES 1030 *------------------------------- 1040 AS.INLIN .EQ $D52E READ A LINE 1050 AS.PARSE .EQ $D559 PARSE INPUT BUFFER 1060 AS.BREAK .EQ $D863 CTRL-C BREAK 1070 AS.ADDON .EQ $D998 ADD (Y) TO TXTPTR 1080 AS.COUT .EQ $DB5C PRINT A CHARACTER 1090 AS.CHKCOM .EQ $DEBE CHECK FOR COMMA 1100 AS.SYNERR .EQ $DEC9 SYNTAX ERROR 1110 AS.GETSPA .EQ $E452 1120 AS.MOVSTR .EQ $E5E2 1130 *-------------------------------- 1140 * MONITOR SUBROUTINES 1150 *-------------------------------- 1160 MON.RDKEY .EQ $FD0C 1170 MON.LF .EQ $FC66 1180 *-------------------------------- 1190 * DP SUBROUTINES PRINTED ELSEWHERE 1200 *-------------------------------- 1210 DP.NEXT.CMD .EQ $FFFF 1220 DP.EVALUATE .EQ $FFFF 1230 MOVE.DAC.YA .EQ $FFFF 1240 DP.VTAB .EQ $FFFF 1250 DP.INT .EQ $FFFF 1260 DP.FALSE .EQ $FFFF 1270 MOVE.DAC.TEMP1 .EQ $FFFF 1280 MOVE.TEMP1.DAC .EQ $FFFF 1290 PRINT.INPUT .EQ $FFFF 1300 FIN .EQ $FFFF 1310 GET.A.VAR .EQ $FFFF 1320 CHECK.DP.VAR .EQ $FFFF 1330 MOVE.YA.DAC .EQ $FFFF 1340 PRUS.CLEAR .EQ $FFFF 1350 PRUS.NEXT .EQ $FFFF 1360 ACCUMULATE.DIGIT .EQ $FFFF 1370 PRT.NUM.1 .EQ $FFFF 1380 PRINT.STR.1 .EQ $FFFF 1390 *------------------------------- 1400 * PAGE ZERO USAGE 1410 *------------------------------- 1420 AS.VALTYP .EQ $11 1430 MON.WNDWIDTH .EQ $21 1440 MON.CH .EQ $24 1450 MON.CV .EQ $25 1460 AS.FRESPA .EQ $71,72 1470 AS.CHRGET .EQ $B1 1480 AS.CHRGOT .EQ $B7 1490 TXTPTR .EQ $B8,B9 1500 P2 .EQ $F9 1510 P1 .EQ $FD GP POINTER 1520 *-------------------------------- 1530 WBUF .EQ $0200 1540 *------------------------------- 1550 * WORK AREAS FOR DPFP 1560 *------------------------------- 1570 DECFLG .BS 1 1580 DAC.EXPONENT .BS 1 1590 DAC.SIGN .BS 1 1600 IBUF .BS 256 1610 STACK.PNTR .BS 1 1620 STACK .BS 12*10 1630 W .BS 1 1640 D .BS 1 1650 OLD.W .BS 1 1660 OLD.D .BS 1 1670 DGTCNT .BS 1 1680 INPUT.TYPE .BS 1 1690 FOUND.NUM .BS 1 1700 FOUND.STR .BS 1 1710 FOUND.LEN .BS 1 1720 FOUND.CHAR .BS 1 1730 FILL.CHAR .BS 1 1740 ZERO.CHAR .BS 1 1750 FLD.FLAG .BS 1 1760 FLD.START .BS 1 1770 TEMP .BS 2 1780 RESULT .BS 2 1790 DEFAULT.FLAG .BS 1 1800 LEN .BS 1 1810 *-------------------------------- 1820 DP.SYN3 JMP AS.SYNERR 1830 *-------------------------------- 1840 DP.INPUT 1850 JSR AS.CHRGET 1860 BEQ DP.SYN3 ...COLON OR EOL 1870 *---INPUT USING------------------ 1880 CMP #'$' INPUT USING PICTURE? 1890 BNE .1 ...NO 1900 LDA #0 ...YES, SIGNAL "INPUT" AND JOIN 1910 JMP PRINT.INPUT "PRINT $" 1920 *---INPUT AN EXPRESSION---------- 1930 .1 STA INPUT.TYPE ="#" IF EXP, ELSE <>"#" 1940 CMP #'#' INPUT AN EXPRESSION? 1950 BNE .2 ...NO 1960 JSR AS.CHRGET ...YES, GET NEXT CHAR 1970 .2 LDX #"?" PROMPT CHAR FOR NO QUOTE 1980 CMP #'"' QUOTE? 1990 BNE .6 ...NO, SIMPLE INPUT 2000 LDY #0 ...YES, PRINT IT NOW 2010 .3 INY 2020 LDA (TXTPTR),Y NEXT QUOTED CHARACTER 2030 BEQ DP.SYN3 ...NO CLOSING QUOTE 2040 CMP #'"' CLOSING QUOTE YET? 2050 BEQ .4 ...YES 2060 JSR AS.COUT ...NO, PRINT CHARACTER 2070 BNE .3 ...ALWAYS 2080 .4 JSR AS.ADDON ADD (Y) TO TXTPTR 2090 .5 JSR AS.CHRGET SCAN NEXT CHAR 2100 CMP #';' ALLOW OPTIONAL SEMICOLON 2110 BEQ .5 ...KEEP LOOKING TILL NOT ';' 2120 LDX #$80 NULL PROMPT CHARACTER 2130 *---READ A LINE OF TEXT---------- 2140 .6 JSR AS.INLIN '?' OR NULL PROMPT 2150 LDA WBUF CHECK FOR EMPTY LINE 2160 BEQ .11 ...EMPTY LINE 2170 CMP #$03 CTRL-C? 2180 BNE .7 ...NO 2190 JMP AS.BREAK ABORT INPUT 2200 *---PARSE THE INPUT LINE--------- 2210 .7 LDA TXTPTR SAVE TXTPTR, WHICH POINTS 2220 PHA AT THE PROGRAM 2230 LDA TXTPTR+1 2240 PHA 2250 STX TXTPTR MAKE TXTPTR POINT AT INPUT BUFFER 2260 STY TXTPTR+1 2270 JSR AS.CHRGET GET FIRST CHAR FROM LINE 2280 LDY INPUT.TYPE SEE IF SIMPLE OR EXPRESSIONS 2290 CPY #'#' 2300 BNE .8 SIMPLE NUMERIC INPUT 2310 JSR AS.PARSE EXPRESSION INPUT, SO PARSE 2320 LDA #WBUF-1 POINT AT INPUT BUFFER AGAIN 2330 STA TXTPTR SO EVALUATE CAN PROCESS THE 2340 LDA /WBUF-1 PARSED LINE 2350 STA TXTPTR+1 2360 JSR AS.CHRGET SCAN FIRST CHAR 2370 JSR DP.EVALUATE EVALUATE THE EXPRESSION 2380 JMP .9 2390 .8 JSR FIN SIMPLE NUMERIC INPUT 2400 .9 PLA RESTORE TXTPTR TO PROGRAM 2410 STA TXTPTR+1 2420 PLA 2430 STA TXTPTR 2440 .10 JSR AS.CHRGOT GET CURRENT PROGRAM CHAR 2450 JSR GET.A.VAR GET INPUT VARIABLE 2460 JSR CHECK.DP.VAR MUST BE DP18 VARIABLE 2470 JSR MOVE.DAC.YA STORE INPUT VALUE 2480 JMP DP.NEXT.CMD ...FINISHED? 2490 *---EMPTY INPUT LINE------------- 2500 .11 JSR DP.FALSE RETURN VALUE = 0 2510 JMP .10 2520 *-------------------------------- 2530 INPUT.NUM 2540 LDA #0 TERMINATE STRING IN BUFFERS 2550 STA IBUF,X 2560 STA WBUF,X 2570 .1 LDA WBUF-1,X COPY STRING TO IBUF 2580 STA IBUF-1,X 2590 DEX 2600 BNE .1 2610 LDA FILL.CHAR 2620 STA TEMP 2630 JSR STACK.IT 2640 JSR AS.CHKCOM MUST HAVE COMMA 2650 JSR GET.A.VAR 2660 JSR CHECK.DP.VAR 2670 STA RESULT SAVE ADR OF VARIABLE 2680 STY RESULT+1 2690 JSR MOVE.YA.DAC MOVE DEFAULT INTO DAC 2700 LDA W 2710 STA OLD.W 2720 LDA #1 2730 STA DEFAULT.FLAG 2740 LDA DAC.EXPONENT IS DAC 0? 2750 BNE INP.X1 NO 2760 INP.X JSR INP.ZERO.DAC DEFAULT IS 0 OR CTRL-X 2770 INP.X1 LDA #0 2780 STA FLD.FLAG 2790 STA DGTCNT 2800 STA DECFLG 2810 LDA D 2820 STA OLD.D 2830 LDA #$5F UNDERLINE 2840 STA FILL.CHAR 2850 INP.NEXT.ZERO.CHAR 2860 STA ZERO.CHAR 2870 *-------------------------------- 2880 INP.NEXT 2890 JSR INP.PRINT.NUM PRINT THE NUMBER 2900 JSR MOVE.TEMP1.DAC 2910 JSR MON.RDKEY 2920 AND #$7F 2930 CMP #$0D RETURN? 2940 BEQ .2 ...YES 2950 LDX DEFAULT.FLAG 2960 BEQ .1 NO DEFAULT 2970 JSR INP.ZERO.DAC IGNORE DEFAULT 2980 CMP #8 BACKSPACE? 2990 BEQ INP.NEXT YES,IGNORE 3000 *---DIGIT------------------------ 3010 .1 CMP #'0 SEE IF NUMBER 3020 BCC .4 NO 3030 CMP #'9+1 3040 BCS .4 NO 3050 JSR ACCUMULATE.DIGIT 3060 JMP INP.NEXT 3070 *---CARRIAGE RETURN-------------- 3080 .2 LDA DGTCNT IS NUMBER 0? 3090 ORA DEFAULT.FLAG 3100 BNE .3 NO 3110 STA DAC.EXPONENT YES,SO ZERO THE EXPONENT 3120 .3 LDA RESULT GET ADR OF VAR 3130 LDY RESULT+1 3140 JSR MOVE.DAC.YA PUT IT IN VAR 3150 LDA TEMP RESTORE ORIGINAL FILL CHAR 3160 STA FILL.CHAR 3170 LDA #'0 3180 STA ZERO.CHAR 3190 JMP INP.PRINT.NUM PRINT THE NUMBER 3200 * AND RETURN 3210 *---DECIMAL POINT---------------- 3220 .4 CMP #'. DEC POINT? 3230 BNE .5 ...NO 3240 * SEC 'CMP' LEFT CARRY SET 3250 ROR DECFLG FOUND DEC PT 3260 BIT DECFLG 3270 BVS INP.NEXT TWO DEC PTS. 3280 LDA #$40 3290 CLC 3300 ADC DGTCNT 3310 STA DAC.EXPONENT 3320 LDA #'0 3330 BEQ INP.NEXT.ZERO.CHAR ALWAYS 3340 *---MINUS SIGN------------------- 3350 .5 CMP #'- MINUS? 3360 BNE .6 3370 * SEC 'CMP' LEFT CARRY SET 3380 ROR DAC.SIGN MAKE DAC NEGATIVE 3390 BNE INP.NEXT ...ALWAYS 3400 *---PLUS SIGN-------------------- 3410 .6 CMP #'+ PLUS? 3420 BNE .7 ...NO 3430 STA DAC.SIGN PUT POSITIVE VALUE IN SIGN 3440 BEQ INP.NEXT ...ALWAYS 3450 *---CTRL-X----------------------- 3460 .7 CMP #$18 CTRL-X? 3470 BNE .8 3480 LDA OLD.D 3490 STA D 3500 JMP INP.X 3510 *---CTRL-C----------------------- 3520 .8 CMP #$3 CTRL-C? 3530 BNE .9 ...NO, TRY BACKSPACE 3540 JMP AS.BREAK 3550 *---BACKSPACE-------------------- 3560 .9 CMP #$08 BACKSPACE? 3570 BNE .17 ...NO, TAKE PATH TO INP.NEXT 3580 LDA DECFLG 3590 BPL .10 3600 LDA DAC.EXPONENT 3610 SEC 3620 SBC #$40 3630 CMP DGTCNT 3640 BEQ .15 REMOVE DEC PT ONLY 3650 *-------------------------------- 3660 .10 LDA DAC.EXPONENT 3670 PHA SAVE EXPONENT 3680 LDA DGTCNT 3690 CLC 3700 ADC #$3F 3710 STA DAC.EXPONENT 3720 JSR DP.INT CHOP OFF LAST DIGIT 3730 LDA DAC.EXPONENT 3740 BEQ .14 THE NUMBER IS 0, SO RESET EVERYTHING 3750 .11 PLA 3760 STA DAC.EXPONENT 3770 LDA DGTCNT 3780 BNE .12 3790 JSR LAST.FLD 3800 JMP INP.NEXT 3810 .12 DEC DGTCNT 3820 BNE .13 3830 DEC DAC.EXPONENT 3840 .13 LDA DECFLG 3850 BPL .16 DELETE BY SHIFT 3860 BMI .17 ALWAYS 3870 *-------------------------------- 3880 .14 LDA DECFLG 3890 BPL .11 3900 PLA 3910 .15 LDA #$3F 3920 SEC 3930 SBC OLD.D 3940 ADC DGTCNT 3950 STA DAC.EXPONENT 3960 LDA #0 3970 STA DECFLG 3980 LDA #$5F 3990 JMP INP.NEXT.ZERO.CHAR 4000 *-------------------------------- 4010 .16 LDA DGTCNT 4020 BEQ .17 4030 DEC DAC.EXPONENT 4040 .17 JMP INP.NEXT 4050 *-------------------------------- 4060 INP.PRINT.NUM 4070 LDX #-1 COPY IBUF TO WBUF 4080 .1 INX 4090 LDA IBUF,X 4100 STA WBUF,X 4110 BNE .1 4120 JSR RESTORE.HV.FROM.STACK 4130 LDA OLD.W 4140 STA W 4150 LDA OLD.D 4160 STA D 4170 JSR MOVE.DAC.TEMP1 4180 LDA DECFLG 4190 PHA 4200 JSR PRT.NUM.1 4210 PLA 4220 STA DECFLG 4230 RTS 4240 *-------------------------------- 4250 INP.ZERO.DAC 4260 PHA 4270 JSR DP.FALSE PUT 0 IN DAC 4280 LDA #$40 4290 SEC 4300 SBC D CALCULATE EXPONENT 4310 STA DAC.EXPONENT 4320 LDA #0 4330 STA DEFAULT.FLAG 4340 PLA 4350 RTS 4360 *-------------------------------- 4370 LAST.FLD 4380 LDY STACK.PNTR 4390 DEY 4400 DEY 4410 DEY 4420 DEY 4430 DEY 4440 BNE .1 4450 RTS FIRST FIELD 4460 .1 PLA DISCARD JSR LAST.FLD 4470 PLA " 4480 PLA DISCARD JSR INPUT.NUM 4490 PLA " 4500 PLA DISCARD Y-REG 4510 PLA DISCARD JSR PRT.NUM.IF.NEEDED 4520 PLA " 4530 PLA DISCARD JSR LOOKUP 4540 PLA " 4550 DEY 4560 LDA STACK,Y 4570 STA TXTPTR+1 4580 DEY 4590 LDA STACK,Y 4600 STA TXTPTR 4610 DEY 4620 LDA STACK,Y 4630 PHA SAVE INDEX INTO PICTURE 4640 DEY 4650 LDA STACK,Y 4660 JSR DP.VTAB 4670 DEY 4680 LDA STACK,Y 4690 STA MON.CH 4700 STY STACK.PNTR 4710 PLA RESTORE INDEX INTO PICTURE 4720 TAY 4730 JSR PRUS.CLEAR 4740 JMP PRUS.NEXT 4750 *-------------------------------- 4760 STACK.IT 4770 LDY STACK.PNTR 4780 LDA MON.CH SAVE WHERE THE FIELD IS 4790 STA STACK,Y 4800 INY 4810 LDA MON.CV 4820 STA STACK,Y 4830 INY 4840 DEC FLD.START 4850 LDA FLD.START 4860 STA STACK,Y 4870 INY 4880 LDA TXTPTR 4890 STA STACK,Y SAVE TXTPTR 4900 INY 4910 LDA TXTPTR+1 4920 STA STACK,Y 4930 INY 4940 STY STACK.PNTR 4950 RTS 4960 *-------------------------------- 4970 INPUT.STR 4980 JSR STACK.IT 4990 JSR AS.CHKCOM MUST HAVE COMMA 5000 JSR GET.A.VAR GET ADR OF VAR 5010 LDX AS.VALTYP STR OR NUM 5020 BMI .1 OK 5030 JMP AS.SYNERR MUST BE STRING 5040 .1 STA P1 5050 STY P1+1 5060 LDY #0 GET STRING 5070 STY DEFAULT.FLAG 5080 STY FLD.FLAG 5090 STY LEN 5100 LDA (P1),Y LENGTH 5110 BEQ .3 NULL STRING, SO DO NOTHING 5120 STA LEN 5130 INY 5140 LDA (P1),Y ADR OF STRING 5150 STA P2 LO ADR 5160 INY 5170 LDA (P1),Y 5180 STA P2+1 HI ADR 5190 LDY LEN GET LENGTH 5200 DEY 5210 .2 LDA (P2),Y 5220 STA WBUF,Y 5230 DEY 5240 BNE .2 5250 LDA (P2),Y MOVE LAST BYTE 5260 STA WBUF 5270 INY Y = 1 5280 STA DEFAULT.FLAG YES THERE IS A DEFAULT 5290 .3 LDA #WBUF 5300 STA P2 5310 LDA /WBUF 5320 STA P2+1 5330 BNE IS.X1 ALWAYS 5340 *-------------------------------- 5350 IS.X LDA #0 5360 STA LEN 5370 IS.X1 LDA FOUND.LEN 5380 PHA 5390 LDA FOUND.CHAR 5400 PHA 5410 JSR RESTORE.HV.FROM.STACK 5420 LDA #$5F UNDERLINE 5430 STA FILL.CHAR 5440 LDA LEN 5450 JSR PRINT.STR.1 5460 PLA 5470 STA FOUND.CHAR 5480 PLA 5490 STA FOUND.LEN 5500 CMP LEN 5510 BCC .3 OVERFLOW 5520 *---FIND END OF STRING & PUT CURSOR THERE--- 5530 JSR RESTORE.HV.FROM.STACK 5540 CLC 5550 ADC LEN ADD LENGTH OF STRING 5560 .1 CMP MON.WNDWIDTH LONGER THAN WINDOW? 5570 BCC .2 5580 SBC MON.WNDWIDTH WRAP AROUND 5590 PHA 5600 JSR MON.LF JUMP DOWN TO NEXT LINE 5610 PLA 5620 JMP .1 5630 .2 STA MON.CH PUT COLUMN BACK IN CH 5640 *---INPUT A CHAR NOW------------- 5650 .3 JSR MON.RDKEY 5660 AND #$7F 5670 *---CARRIAGE RETURN-------------- 5680 CMP #$0D RETURN? 5690 BNE .5 ...NO 5700 LDA DEFAULT.FLAG 5710 BNE .4 DEFAULT, SO LEAVE IT ALONE 5720 LDA LEN GET LENGTH 5730 JSR AS.GETSPA MAKE ROOM FOR STRING 5740 LDY #0 MOVE DATA INTO VARIABLE 5750 STA (P1),Y LENGTH 5760 LDA AS.FRESPA 5770 INY 5780 STA (P1),Y LO ADDRESS 5790 LDA AS.FRESPA+1 5800 INY 5810 STA (P1),Y HI ADDRESS 5820 LDX #WBUF 5830 LDY /WBUF 5840 LDA LEN 5850 JSR AS.MOVSTR 5860 .4 JSR RESTORE.HV.FROM.STACK 5870 LDA #$20 SPACE 5880 STA FILL.CHAR 5890 LDA LEN 5900 JMP PRINT.STR.1 PRINT IT ONE MORE TIME 5910 *-------------------------------- 5920 .5 LDX DEFAULT.FLAG 5930 BEQ .6 ...NO DEFAULT 5940 LDX #0 5950 STX DEFAULT.FLAG GET RID OF DEFAULT 5960 STX LEN NULL STRING 5970 CMP #8 BACKSPACE AND DEFAULT? 5980 BNE .8 5990 JMP IS.X1 6000 *---BACKSPACE-------------------- 6010 .6 CMP #8 BACKSPACE? 6020 BNE .8 6030 LDA LEN 6040 BNE .7 6050 JSR LAST.FLD BACKUP A FIELD 6060 JMP IS.X1 6070 .7 DEC LEN 6080 JMP IS.X1 6090 *---CTRL-X----------------------- 6100 .8 CMP #$18 CTRL-X? 6110 BNE .9 6120 JMP IS.X 6130 *---CTRL-C----------------------- 6140 .9 CMP #3 CTRL-C? 6150 BNE .10 ...NO 6160 JMP AS.BREAK 6170 *---CHAR FOR STRING-------------- 6180 .10 LDY LEN NORMAL CHAR, 6190 STA WBUF,Y SAVE IT 6200 INC LEN 6210 JMP IS.X1 6220 *-------------------------------- 6230 RESTORE.HV.FROM.STACK 6240 LDY STACK.PNTR 6250 LDA STACK-4,Y 6260 JSR DP.VTAB 6270 LDA STACK-5,Y 6280 STA MON.CH 6290 RTS 6300 *-------------------------------- |
First Question:
I have just finished installing Version 2.0 of your assembler, and I have a few questions.
a. First, how is the line length of the escape-L changed? The short line looks ridiculous on an 80-column screen. I would also like to change the first character from "*" to ";".
b. How can I get the assembler to initialize things with DOS's MON CI modes set?
c. In working with big programs, it is easy to exceed line number 9999. It happens all the time. As new lines get added, the formatting of lines around 9999 goes haywire, as the spacing is done according to the line number at the time of entry. Thus when a line number changes from 4 to 5 digits or vice versa due to renumbering the opcode and operand columns no longer line up properly. What can be done about the erratic column alignment?
d. I noticed that the symbol table generated by an assembly takes more memory with version 2.0 than it did with 1.1. Why?
e. There appear to be two errors in the sample program S.INLINE on the Macro 2.0 disk. The comment on how to use it shows a comma between the &INPUT and the string variable, when the program in fact requires that there be NO comma. Then, the first line of the main routine does a CMP, which should be an LDA. With these corrections, the program is great. &INPUT will accept input from keyboard or disk, and reads the complete record including commas, quotes, and colons. This I find rather useful.
Mike Lawrie, South Africa
Our Answer:
a. The routine which generates the star-dash line starts at $DB21, with the following:TXA BEQ ... LDA #$AA change to $BB for ";" JSR ... LDA $D01E ("-" CHAR) CPX #$26 increase as you likeFor example, I changed mine just now like this:
$C083 C083 DB25:BB N DB2D:46b. Whatever selections you have turned on with the MON command are turned off by the DOS "INT" or "FP" commands. I guess if you want the MONCI modes all the time you could add code to the assembler to set the proper bits inside DOS. The flags are in $AA5E: C=$40, I=$20, O=$10. Store $60 into $AA5E to effect MONCI.
c. I agree with you that it is annoying the way the columns stagger when the line numbers are near 9999. There are several possible solutions. One solution, is to start line numbers at 10000. You can do this by changing the code at $D32B:
LDA #990 change to #9990 STA ... LDA /990 change to /9990A better way is to make a the line numbers always print with five digits. To effect this, change the code at $DE63:
LDX #3 change to LDX #4 $C083 C083 DE64:4d. The symbol table does indeed take more space in version 2.0 than it did in previous versions. This is due to the fact that symbols can have values up to 32-bits long. Every symbol has two more bytes in the table now.
e. Right on both counts. Disks with serial numbers 1186 and larger have the corrections you give.
Is there any way of loading a program from the monitor (without going back to Basic) or reload DOS or reboot without losing what is in memory?
Munson Compton, Shreveport, LA
Our Answer:
If you entered the monitor via CALL-151 from Basic, or MNTR or MGO-151 from the S-C Macro Assembler, DOS is still alive and will still respond to commands. You can BLOAD or LOAD a program, but of course using LOAD will flip you into either Applesoft, Integer BASIC, or the Macro Assembler depending on file type and what languages are around. If you want to stay in the monitor after the LOAD file has been read into memory, you could temporarily patch the DOS LOAD code which starts at $A413. The book "Beneath Apple DOS" would be helpful here. It looks to me like you could so subvert type A files by patching the JMP ($9D60) at $A44D to RTS (by putting 60 at $A44D). Type I files might be tricked by putting an RTS (60) at $A5AF. I don't know what other ramifications these patches might have. Beware!!!You can reboot a slave disk without losing the actual text of an assembler source file from memory. However, the pointer which tells the assembler where the program starts will be reset. Before rebooting, record the value stored in $CA and $CB, and after getting back into the assembler restore those two bytes. Of course, if the assembler is in the language card rebooting DOS marks it as not being there. From the monitor you can put it all back by typing:
]CALL-151 *C081 C081 E000:20 *INT :$CA:...(whatever values you recorded earlier) :LIST (Voila!)
Third Question:
I have the Apple ToolKit and the Big Mac assemblers, and use them primarily to key in source files from articles such as yours. I've figured out how to transpose most of the different labels and opcodes, but would like some enlightenment on the use of the .1, .2, .3 etc. labels that are repeated in the code. I assume this is a capability of your assembler that others don't have.
David Roberson
Our Answer:
For help in converting our listings to other assemblers and vice versa, you should refer to my "Directory of Assembler Directives" article in the September 1982 AAL. You are correct in assuming that most other assemblers do not have the kind of local labels as the S-C assemblers, but some do. These numeric labels are one or two digits after a period, and are very convenient for branch points within a sub- routine. They are defined below a normal label, and are only accessible within that area. The local labels are defined internally relative to the preceding normal label, and must be within a 255-byte range after the normal label. Once a new normal label is defined, a whole new set of local labels is available. The use of local labels simplifies programming, because there is no need to think up dozens of unique names like LOOP1, LOP2, LUPA, LUPB, and so on. Local labels also encourage writing good modular code, with only one entry point per module, since the local labels are not accessible outside the routine in which they are defined.The LISA assembler uses a different type of numeric label, which I call a near-by label. These are redefinable at will, and when they are referenced a pointer must be included to tell the assembler which direction to search for the definition. You can refer to the nearest definition in either a forward or backward direction. I get thoroughly confused trying to read and/or modify programs using these.
I have implemented a patch to include a Thunderclock (or compatible) time string in the .TItle for version 2.0 of the S-C Macro Assembler. The patch program automatically loads the assembler and my favorite I/O driver, installs the time patch and several others I like, and writes the assembler back on the disk. The new file includes both assembler and driver, with the patches, as well as a loader which allows the whole thing to be executed with a single BRUN.
I will gladly send a listing of the source code to any Assembly Line reader who is interested. Just send a stamped self- addressed envelope to R.M.Yost, 7436 Pointe, Canton, MI 48187.
Mark IV Designs (Mark Hansen) has come up with a neat way to override the write protect switch in a disk drive. Sometimes you want to write on the back side of a disk, in spite of all good breeding. Yet it is a nuisance to have to cut a notch in the other edge of the disk. We finally bought a hole punch, but it is still a nuisance. Other times you want to write protect a disk, but not put one of those little sticky things over the existing notch. What to do?
Instructions for adding an external toggle switch in series or in parallel with the internal sensor are easy to come by, but who wants to drill holes and solder? The Write Guard kit from Mark IV Desings accomplishes all you could wish for without any drilling, cutting, or soldering.
You get a small (1x2x3 inches) box with three-position toggle switch and LED. A short flat cable runs out the back, and you plug that into a socket inside the disk drive (after removing the 74125 from that socket). A piece of velcro attaches the plastic box to either side of your drive. The switch selects normal, always protected, or always unprotected. The LED lights whenever the disk is not protected. One chip on the disk controller card also is replaced with a chip from the kit.
If this kit sounds like something you have been waiting for, you can order one from us at $40.
Roy E. Myers (author of Microcomputer Graphics) and C.W. Finley, Jr., are the authors of the new book named above, and published by Addison-Wesley. We like it.
Until August of last year we consistently recommended Roger Wagner's "Assembly Lines: the Book" when you asked us which book would best help you learn Apple assembly language. It was especially well-suited to beginners at assembly language who were nevertheless somewhat familiar with the Apple and Applesoft. But it went out of print with the demise of Softalk Publishing, and we can't get them now.
Finley and Myers have not only filled the void, they have improved on our previous favorite. Physically, the book is larger (7x9, paper, 361 + vi pages). It is set in large clear type. And it only costs $16.95 (Wagner's book was $19.95). I especially like the fact that they use the S-C assembler for all of the examples. However, if you don't use our assembler, the book loses no value; all the examples are written so as to be as compatible as possible with other possible assemblers.
Take another look at that title: "Assembly Language for the Applesoft Programmer." There is a double meaning there. This is not only a text for the Applesoft programmer who wants to learn beginning assembly language. It also for the person who wants to USE assembly language along with Applesoft programs. Combining both languages gives the best of both worlds, but doing so involves a lot of work. This book will help.
The book divides into five main sections:
There are five useful appendices and an index.
We think enough of this book to add it to our stock. Check our list of books on page 3 for price.
Last night I re-invented the wheel, and I think I made a pretty good one. I learned a little at the same time.
When you use the DOS "INIT" command, a copy of DOS is written on tracks 0 through 2. If the disk is meant to be a data disk, that wastes three perfectly good tracks. Because of the way DOS checks for the end of track-sector lists and various other things, a standard DOS cannot allow files to be written into track 0. But it is perfectly all right to leave the DOS image off of tracks 1 and 2 and use them for files. Of course it is a good idea to change the image on track 0 so that it will not begin to boot DOS and get lost (when you forget it is DOS-less and try to boot it anyway).
There are some more wasted sectors in track 17, the catalog track. INIT sets up 15 sectors for the catalog, which is enough for 105 files. I have never needed that many, but some of you might have even needed more. Last night I needed only about 30 files, and I needed every sector I could get to store them all. My "wheel" sets up only seven catalog sectors, enough for only 49 files. This frees up eight more sectors for data.
With the help of "Beneath Apple DOS" I examined the code in the DOS File Manager which handles the INIT command ($AE8E-AF07). This routine calls RWTS to initialize 35 empty tracks on a diskette, writes a VTOC in track 17 sector 0 and writes 15 empty catalog sectors on the rest of track 17. Then it scoots back to track 0 and writes the DOS image on the first three tracks.
I used Rak-Ware's DISASM to make a source file out of the INIT code, and then loaded it into the S-C Macro Assembler. Then step-by-step I proceeded to add meaningful labels and comments, and modify the code to do what I wanted.
The File Manager INIT code expects various parameters to have been set up by the DOS command parser, and those will not be set up when my program runs. I decided I would let my program assume that the last disk drive you accessed is the one where you have placed the blank disk you want to initialize.
I also decided to make the volume number always 001. I always do this anyway, and generally consider the volume number to be a nuisance (since I don't have a Corvus which uses the volume numbers for something useful). If you want to be able to choose the volume number, you could add the code for that purpose. Lines 1240-1270 set the volume number into the VTOC image and into the RWTS parameter block (IOB).
Lines 1290-1300 call RWTS to format the blank diskette. Beware! It is entirely too easy to forget to remove your heavily loaded program diskette before running this program! Be absolutely SURE you have the diskette in the drive which you WANT to initialize. After this program runs, the disk will have no remnant of any data which may have been on it before.
Lines 1310-1570 set up a VTOC image. The program assumes that part of the VTOC image at $B3BB is already set up, because you could not run this program without having read at least one VTOC somewhere along the way. The VTOC bitmap is set up first to $FFFF0000 at each sector position, and then the entry for track 0 is cleared. Finally the bits for sector 0 and sectors 9 through 15 of track 17 are cleared. Then lines 1580-1640 call on RWTS to write out the VTOC on track 17, sector 0.
The catalog sectors are chained together with a series of pointers. A pointer in the VTOC points to the first catalog sector, which is almost always track 17 sector 15. A pointer in the first catalog sector points to the second one, and so on. The last catalog sector points at track 0, which is a flag indicating the end of the catalog. (Too bad, because if DOS tested for a final pointer to 0,0 instead of just 0,x we could put the catalog for this data disk all in track 0 and free up even more sectors.)
Lines 1650-1700 clear the catalog buffer, and then lines 1710-1900 insert the forward pointers and call on RWTS to write each sector on the disk.
Finally, lines 1910-2000 write out a bootup program on track 0 sector 0. BOOTER is the code that will be executed if you accidentally try to boot our DOS-less disk.
Lines 2010-2090 finish setting up a call to RWTS, and check for an I/O error. I didn't bother to write any error handler into this program, as you can see by the BRK in line 2090. If you want you can printout the DOS error code at this point, or at least get it in the A-register before the BRK.
The BOOTER program is tricker than it looks. Anyway it tricked me a lot. First notice the .PH and .EP directives in lines 2120 and 2280. These tell the assembler to continue assembling bytes following the preceding code, but to assemble it with the assumption that at execution time it will be origined at $0800. The boot ROM on the disk controller reads track 0 sector 0 into $800-$8FF, so BOOTER has to be set up to run there.
Notice line 2140, which is ".HS 01" The boot ROM reads the first sector into $800-8FF, then checks location $800 to see how many sectors you want the boot ROM to read. About the only disk I have heard of which has anything other than 01 in this byte is the BASICS disk. If you put, for example, 03 in that byte sectors 1 and 2 would be read into $900 and $A00. You can read up to 16 sectors this way, but remember that the sector numbers will not be the same as the ones you use when you write them with RWTS. (RWTS uses a table to convert logical sector numbers into physical sector numbers.)
Line 2150 turns off the disk motor. I forgot the first time, and of course the drive just kept spinning.
Lines 2160-2210 print out the message from lines 2240-2270. My first attempt I called the standard COUT subroutine at $FDED to print each character, and I lost an hour finding out why I never saw my message. Instead, the drive just kept grinding the head to track 0, over and over and over.... But it worked if I first copied the boot ROM code from $C600 down to $8600, and typed 8600G to boot. I finally figured out that PR#6 sets the output hook to slot 6 and leaves it there. Then the next character that is printed (usually the prompt character for whatever language you are in) through COUT goes to the disk interface and proceeds to boot. My message sent another character to COUT and restarted the boot, ad infinitum. Changing line 2190 to "JSR $FDF0" fixed it all.
After printing the message line 2220 jumps to the initial entry point of the monitor, so you get a "*" prompt. If you previously had DOS in memory, you will probably be able to use 3D0G to get back to BASIC or the assembler or whatever. Otherwise, stick in a disk that DOES have DOS and try booting again.
Line 2300 is just window dressing. It assures that the rest of track 0 sector 0 will have nothing but zeroes in it. No particular value, but I like it that way.
1000 *SAVE S.DOSLESS INIT 1010 *-------------------------------- 1020 RWTS .EQ $03D9 1030 GETIOB .EQ $03E3 1040 *-------------------------------- 1050 VTOC .EQ $B3BB 1060 V.VOLUME .EQ $B3C1 1070 V.NXTTRK .EQ $B3EB 1080 V.DIRECT .EQ $B3EC 1090 V.BITMAP .EQ $B3F3 1100 *-------------------------------- 1110 CATALOG.BUFFER .EQ $B4BB 1120 C.TRACK .EQ $B4BC 1130 C.SECTOR .EQ $B4BD 1140 *-------------------------------- 1150 R.PARMS .EQ $B7E8 1160 R.VOLUME .EQ $B7EB 1170 R.TRACK .EQ $B7EC 1180 R.SECTOR .EQ $B7ED 1190 R.BUFFER .EQ $B7F0,B7F1 1200 R.OPCODE .EQ $B7F4 1210 *-------------------------------- 1220 .OR $800 1230 *-------------------------------- 1240 DOSLESS.INIT 1250 LDA #1 INIT AS VOLUME 001 1260 STA R.VOLUME 1270 STA V.VOLUME 1280 *-------------------------------- 1290 LDA #$04 INIT OPCODE FOR RWTS 1300 JSR CALL.RWTS.OP.IN.A 1310 *---MAKE A GENERIC VTOC---------- 1320 LDA #$11 1330 STA V.NXTTRK 1340 STA R.TRACK 1350 LDY #1 1360 STY V.DIRECT FORWARD DIRECTION 1370 DEY Y=0 1380 STY R.SECTOR 1390 *---PREPARE BITMAP--------------- 1400 LDY #4*35 1410 .1 LDA #0 1420 DEY 1430 STA V.BITMAP,Y 1440 DEY 1450 STA V.BITMAP,Y 1460 DEY 1470 LDA #$FF 1480 STA V.BITMAP,Y 1490 DEY 1500 STA V.BITMAP,Y 1510 BNE .1 1520 STY V.BITMAP CANNOT ALLOCATE TRACK 0 1530 STY V.BITMAP+1 1540 INY Y=1, RESERVE F...9 1550 STY 4*17+V.BITMAP FREE SECTOR 8 1560 LDA #$FE RESERVE 0 1570 STA 4*17+V.BITMAP+1 FREE 7...1 1580 *---WRITE VTOC ON NEW DISK------- 1590 LDA #VTOC 1600 STA R.BUFFER 1610 LDA /VTOC 1620 STA R.BUFFER+1 1630 LDA #2 RWTS WRITE OPCODE 1640 JSR CALL.RWTS.OP.IN.A 1650 *---PREPARE CATALOG SECTOR------- 1660 LDX #$00 1670 TXA 1680 .2 STA CATALOG.BUFFER,X 1690 INX 1700 BNE .2 1710 *---WRITE CATALOG CHAIN---------- 1720 LDA #CATALOG.BUFFER 1730 STA R.BUFFER 1740 LDA /CATALOG.BUFFER 1750 STA R.BUFFER+1 1760 LDA #17 TRACK 17 1770 LDY #15 START IN SECTOR 15 1780 .3 STA C.TRACK 1790 .4 STY R.SECTOR 1800 DEY 1810 STY C.SECTOR 1820 JSR CALL.RWTS 1830 LDY C.SECTOR 1840 CPY #9 1850 BNE .4 1860 STY R.SECTOR 1870 LDY #0 1880 STY C.TRACK 1890 STY C.SECTOR 1900 JSR CALL.RWTS 1910 *---WRITE BOOT SECTOR------------ 1920 .5 LDA #BOOTER 1930 STA R.BUFFER 1940 LDA /BOOTER 1950 STA R.BUFFER+1 1960 LDA #0 1970 STA R.TRACK 1980 STA R.SECTOR 1990 JSR CALL.RWTS 2000 RTS 2010 *-------------------------------- 2020 CALL.RWTS.OP.IN.A 2030 STA R.OPCODE 2040 CALL.RWTS 2050 JSR GETIOB 2060 JSR RWTS 2070 BCS .1 ERROR 2080 RTS 2090 .1 BRK 2100 *-------------------------------- 2110 BOOTER 2120 .PH $800 2130 BOOTER.PHASE 2140 .HS 01 2150 LDA $C088,X MOTOR OFF 2160 LDY #0 2170 .1 LDA MESSAGE,Y 2180 BEQ .2 2190 JSR $FDF0 2200 INY 2210 BNE .1 2220 .2 JMP $FF59 2230 *-------------------------------- 2240 MESSAGE 2250 .HS 8D8D8787 2260 .AS -/NO DOS IMAGE ON THIS DISK/ 2270 .HS 8D8D00 2280 .EP 2290 *-------------------------------- 2300 .BS 256,0 2310 *-------------------------------- |
I went to great lengths to verify the address of the entry into RENUMBER used by Peter's and Bruce's program, and the day after picking up the printed newsletters Bill discovered that I had used a pre-release copy of Version 2.0. The address in the actual release is different. The correct line 1060 for the version we are sending out is:
1060 RENUMBER .EQ $D658 for the D000 version or 1060 RENUMBER .EQ $1658 for the 1000 version
In any case, just be sure the address is the location of the CPX #$06 instruction.
Given my interest in everything related to graphics, I read eagerly Bob's article "Generating Tables..." in the Dec 94 issue of AAL. I haven't yet had the chance to read the Apple Supplement of Byte (my local newsstand receives it discontinuously); however, I had already heard about the use of preshift tables in animation. I experimented with this technique some time ago, getting excellent results in moving colored shapes against some very complex backgrounds with relatively simple code.
Maybe one of the most challenging steps is typing in the preshift tables. Writing a program to generate the tables is not difficult, and is probably better. The code that follows only takes $68 bytes as a subroutine, using two page zero variables. And it only takes 24 milliseconds to generate the tables, which is many times faster than reading them from a disk.
The Byte article used 14 tables of 256 bytes each. They correspond to left and right portions of each possible 8-bit value shifted any amount from 1 to 7 bits. No columns are kept in memory for shifting 0 bits, as the result is entirely too predictable.
Since, in hi-res graphics, the high bit does not get shifted, you can deal with it separately. Before looking up the preshifted values you can split off the high bit and rejoin it later. The extra code for this is very minor, and it results in a vast memory saving. By doing it this way we get by with 12 tables of 128 bytes each (six pages instead of 14!). Six tables for the left side results and six for the right, for every possible shift of from 1 to 6 bits, for every possible value from $00 to $7F.
I sometimes find it worthwhile to limit the quotient-remainder tables such as Bob generated in the December article to only 256 bytes each (instead of 280), using code like the following to read them when the X-coordinate is larger than 255:
LDX XCOORD low byte of xcoord LDA QUO+4,X CLC ADC #$24 STA XBYTE LDA REM+4,X STA XBIT
Here now is my program to generate the preshift tables, as modified by Bob. LInes 1080-1210 allocate space for the 12 tables, each 128 bytes long. I put them at $0900 for this example, but of course you can put them wherever you wish.
Lines 1230-1310 are a macro definition. The macro is called out six times in the main loop, once for each shift of a value. For the benefit of those without a macro assembler, I have shown the expansion in the listing of lines 1430-1480. Some of the code in the macro could have been handled by a subroutine, but it would save a negligible amount of space at a cost of an non-negligible amount of time.
The shifting algorithm is familiar to those of you who have been fiddling with hi-res for a while. Remember that the picture bits are stored backwards in each byte, so that shifting the picture on the screen right one bit requires shifting the bits in memory left within each byte, stepping over bit 7, and from byte to byte in a left-to-right direction.
The little program called TIME, lines 1530-1660, calls the BUILD program 1000 times. I ran it and clocked it at a little less than 24 seconds, which means building once took less than 24 milliseconds. The tables would take up six disk sectors if they were stored part of the program on disk. The disk spins at 300 rpm, or 200 milliseconds per revolution. The absolute minimum time to read six sectors would be 67.5 milliseconds, but in actual practice it takes closer to a half second. It depends on whether it is part of a larger file or stored as a separate file, the latter taking longer. Since the program only needs to be executed once, even the memory it occupies is available to the program for other purposes.
1000 *SAVE S.BUILD.PRESHIFT.TABLES 1010 *-------------------------------- 1020 * WRITTEN BY G. L. POMPONI, PISA, ITALY 1030 * MODIFIED BY BOB SANDER-CEDERLOF 1040 *-------------------------------- 1050 L.BYTE .EQ 0 1060 R.BYTE .EQ 1 1070 *-------------------------------- 1080 .OR $900 1090 SHIFT.1 .BS 128 1100 SHIFT.2 .BS 128 1110 SHIFT.3 .BS 128 1120 SHIFT.4 .BS 128 1130 SHIFT.5 .BS 128 1140 SHIFT.6 .BS 128 1150 *-------------------------------- 1160 REMND.1 .BS 128 1170 REMND.2 .BS 128 1180 REMND.3 .BS 128 1190 REMND.4 .BS 128 1200 REMND.5 .BS 128 1210 REMND.6 .BS 128 1220 *-------------------------------- 1230 .MA SHIFT 1240 ASL L.BYTE 1250 ROL R.BYTE 1260 LDA L.BYTE 1270 LSR 1280 STA SHIFT.]1,X 1290 LDA R.BYTE 1300 STA REMND.]1,X 1310 .EM 1320 *-------------------------------- 1330 .OR $800 1340 *-------------------------------- 1350 BUILD.PRESHIFT.TABLES 1360 LDX #0 FOR X = 0 TO $7F 1370 *-------------------------------- 1380 .1 STX L.BYTE 1390 LDA #0 1400 STA R.BYTE 1410 ASL L.BYTE 1420 *-------------------------------- 1430 >SHIFT 1 1440 >SHIFT 2 1450 >SHIFT 3 1460 >SHIFT 4 1470 >SHIFT 5 1480 >SHIFT 6 1490 *-------------------------------- 1500 INX NEXT X 1510 BPL .1 (...UNTIL $80) 1520 RTS 1530 *-------------------------------- 1540 * BUILDS 1000 TIMES IN LESS THAN 24 SECONDS, 1550 * SO LESS THAN 24 MILLISECONDS TO BUILD ONCE 1560 *-------------------------------- 1570 TIME LDA #4 4*250 = 1000 1580 STA $500 1590 .1 LDY #250 1600 .2 JSR BUILD.PRESHIFT.TABLES 1610 DEY 1620 BNE .2 1630 DEC $500 1640 BNE .1 1650 RTS 1660 *-------------------------------- |
Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $12 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).
All material herein is copyrighted by S-C SOFTWARE CORPORATION,
all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)