Source: The Anatomy of the 1541 Disk Drive (Revised); Compute's Mapping the C64
Use these BASIC patterns to send all DOS commands:
10 OPEN 1,8,15 : REM open error/command channel
20 PRINT#1,"I0:" : REM send a command (initialize)
30 INPUT#1,EN,EM$,ET,ES : REM read error (code,msg,track,sector)
40 CLOSE 1
Or for a reusable subroutine:
9000 REM --- DOS COMMAND ---
9010 OPEN 15,8,15,CMD$
9020 INPUT#15,EN,EM$,ET,ES
9030 IF EN>0 AND EN<20 THEN PRINT EN;EM$;ET;ES
9040 CLOSE 15
9050 RETURN
I0:Resets the disk, reads BAM (Block Availability Map). Use after inserting a disk.
OPEN 15,8,15,"I0:":CLOSE 15
V0:Repairs the BAM and removes “scratched” file entries. Like CHKDSK.
OPEN 15,8,15,"V0:":CLOSE 15
N0:diskname,idLow-level format. Destroys all data. ID = 2-char disk identifier.
OPEN 15,8,15,"N0:MYDISK,A1":CLOSE 15
N0:name (no ID) for a fast format that only rewrites the directoryR0:newname=oldnameOPEN 15,8,15,"R0:NEWNAME=OLDNAME":CLOSE 15
S0:filenameMarks file as deleted; frees its blocks in BAM. Wildcards (*, ?) supported.
OPEN 15,8,15,"S0:MYFILE":CLOSE 15
OPEN 15,8,15,"S0:GAME*":CLOSE 15 : REM scratch all files starting with GAME
C0:newfile=0:sourcefileCopies a file on the same drive.
OPEN 15,8,15,"C0:BACKUP=0:ORIGINAL":CLOSE 15
C1:dest=0:sourceCopies between drives (not applicable to 1541 unless using a 1571/1581 in dual mode).
M-R + address (2 bytes, lo then hi)Reads bytes from the 1541's internal RAM/ROM. Advanced use.
OPEN 15,8,15:PRINT#15,"M-R"CHR$(0)CHR$(0)CHR$(2):INPUT#15,A$:CLOSE 15
M-W + address + count + dataWrites bytes to the 1541's RAM. Used for patching drive code.
M-E + addressExecutes code in the 1541's 6502 processor at the given address.
B-R ch dn tr scReads a raw sector. ch=channel, dn=drive(0), tr=track, sc=sector.
OPEN 2,8,2,"#" : REM open buffer channel
OPEN 15,8,15
PRINT#15,"B-R 2 0 18 0" : REM read track 18, sector 0 (directory)
FOR I=1 TO 256:GET#2,A$:PRINT ASC(A$+CHR$(0));:NEXT
CLOSE 2:CLOSE 15
B-W ch dn tr scWrites the buffer to a raw sector.
B-A dn tr scMarks a sector as used in the BAM.
B-F dn tr scMarks a sector as free in the BAM.
B-E ch dn tr scLoads sector into buffer and executes it in the 1541.
U1 through U9, UA–UJ| Command | Equivalent | Description |
|---|---|---|
| U1 | B-R | Read block to buffer |
| U2 | B-W | Write block from buffer |
| U3–U8 | M-E | Execute user subroutines in 1541 |
| U9 | Soft reset (preserves drive address) | |
| U: | UJ | Hard reset (same as power cycle) |
Read from secondary address 15 after any operation:
EN, EM$, ET, ES
| Code | Message | Meaning |
|---|---|---|
| 00 | OK | No error |
| 01 | FILES SCRATCHED | N files were deleted (N is track field) |
| 20 | READ ERROR (block header) | Can't find sector header |
| 21 | READ ERROR (no sync character) | No sync mark found (blank/damaged) |
| 22 | READ ERROR (data block) | Data block not found |
| 23 | READ ERROR (checksum) | Data block checksum failed |
| 24 | READ ERROR (byte decoding) | GCR decoding error |
| 25 | WRITE ERROR (verify) | Verify-after-write failed |
| 26 | WRITE PROTECT ON | Write-protect tab covering notch |
| 27 | READ ERROR (checksum header) | Header block checksum failed |
| 28 | WRITE ERROR (long data block) | Data block too long |
| 29 | DISK ID MISMATCH | Wrong disk inserted |
| 30 | SYNTAX ERROR (general) | DOS command not recognized |
| 31 | SYNTAX ERROR (invalid command) | Command letter not recognized |
| 32 | SYNTAX ERROR (long line) | Command string > 58 characters |
| 33 | SYNTAX ERROR (invalid filename) | Wildcard in wrong context, or bad name |
| 34 | SYNTAX ERROR (no file given) | Missing filename |
| 39 | SYNTAX ERROR (invalid command) | Command exists but not in this context |
| 49 | INVALID FORMAT (loading) | Attempt to load incompatible format |
| 50 | RECORD NOT PRESENT | Relative file record past EOF |
| 51 | OVERFLOW IN RECORD | Data exceeds relative record length |
| 52 | FILE TOO LARGE | Relative file: record length too large |
| 60 | WRITE FILE OPEN | Tried to read a file opened for write |
| 61 | FILE NOT OPEN | File not opened before read/write |
| 62 | FILE NOT FOUND | Named file doesn't exist on disk |
| 63 | FILE EXISTS | Tried to create file that already exists |
| 64 | FILE TYPE MISMATCH | Wrong file type for operation |
| 65 | NO BLOCK | Block-allocate failed (no free block nearby) |
| 66 | ILLEGAL TRACK AND SECTOR | Bad T/S combination |
| 67 | ILLEGAL SYSTEM T&S | Tried to access reserved directory sector |
| 70 | NO CHANNEL | All 5 channels in use |
| 71 | DIRECTORY ERROR | BAM doesn't match actual usage |
| 72 | DISK FULL | No free sectors remaining |
| 73 | CBM DOS V2.6 1541 | Power-on / reset message (not an error) |
| 74 | DRIVE NOT READY | No disk inserted, or door open |
| Code | Extension | Type | Description |
|---|---|---|---|
| $80 | PRG | Program | BASIC or ML program (load address in header) |
| $81 | SEQ | Sequential | Text/data, forward-only access |
| $82 | USR | User | Application-defined format |
| $83 | REL | Relative | Random-access by record number |
| $84 | — | (Internal) | Used by 1571/1581, not 1541 |
Bit 7 set = file is closed (normal). Bit 6 set = file was locked (scratch protected). If bit 7 is clear, file was not properly closed (“splat file” — *filename).
| Tracks | Sectors/Track | Notes |
|---|---|---|
| 1–17 | 21 | Outermost (fastest) |
| 18–24 | 19 | |
| 25–30 | 18 | |
| 31–35 | 17 | Innermost (slowest) |
Track 18, Sector 0 — Directory header and BAM (Block Availability Map) Track 18, Sectors 1–18 — Directory entries (8 entries per sector × 18 sectors = max 144 files)
| Offset | Length | Content |
|---|---|---|
| $00 | 2 | T/S link to first directory sector (18/1) |
| $02 | 1 | DOS version (‘A’ for 1541 format) |
| $03 | 1 | Unused ($00) |
| $04 | 4×35 | BAM entries for tracks 1–35 (4 bytes each) |
| $90 | 16 | Disk name (padded with $A0) |
| $A0 | 2 | $A0 $A0 (padding) |
| $A2 | 2 | Disk ID |
| $A4 | 1 | $A0 |
| $A5 | 2 | DOS type (“2A”) |
| $A7 | 4 | $A0 $A0 $A0 $A0 (padding) |
Each 4-byte BAM entry:
| Offset | Length | Content |
|---|---|---|
| $00 | 2 | T/S of next dir sector (or $00/$00 if last) |
| $02 | 1 | File type byte (see File Types above) |
| $03 | 2 | T/S of first sector of file data |
| $05 | 16 | Filename (padded with $A0) |
| $15 | 2 | T/S of first side-sector (REL files only) |
| $17 | 1 | Record length (REL files only) |
| $18 | 6 | Unused (internal use during write) |
| $1E | 2 | File size in sectors (low byte first) |
Every sector (except the last in a file) uses its first 2 bytes as a T/S link:
| Byte | Meaning |
|---|---|
| 0 | Track of next sector (0 = this is last) |
| 1 | Sector of next sector; or last used byte # |
When track byte = 0, sector byte = number of valid data bytes in this sector (1 to 255). Data starts at byte offset $02 in each sector (bytes $00–$01 are the link).
Usable data per sector: 254 bytes (all sectors except last), variable in last sector.
10 OPEN 1,8,2,"0:MYDATA,S,W" : REM write mode
20 PRINT#1,"HELLO WORLD"
30 PRINT#1,3.14159
40 CLOSE 1
10 OPEN 1,8,2,"0:MYDATA,S,R" : REM read mode
20 INPUT#1,A$
30 IF ST=0 THEN PRINT A$:GOTO 20
40 CLOSE 1
10 OPEN 1,8,3,"0:RELFILE,L,"+CHR$(32) : REM record length 32
20 FOR I=1 TO 100
30 PRINT#1,I;"RECORD";I : REM write record
40 NEXT I
50 CLOSE 1
10 OPEN 1,8,3,"0:RELFILE,L,"+CHR$(32)
20 OPEN 15,8,15
30 INPUT "RECORD NUMBER";R
40 PRINT#15,"P"+CHR$(3)+CHR$(R MOD 256)+CHR$(R/256)+CHR$(1)
50 INPUT#1,A$
60 PRINT A$
70 GOTO 30
P command: channel, record low, record high, byte-within-record (1-based)
LOAD "FILENAME",8 : REM load to original address
LOAD "FILENAME",8,1 : REM load relocate to BASIC start
SAVE "FILENAME",8
LOAD "$",8
LIST
OPEN 15,8,15
INPUT#15,EN,EM$,ET,ES
PRINT EN,EM$,ET,ES
CLOSE 15
; Read track 18, sector 0 (BAM) into buffer at $C000
OPEN_BUFFER:
LDA #2
LDX #8
LDY #2
JSR $FFBA ; SETLFS
LDA #1
LDX #<BUFNAME
LDY #>BUFNAME
JSR $FFBD ; SETNAM "#" opens a buffer
JSR $FFC0 ; OPEN
LDA #0
LDX #15
LDY #0
JSR $FFBA
LDA #0
JSR $FFBD ; SETNAM "" (empty name for cmd channel)
JSR $FFC0 ; OPEN channel 15
LDX #15
JSR $FFC9 ; CKOUT to cmd channel
; Send U1 (block read) command: U1 ch drive track sector
LDA #'U':JSR $FFD2
LDA #'1':JSR $FFD2
LDA #' ':JSR $FFD2
LDA #'2':JSR $FFD2 ; channel 2
LDA #' ':JSR $FFD2
LDA #'0':JSR $FFD2 ; drive 0
LDA #' ':JSR $FFD2
LDA #'1':JSR $FFD2 ; track 18 = "18"
LDA #'8':JSR $FFD2
LDA #' ':JSR $FFD2
LDA #'0':JSR $FFD2 ; sector 0
LDA #$0D:JSR $FFD2 ; RETURN
JSR $FFCC ; CLRCH
; Read 256 bytes from buffer channel
LDX #2
JSR $FFC6 ; CHKIN channel 2
LDY #0
RD: JSR $FFCF ; CHRIN
STA $C000,Y
INY
BNE RD
JSR $FFCC
LDA #2:JSR $FFC3 ; CLOSE 2
LDA #15:JSR $FFC3 ; CLOSE 15
RTS
BUFNAME: .BYTE "#"
*) that won't OPEN or SCRATCH easily.U9 to soft-reset the drive if it hangs without cycling power; U: (UJ) does a complete reset.Powered by TurnKey Linux.