AVRを用いたフロッピーディスクドライブエミュレータ


はじめに

最近は見ることが無くなったフロッピーディスクですが,AVR (ATMega1284P)を使用して,フロッピーディスクドライブ(FDD)と同じように振舞いながら,AVRのフラッシュメモリに書き込んだディスクイメージにアクセスできるFDDエミュレータを作りました.

ネットワークブートしたい古いパソコンがあり,ブート用ROMを搭載していなかったのでgPXEを使おうとしましたが,USBブートに対応しておらず,ブート目的のためにHDDやCD-ROMをわざわざ使いたくなかったのでこのようなものを作りました.
gPXEを起動できればよいという目的だったので,ハードウェアはAVRだけを使ったシンプルな構成ですが,127KB以下のディスクイメージしか保持できずディスクへの書き込みには対応していないという機能制限があります.

本ページでは特に断らない限り,フロッピーディスクと言った場合には3.5インチのフロッピーディスクを指すことにします.

フロッピーディスクの仕組み

1. フロッピーディスクの構造

フロッピーディスクは磁性体が塗られたプラスチックの円盤で,次のように記憶領域が区分けがされています.

ヘッド (サイド)
ディスクの記録面を表し,ヘッド0が下側の面,ヘッド1が上側の面を表す.
トラック (シリンダ)
同心円状にディスクの表面を区分けして,外側から内側へ向かって0から始まる番号をつける.
セクタ (レコード)
各トラックをさらに放射線状に区分けした領域.1から始まる番号をつける.

ヘッド・トラック・セクタ

例えば,ヘッド数が2,片面あたりのトラック数が80,1トラックあたりのセクタ数が18,1セクタが512バイトのディスクの容量は,2*80*18*512=1440KBとなります.
ヘッド,トラック,セクタの3つの値を指定することにより,ディスク上の特定の場所を表現することができます.

一般的によく使われている3.5インチディスクの種類には,表1のようなものがあります.

表1. 一般的な3.5インチディスクの種類
フォーマット時容量 640KB 720KB 1232KB 1440KB
アンフォーマット時容量 1.0MB 1.0MB 1.6MB 2.0MB
記録ディスク 2DD 2DD 2HD 2HD
磁気記録方式 MFM MFM MFM MFM
データ転送速度(kbits/sec) 250 250 500 500
bytes/sector 512 512 1024 512
sectors/track 8 9 8 18
tracks/sides 80 80 77 80
sides 2 2 2 2
回転数(rpm) 300 300 360 300

2. フロッピーディスクの物理フォーマット

表1には,フォーマット時容量というものとアンフォーマット時容量というものが記載されています.例えば,アンフォーマット時には2.0MBの容量を持つディスクでもフォーマット後は1440KBしか利用できません.その差分の領域には,データが壊れていないか確認するためのCRCやディスク上の位置(シリンダ,ヘッド,レコード)を示すための情報,パルス間隔を同期させるための領域(SYNC)や緩衝用領域(GAPn)などが含まれています,
実際の720KBディスクの物理フォーマットは表2のようになっています.

表2. 1トラックのフォーマット (720KBディスクの場合)
領域 バイト数 データ (16進) 内容 備考
プリアンブル
(146 bytes)
80 4e GAP0
12 00 SYNC
4 C2 C2 C2 FC Index Mark C2はミッシングクロックを含む.
50 4E GAP1
セクタ1
(658 bytes)
IDフィールド
(44 bytes)
12 00 SYNC
4 A1 A1 A1 FE ID Address Mark A1はミッシングクロックを含む.
4 シリンダ番号(0~79)
ヘッド番号(0, 1)
レコード番号(1~9)
セクタサイズ(2: 512bytes)
CHRN 128*2^Nがセクタサイズ.
2 xx xx CRC ID Address MarkとCHRNのCRC.
22 4E GAP2
データフィールド
(614 bytes)
12 00 SYNC
4 A1 A1 A1 FB Data Address Mark A1はミッシングクロックを含む.
512 (データ) DATA
2 xx xx CRC Data Address MarkとDATAのCRC.
84 4E GAP3
セクタ2~セクタ9
(658 * 8 bytes)
... ... ... ... ...
ポストアンブル
(182 bytes)
182 4E GAP4

