Classic Computer Magazine Archive COMPUTE! ISSUE 49 / JUNE 1984 / PAGE 106

MACHINE LANGUAGE

Jim Butterfield, Associate Editor

A Program Critique — Part 3

This month we continue with comments on Bud Rasmussen's program to copy files on the Commodore 64 with a single disk unit. The program has so far read into RAM memory a file specified by the user.

In this session, we'll track the routine that writes the file to a new disk.

                ;
                ;
                ; START OUTPUT PHASE
                ;
                ;
C2F7 20 E4 FF  SOP     JSR  GETIN  ;GET CHARACTER
C2FA F0 FB             BEQ  SOP    ;IF NONE,TRY
                                    AGAIN
C2FC C9 0D              CMP  #RK   ;IS THIS
C2FE F0 01              BEQ  POPM  ;RETURN KEY
C300 00                 BRK        ;IF NOT, BRK
		;

Wait for the RETURN key. If any other key is received, the program will break to the machine language monitor (if there is one). This has a possible problem: Keyboard bounce could cause a halt here. I'd prefer something like this:

                    JSR GETIN  ;clear input
              LOOP  JSR GETIN  ;get character
                    CMP #RK    ;if not RETURN…
                    BNE LOOP   ;go back and wait

As mentioned before, a BRK (Break) is to be avoided since users won't understand what it means.

Output Phase Begun

Next, we arrange to print an advice message:

C301 A2 23	  POPM  LDX #OPBML	;PRINT
C303 A0 C3		LDY #>OPBM	;OUTPUT
C305 A9 18		LDA #<OPBM	;PHASE BEGUN'
C307 20 75 C1	JSR PR		;MSG
                ;
C30A A9 00		LDA #0		;CLEAR
C30C 8D 62 03	STA OSF		;OUTPUT STATUS
				      FLAG,
C30F 8D 63 03	STA OEC		;OUTPUT ERROR
                                      CODE
                ;

Again, clearing these flags may be overkill. They will take care of themselves.

C312 20 3F C4     JSR ID  ;INIT DISK
C315 4C 3B C3     JMP SNO ;GOTO SET NAME
                              OUTPUT
                ;

The new disk is initialized. A wise precaution, in case the new disk happens to have the same ID as the old one.

		;
		; OUTPUT PHASE BEGUN MESSAGE
		;
		;
C318 0D 0D 12 OPBM .BYTE$0D, $0D, $12
C31B 2A 2A 2A      .ASC "*** OUTPUT PHASE
                    BEGUN ***"
C339 0D 0D         .BYTE$0D,$0D
C33B         OPBML  =  *-OPBM
                ;

Now we will go through the same routine which was used for input. The main difference is that this time, the name of the file is four characters longer, since ",S,W" is added to make this a write file.

		;
                ; SET NAME (OUTPUT)
                ;
C33B AD AB 02  SNO    LDA  OFNL     ;OUTP FILE NM LEN
C33E A2 40            LDX  #<FNA  ;LOAD FILE NAME LO
C340 A0 03            LDY  #>FNA  ;LOAD FILE NAME HI
C342 20 BD FF         JSR  SETNAM
                ;
                ; SET LOGICAL FILE (OUTPUT)
                ;
                ;
C345 A9 03     SLFO   LDA #3       ;LOGICAL FILE
                                         NUMBER
C347 A2 08            LDX #8       ;LOAD DEVICE
                                         ADDRESS
C349 A0 03            LDY #3        ;LOAD SEC.
C34B 20 BA FF            JSR SETLFS
                ;
                ;
                ; OPEN FILE (OUTPUT)
                ;
                ;
C34E 20 C0 FF  OFO     JSR OPEN     ;OPEN FILE
C351 A5 90             LDA IOS      ;TEST
C353 F0 0B             BEQ OCO      ;STATUS
C358 8D 62 03          STA OSF      ;STORE STATUS
C35A A9 01             LDA #1       ;SET/STORE
C35A 8D 63 03          STA OEC      ;ERROR CODE
C35D 4C C5 C3          JMP OE       ;OUTPUT ERROR

Check The Disk Status

As previously noted, checking location $90, IOS—the BASIC ST variable—isn't enough to insure that the file is properly opened. You must call in the disk status over the command channel. There could be many problems in opening a file for writing: A file of that name may already exist, the disk may have the write-protect tab in place, the disk may be unformatted, or the disk might be full, to name just a few. Location $90 won't tell you about such things.

		;
		; OPEN CHANNEL (OUTPUT)
		;
		;
