Skip to content

Files on the memory card

The CompactFlash card is divided into up to 256 disks of one megabyte each. One disk is current at a time, it holds up to 16 files, and every name is eight characters plus a three-character extension. That is the whole model, and it is small enough to keep in your head — see Storage for the same thing from BASIC's side.

Loading and saving

Everything goes through three places in memory:

STR_PTR ($02)Points at the filename, which ends with a zero
FS_IO_ADDR ($037F)Where the bytes go, or come from
FS_FILE_SIZE ($034A)How many — set by a load, set by you before a save

and then one call, which answers with the carry flag:

FsLoadFileAddrLoad the named file to FS_IO_ADDR
FsSaveFileAddrSave FS_FILE_SIZE bytes from FS_IO_ADDR
FsLoadFileThe same load, always to $0800 — how a program gets loaded
FsDeleteFileRemove the named file
FsFormatDiskEmpty the current disk's directory. No confirmation, no undo.
asm
  lda #<Name
  sta STR_PTR
  lda #>Name
  sta STR_PTR + 1
  lda #<Buffer
  sta FS_IO_ADDR
  lda #>Buffer
  sta FS_IO_ADDR + 1
  jsr FsLoadFileAddr
  bcs Missing                   ; carry set: not found, or the card said no
  ; FS_FILE_SIZE now holds how many bytes arrived

Name: .byte "LEVEL1.DAT", $00

FS_FILE_SIZE is an input to a save

A load fills it in for you. A save reads it, so setting it is your job, and setting it wrong writes the wrong number of bytes without complaining. Count the bytes as you build them, or use the difference between two pointers.

Both directions

asm
; Reading and writing files on the memory card.
;
; Three things have to be true before a load or a save: a pointer to the name,
; a pointer to where the bytes live, and — for a save — how many of them there
; are. Then one call does the rest, and the carry flag says whether it worked.

.setcpu "65C02"

.include "6502.inc"

.segment "CODE"

BasicStartup:
  .byte $0A, $08, $0A, $00, $A5, $32, $30, $36, $30, $00, $00, $00

Start:
  lda HW_PRESENT
  and #HW_CF
  beq NoCard

; --- Read a file that is already there ---------------------------------------
  lda #<HelloName
  ldy #>HelloName
  jsr SetName
  lda #<Buffer
  sta FS_IO_ADDR
  lda #>Buffer
  sta FS_IO_ADDR + 1
  jsr FsLoadFileAddr
  bcs Failed                    ; carry set means it did not happen

  lda #<Found
  ldy #>Found
  jsr PrintStr
  jsr PrintBuffer

; --- Write one of our own ----------------------------------------------------
  ldy #0
@copy:
  lda Note,y
  sta Buffer,y
  beq @copied
  iny
  bra @copy
@copied:
  sty FS_FILE_SIZE              ; the terminator itself is not saved
  stz FS_FILE_SIZE + 1

  lda #<NoteName
  ldy #>NoteName
  jsr SetName
  lda #<Buffer
  sta FS_IO_ADDR
  lda #>Buffer
  sta FS_IO_ADDR + 1
  jsr FsSaveFileAddr
  bcs Failed

; --- And read it straight back ----------------------------------------------
  lda #0                        ; wipe the buffer so the read has to do the work
  ldy #0
@wipe:
  sta Buffer,y                  ; STZ has no Y-indexed form; STA does
  iny
  cpy #64
  bne @wipe

  lda #<NoteName
  ldy #>NoteName
  jsr SetName
  lda #<Buffer
  sta FS_IO_ADDR
  lda #>Buffer
  sta FS_IO_ADDR + 1
  jsr FsLoadFileAddr
  bcs Failed

  lda #<Back
  ldy #>Back
  jsr PrintStr
  jsr PrintBuffer
  rts

Failed:
  lda #<Trouble
  ldy #>Trouble
  jsr PrintStr
  rts

NoCard:
  lda #<NoCardMsg
  ldy #>NoCardMsg
  jsr PrintStr
  rts

; Point the filesystem at a name. A/Y = address of a NUL-terminated 8.3 name.
SetName:
  sta STR_PTR
  sty STR_PTR + 1
  rts

; Print however many bytes the last load brought in.
PrintBuffer:
  ldy #0
@next:
  cpy FS_FILE_SIZE              ; these files are far smaller than a page
  beq @done
  lda Buffer,y
  phy
  jsr Chrout
  ply
  iny
  bra @next
@done:
  jmp PrintCRLF

HelloName: .byte "HELLO.TXT", $00
NoteName:  .byte "NOTE.TXT", $00
Note:      .byte "WRITTEN BY A PROGRAM", $00

Found:     .byte "THE CARD ALREADY HELD: ", $00
Back:      .byte "AND NOW IT HOLDS: ", $00
Trouble:   .byte "THE CARD SAID NO", CHAR_CR, CHAR_LF, $00
NoCardMsg: .byte "NO MEMORY CARD FITTED", CHAR_CR, CHAR_LF, $00

Buffer:    .res 64
RUN
THE CARD ALREADY HELD: HELLO
AND NOW IT HOLDS: WRITTEN BY A PROGRAM

OK

The wipe in the middle is not ceremony — without it, a load that silently did nothing would leave the old bytes sitting in the buffer and the program would print them back happily.

Choosing a disk

FsSetDiskDisk number in A, 0 to 255
FsGetDiskThe current one, back in A
FsPrintDiskPrint DISK n and a new line

Disk 0 is selected at power-on. A disk is a megabyte, its directory lives in its first sector, and its files cannot spill into the next one — which makes disks a genuinely good way to keep a big project's data apart from everything else.

The card itself

Underneath the filesystem there are three routines that move raw 512-byte sectors:

StReadSectorSector number in CF_LBA, destination in CF_BUF_PTR
StWriteSectorThe same, the other way
StWaitReadyBlock until the card is not busy

CF_LBA ($26) is four bytes, little end first, counting 512-byte sectors from the start of the card — not from the start of the current disk. A disk's own first sector is at disk × 2048.

asm
  stz CF_LBA                    ; sector 0 of the card
  stz CF_LBA + 1
  stz CF_LBA + 2
  stz CF_LBA + 3
  lda #<Sector
  sta CF_BUF_PTR
  lda #>Sector
  sta CF_BUF_PTR + 1
  jsr StReadSector
  bcs CardTrouble

Both routines advance CF_BUF_PTR by 512 on success, so reading a run of sectors is a loop that only increments CF_LBA.

Use these when you want your own layout — a save format, a level pack, a database — and the sixteen-file directory is in your way. Use the filesystem calls for anything a person will see the name of.

$0600–$07FF belongs to the filesystem

Every filesystem call reads a sector into the buffer at $0600 to look at the directory. If your program is keeping anything in that 512 bytes, one LOAD will eat it. There is 30 KB of program RAM; use it.

When the card is not there

HW_PRESENT has a bit for the storage card, and everything above sets the carry flag rather than hanging when the card is missing, unformatted, or wedged. A program that checks the bit once at the start and the carry flag after each call can say "put a card in" instead of stopping dead:

asm
  lda HW_PRESENT
  and #HW_CF
  beq NoCard

Next: the serial port.

Written for BIOS v1.5. Released under the MIT License.