# Commodore 1541 Disk DOS — Complete Command Reference > Source: The Anatomy of the 1541 Disk Drive (Revised); Compute's Mapping the C64 --- ## Drive Addressing - Default device number: **8** (jumper-selectable; 8–11) - Drive number: always **0** for 1541 (single drive) - Commands sent to: **secondary address 15** (command channel) - Errors read from: **secondary address 15** --- ## BASIC Command Channel Wrapper Use these BASIC patterns to send all DOS commands: ```basic 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: ```basic 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 ``` --- ## DOS Commands ### Initialize — `I0:` Resets the disk, reads BAM (Block Availability Map). Use after inserting a disk. ```basic OPEN 15,8,15,"I0:":CLOSE 15 ``` ### Validate — `V0:` Repairs the BAM and removes "scratched" file entries. Like CHKDSK. ```basic OPEN 15,8,15,"V0:":CLOSE 15 ``` ### New (Format) — `N0:diskname,id` Low-level format. **Destroys all data.** ID = 2-char disk identifier. ```basic OPEN 15,8,15,"N0:MYDISK,A1":CLOSE 15 ``` - After first format, use `N0:name` (no ID) for a fast format that only rewrites the directory ### Rename — `R0:newname=oldname` ```basic OPEN 15,8,15,"R0:NEWNAME=OLDNAME":CLOSE 15 ``` ### Scratch (Delete) — `S0:filename` Marks file as deleted; frees its blocks in BAM. Wildcards (`*`, `?`) supported. ```basic OPEN 15,8,15,"S0:MYFILE":CLOSE 15 OPEN 15,8,15,"S0:GAME*":CLOSE 15 : REM scratch all files starting with GAME ``` ### Copy — `C0:newfile=0:sourcefile` Copies a file on the same drive. ```basic OPEN 15,8,15,"C0:BACKUP=0:ORIGINAL":CLOSE 15 ``` ### Duplicate (two-drive systems) — `C1:dest=0:source` Copies between drives (not applicable to 1541 unless using a 1571/1581 in dual mode). ### Memory Read — `M-R` + address (2 bytes, lo then hi) Reads bytes from the 1541's internal RAM/ROM. Advanced use. ```basic OPEN 15,8,15:PRINT#15,"M-R"CHR$(0)CHR$(0)CHR$(2):INPUT#15,A$:CLOSE 15 ``` ### Memory Write — `M-W` + address + count + data Writes bytes to the 1541's RAM. Used for patching drive code. ### Memory Execute — `M-E` + address Executes code in the 1541's 6502 processor at the given address. ### Block-Read — `B-R ch dn tr sc` Reads a raw sector. ch=channel, dn=drive(0), tr=track, sc=sector. ```basic 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 ``` ### Block-Write — `B-W ch dn tr sc` Writes the buffer to a raw sector. ### Block-Allocate — `B-A dn tr sc` Marks a sector as used in the BAM. ### Block-Free — `B-F dn tr sc` Marks a sector as free in the BAM. ### Block-Execute — `B-E ch dn tr sc` Loads sector into buffer and executes it in the 1541. ### User Commands — `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) | --- ## Error Channel Format Read from secondary address 15 after any operation: ``` EN, EM$, ET, ES ``` - **EN** = error number (0=OK) - **EM$** = error message string - **ET** = track where error occurred (0 if not disk error) - **ES** = sector where error occurred ### Complete Error Code Table | 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 | --- ## File Types | 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`). --- ## Disk Layout (1541) - **35 tracks**, numbered 1–35 - **683 sectors total** (664 user-available, 19 reserved for directory/BAM) - Sector size: **256 bytes** each - Sectors per track varies with track zone: | 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) ### BAM Structure (Track 18, Sector 0) | 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:** - Byte 0: number of free sectors on this track - Bytes 1–3: bit map — bit=1 means sector is FREE, bit=0 means sector is USED ### Directory Entry Structure (32 bytes each) | 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) | --- ## Sector Chaining 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. --- ## BASIC File Operations Reference ### Sequential Write ```basic 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 ``` ### Sequential Read ```basic 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 ``` ### Relative File — Create ```basic 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 ``` ### Relative File — Random Access ```basic 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 a Program File ```basic LOAD "FILENAME",8 : REM load to original address LOAD "FILENAME",8,1 : REM load relocate to BASIC start ``` ### Save a Program File ```basic SAVE "FILENAME",8 ``` ### Directory Listing ```basic LOAD "$",8 LIST ``` ### Read Error Channel ```basic OPEN 15,8,15 INPUT#15,EN,EM$,ET,ES PRINT EN,EM$,ET,ES CLOSE 15 ``` --- ## ML Direct Block Access Example ```asm ; 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 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 "#" ``` --- ## Tips & Pitfalls 1. **Always read the error channel** after any disk operation. Error 73 at power-on is normal (drive status, not an error). 2. **Scratched files are recoverable** until VALIDATE runs — the data sectors are not zeroed, just unlinked in the BAM. 3. **Never pull a disk during a write** — the directory entry is written last; if interrupted, you get a "splat file" (`*`) that won't OPEN or SCRATCH easily. 4. **Track 18 is sacred** — it holds the directory and BAM. If corrupted, use a disk editor to repair. 5. **Maximum 144 files per disk** — 18 directory sectors × 8 entries each. 6. **Relative files** are the only random-access format; they require a "side sector" on the disk. 7. **The 1541 has only one read/write head** — interleave is important for sequential speed. Original DOS uses interleave of 10. 8. **Use `U9` to soft-reset the drive** if it hangs without cycling power; `U:` (UJ) does a complete reset.