Family Computer Disk System: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(→‎Hardware: restoring information from that split that was actually about the disk format and not .FDS)
(→‎References: ccovell wrote 2 tools with similar (and sometimes switched) names, linking both)
(16 intermediate revisions by 4 users not shown)
Line 1: Line 1:
The '''Famicom Disk System''' was a Japan-exclusive storage device for the Famicom, designed to reduce Nintendo's cost of making copies of games by switching from mask [[ROM]] chips to a storage medium based on Mitsumi's Quick Disk.
The '''Famicom Disk System''' was a Japan-exclusive storage device for the Famicom, designed to reduce Nintendo's cost of making copies of games by switching from mask [[ROM]] chips to a storage medium based on Mitsumi's Quick Disk.
Unfortunately for Nintendo, it also reduced the pirates' cost of making copies of games.
Unfortunately for Nintendo, it also reduced the pirates' cost of making copies of games.
The iNES mapper #20 is assigned to FDS.


== Overview ==
== Overview ==
Line 11: Line 9:
* Nametable [[mirroring]]: Controlled by mapper
* Nametable [[mirroring]]: Controlled by mapper
* Subject to [[bus conflict]]s: No
* Subject to [[bus conflict]]s: No
Games are stored on one or multiple disk sides. The FDS BIOS is used to load data from disks to PRG RAM or VRAM, and games can execute from there.
Games are stored on one or multiple disk sides. The [[FDS BIOS]] is used to load data from disks to PRG RAM or VRAM, and games can execute from there.


== Hardware ==
== Hardware ==
Line 17: Line 15:
The Famicom disk system comes in two parts : The disk drive and the RAM adapter.
The Famicom disk system comes in two parts : The disk drive and the RAM adapter.


The RAM adapter is a special shaped cartridge that contains the RAM chips and an ASIC with DRAM controller, IRQ hardware, sound generation hardware, serial interface for the disk drive, and parallel port. The Disk Drive has to be powered separately and is only connected to the Famicom/NES through the [[FDS RAM adaptor cable_pinout|serial cable]] to the RAM adapter.
The RAM adapter is a special shaped cartridge that contains the RAM chips and an ASIC with DRAM controller, IRQ hardware, sound generation hardware, serial interface for the disk drive, and parallel port. The Disk Drive has to be powered separately and is only connected to the Famicom/NES via a [[FDS RAM adaptor cable pinout|serial cable]] to the RAM adapter.


Most disk drives contains two motors: a rotation motor that spins the disk at a constant rate, and a tracking motor. However, the FDS only contains a single motor that does both at once. There is some mechanism that detects when the head reaches the end of the disc and make it return to the start, which makes a click sound. Because of this limitation, random access to the disc is impossible, in this regard, a FDS disk drive data access behaves similarly to a cassette tape (but faster). Data can only be accessed by spinning the disc and waiting until detection of the head reaching the inner edge of the disc, then waiting again until the desired data file is reached. A complete cycle through the entire disc takes about 7 seconds.
Most disk drives contain two motors: a spindle motor that spins the disk at a specific speed, and a stepper motor which moves the read/write head between each circular data track. By comparison, the FDS only contains a single motor that does both at once, so it instead stores the data in a single spiral-shaped track. There is a mechanism that detects when the head reaches the end of the disc and makes it return to the start (making an audible click). Because of this limitation, random access to the disc is impossible, making FDS disk drive data access behave similarly to a reel of tape (but ''much'' faster). Data can only be accessed by spinning the disc, waiting for the head to reach the inner edge of the disc, then waiting again until the desired data file is reached. A complete cycle through the entire disc takes about 7 seconds.


The disc drive only contains basic electronics, there is no "intelligence" in it; therefore, the serial interface almost directly represents what is stored on the disc.
The disc drive only contains basic electronics, there is no "intelligence" in it; therefore, the serial interface almost directly represents what is stored on the disc.


== FDS Disk Side format ==
=== Disks ===
 
Each disk side must be structured into block as follows :
 
1, 2, 3, 4, 3, 4, ...., 3, 4
 
The 3, 4 pattern should be repeated once per file present on the disk.
 
=== Gaps ===
 
Physically on the disc, there are "gaps" of 0 recorded between blocks and before the start of the disc.
Length of the gaps are as follows:
* Before the start of the disc : At least 26150 bits, 28300 typical.
* Gap between blocks : At least 480 bits, 976 bits typical.
 
Gaps are teminated by a single '1' bit. In terms of bytes, it would be $80, as the data is stored in little endian format.
 
=== CRCs ===
 
At the end of each block, a 16-bit CRC is stored. On loading, the CRC is *not* calculated by the 6502 in the BIOS, but by the RAM adapter, which monitors disc transfers and calculates the CRC. It will automatically send an error code if both CRCs doesn't match.
 
=== True disc capacity ===
 
In the commonly used [[FDS file format]], its disk image size is limited to 65500 bytes, but does not contain CRCs or gaps.
 
The actual disk capacity is somewhat larger, and there are a few variant disk formats that may have even more capacity.
 
== Block format ==
 
=== Disk info block (block 1) ===
 
{| class="wikitable"
|-
! Offset !! Length (bytes) !! Description !! Details
|-
| 0 (00h) || 1 || Block code || Raw byte: $01
|-
| 1 (01h) || 14 || Disk verification || Literal ASCII string: <tt>*NINTENDO-HVC*</tt><br />Used by BIOS to verify legitimate disk image
|-
| 15 (0Fh) || 1 || Manufacturer code || See [[#Manufacturer_codes|Manufacturer codes]]
|-
| 16 (10h) || 3 || Game name || 3-letter ASCII code per game (e.g. ZEL for The Legend of Zelda)
|-
| 19 (13h) || 1 || Game type || $20 = <tt>" "</tt> &mdash; Normal disk<br />$45 = <tt>"E"</tt> &mdash; Event (e.g. Japanese national [http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%83%9F%E3%83%AA%E3%83%BC%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF_%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0#.E3.83.87.E3.82.A3.E3.82.B9.E3.82.AF.E3.83.95.E3.82.A1.E3.82.AF.E3.82.B9 DiskFax] tournaments)<br />$52 = <tt>"R"</tt> &mdash; Reduction in price via advertising
|-
| 20 (14h) || 1 || Game version/revision number || Starts at $00, increments per revision
|-
| 21 (15h) || 1 || Side number || $00 = Side A, $01 = Side B.  Single-sided disks use $00
|-
| 22 (16h) || 1 || Disk number || First disk is $00, second is $01, etc.
|-
| 23 (17h) || 1 || Disk type || $00 = FMC ("normal card"), $01 = FSC ("card with shutter").  May correlate with [http://famicomworld.com/workshop/articles/product-codes-fmc-and-fsc/ FMC and FSC product codes]
|-
| 24 (18h) || 1 || Unknown || Speculative: (Err.10) Possibly indicates disk #; usually $00<br />Speculative: $00 = yellow disk, $01 = blue or gold disk, $FE = white disk, $FF = blue disk
|-
| 25 (19h) || 1 || Boot read file code || Refers to the file code/file number to load upon boot/start-up
|-
| 26 (1Ah) || 5 || Unknown || Raw bytes: $FF $FF $FF $FF $FF
|-
| 31 (1Fh) || 3 || Manufacturing date || See [[#Date_format|Date format]]
|-
| 34 (22h) || 1 || Country code || $49 = Japan
|-
| 35 (23h) || 1 || Unknown || Raw byte: $61.  Speculative: Region code?
|-
| 36 (24h) || 1 || Unknown || Raw byte: $00.  Speculative: Location/site?
|-
| 37 (25h) || 2 || Unknown || Raw bytes: $00 $02
|-
| 39 (27h) || 5 || Unknown || Speculative: some kind of game information representation?
|-
| 44 (2Ch) || 3 || "Rewritten disk" date || See [[#Date_format|Date format]].  It's speculated this refers to the date the disk was formatted and rewritten by something like a [http://www.nintendolife.com/news/2014/03/weirdness_this_footage_of_the_famicom_disk_writer_kiosk_is_a_bit_awesome Disk Writer kiosk].  In the case of an original (non-copied) disk, this should be the same as [[#Manufacturing_date|Manufacturing date]]
|-
| 47 (2Fh) || 1 || Unknown ||
|-
| 48 (30h) || 1 || Unknown || Raw byte: $80
|-
| 49 (31h) || 2 || Disk Writer serial number ||
|-
| 51 (33h) || 1 || Unknown || Raw byte: $07
|-
| 52 (34h) || 1 || Disk rewrite count || Value stored in [[wikipedia:Binary-coded_decimal|BCD]] format.  $00 = Original (no copies).  Also see [[#Price|Price]]
|-
| 53 (35h) || 1 || Actual disk side || $00 = Side A, $01 = Side B
|-
| 54 (36h) || 1 || Unknown ||
|-
| 55 (37h) || 1 || Price || See [[#Price|Price]]
|-
| 56 (38h) || 2 || CRC || (Omitted from .FDS files)
|-
|}
 
The <tt>*NINTENDO-HVC*</tt> string, stored in ASCII, proves that this is a FDS disk. If the string doesn't match, the BIOS will refuse to read the disk further.
 
If the FDS is started with a disk whose side number and disk number aren't both $00, it will be prompted to insert the first disk side. However, some games make this number $00, even for the second disk to make it bootable too.
 
All files with IDs smaller or equals to the ''boot read file code'' will be loaded when the game is booting.
 
The FDS also has a trademark security system similar to [http://segaretro.org/TradeMark_Security_System what Sega used on some of its consoles]:
 
:The 224-byte text at PPU $2800-$28DF must match the data in the BIOS, starting at $ED37.
 
:This data contains an English licensing message stored in the SMB1/Zelda character encoding. The BIOS ensures that it is present in the nametable and displays it on the screen for several seconds after the boot files are loaded.
 
:Traditionally, the first file on a disk is a nametable type file loaded into $2800, which is named <code>KYODAKU-</code> (きょだく or [[wiktionary:許|許]][[wiktionary:諾|諾]] means approval). It must be one of the boot files, or the license message test will fail (error 20) before it proceeds to run the program.
 
:The license screen test and display can be bypassed by using a boot file to enable NMIs, which will interrupt the boot loading process early.<ref>[//forums.nesdev.org/viewtopic.php?p=194826#p194826 Forum post]: Skipping FDS license screen?</ref>
 
==== Date format ====
 
All bytes are stored in BCD format, in order of year, month, and day.  To accurately calculate they year, add 1925 to the BCD value.  For example, values of $63 $11 $28 would represent November ($11) 28th ($28), 1988 ($63 + 1925).  The "magic year" of 1925 comes from the Japanese [[wikipedia:Sh%C5%8Dwa_period|Shōwa period]], despite the era only lasting from 1925 until 1989.  Titles manufactured or updated later than 1989 are considered legitimate (e.g. $83 + 1925 = 2008).
 
==== Price ====
 
When the '''Disk rewrite count''' field is $00, the '''Price''' field represents the cost of a "new/original" disk.  In this situation, a value of $01 means 3400円 (3400 yen), and a value of $03 also means 3400円 but includes peripherals.  The example given by Enri in his Famicom Disk System documentation mentions the game [http://ja.wikipedia.org/wiki/%E3%81%A8%E3%81%B3%E3%81%A0%E3%81%9B%E5%A4%A7%E4%BD%9C%E6%88%A6 とびだせ大作戦 (Tobidase Daisakusen/3D Worldrunner)], where both sales of the game with and without the [http://homepage3.nifty.com/poco-pen/s-te.htm とびだせメガネ (Tobidase 3D glasses)] cost 3400円.  The reason for the delineation within the actual '''Price''' field is unknown.
 
When the '''Disk rewrite count''' field is $01, the '''Price''' field represents incurred costs of disk rewriting (such as that done by a Disk Writer kiosk).  A value of $00 means 500円 (which appears to be specific to Mario Brothers via an in-box advertisement promising that all disk rewrites would only cost 500円), while a value of $01 means 600円.


==== Manufacturer codes ====
The FDS disk is a modified version of the Mitsumi Quick Disk.


The manufacturer codes are similar (quite possibly identical) to that known on the GameBoy.  There are some differences, however (for example, $A6).
See:
 
* [[FDS disk format]] - the disk data format and file structure
{| class="wikitable"
* [[FDS file format]] (.FDS) - an archival file format for storing and emulating FDS disks
|-
! Value !! Company (English) !! Company (Japanese)
|-
| $00 || <unlicensed> || <非公認>
|-
| $01 || Nintendo || 任天堂
|-
| $08 || Capcom || カプコン
|-
| $0A || Jaleco || ジャレコ
|-
| $18 || Hudson Soft || ハドソン
|-
| $49 || Irem || アイレム
|-
| $4A || Gakken || 学習研究社
|-
| $8B || BulletProof Software (BPS) || BPS
|-
| $99 || Pack-In-Video || パックインビデオ
|-
| $9B || Tecmo || テクモ
|-
| $9C || Imagineer || イマジニア
|-
| $A2 || Scorpion Soft || スコーピオンソフト
|-
| $A4 || Konami || コナミ
|-
| $A6 || Kawada Co., Ltd. || 河田
|-
| $A7 || Takara || タカラ
|-
| $A8 || Royal Industries || ロイヤル工業
|-
| $AC || Toei Animation || 東映動画
|-
| $AF || Namco || ナムコ
|-
| $B1 || ASCII Corporation || アスキー
|-
| $B2 || Bandai || バンダイ
|-
| $B3 || Soft Pro Inc. || ソフトプロ
|-
| $B6 || HAL Laboratory || HAL研究所
|-
| $BB || Sunsoft || サンソフト
|-
| $BC || Toshiba EMI || 東芝EMI
|-
| $C0 || Taito || タイトー
|-
| $C1 || Sunsoft / Ask Co., Ltd. || サンソフト アスク講談社
|-
| $C2 || Kemco || ケムコ
|-
| $C3 || Square || スクウェア
|-
| $C4 || Tokuma Shoten || 徳間書店
|-
| $C5 || Data East || データイースト
|-
| $C6 || Tonkin House/Tokyo Shoseki || トンキンハウス
|-
| $C7 || East Cube || イーストキューブ
|-
| $CA || Konami / Ultra / Palcom || コナミ
|-
| $CB || NTVIC / VAP || バップ
|-
| $CC || Use Co., Ltd. || ユース
|-
| $CE || Pony Canyon / FCI || ポニーキャニオン
|-
| $D1 || Sofel || ソフエル
|-
| $D2 || Bothtec, Inc. || ボーステック
|-
| $DB || Hiro Co., Ltd. || ヒロ
|-
| $E7 || Athena || アテナ
|-
| $EB || Atlus || アトラス
|-
|}
 
=== File amount block (block 2) ===
 
This block contains the total number of files recorded on disk.
 
SIZE  CONTENTS
1        $02
1      File Amount
 
More files might exist on the disk, but the BIOS load routine will ignore them, those files are called "hidden" files.
Some games have a simple copy protection this way : They have their own loading routine similar to the one from the BIOS but hard-code the file amount to a higher number, which will allow for loading hidden files. This also allows the game to load faster because the BIOS will stop reading the disc after the last non-hidden file.
 
=== File header block  (block 3) ===
 
SIZE    CONTENTS
1        $03
1        File Number 
1        File Indicate Code (file identification code) 
          ID specified at disk-read function call
8        File Name
2        File Address (16-bit little endian)
          the destination address when loading
2        File Size (16-bit little endian)
1        Kind of File 
          0:Program (PRAM) 
          1:Character (CRAM) 
          2:Name table (VRAM)
 
The file Number must go in increasing order, first file is 0.
File IDs can be freely assigned, and this is the number which will decide which file is loaded from the disk (instead of the file number). An ID smaller than the boot number means the file is a boot file, and will be loaded on first start up.
 
File names are uppercase ASCII.
 
=== File data block (block 4) ===
 
SIZE      CONTENTS
1        $04
--        disk data


== Registers ==
== Registers ==
Line 363: Line 120:
               1: Enable IRQ when the drive becomes ready for  
               1: Enable IRQ when the drive becomes ready for  


A FDS game that wants to change mirroring probably don't want to touch motor related bits, so it should do a read-modify-write from the pseudo registers (see below).
A FDS game that wants to change mirroring probably don't want to touch motor related bits, so it should do a read-modify-write from the pseudo registers (see [[FDS BIOS]]).


=== External connector ($4026) ===
=== External connector ($4026) ===
Line 373: Line 130:
  ---------
  ---------
  IExB xxTD
  IExB xxTD
  ||||  ||
  || |  ||
  ||||  |+- Timer Interrupt (1: an IRQ occurred)
  || |  |+- Timer Interrupt (1: an IRQ occurred)
  ||||  +-- Byte transfer flag. Set every time 8 bits have been transfered between the RAM adaptor & disk drive (service $4024/$4031).  
  || |  +-- Byte transfer flag. Set every time 8 bits have been transfered between the RAM adaptor & disk drive (service $4024/$4031).  
  ||||      Reset when $4024, $4031, or $4030 has been serviced.
  || |      Reset when $4024, $4031, or $4030 has been serviced.
  |||+------ CRC control (0: CRC passed; 1: CRC error)
  || +------ CRC control (0: CRC passed; 1: CRC error)
  |+-------- End of Head (1 when disk head is on the most inner track)
  |+-------- End of Head (1 when disk head is on the most inner track)
  +--------- Disk Data Read/Write Enable (1 when disk is readable/writable)
  +--------- Disk Data Read/Write Enable (1 when disk is readable/writable)
 
=== Read data register ($4031) ===
=== Read data register ($4031) ===


Line 391: Line 148:
       |||
       |||
       ||+- Disk flag  (0: Disk inserted; 1: Disk not inserted)
       ||+- Disk flag  (0: Disk inserted; 1: Disk not inserted)
       |+-- Ready flag (0: Disk read; 1: Disk not ready)
       |+-- Ready flag (0: Disk readу; 1: Disk not ready)
       +--- Protect flag (0: Not write protected; 1: Write protected or disk ejected)
       +--- Protect flag (0: Not write protected; 1: Write protected or disk ejected)


Line 400: Line 157:
  |||| ||||
  |||| ||||
  |+++-++++- Input from expansion terminal where there's a shutter on the back of the ram card.
  |+++-++++- Input from expansion terminal where there's a shutter on the back of the ram card.
  +--------- Battery status (0: Good; 1: Voltage is low).
  +--------- Battery status (0: Voltage is low; 1: Good).


When a bit is clear in $4026 port it will read back as '0' here (including battery bit) because of how open collector input works.
When a bit is clear in $4026 port it will read back as '0' here (including battery bit) because of how open collector input works. Battery bit should be checked when the motor is on, otherwise it always will be read as 0.


=== Sound ($4040-$4092) ===
=== Sound ($4040-$4092) ===
Line 408: Line 165:
For details on sound information, see [[FDS_audio|FDS audio]].
For details on sound information, see [[FDS_audio|FDS audio]].


== Pseudo-registers ==
== BIOS ==
The FDS BIOS uses several bytes on the zeropage. They are used to overcome the problem that NES/FDS registers are write only, so it is effectively possible to modify only one bit of them without affecting other bits.
 
[$FF]:  value last written to [[PPUCTRL|$2000]]  $80 on reset.
[$FE]:  value last written to [[PPUMASK|$2001]]  $06 on reset
[$FD]:  value last written to [[PPUSCROLL|$2005/1]] $00 on reset.
[$FC]:  value last written to [[PPUSCROLL|$2005/2]] $00 on reset.
[$FB]:  value last written to [[Controller_port_registers|$4016]]  $00 on reset.
[$FA]:  value last written to $4025  $2E on reset.
[$F9]:  value last written to $4026  $FF on reset.
$F5..$F8 : Used by controller read routines
$00..$0F is used as temporary memory for the BIOS. The main program can use it as temporary memory too.
 
The FDS BIOS also uses 4 bytes at the lower end of the stack page to control behaviour of interrupt/reset vectors:
 
[$0102]/[$0103]: PC action on reset
[$0101]:        PC action on IRQ. set to $80 on reset
[$0100]:        PC action on NMI. set to $C0 on reset
 
RESET:
($DFFC):        disk game reset vector    (if [$0102] = $35, and [$0103] = $53 or $AC)
 
IRQ:
($DFFE):        disk game IRQ vector      (if [$0101] = %11xxxxxx)
  $E1EF :        BIOS acknowledge and delay (if [$0101] = %10xxxxxx)
  $E1CE :        BIOS disk transfer        (if [$0101] = %01xxxxxx)
  $E1D9 :        BIOS disk skip bytes      (if [$0101] = %00xxxxxx)
 
NMI:
($DFFA):        disk game NMI vector #3    (if [$0100] = %11xxxxxx)
($DFF8):        disk game NMI vector #2    (if [$0100] = %10xxxxxx)
($DFF6):        disk game NMI vector #1    (if [$0100] = %01xxxxxx)
  $E19D :        BIOS disable NMI          (if [$0100] = %00xxxxxx)
 
A few important notes :
* After loading the boot files, $102 is set to $35 so that the ($DFFC) vector is used and the BIOS is skipped.
* $103 indicate reset type : $AC = first boot of the game, $53 = the game was soft-reseted by the user
* To use a custom IRQ routine, $c0 should be manually written to $101
* There are 3 possible NMI vectors, #3 is used by default.
* On first start, the mirroring is set to horizontal, the stack pointer is $ff, and the I flag is ''clear''. System RAM is filled with values used by the BIOS, and PRG RAM is uninitialized, except for parts of it which have files loaded in.
 
== BIOS calls ==


=== Disk access routines ===
The FDS contains a fixed 8KB BIOS at $E000-FFFF. This controls the Famicom at power-on and reset, dispatches the NMI and IRQ, and offers an API for accessing the [[FDS disk format|data on disk]].


* Routines takes one or two pointers as arguments. Those are placed directly after the JSR instruction: the subroutines uses the return address in stack to fetch the pointers and fix the return address.
See: [[FDS BIOS]]
* Memory at $00-$0f will be affected by those routines
* Unlike the vast majority of disk drives, the FDS lacks any kind of intelligent tracking system. All BIOS load and save functions will do access to the whole disk, no matter which data they load/save. A simple way to overcome this problem is to have a custom loading routine, similar to the BIOS one but forcing the # of files to a smaller number than it actually is. That way the later files are not accessed at all and the earlier files load faster. Of course the maximal time is still taken when loading files that are late on the disk.
* All non-disk IRQ sources (timer, DMC and APU frame) should be properly disabled before calling any of these routines. The value at [$0101] however, is preserved on entry, and restored on exit.
* On return of those routines, A = $00 means no error occurred, other number is error #. Main program should test if an error occurred with the BEQ or BNE instruction, BEQ will branch if no error, and BNE will branch if there is an error.
* The structures defined below are used to identify files & disks in the access routines. The argument pointers should point to these structures in the program.
 
{| border="1" cellspacing="0" cellpadding="3"
| Address || Name || Input parameters || Output parameters || Description
|-
| $e1f8 || LoadFiles || Pointer to Disk ID, Pointer to File List || A = error #, Y = # of files loaded || Loads files specified by DiskID into memory from disk. Load addresses are decided by the file's header.
|-
| $e237 || AppendFile || Pointer to Disk ID, Pointer to File Header || A = error # || Appends the file data given by DiskID to the disk. This means that the file is tacked onto the end of the disk, and the disk file count is incremented. The file is then read back to verify the write. If an error occurs during verification, the disk's file count is decremented (logically hiding the written file).
|-
| $e239 || WriteFile || Pointer to Disk ID, Pointer to File Header, A = file # || A = error # || Same as "Append File", but instead of writing the file to the  end of the disk, A specifies the sequential position on the disk to write the file (0 is the first). This also has the effect of setting the disk's file count to the A value, therefore logically hiding any other files that may reside after the written one.
|-
| $e2b7 || CheckFileCount || Pointer to Disk ID, A = # to set file count to || A = error # || Reads in disk's file count, compares it to A, then sets the disk's file count to A.
|-
| $e2bb || AdjustFileCount || Pointer to Disk ID, A = number to reduce current file count by || A = error # || Reads in disk's file count, decrements it by A, then writes the new value back.
|-
| $e301 || SetFileCount1 || Pointer to Disk ID, A = file count minus one = # of the last file || A = error # || Set the file count to A + 1
|-
| $e305 || SetFileCount || Pointer to Disk ID, A = file count || A = error # || Set the file count to A
|-
| $e32a || GetDiskInfo || Pointer to Disk Info || A = error # || Fills DiskInfo up with data read off the current disk.
|-
|}
 
=== Low-Level Disk access routines ===
 
{| border="1" cellspacing="0" cellpadding="3"
| Address || Name || Input parameters || Output parameters || Description
|-
| $e445 || CheckDiskHeader || Pointer to 10 byte string at $00 ||  || Compares the first 10 bytes on the disk coming after the FDS string, to 10 bytes pointed to by Ptr($00). To bypass the checking of any byte, a -1 can be placed in the equivelant place in the compare string.  Otherwise, if the comparison fails, an appropriate error will be generated.
|-
| $e484 || GetNumFiles ||  ||  || Reads number of files stored on disk, stores the result in $06
|-
| $e492 || SetNumFiles ||  || A = number of files || Writes new number of files to disk header.
|-
| $e4a0 || FileMatchTest || Pointer to FileID list at $02 ||  || Uses a byte string pointed at by Ptr($02) to tell the disk system which files to load.  The file ID's number is searched for in the string.  If an exact match is found, [$09] is 0'd, and [$0E] is incremented.  If no matches are found after 20 bytes, or a -1 entry is encountered, [$09] is set to -1.  If the first byte in the string is -1, the BootID number is used for matching files (any FileID that is not greater than the BootID qualifies as a match).
|-
| $e4da || SkipFiles || Number of files to skip in $06 ||  || Skips over specified number of files.
|-
|}
 
=== Example code how to load files ===
 
<pre>
Load
  jsr LoadFiles
  .dw DiskID
  .dw LoadList
  bne _Error        ;Check if there is an error
  rts
_Error
  jsr PrintError      ;If so print the error number and message to screen (include side/disk changing prompts)
_sideError
  lda $4032
  and #$01
  beq _sideError    ;Wait until disk is ejected
_insert
  lda $4032
  and #$01
  bne _instert      ;Wait until disk is inserted
  jmp Load
 
DiskID
  .db $01        ;Manufacturer code
  .db "NAME"    ;4-letter code of game
  .db $00        ;Version
  .db $01        ;Disk side
  .db $00        ;Disk number
  .db $00, $00  ;Extra disk IDs
 
LoadList          ;In this example the files with IDs equal to $02, $03 or $04 will be loaded into memory
  .db $02, $03, $04, $ff
</pre>
 
=== Error list ===
 
Message in bold is the official BIOS message (if there is one) followed by an explanation
*$00: no error
*$01: '''disk set''', ($4032.0) disk not set
*$02: '''battery''', ($4033.7) power supply failure
*$03: ($4032.2) disk is write protected
*$04:  Wrong maker ID
*$05:  Wrong game
*$06:  Wrong game version
*$07:  '''a,b side''', wrong side number
*$08:  '''disk no.''', wrong disk number
*$09:  wrong additional disk ID 1
*$0a:  wrong additional disk ID 2
*$20:  '''disk trouble''', approval check failed
*$21: '''disk trouble''', '*NINTENDO-HVC*' string in block 1 doesn't match
*$22: '''disk trouble''', block type 1 expected
*$23: '''disk trouble''', block type 2 expected
*$24: '''disk trouble''', block type 3 expected
*$25: '''disk trouble''', block type 4 expected
*$27: '''disk trouble''', ($4030.4) block failed CRC
*$28: '''disk trouble''', ($4030.6) file ends prematurely during read
*$29: '''disk trouble''', ($4030.6) file ends prematurely during write
*$30: '''disk trouble''', ($4032.1) disk is full
 
=== Disk ID structure ===
 
This is a commonly used string. It consists of 10 bytes which are all compared directly against bytes 15..24 (right after the '*NINTENDO-HVC*' string) of the disk's header block (block type 1; always the first one on the disk). If any of the bytes fail the comparison, an appropriate error # is generated. Comparisons of immaterial data can be skipped by placing an $FF byte in the appropriate place in the DiskID string (for example, when the ROM BIOS boots a disk, it sets all the fields in the DiskID string to -1, except disk side #, and disk #, which are set to 0 (so these fields have to match 0)). The following chart describes the DiskID structure, and the error #'s returned when a comparison fails.
 
offset size error# description
------ ---- ------ -----------
0 1 $04 game manufacturer code
1 4 $05 game ASCII name string
5 1 $06 game version
6 1 $07 disk side #
7 1 $08 disk #
8 1 $09 extra disk # data
9 1 $10 extra disk # data
A -
 
=== File list structure ===
 
This is a list of 1-byte IDs of files to load. All files that matches any ID in the list are loaded. A list of up to 20 IDs is possible at a time, smaller lists should be terminated by a $ff byte (this implies a file ID can never be $ff).
 
Multiple files are loaded in the order as they exist on the disk, not in the order of the list.
 
=== File header structure ===
 
This structure is specified when a file is to be written to the disk. The first 14 bytes of this structure directly specify the data to use for generating a file header block (type 3, bytes [2..15]) to write to disk. The last 2 entries concern the file data to be written to disk (block type 4). The following is a table describing the FileHeader structure.
 
offset size description
------ ---- -----------
00 1 file ID code
01 8 file name
09 2 load address
0B 2 file data size
0D 1 file type ($00 : Program; $01 : Character; $02 : Nametable)
0E 2 source address of file data (NOT written to disk)
10 1 source address type ($00 : RAM, $01 : VRAM)
11 -
 
=== Disk information structure ===
 
This is a data structure returned by a subroutine, of collected information
from the disk (list of files on disk, disk size, etc.). The following table
is a description of that structure.
 
offset size
------  ----
0 1 game manufacturer code
1 4 game ASCII name string
5 1 game version
6 1 disk side #
7 1 disk #
8 1 extra disk # data
9 1 extra disk # data
A 1 # of files on disk
 
(the following block will appear for as many files as the "# of files on
disk" byte indicates)
 
B 1 file ID code
C 8 file name (ASCII)
 
(the following is present after the last file info block. Disk size is equal
to the sum of each file's size entry, plus an extra 261 per file.)
 
x 1 disk size high byte
x+1 1 disk size low  byte
x+2 -
 
== Other BIOS calls ==
 
{| border="1" cellspacing="0" cellpadding="3"
| Address || Name || Input parameters || Output parameters || Affected RAM/Registers || Description
|-
| $e149 || Delay132 || || || || 132 clock cycle delay
|-
| $e153 || Delayms || || || X, Y || Delay routine, Y = delay in ms (approximate)
|-
| $e161 || DisPFObj || || || A, $fe || Disable sprites and background
|-
| $e16b || EnPFObj || || || A, $fe || Enable sprites and background
|-
| $e171 || DisObj || || || A, $fe || Disable sprites
|-
| $e178 || EnObj || || || A, $fe || Enable sprites
|-
| $e17e || DisPF || || || A, $fe || Disable background
|-
| $e185 || EnPF || || || A, $fe || Enable background
|-
| $e1b2 || VINTWait || || || $ff || Wait until next VBlank NMI fires, and return (for programs that does it the "everything in main" way). NMI vector selection at $100 is preserved, but further VBlanks are disabled.
|-
| $e7bb || VRAMStructWrite || Pointer to VRAM buffer to be written || || A, X, Y, $00, $01, $ff || Set VRAM increment to 1 (clear [[PPUCTRL]]/$ff bit 2), and write a VRAM buffer to VRAM. Read below for information on the structure.
|-
| $e844 || FetchDirectPtr || || $00, $01 = pointer fetched || A, X, Y, $05, $06 || Fetch a direct pointer from the stack (the pointer should be placed after the return address of the routine that calls this one (see "important notes" above)), save the pointer at ($00) and fix the return address.
|-
| $e86a || WriteVRAMBuffer || || || A, X, Y, $301, $302 || Write the VRAM Buffer at $302 to VRAM. Read below for information on the structure.
|-
| $e8b3 || ReadVRAMBuffer || X = start address of read buffer, Y = # of bytes to read || || A, X, Y || Read individual bytes from VRAM to the VRAMBuffer. (see notes below)
|-
| $e8d2 || PrepareVRAMString || A = High VRAM address, X = Low VRAM address, Y = string length, Direct Pointer = data to be written to VRAM || A = $ff : no error, A = $01 : string didn't fit in buffer || A, X, Y, $00, $01, $02, $03, $04, $05, $06 || This routine copies pointed data into the VRAM buffer.
|-
| $e8e1 || PrepareVRAMStrings || A = High VRAM address, X = Low VRAM address, Direct pointer = data to be written to VRAM || A = $ff : no error, A = $01 : data didn't fit in buffer || A, X, Y, $00, $01, $02, $03, $04, $05, $06 || This routine copies a 2D string into the VRAM buffer. The first byte of the data determines the width and height of the following string (in tiles): Upper nybble = height, lower nybble = width.
|-
| $e94f || GetVRAMBufferByte || X = starting index of read buffer, Y = # of address to compare (starting at 1), $00, $01 = address to read from || carry clear : a previously read byte was returned, carry set : no byte was read, should wait next call to ReadVRAMBuffer || A, X, Y || This routine was likely planned to be used in order to avoid useless latency on a VRAM reads (see notes below). It compares the VRAM address in ($00) with the Yth (starting at 1) address of the read buffer. If both addresses match, the corresponding data byte is returned exit with c clear. If the addresses are different, the buffer address is overwritten by the address in ($00) and the routine exit with c set.
|-
| $e97d || Pixel2NamConv || $02 = Pixel X cord, $03 = Pixel Y cord || $00 = High nametable address, $01 = Low nametable address || A || This routine convert pixel screen coordinates to corresponding nametable address (assumes no scrolling, and points to first nametable at $2000-$23ff).
|-
| $e997 || Nam2PixelConv || $00 = High nametable address, $01 = low nametable address || $02 = Pixel X cord, $03 = Pixel Y cord || A || This routine convert a nametable address to corresponding pixel coordinates (assume no scrolling).
|-
| $e9b1 || Random || X = Zero Page address where the random bytes are placed, Y = # of shift register bytes (normally $02) || || A, X, Y, $00 || This is a shift-register based random number generator, normally takes 2 bytes (using more won't affect random sequence). On reset the program is supposed to write some non-zero values here (BIOS uses writes $d0, $d0), and call this routine several times before the data is actually random. Each call of this routine will shift the bytes ''right''.
|-
| $e9c8 || SpriteDMA || || || A || This routine does sprite DMA from RAM $200-$2ff
|-
| $e9d3 || CounterLogic || A, Y = end Zeropage address of counters, X = start zeropage address of counters || || A, X, $00 || This decrements several counters in Zeropage. The first counter is a decimal counter 9 -> 8 -> 7 -> ... -> 1 -> 0 -> 9 -> ... Counters 1...A are simply decremented and stays at 0. Counters A+1...Y are decremented when the first counter does a 0 -> 9 transition, and stays at 0.
|-
| $e9eb || ReadPads || || $f5 = Joypad #1 data, $f6 = Joypad #2 data || A, X, $00, $01, || This read hardwired famicom joypads.
|-
| $ea1a || ReadDownPads || || $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data || A, X, $00, $01 || This reads hardwired famicom joypads, and detect up->down button transitions
|-
| $ea1f || ReadOrDownPads || || $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data || A, X, $00, $01 || This read both hardwired famicom and expansion port joypads and detect up->down button transitions.
|-
| $ea36 || ReadDownVerifyPads || || $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data || A, X, $00, $01 || This reads hardwired Famicom joypads, and detect up->down button transitions. Data is read until two consecutive read matches to work around the DMC reading glitches.
|-
| $ea4c || ReadOrDownVerifyPads || || $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data || A, X, $00, $01 || This read both hardwired famicom and expansion port joypads and detect up->down button transitions. Data is read until two consecutive read matches to work around the DMC reading glitches.
|-
| $ea68 || ReadDownExpPads || $f1-$f4 = up->down transitions, $f5-$f8 = Joypad data in the order : Pad1, Pad2, Expansion1, Expansion2 || || A, X, $00, $01 || This read both hardwired famicom and expansion port joypad, but stores their data separately instead of ORing them together like the other routines does. This routine is NOT DMC fortified.
|-
| $ea84 || VRAMFill || A = High VRAM Address (aka tile row #), X = Fill value, Y = # of tile rows OR attribute fill data || || A, X, Y, $00, $01, $02 || This routine does 2 things : If A < $20, it fills pattern table data with the value in X for 16 * Y tiles. If A >= $20, it fills the corresponding nametable with the value in X and attribute table with the value in Y.
|-
| $ead2 || MemFill || A = fill value, X = first page #, Y = last page # || || A, X, Y, $00, $01 || This routines fills RAM pages with specified value.
|-
| $eaea || SetScroll || || || A || This routine set scroll registers according to values in $fc, $fd and $ff. Should typically be called in VBlank after VRAM updates
|-
| $eafd || JumpEngine || A = Jump table entry || || A, X, Y, $00, $01 || The instruction calling this is supposed to be followed by a jump table (16-bit pointers little endian, up to 128 pointers). A is the entry # to jump to, return address on stack is used to get jump table entries.
|-
| $eb13 || ReadKeyboard || || || || Read Family Basic Keyboard expansion (detail is under analysis)
|-
| $eb66 || LoadTileset || A = Low VRAM Address & Flags, Y = Hi VRAM Address, X = # of tiles to transfer to/from VRAM || || A, X, Y, $00, $01, $02, $03, $04 || This routine can read and write 2BP and 1BP tilesets to/from VRAM. See appendix below about the flags.
|-
| $ec22 || unk_EC22|| || || || Some kind of logic that some games use. (detail is under analysis)
|}
 
 
=== VRAM write transfer structure ===
 
The structure of VRAM buffers are as follows:
 
SIZE  CONTENTS
2      VRAM Address (big endian)
1      bit 0-5 length of data ($0 means a length of 64)
        bit 6 : 0 = copy, 1 = fill
        bit 7 : 0 = increment by 1, 1 = increment by 32
n      Data to copy to VRAM
.....  repeated as many times as needed
1      $ff
 
* The main structure is terminated by a $ff byte (High address is always supposed to be in $00..$3f range)
* $4c is a "call" command. The 2 bytes that follow is the address of a sub-VRAM structure. The sub-structure can call another sub-structure and so on.
* $60 is a "return" command. It will terminate a sub-structure.
* If Fill mode is used, the routine takes only 1 byte of data which is repeated.
 
=== VRAM Buffer notes ===
 
The VRAM buffer is located at $300-$3xx. $300 holds the size of the buffer (maximum), and $301 holds the end index of the buffer. The actual buffer lies at $302-$3xx, and is of variable length.
 
* $300 is initialized to the value $7d, effectively making the buffer lie at $300-$37f. It's possible to change the value here to make it bigger or smaller, but the biggest possible value is $fd, making the buffer lie at $300-$3ff.
* Format of the buffer is equivalent to the VRAM structure above, except that there are no sub-structures, no increment by 32 flag and no fill flag.
* For this reason, the VRAM buffer at $302 can be used as a sub-structure.
* A call to WriteVRAMBuffer will execute faster than a call to VRAMStructWrite with $302 as an argument, but both will have the same effect.
 
=== Read routines and VRAM buffer ===
 
Unlike the write routines which are very complete, the read routines are somewhat incomplete and their functionality have to be completed by the user (which limits their usefulness). Most of what follows is some sort of speculation about the usefulness of the incomplete read routines of the BIOS.
 
The read buffer is a part of the VRAM buffer at $300-$3xx, but the same location can't be used to transfer read and writes on the same frame (for example a RMW operation). Instead the user must manually split the buffer in two parts, the "read" buffer and the "write" buffer. The read buffer is probably supposed to always lies after the write buffer, so that when the read buffer is in use, the value in $300 (size of the write buffer) should be adjusted accordingly. The user should manually keep track of how many bytes are used for the read buffer, as well as the starting point of the read buffer (they are argument to the ReadVRAMBuffer function).
 
The structure of the read buffer itself is trivial - only single bytes are read (there's no runs of data). Very likely the purpose is to read one or a few individual attribute table data, in order to change the color mapping of individual 2x2 squares instead of whole 4x4 square (read-modify-write operation).
All reads are mapped to a structure of 3 bytes in the read buffer :
SIZE  CONTENTS
2      VRAM Address (big endian)
1      data
 
Therfore, for each byte which is read from VRAM, 3 bytes have to be reserved in the read buffer.
Once data from VRAM has been read, if it must be written back after a modification, the user need to copy it to the write buffer manually.
 
The GetVRAMBufferByte was probably designed to prevent reading attribute tables when it has already been read in the past by a call to ReadVRAMBuffer. For example if you don't know if you're modifying another area of the same 4x4 attribute byte as previously (no need to read again), or if you're modifying part of another 4x4 attribute byte (need to read from VRAM). If the routine return with C=0, the accumulator contains the attribute byte that was already read, but if C=1, it means we should first call ReadVRAMBuffer before getting the data we want.
 
Since reading VRAM for nothing is not a harmful operation, the user can call ReadVRAMBuffer every frame even if nobody has to be read (no need for check flags) which is probably why these routines are less complete than the write buffer related routines.
 
=== Load Tileset notes ===
 
The ''flags'' parameters are as follows:
 
7  bit  0
---------
AAAA MMIT
|||| ||||
|||| |||+- Fill bit
|||| ||+-- Transfer direction (0 = Write tiles, 1 = Read tiles)
|||| ++--- Bitplane type (see below)
++++------ Low VRAM Address (aka tile # within a row)
        1st bitplane 2nd bitplane    Description
        ----------- -----------      -----------
    0:  data          data+8          Normal 2-bitplane graphics
    1:  data          fill bit        Single bitplane graphics. Fill bit clear : Use colors 0&1  Fill bit set : Use colors 2&3
    2:  fill bit      data            Single bitplane graphics. Fill bit clear : Use colors 0&2  Fill bit set : Use colors 1&3
    3:  data^fill bit  data            Single bitplane graphics. Fill bit clear : Use colors 0&3  Fill bit set : Use colors 1&2
 
This makes it possible for single bitplane tiles to take all possible color schemes when they end up in VRAM. However, it is not possible to (natively) load single bitplane graphics directly from the disk into VRAM; it should be loaded into PRG-RAM before transferring the data into VRAM. In read mode, all non "data" bitplanes are replaced by dummy reads.


== See Also ==
== See Also ==


* [[FDS file format]]
* [[FDS BIOS]]
* [[FDS disk format]]
* [[FDS file format]] ('''.FDS''')
* [[FDS audio]]
* [[FDS audio]]
* [[FDS RAM adaptor cable pinout]]
* [[FDS RAM adaptor cable pinout]]
* [//forums.nesdev.org/viewtopic.php?p=193235#p193235 Forum post:] Simple FDS example for ca65
* [[RP2C33 pinout]]
* [[iNES mapper 020|iNES mapper 20]] - Reserved for FDS dumps, but not widely used for it.
* [https://github.com/bbbradsmith/NES-ca65-example/tree/fds Github repository:] Simple FDS example for ca65
* [//forums.nesdev.org/viewtopic.php?p=194826#p194826 Forum post:] Skipping the FDS license screen
* [//forums.nesdev.org/viewtopic.php?p=194826#p194826 Forum post:] Skipping the FDS license screen


== References ==
== References ==


* [//nesdev.org/FDS%20technical%20reference.txt FDS technical reference.txt] by Brad Taylor
* [http://www43.tok2.com/home/cmpslv/Famic/Famdis.htm Enri's Famicom Disk System page] (Japanese)
* [http://www43.tok2.com/home/cmpslv/Famic/Famdis.htm Enri's Famicom Disk System page] (Japanese)
* [https://web.archive.org/web/20091023182159/http://www2.odn.ne.jp/~haf09260/Famic/Famdis.htm Enri's Famicom Disk System page] (Japanese) (old/outdated)
* [https://web.archive.org/web/20091023182159/http://www2.odn.ne.jp/~haf09260/Famic/Famdis.htm Enri's Famicom Disk System page] (Japanese) (old/outdated)
* [//nesdev.org/fds-nori.txt fds-nori.txt] - FDS reference in Japanese by Nori
* [//nesdev.org/fds-nori.txt fds-nori.txt] - FDS reference in Japanese by Nori
* [//nesdev.org/FDS%20technical%20reference.txt FDS technical reference.txt] by Brad Taylor
* [https://www.chrismcovell.com/software.html FDS List] by ccovell - command line utility to inspect FDS disk image contents.
* [//nesdev.org/FDSListWIN.zip FDS Lister by ccovell]
* [https://www.chrismcovell.com/fds-lister.html FDS Lister] by ccovell - utility to inspect FDS disk contents that runs on an FDS.
* [//forums.nesdev.org/viewtopic.php?p=194867#p194867 Forum post]: .fds format: Can checksums be heuristically detected? - Includes a CRC implementation in C.
* [//forums.nesdev.org/viewtopic.php?p=194867#p194867 Forum post]: .fds format: Can checksums be heuristically detected? - Includes a CRC implementation in C.
* [//forums.nesdev.org/viewtopic.php?f=3&t=16507 Forum post]: FDS IRQ reload flag/value
* [//forums.nesdev.org/viewtopic.php?f=3&t=16507 Forum post]: FDS IRQ reload flag/value

Revision as of 03:50, 6 June 2021

The Famicom Disk System was a Japan-exclusive storage device for the Famicom, designed to reduce Nintendo's cost of making copies of games by switching from mask ROM chips to a storage medium based on Mitsumi's Quick Disk. Unfortunately for Nintendo, it also reduced the pirates' cost of making copies of games.

Overview

  • PRG ROM : 8 KB BIOS at $E000-$FFFF
  • PRG RAM: 32 KB at $6000-$DFFF
  • CHR capacity: 8 KB RAM
  • Storage capacity : ~64 KB per disk side
  • Nametable mirroring: Controlled by mapper
  • Subject to bus conflicts: No

Games are stored on one or multiple disk sides. The FDS BIOS is used to load data from disks to PRG RAM or VRAM, and games can execute from there.

Hardware

The Famicom disk system comes in two parts : The disk drive and the RAM adapter.

The RAM adapter is a special shaped cartridge that contains the RAM chips and an ASIC with DRAM controller, IRQ hardware, sound generation hardware, serial interface for the disk drive, and parallel port. The Disk Drive has to be powered separately and is only connected to the Famicom/NES via a serial cable to the RAM adapter.

Most disk drives contain two motors: a spindle motor that spins the disk at a specific speed, and a stepper motor which moves the read/write head between each circular data track. By comparison, the FDS only contains a single motor that does both at once, so it instead stores the data in a single spiral-shaped track. There is a mechanism that detects when the head reaches the end of the disc and makes it return to the start (making an audible click). Because of this limitation, random access to the disc is impossible, making FDS disk drive data access behave similarly to a reel of tape (but much faster). Data can only be accessed by spinning the disc, waiting for the head to reach the inner edge of the disc, then waiting again until the desired data file is reached. A complete cycle through the entire disc takes about 7 seconds.

The disc drive only contains basic electronics, there is no "intelligence" in it; therefore, the serial interface almost directly represents what is stored on the disc.

Disks

The FDS disk is a modified version of the Mitsumi Quick Disk.

See:

Registers

$402x registers are write-only $403x registers are read-only


IRQ reload value low ($4020)

7  bit  0
---------
LLLL LLLL
|||| ||||
++++-++++- 8 LSB of IRQ reload value

IRQ reload value high ($4021)

7  bit  0
---------
LLLL LLLL
|||| ||||
++++-++++- 8 MSB of IRQ reload value

Unlike $4022, $4020 and $4021 are not affected by the $4023.0 (disk registers enabled) flag - the reload value can be altered even when disk registers are disabled.

IRQ control ($4022)

7  bit  0
---------
xxxx xxER
       ||
       |-- IRQ Repeat Flag
       +-- IRQ Enabled

When $4022 is written to with bit 1 (IRQ enabled) set, the reload value is copied into the IRQ's counter. Each CPU clock cycle the counter is decremented by one if the enable flag is set.

When the counter's value is 0 and the IRQ enable flag is on, the following happens on every CPU cycle:

  • An IRQ is generated.
  • The IRQ counter is reset to its reload value (contained in $4020+$4021)
  • If the IRQ repeat flag is NOT set, the IRQ enabled flag is cleared and the counter stops.

Notes:

  • This register is affected by the $4023.0 (Enable disk I/O registers) flag - if disk registers are disabled, it is impossible to start the IRQ counter (writing to $4022 has no effect).
  • Clearing $4023.0 will immediately stop the IRQ counter and acknowledge any pending timer IRQs.
  • Writing to $4022 with bit 1 (IRQ enabled) cleared will stop the IRQ counter and acknowledge any pending timer IRQs.
  • Enabling IRQs when the reload value is set to 0 will cause an IRQ immediately. Doing this with the repeat flag enabled will cause an infinite loop of IRQs on every CPU cycle.
  • Since the disk transfer routine also uses IRQs, it's very important to disable timer IRQs before doing any access to the disk.

There are only 3 known ways to acknowledge the timer IRQ:

  • Read $4030
  • Disable IRQs by writing to $4022
  • Disable disk registers by writing to $4023

Master I/O enable ($4023)

7  bit  0
---------
xxxx xxSD
       ||
       |+- Enable disk I/O registers
       +-- Enable sound I/O registers

This register sounds obscure. FDS bios just writes $00 then $83 to it.

As noted above, disabling disk registers also disables timer IRQs.

Write data register ($4024)

The data that this register is programmed with will be the next 8-bit quantity to load into the shift register (next time the byte transfer flag raises), and to be shifted out and appear on pin 5 of the RAM adapter cable (2C33 pin 52).

FDS Control ($4025)

7  bit  0
---------
IS1B MRTD
|||| ||||
|||| |||+- Drive Motor Control  
|||| |||     0: Stop motor
|||| |||     1: Turn on motor
|||| ||+-- Transfer Reset
|||| ||        Set 1 to reset transfer timing to the initial state.
|||| |+--- Read / Write mode
|||| |     (0: write; 1: read)
|||| +---- Mirroring (0: vertical; 1: horizontal)
|||+------ CRC control (set during CRC calculation of transfer)
||+------- Always set to '1'
|+-------- Read/Write Start  
|            Turn on motor.  Set to 1 when the drive becomes ready for read/write
+--------- Interrupt Transfer  
             0: Transfer without using IRQ
             1: Enable IRQ when the drive becomes ready for 

A FDS game that wants to change mirroring probably don't want to touch motor related bits, so it should do a read-modify-write from the pseudo registers (see FDS BIOS).

External connector ($4026)

Output of expansion terminal where there's a shutter on the back of the ram card. The outputs of $4026 (open-collector with 4.7K ohm pull-ups (except on bit 7)), are shared with the inputs on $4033.

Disk Status Register 0 ($4030)

7  bit  0
---------
IExB xxTD
|| |   ||
|| |   |+- Timer Interrupt (1: an IRQ occurred)
|| |   +-- Byte transfer flag. Set every time 8 bits have been transfered between the RAM adaptor & disk drive (service $4024/$4031). 
|| |       Reset when $4024, $4031, or $4030 has been serviced.
|| +------ CRC control (0: CRC passed; 1: CRC error)
|+-------- End of Head (1 when disk head is on the most inner track)
+--------- Disk Data Read/Write Enable (1 when disk is readable/writable)

Read data register ($4031)

This register is loaded with the contents of an internal shift register every time the byte transfer flag raises. The shift register receives its serial data via pin 9 of the RAM adapter cable (2C33 pin 51).

Disk drive status register ($4032)

7  bit  0
---------
xxxx xPRS
      |||
      ||+- Disk flag  (0: Disk inserted; 1: Disk not inserted)
      |+-- Ready flag (0: Disk readу; 1: Disk not ready)
      +--- Protect flag (0: Not write protected; 1: Write protected or disk ejected)

External connector read ($4033)

7  bit  0
---------
BIII IIII
|||| ||||
|+++-++++- Input from expansion terminal where there's a shutter on the back of the ram card.
+--------- Battery status (0: Voltage is low; 1: Good).

When a bit is clear in $4026 port it will read back as '0' here (including battery bit) because of how open collector input works. Battery bit should be checked when the motor is on, otherwise it always will be read as 0.

Sound ($4040-$4092)

For details on sound information, see FDS audio.

BIOS

The FDS contains a fixed 8KB BIOS at $E000-FFFF. This controls the Famicom at power-on and reset, dispatches the NMI and IRQ, and offers an API for accessing the data on disk.

See: FDS BIOS

See Also

References