Volume 6 -- Issue 10 | July 1986

In This Issue...

- New ProDOS Program Selector
- Using DP18 Under ProDOS
- Booting DOS 3.3 from the UniDisk 3.5
- How Many Cycles Does "BRL" Take?

65816 Books

65816/65802 Assembly Language Programming is here! We have Michael Fischer's book in stock and have filled all the back orders for the first substantial book on programming the 658xx chips. We can now ship your copy immediately, for only $18 + shipping, so give us a call.

Simon & Schuster reports that Programming the 65816, by David Eyes, will be ready to ship in about two weeks. This time it sounds believable, so next month you should be reading another notice like the one above. Our thanks to all of you who have been so patiently waiting for this long-delayed title.

A New Debugger

You may have noticed the ads in a couple of recent issues of AAL for a debugger called Quick Tracy. This program proved to be just the thing Bill needed while working on the UniDisk boot program in this issue. Quick Tracy co-resides with the DOS Version 2.0 S-C Macro Assembler, taking up 2K of memory, and you enter all the debugging commands directly from the assembler. You can single-step or trace through your code and set breakpoints on any register's contents (including PC and Stack) or the state of any status flag. This is a useful utility for only $35. Contact Eric Trehus at (408) 379-0563.

New ProDOS Program Selector | Bob Sander-Cederlof

In the November 1985 issue of Apple Assembly Line I printed a complete commented listing of the ProDOS QUIT code. This code resides at $D100-D3FF inside ProDOS, and is downloaded to $1000 and executed by the $65 MLI call.

The BYE command in BASIC.SYSTEM and SCASM.SYSTEM both call ProDOS MLI with call number $65, and so do many other system programs. For some reason FILER has its own quit code, which operates slightly differently from MLI-$65, but not really better. No one seems to particularly like MLI-$65, but they usually learn to live with it. That is, unless they purchase Catalyst, MouseFiler, or one of the other commercially-available ProDOS program selectors.

Not wanting to buy three or four different program selectors until I found one I liked, I decided to try writing my own. It replaces the standard QUIT code inside ProDOS, so that MLI-$65 downloads and executes my new code. My program first lists all of the on-line volume names, so that you can select a volume. You perform the selection by moving the cursor-bar with the arrow keys, and pressing RETURN. ESCAPE makes the program re-do the list of volume names, in case you want to change diskettes. Once a volume is selected, all of the system (SYS) and directory (DIR) filenames in that volume will be listed. Again, you use the arrow keys and RETURN to select either a system program to be executed, or a sub-directory to display. Just a few quick keystrokes and you are in a new application!

Here is an example of the volume name display:

           S/D  VOLUME NAME

           3/2  RAM
           7/1  HARD1
           7/2  HARD2
           6/1  UTILITIES

           USE ARROWS AND <RETURN> TO SELECT
           USE <ESCAPE> TO TRY AGAIN

And here is an example of a filename display:

           /HARD1

           SYS -- PRODOS
           SYS -- SCASM.SYSTEM
           SYS -- BASIC.SYSTEM
           SYS -- CONVERT
           SYS -- UTIL.SYSTEM
           DIR -- ASM1
           DIR -- ASM2
           DIR -- SCI
           DIR -- FSE
           DIR -- XREF
           DIR -- SCWP
           DIR -- TIMEMASTER
           DIR -- THUNDERCLOCK
           DIR -- PHASOR
           DIR -- MINTERMS
           DIR -- DP18
           <<<MORE>>>

           USE ARROWS AND <RETURN> TO SELECT
           USE <ESCAPE> TO TRY AGAIN

All of the SYS files are listed first, and then all of the DIR files, regardless of the order within the directory. This makes it easier to find the file you are looking for. If there are more than 16 filenames to display, the first 16 will be listed, followed by the word "<<<MORE>>>". When you use the arrow keys to move beyond the bottom of the list, if there are more filenames, the list will scroll up to make room for the next name on the screen. When the top name listed is not the first name in the list, the word "<<<MORE>>>" will be displayed above the list. Actually, it is easier to use than it is to describe.

I have gotten so used to an 80-column display now that I decided to make the menu in that mode. Lines 1425-1430 initialize the 80-column display for an enhanced Apple //e or //c. If you want to use some other configuration, or just like 40-columns better, replace those two lines with the following:

           1421    JSR $FE93
           1422    JSR $FE89
           1423    STA $C00C
           1424    STA $C00F
           1425    STA $C000
           1426 .2 JSR HOME

The six lines above make QUITTER a little too long to fit in three pages, so you need to make room for it somehow. I suggest putting the variables from lines 4870-4950 into page zero, say at $06-$0E. This will make the code assemble shorter, so it still fits between $D100 and $D3FF inside ProDOS.

An alternative is to make a further modification to ProDOS. The subroutine which downloads the QUIT code is at $FCE5-FD3A inside ProDOS. It is very inefficient, so there is ample room for adding features. However, by merely changing the LDX #3 at $FD06 to LDX #4, you can make it download four pages instead of three. When you BLOAD PRODOS at $2000, the LDX #3 is found at $4C06. Since the QUIT code is at the end of the PRODOS file, you can write a longer QUIT program if you wish. You also need to change the $03 at $2233 to $04, so that the boot code will install QUIT where it belongs.

Walking through the New QUITTER

The comments in lines 1010-1090 explain how to install the new QUITTER inside the PRODOS system file. Just in case there is an error, I recommend you try this first on a disk you can afford to lose. It all works here, but there's many a slip 'twixt the cup and the lip!

Line 1320 switches on the motherboard ROM code, so that we can use Apple monitor routines. Lines 1330-1410 clear out the memory bitmap in the ProDOS System Global Page. We have to do that so we can load another system file. Once the bitmap has been cleared, it is not safe to try to return to whatever system program was operating before QUITTER was entered. Anyway, the RESET vector has already been pointed at QUITTER, so it is pretty difficult to get out of QUITTER. If you wish, you could add a feature that allows aborting the QUIT call, but be aware that the memory bitmap will have been messed up.

Lines 1420-1590 display a list of all the volumes currently on-line, and allow you to move the cursor bar up and down the list. The subroutine DISPLAY.VOLUMES lists the volume names, displaying the one under the cursor in inverse mode. The subroutine GET.KEY accepts the four arrow keys, RETURN, and ESCAPE. The left and up arrows move the cursor bar up, while the right and down arrows move the cursor bar down. GET.KEY is a little complicated, since it also handles windowing for long lists of filenames.

The subroutine READ.THE.FILE, called from line 1610, reads in an entire volume directory or sub-directory. ProDOS has the built-in ability to read directories just as though they were regular files, so READ.THE.FILE is pretty simple: it merely OPENs the file, READs it, and CLOSEs it. Lines 2030-2150 perform the additional task of appending the current volume or filename to the previous prefix.

Lines 1640-1710 clear the screen and display the pathname of the selected directory, in preparation for display a file menu. Lines 1720-1800 collect a list of pointers to all of the SYS and DIR files in the directory, using the SCAN.DIRECTORY subroutine. SCAN.DIRECTORY appends a pointer to a list of pointers in DIRBUF for each file it finds of the specified type.

Lines 1810-1900 display the SYS and DIR files found in the directory. If there are more than 16 files, the word "<<<MORE>>>" will be displayed after the 16th name. Moving the cursor bar down will scroll the list up, so that you can see the rest of the filenames. If you press ESCAPE or RESET, it all starts over collecting volume names. If you press RETURN when the cursor bar is on a DIR file, the directory name will be added to the current prefix and a new filename list will appear.

If you press RETURN when the cursor bar is on a SYS file, lines 1950-1990 will load the system file and start it running. Lines 1950-1960 set the system prefix to the directory the system file is in. Lines 1970-1980 read the file into RAM starting at $2000, and if there are no errors we blast-off with a JMP $2000. If there ARE errors, the program just starts over.

      1000 *SAVE NEW.QUIT.CODE
      1010 *--------------------------------
      1020 *    Installation:
      1030 *      1.  BLOAD PRODOS,TSYS,A$2000
      1040 *      2.  BLOAD B.NEW.QUITTER,A$5700
      1050 *      3.  BSAVE PRODOS,TSYS,A$2000,L$3A00
      1060 *    Location:
      1070 *      In PRODOS file:   $5700-59FF
      1080 *      In ProDOS image:  $D100-D3FF
      1090 *      For execution:    $1000-12FF
      1100 *--------------------------------
      1110 *    Code which downloads the QUIT code resides at
      1120 *      $FCE5-FD3A.  This is loaded from $4BE5-4C3A.
      1130 *--------------------------------
      1140 BPNTR  .EQ $00,01
      1150 SPNTR  .EQ $02,03
      1160 DPNTR  .EQ $04,05
      1170 CV     .EQ $25
      1180 INVFLG .EQ $32
      1190 *--------------------------------
      1200 HOME   .EQ $FC58
      1210 CLREOL .EQ $FC9C
      1220 COUT   .EQ $FDED
      1230 CROUT  .EQ $FD8E
      1240 *--------------------------------
      1250 MLI    .EQ $BF00
      1260 BITMAP .EQ $BF58
      1270 *--------------------------------
      1280        .OR $1000
      1290        .TF B.NEW.QUITTER
      1300 *--------------------------------
      1310 QUITTER
      1320        LDA $C082    MOTHERBOARD ROMS
      1330        LDX #$16
      1340        LDA #0       PREPARE VIRGIN BITMAP
      1350 .1     STA BITMAP,X
      1360        DEX
      1370        BNE .1
      1380        INX          X=1, LOCKOUT $BF00 PAGE
      1390        STX BITMAP+$17
      1400        LDA #$CF
      1410        STA BITMAP
      1420 *---LIST VOLUME NAMES------------
      1425 .2     LDA #$99     CTRL-Y
      1430        JSR $C300    SET I/O HOOKS, 80-COL MODE, CLEAR SCREEN
      1440        LDY #Q.SDV
      1450        JSR MSG
      1460        JSR CLOSE.ALL.FILES
      1470        JSR MLI
      1480        .DA #$C5,ONLINE
      1490        LDY #0
      1500        STY MAX.DIRPNT
      1510        STY DIR.START
      1520        STY PATHNAME
      1530 .3     STY SEL.LINE
      1540        JSR DISPLAY.VOLUMES
      1550        LDY #Q.VHELP
      1560        JSR MSG
      1570        JSR GET.KEY
      1580        BCC .3       ...ARROW KEYS
      1590        BNE .2       ...ESCAPE KEY
      1600 *---READ DIRECTORY---------------
      1610 .4     JSR READ.THE.FILE
      1620        BCS .7
      1630 *---PRINT PATHNAME---------------
      1640        JSR HOME
      1650        LDY #0
      1660 .5     LDA PATHNAME+1,Y
      1670        ORA #$80
      1680        JSR COUT
      1690        INY
      1700        CPY PATHNAME
      1710        BCC .5
      1720 *---COLLECT FILENAMES------------
      1730        LDX #0
      1740        LDA #$FF          FIRST JUST "SYS" FILES
      1750        JSR SCAN.DIRECTORY
      1760        LDA #$0F          THEN JUST "DIR" FILES
      1770        JSR SCAN.DIRECTORY
      1771        TXA          SEE IF ANY FILES FOUND
      1772        BEQ .2       ...NO, BACK TO THE TOP
      1780        LDA #0       MARK END OF LIST
      1790        STA DIRBUF+256,X
      1800        STX MAX.DIRPNT
      1810 *---LIST THE FILENAMES-----------
      1820        TAY          Y=0
      1830        STY DIR.START
      1840 .6     STY SEL.LINE
      1850        JSR DISPLAY.FILES
      1860        LDY #Q.VHELP
      1870        JSR MSG
      1880        JSR GET.KEY
      1890        BCC .6       ...ARROW KEYS
      1900        BNE .2       ...ESCAPE KEY
      1910        LDY #$10
      1920        LDA (SPNTR),Y     GET FILE TYPE
      1930        BPL .4            DIRECTORY ($0F)
      1940 *---SYS FILE, LOAD & EXECUTE-----
      1950        JSR MLI      SET PREFIX
      1960        .DA #$C6,PATH
      1970        JSR READ.THE.FILE
      1980        BCS .7       ...ERROR IN READING
      1990        JMP BUFFER
      2000 .7     JMP QUITTER
      2010 *--------------------------------
      2020 READ.THE.FILE
      2030        LDY #0       APPEND CURRENTLY SELECTED NAME
      2040        LDA (SPNTR),Y     GET LENGTH OF NAME
      2050        AND #$0F
      2060        STA LENGTH
      2070        LDX PATHNAME      CURRENT LENGTH
      2080        LDA #'/'
      2090 .1     INX
      2100        INY
      2110        STA PATHNAME,X
      2120        LDA (SPNTR),Y
      2130        DEC LENGTH
      2140        BPL .1
      2150        STX PATHNAME
      2160        JSR MLI           OPEN THE FILE
      2170        .DA #$C8,OPEN
      2180        BCS RF.ERR
      2190        LDA O.REF         FILE REFERENCE NUMBER
      2200        STA R.REF
      2210        JSR MLI           READ THE WHOLE FILE
      2220        .DA #$CA,READ
      2221        BCC CLOSE.ALL.FILES
      2222        CMP #$4C          IS IT JUST EOF?
      2223        SEC
      2230        BNE RF.ERR        ...NO
      2240 CLOSE.ALL.FILES
      2250        JSR MLI           CLOSE THE FILE
      2260        .DA #$CC,CLOSE
      2270 RF.ERR RTS
      2280 *--------------------------------
      2290 SCAN.DIRECTORY
      2300        STA CURTYP        TYPE WE ARE COLLECTING
      2310        LDA #0            START WITH FIRST BLOCK
      2320 .1     STA CURBLK
      2330        LDA #BUFFER+4     FIRST 4 BYTES OF BLOCK SKIPPED
      2340        STA DPNTR
      2350        CLC               COMPUTE PAGE OF PNTR
      2360        LDA /BUFFER+4
      2370        ADC CURBLK
      2380        STA DPNTR+1
      2390        LDA ENTCNT
      2400        STA LENGTH
      2410 *--------------------------------
      2420 .2     LDY #0
      2430        LDA (DPNTR),Y
      2440        AND #$F0
      2450        BEQ .4       ...DELETED FILE
      2460        CMP #$E0     ...HEADER?
      2470        BCS .4       ...YES
      2480        LDY #$10
      2490        LDA (DPNTR),Y     LOOK AT FILE TYPE
      2500        CMP CURTYP
      2510        BNE .4            ...NOT CURRENT TYPE
      2520 *---DIR or SYS file--------------
      2530 .3     LDA DPNTR
      2540        STA DIRBUF,X
      2550        LDA DPNTR+1
      2560        STA DIRBUF+256,X
      2570        INX
      2580 *---ADVANCE TO NEXT ENTRY--------
      2590 .4     CLC
      2600        LDA DPNTR
      2610        ADC ENTLEN
      2620        STA DPNTR
      2630        BCC .5
      2640        INC DPNTR+1
      2650 .5     DEC LENGTH        AT END OF BLOCK YET?
      2660        BNE .2            ...NO, CONTINUE IN BLOCK
      2670        CLC
      2680        LDA CURBLK
      2690        ADC #2
      2700        CMP ACTLEN+1
      2710        BCC .1            ...YES, READ NEXT BLOCK
      2720 *--------------------------------
      2730        RTS
      2740 *--------------------------------
      2750 CLOSE  .DA #1,#0
      2760 ONLINE .DA #2,#0,BUFFER
      2770 OPEN   .DA #3,PATHNAME,OPNBUF
      2780 O.REF  .BS 1
      2790 READ   .DA #4
      2800 R.REF  .BS 1
      2810        .DA BUFFER,$9F00
      2820 ACTLEN .BS 2
      2830 PATH   .DA #1,PATHNAME
      2840 *--------------------------------
      2850 DISPLAY.VOLUMES
      2860        JSR SETUP.DISPLAY.LOOP
      2870        LDA #BUFFER
      2880        STA BPNTR
      2890        LDA /BUFFER
      2900        STA BPNTR+1
      2910 *--------------------------------
      2920 .1     LDY #0
      2930        LDA (BPNTR),Y
      2940        AND #$0F
      2950        BEQ .3       ...NO VOLUME HERE
      2960 *--------------------------------
      2970        JSR CHECK.FOR.SEL.LINE
      2980 *--------------------------------
      2990 .2     LDA (BPNTR),Y     GET UNIT NUMBER
      3000        LSR               ISOLATE SLOT NUMBER
      3010        LSR
      3020        LSR
      3030        LSR
      3040        AND #7
      3050        ORA #"0"
      3060        JSR COUT          PRINT SLOT NUMBER
      3070        LDA #"/"
      3080        JSR COUT
      3090        LDA (BPNTR),Y     GET UNIT NUMBER AGAIN
      3100        ASL               SET CARRY IF DRIVE 2
      3110        LDA #"1"          ASSUME DRIVE 1
      3120        ADC #0            CHANGE TO 2 IF TRUE
      3130        JSR COUT
      3140        LDA #" "     PRINT TWO SPACES
      3150        JSR COUT
      3160        JSR COUT
      3170        JSR PRINT.BPNTR.NAME
      3180 *--------------------------------
      3190 .3     CLC               POINT TO NEXT VOLUME NAME
      3200        LDA BPNTR
      3210        ADC #16
      3220        STA BPNTR
      3230        BCC .4
      3240        INC BPNTR+1
      3250 .4     DEC LENGTH        ANY MORE LEFT?
      3260        BNE .1            ...YES
      3270        RTS
      3280 *--------------------------------
      3290 PRINT.BPNTR.NAME
      3300        LDY #0
      3310        LDA (BPNTR),Y      GET NAME LENGTH
      3320        AND #$0F
      3330        TAX
      3340 .1     INY          PRINT THE VOLUME NAME
      3350        LDA (BPNTR),Y
      3360        ORA #$80
      3370        JSR COUT
      3380        DEX
      3390        BNE .1
      3400 *--------------------------------
      3410 .2     LDA #" "     PRINT TRAILING BLANKS
      3420        JSR COUT
      3430        INY
      3440        CPY #16
      3450        BCC .2
      3460        LDA #$FF     NORMAL MODE NOW
      3470        STA INVFLG
      3480        INC MAX.LINE      COUNT THE LINE
      3490        JMP CROUT
      3500 *--------------------------------
      3510 GET.KEY
      3520 .1     LDA $C000    READ KEY FROM KEYBOARD
      3530        BPL .1
      3540        STA $C010    CLEAR THE STROBE
      3550        CMP #$8D
      3560        BEQ .2       <RETURN>
      3570        CMP #$88     <--
      3580        BEQ .3
      3590        CMP #$95     -->
      3600        BEQ .7
      3610        CMP #$8A     DOWN ARROW
      3620        BEQ .7
      3630        CMP #$8B     UP ARROW
      3640        BEQ .3
      3650        CMP #$9B     ESCAPE
      3660        BNE .1       GET ANOTHER CHARACTER
      3670        LDA #$9B     ...SET .NE.
      3680 .2     RTS
      3690 *---<UP OR LEFT ARROW>-----------
      3700 .3     LDY SEL.LINE      CURRENT BRIGHT LINE
      3710        BNE .6            ...NOT TOP LINE
      3720        LDY DIR.START     ARE WE DISPLAYING THE FIRST ONE?
      3730        BEQ .5            ...YES
      3740        DEC DIR.START     ...NO, MOVE TOWARD FIRST LINE
      3750 .4     LDY #0            MAKE FIRST LINE BRIGHT
      3760        CLC
      3770        RTS
      3780 .5     LDY MAX.LINE      MAKE LAST LINE BRIGHT
      3790 .6     DEY
      3800        CLC
      3810        RTS
      3820 *---<DOWN OR RIGHT ARROW>--------
      3830 .7     LDY SEL.LINE      CURRENT BRIGHT LINE
      3840        INY               MOVE TOWARD LAST LINE
      3850        CPY MAX.LINE      BEYOND END OF SCREEN?
      3860        BCC .8            ...NO
      3870        LDA MAX.DIRPNT    ...YES, CHECK IF SHOWING LAST LINE
      3880        SBC #17
      3890        BCC .4            ...YES
      3900        CMP DIR.START
      3910        BCC .4            ...YES
      3920        INC DIR.START     ...NO, MOVE TOWARD LAST LINE
      3930        LDY SEL.LINE
      3940        CLC
      3950 .8     RTS
      3960 *--------------------------------
      3970 DISPLAY.FILES
      3980        JSR SETUP.DISPLAY.LOOP
      3990        LDA DIR.START
      4000        STA DIR.INDEX
      4010        JSR CLEAR.LINE.OR.PRINT.MORE.MSG
      4020 *--------------------------------
      4030 .1     LDX DIR.INDEX
      4040        LDY DIRBUF+256,X
      4050        BEQ .4       ...END OF LIST
      4060        STY BPNTR+1
      4070        LDA DIRBUF,X
      4080        STA BPNTR
      4090        JSR CHECK.FOR.SEL.LINE
      4100 *--------------------------------
      4110 .2     LDY #$10
      4120        LDA (BPNTR),Y
      4130        BMI .3       ...SYS FILE
      4140        LDY #Q.DIR
      4150        .HS 2C
      4160 .3     LDY #Q.SYS
      4170        JSR MSG
      4180        JSR PRINT.BPNTR.NAME
      4190 *--------------------------------
      4200        INC DIR.INDEX
      4210        DEC LENGTH
      4220        BNE .1
      4230 .4     LDA DIR.INDEX
      4240        CMP MAX.DIRPNT
      4250 *--------------------------------
      4260 CLEAR.LINE.OR.PRINT.MORE.MSG
      4270        BEQ .1       CLEAR LINE
      4280        LDY #Q.MORE
      4290        BNE MSG      ...ALWAYS
      4300 .1     JSR CLREOL
      4310        JMP CROUT
      4320 *--------------------------------
      4330 SETUP.DISPLAY.LOOP
      4340        LDA #16      MAX 16 LINES IN LIST
      4350        STA LENGTH
      4360        LDY #0
      4370        STY MAX.LINE
      4380        INY          SAME AS VTAB 3, HTAB 1
      4390        STY CV
      4400        JMP CROUT
      4410 *--------------------------------
      4420 CHECK.FOR.SEL.LINE
      4430        LDA MAX.LINE      SEE IF CURRENT LINE SHOULD
      4440        CMP SEL.LINE           BE INVERSE MODE
      4450        BNE .1            ...NO
      4460        LDA BPNTR         ...YES, SO SETUP POINTER
      4470        STA SPNTR
      4480        LDA BPNTR+1
      4490        STA SPNTR+1
      4500        LDA #$3F                  & SET INVERSE MODE
      4510        STA INVFLG
      4520 .1     RTS
      4530 *--------------------------------
      4540 MSG1   JSR COUT
      4550        INY
      4560 MSG    LDA QTS,Y
      4570        BNE MSG1
      4580        RTS
      4590 *--------------------------------
      4600 QTS    .EQ *
      4610 Q.SDV  .EQ *-QTS
      4620        .AS -"S/D  VOLUME NAME"
      4630        .HS 00
      4640 Q.VHELP .EQ *-QTS
      4650        .HS 8D
      4660        .AS -/USE ARROWS AND <RETURN> TO SELECT/
      4670        .HS 8D
      4680        .AS -/USE <ESCAPE> TO TRY AGAIN/
      4690        .HS 8D
      4700        .HS 00
      4710 Q.SYS  .EQ *-QTS
      4720        .AS -/SYS -- /
      4730        .HS 00
      4740 Q.DIR  .EQ *-QTS
      4750        .AS -/DIR -- /
      4760        .HS 00
      4770 Q.MORE .EQ *-QTS
      4780        .AS -/<<<MORE>>>/
      4790 Q.CR   .EQ *-QTS
      4800        .HS 8D00
      4810 *--------------------------------
      4820            .DUMMY
      4830            .OR $800
      4840 OPNBUF     .BS 1024
      4850 DIRBUF     .BS 512
      4860 PATHNAME   .EQ $280
      4870 DIR.INDEX  .BS 1
      4880 DIR.START  .BS 1
      4890 MAX.DIRPNT .BS 1
      4900 SEL.LINE   .BS 1
      4910 MAX.LINE   .BS 1
      4920 UNIT       .BS 1
      4930 LENGTH     .BS 1
      4940 CURTYP     .BS 1
      4950 CURBLK     .BS 1
      4960            .ED
      4970 *--------------------------------
      4980 BUFFER     .EQ $2000
      4990 ENTLEN     .EQ BUFFER+$23   ENTRY LENGTH
      5000 ENTCNT     .EQ BUFFER+$24   # ENTRIES PER BLOCK
      5010 *--------------------------------

