Page 1 of 14

Adventures in FAT32 with 65c02

Posted: Fri Dec 05, 2025 4:08 pm
by barnacle
This might turn into another of my goes-on-forever screeds as I add to it, but the intent is to describe what I'm doing to get access to a FAT32 CF card on a simple 65c02 system.

For the avoidance of doubt: all code and hardware designs shown in this thread are released under the MIT licence:
MIT License wrote:
Copyright 2025 Neil Barnes

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The system on which I am primarily testing this design consists of a 65c02 running at 1.8342MHz and 5v. Ram is in the lower 32k, eeprom (non-writable in circuit) in the upper 16k, and a 68B50 UART giving a 115200 baud rate through an FTDI usb/serial adaptor.

So, why a CF card?
A handful of reasons, really.
  • it's simple to implement because if its parallel interface; it doesn't need to have (for example) code running to implement SPI (or multibit SPI) in order to operate.
  • it can be very simply used to interchange data between the 65c02 system and a Windows, Mac, or Linux computer; there is no requirement for a serial interface to handle data as well as text, just an inexpensive USB-CF adaptor. It's possible that your computer already has such an adaptor built in. (Though of course this also applies to the various SD card variants, too.)
  • the interface is consistent between a CF and other IDE parts, such as a PATA hard drive or a floppy drive.
The second item in this list deserves more explanation. File compatibility between the 65c02 and a more familiar computer does assume at the least a compatible file system. But that said, it doesn't really matter which file system as long as it is understood by both parties. I've chosen FAT32 because it's well understood by Windows and Linux machines (and probably Apples, too, but I don't use those so can't confirm). Windows and Linux also understand FAT12, FAT16, and exFAT, but they're an unnecessary complication. I'm looking to keep the controlling software small. They are also capable of correctly formatting over existing formats to FAT32 (example: many of my CF test cards were already formatted FAT32, but some 0.5GB were delivered with FAT16. Easily changed).

With that said, it's important to note that I'm not seeking binary compatibility; there is no reason to expect a 65c02 binary to execute on a Windows or Linux machine (without an emulator) nor vice versa. However, it should be possible to create a text or data file on either machine and read it correctly on the other.

Sidenote: For new lines in text files, Windows uses CRLF, Linux uses LF, and Apple uses either CR or LF depending on the age of the OS. You're going to have to handle whichever you choose. Because of my linux system, I shall be using LF as a line break symbol and ignoring CR.

Hardware
There are two obvious choices. Either we attach directly to the card holder, or we use an existing IDE-CF adaptor module. The first choice is probably the cleanest and smallest, since the card mounts parallel to the circuit board, but the pins are on 0.635mm centres and are not the easiest thing in the world to solder. Plus, there are many card holders available, all very similar, but few with identical positioning for the (bolt) mounting holes, the locating pins, and the solder tabs. You're probably going to have to design your own footprint from the maker's datasheet (I got mine wrong!).

IDE to CF adaptor modules are all over the internet. From well-known Chinese sites, they're available for a couple of quid. The interface for the CF card was designed as a superset of the IDE interface and the adaptors are largely passive connections, though they may include things like an activity LED and possibly a driver transistor or two. The 44-pin IDE interface is a 2x22 contact part on 2mm centres (not 0.1") usually with pins, though it may be a socket. This is easy to accommodate on the PCB but with the disadvantage that unless you found one with a right angle pin set, the CF is going to stick out at right angles.

Using the IDE-CF adaptor module
Screenshot from 2025-12-05 08-44-39.png
This derives two signals from the 65c02 control signals: R/~W NAND PHI2 gives a ~RD signal on pin 23 and ~(R/~W) NAND PHI2 gives a ~WR signal. The 74HC245 isolates the CF from the databus when not required, with the data direction selected from R/~W. You may have to investigate your socket to ensure the pins match; the numbers in this diagram match those on my adaptor, viewed from the pin side. Pin 20 is missing.

