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.