C360 A2 03     OCO LDX #3	 ;OPEN
C362 20 C9 FF      JSR CHKOUT ;CHANNEL 3
C365 A5 90         LDA IOS    ;TEST
C367 F0 OB         BEQ SOB    ;STATUS
C369 8D 62 03      STA OSF    ;STORESTATUS
C36C A9 02         LDA #2     ;SET/STORE
C36E 8D 63 03      STA OEC    ;ERROR CODE
C371 4C C5 C3      IMP OE     ;OUTPUT ERROR

As during the reading phase, I'd rather the comments said, "connect channel" rather than "open channel." The word "open" has special significance for a file; we have already performed the open activity with our call to OPEN ($FFC0).

             ;
             ;SET OUTPUT BUFFER
             ;
             ;
C374 A0 00  SOB  LDY #0     ;BUFFER INDEX = 0
C376 A9 00       LDA #0     ;LOAD BFR
C378 85 FB       STA BAL    ;ADDRLO
C37A AD 3D C4    LDA SP     ;LOAD BFR
C37D 85 FC       STA BAH    ;ADDRHI

It May Miss The Address

You may recall that the input section of the program might under some circumstances change the memory start address, moving it down by 4K. If so, this part of the program would miss the changed address completely. Oops.

               ;
               ;OUTPUT LOOP
               ;
               ;
C37F B1 FB    OL   LDA (BAL),Y  ;GETCHAR
C381 20 D2 FF      JSR  CHROUT  ;PUT CHAR

Output has been switched to logical channel 3; instead of printing to the screen, JSR $FFD2 sends to the file.

C384 A5 90     LDA IOS    ;TEST
C386 F0 0B     BEQ IBA    ;STATUS
C388 8D 62 03  STA OSF    ;STORE STATUS
C38B A9 03     LDA #3     ;SET/STORE
C38D 8D 63 03  STA OEC    ;ERROR CODE
C390 4C C5 C3  JMP OE     ;OUTPUT ERROR
              ;
              ;
              ; INCR BUFFER ADDR
              ;
C393         IBA =   *
C393 E6 FB       INC BAL  ;INCR BFR ADDR LO
C395 D0 02       BNE CEA  ;IF NOT 0, CHK END
                           AD
C397 E6 FC       INC BAH  ;INCR BFR ADDR HI
             ;
             ; COMPARE END ADDRESS
             ;
C399 A5 FC  CEA  LDA BAH  ;LOAD BFR ADDR HI
C39B C5 FE       CMP EAH  ;BAH VS END ADDR
                           HI
C39D 90 E0       BCC OL   ;IF LO, CARRY ON
C39F A5 FB       LDA BAL  ;LOAD BFR ADDR LO
C3A1 C5 FD       CMP EAL  ;BAL VS END ADDR
                           LO
C3A3 90 DA       BCC OL   ;IF LO, CARRY ON
              ;

After a comparison, BCC may be taken to mean "Branch if less." Thus, we'll branch back to OL, the output loop, if the high byte of the write address is less than that of the end address, or failing that, if the low byte is less. In this case, BNE (Branch not Equal) would do the job equally well.

Disconnecting The Channel

Next, the program closes the file since all bytes have been written. But there's an omission: Before closing the file, we should disconnect the output channel from it with JSR $FFCC. I wonder if this was overlooked because of the confusing use of the term open, earlier?

At this point, before closing the file, I would recommend looking at the command channel for any possible disk error message that might have been created during the write. The disk could become full as we write the program, for example.

                 ;
                 ; END OF DISK I/O
                 ;
                 ;
C3A5 A9 03            LDA #3     ;SET CH3
C3A7 20 C3  FF        JSR CLOSE  ;FOR CLOSE
                 ;
C3AA A9 0F            LDA #15    ;SET CH 15
C3AC 20 C3  FF        JSR CLOSE  ;FOR CLOSE

Good sequence. Always close the command channel last of all, since closing the command channel automatically causes all outstanding disk files to be closed.

C3AF 20 E7 FF     JSR  CLALL  ;CLOSE ALL FILES
               ;

Not needed, if the output is properly disconnected with JSR $FFCC before closing logical file 3.

