CRCを実装する

出典: Wikimura

SDカードを使うことになって、CRCが必要になりました。 SDカードへ送るコマンドにはCRC-7、データにはCRC-16が使われています。

今回SDカードをつなぐSTM32マイコンでは、SPIにCRC計算回路が付いています。リファレンスマニュアルによれば、CRC-8またはCRC-16を自動的に計算することができるそうです。CRC-16についてはハードウェアで行える事が分かりました。

ところが、次数の異なるCRC-7はできません。CRC-8か16の生成多項式はプログラム可能ですが、次数は固定のようです。(ちなみにPIC24にはプログラマブルCRC回路が付いているそうです)

SDカードのコマンドトークンは6バイトで、内訳はコマンド1バイト、引数4バイト、CRC1バイトとなっています。コマンドと引数の5バイトに対してCRCを行い、その値を付加することになっています。この程度ならソフトウェアで実装できます。

ここでは、[3]で書かれているようにLUT(Look Up Table)を使ったソフトウェア実装をしていきます。 CRC-32を8ビット区切りでやるようなキリの良い場合はLUTは1セットで良いのですが、CRC-7のように中途半端な場合はもう一つ必要になりそうです。 SDカードのCRC-7はキリ良く求められるよう工夫されていました。


目次

1ビットずつやる

CRCの原理は省略しますが、他の解説サイトにあるように、CRCは割り算の余りのようなものです。 そのため、1ビットずつ演算すれば確実に答えが求まります。

その過程を以下に示します。例題として本家Wikipediaの式を使います。 この例題は、以下に示す3次の生成多項式で12ビットのデータからCRC-3を計算するというものです。

この例からわかるように、ビット数分処理を繰り返さなくてはなりません。どう頑張ってもデータ量に比例するわけですが、もう少し楽がしたいものです。

画像:Morita-090724-CRC-3.png

x^3 + x + 1 \rightarrow \mathrm{1011}

               111 1000 1111
         -------------------
    1011 ) 11 0100 1110 1100
           10 11            
           -----------------
            1 100           
           +1 011            <--- XOR
            ----------------
              1110          
             +1011          
              --------------
               101 1        
              +101 1        
              --------------
                    110 1   
                   +101 1   
                   ---------
                     11 01  
                    +10 11  
                    --------
                      1 100 
                     +1 011 
                     -------
                        1110
                       +1011
                       -----
                         101 <--- CRC-3

まとめて計算する

1ビットずつやるのではなく、今度は3ビットずつやってみます。(別に3ビットでなくてもよいのですが、CRCの次数と区切りを同じにすると都合が良いので) まずまとめる前に、先ほどの計算を3ビット毎に区切りを入れて1ビットずつやる方法を書いてみます。

区切り--> [0] [1] [2] [3] [4]
              011 110 001 111
         --------------------
    1011 )011 010 011 101 100
          000 0
          -------------------
           11 01
           10 11             
          -------------------
            1 100            
            1 011            
            =================
              111 0           <--- (a)
              101 1          
              ---------------
               10 11         
               10 11         
               --------------
                0 001        
                0 001        
                =============
                  001 1       <--- (b)
                  001 0      
                  -----------
                   01 10     
                   01 00     
                   ----------
                    1 101    
                    1 011    
                    =========
                      110 1   <--- (c)
                      101 1  
                      -------
                       11 00 
                       10 11 
                       ------
                        1 110
                        1 011
                        =====
                          101

この過程の(a)に注目してください。最初の区切りである[0]011から000になりました(空白になっている)。代わりに、隣の区切りである[1]は010から111に変わっています。今度はこの区切りを000にしていくことになります。


1ステップを考える

[0]番目の区切りを消すために行った操作が[1]番目の区切りに及ぼした影響について考えます。

区切り-> [4]  [3]
         011  010
        --------- 
        +000  0   
        + 10  11 
        +  1  011
        ---------
              111     

すると、次式が成り立っていることが分かります。なお、Sは区切りを表し、添え字は区切り番号と区切り内ビット番号(右がLSB)を表します。


S'_{12} = S_{12} \oplus 0 \oplus 1 \oplus 0

S'_{11} = S_{11} \oplus 1 \oplus 1

S'_{10} = S_{10} \oplus 1