Using the CF card directly
Screenshot from 2025-12-05 08-43-23.png
This shows a section of the single board card described above. Again, ~RD and ~WR are generated as above, and the '245 works the same way. The series resistors are _probably_ not required - they're not there on the adaptor, for example - but sources on the web strongly suggest that they be fitted to slow down the edges of the data.

Note: the CF is specified to work at either 3.3v or 5v; for 3.3v its access time is stated at 600ns and half that for 5v. I suspect that it is in fact significantly less, particularly with modern fast cards.

Threads discussing the development of both designs can be found here: viewtopic.php?f=4&t=8482 and here: viewtopic.php?f=4&t=8501

To be continued.

Neil

Re: Adventures in FAT32 with 65c02

Posted: Fri Dec 05, 2025 7:50 pm
by fachat
You know there is a 6502 FAT32 implementation by Michael Steil et al out there as open source?

I know it's probably not as much fun as writing it yourself. But having been there done that (Fat12/16) back in the day I found it a good help not having to write that again

André

Re: Adventures in FAT32 with 65c02

Posted: Fri Dec 05, 2025 9:07 pm
by barnacle
I do, yes... but as you say, where's the fun in that? Equally, I could use Elm-chan's FatFS and compile that for 65c02, but it doesn't explain what's going on. And we are all here fundamentally for fun.

Also, in the vague hope that my whimsy might prove helpful to someone in the future. Or even educational?

So I'm basically starting with the premise that I have a CF card in one hand and a 65c02 in the other and taking it from there. One thing I will be doing is severely restricting the ability of the final interface. For example, I don't expect to be able to say 'cat ../../../demo/main.c' but rather restrict things to working in the current directory. Nonetheless, here we go :mrgreen:

Neil

Re: Adventures in FAT32 with 65c02

Posted: Fri Dec 05, 2025 10:29 pm
by BigDumbDinosaur
barnacle wrote:
So I'm basically starting with the premise that I have a CF card in one hand and a 65c02 in the other and taking it from there. One thing I will be doing is severely restricting the ability of the final interface. For example, I don't expect to be able to say 'cat ../../../demo/main.c' but rather restrict things to working in the current directory. Nonetheless, here we go :mrgreen:

At a fundamental level, code that can read/write a filesystem has nothing to do with the medium on which the filesystem is loaded, as long as the medium has some basic capabilities.  Hence there are really two parts to your code: the filesystem driver and the storage device driver.  Each treats the other as a black box, with an agreed-upon method for the filesystem driver to issue a command to the storage device driver, for the storage device driver to inform the filesystem driver of the success or failure of the command, and a means of passing blobs of data between the two.

In this scenario, the storage device driver knows all about talking to the hardware and reading or writing blocks, but knows nothing about what the data in those blocks means.  Conversely, the filesystem driver knows all about the filesystem structure, e.g., location of the directory, FAT, etc., and what sequences of bytes in those structures mean, but doesn’t know squat about device commands, block addressing, and other hardware minutia.  This so-called “Chinese wall” separation produces flexibility that allows your filesystem to be stored on almost any kind of medium, as long as it offers random access.  If you one day decide to hook up a floppy disk to your machine, all you need is a floppy disk device driver that can communicate with your filesystem driver using the same protocol as used with the CF card driver.

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 6:08 am
by barnacle
Indeed. The separation between BIOS and BDOS, as it were.

Because I'm using CF, I'll cover the mechanics of talking to the CF first but after that, the file system will be applicable to anything that can read and write (and whose size you know).