The QUIT-code Installer

As I just mentioned, the code which downloads the QUIT-code from $D100-D3FF to $1000-12FF is located at $FCE5 inside ProDOS 1.1.1. Here is a commented listing of that code.

      1000 *SAVE S.FCE5.FD3A
      1010 *--------------------------------
      1020        .OR $FCE5
      1030        .TA $800
      1040 DOWNLOAD.QUITTER
      1050        LDA $C083    SWITCH IN CORRECT D000 BANK
      1060        LDA $C083
      1070 *--------------------------------
      1080        LDA $00      SAVE 00...03 ON STACK
      1090        PHA
      1100        LDA $01
      1110        PHA
      1120        LDA $02
      1130        PHA
      1140        LDA $03
      1150        PHA
      1160 *---SETUP POINTERS FOR MOVING----
      1170        LDA /$1000   Destination Pointer
      1180        STA $03
      1190        LDA /$D100   Source Pointer-
      1200        STA $01
      1210        LDA #0
      1220        STA $00
      1230        STA $02
      1240 *--------------------------------
      1250        TAY          Y=0
      1260        LDX #3       Move 3 Pages
      1270 .1     DEY
      1280        LDA ($00),Y
      1290        STA ($02),Y
      1300        TYA
      1310        BNE .1       ...More in same page
      1320        INC $01
      1330        INC $03      Advance to next pages
      1340        DEX          Count the page
      1350        BNE .1       ...Copy another page
      1360 *---Restore $03...$00------------
      1370        PLA
      1380        STA $03
      1390        PLA
      1400        STA $02
      1410        PLA
      1420        STA $01
      1430        PLA
      1440        STA $00
      1450 *--------------------------------
      1460        LDA $C08B    Select normal D000 bank
      1470        LDA $C08B
      1480 *---Set up RESET Vector----------
      1490        LDA #$1000   Lo-byte
      1500        STA $3F2
      1510        LDA /$1000   Hi-byte
      1520        STA $3F3
      1530        EOR #$A5     Power-up byte
      1540        STA $3F4
      1550 *--------------------------------
      1560        JMP $1000
      1570 *--------------------------------

