In This Issue...
Renewing Subscriptions
The 4-digit number in the upper right corner of your mailing label is the expiration date of your subscription. The first two digits are the year, and the last two digits are the month of the last issue you have paid for. If your label says "8109" or "8110", now is the time to renew to be sure of uninterrupted service.
We now have about 500 subscribers, and are shooting for 1000 by the end of the year. (Look for my full page ad in the next NIBBLE.) I am printing 1000 copies of each issue so there will be plenty of back issues for latecomers.
Notice that I have a new address. The old one will still work for a while, but you should start using the new one: Bob Sander-Cederlof, S-C Software, P. O. Box 280300, Dallas, TX 75228.
Things For Sale
Here is an up-to-date list of some of the things which I have that you might need:
Quarterly Disk #1 (source code from Oct 80 - Dec 80)...$15.00 Quarterly Disk #2 (source code from Jan 81 - Mar 81)...$15.00 Quarterly Disk #3 (source code from Apr 81 - Jun 81)...$15.00 S-C ASSEMBLER II Version 4.0...........................$55.00 Beneath Apple DOS (book)...............................$18.00 Apple Machine Language (book)..........................$11.65 Blank Diskettes (Verbatim, with hub rings, no labels, plain white jackets, in cellophane wrapper).................20 disks for $50.00 Zip-Lock Bags (2-mil, 6"x9")...............100 bags for $8.50
If you are interested in getting a regular monthly shipment of 100 or more disks, we can work out an even lower price.
If you are in Texas, remember to send 5% sales tax on books, disks, or bags.
Sometimes I have needed to know where in memory a certain Applesoft line is located. Maybe I want to patch in a code which cannot be typed from the keyboard. Or maybe the program has been "compressed and optimized", so that the lines are too long to edit. Or maybe I am just curious.
It is simple enough, because the line number is stored in binary at the beginning of each line. I would looke at locations $67,68 to get the address of the first line. Then look at that location to get the address of the next line, and so on. Each line is stored in memory with the first two bytes telling where to find the next line. and the third and fourth bytes giving the line number. Of course, the line number is in binary, and the bytes are backward, and the whole screen is full of hex numbers making it very hard to keep everything straight....
There has to be an easier way! Working with Bob Sander-Cederlof last week, I came up with this simple little program which will print the address of any line in hex. It uses the ampersand (&) statement of Applesoft. You simply BRUN this program, which I call AMPERFIND, and then type an ampersand and the line number. BRUNning sets up the ampersand vector at $3F5-3F7 and returns.
Here is the program. Note that it takes more code to set up the ampersand vector than it takes to do the line number search! Lines 1210-1260 could be put anywhere in memory, just so $3F6 and $3F7 are made to point to that place.
[Bob Potts is an Assistant Vice President at the Bank of Louisville in Kentucky. This bank has 115 Apple IIs in use doing a variety of banking functions.]
1000 *--------------------------------- 1010 * FIND AN APPLESOFT LINE NUMBER 1020 * AND PRINT ADDRESS IN HEX 1030 *--------------------------------- 1040 .OR $300 1050 .TF AMPERFIND 1060 *--------------------------------- 1070 MON.PRNTAX .EQ $F941 PRINT TWO BYTES IN HEX 1080 AS.LINGET .EQ $DA0C CONVERT LINE NUMBER TO BINARY 1090 AS.FNDLIN .EQ $D61A FIND LINE IN APPLESOFT PROGRAM 1100 *--------------------------------- 1110 * SET UP AMPERSAND VECTOR 1120 *--------------------------------- 1130 LDA #$4C "JMP" OPCODE 1140 STA $3F5 1150 LDA #AMPERFIND 1160 STA $3F6 1170 LDA /AMPERFIND 1180 STA $3F7 1190 RTS 1200 *--------------------------------- 1210 AMPERFIND 1220 JSR AS.LINGET CONVERT LINE NUMBER TO BINARY 1230 JSR AS.FNDLIN FIND THE LINE 1240 LDX $9B 1250 LDA $9C GET THE LINE'S ADDRESS 1260 JMP MON.PRNTAX PRINT THE ADDRESS IN HEX |
David Holladay, from Madison, Wisconsin, wrote a recent article for the Adam & Eve Apple II Users Group about a technique he uses for turning the Apple keyboard into a Braille input device. He chose 6 keys which can be "simultaneously" depressed to give a composite code. The keys form a 2-by-3 rectangle, like the dots of Braille characters.
Because the Apple keyboard has N-key rollover, simultaneous depression of several keys results in each keycode being sent to the program one at a time. The order that the codes are produced appears random to the program. Some quirks in the way the Apple keyboard is wired up prevent the N-key rollover from working with every combination of keys. Some of them OR together to create a ghost code, different from the actual depressed keys. Apple has used many different keyboards, so the keys which can be used for David's program vary considerably from one Apple to another.
After playing around with his program for a while, I got interested in making a Binary Input Keyboard, rather than a Braille one. My keyboard, which is almost 4 years old (Apple serial # 219!), allows me to press any combination of the keys J, K, L, 1, 2, 3, and 4. I set up these keys with binary weights of hex 40, 20, 10, 08, 04, 02, and 01 respectively.
When you type a combination of these seven keys all at once, the time interval between keys is much shorter than the normal spacing between keystrokes. The program waits for one keyboard strobe, and then initiates a timeout loop. All keycodes received within the timeout window will be considered to have been struck "simultaneously". Each keycode is compared with the list of seven keys (JKL1234), and the appropriate binary weight ORed into the character. If a keycode is received which is not in the legal character list, the bell rings.
I made a test loop which calls the input routine, and displays the hex code on the screen.
The choice of keys (JKL1234) works fine on my Apple, but it may not work on yours. Experiment with various choices until you find seven keys which will work together on your keyboard. Then modify line 1420 with your list of keys, and it will be ready to go.
Possible applications? Maybe fast input of hexadecimal machine language programs. You would have to add one more key so that all eight bits could be specified. And you would have to train your mind and fingers to instantaneously translate from hex to binary finger-patterns. Or, maybe some sort of a game. The basic idea of reading simultaneous keystrokes could effectively create new keys. Or, maybe the basic idea of simultaneous keystrokes could be used for entering secret passwords.
1000 *--------------------------------- 1010 * BINARY KEYBOARD 1020 *--------------------------------- 1030 MON.CH .EQ $24 1040 MON.CV .EQ $25 1050 KEYBOARD .EQ $C000 1060 STROBE .EQ $C010 1070 MON.VTAB .EQ $FC24 1080 MON.HOME .EQ $FC58 1090 MON.BELL .EQ $FBE2 1100 MON.PRBYTE .EQ $FDDA 1110 *--------------------------------- 1120 GETCHR LDA #0 1130 .1 STA CHARCODE 1140 LDA #-16 1150 STA CNTR 1160 STA CNTR+1 1170 .2 LDA KEYBOARD 1180 BMI .4 SOMETHING TYPED 1190 INC CNTR 1200 BNE .2 1210 INC CNTR+1 1220 BNE .2 1230 LDA CHARCODE GET COMPOSITE CODE 1240 BEQ GETCHR NO KEYS HIT YET 1250 .3 RTS 1260 *--------------------------------- 1270 .4 STA STROBE CLEAR KEYBOARD STROBE 1280 AND #$7F 1290 CMP #$20 HANDLE BLANK SEPARATELY 1300 BEQ .3 1310 LDY #6 SEARCH LIST OF LEGAL KEYS 1320 .5 CMP LEGAL.KEYS,Y 1330 BEQ .6 1340 DEY 1350 BPL .5 1360 JSR MON.BELL 1370 JMP GETCHR 1380 .6 LDA KEY.BITS,Y 1390 ORA CHARCODE 1400 BNE .1 ...ALWAYS 1410 *--------------------------------- 1420 LEGAL.KEYS .AS /JKL1234/ 1430 KEY.BITS .HS 40201008040201 1440 *--------------------------------- 1450 CHARCODE .BS 1 1460 CNTR .BS 2 1470 *--------------------------------- 1480 * TEST BINARY KEYBOARD 1490 *--------------------------------- 1500 TEST JSR MON.HOME 1510 .1 JSR GETCHR 1520 STA $403 LINE 1, COLUMN 4 OF SCREEN 1530 LDA #0 1540 STA MON.CH 1550 STA MON.CV 1560 JSR MON.VTAB 1570 LDA $403 1580 JSR MON.PRBYTE 1590 JMP .1 |
Many of you have asked me, "What book will help me, an absolute beginner, learn 6502 machine language? I don't know what these other books are talking about!"
If these are your words, then the book "Apple Machine Language", by Don and Kurt Inman, is for you. It is published by Reston Publishing Company, in both hardback ($17.95) and paperback ($12.95). The book has 296 pages, is set in clear, easy-to-read type, and has lots of good diagrams and illustrations.
The authors assume that you are at least familiar with Applesoft Basic. Chapter 1 gives a brief review of Applesoft, with special emphasis on the PEEK, POKE, and CALL statements. (These are the statements you will be using to communicate between Basic and machine language programs.) The authors also assume that you have your own Apple, and that you will not just READ the book. They expect you to follow along every example with your own Apple, so you can EXPERIENCE the material. You will not only learn a lot faster, but it will stick with you and you will UNDERSTAND what is going on.
Chapter 2 takes you across the bridge from Basic to machine language, very gently. You develop, with the authors, a little Applesoft program which helps you enter and test machine language programs.
Chapter 3 finally introduces the ideas of binary numbers, hexadecimal, the A-register in the 6502, and a few instruction codes. You will learn how to load a value into the A-register, modify that value, and store the result back into memory.
There are exercises at the end of each chapter which review the material covered. Don't let that worry you, though...they also printed the answers!
Chapter 4 starts to get interesting and useful. You learn how to use machine language to put some simple color graphics on the Apple screen. You can plot individual points, draw rectangles, and color them in. All the while, you are learning more machine instructions, more registers, more about memory addressing, and so forth.
Chapter 5 introduces you to writing text on the screen. You learn how to call some of the monitor subroutines for text output, how to print characters at particular screen locations, and how to write messages of your choice. Some new instructions are covered, and you learn some new address modes. In particular, you learn all about relative branching.
Chapter 6 is one of my favorites. I have always enjoyed twiddling Apple's little built-in speaker, and this chapter shows you how. You build and play with a tone generator program, even to the point of tuning it up to make a simulated piano keyboard.
Chapter 7 takes you deeper into sound and graphics, helping you code a routine to display the notes as you play them from the keyboard. By the time you finish this chapter you will understand how to use 28 of the 6502's 56 instructions, and 8 of its 13 addressing modes. You will also have used 9 of the subroutines found inside the Apple Monitor ROM.
Chapter 8 takes you inside Apple's Monitor...just a little. Until now, you have been using the Applesoft program developed in chapter 2 to enter and test all your machine language programs. In chapter 8 you learn how to do it from the monitor. You will also learn how to do addition and subtraction.
Chapter 9 show you how to add numbers too big to fit in one byte. Since one byte will only hold numbers between 0 and 255, or between -128 and +127, you can see that most numbers ARE too big to fit in one byte. You will also learn all about the way negative numbers are handled in the 6502.
Chapter 10 delves deeper into the Apple Monitor, and explores 6502 decimal mode arithmetic.
Chapter 11 is only for those fortunate readers who have Integer BASIC in their Apples. It doesn't matter whether Integer BASIC is on the Apple Monitor board, on a firmware card in ROM, or in a 16K RAM card...just so you have it. Why? Because there is another program in there you might not even be aware of: the Apple Mini-Assembler. If you are lucky enough to have it, chapter 11 will tell you how to use it. If not, skipover this chapter and use your S-C ASSEMBLER II instead! On second thought, don't skip chapter 11 entirely. It is here that indirect addressing is covered, and you need to know this material.
Chapter 12, "Putting It All Together", puts it all together. The programming experience you work through is a multiplication subroutine.
There are four appendices which summarize the information about the Apple hardware found throughout the book. Several of the charts in Appendix-A list page number references. (Early editions of the book had blank columns where the page numbers were supposed to be, but that has been corrected.) And finally, there is a regular alphabetic index.
By the time you finish this book, you have a solid foundation for learning to use an assembler like the S-C ASSEMBLER II. I would like to think that my assembler is easy enough to learn that books like this one would not be needed, but there are a lot of concepts that are completely foreign to new computer owners.
I want to do all I can to help every one of you become proficient in assembly language, so I am making "Apple Machine Language" available to you at a discount. You can buy the $12.95 paperback edition from me for $11.65 (plus 58 cents tax if you are in Texas). Include a dollar for shipping, so I don't go broke.
I have noticed two ways to compare a byte used inside DOS and other Apple software. In the cases I am thinking of, the following code required the Y-register to be zero. The first way I have seen is straightforward:
LDA ... BYTE TO BE TESTED CMP #$19 VALUE WE WANT TO TEST FOR BNE .1 ALSO AFFECTS CARRY STATUS LDY #0 IF =, CARRY SET ... |
The other way is a little trickier, but it saves one byte:
LDA ... BYTE TO BE TESTED EOR #$19 VALUE WE WANT TO TEST FOR BNE .1 DOESN'T AFFECT CARRY STATUS TAY A AND Y BOTH ZERO ... |
This may help you understand some of those disassemblies you are making, or help you save a byte here and there.
If you have DOS 3.3, you have no doubt enjoyed using the FID program to copy files from one disk to another. The wildcard feature in filenames is especially nice, because it lets you set up a semi-automatic copy of a whole set of files, or even the whole disk.
Sometimes I am reluctant to let the wildcard name go through without prompting, because there might be a file or two I don't want copied which matches the specified name. However, there are so many files involved that I really don't want to sit there and type "Y" for every one of them. What we need is a "selective catalog" command -- a FID command to list all files names which match the wildcarded-name.
Here are some easy patches which you can apply to FID which will convert the VERIFY command to just what we want.
]BLOAD FID load FID ]CALL -151 get to Apple's monitor *DBE:60 return before verifying *C10:EA EA EA no double spacing *3D0G return to BASIC ]BSAVE FID/CATALOG,A$803,L$124E save the new version |
Now if you BRUN FID/CATALOG you will see the normal FID menu. Select option 8 (VERIFY), specify a slot and drive, and type a file name (preferably with the "=" wildcard in it). Specify NO prompting. When you "PRESS ANY OTHER KEY TO BEGIN" you will see a list of all files whose names match the filename you typed.
Someone else will have to figure out how to get the file type and size to print.
When you are writing games or other simulation exercises, you frequently need a source of random numbers. In Basic it's easy, but how about assembly language?
The WozPak from Call A.P.P.L.E. has directions for calling the RND(X) function in the Integer BASIC ROMs. Remember that this function returns a random integer between 0 and X-1 for an argument X. Linda Egan, from Maywood, California, wrote that she had trouble making the WozPak method work. I don't know what that method was, but I looked up the code in the ROM and came up with some working code.
1000 *--------------------------------- 1010 * RANDOM FUNCTION 1020 * --------------- 1030 * CALLS SUBROUTINE IN INTEGER BASIC ROM TO GET 1040 * A RANDOM NUMBER BETWEEN 0 ANT X-1 1050 * 1060 * CALL: VALUE X IN Y- AND A-REGISTERS 1070 * JSR RANDOM 1080 * RETURN: RANDOM NUMBER IN Y- AND A-REGISTERS 1090 * LO-BYTE IN Y, HI-BYTE IN A 1100 *--------------------------------- 1110 IB.ARG .EQ $CE,CF 1120 IB.LOSTACK .EQ $50 THRU $6F 1130 IB.HISTACK .EQ $A0 THRU $BF 1140 *--------------------------------- 1150 IB.RANDOM .EQ $EF51 1160 MON.PRBYTE .EQ $FDDA 1170 MON.COUT .EQ $FDED 1180 *--------------------------------- 1190 RANDOM LDX #$20 I/B NOUN-STACK POINTER 1200 STA IB.ARG+1 1210 STY IB.ARG 1220 LDY #0 FLAG VALUE ON STACK 1230 JSR IB.RANDOM 1240 LDA IB.HISTACK,X 1250 LDY IB.LOSTACK,X 1260 RTS 1270 *--------------------------------- 1280 TEST.RANDOM 1290 LDA #160 1300 STA COUNT 1310 .1 LDY #1000 1320 LDA /1000 1330 JSR RANDOM RND(1000) 1340 JSR MON.PRBYTE 1350 TYA 1360 JSR MON.PRBYTE 1370 LDA #$A0 PRINT BLANK 1380 JSR MON.COUT 1390 DEC COUNT 1400 BNE .1 1410 RTS 1420 COUNT .BS 1 |
Lines 1190-1260 are all you need. They set up a call to the ROM code, and pick up the returned value.
Line 1190 sets the X-register to $20. The ROM code uses X for a stack index, and $20 means an empty stack. This is not the hardware stack ($100-1FF), but a software-implemented stack. The stack is in three parts. The part I call IB.LOSTACK runs from $50 thru $6F. IB.HISTACK runs from $A0 thru $BF. A third part runs from $78 thru $97. The ROM code pushes our argument on these stacks like this: the low byte goes on LOSTACK, the high byte on HISTACK, and a zero (from the Y-register) on the FLAGSTACK. (If the value pushed on FLAGSTACK was not zero, it would be used as the high-byte of an address along with the low-byte from LOSTACK to indirectly address the data value.)
Lines 1200 and 1210 store our argument where the ROM code expects it to be, in $CE and $CF. Lines 1240 and 1250 retrieve the resulting random number from the stack.
Lines 1280 through 1420 are a test loop to demonstrate the random function. Twenty lines of eight random numbers each are printed on the screen in hexadecimal. I used an argument of 1000, so all the numbers are between 0 and 999.
What if you don't have the Integer BASIC ROMs in your Apple? Since the code is not very long, you could make your own copy of Woz's routines. I did that, and came up with the following program. I used the same test loop, but this time it is in lines 1760 thru 1900.
1000 *--------------------------------- 1010 * STAND-ALONE RANDOM FUNCTION 1020 * --------------------------- 1030 * 1040 * GET A RANDOM NUMBER BETWEEN 0 AND X-1 1050 * 1060 * CALL: VALUE X IN Y- AND A-REGISTERS 1070 * JSR RANDOM 1080 * RETURN: RANDOM NUMBER IN Y- AND A-REGISTERS 1090 * LO-BYTE IN Y, HI-BYTE IN A 1100 *--------------------------------- 1110 MON.RNDL .EQ $4E 1120 MON.RNDH .EQ $4F 1130 MON.PRBYTE .EQ $FDDA 1140 MON.COUT .EQ $FDED 1150 *--------------------------------- 1160 RANDOM STY LIMIT SAVE LIMIT VALUE 1170 STA LIMIT+1 1180 LDA MON.RNDH GET SEED HI-BYTE 1190 BNE .1 BE SURE SEED BTWN 1 AND 7FFF 1200 CMP MON.RNDL SET CARRY IF BOTH BYTES ZERO 1210 ADC #0 CHANGE 0000 TO 0100 1220 .1 AND #$7F MAKE SURE NOT LARGER THAN 7FFF 1230 STA MON.RNDH 1240 STA VALUE+1 1250 LDA MON.RNDL 1260 STA VALUE 1270 LDA #0 1280 STA VALUE+2 1290 STA VALUE+3 1300 *--------------------------------- 1310 LDY #17 LOOP TO MAKE NEXT RANDOM VALUE 1320 .2 LDA MON.RNDH (WOZNIAK'S ALGORITHM) 1330 ASL 1340 CLC 1350 ADC #$40 1360 ASL 1370 ROL MON.RNDL 1380 ROL MON.RNDH 1390 DEY 1400 BNE .2 1410 *--------------------------------- 1420 LDA LIMIT 1430 ORA LIMIT+1 1440 BEQ .5 RETURN ZERO 1450 *--------------------------------- 1460 * DIVIDE RANDOM VALUE (1-7FFF) BY LIMIT 1470 * AND USE REMAINDER (0<=REMAINDER<LIMIT) 1480 *--------------------------------- 1490 LDY #16 LOOP FOR 16-BITS 1500 .3 ASL VALUE DOUBLE DIVIDEND 1510 ROL VALUE+1 1520 ROL VALUE+2 1530 ROL VALUE+3 1540 LDA VALUE+2 1550 CMP LIMIT 1560 LDA VALUE+3 1570 SBC LIMIT+1 1580 BCC .4 PARTIAL DIVIDEND < LIMIT 1590 STA VALUE+3 1600 LDA VALUE+2 CARRY IS SET, SUBTRACT 1610 SBC LIMIT LO-BYTE OF LIMIT 1620 STA VALUE+2 1630 INC VALUE SET BIT IN QUOTIENT 1640 .4 DEY 1650 BNE .3 1660 *--------------------------------- 1670 * RETURN RANDOM VALUE MOD LIMIT 1680 *--------------------------------- 1690 .5 LDA VALUE+3 PICK UP REMAINDER FROM DIVISION 1700 LDY VALUE+2 1710 RTS 1720 *--------------------------------- 1730 LIMIT .BS 2 1740 VALUE .BS 4 1750 *--------------------------------- 1760 TEST.RANDOM 1770 LDA #160 1780 STA COUNT 1790 .1 LDY #1000 1800 LDA /1000 1810 JSR RANDOM RND(1000) 1820 JSR MON.PRBYTE 1830 TYA 1840 JSR MON.PRBYTE 1850 LDA #$A0 PRINT BLANK 1860 JSR MON.COUT 1870 DEC COUNT 1880 BNE .1 1890 RTS 1900 COUNT .BS 1 |
Lines 1160 and 1170 save the argument for later use. Lines 1180-1260 get the current random seed from the Apple Monitor and store it in VALUE. However, if the seed was 0000 it is converted to 0100. This is because a seed of 0000 replicates itself forever. Furthermore, the sign bit is stripped off; in other words, VALUE is set to the seed value modulo 32768. This is supposed to force the VALUE to be between 1 and 7FFF.
The random seed is also modified by the monitor whenever you are in KEYIN waiting for an input from the keyboard. This code is at $FD1B thru $FD24 in the monitor ROM. This means the seed might have any (truly random) value between 0000 and FFFF. If by chance it is $8000 when the RND function is called, VALUE will be set to 0000.
Lines 1270-1290 clear two more bytes of VALUE, which will be used later, in the division loop.
Lines 1300-1400 are Woz's algorithm for generating a sequence of random integers. It is a binary polynomial technique, but there seems to be a bug in it. If you run it 32768 times, you should generate each and every value between 0 and $7FFF exactly one time, but in random order. I tested it, and it really generates the values between $6000 and $60FF twice, and never generates $2000-20FF at all! You can play with it and see if there are some seed values which will produce numbers between $2000 and $20FF.
Lines 1420-1440 check the argument. If it is zero, I return the value zero for the function. Integer BASIC would give you "*** >32767 ERR" with a zero argument.
Lines 1490-1650 are a division program, to divide the random VALUE by the LIMIT. After it is finished, the quotient is in VALUE and VALUE+1, and the remainder is in VALUE+2 and VALUE+3. We don't need the quotient; the remainder is the random value we want.
Lines 1690-1710 pick up the result in registers A and Y, and return to the calling program.
What does it do? Why would you want to use it? Those who send in correct answers will get their names published here in a few months with the solution.
SUBROUTINE: BRK PLA PLA PLA RTS |
OK, I'll give you a little hint. One of the five instructions is not used by the 6502 processor. Can you tell which one?
As far as I know, this routine has never before been published; however, I use it in almost every program I write. It's a jewel of a routine, worth many times its weight in gold!
Send your answers to John Broderick, 8635 Shagrock, Dallas, TX 75238. If you have any similar neat code segments, send them with explanation. I'll try to make this a regular column in the AAL.
Volume 1, Issue 2 of Apple Assembly Line contained a program for writing assembly source programs for the S-C Assembler II Version 4.0 on DOS text files. Peter Bartlett of Chicago was trying to use it with a Corvus Hard Disk, and found a problem with the program.
The Corvus system will not accept a CLOSE command unless there is a file name on it (unlike regular DOS). One solution is to delete the two calls to CLOSE.FILE at lines 1410 and 1570.
While talking with Peter I discovered a bug in my program, in the subroutine named ISSUE.DOS.COMMAND. It is supposed to allow slot and drive parameters on the file name. This was described in the write-up on page 11. Two errors made it not work.
First, line 1910 says:
1910 CMP #', COMMA?
but the character in the A-register has the high bit set to one. Change line 1910 to:
1910 CMP #$AC COMMA?
Second, line 1940 says:
1940 STA DOS.BUFFER,Y
Change it to:
1940 STA DOS.BUFFER-1,Y
The line numbers above correspond to the printed listing in the AAL article. They may not be exactly the same as the source code on Quarterly Disk #1. If you have Quarterly Disk #1 with a serial number of 45 or higher, your copy is already fixed.
About Advertising
Do you have a new product you want to test market, which would appeal to the Apple Assembly Line readers? You ought to try an ad in these pages. The current price is $20 for a full page, $10 for a half page. Send it to me just as you want it printed (I can do the reduction to make it fit on the page).The P5A ROM on your Apple Disk II Controller has a 256-byte program in it which reads track 0 sector 0 into memory and starts executing it.
The data in track 0 sector 0 is read into memory from $0800-08FF. Location $0800 contains a value indicating how many sectors to boot in. This is usually zero, meaning to read only sector zero. However, it could be as high as $0F, meaning to read all 16 sectors of track 0 into memory from $0800-17FF. (The BASICS diskette uses this feature.) Once the selected number of sectors has been read, the boot ROM jumps to $0801 to start execution. At this point (in a normal DOS boot) the rest of DOS is loaded.
My listing starts at $C600, which is where it will be if your controller is in slot 6. The code is all independent of position, so that it can be plugged into any slot. In fact, you can move the code into RAM if you like, just so the second digit of the address is the same as the controller card slot number. I do this some times when I am trying to crack locked disks. I go to the monitor, type 8600<C600.C6FFM, and then patch a BRK opcode on top of the JMP $0801 at $86F8. Then 8600G will read in track 0 sector 0 and BRK back to the monitor, and I can analyze the code to see how the rest is read in.
Enough of that, let's get into the code! Lines 1510-1690 are an esoteric loop which generate the nybble conversion table. The table is built in page 3, from $36C through $3D5. I tried out the loop after storing FF bytes throughout page 3, and got this:
0368- FF FF FF FF 00 01 FF FF 03A0- FF 1B FF 1C 1D 1E FF FF 0370- 02 03 FF 04 05 06 FF FF 03A8- FF 1F FF FF 20 21 FF 22 0378- FF FF FF FF 07 08 FF FF 03B0- 23 24 25 26 27 28 FF FF 0380- FF 09 0A 0B 0C 0D FF FF 03B8- FF FF FF 29 2A 2B FF 2C 0388- 0E 0F 10 11 12 13 FF 14 03C0- 2D 2E 2F 30 31 32 FF FF 0390- 15 16 17 18 19 1A FF FF 03C8- 33 34 35 36 37 38 FF 39 0398- FF FF FF FF FF FF FF FF 03D0- 3A 3B 3C 3D 3E 3F FF FF
These bytes are referred to at lines 2670 and 2740, indexed from a base of $02D6. This makes a disk code of $96 give a $00 value, and a code of $FF give a value of $3F.
Lines 1710-1790 determine the slot number and multiply it by 16. The JSR MON.RTS is to an RTS instruction in the monitor ROM. The only purpose of this JSR is to put its own address on the stack. Then lines 1720 and 1730 lift up the high byte of the address from the stack. The second digit of this address is the slot number, and 4 ASL's will isolate it and multiply it by 16. Lines 1800-1830 select drive 0 and turn on the motor. (If you want to boot from drive 2, you can copy this code into RAM at $8600 and change the byte at $8636 from $8A to $8B.)
Lines 1880-1990 move the head to track 0 from wherever it was. If you were already at track 0, it just sits there making a racket as it bangs against the stop. Lines 2030-2070 initialize the track and sector numbers and the memory address to read into.
Lines 2090-2480 read a sector into the input area. Lines 2110-2290 are used two different ways, depending on the CARRY status upon entry. The first time CARRY is clear, and we look for an address header (D5 AA 96). After finding an address header the sector and track are check in lines 2300-2480; if they are the ones we want, CARRY is set and we do lines 2110-2290 over again. This time they look for a data header. If one is found, it's time to read the data.
Lines 2530-2880 read in the sector. First 86 bytes are read into a little buffer at the bottom of page 3 ($0300-0355). Then 256 bytes are read into the target memory area (normally $0800-08FF). A checksum is computed and checked; if it doesn't match, we start all over. Lines 2770-2880 put the bits from $0300-0355 together with those in the main buffer, in the same way discussed two months ago in the listing of DOS 3.3 B800-BCFF.
Lines 2900-2950 check whether we have read all the sectors specified by the first byte of track 0 sector 0. If not, loop back to read the next sector one page higher in memory. When they have all been read, control branches to $0801. The normal DOS boot only reads one sector before branching to $0801.
1000 *--------------------------------- 1010 * DOS 3.3 BOOT ROM $C600.C6FF 1020 * 1030 * COMMENTS BY BOB SANDER-CEDERLOF 1040 * JULY, 4, 1981 1050 *--------------------------------- 1060 * DISK CONTROLLER ADDRESSES 1070 *--------------------------------- 1080 PHOFF .EQ $C080 PHASE-OFF 1090 PHON .EQ $C081 PHASE-ON 1100 MTROFF .EQ $C088 MOTOR OFF 1110 MTRON .EQ $C089 MOTOR ON 1120 DRV0EN .EQ $C08A DRIVE 0 ENABLE 1130 DRV1EN .EQ $C08B DRIVE 1 ENABLE 1140 Q6L .EQ $C08C SET Q6 LOW 1150 Q6H .EQ $C08D SET Q6 HIGH 1160 Q7L .EQ $C08E SET Q7 LOW 1170 Q7H .EQ $C08F SET Q7 HIGH 1180 * 1190 * Q6 Q7 USE OF Q6 AND Q7 LINES 1200 * ---- ---- ---------------------- 1210 * LOW LOW READ (DISK TO SHIFT REGISTER) 1220 * LOW HIGH WRITE (SHIFT REGISTER TO DISK) 1230 * HIGH LOW SENSE WRITE PROTECT 1240 * HIGH HIGH LOAD SHIFT REGISTER FROM DATA BUS 1250 *--------------------------------- 1260 BUFFER.PNTR .EQ $26,27 1270 SLOT16 .EQ $2B SLOT NUMBER TIMES 16 1280 SECTOR .EQ $3D 1290 TRACK .EQ $41 1300 STACK .EQ $0100 1310 POST.NYBBLE.CODES .EQ $02D6 1320 LITTLE.BUFFER .EQ $0300 1330 MON.RTS .EQ $FF58 1340 MON.WAIT .EQ $FCA8 1350 *--------------------------------- 1360 .OR $C600 1370 .TA $0800 1380 *--------------------------------- 1390 BOOT.3.3 1400 LDX #$20 REDUNDANT INSTRUCTION, USED 1410 * TO IDENTIFY CONTROLLER CARD 1420 *--------------------------------- 1430 * GENERATE POST-NYBBLE CONVERSION TABLE 1440 * FILLS IN THOSE SLOTS WHOSE INDEX 1450 * RELATIVE TO POST.NYBBLE.CODES IS 1460 * A VALID NYBBLE CODE. (VALID CODES 1470 * HAVE AT MOST ONE PAIR OF ADJACENT 1480 * 0-BITS, AND AT LEAST ONE PAIR OF 1490 * ADJACENT 1-BITS IN BITS 0-6.) 1500 *--------------------------------- 1510 LDY #0 1520 LDX #3 COULD BE ANY VALUE FROM 0 TO $16 1530 * 3 USED FOR CONTROLLER ID 1540 .1 STX $3C CHECK CODE FOR VALID NYBBLE 1550 TXA 1560 ASL 1570 BIT $3C TEST (X .AND. 2*X) 1580 BEQ .3 NO ADJACENT 1-BITS, NO GOOD 1590 ORA $3C TEST ADJACENT 0-BITS 1600 EOR #$FF CHANGE TO 1'S FOR TEST 1610 AND #$7E DON'T CARE ABOUT BIT 7 1620 .2 BCS .3 NOT VALID NYBBLE CODE 1630 LSR 1640 BNE .2 1650 TYA 1660 STA POST.NYBBLE.CODES+$80,X 1670 INY 1680 .3 INX 1690 BPL .1 1700 *--------------------------------- 1710 JSR MON.RTS GET THIS LOCATION ON STACK 1720 TSX FIND THE PAGE BYTE ON STACK 1730 LDA STACK,X 1740 ASL ISOLATE SLOT NUMBER 1750 ASL AND MULTIPLY BY 16 1760 ASL 1770 ASL 1780 STA SLOT16 SLOT NUMBER TIMES 16 1790 TAX 1800 LDA Q7L,X SET UP TO READ DRIVE 1810 LDA Q6L,X 1820 LDA DRV0EN,X ENABLE DRIVE 0 1830 LDA MTRON,X TURN ON MOTOR 1840 *--------------------------------- 1850 * MOVE TO TRACK 0 (ASSUME WORST CASE 1860 * INITIAL POSITION OF TRACK 40). 1870 *--------------------------------- 1880 LDY #80 80 HALF-TRACKS 1890 .4 LDA PHOFF,X STEPPER MOTOR PHASE OFF 1900 TYA COMPUTE NEXT PHASE 1910 AND #3 YIELDS 3,2,1,0 1920 ASL YIELDS 6,4,2,0 1930 ORA SLOT16 MERGE WITH SLOT*16 1940 TAX 1950 LDA PHON,X STEPPER MOTOR PHASE ON 1960 LDA #86 WAIT 19.2 MILLISECONDS 1970 JSR MON.WAIT NO CHANGE TO X OR Y, A=0 1980 DEY NEXT HALF-TRACK 1990 BPL .4 2000 *--------------------------------- 2010 * A=0, X=SLOT*16 2020 *--------------------------------- 2030 STA BUFFER.PNTR ($00 --> LOW BYTE OF PNTR) 2040 STA SECTOR 0 2050 STA TRACK 0 2060 LDA #8 BUFFER AT $0800 2070 STA BUFFER.PNTR+1 ($08 --> HI-BYTE OF PNTR) 2080 *--------------------------------- 2090 READ.SECTOR 2100 .1 CLC FLAG CLEAR, LOOK FOR $D5 AA 96 2110 .2 PHP SAVE FLAG ON STACK 2120 .3 LDA Q6L,X READ DISK 2130 BPL .3 2140 .4 EOR #$D5 2150 BNE .3 NO 2160 .5 LDA Q6L,X READ DISK 2170 BPL .5 2180 CMP #$AA 2190 BNE .4 2200 NOP 2210 .6 LDA Q6L,X READ DISK 2220 BPL .6 2230 CMP #$96 2240 BEQ .7 FOUND ADDRESS MARK: $D5 AA 96 2250 PLP RETRIEVE FLAG 2260 BCC .1 LOOKING FOR ADDRESS HEADER 2270 EOR #$AD LOOKING FOR DATA HEADER 2280 BEQ FILL.BUFFER 2290 BNE .1 START ALL OVER 2300 *--------------------------------- 2310 .7 LDY #3 READ VOLUME, TRACK, SECTOR 2320 .8 STA $40 2330 .9 LDA Q6L,X READ DISK 2340 BPL .9 2350 ROL SAVE UPPER SLICE 2360 STA $3C 2370 .10 LDA Q6L,X READ DISK 2380 BPL .10 2390 AND $3C MERGE SLICES 2400 DEY 3RD BYTE YET? 2410 BNE .8 NO, GET ANOTHER 2420 PLP THROW AWAY FLAG 2430 CMP SECTOR CORRECT SECTOR? 2440 BNE .1 NO 2450 LDA $40 CORRECT TRACK? 2460 CMP TRACK 2470 BNE .1 NO 2480 BCS .2 YES, SET FLAG FOR DATA HEADER 2490 * AND BRANCH BACK ALWAYS 2500 *--------------------------------- 2510 * A=0 ON ENTRY 2520 *--------------------------------- 2530 FILL.BUFFER 2540 LDY #86 READ 86 BYTES 2550 .1 STY $3C 2560 .2 LDY Q6L,X READ BYTE 2570 BPL .2 2580 EOR POST.NYBBLE.CODES,Y DECODE BYTE 2590 LDY $3C 2600 DEY 2610 STA LITTLE.BUFFER,Y 2620 BNE .1 2630 *--------------------------------- 2640 .3 STY $3C Y=0 2650 .4 LDY Q6L,X READ BYTE 2660 BPL .4 2670 EOR POST.NYBBLE.CODES,Y DECODE BYTE 2680 LDY $3C 2690 STA (BUFFER.PNTR),Y 2700 INY 2710 BNE .3 2720 .5 LDY Q6L,X READ CHECKSUM BYTE 2730 BPL .5 2740 EOR POST.NYBBLE.CODES,Y 2750 .6 BNE READ.SECTOR BAD CHECKSUM, START OVER 2760 *--------------------------------- 2770 LDY #0 2780 .7 LDX #86 PATCH THE 6+2 BACK TOGETHER 2790 .8 DEX 2800 BMI .7 FINISHED A TRIP 2810 LDA (BUFFER.PNTR),Y 2820 LSR LITTLE.BUFFER,X 2830 ROL 2840 LSR LITTLE.BUFFER,X 2850 ROL 2860 STA (BUFFER.PNTR),Y 2870 INY 2880 BNE .8 2890 *--------------------------------- 2900 INC BUFFER.PNTR+1 POINT AT NEXT PAGE 2910 INC SECTOR POINT AT NEXT SECOTR 2920 LDA SECTOR 2930 CMP $0800 SEE IF HAVE READ ENUF SECTORS 2940 LDX SLOT16 2950 BCC .6 NOT ENUF SECTORS YET 2960 JMP $0801 GO TO REST OF BOOT 2970 *--------------------------------- 2980 .HS 0000000000 UNUSED BYTES IN ROM |