C3B2 A2 71      LDX #FCMl    ;PRINT
C3B4 A0 C3      LDY #>FCM ;FILE
C3B6 A9 CC      LDA #<FCM ;COPIED
C3B8 20 75 C1   JSR  PR      ;MSG
             ;

As the program usually does, a message is neatly printed, telling the user what's going on.

C3BB 20 E4 FF FG  JSR GETIN ;GET CHARACTER
C3BE F0 FB        BEQ FG    ;IF NONE, TRY
			     AGAIN
C3C0 C9 0D        CMP #RK   ;IS THIS
C3C4 00           BRK       ;IF NOT,BRK
                  ;

Use RTS Instead Of BRK

See the previous comment on waiting for a key to be pressed. When the program is finished, it should terminate with a BRK (Break) command only if it was invoked from the machine language monitor with a .G (Go) command. Otherwise, an RTS (ReTurn from Subroutine) will return control to BASIC.

                  ;
                  ; OUTPUT ERROR
                  ;
                  ;
C3C5 20 E7 FF    OE   JSR  CLALL  ;CLOSE ALL FILESC3C8 00		      BRK
		  ;

Once again: Errors could be worked through in more detail. A BRK to the machine language monitor is not always explanatory.

               ;  TRY AGAIN
               ;
               ;
C3C9 4C 00 C0 TA    JMP CS
               ;

To do another file, we go back to the beginning of the program.

               ;
               ; FILE COPIED MESSAGE
               ;
               ;
C3CC 12       FCM  .BYTE$12
C3CD 20 20 46      .ASC "FILE SUCCESSFULLY
                    COPIED."
C3F2 0D 0D 12      .BYTE$0D, $0D, $12
C3F5 20 20 50      .ASC "PRESS RETURN TO COPY
                    ANOTHER."
C419 0D 0D 12      .BYTE$0D, $0D, $12
C41C 20 20 50      .ASC "PRESS ANY OTHER KEY TO
                    STOP."
C43B 0D 0D         .BYTE$0D, $0D
C43D         FCML   = *-FCM
             ;

RAM Limits Are Set

Here are the limits of RAM for the program: They are arbitrarily set to allow space from $4000 to $7F00. I'm not sure why, but it's all right with me.

            ;
C43D 40    SP     .BYTE$40    ;START GOREM
C43E 7F    EP     .BYTE$7F    ;END   GOREM
            ;

The following sequence is intended to initialize the disk. It does it in an unsatisfactory way: It opens the command channel again. (We have already opened the command channel as logical file 15.) The following code sends the BASIC equivalent of OPEN 1,8,15"I":CLOSE 1. In a moment, I'll give a preferred approach.

              ;
              ; INIT DISK
              ;
              ;
C43F A9 01   ID    LDA #INL
C441 A0 C4         LDY #>IN
C443 A2 5D         LDX #<IN
C445 20 BD FF      JSR SETNAM
C448 A9 01         LDA #1
C44A A2 08         LDX #8
C44C A0 0F         LDY #15
C44E 20 BA FF      JSR SETLFS
C451 20 C0 FF      JSR OPEN
C454 20 CC FF      JSR CLRCHN
C457 A9 01         LDA #1
C459 20 C3 FF      JSR CLOSE
C45C 60            RTS
               ;
C45D 49       IN   .ASC "I"
C45E          INL  =          *-IN
               ;

What we should be doing is the BASIC equivalent of PRINT#15,"I", which is much easier:

ID	 LDX	#15		;LF15, command
			     channel
      JSR	$FFC9		;..connect to it
      LDA	#"I";Letter I
      JSR	$FFD2           ;..send it
      JSR	$FFCC           ;disconnect channel
      RTS

Error Checking Needs Work

That's the program. It works reasonably well as given. The major improvements I would suggest are additional checking of the disk status (in the program given, the command channel was opened but never used); improved error message procedures; and a little rethinking of the RAM memory allocated.

The program has outstandingly clean documentation; it's a pleasure to read. In the same vein, the messages to the user are good and quite supportive. The coding approach is good, almost classical, in its methodical use of Kernal subroutines. There's a lot to be learned from what's in the program, as well as from what's missing.

I'd like to thank Bud Rasmussen for allowing me to subject his program to analysis, warts and all. It can be embarrassing to have your mistakes—or your style—exposed to public view. I chose to pick through the program in detail because it was well-planned and well-written. Its faults are minor compared to its virtues.