The program above can be written in a lot less space, as follows:

      1580
      1590 *--------------------------------
      1600        .OR $FCE5
      1610        .TA $900
      1620 SC.DOWNLOAD.QUITTER
      1630        LDA $C083    Select D000 bank
      1640 *--------------------------------
      1650        LDY #0
      1660 .1     LDA $D100,Y
      1670        STA $1000,Y
      1680        LDA $D200,Y
      1690        STA $1100,Y
      1700        LDA $D300,Y
      1710        STA $1200,Y
      1720        INY
      1730        BNE .1
      1740 *--------------------------------
      1750        LDA $C08B    Select normal D000 bank
      1760 *---Set up RESET Vector----------
      1770        STY $3F2     RESET Vector Lo-byte
      1780        LDA /$1000   Hi-byte
      1790        STA $3F3
      1800        EOR #$A5     Power-up byte
      1810        STA $3F4
      1820 *--------------------------------
      1830        JMP $1000
      1840 *--------------------------------

Using DP18 Under ProDOS | Bill Morgan

A customer called up the other day to order the DP18 Source Code package, but he wanted it only if it ran under ProDOS. (That's 18-digit Binary Coded Decimal arithmetic for Applesoft.) Well, we hadn't tried to move it over before, but it didn't sound like too much of a problem, so I gave it a shot. It did turn out to be quite easy.

I first tried simply CONVERTing all the files over, including the binary object code, and RUNning the example programs. That almost worked! The DP18 arithmetic all operated just right, but the scheme of moving the Applesoft program up and BLOADing DP18 at $803 ran into a little trouble. The forward pointers in each line of the program weren't set up properly. Bob then pointed out to me that it's very easy to install a program between BASIC.SYSTEM and the buffers, so that might be the way to go in this situation. All it took was a little arithmetic to figure out that DP18 needs $1C pages and should therefore have an origin of $7E00. The .OR directive was the only line inside DP18 that I had to change!

After that I needed only two more things: a short machine language program to get the buffer from BI, issue the BLOAD command, and set the ampersand vector; and a one-line Applesoft routine that checks the vector to find out if DP18 is already installed and call the loader if not.

Here's the Applesoft routine:

     10  IF  PEEK (1014) + 256 *  PEEK (1015) <  > 32563 THEN
        PRINT  CHR$ (4)"BRUN INSTALL.DP18"

And here's all there is to the loader:

      1000 *SAVE S.INSTALL.DP18
      1010 *--------------------------------
      1020 BUFFER      .EQ $200
      1030
      1040 AMPERSAND   .EQ $3F6
      1050
      1060 DP.LINK     .EQ $7E00
      1070 AMP.LINK    .EQ $7E02
      1080
      1090 DOS.COMMAND .EQ $BE03
      1100 GET.BUFFER  .EQ $BEF5
      1110 FREE.BUFFER .EQ $BEF8
      1120
      1130 COUT1       .EQ $FDF0
      1140 *--------------------------------
      1150        .OR $300
      1160 *      .TF INSTALL.DP18
      1170
      1180 T      JSR FREE.BUFFER   kick others out
      1190        LDA #$1C
      1200        JSR GET.BUFFER    get 28 pages
      1210        BCS ERROR
      1220        CMP #$7E          must be at $7E00
      1230        BNE ERROR
      1240
      1250        LDX #LENGTH
      1260 .1     LDA COMMAND,X     "BLOAD DP18"
      1270        STA BUFFER,X
      1280        DEX
      1290        BPL .1
      1300        JSR DOS.COMMAND   do it
      1310        BCS ERROR
      1320
      1330        LDX #1
      1340 .2     LDA AMPERSAND,X   save old vector
      1350        STA AMP.LINK,X
      1360        LDA DP.LINK,X     & point to DP18
      1370        STA AMPERSAND,X
      1380        DEX
      1390        BPL .2
      1400 EXIT   RTS
      1410
      1420 ERROR  LDX #0
      1430 .1     LDA MESSAGE,X     show error message
      1440        BEQ EXIT
      1450        JSR COUT1
      1460        INX
      1470        BNE .1
      1480 *--------------------------------
      1490 MESSAGE .HS 8D
      1500         .AS -/Error loading DP18/
      1510         .HS 8D00
      1520
      1530 COMMAND .AS -/BLOAD DP18/
      1540         .HS 8D
      1550 LENGTH  .EQ *-COMMAND-1
      1560 *--------------------------------

Booting DOS 3.3 from the UniDisk 3.5 | Bill Morgan

A couple of months ago I presented an RWTS so DOS 3.3 could read and write Apple's new UniDisk 3.5. At that time I promised the additional code so we could boot DOS from the new device. Well, it took a little longer than I had planned, but here are the changes necessary so FORMAT.UNIDISK can produce a bootable disk.

There are two parts to the problem. We must first come up with a way to write a DOS image on the disk during formatting, and we must have a boot sector that can read in the new image and start it up. Since we have 32 sectors in track 0, with 30 of them available for the DOS image, I looked up Bob S-C's article from the April '86 issue on stuffing DOS into fewer sectors for faster booting. In that article he pointed out that we don't need to deal with pages $B3-B4 and $BB, since they're used for buffers, and that these days we can assume that all machines have at least 48K of RAM, so we don't need the relocator code from a DOS master. Eliminating these areas shaves the DOS image down to 32 sectors, but I still needed to get rid of two more to avoid carrying the boot code around inside DOS.

One more was easy: page $BC is just buffer in my RWTS. Then I noticed that nearly all of the code in pages $B6 and $B7 is used only by INIT and the boot process, and could therefore be done away with. It turns out that the only necessary part of $B6 is the various APPEND and VERIFY patches from $B65D-B6B2. Similarly, the only vital things in page $B7 are the RWTS entry at $B7B5-B7C1, a buffer clearing routine at $B7D6-B7DE, and RWTS's IOB and DCT from $B7E8-B7FE. I also decided to keep the read/write a group of pages code from $B793-B7B4, since some programs borrow that routine. So, I came up with some routines to pack the necessary areas in $B7 into the unused space in $B6 and thereby save another page.

Since we're booting in from the Protocol Converter, we don't immediately have the ability to read individual sectors. All we can do is read blocks, or pairs of sectors, and the DOS image doesn't conveniently split up into even blocks. Here's how I ended up mapping DOS pages into blocks:

           0 - Boot     4 - A3-A4    8 - AB-AC    C - B3,B6
           1 - 9D-9E    5 - A5-A6    9 - AD-AE    D - B8-B9
           2 - 9F-A0    6 - A7-A8    A - AF-B0    E - BA,BD
           3 - A1-A2    7 - A9-AA    B - B1-B2    F - BE-BF

Note that this formatter uses the DOS image in memory. You need to make sure that this DOS contains all of the patches you want to use, and ONLY the patches that can be used from bootup time. For example, I got into trouble when I initialized a disk with a DOS patched to use my memory expansion card as a RAM disk. Those patches depend on some code being in place in page zero of the alternate banks of the memory card, and that code wasn't there at boot time. The answer is to initialize the disk without these patches, and then make BRUN RAMDRIVE part of the HELLO program. You may have to make some such adjustment for your own system.

If you need to install a bootable DOS image on a disk formatted with the old version of this program, all you have to do is delete lines 1140-1690 and replace line 1710 with:

           LDA #1
           STA DRIVE

This program doesn't automatically save a HELLO program after formatting, so after it's done you need to LOAD your HELLO from another disk and SAVE it onto the freshly formatted one.

To create this new version you need to make three changes in S.FORMAT.UNIDISK: First, since ERROR is going to be too far away for a direct branch, replace lines 1220-1230 with these:

           1220       BCC .1
           1225       JMP ERROR
           1230 .1    LDA #2

Second, we need to replace DO.BOOT.SECTOR with DO.DOS.IMAGE, to write a complete DOS on the disk. Delete lines 1700-1790 and replace them with the following:

      1700 DO.DOS.IMAGE
      1710        INC DRIVE         that WAS drive one
      1720        LDA #0
      1730        STA BUFFER        buffer on even page
      1740        STA TRACK         all in track zero
      1750        LDX #$1F          start at sector $1F
      1760 .1     STX SECTOR        set sector
      1770        LDA PAGE.TABLE,X  get next page
      1780        CMP #$AA          special case?
      1790        BNE .2
      1800        JSR HANDLE.AA
      1810 .2     CMP #$B6          other special case?
      1820        BNE .3
      1830        JSR HANDLE.B6
      1840 .3     STA BUFFER+1      set buffer page
      1850        JSR CALL.RWTS     write it
      1860        LDX SECTOR
      1870        DEX               done?
      1880        BPL .1
      1890        RTS
      1900 *--------------------------------
      1910 HANDLE.AA
      1920        JSR MOVE.PAGE.TO.BUFFER
      1930        LDY #29
      1940 .1     LDA STARTUP.NAME,Y install HELLO name
      1950        STA MY.BUFFER+$75,Y
      1960        DEY
      1970        BPL .1
      1980        LDA #0            mark last command
      1990        STA MY.BUFFER+$5F as INIT
      2000        LDA /MY.BUFFER    point BUFFER here
      2010        RTS
      2020 *--------------------------------
      2030 HANDLE.B6
      2040        JSR MOVE.PAGE.TO.BUFFER
      2050        LDX #0
      2060 .1     LDY B7.TABLE,X    get segment length
      2070        BEQ .5            0 marks end
      2080        INX               next byte
      2090        LDA B7.TABLE,X    get offset into $B7
      2100        STA .3            stuff into LDA
      2110 .2     LDA $B700         get byte from $B7
      2120 .3     .EQ *-2
      2130        STA MY.BUFFER     put it in with $B6
      2140 .4     .EQ *-2
      2150        INC .3            next source byte
      2160        INC .4            next destination
      2170        DEY               done with segment?
      2180        BNE .2
      2190        INX               next byte in table
      2200        BNE .1         ...always
      2210
      2220 .5     LDA /MY.BUFFER    point BUFFER here
      2230        RTS
      2240
      2250 B7.TABLE
      2260        .DA #$2F,#$B793   R/W group & call RWTS
      2270        .DA #$9,#$B7D6    zero a buffer
      2280        .DA #$17,#$B7E8   IOB & DCT
      2290        .DA #0
      2300 *--------------------------------
      2310 MOVE.PAGE.TO.BUFFER
      2320        STA .2       A has either $AA or $B6
      2330        LDY #0
      2340 .1     LDA $FF00,Y
      2350 .2     .EQ *-1
      2360        STA MY.BUFFER,Y
      2370        INY
      2380        BNE .1
      2390        RTS
      2400 *--------------------------------
      2410 PAGE.TABLE
      2420        .DA /BOOT.IMAGE,/BOOT.IMAGE+$100
      2430        .HS 9D.9E.9F.A0.A1.A2.A3.A4
      2440        .HS A5.A6.A7.A8.A9.AA.AB.AC
      2450        .HS AD.AE.AF.B0.B1.B2.B3
      2460        .HS B6.B8.B9.BA.BD.BE.BF
      2470
      2480 STARTUP.NAME
      2490        .AS -/HELLO/
      2500        .BS STARTUP.NAME+30-*," "

The main body of DO.DOS.IMAGE just writes the DOS pages listed in PAGE.TABLE onto sectors 0-$1F of track 0, providing special handling for pages $AA and $B6. In page $AA we must install a startup program name into the filename buffer and then force the last-command-executed index to its INIT value, so the coldstart code will work properly after a boot. Page $B6 is a little more involved. We need to move certain segments from page $B7 in with $B6, using a table containing the lengths and addresses of the segments.

And the last step is installing some real boot code in place of the dummy message. Just delete lines 2200-2390 of the old program and put in the rest of the new code:

      2900 *--------------------------------
      2910
      2920        .BS *+255/256*256-*
      2930
      2940 COLDSTART  .EQ $9D84
      2950 IOB.SLOT   .EQ $B7E9
      2960 IOB.DRIVE  .EQ $B7EA
      2970 IOB.OSLOT  .EQ $B7F7
      2980 IOB.ODRIVE .EQ $B7F8
      2990 MY.COMPARE .EQ $BEB0     }
      3000 MY.READ    .EQ $BF49     }  inside RWTS.3.5
      3010 MY.WRITE   .EQ $BF6B     }
      3020 SETKBD     .EQ $FE89
      3030 SETVID     .EQ $FE93
      3040 *--------------------------------
      3050 BOOT.IMAGE
      3060        .PH $800
      3070        .DA #1
      3080 BOOT   TXA               save slot*16
      3082        PHA
      3090        LSR
      3092        LSR
      3094        LSR
      3096        LSR
      3100        ORA #$C0          form Cslot
      3110        PHA               save it
      3120        STA .1+2          fix entry address
      3130        STA .2+2          fix call address
      3140 .1     LDA $FFFF         get ProDOS entry
      3150        CLC
      3160        ADC #3
      3170        STA .2+1          set PC entry
      3180
      3190 .2     JSR $FFFF         call PC
      3200        .DA #1            READ
      3210        .DA PARMLIST
      3220        INC PC.BUFFER+1   ready for next block
      3230        INC PC.BUFFER+1
      3240        INC BLOCK         next block
      3250        LDA BLOCK
      3260        CMP #$10          we only want $1-F
      3270        BCC .2
      3280
      3290 RELOCATE.PAGES
      3300        LDX #0
      3310 .1     LDA $BA00,X       relocate packed pages
      3320        STA $BF00,X
      3330        LDA $B900,X
      3340        STA $BE00,X
      3350        LDA $B800,X
      3360        STA $BD00,X
      3370        LDA $B700,X
      3380        STA $BA00,X
      3390        LDA $B600,X
      3400        STA $B900,X
      3410        LDA $B500,X
      3420        STA $B800,X
      3430        LDA $B400,X
      3440        STA $B600,X
      3450        INX
      3460        BNE .1
      3470
      3480 FIX.B7 LDY TABLE.B7,X    get segment size
      3490        BEQ FIX.SLOT.REFS done?
      3500        INX
      3510        LDA TABLE.B7,X    get $B7 offset
      3520        STA .3            into STA
      3530 .1     LDA $B600
      3540 .2     .EQ *-2
      3550        STA $B700
      3560 .3     .EQ *-2
      3570        INC .2            next source
      3580        INC .3            next destination
      3590        DEY
      3600        BNE .1            segment done?
      3610        INX
      3620        BNE FIX.B7     ...always
      3630
      3640 FIX.SLOT.REFS
      3650        PLA               get Cslot
      3660        STA MY.READ
      3670        STA MY.WRITE
      3680        PLA               get slot*16
      3690        STA IOB.SLOT
      3700        STA IOB.OSLOT
      3710        STA MY.COMPARE
      3720        LDA #1
      3730        STA IOB.DRIVE
      3740        STA IOB.ODRIVE
      3750        LDX #$FF          new stack
      3760        TXS
      3770        JSR SETKBD        set  I/O hooks
      3780        JSR SETVID
      3790        JMP COLDSTART     & go to DOS
      3800 *--------------------------------
      3810 PARMLIST .DA #3
      3820          .DA #1
      3830 PC.BUFFER .DA $9D00
      3840 BLOCK    .DA <1
      3850
      3860 TABLE.B7 .DA #$2F,#$B793
      3870          .DA #$9,#$B7D6
      3880          .DA #$17,#$B7E8
      3890          .DA #0
      3900
      3910        .EP
      3920 BOOT.SIZE .EQ *-BOOT.IMAGE
      3930 *--------------------------------
      3940        .BS BOOT.IMAGE+$200-*
      3950 MY.BUFFER

