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.