これは、区切り[0]を処理した後で残る区切り[1]'は、元の区切り[1]に「区切り[0]で決まる定数」をXORしたものになる事を表しています。そこで、生成多項式Gを使った時の「区切り[0]で決まる定数」をテーブルTGとします。 区切り[1]の変化をテーブルを使って表すと以下のようになります。


S'_1 = S_1 \oplus T_G(S_0)


これで何が嬉しいかというと...テーブルを参照してXORするだけで、数ビットまとめて処理したことになるということです。 (テーブル自体は先に計算しないといけないのですが) 本当に割り算の代わりになっていることを見ていきます。

テーブルを求める

まずはテーブルを求めることから始めます。テーブルのエントリ数はまとめて処理するビット数で決定します。ここでは3ビットを1区切りとしたため、23 = 8エントリのLUTを作ります。 エントリの長さは、CRCの次数と等しくなります。ここでは3ビットとなります。

テーブルは1ビットずつ計算する普通のアルゴリズムで行います。手計算でも可能です。 手計算で頑張って以下の様に求めました。

Si TG(Si)
000000
001011
010110
011101
100111
101100
110001
111010


テーブルで計算してみる

テーブルを使って計算すると以下のようになります。(a)から(c)は「まとめて計算する」の最初に挙げた1ビットずつ処理する部分との対応を表しています。(a)の「111」、(b)の「001」、(c)の「010」が一致していると思います。

テーブル参照を使えば、数ビット分をまとめて処理できることが理解できます。

区切り--> [0] [1] [2] [3] [4]
         --------------------
    1011 )011 010 011 101 100
          011 101             <--- T(011) = 101
         --------------------
              111 011         <--- (a)と一致
              111 010         <--- T(111) = 010
              ---------------
                  001 101     <--- (b)と一致
                  001 011     <--- T(001) = 011
                  -----------
                      110 100 <--- (c)と一致
                      110 001 <--- T(110) = 001
                      -------
                          101 ---> CRC

漸化式?

この例では次式で表わされる漸化式が成り立っています。

S'_{n} = S_{n} \oplus T_G(S'_{n-1})


処理前の区切りをSnとし、その区切りが処理される前の区切りのテーブル参照の影響を受けたものをS'nとします。今回は隣の1からしか影響を受けません。 (CRC-32を8ビット区切りでやると、過去の四つ分の影響を受けます)

「テーブルで計算してみる」で行われている処理は以下のよう表せます。なお、S' - 1 = 000という初期値を使います。(実際のCRCでは、初期値が1で埋められていたりするそうです)

S'_0 = S_0 \oplus T_G(S'_{-1}) = S_0
S'_1 = S_1 \oplus T_G(S'_ 0) = S_1 \oplus T_G(S_0)
S'_2 = S_2 \oplus T_G(S'_ 1) = S_2 \oplus T_G(S_1 \oplus T_G(S_0))
S'_3 = S_3 \oplus T_G(S'_ 2) = S_3 \oplus T_G(S_2 \oplus T_G(S_1 \oplus T_G(S_0)))
S'_4 = S_4 \oplus T_G(S'_ 3) = S_4 \oplus T_G(S_3 \oplus T_G(S_2 \oplus T_G(S_1 \oplus T_G(S_0)))) \rightarrow \mathrm{CRC}


最終的に、CRC = S'4が求まれば終わりとなります。具体的に数字を当てはめてみます。

\mathrm{CRC} = 100 \oplus T_G(101 \oplus T_G(011 \oplus T_G(010 \oplus T_G(011))))
\mathrm{CRC} = 100 \oplus T_G(101 \oplus T_G(011 \oplus T_G(010 \oplus 101)))
\mathrm{CRC} = 100 \oplus T_G(101 \oplus T_G(011 \oplus 010))
\mathrm{CRC} = 100 \oplus T_G(101 \oplus 011)
\mathrm{CRC} = 100 \oplus 001 = 101

ブロック図

良くCRCはレジスタとXORで回路として、図上のように描かれることがあります。 四角は1ビットシフトレジスタで、左へシフトします。


3ビットを一区切りとしてテーブル参照する場合、そのブロック図は図下のように書くことができます。 四角はレジスタのブロックで、3ビット入っています。このブロックの新しい値は、次のブロックと、現在の値からテーブル参照した値とのXORとなります。図が紛らわしいですが、1ビットずつシフトするわけではありません。


ちなみに初期値とは、データが入る前にループ部分のレジスタに入っている値のことのようです。

Image:Morita-090724-CRC-3-block.png


CRC-7を考える

CRC-7は生成多項式が8ビットで表せます。ブロック図は下図のようなります。 NバイトデータのCRC-7を求めることは、データがレジスタに埋まった状態から開始し、8N-7ステップ演算を繰り返すことに相当します。

Image:Morita-090725-CRC-7-single.png


これをテーブルを使って実装しようとすると、ブロック図は以下のようになります。 区切りサイズは、テーブルサイズの都合と、取り扱いやすさから8ビットとしています。

Image:Morita-090725-CRC-7.png

CRC-7は計算しにくい?

考え方は基本的にCRC-3の例と同じです。ところが、CRC-7のサイズは7ビットで、区切りサイズは8ビットであるため、少し困ったことが起きます。

1ブロックの1ステップは、1ビットの8ステップに相当するため、8の倍数ビットずつしか進めません。 最終的にビットにして8N-7ステップしなくてはならないので、端数が生じます。

結局、N-1ステップを行ったら、残り1ステップの仕上げを別途行わなくてはならないということです。

X'0 = X0
X'_1 = X_1 \oplus T( X'_0)
X'_2 = X_2 \oplus T( X'_1)
 \vdots
X'_{N-1} = X_{N-1} \oplus T( X'_{N-2})
\mathrm{CRC_7} = \left\{ \begin{matrix}
X'_{N-1}         &\left( X'_{N-1}[7] = 0 \right)\\
X'_{N-1}\oplus G &\left( X'_{N-1}[7] = 1 \right)
\end{matrix}\right.


SDカード用CRC

SDカードで使われているようなCRC-7がこんな面倒なのかと思っていたのですが...実際にはそうではありませんでした。ちゃんと工夫されているようです。(末尾に0を付けることに気づかず、一向に間違いに気付かないまま半日を無駄に...)

文献[1]の68ページに、コマンドフォーマットとCRCの計算について書かれています。 これによるとSDカードのCRC-7は、CRCの手前40ビットの末尾に7つの0を加えた47ビットについて、以下に示す生成多項式を使ったCRC-7を計算するとのことです。

Nバイトデータに7ビットが付加されることで、8ビット1ブロックで処理すると、ループ回数がちょうどN回になります。

G(x) = x^7 + x^3 + 1\
M(x) = {\it START} \cdot x^{39} + {\it HOST} \cdot x^{38} 
+ {\it CMD_{5}} \cdot x^{37} + \cdots
+ {\it CMD_0} \cdot x^{32} 
+ {\it ARG_{31}} \cdot x^{31} + \cdots
+ {\it ARG_0} \cdot x^0
CRC_7 = Remainder \left( \frac{M(x) \cdot x^7}{G(x)} \right)
送信順 0 1 2 : 7 8 : 39 40 : 46 47
内容 start bit='0' host='1' command[5:0] argument[31:0] CRC stop bit='1
CRCでの次数 x^{46}\ x^{45}\ x^{44}\ .. \ x^{39} x^{38}\ .. \ x^7 - -


漸化式は以下のようになります。8ビットずつ処理するため、末尾に付いた7つの0の後ろに、更にもう1ビットを付加しています。

こうすると、SDCRC(8ビット)の左側7ビットはCRC-7になります。最下位の1ビットは余ります。 実はこの余ったビットに、ストップビットの1が入るという仕組みになっています。うまくできています。

テーブルの最下位1ビットは常に0なので、最後に0000 0001とXORをとってもお互い影響しません。

X'_0 = X_0\
X'_1 = X_1 \oplus T( X'_0)
X'_2 = X_2 \oplus T( X'_1)
 \vdots
X'_{N-1} = X_{N-1} \oplus T( X'_{N-2})