Here we take note of which slot we're booting from and set up the Protocol Converter calls to read blocks 1-F into memory starting at $9D00. Then we move the last seven pages up to their real locations and unpack the $B7 information from page $B6. The last step is to plug the current slot and drive info into the IOB, so DOS will know where to look for the HELLO program, and the correct slot location into MY.READ, MY.WRITE and MY.COMPARE. Note that these addresses are inside RWTS 3.5 and may change if you modify that program.

How Many Cycles Does "BRL" Take? | Bob Sander-Cederlof

There seems to be some disagreement among the various references for the 65802/16 chip as to how many cycles some of the opcodes take. One in particular that is wrong in almost all places is the BRL (BRanch Long) opcode.

BRL is an unconditional branch, just like BRA. Neither opcode is found in the 6502. BRA (BRanch Always) is found in both the 65C02 and the 65802/16 chips. BRL is only in the 65802/16. BRA takes a single-byte relative offset, just like the conditional branch instructions; therefore, BRA can only branch within -127 to +128 bytes from the instruction. BRL takes a two-byte relative offset, so it can branch anywhere within the 64K bank of memory containing the instruction.

Most references, including the data sheets from Western Design Center and GTE, say that the BRL opcode takes 3 cycles. One reference even went so far as to claim the JMP opcode was obsolete, because BRL and JMP both took 3 cycles and 3 bytes, and BRL had the advantage of being a "run-anywhere" instruction.