I think things will end up with a small eeprom which has code to read the CF (it doesn't even need to write it, though it may), but only really needs to understand how to find the operating system file, load it, and run it.

Neil

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 8:30 am
by BigDumbDinosaur
barnacle wrote:
Indeed. The separation between BIOS and BDOS, as it were.

Yep!

With my POC units, the BIOS (in ROM) has an API for talking to SCSI mass storage, with some functions used to get information about the devices that were enumerated during POST, and other functions for selecting and conversing with a device. The SCSI subsystem software itself has three layers: API, foreground processor, and interrupt-driven device driver.  Callers only know about the API.

The API itself is relatively primitive in nature and is basically the front end to a SCSI command processor.  The caller must assemble a command descriptor block (CDB) somewhere in RAM, the CDB containing the parameters needed by the target device to carry out the desired operation.  Next, the caller has to load the MPU registers with the bus ID of the target device and a 32-bit pointer to the CDB.  The API call is made with a software interrupt (COP).  If the call is intended to pass data between the device and caller, a separate API call must be made to set a buffer pointer.

Applications typically don’t directly call the SCSI API; commonly used SCSI services are accessed through a library that presents a uniform interface for all library functions and deals with the tedious minutia of qualifying parameters, setting up a CDB, calling the API primitive, etc..

Quote:
Because I'm using CF, I'll cover the mechanics of talking to the CF first but after that, the file system will be applicable to anything that can read and write (and whose size you know).

Since the CF looks something like a PATA disk, you probably can do what I did and separate the driver into layers, each with a specific task.  That might give you the best flexibility in accommodating future block-random hardware, e.g., a real disk.

Code: Select all

I think things will end up with a small eeprom which has code to read the CF (it doesn't even need to write it, though it may), but only really needs to understand how to find the operating system file, load it, and run it.

That makes sense.  Load a boot block from the CF, and code in that block can then fetch a stage-2 boot loader that has enough smarts to find the operating system kernel, and load and start it.

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 8:53 am
by barnacle
Talking to the CF card
Depending on the way in which the CF card is initialised, it can be used in various ways. Some are faster than others, some involve DMA, some involve interrupts, some involve 16-bit accesses... and I'm going to ignore them all and restrict things to eight-bit IDE compatible operation - that is, PIO working. This is heavy on the processor since it needs to hang around waiting for the drive to complete its operation, and grab data only when it's told to. In short, file operations will be blocking.

To meet the standard, CF cards are required to operate in legacy IDE mode _if_ pin 9 is held low when the card is powered up. The pins in the connector are of different lengths; when a card is inserted the power and ground connect first, then the control and data pins, and finally two pins - ~CD1 and ~CD2 - which are shorter than the rest are grounded by the card to indicate that it is fully inserted. These pins can be read by the host, though I don't bother.

The point is that pin 9 may or may not be visible to the CF card when it is powered during insertion. In some cases, that doesn't seem to matter and they drop to IDE mode anyway, but I have others which require the power to be cycled with the card inserted to work properly. A classic case of turn it off and on again if it doesn't work.

Referring to the previous drawings will indicate which pins must be connected to either ground or Vcc for things to happen. In particular, the address lines A3-10 should be held low. With the adaptor, most of the lines are held to the required state automatically.

Control registers
The IDE controls are a block of eight registers, offset from a base address:

Code: Select all

CFREG0		equ	CFBASE+0	; data port
CFREG1		equ	CFBASE+1	; read: error code; write: feature
CFREG2		equ	CFBASE+2	; sector transfer count
CFREG3		equ	CFBASE+3	; sector address LBA 0 [0:7]
CFREG4		equ	CFBASE+4	; sector address LBA 1 [8:15]
CFREG5		equ	CFBASE+5	; sector address LBA 2 [16:23]
CFREG6		equ	CFBASE+6	; sector address LBA 3 [24:27 (LSB)](bits 4:7 are control bits)
CFREG7		equ	CFBASE+7	; read: status; write: command
CFREG0 is used to read and write any data; CFREG7 either to start an instruction or to read the status of the interface. The 28 bits of the LBA (logical block address) of a sector but the top four bits of CFREG6 must remain, for our purposes, at $e. We'll see more later.

In general, to send an instruction to the CF, registers are preset with required data and then a command is issued to CFREG7. Often is is necessary to wait for the CF to complete a previous instruction and/or to become ready.

Code: Select all

;-----------------------------------------------------------------------
; wait until the cf is not busy (i.e. bit 7 reg 7 is clear)
; and then until cf is ready for a command (bit 6 reg 7 is set)
;
cf_wait:
	lda CFREG7			; loads sign flag
	bmi cf_wait			; wait until it's zero
cfw_1:
	lda CFREG7
	and #$40			; isolate ready bit
	beq cfw_1			; and loop until it's zero
	rts

It is possible that waiting for the ready bit is not always required.

Initialising the CF

Code: Select all

;-----------------------------------------------------------------------
; initialise the cf in eight bit mode (true IDE set by the hardware)
;
cf_init:
	jsr cf_wait			; wait after hardware reset
	lda #4				; force a software reset
	sta CFREG7
	jsr cf_wait
	
	lda #$e0			; set LBA mode
	sta CFREG6
	lda #1				; eight bit mode
	sta CFREG1
	jsr cf_wait
	lda #$ef			; set feature
	sta CFREG7
	jsr cf_wait
	rts	
This initialisation sequence firstly waits for the card to be available - it may still be doing things after the hardware reset - and then tries a software reset. One reference I found suggests that the reset bit should be zeroed five microseconds later, but without explanation, and this code seems to work.

I use LBA mode exclusively here. This treats the sectors on the CF as a linear sequence with a 28-bit address, rather than the older CHS (cylinder, head, sector) approach which was designed to explicitly address spinning disc hardware but is now obsolete.

Eight bit mode is also used. By default, a sixteen bit data bus is used which, for an eight bit system, would require external latches and sequential reads. Eight bit mode makes things simpler all around.

The last thing is to command the CF to accept these new features, using the command $EF and after that (and the obligatory wait for things to complete) we can talk to the card.

(It's been a long explanation for such a small bit of code :mrgreen: )

Neil

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 10:46 am
by GlennSmith
barnacle wrote:
(It's been a long explanation for such a small bit of code :mrgreen: )
It's perfect ! Especially :
Quote:
The point is that pin 9 may or may not be visible to the CF card when it is powered during insertion. In some cases, that doesn't seem to matter and they drop to IDE mode anyway, but I have others which require the power to be cycled with the card inserted to work properly. A classic case of turn it off and on again if it doesn't work.
I had (still have, somewhere) some HP 'organizers' (HP200LX for the aficionados), which used CF cards and sometimes played-up when I changed cards. Perhaps you have solved one of those nagging questions! (albeit nearly 30years too late).
Keep up the good work and the steady rhythm - it's actually fun reading!

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 12:33 pm
by plasmo
CF interface is simple enough that you almost don’t need a ROM to bootstrap a computer from CF, almost. The initial wait for ‘not busy’ can be a deliberate pause after power on, followed by injection of read command, $20, into CFREG7, another short pause, then processor can read the data coming out of the master boot record. In fact, a processor can EXECUTES the instruction streaming out of the master boot record. A simple state machine, or a few toggle switches is sufficient to bootstrap processor from CF thus a ROMless system (assuming CF is not considered a ROM). Or a small diode-matrix ROM, hint, hint.
Bill

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 1:45 pm
by barnacle
plasmo wrote:
Or a small diode-matrix ROM, hint, hint.
Bill
We're not talking 'bout diode matrices :oops: The last one still works perfectly, right up to where I try and use it :mrgreen:

It _is_ a simple interface: remember it was designed in 8080 days to interface small systems. Terabyte drives were, um, uncommon back then. But it's still, I think, clearer to have an easily identified prom handling the basic IO than a handful of glue logic, or an obscurely programmed CPLD chip.

Neil

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 4:44 pm
by barnacle
Introducing yourself to the CF card
When you talk to the CF card, you can be talking either to the data stored on it, or the card itself. It's important to be aware of the difference... The card has an information section which contains a number of fields which you might find useful, but which aren't required for normal use. Maybe they're useful to know of the disk that's in is the one you started with? (I'm assuming that there is no requirement or probability of a disk being physically changed once mounted; a more sophisticated system might take more care over this).

To get the information block - one sector's worth:

Code: Select all

cf_info:
	jsr cf_wait			; should be already done after init, but...
	lda #$e0
	sta CFREG6			; configure the card (again!)
	lda #$ec			; request card info
	sta CFREG7			; trigger it
	jsr cf_wait			; wait until it's ready
	jsr cf_read_info	; and read the data into transient
cf_read_info is a short-cut into the standard sector read subroutine, which by default requests a sector:

Code: Select all

;-----------------------------------------------------------------------
; read a sector (or the info block) into transient
; transfer is configured before calling; this just moves the data
; from the card into transient

	bss
trans_ptr:	ds 2
	
	code
cf_read:
	jsr cf_wait
	lda # 0x20			; read sector command
	sta CFREG7
cf_read_info:			; enter here if presetting info block ID: 0xec
	jsr cf_wait
	lda #1				; one sector please
	sta CFREG2
	lda # lo transient
	sta trans_ptr
	lda # hi transient
	sta trans_ptr + 1	; pointer to transient start address
cf_r0:
	lda CFREG7
	and #%00001000		; check the DRQ bit
	beq cf_r0			; loop if not set
	ldx #2				; number of pages - 512 bytes
cf_r1:
		ldy #0
cf_r2:
			jsr cf_wait
			lda CFREG0
			sta (trans_ptr),y
			iny
			bne cf_r2
		inc trans_ptr + 1	; page finished; inc pointer
		dex
		bne cf_r1			; get next page if required
	; at this point, transient should be filled with data
	; some sort of error check should be made... later
cf_rx:
	rts
My transient area is fixed at $200; all reads and writes to and from the disc live there at least temporarily. So it gets overwritten regularly (and note for later that while we'll be dealing with 4kB sectors, we'll be handling them one 512B sector at a time).

Order, order...
The data in the card information block consists of 256 16-bit words, stored high bit first (big-endian). I have no idea why they decided to do that, but even setting eight bit mode does not change the order. This applies even to text strings held within the info block.

There are four areas we might be interested in (offsets in decimal):
  • Offset 20: ten words/twenty bytes of ascii giving a serial number
  • Offset 46: four words/eight bytes of ascii firmware version
  • Offset 54: twenty words/forty bytes of model name (I incorrectly read only 20 bytes)
  • Offset 120: four bytes with the available clusters on the disc as a uint32_t, high byte first
It looks like this on one of my cards:

Code: Select all

0200  4A 04 E1 03 00 00 10 00 00 00 00 02 3F 00 0F 00 J...........?...          
0210  F0 45 00 40 31 41 31 37 30 31 33 37 35 30 31 41 .E.@1A170137501A          
0220  71 57 47 42 48 30 77 35 02 00 02 00 04 00 46 43 qWGBH0w5......FC          
0230  42 20 31 36 4B 46 31 35 4D 32 20 42 6F 43 70 6D B 16KF15M2 BoCpm          
0240  63 61 46 74 61 6C 68 73 43 20 72 61 20 64 20 20 caFtalhsC ra d            
0250  20 20 20 20 20 20 20 20 20 20 20 20 20 20 01 80               ..          
0260  00 00 00 0B 00 00 00 02 00 00 07 00 E1 03 10 00 ................          
0270  3F 00 F0 45 0F 00 00 01 F0 45 0F 00 00 00 07 04 ?..E.....E......          
0280  03 00 78 00 78 00 78 00 78 00 00 00 00 00 00 00 ..x.x.x.x.......          
0290  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
02A0  00 00 00 00 6B 70 0C 40 00 40 29 70 0C 40 00 40 ....kp.@.@)p.@.@          
02B0  1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
but it displays like this:

Code: Select all

  Serial: A171107305A1WqBG0H5w                                                  
Firmware: CF B61FK                                                              
   Model: 512MB CompactFlash C                                                  
LBA size: 000F45F0 
I won't trouble you with the details of translating: I wrote a short routine which receives a start address and a length and swaps bytes in place. Then it's just a matter of outputting the right number of characters; there are no terminators!

Neil

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 6:24 pm
by barnacle
Resources online
While I remember, here are some of the places I found useful information... Neil

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 6:51 pm
by BigDumbDinosaur
barnacle wrote:
...The IDE controls are a block of eight registers, offset from a base address:

Code: Select all

CFREG0		equ	CFBASE+0	; data port
CFREG1		equ	CFBASE+1	; read: error code; write: feature
CFREG2		equ	CFBASE+2	; sector transfer count
CFREG3		equ	CFBASE+3	; sector address LBA 0 [0:7]
CFREG4		equ	CFBASE+4	; sector address LBA 1 [8:15]
CFREG5		equ	CFBASE+5	; sector address LBA 2 [16:23]
CFREG6		equ	CFBASE+6	; sector address LBA 3 [24:27 (LSB)]
CFREG7		equ	CFBASE+7	; read: status; write: command
So if I am correctly understanding the above, you set the LBA to be accessed by writing it piecemeal to CFREG3-CFREG6.  Does that have to be done in a certain order, e.g., LSB to MSB?
Quote:
The 28 bits of the LBA (logical block address) of a sector but the top four bits of CFREG6 must remain, for our purposes, at $e.  We'll see more later.
I wasn’t sure I was understanding that until I read later that CFREG6 is actually dual-purpose register.  Perhaps it should be described as sector address LBA 3 [24:27] & access mode [28:31] to make it clearer to the casual reader.
Quote:
In general, to send an instruction to the CF, registers are preset with required data and then a command is issued to CFREG7.  Often is is necessary to wait for the CF to complete a previous instruction and/or to become ready.

Code: Select all

;-----------------------------------------------------------------------
; wait until the cf is not busy (i.e. bit 7 reg 7 is clear)
; and then until cf is ready for a command (bit 6 reg 7 is set)
;
cf_wait:
	lda CFREG7			; loads sign flag
	bmi cf_wait			; wait until it's zero
cfw_1:
	lda CFREG7
	and #$40			; isolate ready bit
	beq cfw_1			; and loop until it's zero
	rts
I’d be inclined to write that as...

Code: Select all

;-----------------------------------------------------------------------
; wait until the cf is not busy (i.e. bit 7 reg 7 is clear)
; and then until cf is ready for a command (bit 6 reg 7 is set)
;
cf_wait  bit CFREG7            ;CF busy?
         bmi cf_wait           ;yes
;
cfw_1    bit CFREG7            ;ready for a command?
         bvc cfw_1             ;no
;
         rts

The above, of course, assumes you don’t need the value of the status byte fetched from CFREG7 for something else.

Quote:
(It's been a long explanation for such a small bit of code :mrgreen: )
It might be a “small bit of code,” but it’s central to your device driver.  Given that during a filesystem access, the driver might be called hundreds of times (once for each byte transferred, which means a call to the CF_WAIT sub for each read/write on the CF), maximum efficiency is critical.  Achieving that efficiency requires carefully analyzing the sequence of events and writing the tightest code possible.  I went through a similar process when I wrote the SCSI driver for my POC unit.  That code got patched so many times before I considered it done that it was starting to look like a well-worn bicycle tire.  :D

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 8:29 pm
by barnacle
BigDumbDinosaur wrote:
So if I am correctly understanding the above, you set the LBA to be accessed by writing it piecemeal to CFREG3-CFREG6.  Does that have to be done in a certain order, e.g., LSB to MSB?
The spec doesn't say, except in the case of LBA48 which loads registers twice and has to load high order group bits first (see https://wiki.osdev.org/ATA_PIO_Mode#48_bit_PIO ), In my code, I load the LBA registers low-to-high, with the highest ored and anded to set the top nibble.

Thanks for the suggestion to change the description of CFREG6; I've changed it.
Quote:
In general, to send an instruction to the CF, registers are preset with required data and then a command is issued to CFREG7.  Often is is necessary to wait for the CF to complete a previous instruction and/or to become ready.

Code: Select all

;-----------------------------------------------------------------------
; wait until the cf is not busy (i.e. bit 7 reg 7 is clear)
; and then until cf is ready for a command (bit 6 reg 7 is set)
;
cf_wait:
	lda CFREG7			; loads sign flag
	bmi cf_wait			; wait until it's zero
cfw_1:
	lda CFREG7
	and #$40			; isolate ready bit
	beq cfw_1			; and loop until it's zero
	rts
I’d be inclined to write that as...

Code: Select all

;-----------------------------------------------------------------------
; wait until the cf is not busy (i.e. bit 7 reg 7 is clear)
; and then until cf is ready for a command (bit 6 reg 7 is set)
;
cf_wait  bit CFREG7            ;CF busy?
         bmi cf_wait           ;yes
;
cfw_1    bit CFREG7            ;ready for a command?
         bvc cfw_1             ;no
;
         rts

The above, of course, assumes you don’t need the value of the status byte fetched from CFREG7 for something else.
Well, they do say that premature optimisation is generally not a good idea, but I take your point about speeding this part up. Though I get the feeling that the BIT instruction is one that confuses some (many?) people... it certainly confuses me. Since BIT ANDs A with the target, don't you need to set A first?

(I have a suspicion that a 6502 is not going to be waiting for a CF capable of tens of megabytes per second!)

One easy way to speed this up is to inline the test with a macro; that's what, ten cycles saved on the call/return? And I still need to test further to see if the bit 6 test is actually required. But at the moment, I will settle for clarity.

Neil

Re: Adventures in FAT32 with 65c02

Posted: Sat Dec 06, 2025 9:42 pm
by barnacle
Oi! What about FAT32 then?
All right, all right, I'm getting there!

But first a digression back to the information block. As well as the text strings, and the sector count, there are sundry other words of data scattered through the block. Most of them are redundant/obsolete references to functionality of spinny discs with CHS addressing, but there are one or two that might be useful.

Table 6-3 on page 60 of the CF card spec (above) has the details. One to consider is the word at word offset 49 decimal (byte offset 98), which has bit 9 set if LBA is supported. Every card I've looked at has this bit set, but if it isn't, you're going to have to address with CHS instead. Good luck!

Now back to our scheduled programmes...

Disc layout
What's on the CF is completely at the liberty of the program that puts the data there. The CF doesn't understand anything other than a sector number, and reading and writing from that. It doesn't even specify a big/little endian byte order, other than in its information block. It can be formatted with anything that you want: Microsoft flavours, Linux flavours, whatever. I choose FAT32 because it is so widely understood, and I ignore the other FAT variants for simplicity.

To repeat: the CF is nothing more than a long pile of sectors, any of which can hold anything you want. (There are internal mechanisms to deal with faulty sectors and error correction, but they're invisible to you unless a non-recoverable error occurs.)

The FAT file system uses little endian storage for all numbers. I will also use byte offsets into sectors to describe fields. If a reference is preceded by '$' (e.g. $123) then I am using hex numbers! (I may forget sometimes and use C style '0x' prefixes but I'll try not to do it too often.)

Microsoft's view of a disc
Microsoft defined a disc standard whereby the very first sector - sector zero - contains (x86) code to boot the system, or to print a message complaining that it is not bootable. This sector also contains up to four partition records, each of 16 bytes and starting at offset $1BE for the first. It's officially known as the master boot record, MBR, and every FAT disc has one.

Since I'm not running on x86, I don't care about the boot code, and since I'm restricting myself to a single partition, I'm ignoring the later partition records.

The partition has two fields we care about: at offset $4 (so $1C2 in the sector) is a byte indicating the partition format type. We're looking for $B or $C for FAT32, most likely $B. Since I formatted the disc, I don't even bother checking, but it would be good practice to do so. The second field starts at offset $8, $1C6 in the sector, and is a UINT32 word giving the address of the first sector in the partition.

Code: Select all

	; let's have a look at the first sector, should be the MBS
	; with the first partition information at 0x01be and the location
	; of the pointer to the partition itself at 0x1c6
	; (We could check 0x01b2 for the type code: 0x0b or 0x0c for fat32)
	; Note: up to three following partitions at 0x1ce, 0x1de, 1xee...
	; which we politely ignore.
	
	jsr cf_wait
	stz lba
	stz lba+1
	stz lba+2
	stz lba+3
	jsr cf_set_lba
	jsr cf_read				; get the MBR
	jsr crlf
	; when we have the MBR, check it to find the volume ID sector
	; which is the start of the partition, which we need to keep
	lda transient+0x1c9
	sta lba+3
	sta partition_begin_lba+3
	lda transient+0x1c8		; high address word
	sta lba+2
	sta partition_begin_lba+2
	lda transient+0x1c7
	sta lba+1
	sta partition_begin_lba+1
	lda transient+0x1c6		; low address word
	sta lba					; save the pointer
	sta partition_begin_lba
	; now we know where the fat32 volume ID is, fetch it
	jsr cf_set_lba
	jsr cf_read
	jsr crlf
(I read the file system location backwards because in testing I was printing each byte; there's nothing significant in that!)

In this code, I read the MBR at sector zero and inspect the first partition record to find where my file system actually lives. I copy that value to partition_begin_lba for safe keeping, and into the temporary 'lba' so cf_set_lba can feed it to the CF registers.

Code: Select all

;-----------------------------------------------------------------------
; preload the lba, setting the correct top bits
; prior to reading or writing a sector
;
cf_set_lba:
	lda lba
	sta CFREG3
	lda lba + 1
	sta CFREG4
	lda lba + 2
	sta CFREG5
	lda lba + 3
	and #0x0f
	ora #0xe0
	sta CFREG6
	rts
That done, I read the desired sector, and find... another start sector. This one is called the Volume Information Block (VIB).

Code: Select all

0200  EB 58 90 6D 6B 66 73 2E 66 61 74 00 02 08 20 00 .X.mkfs.fat... .          
0210  02 00 00 00 00 F8 00 00 3E 00 10 00 00 08 00 00 ........>.......          
0220  D4 37 0F 00 D0 03 00 00 00 00 00 00 02 00 00 00 .7..............          
0230  01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0240  80 01 29 87 8A FA 22 4E 4F 20 4E 41 4D 45 20 20 ..)..."NO NAME            
0250  20 20 46 41 54 33 32 20 20 20 0E 1F BE 77 7C AC   FAT32   ...w|.          
0260  22 C0 74 0B 56 B4 0E BB 07 00 CD 10 5E EB F0 32 ".t.V.......^..2          
0270  E4 CD 16 CD 19 EB FE 54 68 69 73 20 69 73 20 6E .......This is n          
0280  6F 74 20 61 20 62 6F 6F 74 61 62 6C 65 20 64 69 ot a bootable di          
0290  73 6B 2E 20 20 50 6C 65 61 73 65 20 69 6E 73 65 sk.  Please inse          
02A0  72 74 20 61 20 62 6F 6F 74 61 62 6C 65 20 66 6C rt a bootable fl          
02B0  6F 70 70 79 20 61 6E 64 0D 0A 70 72 65 73 73 20 oppy and..press           
02C0  61 6E 79 20 6B 65 79 20 74 6F 20 74 72 79 20 61 any key to try a          
02D0  67 61 69 6E 20 2E 2E 2E 20 0D 0A 00 00 00 00 00 gain ... .......          
02E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
02F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0300  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0310  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0320  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0330  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0340  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0350  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0360  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0370  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0380  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
0390  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
03A0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
03B0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
03C0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
03D0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
03E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................          
03F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA ..............U.
It contains another text block which doesn't concern us, but has some critical values describing the file system which I'll look at in the next installment. From here we have a lot of 32 bit arithmetic (boo!) but at least all the multiplies are powers of two :mrgreen:

Neil