\mathrm{SDCRC} = 
\begin{bmatrix}
\mathrm{CRC_7}& \mathrm{STOP}
\end{bmatrix} = 00000001 \oplus T( X'_{N-1})


テーブルを作る

8ビットのブロック単位で処理するとうまくいくことが分かりました。 そこで、3ビットの例の時と同様、ブロックを消去した時の後続のブロックへの影響を全パターン計算し、テーブル化します。

   XXXXXXXX 00000000 <-- 左の1ブロックを消去すると...
--------------------
0: @@@@@@@@
1:  @@@@@@@ @ 
2:   @@@@@@ @@
3:    @@@@@ @@@
4:     @@@@ @@@@
5:      @@@ @@@@@
6:       @@ @@@@@@
7:        @ @@@@@@@
-------------------- 
   00000000 YYYYYYY0 <-- 隣のブロックの7ビットに影響...テーブルにしておく

CRC-7テーブル作成プログラム

この後の#sdcrc.cを参照してください。 初期値(7ビット)は000 0000で計算しています。

  • テーブルインデックスindex = [0000 0000..1111 1111]でループ
    • 一時変数dataに初期値indexを代入
      • ビット数bit=[0..7]でループ
        • dataのMSBが1ならdataと生成多項式polyをXOR→dataへ代入
          • ここではMSBは処理しなくてもOK
        • dataを1ビット左シフト
          • MSBがどうであれここでMSBが消える
          • 同時に右隣りのブロックから'0'が入る事に相当
      • 結果をテーブルへ代入

完成したCRC-7テーブル

unsigned char SDCRC_TABLE[] = {
	0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee, 
	0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc, 
	0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a, 
	0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8, 
	0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26, 
	0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14, 
	0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42, 
	0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70, 
	0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c, 
	0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e, 
	0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08, 
	0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a, 
	0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4, 
	0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96, 
	0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0, 
	0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2, 
};

テーブルの確認

0x01

                  0 0000001
         -------------------
10001001 ) 00000001 00000000 <-- index: 0x01
          +       1 0001001
                  ----------
                    00010010 <-- table: 0x12 ...OK

0x0F

                  0 0001111
         -------------------
10001001 ) 00001111 00000000 <-- index: 0x0F
          +    1000 1001
               -------------
                111 10010
              + 100 01001
                ------------
                 11 110110
               + 10 001001
                 -----------
                  1 1111110
                + 1 0001001
                  ----------
                    11101110 <-- table: 0xEE ...OK

0x5A

                  0 1011111
         -------------------
10001001 ) 01011010 00000000 <-- index: 0x5A
          + 1000100 1
            ----------------
              11110 100
            + 10001 001
              --------------
               1111 1010
             + 1000 1001
               -------------
                111 00110
              + 100 01001
                ------------
                 11 011110
               + 10 001001
                 -----------
                  1 0101110
                + 1 0001001
                  ----------
                    01001110 <-- table: 0x4E ...OK

SDカード用CRC-7計算関数

これまでの結果を利用して、SDカード用CRC-7計算関数を作りました。

PC上でテストするための関数なので、テーブル作成関数とCRC-7計算関数があります マイコンに乗せるときは、テーブルとCRC-7計算関数だけ載せます。

また、CRC-7計算関数はテーブルを引数にとっていますが、これもマイコンに実装するときは無くした方が良いです。 代わりに、グローバル定数としたテーブルを直接参照します。

sdcrc.h

#ifndef CRC7_H_
#define CRC7_H_

#ifndef TABLE_NAME
#define TABLE_NAME	"SDCRC_TABLE"
#endif

// PC上でテーブルを作るのに利用
// CRC計算テーブル作成
void createSDCRCTable( unsigned char* table);
// CRC計算テーブルを書式付でSTDOUTへ出力
void printSDCRCTable( const unsigned char* table);

// マイコン上で動かす関数
// orgからの5バイトをコマンドとして、CRCを計算する。
// アドレスの若い順に cmd, arg[3], arg[2], arg[1], arg[0] としている。
unsigned char calcSDCRC( const unsigned char* table, const void* org);
#endif


sdcrc.c

#include "sdcrc.h"
#include <stdio.h>

// CRC-7の生成多項式の二進数表現(MSB省略): G(x) = x^7 + x^3 + 1
#define CRC7_POLY       0x09
#define MSB_MASK        0x80
#define TABLE_SIZE      256
#define TABLE_COL       16
#define TABLE_ROW       (TABLE_SIZE / TABLE_COL)
#define BLOCK_BIT       8
#define SD_CMD_LENGTH   5
#define SD_STOPBIT      0x01

// PC上で利用
// テーブルについてはSD以外にもそのまま使える。
void createSDCRCTable( unsigned char* table)
{
    unsigned char data;
    unsigned char bit;
    unsigned int index;

    for( index=0; index<TABLE_SIZE; index++)
    {
        data = index;
        for( bit=0; bit<BLOCK_BIT; bit++)
        {
            // MSBはシフトすると消せる
            if( MSB_MASK & data)
                data ^= CRC7_POLY;
            // XOR後シフトする
            data <<= 1;
        }
        *(table++) = data;
    }
}

// PC上で利用
void printSDCRCTable( const unsigned char* table)
{
    int i,j;
    printf( "unsigned char " TABLE_NAME  "[] = {\n");
    for( i=0; i<TABLE_ROW; i++)
    {
        printf( "\t");
        for( j=0; j<TABLE_COL; j++)
        {
            printf( "0x%02x, ", *(table++));
        }
        printf( "\n");
    }
    printf( "};\n");
}


// SDのコマンドに付加するCRC専用の関数
// ストップビットも付加する
// マイコン側で実装する関数
unsigned char calcSDCRC( const unsigned char* table, const void* org)
{
    int i;
    unsigned char *data = (unsigned char*)org;
    unsigned char tmp = data[0];

    for( i=1; i<SD_CMD_LENGTH; i++)
    {
        // X'[i] =  X[i] ^ T( X'[i-1])
        tmp = data[i] ^ table[tmp];
    }
    // SDCRC = [ CRC7 StopBit] = 0000 0001 xor X'[4]
    return( table[tmp] ^ SD_STOPBIT);
}

createSDCRCTable関数

CRC-7を8ビットずつ処理するためのテーブルを作る関数。SDカードに限らず、CRC-7ならどんな生成多項式にも使うことができる。 (ただしリフレクトとか初期値があるのは無理)

printSDCRCTable関数

256バイトのunsigned char型の配列を、C言語の定数定義として出力する関数。この関数の出力先はSTDOUTだが、ファイルに書き込めるようなものを用意する予定。

calcSDCRC関数

マイコン側でCRCを計算するための関数です。そのままコマンドに付加できるよう、ストップビットも自動的につけています。

テーブルを引数としていますが、実装するときはグローバル定数にした方が良いです。 (PC上で試すために引数にしているだけです)

関数の中身が非常にシンプルになっています。 これは、SDカード用CRC-7は「末尾に7ビット付ける」という条件があることに由来します。 最後の1ステップだけはループ外で行っています。

確認する

完成したプログラムの動作を確認します。

文献(掲示板ですが)[4]の書き込みに、SDカードのコマンドと引数に対するCRC7の値が何組か載っていました。 そのうち5つをピックアップし、比較しました。

確認用プログラム

#include <stdio.h>
#include <stdlib.h>
#include "sdcrc.h"

// 文献[4]からの参考値
// MMC・SD CMD59 Argument=0x00000001 CRC7=0x83 Response=0x00
// MMC・SD CMD58 Argument=0x00000000 CRC7=0xFD Response=0x00
// MMC・SD CMD13 Argument=0x00000000 CRC7=0x0D Response=0x00
// MMC・SD CMD10 Argument=0x00000000 CRC7=0x1B Response=0x00
// MMC・SD CMD09 Argument=0x00000000 CRC7=0xAF Response=0x00

// コマンド作成
const unsigned char CMD09[] = { 0x40 + 9,  0x00, 0x00, 0x00, 0x00};
const unsigned char CMD10[] = { 0x40 + 10, 0x00, 0x00, 0x00, 0x00};
const unsigned char CMD13[] = { 0x40 + 13, 0x00, 0x00, 0x00, 0x00};
const unsigned char CMD58[] = { 0x40 + 58, 0x00, 0x00, 0x00, 0x00};
const unsigned char CMD59[] = { 0x40 + 59, 0x00, 0x00, 0x00, 0x01};

unsigned char table[256];
int main(void) {
    // テーブル作成
    createSDCRCTable( table);

    // 比較
    printf("CMD09(0xAF?): %02X\n", calcSDCRC( table, (void*)CMD09));
    printf("CMD10(0x1B?): %02X\n", calcSDCRC( table, (void*)CMD10));
    printf("CMD13(0x0D?): %02X\n", calcSDCRC( table, (void*)CMD13));
    printf("CMD58(0xFD?): %02X\n", calcSDCRC( table, (void*)CMD58));
    printf("CMD59(0x83?): %02X\n", calcSDCRC( table, (void*)CMD59));

    return 0;
}

