記憶メディアの構造

TOPページへ

 お茶でも飲みながらザクッと...(2006-9改変)

 ウィンドウズや他のOSで記録されたフロッピーやフラッシュメモリー等を自作機器で読み出す場合には、その記録メディアの構造を理解しておかないと出来ません。
 ここでは、SDカードについて説明してみますが他のメディアも記録されている内容は良く似た物なので、覚えておいて損は無いと思います。

 FAT12/FAT16のSDカードがソケットへ挿入された瞬間からファイルの読み書きまでを時系列に沿って説明して行きましょう。SDカードとの通信に使用するSPI通信は良く理解している物とします...ネ

セクタ、クラスタ、FAT、ディレクトリー等の単語は検索すれば多数のドキュメントがヒットするはずです。ここでは省略しました。

 SDカードは、MMCカードの上位互換ですのでMMCカードと同じ方法で読み書きできます。ライセンス契約が無いと製品が出せないような事も耳にしますが、本当の所どうなのか私には判りません。
少し前までは、ワイドバスでの読み書きをするのにライセンスが必要だと自分自身、思っていたのですが根拠は無しです。実際の所は、どうなんでしょう?
最近、SANDISKのH.P.にあった資料が消えたようです。


1.SDカードをSPIモードへ
 SDカードをソケットへ挿入するとカード自身に電源が供給されます。いわゆるリセットされて起動するのですが、その時点でカードはパラレル入出力のモードとなっており、この状態で読み書きする方法は何やら“ライセンス契約”しないと教えてもらえないようです??。そこで速度は遅くなりますが、SPI通信で読み書きできるモードへ移行させます。
 SPIモードへの移行中は、SPIクロック周波数を遅くする必要があります。どれくらいかは忘れました orzググッてください(400KHz以下見たいです)

STEP1:
 CSはHのまま何も考えずに 0xff を10バイト送信しましょう。データ値に意味は無く80クロックに意味があります。
 その後、CSをLにしてコマンド0(GO_IDLE_STATE)をCRC=0x95と設定して送信した後、レスポンスを受信します。この値が 0x01 なら最初のステップは完了です。この時点でカードはSPIモードになっています

STEP2:
 次にコマンド8(SEND_IF_COND)を送信します。32ビットのパラメータは0x000001aaとし、CRCには0x87を設定しておきます。
 このコマンドのレスポンスが0x01か0x00ならSD Ver2.0以上なのでSDHCかもしれません。SDHCの場合、従来のSDカードとは、ほんの少しだけアクセス方法が異なるので判定しておかないといけません。
 以降の初期化はコマンド8に応答したかどうかで2種類に分かれます。

コマンド8に応答しなかった場合
 一旦CSをHにして、次にCS=Lからコマンド1(SEND_OP_COND)を送信した後、同じようにレスポンスを受信してCSをHに戻します。この値が 0x00になるまでコマンド1を繰り返しましょう。普通のカードなら2回程で0x00になるはずです。 カード容量が大きくなると時間が掛かる時があるのでソフトSPIのH8なら400回程度、20MHz動作のPICなら4000回程度リトライしておくのが安心です。
 レスポンスが0x00になればCSをHに戻しておきましょう。
某メーカのminiSDの為にCS=ペコを追記。レスポンス0x01が続いた後、0x05を返す。 ACMD41には応答しました。

コマンド8に応答した場合
 そのままSDカードから4バイト空読みしておきます。返ってくるデータは殆どのカードで0x000001aaでしょう。
 次にコマンド55(APP_CMD)とコマンド41(APP_OP_COND)の組を送信します。32ビットパラメータはAPP_CMDが0で、APP_OP_CONDが0x40000000で送信しましょう。このコマンドのレスポンスがどちらも0x00になるまでコマンド送信を繰り返します。コマンド1と同様に時間が掛かる事があるのでリトライ回数に注意しましょう。旨く両方のコマンドレスポンスが0x00になればCSをHにしておきます。

 0x40000000(bit30=1)の意味はSDHCをホストがサポートしている事を示しています、もしカードがSDHCの場合はこのビットが1でなければ0x00で応答しません。