720KBのディスクの場合,転送速度は250kbpsでディスクの回転数は300rpmなので,1回転には60/300=0.2秒を要してその間に転送されるデータ量は0.2*250e3/8=6250バイトで上の各領域の容量の和(1トラックのバイト数)と一致します.また,そのうちメタデータ以外の実際のデータは512*9=4608バイトです.
ミッシングクロックについては次の節で説明します.

CRCは初期値を0xFFとしたCCITT-CRCで計算されます.

3. MFMエンコーディング

一般的な2DDや2HDのフロッピーディスクでは,ビット列はMFMエンコーディングというもので符号化された上で磁性体に記録されます.
フロッピーディスクのインターフェースではデータの転送はシリアルで行われ,MFMエンコーディングされたビット列のデータがやりとりされるのでこの符号化を理解しておく必要があります.

例えば,0xB1という1バイトの値は10110001というビット列で表現されますが,ディスク上に記録する際には各データビットの間に0か1のクロックビットというものが挿入されて倍のビット長のデータが記録されます.
MFMエンコーディングの場合は次のように符号化されます(他にFMエンコーディングというものもあります):

データ 0xB1
データビット 1 0 1 1 0 0 0 1
クロックビット 0 0 0 0 0 1 1 0
記録されるビット列 0 1 0 0 0 1 0 1 0 0 1 0 1 0 0 1

MFMエンコーディングでは,「データビットで0と0が連続する場合は1を挿入し,それ以外は0を挿入する」という単純な規則によりクロックビットが生成されます.データが連続する場合,先頭のクロックビットは直前のデータにも影響されることになります.

ディスク上にはMSBから順番にデータが記録されていきます.

ところで,表2の中でIndex MarkやAddress Markというものはディスク上の位置を表す印として使われますが,もしユーザーが書き込んだデータ中に同じビット列が含まれているとどれが本物の印か区別がつかなくなります.そのため,Index MarkやAddress Markをディスク上に書き込む際には,ミッシングクロックといって通常のクロックビットから一部のビットを取り除いたビット列を使用します.これにより,ユーザーが書き込んだデータには絶対に出現しないビット列が書き込まれるので,これらの印が区別できることになります.表3にミッシングクロックを示します.

表3. ミッシングクロック
データ 0xC2
データビット 1 1 0 0 0 0 1 0
クロックビット 0 0 0 1 0 1 0 0
記録されるビット列 0 1 0 1 0 0 1 0 0 0 1 0 0 1 0 0
データ 0xA1
データビット 1 0 1 0 0 0 0 1
クロックビット 0 0 0 0 1 0 1 0
記録されるビット列 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 1

4. フロッピーディスクのインターフェース

ここでは,IBM PC互換機で使われている3.5インチフロッピーディスクドライブのインターフェースについて記述します.
インターフェースには34ピンのコネクタが使われます.
各ピンの信号は表4のとおりです.

