Consider the following Basic program for the C64.

10 OPEN 15,8,15
20 PRINT#15, "I"
30 CLOSE 15

This classic gem simply opens the 'command channel' and sends the DOS 'init' command to the disk drive. The purpose of the 'init' command is to refresh the disk id and block allocation map (BAM). Not very exciting, but I am just trying to get my feet on terra firma.

I have tried to perform the same operation in assembly. I think I am really close, but there is something wrong with my implementation. Note, this probably isn't the prettiest code or the most robust (I am ignoring errors, etc) - but it should work nonetheless.

I have two subroutines here, the first sends the 'init' over the command channel and the second reads the 'status' from the command channel. Both subroutines use a common 'open_15' and 'close_15' to do the setup and teardown for the command channel. I am reasonably sure the 'status' works - but the 'init' fails with a 31,syntax error,00,00. This leads me to conclude there is something wrong with how I am sending the init command over the command channel.

I tried some permutations on the data I am sending - just the character I, the character I in quotes, the I followed by a CR, the I followed by a null, I even tried sending a correctly formatted rename command string like this "R:NEW=OLD" - but nothing seems to change that I always get back a syntax error. The fact I get the same error for both an init and a rename command also reinforces my suspicion I am not sending the data correctly.

It shouldn't matter, but all my tests have been conducted inside the VICE emulator x64sc.exe 3.4 r37296M (GTK3 3.22.30, GLib 2.58.3) running in Winders 10 Pro.

Can someone please tell me where I have gone off the rails?


READST          = $FFB7         ; read I/O status word
SETLFS          = $FFBA         ; set logical file parameters
SETNAM          = $FFBD         ; set filename parameters
OPEN            = $FFC0         ; open a logical file
CLOSE           = $FFC3         ; close a logical file
CHKIN           = $FFC6         ; define an input channel
CHKOUT          = $FFC9         ; define an output channel
CLRCHN          = $FFCC         ; restore default devices
CHRIN           = $FFCF         ; input a character
CHROUT          = $FFD2         ; output a character

start           jsr init                ; attempt the "init"
                jsr status              ; read the command channel status

init            jsr open_15
                ldx #$0F                ; use file number 15 for output
                jsr CHKOUT
                lda #$49                ; send the letter "I" thru the
                jsr CHROUT              ; command channel
                jsr close_15
status          jsr open_15             ; open file number 15
                ldx #$0F                ; use file number 15 for input
                jsr CHKIN
@read           jsr READST              ; read status byte
                bne @done               ; either EOF or read error
                jsr CHRIN               ; get a byte from file
                jsr CHROUT              ; print byte to screen
                jmp @read
@done           jsr close_15                

open_15         lda #$00                ; set no filename
                jsr SETNAM

                lda #$0F                ; logical file number 15
                tay                     ; command channel 15
                ldx #$08                ; default to device 8
                jsr SETLFS

                jsr OPEN                ; open file number 15

close_15        lda #$0F                ; close file number 15
                jsr CLOSE
                jsr CLRCHN              ; restore default devices                
  • 1
    I think when you check status it might also be printing the status to the drive. CHROUT says NOTE: Care must be taken when using this routine to send data to a specific serial device since data will be sent to all open output channels on the bus. Unless this is desired, all open output channels on the serial bus other than the intended destination channel must be closed by a call to the KERNAL CLRCHN routine." This codebase64.org/… might help you as well. Commented Oct 12, 2023 at 6:48
  • 1
    Status 31 means the command wasn't found in the 1541's table at FE89 in the drive ROM, and 49 is right there at FE8A, so I think you are sending ISYNTAX ERROR. #$49#$0D should work if you read and buffer the status rather than immediately printing it. Commented Oct 12, 2023 at 6:56
  • It looks as if 31 is just "bad command" - it gets returned for other reasons than not in the table. Commented Oct 12, 2023 at 7:09
  • 1
    Not a solution, sorry, but why not send the one byte to the drive using SETNAM? Comparing to code I use in my programs I do almost exactly what you do (setlfs/setnam/open then chkin and loop over readst( exit if not 0), chrin and store. Finally close and clrchn) but I am not sending individual bytes to the drive.
    – Martijn
    Commented Oct 22, 2023 at 16:06
  • 1
    @Martijn, after posting this I tried doing just that, and that way it works perfectly. But consider a more complex operation, like a rename. The string to send to the drive would be "R0:newname=oldname" (going from memory) and my choices would be to concatenate that into a memory buffer to use with SETNAM or to send the bytes dynamically like in my post. So I was hoping to avoid needing a buffer to concat the prefix (R0:) the filenames and the equals sign. So I left the question open, because I still can't get that technique to work.
    – Geo...
    Commented Oct 25, 2023 at 14:54

1 Answer 1


The Commodore Programmer's Manual does not explain this too clearly IMHO, but we find on page 282 of the Reference Guide: "If one of the channels to be closed is to the serial port, an UNTALK signal is sent first to clear the input channel or an UNLISTEN is sent to clear the output channel." I take this to mean the required order for sending a command is to:

  • open a logical channel
  • CHKOUT (Perhaps 'Change Kernal OUTput channel')
  • CHROUT (Character out)
  • CLRCHN (Tell serial device we're done, perhaps "finish data stream")
  • close a logical channel However, the 1541-II user's guide, chapter 9, page 80 finalizes the code with first CLOSE then CLRCHN. It is possible the example given there happens to not care about the order.

Applying this to the program's code, change from this:

close_15        lda #$0F                ; close file number 15
                jsr CLOSE
                jsr CLRCHN              ; restore default devices                

to this:

close_15        jsr CLRCHN              ; restore default devices
                lda #$0F                ; close file number 15
                jsr CLOSE

That way, the "sending" is completed before the serial bus communication is closed. If you first CLOSE the communication, the CLRCHN would not be able to do its UNLISTEN/UNTALK sending.


In this case: first CLRCHN, then CLOSE.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .