デジタルオーディオあれこれ

半田ごての人。紙と鉛筆だけではちょっと。

いまさら聞けないexFAT。クラスターをデカく。

 96kHz/24bitが主流になった頃、最初はPCからデータを送っていたけれど、スピーカーのある所とPCのある所は違うしHDDからデータを読み出すのもアホ臭い感じがした。安くなり始めたフラッシュから読めば良いだろうと自作を始めたのは、もう五年ぐらい前だろうか。当時はCFかSDで、ベータのようなメモリースティックなんてのもあった。SDは規格を見るのに$1000で、CFは$100だったから迷わずCF。

 

 その後、CFは三年ぐらい前に寿命が尽きたので、マイクロSDに乗り換える。ちょうど良い按配に、その頃SDの規格の基本的部分は無料公開になったし。64Gbyteを二枚挿せれば、96kHz/24bitでも特に不自由はない。元データはPCにあって、十枚程度のマイクロSDがあれば用は足りる。聞き流す事はなくて、昔のレコードのような聴き方なので問題なし。

 

f:id:xx3stksm:20200507190840j:plain

 これは三年ぐらい前に作って、今も使ってるトランスポート。レコードのリッピング用にADCも載っているから、RTC用の電池ホルダーもある。デジタルの音源の場合、マルチシステムはちょっと難しい。そんなの今時する人がいないのもあって、デジタルのままXover(チャンネル・デイバイダ―)して、DACに送れる適当な装置はない。ベリンガーのように、一度アナログに戻してからというのが多くて宜しくない。

 

 自作するのが一番。今でも44.1kHzとか48kHzの音源もあるので、トランスポートには96kHz/24bitに上げるSSRCが必須。これがないと、全ての周波数に対してデジタルフィルターの係数を用意する羽目になり、面倒極まりなし。初めはそうしてたけど、やってられなくてSSRC。この基板は、SDへの書き込みもあってXC7A100Tが乗っているから、問題なくSSRCを実装できる。

 

 三年前のがまだ使えているのは珍しい。必須な機能さえあれば長持ちする。今回は、終に読み出しをexFATから出来るようにした。今まで書き込みはfat32で、読み出しは特殊なフォーマットを使ってた。fat32のままでは、読み出しが難しかった。問題は、今流行のクラスター。fat32ではクラスターが小さ過ぎて、メモリーの少ない組み込みのマイコンでは対応できない。

 

 fat32の最大クラスターは64kbyte。fat32exFATのようなフォーマットでは、クラスター単位でデータを読む。SDの場合だと、セクターは512byteと決まっていて、クラスターが64kbyteだと、128のセクターが一塊になってクラスターを作る(512x128=64kbyte)。何故クラスターが小さいと問題かと言えば、簡単な理屈。64GbyteのSDでクラスターサイズが64kbyteだと、64G/64k=1000000個のクラスターが出来てしまう。

 

 96kHz/24bitで一時間だと大体2Gbyteなので、32000ぐらいのクラスター。fat32は必ず次のクラスターのある場所を取りに行かなければならなくて、それは音声データとは別の領域に書いてある。つまり、音声データを読みながら、64kbyte毎にfat領域(次に読むクラスターの番地を書いてある所)もアクセスする。普通のPCであれば全然問題ないけれど、一瞬でもデータが途切れてはならない音声ファイルでは、ちょっと難しい。

 

 そこで普通は、最初に必要なfat領域のデータを読んでおく。けれどもメモリーの制約があるので、組み込みマイコンでは32000個分のfat情報を先読みできない。もしもexFATのように最大クラスターが32Mbyteだと、読み込むデータはたったの64個で済む。マイクロソフトexFATを作ったのは、多分そういう理由も大きい。4Gbyteの 壁の解消というのもあるだろうけど。

 

 exFATの意味は、32Mbyteの最大クラスター、64bitまでのファイルサイズ、サブディレクトリーの扱い易さ、にある。fat32はそれまでのファイルフォーマットとの互換性で、サブディレクトリーとか八文字以上のファイルサイズの扱いが、滅茶苦茶に面倒。exFATでは、一気に簡単になった。

 

 但し、音声ファイルは途切れてはならないので、一般的なSD用のドライバーは難しい。ハードウエアに依存した物にしかならない。そうしないと、途切れない事を保証できない。専用にすれば、192kHz/24bitでも問題なく使える。必要ないので使わないけど。書き込みはフラッシュメモリーの特性としてウェアレベリングが必須なので、少し大きめのバッファ(512kbyte)が必要。

 

 XC7A100Tならば内蔵できる。読み出しには128kbyteで充分。読み出しにはSSRCとかXoverのデジタルフィルターが入るので、そこそこ内蔵メモリーは使い果たす。使い倒しの状態なので、¥1万ぐらいのXC7A100Tでも十分に元が取れる。FPGAでデジタルフィルターを実装する場合、基本的にはタダ。他の機能で元を取り、余りを使っての実装ぐらいの感じになるのが普通だから。

 

 exFATのもう一つの特徴は、ほとんどの場合fatのチェインを辿らない事。fat32では、最初のクラスターが「5」だと、それを読み終わった時にfat領域の「5」を読みに行く。そうすると、そこに「13」とか書いてあるので、次は「13」のクラスターを読む。これをファイルサイズを超えるまで続けるか、fat領域に「FFFF」のようなデータが出たら終わりとなる。

 

 exFATは違う。最初のクラスターが「5」だと、ほとんどの場合次は「6」でその次は「7」と自動インクリメントする。なので最初のクラスターさえ分かれば、それっきり。辿る必要のあるなしは、file directory entryの所に書いてある。file directory entryには、ファイルの名前、属性、ファイルサイズ、更新日、クラスターの場所、なんかがかいてある。

 

 マイクロソフトの資料はサッパリ意味不明で、ネットで探した資料で確かめる。3種類のfile directory entryを見ると必要な情報が分かる。こんなの。