結果

以下に示すように一致しました。何とかできたと思われます。全部を調べるのは大変なので...これでOKとします。問題があったら修正します。

CMD09(0xAF?): AF
CMD10(0x1B?): 1B
CMD13(0x0D?): 0D
CMD58(0xFD?): FD
CMD59(0x83?): 83


コマンドについて

SDカードのコマンドと、それを表すバイナリコード(オペコード?)の対応について知らなくてはなりません。 これについては、文献[1]の91ページ「5.2.2.1 Detailed Command Description」には以下のように書かれていました。 コマンドを表すバイナリコードは、この数字の部分をそのまま二進数に置き換えたものということです。

The binary code of a command is defined by the mnemonic symbol. As an example, the content of the Command field for CMD0 is (binary) ‘000000’ and for CMD39 is (binary) ‘100111.’

また、コマンドに先行するスタートビットとホストビットは、"01"でした。そのため、最初の1バイトは「0x40」にコマンド番号を加えたものが入ります。確認プログラムの定数定義で使われていた「0x40」はここからきています。

const unsigned char CMD09[] = { 0x40 + 9,  0x00, 0x00, 0x00, 0x00};
const unsigned char CMD10[] = { 0x40 + 10, 0x00, 0x00, 0x00, 0x00};
const unsigned char CMD13[] = { 0x40 + 13, 0x00, 0x00, 0x00, 0x00};
const unsigned char CMD58[] = { 0x40 + 58, 0x00, 0x00, 0x00, 0x00};
const unsigned char CMD59[] = { 0x40 + 59, 0x00, 0x00, 0x00, 0x01};


ちなみにバイナリコードは6ビットですので、SDカードのコマンドはCMD0からCMD63まであります。 ただし64個あるわけではなく、抜け(reserved)があります。 抜けの部分には、アプリケーション固有のコマンドACMDというのが割り当てられていることがあるそうです。 詳しくは文献[1]を参照してください。

SD専用CRC計算関数

テーブルとセットにして、テーブルを指定せずに計算できるようにしました。

// SDカードのCRC対象コマンド長
#define SD_CMD_LENGTH   5

// ストップビットを付加するためのビット
#define SD_STOPBIT      0x01

// SD用CRC-7計算テーブル
static const unsigned char SDCRC_TABLE[] = {
    0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
    0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
    0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
    0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
    0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
    0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
    0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
    0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
    0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
    0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
    0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
    0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
    0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
    0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
    0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
    0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2,
};

// SDのコマンドに付加するCRC専用の関数
// ストップビットも付加する
unsigned char calcSDCRC( const void* org)
{
    int i;
    unsigned char *data = (unsigned char*)org;
    unsigned char tmp = data[0];

    for( i=1; i<SD_CMD_LENGTH; i++)
    {
        // X'[i] =  X[i] ^ T( X'[i-1])に相当
        tmp = data[i] ^ SDCRC_TABLE[tmp];
    }
    // SDCRC = [ CRC7 StopBit] = 0000 0001 xor X'[4]
    return( SD_STOPBIT ^ SDCRC_TABLE[tmp]);
}


コメント

  • CRC-7の計算がWeb上で見かけるサンプルと一向に一致しませんでした。0が7ビット付加されていることに気付かなかったのが原因です。
  • ようやく答えに辿り着きました。ちゃんと仕様書やマニュアルには目を通さないと...思い込みは怖いです。
  • とりあえずではありますが、SDカードで実際動いた5パターンの有効なCRCとの比較を行い、一致を確認しました。

プログラム

ごく簡単なものですが...作成したプログラムをSVNリポジトリにおいておきました。研究室内からは以下のURLから入手できます。

参考文献

  1. SanDisk SD Card Product Manual 1.9(オフィシャルでは入手不可 Googleで検索して出た1つ)
  2. Wikipedia「巡回冗長検査」
  3. CRC最適化
  4. SDカードを自作回路で利用するスレ
  5. SD用CRC7計算ルーチン
個人用ツール