However, David Eyes claims in his book that BRL takes 4 cycles.

Naturally, the final authority is the chip itself. But how do you measure the timing of one little instruction? One way is to use a logic analyzer (a very expensive piece of hardware that can give a cycle-by-cycle description of whatever electronic circuit it is attached to). Another is to write a program that will run a loop millions or billions of times, and time it with a stopwatch. The loop first has to be timed without the opcode you are examining, and later with the opcode; then you subtract and divide to get the number of cycles the opcode took.

I thought of a simpler way. I just use my ears! I wrote a loop that toggled the speaker, and put a known 3-cycle opcode inside the loop. Then I duplicated the loop using a BRL instruction in place of the known 3-cycle opcode. When the program runs, the first loop makes a certain tone until I press a key. Then control passes to the second loop. If the second loop makes the same tone, then BRL also takes 3 cycles. If the second tone is higher, BRL takes less than 3 cycles; if lower, then more than 3 cycles. Here is the program:

      1000 *SAVE S.TEST.BRL.TIMING
      1010        .OP 65802
      1020 *--------------------------------
      1030 TEST.BRL.TIMING
      1040 .1     LDA $C030    PLAY A TONE
      1050        LDY #128
      1060 .2     DEY
      1070        JMP .3       KNOWN TO BE 3 CYCLES
      1080 .3     BNE .2
      1090        LDA $C000    REPEAT TONE UNTIL KEYPRESS
      1100        BPL .1
      1110        STA $C010
      1120 *---NOW MAKE TONE WITH "BRL"---
      1130 .4     LDA $C030
      1140        LDY #128
      1150 .5     DEY
      1160        BRL .6       IF 4 CYCLES, LOWER PITCH TONE
      1170 .6     BNE .5
      1180        LDA $C000    REPEAT TONE UNTIL KEYPRESS
      1190        BPL .4
      1200        STA $C010
      1210        RTS
      1220 *--------------------------------

The known 3-cycle intruction is the JMP at line 1070. The BRL is inside the second loop at line 1160. When I ran the program, it produced a lower pitch for the BRL-loop. So, BRL takes more than 3 cycles. Is it 4 cycles?

I replaced line 1070 with two NOP lines, which takes 4 cycles altogether. Then I ran the program again, and both loops made the same pitch. Voila! BRL does take 4 cycles. Just to be sure, I made some more tests. They all told the same story.

Then I noticed that the references also disagree on the BRA opcode. Most say it takes 2 cycles. Since the other relative branches only take 2 cycles when they DON'T branch, and BRA never does that, something had to be wrong. In a 6502, 65C02, or 65802/16 Emulation mode, other relative branches take 3 cycles to branch when the target is in the same page as the next byte after the instruction itself, and 4 cycles when the target is in a different page. In 65802/16 Native mode, the conditional relative branches never take more than 3 cycles.

I modified my program to set up page-boundary crossings in various combinations and tested the BRA opcode. It always takes 3 cycles in Native mode. In Emulation mode or older chips it takes 3 or 4 cycles, just like the conditional branches.

If you wonder about the timing of other opcodes, you can set up similar tests.

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 $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.)