f:id:xx3stksm:20200507195518j:plain

f:id:xx3stksm:20200507195545j:plain

f:id:xx3stksm:20200507195559j:plain

この3つに全ての情報がある。具体的にはHxDというハッキングするソフトでSDの中身を見ると、こうなっている。オレンジの枠内が一つのwavのファイルに対応している。0x85,0xc0,0xc1とかが、上のentry type。

f:id:xx3stksm:20200507195757j:plain

この場合は、0xc0,0x3なので、fat chainはinvalid。つまり、fat chainは不要なのでentry typeの0xc0のオフセット20に書いてある「8」のクラスターから始まって、後は自動インクリメント。fat chainがvalidの時は、0xc0,0x1となる。exFATの場合、OSは極力fat chainをinvalidにする。意図的に作らないとまず無理なほど。

 

 とはいえ、残り容量のほとんどない状況で消去を何度も繰り返したりすると、fat chainがvalidになるので、fat32のような読み方にも対応する必要はある。この場合は、最初に必要なfat chainの情報を先読みしておく。容量は256byteもないので特に問題なし。後は、必要なクラスターに対応するアドレスを、SDにコマンドとして送れば良い。

 

  SDは6ピンのインターフェース。クロック、コマンド、後は4ビットのデータ線。カードのあるなしのスキャン信号はDCなので、マイコンに直で良し。ちょっと面倒なのは、コマンドと書き込み用データの時はCRCをつけないといけない事。読み出しならば、コマンドの時だけでマイコンでの対応も可能。データ線を1ビットでのSPIは、音声用にはスピード的に無理なので考えない。書き込みまで考えると、やっぱりFPGAは必須。

 

 クロックは、1.8V仕様にすると200MHzまで使える。そうすると、最大でほぼ100Mbyte/secの転送レートになる。SDのスピードテストでは、90Mbyte/secぐらいのがある。あれは嘘ではなく本当に可能。USB3.0以上が必須としても。書き込みは、50Mbyte/secぐらいかな。ただ問題はあって、クラスターが小さいとスピードは上がらない。コマンドを送っても、すぐにデータが出て来るわけではないので。

 

 

f:id:xx3stksm:20200507201732j:plain

これは実際のデータ転送。CMDがコマンド。約500μ経たないと、データは出ない。これは、SD内部のマイコンがコマンドを解析して、必要な内部処理を始めるまでの時間で必ず発生する。なので、小さなクラスターをちまちま読むと、恐ろしく転送レートは落ちる。この例では64kbyteを一度に読むので、24.576MHz/2のクロックだと約10.4msecかかる。それに0.5msec足す事になるので大勢に影響なし。 全部で11msec弱。最低でもそのくらいは連続アクセスしたい。勿論、フルに32Mbyte読むことも可能。

 

f:id:xx3stksm:20200507202409j:plain

 