表4. フロッピーディスクインターフェースの信号
ピン番号 信号 方向 名称 備考
2 - - -
4 - - -
6 - - -
8 /INDEX Out INDEX negative pulseによりトラックの開始位置を示す.
10 - - (MOTOR ON)
12 /DS1 In DRIVE SELECT 1 ドライブの選択.
14 - - (DRIVE SELECT 0)
16 /MOTOR In MOTOR ON LOWになるとモーターを回転させる.
18 /DIR In DIRECTION SELECT STEPが入力された場合のトラックの移動方向を決める.LOWの場合は外側から内側に,HIGHの場合は内側から外側.
20 /STEP In STEP 入力されたパルスの後縁でトラックを1つ移動させる.
22 /WDATA In WRITE DATA
24 /WGATE In WRITE GATE
26 /TRK00 Out TRACK 00 ヘッドがトラック0にあればLOW,それ以外ではHIGHを出力する.
28 /WPT Out WRITE PROTECT 書込み禁止のディスクが挿入されていればLOW,それ以外ではHIGH.
30 /RDATA Out READ DATA negative pulseでクロックとデータが混在したビット列を出力する.
32 /SIDE1 In SIDE ONE SELECT HIGHならばヘッド0を選択し,LOWならばヘッド1を選択する.
34 /DSKCHG Out DISK CHANGE 電源投入後およびディスクイジェクト状態でLOWになる.ディスクが入った状態でSTEPが入力されるとその前縁でHIGHになる.
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33 GND GROUND

フロッピーディスクのインターフェースは,複数のドライブがバス接続されるためオープンコレクタ出力で負論理となっています.
DS1がデバイスを選択する信号で,これがアクティブ(LOW)になるとFDDのアクセスランプが点灯して各入力信号に応じた動作をするようになり,非アクティブ(HIGH)になると全ての出力をHi-Zにして動作を止めます.
Read Enableのような信号は無くて,基本的にMOTORとDS1がアクティブの間は常にヘッドから読み取られたビット列がRDATAに出力されます.

ところでFDDをマザーボードにつなぐケーブルは,通常FDD側の10番から16番がねじれて配線されています(つまり,MBの10番とFDDの16番,MBの11番とFDDの15番,・・・が接続される).
通常のFDDはドライブ1のデバイス選択信号(DS1)だけをチェックするようになっていて本体にはデバイス番号を切り替えるスイッチがついていないので,ケーブルをツイストさせることにより1つめのデバイスのDS1にドライブ0の選択信号が,2つめのデバイスのDS1にドライブ1の選択信号が接続されるようになっています(ケーブルセレクト).

設計と製作

ハードウェア

冒頭で触れたとおり,今回はgPXEのブートに使用するという目的でできるだけシンプルなハードウェアにしました.
128KBのフラッシュROMと16KBのRAMというAVRの中では大容量のリソースを利用できるATMega1284Pを使い,ディスクイメージはAVRのフラッシュROMに格納することにして,他のメモリ等は外付けしないことにしました.おかげで回路が簡単になりましたが,1FD Linux等を入れることはできません.最近は色々な選択肢があるのでAVR以外のマイコンの利用を考えてもよいのかもしれませんが,趣味でたまに電子工作をする程度だと開発環境を用意して新しいデバイスを使い始めるのにはそれなりに手間がかかるので少し躊躇してしまいます.

今回作成するFDDエミュレータの主な仕様です:

回路図と配線図は下記の通りです.
WDATAとWGATEは未接続です.
AVRは内蔵オシレータを使いましたが,OSCCALを設定して18MHzのクロックにしています.しかしながら設定できるクロック周波数は個々のチップに依存すると思われるので,20MHzのセラミック発振子を外付けした方がよいと思います.

回路図 回路図(PDF)  配線図 配線図

完成した基板は次のようになりました:

基板(表) 基板(表) 基板(裏) 基板(裏)

ソフトウェア

このエミュレータの基本的な動作としては,タイマー割り込みを使ってバッファに蓄えられたディスクデータを出力して(RDATAのパルス),割り込み処理を行っている以外の時間は出力すべきデータをひたすらバッファに書き込みます.DS1やMOTORやSTEPといったFDDを制御する入力信号は外部割込みでキャッチして処理します.
720KBのFDDだとデータの転送速度が250kbpsで,クロックビットを考慮すると500kbpsになるので500kHzでタイマー割り込みを発生させます.AVRを18MHzで動作させた場合は各タイマー割り込みの間に18e6/500e3=36クロック分しか命令を実行できません(さらにそのうち10クロックは割り込みハンドラへのジャンプと復帰に使われます).FDDというのはレガシーな低速デバイスだと思っていましたが,AVRで扱う場合はあまり処理能力に余裕がありません.今回はアセンブラで書きました.