STEP3:
 CSをLにしてコマンド9(SEND_CSD)を送信した後、レスポンスを受信すると 0x00 のはずです。その後データの受信を続けていると受信データが 0xff から 0xfe へ変わるので、そのデータ以降16バイトを読み込んでおきます。(buffer[0]〜buffer[15]とします)16バイト読み終わったら後2バイトを空読みしてからCSをHにしておきましょう。

 buffer[0]のD7:D6が01になっているとSDHC、00だと普通のSDなので何処かへ記録しておきます。この情報はセクタのリード/ライト時に使用します。
 buffer[5]の下位4ビットが9なのを確認しておきましょう。(2^9=512バイトセクタ)違っていた場合はコマンド27 16でブロックサイズを512に書き換えておきましょう。
 トップページ写真の2GBのSDカードは、この値が0x0a(1024バイトブロック)でした。

 このデータからカードのサイズ等が読めるのですが、セクタサイズさえ問題なければ後からでも判ります。
 セクタサイズ512バイトを決めうちしているソフトが多いようですから注意が必要です。私もしていますorz
 2007-5-13 SDHCで無い場合には常にコマンド16を送信するようにしました。


2.物理セクタ0を読んでみる
 コマンド17(READ_BLOCK)でアドレス0を読んでおきます。(buffer[0]〜buffer[511]とします)このデータの中のbuffer[454]〜buffer[457]にリトルエンディアンで32ビットの値があります。
 これが論理セクタ0へのオフセットとなるので、以後セクタ番号を読み込む時は、この値をプラスしてセクタを指定します。
 パーティション1の論理セクタ0です。その他は無です(汗
 ちなみにパーティションテーブルは、1=[446]〜、2=[462]〜、3=[478]〜、4=[494]〜から、それぞれ始まっています。



3.論理セクタ0を読んでみる
 ここからのセクタ番号からコマンド17(READ_BLOCK)でのアドレス変換は、
SDHCの場合:
アドレス=セクタ番号+オフセット
通常のSDの場合:
アドレス=(セクタ番号×512)+オフセット
アドレス=(セクタ番号+オフセット)×512 (汗
となります。
(注)SDHCの場合はファイルシステムがFAT32になります。

 適当なメディア(16MBメモリースティック)のセクタ0を読んでみました。

 このセクタ0から「このドライブは、全部で32591 31591セクタ有り、3セクタのFATが2つ有りセクタ1から始まっているのでディレクトリーの開始位置は、1+(3×2)=7から始まってディレクトリーエントリー数が512個なのでデータの開始セクタは7+(512/16)=39から始まる、1クラスタのサイズは32セクタである」と判ります。
 これだけ判れば後は、FATとディレクトリが解決すればファイルの読み書きが出来るはずです。

 ちなみに1Gバイトのメモリースティック前半部分です。

 2バイトの総セクタ数が0になっており、4バイトの総セクタ数が有効になっているのが判ります。

 このセクタ内容を表にしてみました。
オフセット 内容
0x0 near jmpの命令(0xEB)
0x1 IPL本体のアドレス
0x2 nop(0x90)
0x3〜0xA メーカーとか色々
0xB〜 BIOSパラメータブロック

BIOSパラメータブロック(BPB)の内容です。
オフセット 内容
0x0〜0x1 1セクタのバイト数
0x2 1クラスタのセクタ数
0x3〜0x4 FATの開始セクタ番号
0x5 FATの数
0x6〜0x7 ルートディレクトリのエントリー数(FAT32は0)
0x8〜0x9 論理セクタの総数(ここが0なら0x15〜0x18が有効)
0xA メディア ディスクリプタ
0xB〜0xC FATで使用するセクタ数(FAT32は0)
0xD〜0xE 1トラックの物理セクタ数
0xF〜0x10 ヘッドの数
0x11〜0x14 隠されたセクタ数
0x15〜0x18 論理セクタの総数(0x8〜0x9が0の場合)
0x19〜0x1C FAT32-FATで使用するセクタ数
0x1D〜0x1E アクティブFAT数(bit0-3)、ミラーリングフラグ(bit7)
0x1F〜0x20 ファイルシステムバージョン
0x21〜0x24 FAT32-ルートディレクトリの先頭クラスタ番号
0x25〜0x26 FAT32-ファイルシステム情報のセクタ番号
0x27〜0x28 FAT32-ブートレコードのバックアップセクタ番号
0x29〜 予約
FAT32については別ページで...

4.ディレクトリーを検索
 私の携帯電話におまけで付いていたSDカードにファイル名"ABCD.TXT"を保存させた状態でディレクトリー(セクタ7)をリードしてみました。
 ファイル"ABCD.TXT"の内容は"123456789"の9文字だけです。
 まずは、このファイルを読み出す事を考えていきましょう。このSDカードは...
FATタイプ:			FAT12
クラスタサイズ:			16セクタ
FAT1開始セクタ:			1
FAT2開始セクタ:			4
ディレクトリー開始セクタ:		7
データ開始セクタ:			39
である事がBPBから解りました。そこでディレクトリー開始セクタを読み込んでみます。

 赤枠内の32バイトがファイル1つ分のディレクトリーエリアとなります。ロングファイルネームの場合は、幾つかのエリアを使用している場合がありますが、これを扱いだすと文字コードの関係もあり小さなマイコンでは扱いきれなくなるので今回は省略することとします。

 内容の詳細を見ていきましょう。

 最初に読み出したいファイル名を8+3=11文字に変換しておき"ABCD____TXT"(_=space)、1セクタ16個あるディレクトリーエントリーの名前部分11文字と比較していくのですが名前の部分の先頭文字が 0xE5 は、そのエントリーが空きである事、0x00 の場合はディレクトリーの終わりを示しています。
 名前が一致した時にアトリビュートが 0x20 でないといけません、0x10 なら同名のフォルダとなります。日時や日付はビットフィールドになっています。読み出す場合は無視してよいのですがファイルを作成する場合は作った日付、時間を設定しましょう。

 ファイル名が一致したディレクトリーエントリーから先頭クラスタ番号(0x7e=126)とバイトサイズ(9)を記録しておけばファイルの内容を読み出すことが出来ます。
(注)ディレクトリエントリ内のオフセット20部分に2バイトの(0x0000)が有ります。これを上位16ビットとして32ビットのクラスタ番号を作っていても現状問題ありません。が、こうしておくとFAT32のサポートが少しだけ楽になります。


5.FATからクラスタを割り出す
 このディスクはFAT12なので1セクタ当り341.333...クラスタの登録が出来ますので対象となるクラスタ126は、セクタ1に存在するのが判ります。セクタ1を見てみると

 ちょっと判りづらいですが先頭から12ビットづつクラスタ番号が続いています。先頭の2クラスタ(赤枠と青枠部分)は予約済みなのでクラスタ2が先頭です。では、クラスタ126を見てみましょう。
 すると(紫枠)0xFFF と書いてあります。これは、このクラスタが最終(続きが無い)であることを示しています。もしファイルサイズが大きく続きが有る場合は続きのクラスタ番号が書いてあるのです。

 仮に保存したファイルが8200バイトあったとすればクラスタ番号 0x07E の部分は 0x07F となり、クラスタ番号 0x07F の部分には 0xFFFが書いてあるはずです。大きなファイルは、このチェーンを辿りながらデータ部分を読んでいきます。

 もちろんFAT16の場合は16ビットづつクラスタ番号が続いています。先頭は0xFFF8,0xFFFFです。(リトルエンディアン)


5.クラスタ番号からデータセクタを割り出す
 ではクラスタ126を読んでみましょう。先頭クラスタ番号が2、クラスタサイズが16セクタ、データ開始セクタが39なので(126−2)×16+39=2023セクタから16セクタ分がクラスタ126に該当します。

 ファイルの中身へ、たどり着きました。

TOPへ戻る