In This Issue...
Bytestalker Software Analyzer Card
Old-fashioned computers always came with a front panel, loaded with lights and switches. You could usually stop a program, single-step it, and even set a breakpoint address to halt execution at a particular address. Personal computers don't include front panels, but now you can add part of one to your Apple. J. Stuart Enterprises, Box 310, Grass Valley, CA 95945, is offering the Bytestalker Software Analyzer Card. The card includes 16 switches for selecting a breakpoint address, a three-position mode switch (run, stop, trap), and a two position step-hold switch. There are also six single hex digit displays, to show you address and contents. You can choose single-stepping at the machine cycle level, or the instruction level. The price is only $88.50, plus postage and insurance. Contact J. Stuart at the above address or at phone (916)273-9188.
We're Gaining on it...
We've gained about a week in our efforts to get AAL back on schedule: it looks like the April issue will actually get to the printer during April! Next month we'll be shooting to get the May issue in the mail before the end of May, and soon you'll be receiving each month's issue during the appropriate month. Thanks for your patience.
In the very first issue of AAL, back in October 1980, I wrote an article about an interpretive screen display subroutine. A lot of things have happened since 1980! Although perhaps it is insignificant compared to the rise and fall of nations, one change which dramatically affects Apple programmers is that now almost everything is done on an 80-column screen. That old message printer was tied to the 40-column screen, so I think an upgrade is in order.
The following program has more features than the 1980 version, and is also easier to use. Another nice change since 1980 is that most of the assemblers available for the Apple now include a macro capability. Macros make it easier to use special coded sequences of bytes like those necessary in the message printer.
Where is the message?
There are many different ways to tell a message-printing subroutine where the message to be printed is located in memory. The method I used in my 1980 article was to assign a message number to each message, and pass that number in the A-register; the message printer used the message number as an index to a table of message addresses. Sometimes I have used the message number technique without a table of message addresses: I put all the messages together in one consecutive area of memory; the message printer searched through the messages counting end-of-message characters until it found the N-th message, and then started printing.
I show two other techniques in the program which follows. The first one involves loading the full address of the message to be printed into the A- and Y-registers. In my program I put the high-byte of the address in the Y-register, and the low-byte in the A-register. Of course you could use any registers any way you wish, but this is what I used. The code for MESSAGE.PRINTER is shown in lines 1250-1380. I defined a macro named "MSG" in lines 1190-1230 to make it easier to write the code to call the message printer. Calls to the message printer may be written simply as one-line macro calls, such as:
>MSG TITLE >MSG COPYRIGHT
where TITLE and COPYRIGHT are the names of specific messages assembled elsewhere in a program.
The other technique I show here is probably the most popular one, if the programs I have disassembled are any indication. In this method the text of the message to be printed immediately follows the JSR to the message printer. The message printer uses the return address placed on the stack by the JSR to locate the message. After the message is printed, the return address is modified so that it points to the next instruction after the message before RTSing. ANOTHER.MESSAGE.PRINTER is shown in lines 1420-1640.
Both message printers begin by storing the address of the message into a page-zero pointer. Then a subroutine named GET.NEXT.CHAR in lines 1680-1750 is called to pick up characters of the message. Since the last instruction in this subroutine is the one which picks up the character, the Zero-status will be set if the $00 byte indicating end-of-message is picked up.
What is in the Message?
The real intelligence of my message printers is in the subroutine PUT.CHAR, starting at line 1760. We have already mentioned that a $00 byte indicates end-of-message. PUT.CHAR expects the X-register to contain a repeat count, and the A-register to contain the character to be printed. If the byte in the A-register has a value between $40 and $FF, it is sent to COUT to be printed (X) times. However, if the byte in the A-register has a value between $01 and $3F, it is interpreted in special way.
Values between $80 and $9F are control characters, and will be interpreted by the monitor firmware. If you are in an 80-column //e, //c, or //gs, the interpretation will be as shown in lines 1850-2070. In 40-column mode not all of the functions are active. A Viewmaster or other type of 80-column card will have its own set of controls.
Values between $01 and $18 are interpreted by PUT.CHAR as the first byte of a two-byte cursor position code. This first byte indicates the line number (1-24 decimal) to put the cursor on, and the following byte is the column number (0-79 decimal). I defined a macro "POS" in lines 2100-2130 to simplify putting position codes into messages.
Values between $19 and $3F are used to index a special function table. My table is shown in lines 2700-2840. Notice that I used a macro "FUN" to write each entry in the table. This macro defines the special function code as "F.name", and assembles the address-1 of the code which performs the function. I thought of seven handy functions, but you can probably think of a lot more. As you do, just add >FUN lines to the table and write subroutines to perform the functions. Use mine as models, and you should be successful.
The methods I used for setting up the cursor position and handling other special functions work with Apple's firmware in the 40- and 80-column modes; if you have a Viewmaster or other 80-column card, you will need to make some changes in PUT.CHAR and its subroutines.
I made macros for the all of the functions, shown in lines 2140-2460. You might also find it handy to define macros for some of the control-characters processed by the 80-column firmware, but I didn't do it here.
Even though I have the "POS" function for positioning the cursor on the screen, I still wanted the ability to do HTAB and VTAB independently. These are my first two functions. By writing ">HTAB xx" you can position to column xx on the current line. ">VTAB yy" will put the cursor on line yy without changing the column. Again, yy is a number between 1 and 24, and xx runs from 0 to 79 (decimal).
If you remember, normal characters are printed N times according to the value in the X-register when you call PUT.CHAR. The REP function lets you set up the repeat count. ">REP n,char" will cause char to be printed n times.
The CEN funtion is perhaps the most powerful one. It centers a string on the current line. You simply write something like
>CEN "This string will be centered"
...and it will be. The code for this function is in lines 3020-3180. All characters which follow the CEN function code are counted, up to the next function code or control character. Then the position for the first character is computed by halving the difference between window width and string length, and an HTAB performed.
FIL and FCL will fill an entire line with a specified character. FIL lets you say which line, and FCL fills the current line. You could do the same thing by putting the cursor at the beginning of the line to be filled and then using a repeat count larger than the window width followed by the fill character, but this way is easier. Let the computer work for you!
WIN lets you specify a window on the screen. This is very rudimentary as window routines go, because all it does is load the four locations the monitor uses to define the window: left, width, top, and bottom. "Left" is the column number to start the window at, and is a value from 0 to 79. "Width" is the number of characters in a line, and has a value from 1 to 80. Just be sure that these two numbers do not add to a value greater than 80, unless you really want to clobber your program. "Top" is the line number of the top of the window, having a value from 0 to 23. "Bottom" is the line number of the bottom line of the window, plus 1; the value goes from 1 to 24. The full 80-column window would be set up with ">WIN 0,80,0,24".
How about some examples?
I wrote two sample programs. "T", in lines 3500-3540, calls MESSAGE.PRINTER to print the messages in lines 3560-3790. "TT", in lines 3950-4170, calls ANOTHER.MESSAGE.PRINTER to print embedded messages.
1000 .LIST MOFF Do not list macro expansions 1010 *SAVE S-C.MSG.MACHINE 1020 *--------------------------------- 1030 MON.LEFT .EQ $20 1040 MON.WIDTH .EQ $21 1050 MON.TOP .EQ $22 1060 MON.BOTTOM .EQ $23 1070 MON.CH .EQ $24 1080 MON.CV .EQ $25 1090 *-------------------------------- 1100 MON.CH80 .EQ $57B 1110 *-------------------------------- 1120 MON.VTAB .EQ $FC22 1130 MON.COUT .EQ $FDED 1140 *--------------------------------- 1150 MSG.PNTR .EQ $00,01 1160 *--------------------------------- 1170 * Address of message in Y,A registers 1180 *-------------------------------- 1190 .MA MSG >MSG <message address> 1200 LDA #]1 1210 LDY /]1 1220 JSR MESSAGE.PRINTER 1230 .EM 1240 *--------------------------------- 1250 MESSAGE.PRINTER 1260 STA MSG.PNTR Store message address 1270 STY MSG.PNTR+1 into page-zero pointer 1280 TXA Save X-register 1290 PHA 1300 LDX #1 Clear repetition counter 1310 JSR GET.CHAR First char of message 1320 BEQ .2 ...empty message! 1330 .1 JSR PUT.CHAR Process the character 1340 JSR GET.NEXT.CHAR get another one 1350 BNE .1 ...not at end of message yet 1360 .2 PLA Restore X-register 1370 TAX 1380 RTS Return to caller 1390 *-------------------------------- 1400 * Message follows JSR to message printer 1410 *-------------------------------- 1420 ANOTHER.MESSAGE.PRINTER 1430 PLA Store message address (-1) 1440 STA MSG.PNTR into page-zero pointer 1450 PLA 1460 STA MSG.PNTR+1 1470 TXA Save X-register 1480 PHA 1490 TYA Save Y-register 1500 PHA 1510 LDX #1 Clear repetition counter 1520 BNE .2 ...Always 1530 .1 JSR PUT.CHAR Process a character 1540 .2 JSR GET.NEXT.CHAR 1550 BNE .1 ...not at end of message yet 1560 PLA Restore Y-register 1570 TAY 1580 PLA Restore X-register 1590 TAX 1600 LDA MSG.PNTR+1 Get return address back on stack 1610 PHA 1620 LDA MSG.PNTR 1630 PHA 1640 RTS Return to caller 1650 *-------------------------------- 1660 * Get a message character 1670 *--------------------------------- 1680 GET.NEXT.CHAR 1690 INC MSG.PNTR 1700 BNE GET.CHAR 1710 INC MSG.PNTR+1 1720 GET.CHAR 1730 LDY #0 1740 LDA (MSG.PNTR),Y 1750 RTS 1760 *-------------------------------- 1770 * Process a message character 1780 * 1790 * $00 END OF MESSAGE 1800 * $YY XX where is between $01 and $18, position 1810 * the cursor as vtab YY, htab XX. 1820 * $19-3F special functions 1830 * $40-FF print as is 1840 * 1850 * Characters between $80 and $9F are interpreted by 1860 * the Apple 80-column firmware as follows: 1870 * 1880 * $87 bell 1890 * $88 backspace 1900 * $8A move cursor down one line 1910 * $8B clear from cursor to end of window 1920 * $8C clear window and home cursor 1930 * $8D carriage return 1940 * $8E normal video 1950 * $8F inverse video 1960 * $91 switch to 40-column 1970 * $92 switch to 80-column 1980 * $95 de-activate firmware 1990 * $96 scroll display down one line 2000 * $97 scroll display up one line 2010 * $98 disable Mouse Text 2020 * $99 home cursor without clearing window 2030 * $9A clear current line 2040 * $9B enable Mouse Text 2050 * $9C move cursor forward one space 2060 * $9D clear to end of line 2070 * $9F move cursor up one line 2080 *-------------------------------- 2090 * Message Content Macros 2100 *-------------------------------- 2110 .MA POS Position cursor (vtab, htab) 2120 .DA #]1,#]2 2130 .EM 2140 *-------------------------------- 2150 .MA REP Repeat character N times 2160 .DA #F.REP,#]1,#"]2 2170 .EM 2180 *-------------------------------- 2190 .MA CEN Center a string on current line 2200 .DA #F.CEN 2210 .AS -"]1" 2220 .EM 2230 *-------------------------------- 2240 .MA STR normal Ascii string 2250 .AS -"]1" 2260 .EM 2270 *-------------------------------- 2280 .MA FIL Fill specified line with character 2290 .DA #F.FIL,#]1,#"]2 2300 .EM 2310 *-------------------------------- 2320 .MA FCL Fill current line with character 2330 .DA #F.FCL,#"]1 2340 .EM 2350 *-------------------------------- 2360 .MA WIN Define a new text window 2370 .DA #F.WIN,#]1,#]2,#]3,#]4 2380 .EM 2390 *-------------------------------- 2400 .MA HTAB Htab the cursor 2410 .DA #F.HTAB,#]1 2420 .EM 2430 *-------------------------------- 2440 .MA VTAB Vtab the cursor 2450 .DA #F.VTAB,#]1 2460 .EM 2470 *-------------------------------- 2480 PUT.CHAR 2490 CMP #$40 IS IT PRINTABLE? 2500 BCC .2 ...NO, SPECIAL FUNCTION 2510 .1 JSR MON.COUT 2520 DEX Decrement repeat count 2530 BNE .1 ...repeat the character 2540 INX Put the repeat count back to 1 2550 RTS 2560 *---Process function char--------- 2570 .2 CMP #$19 >POS VV,HH or special function? 2580 BCC P.POS ...>POS call 2590 *---Branch to function code------- 2600 ASL Double function # to make index 2610 TAY 2620 CPY #FUN.TBL.SZ 2630 BCS .3 ...Ignore illegal function 2640 LDA FUN.TBL+1,Y 2650 PHA Put function code address-1 on stack 2660 LDA FUN.TBL,Y 2670 PHA 2680 .3 RTS Go to it! 2690 *-------------------------------- 2700 .MA FUN 2710 F.]1 .EQ *-FUN.TBL/2 2720 .DA P.]1-1 2730 .EM 2740 *-------------------------------- 2750 FUN.TBL .EQ *-$19-$19 2760 >FUN VTAB Vtab YY 2770 >FUN HTAB Htab XX 2780 >FUN REP Repeat, count, character 2790 >FUN CEN Center, string, null 2800 >FUN NUL ...ends centered string 2810 >FUN FIL Fill, line #, char 2820 >FUN FCL Fill current line, char 2830 >FUN WIN Window, left, width, top, bottom 2840 FUN.TBL.SZ .EQ *-FUN.TBL 2850 *-------------------------------- 2860 P.POS JSR MY.VTAB Do vertical tab 2870 P.HTAB JSR GET.NEXT.CHAR ...get horoz value 2880 CMP MON.WIDTH Stay inside current window 2890 BCC .1 ...it is inside 2900 LDA MON.WIDTH ...use right edge 2910 SBC #1 2920 .1 STA MON.CH 40-column horiz pos'n 2930 STA MON.CH80 80-column horiz pos'n 2940 RTS 2950 *-------------------------------- 2960 P.REP JSR GET.NEXT.CHAR 2970 TAX REPETITION COUNT 2980 RTS 2990 *-------------------------------- 3000 * Center a string on current line 3010 *-------------------------------- 3020 P.CEN LDY #0 Count # chars in string 3030 .1 INY from next char to char below $40 3040 LDA (MSG.PNTR),Y or btwn $80 and $9F 3050 BMI .2 80-FF 3060 CMP #$40 3070 BCS .1 40-7F, keep counting 3080 .2 CMP #$A0 3090 BCS .1 A0-FF, keep counting 3100 DEY 00-3F or 80-9F, end of string 3110 TYA (Line.width - string.len) / 2 3120 EOR #$FF gives starting horiz posn 3130 SEC 3140 ADC MON.WIDTH 3150 LSR 3160 STA MON.CH 3170 STA MON.CH80 3180 P.NUL RTS 3190 *-------------------------------- 3200 P.FIL JSR GET.NEXT.CHAR 3210 JSR MY.VTAB 3220 P.FCL 3230 LDA #0 3240 STA MON.CH 3250 STA MON.CH80 3260 LDX MON.WIDTH 3270 RTS 3280 *-------------------------------- 3290 P.VTAB JSR GET.NEXT.CHAR 3300 MY.VTAB 3310 CMP MON.BOTTOM Stay inside current window 3320 BCC .1 ...it is inside 3330 LDA MON.BOTTOM Go to bottom line 3340 .1 STA MON.CV We use line # 1 to N, but 3350 DEC MON.CV monitor uses 0 to N-1 3360 JMP MON.VTAB 3370 *-------------------------------- 3380 P.WIN 3390 JSR GET.NEXT.CHAR 3400 STA MON.LEFT 3410 JSR GET.NEXT.CHAR 3420 STA MON.WIDTH 3430 JSR GET.NEXT.CHAR 3440 STA MON.TOP 3450 JSR GET.NEXT.CHAR 3460 STA MON.BOTTOM 3470 LDA #$99 3480 JMP MON.COUT 3490 *-------------------------------- 3500 T >MSG MSG0 3510 >MSG MSG2 3520 >MSG MSG1 3530 >MSG MSG3 3540 RTS 3550 *--------------------------------- 3560 MSG0 .HS 8C HOME SCREEN 3570 >WIN 10,38,0,24 3580 >FIL 1,- FILL TOP LINE WITH DASHES 3590 .HS FC ONE BAR ON NEXT LINE 3600 .HS 8D.88.FC.FC 2 BARS 3610 .HS 8D.88.FC.FC 2 BARS 3620 .HS 8D.88.FC.FC 2 BARS 3630 .HS 8D.88.FC.FC 2 BARS 3640 .HS 8D.88.FC.FC 2 BARS 3650 .HS 8D.88.FC.FC 2 BARS 3660 .HS 8D.88.FC 1 BAR AT END OF LINE 3670 >FCL - FULL CURRENT LINE WITH DASHES 3680 >POS 3,0 VTAB 3, HTAB 0 3690 .HS 8F INVERSE MODE 3700 >CEN "Demonstration of Message Printer" 3710 .HS 8E.8D.8D Normal mode 3720 >CEN "S-C Software Corporation" 3730 .HS 8D 3740 >CEN "P. O. Box 280300" 3750 .HS 8D 3760 >CEN "Dallas, TX 75228" 3770 .HS 8D.8D.8D 3780 >WIN 0,80,0,24 Full screen again 3790 .HS 00 End of message 3800 *--------------------------------- 3810 MSG1 >POS 11,1 VTAB 11, HTAB 1 3820 .HS 9D CLR EOL 3830 >STR "SELECT ONE: " 3840 .HS 00 End of message 3850 *--------------------------------- 3860 MSG2 >POS 24,1 VTAB 24, HTAB 1 3870 .HS 8F INVERSE MODE 3880 >STR " <SPACE> FOR MENU, <RETURN> FOR MORE " 3890 .HS 8E.00 NORMAL MODE, EOM 3900 *--------------------------------- 3910 MSG3 .HS 87.87.8D 3920 >STR "Wasn't that a nice demonstration?" 3930 .HS 8D.00 3940 *-------------------------------- 3950 TT JSR ANOTHER.MESSAGE.PRINTER 3960 .HS 8D.8D 3970 >CEN "This is another way to print messages." 3980 >HTAB 0 3990 >STR "<<<" 4000 >HTAB 99 TAB TO END OF LINE 4010 .HS 88.88 BACK UP TWO SPACES 4020 >STR ">>>" 4030 .HS 8D.00 4040 JSR ANOTHER.MESSAGE.PRINTER 4050 >REP 10,M-$40 10 CARRIAGE RETURNS 4060 >WIN 0,1,14,24 4070 >REP 10,| 4080 >WIN 79,1,14,24 4090 >REP 10,| 4100 >WIN 0,80,13,24 4110 >FCL _ 4120 >FIL 23,- 4130 >WIN 0,80,0,24 4140 >VTAB 24 4150 .HS 00 End of message 4160 RTS 4170 *-------------------------------- 4180 .LIST OFF |
Since we published my patches to allow DOS 3.3 to use the UniDisk 3.5 last May and June I've been using it every day, and just loving all that space. After basking in readers' praise for such a useful piece of code, you can imagine how I felt when I first went to boot that disk on the IIgs and watched it crash into the dirt!
After a little detective work the cause is found: When we boot from the //e Protocol Converter Interface it passes slot*16 in the A- and X-registers and the slot number in location $58. In my boot code in FORMAT.UNIDISK I was using the A-register and the contents of $58 to set up my RWTS. Come to find out, the IIgs only gives us the slot*16 in the X-register, so we have to do a little more manipulation to create the values we need. Here is the revised code:
3080 BOOT TXA 3082 PHA save slot*16 3090 LSR .divide 3092 LSR ..by 16 3094 LSR ...to get 3096 LSR ....slot
To update the boot image on an existing disk you can use the instructions at the top of page 23 of the July '86 issue. Be sure to note the caution in the last paragraph of page 21 in that issue, about not installing a DOS image that already contains RAMdisk patches: I tripped over that again myself!
Digging into the 65816 Apple IIgs has provided more entertain- ment than I've had since the introduction of the Apple II. So far, my programming efforts have been in the ProDOS 8 environment. When Apple gets its act together on ProDOS 16, writing position-independent code will become critical. Why not do a bit of preparing now? (The following techniques will apply to older Apples which have been upgraded with a 65802 cpu chip, also.)
Absolute internal references are the bugaboo of the position- independent programmer. (By the way, I am not referring to those individuals who program as well standing on their heads as they do flat on their backs or seated in a chair; I am referring to programs which run correctly no matter where they are loaded into memory, without any modifications to reflect the position in memory.) The three most common absolute internal references are:
The first problem can be solved simply by using the BRL instruction instead of JMP. For avoiding the second and third traps, the PER opcode is an ideal tool. The solutions outlined below pertain only to code and data within one 64K bank of memory. Solutions which handle programs and data in multiple banks will have to wait for future articles.
PER is one of the new opcodes found in the 65802 and 65816. PER stands for "Push Effective program counter (PC) Relative data word onto stack", and employs a hybrid stack/PC-relative addressing mode. PER adds the 16-bit signed displacement in its two-byte operand to the current value for the PC and pushes the result onto the stack. As usual, the high byte is pushed first, then the low byte. Regardless of the values for the m-, x-, and e-status bits, two bytes are always pushed onto the stack. Because the operand is a displacement relative to the PC, the top two bytes of the stack always will point to the designated program code or data, irrespective of whether it is internal or external to the program. Thus, an internal JSR can be simulated by the following sequence:
PER CONTINUE-1 BRA SUBROUTINE CONTINUE ... ... ... SUBROUTINE ... ... RTS
Note that the continuation address to be placed on the stack must be one less than the desired address (CONTINUE-1 above), because the RTS opcode increments the address it finds on the stack before jumping to that address. Also note that the BRA opcode works well if the subroutine is nearby (within about + or - 128 bytes of the BRA itself); if the subroutine is further away, you will need to use the BRL opcode here.
I have constructed two macros in the listing of the program which follows. >BSR calls nearby subroutines, and >BSL calls both nearby and distant subroutines. The macro definitions are in lines 1130-1210, and I have shown them in use in lines 1320-1330. The listed program itself is rather trivial, designed just to illustrate my contrived solutions. Lines 1270-1340 clear the screen and then print out the hex values from $00 to $F0, with an interval of $10. The two loops in lines 1350-1560 show two different ways to access data which is part of a program and still be position-independent; their function is to print out a string.
To access internal data, PER again comes to the rescue by allowing us to establish an external (zero page or stack) pointer to the data. Here, the operand must represent the exact address of the target data. The first example stores the pointer generated by PER into a pointer-variable in page zero (lines 1360-1400). PER puts the pointer onto the stack, and two PLAs pull it back off. If we were in Native mode with the m-bit cleared for 16-bit operation, only one PLA and STA would be needed. We could replace lines 1370-1400 with:
CLC XCE ENTER NATIVE MODE REP #$20 M=0, 16-BIT DATA PLA PULL OFF BOTH BYTES STA PTR STORE BOTH BYTES XCE BACK TO EMULATION MODE
The second approach is to leave the REP-generated pointer on the stack. Line 1500 shows how we can use it with stack- relative indirect-indexed address mode to access the same text string. Lines 1550 and 1560 are then absolutely necessary, to remove the pointer from the stack when we are finished with it.
Though it is not appropriate for this sample program, the power of native mode is evident when working with 16-bit address pointers. The problem of using native mode in simple examples is that you cannot call the old standard monitor entry points (such as HOME, COUT, and PRBYTE) from native mode.
If you are going to write code which will be crossing 64K banks, you can still use the PER instruction. However, you also have to take into account the program bank and data bank registers. More on this at another time.
1000 *SAVE S.POSITION 1010 *-------------------------------- 1020 * POSITION INDEPENDENT CODE WITH THE 65802/16 1030 *-------------------------------- 1040 .OP 65816 1050 *-------------------------------- 1060 NUM .EQ $06 1070 PTR .EQ $07,08 1080 *-------------------------------- 1090 HOME .EQ $FC58 1100 PRBYTE .EQ $FDDA 1110 COUT .EQ $FDED 1120 *-------------------------------- 1130 .MA BSR BRANCH RELATIVE TO SUBROUTINE 1140 PER :1+1 When Subroutine is Nearby 1150 :1 BRA ]1 1160 .EM 1170 *-------------------------------- 1180 .MA BSL BRANCH RELATIVE TO SUBROUTINE 1190 PER :1+2 When Subroutine is Far Away 1200 :1 BRL ]1 1210 .EM 1220 *-------------------------------- 1230 TRIVIAL.EXAMPLE 1240 SEC SET EMULATION MODE 1250 XCE IN CASE CALLED FROM NATIVE MODE 1260 PHP SAVE E-BIT 1270 *---Show Position-Independent subroutine calls-------- 1280 JSR HOME CLEAR SCREEN 1290 LDA #0 FOR NUM = 0 TO $F0 STEP $10 1300 .1 STA NUM 1310 JSR PRBYTE Print in hexadecimal 1320 >BSL PRBLNK Print two blanks 1330 >BSR ADD.10 ...NEXT NUM 1340 BCC .1 ...NOT FINISHED YET 1350 *---Show Page-Zero Indirect data access--------------- 1360 PER TEXT PUT POINTER TO TEXT ON STACK 1370 PLA Now put pointer into page zero 1380 STA PTR 1390 PLA 1400 STA PTR+1 1410 LDY #0 Point to beginning of text string 1420 .2 LDA (PTR),Y Get next character of string 1430 BEQ .3 End of string 1440 JSR COUT 1450 INY 1460 BNE .2 ...ALWAYS 1470 *---Show Stack-Indirect data access------------------- 1480 .3 PER TEXT POINTER TO TEXT ON STACK 1490 LDY #0 Point to beginning of text string 1500 .4 LDA (1,S),Y Get next character of string 1510 BEQ .5 End of string 1520 JSR COUT 1530 INY 1540 BNE .4 ...ALWAYS 1550 .5 PLA POP OFF TEXT POINTER 1560 PLA 1570 *---Return to caller------------- 1580 PLP GET SAVED E-BIT 1590 XCE RESTORE E 1600 RTS 1610 *-------------------------------- 1620 ADD.10 CLC 1630 LDA NUM ADD $10 TO NUM 1640 ADC #$10 SET CARRY IF WRAPS AROUND 1650 RTS 1660 *-------------------------------- 1670 .BS 256 FILLER TO MOVE PRBLNK FURTHER AWAY 1680 *-------------------------------- 1690 PRBLNK LDA #" " 1700 JSR COUT 1710 JMP COUT 1720 *-------------------------------- 1730 TEXT .HS 8D 1740 .AS -/Position-Independent Text String/ 1750 .HS 00 1760 *-------------------------------- |
The other day I needed a simple subroutine to read a line of text into an assembly language program. The routine had to be short, work in both 40- and 80-columns, and allow very simple editing. It had to read directly from the keyboard, regardless of where the input vector at KSWL ($38,39) might be pointing.
The following listing shows the result. READ.A.LINE is indeed short and fulfills all the needs I had. Backspace is the only editing character. When you type a printing character, it is displayed and the underline cursor advances. When you type a backspace, the cursor backs up and replaces what was previously at that position.
When you type a RETURN, the routine returns with the X-register pointing into the line buffer at the position where the RETURN is stored. If you type an ESCAPE, it nulls the line you just typed. The line is still in the buffer, but READ.A.LINE returns with the X-register equal to zero.
Inside READ.A.LINE I use the X-register to track position in the line buffer. I don't explicitly keep track of position on the screen, at least in absolute terms. Wherever the cursor is when you call READ.A.LINE is the place where the first cursor will be displayed. Since I do all moving around on the screen by simply printing out characters or backspacing, all screen positions are relative to the beginning position. You can use READ.A.LINE inside a small window if you like, and the input line will wrap nicely when it comes to the edge of the window.
Lines 1100-1120 handle the cursor by first printing an underline, and backspacing over it. Then when you type a character I will put it over the top of the cursor, and come back to line 1100 to display the next cursor.
Lines 1130-1150 are the standard way to read the keyboard when you don't want any firmware interference. (The Apple IIgs has more hardware bells and whistles here, with the Front Desk Processor and all, but this method will still work.)
When you type a backspace, I have to output a space to erase the cursor, then backspace twice and place a new cursor over the top of the character which was right before the old cursor. This is in lines 1350-1380. If the cursor is already at the beginning of the line, lines 1330-1340 will simply start over. Because of the way lines 1330 and 1340 are written, the maximum length line you can type and still use backspace is 128 characters.
I wrote a quick and dirty test program, to illustrate how to use READ.A.LINE. Lines 1500-1560 print out a prompting message. Line 1580 calls READ.A.LINE. The cursor will start right after the prompting message. Line 1600 tests for an ESCAPE, signifying a null entry. If you type RETURN, lines 1610-1670 will display the message you just entered.
1000 *SAVE S.READLINE 1010 *-------------------------------- 1020 KEYBOARD .EQ $C000 1030 STROBE .EQ $C010 1040 *-------------------------------- 1050 CROUT .EQ $FD8E 1060 COUT .EQ $FDED 1070 *-------------------------------- 1080 READ.A.LINE 1090 .0 LDX #0 Start at beginning of line 1100 .1 LDA #"_" Use underline for a cursor 1110 JSR COUT Print the Underline 1120 JSR BSOUT ...and back up over it 1130 .2 LDA KEYBOARD 1140 BPL .2 ...wait until key pressed 1150 STA STROBE 1160 STA WBUF,X 1170 CMP #$A0 1180 BCC .4 CONTROL CHAR 1190 CPX #MAXLEN 1200 BCS .2 1210 JSR COUT SHOW IT ON SCREEN 1220 INX 1230 BNE .1 ...ALWAYS 1240 .4 CMP #$88 1250 BEQ .5 BACKSPACE 1260 CMP #$9B 1270 BEQ .6 ESCAPE -- ABORT INPUT 1280 CMP #$8D 1290 BNE .2 ...NOT <RETURN>, IGNORE IT 1300 JSR SPCOUT ERASE CURSOR FROM SCREEN 1310 INX INCLUDE <RETURN> IN X-VALUE 1320 RTS RETURN TO CALLER 1330 .5 DEX 1340 BMI .0 ...ALREADY AT BEGINNING OF LINE 1350 JSR SPCOUT ...SPACE 1360 JSR BSOUT ...BACKSPACE 1370 JSR BSOUT ...AND ANOTHER 1380 JMP .1 1390 .6 LDX #0 SIGNAL ESCAPE TYPED 1400 RTS 1410 *-------------------------------- 1420 SPCOUT LDA #" " SPACE 1430 .HS 2C 1440 BSOUT LDA #$88 BACKSPACE 1450 JMP COUT 1460 *-------------------------------- 1470 MAXLEN .EQ 127 1480 WBUF .BS MAXLEN+1 1490 *-------------------------------- 1500 TEST 1510 LDX #0 PRINT OUT A PROMPT MESSAGE 1520 .0 LDA PROMPT,X 1530 JSR COUT 1540 INX 1550 CPX #PROMPT.LEN 1560 BCC .0 1570 *-------------------------------- 1580 JSR READ.A.LINE 1590 *-------------------------------- 1600 BEQ .2 ESCAPE ABORTED IT 1610 JSR CROUT <RETURN> OUTPUT 1620 LDX #0 PRINT OUT THE LINE YOU TYPED IN 1630 .1 LDA WBUF,X 1640 JSR COUT 1650 INX 1660 CMP #$8D UP TO A <RETURN> 1670 BNE .1 1680 .2 RTS 1690 *-------------------------------- 1700 PROMPT .AS -/Enter a line: / 1710 PROMPT.LEN .EQ *-PROMPT 1720 *-------------------------------- |
Several years ago Bob S-C published a little subroutine to convert two-byte hexadecimal values to decimal and print them. I have written a similar subroutine, but mine allows you to print any character as a leading character when the number has less than five digits, or to have no leading characters at all. Bob's subroutine always printed leading zeroes.
To use my subroutine, you put the number in the A- and X-registers (lo-byte in A, hi-byte in X), and the fill character in the Y-register. If you do not want any fill character, call my subroutine with Y=0. To make calling it easier, Bob S-C constructed a macro named PDEC, defined in lines 1490-1580 below. To print a number with no fill character, simply use the macro like this: >PDEC VALUE; to print with a fill character, use the macro with the fill character as the second parameter like this: >PDEC VALUE,"#".
Now let's look at my subroutine. Lines 1110-1120 store the high byte of the number, and the fill character. I waited to store the low-byte of the number in line 1160, inside the conversion loop, to save a few bytes and cycles.
Lines 1150-1380 comprise a loop controlled by X, counting down from 3 to 0. The loop prints out the first four digits and/or fill characters. X indexes into a power-of-tens table. I first subtract 10000 as many times as possible, counting each subtraction in the Y-register. Since Y starts out as ASCII 0, it increments through successive ASCII digits. When the remainder is finally less than 10000, I print the first digit. Then the process is repeated for 1000, 100, and 10.
Leading-zeroes may be either suppressed, or printed with your choice of fill character. The work is done in lines 1280-1340. I keep a flag in bit 7 of FLAG. This bit starts out 0 (see line 1140). As soon as I get a non-zero digit, I change the flag bit to 1 (see lines 1280-1300). If the flag bit is 1, lines 1310-1320 will decide to print the digit regardless of its value. (We don't want to omit or replace ALL zeroes, only LEADING zeroes.) Lines 1330-1340 will decide whther to print a fill character or nothing at all.
After printing the ten's digit, the remainder in NUM is the units digit in binary. All I have to do is add an ASCII "0" to change this remainder to ASCII, which I do in lines 1390-1400. By handling the unit's digit in this special way I save extra logic which would have been required to make the value $0000 print at least one zero and two bytes in my power-of-tens table, saving a total of six bytes. The cost is lines 1390 and 1400 versus a simple RTS, or four bytes. This method is also faster, because I don't have to cycle though the loop counting down the unit's digit.
For an exercise, you might try extending my subroutine to convert 3- or 4-byte values. It is really not too difficult. Of course, you would no longer be able to pass the value to be converted in the A- and X-registers. For 3-byte values, the power-of-ten tables would have to be extended in both dimensions:
TENTBL .DA #10,#100,#1000,#10000 .DA #100000,#1000000,#10000000 TENTBM .DA /10,/100,/1000,/10000 .DA /100000,/1000000,/10000000 TENTBH .DA #0,#0,#0,#0 .DA /100000/256,/1000000/256,/10000000/256
You would also have to extend the comparison and subtraction code inside the loop. Why not try it?
1000 *SAVE S.PRINT.DECIMAL 1010 *-------------------------------- 1020 * By Jan Eugenides and Bob S-C 1030 * 1040 * Call with A, X, and Y as follows: 1050 * (A) = low-byte of number to be printed 1060 * (X) = high byte of number 1070 * (Y) = fill character (or 00 if no fill) 1080 *-------------------------------- 1090 COUT .EQ $FDED 1100 *-------------------------------- 1110 PRDEC STX NUM+1 Store high byte of number 1120 STY FILL Store fill character 1130 LDX #3 FOR X = 3 TO 0 1140 STX FLAG Clear bit 7 in leading-zero flag 1150 .1 LDY #"0" Start with digit = ASCII 0 1160 .2 STA NUM Compare number to power of ten 1170 CMP TENTBL,X 10^(X+1) ... lo-byte 1180 LDA NUM+1 1190 SBC TENTBH,X 10^(X+1) ... hi-byte 1200 BCC .3 Remainder is smaller than 10^(X+1) 1210 STA NUM+1 Store remainder hi-byte 1220 LDA NUM Get remainder lo-byte 1230 SBC TENTBL,X 1240 INY Increment ASCII digit 1250 BNE .2 ...always 1260 *---Print a digit---------------- 1270 .3 TYA digit in ASCII 1280 CMP #"0" Is it a zero? 1290 BEQ .4 ...yes, might be leading zero 1300 STA FLAG ...no, so clear leading-zero flag 1310 .4 BIT FLAG If this is leading-zero, will be + 1320 BMI .5 ...not a leading zero 1330 LDA FILL ...leading zero, so use fill-char 1340 BEQ .6 ...Oops, no fill-char 1350 .5 JSR COUT Print the digit or fill-char 1360 .6 LDA NUM Get lo-byte of remainder 1370 DEX Next X 1380 BPL .1 Go get next digit 1390 ORA #"0" Change remainder to ASCII 1400 JMP COUT Print Unit's digit & RTS 1410 *-------------------------------- 1420 TENTBL .DA #10,#100,#1000,#10000 1430 TENTBH .DA /10,/100,/1000,/10000 1440 *-------------------------------- 1450 FLAG .BS 1 1460 FILL .BS 1 1470 NUM .BS 2 1480 *-------------------------------- 1490 .ma pdec 1500 lda ]1 1510 ldx ]1+1 1520 .do ]#>1 1530 ldy #"]2" 1540 .else 1550 ldy #0 1560 .fin 1570 jsr prdec 1580 .em 1590 *-------------------------------- 1600 CROUT .EQ $FD8E 1610 1620 T >PDEC VAL1,# 1630 JSR CROUT 1640 >PDEC VAL2,^ 1650 JSR CROUT 1660 >PDEC VAL3 1670 JSR CROUT 1680 >PDEC VAL4 1690 JSR CROUT 1700 >PDEC VAL5," " 1710 JSR CROUT 1720 RTS 1730 1740 VAL1 .DA 1234 1750 VAL2 .DA 79 1760 VAL3 .DA 65535 1770 VAL4 .DA 6987 1780 VAL5 .DA 23 1790 .LIST OFF |
The American Foundation for the Blind is managing a database of individuals with hands-on experience with computers, low vision aids, talking products, and other devices for the blind and visually-impaired. About 500 users and evaluators are currently listed in their database, and are available to help in training, purchase decisions, and evaluation of new products. If you have related experience are interested in helping, or could use such help yourself, you can contact their National Technology Center at 1-800-232-5463.
Some time ago Bruce Love, a mathematics teacher at Hillcrest High School in New Zealand, sent me a macro he has found quite handy. It is shown in lines 1010-1090 in the listing below. It can be used to generate in-line loops to copy a group of bytes from one place to another. The macro works with any group up to 256 bytes long. It is really quite flexible, as the several examples in lines 1460-1550 reveal. Note that the source and destination may be either absolute or indirect in form. Furthermore, the length may either be stored in a variable or in the immediate form.
I added the SET macro (lines 1170-1230) to set up a pointer, to simplify using the MOVE macro with pointers in page zero. An example of its use is in lines 1490 and 1500.
There is another macro that I have found useful for moving larger chunks of RAM around. I use it especially in ProDOS SYSTEM files for relocating program sections. See lines 1100-1160 for the definition of the COPY macro, which moves up to 256 pages from source to destination. The parameters for COPY are reminiscent of the monitor M-command. For example, to copy $4B00 through $5FFF to a destination beginning at $6A00 the monitor command would be "6A00<4B00.5FFFM". The equivalent COPY macro would be "COPY $6A00,$4B00,$5FFF".
With both of these macros, beware of overlapping the range of the source and destination just as you would with the monitor M-command. Unless, of course, you know what you are doing and are doing it on purpose. For example, you can copy the first character of a block to all positions in the block like this:
>MOVE BUFFER,BUFFER+1,LEN
As is usually the case with macros which generate code, the code will probably not be as efficient as you could write given more knowledge of the overall situation. However, macros like these can shorten the time to develop programs, and thus they are worthy in spite of their possible inefficiencies.
1000 *SAVE MOVE.MACROS 1010 *-------------------------------- 1020 .MA MOVE source,destination,length 1030 LDY #0 1040 :1 LDA ]1,Y GET SOURCE BYTE 1050 STA ]2,Y STORE DEST. BYTE 1060 INY NEXT BYTE 1070 CPY ]3 1080 BCC :1 ...IF MORE 1090 .EM 1100 *-------------------------------- 1110 .MA COPY destination,src.start,src.end 1120 LDA /]1 DESTINATION PAGE 1130 LDY /]2 SOURCE BEGINNING PAGE 1140 LDX /]3-]2+256 # OF PAGES 1150 JSR COPY.PAGES 1160 .EM 1170 *-------------------------------- 1180 .MA SET pointer,value 1190 LDA #]2 1200 STA ]1 1210 LDA /]2 1220 STA ]1+1 1230 .EM 1240 *-------------------------------- 1250 SPTR .EQ $00,01 1260 DPTR .EQ $02,03 1270 LENGTH .EQ $04 1280 BUFFER .EQ $200 1290 *-------------------------------- 1300 COPY.PAGES 1310 STA DPTR+1 1320 STY SPTR+1 1330 LDY #0 1340 STY DPTR 1350 STY SPTR 1360 .1 LDA (SPTR),Y 1370 STA (DPTR),Y 1380 INY NEXT BYTE 1390 BNE .1 ...KEEP GOING 1400 INC SPTR+1 ...END OF PAGE 1410 INC DPTR+1 1420 DEX NEXT PAGE 1430 BNE .1 ...KEEP GOING 1440 RTS ...FINISHED 1450 *-------------------------------- 1460 SAMPLE.MOVES 1470 >MOVE $500,$600,#40 1480 *-------------------------------- 1490 >SET SPTR,BUFFER+10 1500 >SET DPTR,BUFFER+40 1510 LDA #9 1520 STA LENGTH 1530 >MOVE (SPTR),(DPTR),LENGTH 1540 *-------------------------------- 1550 >MOVE BUFFER,(DPTR),#10 1560 *-------------------------------- 1570 SAMPLE.COPIES 1580 >COPY $6A00,$4B00,$5FFF 1590 >COPY $5000,$2200,$4AFF 1600 >COPY $1000,BUFFER,BUFFER+255 1610 RTS 1620 *-------------------------------- |
Quality Software has published a new supplement to for "Beneath Apple ProDOS," which includes information on ProDOS versions 1.2 and 1.3. It is 30 pages longer than the previous edition, which covered version 1.1.1 of ProDOS.
You might be wondering, "What is a supplement, anyway?" The book "Beneath Apple ProDOS" ("BAP") contains much reference material needed to really take advantage of ProDOS capabilities. While other books now cover much of the same ground, "BAP" was the first one to put it all into print. The supplement, however, is unique: it contains a complete description of the internal details of both the ProDOS MLI kernel and BASIC.SYSTEM. If you are at all involved with the inner works of ProDOS, or are having trouble finding out the REAL scoop on some issues, you NEED the supplement. I have used my copies extensively, and depended heavily on it when writing the ProDOS version of S-C Macro Assembler.
The original supplement, for versions 1.0.1 and 1.0.2 cost $10. The second and third editions are $12.50 each. Incredibly low-priced! You must order these directly from Quality Software, at 21610 Lassen Street #7, Chatsworth, CA 91311. As I understand it, the supplement is only sold to owners of "BAP", and you have to use the coupon found on page 8-9 of "BAP" to do the ordering. ("BAP" itself is $19.95 retail, but we sell it for $18 here.)
Gina Montagano, of the Software Toolworks, recently sent me "Chessmaster 2000". If you have not heard of it, Chessmaster is a really good chess-playing program which will run on the Apple II family. It is also available for Mac, IBM, and others. I bought Microchess back in 1977, and Sargon in 1978; they did well, considering they ran in such small machines. Microchess would fit in a 4K Apple! We have come a long way since then. Chessmaster has many nice features, is the current reigning "champion", and is well worth the $49.95 price. (My 11-year-old stepson Matthew figured out a neat way to beat it. He let it tromp all over him, all the way to checkmate. Then he stepped back one move, swapped sides, and won!) Some features: display board in color or monochrome, 2-D or 3-D; 20 levels of competence, plus one that purposely makes occasional obviously bad moves; optional hints for your best move; huge opening book; library of famous games; and on and on. Negative: copy protected, although you may order a backup copy for $5. Chessmaster 2000 is available at most software stores.
Apple Assembly Line (ISSN 0889-4302) 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 $14 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.)