IDフィールドとデータフィールドに存在するCRCの値は事前に計算してEEPROMに入れることにしました.また,MFMエンコーディングでデータビット列からクロックビット列を生成するテーブルもEEPROM上に用意しました.
メモリの利用状況は次のようになっています:

メモリ種別 開始アドレス サイズ 内容
Flash ROM 0x00000 1KB以下 ファームウェア本体
0x00400 127KB以下 ディスクイメージ
EEPROM 0x0000 256 * 2 MFMビットパターンテーブル
0x0200 2 * 80 * 9 * 2 IDフィールドのCRC
0x0e00 254 * 2以下 データフィールドのCRC

擬似コードで書いたファームウェアの概要は次のようになります:

interruption_handler<DS1: L->H>() {
goto main;
}
interruption_handler<MOTOR: L->H or H->L>() {
if (MOTOR == H) {
interruption_disable(timer);
} else {
interruption_enable(timer);
}
}
interruption_handler<STEP: L->H>() {
dskchg = H;
if (DIR == L) {
if (track + 1 < 80) track++;
} else {
if (track - 1 >= 0) track--;
}
update();
}
interruption_handler<timer: 500kHz>() {
RDATA = (buf_data & 0x80) ? L : H;
wait(0.2us);
RDATA = H;
buf_data <<= 1;
buf_num--;
if (buf_num == 0) {
buf_data = pop(buffer);
buf_num = 8;
}
}
# 状態出力信号の更新
update() {
DSKCHG = dskchg;
TRK00 = (track == 0) ? L : H;
}
# 初期化
init() {
dskchg = L;
track = 0;
interrupt_enable(DS1, MOTOR, STEP);
}
# メインルーチン
main() {
interruption_all_disable();
LED = L;
WPT = H;
DSKCHG = H;
TRK00 = H;
while (DS1 == H) ; // DS1がアクティブになるまで出力をオフにして待機
LED = H;
WPT = L;
update();
interruption_handler<MOTOR>();
interruption_all_enable();
buffering: // 出力すべきデータをバッファに書き込み続ける
# Output Preamble
push(buffer, xx);
...
# Output ID Field
...
# Output Data Field
...
# Output Postamble
...
goto buffering;
}

動作確認

デバッグは,主にロジックアナライザでINDEXとRDATAを測定して行いました.RDATAから出力されるパルス間隔は4/6/8usのいずれかであり,その間隔からデータ・クロック混合のビット列(01/001/0001)が再現できます.
最終的にファームウェアのサイズは782 bytesになりました.
次のディスクイメージで動作確認を行いました.

TinyIPL
これはテストのために作成した非常に簡単なIPLで,ディスクに書き込んでブートすると画面にhello, worldを表示するだけのものです.
一応ファームウェアと一緒にダウンロードできるように置いておきました.
gPXE
gPXEを書き込んで動かしたところ無事動作しました.
gPXEのバイナリのサイズは選択するNICのチップにもよりますが60KB程度です.
gPXEはここから*.dskというファイルフォーマットで入手できますが,このフォーマットは単純にディスクの先頭のセクタから順に内容をダンプした形式になっています.
memtest86
memtest86というツールのFDブート用イメージデータが公開されているので試したところ,問題なく起動することを確認しました.
試したのはVersion 4.0aで,サイズは約80KBでした.

ファームウェア等

参考資料

  1. TEAC FD-235HG-C304 MICRO FLOPPY DISK DRIVE SPECIFICATION (TEAC)
  2. 製品仕様書YD-702Dシリーズ (Y-E Data)
  3. ディスクとFDDの使い方
  4. Atari ST FD Hardware Information

[戻る]
2012-01-09 ページ作成
(2011-12 製作)
T. Nakagawa