Apple Assembly Line
Volume 6 -- Issue 3 December 1985

In This Issue...


Little RAM Disk Bug Bill Morgan

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.


ProDOS MLI Tracing Ken Kashmarek
Eldridge, Iowa

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.


Ohio Systems Kache Card Bob Sander-Cederlof

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.


More Puzzle Solutions Bruce Love & Charles Putney

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 *--------------------------------

S-C Macro Assembler Quick Reference Booklet Bob Sander-Cederlof

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


Using Pseudo-Variables in Machine Language John Oakey
227 Creekstone Bend, Peachtree City, GA

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 *--------------------------------

Computing the Day of Week Bob Sander-Cederlof

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