In This Issue...
Does that mean a Bug in the Little RAM Disk, or a Little Bug in the RAM Disk? Actually, both. Several of you have called or written to point out a problem in Bob's program last month.
The TAY instruction at line 1280 (on page 8) should be a TYA. It does seem pointless to force Y to zero and then immediately clobber it with whatever the processor read out of $C083. This code worked when Bob tested it on his //e, because that computer does return a zero when you read $C083. My ][+, on the other hand, returns a byte of video data, usually $A0, and that really makes a mess out of the VTOC and Catalog sectors.
There's one other glitch in that article as well. In the fifth paragraph on page 5 there is a reference to line number "189~". I bet you can guess that's really supposed to be "1890".
So, thanks to all of you who caught us on this one! It's nice to know you're keeping an eye on us.
I took Bob S-C's work with ProDOS Snooper (October 1985 AAL) one step further: I added MLI calls to the information that is collected in the trace table. By combining the MLI call data with the device driver data, we get a better idea of what is happening.
The entries below all come from slot 6 drive 1. MLI calls are tagged with an "M" after the hex data. To support both the MLI calls and device driver calls, the hex output provides the data as it exists in memory without taking into account whether a set of bytes is a two byte memory pointer or a single data byte.
For all calls, the return address is still shown as hi-byte first before the colon. Data for the device driver parameter is still from $42-$47. For MLI calls, the return address is to the program that called the routine in the BASIC.SYSTEM global page. All BASIC.SYSTEM calls go to the $BE00 global page and then to the $BF00 ProDOS global page. MLI data is the MLI call number followed by the first five bytes of the parameter list (some bytes do not apply if the list is shorter).
The volume in question is labeled /TEST and has one file, ABC, in the root directory.
First of all, issue: CAT,S6
A6E9:C7 BC BC 02 BC BC M GET PREFIX A85F:C5 60 01 02 00 03 M ON LINE CALL + Not used when EC0C:01 50 00 DC 02 00 READ BLOCK 2 + CAT /TEST entered A825:C4 BC BC C3 0F 00 M GET FILE INFO EC0C:01 60 00 DC 02 00 READ BLOCK 2 EC0C:01 60 00 DC 06 00 READ Bit Map B1B9:C8 BC BC 00 8A 01 M OPEN FILE EC0C:01 60 00 DC 02 00 READ BLOCK 2 EE85:01 60 00 8A 02 00 READ BLOCK 2 B175:CA 01 59 02 2B 00 M READ FILE B201:CE 01 2B 00 00 03 M SET FILE MARK + Appears for each B208:CA 01 59 02 27 00 M READ FILE + file in directory B0A5:CC 01 00 C3 CF D0 M CLOSE FILE B0FB:C5 60 BD BC 00 03 M ON LINE CALL EC0C:01 50 00 DC 02 00 READ BLOCK 2 B10F:C4 BC BC C3 0F 18 M GET FILE INFO EC0C:01 60 00 DC 02 00 READ BLOCK 2 EC0C:01 60 00 DC 06 00 READ Bit Map
For this simple operation, there are ten MLI calls and eight device driver calls (disk I/O operations). I do not understand the reason for the Get Prefix call at the beginning. It would appear that the On Line call and the Get File Info call at the end are unnecessary (we will be checking this out as we go). On Line returns the volume name, but this should already be available through the prefix or pathname of the directory. Get File Info information should already be available from the previous call, and the bit map was already read in once. However, this is a simple catalog operation and may be indicative of some of the steps necessary for more complex catalog operations.
Carrying this one step further, I issued CAT /TEST/DIR. In this case, the first read of the bit map is not performed. Next, the former apparently duplicate read of block 2 now turns into a read of block 7, the key block for subdirectory DIR (in /TEXT/DIR; the device driver return address is $EE85, the buffer address is $8A00). Note: block 2 is the key block of the root directory.
A Get File Info call for a volume name (/TEST) always reads the bit map. Therefore, this call is repeated when cataloging a volume, but not when cataloging a subdirectory. As to the On Line call, it is used to get volume name for the Get File Info call for the free space information for the volume, since the initial catalog command may have been for a subdirectory. This explains (only partially) what appeared to be duplicate reads of the same information.
Now, let's try loading an Applesoft file: LOAD ABC,S6
A85F:C5 60 01 02 00 03 M ON LINE CALL + Not used for EC0C:01 60 00 DC 02 00 READ BLOCK 2 + LOAD /TEST/ABC A825:C4 BC BC E3 FC 01 M GET FILE INFO EC0C:01 60 00 DC 02 00 READ BLOCK 2 AC00:CC 00 00 C3 CF D0 M CLOSE ALL FILES B1B9:C8 BC BC 00 8A 01 M OPEN FILE EC0C:01 60 00 DC 02 00 READ BLOCK 2 EE85:01 60 00 8A 07 00 READ BLOCK 7 AC22:D1 01 01 02 00 03 M GET FILE EOF AC4B:CA 01 01 08 09 00 M READ FILE AC50:CC 01 00 C3 CF D0 M CLOSE FILE
The loaded program is less than 512 bytes in length, so the key block read is the only data I/O operation. As with the catalog operation, the Get File Info call is used to verify the file type. Close All Files is used in case the previous program left any open. Note the Get File EOF call which is used to get the length for the Read File call (which performs the entire load operation). This example is relatively simple. Let's check what happens when we create an Applesoft file that is just over 512 bytes in length (changing our seedling file into a sapling file, which requires an index block and two data blocks).
We'll lengthen the program, and then type: SAVE /TEST/ABC.3
A825:C4 BC BC C3 0F 18 M GET FILE INFO EC0C:01 60 00 DC 02 00 READ BLOCK 2 ACDC:C0 BC BC C3 FC 01 M CREATE FILE EC0C:01 60 00 DC 02 00 READ BLOCK 2 F477:00 60 00 DC 00 00 STATUS S6,D1 EC0C:01 60 00 DA 06 00 READ BIT MAP EC0C:02 60 00 DC 07 00 WRITE BLOCK 7 EC0C:01 60 00 DC 02 00 READ BLOCK 2 EC0C:02 60 00 DC 02 00 WRITE BLOCK 2 EC0C:02 60 00 DA 06 00 WRITE BIT MAP B1B9:C8 BC BC 00 8A 01 M OPEN FILE CALL EC0C:01 60 00 DC 02 00 READ BLOCK 2 EE85:01 60 00 8A 07 00 READ BLOCK 7 AD0A:CB 01 01 08 5B 02 M WRITE FILE CALL F477:00 60 01 08 00 00 STATUS S6,D1 EE85:02 60 00 8A 07 00 WRITE BLOCK 7 EC0C:01 60 00 DA 06 00 READ BIT MAP EC0C:02 60 00 DA 06 00 WRITE BIT MAP EE85:02 60 00 8C 08 00 WRITE BLOCK 8 EC0C:01 60 00 DA 06 00 READ BIT MAP AD11:D0 01 5B 02 00 03 M SET FILE E0F CALL AD16:CC 01 00 C3 CF D0 M CLOSE FILE CALL EE85:02 60 00 8A 09 00 WRITE BLOCK 9 EC0C:02 60 00 DA 06 00 WRITE BIT MAP EE85:02 60 00 8C 08 00 WRITE BLOCK 8 EC0C:01 60 00 DC 02 00 READ BLOCK 2 EC0C:01 60 00 DC 02 00 READ BLOCK 2 EC0C:02 60 00 DC 02 00 WRITE BLOCK 2
This sequence has the same number of MLI calls for a seedling or a sapling file. The big difference is allocating the index block (block number 8) and additional data blocks. This also generates additional calls to read and write the bit map.
If the file already exists, and the SAVE command does not change the length, then the Create File call is not executed, there are no accesses to the bit map (block 6), and the index block does not change. If the file length changes sufficiently to add or delete blocks, then the bit map is updated and the index block is rewritten (this is forced by the Set File EOF call which adjusts the file length).
Interesting note: whenever a file is opened, the first data block is always read in, even if the file will subsequently be written to. Likewise, when a new file is allocated, the first data block is allocated and written, even if no data is placed in the block.
In the above sequence, what appears to be a duplicate read of block 2 (return address $EC0C) is actually a read to separate blocks if the SAVE command was to a subdirectory. It turns out to be duplicate reads to the subdirectory block, write to the subdirectory, then read and write the root directory. Sigh.
LOAD /TEST/ABC.3 is similar to the previous load operation, except that we must also read the index block before reading the data blocks, and there are two data blocks rather than one.
Finally, let's try deleting this file: DELETE /TEST/ABC.3
A825:C4 BC BC E3 04 00 M GET FILE INFO CALL EC0C:01 60 00 DC 02 00 READ BLOCK 2 9AD7:C1 BC BC 02 BC BC M DESTROY FILE CALL EC0C:01 60 00 DC 02 00 READ BLOCK 2 F477:00 60 00 DC 00 00 STATUS S6,D1 EC0C:01 60 00 DC 08 00 READ BLOCK 8 (index block) EC0C:01 60 00 DA 06 00 READ BIT MAP EC0C:02 60 00 DC 08 00 WRITE INDEX BLOCK (zeroed) EC0C:01 60 00 DC 07 00 READ BLOCK 7 EC0C:02 60 00 DA 06 00 WRITE BIT MAP EC0C:01 60 00 DC 02 00 READ BLOCK 2 EC0C:02 60 00 DC 02 00 WRITE BLOCK 2
Again, use Get File Info for file type and status call to see if the disk can be written to. The bit map is read and written to reflect the freed blocks. Block 8, the former file index block, is trashed. I don't know why block 7 is read in. Trashing the index block makes it very hard to reconstruct a DELETEd file.
At this point, we get a feel for what is happening between the MLI calls and the device driver calls. Consider how extensive these simple examples become on a hard disk if working down three or four directory levels and at the second, third, or fourth block in each directroy, and the hard disk has five blocks for the bit map (and we need the fifth block because the disk is almost full). Ouch!
I performed one more test case, far too long to list here. It involved adding a record to a new sparse random access file. The new record caused the file to grow to a tree file. The program used was:
10 D$ - CHR$(4) 20 PRINT D$"OPEN /TEST/NAMES,L140" 30 PRINT D$"WRITE/TEST/NAMES,R936" 40 PRINT "XXX ... XXX": REM 120 X's 50 PRINT D$"CLOSE/TEST/NAMES"
This sequence produced eight MLI calls and 29 device driver calls to perform I/O (there were three status calls). The file ended up with six blocks (master index block, two index blocks, and three data blocks) which generated 12 accesses to read and write the bit map.
A 32 megabyte hard disk, the maximum size supported by ProDOS, requires 16 blocks for the free space bit map. Obviously, such a disk would suffer quite a performance impact when allocating new files, or adding space to existing files, if the hard disk were more than half full.
After reading Ken's article, I came to the conclusion that the Kache Card or something like it is a MUST for users of large hard disks.
The Kache Card has 256K RAM and a controlling Z-80 on it. As far as the Apple is concerned, it is just a hard disk controller. It replaces the controller card which came with your Sider. But it is smarter.
The Kache card remembers the most recently read or most frequently read data blocks. Over 2000 of them. You can see that the entire bit map and at least all the directory blocks associated with the currently used pathnames would stay in RAM on the card. When ProDOS issues a READ command, the DMA interface on the Kache Card simply transfers the block, without doing anything to the hard disk.
When you write to the hard disk, the Kache Card sends it to the hard disk as well as updating its RAM-based copy. You can write to the Kache Card faster than the Kache Card writes to the disk, and your program keeps chugging along while the Kache Card spins out the data to the drive.
The Kache Card is expensive ($695), at least relative to the price of a Sider. A 10-meg Sider is currently $595, and a 20-meg Sider is currently $895. Nevertheless, if you are using 20 megabytes or more you really need a caching system of some kind.
Of course, you could implement caching inside the operating system. ProDOS could be modified (perish the thought) to use about 16K RAM from the //e's auxiliary memory to cache the bit map, root directory, and other frequently used blocks, for each on-line hard disk. (It does not seem profitable to try to cache blocks from floppies, because you can too easily mess things up by removing one floppy and inserting another.)
Like I said, you COULD do it this way. However, it would be very difficult to make it work with the variety of peripherals available to Apple owners. It seems much more reasonable to include caching on the controller card, or even inside the hard disk box itself. I think 256K is probably overkill, 64K per hard drive should be plenty.
My first brush with the Kache Card was not pleasant. I ended up returning the card with a list of complaints. They called me about a month later with the news that they had taken my compaints seriously, and rectified the problems I had pointed out. Or at least most of them.
If you are interested in the Kache Card, contact Ohio Kache Systems Corporation, 75 Tahlequah Trail, Springboro, Ohio 45066. Or call them at (513)746-9160. Tell them where you read this.
It takes a little longer for the mail to carry our messages overseas, so these solutions missed the November issue.
Bruce Love (from Hamilton, New Zealand) uses the power of the 65802 in a different manner than David Johnson did last month. Remember that David used the MVP instruction to fill all RAM with the STP opcode. Bruce uses a combination of a loop and the PHA instruction to fill all of RAM with $4C, which is a JMP opcode.
If you disassemble a series of $4C's, you will see JMP $4C4C. Therefore Bruce positioned his code so that the last byte to be filled is at $4C4C.
The loop in lines 1160-1200 fills all RAM below $4C4C with the $4C value. After finishing, it jumps back to $4C4C where a two-line loop pushes the A-register on the stack. The trick here is that the stack pointer in the 65802 is 16-bits long. Bruce starts it at $BFFF, and each PHA lowers it by one location. The last location to be changed is $4C4C itself, and after that it loops endlessly executing JMP $4C4C at $4C4C.
Bruce points out that you can test the effectiveness of his program (if you have a 65802 in your Apple) by changing lines 1130 and 1160 to LDX ##$4FFF and LDX ##$4000 respectively. Then it will fill the range from $4000 through $4FFF with $4C, and you can examine it to be sure it did.
Charles Putney (from Shankill, Dublin, Ireland) fills RAM with $48, using the normal 6502 instruction set. Charlie used a combination similar to Bob S-C's solution last month. The final loop resides inside the stack page, and the infinite series of PHA's fills the stack. The difference is that Charlie has the user type an "L" key, which loads the keyboard register with $CC. Then he clears the strobe, which changes it to $4C. Since the locations $C000 through $C002 will all read back as $4C, the cpu will execute JMP $4C4C.
1000 *SAVE S.RAMFILL BRUCE LOVE 1010 *-------------------------------- 1020 .OP 65802 1030 .OR $4C49 1040 *-------------------------------- 1050 PAINT JMP .2 1060 *-------------------------------- 1070 .1 PHA PUSH FROM $BFFF DOWN 1080 JMP .1 (NOTE = 4C4C4C) 1090 *-------------------------------- 1100 .2 CLC TURN ON 65802 MODE 1110 XCE 1120 REP #$10 X=16 BIT, A=8 BIT 1130 LDX ##$BFFF POINT STACK TO TOP OF RAM 1140 TXS 1150 LDA #$4C FILL VALUE 1160 LDX ##0 POINT TO BOTTOM OF RAM 1170 .3 STA >0,X FILL FROM $0000 TO $4C4B 1180 INX 1190 CPX ##$4C4C 1200 BCC .3 1210 BCS .1 BACK TO FILL FROM TOP DOWN 1220 *-------------------------------- |
1000 *SAVE S.RAMFILL PUTNEY 1010 *-------------------------------- 1020 * BY CHARLES H. PUTNEY 1030 * 18 QUINNS ROAD 1040 * SHANKILL 1050 * CO. DUBLIN 1060 * IRELAND 1070 *-------------------------------- 1080 .OR $803 NORMAL PLACE 1090 *-------------------------------- 1100 PNTR .EQ $06 BLOCK MOVE POINTER 1110 *-------------------------------- 1120 KEYBD .EQ $C000 KEYBOARD DATA 1130 KEYSTB .EQ $C010 KEYBOARD STROBE 1140 VIDOUT .EQ $FDF0 VIDEO OUTPUT ROUTINE 1150 CROUT .EQ $FD8E SEND A RETURN 1160 *-------------------------------- 1170 WIPE JSR CROUT START A NEW LINE 1180 LDX #$00 1190 .1 LDA MESS,X TELL HIM WHAT KEY TO PUSH 1200 JSR VIDOUT SEND IT 1210 INX NEXT CHAR 1220 TAY CHECK IF LAST ONE 1230 BMI .1 NO 1240 JSR CROUT SEND A RETURN 1250 LDX #$00 1260 .2 LDA IMAGE,X RELOCATE CODE TO PAGE ONE 1270 STA $200-CODEND+CODE,X 1280 INX 1290 CPX #CODEND-CODE 1300 BNE .2 1310 .3 BIT KEYBD KEY PRESSED ? 1320 BPL .3 WAIT UNTIL PUSHED ? 1330 LDA KEYSTB RESET STROBE 1340 LDA KEYBD MAKE SURE ITS THE RIGHT KEY 1350 CMP #$4C IS IT L ? (JMP OPCODE) 1360 BNE WIPE TELL HIM AGAIN 1370 JMP CODE WIPE OUT ! 1380 *-------------------------------- 1390 * THIS CODE IS RELOCATED TO PAGE ONE 1400 *-------------------------------- 1410 IMAGE .PH $1E1 1420 CODE LDA #$00 INITIALIZE POINTER 1430 STA PNTR 1440 LDA #$02 1450 STA PNTR+1 START AT PAGE TWO 1460 LDA #$48 GET A PHA OPCODE 1470 LDY #$00 INIT Y REG 1480 .1 STA (PNTR),Y SAVE PHA OPCODE 1490 INY NEXT 1500 BNE .1 FULL PAGE DONE ? 1510 INC PNTR+1 NEXT PAGE 1520 LDX PNTR+1 CHECK IF DONE 1530 CPX #$C0 AT I/O AREA ? 1540 BNE .1 NOT YET 1550 .2 STA $00,Y SET PAGE ZERO TO $48 1560 INY NEXT 1570 BNE .2 FULL PAGE WIPED ? 1580 * FALL INTO PAGE 2 PHA'S 1590 CODEND .EP 1600 *-------------------------------- 1610 MESS .AT -/TYPE UPPER CASE L TO SET MEMORY TO $48 / 1620 *-------------------------------- |
We have a new Quick Reference Booklet for the S-C Macro Assembler! With all the new features of the Version 2.0 Macro Assemblers, including the 65C02 and 65816 in both DOS and ProDOS, we have outgrown the old Programmer Reference Card. Taking its place is our new Programmer Reference, a 14-page booklet containing even more information on the S-C Macro Assembler, even more information on the 6502/65C02/65802/65816 processors, and even more information on the Apple II, II+, //e, and //c computers.
All this new reference information is organized into an easy-to-read 14-page booklet, with the S-C Macro Assembler commands at the beginning and the 65XXX opcode tables in the center spread, so it will be as easy as possible to flip right to those important items.
These are the major subject headings covered in the new Programmer Reference:
As you see, we've packed just about all of the important assembler, processor and computer information you need into this convenient 5 1/2 x 8 1/2 inch package.
The new S-C Macro Assembler Programmer Reference is only $3.00 (plus $1.00 postage for foreign orders).
A couple of years ago I got a bright idea. I was working on an Applesoft program that required knowing what files were on the disk in a given disk drive. By creating binary "images" of Applesoft variables, I was able to hook into DOS 3.3 and employ Applesoft routines to convert the information DOS 3.3 prints to the screen into regular Applesoft variables.
The whole thing worked beautifully and was printed in the last of only four "Second Grade Chats" ever published in Softalk Magazine -- in the very last issue. ("Sorree -- your number has been dis-co-nected.") I never did get paid. ($!#%~&*)
Oh, well. We Apple owners mainly do it for the love of the little machine anyway. Right? Since that time I have realized that the most important thing which I did in that article was to discover the technique of creating pseudo-variables for use in an applications program which can make available all the subroutines already written in the Applesoft ROMs.
It doesn't require a long explanation. Just one example should be enough, and it so happens that one is printed below. This short program, when called from an Applesoft program, will "poll" an Applied Engineering TIMEMASTER H.O. card from 80-column mode without affecting the screen and move the ASCII string which the time card places in the input buffer into the Applesoft variable TIME$. It not only makes getting the time while in 80-column mode possible without blowing away the screen, but it also is a great deal faster than trying to use an Applesoft interface.
This routine should also work with ThunderClock and other compatible clock cards. Permission is granted to reprint this article and to use the copyrighted program below for non-commercial applications. Have a good TIME$!
1000 *SAVE S.READ.TIME 1010 *-------------------------------- 1020 * READ TIMEMASTER H.O. CARD, PUTTING 1030 * TIME INTO APPLESOFT STRING TI$. 1040 *-------------------------------- 1050 * ORIGINAL BY JOHN OAKEY, 11-22-85 1060 * (c) 1985 1070 * 1080 * MODIFIED BY BOB SANDER-CEDERLOF 1090 *-------------------------------- 1100 FORPNT .EQ $85,86 1110 TXTPTR .EQ $B8,B9 1120 *-------------------------------- 1130 WBUF .EQ $200 1140 *-------------------------------- 1150 SLOT .EQ 5 <<<BE SURE TO PUT YOUR SLOT HERE>>> 1160 *-------------------------------- 1170 AS.GDBUFS .EQ $D539 MARK END, CLEAR HI-BITS 1180 AS.SAVD .EQ $DA9A FINISH INSTALLING STRING 1190 AS.PTRGET .EQ $DFE3 PARSE STRING NAME 1200 AS.STRLIT .EQ $E3E7 BUILD STRING DESCRIPTOR 1210 *-------------------------------- 1220 .OR $300 (WHERE ELSE!) 1230 *-------------------------------- 1240 RDTIME LDA TXTPTR SAVE CURRENT TEXT PNTR 1250 PHA 1260 LDA TXTPTR+1 1270 PHA 1280 *---READ TIME INTO BUFFER-------- 1290 LDA #"%" MODE: "FRI JAN 03 10:11:32 AM" 1300 JSR SLOT*256+$C00B 1310 JSR SLOT*256+$C008 READ TIME STRING 1320 *---PREPARE STRING FOR A/S------- 1330 LDX #23 LENGTH OF STRING 1340 JSR AS.GDBUFS CLEAR HI-BITS AND MARK END 1350 *---SETUP TI$ VARIABLE----------- 1360 LDA #VARNAM 1370 STA TXTPTR 1380 LDA /VARNAM 1390 STA TXTPTR+1 1400 JSR AS.PTRGET 1410 STA FORPNT 1420 STY FORPNT+1 1430 *---MOVE TIME INTO TI$----------- 1440 LDA #WBUF+1 SKIP OVER LEADING QUOTE 1450 LDY /WBUF+1 1460 JSR AS.STRLIT 1470 JSR AS.SAVD 1480 *---RESTORE TXTPTR, RETURN------- 1490 PLA 1500 STA TXTPTR+1 1510 PLA 1520 STA TXTPTR 1530 RTS 1540 *-------------------------------- 1550 VARNAM .AS /TI$/ |
And, as usual, Bob couldn't resist squeezing out a few bytes:
1000 *SAVE S.READ.TIME+ 1010 *-------------------------------- 1020 * READ TIMEMASTER H.O. CARD, PUTTING 1030 * TIME INTO APPLESOFT STRING TI$. 1040 *-------------------------------- 1050 * BY BOB SANDER-CEDERLOF 1060 *-------------------------------- 1070 VARNAM .EQ $81,82 1080 FORPNT .EQ $85,86 1090 *-------------------------------- 1100 WBUF .EQ $200 1110 *-------------------------------- 1120 SLOT .EQ 5 <<<BE SURE TO PUT YOUR SLOT HERE>>> 1130 *-------------------------------- 1140 AS.GDBUFS .EQ $D539 MARK END, CLEAR HI-BITS 1150 AS.SAVD .EQ $DA9A FINISH INSTALLING STRING 1160 AS.PTRGET9 .EQ $E04F FIND OR MAKE VARIABLE 1170 AS.STRLIT .EQ $E3E7 BUILD STRING DESCRIPTOR 1180 *-------------------------------- 1190 .OR $300 (WHERE ELSE!) 1200 *-------------------------------- 1210 RDTIME 1220 LDA #"%" MODE: "FRI JAN 03 10:11:32 AM" 1230 JSR SLOT*256+$C00B 1240 JSR SLOT*256+$C008 READ TIME STRING 1250 *---PREPARE STRING FOR A/S------- 1260 LDX #23 LENGTH OF STRING 1270 JSR AS.GDBUFS CLEAR HI-BITS AND MARK END 1280 *---SETUP TI$ VARIABLE----------- 1290 LDA #'T' HI-BIT OFF FOR STRING VARIABLE 1300 STA VARNAM 1310 LDA #"I" HI-BIT ON FOR STRING VARIABLE 1320 STA VARNAM+1 1330 JSR AS.PTRGET9 1340 STA FORPNT 1350 STY FORPNT+1 1360 *---MOVE TIME INTO TI$----------- 1370 LDA #WBUF+1 SKIP OVER LEADING QUOTE 1380 LDY /WBUF+1 1390 JSR AS.STRLIT 1400 JMP AS.SAVD CLEAN UP STRINGS AND RETURN 1410 *-------------------------------- |
Within reasonable limits, it should be possible for a clock/ calendar card to automatically set the day-of-week number when given the year, month, and day. The algorithm for deriving day-of-week from the date is simple enough. However, as the algorithm is stated in all my reference material, it involves multiplication and division by numbers that are not simple powers of two.
I have simplified the algorithm so that it will work over the range from March 1, 1984 through December 31, 2083. That should be an adequate range for any Apple products!
Years evenly divisible by 4 are leap years, having 366 days. The years ending in 00 are exceptions, unless they are divisible by 400. Thus 1900 was not a leap year, 2100 will not be a leap year, but 2000 is a leap year.
My algorithm started out as a method for converting a Y-M-D date to a Julian date, which is a unique number that was 0 several thousand years ago. I could get the remainder after dividing the Julian date by 7, and use it for a day-of-week index. However, the numbers get rather large; they won't fit in one byte.
By converting all the intermediate values to their modulo 7 equivalents, I can keep the result down to byte-size. Here is an Applesoft program which implements my algorithm:
100 DIM MD(11),D$(6) 110 DATA 3,6,1,4,6,2,5,0,3,5,1,4 120 DATA SUN,MON,TUES,WEDNES,THURS,FRI,SATUR 130 FOR I=0 TO 11 : READ MD(I) : NEXT 140 FOR I=0 TO 6 : READ D$(I) : NEXT 200 INPUT Y,M,D 210 M = M-3 220 IF M<0 THEN M=M+12 : Y=Y-1 230 Y=Y-1984 240 W = Y + INT(Y/4) + MD(M) + D 250 IF W>6 THEN W=W-7 : GO TO 250 260 PRINT D$(W)"DAY" 270 GO TO 200
Lines 100-140 build two arrays. The MD array holds a modulo 7 number for the number of days preceding each month in a normal year (not leap year). The D$ array holds the names of the days, shortened by the last three letters.
Line 200 waits for you to type in the year, month, and day as three numbers. I did not add any error testing, but I expect the year to be from 1984 up. The month should be a number from 1 to 12, and the day from 1 to 31.
Lines 210-220 adjust the month number. I move January and February to the end of the previous year, like it must have been in the olden days. That makes leap day the last day of the year, where it belongs. It also makes the month names for Sept-, Oct-, Nov-, and Dec-ember make linguistic sense! March becomes the first month, December the tenth, and so on. Internally, the value of the variable M will be a number from 0 to 11.
Line 230 adjusts the year to start at 1984. Line 240 adds up the various day-values. We add Y, the number of the years since 1984, because 365 = 1 mod 7. We add INT(Y/4) to get the leap days. MD(M) adds in the bias for the number of days beyond an integral number of weeks to the end of the previous month. D adds in the day number. Altogether we have a number which is still less than 256, and fits in one byte in a machine language version of the algorithm.
Line 250 subtracts 7 (whole weeks) until we get to a number less than 7. The result is the day number in a week with 0 meaning Sunday, 1 meaning Monday, and so on. Line 260 prints the day name, and line 270 lets us try another date.
After making sure of my method with the Applesoft program, I coded it in assembly language. The program which follows is set up to be used from inside Applesoft, and I also list here the Applesoft driver. I did it this way to make it easy to test my assembly language code. Later I will probably put the code inside a larger package which sets the time and day on my clock card. Once it is in there, I can forever forget about the need to tell the card what day of week it is.
1000 *SAVE S.DAY OF WEEK 1001 .OR $300 1010 *-------------------------------- 1020 YEAR .BS 1 84-99 MEANS 1984-1999; 0-83 MEANS 2000-2083 1030 MONTH .BS 1 1...12 FOR JAN...DEC 1040 DAY .BS 1 1...31 1050 W .BS 1 1060 *-------------------------------- 1070 DOW 1080 LDA YEAR NORMALIZE YEAR TO 1984 1090 SEC SO IT RUNS 1...99 1100 SBC #84 (MAR 1, 1984 THROUGH DEC 31, 2083) 1110 BCS .1 WAS 1984-1999 1120 ADC #100 WAS 2000-2083 1130 .1 STA W 1140 *-------------------------------- 1150 LDA MONTH ADJUST MONTH SO FEBRUARY IS END OF YEAR 1160 SEC 1170 SBC #3 1180 BCS .2 1190 DEC W 1200 ADC #12 1210 .2 TAX 1220 *-------------------------------- 1230 LDA W YEAR 1240 LSR 1250 LSR 1260 CLC + INT (YEAR/4) 1270 ADC W 1280 ADC MD,X + MD(ADJ.MONTH) 1290 ADC DAY + DAY 1300 *-------------------------------- 1310 SEC 1320 .3 SBC #7 MOD 7 1330 BCS .3 1340 ADC #7 1350 STA W 1360 RTS 1370 *-------------------------------- 1380 MD .DA #3,#6,#1,#4,#6,#2,#5,#0,#3,#5,#1,#4 1390 *-------------------------------- |
Lines 1020-1050 are the variables used to communicate with the Applesoft test program, by way of PEEKs and POKEs. The program assumes that only the last two digits of the year are used, so that YEAR is a number from 84 to 99 for 1984 to 1999; values from 0 to 83 signify years from 2000 to 2083.
Lines 1080-1130 change the year number, which runs 84...99 and 00...83 to a value based at 1984, running from 00 to 99. 00 means 1984, 99 means 2083.
Lines 1150-1210 are equivalent to the Applesoft lines 210 and 220 in the first program above. Lines 1220-1290 are equivalent to the Applesoft line 240. Lines 1300-1340 reduce the result to a modulo 7 remainder. The final value, a number from 0 to 6, is stored in line 1350 where an Applesoft driver can find it by PEEK(771).
Here is my Applesoft test program. This time I went in for a little range checking on the input values for year, month, and day.
100 DIM D$(6) 110 DATA SUN,MON,TUES,WEDNES,THURS,FRI,SATUR 120 FOR I=0 TO 6 : READ D$(I) : NEXT 200 INPUT "YEAR (1984-2083): ";Y 210 IF Y<1984 OR Y>2083 THEN 200 220 Y = Y - INT(Y/100)*100 230 POKE 768,Y 300 INPUT " MONTH (1-12): ";M 310 IF M<1 OR M>12 THEN 300 320 POKE 769,M 400 INPUT " DAY (1-31): ";D 410 IF D<1 OR D>31 THEN 400 420 POKE 770,D 500 CALL 772 510 W = PEEK(771) 600 PRINT D$(W)"DAY" 610 GO TO 200
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.)