ここで読んだデータは、FPGA内部の128kbyteのFIFOに入る。FIFOのサイズからして、上限は64kbyteになる。これでもオーバーフローはしない。96kHz/24bitの転送レートは、0.576Mbyte/sec。24.576MHz/2のクロックでも転送レートは6Mbyte/secぐらいあるので、10倍以上。FIFOが空くとすぐに必要なデータを埋めてしまう。こんな具合で、ほとんどデータバスは空いている。

f:id:xx3stksm:20200507202907j:plain

時々、SDのマイコンの応答が遅れるけれど、今まで128kbyteのFIFOでオーバーフローした事はない。3年以上の使用経験で。勿論、クロックを上げればもっと余裕は出来るけど必要はない。1.8vの仕様と共に、消費電力考えると、それで十分。

 

SDは、CFよりも初期設定は面倒。数種類の初期化の時にだけ使うコマンドがある。初期化の後は、1セクターを読むコマンドと、連続で読むコマンドと、1セクターの書き込みコマンドしか使わない。書き込みは、ボリュームの値とか、曲の最大音圧、曲のインデックス等のタグを書くために使う。オフラインなので、特に大きなバッファは必要ない。下はコマンド一覧。

 

f:id:xx3stksm:20200507203627j:plain

cmd0からacmd6までが初期化用だと思う。最初の文字がaで始まるのは、大容量になってからの拡張コマンド。初期化は結構長い。仕様書に従ってコマンドを出せば、1.8vへの切り替えまでなんとかなったはず。マイコン側(LPC1347,cortex-M3)から、アドレスなどにクラスターの番地を変換した後、FPGA経由で送る。

 

音声ファイルという特殊性のために、汎用のSDドライバーとはならないが、マイコン側の読み出しコードは比較的単純。CFはsector,head.cyllndrで読んでいた関係上、今も名前がそのまま残っている。完全な32bitのアドレスでも勿論可能。SDはセクター(512byte)単位のアクセスなので、32bitは32+9=41bit=2Tbyteまでアクセス可能。

f:id:xx3stksm:20200507204314j:plain

連続読み出しのcmd18のアドレスフィールドにcylndrの値を変換して(Sd_address1)、コマンドを出したら、終了を待って次の64kbyteを読むループ。読み出しコマンドの終了待ちの間、読んだサイズとwavのファイルサイズを比べたり、stopボタンが押されたとか、分と秒の表示、ミュートボタンのスキャン、ポーズのスキャン等をして、headが16になるのを待つ。fat chainのあるなしで自動インクリメントか、fat情報かの切り替えもする。fat 情報は予め、fatchain_bf[]に書いてある。

 

大文字で始まっている関数は、アセンブラのサブルーチン。上の例では、ほとんどそれ。リアルタイム処理なので、少しでも処理速度が速い方が良いのでこうなる。時間待ちしていて余裕がある所だけ、見易いようにC。組み込み用のファームウェアはこんなもんと思う。とにかくスピード重視。

 

ARMはRISCなので、リアルタイム処理だとそれに合わせたコードも必要。要は、ループさせずにキャッシュを有効利用する。上の例では、時間待ちの所だけをdoとwhileでループする。ループしないとはこういうので、普通のコードではない。loop_rddt1:の所は、メモリーに対するクロック信号をARM側から出している。つまりはプログラム制御。

f:id:xx3stksm:20200507210251j:plain

クロックのリセット(strb   r4,[r3])を2回出しているのは、多分メモリー側が反応できないのでスピードを落としている。LPC1347は72MHzのクロックなので、キャッシュにヒットするならば、ほとんどの場合1/72≒14nsecで1命令を実行する。最速だと、メモリーには36MHzのクロックになるので、追いつけない。それで落として72/3=24MHzにしたんだと思う。

 

こういうのが必要なので、Cは交通整理的な条件分岐の所でしか使えない。組み込みファームとはそういう世界。お蔭で、今まで音切れしたことはない。普通のPCでは、条件次第で何が起きるか分からない。 ハイエンドのオーディオならば、exFATで32Mbyteのクラスターでフォーマットすれば良い。それだけやっとけば、終り。後はリチウムの18650みたいなので、SSRCとXover付きの8チャンネルのトランスポートが10時間は持つ。PCオーディオがらみのあれこれで悩む必要はなくなる。なので自作するのが一番。 linuxも大げさすぎで、cortex-M3ぐらいのARMで、充分ことは足りる。