SPI ハードウェア CRC 回路の MMC/SD カードへの応用 (1)

今回は、m-系列ノイズの話から横道にそれて、STM32 の SPI モジュールのハードウェア CRC 計算を MMC/SD カードのアクセスに応用してみます。
MMC (Multi-Media Card) / SD (Secure Digital) メモリ・カードでは、コマンド部のエラーチェックに CRC-7、データ部のエラーチェックに CRC-16 (-CCITT) が使われています。
CRC-7」で検索してみると、次のような web ベージが見つかりました。

http://www.kimura-lab.net/wiki/index.php/STM32%E3%81%A7SPI%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B(SD%E3%82%AB%E3%83%BC%E3%83%89%E7%B7%A8)

この内容を要約すると、STM32 の SPI で SD カードを使う場合に、CRC-7 の計算をハードウェアで行うのをあきらめ、ソフトウェアで実現したというものです。
確かに、8 ビット SPI モードでは、7 ビット幅の CRC-7 の生成多項式のパターンを書き込んでも期待するような動作にはなりません。
そこで、生成多項式を「加工」して 8 ビット幅としておくと、その 8 ビット幅のパターンに対しては正しい動作となります。
しかし、その結果は CRC-7 に対するものとは違ってくるので、ハードウェアにより得られた計算結果をソフトウェアで「補正」して使うようにしました。
CRC-7 自体を全部ソフトウェアで計算することに比べれば、この「補正」はごく簡単なものとなります。
実際に、ChaN さんの「ぷち FatFs」に組み込んでみて、うまくアクセスできることを確かめてあります。
まず、SPI モードでの MMC / SD カードへ対するコマンドのフォーマットを下に示します。

コマンド部は 6 バイト、48 ビットの固定長のパケットで、その先頭ビットは常に「01」、最後のビットも常に「1」の固定値となっています。
先頭の 1 バイトの下 6 ビットがコマンドの種類を示すインデックスとなっており、続く 4 バイト、32 ビットがコマンドに対する引数となっています。
コマンドおよび引数の計 40 ビットに対して CRC-7 を計算した結果を 6 バイト目に置きます。
初期化シーケンスにより、MMC/SD カードがネイティブモードから SPI モードに移行すると、CRC のチェック機能は無効になるので、CRC は計算せずに済ませるのが普通です。
また、初期化シーケンス中の CMD0、CMD8 のパケットに対しては CRC 機能が有効な状態で実行されますから、プログラムの作成時にあらかじめ計算しておいた CRC の値を定数としてプログラムに埋め込んでおくのが普通です。
STM32 の SPI モジュールのハードウェア CRC 計算機能を利用するためには、CRC 多項式レジスタ (CRCPR) に設定する多項式の次数が 8 次でなければなりません。
CRC-7 は当然 7 次ですから、これを何とかして 8 次に拡大することを考えます。
ここでは、ごく単純に単一パリティ・チェックに相当する
   gp(x) = x + 1
CRC-7 の生成多項式
   g7(x) = x7 + x3 + 1
に掛け、8 次の生成多項式とします。
   g7p(x) = (x + 1)(x7 + x3 + 1)
            = x8 + x7 + x4 + x3 + x + 1
この 8 次の多項式 g7p(x) を使えば、当然、ハードウェアで正しく計算されます。
g7p(x) の因子として CRC-7 の多項式 g7(x) が含まれていますから、計算結果には g7(x) に対する情報も含まれています。
「補正」により、CRC-7 に対する結果を得る「理屈」については、次回で説明することにし、ここでは補正方法を C プログラムの断片によって示します。

  crc_value = SPI_GetCRC(SPI1, SPI_CRC_Tx);

  if (0x01 & crc_value) {
    crc_value ^= (GX_CRC7);
  } // if (0x01 & .. .

  crc_value |= 0x01;

CRC の計算結果のレジスタはリードオンリであり、レジスタを直接書き換えることは不可能なので、補正のためには、まず、送信 CRC の計算結果を変数「crc_value」に読み出します。
そして、CRC 計算結果の b0 が「1」なら補正を行い、「0」なら補正なしで、そのままの値を使います。
そして最後に、最終ビット (b0) を「1」に強制します。
この crc_value を (普通の送信データとして) SPI からコマンド部の最終バイトとして送信します。